@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,51 @@
|
|
|
1
|
+
export declare const CARD_STACK_MOTION: {
|
|
2
|
+
readonly rotateFactor: 3.5;
|
|
3
|
+
readonly scaleFactor: 0.045;
|
|
4
|
+
readonly pivot: "50% 100%";
|
|
5
|
+
readonly kickMs: 200;
|
|
6
|
+
readonly settleMs: 500;
|
|
7
|
+
readonly promoteDelayMs: 150;
|
|
8
|
+
readonly totalMs: 700;
|
|
9
|
+
readonly visibleDepth: 3;
|
|
10
|
+
};
|
|
11
|
+
export declare const CARD_STACK_KICK: {
|
|
12
|
+
readonly x: "40%";
|
|
13
|
+
readonly y: "-8%";
|
|
14
|
+
readonly rotate: 14;
|
|
15
|
+
readonly scale: 0.8;
|
|
16
|
+
};
|
|
17
|
+
export declare const CARD_STACK_ANIMATION_NAMES: {
|
|
18
|
+
readonly kick: "docs-card-preview-deck-kick-next";
|
|
19
|
+
readonly settleBack: "docs-card-preview-deck-settle-back";
|
|
20
|
+
readonly dealFromBack: "docs-card-preview-deal-from-back";
|
|
21
|
+
readonly dealKickToFront: "docs-card-preview-deal-kick-to-front";
|
|
22
|
+
readonly promoteStep: "docs-card-preview-promote-step";
|
|
23
|
+
readonly fanStep: "docs-card-preview-fan-step";
|
|
24
|
+
};
|
|
25
|
+
export declare const CARD_STACK_SWIPE: {
|
|
26
|
+
readonly activationThresholdPx: 10;
|
|
27
|
+
readonly commitThresholdPx: 72;
|
|
28
|
+
readonly rotateFactor: 0.08;
|
|
29
|
+
readonly maxRotateDeg: 12;
|
|
30
|
+
readonly snapBackMs: 220;
|
|
31
|
+
};
|
|
32
|
+
export declare function swipeDragPose(deltaX: number): {
|
|
33
|
+
x: number;
|
|
34
|
+
rotate: number;
|
|
35
|
+
};
|
|
36
|
+
export declare function fanPose(depth: number): {
|
|
37
|
+
rotate: number;
|
|
38
|
+
scale: number;
|
|
39
|
+
};
|
|
40
|
+
export declare function fanStackOpacity(depth: number): 1 | 0.96 | 0.92;
|
|
41
|
+
export declare function fanStackZIndex(depth: number): 1 | 2 | 3;
|
|
42
|
+
export declare function fanTransform(depth: number): string;
|
|
43
|
+
export type CardStackMode = 'browse' | 'study';
|
|
44
|
+
export declare function wrappedCardIndex(index: number, cardCount: number): number;
|
|
45
|
+
export declare function clampCardIndex(index: number, cardCount: number): number;
|
|
46
|
+
export declare function resolveStackIndex(index: number, cardCount: number, mode: CardStackMode): number;
|
|
47
|
+
export declare function nextStackIndex(index: number, cardCount: number, mode: CardStackMode, direction: 'next' | 'prev'): number;
|
|
48
|
+
export declare function canNavigateStack(index: number, cardCount: number, mode: CardStackMode, direction: 'next' | 'prev'): boolean;
|
|
49
|
+
export declare function cardDepthInStack(topIndex: number, targetIndex: number, cardCount: number, visibleDepth?: number): number;
|
|
50
|
+
/** @deprecated Use cardDepthInStack with the start-state top index. */
|
|
51
|
+
export declare function incomingFanDepth(incomingIndex: number, newTopIndex: number, cardCount: number, visibleDepth?: number): number;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export const CARD_STACK_MOTION = {
|
|
2
|
+
rotateFactor: 3.5,
|
|
3
|
+
scaleFactor: 0.045,
|
|
4
|
+
pivot: '50% 100%',
|
|
5
|
+
kickMs: 200,
|
|
6
|
+
settleMs: 500,
|
|
7
|
+
promoteDelayMs: 150,
|
|
8
|
+
totalMs: 700,
|
|
9
|
+
visibleDepth: 3
|
|
10
|
+
};
|
|
11
|
+
export const CARD_STACK_KICK = {
|
|
12
|
+
x: '40%',
|
|
13
|
+
y: '-8%',
|
|
14
|
+
rotate: 14,
|
|
15
|
+
scale: 0.8
|
|
16
|
+
};
|
|
17
|
+
export const CARD_STACK_ANIMATION_NAMES = {
|
|
18
|
+
kick: 'docs-card-preview-deck-kick-next',
|
|
19
|
+
settleBack: 'docs-card-preview-deck-settle-back',
|
|
20
|
+
dealFromBack: 'docs-card-preview-deal-from-back',
|
|
21
|
+
dealKickToFront: 'docs-card-preview-deal-kick-to-front',
|
|
22
|
+
promoteStep: 'docs-card-preview-promote-step',
|
|
23
|
+
fanStep: 'docs-card-preview-fan-step'
|
|
24
|
+
};
|
|
25
|
+
export const CARD_STACK_SWIPE = {
|
|
26
|
+
activationThresholdPx: 10,
|
|
27
|
+
commitThresholdPx: 72,
|
|
28
|
+
rotateFactor: 0.08,
|
|
29
|
+
maxRotateDeg: 12,
|
|
30
|
+
snapBackMs: 220
|
|
31
|
+
};
|
|
32
|
+
export function swipeDragPose(deltaX) {
|
|
33
|
+
const rotate = Math.max(-CARD_STACK_SWIPE.maxRotateDeg, Math.min(CARD_STACK_SWIPE.maxRotateDeg, deltaX * CARD_STACK_SWIPE.rotateFactor));
|
|
34
|
+
return { x: deltaX, rotate };
|
|
35
|
+
}
|
|
36
|
+
export function fanPose(depth) {
|
|
37
|
+
return {
|
|
38
|
+
rotate: depth * CARD_STACK_MOTION.rotateFactor,
|
|
39
|
+
scale: 1 - depth * CARD_STACK_MOTION.scaleFactor
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export function fanStackOpacity(depth) {
|
|
43
|
+
if (depth <= 0)
|
|
44
|
+
return 1;
|
|
45
|
+
if (depth === 1)
|
|
46
|
+
return 0.96;
|
|
47
|
+
return 0.92;
|
|
48
|
+
}
|
|
49
|
+
export function fanStackZIndex(depth) {
|
|
50
|
+
if (depth <= 0)
|
|
51
|
+
return 3;
|
|
52
|
+
if (depth === 1)
|
|
53
|
+
return 2;
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
56
|
+
export function fanTransform(depth) {
|
|
57
|
+
const { rotate, scale } = fanPose(depth);
|
|
58
|
+
return `rotate(${rotate}deg) scale(${scale})`;
|
|
59
|
+
}
|
|
60
|
+
export function wrappedCardIndex(index, cardCount) {
|
|
61
|
+
if (cardCount <= 0)
|
|
62
|
+
return 0;
|
|
63
|
+
return ((index % cardCount) + cardCount) % cardCount;
|
|
64
|
+
}
|
|
65
|
+
export function clampCardIndex(index, cardCount) {
|
|
66
|
+
if (cardCount <= 0)
|
|
67
|
+
return 0;
|
|
68
|
+
return Math.max(0, Math.min(index, cardCount - 1));
|
|
69
|
+
}
|
|
70
|
+
export function resolveStackIndex(index, cardCount, mode) {
|
|
71
|
+
return mode === 'browse' ? wrappedCardIndex(index, cardCount) : clampCardIndex(index, cardCount);
|
|
72
|
+
}
|
|
73
|
+
export function nextStackIndex(index, cardCount, mode, direction) {
|
|
74
|
+
const delta = direction === 'next' ? 1 : -1;
|
|
75
|
+
return resolveStackIndex(index + delta, cardCount, mode);
|
|
76
|
+
}
|
|
77
|
+
export function canNavigateStack(index, cardCount, mode, direction) {
|
|
78
|
+
if (cardCount <= 1 || cardCount === 0)
|
|
79
|
+
return false;
|
|
80
|
+
if (mode === 'browse')
|
|
81
|
+
return true;
|
|
82
|
+
if (direction === 'prev')
|
|
83
|
+
return index > 0;
|
|
84
|
+
return index < cardCount - 1;
|
|
85
|
+
}
|
|
86
|
+
export function cardDepthInStack(topIndex, targetIndex, cardCount, visibleDepth = CARD_STACK_MOTION.visibleDepth) {
|
|
87
|
+
const depth = wrappedCardIndex(targetIndex - topIndex, cardCount);
|
|
88
|
+
return Math.min(depth, visibleDepth - 1);
|
|
89
|
+
}
|
|
90
|
+
/** @deprecated Use cardDepthInStack with the start-state top index. */
|
|
91
|
+
export function incomingFanDepth(incomingIndex, newTopIndex, cardCount, visibleDepth = CARD_STACK_MOTION.visibleDepth) {
|
|
92
|
+
const fromIndex = wrappedCardIndex(newTopIndex + 1, cardCount);
|
|
93
|
+
return cardDepthInStack(fromIndex, incomingIndex, cardCount, visibleDepth);
|
|
94
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type DocsMediaUrlResolver } from "./docs-relative-media";
|
|
2
|
+
export type DocsMediaResolveResult = string | Promise<string>;
|
|
3
|
+
export type DocsMediaResolver = (relativePath: string) => DocsMediaResolveResult;
|
|
4
|
+
/** Sync mapper used during Carta rehype (always emits the sentinel). */
|
|
5
|
+
export declare function docsMediaSentinelResolver(relativePath: string): string;
|
|
6
|
+
export declare function setDocsMediaResolver(resolver: DocsMediaResolver): void;
|
|
7
|
+
export declare function getDocsMediaResolver(): DocsMediaResolver;
|
|
8
|
+
/** Default sync Carta plugin resolver: emit sentinel for post-render. */
|
|
9
|
+
export declare function defaultDocsMediaPluginResolver(): DocsMediaUrlResolver;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getContext, setContext } from "svelte";
|
|
2
|
+
import { encodeDocsMediaSentinel, serverDocsMediaUrl, } from "./docs-relative-media";
|
|
3
|
+
const DOCS_MEDIA_RESOLVER_CONTEXT = "cvstudio:docs-media-url-resolver";
|
|
4
|
+
/** Sync mapper used during Carta rehype (always emits the sentinel). */
|
|
5
|
+
export function docsMediaSentinelResolver(relativePath) {
|
|
6
|
+
return encodeDocsMediaSentinel(relativePath);
|
|
7
|
+
}
|
|
8
|
+
export function setDocsMediaResolver(resolver) {
|
|
9
|
+
setContext(DOCS_MEDIA_RESOLVER_CONTEXT, resolver);
|
|
10
|
+
}
|
|
11
|
+
export function getDocsMediaResolver() {
|
|
12
|
+
return (getContext(DOCS_MEDIA_RESOLVER_CONTEXT) ??
|
|
13
|
+
serverDocsMediaUrl);
|
|
14
|
+
}
|
|
15
|
+
/** Default sync Carta plugin resolver: emit sentinel for post-render. */
|
|
16
|
+
export function defaultDocsMediaPluginResolver() {
|
|
17
|
+
return docsMediaSentinelResolver;
|
|
18
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DocsNote, DocsNoteSummary } from "./types";
|
|
2
|
+
/** Note-like rows used by sidebar lists (full notes or index summaries). */
|
|
3
|
+
export type DocsNoteListItem = DocsNote | DocsNoteSummary;
|
|
4
|
+
export declare function isDocsNoteSummary(note: DocsNoteListItem): note is DocsNoteSummary;
|
|
5
|
+
export declare function docsNoteCardCount(note: DocsNoteListItem): number;
|
|
6
|
+
export declare function docsNoteBlockCount(note: DocsNoteListItem): number;
|
|
7
|
+
export declare function docsNoteBodyLength(note: DocsNoteListItem): number;
|
|
8
|
+
export declare function docsNoteListKey(note: DocsNoteListItem): string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export function isDocsNoteSummary(note) {
|
|
2
|
+
return "cardCount" in note && !("blocks" in note);
|
|
3
|
+
}
|
|
4
|
+
export function docsNoteCardCount(note) {
|
|
5
|
+
if ("cardCount" in note && typeof note.cardCount === "number")
|
|
6
|
+
return note.cardCount;
|
|
7
|
+
if ("cards" in note && Array.isArray(note.cards))
|
|
8
|
+
return note.cards.length;
|
|
9
|
+
return 0;
|
|
10
|
+
}
|
|
11
|
+
export function docsNoteBlockCount(note) {
|
|
12
|
+
if ("blockCount" in note && typeof note.blockCount === "number")
|
|
13
|
+
return note.blockCount;
|
|
14
|
+
if ("blocks" in note && Array.isArray(note.blocks))
|
|
15
|
+
return note.blocks.length;
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
export function docsNoteBodyLength(note) {
|
|
19
|
+
if ("body" in note && typeof note.body === "string")
|
|
20
|
+
return note.body.length;
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
export function docsNoteListKey(note) {
|
|
24
|
+
return `${note.id}:${note.updatedAt}:${note.completedAt ?? ""}:${docsNoteBlockCount(note)}:${docsNoteCardCount(note)}:${docsNoteBodyLength(note)}`;
|
|
25
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve co-located docs media paths from markdown image `src` values.
|
|
3
|
+
* Leaves http(s), data:, and root-absolute paths unchanged.
|
|
4
|
+
* Relative paths resolve against the note file under `data/docs/`.
|
|
5
|
+
* Paths that escape `data/docs/` are left unchanged.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Path-shaped sentinel so rehype-sanitize keeps the src (custom protocols
|
|
9
|
+
* like `cvstudio-docs-media:` are stripped and leave relative `../media/...`
|
|
10
|
+
* URLs that 404 as `/media/...` in the browser).
|
|
11
|
+
*/
|
|
12
|
+
export declare const DOCS_MEDIA_SENTINEL_PREFIX = "/__cvstudio-docs-media__/";
|
|
13
|
+
export type DocsMediaUrlResolver = (relativePath: string) => string | Promise<string>;
|
|
14
|
+
/** Encode a path under data/docs/ as a sync preview sentinel. */
|
|
15
|
+
export declare function encodeDocsMediaSentinel(relativePath: string): string;
|
|
16
|
+
/** Decode a sentinel back to a data/docs-relative path, or null. */
|
|
17
|
+
export declare function decodeDocsMediaSentinel(src: string): string | null;
|
|
18
|
+
/** Default server runtime URL for a path under data/docs/. */
|
|
19
|
+
export declare function serverDocsMediaUrl(relativePath: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Resolve a markdown image src to a path under `data/docs/` (without the prefix).
|
|
22
|
+
* Returns null for external, absolute, invalid, or escaping paths.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveDocsMediaRelativePath(src: string, sourcePath: string): string | null;
|
|
25
|
+
/**
|
|
26
|
+
* Rewrite a markdown image src for preview.
|
|
27
|
+
* By default emits the server API URL; pass `encodeDocsMediaSentinel` for
|
|
28
|
+
* deferred runtime resolution.
|
|
29
|
+
*/
|
|
30
|
+
export declare function rewriteDocsRelativeMediaSrc(src: string, sourcePath: string, resolveUrl?: DocsMediaUrlResolver): string;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve co-located docs media paths from markdown image `src` values.
|
|
3
|
+
* Leaves http(s), data:, and root-absolute paths unchanged.
|
|
4
|
+
* Relative paths resolve against the note file under `data/docs/`.
|
|
5
|
+
* Paths that escape `data/docs/` are left unchanged.
|
|
6
|
+
*/
|
|
7
|
+
const DOCS_ROOT_PREFIX = "data/docs/";
|
|
8
|
+
const MEDIA_API_PREFIX = "/api/docs/media/";
|
|
9
|
+
/**
|
|
10
|
+
* Path-shaped sentinel so rehype-sanitize keeps the src (custom protocols
|
|
11
|
+
* like `cvstudio-docs-media:` are stripped and leave relative `../media/...`
|
|
12
|
+
* URLs that 404 as `/media/...` in the browser).
|
|
13
|
+
*/
|
|
14
|
+
export const DOCS_MEDIA_SENTINEL_PREFIX = "/__cvstudio-docs-media__/";
|
|
15
|
+
function normalizeSourcePath(sourcePath) {
|
|
16
|
+
return sourcePath.trim().replace(/^\/+/, "").replaceAll("\\", "/");
|
|
17
|
+
}
|
|
18
|
+
function resolvePosix(baseDir, relative) {
|
|
19
|
+
const parts = baseDir.split("/").filter(Boolean);
|
|
20
|
+
for (const part of relative.replaceAll("\\", "/").split("/")) {
|
|
21
|
+
if (!part || part === ".")
|
|
22
|
+
continue;
|
|
23
|
+
if (part === "..") {
|
|
24
|
+
if (parts.length === 0)
|
|
25
|
+
return null;
|
|
26
|
+
parts.pop();
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
parts.push(part);
|
|
30
|
+
}
|
|
31
|
+
return parts.join("/");
|
|
32
|
+
}
|
|
33
|
+
/** Encode a path under data/docs/ as a sync preview sentinel. */
|
|
34
|
+
export function encodeDocsMediaSentinel(relativePath) {
|
|
35
|
+
return `${DOCS_MEDIA_SENTINEL_PREFIX}${relativePath
|
|
36
|
+
.split("/")
|
|
37
|
+
.map(encodeURIComponent)
|
|
38
|
+
.join("/")}`;
|
|
39
|
+
}
|
|
40
|
+
/** Decode a sentinel back to a data/docs-relative path, or null. */
|
|
41
|
+
export function decodeDocsMediaSentinel(src) {
|
|
42
|
+
const trimmed = src.trim();
|
|
43
|
+
if (!trimmed.startsWith(DOCS_MEDIA_SENTINEL_PREFIX))
|
|
44
|
+
return null;
|
|
45
|
+
const encoded = trimmed.slice(DOCS_MEDIA_SENTINEL_PREFIX.length);
|
|
46
|
+
if (!encoded)
|
|
47
|
+
return null;
|
|
48
|
+
try {
|
|
49
|
+
return encoded
|
|
50
|
+
.split("/")
|
|
51
|
+
.map((segment) => decodeURIComponent(segment))
|
|
52
|
+
.join("/");
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/** Default server runtime URL for a path under data/docs/. */
|
|
59
|
+
export function serverDocsMediaUrl(relativePath) {
|
|
60
|
+
return `${MEDIA_API_PREFIX}${relativePath
|
|
61
|
+
.split("/")
|
|
62
|
+
.map(encodeURIComponent)
|
|
63
|
+
.join("/")}`;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Resolve a markdown image src to a path under `data/docs/` (without the prefix).
|
|
67
|
+
* Returns null for external, absolute, invalid, or escaping paths.
|
|
68
|
+
*/
|
|
69
|
+
export function resolveDocsMediaRelativePath(src, sourcePath) {
|
|
70
|
+
const trimmed = src.trim();
|
|
71
|
+
if (!trimmed)
|
|
72
|
+
return null;
|
|
73
|
+
if (/^(https?:|data:)/i.test(trimmed))
|
|
74
|
+
return null;
|
|
75
|
+
if (trimmed.startsWith(DOCS_MEDIA_SENTINEL_PREFIX)) {
|
|
76
|
+
return decodeDocsMediaSentinel(trimmed);
|
|
77
|
+
}
|
|
78
|
+
if (trimmed.startsWith("/"))
|
|
79
|
+
return null;
|
|
80
|
+
const notePath = normalizeSourcePath(sourcePath);
|
|
81
|
+
if (!notePath.startsWith(DOCS_ROOT_PREFIX))
|
|
82
|
+
return null;
|
|
83
|
+
const slash = notePath.lastIndexOf("/");
|
|
84
|
+
if (slash < 0)
|
|
85
|
+
return null;
|
|
86
|
+
const noteDir = notePath.slice(0, slash);
|
|
87
|
+
const resolved = resolvePosix(noteDir, trimmed);
|
|
88
|
+
if (!resolved || !resolved.startsWith(DOCS_ROOT_PREFIX))
|
|
89
|
+
return null;
|
|
90
|
+
return resolved.slice(DOCS_ROOT_PREFIX.length);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Rewrite a markdown image src for preview.
|
|
94
|
+
* By default emits the server API URL; pass `encodeDocsMediaSentinel` for
|
|
95
|
+
* deferred runtime resolution.
|
|
96
|
+
*/
|
|
97
|
+
export function rewriteDocsRelativeMediaSrc(src, sourcePath, resolveUrl = serverDocsMediaUrl) {
|
|
98
|
+
const trimmed = src.trim();
|
|
99
|
+
if (!trimmed)
|
|
100
|
+
return trimmed;
|
|
101
|
+
if (/^(https?:|data:)/i.test(trimmed))
|
|
102
|
+
return trimmed;
|
|
103
|
+
if (trimmed.startsWith("/") &&
|
|
104
|
+
!trimmed.startsWith(DOCS_MEDIA_SENTINEL_PREFIX)) {
|
|
105
|
+
return trimmed;
|
|
106
|
+
}
|
|
107
|
+
const relativePath = resolveDocsMediaRelativePath(trimmed, sourcePath);
|
|
108
|
+
if (!relativePath)
|
|
109
|
+
return trimmed;
|
|
110
|
+
const resolved = resolveUrl(relativePath);
|
|
111
|
+
if (typeof resolved !== "string") {
|
|
112
|
+
// Async resolvers must use the sentinel + post-render path.
|
|
113
|
+
return encodeDocsMediaSentinel(relativePath);
|
|
114
|
+
}
|
|
115
|
+
return resolved;
|
|
116
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const REVIEW_RATINGS = ['again', 'hard', 'good', 'easy'];
|
|
2
|
+
export const REVIEW_RATING_LABELS = {
|
|
3
|
+
again: 'Again',
|
|
4
|
+
hard: 'Hard',
|
|
5
|
+
good: 'Good',
|
|
6
|
+
easy: 'Easy'
|
|
7
|
+
};
|
|
8
|
+
export const REVIEW_RATING_KEYS = {
|
|
9
|
+
'1': 'again',
|
|
10
|
+
'2': 'hard',
|
|
11
|
+
'3': 'good',
|
|
12
|
+
'4': 'easy'
|
|
13
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type Card, type CardInput } from "ts-fsrs";
|
|
2
|
+
import { type DocsCard, type DocsFsrsState, type DocsNote, type DocsReviewQueueItem, type DocsReviewRating } from "./types";
|
|
3
|
+
export declare const docsFsrsParameters: import("ts-fsrs").FSRSParameters;
|
|
4
|
+
export declare function fsrsStateFromCard(card: Card): DocsFsrsState;
|
|
5
|
+
export declare function fsrsCardFromState(state: DocsFsrsState | undefined, now?: Date): CardInput | Card;
|
|
6
|
+
export declare function reviewDocsCard(card: DocsCard, rating: DocsReviewRating, reviewedAt?: Date): DocsCard;
|
|
7
|
+
export declare function buildDocsReviewQueue(notes: DocsNote[], now?: Date): DocsReviewQueueItem[];
|
|
8
|
+
export declare function previewDocsCardRatings(card: DocsCard, now?: Date): {
|
|
9
|
+
again: DocsFsrsState;
|
|
10
|
+
hard: DocsFsrsState;
|
|
11
|
+
good: DocsFsrsState;
|
|
12
|
+
easy: DocsFsrsState;
|
|
13
|
+
};
|
|
14
|
+
export declare function formatDocsReviewInterval(due: string, now?: Date): string;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { Rating, State, createEmptyCard, fsrs, generatorParameters, } from "ts-fsrs";
|
|
2
|
+
import {} from "./types";
|
|
3
|
+
export const docsFsrsParameters = generatorParameters({
|
|
4
|
+
request_retention: 0.9,
|
|
5
|
+
enable_fuzz: true,
|
|
6
|
+
enable_short_term: true,
|
|
7
|
+
learning_steps: ["1m", "10m"],
|
|
8
|
+
relearning_steps: ["10m"],
|
|
9
|
+
});
|
|
10
|
+
const ratingMap = {
|
|
11
|
+
again: Rating.Again,
|
|
12
|
+
hard: Rating.Hard,
|
|
13
|
+
good: Rating.Good,
|
|
14
|
+
easy: Rating.Easy,
|
|
15
|
+
};
|
|
16
|
+
const ratingNameByValue = new Map([
|
|
17
|
+
[Rating.Again, "again"],
|
|
18
|
+
[Rating.Hard, "hard"],
|
|
19
|
+
[Rating.Good, "good"],
|
|
20
|
+
[Rating.Easy, "easy"],
|
|
21
|
+
]);
|
|
22
|
+
function stateName(value) {
|
|
23
|
+
return State[value];
|
|
24
|
+
}
|
|
25
|
+
function stateValue(value) {
|
|
26
|
+
return State[value];
|
|
27
|
+
}
|
|
28
|
+
function toDate(value) {
|
|
29
|
+
return value instanceof Date ? value : new Date(value);
|
|
30
|
+
}
|
|
31
|
+
function toIso(value) {
|
|
32
|
+
return toDate(value).toISOString();
|
|
33
|
+
}
|
|
34
|
+
export function fsrsStateFromCard(card) {
|
|
35
|
+
return {
|
|
36
|
+
due: toIso(card.due),
|
|
37
|
+
stability: card.stability,
|
|
38
|
+
difficulty: card.difficulty,
|
|
39
|
+
elapsedDays: card.elapsed_days,
|
|
40
|
+
scheduledDays: card.scheduled_days,
|
|
41
|
+
learningSteps: card.learning_steps,
|
|
42
|
+
reps: card.reps,
|
|
43
|
+
lapses: card.lapses,
|
|
44
|
+
state: stateName(card.state),
|
|
45
|
+
...(card.last_review ? { lastReview: toIso(card.last_review) } : {}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export function fsrsCardFromState(state, now = new Date()) {
|
|
49
|
+
if (!state)
|
|
50
|
+
return createEmptyCard(now);
|
|
51
|
+
return {
|
|
52
|
+
due: toDate(state.due),
|
|
53
|
+
stability: state.stability,
|
|
54
|
+
difficulty: state.difficulty,
|
|
55
|
+
elapsed_days: state.elapsedDays,
|
|
56
|
+
scheduled_days: state.scheduledDays,
|
|
57
|
+
learning_steps: state.learningSteps,
|
|
58
|
+
reps: state.reps,
|
|
59
|
+
lapses: state.lapses,
|
|
60
|
+
state: stateValue(state.state),
|
|
61
|
+
last_review: state.lastReview ? toDate(state.lastReview) : null,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function reviewLogId(cardId, review) {
|
|
65
|
+
const stamp = review
|
|
66
|
+
.toISOString()
|
|
67
|
+
.replace(/\.\d{3}Z$/, "Z")
|
|
68
|
+
.replace(/[^0-9TZ]+/g, "")
|
|
69
|
+
.replace(/Z$/, "")
|
|
70
|
+
.toLowerCase();
|
|
71
|
+
return `${cardId}-${stamp}`;
|
|
72
|
+
}
|
|
73
|
+
function docsReviewLog(cardId, log, fallbackRating) {
|
|
74
|
+
return {
|
|
75
|
+
id: reviewLogId(cardId, log.review),
|
|
76
|
+
rating: ratingNameByValue.get(log.rating) ?? fallbackRating,
|
|
77
|
+
reviewedAt: log.review.toISOString(),
|
|
78
|
+
state: stateName(log.state),
|
|
79
|
+
due: log.due.toISOString(),
|
|
80
|
+
stability: log.stability,
|
|
81
|
+
difficulty: log.difficulty,
|
|
82
|
+
elapsedDays: log.elapsed_days,
|
|
83
|
+
scheduledDays: log.scheduled_days,
|
|
84
|
+
learningSteps: log.learning_steps,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
export function reviewDocsCard(card, rating, reviewedAt = new Date()) {
|
|
88
|
+
if (card.type === "coding")
|
|
89
|
+
throw new Error("Coding cards are evaluated through exercise submissions.");
|
|
90
|
+
const scheduler = fsrs(docsFsrsParameters);
|
|
91
|
+
const result = scheduler.next(fsrsCardFromState(card.fsrs, reviewedAt), reviewedAt, ratingMap[rating]);
|
|
92
|
+
return {
|
|
93
|
+
...card,
|
|
94
|
+
fsrs: fsrsStateFromCard(result.card),
|
|
95
|
+
reviews: [...card.reviews, docsReviewLog(card.id, result.log, rating)],
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function dueAt(card, now) {
|
|
99
|
+
return card.fsrs?.due ?? now.toISOString();
|
|
100
|
+
}
|
|
101
|
+
export function buildDocsReviewQueue(notes, now = new Date()) {
|
|
102
|
+
const nowMs = now.getTime();
|
|
103
|
+
return notes
|
|
104
|
+
.flatMap((note) => note.cards
|
|
105
|
+
.filter((card) => card.status === "active" && card.type !== "coding")
|
|
106
|
+
.map((card) => ({
|
|
107
|
+
noteId: note.id,
|
|
108
|
+
noteTitle: note.title,
|
|
109
|
+
noteSourcePath: note.sourcePath,
|
|
110
|
+
cardId: card.id,
|
|
111
|
+
card,
|
|
112
|
+
dueAt: dueAt(card, now),
|
|
113
|
+
})))
|
|
114
|
+
.filter((item) => new Date(item.dueAt).getTime() <= nowMs)
|
|
115
|
+
.sort((left, right) => {
|
|
116
|
+
const dueDiff = new Date(left.dueAt).getTime() - new Date(right.dueAt).getTime();
|
|
117
|
+
if (dueDiff !== 0)
|
|
118
|
+
return dueDiff;
|
|
119
|
+
return `${left.noteTitle}/${left.cardId}`.localeCompare(`${right.noteTitle}/${right.cardId}`);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
export function previewDocsCardRatings(card, now = new Date()) {
|
|
123
|
+
const scheduler = fsrs(docsFsrsParameters);
|
|
124
|
+
const preview = scheduler.repeat(fsrsCardFromState(card.fsrs, now), now);
|
|
125
|
+
return {
|
|
126
|
+
again: fsrsStateFromCard(preview[Rating.Again].card),
|
|
127
|
+
hard: fsrsStateFromCard(preview[Rating.Hard].card),
|
|
128
|
+
good: fsrsStateFromCard(preview[Rating.Good].card),
|
|
129
|
+
easy: fsrsStateFromCard(preview[Rating.Easy].card),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function formatDateOnly(value) {
|
|
133
|
+
const year = value.getFullYear();
|
|
134
|
+
const month = String(value.getMonth() + 1).padStart(2, "0");
|
|
135
|
+
const day = String(value.getDate()).padStart(2, "0");
|
|
136
|
+
return `${year}-${month}-${day}`;
|
|
137
|
+
}
|
|
138
|
+
export function formatDocsReviewInterval(due, now = new Date()) {
|
|
139
|
+
const dueDate = toDate(due);
|
|
140
|
+
const diffMs = dueDate.getTime() - now.getTime();
|
|
141
|
+
if (diffMs <= 0)
|
|
142
|
+
return "<1m";
|
|
143
|
+
const minutes = Math.round(diffMs / 60_000);
|
|
144
|
+
if (minutes < 60)
|
|
145
|
+
return `${minutes}m`;
|
|
146
|
+
const hours = Math.round(minutes / 60);
|
|
147
|
+
if (hours < 48)
|
|
148
|
+
return `${hours}h`;
|
|
149
|
+
const days = Math.round(hours / 24);
|
|
150
|
+
if (days < 60)
|
|
151
|
+
return `${days}d`;
|
|
152
|
+
return formatDateOnly(dueDate);
|
|
153
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type DocsGraph, type DocsNote, type DocsWikiLink } from "./types";
|
|
2
|
+
export declare function extractDocsWikiLinks(note: DocsNote): DocsWikiLink[];
|
|
3
|
+
export declare function extractDocsTags(value: string): string[];
|
|
4
|
+
export declare function normalizeDocsTag(tag: string): string;
|
|
5
|
+
export declare function docsTagMatchesFolder(tag: string, folder: string): boolean;
|
|
6
|
+
type DocsNoteTagSource = Pick<DocsNote, "tags" | "aliases"> & Partial<Pick<DocsNote, "cards" | "blocks" | "body" | "title" | "docType" | "summary" | "learningGoal">>;
|
|
7
|
+
/** Note, card, and inline body tags used for folder membership and filtering. */
|
|
8
|
+
export declare function docsNoteTagValues(note: DocsNoteTagSource): string[];
|
|
9
|
+
export declare function docsNoteMatchesTagFolder(note: DocsNoteTagSource, folder: string): boolean;
|
|
10
|
+
export declare function resolveDocsNoteId(notes: Array<Pick<DocsNote, "id" | "title" | "aliases">>, target: string | null | undefined): string | null;
|
|
11
|
+
export declare function buildDocsGraph(notes: DocsNote[]): DocsGraph;
|
|
12
|
+
export {};
|