@ai-matrx/office 0.8.39 → 0.8.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/editor-menu-model.ts","../src/core/ribbon-model.ts","../src/core/icons.tsx","../src/core/google-parity.ts","../src/core/ribbon-parity.ts","../src/core/document-page-style.ts","../src/core/hosts.ts"],"sourcesContent":["/**\n * The editor chrome's command model — ONE data tree per editor that the menu\n * bar, the \"Menus\" search and the keyboard bindings all read. A row exists\n * only when it runs a real editor command; nothing here is decorative.\n *\n * Pure (no React, no DOM beyond KeyboardEvent shape) so the command mapping is\n * testable with a fake editor port.\n */\n\nimport type { ComponentType, ReactNode } from \"react\";\n\n/** A keyboard shortcut. `mod` is Cmd on a Mac and Ctrl elsewhere. */\nexport type EditorShortcut = {\n key: string;\n mod?: boolean;\n shift?: boolean;\n alt?: boolean;\n /**\n * true: the chrome binds this key itself (the editor engine does not).\n * false/absent: the engine already binds it; the label only tells the truth.\n */\n bind?: boolean;\n};\n\ntype IconType = ComponentType<{ className?: string }>;\n\nexport type EditorMenuAction = {\n kind: \"action\";\n id: string;\n label: string;\n icon?: IconType;\n shortcut?: EditorShortcut;\n /** Extra words the Menus search matches (e.g. \"freeze\" for \"1 row\"). */\n keywords?: string[];\n run: () => unknown;\n checked?: () => boolean;\n /** Only for edit gates (a viewer); never \"not built\". */\n disabled?: () => boolean;\n /**\n * A Google menu row we cannot run yet: the registered Coming Soon id it announces\n * (`lib/coming-soon/editor-menus.ts`). Drawn in muted ink; `run` announces the promise.\n */\n soon?: string;\n};\n\nexport type EditorMenuSubmenu = {\n kind: \"submenu\";\n id: string;\n label: string;\n icon?: IconType;\n keywords?: string[];\n items: EditorMenuEntry[];\n};\n\nexport type EditorMenuSeparator = { kind: \"separator\" };\n\nexport type EditorMenuEntry = EditorMenuAction | EditorMenuSubmenu | EditorMenuSeparator;\n\nexport type EditorMenu = {\n id: string;\n label: string;\n items: EditorMenuEntry[];\n};\n\n/** One runnable row as the Menus search sees it: the action plus its path. */\nexport type FlatEditorAction = EditorMenuAction & { path: string[] };\n\nexport function flattenMenus(menus: EditorMenu[]): FlatEditorAction[] {\n const out: FlatEditorAction[] = [];\n const walk = (entries: EditorMenuEntry[], path: string[], extraKeywords: string[]) => {\n for (const e of entries) {\n if (e.kind === \"separator\") continue;\n if (e.kind === \"submenu\") {\n walk(e.items, [...path, e.label], [...extraKeywords, ...(e.keywords ?? [])]);\n continue;\n }\n out.push({ ...e, path, keywords: [...extraKeywords, ...(e.keywords ?? [])] });\n }\n };\n for (const m of menus) walk(m.items, [m.label], []);\n return out;\n}\n\n/** Search the flattened commands: every word must hit the label, path or keywords. */\nexport function searchActions(actions: FlatEditorAction[], query: string, limit = 12): FlatEditorAction[] {\n const words = query.toLowerCase().split(/\\s+/).filter(Boolean);\n if (!words.length) return [];\n const scored: { a: FlatEditorAction; score: number }[] = [];\n for (const a of actions) {\n const label = a.label.toLowerCase();\n const hay = `${label} ${a.path.join(\" \").toLowerCase()} ${(a.keywords ?? []).join(\" \").toLowerCase()}`;\n if (!words.every((w) => hay.includes(w))) continue;\n const score = label.startsWith(words[0]!) ? 0 : label.includes(words[0]!) ? 1 : 2;\n scored.push({ a, score });\n }\n return scored.sort((x, y) => x.score - y.score).slice(0, limit).map((s) => s.a);\n}\n\nexport function isMacPlatform(): boolean {\n if (typeof navigator === \"undefined\") return true;\n return /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);\n}\n\nconst KEY_LABEL: Record<string, string> = {\n \"\\\\\": \"\\\\\",\n \"/\": \"/\",\n Delete: \"Delete\",\n Backspace: \"Backspace\",\n \"=\": \"+\",\n \"-\": \"-\",\n};\n\n/** \"⌘⇧1\" on a Mac, \"Ctrl+Shift+1\" elsewhere — Google's notation. */\nexport function formatShortcut(s: EditorShortcut, mac = isMacPlatform()): string {\n const key = KEY_LABEL[s.key] ?? (s.key.length === 1 ? s.key.toUpperCase() : s.key);\n if (mac) return `${s.mod ? \"⌘\" : \"\"}${s.alt ? \"⌥\" : \"\"}${s.shift ? \"⇧\" : \"\"}${key}`;\n return [s.mod ? \"Ctrl\" : \"\", s.alt ? \"Alt\" : \"\", s.shift ? \"Shift\" : \"\", key].filter(Boolean).join(\"+\");\n}\n\n/** The physical key of an event (layout-proof for digits and letters under Shift/Alt). */\nfunction eventKey(e: Pick<KeyboardEvent, \"key\" | \"code\">): string {\n if (/^Digit\\d$/.test(e.code)) return e.code.slice(5);\n if (/^Key[A-Z]$/.test(e.code)) return e.code.slice(3).toLowerCase();\n if (e.code === \"Backslash\") return \"\\\\\";\n if (e.code === \"Slash\") return \"/\";\n return e.key.length === 1 ? e.key.toLowerCase() : e.key;\n}\n\nexport function matchesShortcut(\n e: Pick<KeyboardEvent, \"key\" | \"code\" | \"metaKey\" | \"ctrlKey\" | \"shiftKey\" | \"altKey\">,\n s: EditorShortcut,\n mac = isMacPlatform(),\n): boolean {\n const mod = mac ? e.metaKey : e.ctrlKey;\n if (!!s.mod !== mod) return false;\n if (!!s.shift !== e.shiftKey) return false;\n if (!!s.alt !== e.altKey) return false;\n // Named keys (\"ArrowUp\", \"Enter\", \"Delete\") arrive cased; letters arrive lower-cased.\n return eventKey(e).toLowerCase() === s.key.toLowerCase();\n}\n\n/** The actions whose shortcut the chrome binds itself. */\nexport function boundActions(menus: EditorMenu[]): FlatEditorAction[] {\n return flattenMenus(menus).filter((a) => a.shortcut?.bind);\n}\n\ntype ToolbarIconType = ComponentType<{ className?: string }>;\n\n/** One control in an editor's toolbar row (moved unchanged from EditorToolbar.tsx; pure data). */\nexport type EditorToolbarItem =\n | {\n kind: \"button\";\n id: string;\n label: string;\n icon: ToolbarIconType;\n run: () => unknown;\n shortcut?: EditorShortcut;\n active?: boolean;\n disabled?: boolean;\n }\n | { kind: \"separator\"; id: string }\n | {\n kind: \"menu\";\n id: string;\n label: string;\n icon?: ToolbarIconType;\n /** Text shown on the trigger (e.g. \"100%\", \"Arial\"). */\n text?: string;\n /** Trigger width class for text triggers (keeps the row still as text changes). */\n widthClass?: string;\n items: EditorMenuEntry[];\n disabled?: boolean;\n }\n | { kind: \"node\"; id: string; node: ReactNode };\n","/**\n * The Microsoft 365 look's ribbon — a SECOND PRESENTATION of the editor's ONE command map.\n *\n * A ribbon layout is pure data: tabs → groups → controls, where each control names something that\n * already exists in the editor's model by id —\n * - a toolbar item (`tb.bold`, `tb.font`, …) from the same `EditorToolbarItem[]` the Google\n * toolbar draws,\n * - a menu row (`view.freeze`, `data.filter`, …) from the same `EditorMenu[]` the Google menu\n * bar draws (an action runs; a submenu opens as a dropdown),\n * - a whole menu (`menu:help`) as one dropdown.\n * Nothing here runs anything itself, so the two looks cannot drift: `ribbonCoverage` proves every\n * command of the model is reachable from the ribbon (forcing test per editor).\n *\n * Pure (no React) so the mapping is testable with a fake editor host.\n */\n\nimport type { ComponentType } from \"react\";\nimport type {\n EditorMenu,\n EditorMenuAction,\n EditorMenuEntry,\n EditorMenuSubmenu,\n EditorToolbarItem,\n} from \"./editor-menu-model\";\n\ntype IconType = ComponentType<{ className?: string }>;\n\n/**\n * - `large`: icon over a label, a whole column (Office's Paste, Freeze Panes).\n * - `small`: icon + label in a row, three stacked per column.\n * - `icon`: an icon (or value dropdown) in a row of tools (Office's Font group).\n */\nexport type RibbonSize = \"large\" | \"small\" | \"icon\";\n\nexport type RibbonControlSpec = {\n /** A toolbar item id, a menu entry id, or `menu:<menuId>` for a whole menu. */\n ref: string;\n size?: RibbonSize;\n /** Office's own word for the same command (\"Freeze Panes\" for Freeze). */\n label?: string;\n /** An icon for a row that has none in the model (a large control needs one). */\n icon?: IconType;\n};\n\nexport type RibbonGroupSpec = {\n id: string;\n label: string;\n controls: Array<string | RibbonControlSpec>;\n /** Icon rows hold this many tools before wrapping to the next row (default 6). */\n perRow?: number;\n};\n\nexport type RibbonTabSpec = { id: string; label: string; groups: RibbonGroupSpec[] };\n\n/** Menu rows (or whole submenus) the Microsoft ribbon deliberately does not place, with the reason. */\nexport type GoogleOnlyRows = Record<string, string>;\n\nexport type ResolvedRibbonControl =\n | { kind: \"toolbar\"; key: string; size: RibbonSize; label?: string | undefined; icon?: IconType | undefined; item: Exclude<EditorToolbarItem, { kind: \"separator\" }> }\n | { kind: \"action\"; key: string; size: RibbonSize; label?: string | undefined; icon?: IconType | undefined; action: EditorMenuAction }\n | { kind: \"submenu\"; key: string; size: RibbonSize; label?: string | undefined; icon?: IconType | undefined; submenu: Pick<EditorMenuSubmenu, \"id\" | \"label\" | \"icon\" | \"items\"> };\n\nexport type ResolvedRibbonGroup = { id: string; label: string; perRow: number; controls: ResolvedRibbonControl[] };\nexport type ResolvedRibbonTab = { id: string; label: string; groups: ResolvedRibbonGroup[] };\n\ntype Indexed =\n | { kind: \"toolbar\"; item: Exclude<EditorToolbarItem, { kind: \"separator\" }> }\n | { kind: \"action\"; action: EditorMenuAction }\n | { kind: \"submenu\"; submenu: Pick<EditorMenuSubmenu, \"id\" | \"label\" | \"icon\" | \"items\"> };\n\n/** Every addressable command of one editor: toolbar ids, menu entry ids, `menu:<id>`. */\nexport function indexCommands(menus: EditorMenu[], toolbar: EditorToolbarItem[]): Map<string, Indexed> {\n const out = new Map<string, Indexed>();\n const walk = (entries: EditorMenuEntry[]) => {\n for (const e of entries) {\n if (e.kind === \"separator\") continue;\n if (e.kind === \"submenu\") {\n if (!out.has(e.id)) out.set(e.id, { kind: \"submenu\", submenu: e });\n walk(e.items);\n } else if (!out.has(e.id)) out.set(e.id, { kind: \"action\", action: e });\n }\n };\n for (const m of menus) {\n out.set(`menu:${m.id}`, { kind: \"submenu\", submenu: { id: `menu:${m.id}`, label: m.label, items: m.items } });\n walk(m.items);\n }\n for (const t of toolbar) if (t.kind !== \"separator\") out.set(t.id, { kind: \"toolbar\", item: t });\n return out;\n}\n\nconst spec = (c: string | RibbonControlSpec): RibbonControlSpec => (typeof c === \"string\" ? { ref: c } : c);\n\n/**\n * The layout bound to the live model. A control whose id the model does not hold right now (a\n * row that exists only in some states, e.g. \"Remove image\") is left out; empty groups and tabs go.\n */\nexport function resolveRibbon(\n tabs: RibbonTabSpec[],\n menus: EditorMenu[],\n toolbar: EditorToolbarItem[],\n): ResolvedRibbonTab[] {\n const index = indexCommands(menus, toolbar);\n const out: ResolvedRibbonTab[] = [];\n for (const tab of tabs) {\n const groups: ResolvedRibbonGroup[] = [];\n for (const g of tab.groups) {\n const controls: ResolvedRibbonControl[] = [];\n for (const raw of g.controls) {\n const c = spec(raw);\n const hit = index.get(c.ref);\n if (!hit) continue;\n const base = { key: `${tab.id}/${g.id}/${c.ref}`, size: c.size ?? \"small\", label: c.label, icon: c.icon };\n if (hit.kind === \"toolbar\") controls.push({ ...base, kind: \"toolbar\", item: hit.item });\n else if (hit.kind === \"action\") controls.push({ ...base, kind: \"action\", action: hit.action });\n else controls.push({ ...base, kind: \"submenu\", submenu: hit.submenu });\n }\n if (controls.length) groups.push({ id: g.id, label: g.label, perRow: g.perRow ?? 6, controls });\n }\n if (groups.length) out.push({ id: tab.id, label: tab.label, groups });\n }\n return out;\n}\n\nfunction actionIdsIn(entries: EditorMenuEntry[], into: Set<string>) {\n for (const e of entries) {\n if (e.kind === \"separator\") continue;\n if (e.kind === \"submenu\") actionIdsIn(e.items, into);\n else into.add(e.id);\n }\n}\n\n/**\n * What the ribbon can reach vs. what the model holds. `missingActions`: menu rows no ribbon\n * control leads to; `missingToolbar`: toolbar tools absent from the ribbon; `unknownRefs`: layout\n * ids the model does not have (a typo, or a renamed command).\n */\nexport function ribbonCoverage(\n tabs: RibbonTabSpec[],\n menus: EditorMenu[],\n toolbar: EditorToolbarItem[],\n googleOnly: GoogleOnlyRows = {},\n) {\n const index = indexCommands(menus, toolbar);\n const reached = new Set<string>();\n const placedToolbar = new Set<string>();\n const unknownRefs: string[] = [];\n const wholeMenuRefs: string[] = [];\n for (const tab of tabs)\n for (const g of tab.groups)\n for (const raw of g.controls) {\n const c = spec(raw);\n const hit = index.get(c.ref);\n if (!hit) {\n unknownRefs.push(c.ref);\n continue;\n }\n // A whole-menu dropdown (\"menu:edit\") is a catch-all, not a placement: it must not make\n // a new row look covered. Rows are placed by their own id (or their submenu's).\n if (c.ref.startsWith(\"menu:\")) {\n wholeMenuRefs.push(c.ref);\n continue;\n }\n if (hit.kind === \"action\") reached.add(hit.action.id);\n else if (hit.kind === \"submenu\") actionIdsIn(hit.submenu.items, reached);\n else {\n placedToolbar.add(hit.item.id);\n if (hit.item.kind === \"menu\") actionIdsIn(hit.item.items, reached);\n }\n }\n const all = new Set<string>();\n for (const m of menus) actionIdsIn(m.items, all);\n // A toolbar button that is the same command as a reached menu row (same name or same shortcut —\n // Undo, ⌘K) is reachable through that row; it need not be placed twice.\n const flat: EditorMenuAction[] = [];\n const collect = (entries: EditorMenuEntry[]) => {\n for (const e of entries) {\n if (e.kind === \"submenu\") collect(e.items);\n else if (e.kind === \"action\") flat.push(e);\n }\n };\n for (const m of menus) collect(m.items);\n const reachedRows = flat.filter((a) => reached.has(a.id));\n const sameShortcut = (a?: EditorMenuAction[\"shortcut\"], b?: EditorMenuAction[\"shortcut\"]) =>\n !!a && !!b && a.key.toLowerCase() === b.key.toLowerCase() && !!a.mod === !!b.mod && !!a.shift === !!b.shift && !!a.alt === !!b.alt;\n const viaRow = (t: EditorToolbarItem) =>\n t.kind === \"button\" &&\n reachedRows.some((a) => a.label.toLowerCase() === t.label.toLowerCase() || sameShortcut(a.shortcut, t.shortcut));\n // Rows (or whole submenus) the layout declares Google-only, each with a reason.\n const declared = new Set<string>();\n const badGoogleOnly: string[] = [];\n for (const [id, reason] of Object.entries(googleOnly)) {\n const hit = index.get(id);\n if (!hit || !reason.trim()) {\n badGoogleOnly.push(id);\n continue;\n }\n if (hit.kind === \"action\") declared.add(hit.action.id);\n else if (hit.kind === \"submenu\") actionIdsIn(hit.submenu.items, declared);\n else badGoogleOnly.push(id);\n if (hit.kind === \"action\" && reached.has(hit.action.id)) badGoogleOnly.push(id);\n }\n return {\n missingActions: [...all].filter((id) => !reached.has(id) && !declared.has(id)),\n badGoogleOnly,\n wholeMenuRefs,\n missingToolbar: toolbar\n .filter((t) => t.kind !== \"separator\" && t.kind !== \"node\" && !placedToolbar.has(t.id) && !viaRow(t))\n .map((t) => (t as { id: string }).id),\n unknownRefs,\n };\n}\n","/**\n * The eight glyphs the shared Google/Microsoft parity rows use, inlined as SVG (C19: a package ships\n * its own icons and never depends on an icon library). Paths adapted from Lucide (ISC license,\n * https://lucide.dev), 24x24, 2px round strokes, currentColor.\n */\nimport type { ReactElement } from \"react\";\n\nexport type OfficeIconProps = { className?: string };\n\nfunction glyph(children: ReactElement) {\n return function OfficeGlyph({ className }: OfficeIconProps): ReactElement {\n return (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width={24}\n height={24}\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n focusable=\"false\"\n className={className}\n >\n {children}\n </svg>\n );\n };\n}\n\nexport const CircleHelp = glyph(\n <>\n <circle cx=\"12\" cy=\"12\" r=\"10\" />\n <path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3\" />\n <path d=\"M12 17h.01\" />\n </>,\n);\n\nexport const Code2 = glyph(\n <>\n <path d=\"m18 16 4-4-4-4\" />\n <path d=\"m6 8-4 4 4 4\" />\n <path d=\"m14.5 4-5 16\" />\n </>,\n);\n\nexport const FileText = glyph(\n <>\n <path d=\"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z\" />\n <path d=\"M14 2v5a1 1 0 0 0 1 1h5\" />\n <path d=\"M10 9H8\" />\n <path d=\"M16 13H8\" />\n <path d=\"M16 17H8\" />\n </>,\n);\n\nexport const GraduationCap = glyph(\n <>\n <path d=\"M21.42 10.922a1 1 0 0 0-.019-1.838L12.83 5.18a2 2 0 0 0-1.66 0L2.6 9.08a1 1 0 0 0 0 1.832l8.57 3.908a2 2 0 0 0 1.66 0z\" />\n <path d=\"M22 10v6\" />\n <path d=\"M6 12.5V16a6 3 0 0 0 12 0v-3.5\" />\n </>,\n);\n\nexport const Megaphone = glyph(\n <>\n <path d=\"M11 6a13 13 0 0 0 8.4-2.8A1 1 0 0 1 21 4v12a1 1 0 0 1-1.6.8A13 13 0 0 0 11 14H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2z\" />\n <path d=\"M6 14a12 12 0 0 0 2.4 7.2 2 2 0 0 0 3.2-2.4A8 8 0 0 1 10 14\" />\n <path d=\"M8 6v8\" />\n </>,\n);\n\nexport const MessageSquareWarning = glyph(\n <>\n <path d=\"M22 17a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 21.286V5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2z\" />\n <path d=\"M12 15h.01\" />\n <path d=\"M12 7v4\" />\n </>,\n);\n\nexport const Puzzle = glyph(\n <>\n <path d=\"M15.39 4.39a1 1 0 0 0 1.68-.474 2.5 2.5 0 1 1 3.014 3.015 1 1 0 0 0-.474 1.68l1.683 1.682a2.414 2.414 0 0 1 0 3.414L19.61 15.39a1 1 0 0 1-1.68-.474 2.5 2.5 0 1 0-3.014 3.015 1 1 0 0 1 .474 1.68l-1.683 1.682a2.414 2.414 0 0 1-3.414 0L8.61 19.61a1 1 0 0 0-1.68.474 2.5 2.5 0 1 1-3.014-3.015 1 1 0 0 0 .474-1.68l-1.683-1.682a2.414 2.414 0 0 1 0-3.414L4.39 8.61a1 1 0 0 1 1.68.474 2.5 2.5 0 1 0 3.014-3.015 1 1 0 0 1-.474-1.68l1.683-1.682a2.414 2.414 0 0 1 3.414 0z\" />\n </>,\n);\n\nexport const ShieldCheck = glyph(\n <>\n <path d=\"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z\" />\n <path d=\"m9 12 2 2 4-4\" />\n </>,\n);\n","/**\n * Google parity rows the three editors share: a Coming Soon row (`soon`), Google's Extensions\n * menu, and the head of Google's Help menu. Pure data (the announce module loads on click), so\n * the menu builders stay testable without React.\n */\n\nimport { CircleHelp, Code2, FileText, GraduationCap, Megaphone, MessageSquareWarning, Puzzle, ShieldCheck } from \"./icons\";\nimport type { ComponentType } from \"react\";\nimport { openOutbound } from \"@ai-matrx/kit/outbound\";\n\nimport type { EditorMenu, EditorMenuAction, EditorMenuEntry, EditorShortcut } from \"./editor-menu-model\";\n\nexport const sep: EditorMenuEntry = { kind: \"separator\" };\n\n/** A Google row we cannot run yet: muted, and clicking it announces the registered promise. */\nexport function soon(\n id: string,\n label: string,\n promise: string,\n opts: { icon?: ComponentType<{ className?: string }>; keywords?: string[]; shortcut?: EditorShortcut } = {},\n): EditorMenuAction {\n return {\n kind: \"action\",\n id,\n label,\n soon: promise,\n ...opts,\n run: () => import(\"@ai-matrx/kit/coming-soon\").then((m) => m.announceComingSoon(promise)),\n };\n}\n\nexport type GoogleProduct = \"sheets\" | \"docs\" | \"slides\";\n\n/** Google's Extensions menu (the host's own tools follow these rows). */\nexport function googleExtensionsMenu(product: GoogleProduct, built: { macros?: EditorMenuEntry } = {}): EditorMenu {\n return {\n id: \"extensions\",\n label: \"Extensions\",\n items: [\n soon(\"extensions.add-ons\", \"Add-ons\", \"editor.extensions.add-ons\", { icon: Puzzle }),\n ...(product === \"sheets\" && built.macros ? [built.macros] : []),\n soon(\"extensions.scripts\", \"Scripts\", \"editor.extensions.scripts\", { icon: Code2, keywords: [\"apps script\", \"automation\"] }),\n ],\n };\n}\n\nfunction openPage(href: string) {\n if (typeof window !== \"undefined\") openOutbound(href, \"_blank\", \"noopener\");\n}\n\n/** The head of Google's Help menu; the editor's own rows (shortcuts, function list) follow. */\nexport function googleHelpHead(opts: { sendFeedback?: () => void } = {}): EditorMenuEntry[] {\n return [\n soon(\"help.search\", \"Help\", \"editor.help.search\", { icon: CircleHelp }),\n soon(\"help.training\", \"Training\", \"editor.help.training\", { icon: GraduationCap }),\n soon(\"help.updates\", \"Updates\", \"editor.help.updates\", { icon: Megaphone }),\n ...(opts.sendFeedback\n ? [{ kind: \"action\" as const, id: \"help.feedback\", label: \"Send feedback\", icon: MessageSquareWarning, keywords: [\"report\", \"bug\", \"improve\"], run: opts.sendFeedback }]\n : []),\n sep,\n { kind: \"action\", id: \"help.privacy\", label: \"Privacy Policy\", icon: ShieldCheck, run: () => openPage(\"/privacy-policy\") },\n { kind: \"action\", id: \"help.terms\", label: \"Terms of Service\", icon: FileText, run: () => openPage(\"/terms-of-service\") },\n sep,\n ];\n}\n","/**\n * The Microsoft look's homes for the Google parity rows every editor shares (`google-parity.ts`):\n * the Help group's head and the Automate tab (add-ins, scripts). Spread into each editor's\n * ribbon so the shared rows are placed once, the same way.\n */\n\nimport { CircleHelp, Code2, FileText, GraduationCap, Megaphone, MessageSquareWarning, Puzzle, ShieldCheck } from \"./icons\";\n\nimport type { RibbonControlSpec, RibbonTabSpec } from \"./ribbon-model\";\n\nexport const HELP_HEAD_CONTROLS: RibbonControlSpec[] = [\n { ref: \"help.search\", size: \"large\", icon: CircleHelp },\n { ref: \"help.training\", label: \"Training\", icon: GraduationCap },\n { ref: \"help.updates\", label: \"Updates\", icon: Megaphone },\n { ref: \"help.feedback\", label: \"Feedback\", icon: MessageSquareWarning },\n { ref: \"help.privacy\", label: \"Privacy Policy\", icon: ShieldCheck },\n { ref: \"help.terms\", label: \"Terms of Service\", icon: FileText },\n];\n\nexport const AUTOMATE_TAB: RibbonTabSpec = {\n id: \"automate\",\n label: \"Automate\",\n groups: [\n { id: \"add-ins\", label: \"Add-ins\", controls: [{ ref: \"extensions.add-ons\", size: \"large\", label: \"Add-ins\", icon: Puzzle }] },\n { id: \"scripts\", label: \"Scripts\", controls: [{ ref: \"extensions.scripts\", size: \"large\", label: \"Scripts\", icon: Code2 }] },\n ],\n};\n","/**\n * The ONE page geometry every Matrx cloud document is laid out with.\n *\n * Univer lays a document out inside `documentStyle.pageSize`. With no pageSize\n * the docs renderer has no page box at all: text never wraps (it runs off the\n * right edge) and the vertical extent is unbounded, so the docs viewport\n * reports nothing to scroll — the wheel is swallowed and the page sits frozen\n * at the top. Every writer of a Univer snapshot MUST stamp this style.\n *\n * Consumers: `DocumentEditor` (new empty doc), `markdown-to-univer-doc`\n * (markdown import), and `sanitizeUniverDocSnapshot` (recovery for snapshots\n * that reached storage without it — notably `origin='agent'` writes).\n */\nimport type { DocumentFlavor, IDocumentData, PageOrientType } from \"@univerjs/core\";\n\n// Univer's enum values, written out: this file is read by tests and the markdown importer, which must not\n// pull Univer's runtime in just for four numbers (guarded by the page-style test).\nconst TRADITIONAL = 1 as DocumentFlavor;\nconst MODERN = 2 as DocumentFlavor;\nconst PORTRAIT = 0 as PageOrientType;\nconst LANDSCAPE = 1 as PageOrientType;\n\ntype DocumentStyle = NonNullable<IDocumentData[\"documentStyle\"]>;\n\n/** Univer lays documents out in 96-dpi CSS pixels: one inch is 96 units. */\nexport const PX_PER_INCH = 96;\n\n/** Paper sizes the Page setup dialog offers, in 96-dpi pixels, portrait (width x height). */\nexport const PAPER_SIZES = {\n Letter: { width: 816, height: 1056, label: \"Letter (8.5 x 11 in)\" },\n Legal: { width: 816, height: 1344, label: \"Legal (8.5 x 14 in)\" },\n Tabloid: { width: 1056, height: 1632, label: \"Tabloid (11 x 17 in)\" },\n Executive: { width: 696, height: 1008, label: \"Executive (7.25 x 10.5 in)\" },\n Statement: { width: 528, height: 816, label: \"Statement (5.5 x 8.5 in)\" },\n A3: { width: 1123, height: 1587, label: \"A3 (11.69 x 16.54 in)\" },\n A4: { width: 794, height: 1124, label: \"A4 (8.27 x 11.69 in)\" },\n A5: { width: 559, height: 794, label: \"A5 (5.83 x 8.27 in)\" },\n} as const;\nexport type PaperSizeName = keyof typeof PAPER_SIZES;\n\n/** A real page, like Google Docs: Letter, one-inch margins (624px of text), paginated. */\nexport const DEFAULT_DOCUMENT_PAGE_STYLE: DocumentStyle = {\n pageSize: { width: 816, height: 1056 },\n documentFlavor: TRADITIONAL,\n pageOrient: PORTRAIT,\n marginTop: PX_PER_INCH,\n marginBottom: PX_PER_INCH,\n marginLeft: PX_PER_INCH,\n marginRight: PX_PER_INCH,\n // Univer applies no paragraph spacing to a paginated page by itself; without this every paragraph\n // runs into the next (Google Docs' own default: 1.15 lines, 10px after).\n defaultParagraphStyle: { spaceAbove: { v: 0 }, spaceBelow: { v: 10 }, lineSpacing: 1.15 },\n};\n\n/** Fresh copy — Univer mutates the style it is handed. */\nexport function defaultDocumentPageStyle(): DocumentStyle {\n return {\n ...DEFAULT_DOCUMENT_PAGE_STYLE,\n pageSize: { ...DEFAULT_DOCUMENT_PAGE_STYLE.pageSize! },\n defaultParagraphStyle: { ...DEFAULT_DOCUMENT_PAGE_STYLE.defaultParagraphStyle! },\n };\n}\n\n/**\n * The squeezed geometry every document was stamped with until 2026-10-15: A4 at 72 dpi (595 x 842)\n * with 90/72 margins, in Univer's pageless flavour, so the text column was ~415px with no paper.\n * Pageless was never offered then, so this exact shape is always the old default, never a choice.\n */\nexport function isLegacySqueezedPageStyle(style: DocumentStyle | undefined): boolean {\n const size = style?.pageSize;\n return (\n !!size &&\n size.width === 595 &&\n size.height === 842 &&\n style?.marginLeft === 90 &&\n style?.marginRight === 90 &&\n style?.marginTop === 72 &&\n style?.marginBottom === 72\n );\n}\n\n/** What the Page setup dialog edits. Margins are in inches, like Google's. */\nexport interface PageSetupValues {\n pageless: boolean;\n orientation: \"portrait\" | \"landscape\";\n paper: PaperSizeName;\n marginTop: number;\n marginBottom: number;\n marginLeft: number;\n marginRight: number;\n /** Paper color, hex. Always a light color so the black ink stays readable. */\n pageColor: string;\n}\n\nexport const PAGE_COLORS = [\"#ffffff\", \"#fff8e7\", \"#f3f3f3\", \"#e8f0fe\", \"#e6f4ea\", \"#fef7e0\"] as const;\nexport const DEFAULT_PAGE_COLOR = PAGE_COLORS[0];\n/** Key on `documentStyle` carrying the paper color (Univer paints it from the host's surface theme). */\nexport const PAGE_COLOR_KEY = \"matrxPageColor\";\n\nconst round2 = (n: number) => Math.round(n * 100) / 100;\n\n/** The dialog's values, read from a document's style (missing parts fall back to the default page). */\nexport function pageSetupFromStyle(style: DocumentStyle | undefined): PageSetupValues {\n const landscape = style?.pageOrient === LANDSCAPE;\n const w = style?.pageSize?.width ?? DEFAULT_DOCUMENT_PAGE_STYLE.pageSize!.width!;\n const h = style?.pageSize?.height ?? DEFAULT_DOCUMENT_PAGE_STYLE.pageSize!.height!;\n const [pw, ph] = landscape ? [h, w] : [w, h];\n const paper =\n (Object.keys(PAPER_SIZES) as PaperSizeName[]).find(\n (k) => Math.abs(PAPER_SIZES[k].width - pw) <= 1 && Math.abs(PAPER_SIZES[k].height - ph) <= 1,\n ) ?? \"Letter\";\n const inch = (v: number | undefined) => round2((v ?? PX_PER_INCH) / PX_PER_INCH);\n const color = (style as Record<string, unknown> | undefined)?.[PAGE_COLOR_KEY];\n return {\n pageless: style?.documentFlavor === MODERN,\n orientation: landscape ? \"landscape\" : \"portrait\",\n paper,\n marginTop: inch(style?.marginTop),\n marginBottom: inch(style?.marginBottom),\n marginLeft: inch(style?.marginLeft),\n marginRight: inch(style?.marginRight),\n pageColor: typeof color === \"string\" && color ? color : DEFAULT_PAGE_COLOR,\n };\n}\n\n/** Params for Univer's `docs.command.page-setup` (pageSize is the as-printed size: swapped when landscape). */\nexport function pageSetupCommandParams(v: PageSetupValues) {\n const base = PAPER_SIZES[v.paper];\n const landscape = v.orientation === \"landscape\";\n return {\n documentFlavor: v.pageless ? MODERN : TRADITIONAL,\n pageOrient: landscape ? LANDSCAPE : PORTRAIT,\n pageSize: landscape ? { width: base.height, height: base.width } : { width: base.width, height: base.height },\n marginTop: Math.round(v.marginTop * PX_PER_INCH),\n marginBottom: Math.round(v.marginBottom * PX_PER_INCH),\n marginLeft: Math.round(v.marginLeft * PX_PER_INCH),\n marginRight: Math.round(v.marginRight * PX_PER_INCH),\n };\n}\n","/**\n * Where each Office product lives. The map is CONFIGURATION, not a constant: the app passes its own\n * (production `https://sheets.aimatrx.com`, development `http://sheets.localhost:5190`), so links\n * generated here always match the app that is running.\n *\n * The routes are the same on every host: `/` the product's File Home, `/templates`,\n * `/new?template=<id>`, `/f/<id>` an item.\n */\n\nexport type OfficeProduct = \"sheets\" | \"docs\" | \"slides\" | \"bases\" | \"boards\" | \"pdf\";\n\nexport const OFFICE_PRODUCTS: readonly OfficeProduct[] = [\"sheets\", \"docs\", \"slides\", \"bases\", \"boards\", \"pdf\"];\n\nexport type OfficeHostMap = Record<OfficeProduct, string>;\n\n// Module-level mutable state is banned (a dual ESM/CJS or duplicated bundle would split it): one slot on globalThis.\nconst SLOT = Symbol.for(\"ai-matrx.office.hosts\");\ntype Slot = { map: OfficeHostMap | null };\n\nfunction slot(): Slot {\n const g = globalThis as unknown as Record<symbol, Slot | undefined>;\n let s = g[SLOT];\n if (!s) {\n s = { map: null };\n g[SLOT] = s;\n }\n return s;\n}\n\n/** Set the product → origin map (an origin such as `https://sheets.aimatrx.com`, no trailing slash needed). Call once at start-up. */\nexport function configureOfficeHosts(map: OfficeHostMap): void {\n const cleaned = {} as OfficeHostMap;\n for (const product of OFFICE_PRODUCTS) {\n const origin = map[product];\n if (typeof origin !== \"string\" || !/^https?:\\/\\/[^/\\s]+$/.test(origin.replace(/\\/+$/, \"\"))) {\n throw new Error(`configureOfficeHosts: \"${product}\" needs an origin like \"https://${product}.example.com\", got ${JSON.stringify(origin)}`);\n }\n cleaned[product] = origin.replace(/\\/+$/, \"\");\n }\n slot().map = cleaned;\n}\n\n/** The configured origin of a product. Throws when the app never configured the hosts. */\nexport function officeOrigin(product: OfficeProduct): string {\n const map = slot().map;\n if (!map) throw new Error(\"officeHref: call configureOfficeHosts(map) at start-up before generating Office links\");\n return map[product];\n}\n\n/**\n * An absolute link into a product: no id is its File Home, an id opens that item (`/f/<id>`).\n * `sub` appends a path segment (`officeHref(\"docs\", undefined, { sub: \"templates\" })` is the Templates page).\n */\nexport function officeHref(product: OfficeProduct, id?: string, o?: { sub?: string }): string {\n const origin = officeOrigin(product);\n const sub = o?.sub ? `/${o.sub.replace(/^\\/+/, \"\")}` : \"\";\n if (id === undefined) return `${origin}${sub || \"/\"}`;\n return `${origin}/f/${encodeURIComponent(id)}${sub}`;\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/core/editor-menu-model.ts","../src/core/ribbon-model.ts","../src/core/icons.tsx","../src/core/google-parity.ts","../src/core/ribbon-parity.ts","../src/core/document-page-style.ts","../src/core/hosts.ts"],"sourcesContent":["/**\n * The editor chrome's command model — ONE data tree per editor that the menu\n * bar, the \"Menus\" search and the keyboard bindings all read. A row exists\n * only when it runs a real editor command; nothing here is decorative.\n *\n * Pure (no React, no DOM beyond KeyboardEvent shape) so the command mapping is\n * testable with a fake editor port.\n */\n\nimport type { ComponentType, ReactNode } from \"react\";\n\n/** A keyboard shortcut. `mod` is Cmd on a Mac and Ctrl elsewhere. */\nexport type EditorShortcut = {\n key: string;\n mod?: boolean;\n shift?: boolean;\n alt?: boolean;\n /**\n * true: the chrome binds this key itself (the editor engine does not).\n * false/absent: the engine already binds it; the label only tells the truth.\n */\n bind?: boolean;\n};\n\ntype IconType = ComponentType<{ className?: string }>;\n\nexport type EditorMenuAction = {\n kind: \"action\";\n id: string;\n label: string;\n icon?: IconType;\n shortcut?: EditorShortcut;\n /** Extra words the Menus search matches (e.g. \"freeze\" for \"1 row\"). */\n keywords?: string[];\n run: () => unknown;\n checked?: () => boolean;\n /** Only for edit gates (a viewer); never \"not built\". */\n disabled?: () => boolean;\n /**\n * A Google menu row we cannot run yet: the registered Coming Soon id it announces\n * (`lib/coming-soon/editor-menus.ts`). Drawn in muted ink; `run` announces the promise.\n */\n soon?: string;\n};\n\nexport type EditorMenuSubmenu = {\n kind: \"submenu\";\n id: string;\n label: string;\n icon?: IconType;\n keywords?: string[];\n items: EditorMenuEntry[];\n};\n\nexport type EditorMenuSeparator = { kind: \"separator\" };\n\nexport type EditorMenuEntry = EditorMenuAction | EditorMenuSubmenu | EditorMenuSeparator;\n\nexport type EditorMenu = {\n id: string;\n label: string;\n items: EditorMenuEntry[];\n};\n\n/** One runnable row as the Menus search sees it: the action plus its path. */\nexport type FlatEditorAction = EditorMenuAction & { path: string[] };\n\nexport function flattenMenus(menus: EditorMenu[]): FlatEditorAction[] {\n const out: FlatEditorAction[] = [];\n const walk = (entries: EditorMenuEntry[], path: string[], extraKeywords: string[]) => {\n for (const e of entries) {\n if (e.kind === \"separator\") continue;\n if (e.kind === \"submenu\") {\n walk(e.items, [...path, e.label], [...extraKeywords, ...(e.keywords ?? [])]);\n continue;\n }\n out.push({ ...e, path, keywords: [...extraKeywords, ...(e.keywords ?? [])] });\n }\n };\n for (const m of menus) walk(m.items, [m.label], []);\n return out;\n}\n\n/** Search the flattened commands: every word must hit the label, path or keywords. */\nexport function searchActions(actions: FlatEditorAction[], query: string, limit = 12): FlatEditorAction[] {\n const words = query.toLowerCase().split(/\\s+/).filter(Boolean);\n if (!words.length) return [];\n const scored: { a: FlatEditorAction; score: number }[] = [];\n for (const a of actions) {\n const label = a.label.toLowerCase();\n const hay = `${label} ${a.path.join(\" \").toLowerCase()} ${(a.keywords ?? []).join(\" \").toLowerCase()}`;\n if (!words.every((w) => hay.includes(w))) continue;\n const score = label.startsWith(words[0]!) ? 0 : label.includes(words[0]!) ? 1 : 2;\n scored.push({ a, score });\n }\n return scored.sort((x, y) => x.score - y.score).slice(0, limit).map((s) => s.a);\n}\n\nexport function isMacPlatform(): boolean {\n if (typeof navigator === \"undefined\") return true;\n return /Mac|iPhone|iPad/.test(navigator.platform || navigator.userAgent);\n}\n\nconst KEY_LABEL: Record<string, string> = {\n \"\\\\\": \"\\\\\",\n \"/\": \"/\",\n Delete: \"Delete\",\n Backspace: \"Backspace\",\n \"=\": \"+\",\n \"-\": \"-\",\n};\n\n/** \"⌘⇧1\" on a Mac, \"Ctrl+Shift+1\" elsewhere — Google's notation. */\nexport function formatShortcut(s: EditorShortcut, mac = isMacPlatform()): string {\n const key = KEY_LABEL[s.key] ?? (s.key.length === 1 ? s.key.toUpperCase() : s.key);\n if (mac) return `${s.mod ? \"⌘\" : \"\"}${s.alt ? \"⌥\" : \"\"}${s.shift ? \"⇧\" : \"\"}${key}`;\n return [s.mod ? \"Ctrl\" : \"\", s.alt ? \"Alt\" : \"\", s.shift ? \"Shift\" : \"\", key].filter(Boolean).join(\"+\");\n}\n\n/** The physical key of an event (layout-proof for digits and letters under Shift/Alt). */\nfunction eventKey(e: Pick<KeyboardEvent, \"key\" | \"code\">): string {\n if (/^Digit\\d$/.test(e.code)) return e.code.slice(5);\n if (/^Key[A-Z]$/.test(e.code)) return e.code.slice(3).toLowerCase();\n if (e.code === \"Backslash\") return \"\\\\\";\n if (e.code === \"Slash\") return \"/\";\n return e.key.length === 1 ? e.key.toLowerCase() : e.key;\n}\n\nexport function matchesShortcut(\n e: Pick<KeyboardEvent, \"key\" | \"code\" | \"metaKey\" | \"ctrlKey\" | \"shiftKey\" | \"altKey\">,\n s: EditorShortcut,\n mac = isMacPlatform(),\n): boolean {\n const mod = mac ? e.metaKey : e.ctrlKey;\n if (!!s.mod !== mod) return false;\n if (!!s.shift !== e.shiftKey) return false;\n if (!!s.alt !== e.altKey) return false;\n // Named keys (\"ArrowUp\", \"Enter\", \"Delete\") arrive cased; letters arrive lower-cased.\n return eventKey(e).toLowerCase() === s.key.toLowerCase();\n}\n\n/** The actions whose shortcut the chrome binds itself. */\nexport function boundActions(menus: EditorMenu[]): FlatEditorAction[] {\n return flattenMenus(menus).filter((a) => a.shortcut?.bind);\n}\n\ntype ToolbarIconType = ComponentType<{ className?: string }>;\n\n/** One control in an editor's toolbar row (moved unchanged from EditorToolbar.tsx; pure data). */\nexport type EditorToolbarItem =\n | {\n kind: \"button\";\n id: string;\n label: string;\n icon: ToolbarIconType;\n run: () => unknown;\n shortcut?: EditorShortcut;\n active?: boolean;\n disabled?: boolean;\n }\n | { kind: \"separator\"; id: string }\n | {\n kind: \"menu\";\n id: string;\n label: string;\n icon?: ToolbarIconType;\n /** Text shown on the trigger (e.g. \"100%\", \"Arial\"). */\n text?: string;\n /** Trigger width class for text triggers (keeps the row still as text changes). */\n widthClass?: string;\n items: EditorMenuEntry[];\n disabled?: boolean;\n }\n | { kind: \"node\"; id: string; node: ReactNode };\n","/**\n * The Microsoft 365 look's ribbon — a SECOND PRESENTATION of the editor's ONE command map.\n *\n * A ribbon layout is pure data: tabs → groups → controls, where each control names something that\n * already exists in the editor's model by id —\n * - a toolbar item (`tb.bold`, `tb.font`, …) from the same `EditorToolbarItem[]` the Google\n * toolbar draws,\n * - a menu row (`view.freeze`, `data.filter`, …) from the same `EditorMenu[]` the Google menu\n * bar draws (an action runs; a submenu opens as a dropdown),\n * - a whole menu (`menu:help`) as one dropdown.\n * Nothing here runs anything itself, so the two looks cannot drift: `ribbonCoverage` proves every\n * command of the model is reachable from the ribbon (forcing test per editor).\n *\n * Pure (no React) so the mapping is testable with a fake editor host.\n */\n\nimport type { ComponentType } from \"react\";\nimport type {\n EditorMenu,\n EditorMenuAction,\n EditorMenuEntry,\n EditorMenuSubmenu,\n EditorToolbarItem,\n} from \"./editor-menu-model\";\n\ntype IconType = ComponentType<{ className?: string }>;\n\n/**\n * - `large`: icon over a label, a whole column (Office's Paste, Freeze Panes).\n * - `small`: icon + label in a row, three stacked per column.\n * - `icon`: an icon (or value dropdown) in a row of tools (Office's Font group).\n */\nexport type RibbonSize = \"large\" | \"small\" | \"icon\";\n\nexport type RibbonControlSpec = {\n /** A toolbar item id, a menu entry id, or `menu:<menuId>` for a whole menu. */\n ref: string;\n size?: RibbonSize;\n /** Office's own word for the same command (\"Freeze Panes\" for Freeze). */\n label?: string;\n /** An icon for a row that has none in the model (a large control needs one). */\n icon?: IconType;\n};\n\nexport type RibbonGroupSpec = {\n id: string;\n label: string;\n controls: Array<string | RibbonControlSpec>;\n /** Icon rows hold this many tools before wrapping to the next row (default 6). */\n perRow?: number;\n};\n\nexport type RibbonTabSpec = { id: string; label: string; groups: RibbonGroupSpec[] };\n\n/** Menu rows (or whole submenus) the Microsoft ribbon deliberately does not place, with the reason. */\nexport type GoogleOnlyRows = Record<string, string>;\n\nexport type ResolvedRibbonControl =\n | { kind: \"toolbar\"; key: string; size: RibbonSize; label?: string | undefined; icon?: IconType | undefined; item: Exclude<EditorToolbarItem, { kind: \"separator\" }> }\n | { kind: \"action\"; key: string; size: RibbonSize; label?: string | undefined; icon?: IconType | undefined; action: EditorMenuAction }\n | { kind: \"submenu\"; key: string; size: RibbonSize; label?: string | undefined; icon?: IconType | undefined; submenu: Pick<EditorMenuSubmenu, \"id\" | \"label\" | \"icon\" | \"items\"> };\n\nexport type ResolvedRibbonGroup = { id: string; label: string; perRow: number; controls: ResolvedRibbonControl[] };\nexport type ResolvedRibbonTab = { id: string; label: string; groups: ResolvedRibbonGroup[] };\n\ntype Indexed =\n | { kind: \"toolbar\"; item: Exclude<EditorToolbarItem, { kind: \"separator\" }> }\n | { kind: \"action\"; action: EditorMenuAction }\n | { kind: \"submenu\"; submenu: Pick<EditorMenuSubmenu, \"id\" | \"label\" | \"icon\" | \"items\"> };\n\n/** Every addressable command of one editor: toolbar ids, menu entry ids, `menu:<id>`. */\nexport function indexCommands(menus: EditorMenu[], toolbar: EditorToolbarItem[]): Map<string, Indexed> {\n const out = new Map<string, Indexed>();\n const walk = (entries: EditorMenuEntry[]) => {\n for (const e of entries) {\n if (e.kind === \"separator\") continue;\n if (e.kind === \"submenu\") {\n if (!out.has(e.id)) out.set(e.id, { kind: \"submenu\", submenu: e });\n walk(e.items);\n } else if (!out.has(e.id)) out.set(e.id, { kind: \"action\", action: e });\n }\n };\n for (const m of menus) {\n out.set(`menu:${m.id}`, { kind: \"submenu\", submenu: { id: `menu:${m.id}`, label: m.label, items: m.items } });\n walk(m.items);\n }\n for (const t of toolbar) if (t.kind !== \"separator\") out.set(t.id, { kind: \"toolbar\", item: t });\n return out;\n}\n\nconst spec = (c: string | RibbonControlSpec): RibbonControlSpec => (typeof c === \"string\" ? { ref: c } : c);\n\n/**\n * The layout bound to the live model. A control whose id the model does not hold right now (a\n * row that exists only in some states, e.g. \"Remove image\") is left out; empty groups and tabs go.\n */\nexport function resolveRibbon(\n tabs: RibbonTabSpec[],\n menus: EditorMenu[],\n toolbar: EditorToolbarItem[],\n): ResolvedRibbonTab[] {\n const index = indexCommands(menus, toolbar);\n const out: ResolvedRibbonTab[] = [];\n for (const tab of tabs) {\n const groups: ResolvedRibbonGroup[] = [];\n for (const g of tab.groups) {\n const controls: ResolvedRibbonControl[] = [];\n for (const raw of g.controls) {\n const c = spec(raw);\n const hit = index.get(c.ref);\n if (!hit) continue;\n const base = { key: `${tab.id}/${g.id}/${c.ref}`, size: c.size ?? \"small\", label: c.label, icon: c.icon };\n if (hit.kind === \"toolbar\") controls.push({ ...base, kind: \"toolbar\", item: hit.item });\n else if (hit.kind === \"action\") controls.push({ ...base, kind: \"action\", action: hit.action });\n else controls.push({ ...base, kind: \"submenu\", submenu: hit.submenu });\n }\n if (controls.length) groups.push({ id: g.id, label: g.label, perRow: g.perRow ?? 6, controls });\n }\n if (groups.length) out.push({ id: tab.id, label: tab.label, groups });\n }\n return out;\n}\n\nfunction actionIdsIn(entries: EditorMenuEntry[], into: Set<string>) {\n for (const e of entries) {\n if (e.kind === \"separator\") continue;\n if (e.kind === \"submenu\") actionIdsIn(e.items, into);\n else into.add(e.id);\n }\n}\n\n/**\n * What the ribbon can reach vs. what the model holds. `missingActions`: menu rows no ribbon\n * control leads to; `missingToolbar`: toolbar tools absent from the ribbon; `unknownRefs`: layout\n * ids the model does not have (a typo, or a renamed command).\n */\nexport function ribbonCoverage(\n tabs: RibbonTabSpec[],\n menus: EditorMenu[],\n toolbar: EditorToolbarItem[],\n googleOnly: GoogleOnlyRows = {},\n) {\n const index = indexCommands(menus, toolbar);\n const reached = new Set<string>();\n const placedToolbar = new Set<string>();\n const unknownRefs: string[] = [];\n const wholeMenuRefs: string[] = [];\n for (const tab of tabs)\n for (const g of tab.groups)\n for (const raw of g.controls) {\n const c = spec(raw);\n const hit = index.get(c.ref);\n if (!hit) {\n unknownRefs.push(c.ref);\n continue;\n }\n // A whole-menu dropdown (\"menu:edit\") is a catch-all, not a placement: it must not make\n // a new row look covered. Rows are placed by their own id (or their submenu's).\n if (c.ref.startsWith(\"menu:\")) {\n wholeMenuRefs.push(c.ref);\n continue;\n }\n if (hit.kind === \"action\") reached.add(hit.action.id);\n else if (hit.kind === \"submenu\") actionIdsIn(hit.submenu.items, reached);\n else {\n placedToolbar.add(hit.item.id);\n if (hit.item.kind === \"menu\") actionIdsIn(hit.item.items, reached);\n }\n }\n const all = new Set<string>();\n for (const m of menus) actionIdsIn(m.items, all);\n // A toolbar button that is the same command as a reached menu row (same name or same shortcut —\n // Undo, ⌘K) is reachable through that row; it need not be placed twice.\n const flat: EditorMenuAction[] = [];\n const collect = (entries: EditorMenuEntry[]) => {\n for (const e of entries) {\n if (e.kind === \"submenu\") collect(e.items);\n else if (e.kind === \"action\") flat.push(e);\n }\n };\n for (const m of menus) collect(m.items);\n const reachedRows = flat.filter((a) => reached.has(a.id));\n const sameShortcut = (a?: EditorMenuAction[\"shortcut\"], b?: EditorMenuAction[\"shortcut\"]) =>\n !!a && !!b && a.key.toLowerCase() === b.key.toLowerCase() && !!a.mod === !!b.mod && !!a.shift === !!b.shift && !!a.alt === !!b.alt;\n const viaRow = (t: EditorToolbarItem) =>\n t.kind === \"button\" &&\n reachedRows.some((a) => a.label.toLowerCase() === t.label.toLowerCase() || sameShortcut(a.shortcut, t.shortcut));\n // Rows (or whole submenus) the layout declares Google-only, each with a reason.\n const declared = new Set<string>();\n const badGoogleOnly: string[] = [];\n for (const [id, reason] of Object.entries(googleOnly)) {\n const hit = index.get(id);\n if (!hit || !reason.trim()) {\n badGoogleOnly.push(id);\n continue;\n }\n if (hit.kind === \"action\") declared.add(hit.action.id);\n else if (hit.kind === \"submenu\") actionIdsIn(hit.submenu.items, declared);\n else badGoogleOnly.push(id);\n if (hit.kind === \"action\" && reached.has(hit.action.id)) badGoogleOnly.push(id);\n }\n return {\n missingActions: [...all].filter((id) => !reached.has(id) && !declared.has(id)),\n badGoogleOnly,\n wholeMenuRefs,\n missingToolbar: toolbar\n .filter((t) => t.kind !== \"separator\" && t.kind !== \"node\" && !placedToolbar.has(t.id) && !viaRow(t))\n .map((t) => (t as { id: string }).id),\n unknownRefs,\n };\n}\n","/**\n * The eight glyphs the shared Google/Microsoft parity rows use. Drawn once, in\n * `@ai-matrx/design-system/icons` (the one lucide-free glyph home); the names and the 24px default\n * size this module always had are kept here as aliases.\n */\nimport type { ReactElement } from \"react\";\nimport {\n CircleHelpIcon,\n Code2Icon,\n FileTextIcon,\n GraduationCapIcon,\n MegaphoneIcon,\n MessageSquareWarningIcon,\n PuzzleIcon,\n ShieldCheckIcon,\n withDefaultSize,\n} from \"@ai-matrx/design-system/icons\";\n\nexport type OfficeIconProps = { className?: string };\ntype OfficeGlyph = (props: OfficeIconProps) => ReactElement;\n\nexport const CircleHelp: OfficeGlyph = withDefaultSize(CircleHelpIcon, 24);\nexport const Code2: OfficeGlyph = withDefaultSize(Code2Icon, 24);\nexport const FileText: OfficeGlyph = withDefaultSize(FileTextIcon, 24);\nexport const GraduationCap: OfficeGlyph = withDefaultSize(GraduationCapIcon, 24);\nexport const Megaphone: OfficeGlyph = withDefaultSize(MegaphoneIcon, 24);\nexport const MessageSquareWarning: OfficeGlyph = withDefaultSize(MessageSquareWarningIcon, 24);\nexport const Puzzle: OfficeGlyph = withDefaultSize(PuzzleIcon, 24);\nexport const ShieldCheck: OfficeGlyph = withDefaultSize(ShieldCheckIcon, 24);\n","/**\n * Google parity rows the three editors share: a Coming Soon row (`soon`), Google's Extensions\n * menu, and the head of Google's Help menu. Pure data (the announce module loads on click), so\n * the menu builders stay testable without React.\n */\n\nimport { CircleHelp, Code2, FileText, GraduationCap, Megaphone, MessageSquareWarning, Puzzle, ShieldCheck } from \"./icons\";\nimport type { ComponentType } from \"react\";\nimport { openOutbound } from \"@ai-matrx/kit/outbound\";\n\nimport type { EditorMenu, EditorMenuAction, EditorMenuEntry, EditorShortcut } from \"./editor-menu-model\";\n\nexport const sep: EditorMenuEntry = { kind: \"separator\" };\n\n/** A Google row we cannot run yet: muted, and clicking it announces the registered promise. */\nexport function soon(\n id: string,\n label: string,\n promise: string,\n opts: { icon?: ComponentType<{ className?: string }>; keywords?: string[]; shortcut?: EditorShortcut } = {},\n): EditorMenuAction {\n return {\n kind: \"action\",\n id,\n label,\n soon: promise,\n ...opts,\n run: () => import(\"@ai-matrx/kit/coming-soon\").then((m) => m.announceComingSoon(promise)),\n };\n}\n\nexport type GoogleProduct = \"sheets\" | \"docs\" | \"slides\";\n\n/** Google's Extensions menu (the host's own tools follow these rows). */\nexport function googleExtensionsMenu(product: GoogleProduct, built: { macros?: EditorMenuEntry } = {}): EditorMenu {\n return {\n id: \"extensions\",\n label: \"Extensions\",\n items: [\n soon(\"extensions.add-ons\", \"Add-ons\", \"editor.extensions.add-ons\", { icon: Puzzle }),\n ...(product === \"sheets\" && built.macros ? [built.macros] : []),\n soon(\"extensions.scripts\", \"Scripts\", \"editor.extensions.scripts\", { icon: Code2, keywords: [\"apps script\", \"automation\"] }),\n ],\n };\n}\n\nfunction openPage(href: string) {\n if (typeof window !== \"undefined\") openOutbound(href, \"_blank\", \"noopener\");\n}\n\n/** The head of Google's Help menu; the editor's own rows (shortcuts, function list) follow. */\nexport function googleHelpHead(opts: { sendFeedback?: () => void } = {}): EditorMenuEntry[] {\n return [\n soon(\"help.search\", \"Help\", \"editor.help.search\", { icon: CircleHelp }),\n soon(\"help.training\", \"Training\", \"editor.help.training\", { icon: GraduationCap }),\n soon(\"help.updates\", \"Updates\", \"editor.help.updates\", { icon: Megaphone }),\n ...(opts.sendFeedback\n ? [{ kind: \"action\" as const, id: \"help.feedback\", label: \"Send feedback\", icon: MessageSquareWarning, keywords: [\"report\", \"bug\", \"improve\"], run: opts.sendFeedback }]\n : []),\n sep,\n { kind: \"action\", id: \"help.privacy\", label: \"Privacy Policy\", icon: ShieldCheck, run: () => openPage(\"/privacy-policy\") },\n { kind: \"action\", id: \"help.terms\", label: \"Terms of Service\", icon: FileText, run: () => openPage(\"/terms-of-service\") },\n sep,\n ];\n}\n","/**\n * The Microsoft look's homes for the Google parity rows every editor shares (`google-parity.ts`):\n * the Help group's head and the Automate tab (add-ins, scripts). Spread into each editor's\n * ribbon so the shared rows are placed once, the same way.\n */\n\nimport { CircleHelp, Code2, FileText, GraduationCap, Megaphone, MessageSquareWarning, Puzzle, ShieldCheck } from \"./icons\";\n\nimport type { RibbonControlSpec, RibbonTabSpec } from \"./ribbon-model\";\n\nexport const HELP_HEAD_CONTROLS: RibbonControlSpec[] = [\n { ref: \"help.search\", size: \"large\", icon: CircleHelp },\n { ref: \"help.training\", label: \"Training\", icon: GraduationCap },\n { ref: \"help.updates\", label: \"Updates\", icon: Megaphone },\n { ref: \"help.feedback\", label: \"Feedback\", icon: MessageSquareWarning },\n { ref: \"help.privacy\", label: \"Privacy Policy\", icon: ShieldCheck },\n { ref: \"help.terms\", label: \"Terms of Service\", icon: FileText },\n];\n\nexport const AUTOMATE_TAB: RibbonTabSpec = {\n id: \"automate\",\n label: \"Automate\",\n groups: [\n { id: \"add-ins\", label: \"Add-ins\", controls: [{ ref: \"extensions.add-ons\", size: \"large\", label: \"Add-ins\", icon: Puzzle }] },\n { id: \"scripts\", label: \"Scripts\", controls: [{ ref: \"extensions.scripts\", size: \"large\", label: \"Scripts\", icon: Code2 }] },\n ],\n};\n","/**\n * The ONE page geometry every Matrx cloud document is laid out with.\n *\n * Univer lays a document out inside `documentStyle.pageSize`. With no pageSize\n * the docs renderer has no page box at all: text never wraps (it runs off the\n * right edge) and the vertical extent is unbounded, so the docs viewport\n * reports nothing to scroll — the wheel is swallowed and the page sits frozen\n * at the top. Every writer of a Univer snapshot MUST stamp this style.\n *\n * Consumers: `DocumentEditor` (new empty doc), `markdown-to-univer-doc`\n * (markdown import), and `sanitizeUniverDocSnapshot` (recovery for snapshots\n * that reached storage without it — notably `origin='agent'` writes).\n */\nimport type { DocumentFlavor, IDocumentData, PageOrientType } from \"@univerjs/core\";\n\n// Univer's enum values, written out: this file is read by tests and the markdown importer, which must not\n// pull Univer's runtime in just for four numbers (guarded by the page-style test).\nconst TRADITIONAL = 1 as DocumentFlavor;\nconst MODERN = 2 as DocumentFlavor;\nconst PORTRAIT = 0 as PageOrientType;\nconst LANDSCAPE = 1 as PageOrientType;\n\ntype DocumentStyle = NonNullable<IDocumentData[\"documentStyle\"]>;\n\n/** Univer lays documents out in 96-dpi CSS pixels: one inch is 96 units. */\nexport const PX_PER_INCH = 96;\n\n/** Paper sizes the Page setup dialog offers, in 96-dpi pixels, portrait (width x height). */\nexport const PAPER_SIZES = {\n Letter: { width: 816, height: 1056, label: \"Letter (8.5 x 11 in)\" },\n Legal: { width: 816, height: 1344, label: \"Legal (8.5 x 14 in)\" },\n Tabloid: { width: 1056, height: 1632, label: \"Tabloid (11 x 17 in)\" },\n Executive: { width: 696, height: 1008, label: \"Executive (7.25 x 10.5 in)\" },\n Statement: { width: 528, height: 816, label: \"Statement (5.5 x 8.5 in)\" },\n A3: { width: 1123, height: 1587, label: \"A3 (11.69 x 16.54 in)\" },\n A4: { width: 794, height: 1124, label: \"A4 (8.27 x 11.69 in)\" },\n A5: { width: 559, height: 794, label: \"A5 (5.83 x 8.27 in)\" },\n} as const;\nexport type PaperSizeName = keyof typeof PAPER_SIZES;\n\n/** A real page, like Google Docs: Letter, one-inch margins (624px of text), paginated. */\nexport const DEFAULT_DOCUMENT_PAGE_STYLE: DocumentStyle = {\n pageSize: { width: 816, height: 1056 },\n documentFlavor: TRADITIONAL,\n pageOrient: PORTRAIT,\n marginTop: PX_PER_INCH,\n marginBottom: PX_PER_INCH,\n marginLeft: PX_PER_INCH,\n marginRight: PX_PER_INCH,\n // Univer applies no paragraph spacing to a paginated page by itself; without this every paragraph\n // runs into the next (Google Docs' own default: 1.15 lines, 10px after).\n defaultParagraphStyle: { spaceAbove: { v: 0 }, spaceBelow: { v: 10 }, lineSpacing: 1.15 },\n};\n\n/** Fresh copy — Univer mutates the style it is handed. */\nexport function defaultDocumentPageStyle(): DocumentStyle {\n return {\n ...DEFAULT_DOCUMENT_PAGE_STYLE,\n pageSize: { ...DEFAULT_DOCUMENT_PAGE_STYLE.pageSize! },\n defaultParagraphStyle: { ...DEFAULT_DOCUMENT_PAGE_STYLE.defaultParagraphStyle! },\n };\n}\n\n/**\n * The squeezed geometry every document was stamped with until 2026-10-15: A4 at 72 dpi (595 x 842)\n * with 90/72 margins, in Univer's pageless flavour, so the text column was ~415px with no paper.\n * Pageless was never offered then, so this exact shape is always the old default, never a choice.\n */\nexport function isLegacySqueezedPageStyle(style: DocumentStyle | undefined): boolean {\n const size = style?.pageSize;\n return (\n !!size &&\n size.width === 595 &&\n size.height === 842 &&\n style?.marginLeft === 90 &&\n style?.marginRight === 90 &&\n style?.marginTop === 72 &&\n style?.marginBottom === 72\n );\n}\n\n/** What the Page setup dialog edits. Margins are in inches, like Google's. */\nexport interface PageSetupValues {\n pageless: boolean;\n orientation: \"portrait\" | \"landscape\";\n paper: PaperSizeName;\n marginTop: number;\n marginBottom: number;\n marginLeft: number;\n marginRight: number;\n /** Paper color, hex. Always a light color so the black ink stays readable. */\n pageColor: string;\n}\n\nexport const PAGE_COLORS = [\"#ffffff\", \"#fff8e7\", \"#f3f3f3\", \"#e8f0fe\", \"#e6f4ea\", \"#fef7e0\"] as const;\nexport const DEFAULT_PAGE_COLOR = PAGE_COLORS[0];\n/** Key on `documentStyle` carrying the paper color (Univer paints it from the host's surface theme). */\nexport const PAGE_COLOR_KEY = \"matrxPageColor\";\n\nconst round2 = (n: number) => Math.round(n * 100) / 100;\n\n/** The dialog's values, read from a document's style (missing parts fall back to the default page). */\nexport function pageSetupFromStyle(style: DocumentStyle | undefined): PageSetupValues {\n const landscape = style?.pageOrient === LANDSCAPE;\n const w = style?.pageSize?.width ?? DEFAULT_DOCUMENT_PAGE_STYLE.pageSize!.width!;\n const h = style?.pageSize?.height ?? DEFAULT_DOCUMENT_PAGE_STYLE.pageSize!.height!;\n const [pw, ph] = landscape ? [h, w] : [w, h];\n const paper =\n (Object.keys(PAPER_SIZES) as PaperSizeName[]).find(\n (k) => Math.abs(PAPER_SIZES[k].width - pw) <= 1 && Math.abs(PAPER_SIZES[k].height - ph) <= 1,\n ) ?? \"Letter\";\n const inch = (v: number | undefined) => round2((v ?? PX_PER_INCH) / PX_PER_INCH);\n const color = (style as Record<string, unknown> | undefined)?.[PAGE_COLOR_KEY];\n return {\n pageless: style?.documentFlavor === MODERN,\n orientation: landscape ? \"landscape\" : \"portrait\",\n paper,\n marginTop: inch(style?.marginTop),\n marginBottom: inch(style?.marginBottom),\n marginLeft: inch(style?.marginLeft),\n marginRight: inch(style?.marginRight),\n pageColor: typeof color === \"string\" && color ? color : DEFAULT_PAGE_COLOR,\n };\n}\n\n/** Params for Univer's `docs.command.page-setup` (pageSize is the as-printed size: swapped when landscape). */\nexport function pageSetupCommandParams(v: PageSetupValues) {\n const base = PAPER_SIZES[v.paper];\n const landscape = v.orientation === \"landscape\";\n return {\n documentFlavor: v.pageless ? MODERN : TRADITIONAL,\n pageOrient: landscape ? LANDSCAPE : PORTRAIT,\n pageSize: landscape ? { width: base.height, height: base.width } : { width: base.width, height: base.height },\n marginTop: Math.round(v.marginTop * PX_PER_INCH),\n marginBottom: Math.round(v.marginBottom * PX_PER_INCH),\n marginLeft: Math.round(v.marginLeft * PX_PER_INCH),\n marginRight: Math.round(v.marginRight * PX_PER_INCH),\n };\n}\n","/**\n * Where each Office product lives. The map is CONFIGURATION, not a constant: the app passes its own\n * (production `https://sheets.aimatrx.com`, development `http://sheets.localhost:5190`), so links\n * generated here always match the app that is running.\n *\n * The routes are the same on every host: `/` the product's File Home, `/templates`,\n * `/new?template=<id>`, `/f/<id>` an item.\n */\n\nexport type OfficeProduct = \"sheets\" | \"docs\" | \"slides\" | \"bases\" | \"boards\" | \"pdf\";\n\nexport const OFFICE_PRODUCTS: readonly OfficeProduct[] = [\"sheets\", \"docs\", \"slides\", \"bases\", \"boards\", \"pdf\"];\n\nexport type OfficeHostMap = Record<OfficeProduct, string>;\n\n// Module-level mutable state is banned (a dual ESM/CJS or duplicated bundle would split it): one slot on globalThis.\nconst SLOT = Symbol.for(\"ai-matrx.office.hosts\");\ntype Slot = { map: OfficeHostMap | null };\n\nfunction slot(): Slot {\n const g = globalThis as unknown as Record<symbol, Slot | undefined>;\n let s = g[SLOT];\n if (!s) {\n s = { map: null };\n g[SLOT] = s;\n }\n return s;\n}\n\n/** Set the product → origin map (an origin such as `https://sheets.aimatrx.com`, no trailing slash needed). Call once at start-up. */\nexport function configureOfficeHosts(map: OfficeHostMap): void {\n const cleaned = {} as OfficeHostMap;\n for (const product of OFFICE_PRODUCTS) {\n const origin = map[product];\n if (typeof origin !== \"string\" || !/^https?:\\/\\/[^/\\s]+$/.test(origin.replace(/\\/+$/, \"\"))) {\n throw new Error(`configureOfficeHosts: \"${product}\" needs an origin like \"https://${product}.example.com\", got ${JSON.stringify(origin)}`);\n }\n cleaned[product] = origin.replace(/\\/+$/, \"\");\n }\n slot().map = cleaned;\n}\n\n/** The configured origin of a product. Throws when the app never configured the hosts. */\nexport function officeOrigin(product: OfficeProduct): string {\n const map = slot().map;\n if (!map) throw new Error(\"officeHref: call configureOfficeHosts(map) at start-up before generating Office links\");\n return map[product];\n}\n\n/**\n * An absolute link into a product: no id is its File Home, an id opens that item (`/f/<id>`).\n * `sub` appends a path segment (`officeHref(\"docs\", undefined, { sub: \"templates\" })` is the Templates page).\n */\nexport function officeHref(product: OfficeProduct, id?: string, o?: { sub?: string }): string {\n const origin = officeOrigin(product);\n const sub = o?.sub ? `/${o.sub.replace(/^\\/+/, \"\")}` : \"\";\n if (id === undefined) return `${origin}${sub || \"/\"}`;\n return `${origin}/f/${encodeURIComponent(id)}${sub}`;\n}\n"],"mappings":"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","names":[]}
package/dist/docs-io.cjs CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
2
 
3
3
 
4
4
 
@@ -260,65 +260,144 @@ function documentFileKind(name) {
260
260
  return null;
261
261
  }
262
262
  var W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
263
+ var R = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
263
264
  function markdownTable(rows) {
264
265
  const width = Math.max(...rows.map((r) => r.length));
265
266
  const line = (r) => `| ${Array.from({ length: width }, (_, i) => _nullishCoalesce(r[i], () => ( ""))).join(" | ")} |`;
266
267
  return [line(rows[0]), `|${" --- |".repeat(width)}`, ...rows.slice(1).map(line)].join("\n");
267
268
  }
268
- function docxXmlToMarkdown(xml) {
269
+ function docxRelationships(rels) {
270
+ const out = /* @__PURE__ */ new Map();
271
+ if (!rels) return out;
272
+ const doc = new DOMParser().parseFromString(rels, "application/xml");
273
+ for (const el of Array.from(doc.getElementsByTagName("*"))) {
274
+ if (el.localName !== "Relationship") continue;
275
+ const id = el.getAttribute("Id");
276
+ const target = el.getAttribute("Target");
277
+ if (!id || !target) continue;
278
+ const external = el.getAttribute("TargetMode") === "External" || /^[a-z][a-z0-9+.-]*:/i.test(target);
279
+ out.set(id, external ? target : target.startsWith("/") ? target.slice(1) : `word/${target}`.replace(/\/\.\//g, "/"));
280
+ }
281
+ return out;
282
+ }
283
+ function docxNumbering(xml) {
284
+ if (!xml) return () => null;
285
+ const doc = new DOMParser().parseFromString(xml, "application/xml");
286
+ const val = (el, name) => _nullishCoalesce(_nullishCoalesce(_optionalChain([el, 'optionalAccess', _31 => _31.getAttributeNS, 'call', _32 => _32(W, name)]), () => ( _optionalChain([el, 'optionalAccess', _33 => _33.getAttribute, 'call', _34 => _34(`w:${name}`)]))), () => ( null));
287
+ const abstract = /* @__PURE__ */ new Map();
288
+ for (const a of Array.from(doc.getElementsByTagNameNS(W, "abstractNum"))) {
289
+ const levels = /* @__PURE__ */ new Map();
290
+ for (const lvl of Array.from(a.getElementsByTagNameNS(W, "lvl"))) {
291
+ const fmt = val(lvl.getElementsByTagNameNS(W, "numFmt")[0], "val");
292
+ if (fmt) levels.set(Number(_nullishCoalesce(val(lvl, "ilvl"), () => ( 0))), fmt);
293
+ }
294
+ abstract.set(_nullishCoalesce(val(a, "abstractNumId"), () => ( "")), levels);
295
+ }
296
+ const nums = /* @__PURE__ */ new Map();
297
+ for (const n of Array.from(doc.getElementsByTagNameNS(W, "num"))) {
298
+ const ref = val(n.getElementsByTagNameNS(W, "abstractNumId")[0], "val");
299
+ if (ref != null) nums.set(_nullishCoalesce(val(n, "numId"), () => ( "")), ref);
300
+ }
301
+ return (numId, level) => _nullishCoalesce(_optionalChain([abstract, 'access', _35 => _35.get, 'call', _36 => _36(_nullishCoalesce(nums.get(numId), () => ( ""))), 'optionalAccess', _37 => _37.get, 'call', _38 => _38(level)]), () => ( null));
302
+ }
303
+ function docxXmlToMarkdown(xml, parts = {}) {
269
304
  const doc = new DOMParser().parseFromString(xml, "application/xml");
270
305
  const body = doc.getElementsByTagNameNS(W, "body")[0];
271
306
  if (!body) throw new Error("This Word file has no document body.");
272
- const attr = (el, name) => _nullishCoalesce(_nullishCoalesce(_optionalChain([el, 'optionalAccess', _31 => _31.getAttributeNS, 'call', _32 => _32(W, name)]), () => ( _optionalChain([el, 'optionalAccess', _33 => _33.getAttribute, 'call', _34 => _34(`w:${name}`)]))), () => ( null));
307
+ const rels = docxRelationships(parts.rels);
308
+ const listFormat = docxNumbering(parts.numbering);
309
+ const attr = (el, name) => _nullishCoalesce(_nullishCoalesce(_optionalChain([el, 'optionalAccess', _39 => _39.getAttributeNS, 'call', _40 => _40(W, name)]), () => ( _optionalChain([el, 'optionalAccess', _41 => _41.getAttribute, 'call', _42 => _42(`w:${name}`)]))), () => ( null));
273
310
  const first = (el, name) => el.getElementsByTagNameNS(W, name)[0];
274
311
  const on = (props, name) => {
275
312
  const el = props ? first(props, name) : void 0;
276
313
  return !!el && attr(el, "val") !== "0" && attr(el, "val") !== "false";
277
314
  };
278
- const paragraph = (p) => {
315
+ const picture = (r) => {
316
+ const all = Array.from(r.getElementsByTagName("*"));
317
+ const blip = all.find((e) => e.localName === "blip");
318
+ const id = _optionalChain([blip, 'optionalAccess', _43 => _43.getAttributeNS, 'call', _44 => _44(R, "embed")]) || _optionalChain([blip, 'optionalAccess', _45 => _45.getAttribute, 'call', _46 => _46("r:embed")]) || _optionalChain([blip, 'optionalAccess', _47 => _47.getAttributeNS, 'call', _48 => _48(R, "link")]) || _optionalChain([blip, 'optionalAccess', _49 => _49.getAttribute, 'call', _50 => _50("r:link")]);
319
+ const target = id ? rels.get(id) : void 0;
320
+ if (!target) return "";
321
+ const named = all.find((e) => e.localName === "docPr");
322
+ const alt = (_optionalChain([named, 'optionalAccess', _51 => _51.getAttribute, 'call', _52 => _52("descr")]) || _optionalChain([named, 'optionalAccess', _53 => _53.getAttribute, 'call', _54 => _54("title")]) || "").replace(/[[\]]/g, "");
323
+ return `![${alt}](${target.replace(/ /g, "%20")})`;
324
+ };
325
+ const run = (r) => {
326
+ const t = Array.from(r.childNodes).map((n) => {
327
+ const el = n;
328
+ if (el.localName === "t") return _nullishCoalesce(el.textContent, () => ( ""));
329
+ if (el.localName === "tab") return " ";
330
+ if (el.localName === "br") return " \n";
331
+ return "";
332
+ }).join("");
333
+ const pic = Array.from(r.childNodes).some((n) => n.localName === "drawing" || n.localName === "pict") ? picture(r) : "";
334
+ if (!t) return pic;
335
+ const rPr = first(r, "rPr");
336
+ const bold = on(rPr, "b");
337
+ const italic = on(rPr, "i");
338
+ const strike = on(rPr, "strike");
339
+ const lead = _nullishCoalesce(_optionalChain([t, 'access', _55 => _55.match, 'call', _56 => _56(/^\s*/), 'optionalAccess', _57 => _57[0]]), () => ( ""));
340
+ const trail = _nullishCoalesce(_optionalChain([t, 'access', _58 => _58.match, 'call', _59 => _59(/\s*$/), 'optionalAccess', _60 => _60[0]]), () => ( ""));
341
+ const core = t.trim();
342
+ if (!core) return t + pic;
343
+ let marked = core;
344
+ if (bold) marked = `**${marked}**`;
345
+ if (italic) marked = `*${marked}*`;
346
+ if (strike) marked = `~~${marked}~~`;
347
+ return `${lead}${marked}${trail}${pic}`;
348
+ };
349
+ const inline = (parent) => {
279
350
  let text = "";
280
- for (const r of Array.from(p.getElementsByTagNameNS(W, "r"))) {
281
- const t = Array.from(r.childNodes).map((n) => {
282
- const el = n;
283
- if (el.localName === "t") return _nullishCoalesce(el.textContent, () => ( ""));
284
- if (el.localName === "tab") return " ";
285
- if (el.localName === "br") return " \n";
286
- return "";
287
- }).join("");
288
- if (!t) continue;
289
- const rPr = first(r, "rPr");
290
- const bold = on(rPr, "b");
291
- const italic = on(rPr, "i");
292
- const lead = _nullishCoalesce(_optionalChain([t, 'access', _35 => _35.match, 'call', _36 => _36(/^\s*/), 'optionalAccess', _37 => _37[0]]), () => ( ""));
293
- const trail = _nullishCoalesce(_optionalChain([t, 'access', _38 => _38.match, 'call', _39 => _39(/\s*$/), 'optionalAccess', _40 => _40[0]]), () => ( ""));
294
- const core = t.trim();
295
- text += core ? `${lead}${bold ? "**" : ""}${italic ? "*" : ""}${core}${italic ? "*" : ""}${bold ? "**" : ""}${trail}` : t;
351
+ for (const node of Array.from(parent.childNodes)) {
352
+ const el = node;
353
+ if (el.nodeType !== 1) continue;
354
+ if (el.localName === "r") text += run(el);
355
+ else if (el.localName === "hyperlink") {
356
+ const words = inline(el);
357
+ const id = el.getAttributeNS(R, "id") || el.getAttribute("r:id");
358
+ const href = id ? rels.get(id) : void 0;
359
+ const anchor = el.getAttributeNS(W, "anchor") || el.getAttribute("w:anchor");
360
+ text += words.trim() && (href || anchor) ? `[${words.trim()}](${_nullishCoalesce(href, () => ( `#${anchor}`))})` : words;
361
+ } else if (el.localName === "ins" || el.localName === "smartTag" || el.localName === "sdt" || el.localName === "sdtContent" || el.localName === "fldSimple") text += inline(el);
296
362
  }
363
+ return text;
364
+ };
365
+ const paragraph = (p) => {
366
+ const text = inline(p);
297
367
  const pPr = first(p, "pPr");
298
368
  const style = _nullishCoalesce(attr(pPr ? first(pPr, "pStyle") : void 0, "val"), () => ( ""));
299
- const heading = _nullishCoalesce(_optionalChain([style, 'access', _41 => _41.match, 'call', _42 => _42(/^(?:Heading|heading)\s?(\d)$/), 'optionalAccess', _43 => _43[1]]), () => ( (style === "Title" ? "1" : null)));
369
+ const heading = _nullishCoalesce(_optionalChain([style, 'access', _61 => _61.match, 'call', _62 => _62(/^(?:Heading|heading)\s?(\d)$/), 'optionalAccess', _63 => _63[1]]), () => ( (style === "Title" ? "1" : null)));
300
370
  if (heading && text.trim()) return `${"#".repeat(Math.min(6, Number(heading)))} ${text.replace(/\*+/g, "").trim()}`;
301
- if (pPr && first(pPr, "numPr") && text.trim()) {
302
- const level = Number(_nullishCoalesce(attr(first(first(pPr, "numPr"), "ilvl"), "val"), () => ( 0)));
303
- return `${" ".repeat(level)}- ${text.trim()}`;
371
+ const numPr = pPr ? first(pPr, "numPr") : void 0;
372
+ if (numPr && text.trim()) {
373
+ const level = Number(_nullishCoalesce(attr(first(numPr, "ilvl"), "val"), () => ( 0)));
374
+ const fmt = listFormat(_nullishCoalesce(attr(first(numPr, "numId"), "val"), () => ( "")), level);
375
+ const marker = fmt && fmt !== "bullet" && fmt !== "none" ? "1." : "-";
376
+ return `${" ".repeat(level)}${marker} ${text.trim()}`;
304
377
  }
305
378
  return text;
306
379
  };
307
380
  const out = [];
308
381
  for (const child of Array.from(body.children)) {
309
- if (child.localName === "p") out.push(paragraph(child));
310
- else if (child.localName === "tbl") {
382
+ if (child.localName === "p") {
383
+ const pPr = first(child, "pPr");
384
+ const style = _nullishCoalesce(attr(pPr ? first(pPr, "pStyle") : void 0, "val"), () => ( ""));
385
+ if (/^caption$/i.test(style) && /^!\[[^\]]*\]\([^)]*\)$/.test(_nullishCoalesce(out[out.length - 1], () => ( "")))) continue;
386
+ out.push(paragraph(child));
387
+ } else if (child.localName === "tbl") {
311
388
  const rows = Array.from(child.getElementsByTagNameNS(W, "tr")).map(
312
389
  (tr) => Array.from(tr.getElementsByTagNameNS(W, "tc")).map(
313
390
  (tc) => Array.from(tc.getElementsByTagNameNS(W, "p")).map(paragraph).join(" ").replace(/\|/g, "\\|").trim()
314
391
  )
315
392
  );
393
+ const head = rows[0];
394
+ if (head && head.every((c) => !c || /^\*\*[^*]+\*\*$/.test(c))) rows[0] = head.map((c) => c.replace(/^\*\*|\*\*$/g, ""));
316
395
  if (rows.length) out.push(markdownTable(rows));
317
396
  }
318
397
  }
319
398
  return out.reduce((acc, line, i) => {
320
- const prevList = /^\s*- /.test(_nullishCoalesce(out[i - 1], () => ( "")));
321
- if (i > 0 && !(prevList && /^\s*- /.test(line))) acc.push("");
399
+ const isList = (l) => /^\s*(-|\d+\.) /.test(_nullishCoalesce(l, () => ( "")));
400
+ if (i > 0 && !(isList(out[i - 1]) && isList(line))) acc.push("");
322
401
  acc.push(line);
323
402
  return acc;
324
403
  }, []).join("\n").replace(/\n{3,}/g, "\n\n").trim();
@@ -405,7 +484,10 @@ async function documentFileToMarkdown(file) {
405
484
  const entry = zip.get("word/document.xml");
406
485
  if (!entry) throw new Error("This is not a Word document (.docx).");
407
486
  const xml = await entry.text();
408
- return { markdown: docxXmlToMarkdown(xml), source: "imported_docx" };
487
+ const rels = await _asyncNullishCoalesce(await _optionalChain([zip, 'access', _64 => _64.get, 'call', _65 => _65("word/_rels/document.xml.rels"), 'optionalAccess', _66 => _66.text, 'call', _67 => _67()]), async () => ( null));
488
+ const numbering = await _asyncNullishCoalesce(await _optionalChain([zip, 'access', _68 => _68.get, 'call', _69 => _69("word/numbering.xml"), 'optionalAccess', _70 => _70.text, 'call', _71 => _71()]), async () => ( null));
489
+ const markdown = docxXmlToMarkdown(xml, { rels, numbering }).replace(/!\[[^\]]*\]\(word\/[^)]*\)/g, "");
490
+ return { markdown, source: "imported_docx" };
409
491
  }
410
492
  const text = await file.text();
411
493
  if (kind === "md") return { markdown: text, source: "imported_md" };
@@ -499,5 +581,6 @@ async function downloadOpenFormat(markdown, format, title) {
499
581
 
500
582
 
501
583
 
502
- exports.DOCUMENT_UPLOAD_ACCEPT = DOCUMENT_UPLOAD_ACCEPT; exports.HEADING_LEVEL_BY_FONT_SIZE = HEADING_LEVEL_BY_FONT_SIZE; exports.OPEN_FORMATS = OPEN_FORMATS; exports.deriveDocumentName = _chunkXAIKXHHJcjs.deriveDocumentName; exports.documentFileKind = documentFileKind; exports.documentFileToMarkdown = documentFileToMarkdown; exports.documentImageSources = documentImageSources; exports.docxXmlToMarkdown = docxXmlToMarkdown; exports.downloadOpenFormat = downloadOpenFormat; exports.exportOpenFormat = exportOpenFormat; exports.htmlToDocumentMarkdown = htmlToDocumentMarkdown; exports.imageMarkdownSource = imageMarkdownSource; exports.importDocumentFile = importDocumentFile; exports.markdownToUniverDoc = _chunkXAIKXHHJcjs.markdownToUniverDoc; exports.univerDocToMarkdown = univerDocToMarkdown;
584
+
585
+ exports.DOCUMENT_UPLOAD_ACCEPT = DOCUMENT_UPLOAD_ACCEPT; exports.HEADING_LEVEL_BY_FONT_SIZE = HEADING_LEVEL_BY_FONT_SIZE; exports.OPEN_FORMATS = OPEN_FORMATS; exports.deriveDocumentName = _chunkXAIKXHHJcjs.deriveDocumentName; exports.documentFileKind = documentFileKind; exports.documentFileToMarkdown = documentFileToMarkdown; exports.documentImageSources = documentImageSources; exports.docxRelationships = docxRelationships; exports.docxXmlToMarkdown = docxXmlToMarkdown; exports.downloadOpenFormat = downloadOpenFormat; exports.exportOpenFormat = exportOpenFormat; exports.htmlToDocumentMarkdown = htmlToDocumentMarkdown; exports.imageMarkdownSource = imageMarkdownSource; exports.importDocumentFile = importDocumentFile; exports.markdownToUniverDoc = _chunkXAIKXHHJcjs.markdownToUniverDoc; exports.univerDocToMarkdown = univerDocToMarkdown;
503
586
  //# sourceMappingURL=docs-io.cjs.map