@lapis-notes/lapis-plugin-docs 0.0.0-stage → 0.2.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/CHANGELOG.md +11 -0
- 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 +139 -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 +817 -0
- package/dist/docs/candidates/FlipCardDeck.svelte.d.ts +37 -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,7 @@
|
|
|
1
|
+
import type { DocsNote } from '../model';
|
|
2
|
+
export declare function displaySlideCount(note: DocsNote | null | undefined): number;
|
|
3
|
+
export declare function clampDisplaySlideIndex(index: number, note: DocsNote | null | undefined): number;
|
|
4
|
+
/** Block label "05" maps to slide index 5 (intro is 0). */
|
|
5
|
+
export declare function slideIndexForBlockIndex(blockIndex: number): number;
|
|
6
|
+
export declare function displayFocusSelector(note: DocsNote, slideIndex: number): string;
|
|
7
|
+
export declare function readVisibleDisplaySlideIndex(root: HTMLElement, scrollRoot: Element | null): number;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export function displaySlideCount(note) {
|
|
2
|
+
if (!note)
|
|
3
|
+
return 0;
|
|
4
|
+
return 1 + note.blocks.length + (note.body.trim() ? 1 : 0);
|
|
5
|
+
}
|
|
6
|
+
export function clampDisplaySlideIndex(index, note) {
|
|
7
|
+
const count = displaySlideCount(note);
|
|
8
|
+
if (count <= 0)
|
|
9
|
+
return 0;
|
|
10
|
+
return Math.max(0, Math.min(index, count - 1));
|
|
11
|
+
}
|
|
12
|
+
/** Block label "05" maps to slide index 5 (intro is 0). */
|
|
13
|
+
export function slideIndexForBlockIndex(blockIndex) {
|
|
14
|
+
return blockIndex + 1;
|
|
15
|
+
}
|
|
16
|
+
export function displayFocusSelector(note, slideIndex) {
|
|
17
|
+
const clamped = clampDisplaySlideIndex(slideIndex, note);
|
|
18
|
+
if (clamped === 0)
|
|
19
|
+
return `[data-display-slide-index="0"]`;
|
|
20
|
+
if (clamped <= note.blocks.length) {
|
|
21
|
+
return `[data-display-slide-index="${clamped}"]`;
|
|
22
|
+
}
|
|
23
|
+
return `[data-display-slide-index="${note.blocks.length + 1}"]`;
|
|
24
|
+
}
|
|
25
|
+
export function readVisibleDisplaySlideIndex(root, scrollRoot) {
|
|
26
|
+
const targets = Array.from(root.querySelectorAll('[data-display-slide-index]'));
|
|
27
|
+
if (!targets.length)
|
|
28
|
+
return 0;
|
|
29
|
+
const viewportTop = scrollRoot?.getBoundingClientRect().top ?? 0;
|
|
30
|
+
const focusLine = viewportTop + 56;
|
|
31
|
+
let bestIndex = 0;
|
|
32
|
+
for (const target of targets) {
|
|
33
|
+
const index = Number(target.dataset.displaySlideIndex);
|
|
34
|
+
if (!Number.isFinite(index))
|
|
35
|
+
continue;
|
|
36
|
+
const rect = target.getBoundingClientRect();
|
|
37
|
+
// Classic scrollspy: the active section is the last one at or above the focus line.
|
|
38
|
+
if (rect.top <= focusLine)
|
|
39
|
+
bestIndex = index;
|
|
40
|
+
}
|
|
41
|
+
return bestIndex;
|
|
42
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { DocsNotePanel, DocsReviewScope } from "../model";
|
|
2
|
+
export type DocsDocumentSurface = {
|
|
3
|
+
panel: DocsNotePanel;
|
|
4
|
+
reviewScope?: DocsReviewScope;
|
|
5
|
+
};
|
|
6
|
+
export declare function requestDocsDocumentSurface(path: string, surface: DocsDocumentSurface): void;
|
|
7
|
+
export declare function subscribeDocsDocumentSurface(path: string, listener: (surface: DocsDocumentSurface) => void): () => void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { resolveDocsSidebarPanelSelection, } from "./docs-folder-directory";
|
|
2
|
+
const pending = new Map();
|
|
3
|
+
const listeners = new Map();
|
|
4
|
+
export function requestDocsDocumentSurface(path, surface) {
|
|
5
|
+
const next = {
|
|
6
|
+
panel: resolveDocsSidebarPanelSelection(surface.panel),
|
|
7
|
+
...(surface.reviewScope ? { reviewScope: surface.reviewScope } : {}),
|
|
8
|
+
};
|
|
9
|
+
pending.set(path, next);
|
|
10
|
+
for (const listener of listeners.get(path) ?? [])
|
|
11
|
+
listener(next);
|
|
12
|
+
}
|
|
13
|
+
export function subscribeDocsDocumentSurface(path, listener) {
|
|
14
|
+
const group = listeners.get(path) ?? new Set();
|
|
15
|
+
group.add(listener);
|
|
16
|
+
listeners.set(path, group);
|
|
17
|
+
const current = pending.get(path);
|
|
18
|
+
if (current)
|
|
19
|
+
listener(current);
|
|
20
|
+
return () => {
|
|
21
|
+
group.delete(listener);
|
|
22
|
+
if (group.size === 0)
|
|
23
|
+
listeners.delete(path);
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type DocsNoteListItem } from '../model/docs-note-list';
|
|
2
|
+
import type { DocsNotePanel, DocsWorkspace, DocsWorkspaceIndex } from '../model';
|
|
3
|
+
export type FolderNotePanel = {
|
|
4
|
+
id: DocsNotePanel;
|
|
5
|
+
label: string;
|
|
6
|
+
count?: number;
|
|
7
|
+
};
|
|
8
|
+
export declare function getFolderNotes(workspace: DocsWorkspace | DocsWorkspaceIndex | null | undefined, activeTag: string | null | undefined): (import("..").DocsNote | import("..").DocsNoteSummary)[];
|
|
9
|
+
export declare function getActiveFolderNote(folderNotes: DocsNoteListItem[], activeNoteId: string | null | undefined): DocsNoteListItem | null;
|
|
10
|
+
export declare function getMatchingFolderTags(activeTag: string | null | undefined, activeFolderNote: DocsNoteListItem | null | undefined): string[];
|
|
11
|
+
export declare function getActiveFolderTagKey(activeTag: string | null | undefined): string;
|
|
12
|
+
export declare function getFolderNotePanels(workspace: DocsWorkspace | DocsWorkspaceIndex | null | undefined, note: DocsNoteListItem): FolderNotePanel[];
|
|
13
|
+
/** Sidebar "Note" opens the display/preview surface, not the editor. */
|
|
14
|
+
export declare function resolveDocsSidebarPanelSelection(panel: DocsNotePanel): DocsNotePanel;
|
|
15
|
+
/**
|
|
16
|
+
* The toolbar preview toggle edits cards in place. Note and display still
|
|
17
|
+
* trade places with that same control.
|
|
18
|
+
*/
|
|
19
|
+
export declare function docsPanelAfterPreviewToggle(activePanel: DocsNotePanel, previewMode: boolean): DocsNotePanel;
|
|
20
|
+
/** Keep the Note row highlighted while preview (display) or edit (note) is active. */
|
|
21
|
+
export declare function isDocsSidebarPanelActive(activePanel: DocsNotePanel, panel: DocsNotePanel): boolean;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { docsNoteMatchesTagFolder, docsNoteTagValues, docsTagMatchesFolder, normalizeDocsTag, } from '../model/graph';
|
|
2
|
+
import { docsNoteCardCount } from '../model/docs-note-list';
|
|
3
|
+
export function getFolderNotes(workspace, activeTag) {
|
|
4
|
+
if (!workspace || !activeTag)
|
|
5
|
+
return [];
|
|
6
|
+
return workspace.notes
|
|
7
|
+
.filter((note) => docsNoteMatchesTagFolder(note, activeTag))
|
|
8
|
+
.sort((left, right) => left.title.localeCompare(right.title));
|
|
9
|
+
}
|
|
10
|
+
export function getActiveFolderNote(folderNotes, activeNoteId) {
|
|
11
|
+
return folderNotes.find((note) => note.id === activeNoteId) ?? null;
|
|
12
|
+
}
|
|
13
|
+
export function getMatchingFolderTags(activeTag, activeFolderNote) {
|
|
14
|
+
if (!activeTag || !activeFolderNote)
|
|
15
|
+
return [];
|
|
16
|
+
const tags = new Set();
|
|
17
|
+
for (const tag of docsNoteTagValues(activeFolderNote)) {
|
|
18
|
+
if (docsTagMatchesFolder(tag, activeTag)) {
|
|
19
|
+
tags.add(normalizeDocsTag(tag));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return [...tags].sort((left, right) => left.localeCompare(right));
|
|
23
|
+
}
|
|
24
|
+
export function getActiveFolderTagKey(activeTag) {
|
|
25
|
+
return normalizeDocsTag(activeTag ?? "").toLowerCase();
|
|
26
|
+
}
|
|
27
|
+
function noteDueCount(workspace, noteId) {
|
|
28
|
+
return (workspace?.reviewQueue.filter((item) => item.noteId === noteId).length ?? 0);
|
|
29
|
+
}
|
|
30
|
+
export function getFolderNotePanels(workspace, note) {
|
|
31
|
+
return [
|
|
32
|
+
{ id: "note", label: "Note" },
|
|
33
|
+
{ id: "cards", label: "Cards", count: docsNoteCardCount(note) },
|
|
34
|
+
{ id: "review", label: "Review", count: noteDueCount(workspace, note.id) },
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
/** Sidebar "Note" opens the display/preview surface, not the editor. */
|
|
38
|
+
export function resolveDocsSidebarPanelSelection(panel) {
|
|
39
|
+
return panel === "note" ? "display" : panel;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The toolbar preview toggle edits cards in place. Note and display still
|
|
43
|
+
* trade places with that same control.
|
|
44
|
+
*/
|
|
45
|
+
export function docsPanelAfterPreviewToggle(activePanel, previewMode) {
|
|
46
|
+
if (activePanel === "cards")
|
|
47
|
+
return "cards";
|
|
48
|
+
return previewMode ? "display" : "note";
|
|
49
|
+
}
|
|
50
|
+
/** Keep the Note row highlighted while preview (display) or edit (note) is active. */
|
|
51
|
+
export function isDocsSidebarPanelActive(activePanel, panel) {
|
|
52
|
+
if (panel === "note") {
|
|
53
|
+
return activePanel === "note" || activePanel === "display";
|
|
54
|
+
}
|
|
55
|
+
return activePanel === panel;
|
|
56
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DocsNotePanel } from '../model';
|
|
2
|
+
export type DocsContentLinkTarget = {
|
|
3
|
+
noteId: string;
|
|
4
|
+
/** The id is present in the loaded Docs catalog, so the link can open it. */
|
|
5
|
+
open: boolean;
|
|
6
|
+
};
|
|
7
|
+
/** Note id carried by a Docs content link, including card resource and wiki targets. */
|
|
8
|
+
export declare function docsContentLinkTarget(href: string, noteIds: ReadonlySet<string>): DocsContentLinkTarget | null;
|
|
9
|
+
/**
|
|
10
|
+
* Opens a Docs note from a rendered content link.
|
|
11
|
+
* Protocol links are cancelled even when the note is not in the catalog.
|
|
12
|
+
*/
|
|
13
|
+
export declare function openDocsContentLink(event: MouseEvent, notes: readonly {
|
|
14
|
+
id: string;
|
|
15
|
+
}[], openNote: (noteId: string) => void): boolean;
|
|
16
|
+
export declare function preservePreviewModeForDocsLink(event: MouseEvent, activePanel: DocsNotePanel): void;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const DOCS_PROTOCOL_LINK = /^(?:cvstudio|lapis):\/\/docs\/([^/?#]+)(?:[/?#].*)?$/i;
|
|
2
|
+
/** Note id carried by a Docs content link, including card resource and wiki targets. */
|
|
3
|
+
export function docsContentLinkTarget(href, noteIds) {
|
|
4
|
+
const raw = href.trim();
|
|
5
|
+
if (!raw || raw.startsWith("#"))
|
|
6
|
+
return null;
|
|
7
|
+
const protocol = raw.match(DOCS_PROTOCOL_LINK);
|
|
8
|
+
if (protocol) {
|
|
9
|
+
const noteId = decodeNoteId(protocol[1] ?? "");
|
|
10
|
+
return noteId ? { noteId, open: noteIds.has(noteId) } : null;
|
|
11
|
+
}
|
|
12
|
+
const queryNote = docsNoteIdFromWorkspaceHref(raw);
|
|
13
|
+
if (queryNote)
|
|
14
|
+
return { noteId: queryNote, open: noteIds.has(queryNote) };
|
|
15
|
+
const bare = decodeNoteId(raw.split("#")[0] ?? "");
|
|
16
|
+
if (!bare || bare.includes("/") || bare.includes(":"))
|
|
17
|
+
return null;
|
|
18
|
+
if (!noteIds.has(bare))
|
|
19
|
+
return null;
|
|
20
|
+
return { noteId: bare, open: true };
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Opens a Docs note from a rendered content link.
|
|
24
|
+
* Protocol links are cancelled even when the note is not in the catalog.
|
|
25
|
+
*/
|
|
26
|
+
export function openDocsContentLink(event, notes, openNote) {
|
|
27
|
+
if (event.defaultPrevented || event.button !== 0)
|
|
28
|
+
return false;
|
|
29
|
+
if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey)
|
|
30
|
+
return false;
|
|
31
|
+
if (!(event.target instanceof Element))
|
|
32
|
+
return false;
|
|
33
|
+
const anchor = event.target.closest("a[href]");
|
|
34
|
+
const href = anchor?.getAttribute("href");
|
|
35
|
+
if (!href)
|
|
36
|
+
return false;
|
|
37
|
+
const target = docsContentLinkTarget(href, new Set(notes.map((note) => note.id)));
|
|
38
|
+
if (!target)
|
|
39
|
+
return false;
|
|
40
|
+
event.preventDefault();
|
|
41
|
+
if (target.open)
|
|
42
|
+
openNote(target.noteId);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
function decodeNoteId(value) {
|
|
46
|
+
const trimmed = value.trim();
|
|
47
|
+
if (!trimmed)
|
|
48
|
+
return "";
|
|
49
|
+
try {
|
|
50
|
+
return decodeURIComponent(trimmed);
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return trimmed;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function docsNoteIdFromWorkspaceHref(href) {
|
|
57
|
+
if (!href.includes("note=") || /^[a-z][a-z0-9+.-]*:/i.test(href))
|
|
58
|
+
return null;
|
|
59
|
+
try {
|
|
60
|
+
const url = new URL(href, "http://docs.local");
|
|
61
|
+
const note = url.searchParams.get("note")?.trim();
|
|
62
|
+
if (url.searchParams.get("workspace") !== "docs" || !note)
|
|
63
|
+
return null;
|
|
64
|
+
return decodeNoteId(note);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
export function preservePreviewModeForDocsLink(event, activePanel) {
|
|
71
|
+
if (event.defaultPrevented || activePanel !== "display")
|
|
72
|
+
return;
|
|
73
|
+
if (typeof window === "undefined")
|
|
74
|
+
return;
|
|
75
|
+
if (!(event.target instanceof Element))
|
|
76
|
+
return;
|
|
77
|
+
const anchor = event.target.closest("a[href]");
|
|
78
|
+
if (!anchor)
|
|
79
|
+
return;
|
|
80
|
+
const url = new URL(anchor.href, window.location.href);
|
|
81
|
+
if (url.origin !== window.location.origin)
|
|
82
|
+
return;
|
|
83
|
+
if (url.searchParams.get("workspace") !== "docs")
|
|
84
|
+
return;
|
|
85
|
+
if (!url.searchParams.get("note"))
|
|
86
|
+
return;
|
|
87
|
+
if (url.searchParams.has("preview") ||
|
|
88
|
+
url.searchParams.has("edit") ||
|
|
89
|
+
url.searchParams.has("docsPanel")) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
url.searchParams.set("preview", "1");
|
|
93
|
+
anchor.href = `${url.pathname}${url.search}${url.hash}`;
|
|
94
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { DocsCalloutTone, DocsDocType, DocsNote, DocsResourceItem, DocsRevisionBlock } from '../model';
|
|
2
|
+
export type DisplayMode = 'document' | 'slides';
|
|
3
|
+
export declare const DOC_TYPE_OPTIONS: DocsDocType[];
|
|
4
|
+
export declare const DOC_TYPE_LABELS: Record<string, string>;
|
|
5
|
+
export declare const BLOCK_TYPE_OPTIONS: DocsRevisionBlock['type'][];
|
|
6
|
+
export declare const BLOCK_TYPE_LABELS: Record<string, string>;
|
|
7
|
+
export declare const CALLOUT_TONE_OPTIONS: DocsCalloutTone[];
|
|
8
|
+
export declare const CALLOUT_TONE_LABELS: Record<string, string>;
|
|
9
|
+
export declare const DISPLAY_MODE_LABELS: Record<string, string>;
|
|
10
|
+
export declare function cloneDocsNote(note: DocsNote): DocsNote;
|
|
11
|
+
export declare function createEmptyDocsBlock(type: DocsRevisionBlock['type'], id: string, title?: string): DocsRevisionBlock;
|
|
12
|
+
export declare function docsBlockDisplayTitle(block: DocsRevisionBlock): string;
|
|
13
|
+
export declare function docsBlockPlainText(block: DocsRevisionBlock): string;
|
|
14
|
+
export declare function createDocsBlockFromType(type: DocsRevisionBlock['type'], id: string, previous?: DocsRevisionBlock): DocsRevisionBlock;
|
|
15
|
+
export declare function textToLines(value: string): string[];
|
|
16
|
+
export declare function linesToText(items: string[]): string;
|
|
17
|
+
export declare function tableRowsToText(rows: string[][]): string;
|
|
18
|
+
export declare function textToTableRows(value: string): string[][];
|
|
19
|
+
export declare function resourceItemsToText(items: DocsResourceItem[]): string;
|
|
20
|
+
export declare function textToResourceItems(value: string): {
|
|
21
|
+
title: string;
|
|
22
|
+
url: string;
|
|
23
|
+
kind: string;
|
|
24
|
+
notes: string;
|
|
25
|
+
}[];
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
export const DOC_TYPE_OPTIONS = [
|
|
2
|
+
'revision',
|
|
3
|
+
'coding-drill',
|
|
4
|
+
'system-design',
|
|
5
|
+
'story-bank',
|
|
6
|
+
'index',
|
|
7
|
+
'freeform'
|
|
8
|
+
];
|
|
9
|
+
export const DOC_TYPE_LABELS = {
|
|
10
|
+
revision: 'Revision',
|
|
11
|
+
'coding-drill': 'Coding drill',
|
|
12
|
+
'system-design': 'System design',
|
|
13
|
+
'story-bank': 'Story bank',
|
|
14
|
+
index: 'Index',
|
|
15
|
+
freeform: 'Freeform'
|
|
16
|
+
};
|
|
17
|
+
export const BLOCK_TYPE_OPTIONS = [
|
|
18
|
+
'text',
|
|
19
|
+
'markdown',
|
|
20
|
+
'concept',
|
|
21
|
+
'procedure',
|
|
22
|
+
'prompt',
|
|
23
|
+
'code',
|
|
24
|
+
'table',
|
|
25
|
+
'callout',
|
|
26
|
+
'story',
|
|
27
|
+
'resources'
|
|
28
|
+
];
|
|
29
|
+
export const BLOCK_TYPE_LABELS = {
|
|
30
|
+
text: 'Text',
|
|
31
|
+
markdown: 'Markdown',
|
|
32
|
+
concept: 'Concept',
|
|
33
|
+
procedure: 'Procedure',
|
|
34
|
+
prompt: 'Prompt',
|
|
35
|
+
code: 'Code',
|
|
36
|
+
table: 'Table',
|
|
37
|
+
callout: 'Callout',
|
|
38
|
+
story: 'Story',
|
|
39
|
+
resources: 'Resources'
|
|
40
|
+
};
|
|
41
|
+
export const CALLOUT_TONE_OPTIONS = ['note', 'tip', 'warning', 'critical'];
|
|
42
|
+
export const CALLOUT_TONE_LABELS = {
|
|
43
|
+
note: 'Note',
|
|
44
|
+
tip: 'Tip',
|
|
45
|
+
warning: 'Warning',
|
|
46
|
+
critical: 'Critical'
|
|
47
|
+
};
|
|
48
|
+
export const DISPLAY_MODE_LABELS = {
|
|
49
|
+
document: 'Document',
|
|
50
|
+
slides: 'Slides'
|
|
51
|
+
};
|
|
52
|
+
export function cloneDocsNote(note) {
|
|
53
|
+
return JSON.parse(JSON.stringify(note));
|
|
54
|
+
}
|
|
55
|
+
export function createEmptyDocsBlock(type, id, title = '') {
|
|
56
|
+
const base = title.trim() ? { id, type, title: title.trim() } : { id, type };
|
|
57
|
+
switch (type) {
|
|
58
|
+
case 'concept':
|
|
59
|
+
return { ...base, type, summary: '', keyPoints: [], details: '' };
|
|
60
|
+
case 'procedure':
|
|
61
|
+
return { ...base, type, steps: [], notes: '' };
|
|
62
|
+
case 'prompt':
|
|
63
|
+
return {
|
|
64
|
+
...base,
|
|
65
|
+
type,
|
|
66
|
+
problem: '',
|
|
67
|
+
requirements: [],
|
|
68
|
+
approach: '',
|
|
69
|
+
traps: [],
|
|
70
|
+
tests: [],
|
|
71
|
+
answer: ''
|
|
72
|
+
};
|
|
73
|
+
case 'code':
|
|
74
|
+
return { ...base, type, language: 'ts', code: '', caption: '' };
|
|
75
|
+
case 'table':
|
|
76
|
+
return { ...base, type, columns: [], rows: [] };
|
|
77
|
+
case 'callout':
|
|
78
|
+
return { ...base, type, tone: 'note', body: '' };
|
|
79
|
+
case 'story':
|
|
80
|
+
return {
|
|
81
|
+
...base,
|
|
82
|
+
type,
|
|
83
|
+
useFor: [],
|
|
84
|
+
situation: '',
|
|
85
|
+
task: '',
|
|
86
|
+
action: '',
|
|
87
|
+
result: '',
|
|
88
|
+
metrics: []
|
|
89
|
+
};
|
|
90
|
+
case 'resources':
|
|
91
|
+
return { ...base, type, items: [] };
|
|
92
|
+
case 'markdown':
|
|
93
|
+
return { ...base, type: 'markdown', body: '' };
|
|
94
|
+
case 'text':
|
|
95
|
+
default:
|
|
96
|
+
return { ...base, type: 'text', body: '' };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export function docsBlockDisplayTitle(block) {
|
|
100
|
+
return block.title?.trim() || BLOCK_TYPE_LABELS[block.type];
|
|
101
|
+
}
|
|
102
|
+
export function docsBlockPlainText(block) {
|
|
103
|
+
switch (block.type) {
|
|
104
|
+
case 'concept':
|
|
105
|
+
return [block.summary, block.details].filter(Boolean).join('\n\n');
|
|
106
|
+
case 'procedure':
|
|
107
|
+
return [block.steps.join('\n'), block.notes].filter(Boolean).join('\n\n');
|
|
108
|
+
case 'prompt':
|
|
109
|
+
return [block.problem, block.approach, block.answer].filter(Boolean).join('\n\n');
|
|
110
|
+
case 'code':
|
|
111
|
+
return block.code;
|
|
112
|
+
case 'table':
|
|
113
|
+
return tableRowsToText(block.rows);
|
|
114
|
+
case 'callout':
|
|
115
|
+
return block.body;
|
|
116
|
+
case 'story':
|
|
117
|
+
return [block.situation, block.task, block.action, block.result].filter(Boolean).join('\n\n');
|
|
118
|
+
case 'resources':
|
|
119
|
+
return resourceItemsToText(block.items);
|
|
120
|
+
case 'markdown':
|
|
121
|
+
case 'text':
|
|
122
|
+
default:
|
|
123
|
+
return block.body;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
export function createDocsBlockFromType(type, id, previous) {
|
|
127
|
+
const block = createEmptyDocsBlock(type, previous?.id ?? id, previous?.title ?? '');
|
|
128
|
+
const body = previous ? docsBlockPlainText(previous) : '';
|
|
129
|
+
if (!body)
|
|
130
|
+
return block;
|
|
131
|
+
switch (block.type) {
|
|
132
|
+
case 'concept':
|
|
133
|
+
return { ...block, summary: body };
|
|
134
|
+
case 'procedure':
|
|
135
|
+
return { ...block, notes: body };
|
|
136
|
+
case 'prompt':
|
|
137
|
+
return { ...block, problem: body };
|
|
138
|
+
case 'code':
|
|
139
|
+
return { ...block, code: body };
|
|
140
|
+
case 'callout':
|
|
141
|
+
return { ...block, body };
|
|
142
|
+
case 'story':
|
|
143
|
+
return { ...block, situation: body };
|
|
144
|
+
case 'resources':
|
|
145
|
+
return { ...block, items: textToResourceItems(body) };
|
|
146
|
+
case 'table':
|
|
147
|
+
return { ...block, rows: textToTableRows(body) };
|
|
148
|
+
case 'markdown':
|
|
149
|
+
case 'text':
|
|
150
|
+
default:
|
|
151
|
+
return { ...block, body };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
export function textToLines(value) {
|
|
155
|
+
return value
|
|
156
|
+
.split('\n')
|
|
157
|
+
.map((line) => line.trim())
|
|
158
|
+
.filter(Boolean);
|
|
159
|
+
}
|
|
160
|
+
export function linesToText(items) {
|
|
161
|
+
return items.join('\n');
|
|
162
|
+
}
|
|
163
|
+
export function tableRowsToText(rows) {
|
|
164
|
+
return rows.map((row) => row.join(' | ')).join('\n');
|
|
165
|
+
}
|
|
166
|
+
export function textToTableRows(value) {
|
|
167
|
+
return value
|
|
168
|
+
.split('\n')
|
|
169
|
+
.map((line) => line.split('|').map((cell) => cell.trim()))
|
|
170
|
+
.filter((row) => row.some(Boolean));
|
|
171
|
+
}
|
|
172
|
+
export function resourceItemsToText(items) {
|
|
173
|
+
return items
|
|
174
|
+
.map((item) => {
|
|
175
|
+
const heading = item.title || item.url;
|
|
176
|
+
const details = [item.url && item.title ? item.url : '', item.kind, item.notes].filter(Boolean);
|
|
177
|
+
return [heading, ...details].filter(Boolean).join('\n');
|
|
178
|
+
})
|
|
179
|
+
.filter(Boolean)
|
|
180
|
+
.join('\n\n');
|
|
181
|
+
}
|
|
182
|
+
export function textToResourceItems(value) {
|
|
183
|
+
const lines = textToLines(value);
|
|
184
|
+
if (!lines.length)
|
|
185
|
+
return [];
|
|
186
|
+
const [title = '', ...notes] = lines;
|
|
187
|
+
return [
|
|
188
|
+
{
|
|
189
|
+
title,
|
|
190
|
+
url: '',
|
|
191
|
+
kind: '',
|
|
192
|
+
notes: notes.join('\n')
|
|
193
|
+
}
|
|
194
|
+
].filter((item) => item.title || item.notes);
|
|
195
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/* DESIGN_CORE_CANDIDATE: Docs domain tokens. Prefer shared design-core docs
|
|
2
|
+
* family when one exists. */
|
|
3
|
+
:root,
|
|
4
|
+
[data-ui-theme] {
|
|
5
|
+
--ui-docs-background: var(--ui-background, #fff);
|
|
6
|
+
--ui-docs-foreground: var(--ui-foreground, #111);
|
|
7
|
+
--ui-docs-muted: var(--ui-muted, #f4f4f5);
|
|
8
|
+
--ui-docs-muted-foreground: var(--ui-muted-foreground, #71717a);
|
|
9
|
+
--ui-docs-border: var(--ui-border, #e4e4e7);
|
|
10
|
+
--ui-docs-accent: var(--ui-primary, #2563eb);
|
|
11
|
+
--ui-docs-radius: var(--ui-radius, 0.5rem);
|
|
12
|
+
--ui-docs-card: var(--ui-card, #fff);
|
|
13
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { App } from "@lapis-notes/api";
|
|
2
|
+
import type { DocsRepository } from "../docs-repository";
|
|
3
|
+
import type { AnkiImportResult } from "./docs-anki-import";
|
|
4
|
+
export declare function importAnkiPackageFile(input: {
|
|
5
|
+
app: App;
|
|
6
|
+
repository: DocsRepository;
|
|
7
|
+
docsRoot: string;
|
|
8
|
+
file: File;
|
|
9
|
+
now?: Date;
|
|
10
|
+
}): Promise<AnkiImportResult>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
function arrayBuffer(bytes) {
|
|
2
|
+
const copy = new Uint8Array(bytes.byteLength);
|
|
3
|
+
copy.set(bytes);
|
|
4
|
+
return copy.buffer;
|
|
5
|
+
}
|
|
6
|
+
export async function importAnkiPackageFile(input) {
|
|
7
|
+
const { importAnkiPackage } = await import("./docs-anki-import");
|
|
8
|
+
const existing = await input.repository.listNotes(input.docsRoot);
|
|
9
|
+
return importAnkiPackage({
|
|
10
|
+
bytes: new Uint8Array(await input.file.arrayBuffer()),
|
|
11
|
+
filename: input.file.name,
|
|
12
|
+
docsRoot: input.docsRoot,
|
|
13
|
+
now: input.now,
|
|
14
|
+
existingIds: existing.map((note) => note.id),
|
|
15
|
+
writeNote: (note) => input.repository.writeNote(note),
|
|
16
|
+
writeMedia: async (note, file) => {
|
|
17
|
+
const path = `${input.docsRoot}/anki/${note.id}/media/${file.filename}`;
|
|
18
|
+
const folder = path.slice(0, path.lastIndexOf("/"));
|
|
19
|
+
if (folder)
|
|
20
|
+
await input.app.vault.mkpath(folder);
|
|
21
|
+
const data = arrayBuffer(file.bytes);
|
|
22
|
+
const current = input.app.vault.getFileByPath(path);
|
|
23
|
+
if (current) {
|
|
24
|
+
await input.app.vault.modifyBinary(current, data);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
await input.app.vault.createBinary(path, data);
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { App } from "@lapis-notes/api";
|
|
2
|
+
import { type MiraExtension } from "@lapismd/mira/extensions";
|
|
3
|
+
/** Host stubs take no arguments. A provided renderer accepts the markdown string. */
|
|
4
|
+
export declare function providedMarkdownRenderer(app: App | null): ((markdown: string, el: HTMLElement, sourcePath: string, component: import("@lapis-notes/api").Component) => Promise<void>) | null;
|
|
5
|
+
/**
|
|
6
|
+
* Reading extensions used by the Lapis Markdown view: Mira's Mermaid plugin
|
|
7
|
+
* plus every Markdown post-processor registered with the host.
|
|
8
|
+
*/
|
|
9
|
+
export declare function lapisMarkdownExtensions(app: App | null, sourcePath: string, markdown: string): MiraExtension[];
|