@ecsplico/editor 3.3.0 → 3.5.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 +24 -0
- package/dist/core/abilities.js +1 -0
- package/dist/core/abilities.js.map +1 -1
- package/dist/core/backend-capability.d.ts +1 -1
- package/dist/core/backend-capability.js +2 -1
- package/dist/core/backend-capability.js.map +1 -1
- package/dist/core/backend-types.d.ts +25 -0
- package/dist/core/backend-types.js.map +1 -1
- package/dist/core/backend.d.ts +9 -1
- package/dist/core/backend.js.map +1 -1
- package/dist/core/http-backend.js +17 -0
- package/dist/core/http-backend.js.map +1 -1
- package/dist/core/schema/abilities.d.ts +1 -0
- package/dist/core/schema/abilities.js +4 -0
- package/dist/core/schema/abilities.js.map +1 -1
- package/dist/features/ai/protocol/generate.d.ts +1 -1
- package/dist/features/composites/components/CompositeChrome.js +10 -9
- package/dist/features/composites/components/CompositeChrome.js.map +1 -1
- package/dist/features/composites/kinds.d.ts +4 -0
- package/dist/features/composites/kinds.js.map +1 -1
- package/dist/features/composites/merge.d.ts +1 -1
- package/dist/features/composites/merge.js +5 -2
- package/dist/features/composites/merge.js.map +1 -1
- package/dist/features/composites/nodes.js +1 -0
- package/dist/features/composites/nodes.js.map +1 -1
- package/dist/features/composites/outline.d.ts +9 -2
- package/dist/features/composites/outline.js +41 -3
- package/dist/features/composites/outline.js.map +1 -1
- package/dist/features/composites/react/CompositeChrome.js +11 -9
- package/dist/features/composites/react/CompositeChrome.js.map +1 -1
- package/dist/features/composites/rows.d.ts +2 -0
- package/dist/features/composites/rows.js +1 -0
- package/dist/features/composites/rows.js.map +1 -1
- package/dist/features/image-studio/components/ImageStudioChat.js +121 -0
- package/dist/features/image-studio/components/ImageStudioChat.js.map +1 -0
- package/dist/features/image-studio/components/ImageStudioDialog.js +371 -0
- package/dist/features/image-studio/components/ImageStudioDialog.js.map +1 -0
- package/dist/features/image-studio/components/ImageStudioDialog.vue.d.ts +25 -0
- package/dist/features/image-studio/components/ImageStudioTile.js +74 -0
- package/dist/features/image-studio/components/ImageStudioTile.js.map +1 -0
- package/dist/features/image-studio/components/ImageStudioView.js +117 -0
- package/dist/features/image-studio/components/ImageStudioView.js.map +1 -0
- package/dist/features/image-studio/feature.d.ts +24 -0
- package/dist/features/image-studio/feature.js +53 -0
- package/dist/features/image-studio/feature.js.map +1 -0
- package/dist/features/image-studio/index.d.ts +5 -0
- package/dist/features/image-studio/index.js +4 -0
- package/dist/features/image-studio/node-store.d.ts +27 -0
- package/dist/features/image-studio/node-store.js +64 -0
- package/dist/features/image-studio/node-store.js.map +1 -0
- package/dist/features/image-studio/schema.d.ts +29 -0
- package/dist/features/image-studio/schema.js +86 -0
- package/dist/features/image-studio/schema.js.map +1 -0
- package/dist/features/image-studio/state.d.ts +52 -0
- package/dist/features/image-studio/state.js +147 -0
- package/dist/features/image-studio/state.js.map +1 -0
- package/dist/features/image-studio/store.d.ts +29 -0
- package/dist/features/image-studio/vue.d.ts +17 -0
- package/dist/features/image-studio/vue.js +20 -0
- package/dist/features/image-studio/vue.js.map +1 -0
- package/dist/features/media/schema.d.ts +5 -1
- package/dist/features/media/schema.js +3 -3
- package/dist/features/media/schema.js.map +1 -1
- package/dist/features/transcript/segment-node.js +1 -0
- package/dist/features/transcript/segment-node.js.map +1 -1
- package/dist/i18n/de/image-studio.js +39 -0
- package/dist/i18n/de/image-studio.js.map +1 -0
- package/dist/i18n/de.d.ts +32 -0
- package/dist/i18n/de.js +13 -11
- package/dist/i18n/de.js.map +1 -1
- package/dist/i18n/en/image-studio.js +39 -0
- package/dist/i18n/en/image-studio.js.map +1 -0
- package/dist/i18n/en.js +13 -11
- package/dist/i18n/en.js.map +1 -1
- package/dist/nuxt/module.d.ts +64 -0
- package/dist/server/handlers/images.d.ts +22 -0
- package/dist/server/handlers/images.js +46 -0
- package/dist/server/handlers/images.js.map +1 -0
- package/dist/server/handlers/index.d.ts +2 -1
- package/dist/server/handlers/index.js +2 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/server/ports.d.ts +11 -1
- package/dist/server/ports.js +2 -1
- package/dist/server/ports.js.map +1 -1
- package/dist/server/routes.js +19 -0
- package/dist/server/routes.js.map +1 -1
- package/dist/tree/DocumentTree.js +5 -4
- package/dist/tree/DocumentTree.js.map +1 -1
- package/dist/tree/react/DocumentTree.js +2 -1
- package/dist/tree/react/DocumentTree.js.map +1 -1
- package/dist/tree/tree-items.js +1 -0
- package/dist/tree/tree-items.js.map +1 -1
- package/dist/vue/AdvancedEditor.vue.d.ts +1 -1
- package/package.json +16 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { EMPTY_IMAGE_STUDIO_STATE, addGeneration, addReference, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock } from "./state.js";
|
|
2
|
+
import { imageStudioFeatureCore } from "./feature.js";
|
|
3
|
+
import { createImageStudioNodeStore } from "./node-store.js";
|
|
4
|
+
import _sfc_main from "./components/ImageStudioDialog.js";
|
|
5
|
+
import _sfc_main$1 from "./components/ImageStudioView.js";
|
|
6
|
+
import { VueNodeViewRenderer } from "@tiptap/vue-3";
|
|
7
|
+
//#region src/features/image-studio/vue.ts
|
|
8
|
+
/**
|
|
9
|
+
* The Vue Bild-Studio feature: the `imageStudio` block with its Vue view (picture or description placeholder, opens the
|
|
10
|
+
* studio dialog on double-click and through the block action `image-studio.open`), the `/` entry `bild-studio` and the
|
|
11
|
+
* action. Generation, upload and the Prompt-Bausteine need a backend with the `images` capability; without it the view
|
|
12
|
+
* says that generation is not set up.
|
|
13
|
+
*/
|
|
14
|
+
function imageStudioFeature() {
|
|
15
|
+
return imageStudioFeatureCore({ nodeView: VueNodeViewRenderer(_sfc_main$1) });
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { EMPTY_IMAGE_STUDIO_STATE, _sfc_main as ImageStudioDialog, addGeneration, addReference, createImageStudioNodeStore, imageStudioFeature, isEmptyImageStudioState, normalizeImageStudioState, removeImage, select, selectedImage, setDescription, toggleImage, togglePromptBlock };
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=vue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vue.js","names":["ImageStudioView"],"sources":["../../../src/features/image-studio/vue.ts"],"sourcesContent":["import { VueNodeViewRenderer } from '@tiptap/vue-3'\nimport type { VueFeature } from '../../vue'\nimport ImageStudioView from './components/ImageStudioView.vue'\nimport { imageStudioFeatureCore } from './feature'\n\nexport { default as ImageStudioDialog } from './components/ImageStudioDialog.vue'\nexport { createImageStudioNodeStore, type ImageStudioNodeStore, type ImageStudioNodeStoreOptions } from './node-store'\nexport {\n addGeneration,\n addReference,\n EMPTY_IMAGE_STUDIO_STATE,\n type ImageStudioImage,\n type ImageStudioPromptBlock,\n type ImageStudioState,\n type ImageStudioTurn,\n isEmptyImageStudioState,\n normalizeImageStudioState,\n removeImage,\n select,\n selectedImage,\n setDescription,\n toggleImage,\n togglePromptBlock,\n} from './state'\nexport type { ImageStudioStore } from './store'\n\n/**\n * The Vue Bild-Studio feature: the `imageStudio` block with its Vue view (picture or description placeholder, opens the\n * studio dialog on double-click and through the block action `image-studio.open`), the `/` entry `bild-studio` and the\n * action. Generation, upload and the Prompt-Bausteine need a backend with the `images` capability; without it the view\n * says that generation is not set up.\n */\nexport function imageStudioFeature(): VueFeature {\n return imageStudioFeatureCore({ nodeView: VueNodeViewRenderer(ImageStudioView) })\n}\n"],"mappings":";;;;;;;;;;;;;AAgCA,SAAgB,qBAAiC;CAC/C,OAAO,uBAAuB,EAAE,UAAU,oBAAoBA,WAAe,EAAE,CAAC;AAClF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ASSET_DOCUMENT_ID, ASSET_NAME, AssetManifest, AssetRecord, SCRATCH_DOCUMENT_ID, assetRefOf, assetUrl } from "./assets.js";
|
|
2
|
-
import { AnyExtension, Node } from "@tiptap/core";
|
|
2
|
+
import { AnyExtension, MarkdownTokenizer, Node } from "@tiptap/core";
|
|
3
3
|
//#region src/features/media/schema.d.ts
|
|
4
4
|
/** The media nodes (image, audio, video, file) of the document schema. */
|
|
5
5
|
export declare const mediaSchemaExtensions: () => AnyExtension[];
|
|
@@ -9,6 +9,10 @@ export declare const mediaSchemaExtensions: () => AnyExtension[];
|
|
|
9
9
|
* token), a plain image keeps the stock inline parse.
|
|
10
10
|
*/
|
|
11
11
|
export declare const ImageNode: Node<import("@tiptap/extension-image").ImageOptions, any>;
|
|
12
|
+
/** ` <!-- {"hash":"…"} -->` for the attributes that are set (empty string when none); `>` and the line separators U+2028/U+2029 (which `.` does not match) are escaped so the JSON can neither end the comment nor break the line. Shared with the other block features that keep attributes in a trailing comment. */
|
|
13
|
+
export declare function attrComment(attrs: Record<string, unknown>): string;
|
|
14
|
+
/** Block tokenizer for `[<label>: title](src)` lines of one media node; `labels` are the accepted labels (regex alternatives). */
|
|
15
|
+
export declare function mediaTokenizer(name: string, labels: string): MarkdownTokenizer;
|
|
12
16
|
export type MediaKind = 'image' | 'audio' | 'video' | 'file';
|
|
13
17
|
/** Uploadable MIME types → file extension on disk and media kind. `<uuid>.<ext>` asset names derive from this. */
|
|
14
18
|
export declare const MEDIA_TYPES: Readonly<Record<string, {
|
|
@@ -58,10 +58,10 @@ const ImageNode = Image.extend({
|
|
|
58
58
|
});
|
|
59
59
|
const ATTR_COMMENT_PATTERN = "(?: <!-- (\\{.*?\\}) -->)?[ \\t]*(?:\\n|$)";
|
|
60
60
|
const IMAGE_BLOCK_PATTERN = `^!\\[([^\\]\\n]*)\\]\\(([^)\\s]*)(?: "([^"\\n]*)")?\\) <!-- (\\{.*?\\}) -->[ \\t]*(?:\\n|$)`;
|
|
61
|
-
/** ` <!-- {"hash":"…"} -->` for the attributes that are set (empty string when none); `>`
|
|
61
|
+
/** ` <!-- {"hash":"…"} -->` for the attributes that are set (empty string when none); `>` and the line separators U+2028/U+2029 (which `.` does not match) are escaped so the JSON can neither end the comment nor break the line. Shared with the other block features that keep attributes in a trailing comment. */
|
|
62
62
|
function attrComment(attrs) {
|
|
63
63
|
const set = Object.fromEntries(Object.entries(attrs).filter(([, value]) => value !== null && value !== void 0 && value !== "" && value !== 0));
|
|
64
|
-
return Object.keys(set).length ? ` <!-- ${JSON.stringify(set).replace(
|
|
64
|
+
return Object.keys(set).length ? ` <!-- ${JSON.stringify(set).replace(/[>\u2028\u2029]/g, (char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`)} -->` : "";
|
|
65
65
|
}
|
|
66
66
|
/** The attributes of an `attrComment`; empty for a missing or malformed one. */
|
|
67
67
|
function parseAttrComment(json) {
|
|
@@ -304,6 +304,6 @@ const FileNode = Node.create({
|
|
|
304
304
|
}
|
|
305
305
|
});
|
|
306
306
|
//#endregion
|
|
307
|
-
export { ASSET_DOCUMENT_ID, ASSET_NAME, AudioNode, FileNode, ImageNode, MAX_MEDIA_BYTES, MEDIA_TYPES, SCRATCH_DOCUMENT_ID, VideoNode, acceptFor, assetRefOf, assetUrl, mediaKindOf, mediaMime, mediaSchemaExtensions };
|
|
307
|
+
export { ASSET_DOCUMENT_ID, ASSET_NAME, AudioNode, FileNode, ImageNode, MAX_MEDIA_BYTES, MEDIA_TYPES, SCRATCH_DOCUMENT_ID, VideoNode, acceptFor, assetRefOf, assetUrl, attrComment, mediaKindOf, mediaMime, mediaSchemaExtensions, mediaTokenizer };
|
|
308
308
|
|
|
309
309
|
//# sourceMappingURL=schema.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","names":[],"sources":["../../../src/features/media/schema.ts"],"sourcesContent":["import { type Attributes, type AnyExtension, type MarkdownToken, type MarkdownTokenizer, mergeAttributes, Node } from '@tiptap/core'\nimport { Image } from '@tiptap/extension-image'\nimport { TEXT_ABILITIES } from '../../core/schema'\n\nexport * from './assets'\n\n// Vue-free and server-importable (`@ecsplico/editor/media/schema`): the upload API validates against these types, the\n// schema has the nodes. The client adds the node views in node-views.ts.\n\n/** SHA-256 (hex) of the uploaded bytes, kept on media blocks so derived content can tell when its source changed; null when unknown (memory backend, older documents). */\nconst hashAttribute: Attributes[string] = {\n default: null,\n parseHTML: el => el.getAttribute('data-hash'),\n renderHTML: attrs => (attrs.hash ? { 'data-hash': attrs.hash } : {}),\n}\n\n/** The media nodes (image, audio, video, file) of the document schema. */\nexport const mediaSchemaExtensions = (): AnyExtension[] => [ImageNode, AudioNode, VideoNode, FileNode]\n\n/**\n * The image node with a `hash` attribute. Markdown: ``, followed by ` <!-- {\"hash\":\"…\"} -->` when the\n * block has a hash; only that form is claimed by `imageBlockTokenizer` (a block-level token that still yields an `image`\n * token), a plain image keeps the stock inline parse.\n */\nexport const ImageNode = Image.extend({\n addAttributes() {\n return { ...this.parent?.(), hash: hashAttribute }\n },\n markdownTokenizer: {\n name: 'imageBlock',\n level: 'block',\n start: src => src.search(new RegExp(IMAGE_BLOCK_PATTERN, 'm')),\n tokenize: (src) => {\n const match = new RegExp(IMAGE_BLOCK_PATTERN).exec(src)\n if (!match) return undefined\n return { type: 'image', raw: match[0], text: match[1] ?? '', href: match[2] ?? '', title: match[3], attrs: parseAttrComment(match[4]) }\n },\n },\n parseMarkdown: (token, helpers) => helpers.createNode('image', {\n src: token.href,\n title: token.title,\n alt: token.text,\n hash: typeof token.attrs?.hash === 'string' ? token.attrs.hash : null,\n }),\n renderMarkdown: (node) => {\n const { src = '', alt = '', title = '' } = node.attrs ?? {}\n return `${attrComment({ hash: node.attrs?.hash })}`\n },\n})\n\n// Markdown form of the text-less media blocks. Audio, video and file are a paragraph of nothing but a link\n// (`[Audio: name](url)`), optionally followed by an HTML comment holding the attributes the link cannot carry\n// (`hash`, `mimeType`, `size`) as JSON, the way composite fences hold their meta. The block-level tokenizers below parse\n// that line back into the node; labels of every UI language are accepted, the export always writes the German one.\nconst ATTR_COMMENT_PATTERN = '(?: <!-- (\\\\{.*?\\\\}) -->)?[ \\\\t]*(?:\\\\n|$)'\nconst IMAGE_BLOCK_PATTERN = `^!\\\\[([^\\\\]\\\\n]*)\\\\]\\\\(([^)\\\\s]*)(?: \"([^\"\\\\n]*)\")?\\\\) <!-- (\\\\{.*?\\\\}) -->[ \\\\t]*(?:\\\\n|$)`\n\n/** ` <!-- {\"hash\":\"…\"} -->` for the attributes that are set (empty string when none); `>` is escaped so the JSON cannot end the comment. */\nfunction attrComment(attrs: Record<string, unknown>): string {\n const set = Object.fromEntries(Object.entries(attrs).filter(([, value]) => value !== null && value !== undefined && value !== '' && value !== 0))\n return Object.keys(set).length ? ` <!-- ${JSON.stringify(set).replace(/>/g, '\\\\u003e')} -->` : ''\n}\n\n/** The attributes of an `attrComment`; empty for a missing or malformed one. */\nfunction parseAttrComment(json: string | undefined): Record<string, unknown> {\n if (!json) return {}\n try {\n const value: unknown = JSON.parse(json)\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n }\n catch {\n return {}\n }\n}\n\n/** Block tokenizer for `[<label>: title](src)` lines of one media node; `labels` are the accepted labels (regex alternatives). */\nfunction mediaTokenizer(name: string, labels: string): MarkdownTokenizer {\n const pattern = `^\\\\[(?:${labels})(?:: ((?:\\\\\\\\.|[^\\\\]\\\\\\\\\\\\n])*))?\\\\]\\\\(([^)\\\\s]*)\\\\)${ATTR_COMMENT_PATTERN}`\n return {\n name,\n level: 'block',\n start: src => src.search(new RegExp(pattern, 'm')),\n tokenize: (src) => {\n const match = new RegExp(pattern).exec(src)\n if (!match) return undefined\n return { type: name, raw: match[0], title: (match[1] ?? '').replace(/\\\\(.)/g, '$1'), href: match[2] ?? '', attrs: parseAttrComment(match[3]) }\n },\n }\n}\n\nconst stringAttr = (value: unknown): string => typeof value === 'string' ? value : ''\n\nexport type MediaKind = 'image' | 'audio' | 'video' | 'file'\n\n/** Uploadable MIME types → file extension on disk and media kind. `<uuid>.<ext>` asset names derive from this. */\nexport const MEDIA_TYPES: Readonly<Record<string, { ext: string, kind: MediaKind }>> = {\n 'image/png': { ext: 'png', kind: 'image' },\n 'image/jpeg': { ext: 'jpg', kind: 'image' },\n 'image/gif': { ext: 'gif', kind: 'image' },\n 'image/webp': { ext: 'webp', kind: 'image' },\n 'audio/webm': { ext: 'weba', kind: 'audio' },\n 'audio/mp4': { ext: 'm4a', kind: 'audio' },\n // What Chrome on macOS reports for `.m4a` files picked from disk.\n 'audio/x-m4a': { ext: 'm4a', kind: 'audio' },\n 'audio/mpeg': { ext: 'mp3', kind: 'audio' },\n 'audio/wav': { ext: 'wav', kind: 'audio' },\n 'audio/x-wav': { ext: 'wav', kind: 'audio' },\n 'audio/ogg': { ext: 'ogg', kind: 'audio' },\n 'video/mp4': { ext: 'mp4', kind: 'video' },\n 'video/webm': { ext: 'webm', kind: 'video' },\n 'video/quicktime': { ext: 'mov', kind: 'video' },\n}\n\n/** Upload size limit per kind. */\nexport const MAX_MEDIA_BYTES: Readonly<Record<MediaKind, number>> = {\n image: 5 * 1024 * 1024,\n audio: 50 * 1024 * 1024,\n video: 200 * 1024 * 1024,\n // Generic files (PDF, office documents, …): anything the media kinds do not cover.\n file: 50 * 1024 * 1024,\n}\n\n/** `type` without parameters, lowercased (`audio/webm;codecs=opus` → `audio/webm`): the key into `MEDIA_TYPES`. */\nexport const mediaMime = (type: string): string => (type.split(';')[0] ?? '').trim().toLowerCase()\n\n/** Kind of an uploadable MIME type (parameters such as `;codecs=…` ignored); null when uploads do not accept it. */\nexport function mediaKindOf(mime: string): MediaKind | null {\n return MEDIA_TYPES[mediaMime(mime)]?.kind ?? null\n}\n\n/** `accept` value of a file input for one kind: its MIME types, comma-joined. */\nexport function acceptFor(kind: MediaKind): string {\n return Object.keys(MEDIA_TYPES).filter(mime => MEDIA_TYPES[mime]?.kind === kind).join(',')\n}\n\n/** Attributes of an audio/video node; `title` is the editable caption, `mimeType` the uploaded file's base type. */\nexport interface MediaAttrs {\n src: string\n title?: string\n mimeType?: string\n hash?: string | null\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n audio: {\n /** Inserts an audio block at the selection. */\n insertAudio: (attrs: MediaAttrs) => ReturnType\n }\n video: {\n /** Inserts a video block at the selection. */\n insertVideo: (attrs: MediaAttrs) => ReturnType\n }\n }\n}\n\nconst mediaAttributes: Attributes = {\n src: { default: '' },\n title: {\n default: '',\n parseHTML: el => el.getAttribute('data-title') ?? '',\n renderHTML: attrs => ({ 'data-title': attrs.title }),\n },\n mimeType: {\n default: '',\n parseHTML: el => el.getAttribute('data-mime') ?? '',\n renderHTML: attrs => ({ 'data-mime': attrs.mimeType }),\n },\n hash: hashAttribute,\n}\n\n/** Markdown export (AI context, `getMarkdown()`, source mode): a link labelled with the kind and, when there is one, the title; `extra` and the hash go into the trailing attribute comment. */\nfunction mediaMarkdown(label: string, attrs: Record<string, unknown> | undefined, extra: Record<string, unknown>): string {\n const title = attrs?.title\n const text = title ? `${label}: ${String(title).replace(/[[\\]\\\\]/g, '\\\\$&')}` : label\n return `[${text}](${String(attrs?.src ?? '')})${attrComment({ ...extra, hash: attrs?.hash })}`\n}\n\n/** Node attributes of an audio/video Markdown token. */\nfunction mediaAttrsOfToken(token: MarkdownToken) {\n return { src: token.href, title: token.title, mimeType: stringAttr(token.attrs?.mimeType), hash: stringAttr(token.attrs?.hash) || null }\n}\n\nexport const AudioNode = Node.create({\n name: 'audio',\n group: 'block',\n atom: true,\n draggable: true,\n // A player block holds no text: nothing text-related applies inside it.\n abilities: { revokes: TEXT_ABILITIES },\n addAttributes: () => mediaAttributes,\n parseHTML: () => [{ tag: 'audio[src]' }],\n renderHTML: ({ HTMLAttributes }) => ['audio', mergeAttributes(HTMLAttributes, { controls: 'true', preload: 'metadata' })],\n markdownTokenizer: mediaTokenizer('audio', 'Audio'),\n parseMarkdown: (token, helpers) => helpers.createNode('audio', mediaAttrsOfToken(token)),\n renderMarkdown: node => mediaMarkdown('Audio', node.attrs, { mimeType: node.attrs?.mimeType }),\n addCommands() {\n return {\n insertAudio: attrs => ({ commands }) => commands.insertContent({ type: this.name, attrs }),\n }\n },\n})\n\nexport const VideoNode = Node.create({\n name: 'video',\n group: 'block',\n atom: true,\n draggable: true,\n abilities: { revokes: TEXT_ABILITIES },\n addAttributes: () => mediaAttributes,\n parseHTML: () => [{ tag: 'video[src]' }],\n renderHTML: ({ HTMLAttributes }) => ['video', mergeAttributes(HTMLAttributes, { controls: 'true', preload: 'metadata' })],\n markdownTokenizer: mediaTokenizer('video', 'Video'),\n parseMarkdown: (token, helpers) => helpers.createNode('video', mediaAttrsOfToken(token)),\n renderMarkdown: node => mediaMarkdown('Video', node.attrs, { mimeType: node.attrs?.mimeType }),\n addCommands() {\n return {\n insertVideo: attrs => ({ commands }) => commands.insertContent({ type: this.name, attrs }),\n }\n },\n})\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n file: {\n /** Inserts a file block (download link, PDF preview) at the selection. */\n insertFile: (attrs: FileAttrs) => ReturnType\n }\n }\n}\n\n/** Attributes of a file block; `title` is the editable file name, `size` the byte count. */\nexport interface FileAttrs {\n src: string\n title?: string\n mimeType?: string\n size?: number\n hash?: string | null\n}\n\n/** A generic uploaded file (PDF, office document, …) that has no player: shown as a card with an open button. */\nexport const FileNode = Node.create({\n name: 'file',\n group: 'block',\n atom: true,\n draggable: true,\n abilities: { revokes: TEXT_ABILITIES },\n addAttributes: () => ({\n src: { default: '', parseHTML: el => el.getAttribute('href') ?? '' },\n title: { default: '', parseHTML: el => el.getAttribute('download') ?? '' },\n mimeType: { default: '', parseHTML: el => el.getAttribute('data-mime') ?? '' },\n size: { default: 0, parseHTML: el => Number(el.getAttribute('data-size')) || 0 },\n hash: hashAttribute,\n }),\n parseHTML: () => [{ tag: 'a[data-type=\"file\"]' }],\n renderHTML: ({ node }) => ['a', {\n 'data-type': 'file',\n 'href': node.attrs.src,\n 'download': node.attrs.title,\n 'data-mime': node.attrs.mimeType,\n 'data-size': String(node.attrs.size),\n ...(node.attrs.hash ? { 'data-hash': node.attrs.hash } : {}),\n }, node.attrs.title || 'Datei'],\n markdownTokenizer: mediaTokenizer('file', 'Datei|File'),\n parseMarkdown: (token, helpers) => helpers.createNode('file', {\n src: token.href,\n title: token.title,\n mimeType: stringAttr(token.attrs?.mimeType),\n size: typeof token.attrs?.size === 'number' ? token.attrs.size : 0,\n hash: stringAttr(token.attrs?.hash) || null,\n }),\n renderMarkdown: node => mediaMarkdown('Datei', node.attrs, { mimeType: node.attrs?.mimeType, size: node.attrs?.size }),\n addCommands() {\n return {\n insertFile: attrs => ({ commands }) => commands.insertContent({ type: this.name, attrs }),\n }\n },\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"schema.js","names":[],"sources":["../../../src/features/media/schema.ts"],"sourcesContent":["import { type Attributes, type AnyExtension, type MarkdownToken, type MarkdownTokenizer, mergeAttributes, Node } from '@tiptap/core'\nimport { Image } from '@tiptap/extension-image'\nimport { TEXT_ABILITIES } from '../../core/schema'\n\nexport * from './assets'\n\n// Vue-free and server-importable (`@ecsplico/editor/media/schema`): the upload API validates against these types, the\n// schema has the nodes. The client adds the node views in node-views.ts.\n\n/** SHA-256 (hex) of the uploaded bytes, kept on media blocks so derived content can tell when its source changed; null when unknown (memory backend, older documents). */\nconst hashAttribute: Attributes[string] = {\n default: null,\n parseHTML: el => el.getAttribute('data-hash'),\n renderHTML: attrs => (attrs.hash ? { 'data-hash': attrs.hash } : {}),\n}\n\n/** The media nodes (image, audio, video, file) of the document schema. */\nexport const mediaSchemaExtensions = (): AnyExtension[] => [ImageNode, AudioNode, VideoNode, FileNode]\n\n/**\n * The image node with a `hash` attribute. Markdown: ``, followed by ` <!-- {\"hash\":\"…\"} -->` when the\n * block has a hash; only that form is claimed by `imageBlockTokenizer` (a block-level token that still yields an `image`\n * token), a plain image keeps the stock inline parse.\n */\nexport const ImageNode = Image.extend({\n addAttributes() {\n return { ...this.parent?.(), hash: hashAttribute }\n },\n markdownTokenizer: {\n name: 'imageBlock',\n level: 'block',\n start: src => src.search(new RegExp(IMAGE_BLOCK_PATTERN, 'm')),\n tokenize: (src) => {\n const match = new RegExp(IMAGE_BLOCK_PATTERN).exec(src)\n if (!match) return undefined\n return { type: 'image', raw: match[0], text: match[1] ?? '', href: match[2] ?? '', title: match[3], attrs: parseAttrComment(match[4]) }\n },\n },\n parseMarkdown: (token, helpers) => helpers.createNode('image', {\n src: token.href,\n title: token.title,\n alt: token.text,\n hash: typeof token.attrs?.hash === 'string' ? token.attrs.hash : null,\n }),\n renderMarkdown: (node) => {\n const { src = '', alt = '', title = '' } = node.attrs ?? {}\n return `${attrComment({ hash: node.attrs?.hash })}`\n },\n})\n\n// Markdown form of the text-less media blocks. Audio, video and file are a paragraph of nothing but a link\n// (`[Audio: name](url)`), optionally followed by an HTML comment holding the attributes the link cannot carry\n// (`hash`, `mimeType`, `size`) as JSON, the way composite fences hold their meta. The block-level tokenizers below parse\n// that line back into the node; labels of every UI language are accepted, the export always writes the German one.\nconst ATTR_COMMENT_PATTERN = '(?: <!-- (\\\\{.*?\\\\}) -->)?[ \\\\t]*(?:\\\\n|$)'\nconst IMAGE_BLOCK_PATTERN = `^!\\\\[([^\\\\]\\\\n]*)\\\\]\\\\(([^)\\\\s]*)(?: \"([^\"\\\\n]*)\")?\\\\) <!-- (\\\\{.*?\\\\}) -->[ \\\\t]*(?:\\\\n|$)`\n\n/** ` <!-- {\"hash\":\"…\"} -->` for the attributes that are set (empty string when none); `>` and the line separators U+2028/U+2029 (which `.` does not match) are escaped so the JSON can neither end the comment nor break the line. Shared with the other block features that keep attributes in a trailing comment. */\nexport function attrComment(attrs: Record<string, unknown>): string {\n const set = Object.fromEntries(Object.entries(attrs).filter(([, value]) => value !== null && value !== undefined && value !== '' && value !== 0))\n return Object.keys(set).length ? ` <!-- ${JSON.stringify(set).replace(/[>\\u2028\\u2029]/g, char => `\\\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`)} -->` : ''\n}\n\n/** The attributes of an `attrComment`; empty for a missing or malformed one. */\nfunction parseAttrComment(json: string | undefined): Record<string, unknown> {\n if (!json) return {}\n try {\n const value: unknown = JSON.parse(json)\n return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : {}\n }\n catch {\n return {}\n }\n}\n\n/** Block tokenizer for `[<label>: title](src)` lines of one media node; `labels` are the accepted labels (regex alternatives). */\nexport function mediaTokenizer(name: string, labels: string): MarkdownTokenizer {\n const pattern = `^\\\\[(?:${labels})(?:: ((?:\\\\\\\\.|[^\\\\]\\\\\\\\\\\\n])*))?\\\\]\\\\(([^)\\\\s]*)\\\\)${ATTR_COMMENT_PATTERN}`\n return {\n name,\n level: 'block',\n start: src => src.search(new RegExp(pattern, 'm')),\n tokenize: (src) => {\n const match = new RegExp(pattern).exec(src)\n if (!match) return undefined\n return { type: name, raw: match[0], title: (match[1] ?? '').replace(/\\\\(.)/g, '$1'), href: match[2] ?? '', attrs: parseAttrComment(match[3]) }\n },\n }\n}\n\nconst stringAttr = (value: unknown): string => typeof value === 'string' ? value : ''\n\nexport type MediaKind = 'image' | 'audio' | 'video' | 'file'\n\n/** Uploadable MIME types → file extension on disk and media kind. `<uuid>.<ext>` asset names derive from this. */\nexport const MEDIA_TYPES: Readonly<Record<string, { ext: string, kind: MediaKind }>> = {\n 'image/png': { ext: 'png', kind: 'image' },\n 'image/jpeg': { ext: 'jpg', kind: 'image' },\n 'image/gif': { ext: 'gif', kind: 'image' },\n 'image/webp': { ext: 'webp', kind: 'image' },\n 'audio/webm': { ext: 'weba', kind: 'audio' },\n 'audio/mp4': { ext: 'm4a', kind: 'audio' },\n // What Chrome on macOS reports for `.m4a` files picked from disk.\n 'audio/x-m4a': { ext: 'm4a', kind: 'audio' },\n 'audio/mpeg': { ext: 'mp3', kind: 'audio' },\n 'audio/wav': { ext: 'wav', kind: 'audio' },\n 'audio/x-wav': { ext: 'wav', kind: 'audio' },\n 'audio/ogg': { ext: 'ogg', kind: 'audio' },\n 'video/mp4': { ext: 'mp4', kind: 'video' },\n 'video/webm': { ext: 'webm', kind: 'video' },\n 'video/quicktime': { ext: 'mov', kind: 'video' },\n}\n\n/** Upload size limit per kind. */\nexport const MAX_MEDIA_BYTES: Readonly<Record<MediaKind, number>> = {\n image: 5 * 1024 * 1024,\n audio: 50 * 1024 * 1024,\n video: 200 * 1024 * 1024,\n // Generic files (PDF, office documents, …): anything the media kinds do not cover.\n file: 50 * 1024 * 1024,\n}\n\n/** `type` without parameters, lowercased (`audio/webm;codecs=opus` → `audio/webm`): the key into `MEDIA_TYPES`. */\nexport const mediaMime = (type: string): string => (type.split(';')[0] ?? '').trim().toLowerCase()\n\n/** Kind of an uploadable MIME type (parameters such as `;codecs=…` ignored); null when uploads do not accept it. */\nexport function mediaKindOf(mime: string): MediaKind | null {\n return MEDIA_TYPES[mediaMime(mime)]?.kind ?? null\n}\n\n/** `accept` value of a file input for one kind: its MIME types, comma-joined. */\nexport function acceptFor(kind: MediaKind): string {\n return Object.keys(MEDIA_TYPES).filter(mime => MEDIA_TYPES[mime]?.kind === kind).join(',')\n}\n\n/** Attributes of an audio/video node; `title` is the editable caption, `mimeType` the uploaded file's base type. */\nexport interface MediaAttrs {\n src: string\n title?: string\n mimeType?: string\n hash?: string | null\n}\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n audio: {\n /** Inserts an audio block at the selection. */\n insertAudio: (attrs: MediaAttrs) => ReturnType\n }\n video: {\n /** Inserts a video block at the selection. */\n insertVideo: (attrs: MediaAttrs) => ReturnType\n }\n }\n}\n\nconst mediaAttributes: Attributes = {\n src: { default: '' },\n title: {\n default: '',\n parseHTML: el => el.getAttribute('data-title') ?? '',\n renderHTML: attrs => ({ 'data-title': attrs.title }),\n },\n mimeType: {\n default: '',\n parseHTML: el => el.getAttribute('data-mime') ?? '',\n renderHTML: attrs => ({ 'data-mime': attrs.mimeType }),\n },\n hash: hashAttribute,\n}\n\n/** Markdown export (AI context, `getMarkdown()`, source mode): a link labelled with the kind and, when there is one, the title; `extra` and the hash go into the trailing attribute comment. */\nfunction mediaMarkdown(label: string, attrs: Record<string, unknown> | undefined, extra: Record<string, unknown>): string {\n const title = attrs?.title\n const text = title ? `${label}: ${String(title).replace(/[[\\]\\\\]/g, '\\\\$&')}` : label\n return `[${text}](${String(attrs?.src ?? '')})${attrComment({ ...extra, hash: attrs?.hash })}`\n}\n\n/** Node attributes of an audio/video Markdown token. */\nfunction mediaAttrsOfToken(token: MarkdownToken) {\n return { src: token.href, title: token.title, mimeType: stringAttr(token.attrs?.mimeType), hash: stringAttr(token.attrs?.hash) || null }\n}\n\nexport const AudioNode = Node.create({\n name: 'audio',\n group: 'block',\n atom: true,\n draggable: true,\n // A player block holds no text: nothing text-related applies inside it.\n abilities: { revokes: TEXT_ABILITIES },\n addAttributes: () => mediaAttributes,\n parseHTML: () => [{ tag: 'audio[src]' }],\n renderHTML: ({ HTMLAttributes }) => ['audio', mergeAttributes(HTMLAttributes, { controls: 'true', preload: 'metadata' })],\n markdownTokenizer: mediaTokenizer('audio', 'Audio'),\n parseMarkdown: (token, helpers) => helpers.createNode('audio', mediaAttrsOfToken(token)),\n renderMarkdown: node => mediaMarkdown('Audio', node.attrs, { mimeType: node.attrs?.mimeType }),\n addCommands() {\n return {\n insertAudio: attrs => ({ commands }) => commands.insertContent({ type: this.name, attrs }),\n }\n },\n})\n\nexport const VideoNode = Node.create({\n name: 'video',\n group: 'block',\n atom: true,\n draggable: true,\n abilities: { revokes: TEXT_ABILITIES },\n addAttributes: () => mediaAttributes,\n parseHTML: () => [{ tag: 'video[src]' }],\n renderHTML: ({ HTMLAttributes }) => ['video', mergeAttributes(HTMLAttributes, { controls: 'true', preload: 'metadata' })],\n markdownTokenizer: mediaTokenizer('video', 'Video'),\n parseMarkdown: (token, helpers) => helpers.createNode('video', mediaAttrsOfToken(token)),\n renderMarkdown: node => mediaMarkdown('Video', node.attrs, { mimeType: node.attrs?.mimeType }),\n addCommands() {\n return {\n insertVideo: attrs => ({ commands }) => commands.insertContent({ type: this.name, attrs }),\n }\n },\n})\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n file: {\n /** Inserts a file block (download link, PDF preview) at the selection. */\n insertFile: (attrs: FileAttrs) => ReturnType\n }\n }\n}\n\n/** Attributes of a file block; `title` is the editable file name, `size` the byte count. */\nexport interface FileAttrs {\n src: string\n title?: string\n mimeType?: string\n size?: number\n hash?: string | null\n}\n\n/** A generic uploaded file (PDF, office document, …) that has no player: shown as a card with an open button. */\nexport const FileNode = Node.create({\n name: 'file',\n group: 'block',\n atom: true,\n draggable: true,\n abilities: { revokes: TEXT_ABILITIES },\n addAttributes: () => ({\n src: { default: '', parseHTML: el => el.getAttribute('href') ?? '' },\n title: { default: '', parseHTML: el => el.getAttribute('download') ?? '' },\n mimeType: { default: '', parseHTML: el => el.getAttribute('data-mime') ?? '' },\n size: { default: 0, parseHTML: el => Number(el.getAttribute('data-size')) || 0 },\n hash: hashAttribute,\n }),\n parseHTML: () => [{ tag: 'a[data-type=\"file\"]' }],\n renderHTML: ({ node }) => ['a', {\n 'data-type': 'file',\n 'href': node.attrs.src,\n 'download': node.attrs.title,\n 'data-mime': node.attrs.mimeType,\n 'data-size': String(node.attrs.size),\n ...(node.attrs.hash ? { 'data-hash': node.attrs.hash } : {}),\n }, node.attrs.title || 'Datei'],\n markdownTokenizer: mediaTokenizer('file', 'Datei|File'),\n parseMarkdown: (token, helpers) => helpers.createNode('file', {\n src: token.href,\n title: token.title,\n mimeType: stringAttr(token.attrs?.mimeType),\n size: typeof token.attrs?.size === 'number' ? token.attrs.size : 0,\n hash: stringAttr(token.attrs?.hash) || null,\n }),\n renderMarkdown: node => mediaMarkdown('Datei', node.attrs, { mimeType: node.attrs?.mimeType, size: node.attrs?.size }),\n addCommands() {\n return {\n insertFile: attrs => ({ commands }) => commands.insertContent({ type: this.name, attrs }),\n }\n },\n})\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"segment-node.js","names":[],"sources":["../../../src/features/transcript/segment-node.ts"],"sourcesContent":["import { type JSONContent, mergeAttributes, Node } from '@tiptap/core'\nimport { canSplit } from '@tiptap/pm/transform'\nimport { ABILITY } from '../../core/schema'\nimport { lastSpeaker } from './speakers'\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n transcriptSegment: {\n /** Turn the current block into a speaker segment (or back into a paragraph when it already is one). */\n toggleTranscriptSegment: (attrs?: { speaker?: string, start?: number | null, end?: number | null }) => ReturnType\n }\n }\n}\n\n/** Markdown label for a segment without speaker; read back as an empty speaker so export → import round-trips. */\nconst UNNAMED_SPEAKER = 'Sprecher'\n/** Longer bold paragraph openers („**Bitte beachten Sie Folgendes:** …“) are not treated as speaker labels. */\nconst MAX_SPEAKER_LENGTH = 80\n\n/** A `data-start`/`data-end` attribute as seconds; null when missing or not a non-negative number. */\nfunction timeAttr(value: string | null): number | null {\n if (value === null || value.trim() === '') return null\n const seconds = Number(value)\n return Number.isFinite(seconds) && seconds >= 0 ? seconds : null\n}\n\n/** `mm:ss` of a segment time in seconds (minutes keep counting past 59: `75:03`); '' for null / not a number. */\nexport function formatSegmentTime(seconds: unknown): string {\n if (typeof seconds !== 'number' || !Number.isFinite(seconds) || seconds < 0) return ''\n const whole = Math.floor(seconds)\n return `${String(Math.floor(whole / 60)).padStart(2, '0')}:${String(whole % 60).padStart(2, '0')}`\n}\n\n// Schema-only node (no node view): shared by the client editor and the server-side Yjs seeding.\n// The client adds its node view in node-view.ts.\nexport const TranscriptSegmentNode = Node.create({\n name: 'transcriptSegment',\n // Above StarterKit's default (100) so this Enter / Mod-Shift-s keymap runs first,\n // below Paragraph (1000) so the paragraph stays the schema's default block.\n priority: 101,\n group: 'block',\n content: 'inline*',\n // A speaker turn is plain running text: no structural blocks or media inside it.\n abilities: {\n revokes: [ABILITY.headings, ABILITY.lists, ABILITY.blockquote, ABILITY.codeBlock, ABILITY.blockContent, ABILITY.composite, ABILITY.media, ABILITY.record],\n },\n defining: true,\n addAttributes: () => ({\n speaker: {\n default: '',\n parseHTML: (el) => el.getAttribute('data-speaker') ?? '',\n renderHTML: (attrs) => ({ 'data-speaker': attrs.speaker }),\n },\n // Seconds into the recording of the enclosing source-holding Baustein; null for a segment without timing.\n start: {\n default: null,\n parseHTML: el => timeAttr(el.getAttribute('data-start')),\n renderHTML: attrs => (attrs.start == null ? {} : { 'data-start': attrs.start }),\n },\n end: {\n default: null,\n parseHTML: el => timeAttr(el.getAttribute('data-end')),\n renderHTML: attrs => (attrs.end == null ? {} : { 'data-end': attrs.end }),\n },\n }),\n parseHTML: () => [{ tag: 'div[data-type=\"transcript-segment\"]' }],\n renderHTML: ({ HTMLAttributes }) => ['div', mergeAttributes(HTMLAttributes, { 'data-type': 'transcript-segment' }), 0],\n // Markdown export (editor.getMarkdown(), AI context/selection) keeps the speaker visible to the model as\n // `**Name:** text`; `start`/`end` (seconds into the source recording) live in JSON/Yjs only and are not part of the\n // Markdown, so the Markdown output of a segment is the same with or without timestamps.\n // Import goes through speakerParagraphsToSegments() below, not parseMarkdown: marked yields\n // an ordinary paragraph token, and Paragraph (priority 1000) would always win that token over this node.\n // Backslash-escaped ASCII punctuation parses back to the literal character, so odd names survive the round-trip.\n // No trailing newline: the serializer separates blocks itself; extra blank lines re-import as empty paragraphs.\n renderMarkdown: (node, h) => `**${String(node.attrs?.speaker || UNNAMED_SPEAKER).replace(/[\\\\*_`[\\]<>~]/g, '\\\\$&')}:** ${h.renderChildren(node.content ?? [])}`,\n addCommands() {\n return {\n toggleTranscriptSegment: (attrs = {}) => ({ editor, commands }) =>\n editor.isActive(this.name)\n ? commands.setNode('paragraph')\n : commands.setNode(this.name, { speaker: attrs.speaker ?? lastSpeaker(editor.state.doc), start: attrs.start ?? null, end: attrs.end ?? null }),\n }\n },\n addKeyboardShortcuts() {\n return {\n // Enter inside a segment splits it into two segments with the same speaker — at the start, middle\n // or end alike. `splitBlock` is not used: it creates the default block (paragraph) at the end and\n // re-types the first half at the start. Shift-Enter keeps StarterKit's hard break.\n Enter: () => {\n const { $from } = this.editor.state.selection\n if ($from.parent.type.name !== this.name) return false\n const type = $from.parent.type\n const speaker = String($from.parent.attrs.speaker ?? '')\n return this.editor.commands.command(({ tr, state, dispatch }) => {\n const marks = state.storedMarks ?? ($from.parentOffset ? $from.marks() : null)\n const after = [{ type, attrs: { speaker } }]\n if (!canSplit(state.doc, state.selection.from, 1, after)) return false\n if (dispatch) {\n if (!tr.selection.empty) tr.deleteSelection()\n tr.split(tr.selection.from, 1, after)\n if (marks) tr.ensureMarks(marks)\n tr.scrollIntoView()\n }\n return true\n })\n },\n 'Mod-Shift-s': () => this.editor.commands.toggleTranscriptSegment(),\n }\n },\n})\n\n/**\n * Speaker name of a speaker line, i.e. a paragraph whose first inline node is bold-only text `Name:`\n * (Markdown `**Name:** text`), or null. Bold elsewhere in the paragraph does not count.\n */\nfunction speakerOf(block: JSONContent): string | null {\n const first = block.content?.[0]\n if (block.type !== 'paragraph' || first?.type !== 'text' || !first.text?.endsWith(':')) return null\n if (first.marks?.length !== 1 || first.marks[0]!.type !== 'bold') return null\n const name = first.text.slice(0, -1).trim()\n if (!name || name.length > MAX_SPEAKER_LENGTH || name.includes('\\n')) return null\n return name\n}\n\n/**\n * Turns top-level speaker-line paragraphs of parsed Markdown into transcriptSegment nodes (the inverse of\n * renderMarkdown above); every other block is returned unchanged. Applied to AI results on both apply paths.\n * A label without text (`**Aufgaben:**` above a list) is a bold pseudo-heading, not an empty turn, and stays a paragraph.\n */\nexport function speakerParagraphsToSegments(doc: JSONContent): JSONContent {\n if (!doc.content) return doc\n return {\n ...doc,\n content: doc.content.map((block) => {\n const name = speakerOf(block)\n if (name === null) return block\n const content = block.content!.slice(1)\n if (content.every(node => node.type === 'text' && !node.text?.trim())) return block\n const next = content[0]\n // renderMarkdown puts exactly one space between label and text; drop only that one.\n if (next?.type === 'text' && next.text && /^\\s/.test(next.text)) {\n if (next.text.length === 1) content.shift()\n else content[0] = { ...next, text: next.text.slice(1) }\n }\n return { type: 'transcriptSegment', attrs: { speaker: name === UNNAMED_SPEAKER ? '' : name }, content }\n }),\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"segment-node.js","names":[],"sources":["../../../src/features/transcript/segment-node.ts"],"sourcesContent":["import { type JSONContent, mergeAttributes, Node } from '@tiptap/core'\nimport { canSplit } from '@tiptap/pm/transform'\nimport { ABILITY } from '../../core/schema'\nimport { lastSpeaker } from './speakers'\n\ndeclare module '@tiptap/core' {\n interface Commands<ReturnType> {\n transcriptSegment: {\n /** Turn the current block into a speaker segment (or back into a paragraph when it already is one). */\n toggleTranscriptSegment: (attrs?: { speaker?: string, start?: number | null, end?: number | null }) => ReturnType\n }\n }\n}\n\n/** Markdown label for a segment without speaker; read back as an empty speaker so export → import round-trips. */\nconst UNNAMED_SPEAKER = 'Sprecher'\n/** Longer bold paragraph openers („**Bitte beachten Sie Folgendes:** …“) are not treated as speaker labels. */\nconst MAX_SPEAKER_LENGTH = 80\n\n/** A `data-start`/`data-end` attribute as seconds; null when missing or not a non-negative number. */\nfunction timeAttr(value: string | null): number | null {\n if (value === null || value.trim() === '') return null\n const seconds = Number(value)\n return Number.isFinite(seconds) && seconds >= 0 ? seconds : null\n}\n\n/** `mm:ss` of a segment time in seconds (minutes keep counting past 59: `75:03`); '' for null / not a number. */\nexport function formatSegmentTime(seconds: unknown): string {\n if (typeof seconds !== 'number' || !Number.isFinite(seconds) || seconds < 0) return ''\n const whole = Math.floor(seconds)\n return `${String(Math.floor(whole / 60)).padStart(2, '0')}:${String(whole % 60).padStart(2, '0')}`\n}\n\n// Schema-only node (no node view): shared by the client editor and the server-side Yjs seeding.\n// The client adds its node view in node-view.ts.\nexport const TranscriptSegmentNode = Node.create({\n name: 'transcriptSegment',\n // Above StarterKit's default (100) so this Enter / Mod-Shift-s keymap runs first,\n // below Paragraph (1000) so the paragraph stays the schema's default block.\n priority: 101,\n group: 'block',\n content: 'inline*',\n // A speaker turn is plain running text: no structural blocks or media inside it.\n abilities: {\n revokes: [ABILITY.headings, ABILITY.lists, ABILITY.blockquote, ABILITY.codeBlock, ABILITY.blockContent, ABILITY.composite, ABILITY.media, ABILITY.imageStudio, ABILITY.record],\n },\n defining: true,\n addAttributes: () => ({\n speaker: {\n default: '',\n parseHTML: (el) => el.getAttribute('data-speaker') ?? '',\n renderHTML: (attrs) => ({ 'data-speaker': attrs.speaker }),\n },\n // Seconds into the recording of the enclosing source-holding Baustein; null for a segment without timing.\n start: {\n default: null,\n parseHTML: el => timeAttr(el.getAttribute('data-start')),\n renderHTML: attrs => (attrs.start == null ? {} : { 'data-start': attrs.start }),\n },\n end: {\n default: null,\n parseHTML: el => timeAttr(el.getAttribute('data-end')),\n renderHTML: attrs => (attrs.end == null ? {} : { 'data-end': attrs.end }),\n },\n }),\n parseHTML: () => [{ tag: 'div[data-type=\"transcript-segment\"]' }],\n renderHTML: ({ HTMLAttributes }) => ['div', mergeAttributes(HTMLAttributes, { 'data-type': 'transcript-segment' }), 0],\n // Markdown export (editor.getMarkdown(), AI context/selection) keeps the speaker visible to the model as\n // `**Name:** text`; `start`/`end` (seconds into the source recording) live in JSON/Yjs only and are not part of the\n // Markdown, so the Markdown output of a segment is the same with or without timestamps.\n // Import goes through speakerParagraphsToSegments() below, not parseMarkdown: marked yields\n // an ordinary paragraph token, and Paragraph (priority 1000) would always win that token over this node.\n // Backslash-escaped ASCII punctuation parses back to the literal character, so odd names survive the round-trip.\n // No trailing newline: the serializer separates blocks itself; extra blank lines re-import as empty paragraphs.\n renderMarkdown: (node, h) => `**${String(node.attrs?.speaker || UNNAMED_SPEAKER).replace(/[\\\\*_`[\\]<>~]/g, '\\\\$&')}:** ${h.renderChildren(node.content ?? [])}`,\n addCommands() {\n return {\n toggleTranscriptSegment: (attrs = {}) => ({ editor, commands }) =>\n editor.isActive(this.name)\n ? commands.setNode('paragraph')\n : commands.setNode(this.name, { speaker: attrs.speaker ?? lastSpeaker(editor.state.doc), start: attrs.start ?? null, end: attrs.end ?? null }),\n }\n },\n addKeyboardShortcuts() {\n return {\n // Enter inside a segment splits it into two segments with the same speaker — at the start, middle\n // or end alike. `splitBlock` is not used: it creates the default block (paragraph) at the end and\n // re-types the first half at the start. Shift-Enter keeps StarterKit's hard break.\n Enter: () => {\n const { $from } = this.editor.state.selection\n if ($from.parent.type.name !== this.name) return false\n const type = $from.parent.type\n const speaker = String($from.parent.attrs.speaker ?? '')\n return this.editor.commands.command(({ tr, state, dispatch }) => {\n const marks = state.storedMarks ?? ($from.parentOffset ? $from.marks() : null)\n const after = [{ type, attrs: { speaker } }]\n if (!canSplit(state.doc, state.selection.from, 1, after)) return false\n if (dispatch) {\n if (!tr.selection.empty) tr.deleteSelection()\n tr.split(tr.selection.from, 1, after)\n if (marks) tr.ensureMarks(marks)\n tr.scrollIntoView()\n }\n return true\n })\n },\n 'Mod-Shift-s': () => this.editor.commands.toggleTranscriptSegment(),\n }\n },\n})\n\n/**\n * Speaker name of a speaker line, i.e. a paragraph whose first inline node is bold-only text `Name:`\n * (Markdown `**Name:** text`), or null. Bold elsewhere in the paragraph does not count.\n */\nfunction speakerOf(block: JSONContent): string | null {\n const first = block.content?.[0]\n if (block.type !== 'paragraph' || first?.type !== 'text' || !first.text?.endsWith(':')) return null\n if (first.marks?.length !== 1 || first.marks[0]!.type !== 'bold') return null\n const name = first.text.slice(0, -1).trim()\n if (!name || name.length > MAX_SPEAKER_LENGTH || name.includes('\\n')) return null\n return name\n}\n\n/**\n * Turns top-level speaker-line paragraphs of parsed Markdown into transcriptSegment nodes (the inverse of\n * renderMarkdown above); every other block is returned unchanged. Applied to AI results on both apply paths.\n * A label without text (`**Aufgaben:**` above a list) is a bold pseudo-heading, not an empty turn, and stays a paragraph.\n */\nexport function speakerParagraphsToSegments(doc: JSONContent): JSONContent {\n if (!doc.content) return doc\n return {\n ...doc,\n content: doc.content.map((block) => {\n const name = speakerOf(block)\n if (name === null) return block\n const content = block.content!.slice(1)\n if (content.every(node => node.type === 'text' && !node.text?.trim())) return block\n const next = content[0]\n // renderMarkdown puts exactly one space between label and text; drop only that one.\n if (next?.type === 'text' && next.text && /^\\s/.test(next.text)) {\n if (next.text.length === 1) content.shift()\n else content[0] = { ...next, text: next.text.slice(1) }\n }\n return { type: 'transcriptSegment', attrs: { speaker: name === UNNAMED_SPEAKER ? '' : name }, content }\n }),\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region src/i18n/de/image-studio.ts
|
|
2
|
+
const de = {
|
|
3
|
+
"imageStudio.slash.insert": "Bild-Studio",
|
|
4
|
+
"imageStudio.dialog.title": "Bild-Studio",
|
|
5
|
+
"imageStudio.dialog.description.heading": "Beschreibung",
|
|
6
|
+
"imageStudio.dialog.description.placeholder": "Beschreibe das Bild …",
|
|
7
|
+
"imageStudio.dialog.blocks.heading": "Prompt-Bausteine",
|
|
8
|
+
"imageStudio.dialog.blocks.empty": "Es sind keine Prompt-Bausteine eingerichtet.",
|
|
9
|
+
"imageStudio.dialog.references.heading": "Referenzen",
|
|
10
|
+
"imageStudio.dialog.references.upload": "Bild hochladen",
|
|
11
|
+
"imageStudio.dialog.references.uploading": "Wird hochgeladen …",
|
|
12
|
+
"imageStudio.dialog.references.drop": "Bilder hierher ziehen oder hochladen",
|
|
13
|
+
"imageStudio.dialog.references.failed": "Hochladen fehlgeschlagen: {message}",
|
|
14
|
+
"imageStudio.dialog.generations.heading": "Frühere Erzeugungen",
|
|
15
|
+
"imageStudio.dialog.generations.empty": "Noch keine Erzeugungen.",
|
|
16
|
+
"imageStudio.dialog.tile.toggle": "Für die nächste Erzeugung verwenden: {title}",
|
|
17
|
+
"imageStudio.dialog.tile.use": "Als Bild verwenden",
|
|
18
|
+
"imageStudio.dialog.tile.remove": "Entfernen",
|
|
19
|
+
"imageStudio.dialog.tile.untitled": "Bild ohne Titel",
|
|
20
|
+
"imageStudio.dialog.preview.heading": "Bild",
|
|
21
|
+
"imageStudio.dialog.preview.empty": "Noch kein Bild",
|
|
22
|
+
"imageStudio.dialog.chat.heading": "Chat",
|
|
23
|
+
"imageStudio.dialog.chat.empty": "Noch keine Anweisungen.",
|
|
24
|
+
"imageStudio.dialog.chat.label": "Anweisung",
|
|
25
|
+
"imageStudio.dialog.chat.placeholder": "Anweisung eingeben, z. B. „heller, mit mehr Kontrast“",
|
|
26
|
+
"imageStudio.dialog.chat.send": "Erzeugen",
|
|
27
|
+
"imageStudio.dialog.chat.running": "Wird erzeugt …",
|
|
28
|
+
"imageStudio.dialog.chat.fromDescription": "Aus der Beschreibung",
|
|
29
|
+
"imageStudio.dialog.chat.turnImage": "Erzeugtes Bild: {instruction}",
|
|
30
|
+
"imageStudio.node.noDescription": "Noch keine Beschreibung",
|
|
31
|
+
"imageStudio.node.open": "Bild-Studio öffnen",
|
|
32
|
+
"imageStudio.node.dialogTitle": "Bild-Studio",
|
|
33
|
+
"imageStudio.action.open": "Im Bild-Studio öffnen",
|
|
34
|
+
"imageStudio.action.notConfigured": "Bildgenerierung ist nicht eingerichtet"
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
export { de };
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=image-studio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-studio.js","names":[],"sources":["../../../src/i18n/de/image-studio.ts"],"sourcesContent":["export const de = {\n 'imageStudio.slash.insert': 'Bild-Studio',\n 'imageStudio.dialog.title': 'Bild-Studio',\n 'imageStudio.dialog.description.heading': 'Beschreibung',\n 'imageStudio.dialog.description.placeholder': 'Beschreibe das Bild …',\n 'imageStudio.dialog.blocks.heading': 'Prompt-Bausteine',\n 'imageStudio.dialog.blocks.empty': 'Es sind keine Prompt-Bausteine eingerichtet.',\n 'imageStudio.dialog.references.heading': 'Referenzen',\n 'imageStudio.dialog.references.upload': 'Bild hochladen',\n 'imageStudio.dialog.references.uploading': 'Wird hochgeladen …',\n 'imageStudio.dialog.references.drop': 'Bilder hierher ziehen oder hochladen',\n 'imageStudio.dialog.references.failed': 'Hochladen fehlgeschlagen: {message}',\n 'imageStudio.dialog.generations.heading': 'Frühere Erzeugungen',\n 'imageStudio.dialog.generations.empty': 'Noch keine Erzeugungen.',\n 'imageStudio.dialog.tile.toggle': 'Für die nächste Erzeugung verwenden: {title}',\n 'imageStudio.dialog.tile.use': 'Als Bild verwenden',\n 'imageStudio.dialog.tile.remove': 'Entfernen',\n 'imageStudio.dialog.tile.untitled': 'Bild ohne Titel',\n 'imageStudio.dialog.preview.heading': 'Bild',\n 'imageStudio.dialog.preview.empty': 'Noch kein Bild',\n 'imageStudio.dialog.chat.heading': 'Chat',\n 'imageStudio.dialog.chat.empty': 'Noch keine Anweisungen.',\n 'imageStudio.dialog.chat.label': 'Anweisung',\n 'imageStudio.dialog.chat.placeholder': 'Anweisung eingeben, z. B. „heller, mit mehr Kontrast“',\n 'imageStudio.dialog.chat.send': 'Erzeugen',\n 'imageStudio.dialog.chat.running': 'Wird erzeugt …',\n 'imageStudio.dialog.chat.fromDescription': 'Aus der Beschreibung',\n 'imageStudio.dialog.chat.turnImage': 'Erzeugtes Bild: {instruction}',\n 'imageStudio.node.noDescription': 'Noch keine Beschreibung',\n 'imageStudio.node.open': 'Bild-Studio öffnen',\n 'imageStudio.node.dialogTitle': 'Bild-Studio',\n 'imageStudio.action.open': 'Im Bild-Studio öffnen',\n 'imageStudio.action.notConfigured': 'Bildgenerierung ist nicht eingerichtet',\n} as const\n"],"mappings":";AAAA,MAAa,KAAK;CAChB,4BAA4B;CAC5B,4BAA4B;CAC5B,0CAA0C;CAC1C,8CAA8C;CAC9C,qCAAqC;CACrC,mCAAmC;CACnC,yCAAyC;CACzC,wCAAwC;CACxC,2CAA2C;CAC3C,sCAAsC;CACtC,wCAAwC;CACxC,0CAA0C;CAC1C,wCAAwC;CACxC,kCAAkC;CAClC,+BAA+B;CAC/B,kCAAkC;CAClC,oCAAoC;CACpC,sCAAsC;CACtC,oCAAoC;CACpC,mCAAmC;CACnC,iCAAiC;CACjC,iCAAiC;CACjC,uCAAuC;CACvC,gCAAgC;CAChC,mCAAmC;CACnC,2CAA2C;CAC3C,qCAAqC;CACrC,kCAAkC;CAClC,yBAAyB;CACzB,gCAAgC;CAChC,2BAA2B;CAC3B,oCAAoC;AACtC"}
|
package/dist/i18n/de.d.ts
CHANGED
|
@@ -209,6 +209,38 @@ export declare const de: {
|
|
|
209
209
|
readonly 'themes.delete.label': "Theme „{name}“ löschen";
|
|
210
210
|
readonly 'themes.delete.done': "Theme „{name}“ gelöscht";
|
|
211
211
|
readonly 'themes.delete.failed': "Theme konnte nicht gelöscht werden";
|
|
212
|
+
readonly 'imageStudio.slash.insert': "Bild-Studio";
|
|
213
|
+
readonly 'imageStudio.dialog.title': "Bild-Studio";
|
|
214
|
+
readonly 'imageStudio.dialog.description.heading': "Beschreibung";
|
|
215
|
+
readonly 'imageStudio.dialog.description.placeholder': "Beschreibe das Bild …";
|
|
216
|
+
readonly 'imageStudio.dialog.blocks.heading': "Prompt-Bausteine";
|
|
217
|
+
readonly 'imageStudio.dialog.blocks.empty': "Es sind keine Prompt-Bausteine eingerichtet.";
|
|
218
|
+
readonly 'imageStudio.dialog.references.heading': "Referenzen";
|
|
219
|
+
readonly 'imageStudio.dialog.references.upload': "Bild hochladen";
|
|
220
|
+
readonly 'imageStudio.dialog.references.uploading': "Wird hochgeladen …";
|
|
221
|
+
readonly 'imageStudio.dialog.references.drop': "Bilder hierher ziehen oder hochladen";
|
|
222
|
+
readonly 'imageStudio.dialog.references.failed': "Hochladen fehlgeschlagen: {message}";
|
|
223
|
+
readonly 'imageStudio.dialog.generations.heading': "Frühere Erzeugungen";
|
|
224
|
+
readonly 'imageStudio.dialog.generations.empty': "Noch keine Erzeugungen.";
|
|
225
|
+
readonly 'imageStudio.dialog.tile.toggle': "Für die nächste Erzeugung verwenden: {title}";
|
|
226
|
+
readonly 'imageStudio.dialog.tile.use': "Als Bild verwenden";
|
|
227
|
+
readonly 'imageStudio.dialog.tile.remove': "Entfernen";
|
|
228
|
+
readonly 'imageStudio.dialog.tile.untitled': "Bild ohne Titel";
|
|
229
|
+
readonly 'imageStudio.dialog.preview.heading': "Bild";
|
|
230
|
+
readonly 'imageStudio.dialog.preview.empty': "Noch kein Bild";
|
|
231
|
+
readonly 'imageStudio.dialog.chat.heading': "Chat";
|
|
232
|
+
readonly 'imageStudio.dialog.chat.empty': "Noch keine Anweisungen.";
|
|
233
|
+
readonly 'imageStudio.dialog.chat.label': "Anweisung";
|
|
234
|
+
readonly 'imageStudio.dialog.chat.placeholder': "Anweisung eingeben, z. B. „heller, mit mehr Kontrast“";
|
|
235
|
+
readonly 'imageStudio.dialog.chat.send': "Erzeugen";
|
|
236
|
+
readonly 'imageStudio.dialog.chat.running': "Wird erzeugt …";
|
|
237
|
+
readonly 'imageStudio.dialog.chat.fromDescription': "Aus der Beschreibung";
|
|
238
|
+
readonly 'imageStudio.dialog.chat.turnImage': "Erzeugtes Bild: {instruction}";
|
|
239
|
+
readonly 'imageStudio.node.noDescription': "Noch keine Beschreibung";
|
|
240
|
+
readonly 'imageStudio.node.open': "Bild-Studio öffnen";
|
|
241
|
+
readonly 'imageStudio.node.dialogTitle': "Bild-Studio";
|
|
242
|
+
readonly 'imageStudio.action.open': "Im Bild-Studio öffnen";
|
|
243
|
+
readonly 'imageStudio.action.notConfigured': "Bildgenerierung ist nicht eingerichtet";
|
|
212
244
|
readonly 'media.picker.image': "Bild";
|
|
213
245
|
readonly 'media.picker.audio': "Audio-Datei";
|
|
214
246
|
readonly 'media.picker.video': "Video";
|
package/dist/i18n/de.js
CHANGED
|
@@ -7,16 +7,17 @@ import { de as de$6 } from "./de/comments.js";
|
|
|
7
7
|
import { de as de$7 } from "./de/authors.js";
|
|
8
8
|
import { de as de$8 } from "./de/composites.js";
|
|
9
9
|
import { de as de$9 } from "./de/media.js";
|
|
10
|
-
import { de as de$10 } from "./de/
|
|
11
|
-
import { de as de$11 } from "./de/
|
|
12
|
-
import { de as de$12 } from "./de/
|
|
13
|
-
import { de as de$13 } from "./de/
|
|
14
|
-
import { de as de$14 } from "./de/
|
|
15
|
-
import { de as de$15 } from "./de/
|
|
16
|
-
import { de as de$16 } from "./de/
|
|
17
|
-
import { de as de$17 } from "./de/
|
|
18
|
-
import { de as de$18 } from "./de/
|
|
19
|
-
import { de as de$19 } from "./de/
|
|
10
|
+
import { de as de$10 } from "./de/image-studio.js";
|
|
11
|
+
import { de as de$11 } from "./de/themes.js";
|
|
12
|
+
import { de as de$12 } from "./de/tree.js";
|
|
13
|
+
import { de as de$13 } from "./de/transcript.js";
|
|
14
|
+
import { de as de$14 } from "./de/tables.js";
|
|
15
|
+
import { de as de$15 } from "./de/outline.js";
|
|
16
|
+
import { de as de$16 } from "./de/mentions.js";
|
|
17
|
+
import { de as de$17 } from "./de/integrations.js";
|
|
18
|
+
import { de as de$18 } from "./de/markdown.js";
|
|
19
|
+
import { de as de$19 } from "./de/palette.js";
|
|
20
|
+
import { de as de$20 } from "./de/transfer.js";
|
|
20
21
|
//#region src/i18n/de.ts
|
|
21
22
|
/** The German catalog (the default locale) and the source of every message key. */
|
|
22
23
|
const de = {
|
|
@@ -38,7 +39,8 @@ const de = {
|
|
|
38
39
|
...de$16,
|
|
39
40
|
...de$17,
|
|
40
41
|
...de$18,
|
|
41
|
-
...de$19
|
|
42
|
+
...de$19,
|
|
43
|
+
...de$20
|
|
42
44
|
};
|
|
43
45
|
//#endregion
|
|
44
46
|
export { de };
|
package/dist/i18n/de.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"de.js","names":["common","toolbar","block","settings","ai","comments","authors","composites","media","themes","tree","transcript","tables","outline","mentions","integrations","markdown","palette","transfer"],"sources":["../../src/i18n/de.ts"],"sourcesContent":["import { de as common } from './de/common'\nimport { de as toolbar } from './de/toolbar'\nimport { de as block } from './de/block'\nimport { de as settings } from './de/settings'\nimport { de as ai } from './de/ai'\nimport { de as comments } from './de/comments'\nimport { de as authors } from './de/authors'\nimport { de as composites } from './de/composites'\nimport { de as media } from './de/media'\nimport { de as themes } from './de/themes'\nimport { de as tree } from './de/tree'\nimport { de as transcript } from './de/transcript'\nimport { de as tables } from './de/tables'\nimport { de as outline } from './de/outline'\nimport { de as mentions } from './de/mentions'\nimport { de as integrations } from './de/integrations'\nimport { de as markdown } from './de/markdown'\nimport { de as palette } from './de/palette'\nimport { de as transfer } from './de/transfer'\n\n/** The German catalog (the default locale) and the source of every message key. */\nexport const de = {\n ...common,\n ...toolbar,\n ...block,\n ...settings,\n ...ai,\n ...comments,\n ...authors,\n ...composites,\n ...media,\n ...themes,\n ...tree,\n ...transcript,\n ...tables,\n ...outline,\n ...mentions,\n ...integrations,\n ...markdown,\n ...palette,\n ...transfer,\n} as const\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"de.js","names":["common","toolbar","block","settings","ai","comments","authors","composites","media","imageStudio","themes","tree","transcript","tables","outline","mentions","integrations","markdown","palette","transfer"],"sources":["../../src/i18n/de.ts"],"sourcesContent":["import { de as common } from './de/common'\nimport { de as toolbar } from './de/toolbar'\nimport { de as block } from './de/block'\nimport { de as settings } from './de/settings'\nimport { de as ai } from './de/ai'\nimport { de as comments } from './de/comments'\nimport { de as authors } from './de/authors'\nimport { de as composites } from './de/composites'\nimport { de as media } from './de/media'\nimport { de as imageStudio } from './de/image-studio'\nimport { de as themes } from './de/themes'\nimport { de as tree } from './de/tree'\nimport { de as transcript } from './de/transcript'\nimport { de as tables } from './de/tables'\nimport { de as outline } from './de/outline'\nimport { de as mentions } from './de/mentions'\nimport { de as integrations } from './de/integrations'\nimport { de as markdown } from './de/markdown'\nimport { de as palette } from './de/palette'\nimport { de as transfer } from './de/transfer'\n\n/** The German catalog (the default locale) and the source of every message key. */\nexport const de = {\n ...common,\n ...toolbar,\n ...block,\n ...settings,\n ...ai,\n ...comments,\n ...authors,\n ...composites,\n ...media,\n ...imageStudio,\n ...themes,\n ...tree,\n ...transcript,\n ...tables,\n ...outline,\n ...mentions,\n ...integrations,\n ...markdown,\n ...palette,\n ...transfer,\n} as const\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAsBA,MAAa,KAAK;CAChB,GAAGA;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;AACL"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
//#region src/i18n/en/image-studio.ts
|
|
2
|
+
const en = {
|
|
3
|
+
"imageStudio.slash.insert": "Image studio",
|
|
4
|
+
"imageStudio.dialog.title": "Image studio",
|
|
5
|
+
"imageStudio.dialog.description.heading": "Description",
|
|
6
|
+
"imageStudio.dialog.description.placeholder": "Describe the image …",
|
|
7
|
+
"imageStudio.dialog.blocks.heading": "Prompt blocks",
|
|
8
|
+
"imageStudio.dialog.blocks.empty": "No prompt blocks are set up.",
|
|
9
|
+
"imageStudio.dialog.references.heading": "References",
|
|
10
|
+
"imageStudio.dialog.references.upload": "Upload image",
|
|
11
|
+
"imageStudio.dialog.references.uploading": "Uploading …",
|
|
12
|
+
"imageStudio.dialog.references.drop": "Drop images here or upload",
|
|
13
|
+
"imageStudio.dialog.references.failed": "Upload failed: {message}",
|
|
14
|
+
"imageStudio.dialog.generations.heading": "Previous generations",
|
|
15
|
+
"imageStudio.dialog.generations.empty": "No generations yet.",
|
|
16
|
+
"imageStudio.dialog.tile.toggle": "Use for the next generation: {title}",
|
|
17
|
+
"imageStudio.dialog.tile.use": "Use as image",
|
|
18
|
+
"imageStudio.dialog.tile.remove": "Remove",
|
|
19
|
+
"imageStudio.dialog.tile.untitled": "Untitled image",
|
|
20
|
+
"imageStudio.dialog.preview.heading": "Image",
|
|
21
|
+
"imageStudio.dialog.preview.empty": "No image yet",
|
|
22
|
+
"imageStudio.dialog.chat.heading": "Chat",
|
|
23
|
+
"imageStudio.dialog.chat.empty": "No instructions yet.",
|
|
24
|
+
"imageStudio.dialog.chat.label": "Instruction",
|
|
25
|
+
"imageStudio.dialog.chat.placeholder": "Type an instruction, e.g. “brighter, more contrast”",
|
|
26
|
+
"imageStudio.dialog.chat.send": "Generate",
|
|
27
|
+
"imageStudio.dialog.chat.running": "Generating …",
|
|
28
|
+
"imageStudio.dialog.chat.fromDescription": "From the description",
|
|
29
|
+
"imageStudio.dialog.chat.turnImage": "Generated image: {instruction}",
|
|
30
|
+
"imageStudio.node.noDescription": "No description yet",
|
|
31
|
+
"imageStudio.node.open": "Open image studio",
|
|
32
|
+
"imageStudio.node.dialogTitle": "Image studio",
|
|
33
|
+
"imageStudio.action.open": "Open in the image studio",
|
|
34
|
+
"imageStudio.action.notConfigured": "Image generation is not set up"
|
|
35
|
+
};
|
|
36
|
+
//#endregion
|
|
37
|
+
export { en };
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=image-studio.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-studio.js","names":[],"sources":["../../../src/i18n/en/image-studio.ts"],"sourcesContent":["import type { de } from '../de/image-studio'\n\nexport const en: { [K in keyof typeof de]: string } = {\n 'imageStudio.slash.insert': 'Image studio',\n 'imageStudio.dialog.title': 'Image studio',\n 'imageStudio.dialog.description.heading': 'Description',\n 'imageStudio.dialog.description.placeholder': 'Describe the image …',\n 'imageStudio.dialog.blocks.heading': 'Prompt blocks',\n 'imageStudio.dialog.blocks.empty': 'No prompt blocks are set up.',\n 'imageStudio.dialog.references.heading': 'References',\n 'imageStudio.dialog.references.upload': 'Upload image',\n 'imageStudio.dialog.references.uploading': 'Uploading …',\n 'imageStudio.dialog.references.drop': 'Drop images here or upload',\n 'imageStudio.dialog.references.failed': 'Upload failed: {message}',\n 'imageStudio.dialog.generations.heading': 'Previous generations',\n 'imageStudio.dialog.generations.empty': 'No generations yet.',\n 'imageStudio.dialog.tile.toggle': 'Use for the next generation: {title}',\n 'imageStudio.dialog.tile.use': 'Use as image',\n 'imageStudio.dialog.tile.remove': 'Remove',\n 'imageStudio.dialog.tile.untitled': 'Untitled image',\n 'imageStudio.dialog.preview.heading': 'Image',\n 'imageStudio.dialog.preview.empty': 'No image yet',\n 'imageStudio.dialog.chat.heading': 'Chat',\n 'imageStudio.dialog.chat.empty': 'No instructions yet.',\n 'imageStudio.dialog.chat.label': 'Instruction',\n 'imageStudio.dialog.chat.placeholder': 'Type an instruction, e.g. “brighter, more contrast”',\n 'imageStudio.dialog.chat.send': 'Generate',\n 'imageStudio.dialog.chat.running': 'Generating …',\n 'imageStudio.dialog.chat.fromDescription': 'From the description',\n 'imageStudio.dialog.chat.turnImage': 'Generated image: {instruction}',\n 'imageStudio.node.noDescription': 'No description yet',\n 'imageStudio.node.open': 'Open image studio',\n 'imageStudio.node.dialogTitle': 'Image studio',\n 'imageStudio.action.open': 'Open in the image studio',\n 'imageStudio.action.notConfigured': 'Image generation is not set up',\n}\n"],"mappings":";AAEA,MAAa,KAAyC;CACpD,4BAA4B;CAC5B,4BAA4B;CAC5B,0CAA0C;CAC1C,8CAA8C;CAC9C,qCAAqC;CACrC,mCAAmC;CACnC,yCAAyC;CACzC,wCAAwC;CACxC,2CAA2C;CAC3C,sCAAsC;CACtC,wCAAwC;CACxC,0CAA0C;CAC1C,wCAAwC;CACxC,kCAAkC;CAClC,+BAA+B;CAC/B,kCAAkC;CAClC,oCAAoC;CACpC,sCAAsC;CACtC,oCAAoC;CACpC,mCAAmC;CACnC,iCAAiC;CACjC,iCAAiC;CACjC,uCAAuC;CACvC,gCAAgC;CAChC,mCAAmC;CACnC,2CAA2C;CAC3C,qCAAqC;CACrC,kCAAkC;CAClC,yBAAyB;CACzB,gCAAgC;CAChC,2BAA2B;CAC3B,oCAAoC;AACtC"}
|
package/dist/i18n/en.js
CHANGED
|
@@ -7,16 +7,17 @@ import { en as en$6 } from "./en/comments.js";
|
|
|
7
7
|
import { en as en$7 } from "./en/authors.js";
|
|
8
8
|
import { en as en$8 } from "./en/composites.js";
|
|
9
9
|
import { en as en$9 } from "./en/media.js";
|
|
10
|
-
import { en as en$10 } from "./en/
|
|
11
|
-
import { en as en$11 } from "./en/
|
|
12
|
-
import { en as en$12 } from "./en/
|
|
13
|
-
import { en as en$13 } from "./en/
|
|
14
|
-
import { en as en$14 } from "./en/
|
|
15
|
-
import { en as en$15 } from "./en/
|
|
16
|
-
import { en as en$16 } from "./en/
|
|
17
|
-
import { en as en$17 } from "./en/
|
|
18
|
-
import { en as en$18 } from "./en/
|
|
19
|
-
import { en as en$19 } from "./en/
|
|
10
|
+
import { en as en$10 } from "./en/image-studio.js";
|
|
11
|
+
import { en as en$11 } from "./en/themes.js";
|
|
12
|
+
import { en as en$12 } from "./en/tree.js";
|
|
13
|
+
import { en as en$13 } from "./en/transcript.js";
|
|
14
|
+
import { en as en$14 } from "./en/tables.js";
|
|
15
|
+
import { en as en$15 } from "./en/outline.js";
|
|
16
|
+
import { en as en$16 } from "./en/mentions.js";
|
|
17
|
+
import { en as en$17 } from "./en/integrations.js";
|
|
18
|
+
import { en as en$18 } from "./en/markdown.js";
|
|
19
|
+
import { en as en$19 } from "./en/palette.js";
|
|
20
|
+
import { en as en$20 } from "./en/transfer.js";
|
|
20
21
|
//#region src/i18n/en.ts
|
|
21
22
|
/** The English catalog: the same keys as `de`, checked per area file. */
|
|
22
23
|
const en = {
|
|
@@ -38,7 +39,8 @@ const en = {
|
|
|
38
39
|
...en$16,
|
|
39
40
|
...en$17,
|
|
40
41
|
...en$18,
|
|
41
|
-
...en$19
|
|
42
|
+
...en$19,
|
|
43
|
+
...en$20
|
|
42
44
|
};
|
|
43
45
|
//#endregion
|
|
44
46
|
export { en };
|
package/dist/i18n/en.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"en.js","names":["common","toolbar","block","settings","ai","comments","authors","composites","media","themes","tree","transcript","tables","outline","mentions","integrations","markdown","palette","transfer"],"sources":["../../src/i18n/en.ts"],"sourcesContent":["import { en as common } from './en/common'\nimport { en as toolbar } from './en/toolbar'\nimport { en as block } from './en/block'\nimport { en as settings } from './en/settings'\nimport { en as ai } from './en/ai'\nimport { en as comments } from './en/comments'\nimport { en as authors } from './en/authors'\nimport { en as composites } from './en/composites'\nimport { en as media } from './en/media'\nimport { en as themes } from './en/themes'\nimport { en as tree } from './en/tree'\nimport { en as transcript } from './en/transcript'\nimport { en as tables } from './en/tables'\nimport { en as outline } from './en/outline'\nimport { en as mentions } from './en/mentions'\nimport { en as integrations } from './en/integrations'\nimport { en as markdown } from './en/markdown'\nimport { en as palette } from './en/palette'\nimport { en as transfer } from './en/transfer'\nimport type { de } from './de'\n\n/** The English catalog: the same keys as `de`, checked per area file. */\nexport const en: { [K in keyof typeof de]: string } = {\n ...common,\n ...toolbar,\n ...block,\n ...settings,\n ...ai,\n ...comments,\n ...authors,\n ...composites,\n ...media,\n ...themes,\n ...tree,\n ...transcript,\n ...tables,\n ...outline,\n ...mentions,\n ...integrations,\n ...markdown,\n ...palette,\n ...transfer,\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"en.js","names":["common","toolbar","block","settings","ai","comments","authors","composites","media","imageStudio","themes","tree","transcript","tables","outline","mentions","integrations","markdown","palette","transfer"],"sources":["../../src/i18n/en.ts"],"sourcesContent":["import { en as common } from './en/common'\nimport { en as toolbar } from './en/toolbar'\nimport { en as block } from './en/block'\nimport { en as settings } from './en/settings'\nimport { en as ai } from './en/ai'\nimport { en as comments } from './en/comments'\nimport { en as authors } from './en/authors'\nimport { en as composites } from './en/composites'\nimport { en as media } from './en/media'\nimport { en as imageStudio } from './en/image-studio'\nimport { en as themes } from './en/themes'\nimport { en as tree } from './en/tree'\nimport { en as transcript } from './en/transcript'\nimport { en as tables } from './en/tables'\nimport { en as outline } from './en/outline'\nimport { en as mentions } from './en/mentions'\nimport { en as integrations } from './en/integrations'\nimport { en as markdown } from './en/markdown'\nimport { en as palette } from './en/palette'\nimport { en as transfer } from './en/transfer'\nimport type { de } from './de'\n\n/** The English catalog: the same keys as `de`, checked per area file. */\nexport const en: { [K in keyof typeof de]: string } = {\n ...common,\n ...toolbar,\n ...block,\n ...settings,\n ...ai,\n ...comments,\n ...authors,\n ...composites,\n ...media,\n ...imageStudio,\n ...themes,\n ...tree,\n ...transcript,\n ...tables,\n ...outline,\n ...mentions,\n ...integrations,\n ...markdown,\n ...palette,\n ...transfer,\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAuBA,MAAa,KAAyC;CACpD,GAAGA;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;CACH,GAAGC;AACL"}
|
package/dist/nuxt/module.d.ts
CHANGED
|
@@ -219,6 +219,38 @@ declare const _default: import("@nuxt/schema").NuxtModule<{
|
|
|
219
219
|
"themes.delete.label"?: string | undefined;
|
|
220
220
|
"themes.delete.done"?: string | undefined;
|
|
221
221
|
"themes.delete.failed"?: string | undefined;
|
|
222
|
+
"imageStudio.slash.insert"?: string | undefined;
|
|
223
|
+
"imageStudio.dialog.title"?: string | undefined;
|
|
224
|
+
"imageStudio.dialog.description.heading"?: string | undefined;
|
|
225
|
+
"imageStudio.dialog.description.placeholder"?: string | undefined;
|
|
226
|
+
"imageStudio.dialog.blocks.heading"?: string | undefined;
|
|
227
|
+
"imageStudio.dialog.blocks.empty"?: string | undefined;
|
|
228
|
+
"imageStudio.dialog.references.heading"?: string | undefined;
|
|
229
|
+
"imageStudio.dialog.references.upload"?: string | undefined;
|
|
230
|
+
"imageStudio.dialog.references.uploading"?: string | undefined;
|
|
231
|
+
"imageStudio.dialog.references.drop"?: string | undefined;
|
|
232
|
+
"imageStudio.dialog.references.failed"?: string | undefined;
|
|
233
|
+
"imageStudio.dialog.generations.heading"?: string | undefined;
|
|
234
|
+
"imageStudio.dialog.generations.empty"?: string | undefined;
|
|
235
|
+
"imageStudio.dialog.tile.toggle"?: string | undefined;
|
|
236
|
+
"imageStudio.dialog.tile.use"?: string | undefined;
|
|
237
|
+
"imageStudio.dialog.tile.remove"?: string | undefined;
|
|
238
|
+
"imageStudio.dialog.tile.untitled"?: string | undefined;
|
|
239
|
+
"imageStudio.dialog.preview.heading"?: string | undefined;
|
|
240
|
+
"imageStudio.dialog.preview.empty"?: string | undefined;
|
|
241
|
+
"imageStudio.dialog.chat.heading"?: string | undefined;
|
|
242
|
+
"imageStudio.dialog.chat.empty"?: string | undefined;
|
|
243
|
+
"imageStudio.dialog.chat.label"?: string | undefined;
|
|
244
|
+
"imageStudio.dialog.chat.placeholder"?: string | undefined;
|
|
245
|
+
"imageStudio.dialog.chat.send"?: string | undefined;
|
|
246
|
+
"imageStudio.dialog.chat.running"?: string | undefined;
|
|
247
|
+
"imageStudio.dialog.chat.fromDescription"?: string | undefined;
|
|
248
|
+
"imageStudio.dialog.chat.turnImage"?: string | undefined;
|
|
249
|
+
"imageStudio.node.noDescription"?: string | undefined;
|
|
250
|
+
"imageStudio.node.open"?: string | undefined;
|
|
251
|
+
"imageStudio.node.dialogTitle"?: string | undefined;
|
|
252
|
+
"imageStudio.action.open"?: string | undefined;
|
|
253
|
+
"imageStudio.action.notConfigured"?: string | undefined;
|
|
222
254
|
"media.picker.image"?: string | undefined;
|
|
223
255
|
"media.picker.audio"?: string | undefined;
|
|
224
256
|
"media.picker.video"?: string | undefined;
|
|
@@ -974,6 +1006,38 @@ declare const _default: import("@nuxt/schema").NuxtModule<{
|
|
|
974
1006
|
"themes.delete.label"?: string | undefined;
|
|
975
1007
|
"themes.delete.done"?: string | undefined;
|
|
976
1008
|
"themes.delete.failed"?: string | undefined;
|
|
1009
|
+
"imageStudio.slash.insert"?: string | undefined;
|
|
1010
|
+
"imageStudio.dialog.title"?: string | undefined;
|
|
1011
|
+
"imageStudio.dialog.description.heading"?: string | undefined;
|
|
1012
|
+
"imageStudio.dialog.description.placeholder"?: string | undefined;
|
|
1013
|
+
"imageStudio.dialog.blocks.heading"?: string | undefined;
|
|
1014
|
+
"imageStudio.dialog.blocks.empty"?: string | undefined;
|
|
1015
|
+
"imageStudio.dialog.references.heading"?: string | undefined;
|
|
1016
|
+
"imageStudio.dialog.references.upload"?: string | undefined;
|
|
1017
|
+
"imageStudio.dialog.references.uploading"?: string | undefined;
|
|
1018
|
+
"imageStudio.dialog.references.drop"?: string | undefined;
|
|
1019
|
+
"imageStudio.dialog.references.failed"?: string | undefined;
|
|
1020
|
+
"imageStudio.dialog.generations.heading"?: string | undefined;
|
|
1021
|
+
"imageStudio.dialog.generations.empty"?: string | undefined;
|
|
1022
|
+
"imageStudio.dialog.tile.toggle"?: string | undefined;
|
|
1023
|
+
"imageStudio.dialog.tile.use"?: string | undefined;
|
|
1024
|
+
"imageStudio.dialog.tile.remove"?: string | undefined;
|
|
1025
|
+
"imageStudio.dialog.tile.untitled"?: string | undefined;
|
|
1026
|
+
"imageStudio.dialog.preview.heading"?: string | undefined;
|
|
1027
|
+
"imageStudio.dialog.preview.empty"?: string | undefined;
|
|
1028
|
+
"imageStudio.dialog.chat.heading"?: string | undefined;
|
|
1029
|
+
"imageStudio.dialog.chat.empty"?: string | undefined;
|
|
1030
|
+
"imageStudio.dialog.chat.label"?: string | undefined;
|
|
1031
|
+
"imageStudio.dialog.chat.placeholder"?: string | undefined;
|
|
1032
|
+
"imageStudio.dialog.chat.send"?: string | undefined;
|
|
1033
|
+
"imageStudio.dialog.chat.running"?: string | undefined;
|
|
1034
|
+
"imageStudio.dialog.chat.fromDescription"?: string | undefined;
|
|
1035
|
+
"imageStudio.dialog.chat.turnImage"?: string | undefined;
|
|
1036
|
+
"imageStudio.node.noDescription"?: string | undefined;
|
|
1037
|
+
"imageStudio.node.open"?: string | undefined;
|
|
1038
|
+
"imageStudio.node.dialogTitle"?: string | undefined;
|
|
1039
|
+
"imageStudio.action.open"?: string | undefined;
|
|
1040
|
+
"imageStudio.action.notConfigured"?: string | undefined;
|
|
977
1041
|
"media.picker.image"?: string | undefined;
|
|
978
1042
|
"media.picker.audio"?: string | undefined;
|
|
979
1043
|
"media.picker.video"?: string | undefined;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { HandlerContext, HandlerRequest, HandlerResponse } from "./context.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/server/handlers/images.d.ts
|
|
4
|
+
/** The Prompt-Bausteine the host manages (`ImageStudioPromptBlock[]`). `501` without an `images` port. */
|
|
5
|
+
export declare function imagePromptBlocksGet(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse>;
|
|
6
|
+
/** Body schema of `imageGeneratePost`. */
|
|
7
|
+
export declare const imageGenerateBodySchema: z.ZodObject<{
|
|
8
|
+
description: z.ZodString;
|
|
9
|
+
instruction: z.ZodString;
|
|
10
|
+
promptBlockIds: z.ZodArray<z.ZodString>;
|
|
11
|
+
referenceUrls: z.ZodArray<z.ZodString>;
|
|
12
|
+
}, z.core.$strip>;
|
|
13
|
+
/** Generates one image for the body (`ImageStudioGenerateBody`); answers the stored `ImageStudioImage`. `400` for a malformed body, `501` without an `images` port. */
|
|
14
|
+
export declare function imageGeneratePost(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse>;
|
|
15
|
+
/**
|
|
16
|
+
* Stores a reference image: multipart fields `documentId` (the document it belongs to, or `scratch`) and `file` (an image);
|
|
17
|
+
* answers the stored `ImageStudioImage`. `400` for a malformed `documentId` or a missing file, `413` for a file over the
|
|
18
|
+
* image limit, `415` for a file that is no image, `501` without an `images` port.
|
|
19
|
+
*/
|
|
20
|
+
export declare function imageUploadPost(ctx: HandlerContext, request: HandlerRequest): Promise<HandlerResponse>;
|
|
21
|
+
//#endregion
|
|
22
|
+
//# sourceMappingURL=images.d.ts.map
|