mobile: fix minor bugs in tabs

This commit is contained in:
Ammar Ahmed
2024-03-08 13:00:10 +05:00
committed by Abdullah Atta
parent 08c03bc3a4
commit 3efe977c49
17 changed files with 520 additions and 301 deletions

View File

@@ -26,7 +26,7 @@ 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 { Pressable } 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 { eUnlockNote } from "../../../utils/events";
@@ -48,8 +48,8 @@ const TabItemComponent = (props: {
const [item] = useDBItem(props.tab.noteId, "note"); const [item] = useDBItem(props.tab.noteId, "note");
return ( return (
<PressableButton <Pressable
customStyle={{ style={{
alignItems: "center", alignItems: "center",
justifyContent: "space-between", justifyContent: "space-between",
flexDirection: "row", flexDirection: "row",
@@ -114,7 +114,7 @@ const TabItemComponent = (props: {
right={20} right={20}
bottom={0} bottom={0}
/> />
</PressableButton> </Pressable>
); );
}; };

View File

@@ -32,7 +32,7 @@ import { 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 Input from "../../ui/input"; import Input from "../../ui/input";
import { PressableButton } from "../../ui/pressable"; import { Pressable } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph"; import Paragraph from "../../ui/typography/paragraph";
import type { LinkAttributes } from "@notesnook/editor/dist/extensions/link"; import type { LinkAttributes } from "@notesnook/editor/dist/extensions/link";
import { editorController } from "../../../screens/editor/tiptap/utils"; import { editorController } from "../../../screens/editor/tiptap/utils";
@@ -48,13 +48,13 @@ const ListNoteItem = ({
}) => { }) => {
const [item] = useDBItem(id, "note", items); const [item] = useDBItem(id, "note", items);
return ( return (
<PressableButton <Pressable
onPress={() => { onPress={() => {
if (!item) return; if (!item) return;
onSelectNote(item as Note); onSelectNote(item as Note);
}} }}
type={"transparent"} type={"transparent"}
customStyle={{ style={{
paddingVertical: 12, paddingVertical: 12,
flexDirection: "row", flexDirection: "row",
width: "100%", width: "100%",
@@ -69,7 +69,7 @@ const ListNoteItem = ({
> >
<Paragraph numberOfLines={1}>{item?.title}</Paragraph> <Paragraph numberOfLines={1}>{item?.title}</Paragraph>
</View> </View>
</PressableButton> </Pressable>
); );
}; };
@@ -82,12 +82,12 @@ const ListBlockItem = ({
}) => { }) => {
const { colors } = useThemeColors(); const { colors } = useThemeColors();
return ( return (
<PressableButton <Pressable
onPress={() => { onPress={() => {
onSelectBlock(item); onSelectBlock(item);
}} }}
type={"transparent"} type={"transparent"}
customStyle={{ style={{
flexDirection: "row", flexDirection: "row",
width: "100%", width: "100%",
justifyContent: "flex-start", justifyContent: "flex-start",
@@ -127,7 +127,7 @@ const ListBlockItem = ({
: item.content} : item.content}
</Paragraph> </Paragraph>
</View> </View>
</PressableButton> </Pressable>
); );
}; };
@@ -198,7 +198,7 @@ export default function LinkNote(props: {
setSelectedNote(note); setSelectedNote(note);
inputRef.current?.clear(); inputRef.current?.clear();
setTimeout(async () => { setTimeout(async () => {
nodesRef.current = await db.notes.getBlocks(note.id); nodesRef.current = await db.notes.contentBlocks(note.id);
setNodes(nodesRef.current); setNodes(nodesRef.current);
}); });
// Fetch and set note's nodes. // Fetch and set note's nodes.
@@ -249,13 +249,13 @@ export default function LinkNote(props: {
<Paragraph color={colors.secondary.paragraph} size={SIZE.xs}> <Paragraph color={colors.secondary.paragraph} size={SIZE.xs}>
SELECTED NOTE SELECTED NOTE
</Paragraph> </Paragraph>
<PressableButton <Pressable
onPress={() => { onPress={() => {
setSelectedNote(undefined); setSelectedNote(undefined);
setSelectedNodeId(undefined); setSelectedNodeId(undefined);
setNodes([]); setNodes([]);
}} }}
customStyle={{ style={{
flexDirection: "row", flexDirection: "row",
width: "100%", width: "100%",
justifyContent: "flex-start", justifyContent: "flex-start",
@@ -264,7 +264,7 @@ export default function LinkNote(props: {
borderColor: colors.primary.accent, borderColor: colors.primary.accent,
paddingHorizontal: 12 paddingHorizontal: 12
}} }}
type="grayAccent" type="secondaryAccented"
> >
<View <View
style={{ style={{
@@ -280,7 +280,7 @@ export default function LinkNote(props: {
Tap to deselect Tap to deselect
</Paragraph> </Paragraph>
</View> </View>
</PressableButton> </Pressable>
{nodes?.length > 0 ? ( {nodes?.length > 0 ? (
<Paragraph <Paragraph

View File

@@ -39,7 +39,7 @@ import { SIZE } from "../../../utils/size";
import SheetProvider from "../../sheet-provider"; import SheetProvider from "../../sheet-provider";
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 { Pressable } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph"; import Paragraph from "../../ui/typography/paragraph";
export const useExpandedStore = create<{ export const useExpandedStore = create<{
@@ -68,12 +68,12 @@ const ListBlockItem = ({
}) => { }) => {
const { colors } = useThemeColors(); const { colors } = useThemeColors();
return ( return (
<PressableButton <Pressable
onPress={() => { onPress={() => {
onSelectBlock(); onSelectBlock();
}} }}
type={"transparent"} type={"transparent"}
customStyle={{ style={{
flexDirection: "row", flexDirection: "row",
width: "100%", width: "100%",
paddingLeft: 35, paddingLeft: 35,
@@ -114,7 +114,7 @@ const ListBlockItem = ({
: item.content} : item.content}
</Paragraph> </Paragraph>
</View> </View>
</PressableButton> </Pressable>
); );
}; };
@@ -130,12 +130,12 @@ const ListNoteInternalLink = ({
}) => { }) => {
const { colors } = useThemeColors(); const { colors } = useThemeColors();
return ( return (
<PressableButton <Pressable
onPress={() => { onPress={() => {
onSelect(); onSelect();
}} }}
type={"transparent"} type={"transparent"}
customStyle={{ style={{
flexDirection: "row", flexDirection: "row",
width: "100%", width: "100%",
paddingLeft: 35, paddingLeft: 35,
@@ -177,7 +177,7 @@ const ListNoteInternalLink = ({
</Paragraph> </Paragraph>
))} ))}
</View> </View>
</PressableButton> </Pressable>
); );
}; };
@@ -306,13 +306,13 @@ const ListNoteItem = ({
alignItems: "center" alignItems: "center"
}} }}
> >
<PressableButton <Pressable
type={"gray"} type={"plain"}
onPress={() => { onPress={() => {
if (!item) return; if (!item) return;
onSelect(item as Note); onSelect(item as Note);
}} }}
customStyle={{ style={{
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
justifyContent: "flex-start", justifyContent: "flex-start",
@@ -330,14 +330,14 @@ const ListNoteItem = ({
left={0} left={0}
bottom={0} bottom={0}
right={0} right={0}
customStyle={{ style={{
width: 35, width: 35,
height: 35 height: 35
}} }}
name={expanded ? "chevron-down" : "chevron-right"} name={expanded ? "chevron-down" : "chevron-right"}
/> />
<Paragraph numberOfLines={1}>{item?.title}</Paragraph> <Paragraph numberOfLines={1}>{item?.title}</Paragraph>
</PressableButton> </Pressable>
{expanded && !item?.locked ? ( {expanded && !item?.locked ? (
<View <View
@@ -441,7 +441,7 @@ export const ReferencesList = ({ item, close }: ReferencesListProps) => {
}} }}
> >
<Button <Button
type={"gray"} type={"plain"}
title="Linked notes" title="Linked notes"
style={{ style={{
borderRadius: 0, borderRadius: 0,
@@ -455,7 +455,7 @@ export const ReferencesList = ({ item, close }: ReferencesListProps) => {
}} }}
/> />
<Button <Button
type={"gray"} type={"plain"}
title="Referenced in" title="Referenced in"
style={{ style={{
width: "50%", width: "50%",

View File

@@ -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 { useThemeColors } from "@notesnook/theme";
import React from "react"; import React from "react";
import { View } from "react-native"; import { View } from "react-native";
import { PressableButton } from "../../ui/pressable"; import { Pressable } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph"; import Paragraph from "../../ui/typography/paragraph";
import { SIZE } from "../../../utils/size"; import { SIZE } from "../../../utils/size";
import { presentSheet } from "../../../services/event-manager"; import { presentSheet } from "../../../services/event-manager";
@@ -33,8 +51,8 @@ const TableOfContentsItem: React.FC<{
const { colors } = useThemeColors(); const { colors } = useThemeColors();
return ( return (
<PressableButton <Pressable
customStyle={{ style={{
alignItems: "center", alignItems: "center",
justifyContent: "space-between", justifyContent: "space-between",
flexDirection: "row", flexDirection: "row",
@@ -72,7 +90,7 @@ const TableOfContentsItem: React.FC<{
{item?.title || "New note"} {item?.title || "New note"}
</Paragraph> </Paragraph>
</View> </View>
</PressableButton> </Pressable>
); );
}; };

View File

@@ -471,13 +471,14 @@ export const useAppEvents = () => {
const noteId = useTabStore.getState().getTab(tab.id)?.noteId; const noteId = useTabStore.getState().getTab(tab.id)?.noteId;
if (!noteId) continue; if (!noteId) continue;
const note = await db.notes.note(noteId); 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, { useTabStore.getState().updateTab(tab.id, {
locked: true locked: true
}); });
if ( if (
tab.id === useTabStore.getState().currentTab && tab.id === useTabStore.getState().currentTab &&
note.locked && locked &&
!editorState().movedAway !editorState().movedAway
) { ) {
// Show unlock note screen. // Show unlock note screen.

View File

@@ -23,7 +23,7 @@ import {
deactivateKeepAwake deactivateKeepAwake
} from "@sayem314/react-native-keep-awake"; } from "@sayem314/react-native-keep-awake";
import React, { useCallback, useEffect, useRef, useState } from "react"; 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 changeNavigationBarColor from "react-native-navigation-bar-color";
import { import {
addOrientationListener, addOrientationListener,
@@ -64,11 +64,12 @@ import { useSettingStore } from "../stores/use-setting-store";
import { import {
eClearEditor, eClearEditor,
eCloseFullscreenEditor, eCloseFullscreenEditor,
eOnChangeFluidTab,
eOnLoadNote, eOnLoadNote,
eOpenFullscreenEditor, eOpenFullscreenEditor,
eUnlockNote eUnlockNote
} from "../utils/events"; } from "../utils/events";
import { editorRef, tabBarRef } from "../utils/global-refs"; import { editorRef, inputRef, tabBarRef } from "../utils/global-refs";
import { sleep } from "../utils/time"; import { sleep } from "../utils/time";
import { NavigationStack } from "./navigation-stack"; import { NavigationStack } from "./navigation-stack";
@@ -512,7 +513,6 @@ const onChangeTab = async (obj) => {
editorState().movedAway = false; editorState().movedAway = false;
editorState().isFocused = true; editorState().isFocused = true;
activateKeepAwake(); activateKeepAwake();
if (!useTabStore.getState().getCurrentNoteId()) { if (!useTabStore.getState().getCurrentNoteId()) {
eSendEvent(eOnLoadNote, { eSendEvent(eOnLoadNote, {
newNote: true newNote: true
@@ -521,6 +521,7 @@ const onChangeTab = async (obj) => {
if ( if (
useTabStore.getState().getTab(useTabStore.getState().currentTab).locked useTabStore.getState().getTab(useTabStore.getState().currentTab).locked
) { ) {
console.log("Unlocking note....");
eSendEvent(eUnlockNote); eSendEvent(eUnlockNote);
} }
} }
@@ -530,14 +531,19 @@ const onChangeTab = async (obj) => {
editorState().movedAway = true; editorState().movedAway = true;
editorState().isFocused = false; editorState().isFocused = false;
eSendEvent(eClearEditor, "removeHandler"); eSendEvent(eClearEditor, "removeHandler");
let id = useTabStore.getState().getCurrentNoteId();
let note = await db.notes.note(id); // 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)); const locked = note && (await db.vaults.itemExists(note));
if (locked) { if (locked) {
useTabStore.getState().updateTab(useTabStore.getState().currentTab, { useTabStore.getState().updateTab(tab.id, {
locked: true locked: true
}); });
} }
} }
} }
}
}; };

View File

@@ -26,28 +26,15 @@ import React, {
useEffect, useEffect,
useImperativeHandle, useImperativeHandle,
useLayoutEffect, useLayoutEffect,
useRef, useRef
useState
} from "react"; } from "react";
import { import { Platform, TextInput, ViewStyle } from "react-native";
Platform,
ScrollView,
TextInput,
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 { Button } from "../../components/ui/button";
import { IconButton } from "../../components/ui/icon-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 { useDBItem } from "../../hooks/use-db-item";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import useKeyboard from "../../hooks/use-keyboard"; import useKeyboard from "../../hooks/use-keyboard";
import BiometicService from "../../services/biometrics"; import BiometicService from "../../services/biometrics";
import { import {
@@ -55,15 +42,20 @@ import {
eSendEvent, eSendEvent,
eSubscribeEvent eSubscribeEvent
} from "../../services/event-manager"; } from "../../services/event-manager";
import { useSettingStore } from "../../stores/use-setting-store";
import { getElevationStyle } from "../../utils/elevation"; 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 { openLinkInBrowser } from "../../utils/functions";
import EditorOverlay from "./loading";
import { EDITOR_URI } from "./source"; import { EDITOR_URI } from "./source";
import { EditorProps, useEditorType } from "./tiptap/types"; 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 { syncTabs, useTabStore } from "./tiptap/use-tab-store";
import { editorController, editorState } from "./tiptap/utils"; import { editorController, editorState } from "./tiptap/utils";
const style: ViewStyle = { const style: ViewStyle = {
@@ -181,7 +173,7 @@ const Editor = React.memo(
autoManageStatusBarEnabled={false} autoManageStatusBarEnabled={false}
onMessage={onMessage || undefined} onMessage={onMessage || undefined}
/> />
<EditorOverlay editorId={editorId || ""} editor={editor} /> {/* <EditorOverlay editorId={editorId || ""} editor={editor} /> */}
<ReadonlyButton editor={editor} /> <ReadonlyButton editor={editor} />
<LockOverlay /> <LockOverlay />
</> </>
@@ -193,43 +185,57 @@ const Editor = React.memo(
export default Editor; export default Editor;
let LOADED = false;
const LockOverlay = () => { const LockOverlay = () => {
const tab = useTabStore((state) => const tab = useTabStore((state) =>
state.tabs.find((t) => t.id === state.currentTab) state.tabs.find((t) => t.id === state.currentTab)
); );
const { height } = useWindowDimensions(); const isAppLoading = useSettingStore((state) => state.isAppLoading);
const [item] = useDBItem(tab?.noteId, "note"); const [item] = useDBItem(isAppLoading ? undefined : 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(() => {
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(() => { useEffect(() => {
(async () => { (async () => {
let biometry = await BiometicService.isBiometryAvailable(); const biometry = await BiometicService.isBiometryAvailable();
let fingerprint = await BiometicService.hasInternetCredentials(); const fingerprint = await BiometicService.hasInternetCredentials();
setBiometryAvailable(!!biometry); useTabStore.setState({
setBiometryEnrolled(!!fingerprint); biometryAvailable: !!biometry,
biometryEnrolled: !!fingerprint
});
syncTabs();
})(); })();
}, [isLocked]); }, [tab?.id]);
useEffect(() => {
const unlockWithBiometrics = async () => { const unlockWithBiometrics = async () => {
try { try {
if (!item || !tab) return; if (!item || !tab) return;
console.log("Trying to unlock with biometrics..."); console.log("Trying to unlock with biometrics...");
let credentials = await BiometicService.getCredentials( const credentials = await BiometicService.getCredentials(
"Unlock note", "Unlock note",
"Unlock note to open it in editor. If biometrics are not working, you can enter device pin to unlock vault." "Unlock note to open it in editor. If biometrics are not working, you can enter device pin to unlock vault."
); );
if (credentials && credentials?.password) { if (credentials && credentials?.password) {
let note = await db.vault.open(item.id, credentials?.password); const note = await db.vault.open(item.id, credentials?.password);
eSendEvent(eOnLoadNote, { eSendEvent(eOnLoadNote, {
item: note item: note
}); });
@@ -242,36 +248,15 @@ const LockOverlay = () => {
} }
}; };
useEffect(() => { const onSubmit = async ({
const unlock = () => { password,
if ( biometrics: enrollBiometrics
isLocked && }: {
biometryAvailable && password: string;
biometryEnrolled && biometrics?: boolean;
!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 (!item || !tab) return;
if (!password || password.trim().length === 0) {
if (!password.current || password.current.trim().length === 0) {
ToastManager.show({ ToastManager.show({
heading: "Password not entered", heading: "Password not entered",
message: "Enter a password for the vault and try again.", message: "Enter a password for the vault and try again.",
@@ -281,11 +266,11 @@ const LockOverlay = () => {
} }
try { try {
let note = await db.vault.open(item.id, password.current); const note = await db.vault.open(item.id, password);
if (enrollBiometrics) { if (enrollBiometrics) {
try { try {
await db.vault.unlock(password.current); await db.vault.unlock(password);
await BiometicService.storeCredentials(password.current); await BiometicService.storeCredentials(password);
eSendEvent("vaultUpdated"); eSendEvent("vaultUpdated");
ToastManager.show({ ToastManager.show({
heading: "Biometric unlocking enabled!", heading: "Biometric unlocking enabled!",
@@ -293,6 +278,14 @@ const LockOverlay = () => {
type: "success", type: "success",
context: "global" context: "global"
}); });
const biometry = await BiometicService.isBiometryAvailable();
const fingerprint = await BiometicService.hasInternetCredentials();
useTabStore.setState({
biometryAvailable: !!biometry,
biometryEnrolled: !!fingerprint
});
syncTabs();
} catch (e) { } catch (e) {
ToastManager.show({ ToastManager.show({
heading: "Incorrect password", heading: "Incorrect password",
@@ -319,88 +312,42 @@ const LockOverlay = () => {
} }
}; };
return isLocked ? ( const unlock = () => {
<ScrollView if (
style={{ (tab?.locked,
width: "100%", useTabStore.getState().biometryAvailable &&
height: height, useTabStore.getState().biometryEnrolled &&
backgroundColor: colors.primary.background, !editorState().movedAway)
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(); unlockWithBiometrics();
}} } else {
/> console.log("Biometrics unavailable.", editorState().movedAway);
) : null} if (!editorState().movedAway) {
</ScrollView> setTimeout(() => {
) : null; if (tab && tab?.locked) {
editorController.current?.commands.focus(tab?.id);
}
}, 100);
}
}
};
const subs = [
eSubscribeEvent(eUnlockNote, unlock),
eSubscribeEvent(eUnlockWithBiometrics, () => {
unlock();
}),
eSubscribeEvent(eUnlockWithPassword, onSubmit)
];
if (tab?.locked) {
unlock();
}
return () => {
subs.map((s) => s?.unsubscribe());
};
}, [item, tab]);
return null;
}; };
const ReadonlyButton = ({ editor }: { editor: useEditorType }) => { const ReadonlyButton = ({ editor }: { editor: useEditorType }) => {

View File

@@ -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/>. 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 { Attachment } from "@notesnook/editor/dist/extensions/attachment/index";
import type { ImageAttributes } from "@notesnook/editor/dist/extensions/image/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 { createRef, RefObject } from "react";
import { Platform } from "react-native"; import { Platform } from "react-native";
import { EdgeInsets } from "react-native-safe-area-context"; import { EdgeInsets } from "react-native-safe-area-context";
@@ -26,10 +28,8 @@ import WebView from "react-native-webview";
import { db } from "../../../common/database"; import { db } from "../../../common/database";
import { sleep } from "../../../utils/time"; import { sleep } from "../../../utils/time";
import { Settings } from "./types"; import { Settings } from "./types";
import { getResponse, randId, textInput } from "./utils";
import { Note } from "@notesnook/core/dist/types";
import { useTabStore } from "./use-tab-store"; 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 }; type Action = { job: string; id: string };
@@ -82,7 +82,15 @@ class Commands {
setTimeout(async () => { setTimeout(async () => {
if (!this.ref) return; if (!this.ref) return;
textInput.current?.focus(); 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(); this.ref?.current?.requestFocus();
}, 1); }, 1);
} else { } else {
@@ -98,6 +106,9 @@ class Commands {
const editorTitle = editorTitles[${tabId}]; const editorTitle = editorTitles[${tabId}];
typeof editor !== "undefined" && editor.commands.blur(); typeof editor !== "undefined" && editor.commands.blur();
typeof editorTitle !== "undefined" && editorTitle.current && editorTitle.current.blur(); typeof editorTitle !== "undefined" && editorTitle.current && editorTitle.current.blur();
editorControllers[${tabId}]?.blurPassInput();
`, `,
"blur" "blur"
); );
@@ -119,7 +130,6 @@ typeof editorTitle !== "undefined" && editorTitle.current && editorTitle.current
if (editorController.content) editorController.content.current = null; if (editorController.content) editorController.content.current = null;
editorController.onUpdate(); editorController.onUpdate();
editorController.setTitle(null); editorController.setTitle(null);
editorController.countWords(0);
if (typeof statusBar !== "undefined") { if (typeof statusBar !== "undefined") {
statusBar.current.resetWords(); statusBar.current.resetWords();
statusBar.current.set({date:"",saved:""}); statusBar.current.set({date:"",saved:""});
@@ -237,7 +247,7 @@ editor && editor.commands.insertAttachment(${JSON.stringify(attachment)})`
}; };
setAttachmentProgress = async ( setAttachmentProgress = async (
attachmentProgress: AttachmentProgress, attachmentProgress: Partial<Attachment>,
tabId: number tabId: number
) => { ) => {
await this.doAsync( 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 ( createInternalLink = async (
attributes: LinkAttributes, attributes: LinkAttributes,
resolverId: string resolverId: string

View File

@@ -43,5 +43,7 @@ export const EventTypes = {
tabFocused: "editor-events:tab-focused", tabFocused: "editor-events:tab-focused",
toc: "editor-events:toc", toc: "editor-events:toc",
createInternalLink: "editor-events:create-internal-link", createInternalLink: "editor-events:create-internal-link",
load: "editor-events:load" load: "editor-events:load",
unlock: "editor-events:unlock",
unlockWithBiometrics: "editor-events:unlock-biometrics"
}; };

View File

@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* eslint-disable no-case-declarations */ /* eslint-disable no-case-declarations */
/* eslint-disable @typescript-eslint/no-var-requires */ /* eslint-disable @typescript-eslint/no-var-requires */
import { parseInternalLink } from "@notesnook/core";
import { ItemReference } from "@notesnook/core/dist/types"; import { ItemReference } from "@notesnook/core/dist/types";
import type { Attachment } from "@notesnook/editor/dist/extensions/attachment/index"; import type { Attachment } from "@notesnook/editor/dist/extensions/attachment/index";
import { getDefaultPresets } from "@notesnook/editor/dist/toolbar/tool-definitions"; 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 { db } from "../../../common/database";
import downloadAttachment from "../../../common/filesystem/download-attachment"; import downloadAttachment from "../../../common/filesystem/download-attachment";
import EditorTabs from "../../../components/sheets/editor-tabs"; import EditorTabs from "../../../components/sheets/editor-tabs";
import LinkNote from "../../../components/sheets/link-note";
import ManageTagsSheet from "../../../components/sheets/manage-tags"; import ManageTagsSheet from "../../../components/sheets/manage-tags";
import { RelationsList } from "../../../components/sheets/relations-list"; import { RelationsList } from "../../../components/sheets/relations-list";
import ReminderSheet from "../../../components/sheets/reminder"; import ReminderSheet from "../../../components/sheets/reminder";
import TableOfContents from "../../../components/sheets/toc";
import { DDS } from "../../../services/device-detection"; import { DDS } from "../../../services/device-detection";
import { import {
ToastManager, ToastManager,
@@ -48,7 +51,6 @@ import {
} from "../../../services/event-manager"; } from "../../../services/event-manager";
import Navigation from "../../../services/navigation"; import Navigation from "../../../services/navigation";
import SettingsService from "../../../services/settings"; import SettingsService from "../../../services/settings";
import { useEditorStore } from "../../../stores/use-editor-store";
import { useRelationStore } from "../../../stores/use-relation-store"; import { useRelationStore } from "../../../stores/use-relation-store";
import { useSettingStore } from "../../../stores/use-setting-store"; import { useSettingStore } from "../../../stores/use-setting-store";
import { useTagStore } from "../../../stores/use-tag-store"; import { useTagStore } from "../../../stores/use-tag-store";
@@ -61,7 +63,9 @@ import {
eOpenFullscreenEditor, eOpenFullscreenEditor,
eOpenLoginDialog, eOpenLoginDialog,
eOpenPremiumDialog, eOpenPremiumDialog,
eOpenPublishNoteDialog eOpenPublishNoteDialog,
eUnlockWithBiometrics,
eUnlockWithPassword
} from "../../../utils/events"; } from "../../../utils/events";
import { openLinkInBrowser } from "../../../utils/functions"; import { openLinkInBrowser } from "../../../utils/functions";
import { tabBarRef } from "../../../utils/global-refs"; import { tabBarRef } from "../../../utils/global-refs";
@@ -70,9 +74,6 @@ import { EventTypes } from "./editor-events";
import { EditorMessage, EditorProps, useEditorType } from "./types"; import { EditorMessage, EditorProps, useEditorType } from "./types";
import { useTabStore } from "./use-tab-store"; import { useTabStore } from "./use-tab-store";
import { EditorEvents, editorState } from "./utils"; 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 publishNote = async () => {
const user = useUserStore.getState().user; const user = useUserStore.getState().user;
@@ -368,7 +369,7 @@ export const useEditorEvents = (
case EventTypes.content: case EventTypes.content:
editor.saveContent({ editor.saveContent({
type: editorMessage.type, type: editorMessage.type,
content: editorMessage.value as string, content: editorMessage.value.html as string,
noteId: noteId, noteId: noteId,
tabId: editorMessage.tabId, tabId: editorMessage.tabId,
ignoreEdit: (editorMessage.value as ContentMessage).ignoreEdit ignoreEdit: (editorMessage.value as ContentMessage).ignoreEdit
@@ -620,6 +621,16 @@ export const useEditorEvents = (
break; break;
} }
case EventTypes.unlock: {
eSendEvent(eUnlockWithPassword, editorMessage.value);
break;
}
case EventTypes.unlockWithBiometrics: {
eSendEvent(eUnlockWithBiometrics);
break;
}
default: default:
break; break;
} }

View File

@@ -24,6 +24,7 @@ import { EVENTS } from "@notesnook/core/dist/common";
import { import {
ContentItem, ContentItem,
ContentType, ContentType,
ItemReference,
Note, Note,
UnencryptedContentItem, UnencryptedContentItem,
isDeleted isDeleted
@@ -43,7 +44,6 @@ import {
import Navigation from "../../../services/navigation"; import Navigation from "../../../services/navigation";
import Notifications from "../../../services/notifications"; import Notifications from "../../../services/notifications";
import SettingsService from "../../../services/settings"; import SettingsService from "../../../services/settings";
import { useSettingStore } from "../../../stores/use-setting-store";
import { useTagStore } from "../../../stores/use-tag-store"; import { useTagStore } from "../../../stores/use-tag-store";
import { import {
eClearEditor, eClearEditor,
@@ -168,7 +168,7 @@ export const useEditor = (
console.log(tabId); console.log(tabId);
}); });
return () => { return () => {
event.unsubscribe(); event?.unsubscribe();
}; };
}); });
@@ -202,7 +202,7 @@ export const useEditor = (
console.log("Resetting tab:", tabId); console.log("Resetting tab:", tabId);
const noteId = useTabStore.getState().getNoteIdForTab(tabId); const noteId = useTabStore.getState().getNoteIdForTab(tabId);
if (noteId) { if (noteId) {
currentNotes.current?.id && db.fs().cancel(noteId, "download"); currentNotes.current?.id && db.fs().cancel(noteId);
currentNotes.current[noteId] = null; currentNotes.current[noteId] = null;
currentContents.current[noteId] = null; currentContents.current[noteId] = null;
editorSessionHistory.clearSession(noteId); editorSessionHistory.clearSession(noteId);
@@ -403,13 +403,16 @@ export const useEditor = (
const tabId = useTabStore.getState().currentTab; const tabId = useTabStore.getState().currentTab;
currentNotes.current && (await reset(tabId)); currentNotes.current && (await reset(tabId));
setTimeout(() => { setTimeout(() => {
if (state.current?.ready) commands.focus(tabId); if (state.current?.ready && !state.current.movedAway)
commands.focus(tabId);
}); });
} else { } else {
if (!event.item) return; if (!event.item) return;
const item = event.item; const item = event.item;
const noteIsLocked = 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 note was already opened in a tab, focus that tab.
if (typeof event.tabId !== "number") { 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. // 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: locked: noteIsLocked
event.item.locked && !(event.item as NoteWithContent).content
}); });
} }
} else { } else {
@@ -512,7 +514,15 @@ export const useEditor = (
}, 300); }, 300);
} }
}, },
[commands, editorSessionHistory, loadContent, postMessage, readonly, reset] [
commands,
editorSessionHistory,
loadContent,
overlay,
postMessage,
readonly,
reset
]
); );
const lockNoteWithVault = useCallback((note: Note) => { const lockNoteWithVault = useCallback((note: Note) => {
@@ -549,7 +559,7 @@ export const useEditor = (
if (data.type === "tiptap" && note) { if (data.type === "tiptap" && note) {
// Handle this case where note was locked on another device and synced. // Handle this case where note was locked on another device and synced.
const locked = await db.vaults.itemExists( const locked = await db.vaults.itemExists(
currentNotes.current[note.id] currentNotes.current[note.id] as ItemReference
); );
if (!locked && isContentEncrypted) { if (!locked && isContentEncrypted) {
lockNoteWithVault(note); lockNoteWithVault(note);
@@ -606,7 +616,6 @@ export const useEditor = (
title, title,
content, content,
type, type,
forSessionId,
ignoreEdit, ignoreEdit,
noteId, noteId,
tabId tabId
@@ -615,7 +624,6 @@ export const useEditor = (
title?: string; title?: string;
content?: string; content?: string;
type: string; type: string;
forSessionId: string;
ignoreEdit: boolean; ignoreEdit: boolean;
tabId: number; tabId: number;
}) => { }) => {
@@ -667,7 +675,6 @@ export const useEditor = (
const restoreEditorState = useCallback(async () => { const restoreEditorState = useCallback(async () => {
const appState = getAppState(); const appState = getAppState();
console.log(appState, "appState");
if (!appState) return; if (!appState) return;
state.current.isRestoringState = true; state.current.isRestoringState = true;
state.current.currentlyEditing = true; state.current.currentlyEditing = true;

View File

@@ -20,6 +20,7 @@ import create from "zustand";
import { persist, StateStorage } from "zustand/middleware"; import { persist, StateStorage } from "zustand/middleware";
import { editorController } from "./utils"; import { editorController } from "./utils";
import { MMKV } from "../../../common/database/mmkv"; import { MMKV } from "../../../common/database/mmkv";
import { db } from "../../../common/database";
class History { class History {
history: number[]; history: number[];
@@ -98,6 +99,8 @@ export type TabStore = {
getCurrentNoteId: () => string | undefined; getCurrentNoteId: () => string | undefined;
getTab: (tabId: number) => TabItem | undefined; getTab: (tabId: number) => TabItem | undefined;
tabHistory: number[]; tabHistory: number[];
biometryAvailable?: boolean;
biometryEnrolled?: boolean;
}; };
function getId(id: number, tabs: TabItem[]): number { function getId(id: number, tabs: TabItem[]): number {
@@ -112,7 +115,9 @@ export function syncTabs() {
editorController.current?.commands.doAsync(` editorController.current?.commands.doAsync(`
globalThis.tabStore?.setState({ globalThis.tabStore?.setState({
tabs: ${JSON.stringify(useTabStore.getState().tabs)}, tabs: ${JSON.stringify(useTabStore.getState().tabs)},
currentTab: ${useTabStore.getState().currentTab} currentTab: ${useTabStore.getState().currentTab},
biometryAvailable: ${useTabStore.getState().biometryAvailable},
biometryEnrolled: ${useTabStore.getState().biometryEnrolled}
}); });
`); `);
} }

View File

@@ -166,5 +166,8 @@ 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 = "615";
export const eUnlockNote = "614"; export const eUnlockNote = "616";
export const eOnChangeFluidTab = "617";
export const eUnlockWithBiometrics = "618";
export const eUnlockWithPassword = "619";

View File

@@ -51,6 +51,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;
@@ -71,6 +72,9 @@ const Tiptap = ({
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const noteStateUpdateTimer = useRef<NodeJS.Timeout>(); const noteStateUpdateTimer = useRef<NodeJS.Timeout>();
const tabRef = useRef<TabItem>(tab); const tabRef = useRef<TabItem>(tab);
const biometryAvailable = useTabStore((state) => state.biometryAvailable);
const biometryEnrolled = useTabStore((state) => state.biometryEnrolled);
const isFocusedRef = useRef<boolean>(false); const isFocusedRef = useRef<boolean>(false);
tabRef.current = tab; tabRef.current = tab;
@@ -161,6 +165,7 @@ const Tiptap = ({
timeFormat: settings.timeFormat as "12-hour" | "24-hour" | undefined, timeFormat: settings.timeFormat as "12-hour" | "24-hour" | undefined,
enableInputRules: settings.markdownShortcuts enableInputRules: settings.markdownShortcuts
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
getContentDiv, getContentDiv,
settings.readonly, settings.readonly,
@@ -169,6 +174,7 @@ const Tiptap = ({
settings.dateFormat, settings.dateFormat,
settings.timeFormat, settings.timeFormat,
settings.markdownShortcuts, settings.markdownShortcuts,
tick,
tab.id tab.id
]); ]);
@@ -372,7 +378,7 @@ const Tiptap = ({
position: "relative" position: "relative"
}} }}
> >
{settings.noHeader ? null : ( {settings.noHeader || tab.locked ? null : (
<> <>
<Tags settings={settings} /> <Tags settings={settings} />
<Title <Title
@@ -392,16 +398,188 @@ const Tiptap = ({
<div <div
style={{ style={{
width: "100%", width: "100%",
height: "95%", height: "90%",
position: "absolute", position: "absolute",
zIndex: 999, zIndex: 999,
backgroundColor: colors.primary.background, backgroundColor: colors.primary.background,
paddingRight: 12, paddingRight: 12,
paddingLeft: 12, paddingLeft: 12,
display: "flex", display: "flex",
flexDirection: "column" flexDirection: "column",
alignItems: "center",
justifyContent: "center",
boxSizing: "border-box",
rowGap: 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>
<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
}}
/>
<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 <div
style={{ style={{
height: 16, height: 16,
@@ -431,6 +609,8 @@ const Tiptap = ({
marginTop: 10 marginTop: 10
}} }}
/> />
</>
)}
</div> </div>
) : null} ) : null}

View File

@@ -109,8 +109,10 @@ export type EditorController = {
setLoading: (value: boolean) => void; setLoading: (value: boolean) => void;
getTableOfContents: () => any[]; getTableOfContents: () => any[];
scrollIntoView: (id: string) => void; scrollIntoView: (id: string) => void;
passwordInputRef: MutableRefObject<HTMLInputElement | null>;
focusPassInput: () => void;
blurPassInput: () => void;
}; };
export function useEditorController({ export function useEditorController({
update, update,
getTableOfContents getTableOfContents
@@ -118,6 +120,7 @@ export function useEditorController({
update: () => void; update: () => void;
getTableOfContents: () => any[]; getTableOfContents: () => any[];
}): EditorController { }): EditorController {
const passwordInputRef = useRef<HTMLInputElement | null>(null);
const tab = useTabContext(); const tab = useTabContext();
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const setTheme = useThemeEngineStore((store) => store.setTheme); const setTheme = useThemeEngineStore((store) => store.setTheme);
@@ -240,8 +243,8 @@ export function useEditorController({
break; break;
} }
case "native:html": case "native:html":
// logger("info", "loading html", htmlContentRef.current);
htmlContentRef.current = value; htmlContentRef.current = value;
logger("info", "loading html");
if (!editor) break; if (!editor) break;
update(); update();
countWords(0); countWords(0);
@@ -379,6 +382,14 @@ export function useEditorController({
countWords(); countWords();
logger("info", `Tab ${tab.id} updated.`); logger("info", `Tab ${tab.id} updated.`);
}, 1); }, 1);
},
passwordInputRef,
focusPassInput: () => {
logger("info", "focus pass input...");
passwordInputRef.current?.focus();
},
blurPassInput: () => {
passwordInputRef.current?.blur();
} }
}; };
} }

View File

@@ -62,6 +62,8 @@ export type TabStore = {
getCurrentNoteId: () => string | undefined; getCurrentNoteId: () => string | undefined;
getTab: (tabId: number) => TabItem | undefined; getTab: (tabId: number) => TabItem | undefined;
setNoteState: (noteId: string, state: Partial<NoteState>) => void; setNoteState: (noteId: string, state: Partial<NoteState>) => void;
biometryAvailable?: boolean;
biometryEnrolled?: boolean;
}; };
function getId(id: number, tabs: TabItem[]): number { function getId(id: number, tabs: TabItem[]): number {

View File

@@ -186,7 +186,9 @@ export const EventTypes = {
tabFocused: "editor-events:tab-focused", tabFocused: "editor-events:tab-focused",
toc: "editor-events:toc", toc: "editor-events:toc",
createInternalLink: "editor-events:create-internal-link", 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; } as const;
export function randId(prefix: string) { export function randId(prefix: string) {