@tomehq/theme 0.1.2 → 0.2.1
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 +11 -0
- package/dist/{chunk-JA4PMX6M.js → chunk-CTPOZMMK.js} +220 -17
- package/dist/entry.js +1 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/src/Shell.test.tsx +242 -8
- package/src/Shell.tsx +230 -19
- package/src/entry.tsx +25 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @tomehq/theme
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Shell: logo links back to landing page, dynamic version in sidebar footer
|
|
8
|
+
- Shell: edit link support, table of contents depth config, changelog page layout
|
|
9
|
+
- Entry: pass new config fields (editLink, tableOfContents, plugins) to Shell
|
|
10
|
+
- Updated dependencies
|
|
11
|
+
- @tomehq/core@0.2.0
|
|
12
|
+
- @tomehq/components@0.2.0
|
|
13
|
+
|
|
3
14
|
## 0.1.2
|
|
4
15
|
|
|
5
16
|
### Patch Changes
|
|
@@ -565,6 +565,26 @@ var MoonIcon = () => /* @__PURE__ */ jsx2(Icon, { d: "M21 12.79A9 9 0 1 1 11.21
|
|
|
565
565
|
var SunIcon = () => /* @__PURE__ */ jsx2(Icon, { d: "M12 7a5 5 0 1 0 0 10 5 5 0 0 0 0-10Z" });
|
|
566
566
|
var ArrowLeft = () => /* @__PURE__ */ jsx2(Icon, { d: "M19 12H5M12 19l-7-7 7-7", size: 14 });
|
|
567
567
|
var ArrowRight = () => /* @__PURE__ */ jsx2(Icon, { d: "M5 12h14M12 5l7 7-7 7", size: 14 });
|
|
568
|
+
var PencilIcon = () => /* @__PURE__ */ jsx2(Icon, { d: "M17 3a2.83 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z", size: 13 });
|
|
569
|
+
function formatRelativeDate(isoDate) {
|
|
570
|
+
const date = new Date(isoDate);
|
|
571
|
+
const now = /* @__PURE__ */ new Date();
|
|
572
|
+
const diffMs = now.getTime() - date.getTime();
|
|
573
|
+
if (isNaN(diffMs)) return "";
|
|
574
|
+
const seconds = Math.floor(diffMs / 1e3);
|
|
575
|
+
const minutes = Math.floor(seconds / 60);
|
|
576
|
+
const hours = Math.floor(minutes / 60);
|
|
577
|
+
const days = Math.floor(hours / 24);
|
|
578
|
+
const months = Math.floor(days / 30);
|
|
579
|
+
const years = Math.floor(days / 365);
|
|
580
|
+
if (seconds < 60) return "just now";
|
|
581
|
+
if (minutes < 60) return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
|
|
582
|
+
if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
|
|
583
|
+
if (days < 30) return `${days} day${days === 1 ? "" : "s"} ago`;
|
|
584
|
+
if (months < 12) return `${months} month${months === 1 ? "" : "s"} ago`;
|
|
585
|
+
if (years >= 1) return `${years} year${years === 1 ? "" : "s"} ago`;
|
|
586
|
+
return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
|
|
587
|
+
}
|
|
568
588
|
var pagefindInstance = null;
|
|
569
589
|
var PAGEFIND_PATH = "/_pagefind/pagefind.js";
|
|
570
590
|
async function initPagefind() {
|
|
@@ -686,6 +706,68 @@ function AlgoliaSearchModal({
|
|
|
686
706
|
var VersionIcon = () => /* @__PURE__ */ jsx2(Icon, { d: "M12 8v4l3 3m6-3a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z", size: 14 });
|
|
687
707
|
var GlobeIcon = () => /* @__PURE__ */ jsx2(Icon, { d: "M12 21a9 9 0 1 0 0-18 9 9 0 0 0 0 18ZM3.6 9h16.8M3.6 15h16.8M12 3a15 15 0 0 1 4 9 15 15 0 0 1-4 9 15 15 0 0 1-4-9 15 15 0 0 1 4-9Z", size: 14 });
|
|
688
708
|
var ExtLinkIcon = () => /* @__PURE__ */ jsx2(Icon, { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6M15 3h6v6M10 14L21 3", size: 11 });
|
|
709
|
+
var CHANGELOG_SECTION_COLORS = {
|
|
710
|
+
Added: "#22c55e",
|
|
711
|
+
Changed: "#3b82f6",
|
|
712
|
+
Deprecated: "#f59e0b",
|
|
713
|
+
Removed: "#ef4444",
|
|
714
|
+
Fixed: "#8b5cf6",
|
|
715
|
+
Security: "#f97316"
|
|
716
|
+
};
|
|
717
|
+
function ChangelogView({ entries }) {
|
|
718
|
+
const [showAll, setShowAll] = useState2(entries.length <= 5);
|
|
719
|
+
const visible = showAll ? entries : entries.slice(0, 5);
|
|
720
|
+
return /* @__PURE__ */ jsxs2("div", { "data-testid": "changelog-timeline", style: { position: "relative" }, children: [
|
|
721
|
+
/* @__PURE__ */ jsx2("div", { style: { position: "absolute", left: 15, top: 8, bottom: 8, width: 2, background: "var(--bd)" } }),
|
|
722
|
+
visible.map((entry, i) => /* @__PURE__ */ jsxs2(
|
|
723
|
+
"div",
|
|
724
|
+
{
|
|
725
|
+
"data-testid": `changelog-entry-${entry.version}`,
|
|
726
|
+
style: { position: "relative", paddingLeft: 44, paddingBottom: i < visible.length - 1 ? 32 : 0 },
|
|
727
|
+
children: [
|
|
728
|
+
/* @__PURE__ */ jsx2("div", { style: {
|
|
729
|
+
position: "absolute",
|
|
730
|
+
left: 8,
|
|
731
|
+
top: 6,
|
|
732
|
+
width: 16,
|
|
733
|
+
height: 16,
|
|
734
|
+
borderRadius: "50%",
|
|
735
|
+
background: entry.version === "Unreleased" ? "var(--txM)" : "var(--ac)",
|
|
736
|
+
border: "3px solid var(--bg, #1a1a1a)"
|
|
737
|
+
} }),
|
|
738
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "baseline", gap: 12, marginBottom: 12 }, children: [
|
|
739
|
+
/* @__PURE__ */ jsx2("span", { style: { fontSize: 18, fontWeight: 700, color: "var(--tx)", fontFamily: "var(--font-heading, inherit)" }, children: entry.url ? /* @__PURE__ */ jsx2("a", { href: entry.url, target: "_blank", rel: "noopener noreferrer", style: { color: "inherit", textDecoration: "none" }, children: entry.version }) : entry.version }),
|
|
740
|
+
entry.date && /* @__PURE__ */ jsx2("span", { style: { fontSize: 13, color: "var(--txM)", fontFamily: "var(--font-code, monospace)" }, children: entry.date })
|
|
741
|
+
] }),
|
|
742
|
+
entry.sections.map((section) => {
|
|
743
|
+
const sColor = CHANGELOG_SECTION_COLORS[section.type] || "#6b7280";
|
|
744
|
+
return /* @__PURE__ */ jsxs2("div", { style: { marginBottom: 16 }, children: [
|
|
745
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "inline-flex", alignItems: "center", gap: 6, marginBottom: 8 }, children: [
|
|
746
|
+
/* @__PURE__ */ jsx2("span", { style: { display: "inline-block", width: 8, height: 8, borderRadius: "50%", background: sColor } }),
|
|
747
|
+
/* @__PURE__ */ jsx2("span", { style: { fontSize: 12, fontWeight: 600, textTransform: "uppercase", letterSpacing: ".06em", color: sColor, fontFamily: "var(--font-code, monospace)" }, children: section.type })
|
|
748
|
+
] }),
|
|
749
|
+
/* @__PURE__ */ jsx2("ul", { style: { margin: 0, paddingLeft: 18, listStyleType: "disc", color: "var(--tx2)" }, children: section.items.map((item, j) => /* @__PURE__ */ jsx2("li", { style: { fontSize: 14, lineHeight: 1.7, color: "var(--tx2)", marginBottom: 2 }, children: item }, j)) })
|
|
750
|
+
] }, section.type);
|
|
751
|
+
})
|
|
752
|
+
]
|
|
753
|
+
},
|
|
754
|
+
entry.version
|
|
755
|
+
)),
|
|
756
|
+
!showAll && entries.length > 5 && /* @__PURE__ */ jsx2("div", { style: { textAlign: "center", marginTop: 24 }, children: /* @__PURE__ */ jsxs2(
|
|
757
|
+
"button",
|
|
758
|
+
{
|
|
759
|
+
"data-testid": "changelog-show-more",
|
|
760
|
+
onClick: () => setShowAll(true),
|
|
761
|
+
style: { background: "none", border: "1px solid var(--bd)", borderRadius: 2, padding: "8px 20px", color: "var(--tx2)", fontSize: 13, fontFamily: "var(--font-body, inherit)", cursor: "pointer" },
|
|
762
|
+
children: [
|
|
763
|
+
"Show all ",
|
|
764
|
+
entries.length,
|
|
765
|
+
" releases"
|
|
766
|
+
]
|
|
767
|
+
}
|
|
768
|
+
) })
|
|
769
|
+
] });
|
|
770
|
+
}
|
|
689
771
|
function Shell({
|
|
690
772
|
config: config2,
|
|
691
773
|
navigation: navigation2,
|
|
@@ -696,6 +778,10 @@ function Shell({
|
|
|
696
778
|
pageTitle,
|
|
697
779
|
pageDescription,
|
|
698
780
|
headings,
|
|
781
|
+
tocEnabled = true,
|
|
782
|
+
editUrl,
|
|
783
|
+
lastUpdated,
|
|
784
|
+
changelogEntries,
|
|
699
785
|
onNavigate,
|
|
700
786
|
allPages,
|
|
701
787
|
versioning,
|
|
@@ -744,6 +830,60 @@ function Shell({
|
|
|
744
830
|
useEffect2(() => {
|
|
745
831
|
contentRef.current?.scrollTo(0, 0);
|
|
746
832
|
}, [currentPageId]);
|
|
833
|
+
const tocConfig = config2.toc;
|
|
834
|
+
const tocDepth = tocConfig?.depth ?? 3;
|
|
835
|
+
const tocGlobalEnabled = tocConfig?.enabled !== false;
|
|
836
|
+
const showToc = tocGlobalEnabled && tocEnabled;
|
|
837
|
+
const filteredHeadings = headings.filter((h) => h.depth <= tocDepth);
|
|
838
|
+
const [activeHeadingId, setActiveHeadingId] = useState2("");
|
|
839
|
+
useEffect2(() => {
|
|
840
|
+
if (!showToc || filteredHeadings.length < 2) return;
|
|
841
|
+
const scrollRoot = contentRef.current;
|
|
842
|
+
if (!scrollRoot) return;
|
|
843
|
+
const timerId = setTimeout(() => {
|
|
844
|
+
const headingElements = [];
|
|
845
|
+
for (const h of filteredHeadings) {
|
|
846
|
+
const el = scrollRoot.querySelector(`#${CSS.escape(h.id)}`);
|
|
847
|
+
if (el) headingElements.push(el);
|
|
848
|
+
}
|
|
849
|
+
if (headingElements.length === 0) return;
|
|
850
|
+
const observer = new IntersectionObserver(
|
|
851
|
+
(entries) => {
|
|
852
|
+
const visible = entries.filter((e) => e.isIntersecting).sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top);
|
|
853
|
+
if (visible.length > 0) {
|
|
854
|
+
setActiveHeadingId(visible[0].target.id);
|
|
855
|
+
}
|
|
856
|
+
},
|
|
857
|
+
{
|
|
858
|
+
root: scrollRoot,
|
|
859
|
+
// Trigger when heading enters the top 20% of the scroll container
|
|
860
|
+
rootMargin: "0px 0px -80% 0px",
|
|
861
|
+
threshold: 0
|
|
862
|
+
}
|
|
863
|
+
);
|
|
864
|
+
for (const el of headingElements) observer.observe(el);
|
|
865
|
+
observerRef.current = observer;
|
|
866
|
+
}, 100);
|
|
867
|
+
return () => {
|
|
868
|
+
clearTimeout(timerId);
|
|
869
|
+
observerRef.current?.disconnect();
|
|
870
|
+
observerRef.current = null;
|
|
871
|
+
};
|
|
872
|
+
}, [currentPageId, showToc, filteredHeadings.map((h) => h.id).join(",")]);
|
|
873
|
+
const observerRef = useRef2(null);
|
|
874
|
+
useEffect2(() => {
|
|
875
|
+
setActiveHeadingId("");
|
|
876
|
+
}, [currentPageId]);
|
|
877
|
+
const scrollToHeading = useCallback2((e, id) => {
|
|
878
|
+
e.preventDefault();
|
|
879
|
+
const scrollRoot = contentRef.current;
|
|
880
|
+
if (!scrollRoot) return;
|
|
881
|
+
const target = scrollRoot.querySelector(`#${CSS.escape(id)}`);
|
|
882
|
+
if (target) {
|
|
883
|
+
target.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
884
|
+
setActiveHeadingId(id);
|
|
885
|
+
}
|
|
886
|
+
}, []);
|
|
747
887
|
useEffect2(() => {
|
|
748
888
|
const h = (e) => {
|
|
749
889
|
if ((e.metaKey || e.ctrlKey) && e.key === "k") {
|
|
@@ -815,7 +955,7 @@ function Shell({
|
|
|
815
955
|
transition: "width .2s, min-width .2s",
|
|
816
956
|
overflow: "hidden"
|
|
817
957
|
}, children: [
|
|
818
|
-
/* @__PURE__ */ jsxs2("
|
|
958
|
+
/* @__PURE__ */ jsxs2("a", { href: "/", style: { padding: "18px 20px", display: "flex", alignItems: "baseline", gap: 6, borderBottom: "1px solid var(--bd)", textDecoration: "none", color: "inherit" }, children: [
|
|
819
959
|
/* @__PURE__ */ jsx2("span", { style: { fontFamily: "var(--font-heading)", fontSize: 22, fontWeight: 700, fontStyle: "italic" }, children: config2.name }),
|
|
820
960
|
/* @__PURE__ */ jsx2("span", { style: { width: 5, height: 5, borderRadius: "50%", background: "var(--ac)", display: "inline-block" } })
|
|
821
961
|
] }),
|
|
@@ -1138,14 +1278,44 @@ function Shell({
|
|
|
1138
1278
|
/* @__PURE__ */ jsxs2("main", { style: { flex: 1, maxWidth: 760, padding: "40px 48px 80px", margin: "0 auto" }, children: [
|
|
1139
1279
|
/* @__PURE__ */ jsx2("h1", { style: { fontFamily: "var(--font-heading)", fontSize: 38, fontWeight: 400, fontStyle: "italic", lineHeight: 1.15, marginBottom: 8 }, children: pageTitle }),
|
|
1140
1280
|
pageDescription && /* @__PURE__ */ jsx2("p", { style: { fontSize: 16, color: "var(--tx2)", lineHeight: 1.6, marginBottom: 32 }, children: pageDescription }),
|
|
1141
|
-
/* @__PURE__ */ jsx2("div", { style: { borderTop: "1px solid var(--bd)", paddingTop: 28 }, children: PageComponent ? /* @__PURE__ */ jsx2("div", { className: "tome-content", children: /* @__PURE__ */ jsx2(PageComponent, { components: mdxComponents || {} }) }) : /* @__PURE__ */ jsx2(
|
|
1281
|
+
/* @__PURE__ */ jsx2("div", { style: { borderTop: "1px solid var(--bd)", paddingTop: 28 }, children: changelogEntries && changelogEntries.length > 0 ? /* @__PURE__ */ jsx2(ChangelogView, { entries: changelogEntries }) : PageComponent ? /* @__PURE__ */ jsx2("div", { className: "tome-content", children: /* @__PURE__ */ jsx2(PageComponent, { components: mdxComponents || {} }) }) : /* @__PURE__ */ jsx2(
|
|
1142
1282
|
"div",
|
|
1143
1283
|
{
|
|
1144
1284
|
className: "tome-content",
|
|
1145
|
-
dangerouslySetInnerHTML: { __html: pageHtml || "" }
|
|
1285
|
+
dangerouslySetInnerHTML: { __html: (pageHtml || "").replace(/<h1[^>]*>[\s\S]*?<\/h1>\s*/, "") }
|
|
1146
1286
|
}
|
|
1147
1287
|
) }),
|
|
1148
|
-
/* @__PURE__ */ jsxs2("div", { style: { display: "flex",
|
|
1288
|
+
(editUrl || lastUpdated) && /* @__PURE__ */ jsxs2("div", { style: { marginTop: 40, display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16 }, children: [
|
|
1289
|
+
editUrl && /* @__PURE__ */ jsx2("div", { "data-testid": "edit-page-link", children: /* @__PURE__ */ jsxs2(
|
|
1290
|
+
"a",
|
|
1291
|
+
{
|
|
1292
|
+
href: editUrl,
|
|
1293
|
+
target: "_blank",
|
|
1294
|
+
rel: "noopener noreferrer",
|
|
1295
|
+
style: {
|
|
1296
|
+
display: "inline-flex",
|
|
1297
|
+
alignItems: "center",
|
|
1298
|
+
gap: 6,
|
|
1299
|
+
color: "var(--txM)",
|
|
1300
|
+
textDecoration: "none",
|
|
1301
|
+
fontSize: 13,
|
|
1302
|
+
fontFamily: "var(--font-body)",
|
|
1303
|
+
transition: "color .15s"
|
|
1304
|
+
},
|
|
1305
|
+
onMouseOver: (e) => e.currentTarget.style.color = "var(--ac)",
|
|
1306
|
+
onMouseOut: (e) => e.currentTarget.style.color = "var(--txM)",
|
|
1307
|
+
children: [
|
|
1308
|
+
/* @__PURE__ */ jsx2(PencilIcon, {}),
|
|
1309
|
+
" Edit this page on GitHub"
|
|
1310
|
+
]
|
|
1311
|
+
}
|
|
1312
|
+
) }),
|
|
1313
|
+
lastUpdated && /* @__PURE__ */ jsxs2("div", { "data-testid": "last-updated", style: { fontSize: 12, color: "var(--txM)", fontFamily: "var(--font-body)" }, children: [
|
|
1314
|
+
"Last updated ",
|
|
1315
|
+
formatRelativeDate(lastUpdated)
|
|
1316
|
+
] })
|
|
1317
|
+
] }),
|
|
1318
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", justifyContent: "space-between", marginTop: editUrl || lastUpdated ? 16 : 48, paddingTop: 24, borderTop: "1px solid var(--bd)", gap: 16 }, children: [
|
|
1149
1319
|
prev ? /* @__PURE__ */ jsxs2("button", { onClick: () => onNavigate(prev.id), style: {
|
|
1150
1320
|
display: "flex",
|
|
1151
1321
|
alignItems: "center",
|
|
@@ -1184,18 +1354,34 @@ function Shell({
|
|
|
1184
1354
|
] }) : /* @__PURE__ */ jsx2("div", {})
|
|
1185
1355
|
] })
|
|
1186
1356
|
] }),
|
|
1187
|
-
|
|
1357
|
+
showToc && filteredHeadings.length >= 2 && wide && /* @__PURE__ */ jsxs2("aside", { "data-testid": "toc-sidebar", style: { width: 200, padding: "40px 16px 40px 0", position: "sticky", top: 0, alignSelf: "flex-start", flexShrink: 0 }, children: [
|
|
1188
1358
|
/* @__PURE__ */ jsx2("div", { style: { fontSize: 10, fontWeight: 600, textTransform: "uppercase", letterSpacing: ".1em", color: "var(--txM)", marginBottom: 12, fontFamily: "var(--font-code)" }, children: "On this page" }),
|
|
1189
|
-
/* @__PURE__ */ jsx2("
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1359
|
+
/* @__PURE__ */ jsx2("nav", { "aria-label": "Table of contents", style: { borderLeft: "1px solid var(--bd)", paddingLeft: 0 }, children: filteredHeadings.map((h, i) => {
|
|
1360
|
+
const isActive = activeHeadingId === h.id;
|
|
1361
|
+
return /* @__PURE__ */ jsx2(
|
|
1362
|
+
"a",
|
|
1363
|
+
{
|
|
1364
|
+
href: `#${h.id}`,
|
|
1365
|
+
onClick: (e) => scrollToHeading(e, h.id),
|
|
1366
|
+
"data-testid": `toc-link-${h.id}`,
|
|
1367
|
+
style: {
|
|
1368
|
+
display: "block",
|
|
1369
|
+
fontSize: 12,
|
|
1370
|
+
color: isActive ? "var(--ac)" : "var(--txM)",
|
|
1371
|
+
fontWeight: isActive ? 500 : 400,
|
|
1372
|
+
textDecoration: "none",
|
|
1373
|
+
padding: "4px 12px",
|
|
1374
|
+
paddingLeft: 12 + (h.depth - 2) * 12,
|
|
1375
|
+
lineHeight: 1.4,
|
|
1376
|
+
transition: "color .15s, font-weight .15s",
|
|
1377
|
+
borderLeft: isActive ? "2px solid var(--ac)" : "2px solid transparent",
|
|
1378
|
+
marginLeft: -1
|
|
1379
|
+
},
|
|
1380
|
+
children: h.text
|
|
1381
|
+
},
|
|
1382
|
+
i
|
|
1383
|
+
);
|
|
1384
|
+
}) })
|
|
1199
1385
|
] })
|
|
1200
1386
|
] })
|
|
1201
1387
|
] })
|
|
@@ -1355,7 +1541,8 @@ import {
|
|
|
1355
1541
|
Card,
|
|
1356
1542
|
CardGroup,
|
|
1357
1543
|
Steps,
|
|
1358
|
-
Accordion
|
|
1544
|
+
Accordion,
|
|
1545
|
+
ChangelogTimeline
|
|
1359
1546
|
} from "@tomehq/components";
|
|
1360
1547
|
import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1361
1548
|
var MDX_COMPONENTS = {
|
|
@@ -1364,11 +1551,13 @@ var MDX_COMPONENTS = {
|
|
|
1364
1551
|
Card,
|
|
1365
1552
|
CardGroup,
|
|
1366
1553
|
Steps,
|
|
1367
|
-
Accordion
|
|
1554
|
+
Accordion,
|
|
1555
|
+
ChangelogTimeline
|
|
1368
1556
|
};
|
|
1369
1557
|
var contentStyles = `
|
|
1370
1558
|
@import url('https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:wght@300;400;500;600;700&family=Cormorant+Garamond:ital,wght@0,300;0,400;0,600;0,700;1,300;1,400;1,700&family=Fira+Code:wght@400;500;600&display=swap');
|
|
1371
1559
|
|
|
1560
|
+
.tome-content h1 { display: none; }
|
|
1372
1561
|
.tome-content h2 { font-family: var(--font-body); font-size: 1.35em; font-weight: 600; margin-top: 2em; margin-bottom: 0.5em; display: flex; align-items: center; gap: 10px; letter-spacing: 0.01em; }
|
|
1373
1562
|
.tome-content h2::before { content: "#"; font-family: var(--font-heading); font-size: 1.2em; font-weight: 300; font-style: italic; color: var(--ac); opacity: 0.5; }
|
|
1374
1563
|
.tome-content h3 { font-family: var(--font-body); font-size: 1.15em; font-weight: 600; margin-top: 1.5em; margin-bottom: 0.5em; }
|
|
@@ -1424,6 +1613,9 @@ async function loadPage(id) {
|
|
|
1424
1613
|
};
|
|
1425
1614
|
}
|
|
1426
1615
|
if (!mod.default) return null;
|
|
1616
|
+
if (mod.isChangelog && mod.changelogEntries) {
|
|
1617
|
+
return { isMdx: false, ...mod.default, changelogEntries: mod.changelogEntries };
|
|
1618
|
+
}
|
|
1427
1619
|
return { isMdx: false, ...mod.default };
|
|
1428
1620
|
} catch (err) {
|
|
1429
1621
|
console.error(`Failed to load page: ${id}`, err);
|
|
@@ -1464,6 +1656,13 @@ function App() {
|
|
|
1464
1656
|
title: r.frontmatter.title,
|
|
1465
1657
|
description: r.frontmatter.description
|
|
1466
1658
|
}));
|
|
1659
|
+
const currentRoute = routes.find((r) => r.id === currentPageId);
|
|
1660
|
+
let editUrl;
|
|
1661
|
+
if (config.editLink && currentRoute?.filePath) {
|
|
1662
|
+
const { repo, branch = "main", dir = "" } = config.editLink;
|
|
1663
|
+
const dirPrefix = dir ? `${dir.replace(/\/$/, "")}/` : "";
|
|
1664
|
+
editUrl = `https://github.com/${repo}/edit/${branch}/${dirPrefix}${currentRoute.filePath}`;
|
|
1665
|
+
}
|
|
1467
1666
|
return /* @__PURE__ */ jsxs3(Fragment, { children: [
|
|
1468
1667
|
/* @__PURE__ */ jsx3("style", { children: contentStyles }),
|
|
1469
1668
|
/* @__PURE__ */ jsx3(
|
|
@@ -1478,6 +1677,10 @@ function App() {
|
|
|
1478
1677
|
pageTitle: pageData?.frontmatter.title || (loading ? "Loading..." : "Not Found"),
|
|
1479
1678
|
pageDescription: pageData?.frontmatter.description,
|
|
1480
1679
|
headings: pageData?.headings || [],
|
|
1680
|
+
tocEnabled: pageData?.frontmatter.toc !== false,
|
|
1681
|
+
editUrl,
|
|
1682
|
+
lastUpdated: currentRoute?.lastUpdated,
|
|
1683
|
+
changelogEntries: !pageData?.isMdx ? pageData?.changelogEntries : void 0,
|
|
1481
1684
|
onNavigate: navigateTo,
|
|
1482
1685
|
allPages,
|
|
1483
1686
|
docContext
|
package/dist/entry.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -36,6 +36,10 @@ interface ShellProps {
|
|
|
36
36
|
model?: string;
|
|
37
37
|
apiKeyEnv?: string;
|
|
38
38
|
};
|
|
39
|
+
toc?: {
|
|
40
|
+
enabled?: boolean;
|
|
41
|
+
depth?: number;
|
|
42
|
+
};
|
|
39
43
|
topNav?: Array<{
|
|
40
44
|
label: string;
|
|
41
45
|
href: string;
|
|
@@ -64,6 +68,18 @@ interface ShellProps {
|
|
|
64
68
|
text: string;
|
|
65
69
|
id: string;
|
|
66
70
|
}>;
|
|
71
|
+
tocEnabled?: boolean;
|
|
72
|
+
editUrl?: string;
|
|
73
|
+
lastUpdated?: string;
|
|
74
|
+
changelogEntries?: Array<{
|
|
75
|
+
version: string;
|
|
76
|
+
date?: string;
|
|
77
|
+
url?: string;
|
|
78
|
+
sections: Array<{
|
|
79
|
+
type: string;
|
|
80
|
+
items: string[];
|
|
81
|
+
}>;
|
|
82
|
+
}>;
|
|
67
83
|
onNavigate: (id: string) => void;
|
|
68
84
|
allPages: Array<{
|
|
69
85
|
id: string;
|
|
@@ -80,7 +96,7 @@ interface ShellProps {
|
|
|
80
96
|
content: string;
|
|
81
97
|
}>;
|
|
82
98
|
}
|
|
83
|
-
declare function Shell({ config, navigation, currentPageId, pageHtml, pageComponent, mdxComponents, pageTitle, pageDescription, headings, onNavigate, allPages, versioning, currentVersion, i18n, currentLocale, docContext, }: ShellProps): react_jsx_runtime.JSX.Element;
|
|
99
|
+
declare function Shell({ config, navigation, currentPageId, pageHtml, pageComponent, mdxComponents, pageTitle, pageDescription, headings, tocEnabled, editUrl, lastUpdated, changelogEntries, onNavigate, allPages, versioning, currentVersion, i18n, currentLocale, docContext, }: ShellProps): react_jsx_runtime.JSX.Element;
|
|
84
100
|
|
|
85
101
|
interface AiChatProps {
|
|
86
102
|
provider: "openai" | "anthropic" | "custom";
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tomehq/theme",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Tome default theme and React app shell",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.tsx",
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"./entry": "./src/entry.tsx"
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
|
-
"@tomehq/components": "0.
|
|
13
|
-
"@tomehq/core": "0.
|
|
12
|
+
"@tomehq/components": "0.2.1",
|
|
13
|
+
"@tomehq/core": "0.2.1"
|
|
14
14
|
},
|
|
15
15
|
"peerDependencies": {
|
|
16
16
|
"react": "^18.0.0 || ^19.0.0",
|
package/src/Shell.test.tsx
CHANGED
|
@@ -182,22 +182,121 @@ describe("Shell theme mode", () => {
|
|
|
182
182
|
});
|
|
183
183
|
});
|
|
184
184
|
|
|
185
|
-
// ── TOC
|
|
185
|
+
// ── TOC (TOM-52) ──────────────────────────────────────────
|
|
186
186
|
|
|
187
187
|
describe("Shell table of contents", () => {
|
|
188
|
-
|
|
188
|
+
beforeEach(() => {
|
|
189
189
|
// jsdom window.innerWidth defaults to 0 so 'wide' will be false — we need to set it
|
|
190
190
|
Object.defineProperty(window, "innerWidth", { writable: true, configurable: true, value: 1400 });
|
|
191
191
|
fireEvent(window, new Event("resize"));
|
|
192
|
+
});
|
|
192
193
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
194
|
+
const sampleHeadings = [
|
|
195
|
+
{ depth: 2, text: "Overview", id: "overview" },
|
|
196
|
+
{ depth: 3, text: "Details", id: "details" },
|
|
197
|
+
{ depth: 2, text: "Usage", id: "usage" },
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
it("renders TOC headings when headings are provided", () => {
|
|
201
|
+
renderShell({ headings: sampleHeadings });
|
|
202
|
+
expect(screen.getByTestId("toc-sidebar")).toBeInTheDocument();
|
|
199
203
|
expect(screen.getByText("Overview")).toBeInTheDocument();
|
|
200
204
|
expect(screen.getByText("Details")).toBeInTheDocument();
|
|
205
|
+
expect(screen.getByText("Usage")).toBeInTheDocument();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("renders 'On this page' label", () => {
|
|
209
|
+
renderShell({ headings: sampleHeadings });
|
|
210
|
+
expect(screen.getByText("On this page")).toBeInTheDocument();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
it("renders TOC links with correct href attributes", () => {
|
|
214
|
+
renderShell({ headings: sampleHeadings });
|
|
215
|
+
const overviewLink = screen.getByTestId("toc-link-overview");
|
|
216
|
+
expect(overviewLink).toHaveAttribute("href", "#overview");
|
|
217
|
+
const detailsLink = screen.getByTestId("toc-link-details");
|
|
218
|
+
expect(detailsLink).toHaveAttribute("href", "#details");
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("renders TOC inside a nav element with aria-label", () => {
|
|
222
|
+
renderShell({ headings: sampleHeadings });
|
|
223
|
+
const nav = screen.getByRole("navigation", { name: "Table of contents" });
|
|
224
|
+
expect(nav).toBeInTheDocument();
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("does not render TOC when fewer than 2 headings", () => {
|
|
228
|
+
renderShell({
|
|
229
|
+
headings: [{ depth: 2, text: "Only One", id: "only-one" }],
|
|
230
|
+
});
|
|
231
|
+
expect(screen.queryByTestId("toc-sidebar")).not.toBeInTheDocument();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("does not render TOC when headings array is empty", () => {
|
|
235
|
+
renderShell({ headings: [] });
|
|
236
|
+
expect(screen.queryByTestId("toc-sidebar")).not.toBeInTheDocument();
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("hides TOC when tocEnabled is false (frontmatter toc: false)", () => {
|
|
240
|
+
renderShell({ headings: sampleHeadings, tocEnabled: false });
|
|
241
|
+
expect(screen.queryByTestId("toc-sidebar")).not.toBeInTheDocument();
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("hides TOC when config toc.enabled is false", () => {
|
|
245
|
+
renderShell({
|
|
246
|
+
headings: sampleHeadings,
|
|
247
|
+
config: { ...baseConfig, toc: { enabled: false } },
|
|
248
|
+
});
|
|
249
|
+
expect(screen.queryByTestId("toc-sidebar")).not.toBeInTheDocument();
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("filters headings by config toc.depth", () => {
|
|
253
|
+
const headingsWithH4 = [
|
|
254
|
+
{ depth: 2, text: "Section", id: "section" },
|
|
255
|
+
{ depth: 3, text: "Subsection", id: "subsection" },
|
|
256
|
+
{ depth: 4, text: "Deep", id: "deep" },
|
|
257
|
+
];
|
|
258
|
+
// depth: 2 means only h2 headings
|
|
259
|
+
renderShell({
|
|
260
|
+
headings: headingsWithH4,
|
|
261
|
+
config: { ...baseConfig, toc: { depth: 2 } },
|
|
262
|
+
});
|
|
263
|
+
// Only h2 shown, but need at least 2 headings — only 1 h2 so TOC hidden
|
|
264
|
+
expect(screen.queryByTestId("toc-sidebar")).not.toBeInTheDocument();
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it("shows only headings up to configured depth", () => {
|
|
268
|
+
const headingsMultiDepth = [
|
|
269
|
+
{ depth: 2, text: "First", id: "first" },
|
|
270
|
+
{ depth: 2, text: "Second", id: "second" },
|
|
271
|
+
{ depth: 3, text: "Sub", id: "sub" },
|
|
272
|
+
{ depth: 4, text: "Deep", id: "deep" },
|
|
273
|
+
];
|
|
274
|
+
// depth: 3 means h2 + h3, but not h4
|
|
275
|
+
renderShell({
|
|
276
|
+
headings: headingsMultiDepth,
|
|
277
|
+
config: { ...baseConfig, toc: { depth: 3 } },
|
|
278
|
+
});
|
|
279
|
+
expect(screen.getByTestId("toc-sidebar")).toBeInTheDocument();
|
|
280
|
+
expect(screen.getByText("First")).toBeInTheDocument();
|
|
281
|
+
expect(screen.getByText("Second")).toBeInTheDocument();
|
|
282
|
+
expect(screen.getByText("Sub")).toBeInTheDocument();
|
|
283
|
+
expect(screen.queryByTestId("toc-link-deep")).not.toBeInTheDocument();
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("hides TOC on narrow viewports", () => {
|
|
287
|
+
Object.defineProperty(window, "innerWidth", { writable: true, configurable: true, value: 800 });
|
|
288
|
+
fireEvent(window, new Event("resize"));
|
|
289
|
+
|
|
290
|
+
renderShell({ headings: sampleHeadings });
|
|
291
|
+
expect(screen.queryByTestId("toc-sidebar")).not.toBeInTheDocument();
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
it("shows TOC on wide viewports", () => {
|
|
295
|
+
Object.defineProperty(window, "innerWidth", { writable: true, configurable: true, value: 1400 });
|
|
296
|
+
fireEvent(window, new Event("resize"));
|
|
297
|
+
|
|
298
|
+
renderShell({ headings: sampleHeadings });
|
|
299
|
+
expect(screen.getByTestId("toc-sidebar")).toBeInTheDocument();
|
|
201
300
|
});
|
|
202
301
|
});
|
|
203
302
|
|
|
@@ -541,6 +640,141 @@ describe("Shell language switcher", () => {
|
|
|
541
640
|
});
|
|
542
641
|
});
|
|
543
642
|
|
|
643
|
+
// ── Edit this page on GitHub (TOM-48) ────────────────────
|
|
644
|
+
|
|
645
|
+
describe("Shell edit link", () => {
|
|
646
|
+
it("renders edit link when editUrl is provided", () => {
|
|
647
|
+
renderShell({ editUrl: "https://github.com/org/repo/edit/main/docs/intro.md" });
|
|
648
|
+
const link = screen.getByTestId("edit-page-link");
|
|
649
|
+
expect(link).toBeInTheDocument();
|
|
650
|
+
expect(link.querySelector("a")?.getAttribute("href")).toBe(
|
|
651
|
+
"https://github.com/org/repo/edit/main/docs/intro.md"
|
|
652
|
+
);
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
it("edit link opens in new tab", () => {
|
|
656
|
+
renderShell({ editUrl: "https://github.com/org/repo/edit/main/docs/intro.md" });
|
|
657
|
+
const anchor = screen.getByTestId("edit-page-link").querySelector("a");
|
|
658
|
+
expect(anchor?.getAttribute("target")).toBe("_blank");
|
|
659
|
+
expect(anchor?.getAttribute("rel")).toBe("noopener noreferrer");
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
it("shows 'Edit this page on GitHub' text", () => {
|
|
663
|
+
renderShell({ editUrl: "https://github.com/org/repo/edit/main/docs/intro.md" });
|
|
664
|
+
expect(screen.getByText("Edit this page on GitHub")).toBeInTheDocument();
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
it("does not render edit link when editUrl is not provided", () => {
|
|
668
|
+
renderShell();
|
|
669
|
+
expect(screen.queryByTestId("edit-page-link")).not.toBeInTheDocument();
|
|
670
|
+
});
|
|
671
|
+
|
|
672
|
+
it("does not render edit link when editUrl is undefined", () => {
|
|
673
|
+
renderShell({ editUrl: undefined });
|
|
674
|
+
expect(screen.queryByTestId("edit-page-link")).not.toBeInTheDocument();
|
|
675
|
+
});
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
// ── Last Updated (TOM-54) ────────────────────────────────
|
|
679
|
+
|
|
680
|
+
describe("Shell last updated", () => {
|
|
681
|
+
it("renders last updated when lastUpdated is provided", () => {
|
|
682
|
+
renderShell({ lastUpdated: "2025-01-15T10:00:00Z" });
|
|
683
|
+
const el = screen.getByTestId("last-updated");
|
|
684
|
+
expect(el).toBeInTheDocument();
|
|
685
|
+
expect(el.textContent).toContain("Last updated");
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
it("does not render last updated when lastUpdated is not provided", () => {
|
|
689
|
+
renderShell();
|
|
690
|
+
expect(screen.queryByTestId("last-updated")).not.toBeInTheDocument();
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
it("does not render last updated when lastUpdated is undefined", () => {
|
|
694
|
+
renderShell({ lastUpdated: undefined });
|
|
695
|
+
expect(screen.queryByTestId("last-updated")).not.toBeInTheDocument();
|
|
696
|
+
});
|
|
697
|
+
|
|
698
|
+
it("renders both edit link and last updated when both provided", () => {
|
|
699
|
+
renderShell({
|
|
700
|
+
editUrl: "https://github.com/org/repo/edit/main/docs/intro.md",
|
|
701
|
+
lastUpdated: "2025-06-01T12:00:00Z",
|
|
702
|
+
});
|
|
703
|
+
expect(screen.getByTestId("edit-page-link")).toBeInTheDocument();
|
|
704
|
+
expect(screen.getByTestId("last-updated")).toBeInTheDocument();
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
it("shows relative date text for recent date", () => {
|
|
708
|
+
// Use a date from "1 day ago" to test relative formatting
|
|
709
|
+
const yesterday = new Date(Date.now() - 86400000).toISOString();
|
|
710
|
+
renderShell({ lastUpdated: yesterday });
|
|
711
|
+
const el = screen.getByTestId("last-updated");
|
|
712
|
+
expect(el.textContent).toContain("Last updated 1 day ago");
|
|
713
|
+
});
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
// ── Changelog (TOM-49) ──────────────────────────────────
|
|
717
|
+
|
|
718
|
+
describe("Shell changelog rendering", () => {
|
|
719
|
+
const sampleEntries = [
|
|
720
|
+
{
|
|
721
|
+
version: "2.0.0",
|
|
722
|
+
date: "2025-06-01",
|
|
723
|
+
sections: [
|
|
724
|
+
{ type: "Added", items: ["New feature", "Another feature"] },
|
|
725
|
+
{ type: "Fixed", items: ["Bug fix"] },
|
|
726
|
+
],
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
version: "1.0.0",
|
|
730
|
+
date: "2025-01-15",
|
|
731
|
+
sections: [
|
|
732
|
+
{ type: "Added", items: ["Initial release"] },
|
|
733
|
+
],
|
|
734
|
+
},
|
|
735
|
+
];
|
|
736
|
+
|
|
737
|
+
it("renders changelog timeline when entries provided", () => {
|
|
738
|
+
renderShell({ changelogEntries: sampleEntries });
|
|
739
|
+
expect(screen.getByTestId("changelog-timeline")).toBeInTheDocument();
|
|
740
|
+
});
|
|
741
|
+
|
|
742
|
+
it("renders each changelog entry", () => {
|
|
743
|
+
renderShell({ changelogEntries: sampleEntries });
|
|
744
|
+
expect(screen.getByTestId("changelog-entry-2.0.0")).toBeInTheDocument();
|
|
745
|
+
expect(screen.getByTestId("changelog-entry-1.0.0")).toBeInTheDocument();
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
it("renders version text and dates", () => {
|
|
749
|
+
renderShell({ changelogEntries: sampleEntries });
|
|
750
|
+
expect(screen.getByText("2.0.0")).toBeInTheDocument();
|
|
751
|
+
expect(screen.getByText("2025-06-01")).toBeInTheDocument();
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
it("renders section labels", () => {
|
|
755
|
+
renderShell({ changelogEntries: sampleEntries });
|
|
756
|
+
expect(screen.getAllByText("Added").length).toBeGreaterThan(0);
|
|
757
|
+
expect(screen.getByText("Fixed")).toBeInTheDocument();
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
it("renders change items", () => {
|
|
761
|
+
renderShell({ changelogEntries: sampleEntries });
|
|
762
|
+
expect(screen.getByText("New feature")).toBeInTheDocument();
|
|
763
|
+
expect(screen.getByText("Bug fix")).toBeInTheDocument();
|
|
764
|
+
expect(screen.getByText("Initial release")).toBeInTheDocument();
|
|
765
|
+
});
|
|
766
|
+
|
|
767
|
+
it("does not render changelog when no entries provided", () => {
|
|
768
|
+
renderShell();
|
|
769
|
+
expect(screen.queryByTestId("changelog-timeline")).not.toBeInTheDocument();
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
it("does not render changelog when entries array is empty", () => {
|
|
773
|
+
renderShell({ changelogEntries: [] });
|
|
774
|
+
expect(screen.queryByTestId("changelog-timeline")).not.toBeInTheDocument();
|
|
775
|
+
});
|
|
776
|
+
});
|
|
777
|
+
|
|
544
778
|
// ── AI Chat (TOM-32) ─────────────────────────────────────
|
|
545
779
|
|
|
546
780
|
describe("Shell AI chat integration", () => {
|
package/src/Shell.tsx
CHANGED
|
@@ -44,6 +44,28 @@ const MoonIcon = () => <Icon d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"
|
|
|
44
44
|
const SunIcon = () => <Icon d="M12 7a5 5 0 1 0 0 10 5 5 0 0 0 0-10Z" />;
|
|
45
45
|
const ArrowLeft = () => <Icon d="M19 12H5M12 19l-7-7 7-7" size={14} />;
|
|
46
46
|
const ArrowRight = () => <Icon d="M5 12h14M12 5l7 7-7 7" size={14} />;
|
|
47
|
+
const PencilIcon = () => <Icon d="M17 3a2.83 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" size={13} />;
|
|
48
|
+
|
|
49
|
+
// ── RELATIVE DATE FORMATTER (TOM-54) ─────────────────────
|
|
50
|
+
function formatRelativeDate(isoDate: string): string {
|
|
51
|
+
const date = new Date(isoDate);
|
|
52
|
+
const now = new Date();
|
|
53
|
+
const diffMs = now.getTime() - date.getTime();
|
|
54
|
+
if (isNaN(diffMs)) return "";
|
|
55
|
+
const seconds = Math.floor(diffMs / 1000);
|
|
56
|
+
const minutes = Math.floor(seconds / 60);
|
|
57
|
+
const hours = Math.floor(minutes / 60);
|
|
58
|
+
const days = Math.floor(hours / 24);
|
|
59
|
+
const months = Math.floor(days / 30);
|
|
60
|
+
const years = Math.floor(days / 365);
|
|
61
|
+
if (seconds < 60) return "just now";
|
|
62
|
+
if (minutes < 60) return `${minutes} minute${minutes === 1 ? "" : "s"} ago`;
|
|
63
|
+
if (hours < 24) return `${hours} hour${hours === 1 ? "" : "s"} ago`;
|
|
64
|
+
if (days < 30) return `${days} day${days === 1 ? "" : "s"} ago`;
|
|
65
|
+
if (months < 12) return `${months} month${months === 1 ? "" : "s"} ago`;
|
|
66
|
+
if (years >= 1) return `${years} year${years === 1 ? "" : "s"} ago`;
|
|
67
|
+
return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" });
|
|
68
|
+
}
|
|
47
69
|
|
|
48
70
|
// ── PAGEFIND CLIENT (TOM-15) ─────────────────────────────
|
|
49
71
|
let pagefindInstance: any = null;
|
|
@@ -195,12 +217,79 @@ export interface I18nInfo {
|
|
|
195
217
|
localeNames?: Record<string, string>;
|
|
196
218
|
}
|
|
197
219
|
|
|
220
|
+
// ── CHANGELOG VIEW (TOM-49) ─────────────────────────────
|
|
221
|
+
const CHANGELOG_SECTION_COLORS: Record<string, string> = {
|
|
222
|
+
Added: "#22c55e", Changed: "#3b82f6", Deprecated: "#f59e0b",
|
|
223
|
+
Removed: "#ef4444", Fixed: "#8b5cf6", Security: "#f97316",
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
interface ChangelogViewEntry {
|
|
227
|
+
version: string;
|
|
228
|
+
date?: string;
|
|
229
|
+
url?: string;
|
|
230
|
+
sections: Array<{ type: string; items: string[] }>;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function ChangelogView({ entries }: { entries: ChangelogViewEntry[] }) {
|
|
234
|
+
const [showAll, setShowAll] = useState(entries.length <= 5);
|
|
235
|
+
const visible = showAll ? entries : entries.slice(0, 5);
|
|
236
|
+
|
|
237
|
+
return (
|
|
238
|
+
<div data-testid="changelog-timeline" style={{ position: "relative" }}>
|
|
239
|
+
<div style={{ position: "absolute", left: 15, top: 8, bottom: 8, width: 2, background: "var(--bd)" }} />
|
|
240
|
+
{visible.map((entry, i) => (
|
|
241
|
+
<div key={entry.version} data-testid={`changelog-entry-${entry.version}`}
|
|
242
|
+
style={{ position: "relative", paddingLeft: 44, paddingBottom: i < visible.length - 1 ? 32 : 0 }}>
|
|
243
|
+
<div style={{
|
|
244
|
+
position: "absolute", left: 8, top: 6, width: 16, height: 16, borderRadius: "50%",
|
|
245
|
+
background: entry.version === "Unreleased" ? "var(--txM)" : "var(--ac)",
|
|
246
|
+
border: "3px solid var(--bg, #1a1a1a)",
|
|
247
|
+
}} />
|
|
248
|
+
<div style={{ display: "flex", alignItems: "baseline", gap: 12, marginBottom: 12 }}>
|
|
249
|
+
<span style={{ fontSize: 18, fontWeight: 700, color: "var(--tx)", fontFamily: "var(--font-heading, inherit)" }}>
|
|
250
|
+
{entry.url ? (
|
|
251
|
+
<a href={entry.url} target="_blank" rel="noopener noreferrer" style={{ color: "inherit", textDecoration: "none" }}>{entry.version}</a>
|
|
252
|
+
) : entry.version}
|
|
253
|
+
</span>
|
|
254
|
+
{entry.date && <span style={{ fontSize: 13, color: "var(--txM)", fontFamily: "var(--font-code, monospace)" }}>{entry.date}</span>}
|
|
255
|
+
</div>
|
|
256
|
+
{entry.sections.map((section) => {
|
|
257
|
+
const sColor = CHANGELOG_SECTION_COLORS[section.type] || "#6b7280";
|
|
258
|
+
return (
|
|
259
|
+
<div key={section.type} style={{ marginBottom: 16 }}>
|
|
260
|
+
<div style={{ display: "inline-flex", alignItems: "center", gap: 6, marginBottom: 8 }}>
|
|
261
|
+
<span style={{ display: "inline-block", width: 8, height: 8, borderRadius: "50%", background: sColor }} />
|
|
262
|
+
<span style={{ fontSize: 12, fontWeight: 600, textTransform: "uppercase", letterSpacing: ".06em", color: sColor, fontFamily: "var(--font-code, monospace)" }}>{section.type}</span>
|
|
263
|
+
</div>
|
|
264
|
+
<ul style={{ margin: 0, paddingLeft: 18, listStyleType: "disc", color: "var(--tx2)" }}>
|
|
265
|
+
{section.items.map((item, j) => (
|
|
266
|
+
<li key={j} style={{ fontSize: 14, lineHeight: 1.7, color: "var(--tx2)", marginBottom: 2 }}>{item}</li>
|
|
267
|
+
))}
|
|
268
|
+
</ul>
|
|
269
|
+
</div>
|
|
270
|
+
);
|
|
271
|
+
})}
|
|
272
|
+
</div>
|
|
273
|
+
))}
|
|
274
|
+
{!showAll && entries.length > 5 && (
|
|
275
|
+
<div style={{ textAlign: "center", marginTop: 24 }}>
|
|
276
|
+
<button data-testid="changelog-show-more" onClick={() => setShowAll(true)}
|
|
277
|
+
style={{ background: "none", border: "1px solid var(--bd)", borderRadius: 2, padding: "8px 20px", color: "var(--tx2)", fontSize: 13, fontFamily: "var(--font-body, inherit)", cursor: "pointer" }}>
|
|
278
|
+
Show all {entries.length} releases
|
|
279
|
+
</button>
|
|
280
|
+
</div>
|
|
281
|
+
)}
|
|
282
|
+
</div>
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
198
286
|
interface ShellProps {
|
|
199
287
|
config: {
|
|
200
288
|
name: string;
|
|
201
289
|
theme?: { preset?: string; mode?: string; accent?: string; fonts?: { heading?: string; body?: string; code?: string } };
|
|
202
290
|
search?: { provider?: string; appId?: string; apiKey?: string; indexName?: string };
|
|
203
291
|
ai?: { enabled?: boolean; provider?: "openai" | "anthropic" | "custom"; model?: string; apiKeyEnv?: string };
|
|
292
|
+
toc?: { enabled?: boolean; depth?: number };
|
|
204
293
|
topNav?: Array<{ label: string; href: string }>;
|
|
205
294
|
[key: string]: unknown;
|
|
206
295
|
};
|
|
@@ -215,6 +304,10 @@ interface ShellProps {
|
|
|
215
304
|
pageTitle: string;
|
|
216
305
|
pageDescription?: string;
|
|
217
306
|
headings: Array<{ depth: number; text: string; id: string }>;
|
|
307
|
+
tocEnabled?: boolean;
|
|
308
|
+
editUrl?: string;
|
|
309
|
+
lastUpdated?: string;
|
|
310
|
+
changelogEntries?: Array<{ version: string; date?: string; url?: string; sections: Array<{ type: string; items: string[] }> }>;
|
|
218
311
|
onNavigate: (id: string) => void;
|
|
219
312
|
allPages: Array<{ id: string; title: string; description?: string }>;
|
|
220
313
|
versioning?: VersioningInfo;
|
|
@@ -226,7 +319,7 @@ interface ShellProps {
|
|
|
226
319
|
|
|
227
320
|
export function Shell({
|
|
228
321
|
config, navigation, currentPageId, pageHtml, pageComponent, mdxComponents,
|
|
229
|
-
pageTitle, pageDescription, headings, onNavigate, allPages,
|
|
322
|
+
pageTitle, pageDescription, headings, tocEnabled = true, editUrl, lastUpdated, changelogEntries, onNavigate, allPages,
|
|
230
323
|
versioning, currentVersion, i18n, currentLocale, docContext,
|
|
231
324
|
}: ShellProps) {
|
|
232
325
|
const themeMode = config.theme?.mode || "auto";
|
|
@@ -286,6 +379,77 @@ export function Shell({
|
|
|
286
379
|
|
|
287
380
|
useEffect(() => { contentRef.current?.scrollTo(0, 0); }, [currentPageId]);
|
|
288
381
|
|
|
382
|
+
// ── TOC: Config-based depth filtering + frontmatter opt-out ──
|
|
383
|
+
const tocConfig = config.toc;
|
|
384
|
+
const tocDepth = tocConfig?.depth ?? 3;
|
|
385
|
+
const tocGlobalEnabled = tocConfig?.enabled !== false;
|
|
386
|
+
const showToc = tocGlobalEnabled && tocEnabled;
|
|
387
|
+
const filteredHeadings = headings.filter(h => h.depth <= tocDepth);
|
|
388
|
+
|
|
389
|
+
// ── TOC: Scroll-spy with IntersectionObserver ──
|
|
390
|
+
const [activeHeadingId, setActiveHeadingId] = useState<string>("");
|
|
391
|
+
|
|
392
|
+
useEffect(() => {
|
|
393
|
+
if (!showToc || filteredHeadings.length < 2) return;
|
|
394
|
+
|
|
395
|
+
const scrollRoot = contentRef.current;
|
|
396
|
+
if (!scrollRoot) return;
|
|
397
|
+
|
|
398
|
+
// Small delay to ensure DOM headings are rendered (especially after page load)
|
|
399
|
+
const timerId = setTimeout(() => {
|
|
400
|
+
const headingElements: Element[] = [];
|
|
401
|
+
for (const h of filteredHeadings) {
|
|
402
|
+
const el = scrollRoot.querySelector(`#${CSS.escape(h.id)}`);
|
|
403
|
+
if (el) headingElements.push(el);
|
|
404
|
+
}
|
|
405
|
+
if (headingElements.length === 0) return;
|
|
406
|
+
|
|
407
|
+
const observer = new IntersectionObserver(
|
|
408
|
+
(entries) => {
|
|
409
|
+
// Find the topmost visible heading
|
|
410
|
+
const visible = entries
|
|
411
|
+
.filter(e => e.isIntersecting)
|
|
412
|
+
.sort((a, b) => a.boundingClientRect.top - b.boundingClientRect.top);
|
|
413
|
+
if (visible.length > 0) {
|
|
414
|
+
setActiveHeadingId(visible[0].target.id);
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
root: scrollRoot,
|
|
419
|
+
// Trigger when heading enters the top 20% of the scroll container
|
|
420
|
+
rootMargin: "0px 0px -80% 0px",
|
|
421
|
+
threshold: 0,
|
|
422
|
+
}
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
for (const el of headingElements) observer.observe(el);
|
|
426
|
+
observerRef.current = observer;
|
|
427
|
+
}, 100);
|
|
428
|
+
|
|
429
|
+
return () => {
|
|
430
|
+
clearTimeout(timerId);
|
|
431
|
+
observerRef.current?.disconnect();
|
|
432
|
+
observerRef.current = null;
|
|
433
|
+
};
|
|
434
|
+
}, [currentPageId, showToc, filteredHeadings.map(h => h.id).join(",")]);
|
|
435
|
+
|
|
436
|
+
const observerRef = useRef<IntersectionObserver | null>(null);
|
|
437
|
+
|
|
438
|
+
// Reset active heading when page changes
|
|
439
|
+
useEffect(() => { setActiveHeadingId(""); }, [currentPageId]);
|
|
440
|
+
|
|
441
|
+
// Smooth scroll handler for TOC links
|
|
442
|
+
const scrollToHeading = useCallback((e: React.MouseEvent<HTMLAnchorElement>, id: string) => {
|
|
443
|
+
e.preventDefault();
|
|
444
|
+
const scrollRoot = contentRef.current;
|
|
445
|
+
if (!scrollRoot) return;
|
|
446
|
+
const target = scrollRoot.querySelector(`#${CSS.escape(id)}`);
|
|
447
|
+
if (target) {
|
|
448
|
+
target.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
449
|
+
setActiveHeadingId(id);
|
|
450
|
+
}
|
|
451
|
+
}, []);
|
|
452
|
+
|
|
289
453
|
useEffect(() => {
|
|
290
454
|
const h = (e: KeyboardEvent) => {
|
|
291
455
|
if ((e.metaKey || e.ctrlKey) && e.key === "k") { e.preventDefault(); setSearch(true); }
|
|
@@ -342,12 +506,12 @@ export function Shell({
|
|
|
342
506
|
display: "flex", flexDirection: "column",
|
|
343
507
|
transition: "width .2s, min-width .2s", overflow: "hidden",
|
|
344
508
|
}}>
|
|
345
|
-
<
|
|
509
|
+
<a href="/" style={{ padding: "18px 20px", display: "flex", alignItems: "baseline", gap: 6, borderBottom: "1px solid var(--bd)", textDecoration: "none", color: "inherit" }}>
|
|
346
510
|
<span style={{ fontFamily: "var(--font-heading)", fontSize: 22, fontWeight: 700, fontStyle: "italic" }}>
|
|
347
511
|
{config.name}
|
|
348
512
|
</span>
|
|
349
513
|
<span style={{ width: 5, height: 5, borderRadius: "50%", background: "var(--ac)", display: "inline-block" }} />
|
|
350
|
-
</
|
|
514
|
+
</a>
|
|
351
515
|
|
|
352
516
|
<div style={{ padding: "12px 14px" }}>
|
|
353
517
|
<button onClick={() => setSearch(true)} style={{
|
|
@@ -607,21 +771,52 @@ export function Shell({
|
|
|
607
771
|
</h1>
|
|
608
772
|
{pageDescription && <p style={{ fontSize: 16, color: "var(--tx2)", lineHeight: 1.6, marginBottom: 32 }}>{pageDescription}</p>}
|
|
609
773
|
<div style={{ borderTop: "1px solid var(--bd)", paddingTop: 28 }}>
|
|
610
|
-
{/* TOM-
|
|
611
|
-
{
|
|
774
|
+
{/* TOM-49: Changelog page type */}
|
|
775
|
+
{changelogEntries && changelogEntries.length > 0 ? (
|
|
776
|
+
<ChangelogView entries={changelogEntries} />
|
|
777
|
+
) : PageComponent ? (
|
|
612
778
|
<div className="tome-content">
|
|
613
779
|
<PageComponent components={mdxComponents || {}} />
|
|
614
780
|
</div>
|
|
615
781
|
) : (
|
|
616
782
|
<div
|
|
617
783
|
className="tome-content"
|
|
618
|
-
dangerouslySetInnerHTML={{ __html: pageHtml || "" }}
|
|
784
|
+
dangerouslySetInnerHTML={{ __html: (pageHtml || "").replace(/<h1[^>]*>[\s\S]*?<\/h1>\s*/, "") }}
|
|
619
785
|
/>
|
|
620
786
|
)}
|
|
621
787
|
</div>
|
|
622
788
|
|
|
789
|
+
{/* TOM-48: Edit this page link + TOM-54: Last updated */}
|
|
790
|
+
{(editUrl || lastUpdated) && (
|
|
791
|
+
<div style={{ marginTop: 40, display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16 }}>
|
|
792
|
+
{editUrl && (
|
|
793
|
+
<div data-testid="edit-page-link">
|
|
794
|
+
<a
|
|
795
|
+
href={editUrl}
|
|
796
|
+
target="_blank"
|
|
797
|
+
rel="noopener noreferrer"
|
|
798
|
+
style={{
|
|
799
|
+
display: "inline-flex", alignItems: "center", gap: 6,
|
|
800
|
+
color: "var(--txM)", textDecoration: "none", fontSize: 13,
|
|
801
|
+
fontFamily: "var(--font-body)", transition: "color .15s",
|
|
802
|
+
}}
|
|
803
|
+
onMouseOver={(e) => (e.currentTarget.style.color = "var(--ac)")}
|
|
804
|
+
onMouseOut={(e) => (e.currentTarget.style.color = "var(--txM)")}
|
|
805
|
+
>
|
|
806
|
+
<PencilIcon /> Edit this page on GitHub
|
|
807
|
+
</a>
|
|
808
|
+
</div>
|
|
809
|
+
)}
|
|
810
|
+
{lastUpdated && (
|
|
811
|
+
<div data-testid="last-updated" style={{ fontSize: 12, color: "var(--txM)", fontFamily: "var(--font-body)" }}>
|
|
812
|
+
Last updated {formatRelativeDate(lastUpdated)}
|
|
813
|
+
</div>
|
|
814
|
+
)}
|
|
815
|
+
</div>
|
|
816
|
+
)}
|
|
817
|
+
|
|
623
818
|
{/* Prev / Next */}
|
|
624
|
-
<div style={{ display: "flex", justifyContent: "space-between", marginTop: 48, paddingTop: 24, borderTop: "1px solid var(--bd)", gap: 16 }}>
|
|
819
|
+
<div style={{ display: "flex", justifyContent: "space-between", marginTop: (editUrl || lastUpdated) ? 16 : 48, paddingTop: 24, borderTop: "1px solid var(--bd)", gap: 16 }}>
|
|
625
820
|
{prev ? (
|
|
626
821
|
<button onClick={() => onNavigate(prev.id)} style={{
|
|
627
822
|
display: "flex", alignItems: "center", gap: 8, background: "none",
|
|
@@ -641,19 +836,35 @@ export function Shell({
|
|
|
641
836
|
</div>
|
|
642
837
|
</main>
|
|
643
838
|
|
|
644
|
-
{/* TOC */}
|
|
645
|
-
{
|
|
646
|
-
<aside style={{ width: 200, padding: "40px 16px 40px 0", position: "sticky", top: 0, alignSelf: "flex-start", flexShrink: 0 }}>
|
|
839
|
+
{/* TOC (TOM-52) */}
|
|
840
|
+
{showToc && filteredHeadings.length >= 2 && wide && (
|
|
841
|
+
<aside data-testid="toc-sidebar" style={{ width: 200, padding: "40px 16px 40px 0", position: "sticky", top: 0, alignSelf: "flex-start", flexShrink: 0 }}>
|
|
647
842
|
<div style={{ fontSize: 10, fontWeight: 600, textTransform: "uppercase", letterSpacing: ".1em", color: "var(--txM)", marginBottom: 12, fontFamily: "var(--font-code)" }}>On this page</div>
|
|
648
|
-
<
|
|
649
|
-
{
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
843
|
+
<nav aria-label="Table of contents" style={{ borderLeft: "1px solid var(--bd)", paddingLeft: 0 }}>
|
|
844
|
+
{filteredHeadings.map((h, i) => {
|
|
845
|
+
const isActive = activeHeadingId === h.id;
|
|
846
|
+
return (
|
|
847
|
+
<a
|
|
848
|
+
key={i}
|
|
849
|
+
href={`#${h.id}`}
|
|
850
|
+
onClick={(e) => scrollToHeading(e, h.id)}
|
|
851
|
+
data-testid={`toc-link-${h.id}`}
|
|
852
|
+
style={{
|
|
853
|
+
display: "block", fontSize: 12,
|
|
854
|
+
color: isActive ? "var(--ac)" : "var(--txM)",
|
|
855
|
+
fontWeight: isActive ? 500 : 400,
|
|
856
|
+
textDecoration: "none",
|
|
857
|
+
padding: "4px 12px",
|
|
858
|
+
paddingLeft: 12 + (h.depth - 2) * 12,
|
|
859
|
+
lineHeight: 1.4,
|
|
860
|
+
transition: "color .15s, font-weight .15s",
|
|
861
|
+
borderLeft: isActive ? "2px solid var(--ac)" : "2px solid transparent",
|
|
862
|
+
marginLeft: -1,
|
|
863
|
+
}}
|
|
864
|
+
>{h.text}</a>
|
|
865
|
+
);
|
|
866
|
+
})}
|
|
867
|
+
</nav>
|
|
657
868
|
</aside>
|
|
658
869
|
)}
|
|
659
870
|
</div>
|
package/src/entry.tsx
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
CardGroup,
|
|
21
21
|
Steps,
|
|
22
22
|
Accordion,
|
|
23
|
+
ChangelogTimeline,
|
|
23
24
|
} from "@tomehq/components";
|
|
24
25
|
|
|
25
26
|
const MDX_COMPONENTS: Record<string, React.ComponentType<any>> = {
|
|
@@ -29,12 +30,14 @@ const MDX_COMPONENTS: Record<string, React.ComponentType<any>> = {
|
|
|
29
30
|
CardGroup,
|
|
30
31
|
Steps,
|
|
31
32
|
Accordion,
|
|
33
|
+
ChangelogTimeline,
|
|
32
34
|
};
|
|
33
35
|
|
|
34
36
|
// ── CONTENT STYLES ───────────────────────────────────────
|
|
35
37
|
const contentStyles = `
|
|
36
38
|
@import url('https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:wght@300;400;500;600;700&family=Cormorant+Garamond:ital,wght@0,300;0,400;0,600;0,700;1,300;1,400;1,700&family=Fira+Code:wght@400;500;600&display=swap');
|
|
37
39
|
|
|
40
|
+
.tome-content h1 { display: none; }
|
|
38
41
|
.tome-content h2 { font-family: var(--font-body); font-size: 1.35em; font-weight: 600; margin-top: 2em; margin-bottom: 0.5em; display: flex; align-items: center; gap: 10px; letter-spacing: 0.01em; }
|
|
39
42
|
.tome-content h2::before { content: "#"; font-family: var(--font-heading); font-size: 1.2em; font-weight: 300; font-style: italic; color: var(--ac); opacity: 0.5; }
|
|
40
43
|
.tome-content h3 { font-family: var(--font-body); font-size: 1.15em; font-weight: 600; margin-top: 1.5em; margin-bottom: 0.5em; }
|
|
@@ -82,14 +85,15 @@ const contentStyles = `
|
|
|
82
85
|
interface HtmlPage {
|
|
83
86
|
isMdx: false;
|
|
84
87
|
html: string;
|
|
85
|
-
frontmatter: { title: string; description?: string };
|
|
88
|
+
frontmatter: { title: string; description?: string; toc?: boolean; type?: string };
|
|
86
89
|
headings: Array<{ depth: number; text: string; id: string }>;
|
|
90
|
+
changelogEntries?: Array<{ version: string; date?: string; url?: string; sections: Array<{ type: string; items: string[] }> }>;
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
interface MdxPage {
|
|
90
94
|
isMdx: true;
|
|
91
95
|
component: React.ComponentType<{ components?: Record<string, React.ComponentType> }>;
|
|
92
|
-
frontmatter: { title: string; description?: string };
|
|
96
|
+
frontmatter: { title: string; description?: string; toc?: boolean; type?: string };
|
|
93
97
|
headings: Array<{ depth: number; text: string; id: string }>;
|
|
94
98
|
}
|
|
95
99
|
|
|
@@ -113,6 +117,12 @@ async function loadPage(id: string): Promise<LoadedPage | null> {
|
|
|
113
117
|
|
|
114
118
|
// Regular .md page — mod.default is { html, frontmatter, headings }
|
|
115
119
|
if (!mod.default) return null;
|
|
120
|
+
|
|
121
|
+
// TOM-49: Changelog page type
|
|
122
|
+
if (mod.isChangelog && mod.changelogEntries) {
|
|
123
|
+
return { isMdx: false, ...mod.default, changelogEntries: mod.changelogEntries };
|
|
124
|
+
}
|
|
125
|
+
|
|
116
126
|
return { isMdx: false, ...mod.default };
|
|
117
127
|
} catch (err) {
|
|
118
128
|
console.error(`Failed to load page: ${id}`, err);
|
|
@@ -160,6 +170,15 @@ function App() {
|
|
|
160
170
|
description: r.frontmatter.description,
|
|
161
171
|
}));
|
|
162
172
|
|
|
173
|
+
// TOM-48: Compute edit URL for current page
|
|
174
|
+
const currentRoute = routes.find((r: any) => r.id === currentPageId);
|
|
175
|
+
let editUrl: string | undefined;
|
|
176
|
+
if (config.editLink && currentRoute?.filePath) {
|
|
177
|
+
const { repo, branch = "main", dir = "" } = config.editLink;
|
|
178
|
+
const dirPrefix = dir ? `${dir.replace(/\/$/, "")}/` : "";
|
|
179
|
+
editUrl = `https://github.com/${repo}/edit/${branch}/${dirPrefix}${currentRoute.filePath}`;
|
|
180
|
+
}
|
|
181
|
+
|
|
163
182
|
return (
|
|
164
183
|
<>
|
|
165
184
|
<style>{contentStyles}</style>
|
|
@@ -173,6 +192,10 @@ function App() {
|
|
|
173
192
|
pageTitle={pageData?.frontmatter.title || (loading ? "Loading..." : "Not Found")}
|
|
174
193
|
pageDescription={pageData?.frontmatter.description}
|
|
175
194
|
headings={pageData?.headings || []}
|
|
195
|
+
tocEnabled={pageData?.frontmatter.toc !== false}
|
|
196
|
+
editUrl={editUrl}
|
|
197
|
+
lastUpdated={currentRoute?.lastUpdated}
|
|
198
|
+
changelogEntries={!pageData?.isMdx ? pageData?.changelogEntries : undefined}
|
|
176
199
|
onNavigate={navigateTo}
|
|
177
200
|
allPages={allPages}
|
|
178
201
|
docContext={docContext}
|