-
+
+ {src && (
+
+ )}
+ {!src && previewSrc && (
+
+
+
+
+ )}
+ {!src && !previewSrc && (
+
+
+
+ {placeholder?.name
+ ? t("Uploading {{name}}", { name: placeholder.name })
+ : t("Uploading file")}
+
+
+ )}
+
);
}
diff --git a/apps/client/src/features/editor/components/slash-menu/menu-items.ts b/apps/client/src/features/editor/components/slash-menu/menu-items.ts
index 362c1686..bebefed4 100644
--- a/apps/client/src/features/editor/components/slash-menu/menu-items.ts
+++ b/apps/client/src/features/editor/components/slash-menu/menu-items.ts
@@ -174,9 +174,13 @@ const CommandGroups: SlashMenuGroupedItemsType = {
if (input.files?.length) {
for (const file of input.files) {
const pos = editor.view.state.selection.from;
- uploadImageAction(file, editor.view, pos, pageId);
+
+ uploadImageAction(file, editor, pos, pageId);
}
}
+
+ // Reset the input value to allow uploading the same file again if needed
+ input.value = "";
};
input.click();
},
@@ -197,12 +201,18 @@ const CommandGroups: SlashMenuGroupedItemsType = {
const input = document.createElement("input");
input.type = "file";
input.accept = "video/*";
+ input.multiple = true;
input.onchange = async () => {
if (input.files?.length) {
- const file = input.files[0];
- const pos = editor.view.state.selection.from;
- uploadVideoAction(file, editor.view, pos, pageId);
+ for (const file of input.files) {
+ const pos = editor.view.state.selection.from;
+
+ uploadVideoAction(file, editor, pos, pageId);
+ }
}
+
+ // Reset the input value to allow uploading the same file again if needed
+ input.value = "";
};
input.click();
},
@@ -223,12 +233,18 @@ const CommandGroups: SlashMenuGroupedItemsType = {
const input = document.createElement("input");
input.type = "file";
input.accept = "";
+ input.multiple = true;
input.onchange = async () => {
if (input.files?.length) {
- const file = input.files[0];
- const pos = editor.view.state.selection.from;
- uploadAttachmentAction(file, editor.view, pos, pageId, true);
+ for (const file of input.files) {
+ const pos = editor.view.state.selection.from;
+
+ uploadAttachmentAction(file, editor, pos, pageId, true);
+ }
}
+
+ // Reset the input value to allow uploading the same file again if needed
+ input.value = "";
};
input.click();
},
diff --git a/apps/client/src/features/editor/components/video/video-menu.tsx b/apps/client/src/features/editor/components/video/video-menu.tsx
index 57a012a8..dfece398 100644
--- a/apps/client/src/features/editor/components/video/video-menu.tsx
+++ b/apps/client/src/features/editor/components/video/video-menu.tsx
@@ -20,7 +20,7 @@ export function VideoMenu({ editor }: EditorMenuProps) {
const editorState = useEditorState({
editor,
- selector: ctx => {
+ selector: (ctx) => {
if (!ctx.editor) {
return null;
}
@@ -43,7 +43,7 @@ export function VideoMenu({ editor }: EditorMenuProps) {
return false;
}
- return editor.isActive("video");
+ return editor.isActive("video") && editor.getAttributes("video").src;
},
[editor],
);
diff --git a/apps/client/src/features/editor/components/video/video-view.module.css b/apps/client/src/features/editor/components/video/video-view.module.css
new file mode 100644
index 00000000..c0e7f99d
--- /dev/null
+++ b/apps/client/src/features/editor/components/video/video-view.module.css
@@ -0,0 +1,33 @@
+.videoWrapper {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border-radius: 8px;
+ overflow: hidden;
+ animation: pulse 1.2s ease-in-out infinite;
+
+ @mixin light {
+ background: linear-gradient(-90deg, var(--mantine-color-gray-3) 0%, var(--mantine-color-gray-1) 50%, var(--mantine-color-gray-3) 100%);
+ background-size: 400% 400%;
+ }
+
+ @mixin dark {
+ background: linear-gradient(-90deg, var(--mantine-color-dark-6) 0%, var(--mantine-color-dark-5) 50%, var(--mantine-color-dark-6) 100%);
+ background-size: 400% 400%;
+ }
+
+ @keyframes pulse {
+ 0% {
+ background-position: 0% 0%;
+ }
+ 100% {
+ background-position: -135% 0%;
+ }
+ }
+}
+.video {
+ display: block;
+ width: 100%;
+ height: 100%;
+ border-radius: 8px;
+}
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 d47d9a4a..e2473afc 100644
--- a/apps/client/src/features/editor/components/video/video-view.tsx
+++ b/apps/client/src/features/editor/components/video/video-view.tsx
@@ -1,29 +1,75 @@
import { NodeViewProps, NodeViewWrapper } from "@tiptap/react";
+import { Group, Loader, Text } from "@mantine/core";
import { useMemo } from "react";
import { getFileUrl } from "@/lib/config.ts";
import clsx from "clsx";
+import classes from "./video-view.module.css";
+import { useTranslation } from "react-i18next";
export default function VideoView(props: NodeViewProps) {
- const { node, selected } = props;
- const { src, width, align } = node.attrs;
-
+ const { t } = useTranslation();
+ const { editor, node, selected } = props;
+ const { src, width, align, aspectRatio, placeholder } = node.attrs;
const alignClass = useMemo(() => {
if (align === "left") return "alignLeft";
if (align === "right") return "alignRight";
if (align === "center") return "alignCenter";
return "alignCenter";
}, [align]);
+ const previewSrc = useMemo(() => {
+ editor.storage.shared.videoPreviews =
+ editor.storage.shared.videoPreviews || {};
+
+ if (placeholder?.id) {
+ return editor.storage.shared.videoPreviews[placeholder.id];
+ }
+
+ return null;
+ }, [placeholder, editor]);
return (