diff --git a/apps/mobile/app/components/attachments/actions.tsx b/apps/mobile/app/components/attachments/actions.tsx index 940b61b2b..af09513e0 100644 --- a/apps/mobile/app/components/attachments/actions.tsx +++ b/apps/mobile/app/components/attachments/actions.tsx @@ -40,7 +40,8 @@ import { useAttachmentStore } from "../../stores/use-attachment-store"; import { eCloseAttachmentDialog, eCloseSheet, - eDBItemUpdate + eDBItemUpdate, + eOnLoadNote } from "../../utils/events"; import { SIZE } from "../../utils/size"; import { sleep } from "../../utils/time"; @@ -53,6 +54,8 @@ import { Notice } from "../ui/notice"; import { Pressable } from "../ui/pressable"; import Heading from "../ui/typography/heading"; import Paragraph from "../ui/typography/paragraph"; +import { useTabStore } from "../../screens/editor/tiptap/use-tab-store"; +import { editorController } from "../../screens/editor/tiptap/utils"; const Actions = ({ attachment, @@ -62,7 +65,7 @@ const Actions = ({ }: { attachment: Attachment; setAttachments: (attachments?: VirtualizedGrouping) => void; - close: () => void; + close?: () => void; fwdRef: RefObject; }) => { const { colors } = useThemeColors(); @@ -103,7 +106,7 @@ const Actions = ({ reupload: true, hash: attachment.hash, context: contextId, - type: attachment.type + type: attachment.mimeType.startsWith("image") ? "image" : "file" }); }, icon: "upload" @@ -169,10 +172,27 @@ const Actions = ({ { name: "Delete", onPress: async () => { + const relations = await db.relations.to(attachment, "note").get(); await db.attachments.remove(attachment.hash, false); setAttachments(); eSendEvent(eDBItemUpdate, attachment.id); - close(); + relations + .map((relation) => relation.fromId) + .forEach(async (id) => { + const tab = useTabStore.getState().getTabForNote(id); + if (tab !== undefined) { + const isFocused = useTabStore.getState().currentTab === tab; + if (isFocused) { + eSendEvent(eOnLoadNote, { + item: await db.notes.note(id), + forced: true + }); + } else { + editorController.current.commands.setLoading(true, tab); + } + } + }); + close?.(); }, icon: "delete-outline" } diff --git a/apps/mobile/app/components/dialogs/vault/index.js b/apps/mobile/app/components/dialogs/vault/index.js index 4a7cd1f94..a67561579 100644 --- a/apps/mobile/app/components/dialogs/vault/index.js +++ b/apps/mobile/app/components/dialogs/vault/index.js @@ -37,7 +37,8 @@ import { eCloseActionSheet, eCloseVaultDialog, eOnLoadNote, - eOpenVaultDialog + eOpenVaultDialog, + eUpdateNoteInEditor } from "../../../utils/events"; import { deleteItems } from "../../../utils/functions"; import { tabBarRef } from "../../../utils/global-refs"; @@ -336,7 +337,26 @@ export class VaultDialog extends Component { let verified = await db.user.verifyPassword(this.password); if (!(await db.user.getUser())) verified = true; if (verified) { + let noteIds = []; + if (this.state.deleteAll) { + const vault = await db.vaults.default(); + const relations = await db.relations.from(vault, "note").get(); + noteIds = relations.map((item) => item.toId); + } + await db.vault.delete(this.state.deleteAll); + + noteIds.forEach((id) => { + eSendEvent( + eUpdateNoteInEditor, + { + id: id, + deleted: true + }, + true + ); + }); + eSendEvent("vaultUpdated"); this.setState({ loading: false @@ -363,7 +383,22 @@ export class VaultDialog extends Component { loading: true }); try { + const vault = await db.vaults.default(); + const relations = await db.relations.from(vault, "note").get(); + const noteIds = relations.map((item) => item.toId); + await db.vault.clear(this.password); + + noteIds.forEach((id) => { + eSendEvent( + eUpdateNoteInEditor, + { + id: id, + deleted: true + }, + true + ); + }); this.setState({ loading: false }); @@ -393,9 +428,9 @@ export class VaultDialog extends Component { } else { await db.vault.add(this.state.note.id); - // if (this.state.note.id === editorController.current?.note?.id) { - // eSendEvent(eClearEditor, ); - // } + console.log("update note event..."); + eSendEvent(eUpdateNoteInEditor, this.state.note, true); + this.close(); ToastManager.show({ message: "Note locked successfully", @@ -500,10 +535,7 @@ export class VaultDialog extends Component { } if (this.state.note?.id) { await db.vault.add(this.state.note.id); - // TODO - // if (this.state.note.id === editorController.current?.note?.id) { - // eSendEvent(eClearEditor); - // } + eSendEvent(eUpdateNoteInEditor, this.state.note, true); this.setState({ loading: false }); @@ -533,6 +565,7 @@ export class VaultDialog extends Component { type: "success", context: "global" }); + eSendEvent(eUpdateNoteInEditor, this.state.note, true); this.close(); }) .catch((e) => { diff --git a/apps/mobile/app/components/sheets/editor-tabs/index.tsx b/apps/mobile/app/components/sheets/editor-tabs/index.tsx index 7090f7468..7ff5dda84 100644 --- a/apps/mobile/app/components/sheets/editor-tabs/index.tsx +++ b/apps/mobile/app/components/sheets/editor-tabs/index.tsx @@ -16,11 +16,14 @@ 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/dist/common"; import { useThemeColors } from "@notesnook/theme"; -import React from "react"; +import React, { useEffect } from "react"; import { View } from "react-native"; import { FlatList } from "react-native-actions-sheet"; import Icon from "react-native-vector-icons/MaterialCommunityIcons"; +import { db } from "../../../common/database"; import { useDBItem } from "../../../hooks/use-db-item"; import { useTabStore } from "../../../screens/editor/tiptap/use-tab-store"; import { editorController } from "../../../screens/editor/tiptap/utils"; @@ -38,6 +41,8 @@ type TabItem = { noteId?: string; previewTab?: boolean; locked?: boolean; + noteLocked?: boolean; + readonly?: boolean; }; const TabItemComponent = (props: { @@ -46,7 +51,24 @@ const TabItemComponent = (props: { close?: (ctx?: string | undefined) => void; }) => { const { colors } = useThemeColors(); - const [item] = useDBItem(props.tab.noteId, "note"); + const [item, update] = useDBItem(props.tab.noteId, "note"); + + useEffect(() => { + const syncCompletedSubscription = db.eventManager?.subscribe( + EVENTS.syncItemMerged, + (data: Note) => { + if (data.type === "note") { + const tabId = useTabStore.getState().getTabForNote(data.id); + if (tabId !== undefined && item?.title !== data.title) { + update(); + } + } + } + ); + return () => { + syncCompletedSubscription?.unsubscribe(); + }; + }, [update, item]); return ( - {props.tab.locked ? : null} + {props.tab.noteLocked ? ( + <> + {props.tab.locked ? ( + + ) : ( + + )} + + ) : null} + + {props.tab.readonly ? : null} + @@ -272,7 +271,7 @@ const ListNoteItem = ({ listType, noteInternalLinks.length ]); - + console.log(noteInternalLinks); const renderBlock = React.useCallback( (block: ContentBlock) => ( { - + ) : null} diff --git a/apps/mobile/app/screens/editor/index.tsx b/apps/mobile/app/screens/editor/index.tsx index 5e77638cc..41a45af90 100755 --- a/apps/mobile/app/screens/editor/index.tsx +++ b/apps/mobile/app/screens/editor/index.tsx @@ -57,6 +57,7 @@ import { useEditor } from "./tiptap/use-editor"; import { useEditorEvents } from "./tiptap/use-editor-events"; import { syncTabs, useTabStore } from "./tiptap/use-tab-store"; import { editorController, editorState } from "./tiptap/utils"; +import EditorOverlay from "./loading"; const style: ViewStyle = { height: "100%", @@ -173,8 +174,6 @@ const Editor = React.memo( autoManageStatusBarEnabled={false} onMessage={onMessage || undefined} /> - {/* */} - ); @@ -185,7 +184,6 @@ const Editor = React.memo( export default Editor; -let LOADED = false; const LockOverlay = () => { const tab = useTabStore((state) => state.tabs.find((t) => t.id === state.currentTab) @@ -193,27 +191,19 @@ const LockOverlay = () => { const isAppLoading = useSettingStore((state) => state.isAppLoading); const [item] = useDBItem(isAppLoading ? undefined : tab?.noteId, "note"); const tabRef = useRef(tab); - tabRef.current = tab; useEffect(() => { - if (!isAppLoading && !LOADED) { - LOADED = true; - (async () => { - for (const tab of useTabStore.getState().tabs) { - const noteId = useTabStore.getState().getTab(tab.id)?.noteId; - if (!noteId) continue; - const note = await db.notes.note(noteId); - const locked = note && (await db.vaults.itemExists(note)); - if (locked) { - useTabStore.getState().updateTab(tab.id, { - locked: true - }); - } - } - })(); + for (const tab of useTabStore.getState().tabs) { + const noteId = useTabStore.getState().getTab(tab.id)?.noteId; + if (!noteId) continue; + if (tab.noteLocked) { + useTabStore.getState().updateTab(tab.id, { + locked: true + }); + } } - }, [isAppLoading]); + }, []); useEffect(() => { (async () => { @@ -353,43 +343,3 @@ const LockOverlay = () => { return null; }; - -const ReadonlyButton = ({ editor }: { editor: useEditorType }) => { - const readonly = useTabStore( - (state) => state.tabs.find((t) => t.id === state.currentTab)?.readonly - ); - - const keyboard = useKeyboard(); - const { colors } = useThemeColors(); - - const onPress = async () => { - const noteId = useTabStore - .getState() - .getNoteIdForTab(useTabStore.getState().currentTab); - if (noteId) { - await db.notes.readonly(!editor.note.current.readonly, noteId); - editor.note.current[noteId] = await db.notes?.note(noteId); - - useTabStore.getState().updateTab(useTabStore.getState().currentTab, { - readonly: editor.note.current[noteId as string]?.readonly - }); - } - }; - - return readonly && !keyboard.keyboardShown ? ( - - ) : null; -}; diff --git a/apps/mobile/app/screens/editor/tiptap/editor-events.ts b/apps/mobile/app/screens/editor/tiptap/editor-events.ts index 3d3edf5d3..aa9f41ca8 100644 --- a/apps/mobile/app/screens/editor/tiptap/editor-events.ts +++ b/apps/mobile/app/screens/editor/tiptap/editor-events.ts @@ -45,5 +45,6 @@ export const EventTypes = { createInternalLink: "editor-events:create-internal-link", load: "editor-events:load", unlock: "editor-events:unlock", - unlockWithBiometrics: "editor-events:unlock-biometrics" + unlockWithBiometrics: "editor-events:unlock-biometrics", + disableReadonlyMode: "editor-events:disable-readonly-mode" }; diff --git a/apps/mobile/app/screens/editor/tiptap/picker.js b/apps/mobile/app/screens/editor/tiptap/picker.js index 36b2bf145..5634908d9 100644 --- a/apps/mobile/app/screens/editor/tiptap/picker.js +++ b/apps/mobile/app/screens/editor/tiptap/picker.js @@ -22,7 +22,11 @@ import { isImage } from "@notesnook/core/dist/utils/filename"; import { Platform } from "react-native"; import RNFetchBlob from "react-native-blob-util"; import DocumentPicker from "react-native-document-picker"; -import { launchCamera, launchImageLibrary } from "react-native-image-picker"; +import { + ImagePickerResponse, + launchCamera, + launchImageLibrary +} from "react-native-image-picker"; import { DatabaseLogger, db } from "../../../common/database"; import filesystem from "../../../common/filesystem"; import { compressToFile } from "../../../common/filesystem/compress"; @@ -213,12 +217,22 @@ const gallery = async (options) => { /** * - * @param {{ - * noteId: string, - * tabId: string, + * @typedef {{ + * noteId?: string, + * tabId?: string, * type: "image" | "camera" | "file" * reupload: boolean * hash?: string + * context?: string + * }} ImagePickerOptions + * + * @param {{ + * noteId?: string, + * tabId?: string, + * type: "image" | "camera" | "file" + * reupload: boolean + * hash?: string + * context?: string * }} options * @returns */ @@ -245,7 +259,12 @@ const pick = async (options) => { file(options); } }; - +/** + * + * @param {ImagePickerResponse} response + * @param {ImagePickerOptions} options + * @returns + */ const handleImageResponse = async (response, options) => { if ( response.didCancel || @@ -296,6 +315,7 @@ const handleImageResponse = async (response, options) => { if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri); console.log("attaching image to note..."); if ( + options.tabId !== undefined && useTabStore.getState().getNoteIdForTab(options.tabId) === options.noteId ) { console.log("attaching image to note..."); @@ -315,20 +335,13 @@ const handleImageResponse = async (response, options) => { }; /** - * - * @param {*} uri - * @param {*} hash - * @param {*} type - * @param {*} filename -/** - * @param {{ -* noteId: string, -* tabId: string, -* type: "image" | "camera" | "file" -* reupload: boolean -* hash?: string -* }} options - * @returns + * + * @param {string} uri + * @param {string} hash + * @param {string} type + * @param {string} filename + * @param {ImagePickerOptions} options + * @returns */ export async function attachFile(uri, hash, type, filename, options) { try { diff --git a/apps/mobile/app/screens/editor/tiptap/use-editor-events.ts b/apps/mobile/app/screens/editor/tiptap/use-editor-events.ts index 416c65fec..21fc2f4b7 100644 --- a/apps/mobile/app/screens/editor/tiptap/use-editor-events.ts +++ b/apps/mobile/app/screens/editor/tiptap/use-editor-events.ts @@ -154,7 +154,6 @@ export const useEditorEvents = ( state.dateFormat, state.timeFormat ]); - const handleBack = useRef(); const isPremium = useUserStore((state) => state.premium); const { fontScale } = useWindowDimensions(); @@ -636,6 +635,25 @@ export const useEditorEvents = ( break; } + case EventTypes.disableReadonlyMode: { + const noteId = editorMessage.value; + if (noteId) { + await db.notes.readonly(false, noteId); + editor.note.current[noteId] = await db.notes?.note(noteId); + useTabStore + .getState() + .updateTab(useTabStore.getState().currentTab, { + readonly: false + }); + Navigation.queueRoutesForUpdate(); + ToastManager.show({ + heading: "Readonly mode disabled.", + type: "success" + }); + } + break; + } + default: break; } diff --git a/apps/mobile/app/screens/editor/tiptap/use-editor.ts b/apps/mobile/app/screens/editor/tiptap/use-editor.ts index 2b76ba162..dbdf8f1cc 100644 --- a/apps/mobile/app/screens/editor/tiptap/use-editor.ts +++ b/apps/mobile/app/screens/editor/tiptap/use-editor.ts @@ -24,10 +24,13 @@ import { EVENTS } from "@notesnook/core/dist/common"; import { ContentItem, ContentType, + DeletedItem, ItemReference, Note, + TrashItem, UnencryptedContentItem, - isDeleted + isDeleted, + isTrashItem } from "@notesnook/core/dist/types"; import { useThemeEngineStore } from "@notesnook/theme"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; @@ -38,17 +41,16 @@ import { DDS } from "../../../services/device-detection"; import { eSendEvent, eSubscribeEvent, - eUnSubscribeEvent, - openVault + eUnSubscribeEvent } from "../../../services/event-manager"; import Navigation from "../../../services/navigation"; import Notifications from "../../../services/notifications"; import SettingsService from "../../../services/settings"; import { useTagStore } from "../../../stores/use-tag-store"; import { - eClearEditor, eEditorTabFocused, - eOnLoadNote + eOnLoadNote, + eUpdateNoteInEditor } from "../../../utils/events"; import { tabBarRef } from "../../../utils/global-refs"; import { onNoteCreated } from "../../notes/common"; @@ -66,28 +68,6 @@ import { post } from "./utils"; -// Keep a fixed session id, dont' change it when a new note is opened, session id can stay the same always I think once the app is opened. DONE -// Editor will save any note content & title is recieved. and dispatch update to relavant tab always. - -// Editor keeps track of what tab is opened and which note is currently focused by keeping a synced zustand store with editor. DONE -// the useEditor hook can recieve save messages for different notes at a time. DONE -// When a note is created, the useEditor hook must immediately notify the editor with the note id and set the note id in the editor tabs store -// so further changes will go into that note. DONE -// Events sent to editor have the tab id value added to ensure the correct tab will recieve and return events only. DONE -// The useEditorEvents hook can manage events from different tabs at the same time as long as the attached session id matches. DONE -// useEditor hook will keep historySessionId for different notes instead of a single note. DONE -// -// LIST OF CASES TO VERIFY WITH TABS OPENING & CLOSING -// 1. SWITCHING TAB CLOSES THE SHEET. DONE -// 2. Closing the tab does proper cleanup if it's the last tab and is not empty. DONE -// 3. Swiping left only focuses editor if current tab is empty. DONE -// 4. Pressing + button will open a new tab for new note if an empty tab does not exist. -// 5. Notes will always open in the preview tab. -// 6. If a note is edited, the tab will become persisted. -// 7. If note is already opened in a tab, we focus that tab. -// 8. If app is killed, restore the note in background. -// 9. During realtimes sync, tabs not focused will be updated so if focused, they have the latest and updated content loaded. - type NoteWithContent = Note & { content?: NoteContent; }; @@ -219,7 +199,8 @@ export const useEditor = ( resetContent && (await commands.clearTags(tabId)); useTabStore.getState().updateTab(tabId, { noteId: undefined, - locked: false + locked: false, + noteLocked: false }); }, [commands, editorSessionHistory, postMessage] @@ -431,7 +412,8 @@ export const useEditor = ( if (typeof tabId === "number") { useTabStore.getState().updateTab(tabId, { readonly: event.item.readonly || readonly, - locked: noteIsLocked + locked: noteIsLocked, + noteLocked: noteIsLocked }); useTabStore.getState().focusTab(tabId); setTimeout(() => { @@ -448,7 +430,8 @@ export const useEditor = ( // Otherwise we focus the preview tab or create one to open the note in. useTabStore.getState().focusPreviewTab(event.item.id, { readonly: event.item.readonly || readonly, - locked: noteIsLocked + locked: noteIsLocked, + noteLocked: noteIsLocked }); } } else { @@ -546,48 +529,103 @@ export const useEditor = ( ] ); - const lockNoteWithVault = useCallback((note: Note) => { - eSendEvent(eClearEditor); - openVault({ - item: note, - novault: true, - locked: true, - goToEditor: true, - title: "Open note", - description: "Unlock note to open it in editor." - }); - }, []); - const onSyncComplete = useCallback( - async (data: Note | ContentItem) => { - if (SettingsService.get().disableRealtimeSync) return; + async ( + data: Note | ContentItem | TrashItem | DeletedItem, + isLocal?: boolean + ) => { + console.log("Local changes in editor", isLocal, data?.id); + if (SettingsService.get().disableRealtimeSync && !isLocal) return; if (!data) return; - const noteId = data.type === "tiptap" ? data.noteId : data.id; + + if (isDeleted(data) || isTrashItem(data)) { + const tabId = useTabStore.getState().getTabForNote(data.id); + if (tabId !== undefined) { + useTabStore.getState().removeTab(tabId); + } + return; + } + + const noteId = + (data as ContentItem).type === "tiptap" + ? (data as ContentItem).noteId + : data.id; if (!useTabStore.getState().hasTabForNote(noteId)) return; + const tabId = useTabStore.getState().getTabForNote(noteId) as number; - const tabId = useTabStore.getState().getTabForNote(noteId); - const isContentEncrypted = - typeof (data as ContentItem)?.data === "object"; + const tab = useTabStore.getState().getTab(tabId); - const note = await db.notes?.note(noteId); - - if (lastContentChangeTime.current[noteId] >= (data as Note).dateEdited) - return; + const note = data.type === "note" ? data : await db.notes?.note(noteId); lock.current = true; - if (data.type === "tiptap" && note) { - // Handle this case where note was locked on another device and synced. - const locked = await db.vaults.itemExists( - currentNotes.current[note.id] as ItemReference - ); - if (!locked && isContentEncrypted) { - lockNoteWithVault(note); - } else if (locked && isEncryptedContent(data)) { + // Handle this case where note was locked on another device and synced. + const locked = await db.vaults.itemExists( + currentNotes.current[noteId] as ItemReference + ); + + if (note) { + if (!locked && tab?.noteLocked) { + // Note lock removed. + if (tab.locked) { + if (useTabStore.getState().currentTab === tabId) { + eSendEvent(eOnLoadNote, { + item: note, + forced: true + }); + } else { + useTabStore.getState().updateTab(tabId, { + locked: false, + noteLocked: false + }); + commands.setLoading(true, tabId); + } + } + } else if (!tab?.noteLocked && locked) { + // Note lock added. + useTabStore.getState().updateTab(tabId, { + locked: true, + noteLocked: true + }); + if (useTabStore.getState().currentTab !== tabId) { + commands.clearContent(tabId); + commands.setLoading(true, tabId); + } + } + + if (currentNotes.current[noteId]?.title !== note.title) { + postMessage(EditorEvents.title, note.title, tabId); + } + commands.setTags(note); + if (currentNotes.current[noteId]?.dateEdited !== note.dateEdited) { + commands.setStatus( + getFormattedDate(note.dateEdited, "date-time"), + "Saved", + tabId as number + ); + } + + console.log("readonly state changed...", note.readonly); + useTabStore.getState().updateTab(tabId, { + readonly: note.readonly + }); + } + + if (data.type === "tiptap" && note && !isLocal) { + if (lastContentChangeTime.current[noteId] >= data.dateEdited) return; + + if (locked && isEncryptedContent(data)) { const decryptedContent = await db.vault?.decryptContent(data, noteId); if (!decryptedContent) { - lockNoteWithVault(note); + useTabStore.getState().updateTab(tabId, { + locked: true, + noteLocked: true + }); + if (useTabStore.getState().currentTab !== tabId) { + commands.clearContent(tabId); + commands.setLoading(true, tabId); + } } else { await postMessage( EditorEvents.updatehtml, @@ -605,30 +643,20 @@ export const useEditor = ( currentContents.current[note.id] = data as UnencryptedContentItem; } } - } else { - if (!note) return; - postMessage(EditorEvents.title, note.title, tabId); - commands.setTags(note); - commands.setStatus( - getFormattedDate(note.dateEdited, "date-time"), - "Saved", - tabId as number - ); } lock.current = false; }, - [lockNoteWithVault, postMessage, commands] + [postMessage, commands] ); useEffect(() => { - const syncCompletedSubscription = db.eventManager?.subscribe( - EVENTS.syncItemMerged, - onSyncComplete - ); - eSubscribeEvent(eOnLoadNote + editorId, loadNote); + const subs = [ + db.eventManager.subscribe(EVENTS.syncItemMerged, onSyncComplete), + eSubscribeEvent(eOnLoadNote + editorId, loadNote), + eSubscribeEvent(eUpdateNoteInEditor, onSyncComplete) + ]; return () => { - syncCompletedSubscription?.unsubscribe(); - eUnSubscribeEvent(eOnLoadNote + editorId, loadNote); + subs.forEach((sub) => sub?.unsubscribe()); }; }, [editorId, loadNote, onSyncComplete]); diff --git a/apps/mobile/app/screens/editor/tiptap/use-tab-store.ts b/apps/mobile/app/screens/editor/tiptap/use-tab-store.ts index 69f89e327..1683e01ec 100644 --- a/apps/mobile/app/screens/editor/tiptap/use-tab-store.ts +++ b/apps/mobile/app/screens/editor/tiptap/use-tab-store.ts @@ -18,9 +18,8 @@ along with this program. If not, see . */ import create from "zustand"; import { persist, StateStorage } from "zustand/middleware"; -import { editorController } from "./utils"; import { MMKV } from "../../../common/database/mmkv"; -import { db } from "../../../common/database"; +import { editorController } from "./utils"; class History { history: number[]; @@ -76,6 +75,7 @@ export type TabItem = { previewTab?: boolean; readonly?: boolean; locked?: boolean; + noteLocked?: boolean; }; const history = new History(); diff --git a/apps/mobile/app/services/event-manager.ts b/apps/mobile/app/services/event-manager.ts index b9ef45752..edad58411 100644 --- a/apps/mobile/app/services/event-manager.ts +++ b/apps/mobile/app/services/event-manager.ts @@ -76,8 +76,8 @@ export const eUnSubscribeEvent = ( eventManager.unsubscribe(eventName, action); }; -export const eSendEvent = (eventName: string, data?: unknown) => { - eventManager.publish(eventName, data); +export const eSendEvent = (eventName: string, ...args: any[]) => { + eventManager.publish(eventName, ...args); }; export const openVault = (data: Partial) => { diff --git a/apps/mobile/app/utils/events.js b/apps/mobile/app/utils/events.js index 50f584000..7ce282550 100644 --- a/apps/mobile/app/utils/events.js +++ b/apps/mobile/app/utils/events.js @@ -171,3 +171,4 @@ export const eUnlockNote = "616"; export const eOnChangeFluidTab = "617"; export const eUnlockWithBiometrics = "618"; export const eUnlockWithPassword = "619"; +export const eUpdateNoteInEditor = "620"; diff --git a/apps/mobile/app/utils/functions.js b/apps/mobile/app/utils/functions.js index 3dfc38c99..0f0d0d3bc 100644 --- a/apps/mobile/app/utils/functions.js +++ b/apps/mobile/app/utils/functions.js @@ -25,7 +25,7 @@ import Navigation from "../services/navigation"; import { useMenuStore } from "../stores/use-menu-store"; import { useRelationStore } from "../stores/use-relation-store"; import { useSelectionStore } from "../stores/use-selection-store"; -import { eClearEditor, eOnNotebookUpdated } from "./events"; +import { eOnNotebookUpdated, eUpdateNoteInEditor } from "./events"; import { getParentNotebookId } from "./notebooks"; function confirmDeleteAllNotes(items, type, context) { @@ -92,8 +92,17 @@ export const deleteItems = async (items, type, context) => { continue; } await db.notes.moveToTrash(id); + + eSendEvent( + eUpdateNoteInEditor, + { + type: "trash", + id: id, + itemType: "note" + }, + true + ); } - eSendEvent(eClearEditor); } else if (type === "notebook") { const result = await confirmDeleteAllNotes(ids, "notebook", context); if (!result.delete) return; diff --git a/apps/mobile/native/ios/Podfile.lock b/apps/mobile/native/ios/Podfile.lock index 3b83bf514..5c7ac696c 100644 --- a/apps/mobile/native/ios/Podfile.lock +++ b/apps/mobile/native/ios/Podfile.lock @@ -964,4 +964,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 2b8b28a341b202bf3ca5f231b75bb05893486ed8 -COCOAPODS: 1.12.1 +COCOAPODS: 1.14.2 diff --git a/apps/mobile/package-lock.json b/apps/mobile/package-lock.json index 9a3a1f6d1..253053847 100644 --- a/apps/mobile/package-lock.json +++ b/apps/mobile/package-lock.json @@ -7177,7 +7177,7 @@ }, "../../packages/editor-mobile/node_modules/@types/prop-types": { "version": "15.7.11", - "devOptional": true, + "dev": true, "license": "MIT" }, "../../packages/editor-mobile/node_modules/@types/q": { @@ -7197,7 +7197,7 @@ }, "../../packages/editor-mobile/node_modules/@types/react": { "version": "18.2.39", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -7228,7 +7228,7 @@ }, "../../packages/editor-mobile/node_modules/@types/scheduler": { "version": "0.16.8", - "devOptional": true, + "dev": true, "license": "MIT" }, "../../packages/editor-mobile/node_modules/@types/semver": { @@ -12086,7 +12086,7 @@ }, "../../packages/editor-mobile/node_modules/immer": { "version": "9.0.21", - "devOptional": true, + "dev": true, "license": "MIT", "funding": { "type": "opencollective", @@ -22490,6 +22490,7 @@ }, "../../packages/editor/node_modules/js-tokens": { "version": "4.0.0", + "dev": true, "license": "MIT" }, "../../packages/editor/node_modules/json-parse-even-better-errors": { @@ -22545,6 +22546,7 @@ }, "../../packages/editor/node_modules/loose-envify": { "version": "1.4.0", + "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -23066,6 +23068,7 @@ }, "../../packages/editor/node_modules/react": { "version": "18.2.0", + "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -23084,6 +23087,7 @@ }, "../../packages/editor/node_modules/react-dom": { "version": "18.2.0", + "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", @@ -23203,6 +23207,7 @@ }, "../../packages/editor/node_modules/scheduler": { "version": "0.23.0", + "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -30919,6 +30924,7 @@ }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -30932,6 +30938,7 @@ }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -31078,6 +31085,7 @@ }, "node_modules/@babel/plugin-proposal-private-property-in-object": { "version": "7.21.0-placeholder-for-preset-env.2", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -31088,6 +31096,7 @@ }, "node_modules/@babel/plugin-proposal-unicode-property-regex": { "version": "7.18.6", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", @@ -31133,6 +31142,7 @@ }, "node_modules/@babel/plugin-syntax-class-static-block": { "version": "7.14.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" @@ -31169,6 +31179,7 @@ }, "node_modules/@babel/plugin-syntax-export-namespace-from": { "version": "7.8.3", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.3" @@ -31192,6 +31203,7 @@ }, "node_modules/@babel/plugin-syntax-import-assertions": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -31205,6 +31217,7 @@ }, "node_modules/@babel/plugin-syntax-import-attributes": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -31218,6 +31231,7 @@ }, "node_modules/@babel/plugin-syntax-import-meta": { "version": "7.10.4", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" @@ -31228,6 +31242,7 @@ }, "node_modules/@babel/plugin-syntax-json-strings": { "version": "7.8.3", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" @@ -31251,6 +31266,7 @@ }, "node_modules/@babel/plugin-syntax-logical-assignment-operators": { "version": "7.10.4", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" @@ -31311,6 +31327,7 @@ }, "node_modules/@babel/plugin-syntax-private-property-in-object": { "version": "7.14.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" @@ -31324,6 +31341,7 @@ }, "node_modules/@babel/plugin-syntax-top-level-await": { "version": "7.14.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" @@ -31350,6 +31368,7 @@ }, "node_modules/@babel/plugin-syntax-unicode-sets-regex": { "version": "7.18.6", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", @@ -31377,6 +31396,7 @@ }, "node_modules/@babel/plugin-transform-async-generator-functions": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-environment-visitor": "^7.22.5", @@ -31434,6 +31454,7 @@ }, "node_modules/@babel/plugin-transform-class-properties": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.22.5", @@ -31448,6 +31469,7 @@ }, "node_modules/@babel/plugin-transform-class-static-block": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.22.5", @@ -31511,6 +31533,7 @@ }, "node_modules/@babel/plugin-transform-dotall-regex": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.22.5", @@ -31525,6 +31548,7 @@ }, "node_modules/@babel/plugin-transform-duplicate-keys": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -31538,6 +31562,7 @@ }, "node_modules/@babel/plugin-transform-dynamic-import": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -31566,6 +31591,7 @@ }, "node_modules/@babel/plugin-transform-export-namespace-from": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -31622,6 +31648,7 @@ }, "node_modules/@babel/plugin-transform-json-strings": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -31649,6 +31676,7 @@ }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -31676,6 +31704,7 @@ }, "node_modules/@babel/plugin-transform-modules-amd": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.22.5", @@ -31705,6 +31734,7 @@ }, "node_modules/@babel/plugin-transform-modules-systemjs": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-hoist-variables": "^7.22.5", @@ -31721,6 +31751,7 @@ }, "node_modules/@babel/plugin-transform-modules-umd": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.22.5", @@ -31749,6 +31780,7 @@ }, "node_modules/@babel/plugin-transform-new-target": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -31762,6 +31794,7 @@ }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -31776,6 +31809,7 @@ }, "node_modules/@babel/plugin-transform-numeric-separator": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -31803,6 +31837,7 @@ }, "node_modules/@babel/plugin-transform-object-rest-spread": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.22.5", @@ -31834,6 +31869,7 @@ }, "node_modules/@babel/plugin-transform-optional-catch-binding": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -31848,6 +31884,7 @@ }, "node_modules/@babel/plugin-transform-optional-chaining": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -31876,6 +31913,7 @@ }, "node_modules/@babel/plugin-transform-private-methods": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.22.5", @@ -31890,6 +31928,7 @@ }, "node_modules/@babel/plugin-transform-private-property-in-object": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.22.5", @@ -31989,6 +32028,7 @@ }, "node_modules/@babel/plugin-transform-reserved-words": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -32080,6 +32120,7 @@ }, "node_modules/@babel/plugin-transform-typeof-symbol": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -32109,6 +32150,7 @@ }, "node_modules/@babel/plugin-transform-unicode-escapes": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -32122,6 +32164,7 @@ }, "node_modules/@babel/plugin-transform-unicode-property-regex": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.22.5", @@ -32150,6 +32193,7 @@ }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.22.5", @@ -32164,6 +32208,7 @@ }, "node_modules/@babel/preset-env": { "version": "7.22.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.22.5", @@ -32256,6 +32301,7 @@ }, "node_modules/@babel/preset-env/node_modules/semver": { "version": "6.3.0", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -32278,6 +32324,7 @@ }, "node_modules/@babel/preset-modules": { "version": "0.1.5", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", @@ -34893,6 +34940,7 @@ }, "node_modules/@types/eslint": { "version": "8.40.2", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "*", @@ -34901,6 +34949,7 @@ }, "node_modules/@types/eslint-scope": { "version": "3.7.4", + "dev": true, "license": "MIT", "dependencies": { "@types/eslint": "*", @@ -34909,6 +34958,7 @@ }, "node_modules/@types/estree": { "version": "1.0.1", + "dev": true, "license": "MIT" }, "node_modules/@types/graceful-fs": { @@ -35357,6 +35407,7 @@ }, "node_modules/@webassemblyjs/ast": { "version": "1.11.6", + "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/helper-numbers": "1.11.6", @@ -35365,18 +35416,22 @@ }, "node_modules/@webassemblyjs/floating-point-hex-parser": { "version": "1.11.6", + "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-api-error": { "version": "1.11.6", + "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-buffer": { "version": "1.11.6", + "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-numbers": { "version": "1.11.6", + "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.11.6", @@ -35386,10 +35441,12 @@ }, "node_modules/@webassemblyjs/helper-wasm-bytecode": { "version": "1.11.6", + "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/helper-wasm-section": { "version": "1.11.6", + "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.11.6", @@ -35400,6 +35457,7 @@ }, "node_modules/@webassemblyjs/ieee754": { "version": "1.11.6", + "dev": true, "license": "MIT", "dependencies": { "@xtuc/ieee754": "^1.2.0" @@ -35407,6 +35465,7 @@ }, "node_modules/@webassemblyjs/leb128": { "version": "1.11.6", + "dev": true, "license": "Apache-2.0", "dependencies": { "@xtuc/long": "4.2.2" @@ -35414,10 +35473,12 @@ }, "node_modules/@webassemblyjs/utf8": { "version": "1.11.6", + "dev": true, "license": "MIT" }, "node_modules/@webassemblyjs/wasm-edit": { "version": "1.11.6", + "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.11.6", @@ -35432,6 +35493,7 @@ }, "node_modules/@webassemblyjs/wasm-gen": { "version": "1.11.6", + "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.11.6", @@ -35443,6 +35505,7 @@ }, "node_modules/@webassemblyjs/wasm-opt": { "version": "1.11.6", + "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.11.6", @@ -35453,6 +35516,7 @@ }, "node_modules/@webassemblyjs/wasm-parser": { "version": "1.11.6", + "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.11.6", @@ -35465,6 +35529,7 @@ }, "node_modules/@webassemblyjs/wast-printer": { "version": "1.11.6", + "dev": true, "license": "MIT", "dependencies": { "@webassemblyjs/ast": "1.11.6", @@ -35522,10 +35587,12 @@ }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", + "dev": true, "license": "BSD-3-Clause" }, "node_modules/@xtuc/long": { "version": "4.2.2", + "dev": true, "license": "Apache-2.0" }, "node_modules/@yarnpkg/lockfile": { @@ -35581,6 +35648,7 @@ }, "node_modules/acorn-import-assertions": { "version": "1.9.0", + "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^8" @@ -36682,6 +36750,7 @@ }, "node_modules/chrome-trace-event": { "version": "1.0.3", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0" @@ -37743,6 +37812,7 @@ }, "node_modules/enhanced-resolve": { "version": "5.15.0", + "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -37855,6 +37925,7 @@ }, "node_modules/es-module-lexer": { "version": "1.3.0", + "dev": true, "license": "MIT" }, "node_modules/es-set-tostringtag": { @@ -38185,6 +38256,7 @@ }, "node_modules/eslint-scope": { "version": "5.1.1", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", @@ -38196,6 +38268,7 @@ }, "node_modules/eslint-scope/node_modules/estraverse": { "version": "4.3.0", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -38381,6 +38454,7 @@ }, "node_modules/esrecurse": { "version": "4.3.0", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -38391,6 +38465,7 @@ }, "node_modules/estraverse": { "version": "5.3.0", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -38398,6 +38473,7 @@ }, "node_modules/esutils": { "version": "2.0.3", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -39450,6 +39526,7 @@ }, "node_modules/glob-to-regexp": { "version": "0.4.1", + "dev": true, "license": "BSD-2-Clause" }, "node_modules/global": { @@ -41687,6 +41764,7 @@ }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", + "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { @@ -41952,6 +42030,7 @@ }, "node_modules/loader-runner": { "version": "4.3.0", + "dev": true, "license": "MIT", "engines": { "node": ">=6.11.5" @@ -44574,6 +44653,7 @@ }, "node_modules/randombytes": { "version": "2.1.0", + "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" @@ -45350,6 +45430,7 @@ }, "node_modules/react-test-renderer": { "version": "18.2.0", + "dev": true, "license": "MIT", "dependencies": { "react-is": "^18.2.0", @@ -45362,10 +45443,12 @@ }, "node_modules/react-test-renderer/node_modules/react-is": { "version": "18.2.0", + "dev": true, "license": "MIT" }, "node_modules/react-test-renderer/node_modules/scheduler": { "version": "0.23.0", + "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -46200,6 +46283,7 @@ }, "node_modules/serialize-javascript": { "version": "6.0.1", + "dev": true, "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" @@ -47254,6 +47338,7 @@ }, "node_modules/terser-webpack-plugin": { "version": "5.3.9", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.17", @@ -47286,6 +47371,7 @@ }, "node_modules/terser-webpack-plugin/node_modules/jest-worker": { "version": "27.5.1", + "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", @@ -47298,6 +47384,7 @@ }, "node_modules/terser-webpack-plugin/node_modules/supports-color": { "version": "8.1.1", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -47711,19 +47798,6 @@ "node": ">= 4" } }, - "node_modules/typescript": { - "version": "4.8.4", - "dev": true, - "license": "Apache-2.0", - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, "node_modules/ua-parser-js": { "version": "0.7.35", "funding": [ @@ -48052,6 +48126,7 @@ }, "node_modules/watchpack": { "version": "2.4.0", + "dev": true, "license": "MIT", "dependencies": { "glob-to-regexp": "^0.4.1", @@ -48074,6 +48149,7 @@ }, "node_modules/webpack": { "version": "5.88.2", + "dev": true, "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.3", @@ -48188,6 +48264,7 @@ }, "node_modules/webpack-sources": { "version": "3.2.3", + "dev": true, "license": "MIT", "engines": { "node": ">=10.13.0" diff --git a/packages/editor-mobile/package-lock.json b/packages/editor-mobile/package-lock.json index 62957d375..13e951443 100644 --- a/packages/editor-mobile/package-lock.json +++ b/packages/editor-mobile/package-lock.json @@ -69,6 +69,7 @@ "detect-indent": "^7.0.0", "entities": "^4.5.0", "katex": "0.16.4", + "linkifyjs": "^4.1.3", "nanoid": "^4.0.1", "prism-themes": "^1.9.0", "prosemirror-codemark": "^0.4.2", diff --git a/packages/editor-mobile/src/components/editor.tsx b/packages/editor-mobile/src/components/editor.tsx index ce3ccd44e..1a961377a 100644 --- a/packages/editor-mobile/src/components/editor.tsx +++ b/packages/editor-mobile/src/components/editor.tsx @@ -130,9 +130,9 @@ const Tiptap = ({ }); }, element: getContentDiv(), - editable: !settings.readonly, + editable: !tab.readonly, editorProps: { - editable: () => !settings.readonly + editable: () => !tab.readonly }, content: globalThis.editorControllers[tab.id]?.content?.current, isMobile: true, @@ -168,7 +168,7 @@ const Tiptap = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [ getContentDiv, - settings.readonly, + tab.readonly, settings.doubleSpacedLines, settings.corsProxy, settings.dateFormat, @@ -386,22 +386,22 @@ const Tiptap = ({ > {settings.noHeader || tab.locked ? null : ( <> - + + - {controller.loading ? null : ( - <> - <Title - titlePlaceholder={controller.titlePlaceholder} - readonly={settings.readonly} - controller={controllerRef} - title={controller.title} - fontFamily={settings.fontFamily} - dateFormat={settings.dateFormat} - timeFormat={settings.timeFormat} - /> - <StatusBar container={containerRef} /> - </> - )} + <StatusBar + container={containerRef} + loading={controller.loading} + /> </> )} diff --git a/packages/editor-mobile/src/components/header.tsx b/packages/editor-mobile/src/components/header.tsx index c9ad1f379..23c68937b 100644 --- a/packages/editor-mobile/src/components/header.tsx +++ b/packages/editor-mobile/src/components/header.tsx @@ -26,6 +26,7 @@ import DotsHorizontalIcon from "mdi-react/DotsHorizontalIcon"; import DotsVerticalIcon from "mdi-react/DotsVerticalIcon"; import FullscreenIcon from "mdi-react/FullscreenIcon"; import MagnifyIcon from "mdi-react/MagnifyIcon"; +import PencilLockIcon from "mdi-react/PencilLockIcon"; import TableOfContentsIcon from "mdi-react/TableOfContentsIcon"; import React, { useRef, useState } from "react"; import { useSafeArea } from "../hooks/useSafeArea"; @@ -56,9 +57,11 @@ const Button = ({ children, style, preventDefault = true, - fwdRef + fwdRef, + onClick }: { - onPress: () => void; + onPress?: () => void; + onClick?: (event: any) => void; children: React.ReactNode; style: React.CSSProperties; preventDefault?: boolean; @@ -71,7 +74,8 @@ const Button = ({ style={style} onMouseDown={(e) => { if (preventDefault) e.preventDefault(); - onPress(); + onPress?.(); + onClick?.(e); }} > {children} @@ -219,6 +223,41 @@ function Header({ </Button> ) : null} + {tab.readonly ? ( + <Button + onPress={() => { + post( + "editor-events:disable-readonly-mode", + useTabStore + .getState() + .getNoteIdForTab(useTabStore.getState().currentTab) + ); + }} + fwdRef={btnRef} + preventDefault={false} + style={{ + borderWidth: 0, + borderRadius: 100, + color: "var(--nn_primary_accent)", + marginRight: 12, + width: 39, + height: 39, + display: "flex", + justifyContent: "center", + alignItems: "center", + position: "relative" + }} + > + <PencilLockIcon + size={25 * settings.fontScale} + style={{ + position: "absolute" + }} + color="var(--nn_primary_accent)" + /> + </Button> + ) : null} + <Button onPress={() => { post(EventTypes.showTabs, undefined, tab.id, tab.noteId); @@ -263,10 +302,10 @@ function Header({ </Button> <Button + fwdRef={btnRef} onPress={() => { setOpen(!isOpen); }} - fwdRef={btnRef} preventDefault={false} style={{ borderWidth: 0, @@ -293,7 +332,6 @@ function Header({ <ControlledMenu align="end" anchorRef={btnRef} - transition state={isOpen ? "open" : "closed"} menuClassName={menuClassName} onClose={() => { diff --git a/packages/editor-mobile/src/components/statusbar.tsx b/packages/editor-mobile/src/components/statusbar.tsx index 6775cc1b3..ad29b31d6 100644 --- a/packages/editor-mobile/src/components/statusbar.tsx +++ b/packages/editor-mobile/src/components/statusbar.tsx @@ -21,7 +21,13 @@ import React, { RefObject, useEffect, useRef, useState } from "react"; import { getTotalWords, Editor } from "@notesnook/editor"; import { useTabContext } from "../hooks/useTabStore"; -function StatusBar({ container }: { container: RefObject<HTMLDivElement> }) { +function StatusBar({ + container, + loading +}: { + container: RefObject<HTMLDivElement>; + loading?: boolean; +}) { const [status, setStatus] = useState({ date: "", saved: "" @@ -106,7 +112,7 @@ function StatusBar({ container }: { container: RefObject<HTMLDivElement> }) { <div style={{ flexDirection: "row", - display: "flex", + display: loading ? "none" : "flex", paddingRight: 12, paddingLeft: 12, position: sticky ? "sticky" : "relative", @@ -126,4 +132,7 @@ function StatusBar({ container }: { container: RefObject<HTMLDivElement> }) { ); } -export default React.memo(StatusBar, () => true); +export default React.memo( + StatusBar, + (prev, next) => prev.loading === next.loading +); diff --git a/packages/editor-mobile/src/components/styles.module.css b/packages/editor-mobile/src/components/styles.module.css index 495034ae4..72ad70d79 100644 --- a/packages/editor-mobile/src/components/styles.module.css +++ b/packages/editor-mobile/src/components/styles.module.css @@ -32,7 +32,6 @@ display: none; } - @keyframes menuShow { from { opacity: 0; @@ -51,10 +50,10 @@ z-index: 999; list-style: none; user-select: none; - padding: 6px; + padding: 0px; font-family: sans-serif; font-size: 0.95em; - border: 1px solid rgba(0, 0, 0, 0.1); + border: 1px solid var(--nn_primary_border); box-shadow: 1px 1px 20px 1px rgba(0, 0, 0, 0.1); border-radius: 6px; background-color: var(--nn_secondary_background); @@ -73,12 +72,11 @@ .menuItem { cursor: pointer; - border-radius: 6px; - padding: 0.375rem 0.625rem; + border-radius: 0; + padding: 1rem 0.6rem !important; color: var(--nn_primary_paragraph); - font-family: 'Open Sans'; + font-family: "Open Sans"; padding: 12px 6px; - } .menuItemHover { @@ -102,5 +100,4 @@ height: 1px; margin: 0.1rem 0.1rem; background-color: var(--nn_primary_border); - } diff --git a/packages/editor-mobile/src/components/tags.tsx b/packages/editor-mobile/src/components/tags.tsx index 15fb85624..6c3c37e47 100644 --- a/packages/editor-mobile/src/components/tags.tsx +++ b/packages/editor-mobile/src/components/tags.tsx @@ -22,7 +22,7 @@ import { EventTypes, Settings } from "../utils"; import styles from "./styles.module.css"; import { useTabContext } from "../hooks/useTabStore"; -function Tags(props: { settings: Settings }): JSX.Element { +function Tags(props: { settings: Settings; loading?: boolean }): JSX.Element { const [tags, setTags] = useState< { title: string; alias: string; id: string; type: "tag" }[] >([]); @@ -56,7 +56,8 @@ function Tags(props: { settings: Settings }): JSX.Element { display: "flex", alignItems: "center", overflowX: "scroll", - minHeight: "40px" + minHeight: "40px", + opacity: props.loading ? 0 : 1 }} > <button @@ -135,6 +136,10 @@ function Tags(props: { settings: Settings }): JSX.Element { } export default React.memo(Tags, (prev, next) => { - if (prev.settings.fontScale !== next.settings.fontScale) return false; + if ( + prev.settings.fontScale !== next.settings.fontScale || + prev.loading !== next.loading + ) + return false; return true; }); diff --git a/packages/editor-mobile/src/components/title.tsx b/packages/editor-mobile/src/components/title.tsx index d552b4468..6df8b4a95 100644 --- a/packages/editor-mobile/src/components/title.tsx +++ b/packages/editor-mobile/src/components/title.tsx @@ -30,7 +30,8 @@ function Title({ readonly, fontFamily, dateFormat, - timeFormat + timeFormat, + loading }: { controller: RefObject<EditorController>; title: string; @@ -39,6 +40,7 @@ function Title({ fontFamily: string; dateFormat: string; timeFormat: string; + loading?: boolean; }) { const tab = useTabContext(); const titleRef = useRef<HTMLTextAreaElement>(null); @@ -95,7 +97,8 @@ function Title({ pointerEvents: "none", overflowWrap: "anywhere", paddingTop: 3, - whiteSpace: "break-spaces" + whiteSpace: "break-spaces", + display: loading ? "none" : undefined }} /> <textarea @@ -122,7 +125,8 @@ function Title({ borderRadius: 0, overflow: "hidden", overflowX: "hidden", - overflowY: "hidden" + overflowY: "hidden", + display: loading ? "none" : undefined }} maxLength={1000} onInput={() => { @@ -160,7 +164,8 @@ export default React.memo(Title, (prev, next) => { prev.title !== next.title || prev.titlePlaceholder !== next.titlePlaceholder || prev.readonly !== next.readonly || - prev.fontFamily !== next.fontFamily + prev.fontFamily !== next.fontFamily || + prev.loading !== next.loading ) return false; diff --git a/packages/editor-mobile/src/hooks/useTabStore.ts b/packages/editor-mobile/src/hooks/useTabStore.ts index a03aadc28..79b63807a 100644 --- a/packages/editor-mobile/src/hooks/useTabStore.ts +++ b/packages/editor-mobile/src/hooks/useTabStore.ts @@ -32,6 +32,7 @@ export type TabItem = { previewTab?: boolean; readonly?: boolean; locked?: boolean; + noteLocked?: boolean; }; type NoteState = { diff --git a/packages/editor-mobile/src/utils/index.ts b/packages/editor-mobile/src/utils/index.ts index 87c0367c7..47d630ce8 100644 --- a/packages/editor-mobile/src/utils/index.ts +++ b/packages/editor-mobile/src/utils/index.ts @@ -188,7 +188,8 @@ export const EventTypes = { createInternalLink: "editor-events:create-internal-link", load: "editor-events:load", unlock: "editor-events:unlock", - unlockWithBiometrics: "editor-events:unlock-biometrics" + unlockWithBiometrics: "editor-events:unlock-biometrics", + disableReadonlyMode: "editor-events:disable-readonly-mode" } as const; export function randId(prefix: string) {