mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 14:43:06 +08:00
Compare commits
4 Commits
fix-831
...
bug-fix-23
| Author | SHA1 | Date | |
|---|---|---|---|
| 949972cf41 | |||
| 3e5a1f73ea | |||
| 990273fb02 | |||
| 02334f4106 |
@@ -10,7 +10,7 @@ import { useCallback } from "react";
|
|||||||
export default function AttachmentView(props: NodeViewProps) {
|
export default function AttachmentView(props: NodeViewProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { editor, node, getPos, selected } = props;
|
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 { hovered, ref } = useHover();
|
||||||
|
|
||||||
const isPdf = mime === "application/pdf" || name?.toLowerCase().endsWith(".pdf");
|
const isPdf = mime === "application/pdf" || name?.toLowerCase().endsWith(".pdf");
|
||||||
@@ -49,14 +49,14 @@ export default function AttachmentView(props: NodeViewProps) {
|
|||||||
h={25}
|
h={25}
|
||||||
>
|
>
|
||||||
<Group wrap="nowrap" gap="sm" style={{ minWidth: 0, flex: 1 }}>
|
<Group wrap="nowrap" gap="sm" style={{ minWidth: 0, flex: 1 }}>
|
||||||
{url ? (
|
{!url && placeholder ? (
|
||||||
<IconPaperclip size={20} style={{ flexShrink: 0 }} />
|
|
||||||
) : (
|
|
||||||
<Loader size={20} style={{ flexShrink: 0 }} />
|
<Loader size={20} style={{ flexShrink: 0 }} />
|
||||||
|
) : (
|
||||||
|
<IconPaperclip size={20} style={{ flexShrink: 0 }} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Text component="span" size="md" truncate="end" style={{ minWidth: 0 }}>
|
<Text component="span" size="md" truncate="end" style={{ minWidth: 0 }}>
|
||||||
{url ? name : t("Uploading {{name}}", { name })}
|
{!url && placeholder ? t("Uploading {{name}}", { name }) : name}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text component="span" size="sm" c="dimmed" style={{ flexShrink: 0 }}>
|
<Text component="span" size="sm" c="dimmed" style={{ flexShrink: 0 }}>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default function AudioView(props: NodeViewProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<NodeViewWrapper data-drag-handle>
|
<NodeViewWrapper data-drag-handle>
|
||||||
<div className={`${classes.audioWrapper} ${!safeSrc ? classes.skeleton : ''}`}>
|
<div className={`${classes.audioWrapper} ${!safeSrc && placeholder ? classes.skeleton : ''}`}>
|
||||||
{safeSrc && (
|
{safeSrc && (
|
||||||
<audio
|
<audio
|
||||||
className={classes.audio}
|
className={classes.audio}
|
||||||
@@ -49,7 +49,7 @@ export default function AudioView(props: NodeViewProps) {
|
|||||||
<Loader size={20} pos="absolute" top={6} right={6} />
|
<Loader size={20} pos="absolute" top={6} right={6} />
|
||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
{!safeSrc && !previewSrc && (
|
{!safeSrc && !previewSrc && placeholder && (
|
||||||
<Group justify="center" wrap="nowrap" gap="xs" maw="100%" px="md" h={54}>
|
<Group justify="center" wrap="nowrap" gap="xs" maw="100%" px="md" h={54}>
|
||||||
<Loader size={20} style={{ flexShrink: 0 }} />
|
<Loader size={20} style={{ flexShrink: 0 }} />
|
||||||
<Text component="span" size="sm" truncate="end">
|
<Text component="span" size="sm" truncate="end">
|
||||||
@@ -59,6 +59,9 @@ export default function AudioView(props: NodeViewProps) {
|
|||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
|
{!safeSrc && !previewSrc && !placeholder && (
|
||||||
|
<audio className={classes.audio} controls />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</NodeViewWrapper>
|
</NodeViewWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export default function ImageView(props: NodeViewProps) {
|
|||||||
className={clsx(
|
className={clsx(
|
||||||
selected && "ProseMirror-selectednode",
|
selected && "ProseMirror-selectednode",
|
||||||
classes.imageWrapper,
|
classes.imageWrapper,
|
||||||
!src && classes.skeleton,
|
!src && placeholder && classes.skeleton,
|
||||||
alignClass,
|
alignClass,
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
@@ -55,7 +55,7 @@ export default function ImageView(props: NodeViewProps) {
|
|||||||
<Loader size={20} pos="absolute" bottom={6} right={6} />
|
<Loader size={20} pos="absolute" bottom={6} right={6} />
|
||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
{!src && !previewSrc && (
|
{!src && !previewSrc && placeholder && (
|
||||||
<Group justify="center" wrap="nowrap" gap="xs" maw="100%" px="md">
|
<Group justify="center" wrap="nowrap" gap="xs" maw="100%" px="md">
|
||||||
<Loader size={20} style={{ flexShrink: 0 }} />
|
<Loader size={20} style={{ flexShrink: 0 }} />
|
||||||
<Text component="span" size="sm" truncate="end">
|
<Text component="span" size="sm" truncate="end">
|
||||||
|
|||||||
@@ -73,15 +73,17 @@ export default function PdfView(props: NodeViewProps) {
|
|||||||
if (!src || !safeSrc) {
|
if (!src || !safeSrc) {
|
||||||
return (
|
return (
|
||||||
<NodeViewWrapper data-drag-handle>
|
<NodeViewWrapper data-drag-handle>
|
||||||
<div className={`${classes.pdfWrapper} ${classes.skeleton}`} style={{ height: 600 }}>
|
<div className={`${classes.pdfWrapper} ${placeholder ? classes.skeleton : ''}`} style={{ height: placeholder ? 600 : undefined }}>
|
||||||
<Group justify="center" wrap="nowrap" gap="xs" maw="100%" px="md">
|
{placeholder && (
|
||||||
<Loader size={20} style={{ flexShrink: 0 }} />
|
<Group justify="center" wrap="nowrap" gap="xs" maw="100%" px="md">
|
||||||
<Text component="span" size="sm" truncate="end">
|
<Loader size={20} style={{ flexShrink: 0 }} />
|
||||||
{placeholder?.name
|
<Text component="span" size="sm" truncate="end">
|
||||||
? t("Uploading {{name}}", { name: placeholder.name })
|
{placeholder?.name
|
||||||
: t("Uploading file")}
|
? t("Uploading {{name}}", { name: placeholder.name })
|
||||||
</Text>
|
: t("Uploading file")}
|
||||||
</Group>
|
</Text>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</NodeViewWrapper>
|
</NodeViewWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export default function VideoView(props: NodeViewProps) {
|
|||||||
className={clsx(
|
className={clsx(
|
||||||
selected && "ProseMirror-selectednode",
|
selected && "ProseMirror-selectednode",
|
||||||
classes.videoWrapper,
|
classes.videoWrapper,
|
||||||
!src && classes.skeleton,
|
!src && placeholder && classes.skeleton,
|
||||||
alignClass,
|
alignClass,
|
||||||
)}
|
)}
|
||||||
style={{
|
style={{
|
||||||
@@ -60,7 +60,7 @@ export default function VideoView(props: NodeViewProps) {
|
|||||||
<Loader size={20} pos="absolute" top={6} right={6} />
|
<Loader size={20} pos="absolute" top={6} right={6} />
|
||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
{!src && !previewSrc && (
|
{!src && !previewSrc && placeholder && (
|
||||||
<Group justify="center" wrap="nowrap" gap="xs" maw="100%" px="md">
|
<Group justify="center" wrap="nowrap" gap="xs" maw="100%" px="md">
|
||||||
<Loader size={20} style={{ flexShrink: 0 }} />
|
<Loader size={20} style={{ flexShrink: 0 }} />
|
||||||
<Text component="span" size="sm" truncate="end">
|
<Text component="span" size="sm" truncate="end">
|
||||||
@@ -70,6 +70,9 @@ export default function VideoView(props: NodeViewProps) {
|
|||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
)}
|
)}
|
||||||
|
{!src && !previewSrc && !placeholder && (
|
||||||
|
<video className={classes.video} controls />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</NodeViewWrapper>
|
</NodeViewWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -253,8 +253,8 @@ export const mainExtensions = [
|
|||||||
resize: {
|
resize: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
directions: ["left", "right"],
|
directions: ["left", "right"],
|
||||||
minWidth: 80,
|
minWidth: 24,
|
||||||
minHeight: 40,
|
minHeight: 16,
|
||||||
alwaysPreserveAspectRatio: true,
|
alwaysPreserveAspectRatio: true,
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
createCustomHandle: createImageHandle,
|
createCustomHandle: createImageHandle,
|
||||||
@@ -266,8 +266,8 @@ export const mainExtensions = [
|
|||||||
resize: {
|
resize: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
directions: ["left", "right"],
|
directions: ["left", "right"],
|
||||||
minWidth: 80,
|
minWidth: 24,
|
||||||
minHeight: 40,
|
minHeight: 16,
|
||||||
alwaysPreserveAspectRatio: true,
|
alwaysPreserveAspectRatio: true,
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
createCustomHandle: createResizeHandle,
|
createCustomHandle: createResizeHandle,
|
||||||
@@ -297,8 +297,8 @@ export const mainExtensions = [
|
|||||||
resize: {
|
resize: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
directions: ["left", "right"],
|
directions: ["left", "right"],
|
||||||
minWidth: 80,
|
minWidth: 24,
|
||||||
minHeight: 40,
|
minHeight: 16,
|
||||||
alwaysPreserveAspectRatio: true,
|
alwaysPreserveAspectRatio: true,
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
createCustomHandle: createResizeHandle,
|
createCustomHandle: createResizeHandle,
|
||||||
@@ -310,8 +310,8 @@ export const mainExtensions = [
|
|||||||
resize: {
|
resize: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
directions: ["left", "right"],
|
directions: ["left", "right"],
|
||||||
minWidth: 80,
|
minWidth: 24,
|
||||||
minHeight: 40,
|
minHeight: 16,
|
||||||
alwaysPreserveAspectRatio: true,
|
alwaysPreserveAspectRatio: true,
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
createCustomHandle: createResizeHandle,
|
createCustomHandle: createResizeHandle,
|
||||||
|
|||||||
+1
-1
Submodule apps/server/src/ee updated: f486726088...05f1c816a8
@@ -193,6 +193,8 @@ export class ImportAttachmentService {
|
|||||||
// Build a map from resolved archive path → real filename from Confluence
|
// Build a map from resolved archive path → real filename from Confluence
|
||||||
// metadata. Confluence Server archives often store files under numeric IDs
|
// metadata. Confluence Server archives often store files under numeric IDs
|
||||||
// (e.g. "attachments/65601/65602") instead of the original filename.
|
// (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 pageDir = path.dirname(pageRelativePath);
|
||||||
const attachmentNameByRelPath = new Map<string, string>();
|
const attachmentNameByRelPath = new Map<string, string>();
|
||||||
for (const attachment of pageAttachments) {
|
for (const attachment of pageAttachments) {
|
||||||
@@ -203,6 +205,13 @@ export class ImportAttachmentService {
|
|||||||
);
|
);
|
||||||
if (relPath && attachment.fileName) {
|
if (relPath && attachment.fileName) {
|
||||||
attachmentNameByRelPath.set(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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if already processed (was referenced in HTML)
|
// Resolve the metadata href to the actual archive path
|
||||||
if (processed.has(href)) {
|
const resolvedHref = resolveRelativeAttachmentPath(
|
||||||
continue;
|
href,
|
||||||
}
|
pageDir,
|
||||||
|
attachmentCandidates,
|
||||||
|
);
|
||||||
|
if (!resolvedHref) continue;
|
||||||
|
|
||||||
// Skip if the file doesn't exist
|
// Check if already processed (was referenced in HTML).
|
||||||
if (!attachmentCandidates.has(href)) {
|
// 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This attachment was in the list but not referenced in HTML - add it
|
// 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);
|
const mime = mimeType || getMimeType(abs);
|
||||||
|
|
||||||
// Add as attachment node at the end
|
// Add as attachment node at the end
|
||||||
|
|||||||
Reference in New Issue
Block a user