@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.
- package/README.md +3 -2
- package/astro.config.mjs +26 -12
- package/bin/norna-cli.mjs +12 -0
- package/package.json +14 -5
- package/schemas/card-list.schema.json +121 -0
- package/schemas/category.schema.json +2 -2
- package/schemas/config.schema.json +306 -32
- package/schemas/content-frontmatter.schema.json +7 -7
- package/schemas/image-carousel.schema.json +43 -0
- package/schemas/image-stack.schema.json +43 -0
- package/schemas/manifest.json +6 -1
- package/schemas/page-theme.schema.json +26 -26
- package/schemas/sitewide-content.schema.json +18 -18
- package/schemas/theme.schema.json +237 -262
- package/scripts/dev-local.mjs +24 -2
- package/scripts/generate-schemas.mjs +15 -3
- package/scripts/lib/category-destinations.mjs +48 -0
- package/scripts/lib/code-fence-metadata.mjs +14 -37
- package/scripts/lib/content-tabs.mjs +155 -0
- package/scripts/lib/details-headings.mjs +58 -0
- package/scripts/lib/docusaurus-migration-audit.mjs +603 -0
- package/scripts/lib/editor-language-service.mjs +238 -116
- package/scripts/lib/locale-registry.mjs +597 -0
- package/scripts/lib/locales/az-Latn.mjs +78 -0
- package/scripts/lib/locales/be.mjs +78 -0
- package/scripts/lib/locales/bg.mjs +78 -0
- package/scripts/lib/locales/bs.mjs +78 -0
- package/scripts/lib/locales/ca.mjs +78 -0
- package/scripts/lib/locales/cnr.mjs +78 -0
- package/scripts/lib/locales/cs.mjs +78 -0
- package/scripts/lib/locales/da.mjs +78 -0
- package/scripts/lib/locales/de.mjs +78 -0
- package/scripts/lib/locales/el.mjs +78 -0
- package/scripts/lib/locales/en.mjs +78 -0
- package/scripts/lib/locales/es.mjs +78 -0
- package/scripts/lib/locales/et.mjs +78 -0
- package/scripts/lib/locales/fi.mjs +78 -0
- package/scripts/lib/locales/fil.mjs +78 -0
- package/scripts/lib/locales/fr.mjs +78 -0
- package/scripts/lib/locales/ga.mjs +78 -0
- package/scripts/lib/locales/ha.mjs +78 -0
- package/scripts/lib/locales/hr.mjs +78 -0
- package/scripts/lib/locales/hu.mjs +78 -0
- package/scripts/lib/locales/id.mjs +78 -0
- package/scripts/lib/locales/is.mjs +78 -0
- package/scripts/lib/locales/it.mjs +78 -0
- package/scripts/lib/locales/jv.mjs +78 -0
- package/scripts/lib/locales/lb.mjs +78 -0
- package/scripts/lib/locales/lt.mjs +78 -0
- package/scripts/lib/locales/lv.mjs +78 -0
- package/scripts/lib/locales/mk.mjs +78 -0
- package/scripts/lib/locales/mt.mjs +78 -0
- package/scripts/lib/locales/nb.mjs +78 -0
- package/scripts/lib/locales/nl.mjs +78 -0
- package/scripts/lib/locales/nn.mjs +78 -0
- package/scripts/lib/locales/pcm.mjs +78 -0
- package/scripts/lib/locales/pl.mjs +78 -0
- package/scripts/lib/locales/pt.mjs +78 -0
- package/scripts/lib/locales/ro.mjs +78 -0
- package/scripts/lib/locales/ru.mjs +78 -0
- package/scripts/lib/locales/sk.mjs +78 -0
- package/scripts/lib/locales/sl.mjs +78 -0
- package/scripts/lib/locales/sq.mjs +78 -0
- package/scripts/lib/locales/sr-Cyrl.mjs +78 -0
- package/scripts/lib/locales/sr-Latn.mjs +78 -0
- package/scripts/lib/locales/sv.mjs +78 -0
- package/scripts/lib/locales/sw.mjs +78 -0
- package/scripts/lib/locales/tr.mjs +78 -0
- package/scripts/lib/locales/uk.mjs +78 -0
- package/scripts/lib/locales/vi.mjs +78 -0
- package/scripts/lib/markdown-links.mjs +4 -25
- package/scripts/lib/markdown-notes-render-plugin.mjs +75 -0
- package/scripts/lib/markdown-notes.mjs +165 -0
- package/scripts/lib/norna-markdown-blocks.mjs +32 -447
- package/scripts/lib/norna-markdown-render-plugin.mjs +4 -0
- package/scripts/lib/page-aliases.mjs +12 -1
- package/scripts/lib/page-markdown.mjs +63 -16
- package/scripts/lib/page-move-plan.mjs +22 -5
- package/scripts/lib/pagefind-translations.mjs +620 -0
- package/scripts/lib/presentation-contract.mjs +6 -1
- package/scripts/lib/presentation.mjs +29 -30
- package/scripts/lib/project-config.mjs +2 -173
- package/scripts/lib/schema-definitions.mjs +3 -10
- package/scripts/lib/schema-editor-metadata.mjs +22 -43
- package/scripts/lib/schema-value-definitions.mjs +4 -8
- package/scripts/lib/semantic-callouts.mjs +4 -2
- package/scripts/lib/site-content.mjs +4 -2
- package/scripts/lib/site-link-graph.mjs +16 -6
- package/scripts/lib/site-navigation-tree.mjs +0 -11
- package/scripts/lib/sitemap.mjs +5 -3
- package/scripts/lib/structured-blocks.mjs +108 -0
- package/scripts/lib/table-render-plugin.mjs +46 -3
- package/scripts/lib/table-row-headers.mjs +218 -0
- package/scripts/lib/theme-presets.mjs +3 -10
- package/scripts/lib/yaml-config.mjs +2 -6
- package/scripts/migrate-check.mjs +19 -0
- package/scripts/sync-content-sections.mjs +39 -10
- package/scripts/sync-site-public.mjs +7 -1
- package/src/components/CodeBlockCopyScript.astro +47 -5
- package/src/components/ContentBlocks.astro +39 -0
- package/src/components/ContentTabsScript.astro +60 -0
- package/src/components/DisplaySettings.astro +17 -19
- package/src/components/NavigationPageTree.astro +1 -1
- package/src/components/NoteNavigationScript.astro +4 -0
- package/src/components/PageAliasRedirect.astro +5 -4
- package/src/components/SearchNavigationScript.astro +4 -0
- package/src/components/SearchPage.astro +43 -2
- package/src/components/SectionNavigationScript.astro +43 -11
- package/src/components/SiteBreadcrumbs.astro +1 -1
- package/src/components/SiteNavigation.astro +29 -20
- package/src/components/SitePage.astro +40 -15
- package/src/components/SiteSection.astro +5 -81
- package/src/components/SiteTreeNavigation.astro +2 -2
- package/src/components/TableOverflowScript.astro +386 -58
- package/src/components/TopPageMenu.astro +47 -0
- package/src/components/TreeNavigationScript.astro +10 -3
- package/src/layouts/BaseLayout.astro +8 -12
- package/src/lib/navigationFollowing.ts +142 -0
- package/src/lib/noteNavigation.ts +50 -0
- package/src/lib/searchNavigation.ts +107 -0
- package/src/lib/sectionContent.ts +20 -90
- package/src/lib/siteNavigation.ts +0 -5
- package/src/lib/sitePages.ts +13 -2
- package/src/lib/table-sort-tooltip.ts +109 -0
- package/src/pages/[...slug].astro +16 -6
- package/src/styles/content.css +394 -132
- package/src/styles/foundations.css +9 -0
- package/src/styles/media.css +5 -0
- package/src/styles/navigation.css +63 -74
- package/src/styles/page-layout.css +7 -0
- 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
|
-
|
|
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} />}
|