diff --git a/packages/api/package.json b/packages/api/package.json index 83f12d736..eb1d7ec41 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -74,6 +74,7 @@ "luxon": "^2.3.1", "microsoft-cognitiveservices-speech-sdk": "^1.22.0", "nanoid": "^3.1.25", + "node-html-markdown": "^1.3.0", "nodemailer": "^6.7.3", "normalize-url": "^6.1.0", "oauth": "^0.9.15", diff --git a/packages/api/src/generated/graphql.ts b/packages/api/src/generated/graphql.ts index c3e060389..a62cd8c1e 100644 --- a/packages/api/src/generated/graphql.ts +++ b/packages/api/src/generated/graphql.ts @@ -1685,6 +1685,7 @@ export type Query = { export type QueryArticleArgs = { + format?: InputMaybe; slug: Scalars['String']; username: Scalars['String']; }; diff --git a/packages/api/src/generated/schema.graphql b/packages/api/src/generated/schema.graphql index 76702f090..1d56197f7 100644 --- a/packages/api/src/generated/schema.graphql +++ b/packages/api/src/generated/schema.graphql @@ -1201,7 +1201,7 @@ type Profile { type Query { apiKeys: ApiKeysResult! - article(slug: String!, username: String!): ArticleResult! + article(format: String, slug: String!, username: String!): ArticleResult! articleSavingRequest(id: ID!): ArticleSavingRequestResult! articles(after: String, first: Int, includePending: Boolean, query: String, sharedOnly: Boolean, sort: SortParams): ArticlesResult! deviceTokens: DeviceTokensResult! diff --git a/packages/api/src/resolvers/article/index.ts b/packages/api/src/resolvers/article/index.ts index 2c2bb7699..fee971897 100644 --- a/packages/api/src/resolvers/article/index.ts +++ b/packages/api/src/resolvers/article/index.ts @@ -64,6 +64,7 @@ import { validatedDate, } from '../../utils/helpers' import { + htmlToMarkdown, ParsedContentPuppeteer, parsePreparedContent, } from '../../utils/parser' @@ -401,7 +402,7 @@ export const getArticleResolver: ResolverFn< Record, WithDataSourcesContext, QueryArticleArgs -> = async (_obj, { slug }, { claims, pubsub }, info) => { +> = async (_obj, { slug, format }, { claims, pubsub }, info) => { try { if (!claims?.uid) { return { errorCodes: [ArticleErrorCode.Unauthorized] } @@ -443,6 +444,10 @@ export const getArticleResolver: ResolverFn< page.content = UNPARSEABLE_CONTENT } + if (format === 'markdown') { + page.content = htmlToMarkdown(page.content) + } + return { article: { ...page, isArchived: !!page.archivedAt, linkId: page.id }, } diff --git a/packages/api/src/schema.ts b/packages/api/src/schema.ts index 05ae969d2..64d84d2d5 100755 --- a/packages/api/src/schema.ts +++ b/packages/api/src/schema.ts @@ -2432,7 +2432,7 @@ const schema = gql` query: String includePending: Boolean ): ArticlesResult! - article(username: String!, slug: String!): ArticleResult! + article(username: String!, slug: String!, format: String): ArticleResult! sharedArticle( username: String! slug: String! diff --git a/packages/api/src/utils/parser.ts b/packages/api/src/utils/parser.ts index b5739beba..431f6619e 100644 --- a/packages/api/src/utils/parser.ts +++ b/packages/api/src/utils/parser.ts @@ -20,6 +20,7 @@ import { EmbeddedHighlightData, findEmbeddedHighlight, } from './highlightGenerator' +import { NodeHtmlMarkdown } from 'node-html-markdown' const logger = buildLogger('utils.parse') @@ -467,3 +468,17 @@ export const fetchFavicon = async ( return undefined } } + +/* ********************************************************* * + * Re-use + * If using it several times, creating an instance saves time + * ********************************************************* */ +const nhm = new NodeHtmlMarkdown( + /* options (optional) */ {}, + /* customTransformers (optional) */ undefined, + /* customCodeBlockTranslators (optional) */ undefined +) + +export const htmlToMarkdown = (html: string) => { + return nhm.translate(/* html */ html) +} diff --git a/yarn.lock b/yarn.lock index 7ae03ac98..d469f24e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20088,6 +20088,21 @@ node-gyp@^7.1.0: tar "^6.0.2" which "^2.0.2" +node-html-markdown@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/node-html-markdown/-/node-html-markdown-1.3.0.tgz#ef0b19a3bbfc0f1a880abb9ff2a0c9aa6bbff2a9" + integrity sha512-OeFi3QwC/cPjvVKZ114tzzu+YoR+v9UXW5RwSXGUqGb0qCl0DvP406tzdL7SFn8pZrMyzXoisfG2zcuF9+zw4g== + dependencies: + node-html-parser "^6.1.1" + +node-html-parser@^6.1.1: + version "6.1.4" + resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-6.1.4.tgz#763cd362497e427d51fc73dda3dcd8ac52ba85a3" + integrity sha512-3muP9Uy/Pz7bQa9TNYVQzWJhNZMqyCx7xJle8kz2/y1UgzAUyXXShc1IcPaJy6u07CE3K5rQcRwlvHzmlySRjg== + dependencies: + css-select "^5.1.0" + he "1.2.0" + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"