@ecsplico/editor 3.1.0 → 3.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 +41 -0
- package/dist/adapters/direct-ai.d.ts +36 -0
- package/dist/adapters/direct-ai.js +169 -0
- package/dist/adapters/direct-ai.js.map +1 -0
- package/dist/adapters/memory.js.map +1 -1
- package/dist/code/CodeSurface.vue.d.ts +2 -2
- package/dist/core/block-drag.js +3 -0
- package/dist/core/block-drag.js.map +1 -1
- package/dist/core/block-transfer.d.ts +44 -0
- package/dist/core/block-transfer.js +68 -0
- package/dist/core/block-transfer.js.map +1 -0
- package/dist/core/commands.js +5 -0
- package/dist/core/commands.js.map +1 -1
- package/dist/core/core-feature.d.ts +4 -1
- package/dist/core/core-feature.js +8 -3
- package/dist/core/core-feature.js.map +1 -1
- package/dist/core/editor-ui.d.ts +3 -1
- package/dist/core/editor-ui.js.map +1 -1
- package/dist/core/extension-points.d.ts +17 -1
- package/dist/core/feature.d.ts +6 -1
- package/dist/core/feature.js.map +1 -1
- package/dist/core/index.d.ts +4 -2
- package/dist/core/index.js +3 -2
- package/dist/core/schema/index.d.ts +24 -3
- package/dist/core/schema/index.js +21 -4
- package/dist/core/schema/index.js.map +1 -1
- package/dist/core/surface-session.js +3 -1
- package/dist/core/surface-session.js.map +1 -1
- package/dist/core/toolbar-items.js +1 -1
- package/dist/core/toolbar-items.js.map +1 -1
- package/dist/document/DocumentSurface.vue.d.ts +2 -2
- package/dist/features/ai/agent-op-review.js +1 -1
- package/dist/features/ai/agent-ops.js +2 -2
- package/dist/features/ai/ai-apply.js +1 -1
- package/dist/features/ai/assistant.js +1 -1
- package/dist/features/ai/components/AiSelectionMenu.js +2 -2
- package/dist/features/ai/components/AiSidebar.js +2 -2
- package/dist/features/ai/components/SettingsAiTab.js +1 -1
- package/dist/features/ai/components/SettingsModelProfiles.js +1 -1
- package/dist/features/ai/document-chat-model.js +1 -1
- package/dist/features/ai/document-chat.js +2 -2
- package/dist/features/ai/ghost-autocomplete.js +1 -1
- package/dist/features/ai/index.d.ts +4 -3
- package/dist/features/ai/index.js +6 -5
- package/dist/features/ai/protocol/complete.d.ts +19 -0
- package/dist/features/ai/protocol/complete.js +27 -1
- package/dist/features/ai/protocol/complete.js.map +1 -1
- package/dist/features/ai/protocol/generate.d.ts +95 -0
- package/dist/features/ai/protocol/generate.js +58 -0
- package/dist/features/ai/protocol/generate.js.map +1 -0
- package/dist/features/ai/protocol/index.d.ts +4 -3
- package/dist/features/ai/protocol/index.js +6 -5
- package/dist/features/ai/protocol/tasks.d.ts +3 -1
- package/dist/features/ai/protocol/tasks.js +74 -2
- package/dist/features/ai/protocol/tasks.js.map +1 -1
- package/dist/features/ai/react/AiSelectionMenu.js +2 -2
- package/dist/features/ai/react/AiSidebar.js +2 -2
- package/dist/features/ai/react/SettingsAiTab.js +1 -1
- package/dist/features/ai/react/SettingsModelProfiles.js +1 -1
- package/dist/features/ai/selection-flow.js +1 -1
- package/dist/features/ai/sidebar-flow.js +1 -1
- package/dist/features/block-ui/block-actions.d.ts +19 -3
- package/dist/features/block-ui/block-actions.js +49 -3
- package/dist/features/block-ui/block-actions.js.map +1 -1
- package/dist/features/block-ui/components/BlockActionsOverlay.js +24 -13
- package/dist/features/block-ui/components/BlockActionsOverlay.js.map +1 -1
- package/dist/features/block-ui/components/BlockActionsOverlay.vue.d.ts +12 -5
- package/dist/features/block-ui/components/BlockUi.js +52 -20
- package/dist/features/block-ui/components/BlockUi.js.map +1 -1
- package/dist/features/block-ui/components/BlockUi.vue.d.ts +6 -4
- package/dist/features/block-ui/feature.d.ts +8 -2
- package/dist/features/block-ui/feature.js +7 -2
- package/dist/features/block-ui/feature.js.map +1 -1
- package/dist/features/block-ui/index.d.ts +3 -2
- package/dist/features/block-ui/index.js +2 -2
- package/dist/features/block-ui/react/BlockActionsOverlay.d.ts +14 -7
- package/dist/features/block-ui/react/BlockActionsOverlay.js +25 -13
- package/dist/features/block-ui/react/BlockActionsOverlay.js.map +1 -1
- package/dist/features/block-ui/react/BlockUi.d.ts +6 -4
- package/dist/features/block-ui/react/BlockUi.js +43 -15
- package/dist/features/block-ui/react/BlockUi.js.map +1 -1
- package/dist/features/block-ui/react.d.ts +4 -2
- package/dist/features/block-ui/react.js +3 -3
- package/dist/features/block-ui/react.js.map +1 -1
- package/dist/features/block-ui/vue.d.ts +4 -2
- package/dist/features/block-ui/vue.js +3 -3
- package/dist/features/block-ui/vue.js.map +1 -1
- package/dist/features/composites/baskets.d.ts +27 -0
- package/dist/features/composites/baskets.js +46 -0
- package/dist/features/composites/baskets.js.map +1 -0
- package/dist/features/composites/components/CompositeBasketMenu.js +97 -0
- package/dist/features/composites/components/CompositeBasketMenu.js.map +1 -0
- package/dist/features/composites/components/CompositeChrome.js +79 -23
- package/dist/features/composites/components/CompositeChrome.js.map +1 -1
- package/dist/features/composites/components/CompositeFieldEditor.js +1 -0
- package/dist/features/composites/components/CompositeFieldEditor.js.map +1 -1
- package/dist/features/composites/components/CompositeView.js +17 -2
- package/dist/features/composites/components/CompositeView.js.map +1 -1
- package/dist/features/composites/components/DerivationBadge.js +12 -3
- package/dist/features/composites/components/DerivationBadge.js.map +1 -1
- package/dist/features/composites/components/FocusChip.js +53 -0
- package/dist/features/composites/components/FocusChip.js.map +1 -0
- package/dist/features/composites/components/FocusChip.vue.d.ts +14 -0
- package/dist/features/composites/components/SettingsEditingTab.js +40 -4
- package/dist/features/composites/components/SettingsEditingTab.js.map +1 -1
- package/dist/features/composites/derivation.d.ts +2 -1
- package/dist/features/composites/derivation.js +5 -1
- package/dist/features/composites/derivation.js.map +1 -1
- package/dist/features/composites/derived-chrome.js +13 -3
- package/dist/features/composites/derived-chrome.js.map +1 -1
- package/dist/features/composites/feature.d.ts +7 -1
- package/dist/features/composites/feature.js +96 -23
- package/dist/features/composites/feature.js.map +1 -1
- package/dist/features/composites/focus.d.ts +38 -0
- package/dist/features/composites/focus.js +86 -0
- package/dist/features/composites/focus.js.map +1 -0
- package/dist/features/composites/index.d.ts +6 -3
- package/dist/features/composites/index.js +6 -3
- package/dist/features/composites/meta-keys.d.ts +4 -0
- package/dist/features/composites/meta-keys.js +8 -4
- package/dist/features/composites/meta-keys.js.map +1 -1
- package/dist/features/composites/nodes.d.ts +14 -2
- package/dist/features/composites/nodes.js +61 -5
- package/dist/features/composites/nodes.js.map +1 -1
- package/dist/features/composites/outline.d.ts +4 -0
- package/dist/features/composites/outline.js +3 -0
- package/dist/features/composites/outline.js.map +1 -1
- package/dist/features/composites/react/CompositeBasketMenu.js +88 -0
- package/dist/features/composites/react/CompositeBasketMenu.js.map +1 -0
- package/dist/features/composites/react/CompositeChrome.js +52 -4
- package/dist/features/composites/react/CompositeChrome.js.map +1 -1
- package/dist/features/composites/react/CompositeView.js +12 -2
- package/dist/features/composites/react/CompositeView.js.map +1 -1
- package/dist/features/composites/react/DerivationBadge.js +10 -1
- package/dist/features/composites/react/DerivationBadge.js.map +1 -1
- package/dist/features/composites/react/FocusChip.d.ts +11 -0
- package/dist/features/composites/react/FocusChip.js +50 -0
- package/dist/features/composites/react/FocusChip.js.map +1 -0
- package/dist/features/composites/react/SettingsEditingTab.js +36 -1
- package/dist/features/composites/react/SettingsEditingTab.js.map +1 -1
- package/dist/features/composites/react.d.ts +2 -1
- package/dist/features/composites/react.js +3 -1
- package/dist/features/composites/react.js.map +1 -1
- package/dist/features/composites/schema.d.ts +5 -3
- package/dist/features/composites/schema.js +8 -4
- package/dist/features/composites/schema.js.map +1 -1
- package/dist/features/composites/split.d.ts +62 -0
- package/dist/features/composites/split.js +172 -0
- package/dist/features/composites/split.js.map +1 -0
- package/dist/features/composites/vue.d.ts +4 -3
- package/dist/features/composites/vue.js +7 -5
- package/dist/features/composites/vue.js.map +1 -1
- package/dist/features/media/index.js +1 -1
- package/dist/features/media/react/AudioRecorderModal.js +1 -1
- package/dist/features/media/react.js +1 -1
- package/dist/features/transfer/components/TransferMenu.js +93 -0
- package/dist/features/transfer/components/TransferMenu.js.map +1 -0
- package/dist/features/transfer/components/TransferMenu.vue.d.ts +15 -0
- package/dist/features/transfer/feature.d.ts +76 -0
- package/dist/features/transfer/feature.js +169 -0
- package/dist/features/transfer/feature.js.map +1 -0
- package/dist/features/transfer/index.d.ts +3 -0
- package/dist/features/transfer/index.js +3 -0
- package/dist/features/transfer/react/TransferMenu.d.ts +12 -0
- package/dist/features/transfer/react/TransferMenu.js +71 -0
- package/dist/features/transfer/react/TransferMenu.js.map +1 -0
- package/dist/features/transfer/react.d.ts +10 -0
- package/dist/features/transfer/react.js +11 -0
- package/dist/features/transfer/react.js.map +1 -0
- package/dist/features/transfer/transforms.d.ts +83 -0
- package/dist/features/transfer/transforms.js +137 -0
- package/dist/features/transfer/transforms.js.map +1 -0
- package/dist/features/transfer/vue.d.ts +9 -0
- package/dist/features/transfer/vue.js +11 -0
- package/dist/features/transfer/vue.js.map +1 -0
- package/dist/i18n/de/ai.js +4 -0
- package/dist/i18n/de/ai.js.map +1 -1
- package/dist/i18n/de/composites.js +20 -1
- package/dist/i18n/de/composites.js.map +1 -1
- package/dist/i18n/de/integrations.js +16 -0
- package/dist/i18n/de/integrations.js.map +1 -1
- package/dist/i18n/de/transfer.js +17 -0
- package/dist/i18n/de/transfer.js.map +1 -0
- package/dist/i18n/de/tree.js +6 -0
- package/dist/i18n/de/tree.js.map +1 -1
- package/dist/i18n/de.d.ts +55 -0
- package/dist/i18n/de.js +3 -1
- package/dist/i18n/de.js.map +1 -1
- package/dist/i18n/en/ai.js +4 -0
- package/dist/i18n/en/ai.js.map +1 -1
- package/dist/i18n/en/composites.js +20 -1
- package/dist/i18n/en/composites.js.map +1 -1
- package/dist/i18n/en/integrations.js +16 -0
- package/dist/i18n/en/integrations.js.map +1 -1
- package/dist/i18n/en/transfer.js +17 -0
- package/dist/i18n/en/transfer.js.map +1 -0
- package/dist/i18n/en/tree.js +6 -0
- package/dist/i18n/en/tree.js.map +1 -1
- package/dist/i18n/en.js +3 -1
- package/dist/i18n/en.js.map +1 -1
- package/dist/integrations/composite-ai/composite-fill.js +1 -1
- package/dist/integrations/composite-ai/composite-summary.js +1 -1
- package/dist/integrations/composite-ai/derive.d.ts +21 -0
- package/dist/integrations/composite-ai/derive.js +69 -47
- package/dist/integrations/composite-ai/derive.js.map +1 -1
- package/dist/integrations/composite-ai/index.d.ts +8 -3
- package/dist/integrations/composite-ai/index.js +50 -4
- package/dist/integrations/composite-ai/index.js.map +1 -1
- package/dist/integrations/composite-ai/outline-sanitize.d.ts +48 -0
- package/dist/integrations/composite-ai/outline-sanitize.js +175 -0
- package/dist/integrations/composite-ai/outline-sanitize.js.map +1 -0
- package/dist/integrations/composite-ai/split.d.ts +44 -0
- package/dist/integrations/composite-ai/split.js +187 -0
- package/dist/integrations/composite-ai/split.js.map +1 -0
- package/dist/integrations/composite-ai/transfer-transforms.d.ts +27 -0
- package/dist/integrations/composite-ai/transfer-transforms.js +153 -0
- package/dist/integrations/composite-ai/transfer-transforms.js.map +1 -0
- package/dist/integrations/transcript-ai/protocol.d.ts +13 -0
- package/dist/integrations/transcript-ai/protocol.js +49 -1
- package/dist/integrations/transcript-ai/protocol.js.map +1 -1
- package/dist/integrations/transcript-ai/transcript-cleanup.js +1 -1
- package/dist/markdown/MarkdownSurface.vue.d.ts +6 -6
- package/dist/nuxt/module.d.ts +110 -0
- package/dist/presets/compose.d.ts +24 -8
- package/dist/presets/compose.js +117 -58
- package/dist/presets/compose.js.map +1 -1
- package/dist/react/compose.js +6 -2
- package/dist/react/compose.js.map +1 -1
- package/dist/react/composed-surface.d.ts +9 -0
- package/dist/react/composed-surface.js +4 -1
- package/dist/react/composed-surface.js.map +1 -1
- package/dist/server/ai.d.ts +2 -5
- package/dist/server/ai.js.map +1 -1
- package/dist/server/handlers/ai.d.ts +7 -78
- package/dist/server/handlers/ai.js +30 -94
- package/dist/server/handlers/ai.js.map +1 -1
- package/dist/server/handlers/editor.d.ts +1 -0
- package/dist/server/handlers/index.d.ts +3 -1
- package/dist/server/handlers/index.js +3 -1
- package/dist/server/index.d.ts +2 -1
- package/dist/settings/index.d.ts +8 -0
- package/dist/settings/index.js +16 -4
- package/dist/settings/index.js.map +1 -1
- package/dist/styles/composites.css +32 -0
- package/dist/tree/DocumentTree.js +229 -78
- package/dist/tree/DocumentTree.js.map +1 -1
- package/dist/tree/drop-rules.js +8 -0
- package/dist/tree/drop-rules.js.map +1 -1
- package/dist/tree/react/DocumentTree.js +152 -31
- package/dist/tree/react/DocumentTree.js.map +1 -1
- package/dist/tree/tree-host.js +30 -1
- package/dist/tree/tree-host.js.map +1 -1
- package/dist/tree/tree-items.js +52 -5
- package/dist/tree/tree-items.js.map +1 -1
- package/dist/tree/tree-model.js +113 -10
- package/dist/tree/tree-model.js.map +1 -1
- package/dist/tree/tree-rows.js +20 -7
- package/dist/tree/tree-rows.js.map +1 -1
- package/dist/tree/tree-view-model.d.ts +5 -0
- package/dist/tree/tree-view-model.js +13 -0
- package/dist/tree/tree-view-model.js.map +1 -1
- package/dist/vue/AdvancedEditor.js +6 -0
- package/dist/vue/AdvancedEditor.js.map +1 -1
- package/dist/vue/AdvancedEditor.vue.d.ts +13 -5
- package/dist/vue/SimpleEditor.vue.d.ts +4 -4
- package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
- package/dist/vue/compose.js +6 -2
- package/dist/vue/compose.js.map +1 -1
- package/dist/vue/kit/types.d.ts +2 -0
- package/package.json +21 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derivation.js","names":[],"sources":["../../../src/features/composites/derivation.ts"],"sourcesContent":["// Vue-free and server-importable: where a Baustein's content comes from. A source-holding Baustein (`recording`, `paper`,\n// `book`, `figure`, `document`) carries its original as an `image|audio|video|file` block in the body; a derived\n// Baustein (transcript, full text, description, AI result) records in `meta.derivation` what it was made from.\nimport type { Editor, JSONContent } from '@tiptap/core'\n// Types only: brings in the `editor.markdown` augmentation of `@tiptap/markdown` for contexts that do not otherwise load it.\nimport type {} from '@tiptap/markdown'\nimport type { Node as PMNode } from '@tiptap/pm/model'\nimport { EDITOR_API } from '../../core/api-routes'\nimport { type CompositeKind, compositeKind } from './kinds'\nimport { COMPOSITE_CHROME_COUNT } from './nodes'\nimport { DERIVATION_KEY } from './meta-keys'\n\n/** How a derived Baustein was made; `ai:<kindId>` for a block derived from another block by the model. */\nexport type DerivationMethod = 'transcription' | 'pdf-text' | 'ocr' | 'description' | 'bibliography' | (string & {})\n\n/** What a derived Baustein was made from: an uploaded asset (by asset name) or another block (by block id). */\nexport type DerivationSource = { asset: string } | { block: string }\n\n/** `meta.derivation` of a derived Baustein. */\nexport interface Derivation {\n from: DerivationSource\n method: DerivationMethod\n model?: string\n /** ISO timestamp. */\n createdAt: string\n /** SHA-256 hex of the asset bytes / of the source block's Markdown; `''` when unknown. */\n inputHash: string\n /** SHA-256 hex of the produced body's Markdown; `''` when unknown. */\n outputHash: string\n /** Asset name of the stored raw output (text/markdown). */\n output?: string\n}\n\nexport { DERIVATION_KEY }\n\n/** The derivation of a composite (`meta.derivation` with a `from` object and a `method`); null when it has none. */\nexport function derivationOf(node: PMNode): Derivation | null {\n const meta: unknown = node.attrs.meta\n const value = meta && typeof meta === 'object' ? (meta as Record<string, unknown>)[DERIVATION_KEY] : undefined\n if (!value || typeof value !== 'object') return null\n const { from, method } = value as Record<string, unknown>\n if (!from || typeof from !== 'object' || typeof method !== 'string') return null\n return value as Derivation\n}\n\nconst ASSET_URL_PREFIX = `${EDITOR_API}/assets/`\n// Same patterns as `features/media/assets.ts`; copied because composites must not import media.\nconst ASSET_NAME = /^[0-9a-f-]{36}\\.[a-z0-9]{1,8}$/\nconst ASSET_DOCUMENT_ID = /^[A-Za-z0-9_-]{1,64}$/\n\n/** The stored asset behind an `/api/editor/assets/<documentId>/<name>` URL; null for any other `src`. */\nexport function assetRefOf(src: string): { documentId: string, name: string } | null {\n if (!src.startsWith(ASSET_URL_PREFIX)) return null\n const [documentId, name, ...rest] = src.slice(ASSET_URL_PREFIX.length).split('/')\n if (rest.length || !documentId || !name || !ASSET_DOCUMENT_ID.test(documentId) || !ASSET_NAME.test(name)) return null\n return { documentId, name }\n}\n\n/** The media block a source-holding kind keeps its original in: first body child of a type in `kind.source`. */\nexport function sourceOf(\n composite: PMNode,\n kinds: readonly CompositeKind[],\n): { node: PMNode, index: number, asset: { documentId: string, name: string } | null, hash: string | null } | null {\n if (composite.type.name !== 'composite') return null\n const types = compositeKind(kinds, String(composite.attrs.kind)).source\n if (!types) return null\n for (let index = COMPOSITE_CHROME_COUNT; index < composite.childCount; index++) {\n const node = composite.child(index)\n if (!(types as readonly string[]).includes(node.type.name)) continue\n const hash: unknown = node.attrs.hash\n return { node, index, asset: assetRefOf(String(node.attrs.src ?? '')), hash: typeof hash === 'string' && hash ? hash : null }\n }\n return null\n}\n\n/** The body children of `composite` that are derived Bausteine, with their child index. */\nexport function renditionsOf(composite: PMNode): { node: PMNode, index: number, derivation: Derivation }[] {\n const found: { node: PMNode, index: number, derivation: Derivation }[] = []\n composite.forEach((node, _offset, index) => {\n if (index < COMPOSITE_CHROME_COUNT || node.type.name !== 'composite') return\n const derivation = derivationOf(node)\n if (derivation) found.push({ node, index, derivation })\n })\n return found\n}\n\n/**\n * Whether `derived` (a Baustein with a derivation, a child of `composite`) is out of date or was edited since it was made.\n * `stale`: for an asset source, the block `sourceOf(composite, kinds)` finds has a `hash` that differs from `inputHash`; null\n * when either is unknown. A block source is null here: callers with the document compare the hash of the source block's\n * Markdown. `edited`: the body's\n * hash (computed lazily, it serialises the whole body) differs from `outputHash`; null when `outputHash` is unknown.\n */\nexport function derivationState(\n composite: PMNode,\n derived: PMNode,\n bodyHash: () => string,\n kinds: readonly CompositeKind[],\n): { stale: boolean | null, edited: boolean | null } {\n const derivation = derivationOf(derived)\n if (!derivation) return { stale: null, edited: null }\n let stale: boolean | null = null\n if ('asset' in derivation.from) {\n const hash = sourceOf(composite, kinds)?.hash\n stale = hash && derivation.inputHash ? hash !== derivation.inputHash : null\n }\n const edited = derivation.outputHash ? bodyHash() !== derivation.outputHash : null\n return { stale, edited }\n}\n\n/** Markdown of the body of `composite` (everything after title, description and tags). */\nexport function bodyMarkdown(editor: Editor, composite: PMNode): string {\n const content: JSONContent[] = []\n composite.forEach((child, _offset, index) => {\n if (index >= COMPOSITE_CHROME_COUNT) content.push(child.toJSON() as JSONContent)\n })\n return editor.markdown!.serialize({ type: 'doc', content })\n}\n"],"mappings":";;;;;;AAoCA,SAAgB,aAAa,MAAiC;CAC5D,MAAM,OAAgB,KAAK,MAAM;CACjC,MAAM,QAAQ,QAAQ,OAAO,SAAS,WAAY,KAAiC,kBAAkB,KAAA;CACrG,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO;CAChD,MAAM,EAAE,MAAM,WAAW;CACzB,IAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,OAAO,WAAW,UAAU,OAAO;CAC5E,OAAO;AACT;AAEA,MAAM,mBAAmB,GAAG,WAAW;AAEvC,MAAM,aAAa;AACnB,MAAM,oBAAoB;;AAG1B,SAAgB,WAAW,KAA0D;CACnF,IAAI,CAAC,IAAI,WAAW,gBAAgB,GAAG,OAAO;CAC9C,MAAM,CAAC,YAAY,MAAM,GAAG,QAAQ,IAAI,MAAM,iBAAiB,MAAM,CAAC,CAAC,MAAM,GAAG;CAChF,IAAI,KAAK,UAAU,CAAC,cAAc,CAAC,QAAQ,CAAC,kBAAkB,KAAK,UAAU,KAAK,CAAC,WAAW,KAAK,IAAI,GAAG,OAAO;CACjH,OAAO;EAAE;EAAY;CAAK;AAC5B;;AAGA,SAAgB,SACd,WACA,OACiH;CACjH,IAAI,UAAU,KAAK,SAAS,aAAa,OAAO;CAChD,MAAM,QAAQ,cAAc,OAAO,OAAO,UAAU,MAAM,IAAI,CAAC,CAAC,CAAC;CACjE,IAAI,CAAC,OAAO,OAAO;CACnB,KAAK,IAAI,QAAA,GAAgC,QAAQ,UAAU,YAAY,SAAS;EAC9E,MAAM,OAAO,UAAU,MAAM,KAAK;EAClC,IAAI,CAAE,MAA4B,SAAS,KAAK,KAAK,IAAI,GAAG;EAC5D,MAAM,OAAgB,KAAK,MAAM;EACjC,OAAO;GAAE;GAAM;GAAO,OAAO,WAAW,OAAO,KAAK,MAAM,OAAO,EAAE,CAAC;GAAG,MAAM,OAAO,SAAS,YAAY,OAAO,OAAO;EAAK;CAC9H;CACA,OAAO;AACT;;AAGA,SAAgB,aAAa,WAA8E;CACzG,MAAM,QAAmE,CAAC;CAC1E,UAAU,SAAS,MAAM,SAAS,UAAU;EAC1C,IAAI,QAAA,KAAkC,KAAK,KAAK,SAAS,aAAa;EACtE,MAAM,aAAa,aAAa,IAAI;EACpC,IAAI,YAAY,MAAM,KAAK;GAAE;GAAM;GAAO;EAAW,CAAC;CACxD,CAAC;CACD,OAAO;AACT;;;;;;;;AASA,SAAgB,gBACd,WACA,SACA,UACA,OACmD;CACnD,MAAM,aAAa,aAAa,OAAO;CACvC,IAAI,CAAC,YAAY,OAAO;EAAE,OAAO;EAAM,QAAQ;CAAK;CACpD,IAAI,QAAwB;CAC5B,IAAI,WAAW,WAAW,MAAM;EAC9B,MAAM,OAAO,SAAS,WAAW,KAAK,CAAC,EAAE;EACzC,QAAQ,QAAQ,WAAW,YAAY,SAAS,WAAW,YAAY;CACzE;CACA,MAAM,SAAS,WAAW,aAAa,SAAS,MAAM,WAAW,aAAa;CAC9E,OAAO;EAAE;EAAO;CAAO;AACzB;;AAGA,SAAgB,aAAa,QAAgB,WAA2B;CACtE,MAAM,UAAyB,CAAC;CAChC,UAAU,SAAS,OAAO,SAAS,UAAU;EAC3C,IAAI,SAAA,GAAiC,QAAQ,KAAK,MAAM,OAAO,CAAgB;CACjF,CAAC;CACD,OAAO,OAAO,SAAU,UAAU;EAAE,MAAM;EAAO;CAAQ,CAAC;AAC5D"}
|
|
1
|
+
{"version":3,"file":"derivation.js","names":[],"sources":["../../../src/features/composites/derivation.ts"],"sourcesContent":["// Vue-free and server-importable: where a Baustein's content comes from. A source-holding Baustein (`recording`, `paper`,\n// `book`, `figure`, `document`) carries its original as an `image|audio|video|file` block in the body; a derived\n// Baustein (transcript, full text, description, AI result) records in `meta.derivation` what it was made from.\nimport type { Editor, JSONContent } from '@tiptap/core'\n// Types only: brings in the `editor.markdown` augmentation of `@tiptap/markdown` for contexts that do not otherwise load it.\nimport type {} from '@tiptap/markdown'\nimport type { Node as PMNode } from '@tiptap/pm/model'\nimport { EDITOR_API } from '../../core/api-routes'\nimport { type CompositeKind, compositeKind } from './kinds'\nimport { COMPOSITE_CHROME_COUNT } from './nodes'\nimport { DERIVATION_KEY } from './meta-keys'\n\n/** How a derived Baustein was made; `ai:<kindId>` for a block derived from another block by the model. */\nexport type DerivationMethod = 'transcription' | 'pdf-text' | 'ocr' | 'description' | 'bibliography' | (string & {})\n\n/** What a derived Baustein was made from: an uploaded asset (by asset name) or another block (by block id). */\nexport type DerivationSource = { asset: string } | { block: string, /** Document the block lives in; absent = this document. */ document?: string }\n\n/** `meta.derivation` of a derived Baustein. */\nexport interface Derivation {\n from: DerivationSource\n method: DerivationMethod\n model?: string\n /** ISO timestamp. */\n createdAt: string\n /** SHA-256 hex of the asset bytes / of the source block's Markdown; `''` when unknown. */\n inputHash: string\n /** SHA-256 hex of the produced body's Markdown; `''` when unknown. */\n outputHash: string\n /** Asset name of the stored raw output (text/markdown). */\n output?: string\n}\n\nexport { DERIVATION_KEY }\n\n/** The derivation of a composite (`meta.derivation` with a `from` object and a `method`); null when it has none. */\nexport function derivationOf(node: PMNode): Derivation | null {\n const meta: unknown = node.attrs.meta\n const value = meta && typeof meta === 'object' ? (meta as Record<string, unknown>)[DERIVATION_KEY] : undefined\n if (!value || typeof value !== 'object') return null\n const { from, method } = value as Record<string, unknown>\n if (!from || typeof from !== 'object' || typeof method !== 'string') return null\n return value as Derivation\n}\n\n/** Whether `derivation` points at a block of another document than `documentId` (the open one; null/undefined = unknown, so any named document is another). */\nexport function isForeignSource(derivation: Derivation, documentId: string | null | undefined): boolean {\n return 'block' in derivation.from && !!derivation.from.document && derivation.from.document !== documentId\n}\n\nconst ASSET_URL_PREFIX = `${EDITOR_API}/assets/`\n// Same patterns as `features/media/assets.ts`; copied because composites must not import media.\nconst ASSET_NAME = /^[0-9a-f-]{36}\\.[a-z0-9]{1,8}$/\nconst ASSET_DOCUMENT_ID = /^[A-Za-z0-9_-]{1,64}$/\n\n/** The stored asset behind an `/api/editor/assets/<documentId>/<name>` URL; null for any other `src`. */\nexport function assetRefOf(src: string): { documentId: string, name: string } | null {\n if (!src.startsWith(ASSET_URL_PREFIX)) return null\n const [documentId, name, ...rest] = src.slice(ASSET_URL_PREFIX.length).split('/')\n if (rest.length || !documentId || !name || !ASSET_DOCUMENT_ID.test(documentId) || !ASSET_NAME.test(name)) return null\n return { documentId, name }\n}\n\n/** The media block a source-holding kind keeps its original in: first body child of a type in `kind.source`. */\nexport function sourceOf(\n composite: PMNode,\n kinds: readonly CompositeKind[],\n): { node: PMNode, index: number, asset: { documentId: string, name: string } | null, hash: string | null } | null {\n if (composite.type.name !== 'composite') return null\n const types = compositeKind(kinds, String(composite.attrs.kind)).source\n if (!types) return null\n for (let index = COMPOSITE_CHROME_COUNT; index < composite.childCount; index++) {\n const node = composite.child(index)\n if (!(types as readonly string[]).includes(node.type.name)) continue\n const hash: unknown = node.attrs.hash\n return { node, index, asset: assetRefOf(String(node.attrs.src ?? '')), hash: typeof hash === 'string' && hash ? hash : null }\n }\n return null\n}\n\n/** The body children of `composite` that are derived Bausteine, with their child index. */\nexport function renditionsOf(composite: PMNode): { node: PMNode, index: number, derivation: Derivation }[] {\n const found: { node: PMNode, index: number, derivation: Derivation }[] = []\n composite.forEach((node, _offset, index) => {\n if (index < COMPOSITE_CHROME_COUNT || node.type.name !== 'composite') return\n const derivation = derivationOf(node)\n if (derivation) found.push({ node, index, derivation })\n })\n return found\n}\n\n/**\n * Whether `derived` (a Baustein with a derivation, a child of `composite`) is out of date or was edited since it was made.\n * `stale`: for an asset source, the block `sourceOf(composite, kinds)` finds has a `hash` that differs from `inputHash`; null\n * when either is unknown. A block source is null here: callers with the document compare the hash of the source block's\n * Markdown. `edited`: the body's\n * hash (computed lazily, it serialises the whole body) differs from `outputHash`; null when `outputHash` is unknown.\n */\nexport function derivationState(\n composite: PMNode,\n derived: PMNode,\n bodyHash: () => string,\n kinds: readonly CompositeKind[],\n): { stale: boolean | null, edited: boolean | null } {\n const derivation = derivationOf(derived)\n if (!derivation) return { stale: null, edited: null }\n let stale: boolean | null = null\n if ('asset' in derivation.from) {\n const hash = sourceOf(composite, kinds)?.hash\n stale = hash && derivation.inputHash ? hash !== derivation.inputHash : null\n }\n const edited = derivation.outputHash ? bodyHash() !== derivation.outputHash : null\n return { stale, edited }\n}\n\n/** Markdown of the body of `composite` (everything after title, description and tags). */\nexport function bodyMarkdown(editor: Editor, composite: PMNode): string {\n const content: JSONContent[] = []\n composite.forEach((child, _offset, index) => {\n if (index >= COMPOSITE_CHROME_COUNT) content.push(child.toJSON() as JSONContent)\n })\n return editor.markdown!.serialize({ type: 'doc', content })\n}\n"],"mappings":";;;;;;AAoCA,SAAgB,aAAa,MAAiC;CAC5D,MAAM,OAAgB,KAAK,MAAM;CACjC,MAAM,QAAQ,QAAQ,OAAO,SAAS,WAAY,KAAiC,kBAAkB,KAAA;CACrG,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO;CAChD,MAAM,EAAE,MAAM,WAAW;CACzB,IAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,OAAO,WAAW,UAAU,OAAO;CAC5E,OAAO;AACT;;AAGA,SAAgB,gBAAgB,YAAwB,YAAgD;CACtG,OAAO,WAAW,WAAW,QAAQ,CAAC,CAAC,WAAW,KAAK,YAAY,WAAW,KAAK,aAAa;AAClG;AAEA,MAAM,mBAAmB,GAAG,WAAW;AAEvC,MAAM,aAAa;AACnB,MAAM,oBAAoB;;AAG1B,SAAgB,WAAW,KAA0D;CACnF,IAAI,CAAC,IAAI,WAAW,gBAAgB,GAAG,OAAO;CAC9C,MAAM,CAAC,YAAY,MAAM,GAAG,QAAQ,IAAI,MAAM,iBAAiB,MAAM,CAAC,CAAC,MAAM,GAAG;CAChF,IAAI,KAAK,UAAU,CAAC,cAAc,CAAC,QAAQ,CAAC,kBAAkB,KAAK,UAAU,KAAK,CAAC,WAAW,KAAK,IAAI,GAAG,OAAO;CACjH,OAAO;EAAE;EAAY;CAAK;AAC5B;;AAGA,SAAgB,SACd,WACA,OACiH;CACjH,IAAI,UAAU,KAAK,SAAS,aAAa,OAAO;CAChD,MAAM,QAAQ,cAAc,OAAO,OAAO,UAAU,MAAM,IAAI,CAAC,CAAC,CAAC;CACjE,IAAI,CAAC,OAAO,OAAO;CACnB,KAAK,IAAI,QAAA,GAAgC,QAAQ,UAAU,YAAY,SAAS;EAC9E,MAAM,OAAO,UAAU,MAAM,KAAK;EAClC,IAAI,CAAE,MAA4B,SAAS,KAAK,KAAK,IAAI,GAAG;EAC5D,MAAM,OAAgB,KAAK,MAAM;EACjC,OAAO;GAAE;GAAM;GAAO,OAAO,WAAW,OAAO,KAAK,MAAM,OAAO,EAAE,CAAC;GAAG,MAAM,OAAO,SAAS,YAAY,OAAO,OAAO;EAAK;CAC9H;CACA,OAAO;AACT;;AAGA,SAAgB,aAAa,WAA8E;CACzG,MAAM,QAAmE,CAAC;CAC1E,UAAU,SAAS,MAAM,SAAS,UAAU;EAC1C,IAAI,QAAA,KAAkC,KAAK,KAAK,SAAS,aAAa;EACtE,MAAM,aAAa,aAAa,IAAI;EACpC,IAAI,YAAY,MAAM,KAAK;GAAE;GAAM;GAAO;EAAW,CAAC;CACxD,CAAC;CACD,OAAO;AACT;;;;;;;;AASA,SAAgB,gBACd,WACA,SACA,UACA,OACmD;CACnD,MAAM,aAAa,aAAa,OAAO;CACvC,IAAI,CAAC,YAAY,OAAO;EAAE,OAAO;EAAM,QAAQ;CAAK;CACpD,IAAI,QAAwB;CAC5B,IAAI,WAAW,WAAW,MAAM;EAC9B,MAAM,OAAO,SAAS,WAAW,KAAK,CAAC,EAAE;EACzC,QAAQ,QAAQ,WAAW,YAAY,SAAS,WAAW,YAAY;CACzE;CACA,MAAM,SAAS,WAAW,aAAa,SAAS,MAAM,WAAW,aAAa;CAC9E,OAAO;EAAE;EAAO;CAAO;AACzB;;AAGA,SAAgB,aAAa,QAAgB,WAA2B;CACtE,MAAM,UAAyB,CAAC;CAChC,UAAU,SAAS,OAAO,SAAS,UAAU;EAC3C,IAAI,SAAA,GAAiC,QAAQ,KAAK,MAAM,OAAO,CAAgB;CACjF,CAAC;CACD,OAAO,OAAO,SAAU,UAAU;EAAE,MAAM;EAAO;CAAQ,CAAC;AAC5D"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { findBlockById } from "../../core/schema/block-ids.js";
|
|
2
2
|
import { EDITOR_API } from "../../core/api-routes.js";
|
|
3
3
|
import { sha256Hex } from "../../core/hash.js";
|
|
4
|
-
import { bodyMarkdown, derivationOf, derivationState, sourceOf } from "./derivation.js";
|
|
4
|
+
import { bodyMarkdown, derivationOf, derivationState, isForeignSource, sourceOf } from "./derivation.js";
|
|
5
5
|
import { compositeKindsOf } from "./nodes.js";
|
|
6
6
|
import { compositeOfRow } from "./rows.js";
|
|
7
7
|
//#region src/features/composites/derived-chrome.ts
|
|
@@ -22,6 +22,14 @@ function derivedChromeOf(editor, row) {
|
|
|
22
22
|
const parent = doc.resolve(found.pos).parent;
|
|
23
23
|
return { stale: parent.type.name === "composite" && derivationState(parent, found.node, () => "", compositeKindsOf(editor)).stale === true };
|
|
24
24
|
}
|
|
25
|
+
/** The id of `editor`'s document as the `blockTransfer` storage knows it; null when the editor has none. */
|
|
26
|
+
function documentIdOf(editor) {
|
|
27
|
+
return editor.storage.blockTransfer?.documentId ?? null;
|
|
28
|
+
}
|
|
29
|
+
/** Whether `derivation` was made from a block of another document than `editor`'s (its source cannot be looked up here). */
|
|
30
|
+
function derivationIsForeign(editor, derivation) {
|
|
31
|
+
return isForeignSource(derivation, documentIdOf(editor));
|
|
32
|
+
}
|
|
25
33
|
/**
|
|
26
34
|
* Details of the derived Baustein `node` of `editor`'s document (null when it is not derived). Serialises and hashes
|
|
27
35
|
* the body (and for a block source the source block), so call it for one Baustein at a time, not for every row.
|
|
@@ -37,8 +45,9 @@ function derivationDetailsOf(editor, node) {
|
|
|
37
45
|
const holder = parent?.type.name === "composite" ? parent : null;
|
|
38
46
|
let bodyHash;
|
|
39
47
|
const state = derivationState(holder ?? node, node, () => bodyHash ??= sha256Hex(bodyMarkdown(editor, node)), kinds);
|
|
48
|
+
const foreign = derivationIsForeign(editor, derivation);
|
|
40
49
|
let stale = state.stale;
|
|
41
|
-
if ("block" in derivation.from) {
|
|
50
|
+
if ("block" in derivation.from && !foreign) {
|
|
42
51
|
const source = findBlockById(doc, derivation.from.block);
|
|
43
52
|
stale = source && derivation.inputHash ? sha256Hex(bodyMarkdown(editor, source.node)) !== derivation.inputHash : null;
|
|
44
53
|
}
|
|
@@ -46,6 +55,7 @@ function derivationDetailsOf(editor, node) {
|
|
|
46
55
|
const outputUrl = derivation.output && documentId ? `${EDITOR_API}/assets/${documentId}/${derivation.output}` : null;
|
|
47
56
|
return {
|
|
48
57
|
derivation,
|
|
58
|
+
foreign,
|
|
49
59
|
stale,
|
|
50
60
|
edited: state.edited,
|
|
51
61
|
outputUrl
|
|
@@ -64,6 +74,6 @@ function derivationMethodLabel(t, method) {
|
|
|
64
74
|
return KNOWN_METHODS.includes(method) ? t(`composites.derivation.method.${method}`) : method;
|
|
65
75
|
}
|
|
66
76
|
//#endregion
|
|
67
|
-
export { derivationDetailsOf, derivationMethodLabel, derivedChromeOf };
|
|
77
|
+
export { derivationDetailsOf, derivationIsForeign, derivationMethodLabel, derivedChromeOf };
|
|
68
78
|
|
|
69
79
|
//# sourceMappingURL=derived-chrome.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"derived-chrome.js","names":[],"sources":["../../../src/features/composites/derived-chrome.ts"],"sourcesContent":["// Vue-free: what the composite chrome and the details card show for a derived Baustein (one with a `meta.derivation`).\nimport type { Editor } from '@tiptap/core'\nimport type { Node as PMNode } from '@tiptap/pm/model'\nimport { EDITOR_API } from '../../core/api-routes'\nimport { sha256Hex } from '../../core/hash'\nimport { findBlockById } from '../../core/schema/block-ids'\nimport type { MessageKey, Translate } from '../../i18n'\nimport { type Derivation, bodyMarkdown, derivationOf, derivationState, sourceOf } from './derivation'\nimport { compositeKindsOf } from './nodes'\nimport { type CompositeRow, compositeOfRow } from './rows'\n\n/**\n * The chrome state of the row's Baustein when it is derived (null when it is not): `stale` when its asset source has\n * changed since it was made, i.e. the enclosing Baustein's source block hash differs from the stored `inputHash`.\n * Cheap on purpose (no body hashing): the chrome runs it for every row on every refresh. „bearbeitet“ and the staleness\n * of a block-derived Baustein hash Markdown, so only the details card computes them (`derivationDetailsOf`).\n */\nexport function derivedChromeOf(editor: Editor, row: Pick<CompositeRow, 'id' | 'pos'>): { stale: boolean } | null {\n const { doc } = editor.state\n // The row's position may be stale for a moment after an edit; take it only when it still holds the same Baustein.\n const at = doc.nodeAt(row.pos)\n const found = at?.type.name === 'composite' && (at.attrs.id ?? null) === row.id ? { pos: row.pos, node: at } : compositeOfRow(doc, row)\n if (!found || !derivationOf(found.node)) return null\n const parent = doc.resolve(found.pos).parent\n const stale = parent.type.name === 'composite' && derivationState(parent, found.node, () => '', compositeKindsOf(editor)).stale === true\n return { stale }\n}\n\n/** Everything the details card shows about the derivation of `node`; `stale`/`edited` are null when they cannot be told. */\nexport interface DerivationDetails {\n derivation: Derivation\n /** The source changed since `node` was made: asset hash for an asset source, Markdown hash of the source block for a block source. */\n stale: boolean | null\n /** The body differs from what was generated. */\n edited: boolean | null\n /** URL of the stored raw output, when there is one and its document is known. */\n outputUrl: string | null\n}\n\n/**\n * Details of the derived Baustein `node` of `editor`'s document (null when it is not derived). Serialises and hashes\n * the body (and for a block source the source block), so call it for one Baustein at a time, not for every row.\n */\nexport function derivationDetailsOf(editor: Editor, node: PMNode): DerivationDetails | null {\n const derivation = derivationOf(node)\n if (!derivation) return null\n const { doc } = editor.state\n const kinds = compositeKindsOf(editor)\n const id: unknown = node.attrs.id\n const found = typeof id === 'string' && id ? findBlockById(doc, id) : null\n const parent = found ? doc.resolve(found.pos).parent : null\n const holder = parent?.type.name === 'composite' ? parent : null\n\n let bodyHash: string | undefined\n const state = derivationState(holder ?? node, node, () => (bodyHash ??= sha256Hex(bodyMarkdown(editor, node))), kinds)\n let stale = state.stale\n if ('block' in derivation.from) {\n const source = findBlockById(doc, derivation.from.block)\n stale = source && derivation.inputHash ? sha256Hex(bodyMarkdown(editor, source.node)) !== derivation.inputHash : null\n }\n\n const documentId = holder ? sourceOf(holder, kinds)?.asset?.documentId : undefined\n const outputUrl = derivation.output && documentId ? `${EDITOR_API}/assets/${documentId}/${derivation.output}` : null\n return { derivation, stale, edited: state.edited, outputUrl }\n}\n\nconst KNOWN_METHODS: readonly string[] = ['transcription', 'pdf-text', 'ocr', 'description', 'bibliography']\n\n/** The words for a derivation method: the known ones translated, `ai:<kind>` as „KI-Ableitung“, anything else as stored. */\nexport function derivationMethodLabel(t: Translate, method: string): string {\n if (method.startsWith('ai:')) return t('composites.derivation.method.ai')\n return KNOWN_METHODS.includes(method) ? t(`composites.derivation.method.${method}` as MessageKey) : method\n}\n"],"mappings":";;;;;;;;;;;;;AAiBA,SAAgB,gBAAgB,QAAgB,KAAkE;CAChH,MAAM,EAAE,QAAQ,OAAO;CAEvB,MAAM,KAAK,IAAI,OAAO,IAAI,GAAG;CAC7B,MAAM,QAAQ,IAAI,KAAK,SAAS,gBAAgB,GAAG,MAAM,MAAM,UAAU,IAAI,KAAK;EAAE,KAAK,IAAI;EAAK,MAAM;CAAG,IAAI,eAAe,KAAK,GAAG;CACtI,IAAI,CAAC,SAAS,CAAC,aAAa,MAAM,IAAI,GAAG,OAAO;CAChD,MAAM,SAAS,IAAI,QAAQ,MAAM,GAAG,CAAC,CAAC;CAEtC,OAAO,EAAE,OADK,OAAO,KAAK,SAAS,eAAe,gBAAgB,QAAQ,MAAM,YAAY,IAAI,iBAAiB,MAAM,CAAC,CAAC,CAAC,UAAU,KACrH;AACjB;;;;;
|
|
1
|
+
{"version":3,"file":"derived-chrome.js","names":[],"sources":["../../../src/features/composites/derived-chrome.ts"],"sourcesContent":["// Vue-free: what the composite chrome and the details card show for a derived Baustein (one with a `meta.derivation`).\nimport type { Editor } from '@tiptap/core'\nimport type { Node as PMNode } from '@tiptap/pm/model'\nimport { EDITOR_API } from '../../core/api-routes'\nimport { sha256Hex } from '../../core/hash'\nimport { findBlockById } from '../../core/schema/block-ids'\nimport type { MessageKey, Translate } from '../../i18n'\nimport { type Derivation, bodyMarkdown, derivationOf, derivationState, isForeignSource, sourceOf } from './derivation'\nimport { compositeKindsOf } from './nodes'\nimport { type CompositeRow, compositeOfRow } from './rows'\n\n/**\n * The chrome state of the row's Baustein when it is derived (null when it is not): `stale` when its asset source has\n * changed since it was made, i.e. the enclosing Baustein's source block hash differs from the stored `inputHash`.\n * Cheap on purpose (no body hashing): the chrome runs it for every row on every refresh. „bearbeitet“ and the staleness\n * of a block-derived Baustein hash Markdown, so only the details card computes them (`derivationDetailsOf`).\n */\nexport function derivedChromeOf(editor: Editor, row: Pick<CompositeRow, 'id' | 'pos'>): { stale: boolean } | null {\n const { doc } = editor.state\n // The row's position may be stale for a moment after an edit; take it only when it still holds the same Baustein.\n const at = doc.nodeAt(row.pos)\n const found = at?.type.name === 'composite' && (at.attrs.id ?? null) === row.id ? { pos: row.pos, node: at } : compositeOfRow(doc, row)\n if (!found || !derivationOf(found.node)) return null\n const parent = doc.resolve(found.pos).parent\n const stale = parent.type.name === 'composite' && derivationState(parent, found.node, () => '', compositeKindsOf(editor)).stale === true\n return { stale }\n}\n\n/** The id of `editor`'s document as the `blockTransfer` storage knows it; null when the editor has none. */\nfunction documentIdOf(editor: Editor): string | null {\n const storage = editor.storage as { blockTransfer?: { documentId?: string | null } }\n return storage.blockTransfer?.documentId ?? null\n}\n\n/** Whether `derivation` was made from a block of another document than `editor`'s (its source cannot be looked up here). */\nexport function derivationIsForeign(editor: Editor, derivation: Derivation): boolean {\n return isForeignSource(derivation, documentIdOf(editor))\n}\n\n/** Everything the details card shows about the derivation of `node`; `stale`/`edited` are null when they cannot be told. */\nexport interface DerivationDetails {\n derivation: Derivation\n /** The source lives in another document: nothing to compare with (`stale` is null) and nothing to reveal. */\n foreign: boolean\n /** The source changed since `node` was made: asset hash for an asset source, Markdown hash of the source block for a block source; null for a foreign source. */\n stale: boolean | null\n /** The body differs from what was generated. */\n edited: boolean | null\n /** URL of the stored raw output, when there is one and its document is known. */\n outputUrl: string | null\n}\n\n/**\n * Details of the derived Baustein `node` of `editor`'s document (null when it is not derived). Serialises and hashes\n * the body (and for a block source the source block), so call it for one Baustein at a time, not for every row.\n */\nexport function derivationDetailsOf(editor: Editor, node: PMNode): DerivationDetails | null {\n const derivation = derivationOf(node)\n if (!derivation) return null\n const { doc } = editor.state\n const kinds = compositeKindsOf(editor)\n const id: unknown = node.attrs.id\n const found = typeof id === 'string' && id ? findBlockById(doc, id) : null\n const parent = found ? doc.resolve(found.pos).parent : null\n const holder = parent?.type.name === 'composite' ? parent : null\n\n let bodyHash: string | undefined\n const state = derivationState(holder ?? node, node, () => (bodyHash ??= sha256Hex(bodyMarkdown(editor, node))), kinds)\n const foreign = derivationIsForeign(editor, derivation)\n let stale = state.stale\n if ('block' in derivation.from && !foreign) {\n const source = findBlockById(doc, derivation.from.block)\n stale = source && derivation.inputHash ? sha256Hex(bodyMarkdown(editor, source.node)) !== derivation.inputHash : null\n }\n\n const documentId = holder ? sourceOf(holder, kinds)?.asset?.documentId : undefined\n const outputUrl = derivation.output && documentId ? `${EDITOR_API}/assets/${documentId}/${derivation.output}` : null\n return { derivation, foreign, stale, edited: state.edited, outputUrl }\n}\n\nconst KNOWN_METHODS: readonly string[] = ['transcription', 'pdf-text', 'ocr', 'description', 'bibliography']\n\n/** The words for a derivation method: the known ones translated, `ai:<kind>` as „KI-Ableitung“, anything else as stored. */\nexport function derivationMethodLabel(t: Translate, method: string): string {\n if (method.startsWith('ai:')) return t('composites.derivation.method.ai')\n return KNOWN_METHODS.includes(method) ? t(`composites.derivation.method.${method}` as MessageKey) : method\n}\n"],"mappings":";;;;;;;;;;;;;AAiBA,SAAgB,gBAAgB,QAAgB,KAAkE;CAChH,MAAM,EAAE,QAAQ,OAAO;CAEvB,MAAM,KAAK,IAAI,OAAO,IAAI,GAAG;CAC7B,MAAM,QAAQ,IAAI,KAAK,SAAS,gBAAgB,GAAG,MAAM,MAAM,UAAU,IAAI,KAAK;EAAE,KAAK,IAAI;EAAK,MAAM;CAAG,IAAI,eAAe,KAAK,GAAG;CACtI,IAAI,CAAC,SAAS,CAAC,aAAa,MAAM,IAAI,GAAG,OAAO;CAChD,MAAM,SAAS,IAAI,QAAQ,MAAM,GAAG,CAAC,CAAC;CAEtC,OAAO,EAAE,OADK,OAAO,KAAK,SAAS,eAAe,gBAAgB,QAAQ,MAAM,YAAY,IAAI,iBAAiB,MAAM,CAAC,CAAC,CAAC,UAAU,KACrH;AACjB;;AAGA,SAAS,aAAa,QAA+B;CAEnD,OADgB,OAAO,QACR,eAAe,cAAc;AAC9C;;AAGA,SAAgB,oBAAoB,QAAgB,YAAiC;CACnF,OAAO,gBAAgB,YAAY,aAAa,MAAM,CAAC;AACzD;;;;;AAmBA,SAAgB,oBAAoB,QAAgB,MAAwC;CAC1F,MAAM,aAAa,aAAa,IAAI;CACpC,IAAI,CAAC,YAAY,OAAO;CACxB,MAAM,EAAE,QAAQ,OAAO;CACvB,MAAM,QAAQ,iBAAiB,MAAM;CACrC,MAAM,KAAc,KAAK,MAAM;CAC/B,MAAM,QAAQ,OAAO,OAAO,YAAY,KAAK,cAAc,KAAK,EAAE,IAAI;CACtE,MAAM,SAAS,QAAQ,IAAI,QAAQ,MAAM,GAAG,CAAC,CAAC,SAAS;CACvD,MAAM,SAAS,QAAQ,KAAK,SAAS,cAAc,SAAS;CAE5D,IAAI;CACJ,MAAM,QAAQ,gBAAgB,UAAU,MAAM,YAAa,aAAa,UAAU,aAAa,QAAQ,IAAI,CAAC,GAAI,KAAK;CACrH,MAAM,UAAU,oBAAoB,QAAQ,UAAU;CACtD,IAAI,QAAQ,MAAM;CAClB,IAAI,WAAW,WAAW,QAAQ,CAAC,SAAS;EAC1C,MAAM,SAAS,cAAc,KAAK,WAAW,KAAK,KAAK;EACvD,QAAQ,UAAU,WAAW,YAAY,UAAU,aAAa,QAAQ,OAAO,IAAI,CAAC,MAAM,WAAW,YAAY;CACnH;CAEA,MAAM,aAAa,SAAS,SAAS,QAAQ,KAAK,CAAC,EAAE,OAAO,aAAa,KAAA;CACzE,MAAM,YAAY,WAAW,UAAU,aAAa,GAAG,WAAW,UAAU,WAAW,GAAG,WAAW,WAAW;CAChH,OAAO;EAAE;EAAY;EAAS;EAAO,QAAQ,MAAM;EAAQ;CAAU;AACvE;AAEA,MAAM,gBAAmC;CAAC;CAAiB;CAAY;CAAO;CAAe;AAAc;;AAG3G,SAAgB,sBAAsB,GAAc,QAAwB;CAC1E,IAAI,OAAO,WAAW,KAAK,GAAG,OAAO,EAAE,iCAAiC;CACxE,OAAO,cAAc,SAAS,MAAM,IAAI,EAAE,gCAAgC,QAAsB,IAAI;AACtG"}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { EditorFeature } from "../../core/feature.js";
|
|
2
2
|
import "../../core/index.js";
|
|
3
3
|
import { CompositeKind } from "./kinds.js";
|
|
4
|
+
import { Basket } from "./baskets.js";
|
|
4
5
|
import { CompositeNodeViews } from "./node-views.js";
|
|
5
6
|
//#region src/features/composites/feature.d.ts
|
|
6
7
|
/** What a framework binding supplies to the composites feature. */
|
|
7
8
|
export interface CompositesUi<C> {
|
|
8
9
|
/** The chips on each title line and the details popover on the kind icon; mounted as an overlay. */
|
|
9
10
|
chrome: C;
|
|
11
|
+
/** The focus chip („Fokus: <Titel> · Aufheben“) shown while a Baustein is focused; mounted as an overlay. Absent: no chip (the focus mode itself works). */
|
|
12
|
+
focusChip?: C;
|
|
10
13
|
/** The „Bearbeiten“ settings tab; receives `{ kinds }` as props. */
|
|
11
14
|
settingsTab: C;
|
|
12
15
|
/** The composite block view of the framework. */
|
|
@@ -16,12 +19,15 @@ export interface CompositesUi<C> {
|
|
|
16
19
|
export interface CompositeFeatureOptions {
|
|
17
20
|
/** The composite kinds („Bausteine“) offered in the editor, with their own labels (default: `defaultCompositeKinds(t)`, the built-in kinds in the editor's language); read once. */
|
|
18
21
|
kinds?: readonly CompositeKind[];
|
|
22
|
+
/** The colour baskets a Baustein can be put into (default: `defaultBaskets(t)`, red/orange/yellow/green/blue in the editor's language; colours are the `--sk-basket-<id>` CSS variables); read once. */
|
|
23
|
+
baskets?: readonly Basket[];
|
|
19
24
|
}
|
|
20
25
|
/**
|
|
21
26
|
* Composites („Bausteine“): the node family with its node views, the toolbar group (insert, „In Baustein umwandeln“,
|
|
22
27
|
* „Gliederung umwandeln“), `/` commands, the conversion providers (wrap into / switch kind of / unwrap composites),
|
|
23
28
|
* Markdown `:::` fences, legacy `section` migration, the preferred-structure setting and its `editing` settings tab,
|
|
24
|
-
* the chips on each title line (status, tags) and the details popover on the kind icon (the binding's `chrome` overlay),
|
|
29
|
+
* the chips on each title line (status, tags) and the details popover on the kind icon (the binding's `chrome` overlay), focus mode on one
|
|
30
|
+
* Baustein (`CompositeFocus`, the „Fokussieren“ block action and the binding's `focusChip` overlay), and
|
|
25
31
|
* the AI view of kinds and structure, and the kinds' accepted children (`AcceptedChildren`: an edit that nests a Baustein
|
|
26
32
|
* into one that does not accept its kind is refused with a notice). Contributes the `parseMarkdown`,
|
|
27
33
|
* `structure` and `migrateContent` stages. Imports no AI, media or transcript code.
|
|
@@ -2,13 +2,14 @@ import { ABILITY } from "../../core/schema/abilities.js";
|
|
|
2
2
|
import { abilitiesAtSelection } from "../../core/abilities.js";
|
|
3
3
|
import { settingsTabsOf } from "../../core/define-editor.js";
|
|
4
4
|
import { editorEntryOf } from "../../core/editor-ui.js";
|
|
5
|
-
import { findBlockById } from "../../core/schema/block-ids.js";
|
|
5
|
+
import { blockIdOf, findBlockById } from "../../core/schema/block-ids.js";
|
|
6
6
|
import { downloadText } from "../../core/download.js";
|
|
7
7
|
import { compositeKind, defaultCompositeKinds } from "./kinds.js";
|
|
8
8
|
import { blocksFromMarkdown } from "./fences.js";
|
|
9
|
+
import { defaultBaskets } from "./baskets.js";
|
|
9
10
|
import { bodyMarkdown, derivationOf } from "./derivation.js";
|
|
10
11
|
import { structureBlocks } from "./heading-composites.js";
|
|
11
|
-
import { CompositeDescriptionNode, CompositeTitleNode, compositeKindsOf } from "./nodes.js";
|
|
12
|
+
import { CompositeDescriptionNode, CompositeTitleNode, compositeAround, compositeKindsOf } from "./nodes.js";
|
|
12
13
|
import { bibOf, bibtexOf, cslOf } from "./bibliography.js";
|
|
13
14
|
import { compositeConversions } from "./convert.js";
|
|
14
15
|
import { migrateLegacyContent } from "./legacy.js";
|
|
@@ -17,9 +18,11 @@ import { compositeSchemaExtensions } from "./schema.js";
|
|
|
17
18
|
import { AcceptedChildren, compositeInsertGuard } from "./accepted-children.js";
|
|
18
19
|
import { documentCompositeMarkdown, markdownFileName } from "./markdown-export.js";
|
|
19
20
|
import { compositeNodes } from "./node-views.js";
|
|
21
|
+
import { CompositeFocus } from "./focus.js";
|
|
20
22
|
import { REFERENCE_AI_BLOCK } from "./reference-ai.js";
|
|
21
23
|
import { referenceSuggestion } from "./reference-suggestion.js";
|
|
22
24
|
import { SourceReveal } from "./reveal-source.js";
|
|
25
|
+
import { CompositeSplit } from "./split.js";
|
|
23
26
|
import { Structure, structureOf } from "./structure.js";
|
|
24
27
|
import { STRUCTURE_MODE_KEYS } from "./structure-mode.js";
|
|
25
28
|
//#region src/features/composites/feature.ts
|
|
@@ -29,20 +32,28 @@ const STRUCTURE_STAGE_LEVEL = 2;
|
|
|
29
32
|
* Composites („Bausteine“): the node family with its node views, the toolbar group (insert, „In Baustein umwandeln“,
|
|
30
33
|
* „Gliederung umwandeln“), `/` commands, the conversion providers (wrap into / switch kind of / unwrap composites),
|
|
31
34
|
* Markdown `:::` fences, legacy `section` migration, the preferred-structure setting and its `editing` settings tab,
|
|
32
|
-
* the chips on each title line (status, tags) and the details popover on the kind icon (the binding's `chrome` overlay),
|
|
35
|
+
* the chips on each title line (status, tags) and the details popover on the kind icon (the binding's `chrome` overlay), focus mode on one
|
|
36
|
+
* Baustein (`CompositeFocus`, the „Fokussieren“ block action and the binding's `focusChip` overlay), and
|
|
33
37
|
* the AI view of kinds and structure, and the kinds' accepted children (`AcceptedChildren`: an edit that nests a Baustein
|
|
34
38
|
* into one that does not accept its kind is refused with a notice). Contributes the `parseMarkdown`,
|
|
35
39
|
* `structure` and `migrateContent` stages. Imports no AI, media or transcript code.
|
|
36
40
|
*/
|
|
37
41
|
function compositeFeatureCore(ui, options = {}) {
|
|
38
42
|
const kindsOf = (ctx) => options.kinds ?? defaultCompositeKinds(ctx.t);
|
|
43
|
+
const basketsOf = (ctx) => options.baskets ?? defaultBaskets(ctx.t);
|
|
39
44
|
const { Composite, CompositeTags, Reference } = compositeNodes(ui.nodeViews);
|
|
40
45
|
return {
|
|
41
46
|
id: "composites",
|
|
42
47
|
dimension: "content",
|
|
43
|
-
schemaExtensions: () => compositeSchemaExtensions(
|
|
48
|
+
schemaExtensions: () => compositeSchemaExtensions({
|
|
49
|
+
...options.kinds && { kinds: options.kinds },
|
|
50
|
+
...options.baskets && { baskets: options.baskets }
|
|
51
|
+
}),
|
|
44
52
|
extensions: (ctx) => [
|
|
45
|
-
Composite.configure({
|
|
53
|
+
Composite.configure({
|
|
54
|
+
kinds: kindsOf(ctx),
|
|
55
|
+
baskets: basketsOf(ctx)
|
|
56
|
+
}),
|
|
46
57
|
CompositeTitleNode,
|
|
47
58
|
CompositeDescriptionNode,
|
|
48
59
|
CompositeTags,
|
|
@@ -52,29 +63,49 @@ function compositeFeatureCore(ui, options = {}) {
|
|
|
52
63
|
t: ctx.t
|
|
53
64
|
}),
|
|
54
65
|
SourceReveal,
|
|
66
|
+
CompositeFocus,
|
|
67
|
+
CompositeSplit.configure({
|
|
68
|
+
defaultKind: () => ctx.settings.get().blocks.defaultKind,
|
|
69
|
+
targetChars: () => ctx.settings.get().blocks.splitTargetChars
|
|
70
|
+
}),
|
|
55
71
|
Reference.configure({ suggestion: referenceSuggestion(ctx.t) })
|
|
56
72
|
],
|
|
57
73
|
toolbarGroups: COMPOSITE_TOOLBAR_GROUPS,
|
|
58
74
|
commands: (ctx) => compositeCommands(ctx, kindsOf(ctx)),
|
|
59
75
|
insertGuards: () => [compositeInsertGuard],
|
|
60
|
-
blockActions: (ctx) => [
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
blockActions: (ctx) => [
|
|
77
|
+
{
|
|
78
|
+
id: "document-export",
|
|
79
|
+
label: ctx.t("composites.blockAction.export"),
|
|
80
|
+
icon: "i-lucide-download",
|
|
81
|
+
ability: ABILITY.compositeExport,
|
|
82
|
+
order: 400,
|
|
83
|
+
isVisible: ({ node }) => node.attrs.kind === "document",
|
|
84
|
+
run: ({ editor, node }) => downloadText(markdownFileName(node), documentCompositeMarkdown(editor, node))
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: "bibliography-export",
|
|
88
|
+
label: ctx.t("composites.blockAction.bibtex"),
|
|
89
|
+
icon: "i-lucide-book-marked",
|
|
90
|
+
ability: ABILITY.compositeExport,
|
|
91
|
+
order: 410,
|
|
92
|
+
isVisible: ({ node }) => bibOf(node) !== null,
|
|
93
|
+
run: ({ node }) => downloadText(`${markdownFileName(node).replace(/\.md$/, "")}.bib`, bibtexOf(cslOf(node)), "text/x-bibtex;charset=utf-8")
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: "composite-focus",
|
|
97
|
+
label: ctx.t("composites.focus.action"),
|
|
98
|
+
icon: "i-lucide-focus",
|
|
99
|
+
order: 420,
|
|
100
|
+
isVisible: ({ node }) => node.type.name === "composite",
|
|
101
|
+
run: ({ editor, node }) => {
|
|
102
|
+
const id = blockIdOf(node);
|
|
103
|
+
if (id) editor.commands.focusComposite(id);
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
...splitBlockActions(ctx)
|
|
107
|
+
],
|
|
108
|
+
overlays: [ui.chrome, ui.focusChip].filter((overlay) => overlay !== void 0),
|
|
78
109
|
settingsTabs: (ctx) => [{
|
|
79
110
|
id: "editing",
|
|
80
111
|
label: ctx.t("composites.tab"),
|
|
@@ -112,6 +143,48 @@ function compositeFeatureCore(ui, options = {}) {
|
|
|
112
143
|
}
|
|
113
144
|
};
|
|
114
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* The four split actions of a Baustein: at the caret (as sibling or sub-Bausteine; offered on the Baustein around the
|
|
148
|
+
* caret while the caret is past the first body block) and by length (as sub-Bausteine or siblings; offered while the
|
|
149
|
+
* body falls into two parts or more). Each is hidden when the command refuses, so a kind that accepts no child kind
|
|
150
|
+
* never shows the `children` variants.
|
|
151
|
+
*/
|
|
152
|
+
function splitBlockActions({ t }) {
|
|
153
|
+
const atCaret = (id, labelKey, icon, order, mode) => ({
|
|
154
|
+
id,
|
|
155
|
+
label: t(labelKey),
|
|
156
|
+
icon,
|
|
157
|
+
ability: ABILITY.compositeMeta,
|
|
158
|
+
order,
|
|
159
|
+
isVisible: ({ editor, pos }) => {
|
|
160
|
+
const { $head } = editor.state.selection;
|
|
161
|
+
const found = compositeAround($head);
|
|
162
|
+
return found?.pos === pos && $head.index(found.depth) > 3 && editor.can().splitCompositeAtCaret({ mode });
|
|
163
|
+
},
|
|
164
|
+
run: ({ editor }) => void editor.chain().focus().splitCompositeAtCaret({ mode }).run()
|
|
165
|
+
});
|
|
166
|
+
const byLength = (id, labelKey, icon, order, mode) => ({
|
|
167
|
+
id,
|
|
168
|
+
label: t(labelKey),
|
|
169
|
+
icon,
|
|
170
|
+
ability: ABILITY.compositeMeta,
|
|
171
|
+
order,
|
|
172
|
+
isVisible: ({ editor, pos }) => editor.can().splitCompositeByLength({
|
|
173
|
+
mode,
|
|
174
|
+
pos
|
|
175
|
+
}),
|
|
176
|
+
run: ({ editor, pos }) => void editor.chain().focus().splitCompositeByLength({
|
|
177
|
+
mode,
|
|
178
|
+
pos
|
|
179
|
+
}).run()
|
|
180
|
+
});
|
|
181
|
+
return [
|
|
182
|
+
atCaret("composite-split-here", "composites.split.here", "i-lucide-scissors", 430, "siblings"),
|
|
183
|
+
atCaret("composite-split-children", "composites.split.children", "i-lucide-split", 440, "children"),
|
|
184
|
+
byLength("composite-split-length-children", "composites.split.lengthChildren", "i-lucide-ruler", 450, "children"),
|
|
185
|
+
byLength("composite-split-length-siblings", "composites.split.lengthSiblings", "i-lucide-ruler", 460, "siblings")
|
|
186
|
+
];
|
|
187
|
+
}
|
|
115
188
|
const COMPOSITE_TOOLBAR_GROUPS = [{
|
|
116
189
|
id: "composites",
|
|
117
190
|
order: 60,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"feature.js","names":[],"sources":["../../../src/features/composites/feature.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { CommandDef, EditorFeature, FeatureContext, MenuItem, ToolbarGroupDef } from '../../core'\nimport { abilitiesAtSelection, downloadText, editorEntryOf, settingsTabsOf } from '../../core'\nimport type { Translate } from '../../i18n'\nimport { ABILITY, findBlockById } from '../../core/schema'\nimport { AcceptedChildren, compositeInsertGuard } from './accepted-children'\nimport { bibOf, bibtexOf, cslOf } from './bibliography'\nimport { compositeConversions } from './convert'\nimport { bodyMarkdown, derivationOf } from './derivation'\nimport { blocksFromMarkdown } from './fences'\nimport { structureBlocks } from './heading-composites'\nimport { type CompositeKind, compositeKind, defaultCompositeKinds } from './kinds'\nimport { migrateLegacyContent } from './legacy'\nimport { compositeSchemaExtensions } from './schema'\nimport { documentCompositeMarkdown, markdownFileName } from './markdown-export'\nimport { type CompositeNodeViews, compositeNodes } from './node-views'\nimport { CompositeDescriptionNode, CompositeTitleNode, compositeKindsOf } from './nodes'\nimport { REFERENCE_TRIGGER } from './reference'\nimport { REFERENCE_AI_BLOCK } from './reference-ai'\nimport { referenceSuggestion } from './reference-suggestion'\nimport { compositeIdsOf, normalizeReferences } from './reference-text'\nimport { SourceReveal } from './reveal-source'\nimport { Structure, structureOf } from './structure'\nimport { STRUCTURE_MODE_KEYS } from './structure-mode'\n\n/** What a framework binding supplies to the composites feature. */\nexport interface CompositesUi<C> {\n /** The chips on each title line and the details popover on the kind icon; mounted as an overlay. */\n chrome: C\n /** The „Bearbeiten“ settings tab; receives `{ kinds }` as props. */\n settingsTab: C\n /** The composite block view of the framework. */\n nodeViews: CompositeNodeViews\n}\n\n/** Options of `compositeFeature`. */\nexport interface CompositeFeatureOptions {\n /** The composite kinds („Bausteine“) offered in the editor, with their own labels (default: `defaultCompositeKinds(t)`, the built-in kinds in the editor's language); read once. */\n kinds?: readonly CompositeKind[]\n}\n\n/** Heading level the structure stage starts at when it turns composites into headings (below the document title). */\nconst STRUCTURE_STAGE_LEVEL = 2\n\n/**\n * Composites („Bausteine“): the node family with its node views, the toolbar group (insert, „In Baustein umwandeln“,\n * „Gliederung umwandeln“), `/` commands, the conversion providers (wrap into / switch kind of / unwrap composites),\n * Markdown `:::` fences, legacy `section` migration, the preferred-structure setting and its `editing` settings tab,\n * the chips on each title line (status, tags) and the details popover on the kind icon (the binding's `chrome` overlay), and\n * the AI view of kinds and structure, and the kinds' accepted children (`AcceptedChildren`: an edit that nests a Baustein\n * into one that does not accept its kind is refused with a notice). Contributes the `parseMarkdown`,\n * `structure` and `migrateContent` stages. Imports no AI, media or transcript code.\n */\nexport function compositeFeatureCore<C>(ui: CompositesUi<C>, options: CompositeFeatureOptions = {}): EditorFeature<C> {\n // Host kinds keep their labels; the built-in ones are produced from the context's translator (memoized per `t`).\n const kindsOf = (ctx: { t: Translate }) => options.kinds ?? defaultCompositeKinds(ctx.t)\n const { Composite, CompositeTags, Reference } = compositeNodes(ui.nodeViews)\n return {\n id: 'composites',\n dimension: 'content',\n schemaExtensions: () => compositeSchemaExtensions(options.kinds ? { kinds: options.kinds } : {}),\n extensions: ctx => [\n Composite.configure({ kinds: kindsOf(ctx) }),\n CompositeTitleNode,\n CompositeDescriptionNode,\n CompositeTags,\n Structure.configure({ mode: () => ctx.settings.get().blocks.structure }),\n AcceptedChildren.configure({ notify: ctx.notify, t: ctx.t }),\n SourceReveal,\n Reference.configure({ suggestion: referenceSuggestion(ctx.t) }),\n ],\n toolbarGroups: COMPOSITE_TOOLBAR_GROUPS,\n commands: ctx => compositeCommands(ctx, kindsOf(ctx)),\n insertGuards: () => [compositeInsertGuard],\n blockActions: ctx => [{\n id: 'document-export',\n label: ctx.t('composites.blockAction.export'),\n icon: 'i-lucide-download',\n ability: ABILITY.compositeExport,\n order: 400,\n isVisible: ({ node }) => node.attrs.kind === 'document',\n run: ({ editor, node }) => downloadText(markdownFileName(node), documentCompositeMarkdown(editor, node)),\n }, {\n id: 'bibliography-export',\n label: ctx.t('composites.blockAction.bibtex'),\n icon: 'i-lucide-book-marked',\n ability: ABILITY.compositeExport,\n order: 410,\n isVisible: ({ node }) => bibOf(node) !== null,\n run: ({ node }) => downloadText(`${markdownFileName(node).replace(/\\.md$/, '')}.bib`, bibtexOf(cslOf(node)!), 'text/x-bibtex;charset=utf-8'),\n }],\n overlays: [ui.chrome],\n settingsTabs: ctx => [{ id: 'editing', label: ctx.t('composites.tab'), icon: 'i-lucide-pencil', component: ui.settingsTab, props: { kinds: kindsOf(ctx) } }],\n blocks: {\n parseMarkdown: (editor, markdown) => blocksFromMarkdown(md => editor.markdown!.parse(md), markdown, compositeKindsOf(editor)),\n structure: (editor, blocks) => {\n const { schema } = editor\n return structureBlocks(schema, blocks.map(block => schema.nodeFromJSON(block)), structureOf(editor), STRUCTURE_STAGE_LEVEL, compositeKindsOf(editor)).map(node => node.toJSON())\n },\n migrateContent: migrateLegacyContent,\n normalize: (editor, blocks) => normalizeReferences(blocks, compositeIdsOf(editor.state.doc)),\n },\n conversions: compositeConversions(options.kinds),\n aiContext: editor => ({\n kinds: compositeKindsOf(editor).map(({ id, label, fields, ai }) => ({ id, label, fields, ...(ai?.purpose && { purpose: ai.purpose }) })),\n structure: structureOf(editor),\n blocks: [REFERENCE_AI_BLOCK],\n }),\n // A Baustein derived from another block hands the model that source block's Markdown.\n aiBlockData: ({ editor, node }) => {\n const from = derivationOf(node)?.from\n const source = from && 'block' in from ? findBlockById(editor.state.doc, from.block)?.node : undefined\n // i18n-ignore: model-facing data label (German prompt text, not UI)\n return source?.type.name === 'composite' ? [{ label: `Quelle: ${source.child(0).textContent.trim()}`, text: bodyMarkdown(editor, source) }] : []\n },\n }\n}\n\nconst COMPOSITE_TOOLBAR_GROUPS: ToolbarGroupDef[] = [{ id: 'composites', order: 60, labelKey: 'palette.group.composites' }]\n\n/**\n * Converting headings to Bausteine and back: the checks and actions the toolbar's „Gliederung umwandeln“ menu and the\n * `/gliedern*` and `/aufloesen*` commands share, so both placements gate and act identically. They read the scope and\n * default kind from the settings at call time.\n */\nfunction structureActions({ settings }: FeatureContext) {\n const groupOptions = (recursive: boolean) => ({ recursive, scope: settings.get().editing.headingConversionScope, kind: settings.get().blocks.defaultKind })\n const flattenOptions = (recursive: boolean) => ({ recursive, scope: settings.get().editing.headingConversionScope })\n return {\n canGroup: (editor: Editor, recursive: boolean) => editor.can().convertHeadingsToComposites(groupOptions(recursive)),\n canFlatten: (editor: Editor, recursive: boolean) => editor.can().convertCompositesToHeadings(flattenOptions(recursive)),\n group: (editor: Editor, recursive: boolean) => void editor.chain().focus().convertHeadingsToComposites(groupOptions(recursive)).run(),\n flatten: (editor: Editor, recursive: boolean) => void editor.chain().focus().convertCompositesToHeadings(flattenOptions(recursive)).run(),\n }\n}\n\n/**\n * The commands of the composites feature, each declared once. Toolbar group beside the inserts: the default kind's\n * insert button (it follows the setting, so label/icon/shortcut/`creates` are getters read at render time), „In\n * Baustein umwandeln“ (one entry per kind for the caret's block) and „Gliederung umwandeln“ (headings <-> composites in\n * both directions, the preferred structure's direction first). `/` menu: one command per kind (`creates` that kind, so\n * the insert guard hides kinds the surrounding Baustein does not accept) and the four heading conversions.\n */\nfunction compositeCommands<C>(ctx: FeatureContext, kinds: readonly CompositeKind[]): CommandDef<C>[] {\n const { settings, t } = ctx\n const defaultKind = () => compositeKind(kinds, settings.get().blocks.defaultKind)\n const { canGroup, canFlatten, group, flatten } = structureActions(ctx)\n\n function kindItems(editor: Editor): MenuItem[] {\n return [\n { type: 'label', label: t('composites.menu.kindHeading') },\n ...compositeKindsOf(editor).map((kind): MenuItem => {\n const target = { type: 'composite', kind: kind.id } as const\n return { label: kind.label, icon: kind.icon, disabled: !editor.can().convertBlockTo(target), onSelect: () => editor.chain().focus().convertBlockTo(target).run() }\n }),\n ]\n }\n\n function structureItems(editor: Editor): MenuItem[] {\n const structure = settings.get().blocks.structure\n const abilities = abilitiesAtSelection(editor)\n const composite = abilities.has(ABILITY.composite)\n const headings = abilities.has(ABILITY.headings)\n const sep: MenuItem = { type: 'separator' }\n const toComposites: MenuItem[] = [\n { type: 'label', label: t('composites.menu.toComposites') },\n { label: t('composites.menu.toComposites.top'), description: t('composites.menu.toComposites.top.description'), icon: 'i-lucide-layers', disabled: !composite || !canGroup(editor, false), onSelect: () => group(editor, false) },\n { label: t('composites.menu.toComposites.all'), description: t('composites.menu.toComposites.all.description'), icon: 'i-lucide-list-tree', disabled: !composite || !canGroup(editor, true), onSelect: () => group(editor, true) },\n ]\n const toHeadings: MenuItem[] = [\n { type: 'label', label: t('composites.menu.toHeadings') },\n { label: t('composites.menu.toHeadings.top'), description: t('composites.menu.toHeadings.top.description'), icon: 'i-lucide-heading', disabled: !headings || !canFlatten(editor, false), onSelect: () => flatten(editor, false) },\n { label: t('composites.menu.toHeadings.all'), description: t('composites.menu.toHeadings.all.description'), icon: 'i-lucide-align-left', disabled: !headings || !canFlatten(editor, true), onSelect: () => flatten(editor, true) },\n ]\n const entry = editorEntryOf(editor)\n // The settings entry exists only while some feature contributes a tab to open.\n const settingsEntry: MenuItem[] = entry && settingsTabsOf(entry.preset, entry.ctx.app).length\n ? [sep, { label: t('composites.menu.settings'), icon: 'i-lucide-settings-2', onSelect: () => entry.ui.openSettings('editing') }]\n : []\n return [\n { type: 'label', label: t('composites.menu.preferred', { mode: t(STRUCTURE_MODE_KEYS[structure].labelKey) }) },\n ...(structure === 'composite' ? [...toComposites, sep, ...toHeadings] : [...toHeadings, sep, ...toComposites]),\n ...settingsEntry,\n ]\n }\n\n const insertKind = (kind: string, slashId: string, label: string, icon: string): CommandDef<C> => ({\n id: `composite.${kind}`,\n slash: { id: slashId },\n creates: { type: 'composite', attrs: { kind } },\n icon,\n label,\n ability: ABILITY.composite,\n run: (editor) => { editor.chain().focus().insertComposite(kind).run() },\n })\n\n return [\n {\n id: 'insert',\n toolbar: { group: 'composites', order: 10 },\n // Getters: the default kind arrives with the server settings; the toolbar reads them at render time.\n get icon() { return defaultKind().icon },\n get label() { return defaultKind().label },\n // Mod-Shift-a (CompositeNode) always inserts a `section`, so only then is it shown as the shortcut.\n get kbds() { return defaultKind().id === 'section' ? ['meta', 'shift', 'a'] : undefined },\n get creates() { return { type: 'composite', attrs: { kind: defaultKind().id } } },\n ability: ABILITY.composite,\n can: editor => editor.can().insertComposite(defaultKind().id),\n run: (editor) => { editor.chain().focus().insertComposite(defaultKind().id).run() },\n },\n {\n id: 'to-kind',\n toolbar: { group: 'composites', order: 20 },\n icon: 'i-lucide-replace',\n label: t('composites.toolbar.toKind'),\n ability: ABILITY.composite,\n can: editor => kindItems(editor).some(item => item.type !== 'label' && !item.disabled),\n menu: kindItems,\n },\n {\n id: 'structure',\n toolbar: { group: 'composites', order: 30 },\n icon: 'i-lucide-list-tree',\n label: t('composites.toolbar.structure'),\n can: editor => canGroup(editor, true) || canFlatten(editor, true),\n menu: structureItems,\n },\n insertKind('section', 'abschnitt', t('composites.slash.section'), 'i-lucide-layers'),\n ...kinds.filter(kind => kind.id !== 'section').map(kind => insertKind(kind.id, kind.id, kind.label, kind.icon)),\n { id: 'gliedern', slash: {}, label: t('composites.slash.group'), icon: 'i-lucide-layers', ability: ABILITY.composite, can: editor => canGroup(editor, false), run: editor => group(editor, false) },\n { id: 'gliedern-alle', slash: {}, label: t('composites.slash.groupAll'), icon: 'i-lucide-list-tree', ability: ABILITY.composite, can: editor => canGroup(editor, true), run: editor => group(editor, true) },\n { id: 'aufloesen', slash: {}, label: t('composites.slash.flatten'), icon: 'i-lucide-heading', ability: ABILITY.headings, can: editor => canFlatten(editor, false), run: editor => flatten(editor, false) },\n { id: 'aufloesen-alle', slash: {}, label: t('composites.slash.flattenAll'), icon: 'i-lucide-align-left', ability: ABILITY.headings, can: editor => canFlatten(editor, true), run: editor => flatten(editor, true) },\n { id: 'verweis', slash: {}, label: t('composites.reference.insert'), icon: 'i-lucide-link-2', ability: ABILITY.reference, run: (editor) => { editor.chain().focus().insertContent(REFERENCE_TRIGGER).run() } },\n ]\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"feature.js","names":[],"sources":["../../../src/features/composites/feature.ts"],"sourcesContent":["import type { Editor } from '@tiptap/core'\nimport type { BlockAction, CommandDef, EditorFeature, FeatureContext, MenuItem, ToolbarGroupDef } from '../../core'\nimport { abilitiesAtSelection, downloadText, editorEntryOf, settingsTabsOf } from '../../core'\nimport type { Translate } from '../../i18n'\nimport { ABILITY, blockIdOf, findBlockById } from '../../core/schema'\nimport { AcceptedChildren, compositeInsertGuard } from './accepted-children'\nimport { type Basket, defaultBaskets } from './baskets'\nimport { bibOf, bibtexOf, cslOf } from './bibliography'\nimport { compositeConversions } from './convert'\nimport { bodyMarkdown, derivationOf } from './derivation'\nimport { blocksFromMarkdown } from './fences'\nimport { structureBlocks } from './heading-composites'\nimport { type CompositeKind, compositeKind, defaultCompositeKinds } from './kinds'\nimport { migrateLegacyContent } from './legacy'\nimport { compositeSchemaExtensions } from './schema'\nimport { documentCompositeMarkdown, markdownFileName } from './markdown-export'\nimport { type CompositeNodeViews, compositeNodes } from './node-views'\nimport { COMPOSITE_CHROME_COUNT, CompositeDescriptionNode, CompositeTitleNode, compositeAround, compositeKindsOf } from './nodes'\nimport { CompositeFocus } from './focus'\nimport { REFERENCE_TRIGGER } from './reference'\nimport { REFERENCE_AI_BLOCK } from './reference-ai'\nimport { referenceSuggestion } from './reference-suggestion'\nimport { compositeIdsOf, normalizeReferences } from './reference-text'\nimport { SourceReveal } from './reveal-source'\nimport { CompositeSplit, type SplitMode } from './split'\nimport { Structure, structureOf } from './structure'\nimport { STRUCTURE_MODE_KEYS } from './structure-mode'\n\n/** What a framework binding supplies to the composites feature. */\nexport interface CompositesUi<C> {\n /** The chips on each title line and the details popover on the kind icon; mounted as an overlay. */\n chrome: C\n /** The focus chip („Fokus: <Titel> · Aufheben“) shown while a Baustein is focused; mounted as an overlay. Absent: no chip (the focus mode itself works). */\n focusChip?: C\n /** The „Bearbeiten“ settings tab; receives `{ kinds }` as props. */\n settingsTab: C\n /** The composite block view of the framework. */\n nodeViews: CompositeNodeViews\n}\n\n/** Options of `compositeFeature`. */\nexport interface CompositeFeatureOptions {\n /** The composite kinds („Bausteine“) offered in the editor, with their own labels (default: `defaultCompositeKinds(t)`, the built-in kinds in the editor's language); read once. */\n kinds?: readonly CompositeKind[]\n /** The colour baskets a Baustein can be put into (default: `defaultBaskets(t)`, red/orange/yellow/green/blue in the editor's language; colours are the `--sk-basket-<id>` CSS variables); read once. */\n baskets?: readonly Basket[]\n}\n\n/** Heading level the structure stage starts at when it turns composites into headings (below the document title). */\nconst STRUCTURE_STAGE_LEVEL = 2\n\n/**\n * Composites („Bausteine“): the node family with its node views, the toolbar group (insert, „In Baustein umwandeln“,\n * „Gliederung umwandeln“), `/` commands, the conversion providers (wrap into / switch kind of / unwrap composites),\n * Markdown `:::` fences, legacy `section` migration, the preferred-structure setting and its `editing` settings tab,\n * the chips on each title line (status, tags) and the details popover on the kind icon (the binding's `chrome` overlay), focus mode on one\n * Baustein (`CompositeFocus`, the „Fokussieren“ block action and the binding's `focusChip` overlay), and\n * the AI view of kinds and structure, and the kinds' accepted children (`AcceptedChildren`: an edit that nests a Baustein\n * into one that does not accept its kind is refused with a notice). Contributes the `parseMarkdown`,\n * `structure` and `migrateContent` stages. Imports no AI, media or transcript code.\n */\nexport function compositeFeatureCore<C>(ui: CompositesUi<C>, options: CompositeFeatureOptions = {}): EditorFeature<C> {\n // Host kinds keep their labels; the built-in ones are produced from the context's translator (memoized per `t`).\n const kindsOf = (ctx: { t: Translate }) => options.kinds ?? defaultCompositeKinds(ctx.t)\n const basketsOf = (ctx: { t: Translate }) => options.baskets ?? defaultBaskets(ctx.t)\n const { Composite, CompositeTags, Reference } = compositeNodes(ui.nodeViews)\n return {\n id: 'composites',\n dimension: 'content',\n schemaExtensions: () => compositeSchemaExtensions({ ...(options.kinds && { kinds: options.kinds }), ...(options.baskets && { baskets: options.baskets }) }),\n extensions: ctx => [\n Composite.configure({ kinds: kindsOf(ctx), baskets: basketsOf(ctx) }),\n CompositeTitleNode,\n CompositeDescriptionNode,\n CompositeTags,\n Structure.configure({ mode: () => ctx.settings.get().blocks.structure }),\n AcceptedChildren.configure({ notify: ctx.notify, t: ctx.t }),\n SourceReveal,\n CompositeFocus,\n CompositeSplit.configure({ defaultKind: () => ctx.settings.get().blocks.defaultKind, targetChars: () => ctx.settings.get().blocks.splitTargetChars }),\n Reference.configure({ suggestion: referenceSuggestion(ctx.t) }),\n ],\n toolbarGroups: COMPOSITE_TOOLBAR_GROUPS,\n commands: ctx => compositeCommands(ctx, kindsOf(ctx)),\n insertGuards: () => [compositeInsertGuard],\n blockActions: ctx => [{\n id: 'document-export',\n label: ctx.t('composites.blockAction.export'),\n icon: 'i-lucide-download',\n ability: ABILITY.compositeExport,\n order: 400,\n isVisible: ({ node }) => node.attrs.kind === 'document',\n run: ({ editor, node }) => downloadText(markdownFileName(node), documentCompositeMarkdown(editor, node)),\n }, {\n id: 'bibliography-export',\n label: ctx.t('composites.blockAction.bibtex'),\n icon: 'i-lucide-book-marked',\n ability: ABILITY.compositeExport,\n order: 410,\n isVisible: ({ node }) => bibOf(node) !== null,\n run: ({ node }) => downloadText(`${markdownFileName(node).replace(/\\.md$/, '')}.bib`, bibtexOf(cslOf(node)!), 'text/x-bibtex;charset=utf-8'),\n }, {\n id: 'composite-focus',\n label: ctx.t('composites.focus.action'),\n icon: 'i-lucide-focus',\n order: 420,\n isVisible: ({ node }) => node.type.name === 'composite',\n run: ({ editor, node }) => {\n const id = blockIdOf(node)\n if (id) editor.commands.focusComposite(id)\n },\n }, ...splitBlockActions(ctx)],\n overlays: [ui.chrome, ui.focusChip].filter(overlay => overlay !== undefined),\n settingsTabs: ctx => [{ id: 'editing', label: ctx.t('composites.tab'), icon: 'i-lucide-pencil', component: ui.settingsTab, props: { kinds: kindsOf(ctx) } }],\n blocks: {\n parseMarkdown: (editor, markdown) => blocksFromMarkdown(md => editor.markdown!.parse(md), markdown, compositeKindsOf(editor)),\n structure: (editor, blocks) => {\n const { schema } = editor\n return structureBlocks(schema, blocks.map(block => schema.nodeFromJSON(block)), structureOf(editor), STRUCTURE_STAGE_LEVEL, compositeKindsOf(editor)).map(node => node.toJSON())\n },\n migrateContent: migrateLegacyContent,\n normalize: (editor, blocks) => normalizeReferences(blocks, compositeIdsOf(editor.state.doc)),\n },\n conversions: compositeConversions(options.kinds),\n aiContext: editor => ({\n kinds: compositeKindsOf(editor).map(({ id, label, fields, ai }) => ({ id, label, fields, ...(ai?.purpose && { purpose: ai.purpose }) })),\n structure: structureOf(editor),\n blocks: [REFERENCE_AI_BLOCK],\n }),\n // A Baustein derived from another block hands the model that source block's Markdown.\n aiBlockData: ({ editor, node }) => {\n const from = derivationOf(node)?.from\n const source = from && 'block' in from ? findBlockById(editor.state.doc, from.block)?.node : undefined\n // i18n-ignore: model-facing data label (German prompt text, not UI)\n return source?.type.name === 'composite' ? [{ label: `Quelle: ${source.child(0).textContent.trim()}`, text: bodyMarkdown(editor, source) }] : []\n },\n }\n}\n\n/**\n * The four split actions of a Baustein: at the caret (as sibling or sub-Bausteine; offered on the Baustein around the\n * caret while the caret is past the first body block) and by length (as sub-Bausteine or siblings; offered while the\n * body falls into two parts or more). Each is hidden when the command refuses, so a kind that accepts no child kind\n * never shows the `children` variants.\n */\nfunction splitBlockActions({ t }: FeatureContext): BlockAction[] {\n const atCaret = (id: string, labelKey: 'composites.split.here' | 'composites.split.children', icon: string, order: number, mode: SplitMode): BlockAction => ({\n id,\n label: t(labelKey),\n icon,\n ability: ABILITY.compositeMeta,\n order,\n isVisible: ({ editor, pos }) => {\n const { $head } = editor.state.selection\n const found = compositeAround($head)\n return found?.pos === pos && $head.index(found.depth) > COMPOSITE_CHROME_COUNT && editor.can().splitCompositeAtCaret({ mode })\n },\n run: ({ editor }) => void editor.chain().focus().splitCompositeAtCaret({ mode }).run(),\n })\n const byLength = (id: string, labelKey: 'composites.split.lengthChildren' | 'composites.split.lengthSiblings', icon: string, order: number, mode: SplitMode): BlockAction => ({\n id,\n label: t(labelKey),\n icon,\n ability: ABILITY.compositeMeta,\n order,\n isVisible: ({ editor, pos }) => editor.can().splitCompositeByLength({ mode, pos }),\n run: ({ editor, pos }) => void editor.chain().focus().splitCompositeByLength({ mode, pos }).run(),\n })\n return [\n atCaret('composite-split-here', 'composites.split.here', 'i-lucide-scissors', 430, 'siblings'),\n atCaret('composite-split-children', 'composites.split.children', 'i-lucide-split', 440, 'children'),\n byLength('composite-split-length-children', 'composites.split.lengthChildren', 'i-lucide-ruler', 450, 'children'),\n byLength('composite-split-length-siblings', 'composites.split.lengthSiblings', 'i-lucide-ruler', 460, 'siblings'),\n ]\n}\n\nconst COMPOSITE_TOOLBAR_GROUPS: ToolbarGroupDef[] = [{ id: 'composites', order: 60, labelKey: 'palette.group.composites' }]\n\n/**\n * Converting headings to Bausteine and back: the checks and actions the toolbar's „Gliederung umwandeln“ menu and the\n * `/gliedern*` and `/aufloesen*` commands share, so both placements gate and act identically. They read the scope and\n * default kind from the settings at call time.\n */\nfunction structureActions({ settings }: FeatureContext) {\n const groupOptions = (recursive: boolean) => ({ recursive, scope: settings.get().editing.headingConversionScope, kind: settings.get().blocks.defaultKind })\n const flattenOptions = (recursive: boolean) => ({ recursive, scope: settings.get().editing.headingConversionScope })\n return {\n canGroup: (editor: Editor, recursive: boolean) => editor.can().convertHeadingsToComposites(groupOptions(recursive)),\n canFlatten: (editor: Editor, recursive: boolean) => editor.can().convertCompositesToHeadings(flattenOptions(recursive)),\n group: (editor: Editor, recursive: boolean) => void editor.chain().focus().convertHeadingsToComposites(groupOptions(recursive)).run(),\n flatten: (editor: Editor, recursive: boolean) => void editor.chain().focus().convertCompositesToHeadings(flattenOptions(recursive)).run(),\n }\n}\n\n/**\n * The commands of the composites feature, each declared once. Toolbar group beside the inserts: the default kind's\n * insert button (it follows the setting, so label/icon/shortcut/`creates` are getters read at render time), „In\n * Baustein umwandeln“ (one entry per kind for the caret's block) and „Gliederung umwandeln“ (headings <-> composites in\n * both directions, the preferred structure's direction first). `/` menu: one command per kind (`creates` that kind, so\n * the insert guard hides kinds the surrounding Baustein does not accept) and the four heading conversions.\n */\nfunction compositeCommands<C>(ctx: FeatureContext, kinds: readonly CompositeKind[]): CommandDef<C>[] {\n const { settings, t } = ctx\n const defaultKind = () => compositeKind(kinds, settings.get().blocks.defaultKind)\n const { canGroup, canFlatten, group, flatten } = structureActions(ctx)\n\n function kindItems(editor: Editor): MenuItem[] {\n return [\n { type: 'label', label: t('composites.menu.kindHeading') },\n ...compositeKindsOf(editor).map((kind): MenuItem => {\n const target = { type: 'composite', kind: kind.id } as const\n return { label: kind.label, icon: kind.icon, disabled: !editor.can().convertBlockTo(target), onSelect: () => editor.chain().focus().convertBlockTo(target).run() }\n }),\n ]\n }\n\n function structureItems(editor: Editor): MenuItem[] {\n const structure = settings.get().blocks.structure\n const abilities = abilitiesAtSelection(editor)\n const composite = abilities.has(ABILITY.composite)\n const headings = abilities.has(ABILITY.headings)\n const sep: MenuItem = { type: 'separator' }\n const toComposites: MenuItem[] = [\n { type: 'label', label: t('composites.menu.toComposites') },\n { label: t('composites.menu.toComposites.top'), description: t('composites.menu.toComposites.top.description'), icon: 'i-lucide-layers', disabled: !composite || !canGroup(editor, false), onSelect: () => group(editor, false) },\n { label: t('composites.menu.toComposites.all'), description: t('composites.menu.toComposites.all.description'), icon: 'i-lucide-list-tree', disabled: !composite || !canGroup(editor, true), onSelect: () => group(editor, true) },\n ]\n const toHeadings: MenuItem[] = [\n { type: 'label', label: t('composites.menu.toHeadings') },\n { label: t('composites.menu.toHeadings.top'), description: t('composites.menu.toHeadings.top.description'), icon: 'i-lucide-heading', disabled: !headings || !canFlatten(editor, false), onSelect: () => flatten(editor, false) },\n { label: t('composites.menu.toHeadings.all'), description: t('composites.menu.toHeadings.all.description'), icon: 'i-lucide-align-left', disabled: !headings || !canFlatten(editor, true), onSelect: () => flatten(editor, true) },\n ]\n const entry = editorEntryOf(editor)\n // The settings entry exists only while some feature contributes a tab to open.\n const settingsEntry: MenuItem[] = entry && settingsTabsOf(entry.preset, entry.ctx.app).length\n ? [sep, { label: t('composites.menu.settings'), icon: 'i-lucide-settings-2', onSelect: () => entry.ui.openSettings('editing') }]\n : []\n return [\n { type: 'label', label: t('composites.menu.preferred', { mode: t(STRUCTURE_MODE_KEYS[structure].labelKey) }) },\n ...(structure === 'composite' ? [...toComposites, sep, ...toHeadings] : [...toHeadings, sep, ...toComposites]),\n ...settingsEntry,\n ]\n }\n\n const insertKind = (kind: string, slashId: string, label: string, icon: string): CommandDef<C> => ({\n id: `composite.${kind}`,\n slash: { id: slashId },\n creates: { type: 'composite', attrs: { kind } },\n icon,\n label,\n ability: ABILITY.composite,\n run: (editor) => { editor.chain().focus().insertComposite(kind).run() },\n })\n\n return [\n {\n id: 'insert',\n toolbar: { group: 'composites', order: 10 },\n // Getters: the default kind arrives with the server settings; the toolbar reads them at render time.\n get icon() { return defaultKind().icon },\n get label() { return defaultKind().label },\n // Mod-Shift-a (CompositeNode) always inserts a `section`, so only then is it shown as the shortcut.\n get kbds() { return defaultKind().id === 'section' ? ['meta', 'shift', 'a'] : undefined },\n get creates() { return { type: 'composite', attrs: { kind: defaultKind().id } } },\n ability: ABILITY.composite,\n can: editor => editor.can().insertComposite(defaultKind().id),\n run: (editor) => { editor.chain().focus().insertComposite(defaultKind().id).run() },\n },\n {\n id: 'to-kind',\n toolbar: { group: 'composites', order: 20 },\n icon: 'i-lucide-replace',\n label: t('composites.toolbar.toKind'),\n ability: ABILITY.composite,\n can: editor => kindItems(editor).some(item => item.type !== 'label' && !item.disabled),\n menu: kindItems,\n },\n {\n id: 'structure',\n toolbar: { group: 'composites', order: 30 },\n icon: 'i-lucide-list-tree',\n label: t('composites.toolbar.structure'),\n can: editor => canGroup(editor, true) || canFlatten(editor, true),\n menu: structureItems,\n },\n insertKind('section', 'abschnitt', t('composites.slash.section'), 'i-lucide-layers'),\n ...kinds.filter(kind => kind.id !== 'section').map(kind => insertKind(kind.id, kind.id, kind.label, kind.icon)),\n { id: 'gliedern', slash: {}, label: t('composites.slash.group'), icon: 'i-lucide-layers', ability: ABILITY.composite, can: editor => canGroup(editor, false), run: editor => group(editor, false) },\n { id: 'gliedern-alle', slash: {}, label: t('composites.slash.groupAll'), icon: 'i-lucide-list-tree', ability: ABILITY.composite, can: editor => canGroup(editor, true), run: editor => group(editor, true) },\n { id: 'aufloesen', slash: {}, label: t('composites.slash.flatten'), icon: 'i-lucide-heading', ability: ABILITY.headings, can: editor => canFlatten(editor, false), run: editor => flatten(editor, false) },\n { id: 'aufloesen-alle', slash: {}, label: t('composites.slash.flattenAll'), icon: 'i-lucide-align-left', ability: ABILITY.headings, can: editor => canFlatten(editor, true), run: editor => flatten(editor, true) },\n { id: 'verweis', slash: {}, label: t('composites.reference.insert'), icon: 'i-lucide-link-2', ability: ABILITY.reference, run: (editor) => { editor.chain().focus().insertContent(REFERENCE_TRIGGER).run() } },\n ]\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,MAAM,wBAAwB;;;;;;;;;;;AAY9B,SAAgB,qBAAwB,IAAqB,UAAmC,CAAC,GAAqB;CAEpH,MAAM,WAAW,QAA0B,QAAQ,SAAS,sBAAsB,IAAI,CAAC;CACvF,MAAM,aAAa,QAA0B,QAAQ,WAAW,eAAe,IAAI,CAAC;CACpF,MAAM,EAAE,WAAW,eAAe,cAAc,eAAe,GAAG,SAAS;CAC3E,OAAO;EACL,IAAI;EACJ,WAAW;EACX,wBAAwB,0BAA0B;GAAE,GAAI,QAAQ,SAAS,EAAE,OAAO,QAAQ,MAAM;GAAI,GAAI,QAAQ,WAAW,EAAE,SAAS,QAAQ,QAAQ;EAAG,CAAC;EAC1J,aAAY,QAAO;GACjB,UAAU,UAAU;IAAE,OAAO,QAAQ,GAAG;IAAG,SAAS,UAAU,GAAG;GAAE,CAAC;GACpE;GACA;GACA;GACA,UAAU,UAAU,EAAE,YAAY,IAAI,SAAS,IAAI,CAAC,CAAC,OAAO,UAAU,CAAC;GACvE,iBAAiB,UAAU;IAAE,QAAQ,IAAI;IAAQ,GAAG,IAAI;GAAE,CAAC;GAC3D;GACA;GACA,eAAe,UAAU;IAAE,mBAAmB,IAAI,SAAS,IAAI,CAAC,CAAC,OAAO;IAAa,mBAAmB,IAAI,SAAS,IAAI,CAAC,CAAC,OAAO;GAAiB,CAAC;GACpJ,UAAU,UAAU,EAAE,YAAY,oBAAoB,IAAI,CAAC,EAAE,CAAC;EAChE;EACA,eAAe;EACf,WAAU,QAAO,kBAAkB,KAAK,QAAQ,GAAG,CAAC;EACpD,oBAAoB,CAAC,oBAAoB;EACzC,eAAc,QAAO;GAAC;IACpB,IAAI;IACJ,OAAO,IAAI,EAAE,+BAA+B;IAC5C,MAAM;IACN,SAAS,QAAQ;IACjB,OAAO;IACP,YAAY,EAAE,WAAW,KAAK,MAAM,SAAS;IAC7C,MAAM,EAAE,QAAQ,WAAW,aAAa,iBAAiB,IAAI,GAAG,0BAA0B,QAAQ,IAAI,CAAC;GACzG;GAAG;IACD,IAAI;IACJ,OAAO,IAAI,EAAE,+BAA+B;IAC5C,MAAM;IACN,SAAS,QAAQ;IACjB,OAAO;IACP,YAAY,EAAE,WAAW,MAAM,IAAI,MAAM;IACzC,MAAM,EAAE,WAAW,aAAa,GAAG,iBAAiB,IAAI,CAAC,CAAC,QAAQ,SAAS,EAAE,EAAE,OAAO,SAAS,MAAM,IAAI,CAAE,GAAG,6BAA6B;GAC7I;GAAG;IACD,IAAI;IACJ,OAAO,IAAI,EAAE,yBAAyB;IACtC,MAAM;IACN,OAAO;IACP,YAAY,EAAE,WAAW,KAAK,KAAK,SAAS;IAC5C,MAAM,EAAE,QAAQ,WAAW;KACzB,MAAM,KAAK,UAAU,IAAI;KACzB,IAAI,IAAI,OAAO,SAAS,eAAe,EAAE;IAC3C;GACF;GAAG,GAAG,kBAAkB,GAAG;EAAC;EAC5B,UAAU,CAAC,GAAG,QAAQ,GAAG,SAAS,CAAC,CAAC,QAAO,YAAW,YAAY,KAAA,CAAS;EAC3E,eAAc,QAAO,CAAC;GAAE,IAAI;GAAW,OAAO,IAAI,EAAE,gBAAgB;GAAG,MAAM;GAAmB,WAAW,GAAG;GAAa,OAAO,EAAE,OAAO,QAAQ,GAAG,EAAE;EAAE,CAAC;EAC3J,QAAQ;GACN,gBAAgB,QAAQ,aAAa,oBAAmB,OAAM,OAAO,SAAU,MAAM,EAAE,GAAG,UAAU,iBAAiB,MAAM,CAAC;GAC5H,YAAY,QAAQ,WAAW;IAC7B,MAAM,EAAE,WAAW;IACnB,OAAO,gBAAgB,QAAQ,OAAO,KAAI,UAAS,OAAO,aAAa,KAAK,CAAC,GAAG,YAAY,MAAM,GAAG,uBAAuB,iBAAiB,MAAM,CAAC,CAAC,CAAC,KAAI,SAAQ,KAAK,OAAO,CAAC;GACjL;GACA,gBAAgB;GAChB,YAAY,QAAQ,WAAW,oBAAoB,QAAQ,eAAe,OAAO,MAAM,GAAG,CAAC;EAC7F;EACA,aAAa,qBAAqB,QAAQ,KAAK;EAC/C,YAAW,YAAW;GACpB,OAAO,iBAAiB,MAAM,CAAC,CAAC,KAAK,EAAE,IAAI,OAAO,QAAQ,UAAU;IAAE;IAAI;IAAO;IAAQ,GAAI,IAAI,WAAW,EAAE,SAAS,GAAG,QAAQ;GAAG,EAAE;GACvI,WAAW,YAAY,MAAM;GAC7B,QAAQ,CAAC,kBAAkB;EAC7B;EAEA,cAAc,EAAE,QAAQ,WAAW;GACjC,MAAM,OAAO,aAAa,IAAI,CAAC,EAAE;GACjC,MAAM,SAAS,QAAQ,WAAW,OAAO,cAAc,OAAO,MAAM,KAAK,KAAK,KAAK,CAAC,EAAE,OAAO,KAAA;GAE7F,OAAO,QAAQ,KAAK,SAAS,cAAc,CAAC;IAAE,OAAO,WAAW,OAAO,MAAM,CAAC,CAAC,CAAC,YAAY,KAAK;IAAK,MAAM,aAAa,QAAQ,MAAM;GAAE,CAAC,IAAI,CAAC;EACjJ;CACF;AACF;;;;;;;AAQA,SAAS,kBAAkB,EAAE,KAAoC;CAC/D,MAAM,WAAW,IAAY,UAAiE,MAAc,OAAe,UAAkC;EAC3J;EACA,OAAO,EAAE,QAAQ;EACjB;EACA,SAAS,QAAQ;EACjB;EACA,YAAY,EAAE,QAAQ,UAAU;GAC9B,MAAM,EAAE,UAAU,OAAO,MAAM;GAC/B,MAAM,QAAQ,gBAAgB,KAAK;GACnC,OAAO,OAAO,QAAQ,OAAO,MAAM,MAAM,MAAM,KAAK,IAAA,KAA8B,OAAO,IAAI,CAAC,CAAC,sBAAsB,EAAE,KAAK,CAAC;EAC/H;EACA,MAAM,EAAE,aAAa,KAAK,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,sBAAsB,EAAE,KAAK,CAAC,CAAC,CAAC,IAAI;CACvF;CACA,MAAM,YAAY,IAAY,UAAiF,MAAc,OAAe,UAAkC;EAC5K;EACA,OAAO,EAAE,QAAQ;EACjB;EACA,SAAS,QAAQ;EACjB;EACA,YAAY,EAAE,QAAQ,UAAU,OAAO,IAAI,CAAC,CAAC,uBAAuB;GAAE;GAAM;EAAI,CAAC;EACjF,MAAM,EAAE,QAAQ,UAAU,KAAK,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,uBAAuB;GAAE;GAAM;EAAI,CAAC,CAAC,CAAC,IAAI;CAClG;CACA,OAAO;EACL,QAAQ,wBAAwB,yBAAyB,qBAAqB,KAAK,UAAU;EAC7F,QAAQ,4BAA4B,6BAA6B,kBAAkB,KAAK,UAAU;EAClG,SAAS,mCAAmC,mCAAmC,kBAAkB,KAAK,UAAU;EAChH,SAAS,mCAAmC,mCAAmC,kBAAkB,KAAK,UAAU;CAClH;AACF;AAEA,MAAM,2BAA8C,CAAC;CAAE,IAAI;CAAc,OAAO;CAAI,UAAU;AAA2B,CAAC;;;;;;AAO1H,SAAS,iBAAiB,EAAE,YAA4B;CACtD,MAAM,gBAAgB,eAAwB;EAAE;EAAW,OAAO,SAAS,IAAI,CAAC,CAAC,QAAQ;EAAwB,MAAM,SAAS,IAAI,CAAC,CAAC,OAAO;CAAY;CACzJ,MAAM,kBAAkB,eAAwB;EAAE;EAAW,OAAO,SAAS,IAAI,CAAC,CAAC,QAAQ;CAAuB;CAClH,OAAO;EACL,WAAW,QAAgB,cAAuB,OAAO,IAAI,CAAC,CAAC,4BAA4B,aAAa,SAAS,CAAC;EAClH,aAAa,QAAgB,cAAuB,OAAO,IAAI,CAAC,CAAC,4BAA4B,eAAe,SAAS,CAAC;EACtH,QAAQ,QAAgB,cAAuB,KAAK,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,4BAA4B,aAAa,SAAS,CAAC,CAAC,CAAC,IAAI;EACpI,UAAU,QAAgB,cAAuB,KAAK,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,4BAA4B,eAAe,SAAS,CAAC,CAAC,CAAC,IAAI;CAC1I;AACF;;;;;;;;AASA,SAAS,kBAAqB,KAAqB,OAAkD;CACnG,MAAM,EAAE,UAAU,MAAM;CACxB,MAAM,oBAAoB,cAAc,OAAO,SAAS,IAAI,CAAC,CAAC,OAAO,WAAW;CAChF,MAAM,EAAE,UAAU,YAAY,OAAO,YAAY,iBAAiB,GAAG;CAErE,SAAS,UAAU,QAA4B;EAC7C,OAAO,CACL;GAAE,MAAM;GAAS,OAAO,EAAE,6BAA6B;EAAE,GACzD,GAAG,iBAAiB,MAAM,CAAC,CAAC,KAAK,SAAmB;GAClD,MAAM,SAAS;IAAE,MAAM;IAAa,MAAM,KAAK;GAAG;GAClD,OAAO;IAAE,OAAO,KAAK;IAAO,MAAM,KAAK;IAAM,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC,eAAe,MAAM;IAAG,gBAAgB,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,eAAe,MAAM,CAAC,CAAC,IAAI;GAAE;EACnK,CAAC,CACH;CACF;CAEA,SAAS,eAAe,QAA4B;EAClD,MAAM,YAAY,SAAS,IAAI,CAAC,CAAC,OAAO;EACxC,MAAM,YAAY,qBAAqB,MAAM;EAC7C,MAAM,YAAY,UAAU,IAAI,QAAQ,SAAS;EACjD,MAAM,WAAW,UAAU,IAAI,QAAQ,QAAQ;EAC/C,MAAM,MAAgB,EAAE,MAAM,YAAY;EAC1C,MAAM,eAA2B;GAC/B;IAAE,MAAM;IAAS,OAAO,EAAE,8BAA8B;GAAE;GAC1D;IAAE,OAAO,EAAE,kCAAkC;IAAG,aAAa,EAAE,8CAA8C;IAAG,MAAM;IAAmB,UAAU,CAAC,aAAa,CAAC,SAAS,QAAQ,KAAK;IAAG,gBAAgB,MAAM,QAAQ,KAAK;GAAE;GAChO;IAAE,OAAO,EAAE,kCAAkC;IAAG,aAAa,EAAE,8CAA8C;IAAG,MAAM;IAAsB,UAAU,CAAC,aAAa,CAAC,SAAS,QAAQ,IAAI;IAAG,gBAAgB,MAAM,QAAQ,IAAI;GAAE;EACnO;EACA,MAAM,aAAyB;GAC7B;IAAE,MAAM;IAAS,OAAO,EAAE,4BAA4B;GAAE;GACxD;IAAE,OAAO,EAAE,gCAAgC;IAAG,aAAa,EAAE,4CAA4C;IAAG,MAAM;IAAoB,UAAU,CAAC,YAAY,CAAC,WAAW,QAAQ,KAAK;IAAG,gBAAgB,QAAQ,QAAQ,KAAK;GAAE;GAChO;IAAE,OAAO,EAAE,gCAAgC;IAAG,aAAa,EAAE,4CAA4C;IAAG,MAAM;IAAuB,UAAU,CAAC,YAAY,CAAC,WAAW,QAAQ,IAAI;IAAG,gBAAgB,QAAQ,QAAQ,IAAI;GAAE;EACnO;EACA,MAAM,QAAQ,cAAc,MAAM;EAElC,MAAM,gBAA4B,SAAS,eAAe,MAAM,QAAQ,MAAM,IAAI,GAAG,CAAC,CAAC,SACnF,CAAC,KAAK;GAAE,OAAO,EAAE,0BAA0B;GAAG,MAAM;GAAuB,gBAAgB,MAAM,GAAG,aAAa,SAAS;EAAE,CAAC,IAC7H,CAAC;EACL,OAAO;GACL;IAAE,MAAM;IAAS,OAAO,EAAE,6BAA6B,EAAE,MAAM,EAAE,oBAAoB,UAAU,CAAC,QAAQ,EAAE,CAAC;GAAE;GAC7G,GAAI,cAAc,cAAc;IAAC,GAAG;IAAc;IAAK,GAAG;GAAU,IAAI;IAAC,GAAG;IAAY;IAAK,GAAG;GAAY;GAC5G,GAAG;EACL;CACF;CAEA,MAAM,cAAc,MAAc,SAAiB,OAAe,UAAiC;EACjG,IAAI,aAAa;EACjB,OAAO,EAAE,IAAI,QAAQ;EACrB,SAAS;GAAE,MAAM;GAAa,OAAO,EAAE,KAAK;EAAE;EAC9C;EACA;EACA,SAAS,QAAQ;EACjB,MAAM,WAAW;GAAE,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,gBAAgB,IAAI,CAAC,CAAC,IAAI;EAAE;CACxE;CAEA,OAAO;EACL;GACE,IAAI;GACJ,SAAS;IAAE,OAAO;IAAc,OAAO;GAAG;GAE1C,IAAI,OAAO;IAAE,OAAO,YAAY,CAAC,CAAC;GAAK;GACvC,IAAI,QAAQ;IAAE,OAAO,YAAY,CAAC,CAAC;GAAM;GAEzC,IAAI,OAAO;IAAE,OAAO,YAAY,CAAC,CAAC,OAAO,YAAY;KAAC;KAAQ;KAAS;IAAG,IAAI,KAAA;GAAU;GACxF,IAAI,UAAU;IAAE,OAAO;KAAE,MAAM;KAAa,OAAO,EAAE,MAAM,YAAY,CAAC,CAAC,GAAG;IAAE;GAAE;GAChF,SAAS,QAAQ;GACjB,MAAK,WAAU,OAAO,IAAI,CAAC,CAAC,gBAAgB,YAAY,CAAC,CAAC,EAAE;GAC5D,MAAM,WAAW;IAAE,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,gBAAgB,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI;GAAE;EACpF;EACA;GACE,IAAI;GACJ,SAAS;IAAE,OAAO;IAAc,OAAO;GAAG;GAC1C,MAAM;GACN,OAAO,EAAE,2BAA2B;GACpC,SAAS,QAAQ;GACjB,MAAK,WAAU,UAAU,MAAM,CAAC,CAAC,MAAK,SAAQ,KAAK,SAAS,WAAW,CAAC,KAAK,QAAQ;GACrF,MAAM;EACR;EACA;GACE,IAAI;GACJ,SAAS;IAAE,OAAO;IAAc,OAAO;GAAG;GAC1C,MAAM;GACN,OAAO,EAAE,8BAA8B;GACvC,MAAK,WAAU,SAAS,QAAQ,IAAI,KAAK,WAAW,QAAQ,IAAI;GAChE,MAAM;EACR;EACA,WAAW,WAAW,aAAa,EAAE,0BAA0B,GAAG,iBAAiB;EACnF,GAAG,MAAM,QAAO,SAAQ,KAAK,OAAO,SAAS,CAAC,CAAC,KAAI,SAAQ,WAAW,KAAK,IAAI,KAAK,IAAI,KAAK,OAAO,KAAK,IAAI,CAAC;EAC9G;GAAE,IAAI;GAAY,OAAO,CAAC;GAAG,OAAO,EAAE,wBAAwB;GAAG,MAAM;GAAmB,SAAS,QAAQ;GAAW,MAAK,WAAU,SAAS,QAAQ,KAAK;GAAG,MAAK,WAAU,MAAM,QAAQ,KAAK;EAAE;EAClM;GAAE,IAAI;GAAiB,OAAO,CAAC;GAAG,OAAO,EAAE,2BAA2B;GAAG,MAAM;GAAsB,SAAS,QAAQ;GAAW,MAAK,WAAU,SAAS,QAAQ,IAAI;GAAG,MAAK,WAAU,MAAM,QAAQ,IAAI;EAAE;EAC3M;GAAE,IAAI;GAAa,OAAO,CAAC;GAAG,OAAO,EAAE,0BAA0B;GAAG,MAAM;GAAoB,SAAS,QAAQ;GAAU,MAAK,WAAU,WAAW,QAAQ,KAAK;GAAG,MAAK,WAAU,QAAQ,QAAQ,KAAK;EAAE;EACzM;GAAE,IAAI;GAAkB,OAAO,CAAC;GAAG,OAAO,EAAE,6BAA6B;GAAG,MAAM;GAAuB,SAAS,QAAQ;GAAU,MAAK,WAAU,WAAW,QAAQ,IAAI;GAAG,MAAK,WAAU,QAAQ,QAAQ,IAAI;EAAE;EAClN;GAAE,IAAI;GAAW,OAAO,CAAC;GAAG,OAAO,EAAE,6BAA6B;GAAG,MAAM;GAAmB,SAAS,QAAQ;GAAW,MAAM,WAAW;IAAE,OAAO,MAAM,CAAC,CAAC,MAAM,CAAC,CAAC,cAAA,IAA+B,CAAC,CAAC,IAAI;GAAE;EAAE;CAC/M;AACF"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Extension } from "@tiptap/core";
|
|
2
|
+
import { EditorState, PluginKey } from "@tiptap/pm/state";
|
|
3
|
+
import { Node as Node$1 } from "@tiptap/pm/model";
|
|
4
|
+
import { DecorationSet } from "@tiptap/pm/view";
|
|
5
|
+
//#region src/features/composites/focus.d.ts
|
|
6
|
+
/** State of `compositeFocusKey`: the focused Baustein's block id (`null` = no focus) and the decorations that hide the rest. */
|
|
7
|
+
export interface CompositeFocusState {
|
|
8
|
+
id: string | null;
|
|
9
|
+
decorations: DecorationSet;
|
|
10
|
+
}
|
|
11
|
+
declare module '@tiptap/core' {
|
|
12
|
+
interface Commands<ReturnType> {
|
|
13
|
+
compositeFocus: {
|
|
14
|
+
/**
|
|
15
|
+
* Focus mode: show only the Baustein with block id `id` and the titles of the Bausteine around it (the siblings of it
|
|
16
|
+
* and of every ancestor are hidden); `null` ends it. False when `id` is not a Baustein of the document. A caret outside
|
|
17
|
+
* the focused Baustein moves into its title, so no edit lands in hidden content. Resets by itself when the Baustein is gone.
|
|
18
|
+
*/
|
|
19
|
+
focusComposite: (id: string | null) => ReturnType;
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export declare const compositeFocusKey: PluginKey<CompositeFocusState>;
|
|
24
|
+
/** The block id of the focused Baustein in `state`, `null` without focus (or without the extension). */
|
|
25
|
+
export declare const compositeFocusOf: (state: EditorState) => string | null;
|
|
26
|
+
/**
|
|
27
|
+
* The decorations of focus mode on Baustein `id` in `doc`: every sibling of the Baustein and of each of its ancestors gets
|
|
28
|
+
* `sk-focus-hidden` (a Baustein's title, description and tags are not siblings in this sense: they stay, so the ancestors
|
|
29
|
+
* keep their titles), the Baustein itself `sk-focus-target`, each ancestor `sk-focus-ancestor`. Empty when `id` is not in `doc`.
|
|
30
|
+
*/
|
|
31
|
+
export declare function focusDecorations(doc: Node$1, id: string): DecorationSet;
|
|
32
|
+
/**
|
|
33
|
+
* Focus mode of Bausteine (client-only, no schema): the `focusComposite` command, the plugin behind it and a reset
|
|
34
|
+
* when the focused Baustein disappears. The hiding itself is CSS on the classes the decorations carry (`composites.css`).
|
|
35
|
+
*/
|
|
36
|
+
export declare const CompositeFocus: Extension<any, any>;
|
|
37
|
+
//#endregion
|
|
38
|
+
//# sourceMappingURL=focus.d.ts.map
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { findBlockById } from "../../core/schema/block-ids.js";
|
|
2
|
+
import "./nodes.js";
|
|
3
|
+
import { Extension } from "@tiptap/core";
|
|
4
|
+
import { Plugin, PluginKey, TextSelection } from "@tiptap/pm/state";
|
|
5
|
+
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
6
|
+
//#region src/features/composites/focus.ts
|
|
7
|
+
/** Class of every node hidden by focus mode (siblings of the focused Baustein and of each ancestor). */
|
|
8
|
+
const FOCUS_HIDDEN_CLASS = "sk-focus-hidden";
|
|
9
|
+
/** Class of the focused Baustein. */
|
|
10
|
+
const FOCUS_TARGET_CLASS = "sk-focus-target";
|
|
11
|
+
/** Class of every ancestor of the focused Baustein. */
|
|
12
|
+
const FOCUS_ANCESTOR_CLASS = "sk-focus-ancestor";
|
|
13
|
+
const compositeFocusKey = new PluginKey("compositeFocus");
|
|
14
|
+
/** The block id of the focused Baustein in `state`, `null` without focus (or without the extension). */
|
|
15
|
+
const compositeFocusOf = (state) => compositeFocusKey.getState(state)?.id ?? null;
|
|
16
|
+
/**
|
|
17
|
+
* The decorations of focus mode on Baustein `id` in `doc`: every sibling of the Baustein and of each of its ancestors gets
|
|
18
|
+
* `sk-focus-hidden` (a Baustein's title, description and tags are not siblings in this sense: they stay, so the ancestors
|
|
19
|
+
* keep their titles), the Baustein itself `sk-focus-target`, each ancestor `sk-focus-ancestor`. Empty when `id` is not in `doc`.
|
|
20
|
+
*/
|
|
21
|
+
function focusDecorations(doc, id) {
|
|
22
|
+
const found = findBlockById(doc, id);
|
|
23
|
+
if (!found) return DecorationSet.empty;
|
|
24
|
+
const $pos = doc.resolve(found.pos);
|
|
25
|
+
const decorations = [Decoration.node(found.pos, found.pos + found.node.nodeSize, { class: FOCUS_TARGET_CLASS })];
|
|
26
|
+
for (let depth = $pos.depth; depth >= 0; depth--) {
|
|
27
|
+
const parent = $pos.node(depth);
|
|
28
|
+
const onPath = $pos.index(depth);
|
|
29
|
+
const start = $pos.start(depth);
|
|
30
|
+
const isComposite = parent.type.name === "composite";
|
|
31
|
+
parent.forEach((child, offset, index) => {
|
|
32
|
+
if (index === onPath || isComposite && index < 3) return;
|
|
33
|
+
const pos = start + offset;
|
|
34
|
+
decorations.push(Decoration.node(pos, pos + child.nodeSize, { class: FOCUS_HIDDEN_CLASS }));
|
|
35
|
+
});
|
|
36
|
+
if (depth > 0) decorations.push(Decoration.node($pos.before(depth), $pos.after(depth), { class: FOCUS_ANCESTOR_CLASS }));
|
|
37
|
+
}
|
|
38
|
+
return DecorationSet.create(doc, decorations);
|
|
39
|
+
}
|
|
40
|
+
const unfocused = {
|
|
41
|
+
id: null,
|
|
42
|
+
decorations: DecorationSet.empty
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Focus mode of Bausteine (client-only, no schema): the `focusComposite` command, the plugin behind it and a reset
|
|
46
|
+
* when the focused Baustein disappears. The hiding itself is CSS on the classes the decorations carry (`composites.css`).
|
|
47
|
+
*/
|
|
48
|
+
const CompositeFocus = Extension.create({
|
|
49
|
+
name: "compositeFocus",
|
|
50
|
+
addCommands: () => ({ focusComposite: (id) => ({ state, tr }) => {
|
|
51
|
+
if (id !== null) {
|
|
52
|
+
const found = findBlockById(state.doc, id);
|
|
53
|
+
if (found?.node.type.name !== "composite") return false;
|
|
54
|
+
const { from, to } = state.selection;
|
|
55
|
+
if (from < found.pos || to > found.pos + found.node.nodeSize) tr.setSelection(TextSelection.near(tr.doc.resolve(found.pos + 2)));
|
|
56
|
+
}
|
|
57
|
+
tr.setMeta(compositeFocusKey, { id });
|
|
58
|
+
return true;
|
|
59
|
+
} }),
|
|
60
|
+
addProseMirrorPlugins: () => [new Plugin({
|
|
61
|
+
key: compositeFocusKey,
|
|
62
|
+
state: {
|
|
63
|
+
init: () => unfocused,
|
|
64
|
+
apply(tr, value) {
|
|
65
|
+
const meta = tr.getMeta(compositeFocusKey);
|
|
66
|
+
const id = meta ? meta.id : value.id;
|
|
67
|
+
if (id === null) return value.id === null ? value : unfocused;
|
|
68
|
+
if (!meta && !tr.docChanged) return value;
|
|
69
|
+
return {
|
|
70
|
+
id,
|
|
71
|
+
decorations: focusDecorations(tr.doc, id)
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
appendTransaction(transactions, _before, after) {
|
|
76
|
+
const id = compositeFocusKey.getState(after)?.id;
|
|
77
|
+
if (!id || !transactions.some((tr) => tr.docChanged || tr.getMeta(compositeFocusKey))) return null;
|
|
78
|
+
return findBlockById(after.doc, id) ? null : after.tr.setMeta(compositeFocusKey, { id: null }).setMeta("addToHistory", false);
|
|
79
|
+
},
|
|
80
|
+
props: { decorations: (state) => compositeFocusKey.getState(state)?.decorations }
|
|
81
|
+
})]
|
|
82
|
+
});
|
|
83
|
+
//#endregion
|
|
84
|
+
export { CompositeFocus, FOCUS_ANCESTOR_CLASS, FOCUS_HIDDEN_CLASS, FOCUS_TARGET_CLASS, compositeFocusKey, compositeFocusOf, focusDecorations };
|
|
85
|
+
|
|
86
|
+
//# sourceMappingURL=focus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"focus.js","names":[],"sources":["../../../src/features/composites/focus.ts"],"sourcesContent":["import { Extension } from '@tiptap/core'\nimport type { Node as PMNode } from '@tiptap/pm/model'\nimport { type EditorState, Plugin, PluginKey, TextSelection } from '@tiptap/pm/state'\nimport { Decoration, DecorationSet } from '@tiptap/pm/view'\nimport { findBlockById } from '../../core/schema'\nimport { COMPOSITE_CHROME_COUNT } from './nodes'\n\n/** State of `compositeFocusKey`: the focused Baustein's block id (`null` = no focus) and the decorations that hide the rest. */\nexport interface CompositeFocusState {\n id: string | null\n decorations: DecorationSet\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n compositeFocus: {\n /**\n * Focus mode: show only the Baustein with block id `id` and the titles of the Bausteine around it (the siblings of it\n * and of every ancestor are hidden); `null` ends it. False when `id` is not a Baustein of the document. A caret outside\n * the focused Baustein moves into its title, so no edit lands in hidden content. Resets by itself when the Baustein is gone.\n */\n focusComposite: (id: string | null) => ReturnType\n }\n }\n}\n\n/** Class of every node hidden by focus mode (siblings of the focused Baustein and of each ancestor). */\nexport const FOCUS_HIDDEN_CLASS = 'sk-focus-hidden'\n/** Class of the focused Baustein. */\nexport const FOCUS_TARGET_CLASS = 'sk-focus-target'\n/** Class of every ancestor of the focused Baustein. */\nexport const FOCUS_ANCESTOR_CLASS = 'sk-focus-ancestor'\n\nexport const compositeFocusKey = new PluginKey<CompositeFocusState>('compositeFocus')\n\n/** The block id of the focused Baustein in `state`, `null` without focus (or without the extension). */\nexport const compositeFocusOf = (state: EditorState): string | null => compositeFocusKey.getState(state)?.id ?? null\n\n/**\n * The decorations of focus mode on Baustein `id` in `doc`: every sibling of the Baustein and of each of its ancestors gets\n * `sk-focus-hidden` (a Baustein's title, description and tags are not siblings in this sense: they stay, so the ancestors\n * keep their titles), the Baustein itself `sk-focus-target`, each ancestor `sk-focus-ancestor`. Empty when `id` is not in `doc`.\n */\nexport function focusDecorations(doc: PMNode, id: string): DecorationSet {\n const found = findBlockById(doc, id)\n if (!found) return DecorationSet.empty\n const $pos = doc.resolve(found.pos)\n const decorations: Decoration[] = [Decoration.node(found.pos, found.pos + found.node.nodeSize, { class: FOCUS_TARGET_CLASS })]\n for (let depth = $pos.depth; depth >= 0; depth--) {\n const parent = $pos.node(depth)\n const onPath = $pos.index(depth)\n const start = $pos.start(depth)\n const isComposite = parent.type.name === 'composite'\n parent.forEach((child, offset, index) => {\n if (index === onPath || (isComposite && index < COMPOSITE_CHROME_COUNT)) return\n const pos = start + offset\n decorations.push(Decoration.node(pos, pos + child.nodeSize, { class: FOCUS_HIDDEN_CLASS }))\n })\n if (depth > 0) decorations.push(Decoration.node($pos.before(depth), $pos.after(depth), { class: FOCUS_ANCESTOR_CLASS }))\n }\n return DecorationSet.create(doc, decorations)\n}\n\nconst unfocused: CompositeFocusState = { id: null, decorations: DecorationSet.empty }\n\n/**\n * Focus mode of Bausteine (client-only, no schema): the `focusComposite` command, the plugin behind it and a reset\n * when the focused Baustein disappears. The hiding itself is CSS on the classes the decorations carry (`composites.css`).\n */\nexport const CompositeFocus = Extension.create({\n name: 'compositeFocus',\n addCommands: () => ({\n focusComposite: (id: string | null) => ({ state, tr }) => {\n if (id !== null) {\n const found = findBlockById(state.doc, id)\n if (found?.node.type.name !== 'composite') return false\n const { from, to } = state.selection\n // The caret in hidden content would edit what nobody sees: move it into the focused Baustein's title.\n if (from < found.pos || to > found.pos + found.node.nodeSize) tr.setSelection(TextSelection.near(tr.doc.resolve(found.pos + 2)))\n }\n tr.setMeta(compositeFocusKey, { id })\n return true\n },\n }),\n addProseMirrorPlugins: () => [\n new Plugin<CompositeFocusState>({\n key: compositeFocusKey,\n state: {\n init: () => unfocused,\n apply(tr, value) {\n const meta = tr.getMeta(compositeFocusKey) as { id: string | null } | undefined\n const id = meta ? meta.id : value.id\n if (id === null) return value.id === null ? value : unfocused\n if (!meta && !tr.docChanged) return value\n return { id, decorations: focusDecorations(tr.doc, id) }\n },\n },\n appendTransaction(transactions, _before, after) {\n const id = compositeFocusKey.getState(after)?.id\n if (!id || !transactions.some(tr => tr.docChanged || tr.getMeta(compositeFocusKey))) return null\n return findBlockById(after.doc, id) ? null : after.tr.setMeta(compositeFocusKey, { id: null }).setMeta('addToHistory', false)\n },\n props: {\n decorations: state => compositeFocusKey.getState(state)?.decorations,\n },\n }),\n ],\n})\n"],"mappings":";;;;;;;AA2BA,MAAa,qBAAqB;;AAElC,MAAa,qBAAqB;;AAElC,MAAa,uBAAuB;AAEpC,MAAa,oBAAoB,IAAI,UAA+B,gBAAgB;;AAGpF,MAAa,oBAAoB,UAAsC,kBAAkB,SAAS,KAAK,CAAC,EAAE,MAAM;;;;;;AAOhH,SAAgB,iBAAiB,KAAa,IAA2B;CACvE,MAAM,QAAQ,cAAc,KAAK,EAAE;CACnC,IAAI,CAAC,OAAO,OAAO,cAAc;CACjC,MAAM,OAAO,IAAI,QAAQ,MAAM,GAAG;CAClC,MAAM,cAA4B,CAAC,WAAW,KAAK,MAAM,KAAK,MAAM,MAAM,MAAM,KAAK,UAAU,EAAE,OAAO,mBAAmB,CAAC,CAAC;CAC7H,KAAK,IAAI,QAAQ,KAAK,OAAO,SAAS,GAAG,SAAS;EAChD,MAAM,SAAS,KAAK,KAAK,KAAK;EAC9B,MAAM,SAAS,KAAK,MAAM,KAAK;EAC/B,MAAM,QAAQ,KAAK,MAAM,KAAK;EAC9B,MAAM,cAAc,OAAO,KAAK,SAAS;EACzC,OAAO,SAAS,OAAO,QAAQ,UAAU;GACvC,IAAI,UAAU,UAAW,eAAe,QAAA,GAAiC;GACzE,MAAM,MAAM,QAAQ;GACpB,YAAY,KAAK,WAAW,KAAK,KAAK,MAAM,MAAM,UAAU,EAAE,OAAO,mBAAmB,CAAC,CAAC;EAC5F,CAAC;EACD,IAAI,QAAQ,GAAG,YAAY,KAAK,WAAW,KAAK,KAAK,OAAO,KAAK,GAAG,KAAK,MAAM,KAAK,GAAG,EAAE,OAAO,qBAAqB,CAAC,CAAC;CACzH;CACA,OAAO,cAAc,OAAO,KAAK,WAAW;AAC9C;AAEA,MAAM,YAAiC;CAAE,IAAI;CAAM,aAAa,cAAc;AAAM;;;;;AAMpF,MAAa,iBAAiB,UAAU,OAAO;CAC7C,MAAM;CACN,oBAAoB,EAClB,iBAAiB,QAAuB,EAAE,OAAO,SAAS;EACxD,IAAI,OAAO,MAAM;GACf,MAAM,QAAQ,cAAc,MAAM,KAAK,EAAE;GACzC,IAAI,OAAO,KAAK,KAAK,SAAS,aAAa,OAAO;GAClD,MAAM,EAAE,MAAM,OAAO,MAAM;GAE3B,IAAI,OAAO,MAAM,OAAO,KAAK,MAAM,MAAM,MAAM,KAAK,UAAU,GAAG,aAAa,cAAc,KAAK,GAAG,IAAI,QAAQ,MAAM,MAAM,CAAC,CAAC,CAAC;EACjI;EACA,GAAG,QAAQ,mBAAmB,EAAE,GAAG,CAAC;EACpC,OAAO;CACT,EACF;CACA,6BAA6B,CAC3B,IAAI,OAA4B;EAC9B,KAAK;EACL,OAAO;GACL,YAAY;GACZ,MAAM,IAAI,OAAO;IACf,MAAM,OAAO,GAAG,QAAQ,iBAAiB;IACzC,MAAM,KAAK,OAAO,KAAK,KAAK,MAAM;IAClC,IAAI,OAAO,MAAM,OAAO,MAAM,OAAO,OAAO,QAAQ;IACpD,IAAI,CAAC,QAAQ,CAAC,GAAG,YAAY,OAAO;IACpC,OAAO;KAAE;KAAI,aAAa,iBAAiB,GAAG,KAAK,EAAE;IAAE;GACzD;EACF;EACA,kBAAkB,cAAc,SAAS,OAAO;GAC9C,MAAM,KAAK,kBAAkB,SAAS,KAAK,CAAC,EAAE;GAC9C,IAAI,CAAC,MAAM,CAAC,aAAa,MAAK,OAAM,GAAG,cAAc,GAAG,QAAQ,iBAAiB,CAAC,GAAG,OAAO;GAC5F,OAAO,cAAc,MAAM,KAAK,EAAE,IAAI,OAAO,MAAM,GAAG,QAAQ,mBAAmB,EAAE,IAAI,KAAK,CAAC,CAAC,CAAC,QAAQ,gBAAgB,KAAK;EAC9H;EACA,OAAO,EACL,cAAa,UAAS,kBAAkB,SAAS,KAAK,CAAC,EAAE,YAC3D;CACF,CAAC,CACH;AACF,CAAC"}
|