mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-16 19:57:52 +01:00
editor: fix clipboard on mobile
This commit is contained in:
@@ -35,5 +35,6 @@ export const EventTypes = {
|
||||
link: "editor-event:link",
|
||||
contentchange: "editor-event:content-change",
|
||||
reminders: "editor-event:reminders",
|
||||
previewAttachment: "editor-event:preview-attachment"
|
||||
previewAttachment: "editor-event:preview-attachment",
|
||||
copyToClipboard: "editor-events:copy-to-clipboard"
|
||||
};
|
||||
|
||||
@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/* eslint-disable no-case-declarations */
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import type { Attachment } from "@notesnook/editor/dist/extensions/attachment/index";
|
||||
import { getDefaultPresets } from "@notesnook/editor/dist/toolbar/tool-definitions";
|
||||
import { useCallback, useEffect, useRef } from "react";
|
||||
@@ -63,6 +64,7 @@ import { EventTypes } from "./editor-events";
|
||||
import { EditorMessage, EditorProps, useEditorType } from "./types";
|
||||
import { EditorEvents, editorState } from "./utils";
|
||||
import { useNoteStore } from "../../../stores/use-notes-store";
|
||||
import SettingsService from "../../../services/settings";
|
||||
|
||||
const publishNote = async (editor: useEditorType) => {
|
||||
const user = useUserStore.getState().user;
|
||||
@@ -171,6 +173,12 @@ export const useEditorEvents = (
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) return;
|
||||
if (typeof defaultFontFamily === "object") {
|
||||
SettingsService.set({
|
||||
defaultFontFamily: (defaultFontFamily as any).id
|
||||
});
|
||||
}
|
||||
|
||||
editor.commands.setSettings({
|
||||
deviceMode: deviceMode || "mobile",
|
||||
fullscreen: fullscreen || false,
|
||||
@@ -182,7 +190,7 @@ export const useEditorEvents = (
|
||||
doubleSpacedLines: doubleSpacedLines,
|
||||
corsProxy: corsProxy,
|
||||
fontSize: defaultFontSize,
|
||||
fontFamily: defaultFontFamily,
|
||||
fontFamily: SettingsService.get().defaultFontFamily,
|
||||
dateFormat: db.settings?.getDateFormat(),
|
||||
timeFormat: db.settings?.getTimeFormat()
|
||||
});
|
||||
@@ -425,6 +433,7 @@ export const useEditorEvents = (
|
||||
case EventTypes.previewAttachment: {
|
||||
const hash = (editorMessage.value as Attachment)?.hash;
|
||||
const attachment = db.attachments?.attachment(hash);
|
||||
if (!attachment) return;
|
||||
if (attachment.metadata.type.startsWith("image/")) {
|
||||
eSendEvent("ImagePreview", editorMessage.value);
|
||||
} else {
|
||||
@@ -433,6 +442,10 @@ export const useEditorEvents = (
|
||||
|
||||
break;
|
||||
}
|
||||
case EventTypes.copyToClipboard: {
|
||||
Clipboard.setString(editorMessage.value as string);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -57,6 +57,7 @@ import { showBuyDialog } from "../../common/dialog-controller";
|
||||
import { useStore as useSettingsStore } from "../../stores/setting-store";
|
||||
import { debounce, debounceWithId } from "@notesnook/common";
|
||||
import { store as editorstore } from "../../stores/editor-store";
|
||||
import { writeText } from "clipboard-polyfill";
|
||||
|
||||
type OnChangeHandler = (
|
||||
id: string | undefined,
|
||||
@@ -235,6 +236,9 @@ function TipTap(props: TipTapProps) {
|
||||
canUndo: editor.can().undo()
|
||||
});
|
||||
},
|
||||
copyToClipboard(text) {
|
||||
writeText(text);
|
||||
},
|
||||
onSelectionUpdate: debounce(({ editor, transaction }) => {
|
||||
const isEmptySelection = transaction.selection.empty;
|
||||
configure((old) => {
|
||||
|
||||
@@ -93,6 +93,9 @@ const Tiptap = ({
|
||||
onOpenLink: (url) => {
|
||||
return global.editorController.openLink(url);
|
||||
},
|
||||
copyToClipboard: (text) => {
|
||||
globalThis.editorController.copyToClipboard(text);
|
||||
},
|
||||
downloadOptions: {
|
||||
corsHost: settings.corsProxy
|
||||
},
|
||||
|
||||
@@ -96,6 +96,7 @@ export type EditorController = {
|
||||
openLink: (url: string) => boolean;
|
||||
setTitlePlaceholder: React.Dispatch<React.SetStateAction<string>>;
|
||||
countWords: (ms: number) => void;
|
||||
copyToClipboard: (text: string) => void;
|
||||
};
|
||||
|
||||
export function useEditorController(update: () => void): EditorController {
|
||||
@@ -233,6 +234,10 @@ export function useEditorController(update: () => void): EditorController {
|
||||
return true;
|
||||
}, []);
|
||||
|
||||
const copyToClipboard = (text: string) => {
|
||||
post(EventTypes.copyToClipboard, text);
|
||||
};
|
||||
|
||||
return {
|
||||
contentChange,
|
||||
selectionChange,
|
||||
@@ -248,6 +253,7 @@ export function useEditorController(update: () => void): EditorController {
|
||||
content: htmlContentRef,
|
||||
openLink,
|
||||
onUpdate: onUpdate,
|
||||
countWords
|
||||
countWords,
|
||||
copyToClipboard
|
||||
};
|
||||
}
|
||||
|
||||
@@ -150,7 +150,8 @@ export const EventTypes = {
|
||||
link: "editor-event:link",
|
||||
contentchange: "editor-event:content-change",
|
||||
reminders: "editor-event:reminders",
|
||||
previewAttachment: "editor-event:preview-attachment"
|
||||
previewAttachment: "editor-event:preview-attachment",
|
||||
copyToClipboard: "editor-events:copy-to-clipboard"
|
||||
} as const;
|
||||
|
||||
export function isReactNative(): boolean {
|
||||
|
||||
50
packages/editor/src/extensions/clipboard/clipboard.ts
Normal file
50
packages/editor/src/extensions/clipboard/clipboard.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { Extension } from "@tiptap/core";
|
||||
import { writeText } from "clipboard-polyfill";
|
||||
|
||||
declare module "@tiptap/core" {
|
||||
interface Commands<ReturnType> {
|
||||
clipboard: {
|
||||
copyToClipboard: (text: string) => ReturnType;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export type ClipboardOptions = {
|
||||
copyToClipboard: (text: string) => void;
|
||||
};
|
||||
|
||||
export const Clipboard = Extension.create<ClipboardOptions>({
|
||||
addOptions() {
|
||||
return {
|
||||
copyToClipboard: (text) => {
|
||||
writeText(text);
|
||||
}
|
||||
};
|
||||
},
|
||||
addCommands() {
|
||||
return {
|
||||
copyToClipboard: (text: string) => (props) => {
|
||||
this.options.copyToClipboard(text);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
23
packages/editor/src/extensions/clipboard/index.ts
Normal file
23
packages/editor/src/extensions/clipboard/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { Clipboard } from "./clipboard";
|
||||
|
||||
export * from "./clipboard";
|
||||
|
||||
export default Clipboard;
|
||||
@@ -17,20 +17,18 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Flex, Input, Text } from "@theme-ui/components";
|
||||
import { useRef, useState } from "react";
|
||||
import { Flex, Text } from "@theme-ui/components";
|
||||
import Languages from "./languages.json";
|
||||
import { Input } from "@theme-ui/components";
|
||||
import { Icon } from "../../toolbar/components/icon";
|
||||
import { Icons } from "../../toolbar/icons";
|
||||
import { CodeBlockAttributes } from "./code-block";
|
||||
import { ReactNodeViewProps } from "../react/types";
|
||||
import { ResponsivePresenter } from "../../components/responsive";
|
||||
import { Popup } from "../../toolbar/components/popup";
|
||||
import { useIsMobile } from "../../toolbar/stores/toolbar-store";
|
||||
import { Button } from "../../components/button";
|
||||
import { ResponsivePresenter } from "../../components/responsive";
|
||||
import { useTimer } from "../../hooks/use-timer";
|
||||
import { writeText } from "clipboard-polyfill";
|
||||
import { Icon } from "../../toolbar/components/icon";
|
||||
import { Popup } from "../../toolbar/components/popup";
|
||||
import { Icons } from "../../toolbar/icons";
|
||||
import { useIsMobile } from "../../toolbar/stores/toolbar-store";
|
||||
import { ReactNodeViewProps } from "../react/types";
|
||||
import { CodeBlockAttributes } from "./code-block";
|
||||
import Languages from "./languages.json";
|
||||
|
||||
export function CodeblockComponent(
|
||||
props: ReactNodeViewProps<CodeBlockAttributes>
|
||||
@@ -96,18 +94,18 @@ export function CodeblockComponent(
|
||||
}}
|
||||
>
|
||||
{caretPosition ? (
|
||||
<Text variant={"subBody"} sx={{ mr: 2, color: "codeFg" }}>
|
||||
<Text variant={"subBody"} sx={{ mr: 1, color: "codeFg" }}>
|
||||
Line {caretPosition.line}, Column {caretPosition.column}{" "}
|
||||
{caretPosition.selected
|
||||
? `(${caretPosition.selected} selected)`
|
||||
: ""}
|
||||
</Text>
|
||||
) : null}
|
||||
|
||||
<Button
|
||||
variant={"icon"}
|
||||
sx={{
|
||||
p: 1,
|
||||
mr: 1,
|
||||
opacity: "1 !important",
|
||||
":hover": { bg: "codeSelection" }
|
||||
}}
|
||||
@@ -125,12 +123,12 @@ export function CodeblockComponent(
|
||||
{indentType === "space" ? "Spaces" : "Tabs"}: {indentLength}
|
||||
</Text>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant={"icon"}
|
||||
sx={{
|
||||
opacity: "1 !important",
|
||||
p: 1,
|
||||
mr: 1,
|
||||
bg: isOpen ? "codeSelection" : "transparent",
|
||||
":hover": { bg: "codeSelection" }
|
||||
}}
|
||||
@@ -150,39 +148,34 @@ export function CodeblockComponent(
|
||||
{languageDefinition?.title || "Plaintext"}
|
||||
</Text>
|
||||
</Button>
|
||||
</Flex>
|
||||
|
||||
{node.textContent.length > 0 ? (
|
||||
<Button
|
||||
variant={"tool"}
|
||||
sx={{
|
||||
position: "absolute",
|
||||
opacity: isMobile ? 1 : 0,
|
||||
right: 0,
|
||||
p: 1,
|
||||
mr: 1,
|
||||
my: 1,
|
||||
bg: "bgSecondary",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
border: "1px solid var(--codeBorder)",
|
||||
":hover": { bg: "bgSecondaryHover" },
|
||||
borderColor: enabled ? "primary" : "codeBorder",
|
||||
"div:hover > &": { opacity: isMobile ? 0 : 1 }
|
||||
}}
|
||||
title={enabled ? "Copied to clipboard" : "Copy to clipboard"}
|
||||
onClick={() => {
|
||||
writeText(node.textContent);
|
||||
start();
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
path={enabled ? Icons.check : Icons.copy}
|
||||
size={"big"}
|
||||
color={enabled ? "primary" : "icon"}
|
||||
/>
|
||||
</Button>
|
||||
) : null}
|
||||
{node.textContent?.length > 0 ? (
|
||||
<Button
|
||||
variant={"icon"}
|
||||
sx={{
|
||||
opacity: "1 !important",
|
||||
p: 1,
|
||||
mr: 1,
|
||||
bg: "transparent",
|
||||
":hover": { bg: "codeSelection" }
|
||||
}}
|
||||
disabled={!editor.isEditable}
|
||||
onClick={() => {
|
||||
editor.commands.copyToClipboard(node.textContent);
|
||||
start();
|
||||
}}
|
||||
title="Copy to clipboard"
|
||||
>
|
||||
<Text
|
||||
variant={"subBody"}
|
||||
spellCheck={false}
|
||||
sx={{ color: "codeFg" }}
|
||||
>
|
||||
Copy
|
||||
</Text>
|
||||
</Button>
|
||||
) : null}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<ResponsivePresenter
|
||||
isOpen={isOpen}
|
||||
|
||||
@@ -76,6 +76,7 @@ import Toolbar from "./toolbar";
|
||||
import { useToolbarStore } from "./toolbar/stores/toolbar-store";
|
||||
import { DownloadOptions } from "./utils/downloader";
|
||||
import { Heading } from "./extensions/heading";
|
||||
import Clipboard, { ClipboardOptions } from "./extensions/clipboard";
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
@@ -93,6 +94,7 @@ export type TiptapOptions = EditorOptions &
|
||||
Omit<WebClipOptions, "HTMLAttributes"> &
|
||||
Omit<ImageOptions, "HTMLAttributes"> &
|
||||
DateTimeOptions &
|
||||
ClipboardOptions &
|
||||
OpenLinkOptions & {
|
||||
downloadOptions?: DownloadOptions;
|
||||
theme: Theme;
|
||||
@@ -116,6 +118,7 @@ const useTiptap = (
|
||||
downloadOptions,
|
||||
dateFormat,
|
||||
timeFormat,
|
||||
copyToClipboard,
|
||||
...restOptions
|
||||
} = options;
|
||||
const PortalProviderAPI = usePortalProvider();
|
||||
@@ -207,6 +210,9 @@ const useTiptap = (
|
||||
allowTableNodeSelection: true,
|
||||
cellMinWidth: 50
|
||||
}),
|
||||
Clipboard.configure({
|
||||
copyToClipboard
|
||||
}),
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableHeader,
|
||||
@@ -260,7 +266,8 @@ const useTiptap = (
|
||||
onBeforeCreate,
|
||||
onOpenLink,
|
||||
dateFormat,
|
||||
timeFormat
|
||||
timeFormat,
|
||||
copyToClipboard
|
||||
]
|
||||
);
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"@/*": ["*"]
|
||||
}
|
||||
},
|
||||
"incremental": true
|
||||
},
|
||||
"exclude": ["src/**/*.test.ts"],
|
||||
"include": ["src/**/*"]
|
||||
|
||||
Reference in New Issue
Block a user