mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-19 04:59:33 +01:00
mobile: fix minor bugs in tabs
This commit is contained in:
committed by
Abdullah Atta
parent
08c03bc3a4
commit
3efe977c49
@@ -26,7 +26,7 @@ import { eSendEvent, presentSheet } from "../../../services/event-manager";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Button } from "../../ui/button";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import { PressableButton } from "../../ui/pressable";
|
||||
import { Pressable } from "../../ui/pressable";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { eUnlockNote } from "../../../utils/events";
|
||||
@@ -48,8 +48,8 @@ const TabItemComponent = (props: {
|
||||
const [item] = useDBItem(props.tab.noteId, "note");
|
||||
|
||||
return (
|
||||
<PressableButton
|
||||
customStyle={{
|
||||
<Pressable
|
||||
style={{
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: "row",
|
||||
@@ -114,7 +114,7 @@ const TabItemComponent = (props: {
|
||||
right={20}
|
||||
bottom={0}
|
||||
/>
|
||||
</PressableButton>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ import { presentSheet } from "../../../services/event-manager";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Button } from "../../ui/button";
|
||||
import Input from "../../ui/input";
|
||||
import { PressableButton } from "../../ui/pressable";
|
||||
import { Pressable } from "../../ui/pressable";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import type { LinkAttributes } from "@notesnook/editor/dist/extensions/link";
|
||||
import { editorController } from "../../../screens/editor/tiptap/utils";
|
||||
@@ -48,13 +48,13 @@ const ListNoteItem = ({
|
||||
}) => {
|
||||
const [item] = useDBItem(id, "note", items);
|
||||
return (
|
||||
<PressableButton
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
if (!item) return;
|
||||
onSelectNote(item as Note);
|
||||
}}
|
||||
type={"transparent"}
|
||||
customStyle={{
|
||||
style={{
|
||||
paddingVertical: 12,
|
||||
flexDirection: "row",
|
||||
width: "100%",
|
||||
@@ -69,7 +69,7 @@ const ListNoteItem = ({
|
||||
>
|
||||
<Paragraph numberOfLines={1}>{item?.title}</Paragraph>
|
||||
</View>
|
||||
</PressableButton>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -82,12 +82,12 @@ const ListBlockItem = ({
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
return (
|
||||
<PressableButton
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
onSelectBlock(item);
|
||||
}}
|
||||
type={"transparent"}
|
||||
customStyle={{
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
width: "100%",
|
||||
justifyContent: "flex-start",
|
||||
@@ -127,7 +127,7 @@ const ListBlockItem = ({
|
||||
: item.content}
|
||||
</Paragraph>
|
||||
</View>
|
||||
</PressableButton>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -198,7 +198,7 @@ export default function LinkNote(props: {
|
||||
setSelectedNote(note);
|
||||
inputRef.current?.clear();
|
||||
setTimeout(async () => {
|
||||
nodesRef.current = await db.notes.getBlocks(note.id);
|
||||
nodesRef.current = await db.notes.contentBlocks(note.id);
|
||||
setNodes(nodesRef.current);
|
||||
});
|
||||
// Fetch and set note's nodes.
|
||||
@@ -249,13 +249,13 @@ export default function LinkNote(props: {
|
||||
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
|
||||
SELECTED NOTE
|
||||
</Paragraph>
|
||||
<PressableButton
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
setSelectedNote(undefined);
|
||||
setSelectedNodeId(undefined);
|
||||
setNodes([]);
|
||||
}}
|
||||
customStyle={{
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
width: "100%",
|
||||
justifyContent: "flex-start",
|
||||
@@ -264,7 +264,7 @@ export default function LinkNote(props: {
|
||||
borderColor: colors.primary.accent,
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
type="grayAccent"
|
||||
type="secondaryAccented"
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
@@ -280,7 +280,7 @@ export default function LinkNote(props: {
|
||||
Tap to deselect
|
||||
</Paragraph>
|
||||
</View>
|
||||
</PressableButton>
|
||||
</Pressable>
|
||||
|
||||
{nodes?.length > 0 ? (
|
||||
<Paragraph
|
||||
|
||||
@@ -39,7 +39,7 @@ import { SIZE } from "../../../utils/size";
|
||||
import SheetProvider from "../../sheet-provider";
|
||||
import { Button } from "../../ui/button";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import { PressableButton } from "../../ui/pressable";
|
||||
import { Pressable } from "../../ui/pressable";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
|
||||
export const useExpandedStore = create<{
|
||||
@@ -68,12 +68,12 @@ const ListBlockItem = ({
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
return (
|
||||
<PressableButton
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
onSelectBlock();
|
||||
}}
|
||||
type={"transparent"}
|
||||
customStyle={{
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
width: "100%",
|
||||
paddingLeft: 35,
|
||||
@@ -114,7 +114,7 @@ const ListBlockItem = ({
|
||||
: item.content}
|
||||
</Paragraph>
|
||||
</View>
|
||||
</PressableButton>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -130,12 +130,12 @@ const ListNoteInternalLink = ({
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
return (
|
||||
<PressableButton
|
||||
<Pressable
|
||||
onPress={() => {
|
||||
onSelect();
|
||||
}}
|
||||
type={"transparent"}
|
||||
customStyle={{
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
width: "100%",
|
||||
paddingLeft: 35,
|
||||
@@ -177,7 +177,7 @@ const ListNoteInternalLink = ({
|
||||
</Paragraph>
|
||||
))}
|
||||
</View>
|
||||
</PressableButton>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -306,13 +306,13 @@ const ListNoteItem = ({
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<PressableButton
|
||||
type={"gray"}
|
||||
<Pressable
|
||||
type={"plain"}
|
||||
onPress={() => {
|
||||
if (!item) return;
|
||||
onSelect(item as Note);
|
||||
}}
|
||||
customStyle={{
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
@@ -330,14 +330,14 @@ const ListNoteItem = ({
|
||||
left={0}
|
||||
bottom={0}
|
||||
right={0}
|
||||
customStyle={{
|
||||
style={{
|
||||
width: 35,
|
||||
height: 35
|
||||
}}
|
||||
name={expanded ? "chevron-down" : "chevron-right"}
|
||||
/>
|
||||
<Paragraph numberOfLines={1}>{item?.title}</Paragraph>
|
||||
</PressableButton>
|
||||
</Pressable>
|
||||
|
||||
{expanded && !item?.locked ? (
|
||||
<View
|
||||
@@ -441,7 +441,7 @@ export const ReferencesList = ({ item, close }: ReferencesListProps) => {
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
type={"gray"}
|
||||
type={"plain"}
|
||||
title="Linked notes"
|
||||
style={{
|
||||
borderRadius: 0,
|
||||
@@ -455,7 +455,7 @@ export const ReferencesList = ({ item, close }: ReferencesListProps) => {
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type={"gray"}
|
||||
type={"plain"}
|
||||
title="Referenced in"
|
||||
style={{
|
||||
width: "50%",
|
||||
|
||||
@@ -1,7 +1,25 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { PressableButton } from "../../ui/pressable";
|
||||
import { Pressable } from "../../ui/pressable";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { presentSheet } from "../../../services/event-manager";
|
||||
@@ -33,8 +51,8 @@ const TableOfContentsItem: React.FC<{
|
||||
const { colors } = useThemeColors();
|
||||
|
||||
return (
|
||||
<PressableButton
|
||||
customStyle={{
|
||||
<Pressable
|
||||
style={{
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
flexDirection: "row",
|
||||
@@ -72,7 +90,7 @@ const TableOfContentsItem: React.FC<{
|
||||
{item?.title || "New note"}
|
||||
</Paragraph>
|
||||
</View>
|
||||
</PressableButton>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -471,13 +471,14 @@ export const useAppEvents = () => {
|
||||
const noteId = useTabStore.getState().getTab(tab.id)?.noteId;
|
||||
if (!noteId) continue;
|
||||
const note = await db.notes.note(noteId);
|
||||
if (note?.locked) {
|
||||
const locked = note && (await db.vaults.itemExists(note));
|
||||
if (locked) {
|
||||
useTabStore.getState().updateTab(tab.id, {
|
||||
locked: true
|
||||
});
|
||||
if (
|
||||
tab.id === useTabStore.getState().currentTab &&
|
||||
note.locked &&
|
||||
locked &&
|
||||
!editorState().movedAway
|
||||
) {
|
||||
// Show unlock note screen.
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
deactivateKeepAwake
|
||||
} from "@sayem314/react-native-keep-awake";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Dimensions, Platform, StatusBar, View } from "react-native";
|
||||
import { Dimensions, Keyboard, Platform, StatusBar, View } from "react-native";
|
||||
import changeNavigationBarColor from "react-native-navigation-bar-color";
|
||||
import {
|
||||
addOrientationListener,
|
||||
@@ -64,11 +64,12 @@ import { useSettingStore } from "../stores/use-setting-store";
|
||||
import {
|
||||
eClearEditor,
|
||||
eCloseFullscreenEditor,
|
||||
eOnChangeFluidTab,
|
||||
eOnLoadNote,
|
||||
eOpenFullscreenEditor,
|
||||
eUnlockNote
|
||||
} from "../utils/events";
|
||||
import { editorRef, tabBarRef } from "../utils/global-refs";
|
||||
import { editorRef, inputRef, tabBarRef } from "../utils/global-refs";
|
||||
import { sleep } from "../utils/time";
|
||||
import { NavigationStack } from "./navigation-stack";
|
||||
|
||||
@@ -512,7 +513,6 @@ const onChangeTab = async (obj) => {
|
||||
editorState().movedAway = false;
|
||||
editorState().isFocused = true;
|
||||
activateKeepAwake();
|
||||
|
||||
if (!useTabStore.getState().getCurrentNoteId()) {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
newNote: true
|
||||
@@ -521,6 +521,7 @@ const onChangeTab = async (obj) => {
|
||||
if (
|
||||
useTabStore.getState().getTab(useTabStore.getState().currentTab).locked
|
||||
) {
|
||||
console.log("Unlocking note....");
|
||||
eSendEvent(eUnlockNote);
|
||||
}
|
||||
}
|
||||
@@ -530,13 +531,18 @@ const onChangeTab = async (obj) => {
|
||||
editorState().movedAway = true;
|
||||
editorState().isFocused = false;
|
||||
eSendEvent(eClearEditor, "removeHandler");
|
||||
let id = useTabStore.getState().getCurrentNoteId();
|
||||
let note = await db.notes.note(id);
|
||||
const locked = note && (await db.vaults.itemExists(note));
|
||||
if (locked) {
|
||||
useTabStore.getState().updateTab(useTabStore.getState().currentTab, {
|
||||
locked: true
|
||||
});
|
||||
|
||||
// Lock all tabs with locked notes...
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,28 +26,15 @@ import React, {
|
||||
useEffect,
|
||||
useImperativeHandle,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState
|
||||
useRef
|
||||
} from "react";
|
||||
import {
|
||||
Platform,
|
||||
ScrollView,
|
||||
TextInput,
|
||||
ViewStyle,
|
||||
useWindowDimensions
|
||||
} from "react-native";
|
||||
import { Platform, TextInput, ViewStyle } from "react-native";
|
||||
import WebView from "react-native-webview";
|
||||
import { ShouldStartLoadRequest } from "react-native-webview/lib/WebViewTypes";
|
||||
import { notesnook } from "../../../e2e/test.ids";
|
||||
import { db } from "../../common/database";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { IconButton } from "../../components/ui/icon-button";
|
||||
import Input from "../../components/ui/input";
|
||||
import Seperator from "../../components/ui/seperator";
|
||||
import Heading from "../../components/ui/typography/heading";
|
||||
import Paragraph from "../../components/ui/typography/paragraph";
|
||||
import { useDBItem } from "../../hooks/use-db-item";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import useKeyboard from "../../hooks/use-keyboard";
|
||||
import BiometicService from "../../services/biometrics";
|
||||
import {
|
||||
@@ -55,15 +42,20 @@ import {
|
||||
eSendEvent,
|
||||
eSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { getElevationStyle } from "../../utils/elevation";
|
||||
import { eOnLoadNote, eUnlockNote } from "../../utils/events";
|
||||
import {
|
||||
eOnLoadNote,
|
||||
eUnlockNote,
|
||||
eUnlockWithBiometrics,
|
||||
eUnlockWithPassword
|
||||
} from "../../utils/events";
|
||||
import { openLinkInBrowser } from "../../utils/functions";
|
||||
import EditorOverlay from "./loading";
|
||||
import { EDITOR_URI } from "./source";
|
||||
import { EditorProps, useEditorType } from "./tiptap/types";
|
||||
import { useEditor } from "./tiptap/use-editor";
|
||||
import { useEditorEvents } from "./tiptap/use-editor-events";
|
||||
import { useTabStore } from "./tiptap/use-tab-store";
|
||||
import { syncTabs, useTabStore } from "./tiptap/use-tab-store";
|
||||
import { editorController, editorState } from "./tiptap/utils";
|
||||
|
||||
const style: ViewStyle = {
|
||||
@@ -181,7 +173,7 @@ const Editor = React.memo(
|
||||
autoManageStatusBarEnabled={false}
|
||||
onMessage={onMessage || undefined}
|
||||
/>
|
||||
<EditorOverlay editorId={editorId || ""} editor={editor} />
|
||||
{/* <EditorOverlay editorId={editorId || ""} editor={editor} /> */}
|
||||
<ReadonlyButton editor={editor} />
|
||||
<LockOverlay />
|
||||
</>
|
||||
@@ -193,214 +185,169 @@ const Editor = React.memo(
|
||||
|
||||
export default Editor;
|
||||
|
||||
let LOADED = false;
|
||||
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);
|
||||
const isAppLoading = useSettingStore((state) => state.isAppLoading);
|
||||
const [item] = useDBItem(isAppLoading ? undefined : tab?.noteId, "note");
|
||||
|
||||
console.log("Tab locked", item?.locked, tab?.locked);
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
})();
|
||||
}
|
||||
}, [isAppLoading]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
let biometry = await BiometicService.isBiometryAvailable();
|
||||
let fingerprint = await BiometicService.hasInternetCredentials();
|
||||
setBiometryAvailable(!!biometry);
|
||||
setBiometryEnrolled(!!fingerprint);
|
||||
const biometry = await BiometicService.isBiometryAvailable();
|
||||
const fingerprint = await BiometicService.hasInternetCredentials();
|
||||
useTabStore.setState({
|
||||
biometryAvailable: !!biometry,
|
||||
biometryEnrolled: !!fingerprint
|
||||
});
|
||||
syncTabs();
|
||||
})();
|
||||
}, [isLocked]);
|
||||
}, [tab?.id]);
|
||||
|
||||
const unlockWithBiometrics = async () => {
|
||||
try {
|
||||
useEffect(() => {
|
||||
const unlockWithBiometrics = async () => {
|
||||
try {
|
||||
if (!item || !tab) return;
|
||||
console.log("Trying to unlock with biometrics...");
|
||||
const 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) {
|
||||
const 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);
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit = async ({
|
||||
password,
|
||||
biometrics: enrollBiometrics
|
||||
}: {
|
||||
password: string;
|
||||
biometrics?: boolean;
|
||||
}) => {
|
||||
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 (!password || password.trim().length === 0) {
|
||||
ToastManager.show({
|
||||
heading: "Password not entered",
|
||||
message: "Enter a password for the vault and try again.",
|
||||
type: "error"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (credentials && credentials?.password) {
|
||||
let note = await db.vault.open(item.id, credentials?.password);
|
||||
try {
|
||||
const note = await db.vault.open(item.id, password);
|
||||
if (enrollBiometrics) {
|
||||
try {
|
||||
await db.vault.unlock(password);
|
||||
await BiometicService.storeCredentials(password);
|
||||
eSendEvent("vaultUpdated");
|
||||
ToastManager.show({
|
||||
heading: "Biometric unlocking enabled!",
|
||||
message: "Now you can unlock notes in vault with biometrics.",
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
|
||||
const biometry = await BiometicService.isBiometryAvailable();
|
||||
const fingerprint = await BiometicService.hasInternetCredentials();
|
||||
useTabStore.setState({
|
||||
biometryAvailable: !!biometry,
|
||||
biometryEnrolled: !!fingerprint
|
||||
});
|
||||
syncTabs();
|
||||
} 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"
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const unlock = () => {
|
||||
if (
|
||||
isLocked &&
|
||||
biometryAvailable &&
|
||||
biometryEnrolled &&
|
||||
!editorState().movedAway
|
||||
(tab?.locked,
|
||||
useTabStore.getState().biometryAvailable &&
|
||||
useTabStore.getState().biometryEnrolled &&
|
||||
!editorState().movedAway)
|
||||
) {
|
||||
unlockWithBiometrics();
|
||||
} else {
|
||||
console.log("Biometrics unavailable.");
|
||||
if (isLocked && !editorState().movedAway) {
|
||||
console.log("Biometrics unavailable.", editorState().movedAway);
|
||||
if (!editorState().movedAway) {
|
||||
setTimeout(() => {
|
||||
passInputRef.current?.focus();
|
||||
}, 300);
|
||||
if (tab && tab?.locked) {
|
||||
editorController.current?.commands.focus(tab?.id);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const sub = eSubscribeEvent(eUnlockNote, unlock);
|
||||
unlock();
|
||||
const subs = [
|
||||
eSubscribeEvent(eUnlockNote, unlock),
|
||||
eSubscribeEvent(eUnlockWithBiometrics, () => {
|
||||
unlock();
|
||||
}),
|
||||
eSubscribeEvent(eUnlockWithPassword, onSubmit)
|
||||
];
|
||||
if (tab?.locked) {
|
||||
unlock();
|
||||
}
|
||||
return () => {
|
||||
sub.unsubscribe();
|
||||
subs.map((s) => s?.unsubscribe());
|
||||
};
|
||||
}, [isLocked, biometryAvailable, biometryEnrolled]);
|
||||
}, [item, tab]);
|
||||
|
||||
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;
|
||||
return null;
|
||||
};
|
||||
|
||||
const ReadonlyButton = ({ editor }: { editor: useEditorType }) => {
|
||||
|
||||
@@ -17,8 +17,10 @@ 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/dist/types";
|
||||
import type { Attachment } from "@notesnook/editor/dist/extensions/attachment/index";
|
||||
import type { ImageAttributes } from "@notesnook/editor/dist/extensions/image/index";
|
||||
import type { LinkAttributes } from "@notesnook/editor/dist/extensions/link";
|
||||
import { createRef, RefObject } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import { EdgeInsets } from "react-native-safe-area-context";
|
||||
@@ -26,10 +28,8 @@ import WebView from "react-native-webview";
|
||||
import { db } from "../../../common/database";
|
||||
import { sleep } from "../../../utils/time";
|
||||
import { Settings } from "./types";
|
||||
import { getResponse, randId, textInput } from "./utils";
|
||||
import { Note } from "@notesnook/core/dist/types";
|
||||
import { useTabStore } from "./use-tab-store";
|
||||
import type { LinkAttributes } from "@notesnook/editor/dist/extensions/link";
|
||||
import { getResponse, randId, textInput } from "./utils";
|
||||
|
||||
type Action = { job: string; id: string };
|
||||
|
||||
@@ -82,7 +82,15 @@ class Commands {
|
||||
setTimeout(async () => {
|
||||
if (!this.ref) return;
|
||||
textInput.current?.focus();
|
||||
await this.doAsync(`editors[${tabId}]?.commands.focus()`, "focus");
|
||||
|
||||
const locked = useTabStore.getState().getTab(tabId)?.locked;
|
||||
await this.doAsync(
|
||||
locked
|
||||
? `editorControllers[${tabId}]?.focusPassInput();`
|
||||
: `editors[${tabId}]?.commands.focus()`,
|
||||
"focus"
|
||||
);
|
||||
|
||||
this.ref?.current?.requestFocus();
|
||||
}, 1);
|
||||
} else {
|
||||
@@ -98,6 +106,9 @@ class Commands {
|
||||
const editorTitle = editorTitles[${tabId}];
|
||||
typeof editor !== "undefined" && editor.commands.blur();
|
||||
typeof editorTitle !== "undefined" && editorTitle.current && editorTitle.current.blur();
|
||||
|
||||
editorControllers[${tabId}]?.blurPassInput();
|
||||
|
||||
`,
|
||||
"blur"
|
||||
);
|
||||
@@ -119,7 +130,6 @@ typeof editorTitle !== "undefined" && editorTitle.current && editorTitle.current
|
||||
if (editorController.content) editorController.content.current = null;
|
||||
editorController.onUpdate();
|
||||
editorController.setTitle(null);
|
||||
editorController.countWords(0);
|
||||
if (typeof statusBar !== "undefined") {
|
||||
statusBar.current.resetWords();
|
||||
statusBar.current.set({date:"",saved:""});
|
||||
@@ -237,7 +247,7 @@ editor && editor.commands.insertAttachment(${JSON.stringify(attachment)})`
|
||||
};
|
||||
|
||||
setAttachmentProgress = async (
|
||||
attachmentProgress: AttachmentProgress,
|
||||
attachmentProgress: Partial<Attachment>,
|
||||
tabId: number
|
||||
) => {
|
||||
await this.doAsync(
|
||||
@@ -323,6 +333,20 @@ const image = toBlobURL("${image.dataurl}", "${image.hash}");
|
||||
`);
|
||||
};
|
||||
|
||||
focusPassInput = async () => {
|
||||
const tabId = useTabStore.getState().currentTab;
|
||||
return this.doAsync(`
|
||||
response = editorControllers[${tabId}]?.focusPassInput() || [];
|
||||
`);
|
||||
};
|
||||
|
||||
blurPassInput = async () => {
|
||||
const tabId = useTabStore.getState().currentTab;
|
||||
return this.doAsync(`
|
||||
response = editorControllers[${tabId}]?.blurPassInput() || [];
|
||||
`);
|
||||
};
|
||||
|
||||
createInternalLink = async (
|
||||
attributes: LinkAttributes,
|
||||
resolverId: string
|
||||
|
||||
@@ -43,5 +43,7 @@ export const EventTypes = {
|
||||
tabFocused: "editor-events:tab-focused",
|
||||
toc: "editor-events:toc",
|
||||
createInternalLink: "editor-events:create-internal-link",
|
||||
load: "editor-events:load"
|
||||
load: "editor-events:load",
|
||||
unlock: "editor-events:unlock",
|
||||
unlockWithBiometrics: "editor-events:unlock-biometrics"
|
||||
};
|
||||
|
||||
@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/* eslint-disable no-case-declarations */
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
import { parseInternalLink } from "@notesnook/core";
|
||||
import { ItemReference } from "@notesnook/core/dist/types";
|
||||
import type { Attachment } from "@notesnook/editor/dist/extensions/attachment/index";
|
||||
import { getDefaultPresets } from "@notesnook/editor/dist/toolbar/tool-definitions";
|
||||
@@ -36,9 +37,11 @@ import { WebViewMessageEvent } from "react-native-webview";
|
||||
import { db } from "../../../common/database";
|
||||
import downloadAttachment from "../../../common/filesystem/download-attachment";
|
||||
import EditorTabs from "../../../components/sheets/editor-tabs";
|
||||
import LinkNote from "../../../components/sheets/link-note";
|
||||
import ManageTagsSheet from "../../../components/sheets/manage-tags";
|
||||
import { RelationsList } from "../../../components/sheets/relations-list";
|
||||
import ReminderSheet from "../../../components/sheets/reminder";
|
||||
import TableOfContents from "../../../components/sheets/toc";
|
||||
import { DDS } from "../../../services/device-detection";
|
||||
import {
|
||||
ToastManager,
|
||||
@@ -48,7 +51,6 @@ import {
|
||||
} from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import { useEditorStore } from "../../../stores/use-editor-store";
|
||||
import { useRelationStore } from "../../../stores/use-relation-store";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
import { useTagStore } from "../../../stores/use-tag-store";
|
||||
@@ -61,7 +63,9 @@ import {
|
||||
eOpenFullscreenEditor,
|
||||
eOpenLoginDialog,
|
||||
eOpenPremiumDialog,
|
||||
eOpenPublishNoteDialog
|
||||
eOpenPublishNoteDialog,
|
||||
eUnlockWithBiometrics,
|
||||
eUnlockWithPassword
|
||||
} from "../../../utils/events";
|
||||
import { openLinkInBrowser } from "../../../utils/functions";
|
||||
import { tabBarRef } from "../../../utils/global-refs";
|
||||
@@ -70,9 +74,6 @@ import { EventTypes } from "./editor-events";
|
||||
import { EditorMessage, EditorProps, useEditorType } from "./types";
|
||||
import { useTabStore } from "./use-tab-store";
|
||||
import { EditorEvents, editorState } from "./utils";
|
||||
import TableOfContents from "../../../components/sheets/toc";
|
||||
import LinkNote from "../../../components/sheets/link-note";
|
||||
import { parseInternalLink } from "@notesnook/core";
|
||||
|
||||
const publishNote = async () => {
|
||||
const user = useUserStore.getState().user;
|
||||
@@ -368,7 +369,7 @@ export const useEditorEvents = (
|
||||
case EventTypes.content:
|
||||
editor.saveContent({
|
||||
type: editorMessage.type,
|
||||
content: editorMessage.value as string,
|
||||
content: editorMessage.value.html as string,
|
||||
noteId: noteId,
|
||||
tabId: editorMessage.tabId,
|
||||
ignoreEdit: (editorMessage.value as ContentMessage).ignoreEdit
|
||||
@@ -620,6 +621,16 @@ export const useEditorEvents = (
|
||||
break;
|
||||
}
|
||||
|
||||
case EventTypes.unlock: {
|
||||
eSendEvent(eUnlockWithPassword, editorMessage.value);
|
||||
break;
|
||||
}
|
||||
|
||||
case EventTypes.unlockWithBiometrics: {
|
||||
eSendEvent(eUnlockWithBiometrics);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import { EVENTS } from "@notesnook/core/dist/common";
|
||||
import {
|
||||
ContentItem,
|
||||
ContentType,
|
||||
ItemReference,
|
||||
Note,
|
||||
UnencryptedContentItem,
|
||||
isDeleted
|
||||
@@ -43,7 +44,6 @@ import {
|
||||
import Navigation from "../../../services/navigation";
|
||||
import Notifications from "../../../services/notifications";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
import { useTagStore } from "../../../stores/use-tag-store";
|
||||
import {
|
||||
eClearEditor,
|
||||
@@ -168,7 +168,7 @@ export const useEditor = (
|
||||
console.log(tabId);
|
||||
});
|
||||
return () => {
|
||||
event.unsubscribe();
|
||||
event?.unsubscribe();
|
||||
};
|
||||
});
|
||||
|
||||
@@ -202,7 +202,7 @@ export const useEditor = (
|
||||
console.log("Resetting tab:", tabId);
|
||||
const noteId = useTabStore.getState().getNoteIdForTab(tabId);
|
||||
if (noteId) {
|
||||
currentNotes.current?.id && db.fs().cancel(noteId, "download");
|
||||
currentNotes.current?.id && db.fs().cancel(noteId);
|
||||
currentNotes.current[noteId] = null;
|
||||
currentContents.current[noteId] = null;
|
||||
editorSessionHistory.clearSession(noteId);
|
||||
@@ -403,13 +403,16 @@ export const useEditor = (
|
||||
const tabId = useTabStore.getState().currentTab;
|
||||
currentNotes.current && (await reset(tabId));
|
||||
setTimeout(() => {
|
||||
if (state.current?.ready) commands.focus(tabId);
|
||||
if (state.current?.ready && !state.current.movedAway)
|
||||
commands.focus(tabId);
|
||||
});
|
||||
} else {
|
||||
if (!event.item) return;
|
||||
const item = event.item;
|
||||
|
||||
const noteIsLocked =
|
||||
event.item.locked && !(event.item as NoteWithContent).content;
|
||||
(await db.vaults.itemExists(event.item as ItemReference)) &&
|
||||
!(event.item as NoteWithContent).content;
|
||||
|
||||
// If note was already opened in a tab, focus that tab.
|
||||
if (typeof event.tabId !== "number") {
|
||||
@@ -428,8 +431,7 @@ 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:
|
||||
event.item.locked && !(event.item as NoteWithContent).content
|
||||
locked: noteIsLocked
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -512,7 +514,15 @@ export const useEditor = (
|
||||
}, 300);
|
||||
}
|
||||
},
|
||||
[commands, editorSessionHistory, loadContent, postMessage, readonly, reset]
|
||||
[
|
||||
commands,
|
||||
editorSessionHistory,
|
||||
loadContent,
|
||||
overlay,
|
||||
postMessage,
|
||||
readonly,
|
||||
reset
|
||||
]
|
||||
);
|
||||
|
||||
const lockNoteWithVault = useCallback((note: Note) => {
|
||||
@@ -549,7 +559,7 @@ export const useEditor = (
|
||||
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]
|
||||
currentNotes.current[note.id] as ItemReference
|
||||
);
|
||||
if (!locked && isContentEncrypted) {
|
||||
lockNoteWithVault(note);
|
||||
@@ -606,7 +616,6 @@ export const useEditor = (
|
||||
title,
|
||||
content,
|
||||
type,
|
||||
forSessionId,
|
||||
ignoreEdit,
|
||||
noteId,
|
||||
tabId
|
||||
@@ -615,7 +624,6 @@ export const useEditor = (
|
||||
title?: string;
|
||||
content?: string;
|
||||
type: string;
|
||||
forSessionId: string;
|
||||
ignoreEdit: boolean;
|
||||
tabId: number;
|
||||
}) => {
|
||||
@@ -667,7 +675,6 @@ export const useEditor = (
|
||||
|
||||
const restoreEditorState = useCallback(async () => {
|
||||
const appState = getAppState();
|
||||
console.log(appState, "appState");
|
||||
if (!appState) return;
|
||||
state.current.isRestoringState = true;
|
||||
state.current.currentlyEditing = true;
|
||||
|
||||
@@ -20,6 +20,7 @@ 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";
|
||||
|
||||
class History {
|
||||
history: number[];
|
||||
@@ -98,6 +99,8 @@ export type TabStore = {
|
||||
getCurrentNoteId: () => string | undefined;
|
||||
getTab: (tabId: number) => TabItem | undefined;
|
||||
tabHistory: number[];
|
||||
biometryAvailable?: boolean;
|
||||
biometryEnrolled?: boolean;
|
||||
};
|
||||
|
||||
function getId(id: number, tabs: TabItem[]): number {
|
||||
@@ -112,7 +115,9 @@ export function syncTabs() {
|
||||
editorController.current?.commands.doAsync(`
|
||||
globalThis.tabStore?.setState({
|
||||
tabs: ${JSON.stringify(useTabStore.getState().tabs)},
|
||||
currentTab: ${useTabStore.getState().currentTab}
|
||||
currentTab: ${useTabStore.getState().currentTab},
|
||||
biometryAvailable: ${useTabStore.getState().biometryAvailable},
|
||||
biometryEnrolled: ${useTabStore.getState().biometryEnrolled}
|
||||
});
|
||||
`);
|
||||
}
|
||||
|
||||
@@ -166,5 +166,8 @@ export const eOnRefreshSearch = "612";
|
||||
|
||||
export const eOpenAppLockPasswordDialog = "613";
|
||||
export const eCloseAppLocKPasswordDailog = "614";
|
||||
export const eEditorTabFocused = "613";
|
||||
export const eUnlockNote = "614";
|
||||
export const eEditorTabFocused = "615";
|
||||
export const eUnlockNote = "616";
|
||||
export const eOnChangeFluidTab = "617";
|
||||
export const eUnlockWithBiometrics = "618";
|
||||
export const eUnlockWithPassword = "619";
|
||||
|
||||
@@ -51,6 +51,7 @@ import Header from "./header";
|
||||
import StatusBar from "./statusbar";
|
||||
import Tags from "./tags";
|
||||
import Title from "./title";
|
||||
import FingerprintIcon from "mdi-react/FingerprintIcon";
|
||||
|
||||
globalThis.toBlobURL = toBlobURL as typeof globalThis.toBlobURL;
|
||||
|
||||
@@ -71,6 +72,9 @@ const Tiptap = ({
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const noteStateUpdateTimer = useRef<NodeJS.Timeout>();
|
||||
const tabRef = useRef<TabItem>(tab);
|
||||
const biometryAvailable = useTabStore((state) => state.biometryAvailable);
|
||||
const biometryEnrolled = useTabStore((state) => state.biometryEnrolled);
|
||||
|
||||
const isFocusedRef = useRef<boolean>(false);
|
||||
tabRef.current = tab;
|
||||
|
||||
@@ -161,6 +165,7 @@ const Tiptap = ({
|
||||
timeFormat: settings.timeFormat as "12-hour" | "24-hour" | undefined,
|
||||
enableInputRules: settings.markdownShortcuts
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
getContentDiv,
|
||||
settings.readonly,
|
||||
@@ -169,6 +174,7 @@ const Tiptap = ({
|
||||
settings.dateFormat,
|
||||
settings.timeFormat,
|
||||
settings.markdownShortcuts,
|
||||
tick,
|
||||
tab.id
|
||||
]);
|
||||
|
||||
@@ -372,7 +378,7 @@ const Tiptap = ({
|
||||
position: "relative"
|
||||
}}
|
||||
>
|
||||
{settings.noHeader ? null : (
|
||||
{settings.noHeader || tab.locked ? null : (
|
||||
<>
|
||||
<Tags settings={settings} />
|
||||
<Title
|
||||
@@ -392,45 +398,219 @@ const Tiptap = ({
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "95%",
|
||||
height: "90%",
|
||||
position: "absolute",
|
||||
zIndex: 999,
|
||||
backgroundColor: colors.primary.background,
|
||||
paddingRight: 12,
|
||||
paddingLeft: 12,
|
||||
display: "flex",
|
||||
flexDirection: "column"
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
boxSizing: "border-box",
|
||||
rowGap: 10
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: 16,
|
||||
width: "94%",
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 5,
|
||||
marginTop: 10
|
||||
}}
|
||||
/>
|
||||
{tab.locked ? (
|
||||
<>
|
||||
<p
|
||||
style={{
|
||||
color: colors.primary.paragraph,
|
||||
fontSize: 20,
|
||||
fontWeight: "600",
|
||||
textAlign: "center",
|
||||
padding: "0px 20px",
|
||||
marginBottom: 0,
|
||||
userSelect: "none"
|
||||
}}
|
||||
>
|
||||
{controller.title} ajklsdksa jdlksaj dksljk jdalkjskldj
|
||||
</p>
|
||||
<p
|
||||
style={{
|
||||
color: colors.primary.paragraph,
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
userSelect: "none"
|
||||
}}
|
||||
>
|
||||
This note is locked.
|
||||
</p>
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: 16,
|
||||
width: "94%",
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 5,
|
||||
marginTop: 10
|
||||
}}
|
||||
/>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
const data = new FormData(e.currentTarget);
|
||||
const password = data.get("password");
|
||||
const biometrics = data.get("enrollBiometrics");
|
||||
post("editor-events:unlock", {
|
||||
password,
|
||||
biometrics: biometrics === "on" ? true : false
|
||||
});
|
||||
}}
|
||||
style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
rowGap: 10
|
||||
}}
|
||||
>
|
||||
<input
|
||||
placeholder="Enter password"
|
||||
ref={controller.passwordInputRef}
|
||||
name="password"
|
||||
type="password"
|
||||
required
|
||||
style={{
|
||||
boxSizing: "border-box",
|
||||
width: 300,
|
||||
height: 45,
|
||||
borderRadius: 5,
|
||||
border: `1px solid ${colors.primary.border}`,
|
||||
paddingLeft: 12,
|
||||
paddingRight: 12,
|
||||
fontSize: "1em",
|
||||
backgroundColor: "transparent",
|
||||
caretColor: colors.primary.accent,
|
||||
color: colors.primary.paragraph
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: 16,
|
||||
width: 200,
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 5,
|
||||
marginTop: 10
|
||||
}}
|
||||
/>
|
||||
<button
|
||||
style={{
|
||||
backgroundColor: colors.primary.accent,
|
||||
borderRadius: 5,
|
||||
boxSizing: "border-box",
|
||||
border: "none",
|
||||
color: colors.static.white,
|
||||
width: 300,
|
||||
fontSize: "0.9em",
|
||||
height: 45,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
onMouseDown={(e) => {
|
||||
if (globalThis.keyboardShown) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<p
|
||||
style={{
|
||||
userSelect: "none"
|
||||
}}
|
||||
>
|
||||
Unlock note
|
||||
</p>
|
||||
</button>
|
||||
|
||||
{biometryAvailable && !biometryEnrolled ? (
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 5
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="enrollBiometrics"
|
||||
style={{
|
||||
accentColor: colors.primary.accent
|
||||
}}
|
||||
onMouseDown={(e) => {
|
||||
if (globalThis.keyboardShown) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
<p
|
||||
style={{
|
||||
color: colors.primary.paragraph,
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
userSelect: "none"
|
||||
}}
|
||||
>
|
||||
Enable biometric unlocking
|
||||
</p>
|
||||
</div>
|
||||
) : null}
|
||||
</form>
|
||||
|
||||
{biometryEnrolled && biometryAvailable ? (
|
||||
<button
|
||||
style={{
|
||||
backgroundColor: "transparent",
|
||||
borderRadius: 5,
|
||||
boxSizing: "border-box",
|
||||
border: "none",
|
||||
color: colors.primary.accent,
|
||||
width: 300,
|
||||
fontSize: "0.9em",
|
||||
height: 45,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
columnGap: 5,
|
||||
userSelect: "none"
|
||||
}}
|
||||
onMouseDown={(e) => {
|
||||
if (globalThis.keyboardShown) {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
onClick={() => {
|
||||
post("editor-events:unlock-biometrics");
|
||||
}}
|
||||
>
|
||||
<FingerprintIcon />
|
||||
<p
|
||||
style={{
|
||||
userSelect: "none"
|
||||
}}
|
||||
>
|
||||
Unlock with biometrics
|
||||
</p>
|
||||
</button>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
height: 16,
|
||||
width: "94%",
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 5,
|
||||
marginTop: 10
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: 16,
|
||||
width: "94%",
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 5,
|
||||
marginTop: 10
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
style={{
|
||||
height: 16,
|
||||
width: 200,
|
||||
backgroundColor: colors.secondary.background,
|
||||
borderRadius: 5,
|
||||
marginTop: 10
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -109,8 +109,10 @@ export type EditorController = {
|
||||
setLoading: (value: boolean) => void;
|
||||
getTableOfContents: () => any[];
|
||||
scrollIntoView: (id: string) => void;
|
||||
passwordInputRef: MutableRefObject<HTMLInputElement | null>;
|
||||
focusPassInput: () => void;
|
||||
blurPassInput: () => void;
|
||||
};
|
||||
|
||||
export function useEditorController({
|
||||
update,
|
||||
getTableOfContents
|
||||
@@ -118,6 +120,7 @@ export function useEditorController({
|
||||
update: () => void;
|
||||
getTableOfContents: () => any[];
|
||||
}): EditorController {
|
||||
const passwordInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const tab = useTabContext();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const setTheme = useThemeEngineStore((store) => store.setTheme);
|
||||
@@ -240,8 +243,8 @@ export function useEditorController({
|
||||
break;
|
||||
}
|
||||
case "native:html":
|
||||
// logger("info", "loading html", htmlContentRef.current);
|
||||
htmlContentRef.current = value;
|
||||
logger("info", "loading html");
|
||||
if (!editor) break;
|
||||
update();
|
||||
countWords(0);
|
||||
@@ -379,6 +382,14 @@ export function useEditorController({
|
||||
countWords();
|
||||
logger("info", `Tab ${tab.id} updated.`);
|
||||
}, 1);
|
||||
},
|
||||
passwordInputRef,
|
||||
focusPassInput: () => {
|
||||
logger("info", "focus pass input...");
|
||||
passwordInputRef.current?.focus();
|
||||
},
|
||||
blurPassInput: () => {
|
||||
passwordInputRef.current?.blur();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -62,6 +62,8 @@ export type TabStore = {
|
||||
getCurrentNoteId: () => string | undefined;
|
||||
getTab: (tabId: number) => TabItem | undefined;
|
||||
setNoteState: (noteId: string, state: Partial<NoteState>) => void;
|
||||
biometryAvailable?: boolean;
|
||||
biometryEnrolled?: boolean;
|
||||
};
|
||||
|
||||
function getId(id: number, tabs: TabItem[]): number {
|
||||
|
||||
@@ -186,7 +186,9 @@ export const EventTypes = {
|
||||
tabFocused: "editor-events:tab-focused",
|
||||
toc: "editor-events:toc",
|
||||
createInternalLink: "editor-events:create-internal-link",
|
||||
load: "editor-events:load"
|
||||
load: "editor-events:load",
|
||||
unlock: "editor-events:unlock",
|
||||
unlockWithBiometrics: "editor-events:unlock-biometrics"
|
||||
} as const;
|
||||
|
||||
export function randId(prefix: string) {
|
||||
|
||||
Reference in New Issue
Block a user