mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-23 23:19:40 +01:00
editor: add support for attachment previews (#2123)
This adds support for basic attachment preview support. Currently only image previews are supported on mobile.
Fixed image downloading not working on mobile
Added image preview support on mobile with support for loading full quality images in full screen.
Added Attachment preview logic in editor, so in future we can support preview for other files such as audio & video
This commit is contained in:
@@ -29,20 +29,30 @@ import { db } from "../database";
|
|||||||
import Storage from "../database/storage";
|
import Storage from "../database/storage";
|
||||||
import { cacheDir } from "./utils";
|
import { cacheDir } from "./utils";
|
||||||
|
|
||||||
export default async function downloadAttachment(hash, global = true) {
|
export default async function downloadAttachment(
|
||||||
|
hash,
|
||||||
|
global = true,
|
||||||
|
options = {
|
||||||
|
silent: false,
|
||||||
|
cache: false
|
||||||
|
}
|
||||||
|
) {
|
||||||
let attachment = db.attachments.attachment(hash);
|
let attachment = db.attachments.attachment(hash);
|
||||||
|
console.log(attachment);
|
||||||
if (!attachment) {
|
if (!attachment) {
|
||||||
console.log("attachment not found");
|
console.log("attachment not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let folder = {};
|
let folder = {};
|
||||||
|
if (!options.cache) {
|
||||||
if (Platform.OS === "android") {
|
if (Platform.OS === "android") {
|
||||||
folder = await ScopedStorage.openDocumentTree();
|
folder = await ScopedStorage.openDocumentTree();
|
||||||
if (!folder) return;
|
if (!folder) return;
|
||||||
} else {
|
} else {
|
||||||
folder.uri = await Storage.checkAndCreateDir("/downloads/");
|
folder.uri = await Storage.checkAndCreateDir("/downloads/");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.fs.downloadFile(
|
await db.fs.downloadFile(
|
||||||
@@ -63,19 +73,29 @@ export default async function downloadAttachment(hash, global = true) {
|
|||||||
hash: attachment.metadata.hash,
|
hash: attachment.metadata.hash,
|
||||||
hashType: attachment.metadata.hashType,
|
hashType: attachment.metadata.hashType,
|
||||||
mime: attachment.metadata.type,
|
mime: attachment.metadata.type,
|
||||||
fileName: attachment.metadata.filename,
|
fileName: options.cache ? undefined : attachment.metadata.filename,
|
||||||
uri: folder.uri,
|
uri: options.cache ? undefined : folder.uri,
|
||||||
chunkSize: attachment.chunkSize
|
chunkSize: attachment.chunkSize
|
||||||
};
|
};
|
||||||
|
|
||||||
let fileUri = await Sodium.decryptFile(key, info, "file");
|
let fileUri = await Sodium.decryptFile(
|
||||||
|
key,
|
||||||
|
info,
|
||||||
|
options.cache ? "cache" : "file"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!options.silent) {
|
||||||
ToastEvent.show({
|
ToastEvent.show({
|
||||||
heading: "Download successful",
|
heading: "Download successful",
|
||||||
message: attachment.metadata.filename + " downloaded",
|
message: attachment.metadata.filename + " downloaded",
|
||||||
type: "success"
|
type: "success"
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (attachment.dateUploaded) {
|
if (
|
||||||
|
attachment.dateUploaded &&
|
||||||
|
!attachment.metadata?.type?.startsWith("image")
|
||||||
|
) {
|
||||||
RNFetchBlob.fs
|
RNFetchBlob.fs
|
||||||
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
|
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
|
||||||
.catch(console.log);
|
.catch(console.log);
|
||||||
@@ -85,7 +105,7 @@ export default async function downloadAttachment(hash, global = true) {
|
|||||||
fileUri = folder.uri + `/${attachment.metadata.filename}`;
|
fileUri = folder.uri + `/${attachment.metadata.filename}`;
|
||||||
}
|
}
|
||||||
console.log("saved file uri: ", fileUri);
|
console.log("saved file uri: ", fileUri);
|
||||||
|
if (!options.silent) {
|
||||||
presentSheet({
|
presentSheet({
|
||||||
title: "File downloaded",
|
title: "File downloaded",
|
||||||
paragraph: `${attachment.metadata.filename} saved to ${
|
paragraph: `${attachment.metadata.filename} saved to ${
|
||||||
@@ -103,6 +123,8 @@ export default async function downloadAttachment(hash, global = true) {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return fileUri;
|
return fileUri;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log("download attachment error: ", e);
|
console.log("download attachment error: ", e);
|
||||||
|
|||||||
@@ -20,16 +20,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { View } from "react-native";
|
import { View } from "react-native";
|
||||||
import ImageViewer from "react-native-image-zoom-viewer";
|
import ImageViewer from "react-native-image-zoom-viewer";
|
||||||
|
import downloadAttachment from "../../common/filesystem/download-attachment";
|
||||||
|
import { cacheDir } from "../../common/filesystem/utils";
|
||||||
import {
|
import {
|
||||||
eSubscribeEvent,
|
eSubscribeEvent,
|
||||||
eUnSubscribeEvent
|
eUnSubscribeEvent
|
||||||
} from "../../services/event-manager";
|
} from "../../services/event-manager";
|
||||||
|
import { useThemeStore } from "../../stores/use-theme-store";
|
||||||
import BaseDialog from "../dialog/base-dialog";
|
import BaseDialog from "../dialog/base-dialog";
|
||||||
import { IconButton } from "../ui/icon-button";
|
import { IconButton } from "../ui/icon-button";
|
||||||
|
import { ProgressBarComponent } from "../ui/svg/lazy";
|
||||||
|
|
||||||
const ImagePreview = () => {
|
const ImagePreview = () => {
|
||||||
|
const colors = useThemeStore((state) => state.colors);
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [image, setImage] = useState("");
|
const [image, setImage] = useState("");
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
eSubscribeEvent("ImagePreview", open);
|
eSubscribeEvent("ImagePreview", open);
|
||||||
@@ -39,9 +45,20 @@ const ImagePreview = () => {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const open = (image) => {
|
const open = async (image) => {
|
||||||
setImage(image);
|
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
|
setLoading(true);
|
||||||
|
setTimeout(async () => {
|
||||||
|
const hash = image.hash;
|
||||||
|
const uri = await downloadAttachment(hash, false, {
|
||||||
|
silent: true,
|
||||||
|
cache: true
|
||||||
|
});
|
||||||
|
const path = `${cacheDir}/${uri}`;
|
||||||
|
console.log(path);
|
||||||
|
setImage("file://" + path);
|
||||||
|
setLoading(false);
|
||||||
|
}, 100);
|
||||||
};
|
};
|
||||||
|
|
||||||
const close = () => {
|
const close = () => {
|
||||||
@@ -59,6 +76,21 @@ const ImagePreview = () => {
|
|||||||
backgroundColor: "black"
|
backgroundColor: "black"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{loading ? (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ProgressBarComponent
|
||||||
|
indeterminate
|
||||||
|
color={colors.accent}
|
||||||
|
borderColor="transparent"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
<ImageViewer
|
<ImageViewer
|
||||||
enableImageZoom={true}
|
enableImageZoom={true}
|
||||||
renderIndicator={() => <></>}
|
renderIndicator={() => <></>}
|
||||||
@@ -97,6 +129,7 @@ const ImagePreview = () => {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</View>
|
</View>
|
||||||
</BaseDialog>
|
</BaseDialog>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,5 +28,5 @@ const EditorMobileSourceUrl =
|
|||||||
* The url should be something like this: http://192.168.100.126:3000/index.html
|
* The url should be something like this: http://192.168.100.126:3000/index.html
|
||||||
*/
|
*/
|
||||||
export const EDITOR_URI = __DEV__
|
export const EDITOR_URI = __DEV__
|
||||||
? EditorMobileSourceUrl
|
? "http://192.168.8.103:3000/index.html"
|
||||||
: EditorMobileSourceUrl;
|
: EditorMobileSourceUrl;
|
||||||
|
|||||||
@@ -34,5 +34,6 @@ export const EventTypes = {
|
|||||||
fullscreen: "editor-event:fullscreen",
|
fullscreen: "editor-event:fullscreen",
|
||||||
link: "editor-event:link",
|
link: "editor-event:link",
|
||||||
contentchange: "editor-event:content-change",
|
contentchange: "editor-event:content-change",
|
||||||
reminders: "editor-event:reminders"
|
reminders: "editor-event:reminders",
|
||||||
|
previewAttachment: "editor-event:preview-attachment"
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import {
|
|||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { WebViewMessageEvent } from "react-native-webview";
|
import { WebViewMessageEvent } from "react-native-webview";
|
||||||
import { db } from "../../../common/database";
|
import { db } from "../../../common/database";
|
||||||
|
import ImagePreview from "../../../components/image-preview";
|
||||||
import { RelationsList } from "../../../components/sheets/relations-list";
|
import { RelationsList } from "../../../components/sheets/relations-list";
|
||||||
import ReminderSheet from "../../../components/sheets/reminder";
|
import ReminderSheet from "../../../components/sheets/reminder";
|
||||||
import useKeyboard from "../../../hooks/use-keyboard";
|
import useKeyboard from "../../../hooks/use-keyboard";
|
||||||
@@ -380,6 +381,10 @@ export const useEditorEvents = (
|
|||||||
case EventTypes.link:
|
case EventTypes.link:
|
||||||
openLinkInBrowser(editorMessage.value as string);
|
openLinkInBrowser(editorMessage.value as string);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case EventTypes.previewAttachment:
|
||||||
|
eSendEvent("ImagePreview", editorMessage.value);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,6 +92,10 @@ const Tiptap = ({
|
|||||||
global.editorController.downloadAttachment(attachment);
|
global.editorController.downloadAttachment(attachment);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
onPreviewAttachment(editor, attachment) {
|
||||||
|
global.editorController.previewAttachment(attachment);
|
||||||
|
return true;
|
||||||
|
},
|
||||||
theme: editorTheme,
|
theme: editorTheme,
|
||||||
element: !layout ? undefined : contentRef.current || undefined,
|
element: !layout ? undefined : contentRef.current || undefined,
|
||||||
editable: !settings.readonly,
|
editable: !settings.readonly,
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export type EditorController = {
|
|||||||
setTitle: React.Dispatch<React.SetStateAction<string>>;
|
setTitle: React.Dispatch<React.SetStateAction<string>>;
|
||||||
openFilePicker: (type: "image" | "file" | "camera") => void;
|
openFilePicker: (type: "image" | "file" | "camera") => void;
|
||||||
downloadAttachment: (attachment: Attachment) => void;
|
downloadAttachment: (attachment: Attachment) => void;
|
||||||
|
previewAttachment: (attachment: Attachment) => void;
|
||||||
content: MutableRefObject<string | null>;
|
content: MutableRefObject<string | null>;
|
||||||
onUpdate: () => void;
|
onUpdate: () => void;
|
||||||
titlePlaceholder: string;
|
titlePlaceholder: string;
|
||||||
@@ -170,7 +171,9 @@ export function useEditorController(update: () => void): EditorController {
|
|||||||
const downloadAttachment = useCallback((attachment: Attachment) => {
|
const downloadAttachment = useCallback((attachment: Attachment) => {
|
||||||
post(EventTypes.download, attachment);
|
post(EventTypes.download, attachment);
|
||||||
}, []);
|
}, []);
|
||||||
|
const previewAttachment = useCallback((attachment: Attachment) => {
|
||||||
|
post(EventTypes.previewAttachment, attachment);
|
||||||
|
}, []);
|
||||||
const openLink = useCallback((url: string) => {
|
const openLink = useCallback((url: string) => {
|
||||||
post(EventTypes.link, url);
|
post(EventTypes.link, url);
|
||||||
return true;
|
return true;
|
||||||
@@ -187,6 +190,7 @@ export function useEditorController(update: () => void): EditorController {
|
|||||||
setTitlePlaceholder,
|
setTitlePlaceholder,
|
||||||
openFilePicker,
|
openFilePicker,
|
||||||
downloadAttachment,
|
downloadAttachment,
|
||||||
|
previewAttachment,
|
||||||
content: htmlContentRef,
|
content: htmlContentRef,
|
||||||
openLink,
|
openLink,
|
||||||
onUpdate: onUpdate
|
onUpdate: onUpdate
|
||||||
|
|||||||
@@ -145,7 +145,8 @@ export const EventTypes = {
|
|||||||
fullscreen: "editor-event:fullscreen",
|
fullscreen: "editor-event:fullscreen",
|
||||||
link: "editor-event:link",
|
link: "editor-event:link",
|
||||||
contentchange: "editor-event:content-change",
|
contentchange: "editor-event:content-change",
|
||||||
reminders: "editor-event:reminders"
|
reminders: "editor-event:reminders",
|
||||||
|
previewAttachment: "editor-event:preview-attachment"
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export function isReactNative(): boolean {
|
export function isReactNative(): boolean {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export interface AttachmentOptions {
|
|||||||
HTMLAttributes: Record<string, unknown>;
|
HTMLAttributes: Record<string, unknown>;
|
||||||
onDownloadAttachment: (editor: Editor, attachment: Attachment) => boolean;
|
onDownloadAttachment: (editor: Editor, attachment: Attachment) => boolean;
|
||||||
onOpenAttachmentPicker: (editor: Editor, type: AttachmentType) => boolean;
|
onOpenAttachmentPicker: (editor: Editor, type: AttachmentType) => boolean;
|
||||||
|
onPreviewAttachment: (editor: Editor, attachment: Attachment) => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AttachmentWithProgress = AttachmentProgress & Attachment;
|
export type AttachmentWithProgress = AttachmentProgress & Attachment;
|
||||||
@@ -52,6 +53,7 @@ declare module "@tiptap/core" {
|
|||||||
removeAttachment: () => ReturnType;
|
removeAttachment: () => ReturnType;
|
||||||
downloadAttachment: (attachment: Attachment) => ReturnType;
|
downloadAttachment: (attachment: Attachment) => ReturnType;
|
||||||
setAttachmentProgress: (progress: AttachmentProgress) => ReturnType;
|
setAttachmentProgress: (progress: AttachmentProgress) => ReturnType;
|
||||||
|
previewAttachment: (options: Attachment) => ReturnType;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,7 +69,8 @@ export const AttachmentNode = Node.create<AttachmentOptions>({
|
|||||||
return {
|
return {
|
||||||
HTMLAttributes: {},
|
HTMLAttributes: {},
|
||||||
onDownloadAttachment: () => false,
|
onDownloadAttachment: () => false,
|
||||||
onOpenAttachmentPicker: () => false
|
onOpenAttachmentPicker: () => false,
|
||||||
|
onPreviewAttachment: () => false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -158,6 +161,12 @@ export const AttachmentNode = Node.create<AttachmentOptions>({
|
|||||||
tr.setMeta("addToHistory", false);
|
tr.setMeta("addToHistory", false);
|
||||||
if (dispatch) dispatch(tr);
|
if (dispatch) dispatch(tr);
|
||||||
return true;
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
previewAttachment:
|
||||||
|
(attachment) =>
|
||||||
|
({ editor }) => {
|
||||||
|
return this.options.onPreviewAttachment(editor, attachment);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,64 +17,64 @@ You should have received a copy of the GNU General Public License
|
|||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import "./extensions";
|
|
||||||
import CharacterCount from "@tiptap/extension-character-count";
|
|
||||||
import Placeholder from "@tiptap/extension-placeholder";
|
|
||||||
import Underline from "@tiptap/extension-underline";
|
|
||||||
import StarterKit from "@tiptap/starter-kit";
|
|
||||||
import { useEffect, useMemo } from "react";
|
|
||||||
import Toolbar from "./toolbar";
|
|
||||||
import TextAlign from "@tiptap/extension-text-align";
|
|
||||||
import Subscript from "@tiptap/extension-subscript";
|
|
||||||
import Superscript from "@tiptap/extension-superscript";
|
|
||||||
import FontSize from "./extensions/font-size";
|
|
||||||
import TextDirection from "./extensions/text-direction";
|
|
||||||
import TextStyle from "@tiptap/extension-text-style";
|
|
||||||
import FontFamily from "./extensions/font-family";
|
|
||||||
import BulletList from "./extensions/bullet-list";
|
|
||||||
import OrderedList from "./extensions/ordered-list";
|
|
||||||
import Color from "@tiptap/extension-color";
|
|
||||||
import TableRow from "@tiptap/extension-table-row";
|
|
||||||
import TableCell from "./extensions/table-cell";
|
|
||||||
import TableHeader from "@tiptap/extension-table-header";
|
|
||||||
import { ImageNode } from "./extensions/image";
|
|
||||||
import { Theme } from "@notesnook/theme";
|
import { Theme } from "@notesnook/theme";
|
||||||
import { AttachmentNode, AttachmentOptions } from "./extensions/attachment";
|
|
||||||
import { TaskListNode } from "./extensions/task-list";
|
|
||||||
import { TaskItemNode } from "./extensions/task-item";
|
|
||||||
import { SearchReplace } from "./extensions/search-replace";
|
|
||||||
import { EmbedNode } from "./extensions/embed";
|
|
||||||
import { CodeBlock } from "./extensions/code-block";
|
|
||||||
import { ListItem } from "./extensions/list-item";
|
|
||||||
import { Link } from "@tiptap/extension-link";
|
|
||||||
import { Codemark } from "./extensions/code-mark";
|
|
||||||
import { MathInline, MathBlock } from "./extensions/math";
|
|
||||||
import {
|
|
||||||
NodeViewSelectionNotifier,
|
|
||||||
usePortalProvider
|
|
||||||
} from "./extensions/react";
|
|
||||||
import { OutlineList } from "./extensions/outline-list";
|
|
||||||
import { OutlineListItem } from "./extensions/outline-list-item";
|
|
||||||
import { KeepInView } from "./extensions/keep-in-view";
|
|
||||||
import { SelectionPersist } from "./extensions/selection-persist";
|
|
||||||
import { Table } from "./extensions/table";
|
|
||||||
import { useToolbarStore } from "./toolbar/stores/toolbar-store";
|
|
||||||
import { useEditor } from "./hooks/use-editor";
|
|
||||||
import {
|
import {
|
||||||
EditorOptions,
|
EditorOptions,
|
||||||
extensions as TiptapCoreExtensions,
|
extensions as TiptapCoreExtensions,
|
||||||
getHTMLFromFragment
|
getHTMLFromFragment
|
||||||
} from "@tiptap/core";
|
} from "@tiptap/core";
|
||||||
import { usePermissionHandler } from "./hooks/use-permission-handler";
|
import CharacterCount from "@tiptap/extension-character-count";
|
||||||
import { Highlight } from "./extensions/highlight";
|
|
||||||
import { Paragraph } from "./extensions/paragraph";
|
|
||||||
import { ClipboardTextSerializer } from "./extensions/clipboard-text-serializer";
|
|
||||||
import { Code } from "@tiptap/extension-code";
|
import { Code } from "@tiptap/extension-code";
|
||||||
import { DateTime } from "./extensions/date-time";
|
import Color from "@tiptap/extension-color";
|
||||||
import { OpenLink, OpenLinkOptions } from "./extensions/open-link";
|
|
||||||
import HorizontalRule from "@tiptap/extension-horizontal-rule";
|
import HorizontalRule from "@tiptap/extension-horizontal-rule";
|
||||||
|
import { Link } from "@tiptap/extension-link";
|
||||||
|
import Placeholder from "@tiptap/extension-placeholder";
|
||||||
|
import Subscript from "@tiptap/extension-subscript";
|
||||||
|
import Superscript from "@tiptap/extension-superscript";
|
||||||
|
import TableHeader from "@tiptap/extension-table-header";
|
||||||
|
import TableRow from "@tiptap/extension-table-row";
|
||||||
|
import TextAlign from "@tiptap/extension-text-align";
|
||||||
|
import TextStyle from "@tiptap/extension-text-style";
|
||||||
|
import Underline from "@tiptap/extension-underline";
|
||||||
|
import StarterKit from "@tiptap/starter-kit";
|
||||||
|
import { useEffect, useMemo } from "react";
|
||||||
|
import "./extensions";
|
||||||
|
import { AttachmentNode, AttachmentOptions } from "./extensions/attachment";
|
||||||
|
import BulletList from "./extensions/bullet-list";
|
||||||
|
import { ClipboardTextSerializer } from "./extensions/clipboard-text-serializer";
|
||||||
|
import { CodeBlock } from "./extensions/code-block";
|
||||||
|
import { Codemark } from "./extensions/code-mark";
|
||||||
|
import { DateTime } from "./extensions/date-time";
|
||||||
|
import { EmbedNode } from "./extensions/embed";
|
||||||
|
import FontFamily from "./extensions/font-family";
|
||||||
|
import FontSize from "./extensions/font-size";
|
||||||
|
import { Highlight } from "./extensions/highlight";
|
||||||
|
import { ImageNode, ImageOptions } from "./extensions/image";
|
||||||
|
import { KeepInView } from "./extensions/keep-in-view";
|
||||||
import { KeyMap } from "./extensions/key-map";
|
import { KeyMap } from "./extensions/key-map";
|
||||||
|
import { ListItem } from "./extensions/list-item";
|
||||||
|
import { MathBlock, MathInline } from "./extensions/math";
|
||||||
|
import { OpenLink, OpenLinkOptions } from "./extensions/open-link";
|
||||||
|
import OrderedList from "./extensions/ordered-list";
|
||||||
|
import { OutlineList } from "./extensions/outline-list";
|
||||||
|
import { OutlineListItem } from "./extensions/outline-list-item";
|
||||||
|
import { Paragraph } from "./extensions/paragraph";
|
||||||
|
import {
|
||||||
|
NodeViewSelectionNotifier,
|
||||||
|
usePortalProvider
|
||||||
|
} from "./extensions/react";
|
||||||
|
import { SearchReplace } from "./extensions/search-replace";
|
||||||
|
import { SelectionPersist } from "./extensions/selection-persist";
|
||||||
|
import { Table } from "./extensions/table";
|
||||||
|
import TableCell from "./extensions/table-cell";
|
||||||
|
import { TaskItemNode } from "./extensions/task-item";
|
||||||
|
import { TaskListNode } from "./extensions/task-list";
|
||||||
|
import TextDirection from "./extensions/text-direction";
|
||||||
import { WebClipNode, WebClipOptions } from "./extensions/web-clip";
|
import { WebClipNode, WebClipOptions } from "./extensions/web-clip";
|
||||||
|
import { useEditor } from "./hooks/use-editor";
|
||||||
|
import { usePermissionHandler } from "./hooks/use-permission-handler";
|
||||||
|
import Toolbar from "./toolbar";
|
||||||
|
import { useToolbarStore } from "./toolbar/stores/toolbar-store";
|
||||||
import { DownloadOptions } from "./utils/downloader";
|
import { DownloadOptions } from "./utils/downloader";
|
||||||
|
|
||||||
const CoreExtensions = Object.entries(TiptapCoreExtensions)
|
const CoreExtensions = Object.entries(TiptapCoreExtensions)
|
||||||
@@ -85,6 +85,7 @@ const CoreExtensions = Object.entries(TiptapCoreExtensions)
|
|||||||
type TiptapOptions = EditorOptions &
|
type TiptapOptions = EditorOptions &
|
||||||
Omit<AttachmentOptions, "HTMLAttributes"> &
|
Omit<AttachmentOptions, "HTMLAttributes"> &
|
||||||
Omit<WebClipOptions, "HTMLAttributes"> &
|
Omit<WebClipOptions, "HTMLAttributes"> &
|
||||||
|
Omit<ImageOptions, "HTMLAttributes"> &
|
||||||
OpenLinkOptions & {
|
OpenLinkOptions & {
|
||||||
downloadOptions?: DownloadOptions;
|
downloadOptions?: DownloadOptions;
|
||||||
theme: Theme;
|
theme: Theme;
|
||||||
@@ -104,6 +105,7 @@ const useTiptap = (
|
|||||||
isKeyboardOpen,
|
isKeyboardOpen,
|
||||||
onDownloadAttachment,
|
onDownloadAttachment,
|
||||||
onOpenAttachmentPicker,
|
onOpenAttachmentPicker,
|
||||||
|
onPreviewAttachment,
|
||||||
onOpenLink,
|
onOpenLink,
|
||||||
onBeforeCreate,
|
onBeforeCreate,
|
||||||
downloadOptions,
|
downloadOptions,
|
||||||
@@ -219,7 +221,8 @@ const useTiptap = (
|
|||||||
EmbedNode,
|
EmbedNode,
|
||||||
AttachmentNode.configure({
|
AttachmentNode.configure({
|
||||||
onDownloadAttachment,
|
onDownloadAttachment,
|
||||||
onOpenAttachmentPicker
|
onOpenAttachmentPicker,
|
||||||
|
onPreviewAttachment
|
||||||
}),
|
}),
|
||||||
OutlineListItem,
|
OutlineListItem,
|
||||||
OutlineList,
|
OutlineList,
|
||||||
@@ -241,6 +244,7 @@ const useTiptap = (
|
|||||||
injectCSS: false
|
injectCSS: false
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
|
onPreviewAttachment,
|
||||||
onDownloadAttachment,
|
onDownloadAttachment,
|
||||||
onOpenAttachmentPicker,
|
onOpenAttachmentPicker,
|
||||||
PortalProviderAPI,
|
PortalProviderAPI,
|
||||||
@@ -260,6 +264,12 @@ const useTiptap = (
|
|||||||
return editor;
|
return editor;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export { type Fragment } from "prosemirror-model";
|
||||||
|
export { type Attachment, type AttachmentType } from "./extensions/attachment";
|
||||||
|
export * from "./extensions/react";
|
||||||
|
export * from "./toolbar";
|
||||||
|
export * from "./types";
|
||||||
|
export * from "./utils/word-counter";
|
||||||
export {
|
export {
|
||||||
useTiptap,
|
useTiptap,
|
||||||
Toolbar,
|
Toolbar,
|
||||||
@@ -267,9 +277,3 @@ export {
|
|||||||
getHTMLFromFragment,
|
getHTMLFromFragment,
|
||||||
type DownloadOptions
|
type DownloadOptions
|
||||||
};
|
};
|
||||||
export * from "./types";
|
|
||||||
export * from "./extensions/react";
|
|
||||||
export * from "./toolbar";
|
|
||||||
export { type AttachmentType, type Attachment } from "./extensions/attachment";
|
|
||||||
export { type Fragment } from "prosemirror-model";
|
|
||||||
export * from "./utils/word-counter";
|
|
||||||
|
|||||||
@@ -113,7 +113,8 @@ import {
|
|||||||
mdiWeb,
|
mdiWeb,
|
||||||
mdiPageNextOutline,
|
mdiPageNextOutline,
|
||||||
mdiSortBoolAscendingVariant,
|
mdiSortBoolAscendingVariant,
|
||||||
mdiApplicationCogOutline
|
mdiApplicationCogOutline,
|
||||||
|
mdiArrowExpand
|
||||||
} from "@mdi/js";
|
} from "@mdi/js";
|
||||||
|
|
||||||
export const Icons = {
|
export const Icons = {
|
||||||
@@ -156,6 +157,7 @@ export const Icons = {
|
|||||||
fullscreen: mdiFullscreen,
|
fullscreen: mdiFullscreen,
|
||||||
url: mdiLink,
|
url: mdiLink,
|
||||||
image: mdiImageOutline,
|
image: mdiImageOutline,
|
||||||
|
previewAttachment: mdiArrowExpand,
|
||||||
imageDownload: mdiProgressDownload,
|
imageDownload: mdiProgressDownload,
|
||||||
imageFailed: mdiProgressAlert,
|
imageFailed: mdiProgressAlert,
|
||||||
imageSettings: mdiImageEditOutline,
|
imageSettings: mdiImageEditOutline,
|
||||||
|
|||||||
@@ -237,7 +237,6 @@ const tools: Record<ToolId, ToolDefinition> = {
|
|||||||
title: "Cell border width",
|
title: "Cell border width",
|
||||||
conditional: true
|
conditional: true
|
||||||
},
|
},
|
||||||
|
|
||||||
imageSettings: {
|
imageSettings: {
|
||||||
icon: "imageSettings",
|
icon: "imageSettings",
|
||||||
title: "Image settings",
|
title: "Image settings",
|
||||||
@@ -263,6 +262,11 @@ const tools: Record<ToolId, ToolDefinition> = {
|
|||||||
title: "Image properties",
|
title: "Image properties",
|
||||||
conditional: true
|
conditional: true
|
||||||
},
|
},
|
||||||
|
previewAttachment: {
|
||||||
|
icon: "previewAttachment",
|
||||||
|
title: "Preview attachment",
|
||||||
|
conditional: true
|
||||||
|
},
|
||||||
attachmentSettings: {
|
attachmentSettings: {
|
||||||
icon: "attachmentSettings",
|
icon: "attachmentSettings",
|
||||||
title: "Attachment settings",
|
title: "Attachment settings",
|
||||||
@@ -354,6 +358,7 @@ export const STATIC_TOOLBAR_GROUPS: ToolbarDefinition = [
|
|||||||
"cellProperties",
|
"cellProperties",
|
||||||
"imageSettings",
|
"imageSettings",
|
||||||
"embedSettings",
|
"embedSettings",
|
||||||
|
"previewAttachment",
|
||||||
"attachmentSettings",
|
"attachmentSettings",
|
||||||
"linkSettings",
|
"linkSettings",
|
||||||
"codeRemove",
|
"codeRemove",
|
||||||
|
|||||||
@@ -27,7 +27,11 @@ import { Attachment } from "../../extensions/attachment";
|
|||||||
export function AttachmentSettings(props: ToolProps) {
|
export function AttachmentSettings(props: ToolProps) {
|
||||||
const { editor } = props;
|
const { editor } = props;
|
||||||
const isBottom = useToolbarLocation() === "bottom";
|
const isBottom = useToolbarLocation() === "bottom";
|
||||||
if (!editor.isActive("attachment") || !isBottom) return null;
|
if (
|
||||||
|
(!editor.isActive("attachment") && !editor.isActive("image")) ||
|
||||||
|
!isBottom
|
||||||
|
)
|
||||||
|
return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MoreTools
|
<MoreTools
|
||||||
@@ -58,6 +62,28 @@ export function DownloadAttachment(props: ToolProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function PreviewAttachment(props: ToolProps) {
|
||||||
|
const { editor } = props;
|
||||||
|
const isBottom = useToolbarLocation() === "bottom";
|
||||||
|
|
||||||
|
if (!editor.isActive("image") || !isBottom) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToolButton
|
||||||
|
{...props}
|
||||||
|
toggled={false}
|
||||||
|
onClick={() => {
|
||||||
|
const attachmentNode =
|
||||||
|
findSelectedNode(editor, "attachment") ||
|
||||||
|
findSelectedNode(editor, "image");
|
||||||
|
|
||||||
|
const attachment = (attachmentNode?.attrs || {}) as Attachment;
|
||||||
|
editor.current?.chain().focus().previewAttachment(attachment).run();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function RemoveAttachment(props: ToolProps) {
|
export function RemoveAttachment(props: ToolProps) {
|
||||||
const { editor } = props;
|
const { editor } = props;
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
AttachmentSettings,
|
AttachmentSettings,
|
||||||
DownloadAttachment,
|
DownloadAttachment,
|
||||||
|
PreviewAttachment,
|
||||||
RemoveAttachment
|
RemoveAttachment
|
||||||
} from "./attachment";
|
} from "./attachment";
|
||||||
import {
|
import {
|
||||||
@@ -133,6 +134,7 @@ const tools = {
|
|||||||
webclipOpenSource: WebClipOpenSource,
|
webclipOpenSource: WebClipOpenSource,
|
||||||
webclipSettings: WebClipSettings,
|
webclipSettings: WebClipSettings,
|
||||||
|
|
||||||
|
previewAttachment: PreviewAttachment,
|
||||||
attachmentSettings: AttachmentSettings,
|
attachmentSettings: AttachmentSettings,
|
||||||
downloadAttachment: DownloadAttachment,
|
downloadAttachment: DownloadAttachment,
|
||||||
removeAttachment: RemoveAttachment,
|
removeAttachment: RemoveAttachment,
|
||||||
|
|||||||
Reference in New Issue
Block a user