imprensa 0.1.2 → 0.1.3
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 +11 -9
- package/default.css +3 -1
- package/dist/index.mjs +12 -0
- package/package.json +2 -1
- package/src/components/classes.ts +3 -0
- package/src/components/doc-pager.tsx +97 -0
- package/src/components/doc-toolbar.tsx +143 -0
- package/src/components/doc.tsx +2 -0
- package/src/components/global-search.tsx +141 -0
- package/src/components/icons.tsx +45 -0
- package/src/components/ilha-ui.ts +6 -0
- package/src/components/index.tsx +13 -0
- package/src/components/layout.tsx +110 -0
- package/src/components/mobile-nav.tsx +102 -0
- package/src/components/nav-footer-bar.tsx +34 -0
- package/src/components/preview.tsx +83 -0
- package/src/components/search-core.tsx +47 -0
- package/src/components/search-portal-sync.ts +234 -0
- package/src/components/search-store.ts +34 -0
- package/src/components/search.tsx +140 -0
- package/src/components/sidebar-layout.ts +53 -0
- package/src/components/sidebar.tsx +76 -0
- package/src/components/snippet.tsx +38 -0
- package/src/components/tree-indent.ts +14 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export const SIDEBAR_STORAGE_KEY = "imprensa:sidebar-layout";
|
|
2
|
+
export const DEFAULT_SIDEBAR_LAYOUT: [number, number] = [20, 80];
|
|
3
|
+
|
|
4
|
+
export function readSidebarLayout(): [number, number] {
|
|
5
|
+
if (typeof localStorage === "undefined") return DEFAULT_SIDEBAR_LAYOUT;
|
|
6
|
+
try {
|
|
7
|
+
const layout = JSON.parse(localStorage.getItem(SIDEBAR_STORAGE_KEY) ?? "null");
|
|
8
|
+
if (
|
|
9
|
+
Array.isArray(layout) &&
|
|
10
|
+
layout.length === 2 &&
|
|
11
|
+
layout.every((size) => typeof size === "number")
|
|
12
|
+
) {
|
|
13
|
+
return [layout[0], layout[1]];
|
|
14
|
+
}
|
|
15
|
+
} catch {
|
|
16
|
+
// ignore corrupt storage
|
|
17
|
+
}
|
|
18
|
+
return DEFAULT_SIDEBAR_LAYOUT;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function writeSidebarLayout(layout: number[]) {
|
|
22
|
+
try {
|
|
23
|
+
localStorage.setItem(SIDEBAR_STORAGE_KEY, JSON.stringify(layout));
|
|
24
|
+
} catch {
|
|
25
|
+
// ignore quota / private mode
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Injected into index.html so the saved split applies before first paint. */
|
|
30
|
+
export const SIDEBAR_LAYOUT_BOOT_SCRIPT = `<script>
|
|
31
|
+
(function () {
|
|
32
|
+
try {
|
|
33
|
+
var raw = localStorage.getItem(${JSON.stringify(SIDEBAR_STORAGE_KEY)});
|
|
34
|
+
if (!raw) return;
|
|
35
|
+
var layout = JSON.parse(raw);
|
|
36
|
+
if (!Array.isArray(layout) || layout.length !== 2) return;
|
|
37
|
+
var a = layout[0], b = layout[1];
|
|
38
|
+
if (typeof a !== "number" || typeof b !== "number") return;
|
|
39
|
+
var root = document.documentElement;
|
|
40
|
+
root.dataset.imprensaSidebarLayout = "1";
|
|
41
|
+
root.style.setProperty("--imprensa-sidebar-pct", String(a));
|
|
42
|
+
root.style.setProperty("--imprensa-content-pct", String(b));
|
|
43
|
+
} catch (e) {}
|
|
44
|
+
})();
|
|
45
|
+
</script>
|
|
46
|
+
<style id="imprensa-sidebar-layout-boot">
|
|
47
|
+
html[data-imprensa-sidebar-layout] [data-slot="resizable"] > [data-slot="resizable-panel"]:first-child {
|
|
48
|
+
flex-grow: var(--imprensa-sidebar-pct) !important;
|
|
49
|
+
}
|
|
50
|
+
html[data-imprensa-sidebar-layout] [data-slot="resizable"] > [data-slot="resizable-panel"]:last-child {
|
|
51
|
+
flex-grow: var(--imprensa-content-pct) !important;
|
|
52
|
+
}
|
|
53
|
+
</style>`;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** @jsxImportSource ilha */
|
|
2
|
+
import { useRoute } from "@ilha/router";
|
|
3
|
+
import { Collapsible, Icon, LinkButton } from "areia";
|
|
4
|
+
import ilha from "ilha";
|
|
5
|
+
import { ExternalLink } from "lucide";
|
|
6
|
+
import { contentTree, type ContentTreeNode } from "imprensa/mdx";
|
|
7
|
+
import { LogoButton, SearchSidebarTrigger } from "./search";
|
|
8
|
+
import { NavFooterBar } from "./nav-footer-bar";
|
|
9
|
+
import type { ImprensaUiTree } from "./ilha-ui";
|
|
10
|
+
import { cx } from "./classes";
|
|
11
|
+
import { treeIndentClass } from "./tree-indent";
|
|
12
|
+
|
|
13
|
+
const ACTIVE_LINK_CLASS =
|
|
14
|
+
"border-areia-primary text-areia-primary ring-1 ring-inset ring-areia-primary/30";
|
|
15
|
+
|
|
16
|
+
function renderTree(nodes: ContentTreeNode[], currentPath: string, depth = 0): ImprensaUiTree[] {
|
|
17
|
+
return nodes.map((node) => {
|
|
18
|
+
const href = node.type === "link" ? node.link : node.path;
|
|
19
|
+
const active =
|
|
20
|
+
node.path && node.type !== "link"
|
|
21
|
+
? (node.path.replace(/\/$/, "") || "/") === currentPath
|
|
22
|
+
: false;
|
|
23
|
+
const link = href ? (
|
|
24
|
+
<LinkButton
|
|
25
|
+
href={href}
|
|
26
|
+
external={node.external}
|
|
27
|
+
variant={active ? "outline" : "ghost"}
|
|
28
|
+
class={cx(
|
|
29
|
+
"w-full justify-start",
|
|
30
|
+
node.type === "link" && node.external && "justify-between",
|
|
31
|
+
active && ACTIVE_LINK_CLASS,
|
|
32
|
+
)}
|
|
33
|
+
>
|
|
34
|
+
<span class="min-w-0 truncate">{node.title}</span>
|
|
35
|
+
{node.type === "link" && node.external ? (
|
|
36
|
+
<Icon icon={ExternalLink} class="size-3.5 shrink-0" />
|
|
37
|
+
) : null}
|
|
38
|
+
</LinkButton>
|
|
39
|
+
) : (
|
|
40
|
+
<span class="text-sm font-medium text-areia-subtle">{node.title}</span>
|
|
41
|
+
);
|
|
42
|
+
return (
|
|
43
|
+
<div class={cx("mt-px flex flex-col gap-1", treeIndentClass(depth))}>
|
|
44
|
+
{node.children.length > 0 ? (
|
|
45
|
+
<Collapsible defaultOpen>
|
|
46
|
+
<Collapsible.Trigger class="w-full rounded-md px-2 py-1 text-left hover:bg-areia-control-hover">
|
|
47
|
+
{link}
|
|
48
|
+
</Collapsible.Trigger>
|
|
49
|
+
<Collapsible.Panel class="mt-1 flex flex-col p-px">
|
|
50
|
+
{renderTree(node.children, currentPath, node.path ? depth + 1 : depth)}
|
|
51
|
+
</Collapsible.Panel>
|
|
52
|
+
</Collapsible>
|
|
53
|
+
) : (
|
|
54
|
+
link
|
|
55
|
+
)}
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const Sidebar = ilha.render(() => {
|
|
62
|
+
const { path } = useRoute();
|
|
63
|
+
const currentPath = path().replace(/\/$/, "") || "/";
|
|
64
|
+
return (
|
|
65
|
+
<div class="flex h-full min-h-0 flex-col gap-2 p-2">
|
|
66
|
+
<div class="flex items-center">
|
|
67
|
+
<LogoButton />
|
|
68
|
+
</div>
|
|
69
|
+
<SearchSidebarTrigger />
|
|
70
|
+
<nav class="flex min-h-0 flex-1 flex-col gap-1 overflow-y-auto overscroll-y-contain px-0.5">
|
|
71
|
+
{renderTree(contentTree, currentPath)}
|
|
72
|
+
</nav>
|
|
73
|
+
<NavFooterBar class="mt-auto" />
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** @jsxImportSource ilha */
|
|
2
|
+
import ilha from "ilha";
|
|
3
|
+
import { shikiThemes } from "imprensa/config";
|
|
4
|
+
import type { ImprensaShikiHighlighter } from "../core/shiki-types";
|
|
5
|
+
|
|
6
|
+
const WRAPPER_CLASS =
|
|
7
|
+
"max-w-full overflow-x-auto rounded-xl border border-areia-border text-xs leading-relaxed [&_pre]:min-w-max [&_pre]:p-4 [&_pre]:text-xs [&_pre]:leading-relaxed [&_pre]:!m-0";
|
|
8
|
+
|
|
9
|
+
function escapeHtml(code: string) {
|
|
10
|
+
return code.replace(/&/g, "&").replace(/</g, "<");
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const Snippet = ilha
|
|
14
|
+
.input<{ code: string; lang: string }>()
|
|
15
|
+
.onMount(({ host, input }) => {
|
|
16
|
+
const mount = host.querySelector<HTMLElement>("[data-imprensa-snippet]")!;
|
|
17
|
+
if (mount.querySelector(".shiki")) return;
|
|
18
|
+
|
|
19
|
+
const { code, lang } = input;
|
|
20
|
+
mount.replaceChildren();
|
|
21
|
+
|
|
22
|
+
const pre = document.createElement("pre");
|
|
23
|
+
pre.className =
|
|
24
|
+
"rounded-lg bg-areia-surface-muted border border-areia-border p-4 overflow-x-auto text-xs leading-relaxed";
|
|
25
|
+
pre.innerHTML = `<code>${escapeHtml(code)}</code>`;
|
|
26
|
+
mount.appendChild(pre);
|
|
27
|
+
|
|
28
|
+
void import("imprensa/shiki").then(async ({ shiki }) => {
|
|
29
|
+
const h = shiki as ImprensaShikiHighlighter;
|
|
30
|
+
await h.loadLanguage(lang);
|
|
31
|
+
const div = document.createElement("div");
|
|
32
|
+
div.innerHTML = h.codeToHtml(code, { lang, themes: shikiThemes });
|
|
33
|
+
const highlighted = div.firstElementChild;
|
|
34
|
+
if (!highlighted) return;
|
|
35
|
+
mount.replaceChildren(highlighted);
|
|
36
|
+
});
|
|
37
|
+
})
|
|
38
|
+
.render(() => <div class={WRAPPER_CLASS} data-imprensa-snippet />);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** Sidebar / mobile nav tree depth → Tailwind class from `imprensa/default.css`. */
|
|
2
|
+
const TREE_INDENT = [
|
|
3
|
+
"imprensa-tree-indent-0",
|
|
4
|
+
"imprensa-tree-indent-1",
|
|
5
|
+
"imprensa-tree-indent-2",
|
|
6
|
+
"imprensa-tree-indent-3",
|
|
7
|
+
"imprensa-tree-indent-4",
|
|
8
|
+
"imprensa-tree-indent-5",
|
|
9
|
+
"imprensa-tree-indent-6",
|
|
10
|
+
] as const;
|
|
11
|
+
|
|
12
|
+
export function treeIndentClass(depth: number): string {
|
|
13
|
+
return TREE_INDENT[Math.min(Math.max(depth, 0), TREE_INDENT.length - 1)] ?? TREE_INDENT[6];
|
|
14
|
+
}
|