@ai-matrx/office 0.5.2 → 0.5.3

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/aidream/aidream/apps/shared/office/dist/chunk-ZDZCCOUB.cjs","../src/sheets/ports.ts","../src/sheets/workbook-service.ts"],"names":["error"],"mappings":"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","file":"/home/runner/work/aidream/aidream/apps/shared/office/dist/chunk-ZDZCCOUB.cjs","sourcesContent":[null,"/**\n * The host ports of the Sheets model and io.\n *\n * The package owns the workbook service, the page-setup store and the import flow, but three things\n * belong to the app that mounts them: the database client (a package owns no client), the\n * organization a write acts in (the active-organization ladder is app state) and the file store that\n * keeps a lossless copy of an imported file. The host states them once at start-up with\n * `configureSheetsHost`. State sits on a `globalThis` slot so a dual-bundled copy of the package\n * still sees one host. An unconfigured port THROWS naming itself (nothing fails silently).\n */\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\n\n/** A Supabase client over any schema: the package cannot import the app's generated database types. */\nexport type SheetsSupabase = SupabaseClient<any, any, any>;\n\nexport interface SheetsHostPorts {\n /** The app's database client (signed in as the person). */\n getSupabase(): SheetsSupabase;\n /** The organization a write acts in: the record's own when given, otherwise the active one. */\n ensureOrgId(organizationId: string | null): Promise<string>;\n /** Keep the original of an imported spreadsheet in the file store, in `folderPath`. */\n uploadOriginalFile(file: File, options: { folderPath: string }): Promise<{ fileId: string }>;\n}\n\nconst SLOT = Symbol.for(\"ai-matrx.office.sheets-host\");\ntype Slot = { ports?: SheetsHostPorts };\nconst slot = (): Slot => {\n const g = globalThis as Record<symbol, Slot | undefined>;\n return (g[SLOT] ??= {});\n};\n\nexport function configureSheetsHost(ports: SheetsHostPorts): void {\n slot().ports = ports;\n}\n\nexport function getSheetsHost(): SheetsHostPorts {\n const ports = slot().ports;\n if (!ports) throw new Error(\"@ai-matrx/office/sheets: configureSheetsHost(ports) was not called before the workbook service ran.\");\n return ports;\n}\n","/**\n * Workbook service — typed wrappers for `udt_workbooks` + `udt_workbook_snapshots`.\n *\n * Workbooks are the lossless-spreadsheet surface (P4): metadata in\n * `udt_workbooks`, content state in append-only `udt_workbook_snapshots`. The\n * editor (Univer or equivalent) hydrates from the LATEST snapshot and writes\n * a new snapshot per save (debounced on the client — see WorkbookEditor).\n *\n * What lives elsewhere:\n * - Sharing / permissions: features/sharing/ + `has_permission(...)` RLS\n * - Realtime: @ai-matrx/office/univer/collab useWorkbookRealtime\n * - Component: the Sheets editor\n */\nimport { requireOrganizationContext } from \"@ai-matrx/api\";\nimport { getClaimsUser } from \"@ai-matrx/auth\";\nimport { tryWriteOne, writeOneRow } from \"@ai-matrx/data/db\";\nimport type { ServiceResult } from \"@ai-matrx/records/core\";\n\nimport type { Workbook, WorkbookSnapshot, WorkbookSnapshotOrigin } from \"../core/types\";\nimport { getSheetsHost } from \"./ports\";\n\n/** The app's database client, read through the host port at call time. */\nconst db = () => getSheetsHost().getSupabase();\n\n// ─── workbooks ────────────────────────────────────────────────────────────────\n\nconst WORKBOOK_SOURCE_LABELS: Record<string, string> = {\n created: \"Created\",\n imported_xlsx: \"Imported Excel\",\n imported_gsheet: \"Imported from Google Sheets\",\n imported_csv: \"Imported CSV\",\n linked_gsheet: \"Imported from Google Sheets\",\n};\n\n/** Honest, human label for `udt_workbooks.source`. */\nexport function workbookSourceLabel(source: string): string {\n return WORKBOOK_SOURCE_LABELS[source] ?? source.replace(/_/g, \" \");\n}\n\nexport type CreateWorkbookArgs = {\n name: string;\n description?: string | null;\n /** Origin label for the workbook itself, mirrors `udt_workbooks.source`. */\n source?:\n | \"created\"\n | \"imported_xlsx\"\n | \"imported_gsheet\"\n | \"imported_csv\"\n | \"linked_gsheet\";\n organizationId: string;\n projectId?: string | null;\n taskId?: string | null;\n isPublic?: boolean;\n /**\n * cld_files.id of the source upload (XLSX / CSV blob). Set on the import\n * flow so the lossless original is recoverable; FK is ON DELETE SET NULL,\n * so deleting the file just nulls the link — the workbook survives.\n */\n originalFileId?: string | null;\n};\n\nexport async function createWorkbook(\n args: CreateWorkbookArgs,\n): Promise<ServiceResult<Workbook>> {\n let organizationId: string;\n try {\n organizationId = requireOrganizationContext(undefined, args.organizationId);\n } catch (error) {\n return {\n success: false,\n error:\n error instanceof Error\n ? error.message\n : \"Select an organization before creating a workbook.\",\n };\n }\n\n const { data: userData, error: userErr } = await getClaimsUser(db());\n if (userErr || !userData?.user) {\n return {\n success: false,\n error: userErr?.message ?? \"not authenticated\",\n };\n }\n\n const { data, error } = await db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .insert({\n workbook_name: args.name,\n description: args.description ?? null,\n source: args.source ?? \"created\",\n organization_id: organizationId,\n project_id: args.projectId ?? null,\n task_id: args.taskId ?? null,\n original_file_id: args.originalFileId ?? null,\n // CANONICAL columns (workbench_udt_canonical_step1). `visibility` is the\n // source of truth; legacy `is_public` is derived from it by the\n // workbench._bridge_legacy_owner trigger. A new workbook defaults to\n // `internal` — org work product, not an individual's private thing\n // (db-rules §6) — unless the caller explicitly asked for public.\n created_by: userData.user.id,\n visibility: args.isPublic ? \"public\" : \"internal\",\n })\n .select(\"*\")\n .single();\n if (error) return { success: false, error: error.message };\n return { success: true, data: data as Workbook };\n}\n\nexport async function listAccessibleWorkbooks(): Promise<\n ServiceResult<Workbook[]>\n> {\n // RLS handles owner / published-to-the-web / shared access.\n const { data, error } = await db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .select(\"*\")\n .is(\"deleted_at\", null)\n .order(\"updated_at\", { ascending: false });\n if (error) return { success: false, error: error.message };\n return { success: true, data: (data ?? []) as Workbook[] };\n}\n\nexport async function getWorkbook(\n workbookId: string,\n): Promise<ServiceResult<Workbook>> {\n const { data, error } = await db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .select(\"*\")\n .eq(\"id\", workbookId)\n .is(\"deleted_at\", null)\n .single();\n if (error) return { success: false, error: error.message };\n return { success: true, data: data as Workbook };\n}\n\nexport async function renameWorkbook(\n workbookId: string,\n name: string,\n): Promise<ServiceResult<Workbook>> {\n const { data, error } = await writeOneRow(\n db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .update({ workbook_name: name, updated_at: new Date().toISOString() })\n .eq(\"id\", workbookId)\n .select(\"*\"),\n { action: \"update\", noun: \"workbook\" },\n );\n if (error) return { success: false, error: error.message };\n return { success: true, data: data as Workbook };\n}\n\n/**\n * Move the workbook into a project (or out of one with `null`). Sibling of\n * `renameWorkbook`: the one named setter for `project_id`.\n */\nexport async function moveWorkbook(\n workbookId: string,\n projectId: string | null,\n): Promise<ServiceResult<Workbook>> {\n const { data, error } = await writeOneRow(\n db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .update({ project_id: projectId, updated_at: new Date().toISOString() })\n .eq(\"id\", workbookId)\n .select(\"*\"),\n { action: \"update\", noun: \"workbook\" },\n );\n if (error) return { success: false, error: error.message };\n return { success: true, data: data as Workbook };\n}\n\n/**\n * Rewrite the workbook's description. Sibling of `renameWorkbook` — the two\n * human-authored fields on `udt_workbooks` each get a named setter so callers\n * never hand-roll a `.from(\"udt_workbooks\").update(...)`. Pass `null` (or an\n * empty string) to clear it.\n *\n * Written by the import flow at creation (\"Imported from <file>\") and, since\n * the workbooks surface became agent-writable, by the `workbook_description`\n * write target on `/workbooks/[id]`.\n */\nexport async function updateWorkbookDescription(\n workbookId: string,\n description: string | null,\n): Promise<ServiceResult<Workbook>> {\n const { data, error } = await writeOneRow(\n db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .update({\n description: description && description.length > 0 ? description : null,\n updated_at: new Date().toISOString(),\n })\n .eq(\"id\", workbookId)\n .select(\"*\"),\n { action: \"update\", noun: \"workbook\" },\n );\n if (error) return { success: false, error: error.message };\n return { success: true, data: data as Workbook };\n}\n\n/**\n * Soft delete — the workbook is tombstoned, not destroyed, and its snapshots stay\n * with it. Every read path filters `deleted_at is null`. Pair with\n * restoreWorkbook for undo.\n */\nexport async function deleteWorkbook(\n workbookId: string,\n): Promise<ServiceResult<true>> {\n const { error } = await tryWriteOne(\n db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .update({ deleted_at: new Date().toISOString() })\n .eq(\"id\", workbookId)\n .select(\"id\"),\n { action: \"delete\", noun: \"workbook\" },\n );\n if (error) return { success: false, error: error.message };\n return { success: true, data: true };\n}\n\n/**\n * Archive (delete means archive: never a hard delete) — only for rolling back a workbook this very flow just created and\n * failed to populate (see the import path in app/(core)/workbooks/page.tsx).\n * Never use this for a user-initiated delete: that is deleteWorkbook, which\n * tombstones and stays recoverable.\n */\nexport async function discardFailedWorkbook(\n workbookId: string,\n): Promise<ServiceResult<true>> {\n const { error } = await tryWriteOne(\n db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .update({ deleted_at: new Date().toISOString() })\n .eq(\"id\", workbookId)\n .select(\"id\"),\n { action: \"archive\", noun: \"workbook\" },\n );\n if (error) return { success: false, error: error.message };\n return { success: true, data: true };\n}\n\nexport async function restoreWorkbook(\n workbookId: string,\n): Promise<ServiceResult<true>> {\n const { error } = await tryWriteOne(\n db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .update({ deleted_at: null })\n .eq(\"id\", workbookId)\n .select(\"id\"),\n { action: \"restore\", noun: \"workbook\" },\n );\n if (error) return { success: false, error: error.message };\n return { success: true, data: true };\n}\n\n/** Trash view: the caller's own archived workbooks (RLS decides what is readable). */\nexport async function listTrashedWorkbooks(): Promise<\n ServiceResult<Workbook[]>\n> {\n const { data, error } = await db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .select(\"*\")\n .not(\"deleted_at\", \"is\", null)\n .order(\"deleted_at\", { ascending: false });\n if (error) return { success: false, error: error.message };\n return { success: true, data: (data ?? []) as Workbook[] };\n}\n\n/**\n * An independent copy of a workbook: its own row in the original's organization\n * and project, with the latest snapshot copied over. Editing the copy never\n * touches the original.\n */\nexport async function copyWorkbook(\n workbookId: string,\n name?: string,\n): Promise<ServiceResult<Workbook>> {\n const original = await getWorkbook(workbookId);\n if (!original.success) return original;\n const row = original.data;\n if (!row.organization_id) {\n return {\n success: false,\n error: \"This workbook has no organization and cannot be copied.\",\n };\n }\n const created = await createWorkbook({\n name: name ?? `Copy of ${row.workbook_name}`,\n description: row.description ?? null,\n organizationId: row.organization_id,\n projectId: row.project_id ?? null,\n });\n if (!created.success) return created;\n const latest = await getLatestSnapshot(workbookId);\n if (!latest.success) return latest;\n if (latest.data) {\n const saved = await saveSnapshot({\n workbookId: created.data.id,\n snapshot: latest.data.snapshot,\n label: latest.data.label,\n origin: \"autosave\",\n });\n if (!saved.success) return saved;\n }\n return created;\n}\n\n// ─── snapshots (workbook content) ────────────────────────────────────────────\n\n/**\n * Latest-snapshot fetch — what an opened workbook hydrates from. Returns\n * `data: null` (success path) when the workbook has no snapshots yet (newly\n * created, never saved). Distinguish \"no snapshot\" from \"load error\" by\n * checking `result.data === null`.\n */\nexport async function getLatestSnapshot(\n workbookId: string,\n): Promise<ServiceResult<WorkbookSnapshot | null>> {\n const { data, error } = await db()\n .schema(\"workbench\")\n .from(\"udt_workbook_snapshots\")\n .select(\"*\")\n .eq(\"workbook_id\", workbookId)\n .order(\"created_at\", { ascending: false })\n .limit(1)\n .maybeSingle();\n if (error) return { success: false, error: error.message };\n return { success: true, data: (data ?? null) as WorkbookSnapshot | null };\n}\n\nexport type SaveSnapshotArgs = {\n workbookId: string;\n snapshot: unknown; // opaque to us — editor library decides the shape\n label?: string | null;\n origin?: WorkbookSnapshotOrigin;\n};\n\nexport async function saveSnapshot(\n args: SaveSnapshotArgs,\n): Promise<ServiceResult<WorkbookSnapshot>> {\n const { data: userData } = await getClaimsUser(db());\n // A snapshot is a component of its workbook and carries the workbook's organization.\n const { data: workbook, error: workbookError } = await db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .select(\"organization_id\")\n .eq(\"id\", args.workbookId)\n .single();\n if (workbookError) return { success: false, error: workbookError.message };\n const { data, error } = await db()\n .schema(\"workbench\")\n .from(\"udt_workbook_snapshots\")\n .insert({\n organization_id: workbook.organization_id,\n workbook_id: args.workbookId,\n snapshot: args.snapshot as never,\n label: args.label ?? null,\n origin: args.origin ?? \"autosave\",\n created_by: userData?.user?.id ?? null,\n })\n .select(\"*\")\n .single();\n if (error) return { success: false, error: error.message };\n\n // Touch the parent workbook's updated_at so list views can sort by recency\n // without scanning snapshots. Best-effort — failure here is harmless.\n // write-lands-exempt: best-effort recency stamp on the parent workbook; the snapshot insert above is the real write\n await db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .update({ updated_at: new Date().toISOString() })\n .eq(\"id\", args.workbookId);\n\n return { success: true, data: data as WorkbookSnapshot };\n}\n\nexport async function listSnapshots(\n workbookId: string,\n limit = 50,\n): Promise<ServiceResult<WorkbookSnapshot[]>> {\n const { data, error } = await db()\n .schema(\"workbench\")\n .from(\"udt_workbook_snapshots\")\n // component-created-by-ok: append-only snapshot with NO updated_by column and NO parent-rewrite trigger — created_by is the author the client wrote at insert (the only person field this table has)\n .select(\"id, workbook_id, label, origin, created_by, created_at\")\n .eq(\"workbook_id\", workbookId)\n .order(\"created_at\", { ascending: false })\n .limit(limit);\n if (error) return { success: false, error: error.message };\n return { success: true, data: (data ?? []) as WorkbookSnapshot[] };\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/_work/aidream/aidream/apps/shared/office/dist/chunk-ZDZCCOUB.cjs","../src/sheets/ports.ts","../src/sheets/workbook-service.ts"],"names":["error"],"mappings":"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","file":"/home/runner/_work/aidream/aidream/apps/shared/office/dist/chunk-ZDZCCOUB.cjs","sourcesContent":[null,"/**\n * The host ports of the Sheets model and io.\n *\n * The package owns the workbook service, the page-setup store and the import flow, but three things\n * belong to the app that mounts them: the database client (a package owns no client), the\n * organization a write acts in (the active-organization ladder is app state) and the file store that\n * keeps a lossless copy of an imported file. The host states them once at start-up with\n * `configureSheetsHost`. State sits on a `globalThis` slot so a dual-bundled copy of the package\n * still sees one host. An unconfigured port THROWS naming itself (nothing fails silently).\n */\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\n\n/** A Supabase client over any schema: the package cannot import the app's generated database types. */\nexport type SheetsSupabase = SupabaseClient<any, any, any>;\n\nexport interface SheetsHostPorts {\n /** The app's database client (signed in as the person). */\n getSupabase(): SheetsSupabase;\n /** The organization a write acts in: the record's own when given, otherwise the active one. */\n ensureOrgId(organizationId: string | null): Promise<string>;\n /** Keep the original of an imported spreadsheet in the file store, in `folderPath`. */\n uploadOriginalFile(file: File, options: { folderPath: string }): Promise<{ fileId: string }>;\n}\n\nconst SLOT = Symbol.for(\"ai-matrx.office.sheets-host\");\ntype Slot = { ports?: SheetsHostPorts };\nconst slot = (): Slot => {\n const g = globalThis as Record<symbol, Slot | undefined>;\n return (g[SLOT] ??= {});\n};\n\nexport function configureSheetsHost(ports: SheetsHostPorts): void {\n slot().ports = ports;\n}\n\nexport function getSheetsHost(): SheetsHostPorts {\n const ports = slot().ports;\n if (!ports) throw new Error(\"@ai-matrx/office/sheets: configureSheetsHost(ports) was not called before the workbook service ran.\");\n return ports;\n}\n","/**\n * Workbook service — typed wrappers for `udt_workbooks` + `udt_workbook_snapshots`.\n *\n * Workbooks are the lossless-spreadsheet surface (P4): metadata in\n * `udt_workbooks`, content state in append-only `udt_workbook_snapshots`. The\n * editor (Univer or equivalent) hydrates from the LATEST snapshot and writes\n * a new snapshot per save (debounced on the client — see WorkbookEditor).\n *\n * What lives elsewhere:\n * - Sharing / permissions: features/sharing/ + `has_permission(...)` RLS\n * - Realtime: @ai-matrx/office/univer/collab useWorkbookRealtime\n * - Component: the Sheets editor\n */\nimport { requireOrganizationContext } from \"@ai-matrx/api\";\nimport { getClaimsUser } from \"@ai-matrx/auth\";\nimport { tryWriteOne, writeOneRow } from \"@ai-matrx/data/db\";\nimport type { ServiceResult } from \"@ai-matrx/records/core\";\n\nimport type { Workbook, WorkbookSnapshot, WorkbookSnapshotOrigin } from \"../core/types\";\nimport { getSheetsHost } from \"./ports\";\n\n/** The app's database client, read through the host port at call time. */\nconst db = () => getSheetsHost().getSupabase();\n\n// ─── workbooks ────────────────────────────────────────────────────────────────\n\nconst WORKBOOK_SOURCE_LABELS: Record<string, string> = {\n created: \"Created\",\n imported_xlsx: \"Imported Excel\",\n imported_gsheet: \"Imported from Google Sheets\",\n imported_csv: \"Imported CSV\",\n linked_gsheet: \"Imported from Google Sheets\",\n};\n\n/** Honest, human label for `udt_workbooks.source`. */\nexport function workbookSourceLabel(source: string): string {\n return WORKBOOK_SOURCE_LABELS[source] ?? source.replace(/_/g, \" \");\n}\n\nexport type CreateWorkbookArgs = {\n name: string;\n description?: string | null;\n /** Origin label for the workbook itself, mirrors `udt_workbooks.source`. */\n source?:\n | \"created\"\n | \"imported_xlsx\"\n | \"imported_gsheet\"\n | \"imported_csv\"\n | \"linked_gsheet\";\n organizationId: string;\n projectId?: string | null;\n taskId?: string | null;\n isPublic?: boolean;\n /**\n * cld_files.id of the source upload (XLSX / CSV blob). Set on the import\n * flow so the lossless original is recoverable; FK is ON DELETE SET NULL,\n * so deleting the file just nulls the link — the workbook survives.\n */\n originalFileId?: string | null;\n};\n\nexport async function createWorkbook(\n args: CreateWorkbookArgs,\n): Promise<ServiceResult<Workbook>> {\n let organizationId: string;\n try {\n organizationId = requireOrganizationContext(undefined, args.organizationId);\n } catch (error) {\n return {\n success: false,\n error:\n error instanceof Error\n ? error.message\n : \"Select an organization before creating a workbook.\",\n };\n }\n\n const { data: userData, error: userErr } = await getClaimsUser(db());\n if (userErr || !userData?.user) {\n return {\n success: false,\n error: userErr?.message ?? \"not authenticated\",\n };\n }\n\n const { data, error } = await db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .insert({\n workbook_name: args.name,\n description: args.description ?? null,\n source: args.source ?? \"created\",\n organization_id: organizationId,\n project_id: args.projectId ?? null,\n task_id: args.taskId ?? null,\n original_file_id: args.originalFileId ?? null,\n // CANONICAL columns (workbench_udt_canonical_step1). `visibility` is the\n // source of truth; legacy `is_public` is derived from it by the\n // workbench._bridge_legacy_owner trigger. A new workbook defaults to\n // `internal` — org work product, not an individual's private thing\n // (db-rules §6) — unless the caller explicitly asked for public.\n created_by: userData.user.id,\n visibility: args.isPublic ? \"public\" : \"internal\",\n })\n .select(\"*\")\n .single();\n if (error) return { success: false, error: error.message };\n return { success: true, data: data as Workbook };\n}\n\nexport async function listAccessibleWorkbooks(): Promise<\n ServiceResult<Workbook[]>\n> {\n // RLS handles owner / published-to-the-web / shared access.\n const { data, error } = await db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .select(\"*\")\n .is(\"deleted_at\", null)\n .order(\"updated_at\", { ascending: false });\n if (error) return { success: false, error: error.message };\n return { success: true, data: (data ?? []) as Workbook[] };\n}\n\nexport async function getWorkbook(\n workbookId: string,\n): Promise<ServiceResult<Workbook>> {\n const { data, error } = await db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .select(\"*\")\n .eq(\"id\", workbookId)\n .is(\"deleted_at\", null)\n .single();\n if (error) return { success: false, error: error.message };\n return { success: true, data: data as Workbook };\n}\n\nexport async function renameWorkbook(\n workbookId: string,\n name: string,\n): Promise<ServiceResult<Workbook>> {\n const { data, error } = await writeOneRow(\n db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .update({ workbook_name: name, updated_at: new Date().toISOString() })\n .eq(\"id\", workbookId)\n .select(\"*\"),\n { action: \"update\", noun: \"workbook\" },\n );\n if (error) return { success: false, error: error.message };\n return { success: true, data: data as Workbook };\n}\n\n/**\n * Move the workbook into a project (or out of one with `null`). Sibling of\n * `renameWorkbook`: the one named setter for `project_id`.\n */\nexport async function moveWorkbook(\n workbookId: string,\n projectId: string | null,\n): Promise<ServiceResult<Workbook>> {\n const { data, error } = await writeOneRow(\n db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .update({ project_id: projectId, updated_at: new Date().toISOString() })\n .eq(\"id\", workbookId)\n .select(\"*\"),\n { action: \"update\", noun: \"workbook\" },\n );\n if (error) return { success: false, error: error.message };\n return { success: true, data: data as Workbook };\n}\n\n/**\n * Rewrite the workbook's description. Sibling of `renameWorkbook` — the two\n * human-authored fields on `udt_workbooks` each get a named setter so callers\n * never hand-roll a `.from(\"udt_workbooks\").update(...)`. Pass `null` (or an\n * empty string) to clear it.\n *\n * Written by the import flow at creation (\"Imported from <file>\") and, since\n * the workbooks surface became agent-writable, by the `workbook_description`\n * write target on `/workbooks/[id]`.\n */\nexport async function updateWorkbookDescription(\n workbookId: string,\n description: string | null,\n): Promise<ServiceResult<Workbook>> {\n const { data, error } = await writeOneRow(\n db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .update({\n description: description && description.length > 0 ? description : null,\n updated_at: new Date().toISOString(),\n })\n .eq(\"id\", workbookId)\n .select(\"*\"),\n { action: \"update\", noun: \"workbook\" },\n );\n if (error) return { success: false, error: error.message };\n return { success: true, data: data as Workbook };\n}\n\n/**\n * Soft delete — the workbook is tombstoned, not destroyed, and its snapshots stay\n * with it. Every read path filters `deleted_at is null`. Pair with\n * restoreWorkbook for undo.\n */\nexport async function deleteWorkbook(\n workbookId: string,\n): Promise<ServiceResult<true>> {\n const { error } = await tryWriteOne(\n db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .update({ deleted_at: new Date().toISOString() })\n .eq(\"id\", workbookId)\n .select(\"id\"),\n { action: \"delete\", noun: \"workbook\" },\n );\n if (error) return { success: false, error: error.message };\n return { success: true, data: true };\n}\n\n/**\n * Archive (delete means archive: never a hard delete) — only for rolling back a workbook this very flow just created and\n * failed to populate (see the import path in app/(core)/workbooks/page.tsx).\n * Never use this for a user-initiated delete: that is deleteWorkbook, which\n * tombstones and stays recoverable.\n */\nexport async function discardFailedWorkbook(\n workbookId: string,\n): Promise<ServiceResult<true>> {\n const { error } = await tryWriteOne(\n db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .update({ deleted_at: new Date().toISOString() })\n .eq(\"id\", workbookId)\n .select(\"id\"),\n { action: \"archive\", noun: \"workbook\" },\n );\n if (error) return { success: false, error: error.message };\n return { success: true, data: true };\n}\n\nexport async function restoreWorkbook(\n workbookId: string,\n): Promise<ServiceResult<true>> {\n const { error } = await tryWriteOne(\n db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .update({ deleted_at: null })\n .eq(\"id\", workbookId)\n .select(\"id\"),\n { action: \"restore\", noun: \"workbook\" },\n );\n if (error) return { success: false, error: error.message };\n return { success: true, data: true };\n}\n\n/** Trash view: the caller's own archived workbooks (RLS decides what is readable). */\nexport async function listTrashedWorkbooks(): Promise<\n ServiceResult<Workbook[]>\n> {\n const { data, error } = await db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .select(\"*\")\n .not(\"deleted_at\", \"is\", null)\n .order(\"deleted_at\", { ascending: false });\n if (error) return { success: false, error: error.message };\n return { success: true, data: (data ?? []) as Workbook[] };\n}\n\n/**\n * An independent copy of a workbook: its own row in the original's organization\n * and project, with the latest snapshot copied over. Editing the copy never\n * touches the original.\n */\nexport async function copyWorkbook(\n workbookId: string,\n name?: string,\n): Promise<ServiceResult<Workbook>> {\n const original = await getWorkbook(workbookId);\n if (!original.success) return original;\n const row = original.data;\n if (!row.organization_id) {\n return {\n success: false,\n error: \"This workbook has no organization and cannot be copied.\",\n };\n }\n const created = await createWorkbook({\n name: name ?? `Copy of ${row.workbook_name}`,\n description: row.description ?? null,\n organizationId: row.organization_id,\n projectId: row.project_id ?? null,\n });\n if (!created.success) return created;\n const latest = await getLatestSnapshot(workbookId);\n if (!latest.success) return latest;\n if (latest.data) {\n const saved = await saveSnapshot({\n workbookId: created.data.id,\n snapshot: latest.data.snapshot,\n label: latest.data.label,\n origin: \"autosave\",\n });\n if (!saved.success) return saved;\n }\n return created;\n}\n\n// ─── snapshots (workbook content) ────────────────────────────────────────────\n\n/**\n * Latest-snapshot fetch — what an opened workbook hydrates from. Returns\n * `data: null` (success path) when the workbook has no snapshots yet (newly\n * created, never saved). Distinguish \"no snapshot\" from \"load error\" by\n * checking `result.data === null`.\n */\nexport async function getLatestSnapshot(\n workbookId: string,\n): Promise<ServiceResult<WorkbookSnapshot | null>> {\n const { data, error } = await db()\n .schema(\"workbench\")\n .from(\"udt_workbook_snapshots\")\n .select(\"*\")\n .eq(\"workbook_id\", workbookId)\n .order(\"created_at\", { ascending: false })\n .limit(1)\n .maybeSingle();\n if (error) return { success: false, error: error.message };\n return { success: true, data: (data ?? null) as WorkbookSnapshot | null };\n}\n\nexport type SaveSnapshotArgs = {\n workbookId: string;\n snapshot: unknown; // opaque to us — editor library decides the shape\n label?: string | null;\n origin?: WorkbookSnapshotOrigin;\n};\n\nexport async function saveSnapshot(\n args: SaveSnapshotArgs,\n): Promise<ServiceResult<WorkbookSnapshot>> {\n const { data: userData } = await getClaimsUser(db());\n // A snapshot is a component of its workbook and carries the workbook's organization.\n const { data: workbook, error: workbookError } = await db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .select(\"organization_id\")\n .eq(\"id\", args.workbookId)\n .single();\n if (workbookError) return { success: false, error: workbookError.message };\n const { data, error } = await db()\n .schema(\"workbench\")\n .from(\"udt_workbook_snapshots\")\n .insert({\n organization_id: workbook.organization_id,\n workbook_id: args.workbookId,\n snapshot: args.snapshot as never,\n label: args.label ?? null,\n origin: args.origin ?? \"autosave\",\n created_by: userData?.user?.id ?? null,\n })\n .select(\"*\")\n .single();\n if (error) return { success: false, error: error.message };\n\n // Touch the parent workbook's updated_at so list views can sort by recency\n // without scanning snapshots. Best-effort — failure here is harmless.\n // write-lands-exempt: best-effort recency stamp on the parent workbook; the snapshot insert above is the real write\n await db()\n .schema(\"workbench\")\n .from(\"udt_workbooks\")\n .update({ updated_at: new Date().toISOString() })\n .eq(\"id\", args.workbookId);\n\n return { success: true, data: data as WorkbookSnapshot };\n}\n\nexport async function listSnapshots(\n workbookId: string,\n limit = 50,\n): Promise<ServiceResult<WorkbookSnapshot[]>> {\n const { data, error } = await db()\n .schema(\"workbench\")\n .from(\"udt_workbook_snapshots\")\n // component-created-by-ok: append-only snapshot with NO updated_by column and NO parent-rewrite trigger — created_by is the author the client wrote at insert (the only person field this table has)\n .select(\"id, workbook_id, label, origin, created_by, created_at\")\n .eq(\"workbook_id\", workbookId)\n .order(\"created_at\", { ascending: false })\n .limit(limit);\n if (error) return { success: false, error: error.message };\n return { success: true, data: (data ?? []) as WorkbookSnapshot[] };\n}\n"]}
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/docs/ports.ts
21
+ var ports_exports = {};
22
+ __export(ports_exports, {
23
+ configureDocsHost: () => configureDocsHost,
24
+ getDocsHost: () => getDocsHost
25
+ });
26
+ module.exports = __toCommonJS(ports_exports);
27
+ var SLOT = /* @__PURE__ */ Symbol.for("ai-matrx.office.docs-host");
28
+ var slot = () => {
29
+ const g = globalThis;
30
+ return g[SLOT] ??= {};
31
+ };
32
+ function configureDocsHost(ports) {
33
+ slot().ports = ports;
34
+ }
35
+ function getDocsHost() {
36
+ const ports = slot().ports;
37
+ if (!ports) throw new Error("@ai-matrx/office/docs: configureDocsHost(ports) was not called before the document service ran.");
38
+ return ports;
39
+ }
40
+ //# sourceMappingURL=docs-host.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/docs/ports.ts"],"sourcesContent":["/**\n * The host ports of the Docs model and io.\n *\n * The package owns the document service, the open-document model and the converters, but two things\n * belong to the app that mounts them: the database client (a package owns no client) and the server\n * request that converts a .docx to .odt / .rtf (the app owns the server URL, auth and error capture).\n * The host states them once at start-up with `configureDocsHost`. State sits on a `globalThis` slot\n * so a dual-bundled copy of the package still sees one host. An unconfigured port THROWS naming\n * itself (nothing fails silently).\n */\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\n\n/** A Supabase client over any schema: the package cannot import the app's generated database types. */\nexport type DocsSupabase = SupabaseClient<any, any, any>;\n\nexport interface DocsHostPorts {\n /** The app's database client (signed in as the person). */\n getSupabase(): DocsSupabase;\n /** An authenticated request to the Matrx server returning the raw `Response` (the open-format converter). */\n requestRaw(path: string, init: { method: \"POST\"; body: FormData }): Promise<Response>;\n}\n\nconst SLOT = Symbol.for(\"ai-matrx.office.docs-host\");\ntype Slot = { ports?: DocsHostPorts };\nconst slot = (): Slot => {\n const g = globalThis as Record<symbol, Slot | undefined>;\n return (g[SLOT] ??= {});\n};\n\nexport function configureDocsHost(ports: DocsHostPorts): void {\n slot().ports = ports;\n}\n\nexport function getDocsHost(): DocsHostPorts {\n const ports = slot().ports;\n if (!ports) throw new Error(\"@ai-matrx/office/docs: configureDocsHost(ports) was not called before the document service ran.\");\n return ports;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsBA,IAAM,OAAO,uBAAO,IAAI,2BAA2B;AAEnD,IAAM,OAAO,MAAY;AACvB,QAAM,IAAI;AACV,SAAQ,EAAE,IAAI,MAAM,CAAC;AACvB;AAEO,SAAS,kBAAkB,OAA4B;AAC5D,OAAK,EAAE,QAAQ;AACjB;AAEO,SAAS,cAA6B;AAC3C,QAAM,QAAQ,KAAK,EAAE;AACrB,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,iGAAiG;AAC7H,SAAO;AACT;","names":[]}
@@ -0,0 +1,28 @@
1
+ import { SupabaseClient } from '@supabase/supabase-js';
2
+
3
+ /**
4
+ * The host ports of the Docs model and io.
5
+ *
6
+ * The package owns the document service, the open-document model and the converters, but two things
7
+ * belong to the app that mounts them: the database client (a package owns no client) and the server
8
+ * request that converts a .docx to .odt / .rtf (the app owns the server URL, auth and error capture).
9
+ * The host states them once at start-up with `configureDocsHost`. State sits on a `globalThis` slot
10
+ * so a dual-bundled copy of the package still sees one host. An unconfigured port THROWS naming
11
+ * itself (nothing fails silently).
12
+ */
13
+
14
+ /** A Supabase client over any schema: the package cannot import the app's generated database types. */
15
+ type DocsSupabase = SupabaseClient<any, any, any>;
16
+ interface DocsHostPorts {
17
+ /** The app's database client (signed in as the person). */
18
+ getSupabase(): DocsSupabase;
19
+ /** An authenticated request to the Matrx server returning the raw `Response` (the open-format converter). */
20
+ requestRaw(path: string, init: {
21
+ method: "POST";
22
+ body: FormData;
23
+ }): Promise<Response>;
24
+ }
25
+ declare function configureDocsHost(ports: DocsHostPorts): void;
26
+ declare function getDocsHost(): DocsHostPorts;
27
+
28
+ export { type DocsHostPorts, type DocsSupabase, configureDocsHost, getDocsHost };
@@ -0,0 +1,28 @@
1
+ import { SupabaseClient } from '@supabase/supabase-js';
2
+
3
+ /**
4
+ * The host ports of the Docs model and io.
5
+ *
6
+ * The package owns the document service, the open-document model and the converters, but two things
7
+ * belong to the app that mounts them: the database client (a package owns no client) and the server
8
+ * request that converts a .docx to .odt / .rtf (the app owns the server URL, auth and error capture).
9
+ * The host states them once at start-up with `configureDocsHost`. State sits on a `globalThis` slot
10
+ * so a dual-bundled copy of the package still sees one host. An unconfigured port THROWS naming
11
+ * itself (nothing fails silently).
12
+ */
13
+
14
+ /** A Supabase client over any schema: the package cannot import the app's generated database types. */
15
+ type DocsSupabase = SupabaseClient<any, any, any>;
16
+ interface DocsHostPorts {
17
+ /** The app's database client (signed in as the person). */
18
+ getSupabase(): DocsSupabase;
19
+ /** An authenticated request to the Matrx server returning the raw `Response` (the open-format converter). */
20
+ requestRaw(path: string, init: {
21
+ method: "POST";
22
+ body: FormData;
23
+ }): Promise<Response>;
24
+ }
25
+ declare function configureDocsHost(ports: DocsHostPorts): void;
26
+ declare function getDocsHost(): DocsHostPorts;
27
+
28
+ export { type DocsHostPorts, type DocsSupabase, configureDocsHost, getDocsHost };
@@ -0,0 +1,19 @@
1
+ // src/docs/ports.ts
2
+ var SLOT = /* @__PURE__ */ Symbol.for("ai-matrx.office.docs-host");
3
+ var slot = () => {
4
+ const g = globalThis;
5
+ return g[SLOT] ??= {};
6
+ };
7
+ function configureDocsHost(ports) {
8
+ slot().ports = ports;
9
+ }
10
+ function getDocsHost() {
11
+ const ports = slot().ports;
12
+ if (!ports) throw new Error("@ai-matrx/office/docs: configureDocsHost(ports) was not called before the document service ran.");
13
+ return ports;
14
+ }
15
+ export {
16
+ configureDocsHost,
17
+ getDocsHost
18
+ };
19
+ //# sourceMappingURL=docs-host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/docs/ports.ts"],"sourcesContent":["/**\n * The host ports of the Docs model and io.\n *\n * The package owns the document service, the open-document model and the converters, but two things\n * belong to the app that mounts them: the database client (a package owns no client) and the server\n * request that converts a .docx to .odt / .rtf (the app owns the server URL, auth and error capture).\n * The host states them once at start-up with `configureDocsHost`. State sits on a `globalThis` slot\n * so a dual-bundled copy of the package still sees one host. An unconfigured port THROWS naming\n * itself (nothing fails silently).\n */\nimport type { SupabaseClient } from \"@supabase/supabase-js\";\n\n/** A Supabase client over any schema: the package cannot import the app's generated database types. */\nexport type DocsSupabase = SupabaseClient<any, any, any>;\n\nexport interface DocsHostPorts {\n /** The app's database client (signed in as the person). */\n getSupabase(): DocsSupabase;\n /** An authenticated request to the Matrx server returning the raw `Response` (the open-format converter). */\n requestRaw(path: string, init: { method: \"POST\"; body: FormData }): Promise<Response>;\n}\n\nconst SLOT = Symbol.for(\"ai-matrx.office.docs-host\");\ntype Slot = { ports?: DocsHostPorts };\nconst slot = (): Slot => {\n const g = globalThis as Record<symbol, Slot | undefined>;\n return (g[SLOT] ??= {});\n};\n\nexport function configureDocsHost(ports: DocsHostPorts): void {\n slot().ports = ports;\n}\n\nexport function getDocsHost(): DocsHostPorts {\n const ports = slot().ports;\n if (!ports) throw new Error(\"@ai-matrx/office/docs: configureDocsHost(ports) was not called before the document service ran.\");\n return ports;\n}\n"],"mappings":";AAsBA,IAAM,OAAO,uBAAO,IAAI,2BAA2B;AAEnD,IAAM,OAAO,MAAY;AACvB,QAAM,IAAI;AACV,SAAQ,EAAE,IAAI,MAAM,CAAC;AACvB;AAEO,SAAS,kBAAkB,OAA4B;AAC5D,OAAK,EAAE,QAAQ;AACjB;AAEO,SAAS,cAA6B;AAC3C,QAAM,QAAQ,KAAK,EAAE;AACrB,MAAI,CAAC,MAAO,OAAM,IAAI,MAAM,iGAAiG;AAC7H,SAAO;AACT;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/aidream/aidream/apps/shared/office/dist/docs-io.cjs","../src/docs/univer-doc-to-markdown.ts","../src/docs/import-document-file.ts","../src/docs/export-open-formats.ts"],"names":["out"],"mappings":"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","file":"/home/runner/work/aidream/aidream/apps/shared/office/dist/docs-io.cjs","sourcesContent":[null,"/**\n * univerDocToMarkdown — the reverse of `markdown-to-univer-doc.ts`.\n *\n * Why it exists: the canvas pane for a cloud document (`udt_document`) offers a\n * `Source` tab, and the rule in `features/canvas/core/canvasSource.ts` is that\n * Source shows THE ITEM'S OWN SOURCE — for a document, its markdown — never the\n * redux envelope. A document's content lives as an opaque Univer\n * `IDocumentData` snapshot in `workbench.udt_document_snapshots`, so something\n * has to read it back out. This is that something, and it is the ONLY one:\n * never re-implement a second snapshot reader.\n *\n * What it reads (the half `markdownToUniverDoc` writes):\n * - `body.dataStream` — the text; `\\r` terminates a paragraph, a trailing\n * `\\n` terminates the section.\n * - `body.paragraphs` — one entry per `\\r` (`startIndex` = the `\\r` offset).\n * - `body.textRuns` — `{ st, ed, ts }` style overlays. `ts.bl` + a heading\n * font size is how `markdownToUniverDoc` encodes\n * `#`..`######`, so the level is recovered from `fs`.\n *\n * Fidelity is deliberately MODEST and honest: headings, bold, italic,\n * strikethrough and inline/monospace code round-trip; everything else comes\n * back as the plain text the user actually sees. A document typed by hand in\n * Univer never had markdown syntax in the first place — printing its text is\n * the truthful answer, not a reconstruction.\n *\n * PURE — no React, no Redux, no IO.\n */\n\nconst TABLE_CELL_SEP = \" | \";\n\n// Univer's DataStreamTreeTokenType control characters (literals: no Univer runtime here).\nconst TABLE_START = \"\\x1A\";\nconst TABLE_ROW_START = \"\\x1B\";\nconst TABLE_CELL_START = \"\\x1C\";\nconst TABLE_CELL_END = \"\\x1D\";\nconst TABLE_ROW_END = \"\\x0E\";\nconst TABLE_END = \"\\x0F\";\nconst LINK_START = \"\\x1F\";\nconst LINK_END = \"\\x1E\";\n\ninterface CustomRangeLike {\n startIndex?: number;\n endIndex?: number;\n rangeType?: number;\n properties?: { url?: unknown };\n}\n\n/** The Univer-native shapes (real tables, hyperlink ranges) written back as markdown. */\nfunction tableMarkdown(rows: string[][]): string[] {\n const width = Math.max(1, ...rows.map((r) => r.length));\n const cell = (r: string[], i: number) => (r[i] ?? \"\").replace(/^\\*\\*(.*)\\*\\*$/, \"$1\");\n const line = (r: string[], plain: boolean) =>\n `| ${Array.from({ length: width }, (_, i) => (plain ? cell(r, i) : (r[i] ?? \"\"))).join(\" | \")} |`;\n const out = [line(rows[0]!, true), `|${\" --- |\".repeat(width)}`];\n for (const r of rows.slice(1)) out.push(line(r, false));\n return out;\n}\n\ninterface TextRunLike {\n st?: number;\n ed?: number;\n ts?: {\n bl?: number;\n it?: number;\n ff?: string;\n fs?: number;\n st?: { s?: number } | number;\n };\n}\n\n/** Inverse of markdown-to-univer-doc's HEADING_FONT_SIZE. */\nexport const HEADING_LEVEL_BY_FONT_SIZE: Record<number, number> = {\n 26: 1,\n 22: 2,\n 18: 3,\n 16: 4,\n 14: 5,\n 13: 6,\n};\n\nfunction asArray<T>(value: unknown): T[] {\n return Array.isArray(value) ? (value as T[]) : [];\n}\n\nconst TRUE_ENOUGH = (v: unknown): boolean => v === 1 || v === true;\n\nfunction isStruck(ts: TextRunLike[\"ts\"]): boolean {\n if (!ts) return false;\n const st = ts.st;\n if (typeof st === \"number\") return TRUE_ENOUGH(st);\n return TRUE_ENOUGH(st?.s);\n}\n\n/** The style that applies to the whole paragraph, if the runs agree on one. */\nfunction paragraphHeadingLevel(runs: TextRunLike[]): number | null {\n for (const run of runs) {\n const fs = run.ts?.fs;\n if (typeof fs === \"number\" && TRUE_ENOUGH(run.ts?.bl)) {\n const level = HEADING_LEVEL_BY_FONT_SIZE[fs];\n if (level) return level;\n }\n }\n return null;\n}\n\nfunction emphasize(text: string, ts: TextRunLike[\"ts\"], inHeading: boolean): string {\n if (!text) return text;\n // Never double-mark: a heading's own runs are bold by construction.\n const leading = text.match(/^\\s*/)?.[0] ?? \"\";\n const trailing = text.match(/\\s*$/)?.[0] ?? \"\";\n let core = text.slice(leading.length, text.length - trailing.length);\n if (!core) return text;\n\n const mono = typeof ts?.ff === \"string\" && /mono/i.test(ts.ff);\n if (mono) core = `\\`${core}\\``;\n else {\n if (TRUE_ENOUGH(ts?.it)) core = `*${core}*`;\n if (TRUE_ENOUGH(ts?.bl) && !inHeading) core = `**${core}**`;\n }\n if (isStruck(ts)) core = `~~${core}~~`;\n return `${leading}${core}${trailing}`;\n}\n\n// An inline picture: one CUSTOM_BLOCK token in the stream, its drawing in `snapshot.drawings`.\nconst CUSTOM_BLOCK = \"\\b\";\n\ninterface DrawingLike {\n source?: unknown;\n title?: unknown;\n description?: unknown;\n docTransform?: { size?: { width?: unknown; height?: unknown } };\n}\n\n/**\n * A picture's markdown source. Our own pictures are `matrx-file:<id>`; the\n * drawn size rides as a `#WxH` fragment so a round trip keeps the shape.\n */\nexport function imageMarkdownSource(drawing: DrawingLike): string | null {\n const source = typeof drawing.source === \"string\" ? drawing.source : \"\";\n if (!source || source.startsWith(\"data:\")) return null;\n const w = Number(drawing.docTransform?.size?.width);\n const h = Number(drawing.docTransform?.size?.height);\n return Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0 && !source.includes(\"#\")\n ? `${source}#${Math.round(w)}x${Math.round(h)}`\n : source;\n}\n\n/** Every picture in a snapshot, as its markdown source (`matrx-file:<id>#WxH` or a URL). */\nexport function documentImageSources(snapshot: unknown): string[] {\n const drawings = ((snapshot as { drawings?: unknown } | null)?.drawings ?? {}) as Record<string, DrawingLike>;\n const body = (snapshot as { body?: { customBlocks?: unknown } } | null)?.body;\n return asArray<{ blockId?: unknown }>(body?.customBlocks)\n .map((b) => (typeof b.blockId === \"string\" && drawings[b.blockId] ? imageMarkdownSource(drawings[b.blockId]!) : null))\n .filter((s): s is string => !!s);\n}\n\nexport interface DocToMarkdownOptions {\n /**\n * Where a picture's markdown points. Return null to leave it out (the caller\n * names what was left out). Default: the stored source.\n */\n imageUrl?: (source: string) => string | null;\n}\n\n/**\n * Read a Univer document snapshot back as markdown. Returns `\"\"` for an empty\n * or unreadable snapshot so the caller can say \"no source\" honestly rather than\n * printing an envelope.\n */\nexport function univerDocToMarkdown(snapshot: unknown, options: DocToMarkdownOptions = {}): string {\n if (!snapshot || typeof snapshot !== \"object\") return \"\";\n const body = (snapshot as { body?: unknown }).body;\n if (!body || typeof body !== \"object\") return \"\";\n\n const dataStream = (body as { dataStream?: unknown }).dataStream;\n if (typeof dataStream !== \"string\" || !dataStream) return \"\";\n\n // Pictures: each CUSTOM_BLOCK token is read back as `![alt](source)`.\n const drawings = ((snapshot as { drawings?: unknown }).drawings ?? {}) as Record<string, DrawingLike>;\n const blocks = new Map<number, string>();\n for (const b of asArray<{ startIndex?: unknown; blockId?: unknown }>((body as { customBlocks?: unknown }).customBlocks)) {\n if (typeof b.startIndex !== \"number\" || typeof b.blockId !== \"string\") continue;\n const drawing = drawings[b.blockId];\n const source = drawing ? imageMarkdownSource(drawing) : null;\n if (!source || !drawing) continue;\n const url = options.imageUrl ? options.imageUrl(source) : source;\n if (!url) continue;\n const label = typeof drawing.title === \"string\" && drawing.title ? drawing.title\n : typeof drawing.description === \"string\" && drawing.description ? drawing.description : \"image\";\n blocks.set(b.startIndex, `![${label.replace(/[[\\]]/g, \"\")}](${url})`);\n }\n\n const runs = asArray<TextRunLike>((body as { textRuns?: unknown }).textRuns)\n .filter((r) => typeof r.st === \"number\" && typeof r.ed === \"number\")\n .sort((a, b) => (a.st as number) - (b.st as number));\n\n const links = new Map<number, string>();\n for (const r of asArray<CustomRangeLike>((body as { customRanges?: unknown }).customRanges)) {\n if (r.rangeType === 0 && typeof r.endIndex === \"number\" && typeof r.properties?.url === \"string\") {\n links.set(r.endIndex, r.properties.url);\n }\n }\n // A slice of the stream with its hyperlink tokens read back as `[text](url)`.\n const readSlice = (from: number, to: number): string => {\n let out = \"\";\n for (let k = from; k < to; k++) {\n const c = dataStream[k];\n if (c === LINK_START) out += \"[\";\n else if (c === LINK_END) out += `](${links.get(k) ?? \"\"})`;\n else if (c === CUSTOM_BLOCK) out += blocks.get(k) ?? \"\";\n else out += c;\n }\n return out;\n };\n\n const lines: string[] = [];\n let cursor = 0;\n let runIndex = 0;\n let tableRows: string[][] | null = null;\n\n // Paragraph boundaries are the `\\r`s in the stream itself — reading them from\n // the stream (rather than trusting `paragraphs`) keeps a snapshot written by\n // Univer's own editor, which may index differently, readable.\n for (let i = 0; i <= dataStream.length; i++) {\n const ch = dataStream[i];\n const atEnd = i === dataStream.length;\n if (!atEnd) {\n if (ch === TABLE_START) {\n tableRows = [];\n cursor = i + 1;\n continue;\n }\n if (tableRows && ch === TABLE_ROW_START) {\n tableRows.push([]);\n cursor = i + 1;\n continue;\n }\n if (tableRows && ch === TABLE_CELL_START) {\n tableRows[tableRows.length - 1]?.push(\"\");\n cursor = i + 1;\n continue;\n }\n if (tableRows && (ch === TABLE_CELL_END || ch === TABLE_ROW_END)) {\n cursor = i + 1;\n continue;\n }\n if (tableRows && ch === TABLE_END) {\n if (tableRows.length) lines.push(\"\", ...tableMarkdown(tableRows), \"\");\n tableRows = null;\n cursor = i + 1;\n continue;\n }\n }\n if (!atEnd && ch !== \"\\r\" && ch !== \"\\n\") continue;\n\n const start = cursor;\n const end = i;\n cursor = i + 1;\n if (end <= start) {\n if (ch === \"\\r\" && !tableRows) lines.push(\"\");\n if (atEnd) break;\n continue;\n }\n\n // Collect the runs overlapping this paragraph.\n const paragraphRuns: TextRunLike[] = [];\n let scan = runIndex;\n while (scan < runs.length && (runs[scan]!.st as number) < end) {\n if ((runs[scan]!.ed as number) > start) paragraphRuns.push(runs[scan]!);\n scan++;\n }\n while (\n runIndex < runs.length &&\n (runs[runIndex]!.ed as number) <= end\n ) {\n runIndex++;\n }\n\n const heading = paragraphHeadingLevel(paragraphRuns);\n let text = \"\";\n let at = start;\n for (const run of paragraphRuns) {\n const rs = Math.max(run.st as number, start);\n const re = Math.min(run.ed as number, end);\n if (rs > at) text += readSlice(at, rs);\n if (re > rs) {\n text += emphasize(readSlice(rs, re), run.ts, heading != null);\n }\n at = Math.max(at, re);\n }\n if (at < end) text += readSlice(at, end);\n\n if (tableRows) {\n // A table cell: its paragraphs join into the one cell, pipes escaped.\n const row = tableRows[tableRows.length - 1];\n if (row?.length) {\n const part = text.trim().replace(/\\|/g, \"\\\\|\");\n const last = row.length - 1;\n row[last] = row[last] ? `${row[last]} ${part}` : part;\n }\n } else {\n lines.push(heading ? `${\"#\".repeat(heading)} ${text.trim()}` : text);\n }\n if (atEnd) break;\n }\n\n // Univer keeps one paragraph per visual line; markdown wants a blank line\n // between blocks (but not inside a list or a table), and consecutive blank\n // paragraphs collapse. The writer (`markdownToUniverDoc`) flattens a list item\n // to a literal \"• \" / \"1. \" prefix and a table row to cells joined by\n // TABLE_CELL_SEP, so those shapes are read back as the markdown they came from.\n type Kind = \"text\" | \"list\" | \"table\";\n const kindOf = (line: string): Kind =>\n /^\\s*(?:\\u2022|[-*+]|\\d+[.)])\\s+/.test(line) ? \"list\" : line.includes(TABLE_CELL_SEP) ? \"table\" : \"text\";\n // A document typed or pasted as markdown keeps a table as literal `| a | b |` lines, one paragraph each, often\n // with an empty paragraph between rows: those rows are ONE table, so the gaps between them close.\n const rawRow = (l: string) => /^\\s*\\|.*\\|\\s*$/.test(l);\n const rows = lines.filter((l, i) => {\n if (l.trim()) return true;\n let a = i - 1;\n while (a >= 0 && !lines[a]!.trim()) a--;\n let z = i + 1;\n while (z < lines.length && !lines[z]!.trim()) z++;\n return !(a >= 0 && z < lines.length && rawRow(lines[a]!) && rawRow(lines[z]!));\n });\n const out: string[] = [];\n let prev: Kind | null = null;\n let tableRow = 0;\n for (const raw of rows) {\n if (!raw.trim()) {\n if (out.length && out[out.length - 1] !== \"\") out.push(\"\");\n prev = null;\n continue;\n }\n let line = raw.replace(/\\s+$/, \"\");\n if (rawRow(line)) {\n if (prev !== \"table\" && out.length && out[out.length - 1] !== \"\") out.push(\"\");\n out.push(line.trim());\n prev = \"table\";\n continue;\n }\n const kind = kindOf(line);\n if (kind === \"list\") {\n line = line.replace(/^(\\s*)\\u2022\\s+/, \"$1- \").replace(/^(\\s*)\\*\\s+/, \"$1- \");\n // Four spaces per nesting level in the document; two read as nesting in markdown.\n line = line.replace(/^( +)/, (m) => \" \".repeat(Math.floor(m.length / 2)));\n }\n if (kind === \"table\") {\n const cells = line.split(/\\s*\\|\\s*/).map((c) => c.trim());\n const isHeader = prev !== \"table\";\n const shown = cells.map((c) => (isHeader ? c.replace(/^\\*\\*(.*)\\*\\*$/, \"$1\") : c));\n line = `| ${shown.join(\" | \")} |`;\n if (isHeader) tableRow = shown.length;\n out.push(...(prev === \"table\" || prev === null || !out.length || out[out.length - 1] === \"\" ? [] : [\"\"]));\n out.push(line);\n if (isHeader) out.push(`|${\" --- |\".repeat(tableRow)}`);\n prev = \"table\";\n continue;\n }\n const tight = kind === \"list\" && prev === \"list\";\n if (out.length && out[out.length - 1] !== \"\" && !tight) out.push(\"\");\n out.push(line);\n prev = kind;\n }\n return out.join(\"\\n\").trim();\n}\n","/**\n * A file from the person's computer -> a new document (Word .docx, Markdown,\n * plain text, HTML). Everything lands as markdown first and goes through THE\n * one writer (`markdownToUniverDoc`), so headings, bold, italic, links and\n * lists keep their shape. Parse first: a file we cannot read never leaves an\n * empty document behind.\n */\n\"use client\";\n\nimport { readZip } from \"@ai-matrx/alchemy/operate/zip\";\n\nimport { createDocument, saveDocumentSnapshot, deleteDocument } from \"./document-service\";\nimport { deriveDocumentName, markdownToUniverDoc } from \"./markdown-to-univer-doc\";\nimport { sanitizeUniverDocSnapshot } from \"./sanitize-univer-doc-snapshot\";\n\nexport const DOCUMENT_UPLOAD_ACCEPT = \".docx,.md,.markdown,.txt,.html,.htm\";\n\ntype Source = \"imported_docx\" | \"imported_md\" | \"imported_txt\";\n\nexport function documentFileKind(name: string): \"docx\" | \"md\" | \"txt\" | \"html\" | null {\n const ext = name.toLowerCase().split(\".\").pop() ?? \"\";\n if (ext === \"docx\") return \"docx\";\n if (ext === \"md\" || ext === \"markdown\") return \"md\";\n if (ext === \"txt\") return \"txt\";\n if (ext === \"html\" || ext === \"htm\") return \"html\";\n return null;\n}\n\nconst W = \"http://schemas.openxmlformats.org/wordprocessingml/2006/main\";\n\n/** Word's document.xml -> markdown: headings, bold/italic runs, list paragraphs, tables as rows. */\nexport function docxXmlToMarkdown(xml: string): string {\n const doc = new DOMParser().parseFromString(xml, \"application/xml\");\n const body = doc.getElementsByTagNameNS(W, \"body\")[0];\n if (!body) throw new Error(\"This Word file has no document body.\");\n const attr = (el: Element | undefined, name: string) => el?.getAttributeNS(W, name) ?? el?.getAttribute(`w:${name}`) ?? null;\n const first = (el: Element, name: string) => el.getElementsByTagNameNS(W, name)[0] as Element | undefined;\n const on = (props: Element | undefined, name: string) => {\n const el = props ? first(props, name) : undefined;\n return !!el && attr(el, \"val\") !== \"0\" && attr(el, \"val\") !== \"false\";\n };\n const paragraph = (p: Element): string => {\n let text = \"\";\n for (const r of Array.from(p.getElementsByTagNameNS(W, \"r\"))) {\n const t = Array.from(r.childNodes)\n .map((n) => {\n const el = n as Element;\n if (el.localName === \"t\") return el.textContent ?? \"\";\n if (el.localName === \"tab\") return \"\\t\";\n if (el.localName === \"br\") return \" \\n\";\n return \"\";\n })\n .join(\"\");\n if (!t) continue;\n const rPr = first(r, \"rPr\");\n const bold = on(rPr, \"b\");\n const italic = on(rPr, \"i\");\n const lead = t.match(/^\\s*/)?.[0] ?? \"\";\n const trail = t.match(/\\s*$/)?.[0] ?? \"\";\n const core = t.trim();\n text += core ? `${lead}${bold ? \"**\" : \"\"}${italic ? \"*\" : \"\"}${core}${italic ? \"*\" : \"\"}${bold ? \"**\" : \"\"}${trail}` : t;\n }\n const pPr = first(p, \"pPr\");\n const style = attr(pPr ? first(pPr, \"pStyle\") : undefined, \"val\") ?? \"\";\n const heading = style.match(/^(?:Heading|heading)\\s?(\\d)$/)?.[1] ?? (style === \"Title\" ? \"1\" : null);\n if (heading && text.trim()) return `${\"#\".repeat(Math.min(6, Number(heading)))} ${text.replace(/\\*+/g, \"\").trim()}`;\n if (pPr && first(pPr, \"numPr\") && text.trim()) {\n const level = Number(attr(first(first(pPr, \"numPr\")!, \"ilvl\"), \"val\") ?? 0);\n return `${\" \".repeat(level)}- ${text.trim()}`;\n }\n return text;\n };\n const out: string[] = [];\n for (const child of Array.from(body.children)) {\n if (child.localName === \"p\") out.push(paragraph(child));\n else if (child.localName === \"tbl\") {\n const rows = Array.from(child.getElementsByTagNameNS(W, \"tr\")).map((tr) =>\n Array.from(tr.getElementsByTagNameNS(W, \"tc\")).map((tc) =>\n Array.from(tc.getElementsByTagNameNS(W, \"p\")).map(paragraph).join(\" \").replace(/\\|/g, \"\\\\|\").trim(),\n ),\n );\n if (rows.length) {\n out.push(\"\", `| ${rows[0]!.join(\" | \")} |`, `|${\" --- |\".repeat(rows[0]!.length)}`);\n for (const row of rows.slice(1)) out.push(`| ${row.join(\" | \")} |`);\n out.push(\"\");\n }\n }\n }\n // A list run stays tight; everything else is its own block.\n return out\n .reduce<string[]>((acc, line, i) => {\n const prevList = /^\\s*- /.test(out[i - 1] ?? \"\");\n if (i > 0 && !(prevList && /^\\s*- /.test(line))) acc.push(\"\");\n acc.push(line);\n return acc;\n }, [])\n .join(\"\\n\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n}\n\n/** HTML -> markdown for the blocks a document keeps (headings, paragraphs, lists, emphasis, links). */\nexport function htmlToDocumentMarkdown(html: string): string {\n const doc = new DOMParser().parseFromString(html, \"text/html\");\n const inline = (node: Node): string => {\n if (node.nodeType === 3) return (node.textContent ?? \"\").replace(/\\s+/g, \" \");\n if (node.nodeType !== 1) return \"\";\n const el = node as Element;\n const inner = Array.from(el.childNodes).map(inline).join(\"\");\n switch (el.tagName.toLowerCase()) {\n case \"strong\":\n case \"b\":\n return inner.trim() ? `**${inner.trim()}**` : \"\";\n case \"em\":\n case \"i\":\n return inner.trim() ? `*${inner.trim()}*` : \"\";\n case \"a\": {\n const href = el.getAttribute(\"href\");\n return href && /^https?:|^mailto:/i.test(href) ? `[${inner.trim()}](${href})` : inner;\n }\n case \"br\":\n return \" \\n\";\n case \"code\":\n return `\\`${inner}\\``;\n default:\n return inner;\n }\n };\n const out: string[] = [];\n const block = (el: Element, depth = 0): void => {\n const tag = el.tagName.toLowerCase();\n const h = tag.match(/^h([1-6])$/);\n if (h) {\n out.push(`${\"#\".repeat(Number(h[1]))} ${inline(el).trim()}`);\n return;\n }\n if (tag === \"p\" || tag === \"blockquote\") {\n out.push(inline(el).trim());\n return;\n }\n if (tag === \"pre\") {\n out.push(\"```\", el.textContent ?? \"\", \"```\");\n return;\n }\n if (tag === \"ul\" || tag === \"ol\") {\n Array.from(el.children).forEach((li, n) => {\n if (li.tagName.toLowerCase() !== \"li\") return;\n const own = Array.from(li.childNodes).filter((c) => !(c.nodeType === 1 && /^(ul|ol)$/i.test((c as Element).tagName)));\n out.push(`${\" \".repeat(depth)}${tag === \"ol\" ? `${n + 1}.` : \"-\"} ${own.map(inline).join(\"\").trim()}`);\n Array.from(li.children)\n .filter((c) => /^(ul|ol)$/i.test(c.tagName))\n .forEach((c) => block(c, depth + 1));\n });\n return;\n }\n if (tag === \"script\" || tag === \"style\" || tag === \"head\") return;\n const kids = Array.from(el.children);\n if (kids.length === 0) {\n const t = inline(el).trim();\n if (t) out.push(t);\n return;\n }\n kids.forEach((k) => block(k, depth));\n };\n block(doc.body);\n return out\n .map((l) => l.replace(/\\s+$/, \"\"))\n .reduce<string[]>((acc, line, i) => {\n const list = (s: string | undefined) => /^\\s*(-|\\d+\\.) /.test(s ?? \"\");\n if (i > 0 && !(list(out[i - 1]) && list(line))) acc.push(\"\");\n acc.push(line);\n return acc;\n }, [])\n .join(\"\\n\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n}\n\nexport async function documentFileToMarkdown(file: File): Promise<{ markdown: string; source: Source }> {\n const kind = documentFileKind(file.name);\n if (kind === \"docx\") {\n const zip = await readZip(file);\n const entry = zip.get(\"word/document.xml\");\n if (!entry) throw new Error(\"This is not a Word document (.docx).\");\n const xml = await entry.text();\n return { markdown: docxXmlToMarkdown(xml), source: \"imported_docx\" };\n }\n const text = await file.text();\n if (kind === \"md\") return { markdown: text, source: \"imported_md\" };\n if (kind === \"html\") return { markdown: htmlToDocumentMarkdown(text), source: \"imported_md\" };\n if (kind === \"txt\") {\n // Plain text: each line is its own paragraph; nothing in it is read as markdown.\n const escaped = text\n .replace(/\\r\\n?/g, \"\\n\")\n .split(\"\\n\")\n .map((l) => l.replace(/([\\\\`*_[\\]#>|~])/g, \"\\\\$1\"))\n .join(\"\\n\\n\");\n return { markdown: escaped, source: \"imported_txt\" };\n }\n throw new Error(\"Upload a Word (.docx), Markdown, text or HTML file.\");\n}\n\n/** Create a document from an uploaded file; returns its id. */\nexport async function importDocumentFile(file: File, organizationId: string): Promise<{ id: string; name: string }> {\n const { markdown, source } = await documentFileToMarkdown(file);\n const name = file.name.replace(/\\.[^.]+$/, \"\").trim() || deriveDocumentName(markdown);\n const created = await createDocument({ name, source, organizationId, description: `Imported from ${file.name}` });\n if (!created.success) throw new Error(`Could not create the document: ${created.error}`);\n const id = created.data.id;\n const snapshot = sanitizeUniverDocSnapshot(markdownToUniverDoc(markdown, name), id);\n const saved = await saveDocumentSnapshot({ documentId: id, snapshot, origin: \"imported\", label: `Imported from ${file.name}` });\n if (!saved.success) {\n await deleteDocument(id);\n throw new Error(`Could not save the imported content: ${saved.error}`);\n }\n return { id, name };\n}\n","/**\n * File → Download → EPUB / OpenDocument (.odt) / Rich Text (.rtf).\n * EPUB the print package's EPUB engine, straight from the live text.\n * ODT/RTF the same engine writes the .docx, then aidream's LibreOffice route\n * (`POST /office/convert-document`) converts it. Nothing is stored.\n * Engine, converter and saver are injected so the forcing test runs the real logic.\n */\n\nimport { getDocsHost } from \"./ports\";\n\nexport type OpenFormat = \"epub\" | \"odt\" | \"rtf\";\n\nexport const OPEN_FORMATS: Record<OpenFormat, { label: string; extension: string; mime: string }> = {\n epub: { label: \"EPUB\", extension: \"epub\", mime: \"application/epub+zip\" },\n odt: { label: \"OpenDocument\", extension: \"odt\", mime: \"application/vnd.oasis.opendocument.text\" },\n rtf: { label: \"Rich Text\", extension: \"rtf\", mime: \"application/rtf\" },\n};\n\nexport interface OpenFormatDeps {\n engine: (markdown: string, format: \"docx\" | \"epub\", fileName: string) => Promise<{ bytes: Uint8Array; fileName: string }>;\n convert: (docx: Uint8Array, fileName: string, target: \"odt\" | \"rtf\") => Promise<Uint8Array>;\n save: (fileName: string, bytes: Uint8Array, mime: string) => void;\n}\n\nfunction baseName(title: string | null | undefined): string {\n const base = (title ?? \"\").replace(/[\\\\/:*?\"<>|\\u0000-\\u001f]+/g, \" \").replace(/\\s+/g, \" \").trim().slice(0, 120);\n return base || \"document\";\n}\n\nexport async function exportOpenFormat(markdown: string, format: OpenFormat, title: string | null | undefined, deps: OpenFormatDeps): Promise<string> {\n const name = baseName(title);\n const spec = OPEN_FORMATS[format];\n const fileName = `${name}.${spec.extension}`;\n if (format === \"epub\") {\n const out = await deps.engine(markdown, \"epub\", name);\n deps.save(fileName, out.bytes, spec.mime);\n return fileName;\n }\n const docx = await deps.engine(markdown, \"docx\", name);\n const bytes = await deps.convert(docx.bytes, docx.fileName, format);\n if (!bytes.byteLength) throw new Error(`The ${spec.label} converter returned an empty file.`);\n deps.save(fileName, bytes, spec.mime);\n return fileName;\n}\n\nexport async function downloadOpenFormat(markdown: string, format: OpenFormat, title: string | null | undefined): Promise<string> {\n return exportOpenFormat(markdown, format, title, {\n engine: async (md, fmt, fileName) => {\n const { exportDocument } = await import(\"@ai-matrx/print/document\");\n const exp = await exportDocument(md, fmt, { fileName });\n return { bytes: exp.bytes, fileName: exp.fileName };\n },\n convert: async (docx, fileName, target) => {\n const form = new FormData();\n form.append(\"file\", new Blob([docx as BlobPart]), fileName.endsWith(\".docx\") ? fileName : `${fileName}.docx`);\n form.append(\"target\", target);\n const res = await getDocsHost().requestRaw(\"/office/convert-document\", { method: \"POST\", body: form });\n return new Uint8Array(await res.arrayBuffer());\n },\n save: (fileName, bytes, mime) => {\n const url = URL.createObjectURL(new Blob([bytes as BlobPart], { type: mime }));\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = fileName;\n link.rel = \"noopener\";\n document.body.appendChild(link);\n link.click();\n link.remove();\n setTimeout(() => URL.revokeObjectURL(url), 30_000);\n },\n });\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/_work/aidream/aidream/apps/shared/office/dist/docs-io.cjs","../src/docs/univer-doc-to-markdown.ts","../src/docs/import-document-file.ts","../src/docs/export-open-formats.ts"],"names":["out"],"mappings":"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","file":"/home/runner/_work/aidream/aidream/apps/shared/office/dist/docs-io.cjs","sourcesContent":[null,"/**\n * univerDocToMarkdown — the reverse of `markdown-to-univer-doc.ts`.\n *\n * Why it exists: the canvas pane for a cloud document (`udt_document`) offers a\n * `Source` tab, and the rule in `features/canvas/core/canvasSource.ts` is that\n * Source shows THE ITEM'S OWN SOURCE — for a document, its markdown — never the\n * redux envelope. A document's content lives as an opaque Univer\n * `IDocumentData` snapshot in `workbench.udt_document_snapshots`, so something\n * has to read it back out. This is that something, and it is the ONLY one:\n * never re-implement a second snapshot reader.\n *\n * What it reads (the half `markdownToUniverDoc` writes):\n * - `body.dataStream` — the text; `\\r` terminates a paragraph, a trailing\n * `\\n` terminates the section.\n * - `body.paragraphs` — one entry per `\\r` (`startIndex` = the `\\r` offset).\n * - `body.textRuns` — `{ st, ed, ts }` style overlays. `ts.bl` + a heading\n * font size is how `markdownToUniverDoc` encodes\n * `#`..`######`, so the level is recovered from `fs`.\n *\n * Fidelity is deliberately MODEST and honest: headings, bold, italic,\n * strikethrough and inline/monospace code round-trip; everything else comes\n * back as the plain text the user actually sees. A document typed by hand in\n * Univer never had markdown syntax in the first place — printing its text is\n * the truthful answer, not a reconstruction.\n *\n * PURE — no React, no Redux, no IO.\n */\n\nconst TABLE_CELL_SEP = \" | \";\n\n// Univer's DataStreamTreeTokenType control characters (literals: no Univer runtime here).\nconst TABLE_START = \"\\x1A\";\nconst TABLE_ROW_START = \"\\x1B\";\nconst TABLE_CELL_START = \"\\x1C\";\nconst TABLE_CELL_END = \"\\x1D\";\nconst TABLE_ROW_END = \"\\x0E\";\nconst TABLE_END = \"\\x0F\";\nconst LINK_START = \"\\x1F\";\nconst LINK_END = \"\\x1E\";\n\ninterface CustomRangeLike {\n startIndex?: number;\n endIndex?: number;\n rangeType?: number;\n properties?: { url?: unknown };\n}\n\n/** The Univer-native shapes (real tables, hyperlink ranges) written back as markdown. */\nfunction tableMarkdown(rows: string[][]): string[] {\n const width = Math.max(1, ...rows.map((r) => r.length));\n const cell = (r: string[], i: number) => (r[i] ?? \"\").replace(/^\\*\\*(.*)\\*\\*$/, \"$1\");\n const line = (r: string[], plain: boolean) =>\n `| ${Array.from({ length: width }, (_, i) => (plain ? cell(r, i) : (r[i] ?? \"\"))).join(\" | \")} |`;\n const out = [line(rows[0]!, true), `|${\" --- |\".repeat(width)}`];\n for (const r of rows.slice(1)) out.push(line(r, false));\n return out;\n}\n\ninterface TextRunLike {\n st?: number;\n ed?: number;\n ts?: {\n bl?: number;\n it?: number;\n ff?: string;\n fs?: number;\n st?: { s?: number } | number;\n };\n}\n\n/** Inverse of markdown-to-univer-doc's HEADING_FONT_SIZE. */\nexport const HEADING_LEVEL_BY_FONT_SIZE: Record<number, number> = {\n 26: 1,\n 22: 2,\n 18: 3,\n 16: 4,\n 14: 5,\n 13: 6,\n};\n\nfunction asArray<T>(value: unknown): T[] {\n return Array.isArray(value) ? (value as T[]) : [];\n}\n\nconst TRUE_ENOUGH = (v: unknown): boolean => v === 1 || v === true;\n\nfunction isStruck(ts: TextRunLike[\"ts\"]): boolean {\n if (!ts) return false;\n const st = ts.st;\n if (typeof st === \"number\") return TRUE_ENOUGH(st);\n return TRUE_ENOUGH(st?.s);\n}\n\n/** The style that applies to the whole paragraph, if the runs agree on one. */\nfunction paragraphHeadingLevel(runs: TextRunLike[]): number | null {\n for (const run of runs) {\n const fs = run.ts?.fs;\n if (typeof fs === \"number\" && TRUE_ENOUGH(run.ts?.bl)) {\n const level = HEADING_LEVEL_BY_FONT_SIZE[fs];\n if (level) return level;\n }\n }\n return null;\n}\n\nfunction emphasize(text: string, ts: TextRunLike[\"ts\"], inHeading: boolean): string {\n if (!text) return text;\n // Never double-mark: a heading's own runs are bold by construction.\n const leading = text.match(/^\\s*/)?.[0] ?? \"\";\n const trailing = text.match(/\\s*$/)?.[0] ?? \"\";\n let core = text.slice(leading.length, text.length - trailing.length);\n if (!core) return text;\n\n const mono = typeof ts?.ff === \"string\" && /mono/i.test(ts.ff);\n if (mono) core = `\\`${core}\\``;\n else {\n if (TRUE_ENOUGH(ts?.it)) core = `*${core}*`;\n if (TRUE_ENOUGH(ts?.bl) && !inHeading) core = `**${core}**`;\n }\n if (isStruck(ts)) core = `~~${core}~~`;\n return `${leading}${core}${trailing}`;\n}\n\n// An inline picture: one CUSTOM_BLOCK token in the stream, its drawing in `snapshot.drawings`.\nconst CUSTOM_BLOCK = \"\\b\";\n\ninterface DrawingLike {\n source?: unknown;\n title?: unknown;\n description?: unknown;\n docTransform?: { size?: { width?: unknown; height?: unknown } };\n}\n\n/**\n * A picture's markdown source. Our own pictures are `matrx-file:<id>`; the\n * drawn size rides as a `#WxH` fragment so a round trip keeps the shape.\n */\nexport function imageMarkdownSource(drawing: DrawingLike): string | null {\n const source = typeof drawing.source === \"string\" ? drawing.source : \"\";\n if (!source || source.startsWith(\"data:\")) return null;\n const w = Number(drawing.docTransform?.size?.width);\n const h = Number(drawing.docTransform?.size?.height);\n return Number.isFinite(w) && Number.isFinite(h) && w > 0 && h > 0 && !source.includes(\"#\")\n ? `${source}#${Math.round(w)}x${Math.round(h)}`\n : source;\n}\n\n/** Every picture in a snapshot, as its markdown source (`matrx-file:<id>#WxH` or a URL). */\nexport function documentImageSources(snapshot: unknown): string[] {\n const drawings = ((snapshot as { drawings?: unknown } | null)?.drawings ?? {}) as Record<string, DrawingLike>;\n const body = (snapshot as { body?: { customBlocks?: unknown } } | null)?.body;\n return asArray<{ blockId?: unknown }>(body?.customBlocks)\n .map((b) => (typeof b.blockId === \"string\" && drawings[b.blockId] ? imageMarkdownSource(drawings[b.blockId]!) : null))\n .filter((s): s is string => !!s);\n}\n\nexport interface DocToMarkdownOptions {\n /**\n * Where a picture's markdown points. Return null to leave it out (the caller\n * names what was left out). Default: the stored source.\n */\n imageUrl?: (source: string) => string | null;\n}\n\n/**\n * Read a Univer document snapshot back as markdown. Returns `\"\"` for an empty\n * or unreadable snapshot so the caller can say \"no source\" honestly rather than\n * printing an envelope.\n */\nexport function univerDocToMarkdown(snapshot: unknown, options: DocToMarkdownOptions = {}): string {\n if (!snapshot || typeof snapshot !== \"object\") return \"\";\n const body = (snapshot as { body?: unknown }).body;\n if (!body || typeof body !== \"object\") return \"\";\n\n const dataStream = (body as { dataStream?: unknown }).dataStream;\n if (typeof dataStream !== \"string\" || !dataStream) return \"\";\n\n // Pictures: each CUSTOM_BLOCK token is read back as `![alt](source)`.\n const drawings = ((snapshot as { drawings?: unknown }).drawings ?? {}) as Record<string, DrawingLike>;\n const blocks = new Map<number, string>();\n for (const b of asArray<{ startIndex?: unknown; blockId?: unknown }>((body as { customBlocks?: unknown }).customBlocks)) {\n if (typeof b.startIndex !== \"number\" || typeof b.blockId !== \"string\") continue;\n const drawing = drawings[b.blockId];\n const source = drawing ? imageMarkdownSource(drawing) : null;\n if (!source || !drawing) continue;\n const url = options.imageUrl ? options.imageUrl(source) : source;\n if (!url) continue;\n const label = typeof drawing.title === \"string\" && drawing.title ? drawing.title\n : typeof drawing.description === \"string\" && drawing.description ? drawing.description : \"image\";\n blocks.set(b.startIndex, `![${label.replace(/[[\\]]/g, \"\")}](${url})`);\n }\n\n const runs = asArray<TextRunLike>((body as { textRuns?: unknown }).textRuns)\n .filter((r) => typeof r.st === \"number\" && typeof r.ed === \"number\")\n .sort((a, b) => (a.st as number) - (b.st as number));\n\n const links = new Map<number, string>();\n for (const r of asArray<CustomRangeLike>((body as { customRanges?: unknown }).customRanges)) {\n if (r.rangeType === 0 && typeof r.endIndex === \"number\" && typeof r.properties?.url === \"string\") {\n links.set(r.endIndex, r.properties.url);\n }\n }\n // A slice of the stream with its hyperlink tokens read back as `[text](url)`.\n const readSlice = (from: number, to: number): string => {\n let out = \"\";\n for (let k = from; k < to; k++) {\n const c = dataStream[k];\n if (c === LINK_START) out += \"[\";\n else if (c === LINK_END) out += `](${links.get(k) ?? \"\"})`;\n else if (c === CUSTOM_BLOCK) out += blocks.get(k) ?? \"\";\n else out += c;\n }\n return out;\n };\n\n const lines: string[] = [];\n let cursor = 0;\n let runIndex = 0;\n let tableRows: string[][] | null = null;\n\n // Paragraph boundaries are the `\\r`s in the stream itself — reading them from\n // the stream (rather than trusting `paragraphs`) keeps a snapshot written by\n // Univer's own editor, which may index differently, readable.\n for (let i = 0; i <= dataStream.length; i++) {\n const ch = dataStream[i];\n const atEnd = i === dataStream.length;\n if (!atEnd) {\n if (ch === TABLE_START) {\n tableRows = [];\n cursor = i + 1;\n continue;\n }\n if (tableRows && ch === TABLE_ROW_START) {\n tableRows.push([]);\n cursor = i + 1;\n continue;\n }\n if (tableRows && ch === TABLE_CELL_START) {\n tableRows[tableRows.length - 1]?.push(\"\");\n cursor = i + 1;\n continue;\n }\n if (tableRows && (ch === TABLE_CELL_END || ch === TABLE_ROW_END)) {\n cursor = i + 1;\n continue;\n }\n if (tableRows && ch === TABLE_END) {\n if (tableRows.length) lines.push(\"\", ...tableMarkdown(tableRows), \"\");\n tableRows = null;\n cursor = i + 1;\n continue;\n }\n }\n if (!atEnd && ch !== \"\\r\" && ch !== \"\\n\") continue;\n\n const start = cursor;\n const end = i;\n cursor = i + 1;\n if (end <= start) {\n if (ch === \"\\r\" && !tableRows) lines.push(\"\");\n if (atEnd) break;\n continue;\n }\n\n // Collect the runs overlapping this paragraph.\n const paragraphRuns: TextRunLike[] = [];\n let scan = runIndex;\n while (scan < runs.length && (runs[scan]!.st as number) < end) {\n if ((runs[scan]!.ed as number) > start) paragraphRuns.push(runs[scan]!);\n scan++;\n }\n while (\n runIndex < runs.length &&\n (runs[runIndex]!.ed as number) <= end\n ) {\n runIndex++;\n }\n\n const heading = paragraphHeadingLevel(paragraphRuns);\n let text = \"\";\n let at = start;\n for (const run of paragraphRuns) {\n const rs = Math.max(run.st as number, start);\n const re = Math.min(run.ed as number, end);\n if (rs > at) text += readSlice(at, rs);\n if (re > rs) {\n text += emphasize(readSlice(rs, re), run.ts, heading != null);\n }\n at = Math.max(at, re);\n }\n if (at < end) text += readSlice(at, end);\n\n if (tableRows) {\n // A table cell: its paragraphs join into the one cell, pipes escaped.\n const row = tableRows[tableRows.length - 1];\n if (row?.length) {\n const part = text.trim().replace(/\\|/g, \"\\\\|\");\n const last = row.length - 1;\n row[last] = row[last] ? `${row[last]} ${part}` : part;\n }\n } else {\n lines.push(heading ? `${\"#\".repeat(heading)} ${text.trim()}` : text);\n }\n if (atEnd) break;\n }\n\n // Univer keeps one paragraph per visual line; markdown wants a blank line\n // between blocks (but not inside a list or a table), and consecutive blank\n // paragraphs collapse. The writer (`markdownToUniverDoc`) flattens a list item\n // to a literal \"• \" / \"1. \" prefix and a table row to cells joined by\n // TABLE_CELL_SEP, so those shapes are read back as the markdown they came from.\n type Kind = \"text\" | \"list\" | \"table\";\n const kindOf = (line: string): Kind =>\n /^\\s*(?:\\u2022|[-*+]|\\d+[.)])\\s+/.test(line) ? \"list\" : line.includes(TABLE_CELL_SEP) ? \"table\" : \"text\";\n // A document typed or pasted as markdown keeps a table as literal `| a | b |` lines, one paragraph each, often\n // with an empty paragraph between rows: those rows are ONE table, so the gaps between them close.\n const rawRow = (l: string) => /^\\s*\\|.*\\|\\s*$/.test(l);\n const rows = lines.filter((l, i) => {\n if (l.trim()) return true;\n let a = i - 1;\n while (a >= 0 && !lines[a]!.trim()) a--;\n let z = i + 1;\n while (z < lines.length && !lines[z]!.trim()) z++;\n return !(a >= 0 && z < lines.length && rawRow(lines[a]!) && rawRow(lines[z]!));\n });\n const out: string[] = [];\n let prev: Kind | null = null;\n let tableRow = 0;\n for (const raw of rows) {\n if (!raw.trim()) {\n if (out.length && out[out.length - 1] !== \"\") out.push(\"\");\n prev = null;\n continue;\n }\n let line = raw.replace(/\\s+$/, \"\");\n if (rawRow(line)) {\n if (prev !== \"table\" && out.length && out[out.length - 1] !== \"\") out.push(\"\");\n out.push(line.trim());\n prev = \"table\";\n continue;\n }\n const kind = kindOf(line);\n if (kind === \"list\") {\n line = line.replace(/^(\\s*)\\u2022\\s+/, \"$1- \").replace(/^(\\s*)\\*\\s+/, \"$1- \");\n // Four spaces per nesting level in the document; two read as nesting in markdown.\n line = line.replace(/^( +)/, (m) => \" \".repeat(Math.floor(m.length / 2)));\n }\n if (kind === \"table\") {\n const cells = line.split(/\\s*\\|\\s*/).map((c) => c.trim());\n const isHeader = prev !== \"table\";\n const shown = cells.map((c) => (isHeader ? c.replace(/^\\*\\*(.*)\\*\\*$/, \"$1\") : c));\n line = `| ${shown.join(\" | \")} |`;\n if (isHeader) tableRow = shown.length;\n out.push(...(prev === \"table\" || prev === null || !out.length || out[out.length - 1] === \"\" ? [] : [\"\"]));\n out.push(line);\n if (isHeader) out.push(`|${\" --- |\".repeat(tableRow)}`);\n prev = \"table\";\n continue;\n }\n const tight = kind === \"list\" && prev === \"list\";\n if (out.length && out[out.length - 1] !== \"\" && !tight) out.push(\"\");\n out.push(line);\n prev = kind;\n }\n return out.join(\"\\n\").trim();\n}\n","/**\n * A file from the person's computer -> a new document (Word .docx, Markdown,\n * plain text, HTML). Everything lands as markdown first and goes through THE\n * one writer (`markdownToUniverDoc`), so headings, bold, italic, links and\n * lists keep their shape. Parse first: a file we cannot read never leaves an\n * empty document behind.\n */\n\"use client\";\n\nimport { readZip } from \"@ai-matrx/alchemy/operate/zip\";\n\nimport { createDocument, saveDocumentSnapshot, deleteDocument } from \"./document-service\";\nimport { deriveDocumentName, markdownToUniverDoc } from \"./markdown-to-univer-doc\";\nimport { sanitizeUniverDocSnapshot } from \"./sanitize-univer-doc-snapshot\";\n\nexport const DOCUMENT_UPLOAD_ACCEPT = \".docx,.md,.markdown,.txt,.html,.htm\";\n\ntype Source = \"imported_docx\" | \"imported_md\" | \"imported_txt\";\n\nexport function documentFileKind(name: string): \"docx\" | \"md\" | \"txt\" | \"html\" | null {\n const ext = name.toLowerCase().split(\".\").pop() ?? \"\";\n if (ext === \"docx\") return \"docx\";\n if (ext === \"md\" || ext === \"markdown\") return \"md\";\n if (ext === \"txt\") return \"txt\";\n if (ext === \"html\" || ext === \"htm\") return \"html\";\n return null;\n}\n\nconst W = \"http://schemas.openxmlformats.org/wordprocessingml/2006/main\";\n\n/** Word's document.xml -> markdown: headings, bold/italic runs, list paragraphs, tables as rows. */\nexport function docxXmlToMarkdown(xml: string): string {\n const doc = new DOMParser().parseFromString(xml, \"application/xml\");\n const body = doc.getElementsByTagNameNS(W, \"body\")[0];\n if (!body) throw new Error(\"This Word file has no document body.\");\n const attr = (el: Element | undefined, name: string) => el?.getAttributeNS(W, name) ?? el?.getAttribute(`w:${name}`) ?? null;\n const first = (el: Element, name: string) => el.getElementsByTagNameNS(W, name)[0] as Element | undefined;\n const on = (props: Element | undefined, name: string) => {\n const el = props ? first(props, name) : undefined;\n return !!el && attr(el, \"val\") !== \"0\" && attr(el, \"val\") !== \"false\";\n };\n const paragraph = (p: Element): string => {\n let text = \"\";\n for (const r of Array.from(p.getElementsByTagNameNS(W, \"r\"))) {\n const t = Array.from(r.childNodes)\n .map((n) => {\n const el = n as Element;\n if (el.localName === \"t\") return el.textContent ?? \"\";\n if (el.localName === \"tab\") return \"\\t\";\n if (el.localName === \"br\") return \" \\n\";\n return \"\";\n })\n .join(\"\");\n if (!t) continue;\n const rPr = first(r, \"rPr\");\n const bold = on(rPr, \"b\");\n const italic = on(rPr, \"i\");\n const lead = t.match(/^\\s*/)?.[0] ?? \"\";\n const trail = t.match(/\\s*$/)?.[0] ?? \"\";\n const core = t.trim();\n text += core ? `${lead}${bold ? \"**\" : \"\"}${italic ? \"*\" : \"\"}${core}${italic ? \"*\" : \"\"}${bold ? \"**\" : \"\"}${trail}` : t;\n }\n const pPr = first(p, \"pPr\");\n const style = attr(pPr ? first(pPr, \"pStyle\") : undefined, \"val\") ?? \"\";\n const heading = style.match(/^(?:Heading|heading)\\s?(\\d)$/)?.[1] ?? (style === \"Title\" ? \"1\" : null);\n if (heading && text.trim()) return `${\"#\".repeat(Math.min(6, Number(heading)))} ${text.replace(/\\*+/g, \"\").trim()}`;\n if (pPr && first(pPr, \"numPr\") && text.trim()) {\n const level = Number(attr(first(first(pPr, \"numPr\")!, \"ilvl\"), \"val\") ?? 0);\n return `${\" \".repeat(level)}- ${text.trim()}`;\n }\n return text;\n };\n const out: string[] = [];\n for (const child of Array.from(body.children)) {\n if (child.localName === \"p\") out.push(paragraph(child));\n else if (child.localName === \"tbl\") {\n const rows = Array.from(child.getElementsByTagNameNS(W, \"tr\")).map((tr) =>\n Array.from(tr.getElementsByTagNameNS(W, \"tc\")).map((tc) =>\n Array.from(tc.getElementsByTagNameNS(W, \"p\")).map(paragraph).join(\" \").replace(/\\|/g, \"\\\\|\").trim(),\n ),\n );\n if (rows.length) {\n out.push(\"\", `| ${rows[0]!.join(\" | \")} |`, `|${\" --- |\".repeat(rows[0]!.length)}`);\n for (const row of rows.slice(1)) out.push(`| ${row.join(\" | \")} |`);\n out.push(\"\");\n }\n }\n }\n // A list run stays tight; everything else is its own block.\n return out\n .reduce<string[]>((acc, line, i) => {\n const prevList = /^\\s*- /.test(out[i - 1] ?? \"\");\n if (i > 0 && !(prevList && /^\\s*- /.test(line))) acc.push(\"\");\n acc.push(line);\n return acc;\n }, [])\n .join(\"\\n\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n}\n\n/** HTML -> markdown for the blocks a document keeps (headings, paragraphs, lists, emphasis, links). */\nexport function htmlToDocumentMarkdown(html: string): string {\n const doc = new DOMParser().parseFromString(html, \"text/html\");\n const inline = (node: Node): string => {\n if (node.nodeType === 3) return (node.textContent ?? \"\").replace(/\\s+/g, \" \");\n if (node.nodeType !== 1) return \"\";\n const el = node as Element;\n const inner = Array.from(el.childNodes).map(inline).join(\"\");\n switch (el.tagName.toLowerCase()) {\n case \"strong\":\n case \"b\":\n return inner.trim() ? `**${inner.trim()}**` : \"\";\n case \"em\":\n case \"i\":\n return inner.trim() ? `*${inner.trim()}*` : \"\";\n case \"a\": {\n const href = el.getAttribute(\"href\");\n return href && /^https?:|^mailto:/i.test(href) ? `[${inner.trim()}](${href})` : inner;\n }\n case \"br\":\n return \" \\n\";\n case \"code\":\n return `\\`${inner}\\``;\n default:\n return inner;\n }\n };\n const out: string[] = [];\n const block = (el: Element, depth = 0): void => {\n const tag = el.tagName.toLowerCase();\n const h = tag.match(/^h([1-6])$/);\n if (h) {\n out.push(`${\"#\".repeat(Number(h[1]))} ${inline(el).trim()}`);\n return;\n }\n if (tag === \"p\" || tag === \"blockquote\") {\n out.push(inline(el).trim());\n return;\n }\n if (tag === \"pre\") {\n out.push(\"```\", el.textContent ?? \"\", \"```\");\n return;\n }\n if (tag === \"ul\" || tag === \"ol\") {\n Array.from(el.children).forEach((li, n) => {\n if (li.tagName.toLowerCase() !== \"li\") return;\n const own = Array.from(li.childNodes).filter((c) => !(c.nodeType === 1 && /^(ul|ol)$/i.test((c as Element).tagName)));\n out.push(`${\" \".repeat(depth)}${tag === \"ol\" ? `${n + 1}.` : \"-\"} ${own.map(inline).join(\"\").trim()}`);\n Array.from(li.children)\n .filter((c) => /^(ul|ol)$/i.test(c.tagName))\n .forEach((c) => block(c, depth + 1));\n });\n return;\n }\n if (tag === \"script\" || tag === \"style\" || tag === \"head\") return;\n const kids = Array.from(el.children);\n if (kids.length === 0) {\n const t = inline(el).trim();\n if (t) out.push(t);\n return;\n }\n kids.forEach((k) => block(k, depth));\n };\n block(doc.body);\n return out\n .map((l) => l.replace(/\\s+$/, \"\"))\n .reduce<string[]>((acc, line, i) => {\n const list = (s: string | undefined) => /^\\s*(-|\\d+\\.) /.test(s ?? \"\");\n if (i > 0 && !(list(out[i - 1]) && list(line))) acc.push(\"\");\n acc.push(line);\n return acc;\n }, [])\n .join(\"\\n\")\n .replace(/\\n{3,}/g, \"\\n\\n\")\n .trim();\n}\n\nexport async function documentFileToMarkdown(file: File): Promise<{ markdown: string; source: Source }> {\n const kind = documentFileKind(file.name);\n if (kind === \"docx\") {\n const zip = await readZip(file);\n const entry = zip.get(\"word/document.xml\");\n if (!entry) throw new Error(\"This is not a Word document (.docx).\");\n const xml = await entry.text();\n return { markdown: docxXmlToMarkdown(xml), source: \"imported_docx\" };\n }\n const text = await file.text();\n if (kind === \"md\") return { markdown: text, source: \"imported_md\" };\n if (kind === \"html\") return { markdown: htmlToDocumentMarkdown(text), source: \"imported_md\" };\n if (kind === \"txt\") {\n // Plain text: each line is its own paragraph; nothing in it is read as markdown.\n const escaped = text\n .replace(/\\r\\n?/g, \"\\n\")\n .split(\"\\n\")\n .map((l) => l.replace(/([\\\\`*_[\\]#>|~])/g, \"\\\\$1\"))\n .join(\"\\n\\n\");\n return { markdown: escaped, source: \"imported_txt\" };\n }\n throw new Error(\"Upload a Word (.docx), Markdown, text or HTML file.\");\n}\n\n/** Create a document from an uploaded file; returns its id. */\nexport async function importDocumentFile(file: File, organizationId: string): Promise<{ id: string; name: string }> {\n const { markdown, source } = await documentFileToMarkdown(file);\n const name = file.name.replace(/\\.[^.]+$/, \"\").trim() || deriveDocumentName(markdown);\n const created = await createDocument({ name, source, organizationId, description: `Imported from ${file.name}` });\n if (!created.success) throw new Error(`Could not create the document: ${created.error}`);\n const id = created.data.id;\n const snapshot = sanitizeUniverDocSnapshot(markdownToUniverDoc(markdown, name), id);\n const saved = await saveDocumentSnapshot({ documentId: id, snapshot, origin: \"imported\", label: `Imported from ${file.name}` });\n if (!saved.success) {\n await deleteDocument(id);\n throw new Error(`Could not save the imported content: ${saved.error}`);\n }\n return { id, name };\n}\n","/**\n * File → Download → EPUB / OpenDocument (.odt) / Rich Text (.rtf).\n * EPUB the print package's EPUB engine, straight from the live text.\n * ODT/RTF the same engine writes the .docx, then aidream's LibreOffice route\n * (`POST /office/convert-document`) converts it. Nothing is stored.\n * Engine, converter and saver are injected so the forcing test runs the real logic.\n */\n\nimport { getDocsHost } from \"./ports\";\n\nexport type OpenFormat = \"epub\" | \"odt\" | \"rtf\";\n\nexport const OPEN_FORMATS: Record<OpenFormat, { label: string; extension: string; mime: string }> = {\n epub: { label: \"EPUB\", extension: \"epub\", mime: \"application/epub+zip\" },\n odt: { label: \"OpenDocument\", extension: \"odt\", mime: \"application/vnd.oasis.opendocument.text\" },\n rtf: { label: \"Rich Text\", extension: \"rtf\", mime: \"application/rtf\" },\n};\n\nexport interface OpenFormatDeps {\n engine: (markdown: string, format: \"docx\" | \"epub\", fileName: string) => Promise<{ bytes: Uint8Array; fileName: string }>;\n convert: (docx: Uint8Array, fileName: string, target: \"odt\" | \"rtf\") => Promise<Uint8Array>;\n save: (fileName: string, bytes: Uint8Array, mime: string) => void;\n}\n\nfunction baseName(title: string | null | undefined): string {\n const base = (title ?? \"\").replace(/[\\\\/:*?\"<>|\\u0000-\\u001f]+/g, \" \").replace(/\\s+/g, \" \").trim().slice(0, 120);\n return base || \"document\";\n}\n\nexport async function exportOpenFormat(markdown: string, format: OpenFormat, title: string | null | undefined, deps: OpenFormatDeps): Promise<string> {\n const name = baseName(title);\n const spec = OPEN_FORMATS[format];\n const fileName = `${name}.${spec.extension}`;\n if (format === \"epub\") {\n const out = await deps.engine(markdown, \"epub\", name);\n deps.save(fileName, out.bytes, spec.mime);\n return fileName;\n }\n const docx = await deps.engine(markdown, \"docx\", name);\n const bytes = await deps.convert(docx.bytes, docx.fileName, format);\n if (!bytes.byteLength) throw new Error(`The ${spec.label} converter returned an empty file.`);\n deps.save(fileName, bytes, spec.mime);\n return fileName;\n}\n\nexport async function downloadOpenFormat(markdown: string, format: OpenFormat, title: string | null | undefined): Promise<string> {\n return exportOpenFormat(markdown, format, title, {\n engine: async (md, fmt, fileName) => {\n const { exportDocument } = await import(\"@ai-matrx/print/document\");\n const exp = await exportDocument(md, fmt, { fileName });\n return { bytes: exp.bytes, fileName: exp.fileName };\n },\n convert: async (docx, fileName, target) => {\n const form = new FormData();\n form.append(\"file\", new Blob([docx as BlobPart]), fileName.endsWith(\".docx\") ? fileName : `${fileName}.docx`);\n form.append(\"target\", target);\n const res = await getDocsHost().requestRaw(\"/office/convert-document\", { method: \"POST\", body: form });\n return new Uint8Array(await res.arrayBuffer());\n },\n save: (fileName, bytes, mime) => {\n const url = URL.createObjectURL(new Blob([bytes as BlobPart], { type: mime }));\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = fileName;\n link.rel = \"noopener\";\n document.body.appendChild(link);\n link.click();\n link.remove();\n setTimeout(() => URL.revokeObjectURL(url), 30_000);\n },\n });\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/aidream/aidream/apps/shared/office/dist/docs-tools.cjs","../src/docs/doc-tools-core.ts","../src/docs/doc-edit-ops.ts"],"names":[],"mappings":"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","file":"/home/runner/work/aidream/aidream/apps/shared/office/dist/docs-tools.cjs","sourcesContent":[null,"/**\n * The pure half of the Docs tools: building blocks, bookmarks, paragraph borders, the ruler's\n * geometry and the document language. No Univer import, so each is proven on plain snapshots.\n */\n\nimport { PX_PER_INCH } from \"../core\";\nimport { markdownToUniverDoc } from \"./markdown-to-univer-doc\";\n\nexport interface BuildingBlock {\n id: string;\n label: string;\n description: string;\n markdown: string;\n}\n\nexport const BUILDING_BLOCKS: BuildingBlock[] = [\n { id: \"meeting-notes\", label: \"Meeting notes\", description: \"Date, attendees, agenda, decisions and action items.\", markdown: \"## Meeting notes\\n\\nDate: \\nAttendees: \\n\\n### Agenda\\n\\n1. \\n\\n### Discussion\\n\\n### Decisions\\n\\n### Action items\\n\\n☐ Owner: Task, due date\\n\" },\n { id: \"project-tracker\", label: \"Project tracker\", description: \"Goal, milestones, owners, risks and status.\", markdown: \"## Project tracker\\n\\nGoal: \\nOwner: \\nStatus: On track\\n\\n### Milestones\\n\\n☐ Milestone, owner, due date\\n\\n### Risks\\n\\n### Next steps\\n\" },\n { id: \"email-draft\", label: \"Email draft\", description: \"To, subject and a three-part message ready to fill in.\", markdown: \"## Email draft\\n\\nTo: \\nSubject: \\n\\nHi ,\\n\\nI am writing about .\\n\\nThe next step is .\\n\\nThanks,\\n\" },\n { id: \"weekly-status\", label: \"Weekly status\", description: \"Done, next, and blocked, for one week.\", markdown: \"## Weekly status\\n\\nWeek of: \\n\\n### Done this week\\n\\n### Next week\\n\\n### Blocked\\n\" },\n { id: \"decision-log\", label: \"Decision record\", description: \"Context, options, decision and consequences.\", markdown: \"## Decision record\\n\\nDate: \\nDecider: \\n\\n### Context\\n\\n### Options considered\\n\\n### Decision\\n\\n### Consequences\\n\" },\n { id: \"pros-cons\", label: \"Pros and cons\", description: \"A side-by-side weighing of one choice.\", markdown: \"## Pros and cons\\n\\nChoice: \\n\\n### Pros\\n\\n### Cons\\n\\n### Verdict\\n\" },\n];\n\nexport interface InsertableBody {\n dataStream: string;\n paragraphs: Array<{ startIndex: number; paragraphStyle?: Record<string, unknown> }>;\n textRuns?: Array<{ st: number; ed: number; ts?: Record<string, unknown> }>;\n}\n\n/** Markdown to a body Univer can insert at the caret: the document-closing `\\n` is dropped. */\nexport function insertableBody(markdown: string): InsertableBody {\n const body = (markdownToUniverDoc(markdown).body ?? { dataStream: \"\" }) as unknown as Partial<InsertableBody> & { dataStream: string };\n let stream = body.dataStream;\n if (stream.endsWith(\"\\n\")) stream = stream.slice(0, -1);\n const paragraphs = (body.paragraphs ?? []).filter((p) => p.startIndex < stream.length);\n const textRuns = (body.textRuns ?? []).filter((r) => r.st < stream.length).map((r) => ({ ...r, ed: Math.min(r.ed, stream.length) }));\n return { dataStream: stream, paragraphs, ...(textRuns.length ? { textRuns } : {}) };\n}\n\n/**\n * Where a BLOCK (table of contents, building block) goes for a caret: never inside a paragraph's\n * text, so a heading is never split. Caret at the start of a paragraph (or in an empty one) →\n * insert right there; anywhere else → end the caret's paragraph with a new line and insert at the\n * start of that new paragraph. `dataStream` is Univer's body stream (paragraphs end in `\\r`).\n */\nexport function blockInsertPlan(dataStream: string, caret: number): { at: number; breakAt: number | null } {\n const c = Math.max(0, Math.min(caret, dataStream.length));\n const start = Math.max(dataStream.lastIndexOf(\"\\r\", c - 1), dataStream.lastIndexOf(\"\\n\", c - 1)) + 1;\n let end = dataStream.indexOf(\"\\r\", c);\n if (end < 0) end = dataStream.length;\n // Field marks and anchors are zero-width: a paragraph holding only them is empty.\n const text = dataStream.slice(start, end).replace(/[\\u0000-\\u001f\\u200b]/g, \"\").trim();\n if (c === start || text === \"\") return { at: start, breakAt: null };\n return { at: end + 1, breakAt: end };\n}\n\n/** Univer's CustomRangeType.BOOKMARK. */\nexport const BOOKMARK_RANGE_TYPE = 3;\n/** A bookmark is an invisible anchor: one zero-width character the custom range wraps. */\nexport const BOOKMARK_ANCHOR = \"​\";\n\nexport interface Bookmark {\n id: string;\n name: string;\n offset: number;\n}\n\ninterface BodyWithRanges {\n customRanges?: Array<{ rangeId: string; rangeType: number; startIndex: number; properties?: { name?: unknown } }>;\n}\n\nexport function collectBookmarks(snapshot: unknown): Bookmark[] {\n const body = (snapshot as { body?: BodyWithRanges } | null)?.body;\n const out: Bookmark[] = [];\n for (const r of body?.customRanges ?? []) {\n if (r.rangeType !== BOOKMARK_RANGE_TYPE) continue;\n out.push({ id: r.rangeId, name: typeof r.properties?.name === \"string\" ? r.properties.name : \"\", offset: r.startIndex });\n }\n return out.sort((a, b) => a.offset - b.offset);\n}\n\nexport function bookmarkBody(id: string, name: string) {\n return {\n dataStream: BOOKMARK_ANCHOR,\n customRanges: [{ startIndex: 0, endIndex: 0, rangeId: id, rangeType: BOOKMARK_RANGE_TYPE, properties: { name }, wholeEntity: true }],\n };\n}\n\nexport function cleanBookmarkName(raw: string, existing: readonly Bookmark[]): { ok: true; name: string } | { ok: false; why: string } {\n const name = raw.replace(/\\s+/g, \" \").trim().slice(0, 80);\n if (!name) return { ok: false, why: \"Give the bookmark a name.\" };\n if (existing.some((b) => b.name.toLowerCase() === name.toLowerCase())) return { ok: false, why: `A bookmark named \"${name}\" already exists.` };\n return { ok: true, name };\n}\n\n/** `#bookmark=<name>` in the page address names a bookmark to jump to on open. */\nexport function bookmarkFromHash(hash: string): string | null {\n const m = /^#bookmark=(.+)$/.exec(hash);\n if (!m) return null;\n try {\n return decodeURIComponent(m[1]!);\n } catch {\n return null;\n }\n}\nexport const bookmarkHash = (name: string) => `#bookmark=${encodeURIComponent(name)}`;\n\nexport type BorderSide = \"top\" | \"bottom\" | \"both\" | \"none\";\nexport type BorderDash = \"solid\" | \"dashed\" | \"dotted\";\n/** Univer's DashStyleType values. */\nconst DASH: Record<BorderDash, number> = { solid: 1, dotted: 2, dashed: 3 };\n\nexport interface BorderChoice {\n side: BorderSide;\n color: string;\n width: number;\n dash: BorderDash;\n padding: number;\n}\n\n/** The `paragraph` argument of `doc-paragraph-setting.command` for a choice (Univer draws top and bottom paragraph borders). */\nexport function paragraphBorderParams(c: BorderChoice): Record<string, unknown> {\n const line = { color: { rgb: c.color }, width: c.width, dashStyle: DASH[c.dash], padding: c.padding };\n return {\n borderTop: c.side === \"top\" || c.side === \"both\" ? line : null,\n borderBottom: c.side === \"bottom\" || c.side === \"both\" ? line : null,\n };\n}\n\nexport interface RulerGeometry {\n pageLeft: number;\n pageWidth: number;\n marginLeft: number;\n marginRight: number;\n ticks: Array<{ x: number; kind: \"inch\" | \"half\" | \"eighth\"; label?: string }>;\n}\n\n/** The ruler above the page: inch ticks from the page's left edge (margins shaded), at the current zoom. */\nexport function rulerGeometry(a: { containerWidth: number; zoom: number; pageWidthPx: number; marginLeftPx: number; marginRightPx: number }): RulerGeometry {\n const pageWidth = a.pageWidthPx * a.zoom;\n const pageLeft = Math.max(0, (a.containerWidth - pageWidth) / 2);\n const ticks: RulerGeometry[\"ticks\"] = [];\n const inches = a.pageWidthPx / PX_PER_INCH;\n for (let eighth = 0; eighth <= Math.floor(inches * 8); eighth++) {\n const x = pageLeft + (eighth * PX_PER_INCH * a.zoom) / 8;\n if (eighth % 8 === 0) {\n const marginInches = a.marginLeftPx / PX_PER_INCH;\n ticks.push({ x, kind: \"inch\", ...(eighth / 8 !== marginInches ? { label: String(Math.round(Math.abs(eighth / 8 - marginInches))) } : {}) });\n } else ticks.push({ x, kind: eighth % 4 === 0 ? \"half\" : \"eighth\" });\n }\n return { pageLeft, pageWidth, marginLeft: a.marginLeftPx * a.zoom, marginRight: a.marginRightPx * a.zoom, ticks };\n}\n\n/** The key on the document style that carries the language (the slot the paper colour also uses). */\nexport const DOCUMENT_LANGUAGE_KEY = \"matrxLanguage\";\nexport const DOCUMENT_LANGUAGES: Array<{ code: string; label: string }> = [\n { code: \"en-US\", label: \"English (United States)\" },\n { code: \"en-GB\", label: \"English (United Kingdom)\" },\n { code: \"es\", label: \"Spanish\" },\n { code: \"fr\", label: \"French\" },\n { code: \"de\", label: \"German\" },\n { code: \"it\", label: \"Italian\" },\n { code: \"pt-BR\", label: \"Portuguese (Brazil)\" },\n { code: \"nl\", label: \"Dutch\" },\n { code: \"ja\", label: \"Japanese\" },\n { code: \"ko\", label: \"Korean\" },\n { code: \"zh-CN\", label: \"Chinese (Simplified)\" },\n { code: \"ar\", label: \"Arabic\" },\n { code: \"hi\", label: \"Hindi\" },\n { code: \"ru\", label: \"Russian\" },\n];\n\nexport function readDocumentLanguage(documentStyle: unknown): string {\n const v = (documentStyle as Record<string, unknown> | undefined)?.[DOCUMENT_LANGUAGE_KEY];\n return typeof v === \"string\" && DOCUMENT_LANGUAGES.some((l) => l.code === v) ? v : \"en-US\";\n}\n","/**\n * The Univer-facing half of the Docs tools: insert text, markdown and bookmarks at the caret, move\n * the selection, read the live snapshot, set view-only. Every operation is a real Univer command.\n */\n\nimport { blockInsertPlan, bookmarkBody, collectBookmarks, insertableBody, type Bookmark } from \"./doc-tools-core\";\n\ninterface ApiLike {\n executeCommand(id: string, params?: object): Promise<unknown> | unknown;\n getActiveDocument?(): {\n getId(): string;\n save?(): unknown;\n setSelection?(a: number, b: number): void;\n getPermission?(): { setReadOnly(): Promise<void>; setEditable(editable?: boolean): Promise<void> };\n } | null;\n}\ninterface InjectorLike {\n get<T>(token: unknown): T | undefined;\n}\ninterface TextRangeLike {\n startOffset: number;\n endOffset: number;\n}\n\nexport interface DocEditOps {\n insertText(text: string): Promise<boolean>;\n /** A block (building block) at a new paragraph, never mid-line. */\n insertMarkdown(markdown: string): Promise<boolean>;\n /** Move the caret to where a block goes (`blockInsertPlan`); false when there is no caret. */\n toBlockStart(): Promise<boolean>;\n bookmarks(): Bookmark[];\n addBookmark(name: string): Promise<boolean>;\n deleteBookmark(b: Bookmark): Promise<boolean>;\n goTo(offset: number): void;\n snapshot(): unknown;\n setViewOnly(viewOnly: boolean): Promise<void>;\n /** Calls back when Univer refuses an edit (Viewing mode); returns the unsubscribe. */\n onEditDenied(cb: () => void): Promise<() => void>;\n openImagePanel(): Promise<boolean>;\n}\n\nexport function createDocEditOps(deps: { api: () => ApiLike | null; injector: () => InjectorLike | null }): DocEditOps {\n const doc = () => deps.api()?.getActiveDocument?.() ?? null;\n const snapshot = () => doc()?.save?.() ?? null;\n const range = async (): Promise<TextRangeLike | null> => {\n const { DocSelectionManagerService } = await import(\"@univerjs/docs\");\n const live = deps.injector()?.get<{ getActiveTextRange?(): TextRangeLike | null | undefined }>(DocSelectionManagerService)?.getActiveTextRange?.();\n return live ?? null;\n };\n const exec = async (id: string, params: object) => (await deps.api()?.executeCommand(id, params)) !== false;\n const toBlockStart = async (): Promise<boolean> => {\n const d = doc();\n const r = await range();\n const stream = (snapshot() as { body?: { dataStream?: string } } | null)?.body?.dataStream;\n if (!d || !r || typeof stream !== \"string\") return false;\n const plan = blockInsertPlan(stream, r.startOffset);\n if (plan.breakAt === null) {\n d.setSelection?.(plan.at, plan.at);\n return true;\n }\n d.setSelection?.(plan.breakAt, plan.breakAt);\n return exec(\"doc.command.break-line\", { unitId: d.getId() });\n };\n\n return {\n snapshot,\n async insertText(text) {\n if (!text || !(await range())) return false;\n return exec(\"doc.command.insert-special-character\", { value: text });\n },\n toBlockStart,\n async insertMarkdown(markdown) {\n const d = doc();\n if (!d || !(await toBlockStart())) return false;\n return exec(\"doc.command.replace-selection\", { unitId: d.getId(), body: insertableBody(markdown) });\n },\n bookmarks: () => collectBookmarks(snapshot()),\n async addBookmark(name) {\n const d = doc();\n const r = await range();\n if (!d || !r) return false;\n d.setSelection?.(r.startOffset, r.startOffset);\n const id = `bm_${Math.random().toString(36).slice(2, 10)}`;\n return exec(\"doc.command.replace-selection\", { unitId: d.getId(), body: bookmarkBody(id, name) });\n },\n async deleteBookmark(b) {\n const d = doc();\n if (!d) return false;\n d.setSelection?.(b.offset + 1, b.offset + 1);\n return exec(\"doc.command.delete-left\", { unitId: d.getId() });\n },\n async setViewOnly(viewOnly) {\n const perm = doc()?.getPermission?.();\n if (!perm) return;\n if (viewOnly) await perm.setReadOnly();\n else await perm.setEditable(true);\n // Univer's own refusal notice (\"This content is protected…\") stays off while viewing; the\n // editor shows ours through onEditDenied.\n const { IPermissionService } = await import(\"@univerjs/core\");\n deps.injector()?.get<{ setShowComponents?(show: boolean): void }>(IPermissionService)?.setShowComponents?.(!viewOnly);\n },\n async onEditDenied(cb) {\n const { ErrorService } = await import(\"@univerjs/core\");\n const errors = deps.injector()?.get<{ error$?: { subscribe(fn: (e: { code?: string }) => void): { unsubscribe(): void } } }>(ErrorService);\n const sub = errors?.error$?.subscribe((e) => {\n if (e.code === \"PERMISSION_DENIED\") cb();\n });\n return () => sub?.unsubscribe();\n },\n async openImagePanel() {\n return exec(\"sidebar.operation.doc-image\", { value: \"doc-image\" });\n },\n goTo(offset) {\n doc()?.setSelection?.(offset, offset);\n },\n };\n}\n\n/** Write one plain key onto the document style (it rides the snapshot like the paper colour does). */\nexport async function writeDocumentStyleKey(api: ApiLike, key: string, value: string): Promise<void> {\n const doc = api.getActiveDocument?.();\n if (!doc) return;\n const style = (doc as unknown as { getDocumentDataModel?(): { getSnapshot(): { documentStyle?: Record<string, unknown> } } }).getDocumentDataModel?.()?.getSnapshot().documentStyle ?? {};\n const old = style[key];\n if (old === value) return;\n const { JSONX } = await import(\"@univerjs/core\");\n const jsonX = JSONX.getInstance();\n const path = [\"documentStyle\", key];\n const action = old === undefined ? jsonX.insertOp(path, value) : jsonX.replaceOp(path, old, value);\n if (!action) return;\n await api.executeCommand(\"doc.mutation.rich-text-editing\", { unitId: doc.getId(), actions: action, textRanges: undefined });\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/_work/aidream/aidream/apps/shared/office/dist/docs-tools.cjs","../src/docs/doc-tools-core.ts","../src/docs/doc-edit-ops.ts"],"names":[],"mappings":"AAAA;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACA;ACQO,IAAM,gBAAA,EAAmC;AAAA,EAC9C,EAAE,EAAA,EAAI,eAAA,EAAiB,KAAA,EAAO,eAAA,EAAiB,WAAA,EAAa,sDAAA,EAAwD,QAAA,EAAU,wJAAmJ,CAAA;AAAA,EACjR,EAAE,EAAA,EAAI,iBAAA,EAAmB,KAAA,EAAO,iBAAA,EAAmB,WAAA,EAAa,6CAAA,EAA+C,QAAA,EAAU,kJAA6I,CAAA;AAAA,EACtQ,EAAE,EAAA,EAAI,aAAA,EAAe,KAAA,EAAO,aAAA,EAAe,WAAA,EAAa,wDAAA,EAA0D,QAAA,EAAU,uGAAuG,CAAA;AAAA,EACnO,EAAE,EAAA,EAAI,eAAA,EAAiB,KAAA,EAAO,eAAA,EAAiB,WAAA,EAAa,wCAAA,EAA0C,QAAA,EAAU,wFAAwF,CAAA;AAAA,EACxM,EAAE,EAAA,EAAI,cAAA,EAAgB,KAAA,EAAO,iBAAA,EAAmB,WAAA,EAAa,8CAAA,EAAgD,QAAA,EAAU,yHAAyH,CAAA;AAAA,EAChP,EAAE,EAAA,EAAI,WAAA,EAAa,KAAA,EAAO,eAAA,EAAiB,WAAA,EAAa,wCAAA,EAA0C,QAAA,EAAU,wEAAwE;AACtL,CAAA;AASO,SAAS,cAAA,CAAe,QAAA,EAAkC;AAC/D,EAAA,MAAM,KAAA,mBAAQ,mDAAA,QAA4B,CAAA,CAAE,IAAA,UAAQ,EAAE,UAAA,EAAY,GAAG,GAAA;AACrE,EAAA,IAAI,OAAA,EAAS,IAAA,CAAK,UAAA;AAClB,EAAA,GAAA,CAAI,MAAA,CAAO,QAAA,CAAS,IAAI,CAAA,EAAG,OAAA,EAAS,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,CAAA,CAAE,CAAA;AACtD,EAAA,MAAM,WAAA,EAAA,kBAAc,IAAA,CAAK,UAAA,UAAc,CAAC,GAAA,CAAA,CAAG,MAAA,CAAO,CAAC,CAAA,EAAA,GAAM,CAAA,CAAE,WAAA,EAAa,MAAA,CAAO,MAAM,CAAA;AACrF,EAAA,MAAM,SAAA,EAAA,kBAAY,IAAA,CAAK,QAAA,UAAY,CAAC,GAAA,CAAA,CAAG,MAAA,CAAO,CAAC,CAAA,EAAA,GAAM,CAAA,CAAE,GAAA,EAAK,MAAA,CAAO,MAAM,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,EAAA,GAAA,CAAO,EAAE,GAAG,CAAA,EAAG,EAAA,EAAI,IAAA,CAAK,GAAA,CAAI,CAAA,CAAE,EAAA,EAAI,MAAA,CAAO,MAAM,EAAE,CAAA,CAAE,CAAA;AACnI,EAAA,OAAO,EAAE,UAAA,EAAY,MAAA,EAAQ,UAAA,EAAY,GAAI,QAAA,CAAS,OAAA,EAAS,EAAE,SAAS,EAAA,EAAI,CAAC,EAAG,CAAA;AACpF;AAQO,SAAS,eAAA,CAAgB,UAAA,EAAoB,KAAA,EAAuD;AACzG,EAAA,MAAM,EAAA,EAAI,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,IAAA,CAAK,GAAA,CAAI,KAAA,EAAO,UAAA,CAAW,MAAM,CAAC,CAAA;AACxD,EAAA,MAAM,MAAA,EAAQ,IAAA,CAAK,GAAA,CAAI,UAAA,CAAW,WAAA,CAAY,IAAA,EAAM,EAAA,EAAI,CAAC,CAAA,EAAG,UAAA,CAAW,WAAA,CAAY,IAAA,EAAM,EAAA,EAAI,CAAC,CAAC,EAAA,EAAI,CAAA;AACnG,EAAA,IAAI,IAAA,EAAM,UAAA,CAAW,OAAA,CAAQ,IAAA,EAAM,CAAC,CAAA;AACpC,EAAA,GAAA,CAAI,IAAA,EAAM,CAAA,EAAG,IAAA,EAAM,UAAA,CAAW,MAAA;AAE9B,EAAA,MAAM,KAAA,EAAO,UAAA,CAAW,KAAA,CAAM,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,wBAAA,EAA0B,EAAE,CAAA,CAAE,IAAA,CAAK,CAAA;AACrF,EAAA,GAAA,CAAI,EAAA,IAAM,MAAA,GAAS,KAAA,IAAS,EAAA,EAAI,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,OAAA,EAAS,KAAK,CAAA;AAClE,EAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,CAAA,EAAG,OAAA,EAAS,IAAI,CAAA;AACrC;AAGO,IAAM,oBAAA,EAAsB,CAAA;AAE5B,IAAM,gBAAA,EAAkB,QAAA;AAYxB,SAAS,gBAAA,CAAiB,QAAA,EAA+B;AAC9D,EAAA,MAAM,KAAA,kBAAQ,QAAA,2BAA+C,MAAA;AAC7D,EAAA,MAAM,IAAA,EAAkB,CAAC,CAAA;AACzB,EAAA,IAAA,CAAA,MAAW,EAAA,oCAAK,IAAA,6BAAM,cAAA,UAAgB,CAAC,GAAA,EAAG;AACxC,IAAA,GAAA,CAAI,CAAA,CAAE,UAAA,IAAc,mBAAA,EAAqB,QAAA;AACzC,IAAA,GAAA,CAAI,IAAA,CAAK,EAAE,EAAA,EAAI,CAAA,CAAE,OAAA,EAAS,IAAA,EAAM,uBAAO,CAAA,qBAAE,UAAA,6BAAY,OAAA,IAAS,SAAA,EAAW,CAAA,CAAE,UAAA,CAAW,KAAA,EAAO,EAAA,EAAI,MAAA,EAAQ,CAAA,CAAE,WAAW,CAAC,CAAA;AAAA,EACzH;AACA,EAAA,OAAO,GAAA,CAAI,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,EAAA,GAAM,CAAA,CAAE,OAAA,EAAS,CAAA,CAAE,MAAM,CAAA;AAC/C;AAEO,SAAS,YAAA,CAAa,EAAA,EAAY,IAAA,EAAc;AACrD,EAAA,OAAO;AAAA,IACL,UAAA,EAAY,eAAA;AAAA,IACZ,YAAA,EAAc,CAAC,EAAE,UAAA,EAAY,CAAA,EAAG,QAAA,EAAU,CAAA,EAAG,OAAA,EAAS,EAAA,EAAI,SAAA,EAAW,mBAAA,EAAqB,UAAA,EAAY,EAAE,KAAK,CAAA,EAAG,WAAA,EAAa,KAAK,CAAC;AAAA,EACrI,CAAA;AACF;AAEO,SAAS,iBAAA,CAAkB,GAAA,EAAa,QAAA,EAAwF;AACrI,EAAA,MAAM,KAAA,EAAO,GAAA,CAAI,OAAA,CAAQ,MAAA,EAAQ,GAAG,CAAA,CAAE,IAAA,CAAK,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,EAAE,CAAA;AACxD,EAAA,GAAA,CAAI,CAAC,IAAA,EAAM,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,GAAA,EAAK,4BAA4B,CAAA;AAChE,EAAA,GAAA,CAAI,QAAA,CAAS,IAAA,CAAK,CAAC,CAAA,EAAA,GAAM,CAAA,CAAE,IAAA,CAAK,WAAA,CAAY,EAAA,IAAM,IAAA,CAAK,WAAA,CAAY,CAAC,CAAA,EAAG,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,GAAA,EAAK,CAAA,kBAAA,EAAqB,IAAI,CAAA,iBAAA,EAAoB,CAAA;AAC7I,EAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,KAAK,CAAA;AAC1B;AAGO,SAAS,gBAAA,CAAiB,IAAA,EAA6B;AAC5D,EAAA,MAAM,EAAA,EAAI,kBAAA,CAAmB,IAAA,CAAK,IAAI,CAAA;AACtC,EAAA,GAAA,CAAI,CAAC,CAAA,EAAG,OAAO,IAAA;AACf,EAAA,IAAI;AACF,IAAA,OAAO,kBAAA,CAAmB,CAAA,CAAE,CAAC,CAAE,CAAA;AAAA,EACjC,EAAA,WAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AACO,IAAM,aAAA,EAAe,CAAC,IAAA,EAAA,GAAiB,CAAA,UAAA,EAAa,kBAAA,CAAmB,IAAI,CAAC,CAAA,CAAA;AAKT;AAWM;AACtB,EAAA;AACjD,EAAA;AACqD,IAAA;AACD,IAAA;AAC3D,EAAA;AACF;AAW4J;AACtH,EAAA;AAC2B,EAAA;AACxB,EAAA;AACR,EAAA;AACkC,EAAA;AACR,IAAA;AACjC,IAAA;AACkB,MAAA;AACW,MAAA;AACM,IAAA;AAC3D,EAAA;AAC6D,EAAA;AAC/D;AAGqC;AACqC;AACtB,EAAA;AACC,EAAA;AACpB,EAAA;AACD,EAAA;AACA,EAAA;AACC,EAAA;AACe,EAAA;AACjB,EAAA;AACG,EAAA;AACF,EAAA;AACiB,EAAA;AACjB,EAAA;AACD,EAAA;AACE,EAAA;AACjC;AAEqE;AACqB,EAAA;AACvB,EAAA;AACnE;ADnEmE;AACA;AErEoD;AAC9D,EAAA;AACb,EAAA;AACe,EAAA;AACH,IAAA;AACwE,IAAA;AAC7G,IAAA;AACjB,EAAA;AACsE,EAAA;AACnB,EAAA;AACnC,IAAA;AACQ,IAAA;AAC0D,IAAA;AAC7B,IAAA;AACD,IAAA;AACvB,IAAA;AACQ,sBAAA;AAC1B,MAAA;AACT,IAAA;AAC2C,oBAAA;AACgB,IAAA;AAC7D,EAAA;AAEO,EAAA;AACL,IAAA;AACuB,IAAA;AACiB,MAAA;AACuB,MAAA;AAC/D,IAAA;AACA,IAAA;AAC+B,IAAA;AACf,MAAA;AAC4B,MAAA;AACe,MAAA;AAC3D,IAAA;AAC4C,IAAA;AACpB,IAAA;AACR,MAAA;AACQ,MAAA;AACD,MAAA;AACwB,sBAAA;AACW,MAAA;AACC,MAAA;AAC3D,IAAA;AACwB,IAAA;AACR,MAAA;AACC,MAAA;AAC4B,sBAAA;AACiB,MAAA;AAC9D,IAAA;AAC4B,IAAA;AACU,MAAA;AACzB,MAAA;AAC0B,MAAA;AACL,MAAA;AAG4B,MAAA;AAC2B,sBAAA;AACzF,IAAA;AACuB,IAAA;AACiC,MAAA;AACmF,MAAA;AAC5F,MAAA;AACJ,QAAA;AACxC,MAAA;AAC6B,MAAA;AAChC,IAAA;AACuB,IAAA;AAC+B,MAAA;AACtD,IAAA;AACa,IAAA;AACyB,sBAAA;AACtC,IAAA;AACF,EAAA;AACF;AAGqG;AAC/D,EAAA;AAC1B,EAAA;AAC4J,EAAA;AACjJ,EAAA;AACF,EAAA;AAC4B,EAAA;AACf,EAAA;AACE,EAAA;AAC+B,EAAA;AACpD,EAAA;AACgD,EAAA;AAC/D;AFkEmE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/_work/aidream/aidream/apps/shared/office/dist/docs-tools.cjs","sourcesContent":[null,"/**\n * The pure half of the Docs tools: building blocks, bookmarks, paragraph borders, the ruler's\n * geometry and the document language. No Univer import, so each is proven on plain snapshots.\n */\n\nimport { PX_PER_INCH } from \"../core\";\nimport { markdownToUniverDoc } from \"./markdown-to-univer-doc\";\n\nexport interface BuildingBlock {\n id: string;\n label: string;\n description: string;\n markdown: string;\n}\n\nexport const BUILDING_BLOCKS: BuildingBlock[] = [\n { id: \"meeting-notes\", label: \"Meeting notes\", description: \"Date, attendees, agenda, decisions and action items.\", markdown: \"## Meeting notes\\n\\nDate: \\nAttendees: \\n\\n### Agenda\\n\\n1. \\n\\n### Discussion\\n\\n### Decisions\\n\\n### Action items\\n\\n☐ Owner: Task, due date\\n\" },\n { id: \"project-tracker\", label: \"Project tracker\", description: \"Goal, milestones, owners, risks and status.\", markdown: \"## Project tracker\\n\\nGoal: \\nOwner: \\nStatus: On track\\n\\n### Milestones\\n\\n☐ Milestone, owner, due date\\n\\n### Risks\\n\\n### Next steps\\n\" },\n { id: \"email-draft\", label: \"Email draft\", description: \"To, subject and a three-part message ready to fill in.\", markdown: \"## Email draft\\n\\nTo: \\nSubject: \\n\\nHi ,\\n\\nI am writing about .\\n\\nThe next step is .\\n\\nThanks,\\n\" },\n { id: \"weekly-status\", label: \"Weekly status\", description: \"Done, next, and blocked, for one week.\", markdown: \"## Weekly status\\n\\nWeek of: \\n\\n### Done this week\\n\\n### Next week\\n\\n### Blocked\\n\" },\n { id: \"decision-log\", label: \"Decision record\", description: \"Context, options, decision and consequences.\", markdown: \"## Decision record\\n\\nDate: \\nDecider: \\n\\n### Context\\n\\n### Options considered\\n\\n### Decision\\n\\n### Consequences\\n\" },\n { id: \"pros-cons\", label: \"Pros and cons\", description: \"A side-by-side weighing of one choice.\", markdown: \"## Pros and cons\\n\\nChoice: \\n\\n### Pros\\n\\n### Cons\\n\\n### Verdict\\n\" },\n];\n\nexport interface InsertableBody {\n dataStream: string;\n paragraphs: Array<{ startIndex: number; paragraphStyle?: Record<string, unknown> }>;\n textRuns?: Array<{ st: number; ed: number; ts?: Record<string, unknown> }>;\n}\n\n/** Markdown to a body Univer can insert at the caret: the document-closing `\\n` is dropped. */\nexport function insertableBody(markdown: string): InsertableBody {\n const body = (markdownToUniverDoc(markdown).body ?? { dataStream: \"\" }) as unknown as Partial<InsertableBody> & { dataStream: string };\n let stream = body.dataStream;\n if (stream.endsWith(\"\\n\")) stream = stream.slice(0, -1);\n const paragraphs = (body.paragraphs ?? []).filter((p) => p.startIndex < stream.length);\n const textRuns = (body.textRuns ?? []).filter((r) => r.st < stream.length).map((r) => ({ ...r, ed: Math.min(r.ed, stream.length) }));\n return { dataStream: stream, paragraphs, ...(textRuns.length ? { textRuns } : {}) };\n}\n\n/**\n * Where a BLOCK (table of contents, building block) goes for a caret: never inside a paragraph's\n * text, so a heading is never split. Caret at the start of a paragraph (or in an empty one) →\n * insert right there; anywhere else → end the caret's paragraph with a new line and insert at the\n * start of that new paragraph. `dataStream` is Univer's body stream (paragraphs end in `\\r`).\n */\nexport function blockInsertPlan(dataStream: string, caret: number): { at: number; breakAt: number | null } {\n const c = Math.max(0, Math.min(caret, dataStream.length));\n const start = Math.max(dataStream.lastIndexOf(\"\\r\", c - 1), dataStream.lastIndexOf(\"\\n\", c - 1)) + 1;\n let end = dataStream.indexOf(\"\\r\", c);\n if (end < 0) end = dataStream.length;\n // Field marks and anchors are zero-width: a paragraph holding only them is empty.\n const text = dataStream.slice(start, end).replace(/[\\u0000-\\u001f\\u200b]/g, \"\").trim();\n if (c === start || text === \"\") return { at: start, breakAt: null };\n return { at: end + 1, breakAt: end };\n}\n\n/** Univer's CustomRangeType.BOOKMARK. */\nexport const BOOKMARK_RANGE_TYPE = 3;\n/** A bookmark is an invisible anchor: one zero-width character the custom range wraps. */\nexport const BOOKMARK_ANCHOR = \"​\";\n\nexport interface Bookmark {\n id: string;\n name: string;\n offset: number;\n}\n\ninterface BodyWithRanges {\n customRanges?: Array<{ rangeId: string; rangeType: number; startIndex: number; properties?: { name?: unknown } }>;\n}\n\nexport function collectBookmarks(snapshot: unknown): Bookmark[] {\n const body = (snapshot as { body?: BodyWithRanges } | null)?.body;\n const out: Bookmark[] = [];\n for (const r of body?.customRanges ?? []) {\n if (r.rangeType !== BOOKMARK_RANGE_TYPE) continue;\n out.push({ id: r.rangeId, name: typeof r.properties?.name === \"string\" ? r.properties.name : \"\", offset: r.startIndex });\n }\n return out.sort((a, b) => a.offset - b.offset);\n}\n\nexport function bookmarkBody(id: string, name: string) {\n return {\n dataStream: BOOKMARK_ANCHOR,\n customRanges: [{ startIndex: 0, endIndex: 0, rangeId: id, rangeType: BOOKMARK_RANGE_TYPE, properties: { name }, wholeEntity: true }],\n };\n}\n\nexport function cleanBookmarkName(raw: string, existing: readonly Bookmark[]): { ok: true; name: string } | { ok: false; why: string } {\n const name = raw.replace(/\\s+/g, \" \").trim().slice(0, 80);\n if (!name) return { ok: false, why: \"Give the bookmark a name.\" };\n if (existing.some((b) => b.name.toLowerCase() === name.toLowerCase())) return { ok: false, why: `A bookmark named \"${name}\" already exists.` };\n return { ok: true, name };\n}\n\n/** `#bookmark=<name>` in the page address names a bookmark to jump to on open. */\nexport function bookmarkFromHash(hash: string): string | null {\n const m = /^#bookmark=(.+)$/.exec(hash);\n if (!m) return null;\n try {\n return decodeURIComponent(m[1]!);\n } catch {\n return null;\n }\n}\nexport const bookmarkHash = (name: string) => `#bookmark=${encodeURIComponent(name)}`;\n\nexport type BorderSide = \"top\" | \"bottom\" | \"both\" | \"none\";\nexport type BorderDash = \"solid\" | \"dashed\" | \"dotted\";\n/** Univer's DashStyleType values. */\nconst DASH: Record<BorderDash, number> = { solid: 1, dotted: 2, dashed: 3 };\n\nexport interface BorderChoice {\n side: BorderSide;\n color: string;\n width: number;\n dash: BorderDash;\n padding: number;\n}\n\n/** The `paragraph` argument of `doc-paragraph-setting.command` for a choice (Univer draws top and bottom paragraph borders). */\nexport function paragraphBorderParams(c: BorderChoice): Record<string, unknown> {\n const line = { color: { rgb: c.color }, width: c.width, dashStyle: DASH[c.dash], padding: c.padding };\n return {\n borderTop: c.side === \"top\" || c.side === \"both\" ? line : null,\n borderBottom: c.side === \"bottom\" || c.side === \"both\" ? line : null,\n };\n}\n\nexport interface RulerGeometry {\n pageLeft: number;\n pageWidth: number;\n marginLeft: number;\n marginRight: number;\n ticks: Array<{ x: number; kind: \"inch\" | \"half\" | \"eighth\"; label?: string }>;\n}\n\n/** The ruler above the page: inch ticks from the page's left edge (margins shaded), at the current zoom. */\nexport function rulerGeometry(a: { containerWidth: number; zoom: number; pageWidthPx: number; marginLeftPx: number; marginRightPx: number }): RulerGeometry {\n const pageWidth = a.pageWidthPx * a.zoom;\n const pageLeft = Math.max(0, (a.containerWidth - pageWidth) / 2);\n const ticks: RulerGeometry[\"ticks\"] = [];\n const inches = a.pageWidthPx / PX_PER_INCH;\n for (let eighth = 0; eighth <= Math.floor(inches * 8); eighth++) {\n const x = pageLeft + (eighth * PX_PER_INCH * a.zoom) / 8;\n if (eighth % 8 === 0) {\n const marginInches = a.marginLeftPx / PX_PER_INCH;\n ticks.push({ x, kind: \"inch\", ...(eighth / 8 !== marginInches ? { label: String(Math.round(Math.abs(eighth / 8 - marginInches))) } : {}) });\n } else ticks.push({ x, kind: eighth % 4 === 0 ? \"half\" : \"eighth\" });\n }\n return { pageLeft, pageWidth, marginLeft: a.marginLeftPx * a.zoom, marginRight: a.marginRightPx * a.zoom, ticks };\n}\n\n/** The key on the document style that carries the language (the slot the paper colour also uses). */\nexport const DOCUMENT_LANGUAGE_KEY = \"matrxLanguage\";\nexport const DOCUMENT_LANGUAGES: Array<{ code: string; label: string }> = [\n { code: \"en-US\", label: \"English (United States)\" },\n { code: \"en-GB\", label: \"English (United Kingdom)\" },\n { code: \"es\", label: \"Spanish\" },\n { code: \"fr\", label: \"French\" },\n { code: \"de\", label: \"German\" },\n { code: \"it\", label: \"Italian\" },\n { code: \"pt-BR\", label: \"Portuguese (Brazil)\" },\n { code: \"nl\", label: \"Dutch\" },\n { code: \"ja\", label: \"Japanese\" },\n { code: \"ko\", label: \"Korean\" },\n { code: \"zh-CN\", label: \"Chinese (Simplified)\" },\n { code: \"ar\", label: \"Arabic\" },\n { code: \"hi\", label: \"Hindi\" },\n { code: \"ru\", label: \"Russian\" },\n];\n\nexport function readDocumentLanguage(documentStyle: unknown): string {\n const v = (documentStyle as Record<string, unknown> | undefined)?.[DOCUMENT_LANGUAGE_KEY];\n return typeof v === \"string\" && DOCUMENT_LANGUAGES.some((l) => l.code === v) ? v : \"en-US\";\n}\n","/**\n * The Univer-facing half of the Docs tools: insert text, markdown and bookmarks at the caret, move\n * the selection, read the live snapshot, set view-only. Every operation is a real Univer command.\n */\n\nimport { blockInsertPlan, bookmarkBody, collectBookmarks, insertableBody, type Bookmark } from \"./doc-tools-core\";\n\ninterface ApiLike {\n executeCommand(id: string, params?: object): Promise<unknown> | unknown;\n getActiveDocument?(): {\n getId(): string;\n save?(): unknown;\n setSelection?(a: number, b: number): void;\n getPermission?(): { setReadOnly(): Promise<void>; setEditable(editable?: boolean): Promise<void> };\n } | null;\n}\ninterface InjectorLike {\n get<T>(token: unknown): T | undefined;\n}\ninterface TextRangeLike {\n startOffset: number;\n endOffset: number;\n}\n\nexport interface DocEditOps {\n insertText(text: string): Promise<boolean>;\n /** A block (building block) at a new paragraph, never mid-line. */\n insertMarkdown(markdown: string): Promise<boolean>;\n /** Move the caret to where a block goes (`blockInsertPlan`); false when there is no caret. */\n toBlockStart(): Promise<boolean>;\n bookmarks(): Bookmark[];\n addBookmark(name: string): Promise<boolean>;\n deleteBookmark(b: Bookmark): Promise<boolean>;\n goTo(offset: number): void;\n snapshot(): unknown;\n setViewOnly(viewOnly: boolean): Promise<void>;\n /** Calls back when Univer refuses an edit (Viewing mode); returns the unsubscribe. */\n onEditDenied(cb: () => void): Promise<() => void>;\n openImagePanel(): Promise<boolean>;\n}\n\nexport function createDocEditOps(deps: { api: () => ApiLike | null; injector: () => InjectorLike | null }): DocEditOps {\n const doc = () => deps.api()?.getActiveDocument?.() ?? null;\n const snapshot = () => doc()?.save?.() ?? null;\n const range = async (): Promise<TextRangeLike | null> => {\n const { DocSelectionManagerService } = await import(\"@univerjs/docs\");\n const live = deps.injector()?.get<{ getActiveTextRange?(): TextRangeLike | null | undefined }>(DocSelectionManagerService)?.getActiveTextRange?.();\n return live ?? null;\n };\n const exec = async (id: string, params: object) => (await deps.api()?.executeCommand(id, params)) !== false;\n const toBlockStart = async (): Promise<boolean> => {\n const d = doc();\n const r = await range();\n const stream = (snapshot() as { body?: { dataStream?: string } } | null)?.body?.dataStream;\n if (!d || !r || typeof stream !== \"string\") return false;\n const plan = blockInsertPlan(stream, r.startOffset);\n if (plan.breakAt === null) {\n d.setSelection?.(plan.at, plan.at);\n return true;\n }\n d.setSelection?.(plan.breakAt, plan.breakAt);\n return exec(\"doc.command.break-line\", { unitId: d.getId() });\n };\n\n return {\n snapshot,\n async insertText(text) {\n if (!text || !(await range())) return false;\n return exec(\"doc.command.insert-special-character\", { value: text });\n },\n toBlockStart,\n async insertMarkdown(markdown) {\n const d = doc();\n if (!d || !(await toBlockStart())) return false;\n return exec(\"doc.command.replace-selection\", { unitId: d.getId(), body: insertableBody(markdown) });\n },\n bookmarks: () => collectBookmarks(snapshot()),\n async addBookmark(name) {\n const d = doc();\n const r = await range();\n if (!d || !r) return false;\n d.setSelection?.(r.startOffset, r.startOffset);\n const id = `bm_${Math.random().toString(36).slice(2, 10)}`;\n return exec(\"doc.command.replace-selection\", { unitId: d.getId(), body: bookmarkBody(id, name) });\n },\n async deleteBookmark(b) {\n const d = doc();\n if (!d) return false;\n d.setSelection?.(b.offset + 1, b.offset + 1);\n return exec(\"doc.command.delete-left\", { unitId: d.getId() });\n },\n async setViewOnly(viewOnly) {\n const perm = doc()?.getPermission?.();\n if (!perm) return;\n if (viewOnly) await perm.setReadOnly();\n else await perm.setEditable(true);\n // Univer's own refusal notice (\"This content is protected…\") stays off while viewing; the\n // editor shows ours through onEditDenied.\n const { IPermissionService } = await import(\"@univerjs/core\");\n deps.injector()?.get<{ setShowComponents?(show: boolean): void }>(IPermissionService)?.setShowComponents?.(!viewOnly);\n },\n async onEditDenied(cb) {\n const { ErrorService } = await import(\"@univerjs/core\");\n const errors = deps.injector()?.get<{ error$?: { subscribe(fn: (e: { code?: string }) => void): { unsubscribe(): void } } }>(ErrorService);\n const sub = errors?.error$?.subscribe((e) => {\n if (e.code === \"PERMISSION_DENIED\") cb();\n });\n return () => sub?.unsubscribe();\n },\n async openImagePanel() {\n return exec(\"sidebar.operation.doc-image\", { value: \"doc-image\" });\n },\n goTo(offset) {\n doc()?.setSelection?.(offset, offset);\n },\n };\n}\n\n/** Write one plain key onto the document style (it rides the snapshot like the paper colour does). */\nexport async function writeDocumentStyleKey(api: ApiLike, key: string, value: string): Promise<void> {\n const doc = api.getActiveDocument?.();\n if (!doc) return;\n const style = (doc as unknown as { getDocumentDataModel?(): { getSnapshot(): { documentStyle?: Record<string, unknown> } } }).getDocumentDataModel?.()?.getSnapshot().documentStyle ?? {};\n const old = style[key];\n if (old === value) return;\n const { JSONX } = await import(\"@univerjs/core\");\n const jsonX = JSONX.getInstance();\n const path = [\"documentStyle\", key];\n const action = old === undefined ? jsonX.insertOp(path, value) : jsonX.replaceOp(path, old, value);\n if (!action) return;\n await api.executeCommand(\"doc.mutation.rich-text-editing\", { unitId: doc.getId(), actions: action, textRanges: undefined });\n}\n"]}