diff --git a/apps/mobile/app/components/sheets/editor-tabs/index.tsx b/apps/mobile/app/components/sheets/editor-tabs/index.tsx index cfd740b04..e52f56ea0 100644 --- a/apps/mobile/app/components/sheets/editor-tabs/index.tsx +++ b/apps/mobile/app/components/sheets/editor-tabs/index.tsx @@ -16,8 +16,8 @@ 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 . */ -import { Note } from "@notesnook/core"; -import { EVENTS } from "@notesnook/core"; +import { EVENTS, Note } from "@notesnook/core"; +import { strings } from "@notesnook/intl"; import { useThemeColors } from "@notesnook/theme"; import React, { useEffect } from "react"; import { View } from "react-native"; @@ -33,12 +33,10 @@ import { editorController } from "../../../screens/editor/tiptap/utils"; import { eSendEvent, presentSheet } from "../../../services/event-manager"; import { eUnlockNote } from "../../../utils/events"; import { SIZE } from "../../../utils/size"; -import { Button } from "../../ui/button"; import { IconButton } from "../../ui/icon-button"; import { Pressable } from "../../ui/pressable"; import Heading from "../../ui/typography/heading"; import Paragraph from "../../ui/typography/paragraph"; -import { strings } from "@notesnook/intl"; const TabItemComponent = (props: { tab: TabItem; diff --git a/apps/mobile/app/screens/editor/tiptap/commands.ts b/apps/mobile/app/screens/editor/tiptap/commands.ts index 061c11bdb..50ab1429b 100644 --- a/apps/mobile/app/screens/editor/tiptap/commands.ts +++ b/apps/mobile/app/screens/editor/tiptap/commands.ts @@ -18,9 +18,11 @@ along with this program. If not, see . */ import { Note } from "@notesnook/core"; -import type { Attachment } from "@notesnook/editor"; -import type { ImageAttributes } from "@notesnook/editor"; -import type { LinkAttributes } from "@notesnook/editor"; +import type { + Attachment, + ImageAttributes, + LinkAttributes +} from "@notesnook/editor"; import { createRef, RefObject } from "react"; import { Platform } from "react-native"; import { EdgeInsets } from "react-native-safe-area-context"; @@ -37,9 +39,6 @@ async function call(webview: RefObject, action?: Action) { if (!webview.current || !action) return; setImmediate(() => webview.current?.injectJavaScript(action.job)); const response = await getResponse(action.id); - // if (!response) { - // console.warn("webview job failed", action.id); - // } return response ? response.value : response; } @@ -75,6 +74,17 @@ class Commands { return call(this.ref, fn(job, name)) as Promise; } + async sendCommand(command: string, ...args: any[]) { + return this.doAsync( + `response = globalThis.commands.${command}(${args + .map((arg) => + typeof arg === "string" ? `"${arg}"` : JSON.stringify(arg) + ) + .join(",")})`, + command + ); + } + focus = async (tabId: string) => { if (!this.ref.current) return; @@ -84,107 +94,45 @@ class Commands { setTimeout(async () => { if (!this.ref) return; textInput.current?.focus(); - await this.doAsync( - locked - ? `editorControllers["${tabId}"]?.focusPassInput();` - : `editors["${tabId}"]?.commands.focus()`, - "focus" - ); - + await this.sendCommand("focus", tabId, locked); this.ref?.current?.requestFocus(); }, 1); } else { await sleep(400); - await this.doAsync( - locked - ? `editorControllers["${tabId}"]?.focusPassInput();` - : `editors["${tabId}"]?.commands.focus()`, - "focus" - ); + await this.sendCommand("focus", tabId, locked); } }; - blur = async (tabId: string) => - await this.doAsync( - ` - const editor = editors["${tabId}"]; - const editorTitle = editorTitles["${tabId}"]; - typeof editor !== "undefined" && editor.commands.blur(); - typeof editorTitle !== "undefined" && editorTitle.current && editorTitle.current.blur(); - - editorControllers["${tabId}"]?.blurPassInput(); - - `, - "blur" - ); + blur = async (tabId: string) => this.sendCommand("blur", tabId); clearContent = async (tabId: string) => { this.previousSettings = null; - await this.doAsync( - ` -const editor = editors["${tabId}"]; -const editorController = editorControllers["${tabId}"]; -const editorTitle = editorTitles["${tabId}"]; -const statusBar = statusBars["${tabId}"]; - -if (typeof editor !== "undefined") { - editor.commands.blur(); - editor.commands.clearContent(false); -} - -typeof editorTitle !== "undefined" && editorTitle.current && editorTitle.current?.blur(); -if (typeof editorController.content !== undefined) editorController.content.current = ''; -editorController.onUpdate(); -editorController.setTitle(''); -if (typeof statusBar !== "undefined") { - statusBar.current.resetWords(); - statusBar.current.set({date:"",saved:""}); -}`, - "clearContent" - ); + await this.sendCommand("clearContent", tabId); }; setSessionId = async (id: string | null) => - await this.doAsync(`globalThis.sessionId = "${id}";`); + await this.sendCommand("setSessionId", id); setStatus = async ( date: string | undefined, saved: string, tabId: string ) => { - await this.doAsync( - ` - const statusBar = statusBars["${tabId}"]; - typeof statusBar !== "undefined" && statusBar.current.set({date:"${date}",saved:"${saved}"})`, - "setStatus" + this.sendCommand("setStatus", date, saved, tabId); + }; + + setPlaceholder = async (placeholder: string) => {}; + + setLoading = async (loading?: boolean, tabId?: string) => { + this.sendCommand( + "setLoading", + loading, + tabId === undefined ? useTabStore.getState().currentTab : tabId ); }; - setPlaceholder = async (placeholder: string) => { - // await this.doAsync(` - // const element = document.querySelector(".is-editor-empty"); - // if (element) { - // element.setAttribute("data-placeholder","${placeholder}"); - // } - // `); - }; - - setLoading = async (loading?: boolean, tabId?: string) => { - await this.doAsync(` - const editorController = editorControllers["${ - tabId === undefined ? useTabStore.getState().currentTab : tabId - }"]; - editorController.setLoading(${loading}) - logger("info", editorController.setLoading); - `); - }; - setInsets = async (insets: EdgeInsets) => { - await this.doAsync(` - if (typeof safeAreaController !== "undefined") { - safeAreaController.update(${JSON.stringify(insets)}) - } - `); + this.sendCommand("setInsets", insets); }; updateSettings = async (settings?: Partial) => { @@ -193,13 +141,7 @@ if (typeof statusBar !== "undefined") { ...this.previousSettings, ...settings }; - await this.doAsync(` - if (typeof globalThis.settingsController !== "undefined") { - globalThis.settingsController.update(${JSON.stringify( - this.previousSettings - )}) - } - `); + this.sendCommand("updateSettings", settings); }; setSettings = async (settings?: Partial) => { @@ -212,11 +154,7 @@ if (typeof statusBar !== "undefined") { return; } } - await this.doAsync(` - if (typeof globalThis.settingsController !== "undefined") { - globalThis.settingsController.update(${JSON.stringify(settings)}) - } - `); + this.sendCommand("setSettings", settings); }; setTags = async (note: Note | null | undefined) => { @@ -224,60 +162,23 @@ if (typeof statusBar !== "undefined") { useTabStore.getState().forEachNoteTab(note.id, async (tab) => { const tabId = tab.id; const tags = await db.relations.to(note, "tag").resolve(); - await this.doAsync( - ` - const tags = editorTags["${tabId}"]; - if (tags && tags.current) { - tags.current.setTags(${JSON.stringify( - tags.map((tag) => ({ - title: tag.title, - alias: tag.title, - id: tag.id, - type: tag.type - })) - )}); - } - `, - "setTags" - ); + await this.sendCommand("setTags", tabId, tags); }); }; clearTags = async (tabId: string) => { - await this.doAsync( - ` - const tags = editorTags["${tabId}"]; - logger("info", Object.keys(editorTags), typeof editorTags[0]); - if (tags && tags.current) { - tags.current.setTags([]); - } - `, - "clearTags" - ); + await this.sendCommand("clearTags", tabId); }; insertAttachment = async (attachment: Attachment, tabId: number) => { - await this.doAsync( - `const editor = editors["${tabId}"]; -editor && editor.commands.insertAttachment(${JSON.stringify(attachment)})` - ); + await this.sendCommand("insertAttachment", attachment, tabId); }; setAttachmentProgress = async ( attachmentProgress: Partial, tabId: number ) => { - await this.doAsync( - `const editor = editors["${tabId}"]; -editor && editor.commands.updateAttachment(${JSON.stringify( - attachmentProgress - )}, { - preventUpdate: true, - query: (attachment) => { - return attachment.hash === "${attachmentProgress.hash}"; - } - })` - ); + await this.sendCommand("setAttachmentProgress", attachmentProgress, tabId); }; insertImage = async ( @@ -286,50 +187,30 @@ editor && editor.commands.updateAttachment(${JSON.stringify( }, tabId: number ) => { - await this.doAsync( - `const editor = editors["${tabId}"]; - -const image = toBlobURL("${image.dataurl}", "${image.hash}"); - -editor && editor.commands.insertImage({ - ...${JSON.stringify({ - ...image, - dataurl: undefined - })}, - bloburl: image - })` - ); + await this.sendCommand("insertImage", image, tabId); }; handleBack = async () => { - return this.doAsync( - 'response = window.dispatchEvent(new Event("handleBackPress",{cancelable:true}));' - ); + return this.sendCommand("handleBack"); }; keyboardShown = async (keyboardShown: boolean) => { - return this.doAsync(`globalThis['keyboardShown']=${keyboardShown};`); + return this.sendCommand("keyboardShown", keyboardShown); }; getTableOfContents = async () => { const tabId = useTabStore.getState().currentTab; - return this.doAsync(` - response = editorControllers["${tabId}"]?.getTableOfContents() || []; - `); + return this.sendCommand("getTableOfContents", tabId); }; focusPassInput = async () => { const tabId = useTabStore.getState().currentTab; - return this.doAsync(` - response = editorControllers["${tabId}"]?.focusPassInput() || []; - `); + return this.sendCommand("focusPassInput", tabId); }; blurPassInput = async () => { const tabId = useTabStore.getState().currentTab; - return this.doAsync(` - response = editorControllers["${tabId}"]?.blurPassInput() || []; - `); + return this.sendCommand("blurPassInput", tabId); }; createInternalLink = async ( @@ -337,28 +218,17 @@ editor && editor.commands.insertImage({ resolverId: string ) => { if (!resolverId) return; - return this.doAsync(` - if (globalThis.pendingResolvers["${resolverId}"]) { - globalThis.pendingResolvers["${resolverId}"](${JSON.stringify( - attributes - )}); - }`); + return this.sendCommand("createInternalLink", attributes, resolverId); }; dismissCreateInternalLinkRequest = async (resolverId: string) => { if (!resolverId) return; - return this.doAsync(` - if (globalThis.pendingResolvers["${resolverId}"]) { - globalThis.pendingResolvers["${resolverId}"](undefined); - } - `); + return this.sendCommand("dismissCreateInternalLinkRequest", resolverId); }; scrollIntoViewById = async (id: string) => { const tabId = useTabStore.getState().currentTab; - return this.doAsync(` - response = editorControllers["${tabId}"]?.scrollIntoView("${id}") || []; - `); + return this.sendCommand("scrollIntoViewById", id, tabId); }; } diff --git a/packages/editor-mobile/src/index.tsx b/packages/editor-mobile/src/index.tsx index f327f3a85..1d706116d 100644 --- a/packages/editor-mobile/src/index.tsx +++ b/packages/editor-mobile/src/index.tsx @@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ import "./utils/index"; +import "./utils/commands"; global.Buffer = require("buffer").Buffer; import { i18n } from "@lingui/core"; import "@notesnook/editor/styles/fonts.mobile.css"; diff --git a/packages/editor-mobile/src/utils/commands.ts b/packages/editor-mobile/src/utils/commands.ts new file mode 100644 index 000000000..7f20313bc --- /dev/null +++ b/packages/editor-mobile/src/utils/commands.ts @@ -0,0 +1,200 @@ +/* +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 . +*/ + +import { Attachment, ImageAttributes, LinkAttributes } from "@notesnook/editor"; +import { Settings } from "."; + +globalThis.commands = { + clearContent: (tabId: string) => { + try { + const editor = editors[tabId]; + const editorController = editorControllers[tabId]; + const editorTitle = editorTitles[tabId]; + const statusBar = statusBars[tabId]; + + if (editor) { + editor?.commands.blur(); + editor?.commands.clearContent(false); + } + + if (editorController) { + editorController.content.current = ""; + editorController.onUpdate(); + editorController.setTitle(""); + } + + if (editorTitle?.current) { + editorTitle.current?.blur(); + editorTitle.current.value = ""; + } + + if (statusBar) { + statusBar.current.resetWords(); + statusBar.current.set({ date: "", saved: "" }); + } + } catch (error) { + logger("error", "clearContent", error, (error as Error).stack); + } + }, + + focus: (tabId: string, locked: boolean) => { + const editorController = editorControllers[tabId]; + if (locked) { + editorController?.focusPassInput(); + } else { + editors[tabId]?.commands.focus(); + } + }, + + blur: (tabId: string) => { + const editor = editors[tabId]; + const editorTitle = editorTitles[tabId]; + if (editor) editor.commands.blur(); + if (editorTitle?.current) editorTitle.current.blur(); + editorControllers[tabId]?.blurPassInput(); + }, + + setSessionId: (id: string | undefined) => { + globalThis.sessionId = id; + }, + + setStatus: (date: string | undefined, saved: string, tabId: string) => { + const statusBar = statusBars[tabId]; + if (statusBar?.current) { + statusBar.current.set({ date: date || "", saved }); + } + }, + + setLoading: (loading?: boolean, tabId?: string) => { + if (tabId) { + const editorController = editorControllers[tabId]; + editorController?.setLoading(loading || false); + logger("info", editorController?.setLoading); + } + }, + + setInsets: (insets: any) => { + if (typeof safeAreaController !== "undefined") { + safeAreaController.update(insets); + } + }, + + updateSettings: (settings?: Partial) => { + if (typeof globalThis.settingsController !== "undefined") { + globalThis.settingsController.update(settings as Settings); + } + }, + + setSettings: (settings?: Partial) => { + if (typeof globalThis.settingsController !== "undefined") { + globalThis.settingsController.update(settings as Settings); + } + }, + + setTags: async (tabId: string, tags: any) => { + const current = globalThis.editorTags[tabId]; + if (current?.current) { + current.current.setTags( + tags.map((tag: any) => ({ + title: tag.title, + alias: tag.title, + id: tag.id, + type: tag.type + })) + ); + } + }, + + clearTags: (tabId: string) => { + const tags = editorTags[tabId]; + if (tags?.current) { + tags.current.setTags([]); + } + }, + + insertAttachment: (attachment: Attachment, tabId: number) => { + const editor = editors[tabId]; + if (editor) { + editor.commands.insertAttachment(attachment); + } + }, + + setAttachmentProgress: ( + attachmentProgress: Partial, + tabId: number + ) => { + const editor = editors[tabId]; + if (editor) { + editor.commands.updateAttachment(attachmentProgress, { + preventUpdate: true, + query: (attachment) => attachment.hash === attachmentProgress.hash + }); + } + }, + + insertImage: ( + image: Omit & { dataurl: string }, + tabId: number + ) => { + const editor = editors[tabId]; + if (editor) { + editor.commands.insertImage({ + ...image + }); + } + }, + + handleBack: () => { + return window.dispatchEvent( + new Event("handleBackPress", { cancelable: true }) + ); + }, + + keyboardShown: (keyboardShown: boolean) => { + globalThis["keyboardShown"] = keyboardShown; + }, + + getTableOfContents: (tabId: string) => { + return editorControllers[tabId]?.getTableOfContents() || []; + }, + + focusPassInput: (tabId: string) => { + return editorControllers[tabId]?.focusPassInput() || []; + }, + + blurPassInput: (tabId: string) => { + return editorControllers[tabId]?.blurPassInput() || []; + }, + + createInternalLink: (attributes: LinkAttributes, resolverId: string) => { + if (globalThis.pendingResolvers[resolverId]) { + globalThis.pendingResolvers[resolverId](attributes); + } + }, + + dismissCreateInternalLinkRequest: (resolverId: string) => { + if (globalThis.pendingResolvers[resolverId]) { + globalThis.pendingResolvers[resolverId](undefined); + } + }, + + scrollIntoViewById: (id: string, tabId: string) => { + return editorControllers[tabId]?.scrollIntoView(id) || []; + } +}; diff --git a/packages/editor-mobile/src/utils/index.ts b/packages/editor-mobile/src/utils/index.ts index 0e1fb4d0d..c7f20d599 100644 --- a/packages/editor-mobile/src/utils/index.ts +++ b/packages/editor-mobile/src/utils/index.ts @@ -83,10 +83,12 @@ declare global { var noHeader: boolean; function toBlobURL(dataurl: string, id?: string): string | undefined; var pendingResolvers: { [name: string]: (value: any) => void }; + + var commands: any; /** * Id of current session */ - var sessionId: string; + var sessionId: string | undefined; var tabStore: any; /**