mobile: fix tab bugs

This commit is contained in:
Ammar Ahmed
2024-03-14 10:43:25 +05:00
committed by Abdullah Atta
parent ad41544403
commit 5d2853d002
26 changed files with 489 additions and 245 deletions

View File

@@ -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<Attachment>) => void;
close: () => void;
close?: () => void;
fwdRef: RefObject<ActionSheetRef>;
}) => {
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"
}

View File

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

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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 (
<Pressable
@@ -87,7 +109,18 @@ const TabItemComponent = (props: {
gap: 10
}}
>
{props.tab.locked ? <Icon size={SIZE.md} name="lock" /> : null}
{props.tab.noteLocked ? (
<>
{props.tab.locked ? (
<Icon size={SIZE.md} name="lock" />
) : (
<Icon size={SIZE.md} name="lock-open-outline" />
)}
</>
) : null}
{props.tab.readonly ? <Icon size={SIZE.md} name="pencil-lock" /> : null}
<Paragraph
color={
props.isFocused

View File

@@ -242,7 +242,6 @@ const ListNoteItem = ({
if (noteInternalLinks.length) return;
setLoading(true);
const blocks = await db.notes.contentBlocks(item.id);
setNoteInternalLinks(
blocks
.filter((block) =>
@@ -272,7 +271,7 @@ const ListNoteItem = ({
listType,
noteInternalLinks.length
]);
console.log(noteInternalLinks);
const renderBlock = React.useCallback(
(block: ContentBlock) => (
<ListBlockItem

View File

@@ -61,7 +61,7 @@ import { useSelectionStore } from "../stores/use-selection-store";
import { useTagStore } from "../stores/use-tag-store";
import { useUserStore } from "../stores/use-user-store";
import Errors from "../utils/errors";
import { eOpenLoginDialog } from "../utils/events";
import { eOpenLoginDialog, eUpdateNoteInEditor } from "../utils/events";
import { deleteItems } from "../utils/functions";
import { convertNoteToText } from "../utils/note-to-text";
import { sleep } from "../utils/time";
@@ -704,10 +704,11 @@ export const useActions = ({
}
try {
await db.vault.add(item.id);
const note = await db.notes.note(item.id);
const locked = await db.vaults.itemExists(item);
if (locked) {
close();
Navigation.queueRoutesForUpdate();
eSendEvent(eUpdateNoteInEditor, item, true);
}
} catch (e: any) {
close();

View File

@@ -496,7 +496,11 @@ const _TabsHolder = () => {
</View>
<ScopedThemeProvider value="editor">
<EditorWrapper key="3" width={widths} dimensions={dimensions} />
<EditorWrapper
key="3"
widths={widths}
dimensions={dimensions}
/>
</ScopedThemeProvider>
</FluidTabs>
) : null}

View File

@@ -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}
/>
{/* <EditorOverlay editorId={editorId || ""} editor={editor} /> */}
<ReadonlyButton editor={editor} />
<LockOverlay />
</>
);
@@ -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 ? (
<IconButton
name="pencil-lock"
type="secondary"
onPress={onPress}
color={colors.primary.accent}
style={{
position: "absolute",
bottom: 60,
width: 60,
height: 60,
right: 12,
...getElevationStyle(5)
}}
/>
) : null;
};

View File

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

View File

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

View File

@@ -154,7 +154,6 @@ export const useEditorEvents = (
state.dateFormat,
state.timeFormat
]);
const handleBack = useRef<NativeEventSubscription>();
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;
}

View File

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

View File

@@ -18,9 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
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();

View File

@@ -76,8 +76,8 @@ export const eUnSubscribeEvent = <T = unknown>(
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<Vault>) => {

View File

@@ -171,3 +171,4 @@ export const eUnlockNote = "616";
export const eOnChangeFluidTab = "617";
export const eUnlockWithBiometrics = "618";
export const eUnlockWithPassword = "619";
export const eUpdateNoteInEditor = "620";

View File

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

View File

@@ -964,4 +964,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 2b8b28a341b202bf3ca5f231b75bb05893486ed8
COCOAPODS: 1.12.1
COCOAPODS: 1.14.2

View File

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

View File

@@ -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",

View File

@@ -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 : (
<>
<Tags settings={settings} />
<Tags settings={settings} loading={controller.loading} />
<Title
titlePlaceholder={controller.titlePlaceholder}
readonly={settings.readonly}
controller={controllerRef}
title={controller.title}
fontFamily={settings.fontFamily}
dateFormat={settings.dateFormat}
timeFormat={settings.timeFormat}
loading={controller.loading}
/>
{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}
/>
</>
)}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -32,6 +32,7 @@ export type TabItem = {
previewTab?: boolean;
readonly?: boolean;
locked?: boolean;
noteLocked?: boolean;
};
type NoteState = {

View File

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