@janga/norna 0.7.23 → 0.7.24

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 (71) hide show
  1. package/README.md +14 -18
  2. package/astro.config.mjs +12 -0
  3. package/bin/norna-cli.mjs +6 -0
  4. package/package.json +13 -2
  5. package/schemas/category.schema.json +2 -2
  6. package/schemas/config.schema.json +45 -15
  7. package/schemas/content-frontmatter.schema.json +7 -7
  8. package/schemas/page-theme.schema.json +26 -26
  9. package/schemas/sitewide-content.schema.json +18 -18
  10. package/schemas/theme.schema.json +246 -246
  11. package/scripts/build-site.mjs +1 -0
  12. package/scripts/check-config.mjs +12 -1
  13. package/scripts/generate-search-index.mjs +57 -0
  14. package/scripts/init-site.mjs +1 -0
  15. package/scripts/lib/code-fence-metadata.mjs +223 -0
  16. package/scripts/lib/edit-source-link.mjs +49 -0
  17. package/scripts/lib/editor-language-service.mjs +32 -12
  18. package/scripts/lib/image-presentation.mjs +4 -0
  19. package/scripts/lib/navigation-model.mjs +34 -13
  20. package/scripts/lib/navigation-review.mjs +396 -0
  21. package/scripts/lib/norna-markdown-blocks.mjs +73 -26
  22. package/scripts/lib/norna-markdown-render-plugin.mjs +64 -1
  23. package/scripts/lib/page-aliases.mjs +21 -1
  24. package/scripts/lib/page-markdown.mjs +32 -1
  25. package/scripts/lib/page-move-plan.mjs +659 -0
  26. package/scripts/lib/presentation-palette-metadata.mjs +1 -1
  27. package/scripts/lib/presentation.mjs +1 -10
  28. package/scripts/lib/project-config.mjs +111 -5
  29. package/scripts/lib/public-asset-conventions.mjs +36 -2
  30. package/scripts/lib/schema-definitions.mjs +22 -5
  31. package/scripts/lib/schema-editor-metadata.mjs +19 -4
  32. package/scripts/lib/schema-value-definitions.mjs +1 -1
  33. package/scripts/lib/semantic-callouts.mjs +128 -0
  34. package/scripts/lib/site-content.mjs +2 -1
  35. package/scripts/lib/site-link-graph.mjs +33 -2
  36. package/scripts/lib/site-navigation-tree.mjs +85 -0
  37. package/scripts/lib/social-image-assets.mjs +30 -0
  38. package/scripts/lib/theme-presets.mjs +2 -2
  39. package/scripts/lib/theme-profiles.mjs +0 -4
  40. package/scripts/move-site-page.mjs +256 -0
  41. package/scripts/review-navigation.mjs +32 -0
  42. package/scripts/sync-content-sections.mjs +67 -4
  43. package/scripts/sync-site-public.mjs +24 -5
  44. package/src/components/CodeBlockCopyScript.astro +9 -4
  45. package/src/components/EditSourceLink.astro +17 -0
  46. package/src/components/ImageCarousel.astro +5 -6
  47. package/src/components/NavigationPageTree.astro +66 -55
  48. package/src/components/NavigationTreeControls.astro +77 -0
  49. package/src/components/PageAliasRedirect.astro +1 -1
  50. package/src/components/PageList.astro +33 -0
  51. package/src/components/PageSequenceNavigation.astro +37 -0
  52. package/src/components/SearchPage.astro +151 -0
  53. package/src/components/SiteNavigation.astro +35 -8
  54. package/src/components/SitePage.astro +79 -26
  55. package/src/components/SiteSection.astro +11 -3
  56. package/src/components/SiteTreeNavigation.astro +8 -1
  57. package/src/components/TreeNavigationScript.astro +207 -26
  58. package/src/layouts/BaseLayout.astro +57 -2
  59. package/src/lib/sectionContent.ts +32 -3
  60. package/src/lib/siteNavigation.ts +31 -52
  61. package/src/lib/sitePublicAssets.ts +1 -0
  62. package/src/pages/404.astro +110 -0
  63. package/src/pages/[...slug].astro +15 -5
  64. package/src/styles/content.css +242 -18
  65. package/src/styles/media.css +0 -5
  66. package/src/styles/navigation.css +31 -0
  67. package/src/styles/page-layout.css +438 -89
  68. package/src/styles/responsive.css +77 -8
  69. package/starters/basic/README.md +6 -19
  70. package/starters/basic/package.json +1 -0
  71. package/starters/basic/site/pages/000-home/content.md +13 -50
@@ -1,14 +1,21 @@
1
1
  import { getSiteNodePathname } from './site-page-urls.mjs';
2
2
  import { sitemapFilename } from './sitemap.mjs';
3
3
 
4
- export const generatedSiteRoutes = Object.freeze([
4
+ export const getGeneratedSiteRoutes = ({ searchEnabled = false } = {}) => Object.freeze([
5
5
  Object.freeze({
6
6
  kind: 'generated-route',
7
7
  label: `Norna generated ${sitemapFilename}`,
8
8
  pathname: `/${sitemapFilename}`,
9
9
  }),
10
+ ...(searchEnabled ? [Object.freeze({
11
+ kind: 'generated-route',
12
+ label: 'Norna generated search page',
13
+ pathname: '/search/',
14
+ })] : []),
10
15
  ]);
11
16
 
17
+ export const generatedSiteRoutes = getGeneratedSiteRoutes();
18
+
12
19
  const getPagePathname = (page) => page.pathname ?? getSiteNodePathname(page.contentFile ?? page);
13
20
  const getPageLabel = (page) => page.contentLabel ?? page.contentFile?.contentLabel ?? page.entry?.id ?? getPagePathname(page);
14
21
  const getPageAliases = (page) => page.aliases ?? page.data?.page?.aliases ?? [];
@@ -99,6 +106,19 @@ export const createPageAliasModel = ({
99
106
  for (const identity of getPublicFileIdentities(file)) addIdentity(identity);
100
107
  }
101
108
  for (const route of generatedRoutes) {
109
+ const conflict = identitiesByPathname.get(route.pathname);
110
+ if (conflict) {
111
+ diagnostics.push({
112
+ code: 'generated-route-collision',
113
+ contentFile: conflict.kind === 'page'
114
+ ? conflict.source.contentFile ?? conflict.source
115
+ : undefined,
116
+ fix: `Move or rename the conflicting source. ${route.label} owns ${route.pathname} while the feature is enabled.`,
117
+ message: `Generated route "${route.pathname}" from ${route.label} conflicts with ${describeIdentity(conflict)}.`,
118
+ severity: 'error',
119
+ });
120
+ continue;
121
+ }
102
122
  addIdentity(createIdentity({ ...route, source: route }));
103
123
  }
104
124
 
@@ -1,3 +1,4 @@
1
+ import { getCodeFenceMetadataDiagnostics } from './code-fence-metadata.mjs';
1
2
  import {
2
3
  getHeadingIdentifierIssues,
3
4
  getMarkdownHeadings,
@@ -11,7 +12,9 @@ import {
11
12
  extractMarkdownImageReferences,
12
13
  extractNornaMarkdownBlockDiagnostics,
13
14
  getNornaBlockImageReferences,
15
+ nornaBlockTypes,
14
16
  } from './norna-markdown-blocks.mjs';
17
+ import { getSemanticCalloutDiagnostics } from './semantic-callouts.mjs';
15
18
 
16
19
  const normalizeMarkdownWithOffsets = (source) => {
17
20
  const input = String(source);
@@ -172,7 +175,7 @@ const getRegionContent = (regionMarkdown, blocks, regionOffset) => {
172
175
  return content.filter((item) => item.kind !== 'markdown' || item.markdown.length > 0);
173
176
  };
174
177
 
175
- const createRegion = ({ heading, nextHeading, headings, source, label, lineOffset }) => {
178
+ const createRegion = ({ calloutErrors, codeFenceErrors, heading, nextHeading, headings, source, label, lineOffset }) => {
176
179
  const endOffset = nextHeading?.index ?? source.length;
177
180
  const markdown = source.slice(heading.index, endOffset).trimEnd();
178
181
  const regionLineOffset = lineOffset + heading.line - 1;
@@ -197,6 +200,8 @@ const createRegion = ({ heading, nextHeading, headings, source, label, lineOffse
197
200
  bodyMarkdown: source.slice(heading.index + heading.source.length, endOffset).trimEnd(),
198
201
  blocks: blockResult.blocks,
199
202
  blockErrors: blockResult.errors,
203
+ calloutErrors: calloutErrors.filter((error) => error.offset >= heading.index && error.offset < endOffset),
204
+ codeFenceErrors: codeFenceErrors.filter((error) => error.offset >= heading.index && error.offset < endOffset),
200
205
  content: getRegionContent(markdown, blockResult.blocks, heading.index),
201
206
  endOffset,
202
207
  heading,
@@ -220,12 +225,20 @@ export const parsePageMarkdown = async (markdown, options = {}) => {
220
225
  const label = options.label ?? 'Markdown';
221
226
  const lineOffset = options.lineOffset ?? 0;
222
227
  const { headings, tree } = await getMarkdownHeadings(source);
228
+ const calloutErrors = getSemanticCalloutDiagnostics(tree, { label, lineOffset });
229
+ const codeFenceErrors = getCodeFenceMetadataDiagnostics(tree, {
230
+ excludedLanguages: nornaBlockTypes,
231
+ label,
232
+ lineOffset,
233
+ });
223
234
  const structuralHeadings = headings.filter((heading) => heading.depth <= 2);
224
235
  const prelude = structuralHeadings.length > 0
225
236
  ? source.slice(0, structuralHeadings[0].index)
226
237
  : source;
227
238
  const pageHeadings = structuralHeadings.filter((heading) => heading.depth === 1);
228
239
  const regions = structuralHeadings.map((heading, index) => createRegion({
240
+ calloutErrors,
241
+ codeFenceErrors,
229
242
  heading,
230
243
  nextHeading: structuralHeadings[index + 1],
231
244
  headings,
@@ -250,6 +263,22 @@ export const parsePageMarkdown = async (markdown, options = {}) => {
250
263
  regionId: region.id,
251
264
  severity: 'error',
252
265
  })));
266
+ const calloutDiagnostics = regions.flatMap((region) => region.calloutErrors.map((error) => ({
267
+ code: error.code,
268
+ fix: error.fix,
269
+ line: error.line,
270
+ message: error.message,
271
+ regionId: region.id,
272
+ severity: 'error',
273
+ })));
274
+ const codeFenceDiagnostics = regions.flatMap((region) => region.codeFenceErrors.map((error) => ({
275
+ code: error.code,
276
+ fix: error.fix,
277
+ line: error.line,
278
+ message: error.message,
279
+ regionId: region.id,
280
+ severity: 'error',
281
+ })));
253
282
  const blocks = regions.flatMap((region) => region.blocks);
254
283
  const links = [
255
284
  ...extractMarkdownLinks({ source, tree, lineOffset }),
@@ -279,6 +308,8 @@ export const parsePageMarkdown = async (markdown, options = {}) => {
279
308
  ...headingDiagnostics,
280
309
  ...blockDiagnostics,
281
310
  ...noteDiagnostics,
311
+ ...calloutDiagnostics,
312
+ ...codeFenceDiagnostics,
282
313
  ],
283
314
  headings,
284
315
  headingIssues,