@lapis-notes/lapis-plugin-docs 0.0.0-stage → 0.1.0
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 +188 -2
- package/dist/docs/DocsBlockMarkdownEditor.svelte +37 -0
- package/dist/docs/DocsBlockMarkdownEditor.svelte.d.ts +13 -0
- package/dist/docs/DocsBlockRefPicker.svelte +75 -0
- package/dist/docs/DocsBlockRefPicker.svelte.d.ts +11 -0
- package/dist/docs/DocsBookEditor.svelte +141 -0
- package/dist/docs/DocsBookEditor.svelte.d.ts +10 -0
- package/dist/docs/DocsBookNavigation.svelte +838 -0
- package/dist/docs/DocsBookNavigation.svelte.d.ts +11 -0
- package/dist/docs/DocsBooksColumn.svelte +82 -0
- package/dist/docs/DocsBooksColumn.svelte.d.ts +10 -0
- package/dist/docs/DocsCardBackMatter.svelte +32 -0
- package/dist/docs/DocsCardBackMatter.svelte.d.ts +10 -0
- package/dist/docs/DocsCardBlockExtra.svelte +43 -0
- package/dist/docs/DocsCardBlockExtra.svelte.d.ts +9 -0
- package/dist/docs/DocsCardPreviewFlip.svelte +143 -0
- package/dist/docs/DocsCardPreviewFlip.svelte.d.ts +17 -0
- package/dist/docs/DocsCardPreviewStack.svelte +135 -0
- package/dist/docs/DocsCardPreviewStack.svelte.d.ts +13 -0
- package/dist/docs/DocsCardPrompt.svelte +33 -0
- package/dist/docs/DocsCardPrompt.svelte.d.ts +8 -0
- package/dist/docs/DocsCardReviewAnswer.svelte +45 -0
- package/dist/docs/DocsCardReviewAnswer.svelte.d.ts +8 -0
- package/dist/docs/DocsCardTagChips.svelte +19 -0
- package/dist/docs/DocsCardTagChips.svelte.d.ts +7 -0
- package/dist/docs/DocsCardsPanel.svelte +232 -0
- package/dist/docs/DocsCardsPanel.svelte.d.ts +17 -0
- package/dist/docs/DocsCardsReview.css +1050 -0
- package/dist/docs/DocsChapterFooter.svelte +248 -0
- package/dist/docs/DocsChapterFooter.svelte.d.ts +11 -0
- package/dist/docs/DocsChapterScrollRing.svelte +101 -0
- package/dist/docs/DocsChapterScrollRing.svelte.d.ts +6 -0
- package/dist/docs/DocsDisplayPanel.css +627 -0
- package/dist/docs/DocsDisplayPanel.svelte +78 -0
- package/dist/docs/DocsDisplayPanel.svelte.d.ts +16 -0
- package/dist/docs/DocsEditorPanel.css +325 -0
- package/dist/docs/DocsEditorPanel.svelte +570 -0
- package/dist/docs/DocsEditorPanel.svelte.d.ts +22 -0
- package/dist/docs/DocsFlipCard.svelte +68 -0
- package/dist/docs/DocsFlipCard.svelte.d.ts +8 -0
- package/dist/docs/DocsFolderDirectoryList.svelte +206 -0
- package/dist/docs/DocsFolderDirectoryList.svelte.d.ts +14 -0
- package/dist/docs/DocsFolderPanel.css +357 -0
- package/dist/docs/DocsFolderPanel.svelte +102 -0
- package/dist/docs/DocsFolderPanel.svelte.d.ts +17 -0
- package/dist/docs/DocsLinksPanel.css +61 -0
- package/dist/docs/DocsLinksPanel.svelte +55 -0
- package/dist/docs/DocsLinksPanel.svelte.d.ts +11 -0
- package/dist/docs/DocsMarkdownField.svelte +93 -0
- package/dist/docs/DocsMarkdownField.svelte.d.ts +11 -0
- package/dist/docs/DocsMarkdownPreview.svelte +278 -0
- package/dist/docs/DocsMarkdownPreview.svelte.d.ts +17 -0
- package/dist/docs/DocsReviewPanel.svelte +63 -0
- package/dist/docs/DocsReviewPanel.svelte.d.ts +11 -0
- package/dist/docs/DocsRichBlock.svelte +291 -0
- package/dist/docs/DocsRichBlock.svelte.d.ts +13 -0
- package/dist/docs/DocsRichDisplay.svelte +316 -0
- package/dist/docs/DocsRichDisplay.svelte.d.ts +14 -0
- package/dist/docs/DocsSlidesPreview.css +243 -0
- package/dist/docs/DocsSlidesPreview.svelte +206 -0
- package/dist/docs/DocsSlidesPreview.svelte.d.ts +10 -0
- package/dist/docs/DocsWorkspace.css +570 -0
- package/dist/docs/DocsWorkspace.svelte +831 -0
- package/dist/docs/DocsWorkspace.svelte.d.ts +48 -0
- package/dist/docs/DocsWorkspaceToolbar.svelte +211 -0
- package/dist/docs/DocsWorkspaceToolbar.svelte.d.ts +27 -0
- package/dist/docs/candidates/ChoiceMenu.svelte +114 -0
- package/dist/docs/candidates/ChoiceMenu.svelte.d.ts +10 -0
- package/dist/docs/candidates/FlipCardDeck.svelte +800 -0
- package/dist/docs/candidates/FlipCardDeck.svelte.d.ts +36 -0
- package/dist/docs/candidates/MarkdownFloatingToc.svelte +313 -0
- package/dist/docs/candidates/MarkdownFloatingToc.svelte.d.ts +14 -0
- package/dist/docs/candidates/SearchableChoicePicker.css +161 -0
- package/dist/docs/candidates/SearchableChoicePicker.svelte +208 -0
- package/dist/docs/candidates/SearchableChoicePicker.svelte.d.ts +19 -0
- package/dist/docs/candidates/StructuredTable.svelte +98 -0
- package/dist/docs/candidates/StructuredTable.svelte.d.ts +8 -0
- package/dist/docs/candidates/StructuredTableEditor.svelte +374 -0
- package/dist/docs/candidates/StructuredTableEditor.svelte.d.ts +12 -0
- package/dist/docs/candidates/TagEditor.svelte +31 -0
- package/dist/docs/candidates/TagEditor.svelte.d.ts +14 -0
- package/dist/docs/candidates/WorkspaceHistoryControls.svelte +112 -0
- package/dist/docs/candidates/WorkspaceHistoryControls.svelte.d.ts +8 -0
- package/dist/docs/candidates/markdown-floating-toc.css +151 -0
- package/dist/docs/candidates/markdown-floating-toc.d.ts +1 -0
- package/dist/docs/candidates/markdown-floating-toc.js +1 -0
- package/dist/docs/docs-anki-import.d.ts +34 -0
- package/dist/docs/docs-anki-import.js +516 -0
- package/dist/docs/docs-book-view-model.d.ts +9 -0
- package/dist/docs/docs-book-view-model.js +71 -0
- package/dist/docs/docs-card-answer-markdown.d.ts +1 -0
- package/dist/docs/docs-card-answer-markdown.js +63 -0
- package/dist/docs/docs-card-display.d.ts +10 -0
- package/dist/docs/docs-card-display.js +18 -0
- package/dist/docs/docs-display-focus.d.ts +7 -0
- package/dist/docs/docs-display-focus.js +42 -0
- package/dist/docs/docs-document-surface.d.ts +7 -0
- package/dist/docs/docs-document-surface.js +25 -0
- package/dist/docs/docs-folder-directory.d.ts +21 -0
- package/dist/docs/docs-folder-directory.js +56 -0
- package/dist/docs/docs-link-mode.d.ts +16 -0
- package/dist/docs/docs-link-mode.js +94 -0
- package/dist/docs/docs-workspace-helpers.d.ts +25 -0
- package/dist/docs/docs-workspace-helpers.js +195 -0
- package/dist/docs/docs.tokens.css +13 -0
- package/dist/docs/import-anki-package.d.ts +10 -0
- package/dist/docs/import-anki-package.js +30 -0
- package/dist/docs/lapis-markdown-extensions.d.ts +9 -0
- package/dist/docs/lapis-markdown-extensions.js +128 -0
- package/dist/docs/pin-reading-chrome.d.ts +4 -0
- package/dist/docs/pin-reading-chrome.js +32 -0
- package/dist/docs/sidebar/DocsFoldersSidebar.svelte +134 -0
- package/dist/docs/sidebar/DocsFoldersSidebar.svelte.d.ts +15 -0
- package/dist/docs/sidebar/DocsLinksSidebar.svelte +105 -0
- package/dist/docs/sidebar/DocsLinksSidebar.svelte.d.ts +11 -0
- package/dist/docs/sidebar/DocsSidebar.css +346 -0
- package/dist/docs/sidebar/DocsSidebar.svelte +430 -0
- package/dist/docs/sidebar/DocsSidebar.svelte.d.ts +18 -0
- package/dist/docs/sidebar/DocsSidebarHost.svelte +139 -0
- package/dist/docs/sidebar/DocsSidebarHost.svelte.d.ts +12 -0
- package/dist/docs/sidebar/DocsTagsSidebar.svelte +176 -0
- package/dist/docs/sidebar/DocsTagsSidebar.svelte.d.ts +12 -0
- package/dist/docs/sidebar/docs-book-note-tree.d.ts +23 -0
- package/dist/docs/sidebar/docs-book-note-tree.js +88 -0
- package/dist/docs/sidebar/docs-note-sort.d.ts +26 -0
- package/dist/docs/sidebar/docs-note-sort.js +54 -0
- package/dist/docs/sidebar/docs-sidebar-filters.d.ts +17 -0
- package/dist/docs/sidebar/docs-sidebar-filters.js +74 -0
- package/dist/docs/sidebar/docs-sidebar-ui-storage.d.ts +24 -0
- package/dist/docs/sidebar/docs-sidebar-ui-storage.js +87 -0
- package/dist/docs/sidebar/docs-source-folder-tree.d.ts +16 -0
- package/dist/docs/sidebar/docs-source-folder-tree.js +143 -0
- package/dist/docs/sidebar/docs-tag-tree.d.ts +17 -0
- package/dist/docs/sidebar/docs-tag-tree.js +84 -0
- package/dist/docs-cards-view.d.ts +18 -0
- package/dist/docs-cards-view.js +52 -0
- package/dist/docs-document-view.d.ts +24 -0
- package/dist/docs-document-view.js +127 -0
- package/dist/docs-document-workspace.svelte +215 -0
- package/dist/docs-document-workspace.svelte.d.ts +16 -0
- package/dist/docs-index.d.ts +5 -0
- package/dist/docs-index.js +49 -0
- package/dist/docs-path.d.ts +6 -0
- package/dist/docs-path.js +10 -0
- package/dist/docs-plugin.d.ts +21 -0
- package/dist/docs-plugin.js +140 -0
- package/dist/docs-repository.d.ts +15 -0
- package/dist/docs-repository.js +75 -0
- package/dist/docs-review-view.d.ts +18 -0
- package/dist/docs-review-view.js +52 -0
- package/dist/docs-settings-tab.d.ts +8 -0
- package/dist/docs-settings-tab.js +25 -0
- package/dist/docs-settings.d.ts +17 -0
- package/dist/docs-settings.js +38 -0
- package/dist/docs-view.d.ts +22 -0
- package/dist/docs-view.js +51 -0
- package/dist/docs-workspace.svelte +394 -0
- package/dist/docs-workspace.svelte.d.ts +15 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +12 -0
- package/dist/model/book.d.ts +6 -0
- package/dist/model/book.js +101 -0
- package/dist/model/coding-runtime.d.ts +4 -0
- package/dist/model/coding-runtime.js +9 -0
- package/dist/model/docs-card-context.d.ts +32 -0
- package/dist/model/docs-card-context.js +89 -0
- package/dist/model/docs-card-preview-stack-motion.d.ts +51 -0
- package/dist/model/docs-card-preview-stack-motion.js +94 -0
- package/dist/model/docs-media-context.d.ts +9 -0
- package/dist/model/docs-media-context.js +18 -0
- package/dist/model/docs-note-list.d.ts +8 -0
- package/dist/model/docs-note-list.js +25 -0
- package/dist/model/docs-relative-media.d.ts +30 -0
- package/dist/model/docs-relative-media.js +116 -0
- package/dist/model/docs-review-constants.d.ts +4 -0
- package/dist/model/docs-review-constants.js +13 -0
- package/dist/model/fsrs.d.ts +14 -0
- package/dist/model/fsrs.js +153 -0
- package/dist/model/graph.d.ts +12 -0
- package/dist/model/graph.js +169 -0
- package/dist/model/index.d.ts +14 -0
- package/dist/model/index.js +14 -0
- package/dist/model/markdown-normalize.d.ts +11 -0
- package/dist/model/markdown-normalize.js +755 -0
- package/dist/model/markdown.d.ts +167 -0
- package/dist/model/markdown.js +365 -0
- package/dist/model/sample.d.ts +7 -0
- package/dist/model/sample.js +524 -0
- package/dist/model/types.d.ts +328 -0
- package/dist/model/types.js +1 -0
- package/dist/model/workspace.d.ts +6 -0
- package/dist/model/workspace.js +81 -0
- package/dist/storybook.css +52 -0
- package/dist/styles.css +1 -0
- package/manifest.json +11 -0
- package/package.json +147 -4
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { createMiraEditorExtensions } from "@lapismd/mira-editor";
|
|
2
|
+
import { defineMiraExtension, } from "@lapismd/mira/extensions";
|
|
3
|
+
const DOCS_MARKDOWN_SURFACE = { id: "docs" };
|
|
4
|
+
/** Host stubs take no arguments. A provided renderer accepts the markdown string. */
|
|
5
|
+
export function providedMarkdownRenderer(app) {
|
|
6
|
+
const renderer = app?.props.markdownRenderer;
|
|
7
|
+
if (typeof renderer !== "function" || renderer.length === 0)
|
|
8
|
+
return null;
|
|
9
|
+
return renderer;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Reading extensions used by the Lapis Markdown view: Mira's Mermaid plugin
|
|
13
|
+
* plus every Markdown post-processor registered with the host.
|
|
14
|
+
*/
|
|
15
|
+
export function lapisMarkdownExtensions(app, sourcePath, markdown) {
|
|
16
|
+
const mermaidSetting = app?.configuration
|
|
17
|
+
.getConfiguration()
|
|
18
|
+
.get("markdown.mira.plugins.mermaid.enabled", true);
|
|
19
|
+
const mermaidEnabled = mermaidSetting ?? true;
|
|
20
|
+
const extensions = createMiraEditorExtensions({
|
|
21
|
+
features: { mermaid: mermaidEnabled },
|
|
22
|
+
});
|
|
23
|
+
if (!app)
|
|
24
|
+
return extensions;
|
|
25
|
+
const processors = [
|
|
26
|
+
...app.markdownExtensions
|
|
27
|
+
.getAll()
|
|
28
|
+
.flatMap((contribution) => contribution.postProcessor ? [contribution.postProcessor] : []),
|
|
29
|
+
...(app.mardownPostProcessor ?? []),
|
|
30
|
+
];
|
|
31
|
+
const codeBlockProcessors = app.mardownCodeBlockPostProcessor ?? {};
|
|
32
|
+
if (processors.length === 0 &&
|
|
33
|
+
Object.keys(codeBlockProcessors).length === 0) {
|
|
34
|
+
return extensions;
|
|
35
|
+
}
|
|
36
|
+
return [
|
|
37
|
+
...extensions,
|
|
38
|
+
defineMiraExtension({
|
|
39
|
+
name: "lapis-markdown-plugins",
|
|
40
|
+
postProcessors: [
|
|
41
|
+
adaptLapisMarkdownPlugins(app, {
|
|
42
|
+
sourcePath,
|
|
43
|
+
markdown,
|
|
44
|
+
processors,
|
|
45
|
+
codeBlockProcessors,
|
|
46
|
+
}),
|
|
47
|
+
],
|
|
48
|
+
}),
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
function adaptLapisMarkdownPlugins(app, options) {
|
|
52
|
+
return (el, node) => {
|
|
53
|
+
let disposed = false;
|
|
54
|
+
const children = [];
|
|
55
|
+
const context = {
|
|
56
|
+
docId: options.sourcePath,
|
|
57
|
+
sourcePath: options.sourcePath,
|
|
58
|
+
frontmatter: null,
|
|
59
|
+
mode: "reading",
|
|
60
|
+
surface: DOCS_MARKDOWN_SURFACE,
|
|
61
|
+
addChild(child) {
|
|
62
|
+
children.push(child);
|
|
63
|
+
child.load();
|
|
64
|
+
},
|
|
65
|
+
getSectionInfo(target) {
|
|
66
|
+
return target === el ? sectionInfo(options.markdown, node) : null;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
void app.metadataCache
|
|
70
|
+
.getFileCacheAsync(options.sourcePath)
|
|
71
|
+
.then(async (metadata) => {
|
|
72
|
+
if (disposed)
|
|
73
|
+
return;
|
|
74
|
+
context.frontmatter = metadata?.frontmatter ?? null;
|
|
75
|
+
for (const processor of options.processors) {
|
|
76
|
+
if (disposed)
|
|
77
|
+
return;
|
|
78
|
+
await processor(el, context);
|
|
79
|
+
}
|
|
80
|
+
await runCodeBlockProcessors(el, context, options.codeBlockProcessors);
|
|
81
|
+
});
|
|
82
|
+
return () => {
|
|
83
|
+
disposed = true;
|
|
84
|
+
for (const child of children.splice(0).reverse())
|
|
85
|
+
child.unload();
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
async function runCodeBlockProcessors(el, context, processors) {
|
|
90
|
+
const blocks = el.querySelectorAll("pre");
|
|
91
|
+
for (const block of blocks) {
|
|
92
|
+
const code = block.querySelector("code");
|
|
93
|
+
const language = [...(code?.classList ?? [])]
|
|
94
|
+
.find((name) => name.startsWith("language-"))
|
|
95
|
+
?.slice("language-".length);
|
|
96
|
+
if (!language)
|
|
97
|
+
continue;
|
|
98
|
+
for (const processor of processors[language] ?? []) {
|
|
99
|
+
await processor(block, context);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function sectionInfo(markdown, node) {
|
|
104
|
+
const typed = node;
|
|
105
|
+
const start = typed.position?.start?.offset;
|
|
106
|
+
const nodeEnd = typed.position?.end?.offset;
|
|
107
|
+
if (typeof start !== "number" || typeof nodeEnd !== "number")
|
|
108
|
+
return null;
|
|
109
|
+
let end = nodeEnd;
|
|
110
|
+
if (typed.type === "heading" && typeof typed.depth === "number") {
|
|
111
|
+
const heading = /^ {0,3}(#{1,6})\s+/gm;
|
|
112
|
+
for (const match of markdown.slice(nodeEnd).matchAll(heading)) {
|
|
113
|
+
if ((match[1]?.length ?? 7) <= typed.depth) {
|
|
114
|
+
end = nodeEnd + (match.index ?? 0);
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (end === nodeEnd)
|
|
119
|
+
end = markdown.length;
|
|
120
|
+
}
|
|
121
|
+
const text = markdown.slice(start, end).replace(/\n+$/, "");
|
|
122
|
+
const lineStart = markdown.slice(0, start).split("\n").length - 1;
|
|
123
|
+
return {
|
|
124
|
+
text,
|
|
125
|
+
lineStart,
|
|
126
|
+
lineEnd: lineStart + text.split("\n").length - 1,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Keep reading chrome inside the visible document body, not the workspace sidebars. */
|
|
2
|
+
export function pinReadingChrome(node) {
|
|
3
|
+
const place = () => {
|
|
4
|
+
const frame = node.closest(".docs-display-view");
|
|
5
|
+
if (!(frame instanceof HTMLElement))
|
|
6
|
+
return;
|
|
7
|
+
const rect = frame.getBoundingClientRect();
|
|
8
|
+
node.style.position = "fixed";
|
|
9
|
+
node.style.top = `${rect.top}px`;
|
|
10
|
+
node.style.left = `${rect.left}px`;
|
|
11
|
+
node.style.width = `${rect.width}px`;
|
|
12
|
+
node.style.height = `${rect.height}px`;
|
|
13
|
+
node.style.right = "auto";
|
|
14
|
+
node.style.bottom = "auto";
|
|
15
|
+
};
|
|
16
|
+
place();
|
|
17
|
+
const frame = node.closest(".docs-display-view");
|
|
18
|
+
const observer = frame && typeof ResizeObserver !== "undefined"
|
|
19
|
+
? new ResizeObserver(place)
|
|
20
|
+
: null;
|
|
21
|
+
if (frame)
|
|
22
|
+
observer?.observe(frame);
|
|
23
|
+
window.addEventListener("scroll", place, true);
|
|
24
|
+
window.addEventListener("resize", place);
|
|
25
|
+
return {
|
|
26
|
+
destroy() {
|
|
27
|
+
observer?.disconnect();
|
|
28
|
+
window.removeEventListener("scroll", place, true);
|
|
29
|
+
window.removeEventListener("resize", place);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
|
|
3
|
+
import {
|
|
4
|
+
docsNoteCardCount,
|
|
5
|
+
type DocsNotePanel,
|
|
6
|
+
type DocsNoteSummary,
|
|
7
|
+
} from "../../model";
|
|
8
|
+
import { isDocsSidebarPanelActive } from "../docs-folder-directory";
|
|
9
|
+
import {
|
|
10
|
+
DOCS_SOURCE_ROOT_ID,
|
|
11
|
+
createDocsSourceFolderTree,
|
|
12
|
+
filterDocsSourceFolderTree,
|
|
13
|
+
type DocsSourceFolderTreeNode,
|
|
14
|
+
} from "./docs-source-folder-tree";
|
|
15
|
+
import { DEFAULT_DOCS_NOTE_SORT, type DocsNoteSortMode } from "./docs-note-sort";
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
notes = [],
|
|
19
|
+
sortMode = DEFAULT_DOCS_NOTE_SORT,
|
|
20
|
+
query = "",
|
|
21
|
+
activeNoteId = null,
|
|
22
|
+
activePanel = "display",
|
|
23
|
+
openNodes = $bindable<Record<string, boolean>>({}),
|
|
24
|
+
onSelectNote = () => {},
|
|
25
|
+
onSelectPanel = () => {},
|
|
26
|
+
}: {
|
|
27
|
+
notes?: DocsNoteSummary[];
|
|
28
|
+
sortMode?: DocsNoteSortMode;
|
|
29
|
+
query?: string;
|
|
30
|
+
activeNoteId?: string | null;
|
|
31
|
+
activePanel?: DocsNotePanel;
|
|
32
|
+
openNodes?: Record<string, boolean>;
|
|
33
|
+
onSelectNote?: (noteId: string) => void;
|
|
34
|
+
onSelectPanel?: (noteId: string, panel: DocsNotePanel) => void;
|
|
35
|
+
} = $props();
|
|
36
|
+
|
|
37
|
+
const tree = $derived.by(() => {
|
|
38
|
+
const built = createDocsSourceFolderTree(notes, sortMode);
|
|
39
|
+
return filterDocsSourceFolderTree(built, query, sortMode) ?? built;
|
|
40
|
+
});
|
|
41
|
+
let expandedNotes = $state<Record<string, boolean>>({});
|
|
42
|
+
|
|
43
|
+
function folderOpen(id: string) {
|
|
44
|
+
if (id === DOCS_SOURCE_ROOT_ID) return openNodes[id] !== false;
|
|
45
|
+
return openNodes[id] === true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function toggleFolder(id: string) {
|
|
49
|
+
openNodes = { ...openNodes, [id]: !folderOpen(id) };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function noteOpen(noteId: string) {
|
|
53
|
+
return activeNoteId === noteId ? expandedNotes[noteId] !== false : expandedNotes[noteId] === true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function panels(note: DocsNoteSummary) {
|
|
57
|
+
return [
|
|
58
|
+
{ id: "note" as const, label: "Note" },
|
|
59
|
+
{ id: "cards" as const, label: "Cards", count: docsNoteCardCount(note) },
|
|
60
|
+
{ id: "review" as const, label: "Review", count: note.dueCardCount ?? 0 },
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
{#snippet folder(node: DocsSourceFolderTreeNode, depth: number)}
|
|
66
|
+
<div class="docs-sidebar__group">
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
class="docs-sidebar__row"
|
|
70
|
+
style={`padding-inline-start: ${0.2 + depth * 0.85}rem`}
|
|
71
|
+
onclick={() => toggleFolder(node.id)}
|
|
72
|
+
>
|
|
73
|
+
<span class="docs-sidebar__chevron" data-open={folderOpen(node.id) ? "true" : "false"}>
|
|
74
|
+
<ChevronRightIcon aria-hidden="true" />
|
|
75
|
+
</span>
|
|
76
|
+
<span class="docs-sidebar__label">{node.label}</span>
|
|
77
|
+
<span class="docs-sidebar__count">{node.count}</span>
|
|
78
|
+
</button>
|
|
79
|
+
{#if folderOpen(node.id)}
|
|
80
|
+
{#each node.children as child (child.id)}
|
|
81
|
+
{@render folder(child, depth + 1)}
|
|
82
|
+
{/each}
|
|
83
|
+
{#each node.notes as note (note.id)}
|
|
84
|
+
{@render noteRow(note, depth + 1)}
|
|
85
|
+
{/each}
|
|
86
|
+
{/if}
|
|
87
|
+
</div>
|
|
88
|
+
{/snippet}
|
|
89
|
+
|
|
90
|
+
{#snippet noteRow(note: DocsNoteSummary, depth: number)}
|
|
91
|
+
<div class="docs-sidebar__group">
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
class="docs-sidebar__row"
|
|
95
|
+
style={`padding-inline-start: ${0.2 + depth * 0.85}rem`}
|
|
96
|
+
aria-current={activeNoteId === note.id ? "true" : undefined}
|
|
97
|
+
onclick={() => {
|
|
98
|
+
expandedNotes = { ...expandedNotes, [note.id]: true };
|
|
99
|
+
onSelectNote(note.id);
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
<span
|
|
103
|
+
class="docs-sidebar__chevron"
|
|
104
|
+
data-open={noteOpen(note.id) ? "true" : "false"}
|
|
105
|
+
role="presentation"
|
|
106
|
+
onclick={(event) => {
|
|
107
|
+
event.stopPropagation();
|
|
108
|
+
expandedNotes = { ...expandedNotes, [note.id]: !noteOpen(note.id) };
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
<ChevronRightIcon aria-hidden="true" />
|
|
112
|
+
</span>
|
|
113
|
+
<span class="docs-sidebar__label">{note.title}</span>
|
|
114
|
+
</button>
|
|
115
|
+
{#if noteOpen(note.id)}
|
|
116
|
+
{#each panels(note) as panel (panel.id)}
|
|
117
|
+
<button
|
|
118
|
+
type="button"
|
|
119
|
+
class="docs-sidebar__panel"
|
|
120
|
+
style={`padding-inline-start: ${1.3 + depth * 0.85}rem`}
|
|
121
|
+
aria-current={activeNoteId === note.id && isDocsSidebarPanelActive(activePanel, panel.id) ? "true" : undefined}
|
|
122
|
+
onclick={() => onSelectPanel(note.id, panel.id)}
|
|
123
|
+
>
|
|
124
|
+
<span class="docs-sidebar__label">{panel.label}</span>
|
|
125
|
+
{#if panel.count}
|
|
126
|
+
<span class="docs-sidebar__count">{panel.count}</span>
|
|
127
|
+
{/if}
|
|
128
|
+
</button>
|
|
129
|
+
{/each}
|
|
130
|
+
{/if}
|
|
131
|
+
</div>
|
|
132
|
+
{/snippet}
|
|
133
|
+
|
|
134
|
+
{@render folder(tree, 0)}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type DocsNotePanel, type DocsNoteSummary } from "../../model";
|
|
2
|
+
import { type DocsNoteSortMode } from "./docs-note-sort";
|
|
3
|
+
type $$ComponentProps = {
|
|
4
|
+
notes?: DocsNoteSummary[];
|
|
5
|
+
sortMode?: DocsNoteSortMode;
|
|
6
|
+
query?: string;
|
|
7
|
+
activeNoteId?: string | null;
|
|
8
|
+
activePanel?: DocsNotePanel;
|
|
9
|
+
openNodes?: Record<string, boolean>;
|
|
10
|
+
onSelectNote?: (noteId: string) => void;
|
|
11
|
+
onSelectPanel?: (noteId: string, panel: DocsNotePanel) => void;
|
|
12
|
+
};
|
|
13
|
+
declare const DocsFoldersSidebar: import("svelte").Component<$$ComponentProps, {}, "openNodes">;
|
|
14
|
+
type DocsFoldersSidebar = ReturnType<typeof DocsFoldersSidebar>;
|
|
15
|
+
export default DocsFoldersSidebar;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import ChevronRightIcon from "@lucide/svelte/icons/chevron-right";
|
|
3
|
+
import type { DocsBacklink, DocsNoteSummary, DocsWikiLink } from "../../model";
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
notes = [],
|
|
7
|
+
backlinks = [],
|
|
8
|
+
outgoingLinks = [],
|
|
9
|
+
ambiguousLinks = [],
|
|
10
|
+
onSelectNote = () => {},
|
|
11
|
+
}: {
|
|
12
|
+
notes?: DocsNoteSummary[];
|
|
13
|
+
backlinks?: DocsBacklink[];
|
|
14
|
+
outgoingLinks?: DocsWikiLink[];
|
|
15
|
+
ambiguousLinks?: DocsWikiLink[];
|
|
16
|
+
onSelectNote?: (noteId: string) => void;
|
|
17
|
+
} = $props();
|
|
18
|
+
|
|
19
|
+
let openGroups = $state<Record<string, boolean>>({
|
|
20
|
+
backlinks: true,
|
|
21
|
+
outgoing: true,
|
|
22
|
+
ambiguous: true,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const titleById = $derived(new Map(notes.map((note) => [note.id, note.title])));
|
|
26
|
+
const visibleOutgoing = $derived(
|
|
27
|
+
outgoingLinks.filter((link) => !(link.ambiguousNoteIds?.length ?? 0)),
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
function toggle(group: string) {
|
|
31
|
+
openGroups = { ...openGroups, [group]: !openGroups[group] };
|
|
32
|
+
}
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<div class="docs-sidebar__group">
|
|
36
|
+
<button type="button" class="docs-sidebar__row" onclick={() => toggle("backlinks")}>
|
|
37
|
+
<span class="docs-sidebar__chevron" data-open={openGroups.backlinks ? "true" : "false"}>
|
|
38
|
+
<ChevronRightIcon aria-hidden="true" />
|
|
39
|
+
</span>
|
|
40
|
+
<span class="docs-sidebar__label">Backlinks</span>
|
|
41
|
+
<span class="docs-sidebar__count">{backlinks.length}</span>
|
|
42
|
+
</button>
|
|
43
|
+
{#if openGroups.backlinks}
|
|
44
|
+
<div class="docs-sidebar__children">{@render backlinkList()}</div>
|
|
45
|
+
{/if}
|
|
46
|
+
<button type="button" class="docs-sidebar__row" onclick={() => toggle("outgoing")}>
|
|
47
|
+
<span class="docs-sidebar__chevron" data-open={openGroups.outgoing ? "true" : "false"}>
|
|
48
|
+
<ChevronRightIcon aria-hidden="true" />
|
|
49
|
+
</span>
|
|
50
|
+
<span class="docs-sidebar__label">Outgoing</span>
|
|
51
|
+
<span class="docs-sidebar__count">{visibleOutgoing.length}</span>
|
|
52
|
+
</button>
|
|
53
|
+
{#if openGroups.outgoing}
|
|
54
|
+
<div class="docs-sidebar__children">{@render outgoingList()}</div>
|
|
55
|
+
{/if}
|
|
56
|
+
<button type="button" class="docs-sidebar__row" onclick={() => toggle("ambiguous")}>
|
|
57
|
+
<span class="docs-sidebar__chevron" data-open={openGroups.ambiguous ? "true" : "false"}>
|
|
58
|
+
<ChevronRightIcon aria-hidden="true" />
|
|
59
|
+
</span>
|
|
60
|
+
<span class="docs-sidebar__label">Ambiguous</span>
|
|
61
|
+
<span class="docs-sidebar__count">{ambiguousLinks.length}</span>
|
|
62
|
+
</button>
|
|
63
|
+
{#if openGroups.ambiguous}
|
|
64
|
+
<div class="docs-sidebar__children">{@render ambiguousList()}</div>
|
|
65
|
+
{/if}
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
{#snippet backlinkList()}
|
|
69
|
+
{#each backlinks as link (`${link.sourceNoteId}:${link.raw}`)}
|
|
70
|
+
<button
|
|
71
|
+
type="button"
|
|
72
|
+
class="docs-sidebar__row"
|
|
73
|
+
style="padding-inline-start: 1.6rem"
|
|
74
|
+
onclick={() => onSelectNote(link.sourceNoteId)}
|
|
75
|
+
>
|
|
76
|
+
<span class="docs-sidebar__label">{link.sourceTitle || titleById.get(link.sourceNoteId) || link.sourceNoteId}</span>
|
|
77
|
+
</button>
|
|
78
|
+
{:else}
|
|
79
|
+
<p class="docs-sidebar__empty">No backlinks.</p>
|
|
80
|
+
{/each}
|
|
81
|
+
{/snippet}
|
|
82
|
+
|
|
83
|
+
{#snippet outgoingList()}
|
|
84
|
+
{#each visibleOutgoing as link (`${link.target}:${link.index}`)}
|
|
85
|
+
<button
|
|
86
|
+
type="button"
|
|
87
|
+
class="docs-sidebar__row"
|
|
88
|
+
style="padding-inline-start: 1.6rem"
|
|
89
|
+
disabled={!link.resolvedNoteId}
|
|
90
|
+
onclick={() => link.resolvedNoteId && onSelectNote(link.resolvedNoteId)}
|
|
91
|
+
>
|
|
92
|
+
<span class="docs-sidebar__label">{link.label || titleById.get(link.resolvedNoteId ?? "") || link.target}</span>
|
|
93
|
+
</button>
|
|
94
|
+
{:else}
|
|
95
|
+
<p class="docs-sidebar__empty">No outgoing links.</p>
|
|
96
|
+
{/each}
|
|
97
|
+
{/snippet}
|
|
98
|
+
|
|
99
|
+
{#snippet ambiguousList()}
|
|
100
|
+
{#each ambiguousLinks as link (`ambiguous:${link.target}:${link.index}`)}
|
|
101
|
+
<p class="docs-sidebar__empty">{link.label || link.raw}</p>
|
|
102
|
+
{:else}
|
|
103
|
+
<p class="docs-sidebar__empty">No ambiguous links.</p>
|
|
104
|
+
{/each}
|
|
105
|
+
{/snippet}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DocsBacklink, DocsNoteSummary, DocsWikiLink } from "../../model";
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
notes?: DocsNoteSummary[];
|
|
4
|
+
backlinks?: DocsBacklink[];
|
|
5
|
+
outgoingLinks?: DocsWikiLink[];
|
|
6
|
+
ambiguousLinks?: DocsWikiLink[];
|
|
7
|
+
onSelectNote?: (noteId: string) => void;
|
|
8
|
+
};
|
|
9
|
+
declare const DocsLinksSidebar: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
10
|
+
type DocsLinksSidebar = ReturnType<typeof DocsLinksSidebar>;
|
|
11
|
+
export default DocsLinksSidebar;
|