mobile: support vault notes in tabs

This commit is contained in:
Ammar Ahmed
2024-01-02 10:54:44 +05:00
committed by Abdullah Atta
parent 2ce31fc81c
commit c33c365b62
15 changed files with 766 additions and 58 deletions

View File

@@ -23,7 +23,6 @@ import { InteractionManager, View } from "react-native";
import Share from "react-native-share"; import Share from "react-native-share";
import { notesnook } from "../../../../e2e/test.ids"; import { notesnook } from "../../../../e2e/test.ids";
import { db } from "../../../common/database"; import { db } from "../../../common/database";
import { editorController } from "../../../screens/editor/tiptap/utils";
import BiometricService from "../../../services/biometrics"; import BiometricService from "../../../services/biometrics";
import { DDS } from "../../../services/device-detection"; import { DDS } from "../../../services/device-detection";
import { import {
@@ -35,7 +34,6 @@ import {
import Navigation from "../../../services/navigation"; import Navigation from "../../../services/navigation";
import { getElevationStyle } from "../../../utils/elevation"; import { getElevationStyle } from "../../../utils/elevation";
import { import {
eClearEditor,
eCloseActionSheet, eCloseActionSheet,
eCloseVaultDialog, eCloseVaultDialog,
eOnLoadNote, eOnLoadNote,

View File

@@ -69,17 +69,6 @@ export const openNote = async (
return; return;
} }
if (await db.vaults.itemExists(note)) {
openVault({
item: note,
novault: true,
locked: true,
goToEditor: true,
title: "Open note",
description: "Unlock note to open it in editor."
});
return;
}
if (isTrash) { if (isTrash) {
if (!note.contentId) return; if (!note.contentId) return;

View File

@@ -22,18 +22,21 @@ import { View } from "react-native";
import { useDBItem } from "../../../hooks/use-db-item"; import { useDBItem } from "../../../hooks/use-db-item";
import { useTabStore } from "../../../screens/editor/tiptap/use-tab-store"; import { useTabStore } from "../../../screens/editor/tiptap/use-tab-store";
import { editorController } from "../../../screens/editor/tiptap/utils"; import { editorController } from "../../../screens/editor/tiptap/utils";
import { presentSheet } from "../../../services/event-manager"; import { eSendEvent, presentSheet } from "../../../services/event-manager";
import { SIZE } from "../../../utils/size"; import { SIZE } from "../../../utils/size";
import { Button } from "../../ui/button"; import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button"; import { IconButton } from "../../ui/icon-button";
import { PressableButton } from "../../ui/pressable"; import { PressableButton } from "../../ui/pressable";
import Heading from "../../ui/typography/heading"; import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph"; import Paragraph from "../../ui/typography/paragraph";
import { eUnlockNote } from "../../../utils/events";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
type TabItem = { type TabItem = {
id: number; id: number;
noteId?: string; noteId?: string;
previewTab?: boolean; previewTab?: boolean;
locked?: boolean;
}; };
const TabItemComponent = (props: { const TabItemComponent = (props: {
@@ -58,6 +61,9 @@ const TabItemComponent = (props: {
if (!props.isFocused) { if (!props.isFocused) {
useTabStore.getState().focusTab(props.tab.id); useTabStore.getState().focusTab(props.tab.id);
props.close?.(); props.close?.();
if (props.tab.locked) {
eSendEvent(eUnlockNote);
}
} }
}} }}
onLongPress={() => { onLongPress={() => {
@@ -70,9 +76,11 @@ const TabItemComponent = (props: {
style={{ style={{
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
justifyContent: "flex-start" justifyContent: "flex-start",
gap: 10
}} }}
> >
{props.tab.locked ? <Icon size={SIZE.md} name="lock" /> : null}
<Paragraph <Paragraph
color={ color={
props.isFocused props.isFocused

View File

@@ -90,6 +90,7 @@ import {
eLoginSessionExpired, eLoginSessionExpired,
eOnLoadNote, eOnLoadNote,
eOpenAnnouncementDialog, eOpenAnnouncementDialog,
eUnlockNote,
eUserLoggedIn, eUserLoggedIn,
refreshNotesPage refreshNotesPage
} from "../utils/events"; } from "../utils/events";
@@ -464,6 +465,27 @@ export const useAppEvents = () => {
EV.subscribe(EVENTS.uploadCanceled, (data) => { EV.subscribe(EVENTS.uploadCanceled, (data) => {
useAttachmentStore.getState().setUploading(data); useAttachmentStore.getState().setUploading(data);
}), }),
EV.subscribe(EVENTS.vaultLocked, async () => {
// Lock all notes in all tabs...
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);
if (note?.locked) {
useTabStore.getState().updateTab(tab.id, {
locked: true
});
if (
tab.id === useTabStore.getState().currentTab &&
note.locked &&
!editorState().movedAway
) {
// Show unlock note screen.
eSendEvent(eUnlockNote);
}
}
}
}),
eSubscribeEvent(eUserLoggedIn, onUserUpdated) eSubscribeEvent(eUserLoggedIn, onUserUpdated)
]; ];

View File

@@ -35,6 +35,7 @@ import {
eUnSubscribeEvent eUnSubscribeEvent
} from "../services/event-manager"; } from "../services/event-manager";
import { eDBItemUpdate } from "../utils/events"; import { eDBItemUpdate } from "../utils/events";
import { useSettingStore } from "../stores/use-setting-store";
type ItemTypeKey = { type ItemTypeKey = {
note: Note; note: Note;
@@ -94,7 +95,15 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
} }
} }
}; };
if (useSettingStore.getState().isAppLoading) {
useSettingStore.subscribe((state) => {
if (!state.isAppLoading) {
onUpdateItem(); onUpdateItem();
}
});
} else {
onUpdateItem();
}
eSubscribeEvent(eDBItemUpdate, onUpdateItem); eSubscribeEvent(eDBItemUpdate, onUpdateItem);
return () => { return () => {
eUnSubscribeEvent(eDBItemUpdate, onUpdateItem); eUnSubscribeEvent(eDBItemUpdate, onUpdateItem);

View File

@@ -65,7 +65,8 @@ import {
eClearEditor, eClearEditor,
eCloseFullscreenEditor, eCloseFullscreenEditor,
eOnLoadNote, eOnLoadNote,
eOpenFullscreenEditor eOpenFullscreenEditor,
eUnlockNote
} from "../utils/events"; } from "../utils/events";
import { editorRef, tabBarRef } from "../utils/global-refs"; import { editorRef, tabBarRef } from "../utils/global-refs";
import { sleep } from "../utils/time"; import { sleep } from "../utils/time";
@@ -516,6 +517,12 @@ const onChangeTab = async (obj) => {
eSendEvent(eOnLoadNote, { eSendEvent(eOnLoadNote, {
newNote: true newNote: true
}); });
} else {
if (
useTabStore.getState().getTab(useTabStore.getState().currentTab).locked
) {
eSendEvent(eUnlockNote);
}
} }
} else { } else {
if (obj.from === 2) { if (obj.from === 2) {

View File

@@ -25,16 +25,31 @@ import React, {
useEffect, useEffect,
useImperativeHandle, useImperativeHandle,
useLayoutEffect, useLayoutEffect,
useRef useRef,
useState
} from "react"; } from "react";
import { Platform, ViewStyle } from "react-native"; import {
Dimensions,
Keyboard,
Platform,
ScrollView,
TextInput,
View,
ViewStyle,
useWindowDimensions
} from "react-native";
import WebView from "react-native-webview"; import WebView from "react-native-webview";
import { ShouldStartLoadRequest } from "react-native-webview/lib/WebViewTypes"; import { ShouldStartLoadRequest } from "react-native-webview/lib/WebViewTypes";
import { notesnook } from "../../../e2e/test.ids"; import { notesnook } from "../../../e2e/test.ids";
import { db } from "../../common/database"; import { db } from "../../common/database";
import { IconButton } from "../../components/ui/icon-button"; import { IconButton } from "../../components/ui/icon-button";
import useKeyboard from "../../hooks/use-keyboard"; import useKeyboard from "../../hooks/use-keyboard";
import { eSubscribeEvent } from "../../services/event-manager"; import {
ToastManager,
eSendEvent,
eSubscribeEvent,
openVault
} from "../../services/event-manager";
import { getElevationStyle } from "../../utils/elevation"; import { getElevationStyle } from "../../utils/elevation";
import { openLinkInBrowser } from "../../utils/functions"; import { openLinkInBrowser } from "../../utils/functions";
import EditorOverlay from "./loading"; import EditorOverlay from "./loading";
@@ -43,7 +58,18 @@ import { EditorProps, useEditorType } from "./tiptap/types";
import { useEditor } from "./tiptap/use-editor"; import { useEditor } from "./tiptap/use-editor";
import { useEditorEvents } from "./tiptap/use-editor-events"; import { useEditorEvents } from "./tiptap/use-editor-events";
import { useTabStore } from "./tiptap/use-tab-store"; import { useTabStore } from "./tiptap/use-tab-store";
import { editorController } from "./tiptap/utils"; import { editorController, editorState } from "./tiptap/utils";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { useThemeColors } from "@notesnook/theme";
import { VaultDialog } from "../../components/dialogs/vault";
import { Button } from "../../components/ui/button";
import Heading from "../../components/ui/typography/heading";
import Seperator from "../../components/ui/seperator";
import Paragraph from "../../components/ui/typography/paragraph";
import { useDBItem } from "../../hooks/use-db-item";
import Input from "../../components/ui/input";
import BiometicService from "../../services/biometrics";
import { eOnLoadNote, eUnlockNote } from "../../utils/events";
const style: ViewStyle = { const style: ViewStyle = {
height: "100%", height: "100%",
@@ -163,6 +189,7 @@ const Editor = React.memo(
/> />
<EditorOverlay editorId={editorId || ""} editor={editor} /> <EditorOverlay editorId={editorId || ""} editor={editor} />
<ReadonlyButton editor={editor} /> <ReadonlyButton editor={editor} />
<LockOverlay />
</> </>
); );
} }
@@ -172,6 +199,216 @@ const Editor = React.memo(
export default Editor; export default Editor;
const LockOverlay = () => {
const tab = useTabStore((state) =>
state.tabs.find((t) => t.id === state.currentTab)
);
const { height } = useWindowDimensions();
const [item] = useDBItem(tab?.noteId, "note");
const isLocked = item?.locked && tab?.locked;
const { colors } = useThemeColors();
const insets = useGlobalSafeAreaInsets();
const password = useRef<string>();
const passInputRef = useRef<TextInput>(null);
const [biometryEnrolled, setBiometryEnrolled] = useState(false);
const [biometryAvailable, setBiometryAvailable] = useState(false);
const [enrollBiometrics, setEnrollBiometrics] = useState(false);
console.log("Tab locked", item?.locked, tab?.locked);
useEffect(() => {
(async () => {
let biometry = await BiometicService.isBiometryAvailable();
let fingerprint = await BiometicService.hasInternetCredentials();
setBiometryAvailable(!!biometry);
setBiometryEnrolled(!!fingerprint);
})();
}, [isLocked]);
const unlockWithBiometrics = async () => {
try {
if (!item || !tab) return;
console.log("Trying to unlock with biometrics...");
let credentials = await BiometicService.getCredentials(
"Unlock note",
"Unlock note to open it in editor. If biometrics are not working, you can enter device pin to unlock vault."
);
if (credentials && credentials?.password) {
let note = await db.vault.open(item.id, credentials?.password);
eSendEvent(eOnLoadNote, {
item: note
});
useTabStore.getState().updateTab(tab.id, {
locked: false
});
}
} catch (e) {
console.error(e);
}
};
useEffect(() => {
const unlock = () => {
if (
isLocked &&
biometryAvailable &&
biometryEnrolled &&
!editorState().movedAway
) {
unlockWithBiometrics();
} else {
console.log("Biometrics unavailable.");
if (isLocked && !editorState().movedAway) {
setTimeout(() => {
passInputRef.current?.focus();
}, 300);
}
}
};
const sub = eSubscribeEvent(eUnlockNote, unlock);
unlock();
return () => {
sub.unsubscribe();
};
}, [isLocked, biometryAvailable, biometryEnrolled]);
const onSubmit = async () => {
if (!item || !tab) return;
if (!password.current || password.current.trim().length === 0) {
ToastManager.show({
heading: "Password not entered",
message: "Enter a password for the vault and try again.",
type: "error"
});
return;
}
try {
let note = await db.vault.open(item.id, password.current);
if (enrollBiometrics) {
try {
await db.vault.unlock(password.current);
await BiometicService.storeCredentials(password.current);
eSendEvent("vaultUpdated");
ToastManager.show({
heading: "Biometric unlocking enabled!",
message: "Now you can unlock notes in vault with biometrics.",
type: "success",
context: "global"
});
} catch (e) {
ToastManager.show({
heading: "Incorrect password",
message:
"Please enter the correct vault password to enable biometrics.",
type: "error",
context: "local"
});
}
}
eSendEvent(eOnLoadNote, {
item: note
});
useTabStore.getState().updateTab(tab.id, {
locked: false
});
} catch (e) {
console.log(e);
ToastManager.show({
heading: "Incorrect password",
type: "error",
context: "local"
});
}
};
return isLocked ? (
<ScrollView
style={{
width: "100%",
height: height,
backgroundColor: colors.primary.background,
position: "absolute",
top: 50 + insets.top,
zIndex: 999
}}
contentContainerStyle={{
alignItems: "center",
justifyContent: "center",
height: height
}}
keyboardDismissMode="interactive"
keyboardShouldPersistTaps="handled"
>
<Heading>{item.title}</Heading>
<Paragraph>This note is locked.</Paragraph>
<Seperator />
<Input
fwdRef={passInputRef}
autoCapitalize="none"
testID={notesnook.ids.dialogs.vault.pwd}
onChangeText={(value) => {
password.current = value;
}}
wrapperStyle={{
width: 300
}}
marginBottom={10}
onSubmit={() => {
onSubmit();
}}
autoComplete="password"
returnKeyLabel="Unlock"
returnKeyType={"done"}
secureTextEntry
placeholder="Password"
/>
<Button
title="Unlock note"
type="accent"
onPress={() => {
onSubmit();
}}
/>
{biometryAvailable && !biometryEnrolled ? (
<Button
title="Enable biometric unlocking"
type={enrollBiometrics ? "accent" : "gray"}
onPress={() => {
setEnrollBiometrics(!enrollBiometrics);
}}
style={{
marginTop: 10
}}
icon={
enrollBiometrics
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
iconSize={20}
/>
) : biometryEnrolled && biometryAvailable ? (
<IconButton
name="fingerprint"
type="gray"
customStyle={{
marginTop: 20
}}
size={40}
onPress={() => {
unlockWithBiometrics();
}}
/>
) : null}
</ScrollView>
) : null;
};
const ReadonlyButton = ({ editor }: { editor: useEditorType }) => { const ReadonlyButton = ({ editor }: { editor: useEditorType }) => {
const readonly = useTabStore( const readonly = useTabStore(
(state) => state.tabs.find((t) => t.id === state.currentTab)?.readonly (state) => state.tabs.find((t) => t.id === state.currentTab)?.readonly

View File

@@ -91,6 +91,10 @@ import {
// 8. If app is killed, restore the note in background. // 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. // 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>;
};
export const useEditor = ( export const useEditor = (
editorId = "", editorId = "",
readonly?: boolean, readonly?: boolean,
@@ -216,7 +220,8 @@ export const useEditor = (
resetContent && (await commands.clearContent(tabId)); resetContent && (await commands.clearContent(tabId));
resetContent && (await commands.clearTags(tabId)); resetContent && (await commands.clearTags(tabId));
useTabStore.getState().updateTab(tabId, { useTabStore.getState().updateTab(tabId, {
noteId: undefined noteId: undefined,
locked: false
}); });
}, },
[commands, editorSessionHistory, postMessage] [commands, editorSessionHistory, postMessage]
@@ -407,14 +412,21 @@ export const useEditor = (
} else { } else {
if (!event.item) return; if (!event.item) return;
const item = event.item; const item = event.item;
const noteIsLocked =
event.item.locked && !(event.item as NoteWithContent).content;
console.log("noteIsLocked", noteIsLocked);
// If note was already opened in a tab, focus that tab. // If note was already opened in a tab, focus that tab.
if (typeof event.tabId !== "number") { if (typeof event.tabId !== "number") {
if (useTabStore.getState().hasTabForNote(event.item.id)) { if (useTabStore.getState().hasTabForNote(event.item.id)) {
const tabId = useTabStore.getState().getTabForNote(event.item.id); const tabId = useTabStore.getState().getTabForNote(event.item.id);
if (typeof tabId === "number") { if (typeof tabId === "number") {
useTabStore.getState().updateTab(tabId, { useTabStore.getState().updateTab(tabId, {
readonly: event.item.readonly || readonly readonly: event.item.readonly || readonly,
locked: noteIsLocked
}); });
console.log(noteIsLocked, "focused tab...");
useTabStore.getState().focusTab(tabId); useTabStore.getState().focusTab(tabId);
} }
console.log("Note already loaded, focusing the tab"); console.log("Note already loaded, focusing the tab");
@@ -423,7 +435,8 @@ export const useEditor = (
// Otherwise we focus the preview tab or create one to open the note in. // Otherwise we focus the preview tab or create one to open the note in.
useTabStore.getState().focusPreviewTab(event.item.id, { useTabStore.getState().focusPreviewTab(event.item.id, {
readonly: event.item.readonly || readonly, readonly: event.item.readonly || readonly,
locked: false locked:
event.item.locked && !(event.item as NoteWithContent).content
}); });
} }
} else { } else {
@@ -446,7 +459,9 @@ export const useEditor = (
state.current.movedAway = false; state.current.movedAway = false;
state.current.currentlyEditing = true; state.current.currentlyEditing = true;
if (!noteIsLocked) {
await loadContent(item); await loadContent(item);
}
if ( if (
currentNotes.current[item.id] && currentNotes.current[item.id] &&

View File

@@ -89,7 +89,7 @@ export type TabStore = {
) => void; ) => void;
removeTab: (index: number) => void; removeTab: (index: number) => void;
moveTab: (index: number, toIndex: number) => void; moveTab: (index: number, toIndex: number) => void;
newTab: (noteId?: string, previewTab?: boolean) => void; newTab: (options?: Omit<Partial<TabItem>, "id">) => void;
focusTab: (id: number) => void; focusTab: (id: number) => void;
getNoteIdForTab: (id: number) => string | undefined; getNoteIdForTab: (id: number) => string | undefined;
getTabForNote: (noteId: string) => number | undefined; getTabForNote: (noteId: string) => number | undefined;
@@ -148,12 +148,17 @@ export const useTabStore = create<TabStore>(
options: Omit<Partial<TabItem>, "id" | "noteId"> options: Omit<Partial<TabItem>, "id" | "noteId">
) => { ) => {
const index = get().tabs.findIndex((t) => t.previewTab); const index = get().tabs.findIndex((t) => t.previewTab);
if (index === -1) return get().newTab(noteId, true); if (index === -1)
return get().newTab({
noteId,
previewTab: true,
...options
});
const tabs = [...get().tabs]; const tabs = [...get().tabs];
tabs[index] = { tabs[index] = {
...tabs[index], ...tabs[index],
previewTab: true,
...options, ...options,
previewTab: true,
noteId: noteId noteId: noteId
}; };
console.log("focus preview", noteId); console.log("focus preview", noteId);
@@ -183,14 +188,13 @@ export const useTabStore = create<TabStore>(
); );
} }
}, },
newTab: (noteId?: string, previewTab?: boolean) => { newTab: (options) => {
const id = getId(get().tabs.length, get().tabs); const id = getId(get().tabs.length, get().tabs);
const nextTabs = [ const nextTabs = [
...get().tabs, ...get().tabs,
{ {
id: id, id: id,
noteId, ...options
previewTab: previewTab
} }
]; ];
set({ set({

View File

@@ -167,3 +167,4 @@ export const eOnRefreshSearch = "612";
export const eOpenAppLockPasswordDialog = "613"; export const eOpenAppLockPasswordDialog = "613";
export const eCloseAppLocKPasswordDailog = "614"; export const eCloseAppLocKPasswordDailog = "614";
export const eEditorTabFocused = "613"; export const eEditorTabFocused = "613";
export const eUnlockNote = "614";

View File

@@ -34,7 +34,7 @@ mergedConfig.resolver = {
resolveRequest: (context, moduleName, platform) => { resolveRequest: (context, moduleName, platform) => {
if (moduleName === "node:crypto") { if (moduleName === "node:crypto") {
return { return {
type: 'empty' type:"empty"
} }
} }
if (moduleName ==='react') { if (moduleName ==='react') {

View File

@@ -19,6 +19,7 @@
"@notesnook/editor": "file:../../packages/editor", "@notesnook/editor": "file:../../packages/editor",
"@notesnook/editor-mobile": "file:../../packages/editor-mobile", "@notesnook/editor-mobile": "file:../../packages/editor-mobile",
"@notesnook/logger": "file:../../packages/logger", "@notesnook/logger": "file:../../packages/logger",
"@notesnook/theme": "file:../../packages/theme",
"@notesnook/themes-server": "file:../../servers/themes", "@notesnook/themes-server": "file:../../servers/themes",
"react": "18.2.0", "react": "18.2.0",
"react-native": "0.72.0" "react-native": "0.72.0"
@@ -32962,6 +32963,10 @@
"resolved": "native", "resolved": "native",
"link": true "link": true
}, },
"node_modules/@notesnook/theme": {
"resolved": "../../packages/theme",
"link": true
},
"node_modules/@notesnook/themes-server": { "node_modules/@notesnook/themes-server": {
"resolved": "../../servers/themes", "resolved": "../../servers/themes",
"link": true "link": true
@@ -78964,6 +78969,430 @@
} }
} }
}, },
"@notesnook/theme": {
"version": "file:../../packages/theme",
"requires": {
"@emotion/react": "11.11.1",
"@theme-ui/color": "^0.16.1",
"@theme-ui/components": "^0.16.1",
"@theme-ui/core": "^0.16.1",
"@trpc/server": "^10.31.0",
"@types/react": "^18.2.39",
"@types/tinycolor2": "^1.4.3",
"isomorphic-fetch": "^3.0.0",
"react": "18.2.0",
"tinycolor2": "^1.6.0",
"ts-json-schema-generator": "^1.2.0",
"zustand": "4.4.7"
},
"dependencies": {
"@babel/runtime": {
"version": "7.22.6",
"dev": true,
"requires": {
"regenerator-runtime": "^0.13.11"
}
},
"@emotion/is-prop-valid": {
"version": "0.8.8",
"dev": true,
"requires": {
"@emotion/memoize": "0.7.4"
},
"dependencies": {
"@emotion/memoize": {
"version": "0.7.4",
"dev": true
}
}
},
"@styled-system/background": {
"version": "5.1.2",
"dev": true,
"requires": {
"@styled-system/core": "^5.1.2"
}
},
"@styled-system/border": {
"version": "5.1.5",
"dev": true,
"requires": {
"@styled-system/core": "^5.1.2"
}
},
"@styled-system/color": {
"version": "5.1.2",
"dev": true,
"requires": {
"@styled-system/core": "^5.1.2"
}
},
"@styled-system/core": {
"version": "5.1.2",
"dev": true,
"requires": {
"object-assign": "^4.1.1"
}
},
"@styled-system/css": {
"version": "5.1.5",
"dev": true
},
"@styled-system/flexbox": {
"version": "5.1.2",
"dev": true,
"requires": {
"@styled-system/core": "^5.1.2"
}
},
"@styled-system/grid": {
"version": "5.1.2",
"dev": true,
"requires": {
"@styled-system/core": "^5.1.2"
}
},
"@styled-system/layout": {
"version": "5.1.2",
"dev": true,
"requires": {
"@styled-system/core": "^5.1.2"
}
},
"@styled-system/position": {
"version": "5.1.2",
"dev": true,
"requires": {
"@styled-system/core": "^5.1.2"
}
},
"@styled-system/shadow": {
"version": "5.1.2",
"dev": true,
"requires": {
"@styled-system/core": "^5.1.2"
}
},
"@styled-system/should-forward-prop": {
"version": "5.1.5",
"dev": true,
"requires": {
"@emotion/is-prop-valid": "^0.8.1",
"@emotion/memoize": "^0.7.1",
"styled-system": "^5.1.5"
},
"dependencies": {
"@emotion/memoize": {
"version": "0.7.5",
"dev": true
}
}
},
"@styled-system/space": {
"version": "5.1.2",
"dev": true,
"requires": {
"@styled-system/core": "^5.1.2"
}
},
"@styled-system/typography": {
"version": "5.1.2",
"dev": true,
"requires": {
"@styled-system/core": "^5.1.2"
}
},
"@styled-system/variant": {
"version": "5.1.5",
"dev": true,
"requires": {
"@styled-system/core": "^5.1.2",
"@styled-system/css": "^5.1.5"
}
},
"@theme-ui/color": {
"version": "0.16.0",
"dev": true,
"requires": {
"@theme-ui/css": "^0.16.0",
"polished": "^4.0.5"
},
"dependencies": {
"@theme-ui/css": {
"version": "0.16.0",
"dev": true,
"requires": {
"csstype": "^3.0.10"
}
}
}
},
"@theme-ui/components": {
"version": "0.14.7",
"dev": true,
"requires": {
"@styled-system/color": "^5.1.2",
"@styled-system/should-forward-prop": "^5.1.2",
"@styled-system/space": "^5.1.2",
"@theme-ui/css": "0.14.7",
"@types/styled-system": "^5.1.13"
}
},
"@theme-ui/core": {
"version": "0.14.7",
"dev": true,
"requires": {
"@theme-ui/css": "0.14.7",
"@theme-ui/parse-props": "0.14.7",
"deepmerge": "^4.2.2"
}
},
"@theme-ui/css": {
"version": "0.14.7",
"dev": true,
"requires": {
"csstype": "^3.0.10"
}
},
"@theme-ui/parse-props": {
"version": "0.14.7",
"dev": true,
"requires": {
"@theme-ui/css": "0.14.7"
}
},
"@trpc/server": {
"version": "10.38.3",
"dev": true
},
"@types/json-schema": {
"version": "7.0.12",
"dev": true
},
"@types/prop-types": {
"version": "15.7.5",
"dev": true
},
"@types/react": {
"version": "17.0.2",
"dev": true,
"requires": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
}
},
"@types/styled-system": {
"version": "5.1.16",
"dev": true,
"requires": {
"csstype": "^3.0.2"
}
},
"@types/tinycolor2": {
"version": "1.4.3",
"dev": true
},
"balanced-match": {
"version": "1.0.2",
"dev": true
},
"commander": {
"version": "9.5.0",
"dev": true
},
"csstype": {
"version": "3.1.1",
"dev": true
},
"deepmerge": {
"version": "4.2.2",
"dev": true
},
"fs.realpath": {
"version": "1.0.0",
"dev": true
},
"inflight": {
"version": "1.0.6",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.4",
"dev": true
},
"isomorphic-fetch": {
"version": "3.0.0",
"dev": true,
"requires": {
"node-fetch": "^2.6.1",
"whatwg-fetch": "^3.4.1"
}
},
"js-tokens": {
"version": "4.0.0",
"dev": true
},
"json5": {
"version": "2.2.3",
"dev": true
},
"loose-envify": {
"version": "1.4.0",
"dev": true,
"requires": {
"js-tokens": "^3.0.0 || ^4.0.0"
}
},
"node-fetch": {
"version": "2.6.12",
"dev": true,
"requires": {
"whatwg-url": "^5.0.0"
}
},
"normalize-path": {
"version": "3.0.0",
"dev": true
},
"object-assign": {
"version": "4.1.1",
"dev": true
},
"once": {
"version": "1.4.0",
"dev": true,
"requires": {
"wrappy": "1"
}
},
"polished": {
"version": "4.2.2",
"dev": true,
"requires": {
"@babel/runtime": "^7.17.8"
}
},
"react": {
"version": "17.0.2",
"dev": true,
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1"
}
},
"regenerator-runtime": {
"version": "0.13.11",
"dev": true
},
"safe-stable-stringify": {
"version": "2.4.3",
"dev": true
},
"styled-system": {
"version": "5.1.5",
"dev": true,
"requires": {
"@styled-system/background": "^5.1.2",
"@styled-system/border": "^5.1.5",
"@styled-system/color": "^5.1.2",
"@styled-system/core": "^5.1.2",
"@styled-system/flexbox": "^5.1.2",
"@styled-system/grid": "^5.1.2",
"@styled-system/layout": "^5.1.2",
"@styled-system/position": "^5.1.2",
"@styled-system/shadow": "^5.1.2",
"@styled-system/space": "^5.1.2",
"@styled-system/typography": "^5.1.2",
"@styled-system/variant": "^5.1.5",
"object-assign": "^4.1.1"
}
},
"tr46": {
"version": "0.0.3",
"dev": true
},
"ts-json-schema-generator": {
"version": "1.2.0",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.11",
"commander": "^9.4.1",
"glob": "^8.0.3",
"json5": "^2.2.1",
"normalize-path": "^3.0.0",
"safe-stable-stringify": "^2.4.1",
"typescript": "~4.9.3"
},
"dependencies": {
"brace-expansion": {
"version": "2.0.1",
"dev": true,
"requires": {
"balanced-match": "^1.0.0"
}
},
"glob": {
"version": "8.1.0",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^5.0.1",
"once": "^1.3.0"
}
},
"minimatch": {
"version": "5.1.6",
"dev": true,
"requires": {
"brace-expansion": "^2.0.1"
}
}
}
},
"typescript": {
"version": "4.9.5",
"dev": true
},
"use-sync-external-store": {
"version": "1.2.0",
"dev": true,
"requires": {}
},
"webidl-conversions": {
"version": "3.0.1",
"dev": true
},
"whatwg-fetch": {
"version": "3.6.2",
"dev": true
},
"whatwg-url": {
"version": "5.0.0",
"dev": true,
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"wrappy": {
"version": "1.0.2",
"dev": true
},
"zustand": {
"version": "4.3.8",
"dev": true,
"requires": {
"use-sync-external-store": "1.2.0"
}
}
}
},
"@notesnook/themes-server": { "@notesnook/themes-server": {
"version": "file:../../servers/themes", "version": "file:../../servers/themes",
"requires": { "requires": {

View File

@@ -4360,7 +4360,7 @@
"version": "15.7.11", "version": "15.7.11",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==", "integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
"devOptional": true "dev": true
}, },
"node_modules/@types/q": { "node_modules/@types/q": {
"version": "1.5.8", "version": "1.5.8",
@@ -4384,7 +4384,7 @@
"version": "18.2.39", "version": "18.2.39",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.39.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.39.tgz",
"integrity": "sha512-Oiw+ppED6IremMInLV4HXGbfbG6GyziY3kqAwJYOR0PNbkYDmLWQA3a95EhdSmamsvbkJN96ZNN+YD+fGjzSBA==", "integrity": "sha512-Oiw+ppED6IremMInLV4HXGbfbG6GyziY3kqAwJYOR0PNbkYDmLWQA3a95EhdSmamsvbkJN96ZNN+YD+fGjzSBA==",
"devOptional": true, "dev": true,
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
"@types/scheduler": "*", "@types/scheduler": "*",
@@ -4419,7 +4419,7 @@
"version": "0.16.8", "version": "0.16.8",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
"devOptional": true "dev": true
}, },
"node_modules/@types/semver": { "node_modules/@types/semver": {
"version": "7.5.6", "version": "7.5.6",
@@ -9715,7 +9715,7 @@
"version": "9.0.21", "version": "9.0.21",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
"devOptional": true, "dev": true,
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
"url": "https://opencollective.com/immer" "url": "https://opencollective.com/immer"
@@ -17776,20 +17776,6 @@
"is-typedarray": "^1.0.0" "is-typedarray": "^1.0.0"
} }
}, },
"node_modules/typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"dev": true,
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"node_modules/unbox-primitive": { "node_modules/unbox-primitive": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",

View File

@@ -49,6 +49,7 @@ import Header from "./header";
import StatusBar from "./statusbar"; import StatusBar from "./statusbar";
import Tags from "./tags"; import Tags from "./tags";
import Title from "./title"; import Title from "./title";
import FingerprintIcon from "mdi-react/FingerprintIcon";
globalThis.toBlobURL = toBlobURL as typeof globalThis.toBlobURL; globalThis.toBlobURL = toBlobURL as typeof globalThis.toBlobURL;
@@ -322,7 +323,8 @@ const Tiptap = ({ settings }: { settings: Settings }) => {
style={{ style={{
overflowY: "scroll", overflowY: "scroll",
height: "100%", height: "100%",
display: "block" display: "block",
position: "relative"
}} }}
> >
{settings.noHeader ? null : ( {settings.noHeader ? null : (
@@ -341,14 +343,14 @@ const Tiptap = ({ settings }: { settings: Settings }) => {
</> </>
)} )}
{controller.loading ? ( {controller.loading || tab.locked ? (
<div <div
style={{ style={{
height: "100%",
width: "100%", width: "100%",
height: "95%",
position: "absolute", position: "absolute",
zIndex: 999, zIndex: 999,
backgroundColor: "white", backgroundColor: colors.primary.background,
paddingRight: 12, paddingRight: 12,
paddingLeft: 12, paddingLeft: 12,
display: "flex", display: "flex",
@@ -411,7 +413,7 @@ const Tiptap = ({ settings }: { settings: Settings }) => {
/> />
</div> </div>
{!layout ? null : ( {!layout || tab.locked ? null : (
<EmotionEditorToolbarTheme> <EmotionEditorToolbarTheme>
<Toolbar <Toolbar
className="theme-scope-editorToolbar" className="theme-scope-editorToolbar"

View File

@@ -22,15 +22,16 @@ import { createJSONStorage, persist } from "zustand/middleware";
globalThis.editorControllers = {}; globalThis.editorControllers = {};
globalThis.editors = {}; globalThis.editors = {};
global.editorTags = {}; globalThis.editorTags = {};
global.editorTitles = {}; globalThis.editorTitles = {};
global.statusBars = {}; globalThis.statusBars = {};
export type TabItem = { export type TabItem = {
id: number; id: number;
noteId?: string; noteId?: string;
previewTab?: boolean; previewTab?: boolean;
readonly?: boolean; readonly?: boolean;
locked?: boolean;
}; };
type NoteState = { type NoteState = {