diff --git a/apps/client/src/features/editor/components/attachment/attachment-view.tsx b/apps/client/src/features/editor/components/attachment/attachment-view.tsx index f6c13c80..b72bb00a 100644 --- a/apps/client/src/features/editor/components/attachment/attachment-view.tsx +++ b/apps/client/src/features/editor/components/attachment/attachment-view.tsx @@ -10,7 +10,7 @@ import { useCallback } from "react"; export default function AttachmentView(props: NodeViewProps) { const { t } = useTranslation(); const { editor, node, getPos, selected } = props; - const { url, name, size, mime, attachmentId } = node.attrs; + const { url, name, size, mime, attachmentId, placeholder } = node.attrs; const { hovered, ref } = useHover(); const isPdf = mime === "application/pdf" || name?.toLowerCase().endsWith(".pdf"); @@ -49,14 +49,14 @@ export default function AttachmentView(props: NodeViewProps) { h={25} > - {url ? ( - - ) : ( + {!url && placeholder ? ( + ) : ( + )} - {url ? name : t("Uploading {{name}}", { name })} + {!url && placeholder ? t("Uploading {{name}}", { name }) : name} diff --git a/apps/client/src/features/editor/components/audio/audio-view.tsx b/apps/client/src/features/editor/components/audio/audio-view.tsx index a353ce45..9e5f619f 100644 --- a/apps/client/src/features/editor/components/audio/audio-view.tsx +++ b/apps/client/src/features/editor/components/audio/audio-view.tsx @@ -29,7 +29,7 @@ export default function AudioView(props: NodeViewProps) { return ( -
+
{safeSrc && (
); diff --git a/apps/client/src/features/editor/components/image/image-view.tsx b/apps/client/src/features/editor/components/image/image-view.tsx index 7ec3e26f..1f874694 100644 --- a/apps/client/src/features/editor/components/image/image-view.tsx +++ b/apps/client/src/features/editor/components/image/image-view.tsx @@ -33,7 +33,7 @@ export default function ImageView(props: NodeViewProps) { className={clsx( selected && "ProseMirror-selectednode", classes.imageWrapper, - !src && classes.skeleton, + !src && placeholder && classes.skeleton, alignClass, )} style={{ @@ -55,7 +55,7 @@ export default function ImageView(props: NodeViewProps) { )} - {!src && !previewSrc && ( + {!src && !previewSrc && placeholder && ( diff --git a/apps/client/src/features/editor/components/pdf/pdf-view.tsx b/apps/client/src/features/editor/components/pdf/pdf-view.tsx index 6207da9f..4d06402b 100644 --- a/apps/client/src/features/editor/components/pdf/pdf-view.tsx +++ b/apps/client/src/features/editor/components/pdf/pdf-view.tsx @@ -73,15 +73,17 @@ export default function PdfView(props: NodeViewProps) { if (!src || !safeSrc) { return ( -
- - - - {placeholder?.name - ? t("Uploading {{name}}", { name: placeholder.name }) - : t("Uploading file")} - - +
+ {placeholder && ( + + + + {placeholder?.name + ? t("Uploading {{name}}", { name: placeholder.name }) + : t("Uploading file")} + + + )}
); diff --git a/apps/client/src/features/editor/components/video/video-view.tsx b/apps/client/src/features/editor/components/video/video-view.tsx index 1e662640..46ff7908 100644 --- a/apps/client/src/features/editor/components/video/video-view.tsx +++ b/apps/client/src/features/editor/components/video/video-view.tsx @@ -33,7 +33,7 @@ export default function VideoView(props: NodeViewProps) { className={clsx( selected && "ProseMirror-selectednode", classes.videoWrapper, - !src && classes.skeleton, + !src && placeholder && classes.skeleton, alignClass, )} style={{ @@ -60,7 +60,7 @@ export default function VideoView(props: NodeViewProps) { )} - {!src && !previewSrc && ( + {!src && !previewSrc && placeholder && ( @@ -70,6 +70,9 @@ export default function VideoView(props: NodeViewProps) { )} + {!src && !previewSrc && !placeholder && ( +
); diff --git a/apps/client/src/features/editor/extensions/extensions.ts b/apps/client/src/features/editor/extensions/extensions.ts index c5ca4cd1..8f6e6cdb 100644 --- a/apps/client/src/features/editor/extensions/extensions.ts +++ b/apps/client/src/features/editor/extensions/extensions.ts @@ -253,8 +253,8 @@ export const mainExtensions = [ resize: { enabled: true, directions: ["left", "right"], - minWidth: 80, - minHeight: 40, + minWidth: 24, + minHeight: 16, alwaysPreserveAspectRatio: true, //@ts-ignore createCustomHandle: createImageHandle, @@ -266,8 +266,8 @@ export const mainExtensions = [ resize: { enabled: true, directions: ["left", "right"], - minWidth: 80, - minHeight: 40, + minWidth: 24, + minHeight: 16, alwaysPreserveAspectRatio: true, //@ts-ignore createCustomHandle: createResizeHandle, @@ -297,8 +297,8 @@ export const mainExtensions = [ resize: { enabled: true, directions: ["left", "right"], - minWidth: 80, - minHeight: 40, + minWidth: 24, + minHeight: 16, alwaysPreserveAspectRatio: true, //@ts-ignore createCustomHandle: createResizeHandle, @@ -310,8 +310,8 @@ export const mainExtensions = [ resize: { enabled: true, directions: ["left", "right"], - minWidth: 80, - minHeight: 40, + minWidth: 24, + minHeight: 16, alwaysPreserveAspectRatio: true, //@ts-ignore createCustomHandle: createResizeHandle, diff --git a/apps/server/src/ee b/apps/server/src/ee index f4867260..05f1c816 160000 --- a/apps/server/src/ee +++ b/apps/server/src/ee @@ -1 +1 @@ -Subproject commit f48672608889233c0247c6d4ef7fcddd29540315 +Subproject commit 05f1c816a839072efc1143cce71322a9ed6b4a0a diff --git a/apps/server/src/integrations/import/services/import-attachment.service.ts b/apps/server/src/integrations/import/services/import-attachment.service.ts index 3c14d854..9100149b 100644 --- a/apps/server/src/integrations/import/services/import-attachment.service.ts +++ b/apps/server/src/integrations/import/services/import-attachment.service.ts @@ -193,6 +193,8 @@ export class ImportAttachmentService { // Build a map from resolved archive path → real filename from Confluence // metadata. Confluence Server archives often store files under numeric IDs // (e.g. "attachments/65601/65602") instead of the original filename. + // Also register aliases so HTML references using the original filename + // (e.g. "attachments/pageId/original.mp3") resolve to the numeric path. const pageDir = path.dirname(pageRelativePath); const attachmentNameByRelPath = new Map(); for (const attachment of pageAttachments) { @@ -203,6 +205,13 @@ export class ImportAttachmentService { ); if (relPath && attachment.fileName) { attachmentNameByRelPath.set(relPath, attachment.fileName); + + const dir = path.posix.dirname(relPath); + const aliasKey = `${dir}/${attachment.fileName}`; + if (!attachmentCandidates.has(aliasKey)) { + attachmentCandidates.set(aliasKey, attachmentCandidates.get(relPath)!); + attachmentNameByRelPath.set(aliasKey, attachment.fileName); + } } } @@ -562,18 +571,31 @@ export class ImportAttachmentService { continue; } - // Check if already processed (was referenced in HTML) - if (processed.has(href)) { - continue; - } + // Resolve the metadata href to the actual archive path + const resolvedHref = resolveRelativeAttachmentPath( + href, + pageDir, + attachmentCandidates, + ); + if (!resolvedHref) continue; - // Skip if the file doesn't exist - if (!attachmentCandidates.has(href)) { + // Check if already processed (was referenced in HTML). + // Inline elements may have been processed under an alias key (original + // filename) rather than the numeric archive path, so also check whether + // the underlying absolute file path has already been uploaded. + const absPath = attachmentCandidates.get(resolvedHref); + const alreadyProcessed = + processed.has(resolvedHref) || + (absPath && + Array.from(processed.values()).some( + (entry) => entry.abs === absPath, + )); + if (alreadyProcessed) { continue; } // This attachment was in the list but not referenced in HTML - add it - const { attachmentId, apiFilePath, abs } = processFile(href); + const { attachmentId, apiFilePath, abs } = processFile(resolvedHref); const mime = mimeType || getMimeType(abs); // Add as attachment node at the end