@janga/norna 0.7.25 → 0.7.26

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.
Files changed (131) hide show
  1. package/README.md +3 -2
  2. package/astro.config.mjs +26 -12
  3. package/bin/norna-cli.mjs +12 -0
  4. package/package.json +14 -5
  5. package/schemas/card-list.schema.json +121 -0
  6. package/schemas/category.schema.json +2 -2
  7. package/schemas/config.schema.json +306 -32
  8. package/schemas/content-frontmatter.schema.json +7 -7
  9. package/schemas/image-carousel.schema.json +43 -0
  10. package/schemas/image-stack.schema.json +43 -0
  11. package/schemas/manifest.json +6 -1
  12. package/schemas/page-theme.schema.json +26 -26
  13. package/schemas/sitewide-content.schema.json +18 -18
  14. package/schemas/theme.schema.json +237 -262
  15. package/scripts/dev-local.mjs +24 -2
  16. package/scripts/generate-schemas.mjs +15 -3
  17. package/scripts/lib/category-destinations.mjs +48 -0
  18. package/scripts/lib/code-fence-metadata.mjs +14 -37
  19. package/scripts/lib/content-tabs.mjs +155 -0
  20. package/scripts/lib/details-headings.mjs +58 -0
  21. package/scripts/lib/docusaurus-migration-audit.mjs +603 -0
  22. package/scripts/lib/editor-language-service.mjs +238 -116
  23. package/scripts/lib/locale-registry.mjs +597 -0
  24. package/scripts/lib/locales/az-Latn.mjs +78 -0
  25. package/scripts/lib/locales/be.mjs +78 -0
  26. package/scripts/lib/locales/bg.mjs +78 -0
  27. package/scripts/lib/locales/bs.mjs +78 -0
  28. package/scripts/lib/locales/ca.mjs +78 -0
  29. package/scripts/lib/locales/cnr.mjs +78 -0
  30. package/scripts/lib/locales/cs.mjs +78 -0
  31. package/scripts/lib/locales/da.mjs +78 -0
  32. package/scripts/lib/locales/de.mjs +78 -0
  33. package/scripts/lib/locales/el.mjs +78 -0
  34. package/scripts/lib/locales/en.mjs +78 -0
  35. package/scripts/lib/locales/es.mjs +78 -0
  36. package/scripts/lib/locales/et.mjs +78 -0
  37. package/scripts/lib/locales/fi.mjs +78 -0
  38. package/scripts/lib/locales/fil.mjs +78 -0
  39. package/scripts/lib/locales/fr.mjs +78 -0
  40. package/scripts/lib/locales/ga.mjs +78 -0
  41. package/scripts/lib/locales/ha.mjs +78 -0
  42. package/scripts/lib/locales/hr.mjs +78 -0
  43. package/scripts/lib/locales/hu.mjs +78 -0
  44. package/scripts/lib/locales/id.mjs +78 -0
  45. package/scripts/lib/locales/is.mjs +78 -0
  46. package/scripts/lib/locales/it.mjs +78 -0
  47. package/scripts/lib/locales/jv.mjs +78 -0
  48. package/scripts/lib/locales/lb.mjs +78 -0
  49. package/scripts/lib/locales/lt.mjs +78 -0
  50. package/scripts/lib/locales/lv.mjs +78 -0
  51. package/scripts/lib/locales/mk.mjs +78 -0
  52. package/scripts/lib/locales/mt.mjs +78 -0
  53. package/scripts/lib/locales/nb.mjs +78 -0
  54. package/scripts/lib/locales/nl.mjs +78 -0
  55. package/scripts/lib/locales/nn.mjs +78 -0
  56. package/scripts/lib/locales/pcm.mjs +78 -0
  57. package/scripts/lib/locales/pl.mjs +78 -0
  58. package/scripts/lib/locales/pt.mjs +78 -0
  59. package/scripts/lib/locales/ro.mjs +78 -0
  60. package/scripts/lib/locales/ru.mjs +78 -0
  61. package/scripts/lib/locales/sk.mjs +78 -0
  62. package/scripts/lib/locales/sl.mjs +78 -0
  63. package/scripts/lib/locales/sq.mjs +78 -0
  64. package/scripts/lib/locales/sr-Cyrl.mjs +78 -0
  65. package/scripts/lib/locales/sr-Latn.mjs +78 -0
  66. package/scripts/lib/locales/sv.mjs +78 -0
  67. package/scripts/lib/locales/sw.mjs +78 -0
  68. package/scripts/lib/locales/tr.mjs +78 -0
  69. package/scripts/lib/locales/uk.mjs +78 -0
  70. package/scripts/lib/locales/vi.mjs +78 -0
  71. package/scripts/lib/markdown-links.mjs +4 -25
  72. package/scripts/lib/markdown-notes-render-plugin.mjs +75 -0
  73. package/scripts/lib/markdown-notes.mjs +165 -0
  74. package/scripts/lib/norna-markdown-blocks.mjs +32 -447
  75. package/scripts/lib/norna-markdown-render-plugin.mjs +4 -0
  76. package/scripts/lib/page-aliases.mjs +12 -1
  77. package/scripts/lib/page-markdown.mjs +63 -16
  78. package/scripts/lib/page-move-plan.mjs +22 -5
  79. package/scripts/lib/pagefind-translations.mjs +620 -0
  80. package/scripts/lib/presentation-contract.mjs +6 -1
  81. package/scripts/lib/presentation.mjs +29 -30
  82. package/scripts/lib/project-config.mjs +2 -173
  83. package/scripts/lib/schema-definitions.mjs +3 -10
  84. package/scripts/lib/schema-editor-metadata.mjs +22 -43
  85. package/scripts/lib/schema-value-definitions.mjs +4 -8
  86. package/scripts/lib/semantic-callouts.mjs +4 -2
  87. package/scripts/lib/site-content.mjs +4 -2
  88. package/scripts/lib/site-link-graph.mjs +16 -6
  89. package/scripts/lib/site-navigation-tree.mjs +0 -11
  90. package/scripts/lib/sitemap.mjs +5 -3
  91. package/scripts/lib/structured-blocks.mjs +108 -0
  92. package/scripts/lib/table-render-plugin.mjs +46 -3
  93. package/scripts/lib/table-row-headers.mjs +218 -0
  94. package/scripts/lib/theme-presets.mjs +3 -10
  95. package/scripts/lib/yaml-config.mjs +2 -6
  96. package/scripts/migrate-check.mjs +19 -0
  97. package/scripts/sync-content-sections.mjs +39 -10
  98. package/scripts/sync-site-public.mjs +7 -1
  99. package/src/components/CodeBlockCopyScript.astro +47 -5
  100. package/src/components/ContentBlocks.astro +39 -0
  101. package/src/components/ContentTabsScript.astro +60 -0
  102. package/src/components/DisplaySettings.astro +17 -19
  103. package/src/components/NavigationPageTree.astro +1 -1
  104. package/src/components/NoteNavigationScript.astro +4 -0
  105. package/src/components/PageAliasRedirect.astro +5 -4
  106. package/src/components/SearchNavigationScript.astro +4 -0
  107. package/src/components/SearchPage.astro +43 -2
  108. package/src/components/SectionNavigationScript.astro +43 -11
  109. package/src/components/SiteBreadcrumbs.astro +1 -1
  110. package/src/components/SiteNavigation.astro +29 -20
  111. package/src/components/SitePage.astro +40 -15
  112. package/src/components/SiteSection.astro +5 -81
  113. package/src/components/SiteTreeNavigation.astro +2 -2
  114. package/src/components/TableOverflowScript.astro +386 -58
  115. package/src/components/TopPageMenu.astro +47 -0
  116. package/src/components/TreeNavigationScript.astro +10 -3
  117. package/src/layouts/BaseLayout.astro +8 -12
  118. package/src/lib/navigationFollowing.ts +142 -0
  119. package/src/lib/noteNavigation.ts +50 -0
  120. package/src/lib/searchNavigation.ts +107 -0
  121. package/src/lib/sectionContent.ts +20 -90
  122. package/src/lib/siteNavigation.ts +0 -5
  123. package/src/lib/sitePages.ts +13 -2
  124. package/src/lib/table-sort-tooltip.ts +109 -0
  125. package/src/pages/[...slug].astro +16 -6
  126. package/src/styles/content.css +394 -132
  127. package/src/styles/foundations.css +9 -0
  128. package/src/styles/media.css +5 -0
  129. package/src/styles/navigation.css +63 -74
  130. package/src/styles/page-layout.css +7 -0
  131. package/src/styles/responsive.css +2 -7
@@ -1,4 +1,6 @@
1
1
  import { getCodeFenceMetadataDiagnostics } from './code-fence-metadata.mjs';
2
+ import { parseContentTabs } from './content-tabs.mjs';
3
+ import { getDetailsHeadingDiagnostics } from './details-headings.mjs';
2
4
  import {
3
5
  getHeadingIdentifierIssues,
4
6
  getMarkdownHeadings,
@@ -15,6 +17,8 @@ import {
15
17
  nornaBlockTypes,
16
18
  } from './norna-markdown-blocks.mjs';
17
19
  import { getSemanticCalloutDiagnostics } from './semantic-callouts.mjs';
20
+ import { getTableRowHeaderDiagnostics } from './table-row-headers.mjs';
21
+ import { getStructuredSidenoteDiagnostics } from './markdown-notes.mjs';
18
22
 
19
23
  const normalizeMarkdownWithOffsets = (source) => {
20
24
  const input = String(source);
@@ -175,7 +179,19 @@ const getRegionContent = (regionMarkdown, blocks, regionOffset) => {
175
179
  return content.filter((item) => item.kind !== 'markdown' || item.markdown.length > 0);
176
180
  };
177
181
 
178
- const createRegion = ({ calloutErrors, codeFenceErrors, heading, nextHeading, headings, source, label, lineOffset }) => {
182
+ const createRegion = ({
183
+ calloutErrors,
184
+ codeFenceErrors,
185
+ heading,
186
+ nextHeading,
187
+ headings,
188
+ source,
189
+ label,
190
+ lineOffset,
191
+ tableErrors,
192
+ tabResult,
193
+ pageNotes,
194
+ }) => {
179
195
  const endOffset = nextHeading?.index ?? source.length;
180
196
  const markdown = source.slice(heading.index, endOffset).trimEnd();
181
197
  const regionLineOffset = lineOffset + heading.line - 1;
@@ -183,10 +199,8 @@ const createRegion = ({ calloutErrors, codeFenceErrors, heading, nextHeading, he
183
199
  label,
184
200
  lineOffset: regionLineOffset,
185
201
  });
186
- const noteResult = extractInlineNoteDiagnostics(markdown, {
187
- label,
188
- lineOffset: regionLineOffset,
189
- });
202
+ const inRegion = (offset) => offset >= heading.index && offset < endOffset;
203
+ const fieldNoteErrors = getStructuredSidenoteDiagnostics(blockResult.blocks, { label, regionOffset: heading.index });
190
204
  const markdownImages = extractMarkdownImageReferences(markdown).map((reference) => ({
191
205
  ...reference,
192
206
  line: regionLineOffset + reference.line,
@@ -202,6 +216,8 @@ const createRegion = ({ calloutErrors, codeFenceErrors, heading, nextHeading, he
202
216
  blockErrors: blockResult.errors,
203
217
  calloutErrors: calloutErrors.filter((error) => error.offset >= heading.index && error.offset < endOffset),
204
218
  codeFenceErrors: codeFenceErrors.filter((error) => error.offset >= heading.index && error.offset < endOffset),
219
+ tabErrors: tabResult.diagnostics.filter((error) => error.offset >= heading.index && error.offset < endOffset),
220
+ tabGroups: tabResult.groups.filter((group) => group.start >= heading.index && group.start < endOffset),
205
221
  content: getRegionContent(markdown, blockResult.blocks, heading.index),
206
222
  endOffset,
207
223
  heading,
@@ -213,9 +229,12 @@ const createRegion = ({ calloutErrors, codeFenceErrors, heading, nextHeading, he
213
229
  managedImages: getNornaBlockImageReferences(blockResult.blocks),
214
230
  markdown,
215
231
  markdownImages,
216
- notes: noteResult.notes,
217
- noteErrors: noteResult.errors,
232
+ notes: pageNotes.notes.filter((note) => inRegion(note.referenceOffset)),
233
+ noteErrors: [...pageNotes.errors.filter((error) => inRegion(error.offset)), ...fieldNoteErrors],
234
+ fieldNoteErrors,
235
+ noteWarnings: pageNotes.warnings.filter((warning) => inRegion(warning.offset)),
218
236
  startOffset: heading.index,
237
+ tableErrors: tableErrors.filter((error) => error.offset >= heading.index && error.offset < endOffset),
219
238
  title: heading.title,
220
239
  };
221
240
  };
@@ -224,13 +243,17 @@ export const parsePageMarkdown = async (markdown, options = {}) => {
224
243
  const source = normalizeMarkdown(markdown);
225
244
  const label = options.label ?? 'Markdown';
226
245
  const lineOffset = options.lineOffset ?? 0;
227
- const { headings, tree } = await getMarkdownHeadings(source);
246
+ const tabResult = parseContentTabs(source, { label, lineOffset });
247
+ const pageNotes = extractInlineNoteDiagnostics(source, { label, lineOffset, tabResult });
248
+ const { headings, tree } = await getMarkdownHeadings(tabResult.maskedSource);
249
+ const detailsDiagnostics = getDetailsHeadingDiagnostics(tree, { source: tabResult.maskedSource, label, lineOffset });
228
250
  const calloutErrors = getSemanticCalloutDiagnostics(tree, { label, lineOffset });
229
251
  const codeFenceErrors = getCodeFenceMetadataDiagnostics(tree, {
230
252
  excludedLanguages: nornaBlockTypes,
231
253
  label,
232
254
  lineOffset,
233
255
  });
256
+ const tableErrors = getTableRowHeaderDiagnostics(tree, { label, lineOffset, source });
234
257
  const structuralHeadings = headings.filter((heading) => heading.depth <= 2);
235
258
  const prelude = structuralHeadings.length > 0
236
259
  ? source.slice(0, structuralHeadings[0].index)
@@ -245,10 +268,17 @@ export const parsePageMarkdown = async (markdown, options = {}) => {
245
268
  label,
246
269
  lineOffset,
247
270
  source,
271
+ pageNotes,
272
+ tableErrors,
273
+ tabResult,
248
274
  }));
249
275
  const headingIssues = getHeadingIdentifierIssues(headings);
250
276
  const headingDiagnostics = getHeadingDiagnostics(headings, lineOffset);
251
- const structureDiagnostics = getPageStructureDiagnostics({ headings, pageHeadings, prelude }, lineOffset);
277
+ let contentPrelude = prelude;
278
+ for (const definition of [...pageNotes.definitions].reverse()) {
279
+ if (definition.position.end.offset <= prelude.length) contentPrelude = contentPrelude.slice(0, definition.position.start.offset) + contentPrelude.slice(definition.position.end.offset);
280
+ }
281
+ const structureDiagnostics = getPageStructureDiagnostics({ headings, pageHeadings, prelude: contentPrelude }, lineOffset);
252
282
  const blockDiagnostics = regions.flatMap((region) => region.blockErrors.map((error) => ({
253
283
  code: error.code ?? 'invalid-norna-block',
254
284
  line: error.line,
@@ -256,14 +286,19 @@ export const parsePageMarkdown = async (markdown, options = {}) => {
256
286
  regionId: region.id,
257
287
  severity: 'error',
258
288
  })));
259
- const noteDiagnostics = regions.flatMap((region) => region.noteErrors.map((error) => ({
260
- code: 'invalid-inline-note',
289
+ const noteDiagnostics = [...pageNotes.diagnostics, ...regions.flatMap((region) => region.fieldNoteErrors)].map((issue) => ({
290
+ ...issue,
291
+ regionId: regions.find((region) => issue.offset >= region.startOffset && issue.offset < region.endOffset)?.id ?? null,
292
+ }));
293
+ const calloutDiagnostics = regions.flatMap((region) => region.calloutErrors.map((error) => ({
294
+ code: error.code,
295
+ fix: error.fix,
261
296
  line: error.line,
262
297
  message: error.message,
263
298
  regionId: region.id,
264
- severity: 'error',
299
+ severity: error.severity ?? 'error',
265
300
  })));
266
- const calloutDiagnostics = regions.flatMap((region) => region.calloutErrors.map((error) => ({
301
+ const codeFenceDiagnostics = regions.flatMap((region) => region.codeFenceErrors.map((error) => ({
267
302
  code: error.code,
268
303
  fix: error.fix,
269
304
  line: error.line,
@@ -271,7 +306,7 @@ export const parsePageMarkdown = async (markdown, options = {}) => {
271
306
  regionId: region.id,
272
307
  severity: 'error',
273
308
  })));
274
- const codeFenceDiagnostics = regions.flatMap((region) => region.codeFenceErrors.map((error) => ({
309
+ const tableDiagnostics = regions.flatMap((region) => region.tableErrors.map((error) => ({
275
310
  code: error.code,
276
311
  fix: error.fix,
277
312
  line: error.line,
@@ -282,7 +317,14 @@ export const parsePageMarkdown = async (markdown, options = {}) => {
282
317
  const blocks = regions.flatMap((region) => region.blocks);
283
318
  const links = [
284
319
  ...extractMarkdownLinks({ source, tree, lineOffset }),
285
- ...extractNornaBlockLinks({ source, blocks, lineOffset }),
320
+ ...extractNornaBlockLinks({
321
+ source,
322
+ blocks: regions.flatMap((region) => region.blocks.map((block) => ({
323
+ ...block,
324
+ sourceStartOffset: region.startOffset + block.sourceStartOffset,
325
+ }))),
326
+ lineOffset,
327
+ }),
286
328
  ].sort((left, right) => (
287
329
  left.line - right.line
288
330
  || (left.range?.start ?? 0) - (right.range?.start ?? 0)
@@ -304,12 +346,15 @@ export const parsePageMarkdown = async (markdown, options = {}) => {
304
346
  return {
305
347
  blocks,
306
348
  diagnostics: [
349
+ ...detailsDiagnostics,
307
350
  ...structureDiagnostics,
308
351
  ...headingDiagnostics,
309
352
  ...blockDiagnostics,
310
353
  ...noteDiagnostics,
311
354
  ...calloutDiagnostics,
312
355
  ...codeFenceDiagnostics,
356
+ ...tableDiagnostics,
357
+ ...tabResult.diagnostics,
313
358
  ],
314
359
  headings,
315
360
  headingIssues,
@@ -321,12 +366,14 @@ export const parsePageMarkdown = async (markdown, options = {}) => {
321
366
  markdownImages: regions.flatMap((region) => region.markdownImages),
322
367
  navigationHeadings,
323
368
  notes: regions.flatMap((region) => region.notes),
369
+ noteDiagnostics,
324
370
  pageTitle: pageHeadings[0] ?? null,
325
371
  pageHeadings,
326
- prelude,
372
+ prelude: contentPrelude,
327
373
  regions,
328
374
  sections: regions.filter((region) => region.kind === 'section'),
329
375
  source,
376
+ tabGroups: tabResult.groups,
330
377
  };
331
378
  };
332
379
 
@@ -210,6 +210,9 @@ const getResolvedReferenceIntent = ({
210
210
  reference,
211
211
  }) => {
212
212
  const { resolution, target } = reference;
213
+ if (resolution?.kind === 'category') {
214
+ return { kind: 'category', lookupPathname: movedCurrentToNew.get(resolution.pathname) ?? resolution.pathname };
215
+ }
213
216
  if (resolution?.kind === 'page') {
214
217
  const currentPathname = resolution.page.pathname;
215
218
  const pathname = movedCurrentToNew.get(currentPathname) ?? currentPathname;
@@ -244,7 +247,7 @@ const getResolvedReferenceIntent = ({
244
247
  const movedPathname = movedOldToNew.get(target.pageLookupPathname);
245
248
  if (movedPathname) {
246
249
  return {
247
- kind: 'page',
250
+ kind: pagesByNewPathname.get(movedPathname)?.kind === 'category' ? 'category' : 'page',
248
251
  lookupPathname: movedPathname,
249
252
  page: pagesByNewPathname.get(movedPathname),
250
253
  };
@@ -259,7 +262,8 @@ const getTargetIntent = ({ graph, movedCurrentToNew, movedOldToNew, pagesByNewPa
259
262
  if (target.pageLookupPathname) {
260
263
  const pathname = movedOldToNew.get(target.pageLookupPathname) ?? target.pageLookupPathname;
261
264
  const page = pagesByNewPathname.get(pathname) ?? graph.pagesByPathname.get(pathname);
262
- if (page) return { kind: 'page', lookupPathname: pathname, page };
265
+ if (page) return { kind: page.kind === 'category' ? 'category' : 'page', lookupPathname: pathname, page };
266
+ if (graph.categoryModel.byPathname.has(pathname)) return { kind: 'category', lookupPathname: pathname };
263
267
  const alias = graph.aliasesByPathname.get(target.pageLookupPathname);
264
268
  if (alias) {
265
269
  const aliasTarget = movedCurrentToNew.get(alias.targetPathname) ?? alias.targetPathname;
@@ -361,7 +365,7 @@ const createReferenceChanges = ({ graph, mappings }) => {
361
365
  const value = `${intent.lookupPathname}${getTargetSuffix(reference.targetSource)}`;
362
366
  const key = `${reference.sourceContentFile.contentPath}:${reference.targetRange.start}:${reference.targetRange.end}`;
363
367
  const existing = changeKeys.get(key);
364
- if (existing && existing.value !== value) {
368
+ if (existing && existing.to !== value) {
365
369
  throw new Error(`One link target in ${reference.sourceContentFile.contentLabel} produced conflicting page-move edits.`);
366
370
  }
367
371
  if (existing) continue;
@@ -372,7 +376,7 @@ const createReferenceChanges = ({ graph, mappings }) => {
372
376
  line: reference.definitionLine ?? reference.line,
373
377
  start: reference.targetRange.start,
374
378
  to: value,
375
- value,
379
+ value: reference.yamlScalar ? JSON.stringify(value) : value,
376
380
  };
377
381
  changeKeys.set(key, change);
378
382
  if (!changesByPath.has(reference.sourceContentFile.contentPath)) {
@@ -525,7 +529,20 @@ export const createPageMovePlan = async ({
525
529
  };
526
530
  });
527
531
 
528
- const referenceChangesByPath = createReferenceChanges({ graph, mappings });
532
+ const categoryMappings = nodeMappings.filter(({ originalNode }) => originalNode.kind === 'category'
533
+ && isPathInSubtree(originalNode.pagePath, mode === 'move' ? source.pagePath : destination.pagePath))
534
+ .map(({ originalNode, virtualNode }) => {
535
+ const currentPathname = getSiteNodePathname(originalNode);
536
+ const newPathname = getSiteNodePathname(virtualNode);
537
+ return {
538
+ currentPathname,
539
+ newPathname,
540
+ oldPathname: mode === 'move' ? currentPathname
541
+ : `/${replacePagePathPrefix(originalNode.pagePath, destination.pagePath, source.pagePath)}/`,
542
+ page: { kind: 'category' },
543
+ };
544
+ });
545
+ const referenceChangesByPath = createReferenceChanges({ graph, mappings: [...mappings, ...categoryMappings] });
529
546
  const mappingByContentPath = new Map(mappings.map((mapping) => [mapping.node.contentPath, mapping]));
530
547
  const fileChanges = [];
531
548
  const updatedSources = new Map();