mirror of
https://github.com/streetwriters/notesnook.git
synced 2025-12-19 13:09:30 +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 { 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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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%",
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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,13 +531,18 @@ 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...
|
||||||
const locked = note && (await db.vaults.itemExists(note));
|
for (const tab of useTabStore.getState().tabs) {
|
||||||
if (locked) {
|
const noteId = useTabStore.getState().getTab(tab.id)?.noteId;
|
||||||
useTabStore.getState().updateTab(useTabStore.getState().currentTab, {
|
if (!noteId) continue;
|
||||||
locked: true
|
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,
|
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,214 +185,169 @@ 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]);
|
||||||
|
|
||||||
const unlockWithBiometrics = async () => {
|
useEffect(() => {
|
||||||
try {
|
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;
|
if (!item || !tab) return;
|
||||||
console.log("Trying to unlock with biometrics...");
|
if (!password || password.trim().length === 0) {
|
||||||
let credentials = await BiometicService.getCredentials(
|
ToastManager.show({
|
||||||
"Unlock note",
|
heading: "Password not entered",
|
||||||
"Unlock note to open it in editor. If biometrics are not working, you can enter device pin to unlock vault."
|
message: "Enter a password for the vault and try again.",
|
||||||
);
|
type: "error"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (credentials && credentials?.password) {
|
try {
|
||||||
let note = await db.vault.open(item.id, credentials?.password);
|
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, {
|
eSendEvent(eOnLoadNote, {
|
||||||
item: note
|
item: note
|
||||||
});
|
});
|
||||||
useTabStore.getState().updateTab(tab.id, {
|
useTabStore.getState().updateTab(tab.id, {
|
||||||
locked: false
|
locked: false
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
ToastManager.show({
|
||||||
|
heading: "Incorrect password",
|
||||||
|
type: "error",
|
||||||
|
context: "local"
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
};
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const unlock = () => {
|
const unlock = () => {
|
||||||
if (
|
if (
|
||||||
isLocked &&
|
(tab?.locked,
|
||||||
biometryAvailable &&
|
useTabStore.getState().biometryAvailable &&
|
||||||
biometryEnrolled &&
|
useTabStore.getState().biometryEnrolled &&
|
||||||
!editorState().movedAway
|
!editorState().movedAway)
|
||||||
) {
|
) {
|
||||||
unlockWithBiometrics();
|
unlockWithBiometrics();
|
||||||
} else {
|
} else {
|
||||||
console.log("Biometrics unavailable.");
|
console.log("Biometrics unavailable.", editorState().movedAway);
|
||||||
if (isLocked && !editorState().movedAway) {
|
if (!editorState().movedAway) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
passInputRef.current?.focus();
|
if (tab && tab?.locked) {
|
||||||
}, 300);
|
editorController.current?.commands.focus(tab?.id);
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const sub = eSubscribeEvent(eUnlockNote, unlock);
|
const subs = [
|
||||||
unlock();
|
eSubscribeEvent(eUnlockNote, unlock),
|
||||||
|
eSubscribeEvent(eUnlockWithBiometrics, () => {
|
||||||
|
unlock();
|
||||||
|
}),
|
||||||
|
eSubscribeEvent(eUnlockWithPassword, onSubmit)
|
||||||
|
];
|
||||||
|
if (tab?.locked) {
|
||||||
|
unlock();
|
||||||
|
}
|
||||||
return () => {
|
return () => {
|
||||||
sub.unsubscribe();
|
subs.map((s) => s?.unsubscribe());
|
||||||
};
|
};
|
||||||
}, [isLocked, biometryAvailable, biometryEnrolled]);
|
}, [item, tab]);
|
||||||
|
|
||||||
const onSubmit = async () => {
|
return null;
|
||||||
if (!item || !tab) return;
|
|
||||||
|
|
||||||
if (!password.current || password.current.trim().length === 0) {
|
|
||||||
ToastManager.show({
|
|
||||||
heading: "Password not entered",
|
|
||||||
message: "Enter a password for the vault and try again.",
|
|
||||||
type: "error"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
let note = await db.vault.open(item.id, password.current);
|
|
||||||
if (enrollBiometrics) {
|
|
||||||
try {
|
|
||||||
await db.vault.unlock(password.current);
|
|
||||||
await BiometicService.storeCredentials(password.current);
|
|
||||||
eSendEvent("vaultUpdated");
|
|
||||||
ToastManager.show({
|
|
||||||
heading: "Biometric unlocking enabled!",
|
|
||||||
message: "Now you can unlock notes in vault with biometrics.",
|
|
||||||
type: "success",
|
|
||||||
context: "global"
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
ToastManager.show({
|
|
||||||
heading: "Incorrect password",
|
|
||||||
message:
|
|
||||||
"Please enter the correct vault password to enable biometrics.",
|
|
||||||
type: "error",
|
|
||||||
context: "local"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
eSendEvent(eOnLoadNote, {
|
|
||||||
item: note
|
|
||||||
});
|
|
||||||
useTabStore.getState().updateTab(tab.id, {
|
|
||||||
locked: false
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
ToastManager.show({
|
|
||||||
heading: "Incorrect password",
|
|
||||||
type: "error",
|
|
||||||
context: "local"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return isLocked ? (
|
|
||||||
<ScrollView
|
|
||||||
style={{
|
|
||||||
width: "100%",
|
|
||||||
height: height,
|
|
||||||
backgroundColor: colors.primary.background,
|
|
||||||
position: "absolute",
|
|
||||||
top: 50 + insets.top,
|
|
||||||
zIndex: 999
|
|
||||||
}}
|
|
||||||
contentContainerStyle={{
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "center",
|
|
||||||
height: height
|
|
||||||
}}
|
|
||||||
keyboardDismissMode="interactive"
|
|
||||||
keyboardShouldPersistTaps="handled"
|
|
||||||
>
|
|
||||||
<Heading>{item.title}</Heading>
|
|
||||||
<Paragraph>This note is locked.</Paragraph>
|
|
||||||
<Seperator />
|
|
||||||
<Input
|
|
||||||
fwdRef={passInputRef}
|
|
||||||
autoCapitalize="none"
|
|
||||||
testID={notesnook.ids.dialogs.vault.pwd}
|
|
||||||
onChangeText={(value) => {
|
|
||||||
password.current = value;
|
|
||||||
}}
|
|
||||||
wrapperStyle={{
|
|
||||||
width: 300
|
|
||||||
}}
|
|
||||||
marginBottom={10}
|
|
||||||
onSubmit={() => {
|
|
||||||
onSubmit();
|
|
||||||
}}
|
|
||||||
autoComplete="password"
|
|
||||||
returnKeyLabel="Unlock"
|
|
||||||
returnKeyType={"done"}
|
|
||||||
secureTextEntry
|
|
||||||
placeholder="Password"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
title="Unlock note"
|
|
||||||
type="accent"
|
|
||||||
onPress={() => {
|
|
||||||
onSubmit();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{biometryAvailable && !biometryEnrolled ? (
|
|
||||||
<Button
|
|
||||||
title="Enable biometric unlocking"
|
|
||||||
type={enrollBiometrics ? "accent" : "gray"}
|
|
||||||
onPress={() => {
|
|
||||||
setEnrollBiometrics(!enrollBiometrics);
|
|
||||||
}}
|
|
||||||
style={{
|
|
||||||
marginTop: 10
|
|
||||||
}}
|
|
||||||
icon={
|
|
||||||
enrollBiometrics
|
|
||||||
? "check-circle-outline"
|
|
||||||
: "checkbox-blank-circle-outline"
|
|
||||||
}
|
|
||||||
iconSize={20}
|
|
||||||
/>
|
|
||||||
) : biometryEnrolled && biometryAvailable ? (
|
|
||||||
<IconButton
|
|
||||||
name="fingerprint"
|
|
||||||
type="gray"
|
|
||||||
customStyle={{
|
|
||||||
marginTop: 20
|
|
||||||
}}
|
|
||||||
size={40}
|
|
||||||
onPress={() => {
|
|
||||||
unlockWithBiometrics();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</ScrollView>
|
|
||||||
) : null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const ReadonlyButton = ({ editor }: { editor: useEditorType }) => {
|
const ReadonlyButton = ({ editor }: { editor: useEditorType }) => {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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}
|
||||||
});
|
});
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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,45 +398,219 @@ 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
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
{tab.locked ? (
|
||||||
style={{
|
<>
|
||||||
height: 16,
|
<p
|
||||||
width: "94%",
|
style={{
|
||||||
backgroundColor: colors.secondary.background,
|
color: colors.primary.paragraph,
|
||||||
borderRadius: 5,
|
fontSize: 20,
|
||||||
marginTop: 10
|
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
|
<form
|
||||||
style={{
|
onSubmit={(e) => {
|
||||||
height: 16,
|
e.preventDefault();
|
||||||
width: "94%",
|
const data = new FormData(e.currentTarget);
|
||||||
backgroundColor: colors.secondary.background,
|
const password = data.get("password");
|
||||||
borderRadius: 5,
|
const biometrics = data.get("enrollBiometrics");
|
||||||
marginTop: 10
|
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
|
<button
|
||||||
style={{
|
style={{
|
||||||
height: 16,
|
backgroundColor: colors.primary.accent,
|
||||||
width: 200,
|
borderRadius: 5,
|
||||||
backgroundColor: colors.secondary.background,
|
boxSizing: "border-box",
|
||||||
borderRadius: 5,
|
border: "none",
|
||||||
marginTop: 10
|
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>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user