@lincyaw/kernel 1.0.0-rc.0 → 1.0.0-rc.2
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 +7 -7
- package/package.json +6 -2
- package/src/access.ts +18 -0
- package/src/anchors.ts +5 -1
- package/src/authoring.ts +56 -0
- package/src/blocks.ts +0 -8
- package/src/component-code.ts +6 -0
- package/src/contracts/agent.ts +5 -82
- package/src/contracts/anchors.ts +46 -0
- package/src/contracts/authoring-actions.ts +60 -0
- package/src/contracts/authoring-clients.ts +213 -0
- package/src/contracts/authoring.ts +180 -0
- package/src/contracts/checks.ts +29 -0
- package/src/contracts/classroom.ts +49 -0
- package/src/contracts/diagnostics.ts +37 -0
- package/src/contracts/document-audio.ts +150 -0
- package/src/contracts/document-comments.ts +31 -0
- package/src/contracts/document-history.ts +63 -0
- package/src/contracts/document-image.ts +32 -0
- package/src/contracts/document-reads.ts +57 -0
- package/src/contracts/document-voice.ts +44 -0
- package/src/contracts/document.ts +240 -0
- package/src/contracts/execution.ts +4 -5
- package/src/contracts/locale.ts +3 -0
- package/src/contracts/offline.ts +2 -0
- package/src/contracts/plugin.ts +183 -0
- package/src/contracts/presenter-panels.ts +17 -0
- package/src/contracts/preview.ts +305 -310
- package/src/contracts/production-assets.ts +61 -0
- package/src/contracts/production.ts +109 -0
- package/src/contracts/projects.ts +59 -0
- package/src/contracts/server.ts +1 -51
- package/src/contracts/speech-settings.ts +16 -0
- package/src/contracts/speech.ts +82 -70
- package/src/contracts/steps.ts +15 -0
- package/src/contracts/transcription-adoption.ts +79 -0
- package/src/contracts/transcription.ts +112 -0
- package/src/contracts/voice.ts +97 -0
- package/src/contracts/voiceover-execution.ts +68 -0
- package/src/contracts/workspace.ts +458 -0
- package/src/contracts.ts +28 -278
- package/src/diagnostics.ts +7 -0
- package/src/document-authoring-actions.ts +47 -0
- package/src/document-caption-timings.ts +64 -0
- package/src/document-colors.ts +25 -0
- package/src/document-comments.ts +118 -0
- package/src/document-components.ts +251 -0
- package/src/document-operations.ts +132 -0
- package/src/document-pages.ts +215 -0
- package/src/document-reads.ts +81 -0
- package/src/document-schema.ts +85 -0
- package/src/document-voices.ts +153 -0
- package/src/document.ts +252 -0
- package/src/durations.ts +7 -0
- package/src/errors.ts +8 -0
- package/src/execution.ts +5 -1
- package/src/fingerprint.ts +1 -1
- package/src/follow.ts +26 -30
- package/src/git.ts +1 -1
- package/src/index.ts +66 -13
- package/src/ir.ts +54 -5
- package/src/jupyter-kernel.ts +9 -0
- package/src/kernel.ts +38 -25
- package/src/keyboard.ts +43 -0
- package/src/locale.ts +13 -0
- package/src/lock.ts +4 -8
- package/src/logging.ts +1 -1
- package/src/{markdown.ts → lower-myst.ts} +2 -1
- package/src/node/assets.ts +23 -0
- package/src/node/authoring-actions.ts +50 -0
- package/src/node/authoring-clients.ts +14 -0
- package/src/node/document-audio.ts +22 -0
- package/src/node/document-history.ts +11 -0
- package/src/node/document-image.ts +8 -0
- package/src/node/document-reads.ts +10 -0
- package/src/node/document-voice.ts +7 -0
- package/src/node/ffmpeg.ts +29 -0
- package/src/node/file-version.ts +6 -0
- package/src/node/fonts.ts +22 -0
- package/src/node/index.ts +29 -133
- package/src/node/installed-packages.ts +11 -0
- package/src/node/lines.ts +24 -0
- package/src/node/locale.ts +7 -0
- package/src/node/network.ts +11 -0
- package/src/node/operator-speech.ts +91 -0
- package/src/node/packages.ts +74 -0
- package/src/node/paths.ts +7 -0
- package/src/node/projects.ts +7 -0
- package/src/node/resources.ts +86 -0
- package/src/node/revision.ts +6 -0
- package/src/node/run-directory.ts +70 -0
- package/src/node/transcription-adoption.ts +9 -0
- package/src/node/transcription.ts +10 -0
- package/src/node/voiceover-routes.ts +73 -0
- package/src/node/voiceover.ts +199 -0
- package/src/page-source.ts +359 -0
- package/src/parameters.ts +81 -0
- package/src/preview-edit.ts +18 -0
- package/src/preview-events.ts +57 -0
- package/src/preview-frame.ts +1220 -0
- package/src/props.ts +17 -0
- package/src/push-stream.ts +67 -0
- package/src/reading.ts +12 -7
- package/src/recording.ts +38 -0
- package/src/renderer-components.ts +30 -0
- package/src/responses.ts +49 -0
- package/src/run-outputs.ts +15 -0
- package/src/runner.py +3 -3
- package/src/services.ts +6 -6
- package/src/slides.ts +36 -0
- package/src/speech-words.ts +76 -0
- package/src/speech.ts +13 -0
- package/src/step-keys.ts +38 -0
- package/src/steps.ts +7 -0
- package/src/transcription-adoption.ts +105 -0
- package/src/uri-decoding.ts +8 -0
- package/src/urls.ts +13 -0
- package/src/views.ts +47 -0
- package/src/voice.ts +86 -0
package/src/fingerprint.ts
CHANGED
package/src/follow.ts
CHANGED
|
@@ -1,37 +1,33 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { IRSection } from './ir.ts'
|
|
2
|
+
import { stepTrail, trailStep } from './step-keys.ts'
|
|
3
|
+
import type { StepKeys } from './step-keys.ts'
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
let left = false
|
|
9
|
-
const visit = (blocks: readonly IRBlock[], prefix: string) => {
|
|
10
|
-
for (const block of blocks) {
|
|
11
|
-
if (renderer !== undefined && block.only && !block.only.includes(renderer)) {
|
|
12
|
-
left = true
|
|
13
|
-
continue
|
|
14
|
-
}
|
|
15
|
-
const id = prefix + block.id
|
|
16
|
-
if (block.role === 'visual') {
|
|
17
|
-
for (let k = 0; k <= (block.span ?? 0); k++) add(block.at + k, k === 0 ? id : `${id}+${k}`)
|
|
18
|
-
if (block.until !== undefined) add(block.until, `${id}-`)
|
|
19
|
-
}
|
|
20
|
-
if (block.node.type === 'component' && block.node.children) visit(block.node.children, `${id}/`)
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
visit(section.blocks, '')
|
|
24
|
-
return left ? keys.filter((step, i) => i === 0 || step.length > 0) : keys
|
|
5
|
+
/** The presenter's position by ID, so learners with another version of the document reach the same place. */
|
|
6
|
+
export interface FollowAnchor {
|
|
7
|
+
section?: string
|
|
8
|
+
trail?: string[]
|
|
9
|
+
back?: string
|
|
25
10
|
}
|
|
26
11
|
|
|
27
|
-
|
|
28
|
-
|
|
12
|
+
const LEARNERS = 'student'
|
|
13
|
+
|
|
14
|
+
/** For a section only some audiences have: the nearest earlier section that the other versions have too. */
|
|
15
|
+
export function backSection(sections: readonly Pick<IRSection, 'id' | 'audience'>[], id: string): string | undefined {
|
|
16
|
+
const at = sections.findIndex(section => section.id === id)
|
|
17
|
+
const audience = sections[at]?.audience
|
|
18
|
+
if (!audience?.length) return undefined
|
|
19
|
+
const learners = !audience.includes(LEARNERS)
|
|
20
|
+
return sections.slice(0, at).findLast(section => !section.audience?.length || (learners && section.audience.includes(LEARNERS)))?.id
|
|
29
21
|
}
|
|
30
22
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
23
|
+
/** Where a learner goes in their own version of the document when following the presenter's position. */
|
|
24
|
+
export function followedPlace(position: FollowAnchor & { step: number }, keysOf: (id: string) => StepKeys | undefined): { section: string, step: number } | undefined {
|
|
25
|
+
const { section, trail, back, step } = position
|
|
26
|
+
const keys = section === undefined ? undefined : keysOf(section)
|
|
27
|
+
if (keys) {
|
|
28
|
+
const other = trail !== undefined && stepTrail(keys, step).join('\n') !== trail.join('\n')
|
|
29
|
+
return { section: section!, step: other ? trailStep(keys, trail) : step }
|
|
35
30
|
}
|
|
36
|
-
|
|
31
|
+
const before = back === undefined ? undefined : keysOf(back)
|
|
32
|
+
return before && { section: back!, step: before.length - 1 }
|
|
37
33
|
}
|
package/src/git.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
const GIT_SAFE_CONFIG: readonly string[] = ['core.hooksPath=/dev/null', 'core.fsmonitor=false', 'core.quotePath=false', 'commit.gpgSign=false', 'tag.gpgSign=false']
|
|
2
2
|
|
|
3
3
|
export function gitConfigArgs(): string[] {
|
|
4
4
|
return GIT_SAFE_CONFIG.flatMap(entry => ['-c', entry])
|
package/src/index.ts
CHANGED
|
@@ -1,29 +1,82 @@
|
|
|
1
1
|
export * from './contracts.ts'
|
|
2
|
+
export { applyDocumentOperations } from './document-operations.ts'
|
|
3
|
+
export { validateDocumentChange, DOCUMENT_CHANGE_JSON_SCHEMA } from './authoring.ts'
|
|
4
|
+
export { allocateDocumentPage, createDocumentPage, documentPageDefaults, DOCUMENT_PAGE_DEFAULT_TIMING, duplicateDocumentPage, moveDocumentPage, pageContents, pageDeletion, pageDeletionImpact, removeDocumentPage, renameDocumentPage } from './document-pages.ts'
|
|
5
|
+
export { documentOutline, documentPart, DocumentReadError } from './document-reads.ts'
|
|
6
|
+
export { changeDocumentVoice, documentVoiceStepIds, previewDocumentVoiceChange, resolveDocumentStepVoice } from './document-voices.ts'
|
|
7
|
+
export { DOCUMENT_COLOR_NAMES, documentColorToken, documentColorValue } from './document-colors.ts'
|
|
8
|
+
export type { DocumentColorKind, DocumentColorName } from './document-colors.ts'
|
|
9
|
+
export { applyDocumentCommentAction, isDocumentTextLink, documentCommentTargets, validateDocumentCommentAnchor, DocumentCommentError } from './document-comments.ts'
|
|
10
|
+
export { applyDocumentAuthoringAction } from './document-authoring-actions.ts'
|
|
11
|
+
export { applyDocumentComponentAction, effectiveComponentParameters, createDocumentParameterChanges, documentComponentSection, DocumentComponentError } from './document-components.ts'
|
|
12
|
+
export type { DocumentStepVoice, DocumentVoiceImpact, DocumentVoiceOptions, DocumentVoiceScope } from './document-voices.ts'
|
|
13
|
+
export { prepareTranscriptionAdoption } from './transcription-adoption.ts'
|
|
14
|
+
export { productionAssetIds, recordingStepIssue, recordingSourceRange } from './recording.ts'
|
|
2
15
|
export * from './ir.ts'
|
|
3
|
-
export { createAnchorResolverRegistry, parseAnchor } from './anchors.ts'
|
|
16
|
+
export { createAnchorResolverRegistry, isRenderable, parseAnchor } from './anchors.ts'
|
|
4
17
|
export type { ParsedAnchor } from './anchors.ts'
|
|
5
|
-
export {
|
|
18
|
+
export { diagnosticOf } from './diagnostics.ts'
|
|
19
|
+
export { DOCUMENT_JSON_SCHEMA, validateDocument } from './document.ts'
|
|
20
|
+
export { validateDocumentParameters, documentParameterAccess, documentParameterSchemaForAccess } from './parameters.ts'
|
|
21
|
+
export type { DocumentParameterAccess, DocumentParameterIssue, DocumentParameterSchemaReference, DocumentParameterValidation } from './parameters.ts'
|
|
22
|
+
export { docBlocks, irBlocks, nodeMdast } from './blocks.ts'
|
|
6
23
|
export { CheckRegistry } from './checks.ts'
|
|
7
|
-
export { errorMessage, isRecord } from './errors.ts'
|
|
24
|
+
export { errorMessage, isRecord, PreviewPreparing } from './errors.ts'
|
|
8
25
|
export { createEventBus } from './events.ts'
|
|
9
|
-
export { CREDENTIAL_VARIABLE, failedRun, languageId } from './execution.ts'
|
|
10
|
-
export {
|
|
26
|
+
export { CREDENTIAL_VARIABLE, environmentExecutor, failedRun, languageId } from './execution.ts'
|
|
27
|
+
export { gitConfigArgs, gitEnvironment } from './git.ts'
|
|
11
28
|
export { escapeHtml, htmlAttributes, mapHtmlUrls, URL_ATTRIBUTES } from './html.ts'
|
|
12
29
|
export type { HtmlAttribute } from './html.ts'
|
|
13
30
|
export { hostName, localRequestRefusal } from './requests.ts'
|
|
14
31
|
export type { RequestHeaders } from './requests.ts'
|
|
15
|
-
export { fingerprint, fnv1a64
|
|
16
|
-
export {
|
|
17
|
-
export type {
|
|
32
|
+
export { fingerprint, fnv1a64 } from './fingerprint.ts'
|
|
33
|
+
export { applyDocumentCaptionTiming, captionSourceFingerprint, captionTimingProblem, stepCaptionText, narrationCaptionText, CAPTION_TIMING_MAX_SEGMENTS, DocumentCaptionTimingError } from './document-caption-timings.ts'
|
|
34
|
+
export type { DocumentCaptionTimingAction } from './document-caption-timings.ts'
|
|
35
|
+
export { backSection, followedPlace } from './follow.ts'
|
|
36
|
+
export type { FollowAnchor } from './follow.ts'
|
|
18
37
|
export { Kernel } from './kernel.ts'
|
|
19
38
|
export type { KernelOptions } from './kernel.ts'
|
|
39
|
+
export { deckLocale, localeOf } from './locale.ts'
|
|
20
40
|
export { ANCHOR_LOCK_PATH, AnchorLock } from './lock.ts'
|
|
21
|
-
export {
|
|
22
|
-
export { lowerMyst, roleMark } from './
|
|
23
|
-
export type { LinkedType, LoweredMarkdown, LowerOptions } from './
|
|
41
|
+
export { prefixedLogger } from './logging.ts'
|
|
42
|
+
export { lowerMyst, roleMark } from './lower-myst.ts'
|
|
43
|
+
export type { LinkedType, LoweredMarkdown, LowerOptions } from './lower-myst.ts'
|
|
44
|
+
export { connectPreviewFrame, isPreviewActive, isPreviewEmbedded, previewAppearance, previewFrameAncestors, reportPreview } from './preview-frame.ts'
|
|
45
|
+
export { keyBelongsToTarget, watchFocusSource } from './keyboard.ts'
|
|
46
|
+
export type { PreviewFrame, PreviewPage, PreviewProblem, PreviewTextEdit, PreviewTextHandle } from './preview-frame.ts'
|
|
47
|
+
export { previewEditBinding } from './preview-edit.ts'
|
|
48
|
+
export { pageEditCapability, pinPageSource, renamePageSource, movePageSource, removePageSource, pageRemovalTargets, pageIncomingReferences, writePage } from './page-source.ts'
|
|
49
|
+
export type { PageEditCapability, PageEditReason, PageRemoval } from './page-source.ts'
|
|
50
|
+
export { propText, withProp } from './props.ts'
|
|
24
51
|
export { publishedUrl } from './published.ts'
|
|
25
52
|
export type { PublishedUrlOptions } from './published.ts'
|
|
26
|
-
export { blockState, blockText, blockVisible, chapters, isChapterTitle, mdastText, narrationAt, sectionNarration, tokenize } from './reading.ts'
|
|
53
|
+
export { blockState, blockText, blockVisible, blockStepState, chapters, isChapterTitle, mdastText, narrationAt, sectionNarration, tokenize } from './reading.ts'
|
|
27
54
|
export type { Chapter } from './reading.ts'
|
|
28
|
-
export {
|
|
55
|
+
export { slideChapters, slideShape } from './slides.ts'
|
|
56
|
+
export { compactSteps, viewFor } from './views.ts'
|
|
57
|
+
export type { SlideShape } from './slides.ts'
|
|
58
|
+
export { jsonResponse, problemResponse, sseResponse } from './responses.ts'
|
|
59
|
+
export { kernelIncompatibility, ServiceRegistry } from './services.ts'
|
|
29
60
|
export type { StartPlan } from './services.ts'
|
|
61
|
+
export { remapWords, splitWords } from './speech-words.ts'
|
|
62
|
+
export type { TextWord, WordSpan, WrittenWord } from './speech-words.ts'
|
|
63
|
+
export { stepKeys, stepTrail, trailStep } from './step-keys.ts'
|
|
64
|
+
export type { StepKeys } from './step-keys.ts'
|
|
65
|
+
export { parseDuration } from './durations.ts'
|
|
66
|
+
export { stepStateAt } from './steps.ts'
|
|
67
|
+
export { ABSOLUTE_URL, NON_FILE_URL, urlSlug } from './urls.ts'
|
|
68
|
+
export { isButton } from './lower-myst.ts'
|
|
69
|
+
export { componentProps, findComponentRenderer, hasRendererExtension, WEB_RENDERER_PACKAGE, withPublishedAssets } from './renderer-components.ts'
|
|
70
|
+
export { htmlAsText, isProseRun } from './run-outputs.ts'
|
|
71
|
+
export { safeDecodeURIComponent } from './uri-decoding.ts'
|
|
72
|
+
export { APPROVAL_KINDS, hasRole, isPresentingRole, LOCAL_AUTHOR } from './access.ts'
|
|
73
|
+
export { pushStream } from './push-stream.ts'
|
|
74
|
+
export type { PushStream, PushStreamOptions } from './push-stream.ts'
|
|
75
|
+
export { aigcLabel, aigcString, speechEngineId } from './speech.ts'
|
|
76
|
+
export { documentVoice, resolvedVoice, speechVoice, stepVoiceReason, voiceOverrides, voiceTarget } from './voice.ts'
|
|
77
|
+
export { isCjk } from './speech-words.ts'
|
|
78
|
+
export { componentCode } from './component-code.ts'
|
|
79
|
+
export { jupyterKernelLanguage } from './jupyter-kernel.ts'
|
|
80
|
+
|
|
81
|
+
export { createPreviewEvents } from './preview-events.ts'
|
|
82
|
+
export type { PreviewEvents } from './preview-events.ts'
|
package/src/ir.ts
CHANGED
|
@@ -26,10 +26,21 @@ export interface SourcePos {
|
|
|
26
26
|
file: string
|
|
27
27
|
line: number
|
|
28
28
|
endLine?: number
|
|
29
|
+
format?: 'json'
|
|
30
|
+
pointer?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Compiler-owned identity of the exact canonical authoring entry behind a runtime snapshot. */
|
|
34
|
+
export interface CanonicalSource {
|
|
35
|
+
documentId: string
|
|
36
|
+
version: string
|
|
37
|
+
fingerprint: string
|
|
29
38
|
}
|
|
30
39
|
|
|
31
40
|
export interface IRDocument {
|
|
32
41
|
ir: typeof IR_VERSION
|
|
42
|
+
/** Read-only provenance, never authored metadata or editable document content. */
|
|
43
|
+
source?: CanonicalSource
|
|
33
44
|
meta: {
|
|
34
45
|
title: string
|
|
35
46
|
entry: string
|
|
@@ -46,6 +57,9 @@ export interface IRDocument {
|
|
|
46
57
|
}
|
|
47
58
|
sections: IRSection[]
|
|
48
59
|
diagnostics: Finding[]
|
|
60
|
+
production?: import('./contracts/document.ts').Production
|
|
61
|
+
/** Canonical media declarations available to production renderers without reopening authoring JSON. */
|
|
62
|
+
productionAssets?: Record<string, import('./contracts/document.ts').Asset>
|
|
49
63
|
}
|
|
50
64
|
|
|
51
65
|
export interface IRSection {
|
|
@@ -56,6 +70,11 @@ export interface IRSection {
|
|
|
56
70
|
source: SourcePos
|
|
57
71
|
steps: number
|
|
58
72
|
stepMap?: number[]
|
|
73
|
+
stepIds?: string[]
|
|
74
|
+
/** Display-only canonical narration by stable step, without an audio or timing claim. */
|
|
75
|
+
stepNarrations?: Record<string, { id: string, text: string }[]>
|
|
76
|
+
/** Exact canonical per-step caption basis, independent of overall source version and preceding steps. */
|
|
77
|
+
captionSources?: Record<string, string>
|
|
59
78
|
hints: Record<RendererId, Record<string, unknown>>
|
|
60
79
|
config?: Record<string, Record<string, unknown>>
|
|
61
80
|
blocks: IRBlock[]
|
|
@@ -74,6 +93,8 @@ export interface IRBlock {
|
|
|
74
93
|
audience?: string[]
|
|
75
94
|
source: SourcePos
|
|
76
95
|
node: IRNode
|
|
96
|
+
/** Complete resolved state at every section step, derived from the authoring graph. */
|
|
97
|
+
stepStates?: { visible: boolean, state?: string, parameters?: Record<string, unknown>, assets?: Record<string, string>, localStep: number }[]
|
|
77
98
|
}
|
|
78
99
|
|
|
79
100
|
export type IRNode = MarkdownNode | CodeNode | ComponentNode
|
|
@@ -82,6 +103,8 @@ export interface MarkdownNode {
|
|
|
82
103
|
type: 'markdown'
|
|
83
104
|
mdast: MdastNode
|
|
84
105
|
reveal?: 'items'
|
|
106
|
+
/** An explicitly authored step's speech starts with, or after, its animation. */
|
|
107
|
+
narration?: { mode: 'during' | 'after' }
|
|
85
108
|
}
|
|
86
109
|
|
|
87
110
|
export interface CodeOrigin {
|
|
@@ -129,6 +152,8 @@ export interface CodeNode {
|
|
|
129
152
|
outputs?: RecordedRun[]
|
|
130
153
|
variants?: CodeVariant[]
|
|
131
154
|
echo?: false
|
|
155
|
+
/** Structured source references for the resolver pass, independent of authoring syntax. */
|
|
156
|
+
reference?: { anchor: AnchorString, pin?: string, repo?: string, highlightRegions?: string[], variants?: { anchor?: AnchorString, pin?: string, repo?: string, label: string, value?: string }[] }
|
|
132
157
|
}
|
|
133
158
|
|
|
134
159
|
export interface ComponentNode {
|
|
@@ -137,9 +162,13 @@ export interface ComponentNode {
|
|
|
137
162
|
name: string
|
|
138
163
|
props: Record<string, unknown>
|
|
139
164
|
children?: IRBlock[]
|
|
165
|
+
ownsCode?: boolean
|
|
140
166
|
fallback?: MdastNode
|
|
141
167
|
assets?: Record<string, string>
|
|
142
168
|
layer?: 'overlay'
|
|
169
|
+
durations?: number[]
|
|
170
|
+
/** The authored code that defines this component instance, when supplied by its plugin. */
|
|
171
|
+
definition?: { file: string, language: string, exportName?: string }
|
|
143
172
|
}
|
|
144
173
|
|
|
145
174
|
export type DirectiveOptionType = 'string' | 'number' | 'boolean' | 'list'
|
|
@@ -233,24 +262,32 @@ export interface RenderContext {
|
|
|
233
262
|
outDir: string
|
|
234
263
|
workDir: string
|
|
235
264
|
config: Record<string, unknown>
|
|
236
|
-
extensions:
|
|
265
|
+
extensions: BoundRendererExtension[]
|
|
237
266
|
plugins: string[]
|
|
238
267
|
pluginDirs: Record<string, string>
|
|
239
268
|
lang?: string
|
|
240
269
|
renderers: RendererId[]
|
|
241
270
|
base: string
|
|
242
|
-
|
|
271
|
+
nodeVitePlugins?: NodeVitePlugin[]
|
|
243
272
|
extensionFor(node: ComponentNode): ((node: ComponentNode, block: IRBlock) => unknown) | undefined
|
|
244
273
|
portable(value: unknown, from: string): unknown
|
|
245
274
|
localAsset(source: SourcePos, url: string): LocalAsset | undefined
|
|
246
275
|
documentLink?(source: SourcePos, url: string): DocumentLink | undefined
|
|
247
276
|
report(finding: Finding): void
|
|
277
|
+
/** A line of the build's output (a tool's), for the job that runs it; renderers that start tools give theirs. */
|
|
278
|
+
log?(line: string): void
|
|
248
279
|
service?<T>(token: ServiceToken<T>): T | undefined
|
|
249
280
|
translations?: { lang: string, doc: IRDocument }[]
|
|
250
281
|
server?: { url: string }
|
|
251
282
|
preview?: { origins: string[] }
|
|
252
283
|
}
|
|
253
284
|
|
|
285
|
+
export interface NodeVitePlugin {
|
|
286
|
+
id: string
|
|
287
|
+
entry: string
|
|
288
|
+
config: Record<string, unknown>
|
|
289
|
+
}
|
|
290
|
+
|
|
254
291
|
export interface LocalAsset {
|
|
255
292
|
file: string
|
|
256
293
|
name: string
|
|
@@ -276,8 +313,7 @@ export interface RenderDevServer {
|
|
|
276
313
|
export interface Renderer {
|
|
277
314
|
id: RendererId
|
|
278
315
|
site?: boolean
|
|
279
|
-
support(node: IRNode, extensions:
|
|
280
|
-
prepare?(doc: IRDocument, ctx: RenderContext): Promise<void>
|
|
316
|
+
support(node: IRNode, extensions: BoundRendererExtension[]): Support
|
|
281
317
|
build(doc: IRDocument, ctx: RenderContext): Promise<RenderOutput>
|
|
282
318
|
dev?(doc: IRDocument, ctx: RenderContext): Promise<RenderDevServer>
|
|
283
319
|
buildSite?(site: SiteSpec, ctx: RenderContext): Promise<RenderOutput>
|
|
@@ -287,10 +323,12 @@ export interface Renderer {
|
|
|
287
323
|
export interface SiteDocument {
|
|
288
324
|
dir: string
|
|
289
325
|
title: string
|
|
326
|
+
description?: string
|
|
290
327
|
lang?: string
|
|
291
328
|
tags?: string[]
|
|
292
329
|
doc?: IRDocument
|
|
293
330
|
translations?: { lang: string, doc: IRDocument }[]
|
|
331
|
+
nodeVitePlugins?: NodeVitePlugin[]
|
|
294
332
|
}
|
|
295
333
|
|
|
296
334
|
export interface SiteSpec {
|
|
@@ -303,6 +341,17 @@ export interface SiteSpec {
|
|
|
303
341
|
export interface SiteDevServer {
|
|
304
342
|
url: string
|
|
305
343
|
update(dir: string, doc: IRDocument): Promise<void>
|
|
344
|
+
/** Starts the dev server of document `dir` ahead of its first request (the author is about to open it); may decline. */
|
|
345
|
+
warm?(dir: string): Promise<void>
|
|
346
|
+
/** The origin of the dev server that serves `path` (started on first use), so that a host forwards to it directly. */
|
|
347
|
+
target?(path: string): Promise<string>
|
|
348
|
+
/**
|
|
349
|
+
* The address (below the site's URL) of the workbench's viewer app showing document `dir`, once the app (prebuilt for
|
|
350
|
+
* the workspace's plugins) and the document's own bundle are built: they are started when missing, and waited for a
|
|
351
|
+
* while with `wait`. Undefined until then, and for a document whose plugins the app cannot run (its dev server shows it).
|
|
352
|
+
* With `wait`, a build still running after the wait rejects with `PreviewPreparing`; a failed build with its reason.
|
|
353
|
+
*/
|
|
354
|
+
app?(dir: string, wait?: boolean): Promise<string | undefined>
|
|
306
355
|
close(): Promise<void>
|
|
307
356
|
}
|
|
308
357
|
|
|
@@ -312,7 +361,7 @@ export interface RendererContribution {
|
|
|
312
361
|
components?: Record<string, ComponentRenderer>
|
|
313
362
|
}
|
|
314
363
|
|
|
315
|
-
export interface
|
|
364
|
+
export interface BoundRendererExtension {
|
|
316
365
|
plugin: string
|
|
317
366
|
components?: Record<string, (node: ComponentNode, block: IRBlock) => unknown>
|
|
318
367
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { isRecord } from './errors.ts'
|
|
2
|
+
import { languageId } from './execution.ts'
|
|
3
|
+
|
|
4
|
+
/** The language of the notebook's Jupyter kernel, as `renderers.notebook.kernel` names it (a kernel name or `{ name, language }`); default Python. */
|
|
5
|
+
export function jupyterKernelLanguage(kernel: unknown): string {
|
|
6
|
+
if (typeof kernel === 'string' && kernel) return languageId(kernel.replace(/[-\d.]+$/, '') || kernel)
|
|
7
|
+
if (isRecord(kernel) && typeof kernel.language === 'string') return languageId(kernel.language)
|
|
8
|
+
return 'python'
|
|
9
|
+
}
|
package/src/kernel.ts
CHANGED
|
@@ -11,9 +11,9 @@ import type {
|
|
|
11
11
|
ServiceToken,
|
|
12
12
|
StartReport,
|
|
13
13
|
} from './contracts.ts'
|
|
14
|
-
import { errorMessage } from './errors.ts'
|
|
14
|
+
import { errorMessage, isRecord } from './errors.ts'
|
|
15
15
|
import { createEventBus } from './events.ts'
|
|
16
|
-
import {
|
|
16
|
+
import { prefixedLogger } from './logging.ts'
|
|
17
17
|
import { ServiceRegistry } from './services.ts'
|
|
18
18
|
|
|
19
19
|
export interface KernelOptions {
|
|
@@ -34,6 +34,8 @@ export class Kernel {
|
|
|
34
34
|
#environments?: Readonly<Record<string, EnvironmentSpec>>
|
|
35
35
|
#plugins: PluginDefinition[] = []
|
|
36
36
|
#state: 'idle' | 'starting' | 'started' | 'disposed' = 'idle'
|
|
37
|
+
#starting?: Promise<StartReport>
|
|
38
|
+
#disposing?: Promise<void>
|
|
37
39
|
#report?: StartReport
|
|
38
40
|
|
|
39
41
|
constructor(options: KernelOptions) {
|
|
@@ -54,8 +56,8 @@ export class Kernel {
|
|
|
54
56
|
this.#plugins.push(plugin)
|
|
55
57
|
}
|
|
56
58
|
|
|
57
|
-
provide<T>(token: ServiceToken<T>,
|
|
58
|
-
this.services.provide(token,
|
|
59
|
+
provide<T>(token: ServiceToken<T>, implementation: T): void {
|
|
60
|
+
this.services.provide(token, implementation)
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
getService<T>(token: ServiceToken<T>): T | undefined {
|
|
@@ -66,9 +68,13 @@ export class Kernel {
|
|
|
66
68
|
return this.#report
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
|
|
70
|
-
if (this.#state !== 'idle')
|
|
71
|
+
start(): Promise<StartReport> {
|
|
72
|
+
if (this.#state !== 'idle') return Promise.reject(new Error('kernel already started'))
|
|
71
73
|
this.#state = 'starting'
|
|
74
|
+
return this.#starting = Promise.resolve().then(() => this.#start())
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async #start(): Promise<StartReport> {
|
|
72
78
|
const plan = this.services.plan(this.#plugins.map(p => p.manifest), this.version)
|
|
73
79
|
const reports = new Map<string, PluginReport>()
|
|
74
80
|
for (const [id, reason] of plan.excluded) reports.set(id, this.#disable(id, reason))
|
|
@@ -87,26 +93,26 @@ export class Kernel {
|
|
|
87
93
|
reports.set(id, await this.#setup(plugin, missing))
|
|
88
94
|
}
|
|
89
95
|
|
|
90
|
-
this.#report = { order, plugins: this.#plugins.map(p => reports.get(p.manifest.id)
|
|
91
|
-
this.#state = 'started'
|
|
96
|
+
this.#report = { order, plugins: this.#plugins.map(p => ({ ...reports.get(p.manifest.id)!, ...p.manifest.name ? { name: p.manifest.name } : {} })), cycles: plan.cycles }
|
|
97
|
+
if (this.#state !== 'disposed') this.#state = 'started'
|
|
92
98
|
return this.#report
|
|
93
99
|
}
|
|
94
100
|
|
|
95
|
-
|
|
96
|
-
if (this.#
|
|
101
|
+
dispose(): Promise<void> {
|
|
102
|
+
if (this.#disposing) return this.#disposing
|
|
97
103
|
this.#state = 'disposed'
|
|
104
|
+
return this.#disposing = this.#dispose()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async #dispose(): Promise<void> {
|
|
108
|
+
await this.#starting?.catch(() => undefined)
|
|
98
109
|
const running = new Set(
|
|
99
110
|
this.#report?.plugins.filter(r => r.status === 'started' || r.status === 'degraded').map(r => r.id),
|
|
100
111
|
)
|
|
101
112
|
for (const id of [...(this.#report?.order ?? [])].reverse()) {
|
|
102
113
|
if (!running.has(id)) continue
|
|
103
114
|
const plugin = this.#plugins.find(p => p.manifest.id === id)!
|
|
104
|
-
|
|
105
|
-
await plugin.dispose?.()
|
|
106
|
-
} catch (error) {
|
|
107
|
-
this.#log.error(`[presmium] ${id} dispose failed`, error)
|
|
108
|
-
}
|
|
109
|
-
this.services.revoke(id)
|
|
115
|
+
await this.#disposePlugin(plugin)
|
|
110
116
|
}
|
|
111
117
|
}
|
|
112
118
|
|
|
@@ -117,7 +123,7 @@ export class Kernel {
|
|
|
117
123
|
const unprovided = provides.filter(t => this.services.owner(t) !== id).map(t => t.id)
|
|
118
124
|
if (unprovided.length > 0) throw new Error(`declared but did not provide: ${unprovided.join(', ')}`)
|
|
119
125
|
} catch (error) {
|
|
120
|
-
this
|
|
126
|
+
await this.#disposePlugin(plugin)
|
|
121
127
|
const reason = errorMessage(error)
|
|
122
128
|
this.#log.error(`[presmium] ${id} failed to start`, error)
|
|
123
129
|
this.#emit({ type: 'kernel.plugin.failed', payload: { id, error: reason } })
|
|
@@ -132,6 +138,16 @@ export class Kernel {
|
|
|
132
138
|
return { id, status: 'started' }
|
|
133
139
|
}
|
|
134
140
|
|
|
141
|
+
async #disposePlugin(plugin: PluginDefinition): Promise<void> {
|
|
142
|
+
try {
|
|
143
|
+
await plugin.dispose?.()
|
|
144
|
+
} catch (error) {
|
|
145
|
+
this.#log.error(`[presmium] ${plugin.manifest.id} dispose failed`, error)
|
|
146
|
+
} finally {
|
|
147
|
+
this.services.revoke(plugin.manifest.id)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
135
151
|
#disable(id: string, reason: string, missing?: string[]): PluginReport {
|
|
136
152
|
this.#log.warn(`[presmium] ${id} disabled: ${reason}`)
|
|
137
153
|
this.#emit({ type: 'kernel.plugin.disabled', payload: { id, reason } })
|
|
@@ -148,14 +164,15 @@ export class Kernel {
|
|
|
148
164
|
const assertDeclared = (token: ServiceToken<unknown>) => {
|
|
149
165
|
if (!declared.has(token.id)) throw new Error(`${id} did not declare service ${token.id} in its manifest`)
|
|
150
166
|
}
|
|
167
|
+
const config = this.#config[id]
|
|
151
168
|
return {
|
|
152
|
-
config:
|
|
169
|
+
config: isRecord(config) ? { ...config } : {},
|
|
153
170
|
mode: this.mode,
|
|
154
|
-
provide: (token,
|
|
171
|
+
provide: (token, implementation) => {
|
|
155
172
|
if (!provides.some(t => t.id === token.id)) {
|
|
156
173
|
throw new Error(`${id} provides ${token.id} without declaring it in manifest.provides`)
|
|
157
174
|
}
|
|
158
|
-
this.services.provide(token,
|
|
175
|
+
this.services.provide(token, implementation, id)
|
|
159
176
|
},
|
|
160
177
|
get: token => {
|
|
161
178
|
assertDeclared(token)
|
|
@@ -166,12 +183,8 @@ export class Kernel {
|
|
|
166
183
|
return this.services.tryGet(token)
|
|
167
184
|
},
|
|
168
185
|
events: this.events,
|
|
169
|
-
log:
|
|
186
|
+
log: prefixedLogger(this.#log, id),
|
|
170
187
|
...this.#environments ? { environments: this.#environments } : {},
|
|
171
188
|
}
|
|
172
189
|
}
|
|
173
190
|
}
|
|
174
|
-
|
|
175
|
-
function pluginConfig(value: unknown): Record<string, unknown> {
|
|
176
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value) ? { ...value } : {}
|
|
177
|
-
}
|
package/src/keyboard.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Which key presses a page shortcut may take. Text entry, editors and open menus or dialogs keep every key. A control
|
|
2
|
+
// the keyboard moved to keeps the keys it uses itself: Space presses a button or toggles a checkbox, and the arrows move
|
|
3
|
+
// a slider, a select, a pane separator, the items of a toolbar or tab list, or scroll a focusable region. A click leaves the focus on the
|
|
4
|
+
// clicked control; Space and the arrows then mean what they mean everywhere else on the page (play or pause, the next
|
|
5
|
+
// step) instead of pressing that control again.
|
|
6
|
+
|
|
7
|
+
const TEXT = 'textarea, [contenteditable]:not([contenteditable="false"]), input:not([type="button"], [type="checkbox"], [type="radio"], [type="range"], [type="reset"], [type="submit"], [type="color"], [type="file"], [type="image"])'
|
|
8
|
+
const LAYER = '[role="dialog"], [role="alertdialog"], [role="menu"], [role="listbox"]'
|
|
9
|
+
const PRESSABLE = 'button, a[href], summary, select, input, [role="button"], [role="link"], [role="checkbox"], [role="radio"], [role="switch"], [role="tab"], [role="option"], [role="treeitem"]'
|
|
10
|
+
const NAVIGABLE = 'select, input[type="range"], [role="slider"], [role="spinbutton"], [role="tablist"], [role="toolbar"], [role="radiogroup"], [role="grid"], [role="tree"], [role="menubar"]'
|
|
11
|
+
const NAVIGATION_KEYS = new Set(['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End', 'PageUp', 'PageDown'])
|
|
12
|
+
|
|
13
|
+
const sources = new WeakMap<Document, { pointer: boolean, byPointer: WeakSet<Element> }>()
|
|
14
|
+
|
|
15
|
+
/** Starts noting, in `document`, which elements took the focus from a pointer press. Call it before the first click. */
|
|
16
|
+
export function watchFocusSource(document: Document | undefined = globalThis.document): void {
|
|
17
|
+
if (!document || sources.has(document)) return
|
|
18
|
+
const source = { pointer: false, byPointer: new WeakSet<Element>() }
|
|
19
|
+
sources.set(document, source)
|
|
20
|
+
document.addEventListener('pointerdown', () => { source.pointer = true }, true)
|
|
21
|
+
document.addEventListener('keydown', () => { source.pointer = false }, true)
|
|
22
|
+
document.addEventListener('focusin', event => {
|
|
23
|
+
const target = event.composedPath()[0]
|
|
24
|
+
if (!(target instanceof Element)) return
|
|
25
|
+
if (source.pointer) source.byPointer.add(target)
|
|
26
|
+
else source.byPointer.delete(target)
|
|
27
|
+
}, true)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Whether a key press belongs to the element it was pressed on, so that a page shortcut leaves it alone. */
|
|
31
|
+
export function keyBelongsToTarget(event: KeyboardEvent): boolean {
|
|
32
|
+
if (event.isComposing || event.keyCode === 229) return true
|
|
33
|
+
const target = event.composedPath?.()[0] ?? event.target
|
|
34
|
+
if (!(target instanceof Element)) return false
|
|
35
|
+
if ((target as HTMLElement).isContentEditable || target.closest(TEXT) || target.closest(LAYER)) return true
|
|
36
|
+
if (event.key !== ' ' && !NAVIGATION_KEYS.has(event.key)) return false
|
|
37
|
+
// Resize handles keep their keys after pointer focus too; resizing must never
|
|
38
|
+
// navigate a teaching step or start its playback.
|
|
39
|
+
if (target.closest('[role="separator"]')) return true
|
|
40
|
+
if (sources.get(target.ownerDocument)?.byPointer.has(target)) return false
|
|
41
|
+
const region = (target as HTMLElement).tabIndex >= 0 && !target.matches(`${PRESSABLE}, ${NAVIGABLE}`)
|
|
42
|
+
return region || Boolean(target.closest(event.key === ' ' ? PRESSABLE : NAVIGABLE))
|
|
43
|
+
}
|
package/src/locale.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { DEFAULT_LANG } from './contracts/locale.ts'
|
|
2
|
+
import type { Locale } from './contracts/locale.ts'
|
|
3
|
+
|
|
4
|
+
export function localeOf(lang: string | null | undefined): Locale | undefined {
|
|
5
|
+
const tag = lang?.trim().toLowerCase() ?? ''
|
|
6
|
+
if (tag === 'zh' || tag.startsWith('zh-')) return 'zh-CN'
|
|
7
|
+
if (tag === 'en' || tag.startsWith('en-')) return 'en'
|
|
8
|
+
return undefined
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function deckLocale(lang?: string): Locale {
|
|
12
|
+
return localeOf(lang || DEFAULT_LANG) ?? 'en'
|
|
13
|
+
}
|
package/src/lock.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { isRenderable } from './
|
|
1
|
+
import { isRenderable } from './anchors.ts'
|
|
2
2
|
import type { AnchorLockFile, RecordedAnchor, Resolution } from './contracts.ts'
|
|
3
|
-
import { errorMessage } from './errors.ts'
|
|
3
|
+
import { errorMessage, isRecord } from './errors.ts'
|
|
4
4
|
|
|
5
5
|
export const ANCHOR_LOCK_PATH = '.presmium/anchors.lock.json'
|
|
6
6
|
|
|
@@ -67,11 +67,11 @@ export class AnchorLock {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
function validate(data: unknown): AnchorLockFile {
|
|
70
|
-
if (!
|
|
70
|
+
if (!isRecord(data) || data.version !== 1 || !isRecord(data.anchors)) {
|
|
71
71
|
throw new Error('invalid anchor lock: expected { "version": 1, "anchors": { ... } }')
|
|
72
72
|
}
|
|
73
73
|
for (const [anchor, record] of Object.entries(data.anchors)) {
|
|
74
|
-
const valid =
|
|
74
|
+
const valid = isRecord(record)
|
|
75
75
|
&& typeof record.fingerprint === 'string' && record.fingerprint !== ''
|
|
76
76
|
&& typeof record.file === 'string'
|
|
77
77
|
&& Number.isInteger(record.startLine) && (record.startLine as number) >= 1
|
|
@@ -79,7 +79,3 @@ function validate(data: unknown): AnchorLockFile {
|
|
|
79
79
|
}
|
|
80
80
|
return data as unknown as AnchorLockFile
|
|
81
81
|
}
|
|
82
|
-
|
|
83
|
-
function isObject(value: unknown): value is Record<string, unknown> {
|
|
84
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
85
|
-
}
|
package/src/logging.ts
CHANGED
|
@@ -198,7 +198,8 @@ function cardsAs(options: LowerOptions): 'element' | 'headings' {
|
|
|
198
198
|
return options.cards ?? (options.iframes === 'element' ? 'element' : 'headings')
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
|
|
201
|
+
/** Whether a MyST node has the class `button`, which renders a link or span as a button. */
|
|
202
|
+
export function isButton(node: MdastNode): boolean {
|
|
202
203
|
return typeof node.class === 'string' && node.class.split(/\s+/).includes('button')
|
|
203
204
|
}
|
|
204
205
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { cpSync, existsSync, mkdirSync } from 'node:fs'
|
|
2
|
+
import { join, resolve } from 'node:path'
|
|
3
|
+
import type { LocalAsset } from '../ir.ts'
|
|
4
|
+
|
|
5
|
+
/** Production assets are local to one document; hidden, remote and shared paths are not admitted. */
|
|
6
|
+
export function productionAssetFile(deckDir: string, location: string): string | undefined {
|
|
7
|
+
if (!location || /^(?:[a-z][a-z\d+.-]*:|\/)/i.test(location) || /[\\\0?#]/.test(location)) return undefined
|
|
8
|
+
let decoded: string
|
|
9
|
+
try { decoded = decodeURIComponent(location) } catch { return undefined }
|
|
10
|
+
if (/[\\\0?#]/.test(decoded)) return undefined
|
|
11
|
+
const parts = decoded.split('/')
|
|
12
|
+
if (parts.some(part => !part || part.startsWith('.') || part === 'node_modules' || part === 'dist' || part.includes(':'))) return undefined
|
|
13
|
+
return resolve(deckDir, ...parts)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function copyPublished(found: LocalAsset, dir: string): string {
|
|
17
|
+
const target = join(dir, found.name)
|
|
18
|
+
if (!existsSync(target)) {
|
|
19
|
+
mkdirSync(dir, { recursive: true })
|
|
20
|
+
cpSync(found.file, target, { recursive: true })
|
|
21
|
+
}
|
|
22
|
+
return target
|
|
23
|
+
}
|