@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
@@ -0,0 +1,109 @@
1
+ export const createTableSortTooltip = () => {
2
+ const tooltip = document.createElement('div');
3
+ tooltip.id = 'norna-table-sort-tooltip';
4
+ tooltip.className = 'norna-table-sort-tooltip';
5
+ tooltip.setAttribute('role', 'tooltip');
6
+ tooltip.hidden = true;
7
+ document.body.append(tooltip);
8
+ let owner: HTMLButtonElement | null = null;
9
+ let openTimer: ReturnType<typeof setTimeout> | undefined;
10
+ let closeTimer: ReturnType<typeof setTimeout> | undefined;
11
+
12
+ const setDescription = (button: HTMLButtonElement, enabled: boolean) => {
13
+ const ids = new Set((button.getAttribute('aria-describedby') ?? '').split(/\s+/u).filter(Boolean));
14
+ if (enabled) ids.add(tooltip.id);
15
+ else ids.delete(tooltip.id);
16
+ if (ids.size) button.setAttribute('aria-describedby', [...ids].join(' '));
17
+ else button.removeAttribute('aria-describedby');
18
+ };
19
+ const hide = () => {
20
+ clearTimeout(openTimer);
21
+ clearTimeout(closeTimer);
22
+ if (owner) setDescription(owner, false);
23
+ owner = null;
24
+ tooltip.hidden = true;
25
+ };
26
+ const position = () => {
27
+ if (!owner || tooltip.hidden) return;
28
+ const bounds = owner.getBoundingClientRect();
29
+ const tip = tooltip.getBoundingClientRect();
30
+ const width = document.documentElement.clientWidth;
31
+ const height = window.innerHeight;
32
+ const left = Math.max(8, Math.min(width - tip.width - 8, bounds.left + (bounds.width - tip.width) / 2));
33
+ const below = bounds.bottom + 4;
34
+ const top = below + tip.height <= height - 8 ? below : Math.max(8, bounds.top - tip.height - 4);
35
+ tooltip.style.left = `${left}px`;
36
+ tooltip.style.top = `${top}px`;
37
+ };
38
+ const refresh = (button: HTMLButtonElement) => {
39
+ if (button !== owner || tooltip.hidden) return;
40
+ tooltip.textContent = button.dataset.tableSortAction ?? '';
41
+ position();
42
+ };
43
+ const show = (button: HTMLButtonElement, delay: number) => {
44
+ if (button === owner && !tooltip.hidden) {
45
+ clearTimeout(closeTimer);
46
+ return;
47
+ }
48
+ hide();
49
+ owner = button;
50
+ const open = () => {
51
+ if (!button.isConnected || button.closest('[inert]') || button.getAttribute('aria-hidden') === 'true') {
52
+ hide();
53
+ return;
54
+ }
55
+ tooltip.hidden = false;
56
+ setDescription(button, true);
57
+ refresh(button);
58
+ };
59
+ if (delay === 0) open();
60
+ else openTimer = setTimeout(open, delay);
61
+ };
62
+ const scheduleHide = () => {
63
+ clearTimeout(openTimer);
64
+ clearTimeout(closeTimer);
65
+ // Allow the pointer to cross the small gap into the tooltip.
66
+ closeTimer = setTimeout(() => {
67
+ if (!tooltip.matches(':hover') && !owner?.matches(':hover, :focus-visible')) hide();
68
+ }, 120);
69
+ };
70
+ tooltip.addEventListener('pointerenter', () => clearTimeout(closeTimer));
71
+ tooltip.addEventListener('pointerleave', scheduleHide);
72
+ document.addEventListener('keydown', (event) => {
73
+ if (event.key === 'Escape' && owner) {
74
+ hide();
75
+ event.preventDefault();
76
+ }
77
+ });
78
+ document.addEventListener('scroll', () => {
79
+ // Keyboard focus may scroll a clipped heading into view after opening its tip.
80
+ if (owner?.matches(':focus-visible')) {
81
+ const bounds = owner.getBoundingClientRect();
82
+ if (bounds.bottom > 0 && bounds.top < window.innerHeight) {
83
+ position();
84
+ return;
85
+ }
86
+ }
87
+ hide();
88
+ }, { capture: true, passive: true });
89
+ window.addEventListener('resize', hide, { passive: true });
90
+
91
+ return {
92
+ refresh,
93
+ attach(button: HTMLButtonElement) {
94
+ setDescription(button, false);
95
+ button.addEventListener('pointerenter', (event) => {
96
+ if (event.pointerType !== 'touch') show(button, 500);
97
+ });
98
+ button.addEventListener('pointerleave', () => {
99
+ if (button === owner) scheduleHide();
100
+ });
101
+ button.addEventListener('focus', () => {
102
+ if (button.matches(':focus-visible')) show(button, 0);
103
+ });
104
+ button.addEventListener('blur', () => {
105
+ if (button === owner) scheduleHide();
106
+ });
107
+ },
108
+ };
109
+ };
@@ -45,18 +45,28 @@ export async function getStaticPaths() {
45
45
  ? [{ params: { slug: 'search' }, props: { searchPage: true } }]
46
46
  : [];
47
47
 
48
- return [...pagePaths, ...aliasPaths, ...searchPath];
48
+ const categoryPaths = siteModel.categoryDestinations.destinations.map((destination) => ({
49
+ params: { slug: destination.category.pathSegment },
50
+ props: destination.kind === 'redirect' ? {
51
+ alias: {
52
+ targetPathname: destination.target.pathname,
53
+ targetTitle: destination.target.title,
54
+ categoryTitle: destination.category.title,
55
+ },
56
+ } : { categoryPath: destination.pathname },
57
+ }));
58
+ return [...pagePaths, ...aliasPaths, ...searchPath, ...categoryPaths];
49
59
  }
50
60
 
51
- const { alias, entry, searchPage } = Astro.props;
61
+ const { alias, entry, searchPage, categoryPath } = Astro.props;
52
62
 
53
- if (!searchPage && !alias && (!entry || isHomePageEntry(entry))) {
63
+ if (!searchPage && !alias && !categoryPath && (!entry || isHomePageEntry(entry))) {
54
64
  throw new Error('Page entry is missing.');
55
65
  }
56
66
 
57
67
  const currentPage = entry ? await getSitePage(entry) : null;
58
68
 
59
- if (!searchPage && !alias && !currentPage?.pathSegment) {
69
+ if (!searchPage && !alias && !categoryPath && !currentPage?.pathSegment) {
60
70
  throw new Error(`Page "${entry.id}" needs a page id.`);
61
71
  }
62
72
  ---
@@ -64,5 +74,5 @@ if (!searchPage && !alias && !currentPage?.pathSegment) {
64
74
  {searchPage
65
75
  ? <SearchPage />
66
76
  : alias
67
- ? <PageAliasRedirect targetPathname={alias.targetPathname} targetTitle={alias.targetTitle} />
68
- : <SitePage entry={entry} />}
77
+ ? <PageAliasRedirect targetPathname={alias.targetPathname} targetTitle={alias.targetTitle} categoryTitle={alias.categoryTitle} />
78
+ : <SitePage entry={entry} categoryPath={categoryPath} />}