diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/file-viewer.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/file-viewer.tsx index fe61b735738..8b729a1257d 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/file-viewer.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/file-viewer.tsx @@ -131,7 +131,10 @@ interface FileViewerProps { export function FileViewer(props: FileViewerProps) { const { contentSource, workspaceId } = props - const imageDimensions = useWorkspaceImageDimensionsAdapter(workspaceId) + // A caller-supplied contentSource means the adapter is unused (and its `workspaceId` may be a share token). + const imageDimensions = useWorkspaceImageDimensionsAdapter(workspaceId, { + enabled: !contentSource, + }) const source = useMemo( () => contentSource ?? createWorkspaceFileContentSource(workspaceId, imageDimensions), [contentSource, workspaceId, imageDimensions] diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx index b3e08f18da1..f17fa37ebd7 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.test.tsx @@ -26,7 +26,7 @@ function fakeNode(attrs: Record) { } function fakeEditor(): Editor { - return { storage: { mention: { navigable: false } } } as unknown as Editor + return { storage: { mentionMenu: { navigable: false } } } as unknown as Editor } let container: HTMLDivElement | null = null diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.tsx b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.tsx index cecd097fd81..30c27c1bed7 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.tsx +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention-chip.tsx @@ -38,7 +38,7 @@ export function MentionChipView({ node, editor }: ReactNodeViewProps) { const { kind, id, label } = node.attrs as MentionAttrs const Icon = mentionIcon(kind, id, label) as StyleableIcon | undefined const iconStyle = Icon ? getBareIconStyle(Icon) : undefined - const navigable = editor.storage.mention?.navigable === true + const navigable = editor.storage.mentionMenu?.navigable === true const workspaceId = typeof params.workspaceId === 'string' ? params.workspaceId : undefined const path = navigable && workspaceId ? simLinkPath(workspaceId, kind, id) : null diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention.ts b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention.ts index bb4b621d78d..1ab92bd938a 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention.ts +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/mention.ts @@ -25,7 +25,7 @@ export interface MentionStorage { declare module '@tiptap/core' { interface Storage { - mention: MentionStorage + mentionMenu: MentionStorage } } @@ -35,10 +35,10 @@ declare module '@tiptap/core' { * entity inserts it as a portable `sim:/` markdown link (same wire format as the chat * composer's `chip-clipboard-codec`), so it round-trips natively through the editor's link + markdown * machinery. The plugin's `items` is an empty gate; the real list is sourced reactively from the store - * inside {@link MentionList}, populated by the host via the extension's `mention` storage. + * inside {@link MentionList}, populated by the host via the extension's `mentionMenu` storage. */ export const Mention = Extension.create, MentionStorage>({ - name: 'mention', + name: 'mentionMenu', addStorage() { return { store: createMentionStore(), onOpen: null, enabled: true, navigable: false } @@ -53,7 +53,7 @@ export const Mention = Extension.create, MentionStorage>({ allowSpaces: false, startOfLine: false, allow: ({ editor, range }) => { - if (!editor.storage.mention.enabled) return false + if (!editor.storage.mentionMenu.enabled) return false if (editor.isActive('codeBlock') || editor.isActive('link') || editor.isActive('code')) { return false } @@ -78,10 +78,10 @@ export const Mention = Extension.create, MentionStorage>({ mapProps: (props) => ({ query: props.query, command: props.command, - store: props.editor.storage.mention.store, + store: props.editor.storage.mentionMenu.store, editor: props.editor, }), - onOpen: (props) => props.editor.storage.mention?.onOpen?.(), + onOpen: (props) => props.editor.storage.mentionMenu?.onOpen?.(), }), }), ] diff --git a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/use-editor-mentions.ts b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/use-editor-mentions.ts index 5ac2ce036fc..72ebc583a18 100644 --- a/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/use-editor-mentions.ts +++ b/apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/rich-markdown-editor/mention/use-editor-mentions.ts @@ -27,15 +27,15 @@ export function useEditorMentions( useEffect(() => { if (!editor) return const taggingOn = Boolean(workspaceId) && !disableTagging - editor.storage.mention.enabled = taggingOn - editor.storage.mention.navigable = navigable - editor.storage.mention.onOpen = taggingOn ? () => setActive(true) : null + editor.storage.mentionMenu.enabled = taggingOn + editor.storage.mentionMenu.navigable = navigable + editor.storage.mentionMenu.onOpen = taggingOn ? () => setActive(true) : null return () => { - editor.storage.mention.onOpen = null + editor.storage.mentionMenu.onOpen = null } }, [editor, workspaceId, navigable, disableTagging]) useEffect(() => { - editor?.storage.mention.store.set(items) + editor?.storage.mentionMenu.store.set(items) }, [editor, items]) } diff --git a/apps/sim/hooks/queries/workspace-files.ts b/apps/sim/hooks/queries/workspace-files.ts index 65469cbe202..8a5b503cd8c 100644 --- a/apps/sim/hooks/queries/workspace-files.ts +++ b/apps/sim/hooks/queries/workspace-files.ts @@ -142,20 +142,28 @@ export function useWorkspaceFiles( } /** - * Back the file content source's image-dimension capability with workspace file metadata. Reads intrinsic - * dimensions synchronously from the already-loaded active file list (so a stored image reserves its box on - * the first render), and persists the browser's measured dimensions when they're absent or disagree with - * what's stored — an overwrite, so a stale value (left over after a content swap, or a non-EXIF-corrected - * one) self-corrects rather than sticking. The write is fire-and-forget and de-duped (an exact-match cache - * check plus mismatch-only reporting from the caller), so it never storms, never blocks render, and never - * touches the collaborative document. + * Back the file content source's image-dimension capability with workspace file metadata. Subscribes to + * the active file list ({@link useWorkspaceFiles}) and reads each image's stored intrinsic dimensions from + * it, so a stored image reserves its box before it downloads. A reactive read (not a one-shot + * `getQueryData`), so it also works on a cold direct file-view load where the list isn't cached until after + * the image first renders: the subscription re-runs the node view's dimension read once the list resolves. + * Persists the browser's measured dimensions when they're absent or disagree with what's stored — an + * overwrite, so a stale value (left over after a content swap, or a non-EXIF-corrected one) self-corrects + * rather than sticking. The write is fire-and-forget and de-duped (an exact-match cache check plus + * mismatch-only reporting from the caller), so it never storms, never blocks render, and never touches the + * collaborative document. `options.enabled` turns the subscription off for callers that supply their own + * content source (the public share page, whose `workspaceId` is a share token that would 404). */ -export function useWorkspaceImageDimensionsAdapter(workspaceId: string): ImageDimensionsSource { +export function useWorkspaceImageDimensionsAdapter( + workspaceId: string, + options?: { enabled?: boolean } +): ImageDimensionsSource { const queryClient = useQueryClient() + const { data: files } = useWorkspaceFiles(workspaceId, 'active', options) return useMemo(() => { const listKey = workspaceFilesKeys.list(workspaceId, 'active') const findRecord = (src: string | undefined): WorkspaceFileRecord | undefined => - findWorkspaceFileBySrc(queryClient.getQueryData(listKey), src) + findWorkspaceFileBySrc(files, src) return { getImageDimensions: (src) => { const record = findRecord(src) @@ -194,7 +202,7 @@ export function useWorkspaceImageDimensionsAdapter(workspaceId: string): ImageDi .catch(() => {}) }, } - }, [queryClient, workspaceId]) + }, [files, queryClient, workspaceId]) } /**