editor: fix clipboard on mobile

This commit is contained in:
ammarahm-ed
2023-07-06 11:10:09 +05:00
parent 13b8580b2f
commit c61649f7fa
11 changed files with 154 additions and 52 deletions

View File

@@ -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"
};

View File

@@ -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;

View File

@@ -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) => {

View File

@@ -93,6 +93,9 @@ const Tiptap = ({
onOpenLink: (url) => {
return global.editorController.openLink(url);
},
copyToClipboard: (text) => {
globalThis.editorController.copyToClipboard(text);
},
downloadOptions: {
corsHost: settings.corsProxy
},

View File

@@ -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
};
}

View File

@@ -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 {

View 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;
}
};
}
});

View 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;

View File

@@ -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}

View File

@@ -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
]
);

View File

@@ -7,7 +7,8 @@
"baseUrl": "./",
"paths": {
"@/*": ["*"]
}
},
"incremental": true
},
"exclude": ["src/**/*.test.ts"],
"include": ["src/**/*"]