@open-mercato/documents 0.10.1-develop.7808.1.8128cefd28 → 0.10.1-develop.7809.1.ea24461aeb

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.
Files changed (22) hide show
  1. package/dist/contentSecurityPolicy.js +23 -0
  2. package/dist/contentSecurityPolicy.js.map +7 -0
  3. package/dist/modules/documents/backend/documents/[id]/DocumentCanvas.js +3 -1
  4. package/dist/modules/documents/backend/documents/[id]/DocumentCanvas.js.map +2 -2
  5. package/dist/modules/documents/backend/documents/[id]/DocumentEditorSurface.js +3 -1
  6. package/dist/modules/documents/backend/documents/[id]/DocumentEditorSurface.js.map +2 -2
  7. package/dist/modules/documents/backend/documents/[id]/VersionPreviewDialog.js +3 -0
  8. package/dist/modules/documents/backend/documents/[id]/VersionPreviewDialog.js.map +2 -2
  9. package/dist/modules/documents/backend/documents/[id]/useDocumentEditor.js +3 -0
  10. package/dist/modules/documents/backend/documents/[id]/useDocumentEditor.js.map +2 -2
  11. package/dist/modules/documents/backend/documents/components/TemplateBodyEditor.js +3 -0
  12. package/dist/modules/documents/backend/documents/components/TemplateBodyEditor.js.map +2 -2
  13. package/dist/modules/documents/backend/documents/components/TemplatePreview.js +3 -0
  14. package/dist/modules/documents/backend/documents/components/TemplatePreview.js.map +2 -2
  15. package/package.json +6 -6
  16. package/src/contentSecurityPolicy.ts +21 -0
  17. package/src/modules/documents/backend/documents/[id]/DocumentCanvas.tsx +3 -1
  18. package/src/modules/documents/backend/documents/[id]/DocumentEditorSurface.tsx +3 -1
  19. package/src/modules/documents/backend/documents/[id]/VersionPreviewDialog.tsx +3 -0
  20. package/src/modules/documents/backend/documents/[id]/useDocumentEditor.ts +3 -0
  21. package/src/modules/documents/backend/documents/components/TemplateBodyEditor.tsx +3 -0
  22. package/src/modules/documents/backend/documents/components/TemplatePreview.tsx +3 -0
@@ -0,0 +1,23 @@
1
+ import { createLogger } from "@open-mercato/shared/lib/logger";
2
+ import { resolveDocumentsCollaborationEndpoint } from "./modules/documents/lib/collabEndpoint.js";
3
+ let warnedAboutExcludedEndpoint = false;
4
+ function resolveDocumentsCollaborationCspOrigins() {
5
+ const endpoint = resolveDocumentsCollaborationEndpoint();
6
+ if (!endpoint) return [];
7
+ const url = new URL(endpoint);
8
+ if (url.username || url.password || !/^(?:[a-z0-9.-]+|\[[0-9a-f:]+\])$/i.test(url.hostname)) {
9
+ if (!warnedAboutExcludedEndpoint) {
10
+ warnedAboutExcludedEndpoint = true;
11
+ createLogger("documents:csp").warn(
12
+ "[internal] Collaboration endpoint excluded from CSP; use a credential-free DNS or IPv6 URL",
13
+ { configuration: "NEXT_PUBLIC_DOCUMENTS_COLLAB_URL" }
14
+ );
15
+ }
16
+ return [];
17
+ }
18
+ return [url.origin];
19
+ }
20
+ export {
21
+ resolveDocumentsCollaborationCspOrigins
22
+ };
23
+ //# sourceMappingURL=contentSecurityPolicy.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/contentSecurityPolicy.ts"],
4
+ "sourcesContent": ["import { createLogger } from '@open-mercato/shared/lib/logger'\nimport { resolveDocumentsCollaborationEndpoint } from './modules/documents/lib/collabEndpoint'\n\nlet warnedAboutExcludedEndpoint = false\n\nexport function resolveDocumentsCollaborationCspOrigins(): string[] {\n const endpoint = resolveDocumentsCollaborationEndpoint()\n if (!endpoint) return []\n const url = new URL(endpoint)\n if (url.username || url.password || !/^(?:[a-z0-9.-]+|\\[[0-9a-f:]+\\])$/i.test(url.hostname)) {\n if (!warnedAboutExcludedEndpoint) {\n warnedAboutExcludedEndpoint = true\n createLogger('documents:csp').warn(\n '[internal] Collaboration endpoint excluded from CSP; use a credential-free DNS or IPv6 URL',\n { configuration: 'NEXT_PUBLIC_DOCUMENTS_COLLAB_URL' },\n )\n }\n return []\n }\n return [url.origin]\n}\n"],
5
+ "mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,6CAA6C;AAEtD,IAAI,8BAA8B;AAE3B,SAAS,0CAAoD;AAClE,QAAM,WAAW,sCAAsC;AACvD,MAAI,CAAC,SAAU,QAAO,CAAC;AACvB,QAAM,MAAM,IAAI,IAAI,QAAQ;AAC5B,MAAI,IAAI,YAAY,IAAI,YAAY,CAAC,oCAAoC,KAAK,IAAI,QAAQ,GAAG;AAC3F,QAAI,CAAC,6BAA6B;AAChC,oCAA8B;AAC9B,mBAAa,eAAe,EAAE;AAAA,QAC5B;AAAA,QACA,EAAE,eAAe,mCAAmC;AAAA,MACtD;AAAA,IACF;AACA,WAAO,CAAC;AAAA,EACV;AACA,SAAO,CAAC,IAAI,MAAM;AACpB;",
6
+ "names": []
7
+ }
@@ -14,6 +14,7 @@ import { OutlinePane } from "./OutlinePane.js";
14
14
  import { captureCommentAnchor } from "./CommentAnchorNavigation.js";
15
15
  import { DOCUMENT_EDITOR_CONTENT_CLASS } from "./editorTypes.js";
16
16
  import { DOCUMENT_PAGINATION_STYLES } from "./documentPagination.js";
17
+ import { useContentSecurityPolicyNonce } from "@open-mercato/ui/theme/ContentSecurityPolicyProvider";
17
18
  const EMPTY_BUBBLE_MENU_STATE = {
18
19
  bold: false,
19
20
  italic: false,
@@ -30,6 +31,7 @@ function DocumentCanvas({
30
31
  onComment
31
32
  }) {
32
33
  const t = useT();
34
+ const nonce = useContentSecurityPolicyNonce();
33
35
  const editorState = useEditorState({
34
36
  editor,
35
37
  selector: ({ editor: current }) => current ? {
@@ -50,7 +52,7 @@ function DocumentCanvas({
50
52
  [onComment, readOnly]
51
53
  );
52
54
  return /* @__PURE__ */ jsxs("div", { className: "om-doc-canvas max-h-[calc(100dvh-var(--topbar-height,0px)-12rem)] min-h-96 overflow-auto bg-muted p-0 sm:px-4 sm:py-8 md:px-8 md:py-10", children: [
53
- /* @__PURE__ */ jsx("style", { children: DOCUMENT_PAGINATION_STYLES }),
55
+ /* @__PURE__ */ jsx("style", { nonce, children: DOCUMENT_PAGINATION_STYLES }),
54
56
  notice ? /* @__PURE__ */ jsx(Alert, { status: "information", style: "lighter", className: "mx-auto mb-4 max-w-3xl", children: /* @__PURE__ */ jsx(AlertDescription, { children: notice }) }) : null,
55
57
  /* @__PURE__ */ jsxs("div", { className: "mx-auto flex w-fit min-w-full flex-col gap-4 lg:flex-row lg:items-start", children: [
56
58
  outlineOpen ? /* @__PURE__ */ jsx(OutlinePane, { editor }) : null,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/documents/backend/documents/%5Bid%5D/DocumentCanvas.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { Editor } from '@tiptap/core'\nimport { EditorContent, useEditorState } from '@tiptap/react'\nimport { BubbleMenu } from '@tiptap/react/menus'\nimport { Bold, Italic, Link2, MessageSquare, Underline } from 'lucide-react'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { OutlinePane } from './OutlinePane'\nimport { captureCommentAnchor, type CommentAnchor } from './CommentAnchorNavigation'\nimport { DOCUMENT_EDITOR_CONTENT_CLASS } from './editorTypes'\nimport { DOCUMENT_PAGINATION_STYLES } from './documentPagination'\n\ntype TitleModel = {\n value: string\n setValue: (value: string) => void\n saving: boolean\n commit: () => Promise<void>\n onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => void\n}\n\nconst EMPTY_BUBBLE_MENU_STATE = {\n bold: false,\n italic: false,\n underline: false,\n link: false,\n}\n\nexport function DocumentCanvas({\n editor,\n title,\n readOnly,\n outlineOpen,\n notice,\n onOpenLink,\n onComment,\n}: {\n editor: Editor | null\n title: TitleModel\n readOnly: boolean\n outlineOpen: boolean\n notice: string | null\n onOpenLink: () => void\n onComment?: (anchor: CommentAnchor) => void\n}) {\n const t = useT()\n const editorState = useEditorState({\n editor,\n selector: ({ editor: current }) => current ? {\n bold: current.isActive('bold'),\n italic: current.isActive('italic'),\n underline: current.isActive('underline'),\n link: current.isActive('link'),\n } : EMPTY_BUBBLE_MENU_STATE,\n }) ?? EMPTY_BUBBLE_MENU_STATE\n const keepSelection = React.useCallback((event: React.MouseEvent<HTMLButtonElement>) => event.preventDefault(), [])\n const comment = React.useCallback(() => {\n if (!editor || !onComment) return\n const anchor = captureCommentAnchor(editor)\n if (anchor) onComment(anchor)\n }, [editor, onComment])\n const shouldShow = React.useCallback(\n ({ from, to }: { from: number; to: number }) => from !== to && (!readOnly || Boolean(onComment)),\n [onComment, readOnly],\n )\n\n return (\n <div className=\"om-doc-canvas max-h-[calc(100dvh-var(--topbar-height,0px)-12rem)] min-h-96 overflow-auto bg-muted p-0 sm:px-4 sm:py-8 md:px-8 md:py-10\">\n <style>{DOCUMENT_PAGINATION_STYLES}</style>\n {notice ? <Alert status=\"information\" style=\"lighter\" className=\"mx-auto mb-4 max-w-3xl\"><AlertDescription>{notice}</AlertDescription></Alert> : null}\n <div className=\"mx-auto flex w-fit min-w-full flex-col gap-4 lg:flex-row lg:items-start\">\n {outlineOpen ? <OutlinePane editor={editor} /> : null}\n <article className=\"om-doc-paper mx-auto flex-none bg-card sm:rounded-sm sm:shadow-lg\">\n {readOnly ? (\n <h1 className=\"mb-6 text-xl font-semibold leading-tight text-foreground sm:mb-8 sm:text-3xl\">{title.value}</h1>\n ) : (\n <Input\n value={title.value}\n onChange={(event) => title.setValue(event.target.value)}\n onBlur={() => void title.commit()}\n onKeyDown={title.onKeyDown}\n maxLength={512}\n disabled={title.saving}\n aria-label={t('documents.editor.title.ariaLabel')}\n className=\"mb-6 h-auto border-0 bg-transparent px-0 py-0 text-xl font-semibold leading-tight text-foreground shadow-none focus-visible:shadow-focus sm:mb-8 sm:text-3xl\"\n />\n )}\n {editor ? (\n <BubbleMenu editor={editor} shouldShow={shouldShow} className=\"z-popover flex items-center gap-1 rounded-md border border-border bg-card p-1 shadow-md\">\n {!readOnly ? (\n <>\n <IconButton type=\"button\" size=\"sm\" variant={editorState.bold ? 'outline' : 'ghost'} aria-label={t('documents.editor.toolbar.bold')} aria-pressed={editorState.bold} onMouseDown={keepSelection} onClick={() => editor.chain().focus().toggleBold().run()}><Bold /></IconButton>\n <IconButton type=\"button\" size=\"sm\" variant={editorState.italic ? 'outline' : 'ghost'} aria-label={t('documents.editor.toolbar.italic')} aria-pressed={editorState.italic} onMouseDown={keepSelection} onClick={() => editor.chain().focus().toggleItalic().run()}><Italic /></IconButton>\n <IconButton type=\"button\" size=\"sm\" variant={editorState.underline ? 'outline' : 'ghost'} aria-label={t('documents.editor.toolbar.underline')} aria-pressed={editorState.underline} onMouseDown={keepSelection} onClick={() => editor.chain().focus().toggleMark('underline').run()}><Underline /></IconButton>\n <IconButton type=\"button\" size=\"sm\" variant={editorState.link ? 'outline' : 'ghost'} aria-label={t('documents.editor.toolbar.link')} aria-pressed={editorState.link} onMouseDown={keepSelection} onClick={onOpenLink}><Link2 /></IconButton>\n </>\n ) : null}\n {onComment ? <Button type=\"button\" size=\"sm\" variant=\"ghost\" onMouseDown={keepSelection} onClick={comment}><MessageSquare />{t('documents.editor.toolbar.comment')}</Button> : null}\n </BubbleMenu>\n ) : null}\n <EditorContent className={cn(DOCUMENT_EDITOR_CONTENT_CLASS, readOnly && 'select-text')} editor={editor} />\n </article>\n </div>\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { Editor } from '@tiptap/core'\nimport { EditorContent, useEditorState } from '@tiptap/react'\nimport { BubbleMenu } from '@tiptap/react/menus'\nimport { Bold, Italic, Link2, MessageSquare, Underline } from 'lucide-react'\nimport { Alert, AlertDescription } from '@open-mercato/ui/primitives/alert'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { OutlinePane } from './OutlinePane'\nimport { captureCommentAnchor, type CommentAnchor } from './CommentAnchorNavigation'\nimport { DOCUMENT_EDITOR_CONTENT_CLASS } from './editorTypes'\nimport { DOCUMENT_PAGINATION_STYLES } from './documentPagination'\nimport { useContentSecurityPolicyNonce } from '@open-mercato/ui/theme/ContentSecurityPolicyProvider'\n\ntype TitleModel = {\n value: string\n setValue: (value: string) => void\n saving: boolean\n commit: () => Promise<void>\n onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => void\n}\n\nconst EMPTY_BUBBLE_MENU_STATE = {\n bold: false,\n italic: false,\n underline: false,\n link: false,\n}\n\nexport function DocumentCanvas({\n editor,\n title,\n readOnly,\n outlineOpen,\n notice,\n onOpenLink,\n onComment,\n}: {\n editor: Editor | null\n title: TitleModel\n readOnly: boolean\n outlineOpen: boolean\n notice: string | null\n onOpenLink: () => void\n onComment?: (anchor: CommentAnchor) => void\n}) {\n const t = useT()\n const nonce = useContentSecurityPolicyNonce()\n const editorState = useEditorState({\n editor,\n selector: ({ editor: current }) => current ? {\n bold: current.isActive('bold'),\n italic: current.isActive('italic'),\n underline: current.isActive('underline'),\n link: current.isActive('link'),\n } : EMPTY_BUBBLE_MENU_STATE,\n }) ?? EMPTY_BUBBLE_MENU_STATE\n const keepSelection = React.useCallback((event: React.MouseEvent<HTMLButtonElement>) => event.preventDefault(), [])\n const comment = React.useCallback(() => {\n if (!editor || !onComment) return\n const anchor = captureCommentAnchor(editor)\n if (anchor) onComment(anchor)\n }, [editor, onComment])\n const shouldShow = React.useCallback(\n ({ from, to }: { from: number; to: number }) => from !== to && (!readOnly || Boolean(onComment)),\n [onComment, readOnly],\n )\n\n return (\n <div className=\"om-doc-canvas max-h-[calc(100dvh-var(--topbar-height,0px)-12rem)] min-h-96 overflow-auto bg-muted p-0 sm:px-4 sm:py-8 md:px-8 md:py-10\">\n <style nonce={nonce}>{DOCUMENT_PAGINATION_STYLES}</style>\n {notice ? <Alert status=\"information\" style=\"lighter\" className=\"mx-auto mb-4 max-w-3xl\"><AlertDescription>{notice}</AlertDescription></Alert> : null}\n <div className=\"mx-auto flex w-fit min-w-full flex-col gap-4 lg:flex-row lg:items-start\">\n {outlineOpen ? <OutlinePane editor={editor} /> : null}\n <article className=\"om-doc-paper mx-auto flex-none bg-card sm:rounded-sm sm:shadow-lg\">\n {readOnly ? (\n <h1 className=\"mb-6 text-xl font-semibold leading-tight text-foreground sm:mb-8 sm:text-3xl\">{title.value}</h1>\n ) : (\n <Input\n value={title.value}\n onChange={(event) => title.setValue(event.target.value)}\n onBlur={() => void title.commit()}\n onKeyDown={title.onKeyDown}\n maxLength={512}\n disabled={title.saving}\n aria-label={t('documents.editor.title.ariaLabel')}\n className=\"mb-6 h-auto border-0 bg-transparent px-0 py-0 text-xl font-semibold leading-tight text-foreground shadow-none focus-visible:shadow-focus sm:mb-8 sm:text-3xl\"\n />\n )}\n {editor ? (\n <BubbleMenu editor={editor} shouldShow={shouldShow} className=\"z-popover flex items-center gap-1 rounded-md border border-border bg-card p-1 shadow-md\">\n {!readOnly ? (\n <>\n <IconButton type=\"button\" size=\"sm\" variant={editorState.bold ? 'outline' : 'ghost'} aria-label={t('documents.editor.toolbar.bold')} aria-pressed={editorState.bold} onMouseDown={keepSelection} onClick={() => editor.chain().focus().toggleBold().run()}><Bold /></IconButton>\n <IconButton type=\"button\" size=\"sm\" variant={editorState.italic ? 'outline' : 'ghost'} aria-label={t('documents.editor.toolbar.italic')} aria-pressed={editorState.italic} onMouseDown={keepSelection} onClick={() => editor.chain().focus().toggleItalic().run()}><Italic /></IconButton>\n <IconButton type=\"button\" size=\"sm\" variant={editorState.underline ? 'outline' : 'ghost'} aria-label={t('documents.editor.toolbar.underline')} aria-pressed={editorState.underline} onMouseDown={keepSelection} onClick={() => editor.chain().focus().toggleMark('underline').run()}><Underline /></IconButton>\n <IconButton type=\"button\" size=\"sm\" variant={editorState.link ? 'outline' : 'ghost'} aria-label={t('documents.editor.toolbar.link')} aria-pressed={editorState.link} onMouseDown={keepSelection} onClick={onOpenLink}><Link2 /></IconButton>\n </>\n ) : null}\n {onComment ? <Button type=\"button\" size=\"sm\" variant=\"ghost\" onMouseDown={keepSelection} onClick={comment}><MessageSquare />{t('documents.editor.toolbar.comment')}</Button> : null}\n </BubbleMenu>\n ) : null}\n <EditorContent className={cn(DOCUMENT_EDITOR_CONTENT_CLASS, readOnly && 'select-text')} editor={editor} />\n </article>\n </div>\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
+ import { useContentSecurityPolicyNonce } from "@open-mercato/ui/theme/ContentSecurityPolicyProvider";
4
5
  import { Link2, Unlink } from "lucide-react";
5
6
  import { Button } from "@open-mercato/ui/primitives/button";
6
7
  import { Input } from "@open-mercato/ui/primitives/input";
@@ -20,6 +21,7 @@ import { useDocumentTitle } from "./useDocumentTitle.js";
20
21
  import { useEditorInsertions } from "./useEditorInsertions.js";
21
22
  function DocumentEditorSurface(input) {
22
23
  const t = useT();
24
+ const nonce = useContentSecurityPolicyNonce();
23
25
  const [outlineOpen, setOutlineOpen] = React.useState(false);
24
26
  const [suggestionRange, setSuggestionRange] = React.useState(null);
25
27
  const modeChangeInFlightRef = React.useRef(false);
@@ -82,7 +84,7 @@ function DocumentEditorSurface(input) {
82
84
  }
83
85
  }, [fallbackPersistence.saveNow, input.mode, input.onModeChange, input.transport]);
84
86
  return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", input.transport === "collab" && "om-doc-collab", input.mode === "preview" && "om-doc-preview"), children: [
85
- /* @__PURE__ */ jsx("style", { children: EDITOR_PRESENCE_STYLES }),
87
+ /* @__PURE__ */ jsx("style", { nonce, children: EDITOR_PRESENCE_STYLES }),
86
88
  /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-lg border border-border bg-muted shadow-sm", children: [
87
89
  /* @__PURE__ */ jsxs("div", { className: "sticky top-0 z-sticky border-b border-border bg-card/95", children: [
88
90
  /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center justify-between gap-2 px-3 py-2", children: /* @__PURE__ */ jsx(
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/documents/backend/documents/%5Bid%5D/DocumentEditorSurface.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Link2, Unlink } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { EntityPicker } from '../components/EntityPicker'\nimport { DocumentCanvas } from './DocumentCanvas'\nimport { EditorStatusPresence } from './EditorStatusPresence'\nimport { EditorToolbar } from './EditorToolbar'\nimport { RecordFieldsDialog } from './RecordFieldsDialog'\nimport { EDITOR_PRESENCE_STYLES } from './editorPresenceStyles'\nimport { canCreateSelectionComment, type CollabResources, type ConnectionStatus, type DocumentEditorIslandProps, type EditorMode, type PresenceUser } from './editorTypes'\nimport { useDocumentEditor } from './useDocumentEditor'\nimport { useFallbackContentPersistence } from './useFallbackContentPersistence'\nimport { useDocumentTitle } from './useDocumentTitle'\nimport { useEditorInsertions } from './useEditorInsertions'\n\ntype Props = DocumentEditorIslandProps & {\n transport: 'collab' | 'fallback'\n collabResources?: CollabResources\n connectionStatus: ConnectionStatus\n presenceUsers: PresenceUser[]\n mode: EditorMode\n onModeChange: (mode: EditorMode) => void\n}\n\nexport function DocumentEditorSurface(input: Props) {\n const t = useT()\n const [outlineOpen, setOutlineOpen] = React.useState(false)\n const [suggestionRange, setSuggestionRange] = React.useState<{ from: number; to: number } | null>(null)\n const modeChangeInFlightRef = React.useRef(false)\n const effectiveReadOnly = input.readOnly || input.mode === 'preview'\n const openSuggestion = React.useCallback((range: { from: number; to: number }) => setSuggestionRange(range), [])\n const closeSuggestion = React.useCallback(() => setSuggestionRange(null), [])\n const fallbackPersistence = useFallbackContentPersistence({\n documentId: input.documentId,\n initialUpdatedAt: input.contentUpdatedAt ?? null,\n enabled: input.transport === 'fallback' && !input.readOnly,\n onConflictRefresh: input.onContentConflict,\n })\n const editorModel = useDocumentEditor({\n documentId: input.documentId,\n initialContentHtml: input.initialContentHtml,\n editorMode: input.transport,\n collabResources: input.collabResources,\n readOnly: effectiveReadOnly,\n onEditorReady: input.onEditorReady,\n onUpdate: fallbackPersistence.onEditorUpdate,\n onEntitySuggestion: openSuggestion,\n onSuggestionClose: closeSuggestion,\n })\n const title = useDocumentTitle({\n documentId: input.documentId,\n title: input.title,\n updatedAt: input.documentUpdatedAt ?? null,\n readOnly: effectiveReadOnly,\n onTitleChange: input.onTitleChange,\n })\n const insertions = useEditorInsertions({\n documentId: input.documentId,\n editorRef: editorModel.editorRef,\n disabled: effectiveReadOnly,\n suggestionRange,\n setSuggestionRange,\n })\n React.useEffect(() => {\n if (suggestionRange && !effectiveReadOnly) insertions.setPickerOpen(true)\n }, [effectiveReadOnly, insertions.setPickerOpen, suggestionRange])\n\n const status = input.transport === 'fallback' ? 'offline' : input.connectionStatus\n const notice = input.transport === 'fallback'\n ? t(input.readOnly ? 'documents.editor.realtime.readOnlyFallback' : 'documents.editor.realtime.singleUserFallback')\n : input.readOnly\n ? t('documents.editor.readOnly')\n : input.mode === 'preview'\n ? t('documents.editor.mode.previewNotice')\n : null\n const selectionCommentHandler = canCreateSelectionComment({\n permissionReadOnly: input.readOnly,\n mode: input.mode,\n transport: input.transport,\n hasCommentHandler: Boolean(input.onComment),\n }) ? input.onComment : undefined\n const handleModeChange = React.useCallback(async (nextMode: EditorMode) => {\n if (nextMode === input.mode || modeChangeInFlightRef.current) return\n if (input.transport !== 'fallback' || nextMode !== 'preview') {\n input.onModeChange(nextMode)\n return\n }\n\n modeChangeInFlightRef.current = true\n try {\n if (await fallbackPersistence.saveNow()) input.onModeChange(nextMode)\n } finally {\n modeChangeInFlightRef.current = false\n }\n }, [fallbackPersistence.saveNow, input.mode, input.onModeChange, input.transport])\n\n return (\n <div className={cn('space-y-3', input.transport === 'collab' && 'om-doc-collab', input.mode === 'preview' && 'om-doc-preview')}>\n <style>{EDITOR_PRESENCE_STYLES}</style>\n <div className=\"overflow-hidden rounded-lg border border-border bg-muted shadow-sm\">\n <div className=\"sticky top-0 z-sticky border-b border-border bg-card/95\">\n <div className=\"flex flex-wrap items-center justify-between gap-2 px-3 py-2\">\n <EditorStatusPresence\n status={status}\n users={input.presenceUsers}\n counts={editorModel.counts}\n mode={input.mode}\n canEdit={!input.readOnly}\n onModeChange={(nextMode) => { void handleModeChange(nextMode) }}\n fallbackSave={input.transport === 'fallback' && !input.readOnly ? {\n status: fallbackPersistence.status,\n onSave: fallbackPersistence.saveNow,\n } : undefined}\n />\n </div>\n {input.mode === 'edit' ? (\n <EditorToolbar\n editor={editorModel.editor}\n disabled={effectiveReadOnly || !editorModel.editor}\n outlineOpen={outlineOpen}\n uploading={insertions.uploading}\n onToggleOutline={() => setOutlineOpen((value) => !value)}\n onOpenEntityPicker={insertions.openEntityPicker}\n onOpenLink={insertions.openLink}\n onImage={() => insertions.fileInputRef.current?.click()}\n />\n ) : null}\n {insertions.linkOpen && !effectiveReadOnly ? (\n <div className=\"flex flex-col gap-3 border-t border-border bg-muted/30 p-3 md:flex-row md:items-end\" onKeyDown={(event) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') { event.preventDefault(); insertions.applyLink() }\n if (event.key === 'Escape') { event.preventDefault(); insertions.setLinkOpen(false) }\n }}>\n <div className=\"min-w-0 flex-1 space-y-2\">\n <Label htmlFor={`document-link-${input.documentId}`}>{t('documents.editor.link.url')}</Label>\n <Input id={`document-link-${input.documentId}`} type=\"url\" value={insertions.linkHref} onChange={(event) => insertions.setLinkHref(event.target.value)} placeholder={t('documents.editor.link.placeholder')} />\n </div>\n <Button type=\"button\" onClick={insertions.applyLink}><Link2 />{t('documents.editor.link.apply')}</Button>\n <Button type=\"button\" variant=\"outline\" onClick={insertions.removeLink}><Unlink />{t('documents.editor.link.remove')}</Button>\n <Button type=\"button\" variant=\"ghost\" onClick={() => insertions.setLinkOpen(false)}>{t('documents.actions.cancel')}</Button>\n </div>\n ) : null}\n </div>\n <DocumentCanvas editor={editorModel.editor} title={title} readOnly={effectiveReadOnly} outlineOpen={outlineOpen} notice={notice} onOpenLink={insertions.openLink} onComment={selectionCommentHandler} />\n </div>\n <EntityPicker open={insertions.entityPickerOpen} onOpenChange={insertions.setPickerOpen} onPick={(pick) => { void insertions.handleEntityPick(pick) }} />\n <RecordFieldsDialog\n documentId={input.documentId}\n linkId={insertions.recordFieldsLinkId}\n editor={editorModel.editor}\n canInsert={!effectiveReadOnly && Boolean(editorModel.editor)}\n onOpenChange={(open) => { if (!open) insertions.closeRecordFields() }}\n />\n <input ref={insertions.fileInputRef} type=\"file\" accept=\"image/*\" className=\"hidden\" aria-label={t('documents.editor.imageInput')} onChange={(event) => { void insertions.handleFiles(event.target.files) }} />\n </div>\n )\n}\n"],
5
- "mappings": ";AAwGM,cAkCQ,YAlCR;AAtGN,YAAY,WAAW;AACvB,SAAS,OAAO,cAAc;AAC9B,SAAS,cAAc;AACvB,SAAS,aAAa;AACtB,SAAS,aAAa;AACtB,SAAS,YAAY;AACrB,SAAS,UAAU;AACnB,SAAS,oBAAoB;AAC7B,SAAS,sBAAsB;AAC/B,SAAS,4BAA4B;AACrC,SAAS,qBAAqB;AAC9B,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,iCAAkJ;AAC3J,SAAS,yBAAyB;AAClC,SAAS,qCAAqC;AAC9C,SAAS,wBAAwB;AACjC,SAAS,2BAA2B;AAW7B,SAAS,sBAAsB,OAAc;AAClD,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,KAAK;AAC1D,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAA8C,IAAI;AACtG,QAAM,wBAAwB,MAAM,OAAO,KAAK;AAChD,QAAM,oBAAoB,MAAM,YAAY,MAAM,SAAS;AAC3D,QAAM,iBAAiB,MAAM,YAAY,CAAC,UAAwC,mBAAmB,KAAK,GAAG,CAAC,CAAC;AAC/G,QAAM,kBAAkB,MAAM,YAAY,MAAM,mBAAmB,IAAI,GAAG,CAAC,CAAC;AAC5E,QAAM,sBAAsB,8BAA8B;AAAA,IACxD,YAAY,MAAM;AAAA,IAClB,kBAAkB,MAAM,oBAAoB;AAAA,IAC5C,SAAS,MAAM,cAAc,cAAc,CAAC,MAAM;AAAA,IAClD,mBAAmB,MAAM;AAAA,EAC3B,CAAC;AACD,QAAM,cAAc,kBAAkB;AAAA,IACpC,YAAY,MAAM;AAAA,IAClB,oBAAoB,MAAM;AAAA,IAC1B,YAAY,MAAM;AAAA,IAClB,iBAAiB,MAAM;AAAA,IACvB,UAAU;AAAA,IACV,eAAe,MAAM;AAAA,IACrB,UAAU,oBAAoB;AAAA,IAC9B,oBAAoB;AAAA,IACpB,mBAAmB;AAAA,EACrB,CAAC;AACD,QAAM,QAAQ,iBAAiB;AAAA,IAC7B,YAAY,MAAM;AAAA,IAClB,OAAO,MAAM;AAAA,IACb,WAAW,MAAM,qBAAqB;AAAA,IACtC,UAAU;AAAA,IACV,eAAe,MAAM;AAAA,EACvB,CAAC;AACD,QAAM,aAAa,oBAAoB;AAAA,IACrC,YAAY,MAAM;AAAA,IAClB,WAAW,YAAY;AAAA,IACvB,UAAU;AAAA,IACV;AAAA,IACA;AAAA,EACF,CAAC;AACD,QAAM,UAAU,MAAM;AACpB,QAAI,mBAAmB,CAAC,kBAAmB,YAAW,cAAc,IAAI;AAAA,EAC1E,GAAG,CAAC,mBAAmB,WAAW,eAAe,eAAe,CAAC;AAEjE,QAAM,SAAS,MAAM,cAAc,aAAa,YAAY,MAAM;AAClE,QAAM,SAAS,MAAM,cAAc,aAC/B,EAAE,MAAM,WAAW,+CAA+C,8CAA8C,IAChH,MAAM,WACJ,EAAE,2BAA2B,IAC7B,MAAM,SAAS,YACb,EAAE,qCAAqC,IACvC;AACR,QAAM,0BAA0B,0BAA0B;AAAA,IACxD,oBAAoB,MAAM;AAAA,IAC1B,MAAM,MAAM;AAAA,IACZ,WAAW,MAAM;AAAA,IACjB,mBAAmB,QAAQ,MAAM,SAAS;AAAA,EAC5C,CAAC,IAAI,MAAM,YAAY;AACvB,QAAM,mBAAmB,MAAM,YAAY,OAAO,aAAyB;AACzE,QAAI,aAAa,MAAM,QAAQ,sBAAsB,QAAS;AAC9D,QAAI,MAAM,cAAc,cAAc,aAAa,WAAW;AAC5D,YAAM,aAAa,QAAQ;AAC3B;AAAA,IACF;AAEA,0BAAsB,UAAU;AAChC,QAAI;AACF,UAAI,MAAM,oBAAoB,QAAQ,EAAG,OAAM,aAAa,QAAQ;AAAA,IACtE,UAAE;AACA,4BAAsB,UAAU;AAAA,IAClC;AAAA,EACF,GAAG,CAAC,oBAAoB,SAAS,MAAM,MAAM,MAAM,cAAc,MAAM,SAAS,CAAC;AAEjF,SACE,qBAAC,SAAI,WAAW,GAAG,aAAa,MAAM,cAAc,YAAY,iBAAiB,MAAM,SAAS,aAAa,gBAAgB,GAC3H;AAAA,wBAAC,WAAO,kCAAuB;AAAA,IAC/B,qBAAC,SAAI,WAAU,sEACb;AAAA,2BAAC,SAAI,WAAU,2DACb;AAAA,4BAAC,SAAI,WAAU,+DACb;AAAA,UAAC;AAAA;AAAA,YACC;AAAA,YACA,OAAO,MAAM;AAAA,YACb,QAAQ,YAAY;AAAA,YACpB,MAAM,MAAM;AAAA,YACZ,SAAS,CAAC,MAAM;AAAA,YAChB,cAAc,CAAC,aAAa;AAAE,mBAAK,iBAAiB,QAAQ;AAAA,YAAE;AAAA,YAC9D,cAAc,MAAM,cAAc,cAAc,CAAC,MAAM,WAAW;AAAA,cAChE,QAAQ,oBAAoB;AAAA,cAC5B,QAAQ,oBAAoB;AAAA,YAC9B,IAAI;AAAA;AAAA,QACN,GACF;AAAA,QACC,MAAM,SAAS,SACd;AAAA,UAAC;AAAA;AAAA,YACC,QAAQ,YAAY;AAAA,YACpB,UAAU,qBAAqB,CAAC,YAAY;AAAA,YAC5C;AAAA,YACA,WAAW,WAAW;AAAA,YACtB,iBAAiB,MAAM,eAAe,CAAC,UAAU,CAAC,KAAK;AAAA,YACvD,oBAAoB,WAAW;AAAA,YAC/B,YAAY,WAAW;AAAA,YACvB,SAAS,MAAM,WAAW,aAAa,SAAS,MAAM;AAAA;AAAA,QACxD,IACE;AAAA,QACH,WAAW,YAAY,CAAC,oBACvB,qBAAC,SAAI,WAAU,uFAAsF,WAAW,CAAC,UAAU;AACzH,eAAK,MAAM,WAAW,MAAM,YAAY,MAAM,QAAQ,SAAS;AAAE,kBAAM,eAAe;AAAG,uBAAW,UAAU;AAAA,UAAE;AAChH,cAAI,MAAM,QAAQ,UAAU;AAAE,kBAAM,eAAe;AAAG,uBAAW,YAAY,KAAK;AAAA,UAAE;AAAA,QACtF,GACE;AAAA,+BAAC,SAAI,WAAU,4BACb;AAAA,gCAAC,SAAM,SAAS,iBAAiB,MAAM,UAAU,IAAK,YAAE,2BAA2B,GAAE;AAAA,YACrF,oBAAC,SAAM,IAAI,iBAAiB,MAAM,UAAU,IAAI,MAAK,OAAM,OAAO,WAAW,UAAU,UAAU,CAAC,UAAU,WAAW,YAAY,MAAM,OAAO,KAAK,GAAG,aAAa,EAAE,mCAAmC,GAAG;AAAA,aAC/M;AAAA,UACA,qBAAC,UAAO,MAAK,UAAS,SAAS,WAAW,WAAW;AAAA,gCAAC,SAAM;AAAA,YAAG,EAAE,6BAA6B;AAAA,aAAE;AAAA,UAChG,qBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,SAAS,WAAW,YAAY;AAAA,gCAAC,UAAO;AAAA,YAAG,EAAE,8BAA8B;AAAA,aAAE;AAAA,UACrH,oBAAC,UAAO,MAAK,UAAS,SAAQ,SAAQ,SAAS,MAAM,WAAW,YAAY,KAAK,GAAI,YAAE,0BAA0B,GAAE;AAAA,WACrH,IACE;AAAA,SACN;AAAA,MACA,oBAAC,kBAAe,QAAQ,YAAY,QAAQ,OAAc,UAAU,mBAAmB,aAA0B,QAAgB,YAAY,WAAW,UAAU,WAAW,yBAAyB;AAAA,OACxM;AAAA,IACA,oBAAC,gBAAa,MAAM,WAAW,kBAAkB,cAAc,WAAW,eAAe,QAAQ,CAAC,SAAS;AAAE,WAAK,WAAW,iBAAiB,IAAI;AAAA,IAAE,GAAG;AAAA,IACvJ;AAAA,MAAC;AAAA;AAAA,QACC,YAAY,MAAM;AAAA,QAClB,QAAQ,WAAW;AAAA,QACnB,QAAQ,YAAY;AAAA,QACpB,WAAW,CAAC,qBAAqB,QAAQ,YAAY,MAAM;AAAA,QAC3D,cAAc,CAAC,SAAS;AAAE,cAAI,CAAC,KAAM,YAAW,kBAAkB;AAAA,QAAE;AAAA;AAAA,IACtE;AAAA,IACA,oBAAC,WAAM,KAAK,WAAW,cAAc,MAAK,QAAO,QAAO,WAAU,WAAU,UAAS,cAAY,EAAE,6BAA6B,GAAG,UAAU,CAAC,UAAU;AAAE,WAAK,WAAW,YAAY,MAAM,OAAO,KAAK;AAAA,IAAE,GAAG;AAAA,KAC/M;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useContentSecurityPolicyNonce } from '@open-mercato/ui/theme/ContentSecurityPolicyProvider'\nimport { Link2, Unlink } from 'lucide-react'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { EntityPicker } from '../components/EntityPicker'\nimport { DocumentCanvas } from './DocumentCanvas'\nimport { EditorStatusPresence } from './EditorStatusPresence'\nimport { EditorToolbar } from './EditorToolbar'\nimport { RecordFieldsDialog } from './RecordFieldsDialog'\nimport { EDITOR_PRESENCE_STYLES } from './editorPresenceStyles'\nimport { canCreateSelectionComment, type CollabResources, type ConnectionStatus, type DocumentEditorIslandProps, type EditorMode, type PresenceUser } from './editorTypes'\nimport { useDocumentEditor } from './useDocumentEditor'\nimport { useFallbackContentPersistence } from './useFallbackContentPersistence'\nimport { useDocumentTitle } from './useDocumentTitle'\nimport { useEditorInsertions } from './useEditorInsertions'\n\ntype Props = DocumentEditorIslandProps & {\n transport: 'collab' | 'fallback'\n collabResources?: CollabResources\n connectionStatus: ConnectionStatus\n presenceUsers: PresenceUser[]\n mode: EditorMode\n onModeChange: (mode: EditorMode) => void\n}\n\nexport function DocumentEditorSurface(input: Props) {\n const t = useT()\n const nonce = useContentSecurityPolicyNonce()\n const [outlineOpen, setOutlineOpen] = React.useState(false)\n const [suggestionRange, setSuggestionRange] = React.useState<{ from: number; to: number } | null>(null)\n const modeChangeInFlightRef = React.useRef(false)\n const effectiveReadOnly = input.readOnly || input.mode === 'preview'\n const openSuggestion = React.useCallback((range: { from: number; to: number }) => setSuggestionRange(range), [])\n const closeSuggestion = React.useCallback(() => setSuggestionRange(null), [])\n const fallbackPersistence = useFallbackContentPersistence({\n documentId: input.documentId,\n initialUpdatedAt: input.contentUpdatedAt ?? null,\n enabled: input.transport === 'fallback' && !input.readOnly,\n onConflictRefresh: input.onContentConflict,\n })\n const editorModel = useDocumentEditor({\n documentId: input.documentId,\n initialContentHtml: input.initialContentHtml,\n editorMode: input.transport,\n collabResources: input.collabResources,\n readOnly: effectiveReadOnly,\n onEditorReady: input.onEditorReady,\n onUpdate: fallbackPersistence.onEditorUpdate,\n onEntitySuggestion: openSuggestion,\n onSuggestionClose: closeSuggestion,\n })\n const title = useDocumentTitle({\n documentId: input.documentId,\n title: input.title,\n updatedAt: input.documentUpdatedAt ?? null,\n readOnly: effectiveReadOnly,\n onTitleChange: input.onTitleChange,\n })\n const insertions = useEditorInsertions({\n documentId: input.documentId,\n editorRef: editorModel.editorRef,\n disabled: effectiveReadOnly,\n suggestionRange,\n setSuggestionRange,\n })\n React.useEffect(() => {\n if (suggestionRange && !effectiveReadOnly) insertions.setPickerOpen(true)\n }, [effectiveReadOnly, insertions.setPickerOpen, suggestionRange])\n\n const status = input.transport === 'fallback' ? 'offline' : input.connectionStatus\n const notice = input.transport === 'fallback'\n ? t(input.readOnly ? 'documents.editor.realtime.readOnlyFallback' : 'documents.editor.realtime.singleUserFallback')\n : input.readOnly\n ? t('documents.editor.readOnly')\n : input.mode === 'preview'\n ? t('documents.editor.mode.previewNotice')\n : null\n const selectionCommentHandler = canCreateSelectionComment({\n permissionReadOnly: input.readOnly,\n mode: input.mode,\n transport: input.transport,\n hasCommentHandler: Boolean(input.onComment),\n }) ? input.onComment : undefined\n const handleModeChange = React.useCallback(async (nextMode: EditorMode) => {\n if (nextMode === input.mode || modeChangeInFlightRef.current) return\n if (input.transport !== 'fallback' || nextMode !== 'preview') {\n input.onModeChange(nextMode)\n return\n }\n\n modeChangeInFlightRef.current = true\n try {\n if (await fallbackPersistence.saveNow()) input.onModeChange(nextMode)\n } finally {\n modeChangeInFlightRef.current = false\n }\n }, [fallbackPersistence.saveNow, input.mode, input.onModeChange, input.transport])\n\n return (\n <div className={cn('space-y-3', input.transport === 'collab' && 'om-doc-collab', input.mode === 'preview' && 'om-doc-preview')}>\n <style nonce={nonce}>{EDITOR_PRESENCE_STYLES}</style>\n <div className=\"overflow-hidden rounded-lg border border-border bg-muted shadow-sm\">\n <div className=\"sticky top-0 z-sticky border-b border-border bg-card/95\">\n <div className=\"flex flex-wrap items-center justify-between gap-2 px-3 py-2\">\n <EditorStatusPresence\n status={status}\n users={input.presenceUsers}\n counts={editorModel.counts}\n mode={input.mode}\n canEdit={!input.readOnly}\n onModeChange={(nextMode) => { void handleModeChange(nextMode) }}\n fallbackSave={input.transport === 'fallback' && !input.readOnly ? {\n status: fallbackPersistence.status,\n onSave: fallbackPersistence.saveNow,\n } : undefined}\n />\n </div>\n {input.mode === 'edit' ? (\n <EditorToolbar\n editor={editorModel.editor}\n disabled={effectiveReadOnly || !editorModel.editor}\n outlineOpen={outlineOpen}\n uploading={insertions.uploading}\n onToggleOutline={() => setOutlineOpen((value) => !value)}\n onOpenEntityPicker={insertions.openEntityPicker}\n onOpenLink={insertions.openLink}\n onImage={() => insertions.fileInputRef.current?.click()}\n />\n ) : null}\n {insertions.linkOpen && !effectiveReadOnly ? (\n <div className=\"flex flex-col gap-3 border-t border-border bg-muted/30 p-3 md:flex-row md:items-end\" onKeyDown={(event) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') { event.preventDefault(); insertions.applyLink() }\n if (event.key === 'Escape') { event.preventDefault(); insertions.setLinkOpen(false) }\n }}>\n <div className=\"min-w-0 flex-1 space-y-2\">\n <Label htmlFor={`document-link-${input.documentId}`}>{t('documents.editor.link.url')}</Label>\n <Input id={`document-link-${input.documentId}`} type=\"url\" value={insertions.linkHref} onChange={(event) => insertions.setLinkHref(event.target.value)} placeholder={t('documents.editor.link.placeholder')} />\n </div>\n <Button type=\"button\" onClick={insertions.applyLink}><Link2 />{t('documents.editor.link.apply')}</Button>\n <Button type=\"button\" variant=\"outline\" onClick={insertions.removeLink}><Unlink />{t('documents.editor.link.remove')}</Button>\n <Button type=\"button\" variant=\"ghost\" onClick={() => insertions.setLinkOpen(false)}>{t('documents.actions.cancel')}</Button>\n </div>\n ) : null}\n </div>\n <DocumentCanvas editor={editorModel.editor} title={title} readOnly={effectiveReadOnly} outlineOpen={outlineOpen} notice={notice} onOpenLink={insertions.openLink} onComment={selectionCommentHandler} />\n </div>\n <EntityPicker open={insertions.entityPickerOpen} onOpenChange={insertions.setPickerOpen} onPick={(pick) => { void insertions.handleEntityPick(pick) }} />\n <RecordFieldsDialog\n documentId={input.documentId}\n linkId={insertions.recordFieldsLinkId}\n editor={editorModel.editor}\n canInsert={!effectiveReadOnly && Boolean(editorModel.editor)}\n onOpenChange={(open) => { if (!open) insertions.closeRecordFields() }}\n />\n <input ref={insertions.fileInputRef} type=\"file\" accept=\"image/*\" className=\"hidden\" aria-label={t('documents.editor.imageInput')} onChange={(event) => { void insertions.handleFiles(event.target.files) }} />\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
+ import { useContentSecurityPolicyNonce } from "@open-mercato/ui/theme/ContentSecurityPolicyProvider";
4
5
  import { EditorContent, useEditor } from "@tiptap/react";
5
6
  import { RotateCcw } from "lucide-react";
6
7
  import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
@@ -41,7 +42,9 @@ function normalizeVersionPreview(payload, fallbackCreatorLabel) {
41
42
  };
42
43
  }
43
44
  function ReadOnlyVersion({ contentHtml, fallbackLabel }) {
45
+ const nonce = useContentSecurityPolicyNonce();
44
46
  const editor = useEditor({
47
+ injectNonce: nonce,
45
48
  extensions: getDocumentEditorExtensions({ entityRefFallbackLabel: fallbackLabel }),
46
49
  content: contentHtml,
47
50
  editable: false,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/documents/backend/documents/%5Bid%5D/VersionPreviewDialog.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { EditorContent, useEditor } from '@tiptap/react'\nimport { RotateCcw } from 'lucide-react'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { getDocumentEditorExtensions } from '../../../lib/editorConfig'\nimport { firstSafeDocumentsDisplayLabel } from '../../../lib/displayLabels'\nimport { sanitizeDocumentVersionLabel } from '../../../lib/versionLabels'\nimport { readRecord, readString } from '../documentUi'\nimport { DOCUMENT_EDITOR_CONTENT_CLASS } from './editorTypes'\n\nexport type VersionPreview = {\n id: string\n label: string | null\n creatorLabel: string\n createdAt: string\n contentHtml: string\n}\n\ntype VersionPreviewDialogProps = {\n documentId: string\n versionId: string | null\n canRestore: boolean\n isRestoring: boolean\n onOpenChange: (open: boolean) => void\n onRestore: (version: VersionPreview) => void | Promise<void>\n}\n\nexport function normalizeVersionPreview(\n payload: unknown,\n fallbackCreatorLabel?: string,\n): VersionPreview | null {\n const root = readRecord(payload)\n const record = readRecord(root?.item) ?? readRecord(root?.data) ?? root\n if (!record) return null\n const id = readString(record, 'id')\n const creatorLabel = firstSafeDocumentsDisplayLabel(\n readString(record, 'creatorLabel', 'creator_label'),\n fallbackCreatorLabel,\n )\n const createdAt = readString(record, 'createdAt', 'created_at')\n if (!id || !creatorLabel || !createdAt) return null\n return {\n id,\n creatorLabel,\n createdAt,\n label: sanitizeDocumentVersionLabel(readString(record, 'label')),\n contentHtml: readString(record, 'contentHtml', 'content_html') ?? '',\n }\n}\n\nfunction ReadOnlyVersion({ contentHtml, fallbackLabel }: { contentHtml: string; fallbackLabel: string }) {\n const editor = useEditor({\n extensions: getDocumentEditorExtensions({ entityRefFallbackLabel: fallbackLabel }),\n content: contentHtml,\n editable: false,\n editorProps: { attributes: { class: 'min-h-64 text-base leading-7 text-foreground focus-visible:outline-none' } },\n }, [contentHtml])\n React.useEffect(() => { editor?.commands.setContent(contentHtml) }, [contentHtml, editor])\n // Share the live editor's typography so tables keep their grid lines and\n // headings/lists read the same in the preview as on the canvas.\n return <EditorContent editor={editor} className={cn(DOCUMENT_EDITOR_CONTENT_CLASS, 'rounded-md border border-border bg-card p-4')} />\n}\n\nexport function VersionPreviewDialog({\n documentId,\n versionId,\n canRestore,\n isRestoring,\n onOpenChange,\n onRestore,\n}: VersionPreviewDialogProps) {\n const t = useT()\n const [preview, setPreview] = React.useState<VersionPreview | null>(null)\n const [error, setError] = React.useState<string | null>(null)\n const [loading, setLoading] = React.useState(false)\n const [loadAttempt, setLoadAttempt] = React.useState(0)\n const [confirmingRestore, setConfirmingRestore] = React.useState(false)\n const cancelRestoreRef = React.useRef<HTMLButtonElement>(null)\n\n React.useEffect(() => {\n setConfirmingRestore(false)\n if (!versionId) { setPreview(null); setError(null); return }\n let active = true\n setLoading(true)\n setError(null)\n void apiCall<unknown>(`/api/documents/${encodeURIComponent(documentId)}/versions/${encodeURIComponent(versionId)}`)\n .then((call) => {\n if (!active) return\n const next = call.ok\n ? normalizeVersionPreview(call.result, t('documents.users.unknown'))\n : null\n if (!next) setError(t('documents.versions.preview.error'))\n setPreview(next)\n })\n .catch(() => { if (active) setError(t('documents.versions.preview.error')) })\n .finally(() => { if (active) setLoading(false) })\n return () => { active = false }\n }, [documentId, loadAttempt, t, versionId])\n\n React.useEffect(() => {\n if (confirmingRestore) cancelRestoreRef.current?.focus()\n }, [confirmingRestore])\n\n const requestRestore = React.useCallback(() => {\n if (!canRestore || !preview || isRestoring) return\n setConfirmingRestore(true)\n }, [canRestore, isRestoring, preview])\n\n const confirmRestore = React.useCallback(() => {\n if (!canRestore || !preview || isRestoring) return\n void onRestore(preview)\n }, [canRestore, isRestoring, onRestore, preview])\n\n const handleOpenChange = React.useCallback((open: boolean) => {\n if (!open) setConfirmingRestore(false)\n onOpenChange(open)\n }, [onOpenChange])\n\n return (\n <Dialog open={versionId !== null} onOpenChange={handleOpenChange}>\n <DialogContent size=\"xl\" onKeyDown={(event) => {\n if (event.key === 'Escape') {\n if (confirmingRestore) {\n event.preventDefault()\n event.stopPropagation()\n if (!isRestoring) setConfirmingRestore(false)\n } else {\n onOpenChange(false)\n }\n }\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter' && canRestore && preview && !isRestoring) {\n event.preventDefault()\n if (confirmingRestore) confirmRestore()\n else requestRestore()\n }\n }}>\n <DialogHeader>\n <DialogTitle>\n {confirmingRestore\n ? t('documents.versions.restore.confirmTitle')\n : preview?.label ?? t('documents.versions.preview.title')}\n </DialogTitle>\n <DialogDescription aria-live={confirmingRestore ? 'assertive' : undefined}>\n {confirmingRestore\n ? t('documents.versions.restore.confirmBody')\n : preview\n ? t('documents.versions.preview.description', { creator: preview.creatorLabel, date: new Date(preview.createdAt).toLocaleString() })\n : t('documents.versions.preview.loading')}\n </DialogDescription>\n </DialogHeader>\n {!confirmingRestore && loading ? <LoadingMessage label={t('documents.versions.preview.loading')} /> : null}\n {!confirmingRestore && error ? (\n <ErrorMessage\n label={error}\n action={<Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={() => setLoadAttempt((current) => current + 1)}>{t('documents.actions.retry')}</Button>}\n />\n ) : null}\n {!confirmingRestore && preview && !loading && !error ? <ReadOnlyVersion contentHtml={preview.contentHtml} fallbackLabel={t('documents.editor.entityRef.fallbackLabel')} /> : null}\n <DialogFooter>\n {confirmingRestore ? (\n <>\n <Button ref={cancelRestoreRef} type=\"button\" variant=\"outline\" onClick={() => setConfirmingRestore(false)} disabled={isRestoring}>\n {t('documents.actions.cancel')}\n </Button>\n <Button type=\"button\" onClick={confirmRestore} disabled={isRestoring}>\n <RotateCcw />{t('documents.versions.actions.restore')}\n </Button>\n </>\n ) : (\n <>\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)}>{t('documents.actions.close')}</Button>\n {canRestore && preview ? (\n <Button type=\"button\" onClick={requestRestore} disabled={isRestoring}>\n <RotateCcw />{t('documents.versions.actions.restore')}\n </Button>\n ) : null}\n </>\n )}\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useContentSecurityPolicyNonce } from '@open-mercato/ui/theme/ContentSecurityPolicyProvider'\nimport { EditorContent, useEditor } from '@tiptap/react'\nimport { RotateCcw } from 'lucide-react'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { LoadingMessage, ErrorMessage } from '@open-mercato/ui/backend/detail'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { getDocumentEditorExtensions } from '../../../lib/editorConfig'\nimport { firstSafeDocumentsDisplayLabel } from '../../../lib/displayLabels'\nimport { sanitizeDocumentVersionLabel } from '../../../lib/versionLabels'\nimport { readRecord, readString } from '../documentUi'\nimport { DOCUMENT_EDITOR_CONTENT_CLASS } from './editorTypes'\n\nexport type VersionPreview = {\n id: string\n label: string | null\n creatorLabel: string\n createdAt: string\n contentHtml: string\n}\n\ntype VersionPreviewDialogProps = {\n documentId: string\n versionId: string | null\n canRestore: boolean\n isRestoring: boolean\n onOpenChange: (open: boolean) => void\n onRestore: (version: VersionPreview) => void | Promise<void>\n}\n\nexport function normalizeVersionPreview(\n payload: unknown,\n fallbackCreatorLabel?: string,\n): VersionPreview | null {\n const root = readRecord(payload)\n const record = readRecord(root?.item) ?? readRecord(root?.data) ?? root\n if (!record) return null\n const id = readString(record, 'id')\n const creatorLabel = firstSafeDocumentsDisplayLabel(\n readString(record, 'creatorLabel', 'creator_label'),\n fallbackCreatorLabel,\n )\n const createdAt = readString(record, 'createdAt', 'created_at')\n if (!id || !creatorLabel || !createdAt) return null\n return {\n id,\n creatorLabel,\n createdAt,\n label: sanitizeDocumentVersionLabel(readString(record, 'label')),\n contentHtml: readString(record, 'contentHtml', 'content_html') ?? '',\n }\n}\n\nfunction ReadOnlyVersion({ contentHtml, fallbackLabel }: { contentHtml: string; fallbackLabel: string }) {\n const nonce = useContentSecurityPolicyNonce()\n const editor = useEditor({\n injectNonce: nonce,\n extensions: getDocumentEditorExtensions({ entityRefFallbackLabel: fallbackLabel }),\n content: contentHtml,\n editable: false,\n editorProps: { attributes: { class: 'min-h-64 text-base leading-7 text-foreground focus-visible:outline-none' } },\n }, [contentHtml])\n React.useEffect(() => { editor?.commands.setContent(contentHtml) }, [contentHtml, editor])\n // Share the live editor's typography so tables keep their grid lines and\n // headings/lists read the same in the preview as on the canvas.\n return <EditorContent editor={editor} className={cn(DOCUMENT_EDITOR_CONTENT_CLASS, 'rounded-md border border-border bg-card p-4')} />\n}\n\nexport function VersionPreviewDialog({\n documentId,\n versionId,\n canRestore,\n isRestoring,\n onOpenChange,\n onRestore,\n}: VersionPreviewDialogProps) {\n const t = useT()\n const [preview, setPreview] = React.useState<VersionPreview | null>(null)\n const [error, setError] = React.useState<string | null>(null)\n const [loading, setLoading] = React.useState(false)\n const [loadAttempt, setLoadAttempt] = React.useState(0)\n const [confirmingRestore, setConfirmingRestore] = React.useState(false)\n const cancelRestoreRef = React.useRef<HTMLButtonElement>(null)\n\n React.useEffect(() => {\n setConfirmingRestore(false)\n if (!versionId) { setPreview(null); setError(null); return }\n let active = true\n setLoading(true)\n setError(null)\n void apiCall<unknown>(`/api/documents/${encodeURIComponent(documentId)}/versions/${encodeURIComponent(versionId)}`)\n .then((call) => {\n if (!active) return\n const next = call.ok\n ? normalizeVersionPreview(call.result, t('documents.users.unknown'))\n : null\n if (!next) setError(t('documents.versions.preview.error'))\n setPreview(next)\n })\n .catch(() => { if (active) setError(t('documents.versions.preview.error')) })\n .finally(() => { if (active) setLoading(false) })\n return () => { active = false }\n }, [documentId, loadAttempt, t, versionId])\n\n React.useEffect(() => {\n if (confirmingRestore) cancelRestoreRef.current?.focus()\n }, [confirmingRestore])\n\n const requestRestore = React.useCallback(() => {\n if (!canRestore || !preview || isRestoring) return\n setConfirmingRestore(true)\n }, [canRestore, isRestoring, preview])\n\n const confirmRestore = React.useCallback(() => {\n if (!canRestore || !preview || isRestoring) return\n void onRestore(preview)\n }, [canRestore, isRestoring, onRestore, preview])\n\n const handleOpenChange = React.useCallback((open: boolean) => {\n if (!open) setConfirmingRestore(false)\n onOpenChange(open)\n }, [onOpenChange])\n\n return (\n <Dialog open={versionId !== null} onOpenChange={handleOpenChange}>\n <DialogContent size=\"xl\" onKeyDown={(event) => {\n if (event.key === 'Escape') {\n if (confirmingRestore) {\n event.preventDefault()\n event.stopPropagation()\n if (!isRestoring) setConfirmingRestore(false)\n } else {\n onOpenChange(false)\n }\n }\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter' && canRestore && preview && !isRestoring) {\n event.preventDefault()\n if (confirmingRestore) confirmRestore()\n else requestRestore()\n }\n }}>\n <DialogHeader>\n <DialogTitle>\n {confirmingRestore\n ? t('documents.versions.restore.confirmTitle')\n : preview?.label ?? t('documents.versions.preview.title')}\n </DialogTitle>\n <DialogDescription aria-live={confirmingRestore ? 'assertive' : undefined}>\n {confirmingRestore\n ? t('documents.versions.restore.confirmBody')\n : preview\n ? t('documents.versions.preview.description', { creator: preview.creatorLabel, date: new Date(preview.createdAt).toLocaleString() })\n : t('documents.versions.preview.loading')}\n </DialogDescription>\n </DialogHeader>\n {!confirmingRestore && loading ? <LoadingMessage label={t('documents.versions.preview.loading')} /> : null}\n {!confirmingRestore && error ? (\n <ErrorMessage\n label={error}\n action={<Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={() => setLoadAttempt((current) => current + 1)}>{t('documents.actions.retry')}</Button>}\n />\n ) : null}\n {!confirmingRestore && preview && !loading && !error ? <ReadOnlyVersion contentHtml={preview.contentHtml} fallbackLabel={t('documents.editor.entityRef.fallbackLabel')} /> : null}\n <DialogFooter>\n {confirmingRestore ? (\n <>\n <Button ref={cancelRestoreRef} type=\"button\" variant=\"outline\" onClick={() => setConfirmingRestore(false)} disabled={isRestoring}>\n {t('documents.actions.cancel')}\n </Button>\n <Button type=\"button\" onClick={confirmRestore} disabled={isRestoring}>\n <RotateCcw />{t('documents.versions.actions.restore')}\n </Button>\n </>\n ) : (\n <>\n <Button type=\"button\" variant=\"outline\" onClick={() => onOpenChange(false)}>{t('documents.actions.close')}</Button>\n {canRestore && preview ? (\n <Button type=\"button\" onClick={requestRestore} disabled={isRestoring}>\n <RotateCcw />{t('documents.versions.actions.restore')}\n </Button>\n ) : null}\n </>\n )}\n </DialogFooter>\n </DialogContent>\n </Dialog>\n )\n}\n"],
5
+ "mappings": ";AA6ES,SAoGG,UApGH,KA4ED,YA5EC;AA3ET,YAAY,WAAW;AACvB,SAAS,qCAAqC;AAC9C,SAAS,eAAe,iBAAiB;AACzC,SAAS,iBAAiB;AAC1B,SAAS,eAAe;AACxB,SAAS,gBAAgB,oBAAoB;AAC7C,SAAS,cAAc;AACvB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,YAAY;AACrB,SAAS,UAAU;AACnB,SAAS,mCAAmC;AAC5C,SAAS,sCAAsC;AAC/C,SAAS,oCAAoC;AAC7C,SAAS,YAAY,kBAAkB;AACvC,SAAS,qCAAqC;AAmBvC,SAAS,wBACd,SACA,sBACuB;AACvB,QAAM,OAAO,WAAW,OAAO;AAC/B,QAAM,SAAS,WAAW,MAAM,IAAI,KAAK,WAAW,MAAM,IAAI,KAAK;AACnE,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,KAAK,WAAW,QAAQ,IAAI;AAClC,QAAM,eAAe;AAAA,IACnB,WAAW,QAAQ,gBAAgB,eAAe;AAAA,IAClD;AAAA,EACF;AACA,QAAM,YAAY,WAAW,QAAQ,aAAa,YAAY;AAC9D,MAAI,CAAC,MAAM,CAAC,gBAAgB,CAAC,UAAW,QAAO;AAC/C,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO,6BAA6B,WAAW,QAAQ,OAAO,CAAC;AAAA,IAC/D,aAAa,WAAW,QAAQ,eAAe,cAAc,KAAK;AAAA,EACpE;AACF;AAEA,SAAS,gBAAgB,EAAE,aAAa,cAAc,GAAmD;AACvG,QAAM,QAAQ,8BAA8B;AAC5C,QAAM,SAAS,UAAU;AAAA,IACvB,aAAa;AAAA,IACb,YAAY,4BAA4B,EAAE,wBAAwB,cAAc,CAAC;AAAA,IACjF,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa,EAAE,YAAY,EAAE,OAAO,0EAA0E,EAAE;AAAA,EAClH,GAAG,CAAC,WAAW,CAAC;AAChB,QAAM,UAAU,MAAM;AAAE,YAAQ,SAAS,WAAW,WAAW;AAAA,EAAE,GAAG,CAAC,aAAa,MAAM,CAAC;AAGzF,SAAO,oBAAC,iBAAc,QAAgB,WAAW,GAAG,+BAA+B,6CAA6C,GAAG;AACrI;AAEO,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA8B;AAC5B,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAgC,IAAI;AACxE,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAC5D,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,KAAK;AAClD,QAAM,CAAC,aAAa,cAAc,IAAI,MAAM,SAAS,CAAC;AACtD,QAAM,CAAC,mBAAmB,oBAAoB,IAAI,MAAM,SAAS,KAAK;AACtE,QAAM,mBAAmB,MAAM,OAA0B,IAAI;AAE7D,QAAM,UAAU,MAAM;AACpB,yBAAqB,KAAK;AAC1B,QAAI,CAAC,WAAW;AAAE,iBAAW,IAAI;AAAG,eAAS,IAAI;AAAG;AAAA,IAAO;AAC3D,QAAI,SAAS;AACb,eAAW,IAAI;AACf,aAAS,IAAI;AACb,SAAK,QAAiB,kBAAkB,mBAAmB,UAAU,CAAC,aAAa,mBAAmB,SAAS,CAAC,EAAE,EAC/G,KAAK,CAAC,SAAS;AACd,UAAI,CAAC,OAAQ;AACb,YAAM,OAAO,KAAK,KACd,wBAAwB,KAAK,QAAQ,EAAE,yBAAyB,CAAC,IACjE;AACJ,UAAI,CAAC,KAAM,UAAS,EAAE,kCAAkC,CAAC;AACzD,iBAAW,IAAI;AAAA,IACjB,CAAC,EACA,MAAM,MAAM;AAAE,UAAI,OAAQ,UAAS,EAAE,kCAAkC,CAAC;AAAA,IAAE,CAAC,EAC3E,QAAQ,MAAM;AAAE,UAAI,OAAQ,YAAW,KAAK;AAAA,IAAE,CAAC;AAClD,WAAO,MAAM;AAAE,eAAS;AAAA,IAAM;AAAA,EAChC,GAAG,CAAC,YAAY,aAAa,GAAG,SAAS,CAAC;AAE1C,QAAM,UAAU,MAAM;AACpB,QAAI,kBAAmB,kBAAiB,SAAS,MAAM;AAAA,EACzD,GAAG,CAAC,iBAAiB,CAAC;AAEtB,QAAM,iBAAiB,MAAM,YAAY,MAAM;AAC7C,QAAI,CAAC,cAAc,CAAC,WAAW,YAAa;AAC5C,yBAAqB,IAAI;AAAA,EAC3B,GAAG,CAAC,YAAY,aAAa,OAAO,CAAC;AAErC,QAAM,iBAAiB,MAAM,YAAY,MAAM;AAC7C,QAAI,CAAC,cAAc,CAAC,WAAW,YAAa;AAC5C,SAAK,UAAU,OAAO;AAAA,EACxB,GAAG,CAAC,YAAY,aAAa,WAAW,OAAO,CAAC;AAEhD,QAAM,mBAAmB,MAAM,YAAY,CAAC,SAAkB;AAC5D,QAAI,CAAC,KAAM,sBAAqB,KAAK;AACrC,iBAAa,IAAI;AAAA,EACnB,GAAG,CAAC,YAAY,CAAC;AAEjB,SACE,oBAAC,UAAO,MAAM,cAAc,MAAM,cAAc,kBAC9C,+BAAC,iBAAc,MAAK,MAAK,WAAW,CAAC,UAAU;AAC7C,QAAI,MAAM,QAAQ,UAAU;AAC1B,UAAI,mBAAmB;AACrB,cAAM,eAAe;AACrB,cAAM,gBAAgB;AACtB,YAAI,CAAC,YAAa,sBAAqB,KAAK;AAAA,MAC9C,OAAO;AACL,qBAAa,KAAK;AAAA,MACpB;AAAA,IACF;AACA,SAAK,MAAM,WAAW,MAAM,YAAY,MAAM,QAAQ,WAAW,cAAc,WAAW,CAAC,aAAa;AACtG,YAAM,eAAe;AACrB,UAAI,kBAAmB,gBAAe;AAAA,UACjC,gBAAe;AAAA,IACtB;AAAA,EACF,GACE;AAAA,yBAAC,gBACC;AAAA,0BAAC,eACE,8BACG,EAAE,yCAAyC,IAC3C,SAAS,SAAS,EAAE,kCAAkC,GAC5D;AAAA,MACA,oBAAC,qBAAkB,aAAW,oBAAoB,cAAc,QAC7D,8BACG,EAAE,wCAAwC,IAC1C,UACE,EAAE,0CAA0C,EAAE,SAAS,QAAQ,cAAc,MAAM,IAAI,KAAK,QAAQ,SAAS,EAAE,eAAe,EAAE,CAAC,IACjI,EAAE,oCAAoC,GAC9C;AAAA,OACF;AAAA,IACC,CAAC,qBAAqB,UAAU,oBAAC,kBAAe,OAAO,EAAE,oCAAoC,GAAG,IAAK;AAAA,IACrG,CAAC,qBAAqB,QACrB;AAAA,MAAC;AAAA;AAAA,QACC,OAAO;AAAA,QACP,QAAQ,oBAAC,UAAO,MAAK,UAAS,MAAK,MAAK,SAAQ,WAAU,SAAS,MAAM,eAAe,CAAC,YAAY,UAAU,CAAC,GAAI,YAAE,yBAAyB,GAAE;AAAA;AAAA,IACnJ,IACE;AAAA,IACH,CAAC,qBAAqB,WAAW,CAAC,WAAW,CAAC,QAAQ,oBAAC,mBAAgB,aAAa,QAAQ,aAAa,eAAe,EAAE,0CAA0C,GAAG,IAAK;AAAA,IAC7K,oBAAC,gBACE,8BACC,iCACE;AAAA,0BAAC,UAAO,KAAK,kBAAkB,MAAK,UAAS,SAAQ,WAAU,SAAS,MAAM,qBAAqB,KAAK,GAAG,UAAU,aAClH,YAAE,0BAA0B,GAC/B;AAAA,MACA,qBAAC,UAAO,MAAK,UAAS,SAAS,gBAAgB,UAAU,aACvD;AAAA,4BAAC,aAAU;AAAA,QAAG,EAAE,oCAAoC;AAAA,SACtD;AAAA,OACF,IAEA,iCACE;AAAA,0BAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,SAAS,MAAM,aAAa,KAAK,GAAI,YAAE,yBAAyB,GAAE;AAAA,MACzG,cAAc,UACb,qBAAC,UAAO,MAAK,UAAS,SAAS,gBAAgB,UAAU,aACvD;AAAA,4BAAC,aAAU;AAAA,QAAG,EAAE,oCAAoC;AAAA,SACtD,IACE;AAAA,OACN,GAEJ;AAAA,KACF,GACF;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { useContentSecurityPolicyNonce } from "@open-mercato/ui/theme/ContentSecurityPolicyProvider";
2
3
  import * as React from "react";
3
4
  import { useEditor } from "@tiptap/react";
4
5
  import { useT } from "@open-mercato/shared/lib/i18n/context";
@@ -41,6 +42,7 @@ function documentCounts(editor) {
41
42
  }
42
43
  function useDocumentEditor(input) {
43
44
  const t = useT();
45
+ const nonce = useContentSecurityPolicyNonce();
44
46
  const editorRef = React.useRef(null);
45
47
  const countTimerRef = React.useRef(null);
46
48
  const readOnlyRef = React.useRef(input.readOnly);
@@ -82,6 +84,7 @@ function useDocumentEditor(input) {
82
84
  }), ...getClientEditorExtras(), DocumentPagination, ...suggestions];
83
85
  }, [input.collabResources, input.editorMode, t]);
84
86
  const editor = useEditor({
87
+ injectNonce: nonce,
85
88
  extensions,
86
89
  content: input.editorMode === "fallback" ? input.initialContentHtml : void 0,
87
90
  editable: !input.readOnly,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/documents/backend/documents/%5Bid%5D/useDocumentEditor.ts"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { Editor } from '@tiptap/core'\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { useEditor } from '@tiptap/react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n activateEntityRefFromKeyboardEvent,\n activateEntityRefFromPointerEvent,\n getClientEditorExtras,\n getCollaborativeEditorExtensions,\n getDocumentEditorExtensions,\n} from '../../../lib/editorConfig'\nimport { createEntitySuggestionExtension } from '../../../lib/entitySuggestion'\nimport { DocumentPagination } from './documentPagination'\nimport type { CollabResources, EditorSelectionRange, EditorWordCount } from './editorTypes'\n\ntype UseDocumentEditorInput = {\n documentId: string\n initialContentHtml: string\n editorMode: 'collab' | 'fallback'\n collabResources?: CollabResources\n readOnly: boolean\n onEditorReady?: (editor: Editor | null) => void\n onUpdate?: (editor: Editor) => void\n onEntitySuggestion: (range: EditorSelectionRange) => void\n onSuggestionClose: () => void\n}\n\nexport const DOCUMENT_COUNT_UPDATE_DELAY_MS = 150\n\nexport function countDocumentWordsAndCharacters(doc: ProseMirrorNode): EditorWordCount {\n let words = 0\n let characters = 0\n let firstBlock = true\n let insideWord = false\n\n doc.nodesBetween(0, doc.content.size, (node) => {\n const nodeText = node.isText ? node.text ?? '' : node.isLeaf ? ' ' : ''\n if (node.isBlock && ((node.isLeaf && nodeText.length > 0) || node.isTextblock)) {\n if (firstBlock) firstBlock = false\n else insideWord = false\n }\n characters += nodeText.length\n for (let index = 0; index < nodeText.length; index += 1) {\n if (nodeText[index] === ' ') {\n insideWord = false\n } else if (!insideWord) {\n words += 1\n insideWord = true\n }\n }\n })\n return { words, characters }\n}\n\nfunction documentCounts(editor: Editor | null): EditorWordCount {\n if (!editor) return { words: 0, characters: 0 }\n return countDocumentWordsAndCharacters(editor.state.doc)\n}\n\nexport function useDocumentEditor(input: UseDocumentEditorInput) {\n const t = useT()\n const editorRef = React.useRef<Editor | null>(null)\n const countTimerRef = React.useRef<number | null>(null)\n const readOnlyRef = React.useRef(input.readOnly)\n const onEntitySuggestionRef = React.useRef(input.onEntitySuggestion)\n const onSuggestionCloseRef = React.useRef(input.onSuggestionClose)\n const [counts, setCounts] = React.useState<EditorWordCount>({ words: 0, characters: 0 })\n readOnlyRef.current = input.readOnly\n onEntitySuggestionRef.current = input.onEntitySuggestion\n onSuggestionCloseRef.current = input.onSuggestionClose\n\n const applyCounts = React.useCallback((updated: Editor | null) => {\n const next = documentCounts(updated)\n React.startTransition(() => {\n setCounts((current) => current.words === next.words && current.characters === next.characters ? current : next)\n })\n }, [])\n const scheduleCounts = React.useCallback((updated: Editor) => {\n if (countTimerRef.current !== null) window.clearTimeout(countTimerRef.current)\n countTimerRef.current = window.setTimeout(() => {\n countTimerRef.current = null\n applyCounts(updated)\n }, DOCUMENT_COUNT_UPDATE_DELAY_MS)\n }, [applyCounts])\n\n const extensions = React.useMemo(() => {\n const suggestions = [createEntitySuggestionExtension({\n onTrigger: ({ range }) => {\n if (!readOnlyRef.current) onEntitySuggestionRef.current(range)\n },\n onClose: () => onSuggestionCloseRef.current(),\n })]\n return input.editorMode === 'collab' && input.collabResources\n ? [...getCollaborativeEditorExtensions({\n ydoc: input.collabResources.ydoc,\n provider: input.collabResources.provider,\n user: input.collabResources.user,\n placeholder: t('documents.editor.placeholder'),\n fallbackUserLabel: t('documents.users.unknown'),\n fallbackEntityRefLabel: t('documents.editor.entityRef.fallbackLabel'),\n }), ...getClientEditorExtras(), DocumentPagination, ...suggestions]\n : [...getDocumentEditorExtensions({\n entityRefFallbackLabel: t('documents.editor.entityRef.fallbackLabel'),\n }), ...getClientEditorExtras(), DocumentPagination, ...suggestions]\n }, [input.collabResources, input.editorMode, t])\n\n const editor = useEditor({\n extensions,\n content: input.editorMode === 'fallback' ? input.initialContentHtml : undefined,\n editable: !input.readOnly,\n editorProps: {\n attributes: {\n class: 'min-h-96 text-base leading-7 text-foreground focus-visible:outline-none',\n role: 'textbox',\n 'aria-label': t('documents.editor.content.ariaLabel'),\n 'aria-multiline': 'true',\n },\n handleClick(_view, _position, event) {\n return activateEntityRefFromPointerEvent(\n event,\n (href) => { window.open(href, '_blank', 'noopener') },\n )\n },\n handleKeyDown(_view, event) {\n return activateEntityRefFromKeyboardEvent(\n event,\n (href) => { window.open(href, '_blank', 'noopener') },\n )\n },\n },\n onCreate: ({ editor: created }) => { editorRef.current = created; applyCounts(created); input.onEditorReady?.(created) },\n onDestroy: () => {\n if (countTimerRef.current !== null) window.clearTimeout(countTimerRef.current)\n countTimerRef.current = null\n editorRef.current = null\n input.onEditorReady?.(null)\n },\n onUpdate: ({ editor: updated }) => {\n editorRef.current = updated\n scheduleCounts(updated)\n input.onUpdate?.(updated)\n },\n }, [input.documentId, input.editorMode, extensions])\n\n React.useEffect(() => { editorRef.current = editor; editor?.setEditable(!input.readOnly) }, [editor, input.readOnly])\n React.useEffect(() => {\n if (input.readOnly) onSuggestionCloseRef.current()\n }, [input.readOnly])\n React.useEffect(() => () => {\n if (countTimerRef.current !== null) window.clearTimeout(countTimerRef.current)\n }, [])\n React.useEffect(() => () => { input.onEditorReady?.(null) }, [input.onEditorReady])\n return { editor, editorRef, counts }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport { useContentSecurityPolicyNonce } from '@open-mercato/ui/theme/ContentSecurityPolicyProvider'\n\nimport * as React from 'react'\nimport type { Editor } from '@tiptap/core'\nimport type { Node as ProseMirrorNode } from '@tiptap/pm/model'\nimport { useEditor } from '@tiptap/react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n activateEntityRefFromKeyboardEvent,\n activateEntityRefFromPointerEvent,\n getClientEditorExtras,\n getCollaborativeEditorExtensions,\n getDocumentEditorExtensions,\n} from '../../../lib/editorConfig'\nimport { createEntitySuggestionExtension } from '../../../lib/entitySuggestion'\nimport { DocumentPagination } from './documentPagination'\nimport type { CollabResources, EditorSelectionRange, EditorWordCount } from './editorTypes'\n\ntype UseDocumentEditorInput = {\n documentId: string\n initialContentHtml: string\n editorMode: 'collab' | 'fallback'\n collabResources?: CollabResources\n readOnly: boolean\n onEditorReady?: (editor: Editor | null) => void\n onUpdate?: (editor: Editor) => void\n onEntitySuggestion: (range: EditorSelectionRange) => void\n onSuggestionClose: () => void\n}\n\nexport const DOCUMENT_COUNT_UPDATE_DELAY_MS = 150\n\nexport function countDocumentWordsAndCharacters(doc: ProseMirrorNode): EditorWordCount {\n let words = 0\n let characters = 0\n let firstBlock = true\n let insideWord = false\n\n doc.nodesBetween(0, doc.content.size, (node) => {\n const nodeText = node.isText ? node.text ?? '' : node.isLeaf ? ' ' : ''\n if (node.isBlock && ((node.isLeaf && nodeText.length > 0) || node.isTextblock)) {\n if (firstBlock) firstBlock = false\n else insideWord = false\n }\n characters += nodeText.length\n for (let index = 0; index < nodeText.length; index += 1) {\n if (nodeText[index] === ' ') {\n insideWord = false\n } else if (!insideWord) {\n words += 1\n insideWord = true\n }\n }\n })\n return { words, characters }\n}\n\nfunction documentCounts(editor: Editor | null): EditorWordCount {\n if (!editor) return { words: 0, characters: 0 }\n return countDocumentWordsAndCharacters(editor.state.doc)\n}\n\nexport function useDocumentEditor(input: UseDocumentEditorInput) {\n const t = useT()\n const nonce = useContentSecurityPolicyNonce()\n const editorRef = React.useRef<Editor | null>(null)\n const countTimerRef = React.useRef<number | null>(null)\n const readOnlyRef = React.useRef(input.readOnly)\n const onEntitySuggestionRef = React.useRef(input.onEntitySuggestion)\n const onSuggestionCloseRef = React.useRef(input.onSuggestionClose)\n const [counts, setCounts] = React.useState<EditorWordCount>({ words: 0, characters: 0 })\n readOnlyRef.current = input.readOnly\n onEntitySuggestionRef.current = input.onEntitySuggestion\n onSuggestionCloseRef.current = input.onSuggestionClose\n\n const applyCounts = React.useCallback((updated: Editor | null) => {\n const next = documentCounts(updated)\n React.startTransition(() => {\n setCounts((current) => current.words === next.words && current.characters === next.characters ? current : next)\n })\n }, [])\n const scheduleCounts = React.useCallback((updated: Editor) => {\n if (countTimerRef.current !== null) window.clearTimeout(countTimerRef.current)\n countTimerRef.current = window.setTimeout(() => {\n countTimerRef.current = null\n applyCounts(updated)\n }, DOCUMENT_COUNT_UPDATE_DELAY_MS)\n }, [applyCounts])\n\n const extensions = React.useMemo(() => {\n const suggestions = [createEntitySuggestionExtension({\n onTrigger: ({ range }) => {\n if (!readOnlyRef.current) onEntitySuggestionRef.current(range)\n },\n onClose: () => onSuggestionCloseRef.current(),\n })]\n return input.editorMode === 'collab' && input.collabResources\n ? [...getCollaborativeEditorExtensions({\n ydoc: input.collabResources.ydoc,\n provider: input.collabResources.provider,\n user: input.collabResources.user,\n placeholder: t('documents.editor.placeholder'),\n fallbackUserLabel: t('documents.users.unknown'),\n fallbackEntityRefLabel: t('documents.editor.entityRef.fallbackLabel'),\n }), ...getClientEditorExtras(), DocumentPagination, ...suggestions]\n : [...getDocumentEditorExtensions({\n entityRefFallbackLabel: t('documents.editor.entityRef.fallbackLabel'),\n }), ...getClientEditorExtras(), DocumentPagination, ...suggestions]\n }, [input.collabResources, input.editorMode, t])\n\n const editor = useEditor({\n injectNonce: nonce,\n extensions,\n content: input.editorMode === 'fallback' ? input.initialContentHtml : undefined,\n editable: !input.readOnly,\n editorProps: {\n attributes: {\n class: 'min-h-96 text-base leading-7 text-foreground focus-visible:outline-none',\n role: 'textbox',\n 'aria-label': t('documents.editor.content.ariaLabel'),\n 'aria-multiline': 'true',\n },\n handleClick(_view, _position, event) {\n return activateEntityRefFromPointerEvent(\n event,\n (href) => { window.open(href, '_blank', 'noopener') },\n )\n },\n handleKeyDown(_view, event) {\n return activateEntityRefFromKeyboardEvent(\n event,\n (href) => { window.open(href, '_blank', 'noopener') },\n )\n },\n },\n onCreate: ({ editor: created }) => { editorRef.current = created; applyCounts(created); input.onEditorReady?.(created) },\n onDestroy: () => {\n if (countTimerRef.current !== null) window.clearTimeout(countTimerRef.current)\n countTimerRef.current = null\n editorRef.current = null\n input.onEditorReady?.(null)\n },\n onUpdate: ({ editor: updated }) => {\n editorRef.current = updated\n scheduleCounts(updated)\n input.onUpdate?.(updated)\n },\n }, [input.documentId, input.editorMode, extensions])\n\n React.useEffect(() => { editorRef.current = editor; editor?.setEditable(!input.readOnly) }, [editor, input.readOnly])\n React.useEffect(() => {\n if (input.readOnly) onSuggestionCloseRef.current()\n }, [input.readOnly])\n React.useEffect(() => () => {\n if (countTimerRef.current !== null) window.clearTimeout(countTimerRef.current)\n }, [])\n React.useEffect(() => () => { input.onEditorReady?.(null) }, [input.onEditorReady])\n return { editor, editorRef, counts }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
+ import { useContentSecurityPolicyNonce } from "@open-mercato/ui/theme/ContentSecurityPolicyProvider";
4
5
  import { EditorContent, useEditor, useEditorState } from "@tiptap/react";
5
6
  import { Bold, Heading1, Heading2, Italic, List, ListOrdered } from "lucide-react";
6
7
  import { IconButton } from "@open-mercato/ui/primitives/icon-button";
@@ -23,9 +24,11 @@ function EditorButton({ editor, label, active, children, onClick }) {
23
24
  }
24
25
  function TemplateBodyEditor({ bodyHtml, tokenOptions, onChange }) {
25
26
  const t = useT();
27
+ const nonce = useContentSecurityPolicyNonce();
26
28
  const editorLabelId = React.useId();
27
29
  const [token, setToken] = React.useState();
28
30
  const editor = useEditor({
31
+ injectNonce: nonce,
29
32
  extensions: getDocumentEditorExtensions({
30
33
  entityRefFallbackLabel: t("documents.editor.entityRef.fallbackLabel")
31
34
  }),
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/documents/backend/documents/components/TemplateBodyEditor.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { Editor } from '@tiptap/core'\nimport { EditorContent, useEditor, useEditorState } from '@tiptap/react'\nimport { Bold, Heading1, Heading2, Italic, List, ListOrdered } from 'lucide-react'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@open-mercato/ui/primitives/select'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { getDocumentEditorExtensions } from '../../../lib/editorConfig'\n\nconst EMPTY_TEMPLATE_EDITOR_STATE = {\n bold: false,\n italic: false,\n heading1: false,\n heading2: false,\n bulletList: false,\n orderedList: false,\n}\n\nfunction EditorButton({ editor, label, active, children, onClick }: {\n editor: Editor | null\n label: string\n active: boolean\n children: React.ReactNode\n onClick: (editor: Editor) => void\n}) {\n return <IconButton type=\"button\" variant={active ? 'outline' : 'ghost'} aria-label={label} aria-pressed={active} title={label} disabled={!editor} onMouseDown={(event) => event.preventDefault()} onClick={() => { if (editor) onClick(editor) }}>{children}</IconButton>\n}\n\nexport function TemplateBodyEditor({ bodyHtml, tokenOptions, onChange }: {\n bodyHtml: string\n tokenOptions: string[]\n onChange: (html: string) => void\n}) {\n const t = useT()\n const editorLabelId = React.useId()\n const [token, setToken] = React.useState<string | undefined>()\n const editor = useEditor({\n extensions: getDocumentEditorExtensions({\n entityRefFallbackLabel: t('documents.editor.entityRef.fallbackLabel'),\n }),\n content: bodyHtml,\n editable: true,\n editorProps: {\n attributes: {\n class: 'min-h-80 text-base leading-7 text-foreground focus-visible:outline-none',\n role: 'textbox',\n 'aria-labelledby': editorLabelId,\n 'aria-multiline': 'true',\n },\n },\n onUpdate: ({ editor: updated }) => onChange(updated.getHTML()),\n }, [])\n React.useEffect(() => {\n if (editor && editor.getHTML() !== bodyHtml) editor.commands.setContent(bodyHtml)\n }, [bodyHtml, editor])\n const editorState = useEditorState({\n editor,\n selector: ({ editor: current }) => current ? {\n bold: current.isActive('bold'),\n italic: current.isActive('italic'),\n heading1: current.isActive('heading', { level: 1 }),\n heading2: current.isActive('heading', { level: 2 }),\n bulletList: current.isActive('bulletList'),\n orderedList: current.isActive('orderedList'),\n } : EMPTY_TEMPLATE_EDITOR_STATE,\n }) ?? EMPTY_TEMPLATE_EDITOR_STATE\n return (\n <div className=\"space-y-3\">\n <Label id={editorLabelId}>{t('documents.templates.fields.body')}</Label>\n <div className=\"rounded-md border border-border bg-card\">\n <div className=\"flex flex-wrap items-center gap-1 border-b border-border p-2\">\n <EditorButton editor={editor} label={t('documents.editor.toolbar.bold')} active={editorState.bold} onClick={(current) => { current.chain().focus().toggleBold().run() }}><Bold /></EditorButton>\n <EditorButton editor={editor} label={t('documents.editor.toolbar.italic')} active={editorState.italic} onClick={(current) => { current.chain().focus().toggleItalic().run() }}><Italic /></EditorButton>\n <EditorButton editor={editor} label={t('documents.editor.toolbar.heading1')} active={editorState.heading1} onClick={(current) => { current.chain().focus().toggleHeading({ level: 1 }).run() }}><Heading1 /></EditorButton>\n <EditorButton editor={editor} label={t('documents.editor.toolbar.heading2')} active={editorState.heading2} onClick={(current) => { current.chain().focus().toggleHeading({ level: 2 }).run() }}><Heading2 /></EditorButton>\n <EditorButton editor={editor} label={t('documents.editor.toolbar.bulletList')} active={editorState.bulletList} onClick={(current) => { current.chain().focus().toggleBulletList().run() }}><List /></EditorButton>\n <EditorButton editor={editor} label={t('documents.editor.toolbar.orderedList')} active={editorState.orderedList} onClick={(current) => { current.chain().focus().toggleOrderedList().run() }}><ListOrdered /></EditorButton>\n <div className=\"min-w-52\">\n <Select value={token} onValueChange={(value) => {\n setToken(value)\n editor?.chain().focus().insertContent(value).run()\n window.setTimeout(() => setToken(undefined), 0)\n }}>\n <SelectTrigger aria-label={t('documents.templates.editor.insertField')}><SelectValue placeholder={t('documents.templates.editor.insertField')} /></SelectTrigger>\n <SelectContent>{tokenOptions.map((value) => <SelectItem key={value} value={value}>{value}</SelectItem>)}</SelectContent>\n </Select>\n </div>\n </div>\n <EditorContent editor={editor} className=\"max-w-none p-4 text-foreground [&_.ProseMirror]:min-h-80 [&_.ProseMirror]:focus-visible:outline-none\" />\n </div>\n </div>\n )\n}\n"],
5
- "mappings": ";AA4BS,cAqDG,YArDH;AA1BT,YAAY,WAAW;AAEvB,SAAS,eAAe,WAAW,sBAAsB;AACzD,SAAS,MAAM,UAAU,UAAU,QAAQ,MAAM,mBAAmB;AACpE,SAAS,kBAAkB;AAC3B,SAAS,aAAa;AACtB,SAAS,QAAQ,eAAe,YAAY,eAAe,mBAAmB;AAC9E,SAAS,YAAY;AACrB,SAAS,mCAAmC;AAE5C,MAAM,8BAA8B;AAAA,EAClC,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,aAAa;AACf;AAEA,SAAS,aAAa,EAAE,QAAQ,OAAO,QAAQ,UAAU,QAAQ,GAM9D;AACD,SAAO,oBAAC,cAAW,MAAK,UAAS,SAAS,SAAS,YAAY,SAAS,cAAY,OAAO,gBAAc,QAAQ,OAAO,OAAO,UAAU,CAAC,QAAQ,aAAa,CAAC,UAAU,MAAM,eAAe,GAAG,SAAS,MAAM;AAAE,QAAI,OAAQ,SAAQ,MAAM;AAAA,EAAE,GAAI,UAAS;AAC9P;AAEO,SAAS,mBAAmB,EAAE,UAAU,cAAc,SAAS,GAInE;AACD,QAAM,IAAI,KAAK;AACf,QAAM,gBAAgB,MAAM,MAAM;AAClC,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAA6B;AAC7D,QAAM,SAAS,UAAU;AAAA,IACvB,YAAY,4BAA4B;AAAA,MACtC,wBAAwB,EAAE,0CAA0C;AAAA,IACtE,CAAC;AAAA,IACD,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa;AAAA,MACX,YAAY;AAAA,QACV,OAAO;AAAA,QACP,MAAM;AAAA,QACN,mBAAmB;AAAA,QACnB,kBAAkB;AAAA,MACpB;AAAA,IACF;AAAA,IACA,UAAU,CAAC,EAAE,QAAQ,QAAQ,MAAM,SAAS,QAAQ,QAAQ,CAAC;AAAA,EAC/D,GAAG,CAAC,CAAC;AACL,QAAM,UAAU,MAAM;AACpB,QAAI,UAAU,OAAO,QAAQ,MAAM,SAAU,QAAO,SAAS,WAAW,QAAQ;AAAA,EAClF,GAAG,CAAC,UAAU,MAAM,CAAC;AACrB,QAAM,cAAc,eAAe;AAAA,IACjC;AAAA,IACA,UAAU,CAAC,EAAE,QAAQ,QAAQ,MAAM,UAAU;AAAA,MAC3C,MAAM,QAAQ,SAAS,MAAM;AAAA,MAC7B,QAAQ,QAAQ,SAAS,QAAQ;AAAA,MACjC,UAAU,QAAQ,SAAS,WAAW,EAAE,OAAO,EAAE,CAAC;AAAA,MAClD,UAAU,QAAQ,SAAS,WAAW,EAAE,OAAO,EAAE,CAAC;AAAA,MAClD,YAAY,QAAQ,SAAS,YAAY;AAAA,MACzC,aAAa,QAAQ,SAAS,aAAa;AAAA,IAC7C,IAAI;AAAA,EACN,CAAC,KAAK;AACN,SACE,qBAAC,SAAI,WAAU,aACb;AAAA,wBAAC,SAAM,IAAI,eAAgB,YAAE,iCAAiC,GAAE;AAAA,IAChE,qBAAC,SAAI,WAAU,2CACb;AAAA,2BAAC,SAAI,WAAU,gEACb;AAAA,4BAAC,gBAAa,QAAgB,OAAO,EAAE,+BAA+B,GAAG,QAAQ,YAAY,MAAM,SAAS,CAAC,YAAY;AAAE,kBAAQ,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI;AAAA,QAAE,GAAG,8BAAC,QAAK,GAAE;AAAA,QACjL,oBAAC,gBAAa,QAAgB,OAAO,EAAE,iCAAiC,GAAG,QAAQ,YAAY,QAAQ,SAAS,CAAC,YAAY;AAAE,kBAAQ,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,IAAI;AAAA,QAAE,GAAG,8BAAC,UAAO,GAAE;AAAA,QACzL,oBAAC,gBAAa,QAAgB,OAAO,EAAE,mCAAmC,GAAG,QAAQ,YAAY,UAAU,SAAS,CAAC,YAAY;AAAE,kBAAQ,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI;AAAA,QAAE,GAAG,8BAAC,YAAS,GAAE;AAAA,QAC5M,oBAAC,gBAAa,QAAgB,OAAO,EAAE,mCAAmC,GAAG,QAAQ,YAAY,UAAU,SAAS,CAAC,YAAY;AAAE,kBAAQ,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI;AAAA,QAAE,GAAG,8BAAC,YAAS,GAAE;AAAA,QAC5M,oBAAC,gBAAa,QAAgB,OAAO,EAAE,qCAAqC,GAAG,QAAQ,YAAY,YAAY,SAAS,CAAC,YAAY;AAAE,kBAAQ,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,IAAI;AAAA,QAAE,GAAG,8BAAC,QAAK,GAAE;AAAA,QACnM,oBAAC,gBAAa,QAAgB,OAAO,EAAE,sCAAsC,GAAG,QAAQ,YAAY,aAAa,SAAS,CAAC,YAAY;AAAE,kBAAQ,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,IAAI;AAAA,QAAE,GAAG,8BAAC,eAAY,GAAE;AAAA,QAC7M,oBAAC,SAAI,WAAU,YACb,+BAAC,UAAO,OAAO,OAAO,eAAe,CAAC,UAAU;AAC9C,mBAAS,KAAK;AACd,kBAAQ,MAAM,EAAE,MAAM,EAAE,cAAc,KAAK,EAAE,IAAI;AACjD,iBAAO,WAAW,MAAM,SAAS,MAAS,GAAG,CAAC;AAAA,QAChD,GACE;AAAA,8BAAC,iBAAc,cAAY,EAAE,wCAAwC,GAAG,8BAAC,eAAY,aAAa,EAAE,wCAAwC,GAAG,GAAE;AAAA,UACjJ,oBAAC,iBAAe,uBAAa,IAAI,CAAC,UAAU,oBAAC,cAAuB,OAAe,mBAAtB,KAA4B,CAAa,GAAE;AAAA,WAC1G,GACF;AAAA,SACF;AAAA,MACA,oBAAC,iBAAc,QAAgB,WAAU,wGAAuG;AAAA,OAClJ;AAAA,KACF;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useContentSecurityPolicyNonce } from '@open-mercato/ui/theme/ContentSecurityPolicyProvider'\nimport type { Editor } from '@tiptap/core'\nimport { EditorContent, useEditor, useEditorState } from '@tiptap/react'\nimport { Bold, Heading1, Heading2, Italic, List, ListOrdered } from 'lucide-react'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@open-mercato/ui/primitives/select'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { getDocumentEditorExtensions } from '../../../lib/editorConfig'\n\nconst EMPTY_TEMPLATE_EDITOR_STATE = {\n bold: false,\n italic: false,\n heading1: false,\n heading2: false,\n bulletList: false,\n orderedList: false,\n}\n\nfunction EditorButton({ editor, label, active, children, onClick }: {\n editor: Editor | null\n label: string\n active: boolean\n children: React.ReactNode\n onClick: (editor: Editor) => void\n}) {\n return <IconButton type=\"button\" variant={active ? 'outline' : 'ghost'} aria-label={label} aria-pressed={active} title={label} disabled={!editor} onMouseDown={(event) => event.preventDefault()} onClick={() => { if (editor) onClick(editor) }}>{children}</IconButton>\n}\n\nexport function TemplateBodyEditor({ bodyHtml, tokenOptions, onChange }: {\n bodyHtml: string\n tokenOptions: string[]\n onChange: (html: string) => void\n}) {\n const t = useT()\n const nonce = useContentSecurityPolicyNonce()\n const editorLabelId = React.useId()\n const [token, setToken] = React.useState<string | undefined>()\n const editor = useEditor({\n injectNonce: nonce,\n extensions: getDocumentEditorExtensions({\n entityRefFallbackLabel: t('documents.editor.entityRef.fallbackLabel'),\n }),\n content: bodyHtml,\n editable: true,\n editorProps: {\n attributes: {\n class: 'min-h-80 text-base leading-7 text-foreground focus-visible:outline-none',\n role: 'textbox',\n 'aria-labelledby': editorLabelId,\n 'aria-multiline': 'true',\n },\n },\n onUpdate: ({ editor: updated }) => onChange(updated.getHTML()),\n }, [])\n React.useEffect(() => {\n if (editor && editor.getHTML() !== bodyHtml) editor.commands.setContent(bodyHtml)\n }, [bodyHtml, editor])\n const editorState = useEditorState({\n editor,\n selector: ({ editor: current }) => current ? {\n bold: current.isActive('bold'),\n italic: current.isActive('italic'),\n heading1: current.isActive('heading', { level: 1 }),\n heading2: current.isActive('heading', { level: 2 }),\n bulletList: current.isActive('bulletList'),\n orderedList: current.isActive('orderedList'),\n } : EMPTY_TEMPLATE_EDITOR_STATE,\n }) ?? EMPTY_TEMPLATE_EDITOR_STATE\n return (\n <div className=\"space-y-3\">\n <Label id={editorLabelId}>{t('documents.templates.fields.body')}</Label>\n <div className=\"rounded-md border border-border bg-card\">\n <div className=\"flex flex-wrap items-center gap-1 border-b border-border p-2\">\n <EditorButton editor={editor} label={t('documents.editor.toolbar.bold')} active={editorState.bold} onClick={(current) => { current.chain().focus().toggleBold().run() }}><Bold /></EditorButton>\n <EditorButton editor={editor} label={t('documents.editor.toolbar.italic')} active={editorState.italic} onClick={(current) => { current.chain().focus().toggleItalic().run() }}><Italic /></EditorButton>\n <EditorButton editor={editor} label={t('documents.editor.toolbar.heading1')} active={editorState.heading1} onClick={(current) => { current.chain().focus().toggleHeading({ level: 1 }).run() }}><Heading1 /></EditorButton>\n <EditorButton editor={editor} label={t('documents.editor.toolbar.heading2')} active={editorState.heading2} onClick={(current) => { current.chain().focus().toggleHeading({ level: 2 }).run() }}><Heading2 /></EditorButton>\n <EditorButton editor={editor} label={t('documents.editor.toolbar.bulletList')} active={editorState.bulletList} onClick={(current) => { current.chain().focus().toggleBulletList().run() }}><List /></EditorButton>\n <EditorButton editor={editor} label={t('documents.editor.toolbar.orderedList')} active={editorState.orderedList} onClick={(current) => { current.chain().focus().toggleOrderedList().run() }}><ListOrdered /></EditorButton>\n <div className=\"min-w-52\">\n <Select value={token} onValueChange={(value) => {\n setToken(value)\n editor?.chain().focus().insertContent(value).run()\n window.setTimeout(() => setToken(undefined), 0)\n }}>\n <SelectTrigger aria-label={t('documents.templates.editor.insertField')}><SelectValue placeholder={t('documents.templates.editor.insertField')} /></SelectTrigger>\n <SelectContent>{tokenOptions.map((value) => <SelectItem key={value} value={value}>{value}</SelectItem>)}</SelectContent>\n </Select>\n </div>\n </div>\n <EditorContent editor={editor} className=\"max-w-none p-4 text-foreground [&_.ProseMirror]:min-h-80 [&_.ProseMirror]:focus-visible:outline-none\" />\n </div>\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,13 +1,16 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
+ import { useContentSecurityPolicyNonce } from "@open-mercato/ui/theme/ContentSecurityPolicyProvider";
4
5
  import { EditorContent, useEditor } from "@tiptap/react";
5
6
  import { Alert, AlertDescription, AlertTitle } from "@open-mercato/ui/primitives/alert";
6
7
  import { LoadingMessage } from "@open-mercato/ui/backend/detail";
7
8
  import { useT } from "@open-mercato/shared/lib/i18n/context";
8
9
  import { getDocumentEditorExtensions } from "../../../lib/editorConfig.js";
9
10
  function PreviewSurface({ html, fallbackLabel }) {
11
+ const nonce = useContentSecurityPolicyNonce();
10
12
  const editor = useEditor({
13
+ injectNonce: nonce,
11
14
  extensions: getDocumentEditorExtensions({ entityRefFallbackLabel: fallbackLabel }),
12
15
  content: html,
13
16
  editable: false,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/documents/backend/documents/components/TemplatePreview.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { EditorContent, useEditor } from '@tiptap/react'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { getDocumentEditorExtensions } from '../../../lib/editorConfig'\nimport type { TemplatePreviewResult } from './templateUi'\n\nfunction PreviewSurface({ html, fallbackLabel }: { html: string; fallbackLabel: string }) {\n const editor = useEditor({\n extensions: getDocumentEditorExtensions({ entityRefFallbackLabel: fallbackLabel }),\n content: html,\n editable: false,\n editorProps: { attributes: { class: 'min-h-48 text-base leading-7 text-foreground focus-visible:outline-none' } },\n }, [fallbackLabel])\n React.useEffect(() => { editor?.commands.setContent(html) }, [editor, html])\n return <EditorContent editor={editor} className=\"max-w-none rounded-md border border-border bg-card p-4\" />\n}\n\nexport function TemplatePreview({ preview, isLoading }: { preview: TemplatePreviewResult | null; isLoading: boolean }) {\n const t = useT()\n if (isLoading) return <LoadingMessage label={t('documents.templates.preview.loading')} />\n if (!preview) {\n return (\n <div className=\"rounded-md border border-dashed border-border p-4 text-sm text-muted-foreground\">\n {t('documents.templates.preview.empty')}\n </div>\n )\n }\n return (\n <div className=\"space-y-3\">\n {preview.unresolvedTokens.length > 0 ? (\n <Alert status=\"warning\">\n <AlertTitle>{t('documents.templates.preview.unresolvedTitle')}</AlertTitle>\n <AlertDescription>{t('documents.templates.preview.unresolvedBody', { tokens: preview.unresolvedTokens.join(', ') })}</AlertDescription>\n </Alert>\n ) : null}\n <PreviewSurface html={preview.contentHtml} fallbackLabel={t('documents.editor.entityRef.fallbackLabel')} />\n </div>\n )\n}\n"],
5
- "mappings": ";AAkBS,cAgBD,YAhBC;AAhBT,YAAY,WAAW;AACvB,SAAS,eAAe,iBAAiB;AACzC,SAAS,OAAO,kBAAkB,kBAAkB;AACpD,SAAS,sBAAsB;AAC/B,SAAS,YAAY;AACrB,SAAS,mCAAmC;AAG5C,SAAS,eAAe,EAAE,MAAM,cAAc,GAA4C;AACxF,QAAM,SAAS,UAAU;AAAA,IACvB,YAAY,4BAA4B,EAAE,wBAAwB,cAAc,CAAC;AAAA,IACjF,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa,EAAE,YAAY,EAAE,OAAO,0EAA0E,EAAE;AAAA,EAClH,GAAG,CAAC,aAAa,CAAC;AAClB,QAAM,UAAU,MAAM;AAAE,YAAQ,SAAS,WAAW,IAAI;AAAA,EAAE,GAAG,CAAC,QAAQ,IAAI,CAAC;AAC3E,SAAO,oBAAC,iBAAc,QAAgB,WAAU,0DAAyD;AAC3G;AAEO,SAAS,gBAAgB,EAAE,SAAS,UAAU,GAAkE;AACrH,QAAM,IAAI,KAAK;AACf,MAAI,UAAW,QAAO,oBAAC,kBAAe,OAAO,EAAE,qCAAqC,GAAG;AACvF,MAAI,CAAC,SAAS;AACZ,WACE,oBAAC,SAAI,WAAU,mFACZ,YAAE,mCAAmC,GACxC;AAAA,EAEJ;AACA,SACE,qBAAC,SAAI,WAAU,aACZ;AAAA,YAAQ,iBAAiB,SAAS,IACjC,qBAAC,SAAM,QAAO,WACZ;AAAA,0BAAC,cAAY,YAAE,6CAA6C,GAAE;AAAA,MAC9D,oBAAC,oBAAkB,YAAE,8CAA8C,EAAE,QAAQ,QAAQ,iBAAiB,KAAK,IAAI,EAAE,CAAC,GAAE;AAAA,OACtH,IACE;AAAA,IACJ,oBAAC,kBAAe,MAAM,QAAQ,aAAa,eAAe,EAAE,0CAA0C,GAAG;AAAA,KAC3G;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useContentSecurityPolicyNonce } from '@open-mercato/ui/theme/ContentSecurityPolicyProvider'\nimport { EditorContent, useEditor } from '@tiptap/react'\nimport { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'\nimport { LoadingMessage } from '@open-mercato/ui/backend/detail'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { getDocumentEditorExtensions } from '../../../lib/editorConfig'\nimport type { TemplatePreviewResult } from './templateUi'\n\nfunction PreviewSurface({ html, fallbackLabel }: { html: string; fallbackLabel: string }) {\n const nonce = useContentSecurityPolicyNonce()\n const editor = useEditor({\n injectNonce: nonce,\n extensions: getDocumentEditorExtensions({ entityRefFallbackLabel: fallbackLabel }),\n content: html,\n editable: false,\n editorProps: { attributes: { class: 'min-h-48 text-base leading-7 text-foreground focus-visible:outline-none' } },\n }, [fallbackLabel])\n React.useEffect(() => { editor?.commands.setContent(html) }, [editor, html])\n return <EditorContent editor={editor} className=\"max-w-none rounded-md border border-border bg-card p-4\" />\n}\n\nexport function TemplatePreview({ preview, isLoading }: { preview: TemplatePreviewResult | null; isLoading: boolean }) {\n const t = useT()\n if (isLoading) return <LoadingMessage label={t('documents.templates.preview.loading')} />\n if (!preview) {\n return (\n <div className=\"rounded-md border border-dashed border-border p-4 text-sm text-muted-foreground\">\n {t('documents.templates.preview.empty')}\n </div>\n )\n }\n return (\n <div className=\"space-y-3\">\n {preview.unresolvedTokens.length > 0 ? (\n <Alert status=\"warning\">\n <AlertTitle>{t('documents.templates.preview.unresolvedTitle')}</AlertTitle>\n <AlertDescription>{t('documents.templates.preview.unresolvedBody', { tokens: preview.unresolvedTokens.join(', ') })}</AlertDescription>\n </Alert>\n ) : null}\n <PreviewSurface html={preview.contentHtml} fallbackLabel={t('documents.editor.entityRef.fallbackLabel')} />\n </div>\n )\n}\n"],
5
+ "mappings": ";AAqBS,cAgBD,YAhBC;AAnBT,YAAY,WAAW;AACvB,SAAS,qCAAqC;AAC9C,SAAS,eAAe,iBAAiB;AACzC,SAAS,OAAO,kBAAkB,kBAAkB;AACpD,SAAS,sBAAsB;AAC/B,SAAS,YAAY;AACrB,SAAS,mCAAmC;AAG5C,SAAS,eAAe,EAAE,MAAM,cAAc,GAA4C;AACxF,QAAM,QAAQ,8BAA8B;AAC5C,QAAM,SAAS,UAAU;AAAA,IACvB,aAAa;AAAA,IACb,YAAY,4BAA4B,EAAE,wBAAwB,cAAc,CAAC;AAAA,IACjF,SAAS;AAAA,IACT,UAAU;AAAA,IACV,aAAa,EAAE,YAAY,EAAE,OAAO,0EAA0E,EAAE;AAAA,EAClH,GAAG,CAAC,aAAa,CAAC;AAClB,QAAM,UAAU,MAAM;AAAE,YAAQ,SAAS,WAAW,IAAI;AAAA,EAAE,GAAG,CAAC,QAAQ,IAAI,CAAC;AAC3E,SAAO,oBAAC,iBAAc,QAAgB,WAAU,0DAAyD;AAC3G;AAEO,SAAS,gBAAgB,EAAE,SAAS,UAAU,GAAkE;AACrH,QAAM,IAAI,KAAK;AACf,MAAI,UAAW,QAAO,oBAAC,kBAAe,OAAO,EAAE,qCAAqC,GAAG;AACvF,MAAI,CAAC,SAAS;AACZ,WACE,oBAAC,SAAI,WAAU,mFACZ,YAAE,mCAAmC,GACxC;AAAA,EAEJ;AACA,SACE,qBAAC,SAAI,WAAU,aACZ;AAAA,YAAQ,iBAAiB,SAAS,IACjC,qBAAC,SAAM,QAAO,WACZ;AAAA,0BAAC,cAAY,YAAE,6CAA6C,GAAE;AAAA,MAC9D,oBAAC,oBAAkB,YAAE,8CAA8C,EAAE,QAAQ,QAAQ,iBAAiB,KAAK,IAAI,EAAE,CAAC,GAAE;AAAA,OACtH,IACE;AAAA,IACJ,oBAAC,kBAAe,MAAM,QAAQ,aAAa,eAAe,EAAE,0CAA0C,GAAG;AAAA,KAC3G;AAEJ;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/documents",
3
- "version": "0.10.1-develop.7808.1.8128cefd28",
3
+ "version": "0.10.1-develop.7809.1.ea24461aeb",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -75,8 +75,8 @@
75
75
  "@hocuspocus/server": "^4.4.0",
76
76
  "@hocuspocus/transformer": "^4.3.0",
77
77
  "@mikro-orm/core": "^7.1.14",
78
- "@open-mercato/events": "0.10.1-develop.7808.1.8128cefd28",
79
- "@open-mercato/ui": "0.10.1-develop.7808.1.8128cefd28",
78
+ "@open-mercato/events": "0.10.1-develop.7809.1.ea24461aeb",
79
+ "@open-mercato/ui": "0.10.1-develop.7809.1.ea24461aeb",
80
80
  "@tiptap/core": "^3.32.2",
81
81
  "@tiptap/extension-bubble-menu": "^3.32.2",
82
82
  "@tiptap/extension-collaboration": "^3.32.2",
@@ -113,13 +113,13 @@
113
113
  },
114
114
  "peerDependencies": {
115
115
  "@mikro-orm/postgresql": "^7.0.14",
116
- "@open-mercato/shared": "0.10.1-develop.7808.1.8128cefd28",
116
+ "@open-mercato/shared": "0.10.1-develop.7809.1.ea24461aeb",
117
117
  "react": "^19.0.0",
118
118
  "react-dom": "^19.0.0"
119
119
  },
120
120
  "devDependencies": {
121
- "@open-mercato/core": "0.10.1-develop.7808.1.8128cefd28",
122
- "@open-mercato/shared": "0.10.1-develop.7808.1.8128cefd28",
121
+ "@open-mercato/core": "0.10.1-develop.7809.1.ea24461aeb",
122
+ "@open-mercato/shared": "0.10.1-develop.7809.1.ea24461aeb",
123
123
  "@types/jest": "^30.0.0",
124
124
  "@types/pg": "^8.23.1",
125
125
  "@types/react": "^19.2.18",
@@ -0,0 +1,21 @@
1
+ import { createLogger } from '@open-mercato/shared/lib/logger'
2
+ import { resolveDocumentsCollaborationEndpoint } from './modules/documents/lib/collabEndpoint'
3
+
4
+ let warnedAboutExcludedEndpoint = false
5
+
6
+ export function resolveDocumentsCollaborationCspOrigins(): string[] {
7
+ const endpoint = resolveDocumentsCollaborationEndpoint()
8
+ if (!endpoint) return []
9
+ const url = new URL(endpoint)
10
+ if (url.username || url.password || !/^(?:[a-z0-9.-]+|\[[0-9a-f:]+\])$/i.test(url.hostname)) {
11
+ if (!warnedAboutExcludedEndpoint) {
12
+ warnedAboutExcludedEndpoint = true
13
+ createLogger('documents:csp').warn(
14
+ '[internal] Collaboration endpoint excluded from CSP; use a credential-free DNS or IPv6 URL',
15
+ { configuration: 'NEXT_PUBLIC_DOCUMENTS_COLLAB_URL' },
16
+ )
17
+ }
18
+ return []
19
+ }
20
+ return [url.origin]
21
+ }
@@ -15,6 +15,7 @@ import { OutlinePane } from './OutlinePane'
15
15
  import { captureCommentAnchor, type CommentAnchor } from './CommentAnchorNavigation'
16
16
  import { DOCUMENT_EDITOR_CONTENT_CLASS } from './editorTypes'
17
17
  import { DOCUMENT_PAGINATION_STYLES } from './documentPagination'
18
+ import { useContentSecurityPolicyNonce } from '@open-mercato/ui/theme/ContentSecurityPolicyProvider'
18
19
 
19
20
  type TitleModel = {
20
21
  value: string
@@ -49,6 +50,7 @@ export function DocumentCanvas({
49
50
  onComment?: (anchor: CommentAnchor) => void
50
51
  }) {
51
52
  const t = useT()
53
+ const nonce = useContentSecurityPolicyNonce()
52
54
  const editorState = useEditorState({
53
55
  editor,
54
56
  selector: ({ editor: current }) => current ? {
@@ -71,7 +73,7 @@ export function DocumentCanvas({
71
73
 
72
74
  return (
73
75
  <div className="om-doc-canvas max-h-[calc(100dvh-var(--topbar-height,0px)-12rem)] min-h-96 overflow-auto bg-muted p-0 sm:px-4 sm:py-8 md:px-8 md:py-10">
74
- <style>{DOCUMENT_PAGINATION_STYLES}</style>
76
+ <style nonce={nonce}>{DOCUMENT_PAGINATION_STYLES}</style>
75
77
  {notice ? <Alert status="information" style="lighter" className="mx-auto mb-4 max-w-3xl"><AlertDescription>{notice}</AlertDescription></Alert> : null}
76
78
  <div className="mx-auto flex w-fit min-w-full flex-col gap-4 lg:flex-row lg:items-start">
77
79
  {outlineOpen ? <OutlinePane editor={editor} /> : null}
@@ -1,6 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
+ import { useContentSecurityPolicyNonce } from '@open-mercato/ui/theme/ContentSecurityPolicyProvider'
4
5
  import { Link2, Unlink } from 'lucide-react'
5
6
  import { Button } from '@open-mercato/ui/primitives/button'
6
7
  import { Input } from '@open-mercato/ui/primitives/input'
@@ -30,6 +31,7 @@ type Props = DocumentEditorIslandProps & {
30
31
 
31
32
  export function DocumentEditorSurface(input: Props) {
32
33
  const t = useT()
34
+ const nonce = useContentSecurityPolicyNonce()
33
35
  const [outlineOpen, setOutlineOpen] = React.useState(false)
34
36
  const [suggestionRange, setSuggestionRange] = React.useState<{ from: number; to: number } | null>(null)
35
37
  const modeChangeInFlightRef = React.useRef(false)
@@ -102,7 +104,7 @@ export function DocumentEditorSurface(input: Props) {
102
104
 
103
105
  return (
104
106
  <div className={cn('space-y-3', input.transport === 'collab' && 'om-doc-collab', input.mode === 'preview' && 'om-doc-preview')}>
105
- <style>{EDITOR_PRESENCE_STYLES}</style>
107
+ <style nonce={nonce}>{EDITOR_PRESENCE_STYLES}</style>
106
108
  <div className="overflow-hidden rounded-lg border border-border bg-muted shadow-sm">
107
109
  <div className="sticky top-0 z-sticky border-b border-border bg-card/95">
108
110
  <div className="flex flex-wrap items-center justify-between gap-2 px-3 py-2">
@@ -1,6 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
+ import { useContentSecurityPolicyNonce } from '@open-mercato/ui/theme/ContentSecurityPolicyProvider'
4
5
  import { EditorContent, useEditor } from '@tiptap/react'
5
6
  import { RotateCcw } from 'lucide-react'
6
7
  import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
@@ -63,7 +64,9 @@ export function normalizeVersionPreview(
63
64
  }
64
65
 
65
66
  function ReadOnlyVersion({ contentHtml, fallbackLabel }: { contentHtml: string; fallbackLabel: string }) {
67
+ const nonce = useContentSecurityPolicyNonce()
66
68
  const editor = useEditor({
69
+ injectNonce: nonce,
67
70
  extensions: getDocumentEditorExtensions({ entityRefFallbackLabel: fallbackLabel }),
68
71
  content: contentHtml,
69
72
  editable: false,
@@ -1,4 +1,5 @@
1
1
  "use client"
2
+ import { useContentSecurityPolicyNonce } from '@open-mercato/ui/theme/ContentSecurityPolicyProvider'
2
3
 
3
4
  import * as React from 'react'
4
5
  import type { Editor } from '@tiptap/core'
@@ -62,6 +63,7 @@ function documentCounts(editor: Editor | null): EditorWordCount {
62
63
 
63
64
  export function useDocumentEditor(input: UseDocumentEditorInput) {
64
65
  const t = useT()
66
+ const nonce = useContentSecurityPolicyNonce()
65
67
  const editorRef = React.useRef<Editor | null>(null)
66
68
  const countTimerRef = React.useRef<number | null>(null)
67
69
  const readOnlyRef = React.useRef(input.readOnly)
@@ -108,6 +110,7 @@ export function useDocumentEditor(input: UseDocumentEditorInput) {
108
110
  }, [input.collabResources, input.editorMode, t])
109
111
 
110
112
  const editor = useEditor({
113
+ injectNonce: nonce,
111
114
  extensions,
112
115
  content: input.editorMode === 'fallback' ? input.initialContentHtml : undefined,
113
116
  editable: !input.readOnly,
@@ -1,6 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
+ import { useContentSecurityPolicyNonce } from '@open-mercato/ui/theme/ContentSecurityPolicyProvider'
4
5
  import type { Editor } from '@tiptap/core'
5
6
  import { EditorContent, useEditor, useEditorState } from '@tiptap/react'
6
7
  import { Bold, Heading1, Heading2, Italic, List, ListOrdered } from 'lucide-react'
@@ -35,9 +36,11 @@ export function TemplateBodyEditor({ bodyHtml, tokenOptions, onChange }: {
35
36
  onChange: (html: string) => void
36
37
  }) {
37
38
  const t = useT()
39
+ const nonce = useContentSecurityPolicyNonce()
38
40
  const editorLabelId = React.useId()
39
41
  const [token, setToken] = React.useState<string | undefined>()
40
42
  const editor = useEditor({
43
+ injectNonce: nonce,
41
44
  extensions: getDocumentEditorExtensions({
42
45
  entityRefFallbackLabel: t('documents.editor.entityRef.fallbackLabel'),
43
46
  }),
@@ -1,6 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
+ import { useContentSecurityPolicyNonce } from '@open-mercato/ui/theme/ContentSecurityPolicyProvider'
4
5
  import { EditorContent, useEditor } from '@tiptap/react'
5
6
  import { Alert, AlertDescription, AlertTitle } from '@open-mercato/ui/primitives/alert'
6
7
  import { LoadingMessage } from '@open-mercato/ui/backend/detail'
@@ -9,7 +10,9 @@ import { getDocumentEditorExtensions } from '../../../lib/editorConfig'
9
10
  import type { TemplatePreviewResult } from './templateUi'
10
11
 
11
12
  function PreviewSurface({ html, fallbackLabel }: { html: string; fallbackLabel: string }) {
13
+ const nonce = useContentSecurityPolicyNonce()
12
14
  const editor = useEditor({
15
+ injectNonce: nonce,
13
16
  extensions: getDocumentEditorExtensions({ entityRefFallbackLabel: fallbackLabel }),
14
17
  content: html,
15
18
  editable: false,