@skaile/forge-editor 6.4.0 → 6.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/code/CodeSurface.vue.d.ts +2 -2
- package/dist/core/block-index.js +3 -2
- package/dist/core/block-index.js.map +1 -1
- package/dist/core/document-markdown.d.ts +11 -6
- package/dist/core/document-markdown.js +34 -23
- package/dist/core/document-markdown.js.map +1 -1
- package/dist/core/soft-line-breaks.js +44 -0
- package/dist/core/soft-line-breaks.js.map +1 -0
- package/dist/document/DocumentSurface.vue.d.ts +2 -2
- package/dist/features/ai/components/AiReviewBar.js +1 -1
- package/dist/features/ai/components/AiSelectionMenu.js +1 -1
- package/dist/features/ai/components/AiSidebar.js +2 -2
- package/dist/features/ai/components/DocumentChatAnswer.js +1 -1
- package/dist/features/ai/components/SettingsModelProfiles.js +1 -1
- package/dist/features/ai/feature.js +1 -1
- package/dist/features/ai/index.js +7 -7
- package/dist/features/ai/react/AiSelectionMenu.js +4 -4
- package/dist/features/ai/react/AiSidebar.js +4 -4
- package/dist/features/ai/react/DocumentChatOps.js +1 -1
- package/dist/features/ai/react/ModelQuickSwitch.js +1 -1
- package/dist/features/ai/react/SettingsModelProfiles.js +1 -1
- package/dist/features/ai/react/use-ai-assistant.js +1 -1
- package/dist/features/ai/react/use-document-chat.js +1 -1
- package/dist/features/ai/use-ai-assistant.js +2 -2
- package/dist/features/ai/use-document-chat.js +1 -1
- package/dist/features/ai/vue.js +1 -1
- package/dist/features/authors/components/AuthorColorsPanel.js +1 -1
- package/dist/features/authors/components/SettingsAuthorsTab.js +1 -1
- package/dist/features/authors/index.js +1 -1
- package/dist/features/authors/react/AuthorColorsPanel.js +2 -2
- package/dist/features/authors/react/SettingsAuthorsTab.js +1 -1
- package/dist/features/authors/vue.js +1 -1
- package/dist/features/block-ui/index.js +1 -1
- package/dist/features/block-ui/react/BlockActionsOverlay.js +1 -1
- package/dist/features/block-ui/vue.js +1 -1
- package/dist/features/comments/react/CommentThreadView.js +1 -1
- package/dist/features/composites/components/ReferenceListFieldEditor.js +1 -1
- package/dist/features/composites/components/SettingsEditingTab.js +1 -1
- package/dist/features/composites/index.js +5 -5
- package/dist/features/composites/react/BibliographyEditor.js +1 -1
- package/dist/features/composites/react/CompositeChrome.js +1 -1
- package/dist/features/composites/react/CompositeDetails.js +1 -1
- package/dist/features/composites/react/CompositeInspector.js +3 -3
- package/dist/features/composites/react/CompositeTable.js +1 -1
- package/dist/features/composites/react/ReferenceListFieldEditor.js +1 -1
- package/dist/features/composites/react/ReferenceView.js +1 -1
- package/dist/features/composites/react/SettingsEditingTab.js +3 -3
- package/dist/features/composites/react.js +2 -2
- package/dist/features/composites/vue.js +1 -1
- package/dist/features/pruefung/vue.js +1 -1
- package/dist/features/themes/vue.js +1 -1
- package/dist/features/transfer/vue.js +1 -1
- package/dist/integrations/composite-ai/index.js +1 -1
- package/dist/markdown/MarkdownSurface.vue.d.ts +6 -6
- package/dist/react/EditorToolbar.js +2 -2
- package/dist/react/compose.js +2 -2
- package/dist/react/index.js +1 -1
- package/dist/search/react/SearchPanel.js +1 -1
- package/dist/server/document-tools.js +2 -18
- package/dist/server/document-tools.js.map +1 -1
- package/dist/tree/react/DocumentTree.js +3 -3
- package/dist/tree/react/use-tree-model.js +2 -2
- package/dist/vue/AdvancedEditor.vue.d.ts +4 -4
- package/dist/vue/SimpleEditor.vue.d.ts +4 -4
- package/dist/vue/components/EditorSurface.vue.d.ts +4 -4
- package/dist/vue/compose.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 6.4.2 - 2026-11-03
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Typing in a paragraph no longer turns its soft line breaks (a single `\n` in the Markdown) into hard breaks: after a keystroke the editor re-read the changed text with every `\n` as a `hardBreak`, so saving wrote ` \n` after each line, including lines that were never touched. `DocumentMarkdown` now installs a DOM parser that keeps the `\n` of the textblocks the editor rendered when it re-reads them, for a change in one paragraph and for a change across several. Shift+Enter still inserts a hard break; pasting and every other parse are unchanged.
|
|
8
|
+
|
|
9
|
+
## 6.4.1 - 2026-11-02
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- `document_outline` titles a heading or Typed block exactly as the Block index does: a hard break in the title is a line break, no longer a space, and the title is no longer trimmed.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Tests of the chat agent seam and the document tools check behaviour instead of the wording of tool results; the glossary (`CONTEXT.md`) names the Chat agent and how `document_outline` relates to the Block outline.
|
|
18
|
+
|
|
3
19
|
## 6.4.0 - 2026-11-02
|
|
4
20
|
|
|
5
21
|
### Added
|
|
@@ -11,12 +11,12 @@ type __VLS_Props = {
|
|
|
11
11
|
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {
|
|
12
12
|
view: import("vue").ShallowRef<EditorView | undefined, EditorView | undefined>;
|
|
13
13
|
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
14
|
-
ready: (view: EditorView) => any;
|
|
15
14
|
"update:modelValue": (text: string) => any;
|
|
15
|
+
ready: (view: EditorView) => any;
|
|
16
16
|
save: (text: string) => any;
|
|
17
17
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
18
|
-
onReady?: ((view: EditorView) => any) | undefined;
|
|
19
18
|
"onUpdate:modelValue"?: ((text: string) => any) | undefined;
|
|
19
|
+
onReady?: ((view: EditorView) => any) | undefined;
|
|
20
20
|
onSave?: ((text: string) => any) | undefined;
|
|
21
21
|
}>, {
|
|
22
22
|
modelValue: string;
|
package/dist/core/block-index.js
CHANGED
|
@@ -50,7 +50,7 @@ const INLINE = /* @__PURE__ */ new Set([
|
|
|
50
50
|
CITE_NODE
|
|
51
51
|
]);
|
|
52
52
|
const attrString = (node, key) => typeof node.attrs?.[key] === "string" ? node.attrs[key] : "";
|
|
53
|
-
/** The plain text of an inline node or a textblock's inline content. */
|
|
53
|
+
/** The plain text of an inline node or a textblock's inline content. Internal: `document_outline` counts and titles Blocks with it. */
|
|
54
54
|
function inlineText(node) {
|
|
55
55
|
switch (node.type) {
|
|
56
56
|
case TEXT_NODE: return node.text ?? "";
|
|
@@ -68,6 +68,7 @@ function plainText(node) {
|
|
|
68
68
|
if (holdsInline(node)) return inlineText(node);
|
|
69
69
|
return (node.content ?? []).filter((child) => !(child.type !== void 0 && NOT_TEXT.has(child.type))).map(plainText).filter(Boolean).join("\n");
|
|
70
70
|
}
|
|
71
|
+
/** The title of a Block: a Typed block's title row, a heading's text, `''` for any other Block. Internal, like `inlineText`. */
|
|
71
72
|
const titleOf = (node) => {
|
|
72
73
|
if (isTypedBlock(node)) return (node.content ?? []).filter((child) => child.type === TYPED_BLOCK_TITLE).map(inlineText).join("");
|
|
73
74
|
return node.type === "heading" ? inlineText(node) : "";
|
|
@@ -274,6 +275,6 @@ function findBlock(node, blockId) {
|
|
|
274
275
|
return null;
|
|
275
276
|
}
|
|
276
277
|
//#endregion
|
|
277
|
-
export { META_EDGES_KEY, REFERENCE_NODE, addressKey, authoredEdgesOf, blockContentOf, blockEdgesOf, edgesInto, isTagEdgeTarget, projectDocument, sameAddress };
|
|
278
|
+
export { META_EDGES_KEY, REFERENCE_NODE, addressKey, authoredEdgesOf, blockContentOf, blockEdgesOf, edgesInto, inlineText, isTagEdgeTarget, projectDocument, sameAddress, titleOf };
|
|
278
279
|
|
|
279
280
|
//# sourceMappingURL=block-index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"block-index.js","names":[],"sources":["../../src/core/block-index.ts"],"sourcesContent":["import type { JSONContent } from '@tiptap/core'\nimport { sha256Hex } from './hash'\nimport { DERIVATION_KEY, derivationOf, isTypedBlock, metaOf, TYPED_BLOCK_DESCRIPTION, TYPED_BLOCK_TAGS, TYPED_BLOCK_TITLE, typeIdOf } from './typed-block'\n\n// Framework-free and server-importable: a Document's blocks as rows of the workspace's Block index. `projectDocument` is pure\n// (JSON in, rows out); where the rows live is the `BlockIndex` port (`server/ports`) and its adapters.\n\n// Same names as `features/mentions` and `features/composites`; copied because core must not import features.\nconst TAG_NODE = 'tag'\nconst MENTION_NODE = 'mention'\n/** Node name of a reference chip; `features/composites` spells it the same. */\nexport const REFERENCE_NODE = 'reference'\nconst CITE_NODE = 'cite'\nconst HARD_BREAK_NODE = 'hardBreak'\nconst TEXT_NODE = 'text'\n\n/** Where a Block is: the Document and the block ID inside it. Block IDs are unique per Document only. */\nexport interface BlockAddress {\n documentId: string\n blockId: string\n}\n\n/** A string that names `address` and nothing else, for a `Map` or `Set` key; an address without a Document ID (`''`, or missing) is the same key. */\nexport const addressKey = (address: BlockAddress): string => `${address.documentId ?? ''}\\u0000${address.blockId}`\n\n/** Whether two addresses name the same Block; an address without a Document ID (`''`, or missing) equals another one without. */\nexport const sameAddress = (a: BlockAddress, b: BlockAddress): boolean => (a.documentId ?? '') === (b.documentId ?? '') && a.blockId === b.blockId\n\n/** A reference chip in a Block: the Block it points at (`documentId` resolved: a chip without one points into its own Document) and its locator. */\nexport interface BlockReferenceRow extends BlockAddress {\n /** Position inside the target's source (`S. 12`, `03:14`); `null` = the whole Block. */\n locator: string | null\n}\n\n/** What a Derived block was made from, as an address: another Block (`documentId` resolved like a chip's) or an uploaded asset by name. */\nexport type BlockDerivationRow = BlockAddress | { asset: string }\n\n/** The Block property that holds a Block's authored edges (`AuthoredEdge[]`); `projectDocument` turns them into `BlockEdgeRow`s of origin `meta`. */\nexport const META_EDGES_KEY = 'edges'\n\n// Same node name as `features/composites/embed`; copied because core must not import features.\nconst EMBED_NODE = 'blockEmbed'\n\n/** What an Edge says about its two ends. The four built-in kinds are projected from the content; any other string is an authored kind (`supports`, `contradicts`, …). */\nexport type EdgeKind = 'reference' | 'derivation' | 'embed' | 'tag' | (string & {})\n\n/** Where an Edge was written: in the Block's content (a chip, an embed, a tag, the Derivation) or by hand in its `meta.edges`. */\nexport type EdgeOrigin = 'content' | 'meta'\n\n/** One outgoing Edge of a Block as its row holds it: the Block it points at (`documentId` resolved), or a tag name for a `tag` Edge. */\nexport interface BlockEdgeRow {\n to: BlockAddress | { tag: string }\n kind: EdgeKind\n origin: EdgeOrigin\n}\n\n/** An Edge with both ends: what `BlockIndex.edges` answers. `from` is the Block the Edge is written in. */\nexport interface BlockEdge extends BlockEdgeRow {\n from: BlockAddress\n}\n\n/** An Edge a person added to a Typed block (`meta.edges`): the target Block (`documentId` absent = this Document) and its kind. */\nexport interface AuthoredEdge {\n to: { documentId?: string, blockId: string }\n kind: string\n}\n\n/** `true` for the Edge that points at a tag name instead of a Block. */\nexport const isTagEdgeTarget = (to: BlockEdgeRow['to']): to is { tag: string } => 'tag' in to\n\n/** The authored edges in a `meta.edges` value; entries without a block ID or a kind are dropped, anything but an array is `[]`. */\nexport function authoredEdgesOf(value: unknown): AuthoredEdge[] {\n if (!Array.isArray(value)) return []\n const edges: AuthoredEdge[] = []\n for (const entry of value as unknown[]) {\n const { to, kind } = (entry ?? {}) as { to?: { documentId?: unknown, blockId?: unknown }, kind?: unknown }\n if (typeof to?.blockId !== 'string' || !to.blockId || typeof kind !== 'string' || !kind) continue\n edges.push({ to: { ...(typeof to.documentId === 'string' && to.documentId && { documentId: to.documentId }), blockId: to.blockId }, kind })\n }\n return edges\n}\n\n/**\n * One Block of a Document as the index holds it. Every node that carries a block ID is a Block (the `id` attribute of the\n * stored JSON): Typed blocks, paragraphs, headings, lists, media. A Block without an ID has no address and no row.\n * The content fields (`text`, `tags`, `references`) cover the Block's whole content, nested Blocks included: a Typed block's row\n * answers for its body, and the nested Blocks have rows of their own.\n */\nexport interface BlockRow extends BlockAddress {\n /** Block type id (`note`, `paper`, …) of a Typed block, the node type (`paragraph`, `heading`, …) of any other Block. */\n type: string\n /** The title of a Typed block, the text of a heading; `''` for any other Block. */\n title: string\n /**\n * The Block properties of a Typed block: its `meta` without the derivation (which is `derivedFrom`); `{}` for any other Block.\n * A Block that holds an uploaded file (a media block, or a Typed block whose first body block is one) also has `sha256`,\n * the file's SHA-256 hex, which the import check compares dropped files with.\n */\n properties: Record<string, unknown>\n /** Tag names in the Block: its tags row (Typed block) and every `#` chip in its content, in document order, without repeats. */\n tags: string[]\n /** The reference chips in the Block's content, in document order, without repeats. */\n references: BlockReferenceRow[]\n /**\n * The Edges that leave the Block, without repeats: a `reference` per chip target, a `derivation` for a Derivation from another\n * Block, an `embed` per embedded Block, a `tag` per tag name, then the authored `meta.edges` (origin `meta`). Like the other\n * content fields it covers the Block's whole content, nested Blocks included.\n */\n edges: BlockEdgeRow[]\n /** What the Block was derived from; `null` for a block that carries no Derivation. */\n derivedFrom: BlockDerivationRow | null\n /** SHA-256 hex of the Block's content, the `pruefungHash` formula (see `ProjectOptions.markdown`). */\n hash: string\n /**\n * SHA-256 hex of what a Derivation made from this Block records as its `inputHash`: the Markdown of a Typed block's body\n * (without the title, unlike `hash`), the Markdown of any other Block (then equal to `hash`). A Derived block in another\n * Document compares it to tell whether its original changed.\n */\n sourceHash: string\n /** The plain text of the Block's content (a Typed block: description and body, the title is `title`); for search. */\n text: string\n}\n\n/** What of a Block `blockGet` can answer with besides the summary: the Block itself (`block`) or the Blocks nested in it (`children`). */\nexport type BlockContentMode = 'block' | 'children'\n\n/** The part of a row the block route answers with, and what the client's `index.blockGet` resolves to. */\nexport interface BlockSummary {\n title: string\n type: string\n hash: string\n /** The row's `sourceHash`: what a Derivation made from the Block records as `inputHash`. */\n sourceHash: string\n /** `true` when no such Block exists (a client turns the `404` into it); the server answers `false`. */\n gone: boolean\n /** The Block's nodes as TipTap JSON (`blockContentOf`), only when the request asked for a `BlockContentMode`; absent otherwise. */\n content?: JSONContent[]\n}\n\n/**\n * One hit of `BlockIndex.search`: the Block that matched, with what a result list shows of it. A Block without a title (a\n * paragraph, a list) has `title: ''`; `type` then says what it is.\n */\nexport interface BlockHit extends BlockAddress {\n title: string\n type: string\n /** How well the Block matched, higher is better. Comparable within one result only; the search route leaves it out. */\n score: number\n}\n\n/** A hit as the search route answers it and a client gets it: where the Block is, its title and type, in the order of the ranking. */\nexport type BlockSearchHit = Omit<BlockHit, 'score'>\n\n/** Markdown of `nodes` serialised as the content of one document. */\nexport type BlockMarkdown = (nodes: readonly JSONContent[]) => string\n\nexport interface ProjectOptions {\n /**\n * The Markdown serializer of the hash: `pruefungHash` is the SHA-256 of the title line, a blank line and the Markdown of the\n * body (of a Typed block), and the hash of any other Block is that of its Markdown. Core has no serializer (it needs the\n * schema's Markdown handlers), so a host passes one; without it the plain text takes its place (`title`, a blank line, the\n * body text) and the hash is only comparable with other hashes of the same projection.\n */\n markdown?: BlockMarkdown\n}\n\nconst CHROME: ReadonlySet<string> = new Set([TYPED_BLOCK_TITLE, TYPED_BLOCK_DESCRIPTION, TYPED_BLOCK_TAGS])\n/** The chrome of a Typed block that is not in its text: the title is its own field, the tags row is data. */\nconst NOT_TEXT: ReadonlySet<string> = new Set([TYPED_BLOCK_TITLE, TYPED_BLOCK_TAGS])\nconst INLINE: ReadonlySet<string> = new Set([TEXT_NODE, HARD_BREAK_NODE, MENTION_NODE, TAG_NODE, REFERENCE_NODE, CITE_NODE])\n\nconst attrString = (node: JSONContent, key: string): string => typeof node.attrs?.[key] === 'string' ? node.attrs[key] as string : ''\n\n/** The plain text of an inline node or a textblock's inline content. */\nfunction inlineText(node: JSONContent): string {\n switch (node.type) {\n case TEXT_NODE: return node.text ?? ''\n case HARD_BREAK_NODE: return '\\n'\n case MENTION_NODE: return `@${attrString(node, 'label') || attrString(node, 'id')}`\n case TAG_NODE: return `#${attrString(node, 'name')}`\n case REFERENCE_NODE: return attrString(node, 'label')\n case CITE_NODE: return `[cite:${attrString(node, 'key')}]`\n default: return (node.content ?? []).map(inlineText).join('')\n }\n}\n\nconst holdsInline = (node: JSONContent): boolean => node.content?.some(child => child.type !== undefined && INLINE.has(child.type)) === true\n\n/** The plain text of `node`'s content: one line per textblock, chrome of a Typed block left out. */\nfunction plainText(node: JSONContent): string {\n if (holdsInline(node)) return inlineText(node)\n return (node.content ?? []).filter(child => !(child.type !== undefined && NOT_TEXT.has(child.type))).map(plainText).filter(Boolean).join('\\n')\n}\n\nconst titleOf = (node: JSONContent): string => {\n if (isTypedBlock(node)) return (node.content ?? []).filter(child => child.type === TYPED_BLOCK_TITLE).map(inlineText).join('')\n return node.type === 'heading' ? inlineText(node) : ''\n}\n\n/** Collects the tag names, reference chips and embedded Blocks (`embeds`, by `documentId/blockId`) of `node`'s whole subtree. */\nfunction collect(node: JSONContent, documentId: string, tags: Set<string>, references: Map<string, BlockReferenceRow>, embeds: Map<string, BlockAddress>): void {\n if (node.type === TAG_NODE) {\n const name = attrString(node, 'name')\n if (name) tags.add(name)\n }\n else if (node.type === TYPED_BLOCK_TAGS && Array.isArray(node.attrs?.tags)) {\n for (const name of node.attrs.tags) if (typeof name === 'string' && name) tags.add(name)\n }\n else if (node.type === REFERENCE_NODE) {\n const blockId = attrString(node, 'target')\n if (blockId) {\n const target = attrString(node, 'documentId') || documentId\n const locator = attrString(node, 'locator') || null\n references.set(`${addressKey({ documentId: target, blockId })}\\u0000${locator ?? ''}`, { documentId: target, blockId, locator })\n }\n }\n else if (node.type === EMBED_NODE) {\n const blockId = attrString(node, 'blockId')\n if (blockId) {\n const target = attrString(node, 'documentId') || documentId\n embeds.set(addressKey({ documentId: target, blockId }), { documentId: target, blockId })\n }\n }\n for (const child of node.content ?? []) collect(child, documentId, tags, references, embeds)\n}\n\n/** The Edges of a Block (`BlockRow.edges`): content Edges in the order reference, derivation, embed, tag, then the authored ones; one per kind and target. */\nfunction edgesOf(documentId: string, references: Iterable<BlockReferenceRow>, derivedFrom: BlockDerivationRow | null, embeds: Iterable<BlockAddress>, tags: Iterable<string>, authored: readonly AuthoredEdge[]): BlockEdgeRow[] {\n const edges = new Map<string, BlockEdgeRow>()\n const add = (edge: BlockEdgeRow): void => {\n const target = isTagEdgeTarget(edge.to) ? `tag\\n${edge.to.tag}` : addressKey(edge.to)\n const key = `${edge.origin}\\n${edge.kind}\\n${target}`\n if (!edges.has(key)) edges.set(key, edge)\n }\n for (const { documentId: target, blockId } of references) add({ to: { documentId: target, blockId }, kind: 'reference', origin: 'content' })\n if (derivedFrom && 'blockId' in derivedFrom) add({ to: { documentId: derivedFrom.documentId, blockId: derivedFrom.blockId }, kind: 'derivation', origin: 'content' })\n for (const { documentId: target, blockId } of embeds) add({ to: { documentId: target, blockId }, kind: 'embed', origin: 'content' })\n for (const tag of tags) add({ to: { tag }, kind: 'tag', origin: 'content' })\n for (const { to, kind } of authored) add({ to: { documentId: to.documentId ?? documentId, blockId: to.blockId }, kind, origin: 'meta' })\n return [...edges.values()]\n}\n\n/** The `hash` and the `sourceHash` of `node`: for a Typed block the title line plus the body's Markdown, and the body's Markdown alone; for any other Block both the Block's Markdown. */\nfunction hashesOf(node: JSONContent, title: string, text: string, markdown: BlockMarkdown | undefined): { hash: string, sourceHash: string } {\n if (!isTypedBlock(node)) {\n const hash = sha256Hex(markdown ? markdown([node]) : text)\n return { hash, sourceHash: hash }\n }\n const body = (node.content ?? []).filter(child => !(child.type !== undefined && CHROME.has(child.type)))\n const bodyText = markdown ? markdown(body) : body.map(plainText).filter(Boolean).join('\\n')\n return { hash: sha256Hex(`${title}\\n\\n${bodyText}`), sourceHash: sha256Hex(bodyText) }\n}\n\nconst FILE_HASH = /^[0-9a-f]{64}$/i\n\n/** The SHA-256 of the uploaded file a Block holds: the `hash` of a media node (image, audio, video, file) itself, or of the first body block of a Typed block (its source file); `''` for a Block without one. */\nfunction fileHashOf(node: JSONContent): string {\n const source = isTypedBlock(node) ? node.content?.find(child => !(child.type !== undefined && CHROME.has(child.type))) : node\n const hash = source?.attrs?.hash\n return typeof hash === 'string' && FILE_HASH.test(hash) ? hash.toLowerCase() : ''\n}\n\nfunction rowOf(node: JSONContent, blockId: string, documentId: string, markdown: BlockMarkdown | undefined): BlockRow {\n const typed = isTypedBlock(node)\n const title = titleOf(node)\n const text = plainText(node)\n const tags = new Set<string>()\n const references = new Map<string, BlockReferenceRow>()\n const embeds = new Map<string, BlockAddress>()\n collect(node, documentId, tags, references, embeds)\n const derivation = derivationOf(node)\n const from = derivation?.from\n const { [DERIVATION_KEY]: _derivation, ...meta } = typed ? metaOf(node) : {}\n const sha256 = fileHashOf(node)\n // The file's SHA-256 is a property of the row, so a drop of the same file is found (`core/import-check`); an authored `sha256` property wins.\n const properties = sha256 && !('sha256' in meta) ? { ...meta, sha256 } : meta\n const derivedFrom: BlockDerivationRow | null = !from ? null : 'block' in from ? { documentId: from.document || documentId, blockId: from.block } : { asset: from.asset }\n return {\n documentId,\n blockId,\n type: typed ? typeIdOf(node) : node.type ?? '',\n title,\n properties,\n tags: [...tags],\n references: [...references.values()],\n edges: edgesOf(documentId, references.values(), derivedFrom, embeds.values(), tags, authoredEdgesOf(properties[META_EDGES_KEY])),\n derivedFrom,\n ...hashesOf(node, title, text, markdown),\n text,\n }\n}\n\n/**\n * The rows of a Document's content: one per node with a block ID, in document order (a Block before the Blocks nested in it).\n * `content` is the Document's TipTap JSON in the current shape; nodes without an ID, and the inline nodes inside a textblock\n * (a mention's `id` is a user's, not a block's), have no row. Pure: the same content and options give the same rows.\n */\nexport function projectDocument(content: JSONContent, documentId: string, options: ProjectOptions = {}): BlockRow[] {\n const rows: BlockRow[] = []\n const walk = (node: JSONContent): void => {\n if (holdsInline(node)) return\n for (const child of node.content ?? []) {\n const id = child.attrs?.id\n if (typeof id === 'string' && id) rows.push(rowOf(child, id, documentId, options.markdown))\n walk(child)\n }\n }\n walk(content)\n return rows\n}\n\n/** The Edges that leave `row`, each with the Block it is written in. */\nexport const blockEdgesOf = (row: Pick<BlockRow, 'documentId' | 'blockId' | 'edges'>): BlockEdge[] =>\n row.edges.map(edge => ({ from: { documentId: row.documentId, blockId: row.blockId }, ...structuredClone(edge) }))\n\n/** The Edges of `rows` that point at the Block `address` (`tag` Edges never do), in the order of the rows. */\nexport function edgesInto(rows: Iterable<Pick<BlockRow, 'documentId' | 'blockId' | 'edges'>>, address: BlockAddress): BlockEdge[] {\n const found: BlockEdge[] = []\n for (const row of rows) {\n for (const edge of row.edges) {\n if (isTagEdgeTarget(edge.to) || !sameAddress(edge.to, address)) continue\n found.push({ from: { documentId: row.documentId, blockId: row.blockId }, ...structuredClone(edge) })\n }\n }\n return found\n}\n\n/**\n * The nodes of Block `blockId` in a Document's content, as `blockContentOf` of the block route: `block` = the Block itself,\n * `children` = what it holds (a Typed block: its body without the title, description and tags rows; a list or a quote: its\n * items; a Block that holds only text: nothing). `null` when the content has no Block with that ID. Pure and a copy-free view:\n * the nodes are those of `content`.\n */\nexport function blockContentOf(content: JSONContent, blockId: string, mode: BlockContentMode): JSONContent[] | null {\n const found = findBlock(content, blockId)\n if (!found) return null\n if (mode === 'block') return [found]\n if (holdsInline(found)) return []\n return (found.content ?? []).filter(child => !(isTypedBlock(found) && child.type !== undefined && CHROME.has(child.type)))\n}\n\nfunction findBlock(node: JSONContent, blockId: string): JSONContent | null {\n if (holdsInline(node)) return null\n for (const child of node.content ?? []) {\n if (child.attrs?.id === blockId) return child\n const nested = findBlock(child, blockId)\n if (nested) return nested\n }\n return null\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"block-index.js","names":[],"sources":["../../src/core/block-index.ts"],"sourcesContent":["import type { JSONContent } from '@tiptap/core'\nimport { sha256Hex } from './hash'\nimport { DERIVATION_KEY, derivationOf, isTypedBlock, metaOf, TYPED_BLOCK_DESCRIPTION, TYPED_BLOCK_TAGS, TYPED_BLOCK_TITLE, typeIdOf } from './typed-block'\n\n// Framework-free and server-importable: a Document's blocks as rows of the workspace's Block index. `projectDocument` is pure\n// (JSON in, rows out); where the rows live is the `BlockIndex` port (`server/ports`) and its adapters.\n\n// Same names as `features/mentions` and `features/composites`; copied because core must not import features.\nconst TAG_NODE = 'tag'\nconst MENTION_NODE = 'mention'\n/** Node name of a reference chip; `features/composites` spells it the same. */\nexport const REFERENCE_NODE = 'reference'\nconst CITE_NODE = 'cite'\nconst HARD_BREAK_NODE = 'hardBreak'\nconst TEXT_NODE = 'text'\n\n/** Where a Block is: the Document and the block ID inside it. Block IDs are unique per Document only. */\nexport interface BlockAddress {\n documentId: string\n blockId: string\n}\n\n/** A string that names `address` and nothing else, for a `Map` or `Set` key; an address without a Document ID (`''`, or missing) is the same key. */\nexport const addressKey = (address: BlockAddress): string => `${address.documentId ?? ''}\\u0000${address.blockId}`\n\n/** Whether two addresses name the same Block; an address without a Document ID (`''`, or missing) equals another one without. */\nexport const sameAddress = (a: BlockAddress, b: BlockAddress): boolean => (a.documentId ?? '') === (b.documentId ?? '') && a.blockId === b.blockId\n\n/** A reference chip in a Block: the Block it points at (`documentId` resolved: a chip without one points into its own Document) and its locator. */\nexport interface BlockReferenceRow extends BlockAddress {\n /** Position inside the target's source (`S. 12`, `03:14`); `null` = the whole Block. */\n locator: string | null\n}\n\n/** What a Derived block was made from, as an address: another Block (`documentId` resolved like a chip's) or an uploaded asset by name. */\nexport type BlockDerivationRow = BlockAddress | { asset: string }\n\n/** The Block property that holds a Block's authored edges (`AuthoredEdge[]`); `projectDocument` turns them into `BlockEdgeRow`s of origin `meta`. */\nexport const META_EDGES_KEY = 'edges'\n\n// Same node name as `features/composites/embed`; copied because core must not import features.\nconst EMBED_NODE = 'blockEmbed'\n\n/** What an Edge says about its two ends. The four built-in kinds are projected from the content; any other string is an authored kind (`supports`, `contradicts`, …). */\nexport type EdgeKind = 'reference' | 'derivation' | 'embed' | 'tag' | (string & {})\n\n/** Where an Edge was written: in the Block's content (a chip, an embed, a tag, the Derivation) or by hand in its `meta.edges`. */\nexport type EdgeOrigin = 'content' | 'meta'\n\n/** One outgoing Edge of a Block as its row holds it: the Block it points at (`documentId` resolved), or a tag name for a `tag` Edge. */\nexport interface BlockEdgeRow {\n to: BlockAddress | { tag: string }\n kind: EdgeKind\n origin: EdgeOrigin\n}\n\n/** An Edge with both ends: what `BlockIndex.edges` answers. `from` is the Block the Edge is written in. */\nexport interface BlockEdge extends BlockEdgeRow {\n from: BlockAddress\n}\n\n/** An Edge a person added to a Typed block (`meta.edges`): the target Block (`documentId` absent = this Document) and its kind. */\nexport interface AuthoredEdge {\n to: { documentId?: string, blockId: string }\n kind: string\n}\n\n/** `true` for the Edge that points at a tag name instead of a Block. */\nexport const isTagEdgeTarget = (to: BlockEdgeRow['to']): to is { tag: string } => 'tag' in to\n\n/** The authored edges in a `meta.edges` value; entries without a block ID or a kind are dropped, anything but an array is `[]`. */\nexport function authoredEdgesOf(value: unknown): AuthoredEdge[] {\n if (!Array.isArray(value)) return []\n const edges: AuthoredEdge[] = []\n for (const entry of value as unknown[]) {\n const { to, kind } = (entry ?? {}) as { to?: { documentId?: unknown, blockId?: unknown }, kind?: unknown }\n if (typeof to?.blockId !== 'string' || !to.blockId || typeof kind !== 'string' || !kind) continue\n edges.push({ to: { ...(typeof to.documentId === 'string' && to.documentId && { documentId: to.documentId }), blockId: to.blockId }, kind })\n }\n return edges\n}\n\n/**\n * One Block of a Document as the index holds it. Every node that carries a block ID is a Block (the `id` attribute of the\n * stored JSON): Typed blocks, paragraphs, headings, lists, media. A Block without an ID has no address and no row.\n * The content fields (`text`, `tags`, `references`) cover the Block's whole content, nested Blocks included: a Typed block's row\n * answers for its body, and the nested Blocks have rows of their own.\n */\nexport interface BlockRow extends BlockAddress {\n /** Block type id (`note`, `paper`, …) of a Typed block, the node type (`paragraph`, `heading`, …) of any other Block. */\n type: string\n /** The title of a Typed block, the text of a heading; `''` for any other Block. */\n title: string\n /**\n * The Block properties of a Typed block: its `meta` without the derivation (which is `derivedFrom`); `{}` for any other Block.\n * A Block that holds an uploaded file (a media block, or a Typed block whose first body block is one) also has `sha256`,\n * the file's SHA-256 hex, which the import check compares dropped files with.\n */\n properties: Record<string, unknown>\n /** Tag names in the Block: its tags row (Typed block) and every `#` chip in its content, in document order, without repeats. */\n tags: string[]\n /** The reference chips in the Block's content, in document order, without repeats. */\n references: BlockReferenceRow[]\n /**\n * The Edges that leave the Block, without repeats: a `reference` per chip target, a `derivation` for a Derivation from another\n * Block, an `embed` per embedded Block, a `tag` per tag name, then the authored `meta.edges` (origin `meta`). Like the other\n * content fields it covers the Block's whole content, nested Blocks included.\n */\n edges: BlockEdgeRow[]\n /** What the Block was derived from; `null` for a block that carries no Derivation. */\n derivedFrom: BlockDerivationRow | null\n /** SHA-256 hex of the Block's content, the `pruefungHash` formula (see `ProjectOptions.markdown`). */\n hash: string\n /**\n * SHA-256 hex of what a Derivation made from this Block records as its `inputHash`: the Markdown of a Typed block's body\n * (without the title, unlike `hash`), the Markdown of any other Block (then equal to `hash`). A Derived block in another\n * Document compares it to tell whether its original changed.\n */\n sourceHash: string\n /** The plain text of the Block's content (a Typed block: description and body, the title is `title`); for search. */\n text: string\n}\n\n/** What of a Block `blockGet` can answer with besides the summary: the Block itself (`block`) or the Blocks nested in it (`children`). */\nexport type BlockContentMode = 'block' | 'children'\n\n/** The part of a row the block route answers with, and what the client's `index.blockGet` resolves to. */\nexport interface BlockSummary {\n title: string\n type: string\n hash: string\n /** The row's `sourceHash`: what a Derivation made from the Block records as `inputHash`. */\n sourceHash: string\n /** `true` when no such Block exists (a client turns the `404` into it); the server answers `false`. */\n gone: boolean\n /** The Block's nodes as TipTap JSON (`blockContentOf`), only when the request asked for a `BlockContentMode`; absent otherwise. */\n content?: JSONContent[]\n}\n\n/**\n * One hit of `BlockIndex.search`: the Block that matched, with what a result list shows of it. A Block without a title (a\n * paragraph, a list) has `title: ''`; `type` then says what it is.\n */\nexport interface BlockHit extends BlockAddress {\n title: string\n type: string\n /** How well the Block matched, higher is better. Comparable within one result only; the search route leaves it out. */\n score: number\n}\n\n/** A hit as the search route answers it and a client gets it: where the Block is, its title and type, in the order of the ranking. */\nexport type BlockSearchHit = Omit<BlockHit, 'score'>\n\n/** Markdown of `nodes` serialised as the content of one document. */\nexport type BlockMarkdown = (nodes: readonly JSONContent[]) => string\n\nexport interface ProjectOptions {\n /**\n * The Markdown serializer of the hash: `pruefungHash` is the SHA-256 of the title line, a blank line and the Markdown of the\n * body (of a Typed block), and the hash of any other Block is that of its Markdown. Core has no serializer (it needs the\n * schema's Markdown handlers), so a host passes one; without it the plain text takes its place (`title`, a blank line, the\n * body text) and the hash is only comparable with other hashes of the same projection.\n */\n markdown?: BlockMarkdown\n}\n\nconst CHROME: ReadonlySet<string> = new Set([TYPED_BLOCK_TITLE, TYPED_BLOCK_DESCRIPTION, TYPED_BLOCK_TAGS])\n/** The chrome of a Typed block that is not in its text: the title is its own field, the tags row is data. */\nconst NOT_TEXT: ReadonlySet<string> = new Set([TYPED_BLOCK_TITLE, TYPED_BLOCK_TAGS])\nconst INLINE: ReadonlySet<string> = new Set([TEXT_NODE, HARD_BREAK_NODE, MENTION_NODE, TAG_NODE, REFERENCE_NODE, CITE_NODE])\n\nconst attrString = (node: JSONContent, key: string): string => typeof node.attrs?.[key] === 'string' ? node.attrs[key] as string : ''\n\n/** The plain text of an inline node or a textblock's inline content. Internal: `document_outline` counts and titles Blocks with it. */\nexport function inlineText(node: JSONContent): string {\n switch (node.type) {\n case TEXT_NODE: return node.text ?? ''\n case HARD_BREAK_NODE: return '\\n'\n case MENTION_NODE: return `@${attrString(node, 'label') || attrString(node, 'id')}`\n case TAG_NODE: return `#${attrString(node, 'name')}`\n case REFERENCE_NODE: return attrString(node, 'label')\n case CITE_NODE: return `[cite:${attrString(node, 'key')}]`\n default: return (node.content ?? []).map(inlineText).join('')\n }\n}\n\nconst holdsInline = (node: JSONContent): boolean => node.content?.some(child => child.type !== undefined && INLINE.has(child.type)) === true\n\n/** The plain text of `node`'s content: one line per textblock, chrome of a Typed block left out. */\nfunction plainText(node: JSONContent): string {\n if (holdsInline(node)) return inlineText(node)\n return (node.content ?? []).filter(child => !(child.type !== undefined && NOT_TEXT.has(child.type))).map(plainText).filter(Boolean).join('\\n')\n}\n\n/** The title of a Block: a Typed block's title row, a heading's text, `''` for any other Block. Internal, like `inlineText`. */\nexport const titleOf = (node: JSONContent): string => {\n if (isTypedBlock(node)) return (node.content ?? []).filter(child => child.type === TYPED_BLOCK_TITLE).map(inlineText).join('')\n return node.type === 'heading' ? inlineText(node) : ''\n}\n\n/** Collects the tag names, reference chips and embedded Blocks (`embeds`, by `documentId/blockId`) of `node`'s whole subtree. */\nfunction collect(node: JSONContent, documentId: string, tags: Set<string>, references: Map<string, BlockReferenceRow>, embeds: Map<string, BlockAddress>): void {\n if (node.type === TAG_NODE) {\n const name = attrString(node, 'name')\n if (name) tags.add(name)\n }\n else if (node.type === TYPED_BLOCK_TAGS && Array.isArray(node.attrs?.tags)) {\n for (const name of node.attrs.tags) if (typeof name === 'string' && name) tags.add(name)\n }\n else if (node.type === REFERENCE_NODE) {\n const blockId = attrString(node, 'target')\n if (blockId) {\n const target = attrString(node, 'documentId') || documentId\n const locator = attrString(node, 'locator') || null\n references.set(`${addressKey({ documentId: target, blockId })}\\u0000${locator ?? ''}`, { documentId: target, blockId, locator })\n }\n }\n else if (node.type === EMBED_NODE) {\n const blockId = attrString(node, 'blockId')\n if (blockId) {\n const target = attrString(node, 'documentId') || documentId\n embeds.set(addressKey({ documentId: target, blockId }), { documentId: target, blockId })\n }\n }\n for (const child of node.content ?? []) collect(child, documentId, tags, references, embeds)\n}\n\n/** The Edges of a Block (`BlockRow.edges`): content Edges in the order reference, derivation, embed, tag, then the authored ones; one per kind and target. */\nfunction edgesOf(documentId: string, references: Iterable<BlockReferenceRow>, derivedFrom: BlockDerivationRow | null, embeds: Iterable<BlockAddress>, tags: Iterable<string>, authored: readonly AuthoredEdge[]): BlockEdgeRow[] {\n const edges = new Map<string, BlockEdgeRow>()\n const add = (edge: BlockEdgeRow): void => {\n const target = isTagEdgeTarget(edge.to) ? `tag\\n${edge.to.tag}` : addressKey(edge.to)\n const key = `${edge.origin}\\n${edge.kind}\\n${target}`\n if (!edges.has(key)) edges.set(key, edge)\n }\n for (const { documentId: target, blockId } of references) add({ to: { documentId: target, blockId }, kind: 'reference', origin: 'content' })\n if (derivedFrom && 'blockId' in derivedFrom) add({ to: { documentId: derivedFrom.documentId, blockId: derivedFrom.blockId }, kind: 'derivation', origin: 'content' })\n for (const { documentId: target, blockId } of embeds) add({ to: { documentId: target, blockId }, kind: 'embed', origin: 'content' })\n for (const tag of tags) add({ to: { tag }, kind: 'tag', origin: 'content' })\n for (const { to, kind } of authored) add({ to: { documentId: to.documentId ?? documentId, blockId: to.blockId }, kind, origin: 'meta' })\n return [...edges.values()]\n}\n\n/** The `hash` and the `sourceHash` of `node`: for a Typed block the title line plus the body's Markdown, and the body's Markdown alone; for any other Block both the Block's Markdown. */\nfunction hashesOf(node: JSONContent, title: string, text: string, markdown: BlockMarkdown | undefined): { hash: string, sourceHash: string } {\n if (!isTypedBlock(node)) {\n const hash = sha256Hex(markdown ? markdown([node]) : text)\n return { hash, sourceHash: hash }\n }\n const body = (node.content ?? []).filter(child => !(child.type !== undefined && CHROME.has(child.type)))\n const bodyText = markdown ? markdown(body) : body.map(plainText).filter(Boolean).join('\\n')\n return { hash: sha256Hex(`${title}\\n\\n${bodyText}`), sourceHash: sha256Hex(bodyText) }\n}\n\nconst FILE_HASH = /^[0-9a-f]{64}$/i\n\n/** The SHA-256 of the uploaded file a Block holds: the `hash` of a media node (image, audio, video, file) itself, or of the first body block of a Typed block (its source file); `''` for a Block without one. */\nfunction fileHashOf(node: JSONContent): string {\n const source = isTypedBlock(node) ? node.content?.find(child => !(child.type !== undefined && CHROME.has(child.type))) : node\n const hash = source?.attrs?.hash\n return typeof hash === 'string' && FILE_HASH.test(hash) ? hash.toLowerCase() : ''\n}\n\nfunction rowOf(node: JSONContent, blockId: string, documentId: string, markdown: BlockMarkdown | undefined): BlockRow {\n const typed = isTypedBlock(node)\n const title = titleOf(node)\n const text = plainText(node)\n const tags = new Set<string>()\n const references = new Map<string, BlockReferenceRow>()\n const embeds = new Map<string, BlockAddress>()\n collect(node, documentId, tags, references, embeds)\n const derivation = derivationOf(node)\n const from = derivation?.from\n const { [DERIVATION_KEY]: _derivation, ...meta } = typed ? metaOf(node) : {}\n const sha256 = fileHashOf(node)\n // The file's SHA-256 is a property of the row, so a drop of the same file is found (`core/import-check`); an authored `sha256` property wins.\n const properties = sha256 && !('sha256' in meta) ? { ...meta, sha256 } : meta\n const derivedFrom: BlockDerivationRow | null = !from ? null : 'block' in from ? { documentId: from.document || documentId, blockId: from.block } : { asset: from.asset }\n return {\n documentId,\n blockId,\n type: typed ? typeIdOf(node) : node.type ?? '',\n title,\n properties,\n tags: [...tags],\n references: [...references.values()],\n edges: edgesOf(documentId, references.values(), derivedFrom, embeds.values(), tags, authoredEdgesOf(properties[META_EDGES_KEY])),\n derivedFrom,\n ...hashesOf(node, title, text, markdown),\n text,\n }\n}\n\n/**\n * The rows of a Document's content: one per node with a block ID, in document order (a Block before the Blocks nested in it).\n * `content` is the Document's TipTap JSON in the current shape; nodes without an ID, and the inline nodes inside a textblock\n * (a mention's `id` is a user's, not a block's), have no row. Pure: the same content and options give the same rows.\n */\nexport function projectDocument(content: JSONContent, documentId: string, options: ProjectOptions = {}): BlockRow[] {\n const rows: BlockRow[] = []\n const walk = (node: JSONContent): void => {\n if (holdsInline(node)) return\n for (const child of node.content ?? []) {\n const id = child.attrs?.id\n if (typeof id === 'string' && id) rows.push(rowOf(child, id, documentId, options.markdown))\n walk(child)\n }\n }\n walk(content)\n return rows\n}\n\n/** The Edges that leave `row`, each with the Block it is written in. */\nexport const blockEdgesOf = (row: Pick<BlockRow, 'documentId' | 'blockId' | 'edges'>): BlockEdge[] =>\n row.edges.map(edge => ({ from: { documentId: row.documentId, blockId: row.blockId }, ...structuredClone(edge) }))\n\n/** The Edges of `rows` that point at the Block `address` (`tag` Edges never do), in the order of the rows. */\nexport function edgesInto(rows: Iterable<Pick<BlockRow, 'documentId' | 'blockId' | 'edges'>>, address: BlockAddress): BlockEdge[] {\n const found: BlockEdge[] = []\n for (const row of rows) {\n for (const edge of row.edges) {\n if (isTagEdgeTarget(edge.to) || !sameAddress(edge.to, address)) continue\n found.push({ from: { documentId: row.documentId, blockId: row.blockId }, ...structuredClone(edge) })\n }\n }\n return found\n}\n\n/**\n * The nodes of Block `blockId` in a Document's content, as `blockContentOf` of the block route: `block` = the Block itself,\n * `children` = what it holds (a Typed block: its body without the title, description and tags rows; a list or a quote: its\n * items; a Block that holds only text: nothing). `null` when the content has no Block with that ID. Pure and a copy-free view:\n * the nodes are those of `content`.\n */\nexport function blockContentOf(content: JSONContent, blockId: string, mode: BlockContentMode): JSONContent[] | null {\n const found = findBlock(content, blockId)\n if (!found) return null\n if (mode === 'block') return [found]\n if (holdsInline(found)) return []\n return (found.content ?? []).filter(child => !(isTypedBlock(found) && child.type !== undefined && CHROME.has(child.type)))\n}\n\nfunction findBlock(node: JSONContent, blockId: string): JSONContent | null {\n if (holdsInline(node)) return null\n for (const child of node.content ?? []) {\n if (child.attrs?.id === blockId) return child\n const nested = findBlock(child, blockId)\n if (nested) return nested\n }\n return null\n}\n"],"mappings":"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"}
|
|
@@ -2,12 +2,17 @@ import "./schema/mark-flags.js";
|
|
|
2
2
|
import { AnyExtension, JSONContent } from "@tiptap/core";
|
|
3
3
|
//#region src/core/document-markdown.d.ts
|
|
4
4
|
/**
|
|
5
|
-
* `@tiptap/markdown`'s `Markdown`
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* `@tiptap/markdown`'s `Markdown` with two fixes for the documents of a preset. Use it instead of `Markdown` in every
|
|
6
|
+
* editor of a preset.
|
|
7
|
+
*
|
|
8
|
+
* - Its serializer ignores every mark that declares `markdownInvisible` (author and comment marks):
|
|
9
|
+
* `editor.getMarkdown()` and every `editor.markdown.serialize()` (AI context and selection, chat blocks, summaries,
|
|
10
|
+
* transcript cleanup) render as if those marks did not exist. `@tiptap/markdown` already renders marks without a
|
|
11
|
+
* handler as nothing, but they still split text runs, and its mark-boundary logic then closes and reopens the
|
|
12
|
+
* formatting around the split (`Ein **Sa**<strong>btz</strong>` for one bold word with another author's letter
|
|
13
|
+
* inside). The document keeps the marks.
|
|
14
|
+
* - Editing a paragraph keeps the soft line breaks (`\n` in its text nodes) that `@tiptap/markdown` parses Markdown
|
|
15
|
+
* soft breaks into, instead of turning them into hard breaks (`softLineBreaksPlugin`).
|
|
11
16
|
*/
|
|
12
17
|
export declare const DocumentMarkdown: import("@tiptap/core").Extension<import("@tiptap/markdown").MarkdownExtensionOptions, import("@tiptap/markdown").MarkdownExtensionStorage>;
|
|
13
18
|
//#endregion
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { softLineBreaksPlugin } from "./soft-line-breaks.js";
|
|
1
2
|
import { getExtensionField } from "@tiptap/core";
|
|
2
3
|
import { Markdown } from "@tiptap/markdown";
|
|
3
4
|
//#region src/core/document-markdown.ts
|
|
@@ -14,30 +15,40 @@ function invisibleMarkNames(extensions) {
|
|
|
14
15
|
return new Set(extensions.filter((extension) => extension.type === "mark" && getExtensionField(extension, "markdownInvisible")).map((extension) => extension.name));
|
|
15
16
|
}
|
|
16
17
|
/**
|
|
17
|
-
* `@tiptap/markdown`'s `Markdown`
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
18
|
+
* `@tiptap/markdown`'s `Markdown` with two fixes for the documents of a preset. Use it instead of `Markdown` in every
|
|
19
|
+
* editor of a preset.
|
|
20
|
+
*
|
|
21
|
+
* - Its serializer ignores every mark that declares `markdownInvisible` (author and comment marks):
|
|
22
|
+
* `editor.getMarkdown()` and every `editor.markdown.serialize()` (AI context and selection, chat blocks, summaries,
|
|
23
|
+
* transcript cleanup) render as if those marks did not exist. `@tiptap/markdown` already renders marks without a
|
|
24
|
+
* handler as nothing, but they still split text runs, and its mark-boundary logic then closes and reopens the
|
|
25
|
+
* formatting around the split (`Ein **Sa**<strong>btz</strong>` for one bold word with another author's letter
|
|
26
|
+
* inside). The document keeps the marks.
|
|
27
|
+
* - Editing a paragraph keeps the soft line breaks (`\n` in its text nodes) that `@tiptap/markdown` parses Markdown
|
|
28
|
+
* soft breaks into, instead of turning them into hard breaks (`softLineBreaksPlugin`).
|
|
23
29
|
*/
|
|
24
|
-
const DocumentMarkdown = Markdown.extend({
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (!
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
30
|
+
const DocumentMarkdown = Markdown.extend({
|
|
31
|
+
addProseMirrorPlugins() {
|
|
32
|
+
return [...this.parent?.() ?? [], softLineBreaksPlugin(this.editor.schema)];
|
|
33
|
+
},
|
|
34
|
+
onBeforeCreate(event) {
|
|
35
|
+
this.parent?.(event);
|
|
36
|
+
const extensions = this.editor.extensionManager.extensions;
|
|
37
|
+
const invisible = invisibleMarkNames(extensions);
|
|
38
|
+
const prepares = extensions.flatMap((extension) => {
|
|
39
|
+
const prepare = extension.type === "node" ? getExtensionField(extension, "markdownPrepare") : void 0;
|
|
40
|
+
return prepare ? [prepare] : [];
|
|
41
|
+
});
|
|
42
|
+
if (!invisible.size && !prepares.length) return;
|
|
43
|
+
const manager = this.storage.manager;
|
|
44
|
+
const serialize = manager.serialize.bind(manager);
|
|
45
|
+
manager.serialize = (json) => {
|
|
46
|
+
if (!json) return serialize(json);
|
|
47
|
+
const prepared = prepares.reduce((current, prepare) => prepare(current, this.editor), json);
|
|
48
|
+
return serialize(invisible.size ? withoutMarks(prepared, invisible) : prepared);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
});
|
|
41
52
|
//#endregion
|
|
42
53
|
export { DocumentMarkdown, invisibleMarkNames, withoutMarks };
|
|
43
54
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document-markdown.js","names":[],"sources":["../../src/core/document-markdown.ts"],"sourcesContent":["import { type AnyExtension, getExtensionField, type JSONContent, type NodeConfig } from '@tiptap/core'\nimport { Markdown } from '@tiptap/markdown'\nimport './schema/mark-flags'\n\n/** `json` without the marks named in `invisible` on any node; the runs they split become identically marked neighbours. */\nexport function withoutMarks(json: JSONContent, invisible: ReadonlySet<string>): JSONContent {\n return {\n ...json,\n ...(json.marks && { marks: json.marks.filter(mark => !invisible.has(mark.type)) }),\n ...(json.content && { content: json.content.map(child => withoutMarks(child, invisible)) }),\n }\n}\n\n/** The names of the marks among `extensions` that declare `markdownInvisible` (author and comment marks). */\nexport function invisibleMarkNames(extensions: readonly AnyExtension[]): Set<string> {\n return new Set(\n extensions\n .filter(extension => extension.type === 'mark' && getExtensionField<boolean | undefined>(extension, 'markdownInvisible'))\n .map(extension => extension.name),\n )\n}\n\n/**\n * `@tiptap/markdown`'s `Markdown`
|
|
1
|
+
{"version":3,"file":"document-markdown.js","names":[],"sources":["../../src/core/document-markdown.ts"],"sourcesContent":["import { type AnyExtension, getExtensionField, type JSONContent, type NodeConfig } from '@tiptap/core'\nimport { Markdown } from '@tiptap/markdown'\nimport './schema/mark-flags'\nimport { softLineBreaksPlugin } from './soft-line-breaks'\n\n/** `json` without the marks named in `invisible` on any node; the runs they split become identically marked neighbours. */\nexport function withoutMarks(json: JSONContent, invisible: ReadonlySet<string>): JSONContent {\n return {\n ...json,\n ...(json.marks && { marks: json.marks.filter(mark => !invisible.has(mark.type)) }),\n ...(json.content && { content: json.content.map(child => withoutMarks(child, invisible)) }),\n }\n}\n\n/** The names of the marks among `extensions` that declare `markdownInvisible` (author and comment marks). */\nexport function invisibleMarkNames(extensions: readonly AnyExtension[]): Set<string> {\n return new Set(\n extensions\n .filter(extension => extension.type === 'mark' && getExtensionField<boolean | undefined>(extension, 'markdownInvisible'))\n .map(extension => extension.name),\n )\n}\n\n/**\n * `@tiptap/markdown`'s `Markdown` with two fixes for the documents of a preset. Use it instead of `Markdown` in every\n * editor of a preset.\n *\n * - Its serializer ignores every mark that declares `markdownInvisible` (author and comment marks):\n * `editor.getMarkdown()` and every `editor.markdown.serialize()` (AI context and selection, chat blocks, summaries,\n * transcript cleanup) render as if those marks did not exist. `@tiptap/markdown` already renders marks without a\n * handler as nothing, but they still split text runs, and its mark-boundary logic then closes and reopens the\n * formatting around the split (`Ein **Sa**<strong>btz</strong>` for one bold word with another author's letter\n * inside). The document keeps the marks.\n * - Editing a paragraph keeps the soft line breaks (`\\n` in its text nodes) that `@tiptap/markdown` parses Markdown\n * soft breaks into, instead of turning them into hard breaks (`softLineBreaksPlugin`).\n */\nexport const DocumentMarkdown = Markdown.extend({\n addProseMirrorPlugins() {\n return [...(this.parent?.() ?? []), softLineBreaksPlugin(this.editor.schema)]\n },\n onBeforeCreate(event) {\n this.parent?.(event)\n const extensions = this.editor.extensionManager.extensions\n const invisible = invisibleMarkNames(extensions)\n const prepares = extensions.flatMap((extension) => {\n const prepare = extension.type === 'node' ? getExtensionField<NodeConfig['markdownPrepare']>(extension, 'markdownPrepare') : undefined\n return prepare ? [prepare] : []\n })\n if (!invisible.size && !prepares.length) return\n // `getMarkdown()` serializes through the same manager instance as `editor.markdown`.\n const manager = this.storage.manager\n const serialize = manager.serialize.bind(manager)\n manager.serialize = (json) => {\n if (!json) return serialize(json)\n const prepared = prepares.reduce((current, prepare) => prepare(current, this.editor), json)\n return serialize(invisible.size ? withoutMarks(prepared, invisible) : prepared)\n }\n },\n})\n"],"mappings":";;;;;AAMA,SAAgB,aAAa,MAAmB,WAA6C;CAC3F,OAAO;EACL,GAAG;EACH,GAAI,KAAK,SAAS,EAAE,OAAO,KAAK,MAAM,QAAO,SAAQ,CAAC,UAAU,IAAI,KAAK,IAAI,CAAC,EAAE;EAChF,GAAI,KAAK,WAAW,EAAE,SAAS,KAAK,QAAQ,KAAI,UAAS,aAAa,OAAO,SAAS,CAAC,EAAE;CAC3F;AACF;;AAGA,SAAgB,mBAAmB,YAAkD;CACnF,OAAO,IAAI,IACT,WACG,QAAO,cAAa,UAAU,SAAS,UAAU,kBAAuC,WAAW,mBAAmB,CAAC,CAAC,CACxH,KAAI,cAAa,UAAU,IAAI,CACpC;AACF;;;;;;;;;;;;;;AAeA,MAAa,mBAAmB,SAAS,OAAO;CAC9C,wBAAwB;EACtB,OAAO,CAAC,GAAI,KAAK,SAAS,KAAK,CAAC,GAAI,qBAAqB,KAAK,OAAO,MAAM,CAAC;CAC9E;CACA,eAAe,OAAO;EACpB,KAAK,SAAS,KAAK;EACnB,MAAM,aAAa,KAAK,OAAO,iBAAiB;EAChD,MAAM,YAAY,mBAAmB,UAAU;EAC/C,MAAM,WAAW,WAAW,SAAS,cAAc;GACjD,MAAM,UAAU,UAAU,SAAS,SAAS,kBAAiD,WAAW,iBAAiB,IAAI,KAAA;GAC7H,OAAO,UAAU,CAAC,OAAO,IAAI,CAAC;EAChC,CAAC;EACD,IAAI,CAAC,UAAU,QAAQ,CAAC,SAAS,QAAQ;EAEzC,MAAM,UAAU,KAAK,QAAQ;EAC7B,MAAM,YAAY,QAAQ,UAAU,KAAK,OAAO;EAChD,QAAQ,aAAa,SAAS;GAC5B,IAAI,CAAC,MAAM,OAAO,UAAU,IAAI;GAChC,MAAM,WAAW,SAAS,QAAQ,SAAS,YAAY,QAAQ,SAAS,KAAK,MAAM,GAAG,IAAI;GAC1F,OAAO,UAAU,UAAU,OAAO,aAAa,UAAU,SAAS,IAAI,QAAQ;EAChF;CACF;AACF,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
2
|
+
import { DOMParser } from "@tiptap/pm/model";
|
|
3
|
+
//#region src/core/soft-line-breaks.ts
|
|
4
|
+
/**
|
|
5
|
+
* Re-reads the DOM of the document's own textblocks with their whitespace in full. `@tiptap/markdown` parses a Markdown
|
|
6
|
+
* soft line break (a single `\n` inside a paragraph) into a `\n` in the text node and writes it back as is; the editor
|
|
7
|
+
* shows it through `white-space: pre-wrap`. After a keystroke ProseMirror re-parses the changed DOM with
|
|
8
|
+
* `preserveWhitespace: true`, which turns every `\n` there into the schema's line-break replacement (`hardBreak`,
|
|
9
|
+
* written as ` \n`), so editing one line hard-broke every line of its paragraph. The DOM text of a textblock the view
|
|
10
|
+
* rendered is the document text, so keeping its `\n` is exact: in a re-read of one textblock (`topNode`) and in a re-read
|
|
11
|
+
* across blocks, for each textblock that already has a view (`ruleFromNode`). Elements the browser added are read as
|
|
12
|
+
* before; so are the clipboard (`parseSlice`) and code (`whitespace: 'pre'`, already in full).
|
|
13
|
+
*/
|
|
14
|
+
var SoftLineBreakParser = class extends DOMParser {
|
|
15
|
+
parse(dom, options = {}) {
|
|
16
|
+
if (options.preserveWhitespace !== true) return super.parse(dom, options);
|
|
17
|
+
const { topNode, ruleFromNode } = options;
|
|
18
|
+
const read = {
|
|
19
|
+
...options,
|
|
20
|
+
preserveWhitespace: topNode?.inlineContent ? "full" : true,
|
|
21
|
+
ruleFromNode: ruleFromNode && ((node) => this.inFull(ruleFromNode(node)))
|
|
22
|
+
};
|
|
23
|
+
return super.parse(dom, read);
|
|
24
|
+
}
|
|
25
|
+
/** `rule` reading its node's whitespace in full when that node is a textblock and the rule does not say otherwise. */
|
|
26
|
+
inFull(rule) {
|
|
27
|
+
if (!rule?.node || rule.preserveWhitespace != null || !this.schema.nodes[rule.node]?.isTextblock) return rule;
|
|
28
|
+
return {
|
|
29
|
+
...rule,
|
|
30
|
+
preserveWhitespace: "full"
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
/** The plugin that installs `SoftLineBreakParser` as the view's `domParser` for `schema`. */
|
|
35
|
+
function softLineBreaksPlugin(schema) {
|
|
36
|
+
return new Plugin({
|
|
37
|
+
key: new PluginKey("softLineBreaks"),
|
|
38
|
+
props: { domParser: new SoftLineBreakParser(schema, DOMParser.fromSchema(schema).rules) }
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
//#endregion
|
|
42
|
+
export { softLineBreaksPlugin };
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=soft-line-breaks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"soft-line-breaks.js","names":[],"sources":["../../src/core/soft-line-breaks.ts"],"sourcesContent":["import { DOMParser, type ParseOptions, type Schema, type TagParseRule } from '@tiptap/pm/model'\nimport { Plugin, PluginKey } from '@tiptap/pm/state'\n\ntype NodeRule = Omit<TagParseRule, 'tag'>\n\n/** `ParseOptions` with the `ruleFromNode` hook prosemirror-view passes when it re-reads its DOM (internal in prosemirror-model's types). */\ntype ReadOptions = ParseOptions & { ruleFromNode?: (node: globalThis.Node) => NodeRule | null }\n\n/**\n * Re-reads the DOM of the document's own textblocks with their whitespace in full. `@tiptap/markdown` parses a Markdown\n * soft line break (a single `\\n` inside a paragraph) into a `\\n` in the text node and writes it back as is; the editor\n * shows it through `white-space: pre-wrap`. After a keystroke ProseMirror re-parses the changed DOM with\n * `preserveWhitespace: true`, which turns every `\\n` there into the schema's line-break replacement (`hardBreak`,\n * written as ` \\n`), so editing one line hard-broke every line of its paragraph. The DOM text of a textblock the view\n * rendered is the document text, so keeping its `\\n` is exact: in a re-read of one textblock (`topNode`) and in a re-read\n * across blocks, for each textblock that already has a view (`ruleFromNode`). Elements the browser added are read as\n * before; so are the clipboard (`parseSlice`) and code (`whitespace: 'pre'`, already in full).\n */\nclass SoftLineBreakParser extends DOMParser {\n override parse(dom: globalThis.Node, options: ReadOptions = {}) {\n if (options.preserveWhitespace !== true) return super.parse(dom, options)\n const { topNode, ruleFromNode } = options\n const read: ReadOptions = {\n ...options,\n preserveWhitespace: topNode?.inlineContent ? 'full' : true,\n ruleFromNode: ruleFromNode && (node => this.inFull(ruleFromNode(node))),\n }\n return super.parse(dom, read)\n }\n\n /** `rule` reading its node's whitespace in full when that node is a textblock and the rule does not say otherwise. */\n private inFull(rule: NodeRule | null): NodeRule | null {\n if (!rule?.node || rule.preserveWhitespace != null || !this.schema.nodes[rule.node]?.isTextblock) return rule\n return { ...rule, preserveWhitespace: 'full' }\n }\n}\n\n/** The plugin that installs `SoftLineBreakParser` as the view's `domParser` for `schema`. */\nexport function softLineBreaksPlugin(schema: Schema): Plugin {\n return new Plugin({\n key: new PluginKey('softLineBreaks'),\n props: { domParser: new SoftLineBreakParser(schema, DOMParser.fromSchema(schema).rules) },\n })\n}\n"],"mappings":";;;;;;;;;;;;;AAkBA,IAAM,sBAAN,cAAkC,UAAU;CAC1C,MAAe,KAAsB,UAAuB,CAAC,GAAG;EAC9D,IAAI,QAAQ,uBAAuB,MAAM,OAAO,MAAM,MAAM,KAAK,OAAO;EACxE,MAAM,EAAE,SAAS,iBAAiB;EAClC,MAAM,OAAoB;GACxB,GAAG;GACH,oBAAoB,SAAS,gBAAgB,SAAS;GACtD,cAAc,kBAAiB,SAAQ,KAAK,OAAO,aAAa,IAAI,CAAC;EACvE;EACA,OAAO,MAAM,MAAM,KAAK,IAAI;CAC9B;;CAGA,OAAe,MAAwC;EACrD,IAAI,CAAC,MAAM,QAAQ,KAAK,sBAAsB,QAAQ,CAAC,KAAK,OAAO,MAAM,KAAK,KAAK,EAAE,aAAa,OAAO;EACzG,OAAO;GAAE,GAAG;GAAM,oBAAoB;EAAO;CAC/C;AACF;;AAGA,SAAgB,qBAAqB,QAAwB;CAC3D,OAAO,IAAI,OAAO;EAChB,KAAK,IAAI,UAAU,gBAAgB;EACnC,OAAO,EAAE,WAAW,IAAI,oBAAoB,QAAQ,UAAU,WAAW,MAAM,CAAC,CAAC,KAAK,EAAE;CAC1F,CAAC;AACH"}
|
|
@@ -32,12 +32,12 @@ type __VLS_Slots = {} & { [K in NonNullable<typeof __VLS_13>]?: (props: typeof _
|
|
|
32
32
|
binary?: (props: typeof __VLS_26) => any;
|
|
33
33
|
};
|
|
34
34
|
declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
35
|
-
ready: (target: DocumentSurfaceTarget) => any;
|
|
36
35
|
"update:modelValue": (text: string) => any;
|
|
36
|
+
ready: (target: DocumentSurfaceTarget) => any;
|
|
37
37
|
save: (text: string) => any;
|
|
38
38
|
}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{
|
|
39
|
-
onReady?: ((target: DocumentSurfaceTarget) => any) | undefined;
|
|
40
39
|
"onUpdate:modelValue"?: ((text: string) => any) | undefined;
|
|
40
|
+
onReady?: ((target: DocumentSurfaceTarget) => any) | undefined;
|
|
41
41
|
onSave?: ((text: string) => any) | undefined;
|
|
42
42
|
}>, {
|
|
43
43
|
modelValue: string;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useUiKit } from "../../../vue/kit/use-ui-kit.js";
|
|
2
2
|
import { useEditorI18n } from "../../../vue/use-editor-i18n.js";
|
|
3
3
|
import { useStoreRef } from "../../../vue/store-ref.js";
|
|
4
|
-
import { createAiReviewBar } from "../review-bar-model.js";
|
|
5
4
|
import { useNotifier } from "../../../vue/use-notifier.js";
|
|
5
|
+
import { createAiReviewBar } from "../review-bar-model.js";
|
|
6
6
|
import { Fragment, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, onBeforeUnmount, openBlock, toDisplayString, unref, withCtx } from "vue";
|
|
7
7
|
//#region src/features/ai/components/AiReviewBar.vue
|
|
8
8
|
const _hoisted_1 = { class: "flex gap-1 p-1" };
|
|
@@ -6,10 +6,10 @@ import { useUiKit } from "../../../vue/kit/use-ui-kit.js";
|
|
|
6
6
|
import { useEditorSettings } from "../../../vue/settings.js";
|
|
7
7
|
import { useEditorI18n } from "../../../vue/use-editor-i18n.js";
|
|
8
8
|
import { useStoreRef } from "../../../vue/store-ref.js";
|
|
9
|
+
import { useNotifier } from "../../../vue/use-notifier.js";
|
|
9
10
|
import { aiSelectionHighlightKey } from "../ai-selection-highlight.js";
|
|
10
11
|
import { previewExtensions } from "../preview-extensions.js";
|
|
11
12
|
import { AI_SELECTION_MENU_KEY, createAiSelectionFlow } from "../selection-flow.js";
|
|
12
|
-
import { useNotifier } from "../../../vue/use-notifier.js";
|
|
13
13
|
import { useAiAssistant } from "../use-ai-assistant.js";
|
|
14
14
|
import _sfc_main$1 from "./ModelQuickSwitch.js";
|
|
15
15
|
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, onBeforeUnmount, openBlock, renderList, toDisplayString, unref, useTemplateRef, watch, withCtx, withModifiers } from "vue";
|
|
@@ -4,12 +4,12 @@ import { conversionsOf } from "../../../core/pipeline.js";
|
|
|
4
4
|
import { useUiKit } from "../../../vue/kit/use-ui-kit.js";
|
|
5
5
|
import { useEditorI18n } from "../../../vue/use-editor-i18n.js";
|
|
6
6
|
import { useStoreRef } from "../../../vue/store-ref.js";
|
|
7
|
-
import { selectionScope } from "../agent-ops.js";
|
|
8
|
-
import { createAiSidebarFlow } from "../sidebar-flow.js";
|
|
9
7
|
import { useNotifier } from "../../../vue/use-notifier.js";
|
|
10
8
|
import { useAiAssistant } from "../use-ai-assistant.js";
|
|
11
9
|
import _sfc_main$1 from "./ModelQuickSwitch.js";
|
|
10
|
+
import { selectionScope } from "../agent-ops.js";
|
|
12
11
|
import { useDocumentChat } from "../use-document-chat.js";
|
|
12
|
+
import { createAiSidebarFlow } from "../sidebar-flow.js";
|
|
13
13
|
import { AI_SIDEBAR } from "../sidebar-context.js";
|
|
14
14
|
import { sidebarMessages } from "../sidebar-messages.js";
|
|
15
15
|
import _sfc_main$2 from "./DocumentChatGenerationPart.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { citationBlockId } from "../protocol/chat.js";
|
|
2
2
|
import { useEditorI18n } from "../../../vue/use-editor-i18n.js";
|
|
3
|
+
import { useNotifier } from "../../../vue/use-notifier.js";
|
|
3
4
|
import { previewExtensions } from "../preview-extensions.js";
|
|
4
5
|
import { documentChatAnswerContent } from "../document-chat.js";
|
|
5
|
-
import { useNotifier } from "../../../vue/use-notifier.js";
|
|
6
6
|
import { createBlock, createCommentVNode, createElementBlock, defineComponent, onBeforeUnmount, openBlock, unref, watch } from "vue";
|
|
7
7
|
import { EditorContent, useEditor } from "@tiptap/vue-3";
|
|
8
8
|
//#region src/features/ai/components/DocumentChatAnswer.vue
|
|
@@ -3,8 +3,8 @@ import { useUiKit } from "../../../vue/kit/use-ui-kit.js";
|
|
|
3
3
|
import { useEditorContext } from "../../../vue/context.js";
|
|
4
4
|
import { useEditorI18n } from "../../../vue/use-editor-i18n.js";
|
|
5
5
|
import { useStoreRef } from "../../../vue/store-ref.js";
|
|
6
|
-
import { createModelTestFlow } from "../model-test-flow.js";
|
|
7
6
|
import { useModelProfiles } from "../use-model-profiles.js";
|
|
7
|
+
import { createModelTestFlow } from "../model-test-flow.js";
|
|
8
8
|
import { Fragment, computed, createBlock, createCommentVNode, createElementBlock, createElementVNode, createVNode, defineComponent, openBlock, ref, renderList, toDisplayString, unref, watch, withCtx, withModifiers } from "vue";
|
|
9
9
|
//#region src/features/ai/components/SettingsModelProfiles.vue
|
|
10
10
|
const _hoisted_1 = { class: "flex flex-col divide-y divide-[var(--sk-border)]" };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { AiReview } from "./ai-review.js";
|
|
2
2
|
import { AiSelectionHighlight } from "./ai-selection-highlight.js";
|
|
3
|
+
import { modelProfiles } from "./model-profiles.js";
|
|
3
4
|
import { aiBlockAction, aiCommand } from "./block-actions.js";
|
|
4
5
|
import { completionFetcher, ghostAutocomplete } from "./ghost-autocomplete.js";
|
|
5
|
-
import { modelProfiles } from "./model-profiles.js";
|
|
6
6
|
import { Extension } from "@tiptap/core";
|
|
7
7
|
//#region src/features/ai/feature.ts
|
|
8
8
|
/** Pause after the last keystroke before an autocomplete suggestion is requested (ms). */
|
|
@@ -9,20 +9,20 @@ import { AGENT_LIMITS, AGENT_OP_OUTCOME_TEXT, AGENT_TOOL, agentFeedbackText, age
|
|
|
9
9
|
import { CHAT_LIMITS, CITATION_HREF_PREFIX, citationBlockId, documentChatMessages, documentChatSystem } from "./protocol/chat.js";
|
|
10
10
|
import { aiApplyFailure, applyAiResult, captureTarget, documentMarkdown, formatTaskResult, insertAtCaretLine, insertBlocksAtTarget, insertBlocksBelow, rangeMarkdown, resultBlocks, selectionMarkdown } from "./ai-apply.js";
|
|
11
11
|
import { AiReview, acceptAiReview, aiReviewKey, rejectAiReview, setActiveAiReviewHunk, startAiReview } from "./ai-review.js";
|
|
12
|
+
import { createAiReviewBar } from "./review-bar-model.js";
|
|
12
13
|
import { AiSelectionHighlight, aiSelectionHighlightKey, hideAiSelectionHighlight, showAiSelectionHighlight } from "./ai-selection-highlight.js";
|
|
13
14
|
import { AI_MENU_AT_CARET, AI_MENU_FOR_SELECTION, aiMenuActionsOf, openAiMenuAtCaret, openAiMenuForSelection } from "./ai-menu.js";
|
|
14
|
-
import { aiBlockAction, aiCommand, blockTextRange } from "./block-actions.js";
|
|
15
|
-
import { completionFetcher, ghostAutocomplete, ghostAutocompleteKey } from "./ghost-autocomplete.js";
|
|
16
|
-
import { modelProfiles } from "./model-profiles.js";
|
|
17
|
-
import { aiFeatureCore } from "./feature.js";
|
|
18
|
-
import { createAiReviewBar } from "./review-bar-model.js";
|
|
19
15
|
import { AI_SELECTION_MENU_KEY, createAiSelectionFlow } from "./selection-flow.js";
|
|
20
16
|
import { isRateLimited, notifyAiRateLimited } from "./rate-limit.js";
|
|
21
17
|
import { postAiSse } from "./ai-sse.js";
|
|
22
18
|
import { createAiAssistant } from "./assistant.js";
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
19
|
+
import { modelProfiles } from "./model-profiles.js";
|
|
20
|
+
import { aiBlockAction, aiCommand, blockTextRange } from "./block-actions.js";
|
|
21
|
+
import { completionFetcher, ghostAutocomplete, ghostAutocompleteKey } from "./ghost-autocomplete.js";
|
|
22
|
+
import { aiFeatureCore } from "./feature.js";
|
|
25
23
|
import { documentChatText } from "./document-chat.js";
|
|
24
|
+
import { selectionScope } from "./agent-ops.js";
|
|
26
25
|
import { createAgentOpReview } from "./agent-op-review.js";
|
|
27
26
|
import { createDocumentChat } from "./document-chat-model.js";
|
|
27
|
+
import { createAiSidebarFlow } from "./sidebar-flow.js";
|
|
28
28
|
export { AGENT_LIMITS, AGENT_OP_OUTCOMES, AGENT_OP_OUTCOME_TEXT, AGENT_OP_TYPES, AGENT_TOOL, AI_COMPLETE_LIMITS, AI_ID_PATTERN, AI_LANGUAGE_KEYS, AI_LIMITS, AI_MENU_AT_CARET, AI_MENU_FOR_SELECTION, AI_SELECTION_MENU_KEY, AI_TASKS, AI_TASK_IDS, AiReview, AiSelectionHighlight, BLOCK_LINES_LABEL, BLOCK_LINE_COMPOSITE_TEXT, BLOCK_LINE_ECHO_TEXT, BLOCK_LINE_EXAMPLE, BLOCK_LINE_EXAMPLE_ID, BLOCK_LINE_EXAMPLE_TEXT, BLOCK_LINE_HOST_TEXT, BLOCK_LINE_IDS_HINT, BLOCK_LINE_INTRO, BLOCK_LINE_LIST_TEXT, BLOCK_LINE_MARKERS, CHAT_LIMITS, CITATION_HREF_PREFIX, MODEL_REF_LIMITS, MODEL_ROLE_KEYS, TRUNCATED_LINE, acceptAiReview, agentFeedbackText, agentOpSummary, agentOpTarget, agentRetryMessage, agentShownMessage, agentUnreadableProblem, aiApplyFailure, aiBlockAction, aiBlockInputSchema, aiBlockSchema, aiCommand, aiFeatureCore, aiMenuActionsOf, aiReviewKey, aiSelectionHighlightKey, applyAiResult, blockLine, blockTasks, blockTextRange, captureTarget, checkAgentOps, citationBlockId, cleanCompletion, completeBodySchema, completeMessages, completionFetcher, completionReady, compositeChromePart, compositeLineClose, compositeLineHeader, continuedLinePrefix, createAgentOpReview, createAiAssistant, createAiReviewBar, createAiSelectionFlow, createAiSidebarFlow, createDocumentChat, documentChatMessages, documentChatSystem, documentChatText, documentMarkdown, formatTaskResult, generateBodySchema, generateProblem, ghostAutocomplete, ghostAutocompleteKey, hideAiSelectionHighlight, inBlockTasks, insertAtCaretLine, insertBlocksAtTarget, insertBlocksBelow, isRateLimited, listLinePrefix, modelProfiles, modelRefSchema, nestedLinePrefix, notifyAiRateLimited, openAiMenuAtCaret, openAiMenuForSelection, postAiSse, quoteLinePrefix, rangeMarkdown, readBlockLines, rejectAiReview, resultBlocks, runCompletion, runGenerate, selectionMarkdown, selectionScope, setActiveAiReviewHunk, showAiSelectionHighlight, singleBlockLine, startAiReview, stripBlockLineEcho, stripListMarkerEcho, withHeading, withTaskHeading };
|
|
@@ -7,14 +7,14 @@ import { useEditorI18n } from "../../../react/use-editor-i18n.js";
|
|
|
7
7
|
import { useStore } from "../../../react/use-store.js";
|
|
8
8
|
import { Button } from "../../../react/ui/button.js";
|
|
9
9
|
import { useEditorSettings } from "../../../react/settings.js";
|
|
10
|
-
import { InputGroup, InputGroupAddon, InputGroupInput } from "../../../react/ui/input-group.js";
|
|
11
|
-
import { aiSelectionHighlightKey } from "../ai-selection-highlight.js";
|
|
12
10
|
import { Popover, PopoverAnchor, PopoverContent } from "../../../react/ui/popover.js";
|
|
13
11
|
import { useNotifier } from "../../../react/use-notifier.js";
|
|
14
|
-
import {
|
|
15
|
-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../../react/ui/select.js";
|
|
12
|
+
import { aiSelectionHighlightKey } from "../ai-selection-highlight.js";
|
|
16
13
|
import { previewExtensions } from "../preview-extensions.js";
|
|
17
14
|
import { AI_SELECTION_MENU_KEY } from "../selection-flow.js";
|
|
15
|
+
import { InputGroup, InputGroupAddon, InputGroupInput } from "../../../react/ui/input-group.js";
|
|
16
|
+
import { Alert, AlertTitle } from "../../../react/ui/alert.js";
|
|
17
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../../../react/ui/select.js";
|
|
18
18
|
import { ModelQuickSwitch } from "./ModelQuickSwitch.js";
|
|
19
19
|
import { useAiAssistant } from "./use-ai-assistant.js";
|
|
20
20
|
import { useAiSelectionFlow } from "./use-ai-selection-flow.js";
|
|
@@ -8,14 +8,14 @@ import { useStore } from "../../../react/use-store.js";
|
|
|
8
8
|
import { Badge } from "../../../react/ui/badge.js";
|
|
9
9
|
import { Button } from "../../../react/ui/button.js";
|
|
10
10
|
import { Input } from "../../../react/ui/input.js";
|
|
11
|
-
import {
|
|
11
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../../../react/ui/tooltip.js";
|
|
12
12
|
import { useNotifier } from "../../../react/use-notifier.js";
|
|
13
|
+
import { selectionScope } from "../agent-ops.js";
|
|
14
|
+
import { createAiSidebarFlow } from "../sidebar-flow.js";
|
|
15
|
+
import { Textarea } from "../../../react/ui/textarea.js";
|
|
13
16
|
import { Alert, AlertTitle } from "../../../react/ui/alert.js";
|
|
14
17
|
import { ModelQuickSwitch } from "./ModelQuickSwitch.js";
|
|
15
18
|
import { useAiAssistant } from "./use-ai-assistant.js";
|
|
16
|
-
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../../../react/ui/tooltip.js";
|
|
17
|
-
import { selectionScope } from "../agent-ops.js";
|
|
18
|
-
import { createAiSidebarFlow } from "../sidebar-flow.js";
|
|
19
19
|
import { DocumentChatAnswer } from "./DocumentChatAnswer.js";
|
|
20
20
|
import { DocumentChatOps } from "./DocumentChatOps.js";
|
|
21
21
|
import { useDocumentChat } from "./use-document-chat.js";
|
|
@@ -4,8 +4,8 @@ import { Icon } from "../../../react/Icon.js";
|
|
|
4
4
|
import { useEditorI18n } from "../../../react/use-editor-i18n.js";
|
|
5
5
|
import { Badge } from "../../../react/ui/badge.js";
|
|
6
6
|
import { Button } from "../../../react/ui/button.js";
|
|
7
|
-
import { Alert, AlertDescription, AlertTitle } from "../../../react/ui/alert.js";
|
|
8
7
|
import { AGENT_OP_OUTCOME_KEYS } from "../agent-ops.js";
|
|
8
|
+
import { Alert, AlertDescription, AlertTitle } from "../../../react/ui/alert.js";
|
|
9
9
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
10
|
//#region src/features/ai/react/DocumentChatOps.tsx
|
|
11
11
|
const ICONS = {
|
|
@@ -4,8 +4,8 @@ import { Icon } from "../../../react/Icon.js";
|
|
|
4
4
|
import { useEditorI18n } from "../../../react/use-editor-i18n.js";
|
|
5
5
|
import { useEditorSettings } from "../../../react/settings.js";
|
|
6
6
|
import { useEditorUi } from "../../../react/editor-ui.js";
|
|
7
|
-
import { Select, SelectContent, SelectItem, SelectSeparator, SelectTrigger, SelectValue } from "../../../react/ui/select.js";
|
|
8
7
|
import { chooseQuickSwitchValue, profileIcon } from "../model-quick-switch.js";
|
|
8
|
+
import { Select, SelectContent, SelectItem, SelectSeparator, SelectTrigger, SelectValue } from "../../../react/ui/select.js";
|
|
9
9
|
import { useModelProfiles } from "./use-model-profiles.js";
|
|
10
10
|
import { useState } from "react";
|
|
11
11
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -6,10 +6,10 @@ import { useStore } from "../../../react/use-store.js";
|
|
|
6
6
|
import { Badge } from "../../../react/ui/badge.js";
|
|
7
7
|
import { Button } from "../../../react/ui/button.js";
|
|
8
8
|
import { Input } from "../../../react/ui/input.js";
|
|
9
|
+
import { createModelTestFlow } from "../model-test-flow.js";
|
|
9
10
|
import { Alert, AlertTitle } from "../../../react/ui/alert.js";
|
|
10
11
|
import { useModelProfiles } from "./use-model-profiles.js";
|
|
11
12
|
import { Label } from "../../../react/ui/label.js";
|
|
12
|
-
import { createModelTestFlow } from "../model-test-flow.js";
|
|
13
13
|
import { useEffect, useMemo, useState } from "react";
|
|
14
14
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
15
15
|
//#region src/features/ai/react/SettingsModelProfiles.tsx
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useEditorContext } from "../../../react/context.js";
|
|
2
2
|
import { useStore } from "../../../react/use-store.js";
|
|
3
|
-
import { modelProfiles } from "../model-profiles.js";
|
|
4
3
|
import { useNotifier } from "../../../react/use-notifier.js";
|
|
5
4
|
import { createAiAssistant } from "../assistant.js";
|
|
5
|
+
import { modelProfiles } from "../model-profiles.js";
|
|
6
6
|
import { useEffect, useMemo } from "react";
|
|
7
7
|
//#region src/features/ai/react/use-ai-assistant.ts
|
|
8
8
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEditorContext } from "../../../react/context.js";
|
|
2
2
|
import { useStore } from "../../../react/use-store.js";
|
|
3
|
-
import { modelProfiles } from "../model-profiles.js";
|
|
4
3
|
import { useNotifier } from "../../../react/use-notifier.js";
|
|
4
|
+
import { modelProfiles } from "../model-profiles.js";
|
|
5
5
|
import { createDocumentChat } from "../document-chat-model.js";
|
|
6
6
|
import { useEffect, useMemo } from "react";
|
|
7
7
|
//#region src/features/ai/react/use-document-chat.ts
|