@fullstackdatasolutions/articles 1.2.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/nextjs.cjs +14 -4
- package/dist/nextjs.cjs.map +1 -1
- package/dist/nextjs.js +14 -4
- package/dist/nextjs.js.map +1 -1
- package/dist/server.cjs +14 -4
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +14 -4
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/server-articles.test.ts +4 -3
- package/src/markdown.ts +48 -3
- package/src/server-articles.ts +1 -3
package/package.json
CHANGED
|
@@ -358,9 +358,10 @@ describe('getArticleMetadata - frontmatter parsing', () => {
|
|
|
358
358
|
})
|
|
359
359
|
expect(article).not.toBeNull()
|
|
360
360
|
expect(article!.authorSlug).toBe('andrew-blase')
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
)
|
|
361
|
+
// Relative, not absolute: authorAvatar is only ever rendered as an
|
|
362
|
+
// <img src>, never used for JSON-LD (which resolves its own absolute
|
|
363
|
+
// URL separately via getAuthorAvatar(author, config) at render time).
|
|
364
|
+
expect(article!.authorAvatar).toBe('/articles/authors/andrew-blase/avatar.jpg')
|
|
364
365
|
})
|
|
365
366
|
|
|
366
367
|
it('leaves authorSlug and authorAvatar unset when no config is passed', async () => {
|
package/src/markdown.ts
CHANGED
|
@@ -362,7 +362,9 @@ export async function markdownToHtml(
|
|
|
362
362
|
processor = processor.use(rehypeProcessImages, { articleSlug })
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
-
const result = await processor
|
|
365
|
+
const result = await processor
|
|
366
|
+
.use(rehypeStringify)
|
|
367
|
+
.process(stripInlineTagsFromHeadings(markdown))
|
|
366
368
|
|
|
367
369
|
return result.toString()
|
|
368
370
|
} catch (error) {
|
|
@@ -377,8 +379,51 @@ export async function markdownToHtml(
|
|
|
377
379
|
}
|
|
378
380
|
}
|
|
379
381
|
|
|
382
|
+
/**
|
|
383
|
+
* Strips inline HTML/JSX tags from heading lines only, keeping their inner
|
|
384
|
+
* text, before the markdown reaches a plain (non-MDX) remark parse.
|
|
385
|
+
*
|
|
386
|
+
* Article headings commonly carry a reader-facing rating dot written as JSX,
|
|
387
|
+
* e.g. `### Rage <span style={{ color: '#3b82f6' }}>●</span>`. `renderMdxSource`
|
|
388
|
+
* (real MDX compilation via `@mdx-js/mdx`) parses that correctly and renders
|
|
389
|
+
* a real `<span>` element. But `extractToc` and `markdownToHtml` both run
|
|
390
|
+
* headings through plain `remark-parse` with no MDX support, and CommonMark's
|
|
391
|
+
* raw-inline-HTML grammar does not accept a JSX object-literal attribute
|
|
392
|
+
* expression like `style={{ ... }}` - remark's HTML tokenizer fails to match
|
|
393
|
+
* it as a tag and falls back to treating the whole thing as literal text.
|
|
394
|
+
* That garbled text then (a) becomes the visible TOC label, and (b) feeds
|
|
395
|
+
* `rehype-slug`, producing a slug built from the raw markup instead of the
|
|
396
|
+
* heading's real words - which does not match the id `renderMdxSource`'s
|
|
397
|
+
* correctly-parsed pipeline assigns to the same heading in the live page, so
|
|
398
|
+
* the TOC entry silently links to an id that does not exist in the DOM.
|
|
399
|
+
*
|
|
400
|
+
* Stripping tags (not their inner content) from heading lines before parsing
|
|
401
|
+
* keeps the extracted text and generated slug consistent with what the real
|
|
402
|
+
* MDX render puts in the page, for any heading-level inline markup - not
|
|
403
|
+
* only the rating-dot convention that surfaced the bug.
|
|
404
|
+
*/
|
|
405
|
+
function stripInlineTagsFromHeadings(markdown: string): string {
|
|
406
|
+
return markdown.replace(/^(#{1,6}[ \t].*)$/gm, (line) =>
|
|
407
|
+
line.replace(/<\/?[a-zA-Z][^<>\n]*>/g, '')
|
|
408
|
+
)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Recursively extracts a node's text content, including text nested inside
|
|
413
|
+
* child elements (for example a markdown link's `<a>Dwarf</a>` inside a
|
|
414
|
+
* heading like `### [Dwarf](/link)`). A shallow, direct-children-only check
|
|
415
|
+
* here previously dropped link text from every heading that used a link as
|
|
416
|
+
* part of its heading text - a distinct bug from the JSX-in-heading garbling
|
|
417
|
+
* `stripInlineTagsFromHeadings` fixes, but with a worse symptom: the heading
|
|
418
|
+
* was silently omitted from the TOC entirely (both `id` and `text` came back
|
|
419
|
+
* empty, so `extractHeadingItem` returned `null`) rather than merely garbled.
|
|
420
|
+
*/
|
|
380
421
|
function nodeTextValue(c: ElementContent): string {
|
|
381
|
-
|
|
422
|
+
if (c.type === 'text') return (c as { value: string }).value
|
|
423
|
+
if (c.type === 'element' && 'children' in c) {
|
|
424
|
+
return (c as Element).children.map(nodeTextValue).join('')
|
|
425
|
+
}
|
|
426
|
+
return ''
|
|
382
427
|
}
|
|
383
428
|
|
|
384
429
|
function extractHeadingItem(node: Element): TocItem | null {
|
|
@@ -446,6 +491,6 @@ export async function extractToc(markdown: string): Promise<TocItem[]> {
|
|
|
446
491
|
.use(rehypeSlug)
|
|
447
492
|
.use(collectHeadings)
|
|
448
493
|
.use(rehypeStringify)
|
|
449
|
-
.process(markdown)
|
|
494
|
+
.process(stripInlineTagsFromHeadings(markdown))
|
|
450
495
|
return headings
|
|
451
496
|
}
|
package/src/server-articles.ts
CHANGED
|
@@ -296,9 +296,7 @@ async function getArticleSummary(slug: string, config?: ArticlesConfig): Promise
|
|
|
296
296
|
author,
|
|
297
297
|
authors,
|
|
298
298
|
authorSlug: primaryAuthorProfile?.slug,
|
|
299
|
-
authorAvatar: primaryAuthorProfile
|
|
300
|
-
? getAuthorAvatar(primaryAuthorProfile, config)
|
|
301
|
-
: undefined,
|
|
299
|
+
authorAvatar: primaryAuthorProfile ? getAuthorAvatar(primaryAuthorProfile) : undefined,
|
|
302
300
|
category: categories[0],
|
|
303
301
|
categories,
|
|
304
302
|
readTime,
|