mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-30 10:39:07 +02:00
Compare commits
1 Commits
fix_releas
...
fix-backup
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d68b7f1963 |
@@ -182,17 +182,18 @@ const Actions = ({
|
||||
relations
|
||||
.map((relation) => relation.fromId)
|
||||
.forEach(async (id) => {
|
||||
useTabStore.getState().forEachNoteTab(id, async (tab) => {
|
||||
const isFocused = useTabStore.getState().currentTab === tab.id;
|
||||
const tab = useTabStore.getState().getTabForNote(id);
|
||||
if (tab !== undefined) {
|
||||
const isFocused = useTabStore.getState().currentTab === tab;
|
||||
if (isFocused) {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: await db.notes.note(id),
|
||||
forced: true
|
||||
});
|
||||
} else {
|
||||
editorController.current.commands.setLoading(true, tab.id);
|
||||
editorController.current.commands.setLoading(true, tab);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
close?.();
|
||||
},
|
||||
|
||||
@@ -141,7 +141,7 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
|
||||
close();
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: item,
|
||||
newTab: true
|
||||
presistTab: true
|
||||
});
|
||||
if (!DDS.isTab) {
|
||||
tabBarRef.current?.goToPage(1);
|
||||
|
||||
@@ -16,8 +16,8 @@ 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 { EVENTS, Note } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { Note } from "@notesnook/core";
|
||||
import { EVENTS } from "@notesnook/core";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useEffect } from "react";
|
||||
import { View } from "react-native";
|
||||
@@ -25,18 +25,27 @@ import { FlatList } from "react-native-actions-sheet";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../../common/database";
|
||||
import { useDBItem } from "../../../hooks/use-db-item";
|
||||
import {
|
||||
TabItem,
|
||||
useTabStore
|
||||
} from "../../../screens/editor/tiptap/use-tab-store";
|
||||
import { useTabStore } from "../../../screens/editor/tiptap/use-tab-store";
|
||||
import { editorController } from "../../../screens/editor/tiptap/utils";
|
||||
import { eSendEvent, presentSheet } from "../../../services/event-manager";
|
||||
import { eUnlockNote } from "../../../utils/events";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Button } from "../../ui/button";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import { Pressable } from "../../ui/pressable";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type TabItem = {
|
||||
id: number;
|
||||
noteId?: string;
|
||||
previewTab?: boolean;
|
||||
locked?: boolean;
|
||||
noteLocked?: boolean;
|
||||
readonly?: boolean;
|
||||
pinned?: boolean;
|
||||
};
|
||||
|
||||
const TabItemComponent = (props: {
|
||||
tab: TabItem;
|
||||
@@ -44,7 +53,7 @@ const TabItemComponent = (props: {
|
||||
close?: (ctx?: string | undefined) => void;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const [item, update] = useDBItem(props.tab.session?.noteId, "note");
|
||||
const [item, update] = useDBItem(props.tab.noteId, "note");
|
||||
|
||||
useEffect(() => {
|
||||
const syncCompletedSubscription = db.eventManager?.subscribe(
|
||||
@@ -76,11 +85,11 @@ const TabItemComponent = (props: {
|
||||
onPress={() => {
|
||||
if (!props.isFocused) {
|
||||
useTabStore.getState().focusTab(props.tab.id);
|
||||
if (props.tab.session?.locked) {
|
||||
if (props.tab.locked) {
|
||||
eSendEvent(eUnlockNote);
|
||||
}
|
||||
|
||||
if (!props.tab.session?.noteId) {
|
||||
if (!props.tab.noteId) {
|
||||
setTimeout(() => {
|
||||
editorController?.current?.commands?.focus(props.tab.id);
|
||||
}, 300);
|
||||
@@ -88,6 +97,11 @@ const TabItemComponent = (props: {
|
||||
}
|
||||
props.close?.();
|
||||
}}
|
||||
onLongPress={() => {
|
||||
useTabStore.getState().updateTab(props.tab.id, {
|
||||
previewTab: false
|
||||
});
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
@@ -98,9 +112,9 @@ const TabItemComponent = (props: {
|
||||
flexShrink: 1
|
||||
}}
|
||||
>
|
||||
{props.tab.session?.noteLocked ? (
|
||||
{props.tab.noteLocked ? (
|
||||
<>
|
||||
{props.tab.session?.locked ? (
|
||||
{props.tab.locked ? (
|
||||
<Icon size={SIZE.md} name="lock" />
|
||||
) : (
|
||||
<Icon size={SIZE.md} name="lock-open-outline" />
|
||||
@@ -108,9 +122,7 @@ const TabItemComponent = (props: {
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{props.tab.session?.readonly ? (
|
||||
<Icon size={SIZE.md} name="pencil-lock" />
|
||||
) : null}
|
||||
{props.tab.readonly ? <Icon size={SIZE.md} name="pencil-lock" /> : null}
|
||||
|
||||
<Paragraph
|
||||
color={
|
||||
@@ -118,10 +130,15 @@ const TabItemComponent = (props: {
|
||||
? colors.selected.paragraph
|
||||
: colors.primary.paragraph
|
||||
}
|
||||
style={{
|
||||
fontFamily: props.tab.previewTab
|
||||
? "OpenSans-Italic"
|
||||
: "OpenSans-Regular"
|
||||
}}
|
||||
numberOfLines={1}
|
||||
size={SIZE.md}
|
||||
>
|
||||
{props.tab.session?.noteId
|
||||
{props.tab.noteId
|
||||
? item?.title || strings.untitledNote()
|
||||
: strings.newNote()}
|
||||
</Paragraph>
|
||||
@@ -140,7 +157,8 @@ const TabItemComponent = (props: {
|
||||
color={props.tab.pinned ? colors.primary.accent : colors.primary.icon}
|
||||
onPress={() => {
|
||||
useTabStore.getState().updateTab(props.tab.id, {
|
||||
pinned: !props.tab.pinned
|
||||
pinned: !props.tab.pinned,
|
||||
previewTab: false
|
||||
});
|
||||
}}
|
||||
top={0}
|
||||
@@ -179,7 +197,6 @@ export default function EditorTabs({
|
||||
}: {
|
||||
close?: (ctx?: string | undefined) => void;
|
||||
}) {
|
||||
const { colors } = useThemeColors();
|
||||
const [tabs, currentTab] = useTabStore((state) => [
|
||||
state.tabs,
|
||||
state.currentTab
|
||||
@@ -216,13 +233,25 @@ export default function EditorTabs({
|
||||
}}
|
||||
>
|
||||
<Heading size={SIZE.lg}>{strings.tabs()}</Heading>
|
||||
<IconButton
|
||||
<Button
|
||||
onPress={() => {
|
||||
useTabStore.getState().newTab();
|
||||
setTimeout(() => {
|
||||
editorController?.current?.commands?.focus(
|
||||
useTabStore.getState().currentTab
|
||||
);
|
||||
}, 500);
|
||||
close?.();
|
||||
}}
|
||||
name="plus"
|
||||
color={colors.primary.accent}
|
||||
title={strings.newTab()}
|
||||
icon="plus"
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-start",
|
||||
borderRadius: 100,
|
||||
height: 35
|
||||
}}
|
||||
iconSize={SIZE.lg}
|
||||
/>
|
||||
</View>
|
||||
|
||||
|
||||
@@ -22,22 +22,24 @@ import {
|
||||
VirtualizedGrouping,
|
||||
createInternalLink
|
||||
} from "@notesnook/core";
|
||||
import type { LinkAttributes } from "@notesnook/editor";
|
||||
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { TextInput, View } from "react-native";
|
||||
import { FlatList } from "react-native-actions-sheet";
|
||||
import { db } from "../../../common/database";
|
||||
import { useDBItem } from "../../../hooks/use-db-item";
|
||||
import { editorController } from "../../../screens/editor/tiptap/utils";
|
||||
import { presentSheet } from "../../../services/event-manager";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Button } from "../../ui/button";
|
||||
import Input from "../../ui/input";
|
||||
import { Pressable } from "../../ui/pressable";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import type { LinkAttributes } from "@notesnook/editor";
|
||||
import {
|
||||
EditorEvents,
|
||||
editorController
|
||||
} from "../../../screens/editor/tiptap/utils";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
const ListNoteItem = ({
|
||||
id,
|
||||
@@ -192,7 +194,7 @@ export default function LinkNote(props: {
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
editorController.current?.postMessage(NativeEvents.resolve, {
|
||||
editorController.current?.postMessage(EditorEvents.resolve, {
|
||||
data: {
|
||||
href: link,
|
||||
title: selectedNote.title
|
||||
|
||||
@@ -43,7 +43,7 @@ export const Update = ({ version: appVersion, fwdRef }) => {
|
||||
let notes = version?.notes
|
||||
? version.notes.replace("Thank you for using Notesnook!", "").split("- ")
|
||||
: ["Bug fixes and performance improvements"];
|
||||
notes = notes?.map((n) => n.replace(/\n|<br>/g, ""));
|
||||
notes = notes?.map((n) => n.replace(/\n/g, ""));
|
||||
const isGithubRelease = Config.GITHUB_RELEASE === "true";
|
||||
|
||||
const getSupportedAbi = () => {
|
||||
@@ -178,20 +178,18 @@ export const Update = ({ version: appVersion, fwdRef }) => {
|
||||
{version.body}
|
||||
</Paragraph>
|
||||
) : null}
|
||||
{notes.map((item) =>
|
||||
item && item !== "" ? (
|
||||
<Paragraph
|
||||
key={item}
|
||||
color={colors.secondary.paragraph}
|
||||
style={{
|
||||
marginBottom: 5
|
||||
}}
|
||||
selectable
|
||||
>
|
||||
• {item}
|
||||
</Paragraph>
|
||||
) : null
|
||||
)}
|
||||
{notes.map((item) => (
|
||||
<Paragraph
|
||||
key={item}
|
||||
color={colors.secondary.paragraph}
|
||||
style={{
|
||||
marginBottom: 5
|
||||
}}
|
||||
selectable
|
||||
>
|
||||
• {item}
|
||||
</Paragraph>
|
||||
))}
|
||||
</ScrollView>
|
||||
<Seperator />
|
||||
<Button
|
||||
|
||||
@@ -19,14 +19,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
/* eslint-disable no-inner-declarations */
|
||||
import {
|
||||
Color,
|
||||
createInternalLink,
|
||||
ItemReference,
|
||||
Note,
|
||||
Notebook,
|
||||
Reminder,
|
||||
Tag,
|
||||
TrashItem,
|
||||
VAULT_ERRORS
|
||||
VAULT_ERRORS,
|
||||
createInternalLink
|
||||
} from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { DisplayedNotification } from "@notifee/react-native";
|
||||
@@ -50,11 +50,11 @@ import ReminderSheet from "../components/sheets/reminder";
|
||||
import { useSideBarDraggingStore } from "../components/side-menu/dragging-store";
|
||||
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
|
||||
import {
|
||||
ToastManager,
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
openVault,
|
||||
presentSheet,
|
||||
ToastManager
|
||||
presentSheet
|
||||
} from "../services/event-manager";
|
||||
import Navigation from "../services/navigation";
|
||||
import Notifications from "../services/notifications";
|
||||
@@ -544,13 +544,14 @@ export const useActions = ({
|
||||
const toggleReadyOnlyMode = async () => {
|
||||
const currentReadOnly = (item as Note).readonly;
|
||||
await db.notes.readonly(!currentReadOnly, item?.id);
|
||||
useTabStore.getState().forEachNoteTab(item.id, (tab) => {
|
||||
useTabStore.getState().updateTab(tab.id, {
|
||||
session: {
|
||||
readonly: !currentReadOnly
|
||||
}
|
||||
|
||||
if (useTabStore.getState().hasTabForNote(item.id)) {
|
||||
const tabId = useTabStore.getState().getTabForNote(item.id);
|
||||
if (!tabId) return;
|
||||
useTabStore.getState().updateTab(tabId, {
|
||||
readonly: !currentReadOnly
|
||||
});
|
||||
});
|
||||
}
|
||||
Navigation.queueRoutesForUpdate();
|
||||
close();
|
||||
};
|
||||
|
||||
@@ -51,10 +51,7 @@ import { endProgress, startProgress } from "../components/dialogs/progress";
|
||||
import Migrate from "../components/sheets/migrate";
|
||||
import NewFeature from "../components/sheets/new-feature";
|
||||
import { Walkthrough } from "../components/walkthroughs";
|
||||
import {
|
||||
resetTabStore,
|
||||
useTabStore
|
||||
} from "../screens/editor/tiptap/use-tab-store";
|
||||
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
|
||||
import {
|
||||
clearAppState,
|
||||
editorController,
|
||||
@@ -106,7 +103,6 @@ import { getGithubVersion } from "../utils/github-version";
|
||||
import { tabBarRef } from "../utils/global-refs";
|
||||
import { NotesnookModule } from "../utils/notesnook-module";
|
||||
import { sleep } from "../utils/time";
|
||||
import ReminderSheet from "../components/sheets/reminder";
|
||||
|
||||
const onCheckSyncStatus = async (type: SyncStatusEvent) => {
|
||||
const { disableSync, disableAutoSync } = SettingsService.get();
|
||||
@@ -161,8 +157,6 @@ const onUserSessionExpired = async () => {
|
||||
|
||||
const onAppOpenedFromURL = async (event: { url: string }) => {
|
||||
const url = event.url;
|
||||
|
||||
console.log("URL", url);
|
||||
try {
|
||||
if (url.startsWith("https://app.notesnook.com/account/verified")) {
|
||||
await onUserEmailVerified();
|
||||
@@ -172,25 +166,6 @@ const onAppOpenedFromURL = async (event: { url: string }) => {
|
||||
eSendEvent(eOnLoadNote, { newNote: true });
|
||||
tabBarRef.current?.goToPage(1, false);
|
||||
return;
|
||||
} else if (url.startsWith("https://notesnook.com/open_note")) {
|
||||
const id = new URL(url).searchParams.get("id");
|
||||
if (id) {
|
||||
const note = await db.notes.note(id);
|
||||
if (note) {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note
|
||||
});
|
||||
tabBarRef.current?.goToPage(1, false);
|
||||
}
|
||||
}
|
||||
} else if (url.startsWith("https://notesnook.com/open_reminder")) {
|
||||
const id = new URL(url).searchParams.get("id");
|
||||
if (id) {
|
||||
const reminder = await db.reminders.reminder(id);
|
||||
if (reminder) ReminderSheet.present(reminder);
|
||||
}
|
||||
} else if (url.startsWith("https://notesnook.com/new_reminder")) {
|
||||
ReminderSheet.present();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@@ -247,7 +222,6 @@ const onLogout = async (reason: string) => {
|
||||
await PremiumService.setPremiumStatus();
|
||||
await BiometricService.resetCredentials();
|
||||
MMKV.clearStore();
|
||||
resetTabStore();
|
||||
clearAllStores();
|
||||
setImmediate(() => {
|
||||
refreshAllStores();
|
||||
@@ -646,17 +620,22 @@ export const useAppEvents = () => {
|
||||
EV.subscribe(EVENTS.vaultLocked, async () => {
|
||||
// Lock all notes in all tabs...
|
||||
for (const tab of useTabStore.getState().tabs) {
|
||||
const noteId = useTabStore.getState().getTab(tab.id)?.session?.noteId;
|
||||
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, {
|
||||
session: {
|
||||
locked: true,
|
||||
noteLocked: true
|
||||
}
|
||||
locked: true
|
||||
});
|
||||
if (
|
||||
tab.id === useTabStore.getState().currentTab &&
|
||||
locked &&
|
||||
!editorState().movedAway
|
||||
) {
|
||||
// Show unlock note screen.
|
||||
eSendEvent(eUnlockNote);
|
||||
}
|
||||
}
|
||||
}
|
||||
}),
|
||||
@@ -716,6 +695,10 @@ export const useAppEvents = () => {
|
||||
// Reset the editor if the app has been in background for more than 10 minutes.
|
||||
eSendEvent(eEditorReset);
|
||||
}
|
||||
|
||||
setTimeout(async () => {
|
||||
IntentService.onAppStateChanged();
|
||||
}, 100);
|
||||
} else {
|
||||
await saveEditorState();
|
||||
if (
|
||||
|
||||
@@ -529,9 +529,7 @@ const onChangeTab = async (event) => {
|
||||
const locked = note && (await db.vaults.itemExists(note));
|
||||
if (locked) {
|
||||
useTabStore.getState().updateTab(tab.id, {
|
||||
session: {
|
||||
locked: true
|
||||
}
|
||||
locked: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,12 +39,7 @@
|
||||
"@lingui/core": "5.1.2",
|
||||
"@lingui/react": "5.1.2",
|
||||
"react-native-check-version": "^1.3.0",
|
||||
"react-native-material-menu": "^2.0.0",
|
||||
"@trpc/client": "^10.45.2",
|
||||
"@trpc/react-query": "^10.45.2",
|
||||
"@trpc/server": "^10.45.2",
|
||||
"@tanstack/react-query": "^4.36.1",
|
||||
"async-mutex": "0.5.0"
|
||||
"react-native-material-menu": "^2.0.0"
|
||||
},
|
||||
"sideEffects": false
|
||||
}
|
||||
|
||||
@@ -19,8 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
|
||||
import { i18n } from "@lingui/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import React, {
|
||||
forwardRef,
|
||||
useCallback,
|
||||
@@ -48,7 +46,6 @@ import {
|
||||
eUnlockWithPassword
|
||||
} from "../../utils/events";
|
||||
import { openLinkInBrowser } from "../../utils/functions";
|
||||
import { tabBarRef } from "../../utils/global-refs";
|
||||
import EditorOverlay from "./loading";
|
||||
import { EDITOR_URI } from "./source";
|
||||
import { EditorProps, useEditorType } from "./tiptap/types";
|
||||
@@ -61,6 +58,9 @@ import {
|
||||
openInternalLink,
|
||||
randId
|
||||
} from "./tiptap/utils";
|
||||
import { tabBarRef } from "../../utils/global-refs";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { i18n } from "@lingui/core";
|
||||
|
||||
const style: ViewStyle = {
|
||||
height: "100%",
|
||||
@@ -203,13 +203,11 @@ const useLockedNoteHandler = () => {
|
||||
|
||||
useEffect(() => {
|
||||
for (const tab of useTabStore.getState().tabs) {
|
||||
const noteId = useTabStore.getState().getTab(tab.id)?.session?.noteId;
|
||||
const noteId = useTabStore.getState().getTab(tab.id)?.noteId;
|
||||
if (!noteId) continue;
|
||||
if (tabRef.current && tabRef.current.session?.noteLocked) {
|
||||
if (tabRef.current && tabRef.current.noteLocked) {
|
||||
useTabStore.getState().updateTab(tabRef.current.id, {
|
||||
session: {
|
||||
locked: true
|
||||
}
|
||||
locked: true
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -223,27 +221,23 @@ const useLockedNoteHandler = () => {
|
||||
biometryAvailable: !!biometry,
|
||||
biometryEnrolled: !!fingerprint
|
||||
});
|
||||
syncTabs("biometry");
|
||||
syncTabs();
|
||||
})();
|
||||
}, [tab?.id]);
|
||||
|
||||
useEffect(() => {
|
||||
const unlockWithBiometrics = async () => {
|
||||
try {
|
||||
if (!tabRef.current?.session?.noteLocked || !tabRef.current) return;
|
||||
console.log("Trying to unlock with biometrics...");
|
||||
if (!tabRef.current?.noteLocked || !tabRef.current) return;
|
||||
|
||||
const credentials = await BiometricService.getCredentials(
|
||||
"Unlock note",
|
||||
"Unlock note to open it in editor."
|
||||
);
|
||||
|
||||
if (
|
||||
credentials &&
|
||||
credentials?.password &&
|
||||
tabRef.current.session?.noteId
|
||||
) {
|
||||
if (credentials && credentials?.password && tabRef.current.noteId) {
|
||||
const note = await db.vault.open(
|
||||
tabRef.current.session?.noteId,
|
||||
tabRef.current.noteId,
|
||||
credentials?.password
|
||||
);
|
||||
|
||||
@@ -252,9 +246,7 @@ const useLockedNoteHandler = () => {
|
||||
});
|
||||
|
||||
useTabStore.getState().updateTab(tabRef.current.id, {
|
||||
session: {
|
||||
locked: false
|
||||
}
|
||||
locked: false
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -269,7 +261,7 @@ const useLockedNoteHandler = () => {
|
||||
password: string;
|
||||
biometrics?: boolean;
|
||||
}) => {
|
||||
if (!tabRef.current?.session?.noteId || !tabRef.current) return;
|
||||
if (!tabRef.current?.noteId || !tabRef.current) return;
|
||||
if (!password || password.trim().length === 0) {
|
||||
ToastManager.show({
|
||||
heading: strings.passwordNotEntered(),
|
||||
@@ -279,10 +271,7 @@ const useLockedNoteHandler = () => {
|
||||
}
|
||||
|
||||
try {
|
||||
const note = await db.vault.open(
|
||||
tabRef.current?.session?.noteId,
|
||||
password
|
||||
);
|
||||
const note = await db.vault.open(tabRef.current?.noteId, password);
|
||||
if (enrollBiometrics && note) {
|
||||
try {
|
||||
const unlocked = await db.vault.unlock(password);
|
||||
@@ -313,9 +302,7 @@ const useLockedNoteHandler = () => {
|
||||
item: note
|
||||
});
|
||||
useTabStore.getState().updateTab(tabRef.current.id, {
|
||||
session: {
|
||||
locked: false
|
||||
}
|
||||
locked: false
|
||||
});
|
||||
} catch (e) {
|
||||
ToastManager.show({
|
||||
@@ -327,7 +314,7 @@ const useLockedNoteHandler = () => {
|
||||
|
||||
const unlock = () => {
|
||||
if (
|
||||
(tabRef.current?.session?.locked,
|
||||
(tabRef.current?.locked,
|
||||
useTabStore.getState().biometryAvailable &&
|
||||
useTabStore.getState().biometryEnrolled &&
|
||||
!editorState().movedAway)
|
||||
@@ -338,7 +325,7 @@ const useLockedNoteHandler = () => {
|
||||
} else {
|
||||
if (!editorState().movedAway) {
|
||||
setTimeout(() => {
|
||||
if (tabRef.current && tabRef.current?.session?.locked) {
|
||||
if (tabRef.current && tabRef.current?.locked) {
|
||||
editorController.current?.commands.focus(tabRef.current?.id);
|
||||
}
|
||||
}, 100);
|
||||
@@ -353,13 +340,13 @@ const useLockedNoteHandler = () => {
|
||||
}),
|
||||
eSubscribeEvent(eUnlockWithPassword, onSubmit)
|
||||
];
|
||||
if (tabRef.current?.session?.locked && tabBarRef.current?.page() === 2) {
|
||||
if (tabRef.current?.locked && tabBarRef.current?.page() === 2) {
|
||||
unlock();
|
||||
}
|
||||
return () => {
|
||||
subs.map((s) => s?.unsubscribe());
|
||||
};
|
||||
}, [tab?.id, tab?.session?.locked]);
|
||||
}, [tab?.id, tab?.locked]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -27,10 +27,10 @@ import WebView from "react-native-webview";
|
||||
import { useRef } from "react";
|
||||
import { EDITOR_URI } from "./source";
|
||||
import { EditorMessage } from "./tiptap/types";
|
||||
import { EditorEvents } from "@notesnook/editor-mobile/src/utils/editor-events";
|
||||
import { EventTypes } from "./tiptap/editor-events";
|
||||
import { Attachment } from "@notesnook/editor";
|
||||
import downloadAttachment from "../../common/filesystem/download-attachment";
|
||||
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
|
||||
import { EditorEvents } from "./tiptap/utils";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import { db } from "../../common/database";
|
||||
@@ -69,11 +69,11 @@ export function ReadonlyEditor(props: {
|
||||
const data = event.nativeEvent.data;
|
||||
const editorMessage = JSON.parse(data) as EditorMessage<any>;
|
||||
|
||||
if (editorMessage.type === EditorEvents.logger) {
|
||||
if (editorMessage.type === EventTypes.logger) {
|
||||
logger.info("[READONLY EDITOR LOG]", editorMessage.value);
|
||||
}
|
||||
|
||||
if (editorMessage.type === EditorEvents.readonlyEditorLoaded) {
|
||||
if (editorMessage.type === EventTypes.readonlyEditorLoaded) {
|
||||
props.onLoad?.((content: { data: string; id: string }) => {
|
||||
setTimeout(() => {
|
||||
noteId.current = content.id;
|
||||
@@ -86,7 +86,7 @@ export function ReadonlyEditor(props: {
|
||||
setLoading(false);
|
||||
}, 300);
|
||||
});
|
||||
} else if (editorMessage.type === EditorEvents.getAttachmentData) {
|
||||
} else if (editorMessage.type === EventTypes.getAttachmentData) {
|
||||
const attachment = (editorMessage.value as any).attachment as Attachment;
|
||||
|
||||
downloadAttachment(attachment.hash, true, {
|
||||
@@ -104,7 +104,7 @@ export function ReadonlyEditor(props: {
|
||||
);
|
||||
editorRef.current?.postMessage(
|
||||
JSON.stringify({
|
||||
type: NativeEvents.attachmentData,
|
||||
type: EditorEvents.attachmentData,
|
||||
value: {
|
||||
resolverId: (editorMessage.value as any).resolverId,
|
||||
data
|
||||
@@ -115,7 +115,7 @@ export function ReadonlyEditor(props: {
|
||||
.catch(() => {
|
||||
editorRef.current?.postMessage(
|
||||
JSON.stringify({
|
||||
type: NativeEvents.attachmentData,
|
||||
type: EditorEvents.attachmentData,
|
||||
data: {
|
||||
resolverId: (editorMessage.value as any).resolverId,
|
||||
data: undefined
|
||||
|
||||
@@ -18,11 +18,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Note } from "@notesnook/core";
|
||||
import type {
|
||||
Attachment,
|
||||
ImageAttributes,
|
||||
LinkAttributes
|
||||
} from "@notesnook/editor";
|
||||
import type { Attachment } from "@notesnook/editor";
|
||||
import type { ImageAttributes } from "@notesnook/editor";
|
||||
import type { LinkAttributes } from "@notesnook/editor";
|
||||
import { createRef, RefObject } from "react";
|
||||
import { Platform } from "react-native";
|
||||
import { EdgeInsets } from "react-native-safe-area-context";
|
||||
@@ -39,6 +37,9 @@ async function call(webview: RefObject<WebView | undefined>, action?: Action) {
|
||||
if (!webview.current || !action) return;
|
||||
setImmediate(() => webview.current?.injectJavaScript(action.job));
|
||||
const response = await getResponse(action.id);
|
||||
// if (!response) {
|
||||
// console.warn("webview job failed", action.id);
|
||||
// }
|
||||
return response ? response.value : response;
|
||||
}
|
||||
|
||||
@@ -74,65 +75,110 @@ class Commands {
|
||||
return call(this.ref, fn(job, name)) as Promise<T>;
|
||||
}
|
||||
|
||||
async sendCommand<T>(command: string, ...args: any[]) {
|
||||
return this.doAsync(
|
||||
`response = globalThis.commands.${command}(${args
|
||||
.map((arg) =>
|
||||
typeof arg === "string" ? `"${arg}"` : JSON.stringify(arg)
|
||||
)
|
||||
.join(",")})`,
|
||||
command
|
||||
);
|
||||
}
|
||||
|
||||
focus = async (tabId: string) => {
|
||||
focus = async (tabId: number) => {
|
||||
if (!this.ref.current) return;
|
||||
|
||||
const locked = useTabStore.getState().getTab(tabId)?.session?.locked;
|
||||
if (Platform.OS === "android") {
|
||||
//this.ref.current?.requestFocus();
|
||||
setTimeout(async () => {
|
||||
if (!this.ref) return;
|
||||
textInput.current?.focus();
|
||||
await this.sendCommand("focus", tabId, locked);
|
||||
|
||||
const locked = useTabStore.getState().getTab(tabId)?.locked;
|
||||
await this.doAsync(
|
||||
locked
|
||||
? `editorControllers[${tabId}]?.focusPassInput();`
|
||||
: `editors[${tabId}]?.commands.focus()`,
|
||||
"focus"
|
||||
);
|
||||
|
||||
this.ref?.current?.requestFocus();
|
||||
}, 1);
|
||||
} else {
|
||||
await sleep(400);
|
||||
await this.sendCommand("focus", tabId, locked);
|
||||
await this.doAsync(`editors[${tabId}]?.commands.focus()`, "focus");
|
||||
}
|
||||
};
|
||||
|
||||
blur = async (tabId: string) => this.sendCommand("blur", tabId);
|
||||
blur = async (tabId: number) =>
|
||||
await this.doAsync(
|
||||
`
|
||||
const editor = editors[${tabId}];
|
||||
const editorTitle = editorTitles[${tabId}];
|
||||
typeof editor !== "undefined" && editor.commands.blur();
|
||||
typeof editorTitle !== "undefined" && editorTitle.current && editorTitle.current.blur();
|
||||
|
||||
editorControllers[${tabId}]?.blurPassInput();
|
||||
|
||||
clearContent = async (tabId: string) => {
|
||||
`,
|
||||
"blur"
|
||||
);
|
||||
|
||||
clearContent = async (tabId: number) => {
|
||||
this.previousSettings = null;
|
||||
await this.sendCommand("clearContent", tabId);
|
||||
await this.doAsync(
|
||||
`
|
||||
const editor = editors[${tabId}];
|
||||
const editorController = editorControllers[${tabId}];
|
||||
const editorTitle = editorTitles[${tabId}];
|
||||
const statusBar = statusBars[${tabId}];
|
||||
|
||||
if (typeof editor !== "undefined") {
|
||||
editor.commands.blur();
|
||||
editor.commands.clearContent(false);
|
||||
}
|
||||
|
||||
typeof editorTitle !== "undefined" && editorTitle.current && editorTitle.current?.blur();
|
||||
if (typeof editorController.content !== undefined) editorController.content.current = '';
|
||||
editorController.onUpdate();
|
||||
editorController.setTitle('');
|
||||
if (typeof statusBar !== "undefined") {
|
||||
statusBar.current.resetWords();
|
||||
statusBar.current.set({date:"",saved:""});
|
||||
}`,
|
||||
"clearContent"
|
||||
);
|
||||
};
|
||||
|
||||
setSessionId = async (id: string | null) =>
|
||||
await this.sendCommand("setSessionId", id);
|
||||
await this.doAsync(`globalThis.sessionId = "${id}";`);
|
||||
|
||||
setStatus = async (
|
||||
date: string | undefined,
|
||||
saved: string,
|
||||
tabId: string
|
||||
tabId: number
|
||||
) => {
|
||||
this.sendCommand("setStatus", date, saved, tabId);
|
||||
};
|
||||
|
||||
setPlaceholder = async (placeholder: string) => {};
|
||||
|
||||
setLoading = async (loading?: boolean, tabId?: string) => {
|
||||
this.sendCommand(
|
||||
"setLoading",
|
||||
loading,
|
||||
tabId === undefined ? useTabStore.getState().currentTab : tabId
|
||||
await this.doAsync(
|
||||
`
|
||||
const statusBar = statusBars[${tabId}];
|
||||
typeof statusBar !== "undefined" && statusBar.current.set({date:"${date}",saved:"${saved}"})`,
|
||||
"setStatus"
|
||||
);
|
||||
};
|
||||
|
||||
setPlaceholder = async (placeholder: string) => {
|
||||
// await this.doAsync(`
|
||||
// const element = document.querySelector(".is-editor-empty");
|
||||
// if (element) {
|
||||
// element.setAttribute("data-placeholder","${placeholder}");
|
||||
// }
|
||||
// `);
|
||||
};
|
||||
|
||||
setLoading = async (loading?: boolean, tabId?: number) => {
|
||||
await this.doAsync(`
|
||||
const editorController = editorControllers[${
|
||||
tabId || useTabStore.getState().currentTab
|
||||
}];
|
||||
editorController.setLoading(${loading})
|
||||
`);
|
||||
};
|
||||
|
||||
setInsets = async (insets: EdgeInsets) => {
|
||||
this.sendCommand("setInsets", insets);
|
||||
await this.doAsync(`
|
||||
if (typeof safeAreaController !== "undefined") {
|
||||
safeAreaController.update(${JSON.stringify(insets)})
|
||||
}
|
||||
`);
|
||||
};
|
||||
|
||||
updateSettings = async (settings?: Partial<Settings>) => {
|
||||
@@ -141,7 +187,13 @@ class Commands {
|
||||
...this.previousSettings,
|
||||
...settings
|
||||
};
|
||||
this.sendCommand("updateSettings", settings);
|
||||
await this.doAsync(`
|
||||
if (typeof globalThis.settingsController !== "undefined") {
|
||||
globalThis.settingsController.update(${JSON.stringify(
|
||||
this.previousSettings
|
||||
)})
|
||||
}
|
||||
`);
|
||||
};
|
||||
|
||||
setSettings = async (settings?: Partial<Settings>) => {
|
||||
@@ -154,31 +206,71 @@ class Commands {
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.sendCommand("setSettings", settings);
|
||||
await this.doAsync(`
|
||||
if (typeof globalThis.settingsController !== "undefined") {
|
||||
globalThis.settingsController.update(${JSON.stringify(settings)})
|
||||
}
|
||||
`);
|
||||
};
|
||||
|
||||
setTags = async (note: Note | null | undefined) => {
|
||||
if (!note) return;
|
||||
useTabStore.getState().forEachNoteTab(note.id, async (tab) => {
|
||||
const tabId = tab.id;
|
||||
const tags = await db.relations.to(note, "tag").resolve();
|
||||
await this.sendCommand("setTags", tabId, tags);
|
||||
});
|
||||
const tabId = useTabStore.getState().getTabForNote(note.id);
|
||||
|
||||
const tags = await db.relations.to(note, "tag").resolve();
|
||||
await this.doAsync(
|
||||
`
|
||||
const tags = editorTags[${tabId}];
|
||||
if (tags && tags.current) {
|
||||
tags.current.setTags(${JSON.stringify(
|
||||
tags.map((tag) => ({
|
||||
title: tag.title,
|
||||
alias: tag.title,
|
||||
id: tag.id,
|
||||
type: tag.type
|
||||
}))
|
||||
)});
|
||||
}
|
||||
`,
|
||||
"setTags"
|
||||
);
|
||||
};
|
||||
|
||||
clearTags = async (tabId: string) => {
|
||||
await this.sendCommand("clearTags", tabId);
|
||||
clearTags = async (tabId: number) => {
|
||||
await this.doAsync(
|
||||
`
|
||||
const tags = editorTags[${tabId}];
|
||||
logger("info", Object.keys(editorTags), typeof editorTags[0]);
|
||||
if (tags && tags.current) {
|
||||
tags.current.setTags([]);
|
||||
}
|
||||
`,
|
||||
"clearTags"
|
||||
);
|
||||
};
|
||||
|
||||
insertAttachment = async (attachment: Attachment, tabId: number) => {
|
||||
await this.sendCommand("insertAttachment", attachment, tabId);
|
||||
await this.doAsync(
|
||||
`const editor = editors[${tabId}];
|
||||
editor && editor.commands.insertAttachment(${JSON.stringify(attachment)})`
|
||||
);
|
||||
};
|
||||
|
||||
setAttachmentProgress = async (
|
||||
attachmentProgress: Partial<Attachment>,
|
||||
tabId: number
|
||||
) => {
|
||||
await this.sendCommand("setAttachmentProgress", attachmentProgress, tabId);
|
||||
await this.doAsync(
|
||||
`const editor = editors[${tabId}];
|
||||
editor && editor.commands.updateAttachment(${JSON.stringify(
|
||||
attachmentProgress
|
||||
)}, {
|
||||
preventUpdate: true,
|
||||
query: (attachment) => {
|
||||
return attachment.hash === "${attachmentProgress.hash}";
|
||||
}
|
||||
})`
|
||||
);
|
||||
};
|
||||
|
||||
insertImage = async (
|
||||
@@ -187,30 +279,50 @@ class Commands {
|
||||
},
|
||||
tabId: number
|
||||
) => {
|
||||
await this.sendCommand("insertImage", image, tabId);
|
||||
await this.doAsync(
|
||||
`const editor = editors[${tabId}];
|
||||
|
||||
const image = toBlobURL("${image.dataurl}", "${image.hash}");
|
||||
|
||||
editor && editor.commands.insertImage({
|
||||
...${JSON.stringify({
|
||||
...image,
|
||||
dataurl: undefined
|
||||
})},
|
||||
bloburl: image
|
||||
})`
|
||||
);
|
||||
};
|
||||
|
||||
handleBack = async () => {
|
||||
return this.sendCommand("handleBack");
|
||||
return this.doAsync<boolean>(
|
||||
'response = window.dispatchEvent(new Event("handleBackPress",{cancelable:true}));'
|
||||
);
|
||||
};
|
||||
|
||||
keyboardShown = async (keyboardShown: boolean) => {
|
||||
return this.sendCommand("keyboardShown", keyboardShown);
|
||||
return this.doAsync(`globalThis['keyboardShown']=${keyboardShown};`);
|
||||
};
|
||||
|
||||
getTableOfContents = async () => {
|
||||
const tabId = useTabStore.getState().currentTab;
|
||||
return this.sendCommand("getTableOfContents", tabId);
|
||||
return this.doAsync(`
|
||||
response = editorControllers[${tabId}]?.getTableOfContents() || [];
|
||||
`);
|
||||
};
|
||||
|
||||
focusPassInput = async () => {
|
||||
const tabId = useTabStore.getState().currentTab;
|
||||
return this.sendCommand("focusPassInput", tabId);
|
||||
return this.doAsync(`
|
||||
response = editorControllers[${tabId}]?.focusPassInput() || [];
|
||||
`);
|
||||
};
|
||||
|
||||
blurPassInput = async () => {
|
||||
const tabId = useTabStore.getState().currentTab;
|
||||
return this.sendCommand("blurPassInput", tabId);
|
||||
return this.doAsync(`
|
||||
response = editorControllers[${tabId}]?.blurPassInput() || [];
|
||||
`);
|
||||
};
|
||||
|
||||
createInternalLink = async (
|
||||
@@ -218,18 +330,30 @@ class Commands {
|
||||
resolverId: string
|
||||
) => {
|
||||
if (!resolverId) return;
|
||||
return this.sendCommand("createInternalLink", attributes, resolverId);
|
||||
return this.doAsync(`
|
||||
if (globalThis.pendingResolvers["${resolverId}"]) {
|
||||
globalThis.pendingResolvers["${resolverId}"](${JSON.stringify(
|
||||
attributes
|
||||
)});
|
||||
}`);
|
||||
};
|
||||
|
||||
dismissCreateInternalLinkRequest = async (resolverId: string) => {
|
||||
if (!resolverId) return;
|
||||
return this.sendCommand("dismissCreateInternalLinkRequest", resolverId);
|
||||
return this.doAsync(`
|
||||
if (globalThis.pendingResolvers["${resolverId}"]) {
|
||||
globalThis.pendingResolvers["${resolverId}"](undefined);
|
||||
}
|
||||
`);
|
||||
};
|
||||
|
||||
scrollIntoViewById = async (id: string) => {
|
||||
const tabId = useTabStore.getState().currentTab;
|
||||
return this.sendCommand("scrollIntoViewById", id, tabId);
|
||||
return this.doAsync(`
|
||||
response = editorControllers[${tabId}]?.scrollIntoView("${id}") || [];
|
||||
`);
|
||||
};
|
||||
//todo add replace image function
|
||||
}
|
||||
|
||||
export default Commands;
|
||||
|
||||
@@ -16,8 +16,7 @@ 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/>.
|
||||
*/
|
||||
|
||||
export const EditorEvents = {
|
||||
export const EventTypes = {
|
||||
selection: "editor-event:selection",
|
||||
content: "editor-event:content",
|
||||
title: "editor-event:title",
|
||||
@@ -50,9 +49,5 @@ export const EditorEvents = {
|
||||
disableReadonlyMode: "editor-events:disable-readonly-mode",
|
||||
readonlyEditorLoaded: "readonlyEditorLoaded",
|
||||
error: "editorError",
|
||||
dbLogger: "editor-events:dbLogger",
|
||||
goBack: "editor-events:go-back",
|
||||
goForward: "editor-events:go-forward",
|
||||
saveScroll: "editor-events:save-scroll",
|
||||
newNote: "editor-events:new-note"
|
||||
} as const;
|
||||
dbLogger: "editor-events:dbLogger"
|
||||
};
|
||||
@@ -44,6 +44,7 @@ import { useTabStore } from "./use-tab-store";
|
||||
import { editorController, editorState } from "./utils";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useUserStore } from "../../../stores/use-user-store";
|
||||
import { sleep } from "../../../utils/time";
|
||||
|
||||
const showEncryptionSheet = (file: DocumentPickerResponse) => {
|
||||
presentSheet({
|
||||
@@ -132,6 +133,7 @@ const file = async (fileOptions: PickerOptions) => {
|
||||
|
||||
if (
|
||||
fileOptions.tabId !== undefined &&
|
||||
fileOptions.noteId &&
|
||||
useTabStore.getState().getNoteIdForTab(fileOptions.tabId) ===
|
||||
fileOptions.noteId
|
||||
) {
|
||||
|
||||
@@ -74,7 +74,7 @@ export type EditorMessage<T> = {
|
||||
value: T;
|
||||
type: string;
|
||||
noteId: string;
|
||||
tabId: string;
|
||||
tabId: number;
|
||||
resolverId?: string;
|
||||
hasTimeout?: boolean;
|
||||
};
|
||||
@@ -86,7 +86,7 @@ export type SavePayload = {
|
||||
type?: "tiptap";
|
||||
sessionHistoryId?: number;
|
||||
ignoreEdit: boolean;
|
||||
tabId: string;
|
||||
tabId: number;
|
||||
pendingChanges?: boolean;
|
||||
};
|
||||
|
||||
|
||||
@@ -21,10 +21,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
import { ItemReference } from "@notesnook/core";
|
||||
import type { Attachment } from "@notesnook/editor";
|
||||
import { EditorEvents } from "@notesnook/editor-mobile/src/utils/editor-events";
|
||||
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
|
||||
import { getDefaultPresets } from "@notesnook/editor/dist/cjs/toolbar/tool-definitions";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import React, { useCallback, useEffect, useRef } from "react";
|
||||
import {
|
||||
@@ -75,9 +72,11 @@ import {
|
||||
import { openLinkInBrowser } from "../../../utils/functions";
|
||||
import { tabBarRef } from "../../../utils/global-refs";
|
||||
import { useDragState } from "../../settings/editor/state";
|
||||
import { EventTypes } from "./editor-events";
|
||||
import { EditorMessage, EditorProps, useEditorType } from "./types";
|
||||
import { useTabStore } from "./use-tab-store";
|
||||
import { editorState, openInternalLink } from "./utils";
|
||||
import { EditorEvents, editorState, openInternalLink } from "./utils";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
const publishNote = async () => {
|
||||
const user = useUserStore.getState().user;
|
||||
@@ -102,7 +101,7 @@ const publishNote = async () => {
|
||||
}
|
||||
const noteId = useTabStore
|
||||
.getState()
|
||||
.getNoteIdForTab(useTabStore.getState().currentTab!);
|
||||
.getNoteIdForTab(useTabStore.getState().currentTab);
|
||||
|
||||
if (noteId) {
|
||||
const note = await db.notes?.note(noteId);
|
||||
@@ -125,7 +124,7 @@ const publishNote = async () => {
|
||||
const showActionsheet = async () => {
|
||||
const noteId = useTabStore
|
||||
.getState()
|
||||
.getNoteIdForTab(useTabStore.getState().currentTab!);
|
||||
.getNoteIdForTab(useTabStore.getState().currentTab);
|
||||
if (noteId) {
|
||||
const note = await db.notes?.note(noteId);
|
||||
if (editorState().isFocused || editorState().isFocused) {
|
||||
@@ -178,7 +177,7 @@ export const useEditorEvents = (
|
||||
useEffect(() => {
|
||||
const handleKeyboardDidShow: KeyboardEventListener = () => {
|
||||
editor.commands.keyboardShown(true);
|
||||
editor.postMessage(NativeEvents.keyboardShown, undefined);
|
||||
editor.postMessage(EditorEvents.keyboardShown, undefined);
|
||||
};
|
||||
const handleKeyboardDidHide: KeyboardEventListener = () => {
|
||||
editor.commands.keyboardShown(false);
|
||||
@@ -248,7 +247,7 @@ export const useEditorEvents = (
|
||||
}
|
||||
editorState().currentlyEditing = false;
|
||||
// editor.reset(); Notes remain open.
|
||||
editor.commands?.blur(useTabStore.getState().currentTab!);
|
||||
editor.commands?.blur(useTabStore.getState().currentTab);
|
||||
setTimeout(async () => {
|
||||
if (deviceMode !== "mobile" && fullscreen) {
|
||||
if (fullscreen) {
|
||||
@@ -353,25 +352,25 @@ export const useEditorEvents = (
|
||||
const editorMessage = JSON.parse(data) as EditorMessage<any>;
|
||||
|
||||
if (editorMessage.hasTimeout && editorMessage.resolverId) {
|
||||
editor.postMessage(NativeEvents.resolve, {
|
||||
editor.postMessage(EditorEvents.resolve, {
|
||||
data: true,
|
||||
resolverId: editorMessage.resolverId
|
||||
});
|
||||
}
|
||||
|
||||
if (editorMessage.type === EditorEvents.load) {
|
||||
if (editorMessage.type === EventTypes.load) {
|
||||
DatabaseLogger.log("Editor is ready");
|
||||
editor.onLoad();
|
||||
return;
|
||||
}
|
||||
|
||||
if (editorMessage.type === EditorEvents.back) {
|
||||
if (editorMessage.type === EventTypes.back) {
|
||||
return onBackPress();
|
||||
}
|
||||
|
||||
if (
|
||||
editorMessage.sessionId !== editor.sessionId.current &&
|
||||
editorMessage.type !== NativeEvents.status
|
||||
editorMessage.type !== EditorEvents.status
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -381,8 +380,8 @@ export const useEditorEvents = (
|
||||
.getNoteIdForTab(editorMessage.tabId);
|
||||
|
||||
switch (editorMessage.type) {
|
||||
case EditorEvents.content:
|
||||
DatabaseLogger.log("EditorEvents.content");
|
||||
case EventTypes.content:
|
||||
DatabaseLogger.log("EventTypes.content");
|
||||
editor.saveContent({
|
||||
type: editorMessage.type,
|
||||
content: editorMessage.value.html as string,
|
||||
@@ -392,8 +391,8 @@ export const useEditorEvents = (
|
||||
pendingChanges: editorMessage.value?.pendingChanges
|
||||
});
|
||||
break;
|
||||
case EditorEvents.title:
|
||||
DatabaseLogger.log("EditorEvents.title");
|
||||
case EventTypes.title:
|
||||
DatabaseLogger.log("EventTypes.title");
|
||||
editor.saveContent({
|
||||
type: editorMessage.type,
|
||||
title: editorMessage.value?.title as string,
|
||||
@@ -403,10 +402,10 @@ export const useEditorEvents = (
|
||||
pendingChanges: editorMessage.value?.pendingChanges
|
||||
});
|
||||
break;
|
||||
case EditorEvents.logger:
|
||||
case EventTypes.logger:
|
||||
logger.info("[EDITOR LOG]", editorMessage.value);
|
||||
break;
|
||||
case EditorEvents.dbLogger:
|
||||
case EventTypes.dbLogger:
|
||||
if (editorMessage.value.error) {
|
||||
DatabaseLogger.error(
|
||||
editorMessage.value.error,
|
||||
@@ -419,12 +418,12 @@ export const useEditorEvents = (
|
||||
DatabaseLogger.info("[EDITOR_LOG]" + editorMessage.value.message);
|
||||
}
|
||||
break;
|
||||
case EditorEvents.contentchange:
|
||||
case EventTypes.contentchange:
|
||||
editor.onContentChanged(editorMessage.noteId);
|
||||
break;
|
||||
case EditorEvents.selection:
|
||||
case EventTypes.selection:
|
||||
break;
|
||||
case EditorEvents.reminders:
|
||||
case EventTypes.reminders:
|
||||
if (!noteId) {
|
||||
ToastManager.show({
|
||||
heading: strings.createNoteFirst(),
|
||||
@@ -442,7 +441,7 @@ export const useEditorEvents = (
|
||||
onAdd: () => ReminderSheet.present(undefined, note, true)
|
||||
});
|
||||
break;
|
||||
case EditorEvents.newtag:
|
||||
case EventTypes.newtag:
|
||||
if (!noteId) {
|
||||
ToastManager.show({
|
||||
heading: strings.createNoteFirst(),
|
||||
@@ -452,7 +451,7 @@ export const useEditorEvents = (
|
||||
}
|
||||
ManageTagsSheet.present([noteId]);
|
||||
break;
|
||||
case EditorEvents.tag:
|
||||
case EventTypes.tag:
|
||||
if (editorMessage.value) {
|
||||
if (!noteId) return;
|
||||
const note = await db.notes.note(noteId);
|
||||
@@ -468,7 +467,7 @@ export const useEditorEvents = (
|
||||
});
|
||||
}
|
||||
break;
|
||||
case EditorEvents.filepicker:
|
||||
case EventTypes.filepicker:
|
||||
editorState().isAwaitingResult = true;
|
||||
const { pick } = require("./picker").default;
|
||||
pick({
|
||||
@@ -480,14 +479,14 @@ export const useEditorEvents = (
|
||||
editorState().isAwaitingResult = false;
|
||||
}, 1000);
|
||||
break;
|
||||
case EditorEvents.download: {
|
||||
case EventTypes.download: {
|
||||
const downloadAttachment =
|
||||
require("../../../common/filesystem/download-attachment").default;
|
||||
downloadAttachment((editorMessage.value as Attachment)?.hash, true);
|
||||
break;
|
||||
}
|
||||
|
||||
case EditorEvents.getAttachmentData: {
|
||||
case EventTypes.getAttachmentData: {
|
||||
const attachment = (editorMessage.value as any)
|
||||
?.attachment as Attachment;
|
||||
|
||||
@@ -507,14 +506,14 @@ export const useEditorEvents = (
|
||||
!!data,
|
||||
editorMessage.resolverId
|
||||
);
|
||||
editor.postMessage(NativeEvents.resolve, {
|
||||
editor.postMessage(EditorEvents.resolve, {
|
||||
resolverId: editorMessage.resolverId,
|
||||
data
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
DatabaseLogger.error(e);
|
||||
editor.postMessage(NativeEvents.resolve, {
|
||||
editor.postMessage(EditorEvents.resolve, {
|
||||
resolverId: editorMessage.resolverId,
|
||||
data: undefined
|
||||
});
|
||||
@@ -523,26 +522,26 @@ export const useEditorEvents = (
|
||||
break;
|
||||
}
|
||||
|
||||
case EditorEvents.pro:
|
||||
case EventTypes.pro:
|
||||
if (editor.state.current?.isFocused) {
|
||||
editor.state.current.isFocused = true;
|
||||
}
|
||||
eSendEvent(eOpenPremiumDialog);
|
||||
break;
|
||||
case EditorEvents.monograph:
|
||||
case EventTypes.monograph:
|
||||
publishNote();
|
||||
break;
|
||||
case EditorEvents.properties:
|
||||
case EventTypes.properties:
|
||||
showActionsheet();
|
||||
break;
|
||||
case EditorEvents.scroll:
|
||||
case EventTypes.scroll:
|
||||
editorState().scrollPosition = editorMessage.value;
|
||||
break;
|
||||
case EditorEvents.fullscreen:
|
||||
case EventTypes.fullscreen:
|
||||
editorState().isFullscreen = true;
|
||||
eSendEvent(eOpenFullscreenEditor);
|
||||
break;
|
||||
case EditorEvents.link:
|
||||
case EventTypes.link:
|
||||
if (editorMessage.value.startsWith("nn://")) {
|
||||
openInternalLink(editorMessage.value);
|
||||
console.log(
|
||||
@@ -554,7 +553,7 @@ export const useEditorEvents = (
|
||||
}
|
||||
break;
|
||||
|
||||
case EditorEvents.previewAttachment: {
|
||||
case EventTypes.previewAttachment: {
|
||||
const hash = (editorMessage.value as Attachment)?.hash;
|
||||
const attachment = await db.attachments?.attachment(hash);
|
||||
if (!attachment) return;
|
||||
@@ -565,26 +564,11 @@ export const useEditorEvents = (
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EditorEvents.copyToClipboard: {
|
||||
case EventTypes.copyToClipboard: {
|
||||
Clipboard.setString(editorMessage.value as string);
|
||||
break;
|
||||
}
|
||||
case EditorEvents.saveScroll: {
|
||||
useTabStore.getState().updateTab(editorMessage.tabId, {
|
||||
session: {
|
||||
...editorMessage.value
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
case EditorEvents.newNote: {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
tabId: editorMessage.tabId,
|
||||
newNote: true
|
||||
});
|
||||
break;
|
||||
}
|
||||
case EditorEvents.tabsChanged: {
|
||||
case EventTypes.tabsChanged: {
|
||||
// useTabStore.setState({
|
||||
// tabs: (editorMessage.value as any)?.tabs,
|
||||
// currentTab: (editorMessage.value as any)?.currentTab
|
||||
@@ -592,14 +576,14 @@ export const useEditorEvents = (
|
||||
//
|
||||
break;
|
||||
}
|
||||
case EditorEvents.toc:
|
||||
case EventTypes.toc:
|
||||
TableOfContents.present(editorMessage.value);
|
||||
break;
|
||||
case EditorEvents.showTabs: {
|
||||
case EventTypes.showTabs: {
|
||||
EditorTabs.present();
|
||||
break;
|
||||
}
|
||||
case EditorEvents.error: {
|
||||
case EventTypes.error: {
|
||||
presentSheet({
|
||||
component: (
|
||||
<Issue
|
||||
@@ -611,15 +595,19 @@ export const useEditorEvents = (
|
||||
});
|
||||
break;
|
||||
}
|
||||
case EditorEvents.tabFocused: {
|
||||
case EventTypes.tabFocused: {
|
||||
eSendEvent(eEditorTabFocused, editorMessage.tabId);
|
||||
|
||||
if (editorMessage.noteId) {
|
||||
if (
|
||||
(!editorMessage.value || editor.currentLoadingNoteId.current) &&
|
||||
editorMessage.noteId
|
||||
) {
|
||||
if (!useSettingStore.getState().isAppLoading) {
|
||||
const note = await db.notes.note(editorMessage.noteId);
|
||||
if (note) {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note,
|
||||
forced: true,
|
||||
tabId: editorMessage.tabId
|
||||
});
|
||||
}
|
||||
@@ -631,6 +619,7 @@ export const useEditorEvents = (
|
||||
if (note) {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note,
|
||||
forced: true,
|
||||
tabId: editorMessage.tabId
|
||||
});
|
||||
}
|
||||
@@ -641,7 +630,7 @@ export const useEditorEvents = (
|
||||
|
||||
break;
|
||||
}
|
||||
case EditorEvents.createInternalLink: {
|
||||
case EventTypes.createInternalLink: {
|
||||
LinkNote.present(
|
||||
editorMessage.value.attributes,
|
||||
editorMessage.resolverId as string
|
||||
@@ -649,37 +638,25 @@ export const useEditorEvents = (
|
||||
break;
|
||||
}
|
||||
|
||||
case EditorEvents.unlock: {
|
||||
case EventTypes.unlock: {
|
||||
eSendEvent(eUnlockWithPassword, editorMessage.value);
|
||||
break;
|
||||
}
|
||||
|
||||
case EditorEvents.goBack: {
|
||||
useTabStore.getState().goBack();
|
||||
break;
|
||||
}
|
||||
|
||||
case EditorEvents.goForward: {
|
||||
useTabStore.getState().goForward();
|
||||
break;
|
||||
}
|
||||
|
||||
case EditorEvents.unlockWithBiometrics: {
|
||||
case EventTypes.unlockWithBiometrics: {
|
||||
eSendEvent(eUnlockWithBiometrics);
|
||||
break;
|
||||
}
|
||||
|
||||
case EditorEvents.disableReadonlyMode: {
|
||||
case EventTypes.disableReadonlyMode: {
|
||||
const noteId = editorMessage.value;
|
||||
if (noteId) {
|
||||
await db.notes.readonly(false, noteId);
|
||||
editor.note.current[noteId] = await db.notes?.note(noteId);
|
||||
useTabStore
|
||||
.getState()
|
||||
.updateTab(useTabStore.getState().currentTab!, {
|
||||
session: {
|
||||
readonly: false
|
||||
}
|
||||
.updateTab(useTabStore.getState().currentTab, {
|
||||
readonly: false
|
||||
});
|
||||
setTimeout(() => {
|
||||
Navigation.queueRoutesForUpdate();
|
||||
|
||||
@@ -32,11 +32,8 @@ import {
|
||||
isEncryptedContent,
|
||||
isTrashItem
|
||||
} from "@notesnook/core";
|
||||
import { EditorEvents } from "@notesnook/editor-mobile/src/utils/editor-events";
|
||||
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeEngineStore } from "@notesnook/theme";
|
||||
import { Mutex } from "async-mutex";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import WebView from "react-native-webview";
|
||||
import { DatabaseLogger, db } from "../../../common/database";
|
||||
@@ -66,10 +63,12 @@ import { sleep } from "../../../utils/time";
|
||||
import { unlockVault } from "../../../utils/unlock-vault";
|
||||
import { onNoteCreated } from "../../notes/common";
|
||||
import Commands from "./commands";
|
||||
import { EventTypes } from "./editor-events";
|
||||
import { SessionHistory } from "./session-history";
|
||||
import { EditorState, SavePayload } from "./types";
|
||||
import { TabSessionItem, syncTabs, useTabStore } from "./use-tab-store";
|
||||
import { syncTabs, useTabStore } from "./use-tab-store";
|
||||
import {
|
||||
EditorEvents,
|
||||
clearAppState,
|
||||
defaultState,
|
||||
getAppState,
|
||||
@@ -78,68 +77,10 @@ import {
|
||||
post
|
||||
} from "./utils";
|
||||
|
||||
const loadNoteMutex = new Mutex();
|
||||
|
||||
type NoteWithContent = Note & {
|
||||
content?: NoteContent<false>;
|
||||
};
|
||||
|
||||
type LocalTabStateT = {
|
||||
editedAt: number;
|
||||
lastFocusedAt: number;
|
||||
};
|
||||
|
||||
class LocalTabState {
|
||||
state: Record<string, LocalTabStateT> = {};
|
||||
noteEditedTime: Record<string, number> = {};
|
||||
|
||||
setEditTime(noteId: string, time: number) {
|
||||
this.noteEditedTime[noteId] = time;
|
||||
}
|
||||
|
||||
get(tabId: string) {
|
||||
return this.state[tabId] || {};
|
||||
}
|
||||
|
||||
set(tabId: string, state: Partial<LocalTabStateT>) {
|
||||
this.state[tabId] = {
|
||||
...this.state[tabId],
|
||||
...state
|
||||
};
|
||||
}
|
||||
|
||||
clear(tabId: string) {
|
||||
delete this.state[tabId];
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
needsRefresh(tabId: string, locked: boolean, readonly: boolean) {
|
||||
const state = this.get(tabId);
|
||||
const tabSession = useTabStore.getState().getTab(tabId)?.session;
|
||||
const noteId = useTabStore.getState().getNoteIdForTab(tabId);
|
||||
|
||||
if (
|
||||
tabSession?.locked !== locked ||
|
||||
tabSession?.readonly !== readonly ||
|
||||
!noteId
|
||||
) {
|
||||
console.log("tab is refreshing...");
|
||||
return true;
|
||||
}
|
||||
console.log(
|
||||
"Tab refreshing...",
|
||||
state.editedAt < this.noteEditedTime[noteId]
|
||||
);
|
||||
|
||||
console.log(state.editedAt, this.noteEditedTime[noteId]);
|
||||
|
||||
return !state.editedAt || state.editedAt < this.noteEditedTime[noteId];
|
||||
}
|
||||
}
|
||||
|
||||
export const useEditor = (
|
||||
editorId = "",
|
||||
readonly?: boolean,
|
||||
@@ -158,6 +99,7 @@ export const useEditor = (
|
||||
isPreview?: boolean;
|
||||
};
|
||||
})
|
||||
| null
|
||||
| undefined
|
||||
>
|
||||
>({});
|
||||
@@ -177,17 +119,15 @@ export const useEditor = (
|
||||
const lastContentChangeTime = useRef<Record<string, number>>({});
|
||||
const lock = useRef(false);
|
||||
const currentLoadingNoteId = useRef<string>();
|
||||
const lastTabFocused = useRef<string>();
|
||||
|
||||
const localTabState = useRef<LocalTabState>(new LocalTabState());
|
||||
|
||||
const loadingState = useRef<string>();
|
||||
const lastTabFocused = useRef(0);
|
||||
const blockIdRef = useRef<string>();
|
||||
const postMessage = useCallback(
|
||||
async <T>(type: string, data: T, tabId?: string, waitFor = 300) =>
|
||||
async <T>(type: string, data: T, tabId?: number, waitFor = 300) =>
|
||||
await post(
|
||||
editorRef,
|
||||
sessionIdRef.current,
|
||||
tabId || useTabStore.getState().currentTab!,
|
||||
typeof tabId !== "number" ? useTabStore.getState().currentTab : tabId,
|
||||
type,
|
||||
data,
|
||||
waitFor
|
||||
@@ -202,7 +142,7 @@ export const useEditor = (
|
||||
}, [commands, insets, isDefaultEditor]);
|
||||
|
||||
useEffect(() => {
|
||||
postMessage(NativeEvents.theme, theme);
|
||||
postMessage(EditorEvents.theme, theme);
|
||||
}, [theme, postMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -212,14 +152,14 @@ export const useEditor = (
|
||||
}, [commands, tags]);
|
||||
|
||||
useEffect(() => {
|
||||
const event = eSubscribeEvent(eEditorTabFocused, (tabId: string) => {
|
||||
const event = eSubscribeEvent(eEditorTabFocused, (tabId) => {
|
||||
if (lastTabFocused.current !== tabId) lock.current = false;
|
||||
lastTabFocused.current = tabId;
|
||||
lastTabFocused.current = tabId as number;
|
||||
});
|
||||
return () => {
|
||||
event?.unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
});
|
||||
|
||||
const overlay = useCallback(
|
||||
(show: boolean, data = { type: "new" }) => {
|
||||
@@ -248,11 +188,11 @@ export const useEditor = (
|
||||
);
|
||||
|
||||
const reset = useCallback(
|
||||
async (tabId: string, resetState = true, resetContent = true) => {
|
||||
async (tabId: number, resetState = true, resetContent = true) => {
|
||||
const noteId = useTabStore.getState().getNoteIdForTab(tabId);
|
||||
if (noteId) {
|
||||
currentNotes.current?.id && db.fs().cancel(noteId);
|
||||
currentNotes.current[noteId] = undefined;
|
||||
currentNotes.current[noteId] = null;
|
||||
currentContents.current[noteId] = null;
|
||||
editorSessionHistory.clearSession(noteId);
|
||||
lastContentChangeTime.current[noteId] = 0;
|
||||
@@ -260,11 +200,18 @@ export const useEditor = (
|
||||
}
|
||||
|
||||
saveCount.current = 0;
|
||||
currentLoadingNoteId.current = undefined;
|
||||
loadingState.current = undefined;
|
||||
lock.current = false;
|
||||
resetContent && postMessage(NativeEvents.title, "", tabId);
|
||||
resetContent && postMessage(EditorEvents.title, "", tabId);
|
||||
|
||||
resetContent && (await commands.clearContent(tabId));
|
||||
resetContent && (await commands.clearTags(tabId));
|
||||
useTabStore.getState().updateTab(tabId, {
|
||||
noteId: undefined,
|
||||
locked: false,
|
||||
noteLocked: false,
|
||||
readonly: false
|
||||
});
|
||||
},
|
||||
[commands, editorSessionHistory, postMessage]
|
||||
);
|
||||
@@ -284,16 +231,6 @@ export const useEditor = (
|
||||
try {
|
||||
if (id && !(await db.notes?.note(id))) {
|
||||
await reset(tabId);
|
||||
useTabStore.getState().updateTab(tabId, {
|
||||
session: {
|
||||
noteId: undefined,
|
||||
noteLocked: undefined,
|
||||
locked: undefined,
|
||||
readonly: undefined,
|
||||
scrollTop: undefined,
|
||||
selection: undefined
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
let note = id ? await db.notes?.note(id) : undefined;
|
||||
@@ -332,6 +269,13 @@ export const useEditor = (
|
||||
};
|
||||
}
|
||||
|
||||
// If note is edited, the tab becomes a persistent tab automatically.
|
||||
if (useTabStore.getState().getTab(tabId)?.previewTab) {
|
||||
useTabStore.getState().updateTab(tabId, {
|
||||
previewTab: false
|
||||
});
|
||||
}
|
||||
|
||||
let saved = false;
|
||||
setTimeout(() => {
|
||||
if (saved) return;
|
||||
@@ -366,9 +310,7 @@ export const useEditor = (
|
||||
}
|
||||
|
||||
useTabStore.getState().updateTab(tabId, {
|
||||
session: {
|
||||
noteId: id
|
||||
}
|
||||
noteId: id
|
||||
});
|
||||
|
||||
const defaultNotebook = db.settings.getDefaultNotebook();
|
||||
@@ -383,7 +325,7 @@ export const useEditor = (
|
||||
|
||||
if (!noteData.title) {
|
||||
postMessage(
|
||||
NativeEvents.title,
|
||||
EditorEvents.title,
|
||||
currentNotes.current[id]?.title,
|
||||
tabId
|
||||
);
|
||||
@@ -443,8 +385,8 @@ export const useEditor = (
|
||||
id === useTabStore.getState().getCurrentNoteId() &&
|
||||
pendingChanges
|
||||
) {
|
||||
postMessage(NativeEvents.title, title || note?.title, tabId);
|
||||
postMessage(NativeEvents.html, data, tabId);
|
||||
postMessage(EditorEvents.title, title || note?.title, tabId);
|
||||
postMessage(EditorEvents.html, data, tabId);
|
||||
currentNotes.current[id] = note;
|
||||
}
|
||||
|
||||
@@ -493,211 +435,179 @@ export const useEditor = (
|
||||
);
|
||||
|
||||
const loadNote = useCallback(
|
||||
(event: {
|
||||
async (event: {
|
||||
item?: Note;
|
||||
forced?: boolean;
|
||||
newNote?: boolean;
|
||||
tabId?: string;
|
||||
tabId?: number;
|
||||
blockId?: string;
|
||||
session?: TabSessionItem;
|
||||
newTab?: boolean;
|
||||
presistTab?: boolean;
|
||||
}) => {
|
||||
loadNoteMutex.runExclusive(async () => {
|
||||
if (!event) return;
|
||||
if (event.blockId) {
|
||||
blockIdRef.current = event.blockId;
|
||||
}
|
||||
state.current.currentlyEditing = true;
|
||||
if (!event) return;
|
||||
|
||||
if (
|
||||
!state.current.ready &&
|
||||
(await isEditorLoaded(
|
||||
editorRef,
|
||||
sessionIdRef.current,
|
||||
useTabStore.getState().currentTab!
|
||||
))
|
||||
) {
|
||||
state.current.ready = true;
|
||||
if (event.blockId) {
|
||||
blockIdRef.current = event.blockId;
|
||||
}
|
||||
state.current.currentlyEditing = true;
|
||||
|
||||
if (
|
||||
!state.current.ready &&
|
||||
(await isEditorLoaded(
|
||||
editorRef,
|
||||
sessionIdRef.current,
|
||||
useTabStore.getState().currentTab
|
||||
))
|
||||
) {
|
||||
state.current.ready = true;
|
||||
}
|
||||
|
||||
if (event.newNote) {
|
||||
useTabStore.getState().focusEmptyTab();
|
||||
const tabId = useTabStore.getState().currentTab;
|
||||
currentNotes.current && (await reset(tabId));
|
||||
setTimeout(() => {
|
||||
if (state.current?.ready && !state.current.movedAway)
|
||||
commands.focus(tabId);
|
||||
});
|
||||
} else {
|
||||
if (!event.item) {
|
||||
overlay(false);
|
||||
return;
|
||||
}
|
||||
if (event.newNote && !currentLoadingNoteId.current) {
|
||||
let tabId;
|
||||
if (useTabStore.getState().tabs.length === 0) {
|
||||
tabId = useTabStore.getState().newTab();
|
||||
|
||||
const item = event.item;
|
||||
|
||||
const currentTab = useTabStore
|
||||
.getState()
|
||||
.getTab(useTabStore.getState().currentTab);
|
||||
if (currentTab?.previewTab && item.id !== currentTab.noteId) {
|
||||
await commands.setLoading(true, useTabStore.getState().currentTab);
|
||||
}
|
||||
const isLockedNote = await db.vaults.itemExists(
|
||||
event.item as ItemReference
|
||||
);
|
||||
const tabLocked =
|
||||
isLockedNote && !(event.item as NoteWithContent).content;
|
||||
|
||||
// If note was already opened in a tab, focus that tab.
|
||||
if (typeof event.tabId !== "number") {
|
||||
if (useTabStore.getState().hasTabForNote(event.item.id)) {
|
||||
const tabId = useTabStore.getState().getTabForNote(event.item.id);
|
||||
if (typeof tabId === "number") {
|
||||
useTabStore.getState().updateTab(tabId, {
|
||||
readonly: event.item.readonly || readonly,
|
||||
locked: tabLocked,
|
||||
noteLocked: isLockedNote
|
||||
});
|
||||
useTabStore.getState().focusTab(tabId);
|
||||
setTimeout(() => {
|
||||
if (blockIdRef.current) {
|
||||
commands.scrollIntoViewById(blockIdRef.current);
|
||||
blockIdRef.current = undefined;
|
||||
}
|
||||
}, 150);
|
||||
}
|
||||
} else {
|
||||
tabId = useTabStore.getState().currentTab;
|
||||
await reset(tabId!, true, true);
|
||||
if (
|
||||
event.session?.noteId ||
|
||||
useTabStore.getState().getTab(tabId!)?.session?.noteId
|
||||
) {
|
||||
useTabStore.getState().newTabSession(tabId!, {});
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (state.current?.ready && !state.current.movedAway)
|
||||
commands.focus(tabId!);
|
||||
});
|
||||
} else {
|
||||
if (!event.item) {
|
||||
overlay(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const item = event.item;
|
||||
|
||||
// If note is already open in a tab, focus that tab.
|
||||
if (useTabStore.getState().hasTabForNote(item.id) && !event.newTab) {
|
||||
const tabId = useTabStore.getState().getTabForNote(item.id);
|
||||
|
||||
const currentTab = useTabStore
|
||||
.getState()
|
||||
.getTab(useTabStore.getState().currentTab as string);
|
||||
|
||||
if (
|
||||
currentTab?.session?.noteId !== item.id &&
|
||||
tabId !== useTabStore.getState().currentTab
|
||||
) {
|
||||
useTabStore.getState().focusTab(tabId as string);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const isLockedNote = await db.vaults.itemExists(
|
||||
event.item as ItemReference
|
||||
);
|
||||
|
||||
const tabLocked =
|
||||
isLockedNote && !(event.item as NoteWithContent).content;
|
||||
|
||||
const tabId = event.tabId
|
||||
? event.tabId
|
||||
: useTabStore.getState().currentTab;
|
||||
|
||||
console.log(tabId === useTabStore.getState().currentTab);
|
||||
|
||||
// Check if tab needs to be refreshed.
|
||||
if (!event.newTab) {
|
||||
if (
|
||||
tabId &&
|
||||
event.item.id === useTabStore.getState().getNoteIdForTab(tabId) &&
|
||||
!localTabState.current?.needsRefresh(
|
||||
tabId,
|
||||
isLockedNote,
|
||||
item.readonly
|
||||
)
|
||||
) {
|
||||
commands.setLoading(false, tabId);
|
||||
return;
|
||||
if (event.presistTab) {
|
||||
// Open note in new tab.
|
||||
useTabStore.getState().newTab({
|
||||
readonly: event.item.readonly || readonly,
|
||||
locked: tabLocked,
|
||||
noteLocked: isLockedNote,
|
||||
noteId: event.item.id,
|
||||
previewTab: false
|
||||
});
|
||||
} else {
|
||||
localTabState.current?.setEditTime(
|
||||
item.id,
|
||||
localTabState.current?.noteEditedTime[item.id] ||
|
||||
item.dateEdited
|
||||
);
|
||||
localTabState.current?.set(tabId!, {
|
||||
editedAt:
|
||||
localTabState.current?.noteEditedTime[item.id] ||
|
||||
item.dateEdited
|
||||
// Otherwise we focus the preview tab or create one to open the note in.
|
||||
useTabStore.getState().focusPreviewTab(event.item.id, {
|
||||
readonly: event.item.readonly || readonly,
|
||||
locked: tabLocked,
|
||||
noteLocked: isLockedNote
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (lastTabFocused.current !== event.tabId) {
|
||||
useTabStore.getState().focusTab(event.tabId);
|
||||
}
|
||||
}
|
||||
|
||||
const tabId = event.tabId || useTabStore.getState().currentTab;
|
||||
if (lastTabFocused.current !== tabId) {
|
||||
// if ((await waitForEvent(eEditorTabFocused, 1000)) !== tabId) {
|
||||
//
|
||||
// return;
|
||||
// }
|
||||
currentLoadingNoteId.current = item.id;
|
||||
|
||||
// Show loading overlay if note is not already loaded.
|
||||
if (
|
||||
tabId &&
|
||||
(event.item?.id !== useTabStore.getState().getNoteIdForTab(tabId) ||
|
||||
!currentContents.current[event.item.id]?.data)
|
||||
) {
|
||||
await commands.setLoading(true, tabId);
|
||||
}
|
||||
|
||||
const session: Partial<TabSessionItem> = event.session || {
|
||||
noteId: event.item.id
|
||||
};
|
||||
|
||||
session.noteLocked = isLockedNote;
|
||||
session.locked = tabLocked;
|
||||
session.readonly = item.readonly;
|
||||
|
||||
const tab = useTabStore.getState().getTab(tabId!);
|
||||
|
||||
if (useTabStore.getState().tabs.length === 0 || event.newTab) {
|
||||
useTabStore.getState().newTab({
|
||||
session: session
|
||||
});
|
||||
} else {
|
||||
// A new session is created if the note is changed.
|
||||
// If the note is already opened, the session is updated.
|
||||
if (
|
||||
!tab?.session ||
|
||||
(event.item.id !== tab?.session?.noteId && tab?.session?.noteId)
|
||||
) {
|
||||
useTabStore.getState().newTabSession(tabId!, session);
|
||||
} else {
|
||||
useTabStore.getState().updateTab(tabId!, {
|
||||
session: session
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (lastTabFocused.current !== tabId) return;
|
||||
|
||||
if (tabBarRef.current?.page() === 2) {
|
||||
state.current.movedAway = false;
|
||||
}
|
||||
|
||||
state.current.currentlyEditing = true;
|
||||
if (!tabLocked) {
|
||||
await loadContent(item);
|
||||
} else {
|
||||
commands.focus(tabId!);
|
||||
}
|
||||
|
||||
lastContentChangeTime.current[item.id] = item.dateEdited;
|
||||
currentNotes.current[item.id] = item;
|
||||
|
||||
if (!currentNotes.current[item.id]) return;
|
||||
|
||||
editorSessionHistory.newSession(item.id);
|
||||
|
||||
await commands.setStatus(
|
||||
getFormattedDate(item.dateEdited, "date-time"),
|
||||
"Saved",
|
||||
tabId!
|
||||
);
|
||||
await postMessage(NativeEvents.title, item.title, tabId);
|
||||
overlay(false);
|
||||
|
||||
await postMessage(
|
||||
NativeEvents.html,
|
||||
{
|
||||
data: currentContents.current[item.id]?.data || "",
|
||||
scrollTop: tab?.session?.scrollTop,
|
||||
selection: tab?.session?.selection
|
||||
},
|
||||
tabId,
|
||||
10000
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
if (blockIdRef.current) {
|
||||
commands.scrollIntoViewById(blockIdRef.current);
|
||||
blockIdRef.current = undefined;
|
||||
}
|
||||
}, 300);
|
||||
|
||||
await commands.setTags(item);
|
||||
commands.setSettings();
|
||||
setTimeout(() => {
|
||||
if (currentLoadingNoteId.current === event.item?.id) {
|
||||
currentLoadingNoteId.current = undefined;
|
||||
}
|
||||
}, 300);
|
||||
return;
|
||||
}
|
||||
postMessage(NativeEvents.theme, theme);
|
||||
});
|
||||
|
||||
state.current.movedAway = false;
|
||||
state.current.currentlyEditing = true;
|
||||
|
||||
if (!tabLocked) {
|
||||
await loadContent(item);
|
||||
}
|
||||
|
||||
if (
|
||||
currentNotes.current[item.id] &&
|
||||
loadingState.current &&
|
||||
currentContents.current[item.id]?.data &&
|
||||
loadingState.current === currentContents.current[item.id]?.data
|
||||
) {
|
||||
// If note is already loading, return.
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!state.current.ready) {
|
||||
currentNotes.current[item.id] = item;
|
||||
return;
|
||||
}
|
||||
|
||||
lastContentChangeTime.current[item.id] = 0;
|
||||
currentLoadingNoteId.current = item.id;
|
||||
currentNotes.current[item.id] = item;
|
||||
|
||||
if (!currentNotes.current[item.id]) return;
|
||||
|
||||
editorSessionHistory.newSession(item.id);
|
||||
|
||||
await commands.setStatus(
|
||||
getFormattedDate(item.dateEdited, "date-time"),
|
||||
strings.saved(),
|
||||
tabId
|
||||
);
|
||||
|
||||
await postMessage(EditorEvents.title, item.title, tabId);
|
||||
overlay(false);
|
||||
loadingState.current = currentContents.current[item.id]?.data;
|
||||
|
||||
await postMessage(
|
||||
EditorEvents.html,
|
||||
currentContents.current[item.id]?.data || "",
|
||||
tabId,
|
||||
10000
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
if (blockIdRef.current) {
|
||||
commands.scrollIntoViewById(blockIdRef.current);
|
||||
blockIdRef.current = undefined;
|
||||
}
|
||||
}, 300);
|
||||
|
||||
loadingState.current = undefined;
|
||||
await commands.setTags(item);
|
||||
commands.setSettings();
|
||||
setTimeout(() => {
|
||||
if (currentLoadingNoteId.current === event.item?.id) {
|
||||
currentLoadingNoteId.current = undefined;
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
postMessage(EditorEvents.theme, theme);
|
||||
},
|
||||
[
|
||||
commands,
|
||||
@@ -705,6 +615,7 @@ export const useEditor = (
|
||||
loadContent,
|
||||
overlay,
|
||||
postMessage,
|
||||
readonly,
|
||||
reset,
|
||||
theme
|
||||
]
|
||||
@@ -716,90 +627,83 @@ export const useEditor = (
|
||||
isLocal?: boolean
|
||||
) => {
|
||||
try {
|
||||
if (SettingsService.get().disableRealtimeSync && !isLocal) return;
|
||||
if (!data) return;
|
||||
await (async () => {
|
||||
if (SettingsService.get().disableRealtimeSync && !isLocal) return;
|
||||
if (!data) return;
|
||||
|
||||
if (isDeleted(data) || isTrashItem(data)) {
|
||||
const tabId = useTabStore.getState().getTabForNote(data.id);
|
||||
if (tabId !== undefined) {
|
||||
await commands.clearContent(tabId);
|
||||
useTabStore.getState().removeTab(tabId);
|
||||
if (isDeleted(data) || isTrashItem(data)) {
|
||||
const tabId = useTabStore.getState().getTabForNote(data.id);
|
||||
if (tabId !== undefined) {
|
||||
await commands.clearContent(tabId);
|
||||
useTabStore.getState().removeTab(tabId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const noteId =
|
||||
(data as ContentItem).type === "tiptap"
|
||||
? (data as ContentItem).noteId
|
||||
: data.id;
|
||||
const noteId =
|
||||
(data as ContentItem).type === "tiptap"
|
||||
? (data as ContentItem).noteId
|
||||
: data.id;
|
||||
|
||||
const note = data.type === "note" ? data : await db.notes?.note(noteId);
|
||||
if (!useTabStore.getState().hasTabForNote(noteId)) return;
|
||||
const tabId = useTabStore.getState().getTabForNote(noteId) as number;
|
||||
|
||||
lock.current = true;
|
||||
const tab = useTabStore.getState().getTab(tabId);
|
||||
|
||||
// Handle this case where note was locked on another device and synced.
|
||||
const locked = note
|
||||
? await db.vaults.itemExists(note as ItemReference)
|
||||
: false;
|
||||
const note =
|
||||
data.type === "note" ? data : await db.notes?.note(noteId);
|
||||
|
||||
useTabStore.getState().forEachNoteTab(noteId, async (tab) => {
|
||||
const tabId = tab.id;
|
||||
lock.current = true;
|
||||
|
||||
// Handle this case where note was locked on another device and synced.
|
||||
const locked = note
|
||||
? await db.vaults.itemExists(note as ItemReference)
|
||||
: false;
|
||||
|
||||
if (note) {
|
||||
if (!locked && tab?.session?.noteLocked) {
|
||||
if (!locked && tab?.noteLocked) {
|
||||
// Note lock removed.
|
||||
if (tab.session?.locked) {
|
||||
if (tab.locked) {
|
||||
if (useTabStore.getState().currentTab === tabId) {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note
|
||||
item: note,
|
||||
forced: true
|
||||
});
|
||||
} else {
|
||||
useTabStore.getState().updateTab(tabId, {
|
||||
session: {
|
||||
locked: false,
|
||||
noteLocked: false
|
||||
}
|
||||
});
|
||||
localTabState.current?.set(tabId, {
|
||||
editedAt: 0
|
||||
locked: false,
|
||||
noteLocked: false
|
||||
});
|
||||
commands.setLoading(true, tabId);
|
||||
}
|
||||
}
|
||||
} else if (!tab?.session?.noteLocked && locked) {
|
||||
} else if (!tab?.noteLocked && locked) {
|
||||
// Note lock added.
|
||||
useTabStore.getState().updateTab(tabId, {
|
||||
session: {
|
||||
locked: true,
|
||||
noteLocked: true
|
||||
}
|
||||
locked: true,
|
||||
noteLocked: true
|
||||
});
|
||||
commands.clearContent(tabId);
|
||||
if (useTabStore.getState().currentTab !== tabId) {
|
||||
commands.clearContent(tabId);
|
||||
commands.setLoading(true, tabId);
|
||||
}
|
||||
localTabState.current?.set(tabId, {
|
||||
editedAt: 0
|
||||
});
|
||||
}
|
||||
|
||||
if (currentNotes.current[noteId]?.title !== note.title) {
|
||||
postMessage(NativeEvents.title, note.title, tabId);
|
||||
postMessage(EditorEvents.title, note.title, tabId);
|
||||
}
|
||||
commands.setTags(note);
|
||||
if (currentNotes.current[noteId]?.dateEdited !== note.dateEdited) {
|
||||
commands.setStatus(
|
||||
getFormattedDate(note.dateEdited, "date-time"),
|
||||
strings.saved(),
|
||||
tabId as string
|
||||
tabId as number
|
||||
);
|
||||
}
|
||||
if (tab.session?.readonly !== note.readonly) {
|
||||
useTabStore.getState().updateTab(tabId, {
|
||||
session: {
|
||||
readonly: note.readonly
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
useTabStore.getState().updateTab(tabId, {
|
||||
readonly: note.readonly
|
||||
});
|
||||
}
|
||||
|
||||
if (data.type === "tiptap" && note && !isLocal) {
|
||||
@@ -810,11 +714,9 @@ export const useEditor = (
|
||||
if (locked && isEncryptedContent(data)) {
|
||||
const decryptedContent = await db.vault?.decryptContent(data);
|
||||
if (!decryptedContent) {
|
||||
useTabStore.getState().updateTab(tab.id, {
|
||||
session: {
|
||||
locked: true,
|
||||
noteLocked: true
|
||||
}
|
||||
useTabStore.getState().updateTab(tabId, {
|
||||
locked: true,
|
||||
noteLocked: true
|
||||
});
|
||||
if (useTabStore.getState().currentTab !== tabId) {
|
||||
commands.clearContent(tabId);
|
||||
@@ -822,7 +724,7 @@ export const useEditor = (
|
||||
}
|
||||
} else {
|
||||
await postMessage(
|
||||
NativeEvents.updatehtml,
|
||||
EditorEvents.updatehtml,
|
||||
decryptedContent.data,
|
||||
tabId
|
||||
);
|
||||
@@ -834,14 +736,14 @@ export const useEditor = (
|
||||
return;
|
||||
}
|
||||
lastContentChangeTime.current[note.id] = note.dateEdited;
|
||||
await postMessage(NativeEvents.updatehtml, _nextContent, tabId);
|
||||
await postMessage(EditorEvents.updatehtml, _nextContent, tabId);
|
||||
if (!isEncryptedContent(data)) {
|
||||
currentContents.current[note.id] =
|
||||
data as UnencryptedContentItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e as Error, "Error when applying sync changes");
|
||||
} finally {
|
||||
@@ -877,7 +779,7 @@ export const useEditor = (
|
||||
content?: string;
|
||||
type: string;
|
||||
ignoreEdit: boolean;
|
||||
tabId: string;
|
||||
tabId: number;
|
||||
pendingChanges?: boolean;
|
||||
}) => {
|
||||
DatabaseLogger.log(
|
||||
@@ -906,13 +808,9 @@ export const useEditor = (
|
||||
|
||||
if (noteId) {
|
||||
lastContentChangeTime.current[noteId] = Date.now();
|
||||
localTabState.current?.setEditTime(noteId, Date.now());
|
||||
localTabState?.current?.set(tabId, {
|
||||
editedAt: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
if (type === EditorEvents.content && noteId) {
|
||||
if (type === EventTypes.content && noteId) {
|
||||
currentContents.current[noteId as string] = {
|
||||
data: content,
|
||||
type: "tiptap",
|
||||
@@ -962,9 +860,7 @@ export const useEditor = (
|
||||
if (!appState) return;
|
||||
state.current.isRestoringState = true;
|
||||
state.current.currentlyEditing = true;
|
||||
if (tabBarRef.current?.page() === 2) {
|
||||
state.current.movedAway = false;
|
||||
}
|
||||
state.current.movedAway = false;
|
||||
|
||||
if (!state.current.editorStateRestored) {
|
||||
state.current.isRestoringState = true;
|
||||
@@ -995,12 +891,9 @@ export const useEditor = (
|
||||
!(await isEditorLoaded(
|
||||
editorRef,
|
||||
sessionIdRef.current,
|
||||
useTabStore.getState().currentTab!
|
||||
useTabStore.getState().currentTab
|
||||
))
|
||||
) {
|
||||
localTabState.current?.set(useTabStore.getState().currentTab!, {
|
||||
editedAt: 0
|
||||
});
|
||||
eSendEvent(eEditorReset, "onReady");
|
||||
return false;
|
||||
} else {
|
||||
@@ -1012,17 +905,13 @@ export const useEditor = (
|
||||
|
||||
const onLoad = useCallback(async () => {
|
||||
setTimeout(() => {
|
||||
postMessage(NativeEvents.theme, theme);
|
||||
postMessage(EditorEvents.theme, theme);
|
||||
});
|
||||
commands.setInsets(
|
||||
isDefaultEditor ? insets : { top: 0, left: 0, right: 0, bottom: 0 }
|
||||
);
|
||||
await commands.setSettings();
|
||||
|
||||
localTabState.current?.set(useTabStore.getState().currentTab!, {
|
||||
editedAt: 0
|
||||
});
|
||||
|
||||
if (!state.current.ready) {
|
||||
state.current.ready = true;
|
||||
}
|
||||
|
||||
@@ -16,27 +16,18 @@ 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 {
|
||||
TabHistory as TabHistoryType,
|
||||
TabSessionHistory
|
||||
} from "@notesnook/common";
|
||||
import { getId } from "@notesnook/core";
|
||||
import { MMKVLoader } from "react-native-mmkv-storage";
|
||||
import create from "zustand";
|
||||
import { persist, StateStorage } from "zustand/middleware";
|
||||
import { db } from "../../../common/database";
|
||||
import { MMKV } from "../../../common/database/mmkv";
|
||||
import { eSendEvent } from "../../../services/event-manager";
|
||||
import { eOnLoadNote } from "../../../utils/events";
|
||||
import { editorController } from "./utils";
|
||||
|
||||
class TabHistory {
|
||||
history: string[];
|
||||
class History {
|
||||
history: number[];
|
||||
constructor() {
|
||||
this.history = [];
|
||||
this.history = [0];
|
||||
}
|
||||
|
||||
add(item: string) {
|
||||
add(item: number) {
|
||||
const index = this.history.findIndex((id) => item === id);
|
||||
if (index !== -1) {
|
||||
// Item already exists, move it to the top
|
||||
@@ -45,19 +36,19 @@ class TabHistory {
|
||||
this.history.unshift(item); // Add item to the beginning of the array
|
||||
|
||||
useTabStore.setState({
|
||||
historyNew: this.history.slice()
|
||||
tabHistory: this.history.slice()
|
||||
});
|
||||
return true; // Item added successfully
|
||||
}
|
||||
|
||||
remove(id: string) {
|
||||
remove(id: number) {
|
||||
const index = this.history.findIndex((item) => item === id);
|
||||
if (index >= -1 && index < this.history.length) {
|
||||
const removedItem = this.history.splice(index, 1)[0];
|
||||
return removedItem;
|
||||
}
|
||||
useTabStore.setState({
|
||||
historyNew: this.history.slice()
|
||||
tabHistory: this.history.slice()
|
||||
});
|
||||
return null; // Invalid index
|
||||
}
|
||||
@@ -68,7 +59,7 @@ class TabHistory {
|
||||
return restoredItem;
|
||||
}
|
||||
useTabStore.setState({
|
||||
historyNew: this.history.slice()
|
||||
tabHistory: this.history.slice()
|
||||
});
|
||||
return null; // History is empty
|
||||
}
|
||||
@@ -78,354 +69,147 @@ class TabHistory {
|
||||
}
|
||||
}
|
||||
|
||||
export type TabSessionItem = {
|
||||
id: string;
|
||||
noteId?: string;
|
||||
scrollTop?: number;
|
||||
selection?: { to: number; from: number };
|
||||
noteLocked?: boolean;
|
||||
locked?: boolean;
|
||||
readonly?: boolean;
|
||||
};
|
||||
|
||||
const TabSessionStorageKV = new MMKVLoader()
|
||||
.withInstanceID("tab-session-storage")
|
||||
.disableIndexing()
|
||||
.initialize();
|
||||
|
||||
class TabSessionStorage {
|
||||
static storage: typeof TabSessionStorageKV = TabSessionStorageKV;
|
||||
|
||||
static get(id: string): TabSessionItem | null {
|
||||
return TabSessionStorage.storage.getMap(id);
|
||||
}
|
||||
|
||||
static set(id: string, session: TabSessionItem): void {
|
||||
TabSessionStorage.storage.setMap(id, session);
|
||||
}
|
||||
|
||||
static update(id: string, session: Partial<TabSessionItem>) {
|
||||
const currentSession = TabSessionStorage.get(id);
|
||||
const newSession = {
|
||||
...currentSession,
|
||||
...session
|
||||
};
|
||||
TabSessionStorage.set(id, newSession as TabSessionItem);
|
||||
return newSession;
|
||||
}
|
||||
|
||||
static remove(id: string) {
|
||||
TabSessionStorageKV.removeItem(id);
|
||||
}
|
||||
}
|
||||
|
||||
export function syncTabs(
|
||||
type: "tabs" | "history" | "biometry" | "all" = "all"
|
||||
) {
|
||||
const data: Partial<TabStore> = {};
|
||||
if (type === "tabs" || type === "all") {
|
||||
data.tabs = useTabStore.getState().tabs;
|
||||
data.currentTab = useTabStore.getState().currentTab;
|
||||
}
|
||||
if (type === "history" || type === "all") {
|
||||
data.canGoBack = useTabStore.getState().canGoBack;
|
||||
data.canGoForward = useTabStore.getState().canGoForward;
|
||||
data.sessionId = useTabStore.getState().sessionId;
|
||||
}
|
||||
|
||||
if (type === "biometry" || type === "all") {
|
||||
data.biometryAvailable = useTabStore.getState().biometryAvailable;
|
||||
data.biometryEnrolled = useTabStore.getState().biometryEnrolled;
|
||||
}
|
||||
|
||||
editorController.current?.commands.doAsync(`
|
||||
globalThis.tabStore?.setState(${JSON.stringify(data)});
|
||||
`);
|
||||
}
|
||||
export const tabSessionHistory = new TabSessionHistory({
|
||||
get() {
|
||||
return useTabStore.getState();
|
||||
},
|
||||
set(state) {
|
||||
useTabStore.setState({
|
||||
...state
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export type TabItem = {
|
||||
id: string;
|
||||
id: number;
|
||||
noteId?: string;
|
||||
previewTab?: boolean;
|
||||
readonly?: boolean;
|
||||
locked?: boolean;
|
||||
noteLocked?: boolean;
|
||||
pinned?: boolean;
|
||||
needsRefresh?: boolean;
|
||||
session?: Partial<TabSessionItem>;
|
||||
};
|
||||
|
||||
const history = new TabHistory();
|
||||
const history = new History();
|
||||
|
||||
export type TabStore = {
|
||||
tabs: TabItem[];
|
||||
currentTab?: string;
|
||||
updateTab: (id: string, options: Omit<Partial<TabItem>, "id">) => void;
|
||||
currentTab: number;
|
||||
updateTab: (id: number, options: Omit<Partial<TabItem>, "id">) => void;
|
||||
focusPreviewTab: (
|
||||
noteId: string,
|
||||
options: Omit<Partial<TabItem>, "id">
|
||||
) => void;
|
||||
removeTab: (index: string) => void;
|
||||
removeTab: (index: number) => void;
|
||||
moveTab: (index: number, toIndex: number) => void;
|
||||
newTab: (options?: Omit<Partial<TabItem>, "id">) => string;
|
||||
focusTab: (id: string) => void;
|
||||
getNoteIdForTab: (id: string) => string | undefined;
|
||||
getTabForNote: (noteId: string) => string | undefined;
|
||||
getTabsForNote: (noteId: string) => TabItem[];
|
||||
forEachNoteTab: (noteId: string, cb: (tab: TabItem) => void) => void;
|
||||
newTab: (options?: Omit<Partial<TabItem>, "id">) => void;
|
||||
focusTab: (id: number) => void;
|
||||
getNoteIdForTab: (id: number) => string | undefined;
|
||||
getTabForNote: (noteId: string) => number | undefined;
|
||||
hasTabForNote: (noteId: string) => boolean;
|
||||
focusEmptyTab: () => void;
|
||||
getCurrentNoteId: () => string | undefined;
|
||||
getTab: (tabId: string) => TabItem | undefined;
|
||||
newTabSession: (
|
||||
id: string,
|
||||
options?: Omit<Partial<TabSessionItem>, "id">
|
||||
) => void;
|
||||
historyNew: string[];
|
||||
getTab: (tabId: number) => TabItem | undefined;
|
||||
tabHistory: number[];
|
||||
biometryAvailable?: boolean;
|
||||
biometryEnrolled?: boolean;
|
||||
tabSessionHistory: TabHistoryType;
|
||||
goBack(): void;
|
||||
goForward(): void;
|
||||
loadSession: (id: string) => Promise<boolean>;
|
||||
canGoBack?: boolean;
|
||||
canGoForward?: boolean;
|
||||
sessionId?: string;
|
||||
};
|
||||
|
||||
const DEFAULT_TABS = {
|
||||
tabs: [
|
||||
{
|
||||
id: "679da59a3924d4bd56d16d3f",
|
||||
session: {
|
||||
id: "679da5a5667a16db2353a062"
|
||||
}
|
||||
}
|
||||
],
|
||||
tabSessionHistory: {
|
||||
"679da59a3924d4bd56d16d3f": {
|
||||
backStack: ["679da5a5667a16db2353a062"],
|
||||
forwardStack: [] as string[]
|
||||
}
|
||||
} as TabHistoryType,
|
||||
historyNew: ["679da59a3924d4bd56d16d3f"],
|
||||
currentTab: "679da59a3924d4bd56d16d3f"
|
||||
} as TabStore;
|
||||
function getId(id: number, tabs: TabItem[]): number {
|
||||
const exists = tabs.find((t) => t.id === id);
|
||||
if (exists) {
|
||||
return getId(id + 1, tabs);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
export function resetTabStore() {
|
||||
useTabStore.setState({
|
||||
...DEFAULT_TABS
|
||||
});
|
||||
TabSessionStorage.storage.clearStore();
|
||||
export function syncTabs() {
|
||||
editorController.current?.commands.doAsync(`
|
||||
globalThis.tabStore?.setState({
|
||||
tabs: ${JSON.stringify(useTabStore.getState().tabs)},
|
||||
currentTab: ${useTabStore.getState().currentTab},
|
||||
biometryAvailable: ${useTabStore.getState().biometryAvailable},
|
||||
biometryEnrolled: ${useTabStore.getState().biometryEnrolled}
|
||||
});
|
||||
`);
|
||||
}
|
||||
|
||||
export const useTabStore = create<TabStore>(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
...DEFAULT_TABS,
|
||||
newTabSession: (
|
||||
_id?: string,
|
||||
options?: Omit<Partial<TabSessionItem>, "id">
|
||||
) => {
|
||||
const tabId = _id || (get().currentTab as string);
|
||||
|
||||
const sessionHistory = get().tabSessionHistory[tabId];
|
||||
|
||||
let oldSessionId: string | undefined = undefined;
|
||||
if (sessionHistory) {
|
||||
const allSessions = sessionHistory.backStack.concat(
|
||||
sessionHistory.forwardStack
|
||||
);
|
||||
allSessions.forEach((id) => {
|
||||
if (TabSessionStorage.get(id)?.noteId === options?.noteId) {
|
||||
oldSessionId = id;
|
||||
}
|
||||
});
|
||||
tabs: [
|
||||
{
|
||||
id: 0
|
||||
}
|
||||
|
||||
const sessionId =
|
||||
oldSessionId &&
|
||||
tabSessionHistory.currentSessionId(tabId) === oldSessionId
|
||||
? oldSessionId
|
||||
: tabSessionHistory.add(tabId, oldSessionId);
|
||||
|
||||
let session: Partial<TabSessionItem>;
|
||||
|
||||
if (!oldSessionId) {
|
||||
session = {
|
||||
id: sessionId,
|
||||
...options
|
||||
};
|
||||
TabSessionStorage.set(sessionId, session as TabSessionItem);
|
||||
} else {
|
||||
session = {
|
||||
...TabSessionStorage.get(oldSessionId),
|
||||
...options
|
||||
};
|
||||
}
|
||||
|
||||
const index = get().tabs.findIndex((t) => t.id === tabId);
|
||||
if (index == -1) return;
|
||||
const tabs = [...get().tabs];
|
||||
tabs[index] = {
|
||||
...tabs[index],
|
||||
session: session
|
||||
} as TabItem;
|
||||
|
||||
set({
|
||||
tabs: tabs
|
||||
});
|
||||
syncTabs();
|
||||
},
|
||||
updateTab: (id: string, options: Omit<Partial<TabItem>, "id">) => {
|
||||
],
|
||||
tabHistory: [0],
|
||||
history: new History(),
|
||||
currentTab: 0,
|
||||
updateTab: (id: number, options: Omit<Partial<TabItem>, "id">) => {
|
||||
if (!options) return;
|
||||
const index = get().tabs.findIndex((t) => t.id === id);
|
||||
if (index == -1) return;
|
||||
const tabs = [...get().tabs];
|
||||
const sessionId =
|
||||
options.session?.id || (tabs[index].session?.id as string);
|
||||
const updatedSession = !options.session
|
||||
? tabs[index].session
|
||||
: TabSessionStorage.update(sessionId, options.session);
|
||||
|
||||
tabs[index] = {
|
||||
...tabs[index],
|
||||
...options,
|
||||
session: updatedSession
|
||||
} as TabItem;
|
||||
...options
|
||||
};
|
||||
|
||||
set({
|
||||
tabs: tabs
|
||||
});
|
||||
syncTabs();
|
||||
},
|
||||
goBack: async () => {
|
||||
const currentTab = get().currentTab;
|
||||
if (!currentTab) return;
|
||||
if (!tabSessionHistory.canGoBack(currentTab)) return;
|
||||
|
||||
const id = tabSessionHistory.back(currentTab) as string;
|
||||
const sessionLoaded = await get().loadSession(id);
|
||||
|
||||
if (!sessionLoaded) {
|
||||
tabSessionHistory.remove(currentTab, id);
|
||||
TabSessionStorage.remove(id);
|
||||
if (!tabSessionHistory.canGoBack(currentTab)) {
|
||||
tabSessionHistory.forward(currentTab);
|
||||
syncTabs();
|
||||
} else {
|
||||
return get().goBack();
|
||||
}
|
||||
} else {
|
||||
syncTabs();
|
||||
}
|
||||
},
|
||||
goForward: async () => {
|
||||
const currentTab = get().currentTab;
|
||||
if (!currentTab) return;
|
||||
|
||||
if (!tabSessionHistory.canGoForward(currentTab)) return;
|
||||
const id = tabSessionHistory.forward(currentTab) as string;
|
||||
if (!(await get().loadSession(id))) {
|
||||
tabSessionHistory.remove(currentTab, id);
|
||||
TabSessionStorage.remove(id);
|
||||
if (!tabSessionHistory.canGoForward(currentTab)) {
|
||||
tabSessionHistory.back(currentTab);
|
||||
syncTabs();
|
||||
} else {
|
||||
return get().goForward();
|
||||
}
|
||||
} else {
|
||||
syncTabs();
|
||||
}
|
||||
},
|
||||
loadSession: async (id: string) => {
|
||||
const session = TabSessionStorage.get(id);
|
||||
if (!session) return false;
|
||||
|
||||
const note = session?.noteId
|
||||
? await db.notes.note(session?.noteId)
|
||||
: undefined;
|
||||
|
||||
if (note) {
|
||||
const isLocked = await db.vaults.itemExists(note);
|
||||
session.locked = isLocked;
|
||||
session.noteLocked = isLocked && !session?.noteLocked;
|
||||
|
||||
session.readonly = note.readonly;
|
||||
} else if (session.noteId) {
|
||||
console.log("Failed to load session...");
|
||||
return false;
|
||||
}
|
||||
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note,
|
||||
newNote: !note,
|
||||
tabId: get().currentTab,
|
||||
session: session
|
||||
});
|
||||
|
||||
return true;
|
||||
},
|
||||
focusPreviewTab: (
|
||||
noteId: string,
|
||||
options: Omit<Partial<TabItem>, "id" | "noteId">
|
||||
) => {},
|
||||
) => {
|
||||
const index = get().tabs.findIndex((t) => t.previewTab);
|
||||
if (index === -1)
|
||||
return get().newTab({
|
||||
noteId,
|
||||
previewTab: true,
|
||||
...options
|
||||
});
|
||||
const tabs = [...get().tabs];
|
||||
tabs[index] = {
|
||||
...tabs[index],
|
||||
...options,
|
||||
previewTab: true,
|
||||
noteId: noteId
|
||||
};
|
||||
|
||||
removeTab: (id: string) => {
|
||||
set({
|
||||
tabs: tabs
|
||||
});
|
||||
get().focusTab(tabs[index].id);
|
||||
},
|
||||
removeTab: (id: number) => {
|
||||
const index = get().tabs.findIndex((t) => t.id === id);
|
||||
|
||||
if (index > -1) {
|
||||
const isFocused = id === get().currentTab;
|
||||
const nextTabs = get().tabs.slice();
|
||||
nextTabs.splice(index, 1);
|
||||
history.remove(id);
|
||||
|
||||
const tabSessions = tabSessionHistory.getTabHistory(id);
|
||||
tabSessions.back.forEach((id) => TabSessionStorage.remove(id));
|
||||
tabSessions.forward.forEach((id) => TabSessionStorage.remove(id));
|
||||
tabSessionHistory.clearStackForTab(id);
|
||||
|
||||
if (nextTabs.length === 0) {
|
||||
const id = getId();
|
||||
set({
|
||||
tabs: [{ id: id }]
|
||||
nextTabs.push({
|
||||
id: 0
|
||||
});
|
||||
get().newTabSession(id, {});
|
||||
get().focusTab(id);
|
||||
} else {
|
||||
set({
|
||||
tabs: nextTabs
|
||||
});
|
||||
if (isFocused) {
|
||||
const lastTab = history.restoreLast();
|
||||
if (lastTab) get().focusTab(lastTab);
|
||||
}
|
||||
}
|
||||
syncTabs();
|
||||
set({
|
||||
tabs: nextTabs
|
||||
});
|
||||
get().focusTab(
|
||||
isFocused ? history.restoreLast() || 0 : get().currentTab
|
||||
);
|
||||
}
|
||||
},
|
||||
newTab: (options) => {
|
||||
const id = getId();
|
||||
const id = getId(get().tabs.length, get().tabs);
|
||||
const nextTabs = [
|
||||
...get().tabs,
|
||||
{
|
||||
id: id,
|
||||
...options
|
||||
}
|
||||
];
|
||||
set({
|
||||
tabs: [
|
||||
...get().tabs,
|
||||
{
|
||||
id: id,
|
||||
...options
|
||||
}
|
||||
]
|
||||
tabs: nextTabs
|
||||
});
|
||||
get().newTabSession(id, options?.session || {});
|
||||
get().focusTab(id);
|
||||
return id;
|
||||
},
|
||||
focusEmptyTab: () => {
|
||||
const index = get().tabs.findIndex((t) => !t.session?.noteId);
|
||||
const index = get().tabs.findIndex((t) => !t.noteId);
|
||||
if (index === -1) return get().newTab();
|
||||
|
||||
get().focusTab(get().tabs[index].id);
|
||||
@@ -439,46 +223,37 @@ export const useTabStore = create<TabStore>(
|
||||
syncTabs();
|
||||
},
|
||||
|
||||
focusTab: (id: string) => {
|
||||
focusTab: (id: number) => {
|
||||
history.add(id);
|
||||
set({
|
||||
currentTab: id,
|
||||
canGoBack: tabSessionHistory.canGoBack(id),
|
||||
canGoForward: tabSessionHistory.canGoForward(id),
|
||||
sessionId: tabSessionHistory.currentSessionId(id)
|
||||
currentTab: id
|
||||
});
|
||||
syncTabs();
|
||||
},
|
||||
getNoteIdForTab: (id: string) => {
|
||||
return get().tabs.find((t) => t.id === id)?.session?.noteId;
|
||||
getNoteIdForTab: (id: number) => {
|
||||
return get().tabs.find((t) => t.id === id)?.noteId;
|
||||
},
|
||||
hasTabForNote: (noteId: string) => {
|
||||
return !!get().tabs.find((t) => t.session?.noteId === noteId);
|
||||
return (
|
||||
typeof get().tabs.find((t) => t.noteId === noteId)?.id === "number"
|
||||
);
|
||||
},
|
||||
getTabForNote: (noteId: string) => {
|
||||
return get().tabs.find((t) => t.session?.noteId === noteId)?.id;
|
||||
},
|
||||
getTabsForNote(noteId: string) {
|
||||
return get().tabs.filter((t) => t.session?.noteId === noteId);
|
||||
},
|
||||
forEachNoteTab: (noteId: string, cb: (tab: TabItem) => void) => {
|
||||
const tabs = get().tabs.filter((t) => t.session?.noteId === noteId);
|
||||
tabs.forEach(cb);
|
||||
return get().tabs.find((t) => t.noteId === noteId)?.id;
|
||||
},
|
||||
getCurrentNoteId: () => {
|
||||
return get().tabs.find((t) => t.id === get().currentTab)?.session
|
||||
?.noteId;
|
||||
return get().tabs.find((t) => t.id === get().currentTab)?.noteId;
|
||||
},
|
||||
getTab: (tabId) => {
|
||||
return get().tabs.find((t) => t.id === tabId);
|
||||
}
|
||||
}),
|
||||
{
|
||||
name: "tabs-storage-v3",
|
||||
name: "tabs-storage",
|
||||
getStorage: () => MMKV as unknown as StateStorage,
|
||||
onRehydrateStorage: () => {
|
||||
return (state) => {
|
||||
history.history = state?.historyNew || [];
|
||||
history.history = state?.tabHistory.slice() || [];
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,6 @@ import { eOnLoadNote } from "../../../utils/events";
|
||||
import { NotesnookModule } from "../../../utils/notesnook-module";
|
||||
import { AppState, EditorState, useEditorType } from "./types";
|
||||
import { useTabStore } from "./use-tab-store";
|
||||
import { NativeEvents } from "@notesnook/editor-mobile/src/utils/native-events";
|
||||
|
||||
export const textInput = createRef<TextInput>();
|
||||
export const editorController =
|
||||
createRef<useEditorType>() as MutableRefObject<useEditorType>;
|
||||
@@ -48,6 +46,19 @@ export function editorState() {
|
||||
return editorController.current?.state.current || defaultState;
|
||||
}
|
||||
|
||||
export const EditorEvents = {
|
||||
html: "native:html",
|
||||
updatehtml: "native:updatehtml",
|
||||
title: "native:title",
|
||||
theme: "native:theme",
|
||||
titleplaceholder: "native:titleplaceholder",
|
||||
logger: "native:logger",
|
||||
status: "native:status",
|
||||
keyboardShown: "native:keyboardShown",
|
||||
attachmentData: "native:attachment-data",
|
||||
resolve: "native:resolve"
|
||||
};
|
||||
|
||||
export function randId(prefix: string) {
|
||||
return Math.random()
|
||||
.toString(36)
|
||||
@@ -61,15 +72,15 @@ export function makeSessionId(id?: string) {
|
||||
export async function isEditorLoaded(
|
||||
ref: RefObject<WebView>,
|
||||
sessionId: string,
|
||||
tabId: string
|
||||
tabId: number
|
||||
) {
|
||||
return await post(ref, sessionId, tabId, NativeEvents.status);
|
||||
return await post(ref, sessionId, tabId, EditorEvents.status);
|
||||
}
|
||||
|
||||
export async function post<T>(
|
||||
ref: RefObject<WebView>,
|
||||
sessionId: string,
|
||||
tabId: string,
|
||||
tabId: number,
|
||||
type: string,
|
||||
value: T | null = null,
|
||||
waitFor = 300
|
||||
@@ -174,7 +185,7 @@ export async function openInternalLink(url: string) {
|
||||
if (!data?.id) return false;
|
||||
if (
|
||||
data.id ===
|
||||
useTabStore.getState().getNoteIdForTab(useTabStore.getState().currentTab!)
|
||||
useTabStore.getState().getNoteIdForTab(useTabStore.getState().currentTab)
|
||||
) {
|
||||
if (data.params?.blockId) {
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -43,7 +43,6 @@ import { useRelationStore } from "../stores/use-relation-store";
|
||||
import { useReminderStore } from "../stores/use-reminder-store";
|
||||
import { useSettingStore } from "../stores/use-setting-store";
|
||||
import { useUserStore } from "../stores/use-user-store";
|
||||
import { eOnLoadNote } from "../utils/events";
|
||||
import { tabBarRef } from "../utils/global-refs";
|
||||
import { convertNoteToText } from "../utils/note-to-text";
|
||||
import { NotesnookModule } from "../utils/notesnook-module";
|
||||
@@ -450,10 +449,19 @@ async function loadNote(id: string, jump: boolean) {
|
||||
})
|
||||
);
|
||||
|
||||
const isLocked = await db.vaults.itemExists({
|
||||
type: "note",
|
||||
id: id
|
||||
});
|
||||
|
||||
const tab = useTabStore.getState().getTabForNote(id);
|
||||
if (useTabStore.getState().currentTab !== tab) {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
note: note
|
||||
if (tab !== undefined) {
|
||||
useTabStore.getState().focusTab(tab);
|
||||
} else {
|
||||
useTabStore.getState().focusPreviewTab(id, {
|
||||
noteId: id,
|
||||
readonly: note.readonly,
|
||||
noteLocked: isLocked
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,5 +62,4 @@
|
||||
#Gson
|
||||
-keepattributes Signature
|
||||
-keep class com.google.gson.reflect.TypeToken { *; }
|
||||
-keep class * extends com.google.gson.reflect.TypeToken
|
||||
-keep class com.streetwriters.notesnook.datatypes.* { *; }
|
||||
-keep class * extends com.google.gson.reflect.TypeToken
|
||||
@@ -31,20 +31,14 @@ public class NotePreviewWidget extends AppWidgetProvider {
|
||||
|
||||
Intent intent = new Intent(context, MainActivity.class);
|
||||
intent.putExtra(OpenNoteId, note.getId());
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
intent.putExtra(RCTNNativeModule.IntentType, "OpenNote");
|
||||
intent.setData(Uri.parse("https://notesnook.com/open_note?id=" + note.getId()));
|
||||
intent.setData(Uri.parse("https://notesnook.com/open_note"));
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE, getActivityOptionsBundle());
|
||||
views.setOnClickPendingIntent(R.id.open_note, pendingIntent);
|
||||
|
||||
appWidgetManager.updateAppWidget(appWidgetId, views);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
|
||||
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
|
||||
}
|
||||
|
||||
private static Bundle getActivityOptionsBundle() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
ActivityOptions activityOptions = ActivityOptions.makeBasic();
|
||||
|
||||
@@ -4,7 +4,6 @@ import android.app.ActivityOptions;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
@@ -46,7 +45,6 @@ class ReminderRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactor
|
||||
SharedPreferences preferences = context.getSharedPreferences("appPreview", Context.MODE_PRIVATE);
|
||||
Gson gson = new Gson();
|
||||
reminders = gson.fromJson(preferences.getString("remindersList","[]"), new TypeToken<List<Reminder>>(){}.getType());
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,16 +64,14 @@ class ReminderRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactor
|
||||
boolean useMiniLayout = reminder.getDescription() == null || reminder.getDescription().isEmpty();
|
||||
|
||||
RemoteViews views = new RemoteViews(context.getPackageName(), useMiniLayout ? R.layout.widget_reminder_layout_small : R.layout.widget_reminder_layout);
|
||||
|
||||
views.setTextViewText(R.id.reminder_title, reminder.getTitle());
|
||||
if (!useMiniLayout) {
|
||||
if (!useMiniLayout) {
|
||||
views.setTextViewText(R.id.reminder_description, reminder.getDescription());
|
||||
}
|
||||
}
|
||||
views.setTextViewText(R.id.reminder_time, reminder.getFormattedTime());
|
||||
final Intent fillInIntent = new Intent();
|
||||
final Bundle extras = new Bundle();
|
||||
extras.putString(ReminderViewsService.OpenReminderId, reminder.getId());
|
||||
fillInIntent.setData(Uri.parse("https://notesnook.com/open_reminder?id=" + reminder.getId()));
|
||||
fillInIntent.putExtra(RCTNNativeModule.IntentType, "OpenReminder");
|
||||
fillInIntent.putExtras(extras);
|
||||
views.setOnClickFillInIntent(R.id.reminder_item_btn, fillInIntent);
|
||||
@@ -90,12 +86,11 @@ class ReminderRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactor
|
||||
|
||||
@Override
|
||||
public int getViewTypeCount() {
|
||||
return 2;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,13 +34,12 @@ public class ReminderWidgetProvider extends AppWidgetProvider {
|
||||
|
||||
static void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId, RemoteViews views) {
|
||||
Intent listview_intent_template = new Intent(context, MainActivity.class);
|
||||
listview_intent_template.setAction(Intent.ACTION_VIEW);
|
||||
listview_intent_template.setData(Uri.parse("https://notesnook.com/open_reminder"));
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId, listview_intent_template, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE, getActivityOptionsBundle());
|
||||
views.setPendingIntentTemplate(R.id.widget_list_view, pendingIntent);
|
||||
|
||||
Intent new_reminder_intent = new Intent(context, MainActivity.class);
|
||||
new_reminder_intent.putExtra(NewReminder, NewReminder);
|
||||
new_reminder_intent.setAction(Intent.ACTION_VIEW);
|
||||
new_reminder_intent.putExtra(RCTNNativeModule.IntentType, "NewReminder");
|
||||
new_reminder_intent.setData(Uri.parse("https://notesnook.com/new_reminder"));
|
||||
PendingIntent pendingIntent2 = PendingIntent.getActivity(context, appWidgetId, new_reminder_intent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE, getActivityOptionsBundle());
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<color name="light_blue_600">#FF039BE5</color>
|
||||
<color name="light_blue_900">#FF01579B</color>
|
||||
<color name="bootsplash_background">#FFFFFF</color>
|
||||
<color name="background">#DCEDEDED</color>
|
||||
<color name="border">#BFBFBF</color>
|
||||
<color name="background">#D8000000</color>
|
||||
<color name="border">#CCCCCC</color>
|
||||
<color name="text">#1D1D1D</color>
|
||||
</resources>
|
||||
166
apps/mobile/package-lock.json
generated
166
apps/mobile/package-lock.json
generated
@@ -3821,7 +3821,6 @@
|
||||
"@lingui/react": "5.1.2",
|
||||
"@mdi/js": "^7.2.96",
|
||||
"@mdi/react": "^1.6.0",
|
||||
"@notesnook/common": "file:../common",
|
||||
"@notesnook/editor": "file:../editor",
|
||||
"@notesnook/intl": "file:../intl",
|
||||
"@notesnook/theme": "file:../theme",
|
||||
@@ -3832,7 +3831,6 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-freeze": "^1.0.3",
|
||||
"tinycolor2": "1.6.0",
|
||||
"zustand": "^4.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -7742,7 +7740,7 @@
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/@types/prop-types": {
|
||||
"version": "15.7.11",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/@types/q": {
|
||||
@@ -7762,7 +7760,7 @@
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/@types/react": {
|
||||
"version": "18.2.39",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
@@ -7793,7 +7791,7 @@
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/@types/scheduler": {
|
||||
"version": "0.16.8",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/@types/semver": {
|
||||
@@ -12618,7 +12616,7 @@
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/immer": {
|
||||
"version": "9.0.21",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@@ -23090,7 +23088,6 @@
|
||||
},
|
||||
"../../packages/editor/node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"../../packages/editor/node_modules/jsesc": {
|
||||
@@ -23141,7 +23138,6 @@
|
||||
},
|
||||
"../../packages/editor/node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
@@ -23653,7 +23649,6 @@
|
||||
},
|
||||
"../../packages/editor/node_modules/react": {
|
||||
"version": "18.3.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
@@ -23672,7 +23667,6 @@
|
||||
},
|
||||
"../../packages/editor/node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
@@ -23811,7 +23805,6 @@
|
||||
},
|
||||
"../../packages/editor/node_modules/scheduler": {
|
||||
"version": "0.23.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
@@ -28275,12 +28268,7 @@
|
||||
"@readme/data-urls": "3.0.0",
|
||||
"@streetwriters/kysely": "^0.27.4",
|
||||
"@streetwriters/showdown": "^3.0.1-alpha.2",
|
||||
"@tanstack/react-query": "^4.36.1",
|
||||
"@trpc/client": "^10.45.2",
|
||||
"@trpc/react-query": "^10.45.2",
|
||||
"@trpc/server": "^10.45.2",
|
||||
"absolutify": "^0.1.0",
|
||||
"async-mutex": "0.5.0",
|
||||
"buffer": "^6.0.3",
|
||||
"dayjs": "^1.11.13",
|
||||
"deprecated-react-native-prop-types": "^4.1.0",
|
||||
@@ -28594,7 +28582,6 @@
|
||||
},
|
||||
"node_modules/@babel/helper-builder-binary-assignment-operator-visitor": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.22.5"
|
||||
@@ -28714,7 +28701,6 @@
|
||||
},
|
||||
"node_modules/@babel/helper-hoist-variables": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.22.5"
|
||||
@@ -28974,7 +28960,6 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz",
|
||||
"integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
},
|
||||
@@ -28989,7 +28974,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz",
|
||||
"integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
"@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
|
||||
@@ -29138,7 +29122,6 @@
|
||||
"version": "7.21.0-placeholder-for-preset-env.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
|
||||
"integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
@@ -29151,7 +29134,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz",
|
||||
"integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==",
|
||||
"deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-unicode-property-regex instead.",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-create-regexp-features-plugin": "^7.18.6",
|
||||
"@babel/helper-plugin-utils": "^7.18.6"
|
||||
@@ -29186,7 +29168,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-syntax-class-properties": {
|
||||
"version": "7.12.13",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.12.13"
|
||||
@@ -29199,7 +29180,6 @@
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
|
||||
"integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.14.5"
|
||||
},
|
||||
@@ -29237,7 +29217,6 @@
|
||||
"version": "7.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz",
|
||||
"integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.8.3"
|
||||
},
|
||||
@@ -29262,7 +29241,6 @@
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.25.6.tgz",
|
||||
"integrity": "sha512-aABl0jHw9bZ2karQ/uUD6XP4u0SG22SJrOHFoL6XB1R7dTovOP4TzTlsxOYC5yQ1pdscVK2JTUnF6QL3ARoAiQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
},
|
||||
@@ -29277,7 +29255,6 @@
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.6.tgz",
|
||||
"integrity": "sha512-sXaDXaJN9SNLymBdlWFA+bjzBhFD617ZaFiY13dGt7TVslVvVgA6fkZOP7Ki3IGElC45lwHdOTrCtKZGVAWeLQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
},
|
||||
@@ -29292,7 +29269,6 @@
|
||||
"version": "7.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
|
||||
"integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.10.4"
|
||||
},
|
||||
@@ -29304,7 +29280,6 @@
|
||||
"version": "7.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
|
||||
"integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.8.0"
|
||||
},
|
||||
@@ -29404,7 +29379,6 @@
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
|
||||
"integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.14.5"
|
||||
},
|
||||
@@ -29432,7 +29406,6 @@
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz",
|
||||
"integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-create-regexp-features-plugin": "^7.18.6",
|
||||
"@babel/helper-plugin-utils": "^7.18.6"
|
||||
@@ -29461,7 +29434,6 @@
|
||||
"version": "7.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.4.tgz",
|
||||
"integrity": "sha512-jz8cV2XDDTqjKPwVPJBIjORVEmSGYhdRa8e5k5+vN+uwcjSrSxUaebBRa4ko1jqNF2uxyg8G6XYk30Jv285xzg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-environment-visitor": "^7.22.5",
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
@@ -29492,7 +29464,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-block-scoped-functions": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -29521,7 +29492,6 @@
|
||||
"version": "7.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.4.tgz",
|
||||
"integrity": "sha512-nZeZHyCWPfjkdU5pA/uHiTaDAFUEqkpzf1YoQT2NeSynCGYq9rxfyI3XpQbfx/a0hSnFH6TGlEXvae5Vi7GD8g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-create-class-features-plugin": "^7.22.5",
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -29537,7 +29507,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz",
|
||||
"integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-create-class-features-plugin": "^7.22.5",
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
@@ -29602,7 +29571,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz",
|
||||
"integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-create-regexp-features-plugin": "^7.22.5",
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -29618,7 +29586,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz",
|
||||
"integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
},
|
||||
@@ -29633,7 +29600,6 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz",
|
||||
"integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
},
|
||||
@@ -29646,7 +29612,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-exponentiation-operator": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5",
|
||||
@@ -29663,7 +29628,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz",
|
||||
"integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
"@babel/plugin-syntax-export-namespace-from": "^7.8.3"
|
||||
@@ -29691,7 +29655,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-for-of": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -29722,7 +29685,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz",
|
||||
"integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
"@babel/plugin-syntax-json-strings": "^7.8.3"
|
||||
@@ -29751,7 +29713,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz",
|
||||
"integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
"@babel/plugin-syntax-logical-assignment-operators": "^7.10.4"
|
||||
@@ -29765,7 +29726,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-member-expression-literals": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -29781,7 +29741,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz",
|
||||
"integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-module-transforms": "^7.22.5",
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -29812,7 +29771,6 @@
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz",
|
||||
"integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-hoist-variables": "^7.22.5",
|
||||
"@babel/helper-module-transforms": "^7.22.5",
|
||||
@@ -29830,7 +29788,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz",
|
||||
"integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-module-transforms": "^7.22.5",
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -29860,7 +29817,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz",
|
||||
"integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
},
|
||||
@@ -29890,7 +29846,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz",
|
||||
"integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
"@babel/plugin-syntax-numeric-separator": "^7.10.4"
|
||||
@@ -29906,7 +29861,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz",
|
||||
"integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.22.5",
|
||||
"@babel/helper-compilation-targets": "^7.22.5",
|
||||
@@ -29923,7 +29877,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-object-super": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
@@ -29940,7 +29893,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz",
|
||||
"integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
"@babel/plugin-syntax-optional-catch-binding": "^7.8.3"
|
||||
@@ -30014,7 +29966,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-property-literals": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -30084,7 +30035,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-regenerator": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
@@ -30101,7 +30051,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz",
|
||||
"integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
},
|
||||
@@ -30194,7 +30143,6 @@
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz",
|
||||
"integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
},
|
||||
@@ -30225,7 +30173,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz",
|
||||
"integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
},
|
||||
@@ -30240,7 +30187,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz",
|
||||
"integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-create-regexp-features-plugin": "^7.22.5",
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -30270,7 +30216,6 @@
|
||||
"version": "7.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.4.tgz",
|
||||
"integrity": "sha512-qesBxiWkgN1Q+31xUE9RcMk79eOXXDCv6tfyGMRSs4RGlioSg2WVyQAm07k726cSE56pa+Kb0y9epX2qaXzTvA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-create-regexp-features-plugin": "^7.22.5",
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -30286,7 +30231,6 @@
|
||||
"version": "7.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.4.tgz",
|
||||
"integrity": "sha512-W9Gyo+KmcxjGahtt3t9fb14vFRWvPpu5pT6GBlovAK6BTBcxgjfVMSQCfJl4oi35ODrxP6xx2Wr8LNST57Mraw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.22.5",
|
||||
"@babel/helper-compilation-targets": "^7.22.5",
|
||||
@@ -30380,7 +30324,6 @@
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6.tgz",
|
||||
"integrity": "sha512-ID2yj6K/4lKfhuU3+EX4UvNbIt7eACFbHmNUjzA+ep+B5971CknnA/9DEWKbRokfbbtblxxxXFJJrH47UEAMVg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.0.0",
|
||||
"@babel/plugin-proposal-unicode-property-regex": "^7.4.4",
|
||||
@@ -30396,7 +30339,6 @@
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
@@ -33700,8 +33642,7 @@
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
|
||||
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
|
||||
},
|
||||
"node_modules/@types/graceful-fs": {
|
||||
"version": "4.1.6",
|
||||
@@ -33807,14 +33748,14 @@
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.5",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
|
||||
"integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
@@ -34170,7 +34111,6 @@
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz",
|
||||
"integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/helper-numbers": "1.11.6",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.6"
|
||||
@@ -34179,26 +34119,22 @@
|
||||
"node_modules/@webassemblyjs/floating-point-hex-parser": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz",
|
||||
"integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw=="
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-api-error": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz",
|
||||
"integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q=="
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-buffer": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz",
|
||||
"integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw=="
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-numbers": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz",
|
||||
"integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/floating-point-hex-parser": "1.11.6",
|
||||
"@webassemblyjs/helper-api-error": "1.11.6",
|
||||
@@ -34208,14 +34144,12 @@
|
||||
"node_modules/@webassemblyjs/helper-wasm-bytecode": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz",
|
||||
"integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA=="
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-wasm-section": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz",
|
||||
"integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.11.6",
|
||||
"@webassemblyjs/helper-buffer": "1.11.6",
|
||||
@@ -34227,7 +34161,6 @@
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz",
|
||||
"integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@xtuc/ieee754": "^1.2.0"
|
||||
}
|
||||
@@ -34236,7 +34169,6 @@
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz",
|
||||
"integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
@@ -34244,14 +34176,12 @@
|
||||
"node_modules/@webassemblyjs/utf8": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz",
|
||||
"integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA=="
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-edit": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz",
|
||||
"integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.11.6",
|
||||
"@webassemblyjs/helper-buffer": "1.11.6",
|
||||
@@ -34267,7 +34197,6 @@
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz",
|
||||
"integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.11.6",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
|
||||
@@ -34280,7 +34209,6 @@
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz",
|
||||
"integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.11.6",
|
||||
"@webassemblyjs/helper-buffer": "1.11.6",
|
||||
@@ -34292,7 +34220,6 @@
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz",
|
||||
"integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.11.6",
|
||||
"@webassemblyjs/helper-api-error": "1.11.6",
|
||||
@@ -34306,7 +34233,6 @@
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz",
|
||||
"integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.11.6",
|
||||
"@xtuc/long": "4.2.2"
|
||||
@@ -34364,14 +34290,12 @@
|
||||
"node_modules/@xtuc/ieee754": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
|
||||
"integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="
|
||||
},
|
||||
"node_modules/@xtuc/long": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
|
||||
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="
|
||||
},
|
||||
"node_modules/@yarnpkg/lockfile": {
|
||||
"version": "1.1.0",
|
||||
@@ -34672,14 +34596,6 @@
|
||||
"version": "1.0.1",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/async-mutex": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz",
|
||||
"integrity": "sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/at-least-node": {
|
||||
"version": "1.0.0",
|
||||
"dev": true,
|
||||
@@ -35370,7 +35286,6 @@
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
|
||||
"integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
}
|
||||
@@ -35801,7 +35716,7 @@
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.2",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
@@ -36401,7 +36316,6 @@
|
||||
"version": "5.17.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
|
||||
"integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
"tapable": "^2.2.0"
|
||||
@@ -36521,8 +36435,7 @@
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
|
||||
"integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw=="
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.0.0",
|
||||
@@ -36867,7 +36780,6 @@
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esrecurse": "^4.3.0",
|
||||
"estraverse": "^4.1.1"
|
||||
@@ -36880,7 +36792,6 @@
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
|
||||
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
@@ -37044,7 +36955,6 @@
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
|
||||
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"estraverse": "^5.2.0"
|
||||
},
|
||||
@@ -37056,7 +36966,6 @@
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
|
||||
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
@@ -37065,7 +36974,6 @@
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -37825,8 +37733,7 @@
|
||||
"node_modules/glob-to-regexp": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
|
||||
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
|
||||
},
|
||||
"node_modules/global": {
|
||||
"version": "4.4.0",
|
||||
@@ -39915,8 +39822,7 @@
|
||||
"node_modules/json-parse-even-better-errors": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||
"dev": true
|
||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
|
||||
},
|
||||
"node_modules/json-schema-ref-resolver": {
|
||||
"version": "1.0.1",
|
||||
@@ -40392,7 +40298,6 @@
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
|
||||
"integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.11.5"
|
||||
}
|
||||
@@ -42622,7 +42527,6 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
@@ -42644,6 +42548,30 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom/node_modules/scheduler": {
|
||||
"version": "0.23.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-freeze": {
|
||||
"version": "1.0.3",
|
||||
"license": "MIT",
|
||||
@@ -43437,7 +43365,6 @@
|
||||
"resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-18.2.0.tgz",
|
||||
"integrity": "sha512-JWD+aQ0lh2gvh4NM3bBM42Kx+XybOxCpgYK7F8ugAlpaTSnWsX+39Z4XkOykGZAHrjwwTZT3x3KxswVWxHPUqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-is": "^18.2.0",
|
||||
"react-shallow-renderer": "^16.15.0",
|
||||
@@ -43563,7 +43490,6 @@
|
||||
},
|
||||
"node_modules/regenerator-transform": {
|
||||
"version": "0.15.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.8.4"
|
||||
@@ -43916,7 +43842,6 @@
|
||||
},
|
||||
"node_modules/serialize-javascript": {
|
||||
"version": "6.0.1",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"randombytes": "^2.1.0"
|
||||
@@ -44574,7 +44499,6 @@
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.2.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -44652,7 +44576,6 @@
|
||||
"version": "5.3.10",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz",
|
||||
"integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.17",
|
||||
"jest-worker": "^27.4.5",
|
||||
@@ -44686,7 +44609,6 @@
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
|
||||
"integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"merge-stream": "^2.0.0",
|
||||
@@ -44700,7 +44622,6 @@
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
@@ -45277,7 +45198,6 @@
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
|
||||
"integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
"graceful-fs": "^4.1.2"
|
||||
@@ -45301,7 +45221,6 @@
|
||||
"version": "5.94.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
|
||||
"integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.3",
|
||||
"@types/estree": "^1.0.0",
|
||||
@@ -45417,7 +45336,6 @@
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
|
||||
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
|
||||
@@ -54,4 +54,4 @@
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.74.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,7 @@ export class AppModel {
|
||||
}
|
||||
|
||||
goBack() {
|
||||
const goBackButton = this.page.locator(getTestId("route-go-back"));
|
||||
const goBackButton = this.page.locator(getTestId("go-back"));
|
||||
return goBackButton.click();
|
||||
}
|
||||
|
||||
|
||||
@@ -39,12 +39,10 @@ export class BaseItemModel {
|
||||
return await this.locator.evaluate((el) => el === document.activeElement);
|
||||
}
|
||||
|
||||
async click(options?: { middleClick?: boolean }) {
|
||||
async click() {
|
||||
if (!(await this.locator.isVisible()))
|
||||
await this.locator.scrollIntoViewIfNeeded();
|
||||
await this.locator.click({
|
||||
button: options?.middleClick ? "middle" : "left"
|
||||
});
|
||||
await this.locator.click();
|
||||
}
|
||||
|
||||
async getId() {
|
||||
|
||||
@@ -36,9 +36,6 @@ export class EditorModel {
|
||||
private readonly dateEditedText: Locator;
|
||||
private readonly searchButton: Locator;
|
||||
private readonly tabsList: Locator;
|
||||
private readonly goBackButton: Locator;
|
||||
private readonly goForwardButton: Locator;
|
||||
private readonly newTabButton: Locator;
|
||||
readonly savedIcon: Locator;
|
||||
readonly notSavedIcon: Locator;
|
||||
|
||||
@@ -62,9 +59,6 @@ export class EditorModel {
|
||||
this.savedIcon = page.locator(getTestId("editor-save-state-saved"));
|
||||
this.notSavedIcon = page.locator(getTestId("editor-save-state-notsaved"));
|
||||
this.tabsList = page.locator(getTestId("tabs"));
|
||||
this.goBackButton = page.locator(getTestId("go-back"));
|
||||
this.goForwardButton = page.locator(getTestId("go-forward"));
|
||||
this.newTabButton = page.locator(getTestId("New tab"));
|
||||
}
|
||||
|
||||
async waitForLoading(title?: string, content?: string) {
|
||||
@@ -249,26 +243,6 @@ export class EditorModel {
|
||||
}
|
||||
}
|
||||
|
||||
async getTabs() {
|
||||
const tabs: TabItemModel[] = [];
|
||||
for await (const item of iterateList(this.tabsList.locator(".tab"))) {
|
||||
tabs.push(new TabItemModel(item, this.page));
|
||||
}
|
||||
return tabs;
|
||||
}
|
||||
|
||||
async goBack() {
|
||||
await this.goBackButton.click();
|
||||
}
|
||||
|
||||
async goForward() {
|
||||
await this.goForwardButton.click();
|
||||
}
|
||||
|
||||
async newTab() {
|
||||
await this.newTabButton.click();
|
||||
}
|
||||
|
||||
async attachImage() {
|
||||
await this.page
|
||||
.context()
|
||||
|
||||
@@ -38,8 +38,8 @@ export class NoteItemModel extends BaseItemModel {
|
||||
this.editor = new EditorModel(this.page);
|
||||
}
|
||||
|
||||
async openNote(openInNewTab?: boolean) {
|
||||
await this.click({ middleClick: openInNewTab });
|
||||
async openNote() {
|
||||
await this.click();
|
||||
const title = await this.getTitle();
|
||||
await this.editor.waitForLoading(title);
|
||||
}
|
||||
|
||||
@@ -211,11 +211,6 @@ export class NoteContextMenuModel extends BaseProperties {
|
||||
this.menu = new ContextMenuModel(page);
|
||||
}
|
||||
|
||||
async openInNewTab() {
|
||||
await this.open();
|
||||
await this.menu.clickOnItem("openinnewtab");
|
||||
}
|
||||
|
||||
async isColored(color: string): Promise<boolean> {
|
||||
await this.open();
|
||||
await this.menu.clickOnItem("colors");
|
||||
|
||||
@@ -30,20 +30,6 @@ export class TabItemModel {
|
||||
const testId = await this.locator.getAttribute("data-test-id");
|
||||
return testId?.replace("tab-", "");
|
||||
}
|
||||
|
||||
async click() {
|
||||
return this.locator.click();
|
||||
}
|
||||
|
||||
async title() {
|
||||
return this.locator.locator(getTestId("tab-title")).textContent();
|
||||
}
|
||||
|
||||
async isActive() {
|
||||
const classList = await this.locator.getAttribute("class");
|
||||
return !!classList?.includes("active");
|
||||
}
|
||||
|
||||
close() {
|
||||
return this.closeButton.click();
|
||||
}
|
||||
|
||||
@@ -17,11 +17,47 @@ 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 { test, expect } from "@playwright/test";
|
||||
import { createHistorySession, PASSWORD } from "./utils";
|
||||
import { test, expect, Page } from "@playwright/test";
|
||||
import { AppModel } from "./models/app.model";
|
||||
import { NOTE, PASSWORD } from "./utils";
|
||||
|
||||
test.setTimeout(60 * 1000);
|
||||
|
||||
async function createSession(page: Page, locked = false) {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
let note = await notes.createNote(NOTE);
|
||||
|
||||
if (locked) {
|
||||
await note?.contextMenu.lock(PASSWORD);
|
||||
await note?.openLockedNote(PASSWORD);
|
||||
}
|
||||
|
||||
const edits = ["Some edited text.", "Some more edited text."];
|
||||
for (const edit of edits) {
|
||||
await notes.editor.setContent(edit);
|
||||
|
||||
await page.waitForTimeout(600);
|
||||
|
||||
await page.reload().catch(console.error);
|
||||
await notes.waitForItem(NOTE.title);
|
||||
note = await notes.findNote(NOTE);
|
||||
locked ? await note?.openLockedNote(PASSWORD) : await note?.openNote();
|
||||
}
|
||||
const contents = [
|
||||
`${NOTE.content}${edits[0]}${edits[1]}`,
|
||||
`${NOTE.content}${edits[0]}`
|
||||
];
|
||||
|
||||
return {
|
||||
note,
|
||||
notes,
|
||||
app,
|
||||
contents
|
||||
};
|
||||
}
|
||||
|
||||
const sessionTypes = ["locked", "unlocked"] as const;
|
||||
|
||||
for (const type of sessionTypes) {
|
||||
@@ -30,7 +66,7 @@ for (const type of sessionTypes) {
|
||||
test(`editing a note should create a new ${type} session in its session history`, async ({
|
||||
page
|
||||
}) => {
|
||||
const { note } = await createHistorySession(page, isLocked);
|
||||
const { note } = await createSession(page, isLocked);
|
||||
|
||||
const history = await note?.properties.getSessionHistory();
|
||||
expect(history?.length).toBeGreaterThan(1);
|
||||
@@ -45,7 +81,7 @@ for (const type of sessionTypes) {
|
||||
test(`switching ${type} sessions should change editor content`, async ({
|
||||
page
|
||||
}) => {
|
||||
const { note, contents } = await createHistorySession(page, isLocked);
|
||||
const { note, contents } = await createSession(page, isLocked);
|
||||
|
||||
const history = await note?.properties.getSessionHistory();
|
||||
let preview = await history?.at(1)?.open();
|
||||
@@ -55,7 +91,6 @@ for (const type of sessionTypes) {
|
||||
contents[1]
|
||||
);
|
||||
await note?.click();
|
||||
if (type === "locked") await note?.openLockedNote(PASSWORD);
|
||||
await note?.properties.close();
|
||||
preview = await history?.at(0)?.open();
|
||||
if (type === "locked") await preview?.unlock(PASSWORD);
|
||||
@@ -68,10 +103,7 @@ for (const type of sessionTypes) {
|
||||
test(`restoring a ${type} session should change note's content`, async ({
|
||||
page
|
||||
}) => {
|
||||
const { note, notes, contents } = await createHistorySession(
|
||||
page,
|
||||
isLocked
|
||||
);
|
||||
const { note, notes, contents } = await createSession(page, isLocked);
|
||||
const history = await note?.properties.getSessionHistory();
|
||||
const preview = await history?.at(1)?.open();
|
||||
if (type === "locked") await preview?.unlock(PASSWORD);
|
||||
@@ -83,3 +115,44 @@ for (const type of sessionTypes) {
|
||||
expect(await notes.editor.getContent("text")).toBe(contents[1]);
|
||||
});
|
||||
}
|
||||
// test("editing locked note should create locked history sessions", async ({
|
||||
// page
|
||||
// }) => {
|
||||
// const { note } = await createLockedSession(page);
|
||||
|
||||
// const history = await note?.properties.getSessionHistory();
|
||||
// expect(history).toHaveLength(2);
|
||||
// for (const item of history || []) {
|
||||
// expect(await item.isLocked()).toBeTruthy();
|
||||
// }
|
||||
// });
|
||||
|
||||
// test("switching locked sessions should change editor content", async ({
|
||||
// page
|
||||
// }) => {
|
||||
// const { note, notes, contents } = await createLockedSession(page);
|
||||
|
||||
// const history = await note?.properties.getSessionHistory();
|
||||
// await history?.at(1)?.previewLocked(PASSWORD);
|
||||
// await notes.editor.waitForLoading(NOTE.title, contents[1]);
|
||||
// const content1 = await notes.editor.getContent("text");
|
||||
|
||||
// await history?.at(0)?.previewLocked(PASSWORD);
|
||||
// await notes.editor.waitForLoading(NOTE.title, contents[0]);
|
||||
// const content0 = await notes.editor.getContent("text");
|
||||
|
||||
// expect(content1).toBe(contents[1]);
|
||||
// expect(content0).toBe(contents[0]);
|
||||
// });
|
||||
|
||||
// test("restore a locked session", async ({ page }) => {
|
||||
// const { note, notes, contents } = await createLockedSession(page);
|
||||
// const history = await note?.properties.getSessionHistory();
|
||||
// await history?.at(1)?.previewLocked(PASSWORD);
|
||||
// await notes.editor.waitForLoading(NOTE.title, contents[1]);
|
||||
|
||||
// await notes.editor.restoreSession();
|
||||
|
||||
// const content = await notes.editor.getContent("text");
|
||||
// expect(content).toBe(contents[1]);
|
||||
// });
|
||||
|
||||
@@ -20,9 +20,6 @@ import { test, Browser, expect } from "@playwright/test";
|
||||
import { AppModel } from "./models/app.model";
|
||||
import { USER } from "./utils";
|
||||
|
||||
// run this test file sequentially
|
||||
test.describe.configure({ mode: "serial" });
|
||||
|
||||
async function createDevice(browser: Browser) {
|
||||
// Create two isolated browser contexts
|
||||
const context = await browser.newContext();
|
||||
@@ -51,13 +48,13 @@ async function actAndSync<T>(
|
||||
return results.slice(0, actions.length) as T[];
|
||||
}
|
||||
|
||||
test(`content edits in a note opened on 2 devices in multiple tabs should sync in real-time`, async ({
|
||||
const NOTE = {
|
||||
title: `Note ${makeid(20)}`
|
||||
};
|
||||
|
||||
test(`edits in a note opened on 2 devices should sync in real-time`, async ({
|
||||
browser
|
||||
}, info) => {
|
||||
const NOTE = {
|
||||
title: `Note ${makeid(20)}`
|
||||
};
|
||||
|
||||
info.setTimeout(70 * 1000);
|
||||
const newContent = makeid(24).repeat(2);
|
||||
|
||||
@@ -74,7 +71,6 @@ test(`content edits in a note opened on 2 devices in multiple tabs should sync i
|
||||
)[0];
|
||||
const noteA = await notesA.findNote(NOTE);
|
||||
await Promise.all([noteA, noteB].map((note) => note?.openNote()));
|
||||
await noteA?.openNote(true);
|
||||
|
||||
if ((await notesB.editor.getContent("text")) !== "")
|
||||
await actAndSync([deviceA, deviceB], notesB.editor.clear());
|
||||
@@ -86,52 +82,6 @@ test(`content edits in a note opened on 2 devices in multiple tabs should sync i
|
||||
expect(noteB).toBeDefined();
|
||||
await expect(notesA.editor.content).toHaveText(newContent);
|
||||
await expect(notesB.editor.content).toHaveText(newContent);
|
||||
const tabsA = await notesA.editor.getTabs();
|
||||
await tabsA[0].click();
|
||||
await expect(notesA.editor.content).toHaveText(newContent);
|
||||
|
||||
await (await deviceA.goToSettings())?.logout();
|
||||
await (await deviceB.goToSettings())?.logout();
|
||||
});
|
||||
|
||||
test(`title edits in a note opened on 2 devices in multiple tabs should sync in real-time`, async ({
|
||||
browser
|
||||
}, info) => {
|
||||
const NOTE = {
|
||||
title: `Note ${makeid(20)}`
|
||||
};
|
||||
|
||||
info.setTimeout(70 * 1000);
|
||||
const newContent = makeid(24).repeat(2);
|
||||
|
||||
const [deviceA, deviceB] = await Promise.all([
|
||||
createDevice(browser),
|
||||
createDevice(browser)
|
||||
]);
|
||||
|
||||
const [notesA, notesB] = await Promise.all(
|
||||
[deviceA, deviceB].map((d) => d.goToNotes())
|
||||
);
|
||||
const noteB = (
|
||||
await actAndSync([deviceA, deviceB], notesB.createNote(NOTE))
|
||||
)[0];
|
||||
const noteA = await notesA.findNote(NOTE);
|
||||
await Promise.all([noteA, noteB].map((note) => note?.openNote()));
|
||||
await noteA?.openNote(true);
|
||||
|
||||
if ((await notesB.editor.getContent("text")) !== "")
|
||||
await actAndSync([deviceA, deviceB], notesB.editor.clear());
|
||||
await expect(notesA.editor.content).toBeEmpty();
|
||||
await expect(notesB.editor.content).toBeEmpty();
|
||||
await actAndSync([deviceA, deviceB], notesB.editor.setTitle(newContent));
|
||||
|
||||
expect(noteA).toBeDefined();
|
||||
expect(noteB).toBeDefined();
|
||||
expect(await notesA.editor.getTitle()).toBe(newContent);
|
||||
expect(await notesB.editor.getTitle()).toBe(newContent);
|
||||
const tabsA = await notesA.editor.getTabs();
|
||||
await tabsA[0].click();
|
||||
expect(await notesA.editor.getTitle()).toBe(newContent);
|
||||
|
||||
await (await deviceA.goToSettings())?.logout();
|
||||
await (await deviceB.goToSettings())?.logout();
|
||||
|
||||
@@ -1,256 +0,0 @@
|
||||
/*
|
||||
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 { test, expect } from "@playwright/test";
|
||||
import { AppModel } from "./models/app.model";
|
||||
import { createHistorySession } from "./utils";
|
||||
|
||||
test("notes should open in the same tab", async ({ page }) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
await notes.createNote({ title: "Note 1" });
|
||||
await notes.createNote({ title: "Note 2" });
|
||||
await notes.createNote({ title: "Note 3" });
|
||||
await page.reload();
|
||||
|
||||
const note = await notes.findNote({ title: "Note 2" });
|
||||
await note?.click();
|
||||
|
||||
const tabs = await notes.editor.getTabs();
|
||||
expect(tabs.length).toBe(1);
|
||||
});
|
||||
|
||||
test("new note should open in the same tab", async ({ page }) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
await notes.createNote({ title: "Note 1" });
|
||||
|
||||
await notes.newNote();
|
||||
|
||||
const tabs = await notes.editor.getTabs();
|
||||
expect(tabs.length).toBe(1);
|
||||
});
|
||||
|
||||
test("open note in new tab (using context menu)", async ({ page }) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
await notes.createNote({ title: "Note 1" });
|
||||
await notes.createNote({ title: "Note 2" });
|
||||
await notes.createNote({ title: "Note 3" });
|
||||
|
||||
const note = await notes.findNote({ title: "Note 2" });
|
||||
await note?.contextMenu.openInNewTab();
|
||||
await notes.editor.waitForLoading();
|
||||
|
||||
const tabs = await notes.editor.getTabs();
|
||||
expect(tabs.length).toBe(2);
|
||||
expect(await tabs[1].title()).toBe("Note 2");
|
||||
});
|
||||
|
||||
test("open note in new tab (using middle click)", async ({ page }) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
await notes.createNote({ title: "Note 1" });
|
||||
await notes.createNote({ title: "Note 2" });
|
||||
await notes.createNote({ title: "Note 3" });
|
||||
|
||||
const note = await notes.findNote({ title: "Note 2" });
|
||||
await note?.click({ middleClick: true });
|
||||
await notes.editor.waitForLoading();
|
||||
|
||||
const tabs = await notes.editor.getTabs();
|
||||
expect(tabs.length).toBe(2);
|
||||
expect(await tabs[1].title()).toBe("Note 2");
|
||||
});
|
||||
|
||||
test("go back should open previous note", async ({ page }) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const tabs = await notes.editor.getTabs();
|
||||
await notes.createNote({ title: "Note 1" });
|
||||
await notes.createNote({ title: "Note 2" });
|
||||
await notes.createNote({ title: "Note 3" });
|
||||
|
||||
await notes.editor.goBack();
|
||||
expect(await tabs[0].title()).toBe("Note 2");
|
||||
await notes.editor.goBack();
|
||||
expect(await tabs[0].title()).toBe("Note 1");
|
||||
});
|
||||
|
||||
test("go forward should open next note", async ({ page }) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const tabs = await notes.editor.getTabs();
|
||||
await notes.createNote({ title: "Note 1" });
|
||||
await notes.createNote({ title: "Note 2" });
|
||||
await notes.createNote({ title: "Note 3" });
|
||||
|
||||
await notes.editor.goBack();
|
||||
await notes.editor.goBack();
|
||||
await notes.editor.goForward();
|
||||
expect(await tabs[0].title()).toBe("Note 2");
|
||||
await notes.editor.goForward();
|
||||
expect(await tabs[0].title()).toBe("Note 3");
|
||||
});
|
||||
|
||||
test("new tab button should open a new tab", async ({ page }) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
await notes.createNote({ title: "Note 1" });
|
||||
|
||||
await notes.editor.newTab();
|
||||
|
||||
const tabs = await notes.editor.getTabs();
|
||||
expect(await tabs[0].title()).toBe("Note 1");
|
||||
expect(await tabs[1].title()).toBe("Untitled");
|
||||
});
|
||||
|
||||
test("changes in a note opened in multiple tabs should sync", async ({
|
||||
page
|
||||
}) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const note = await notes.createNote({ title: "Note 1" });
|
||||
await note?.contextMenu.openInNewTab();
|
||||
|
||||
await notes.editor.editAndWait(async () => {
|
||||
await notes.editor.setContent("This change should sync.");
|
||||
});
|
||||
|
||||
const tabs = await notes.editor.getTabs();
|
||||
await tabs[0].click();
|
||||
expect(await notes.editor.getContent("text")).toBe(
|
||||
"This change should sync."
|
||||
);
|
||||
});
|
||||
|
||||
test("open same note in 2 tabs and refresh page", async ({ page }) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const note = await notes.createNote({
|
||||
title: "Note 1",
|
||||
content: "Some edits."
|
||||
});
|
||||
await note?.contextMenu.openInNewTab();
|
||||
await notes.editor.waitForLoading();
|
||||
|
||||
await page.reload();
|
||||
await notes.waitForList();
|
||||
|
||||
const tabs = await notes.editor.getTabs();
|
||||
await tabs[0].click();
|
||||
await notes.editor.waitForLoading();
|
||||
expect(await notes.editor.getContent("text")).toBe("Some edits.");
|
||||
});
|
||||
|
||||
test("reloading with a note diff open in a tab", async ({ page }) => {
|
||||
const { note, contents } = await createHistorySession(page);
|
||||
const history = await note?.properties.getSessionHistory();
|
||||
const preview = await history?.[0].open();
|
||||
await preview!.firstEditor.waitFor({ state: "visible" });
|
||||
|
||||
await page.reload();
|
||||
await preview!.firstEditor.waitFor({ state: "visible" });
|
||||
|
||||
await expect(preview!.firstEditor.locator(".ProseMirror")).toHaveText(
|
||||
contents[0]
|
||||
);
|
||||
await expect(preview!.secondEditor.locator(".ProseMirror")).toHaveText(
|
||||
contents[0]
|
||||
);
|
||||
});
|
||||
|
||||
test("navigate back and forth between normal and diff session", async ({
|
||||
page
|
||||
}) => {
|
||||
const { note, contents, notes } = await createHistorySession(page);
|
||||
const history = await note?.properties.getSessionHistory();
|
||||
const preview = await history?.[0].open();
|
||||
await preview!.firstEditor.waitFor({ state: "visible" });
|
||||
|
||||
await notes.editor.goBack();
|
||||
await preview!.firstEditor.waitFor({ state: "hidden" });
|
||||
|
||||
expect(await notes.editor.getContent("text")).toBe(contents[0]);
|
||||
|
||||
await notes.editor.goForward();
|
||||
await preview!.firstEditor.waitFor({ state: "visible" });
|
||||
|
||||
await expect(preview!.firstEditor.locator(".ProseMirror")).toHaveText(
|
||||
contents[0]
|
||||
);
|
||||
await expect(preview!.secondEditor.locator(".ProseMirror")).toHaveText(
|
||||
contents[0]
|
||||
);
|
||||
});
|
||||
|
||||
test("clicking on a note that's already opened in another tab should focus the tab", async ({
|
||||
page
|
||||
}) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const note = await notes.createNote({
|
||||
title: "Note 1"
|
||||
});
|
||||
await note?.contextMenu.openInNewTab();
|
||||
await notes.editor.waitForLoading();
|
||||
await notes.createNote({
|
||||
title: "Note 2"
|
||||
});
|
||||
|
||||
await note?.openNote();
|
||||
|
||||
const tabs = await notes.editor.getTabs();
|
||||
expect(await tabs[0].isActive()).toBe(true);
|
||||
});
|
||||
|
||||
test("open a note in 2 tabs then open another note and navigate back", async ({
|
||||
page
|
||||
}) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const note = await notes.createNote({
|
||||
title: "Note 1"
|
||||
});
|
||||
await note?.contextMenu.openInNewTab();
|
||||
await notes.editor.waitForLoading();
|
||||
await notes.createNote({
|
||||
title: "Note 2"
|
||||
});
|
||||
|
||||
await notes.editor.goBack();
|
||||
|
||||
const tabs = await notes.editor.getTabs();
|
||||
expect(await tabs[1].isActive()).toBe(true);
|
||||
expect(await tabs[1].title()).toBe("Note 1");
|
||||
});
|
||||
|
||||
test.skip("TODO: open a locked note, switch to another note and navigate back", () => {});
|
||||
test.skip("TODO: open a locked note, switch to another note, unlock the note and navigate back", () => {});
|
||||
@@ -28,7 +28,6 @@ import {
|
||||
SortByOptions
|
||||
} from "../models/types";
|
||||
import { tmpdir } from "os";
|
||||
import { AppModel } from "../models/app.model";
|
||||
|
||||
type Note = {
|
||||
title: string;
|
||||
@@ -75,10 +74,7 @@ const PASSWORD = "123abc123abc";
|
||||
|
||||
const APP_LOCK_PASSWORD = "lockapporelse🔪";
|
||||
|
||||
function getTestId(
|
||||
id: string,
|
||||
variant: "data-test-id" | "data-testid" = "data-test-id"
|
||||
) {
|
||||
function getTestId(id: string, variant: "data-test-id" | "data-testid" = "data-test-id") {
|
||||
return `[${variant}="${id}"]`;
|
||||
}
|
||||
|
||||
@@ -157,41 +153,6 @@ const groupByOptions: GroupByOptions[] = [
|
||||
"week"
|
||||
];
|
||||
|
||||
export async function createHistorySession(page: Page, locked = false) {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
let note = await notes.createNote(NOTE);
|
||||
|
||||
if (locked) {
|
||||
await note?.contextMenu.lock(PASSWORD);
|
||||
await note?.openLockedNote(PASSWORD);
|
||||
}
|
||||
|
||||
const edits = ["Some edited text.", "Some more edited text."];
|
||||
for (const edit of edits) {
|
||||
await notes.editor.setContent(edit);
|
||||
|
||||
await page.waitForTimeout(600);
|
||||
|
||||
await page.reload().catch(console.error);
|
||||
await notes.waitForItem(NOTE.title);
|
||||
note = await notes.findNote(NOTE);
|
||||
locked ? await note?.openLockedNote(PASSWORD) : await note?.openNote();
|
||||
}
|
||||
const contents = [
|
||||
`${NOTE.content}${edits[0]}${edits[1]}`,
|
||||
`${NOTE.content}${edits[0]}`
|
||||
];
|
||||
|
||||
return {
|
||||
note,
|
||||
notes,
|
||||
app,
|
||||
contents
|
||||
};
|
||||
}
|
||||
|
||||
export {
|
||||
USER,
|
||||
NOTE,
|
||||
|
||||
@@ -18,16 +18,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import hotkeys from "hotkeys-js";
|
||||
import { GlobalKeyboard } from "../utils/keyboard";
|
||||
import { useEditorStore } from "../stores/editor-store";
|
||||
import { useStore as useSearchStore } from "../stores/search-store";
|
||||
import { useEditorManager } from "../components/editor/manager";
|
||||
|
||||
function isInEditor(e: KeyboardEvent) {
|
||||
return (
|
||||
e.target instanceof HTMLElement && !!e.target?.closest(".editor-container")
|
||||
);
|
||||
}
|
||||
|
||||
const KEYMAP = [
|
||||
// {
|
||||
// keys: ["command+n", "ctrl+n", "command+alt+n", "ctrl+alt+n"],
|
||||
@@ -59,34 +54,15 @@ const KEYMAP = [
|
||||
// });
|
||||
// },
|
||||
// },
|
||||
{
|
||||
keys: [
|
||||
"command+option+right",
|
||||
"ctrl+alt+right",
|
||||
"command+option+shift+right",
|
||||
"ctrl+alt+shift+right"
|
||||
],
|
||||
description: "Go to next tab",
|
||||
global: false,
|
||||
action: () => useEditorStore.getState().focusNextTab()
|
||||
},
|
||||
{
|
||||
keys: [
|
||||
"command+option+left",
|
||||
"ctrl+alt+left",
|
||||
"command+option+shift+left",
|
||||
"ctrl+alt+shift+left"
|
||||
],
|
||||
description: "Go to next tab",
|
||||
global: false,
|
||||
action: () => useEditorStore.getState().focusPreviousTab()
|
||||
},
|
||||
{
|
||||
keys: ["command+f", "ctrl+f"],
|
||||
description: "Search all notes",
|
||||
global: false,
|
||||
action: (e: KeyboardEvent) => {
|
||||
if (isInEditor(e)) {
|
||||
const isInEditor =
|
||||
e.target instanceof HTMLElement &&
|
||||
!!e.target?.closest(".editor-container");
|
||||
if (isInEditor) {
|
||||
const activeSession = useEditorStore.getState().getActiveSession();
|
||||
if (activeSession?.type === "readonly") {
|
||||
e.preventDefault();
|
||||
@@ -173,9 +149,14 @@ export function registerKeyMap() {
|
||||
};
|
||||
|
||||
KEYMAP.forEach((key) => {
|
||||
hotkeys(key.keys.join(","), (e) => {
|
||||
e.preventDefault();
|
||||
key.action?.(e);
|
||||
});
|
||||
hotkeys(
|
||||
key.keys.join(","),
|
||||
{
|
||||
element: key.global
|
||||
? (GlobalKeyboard as unknown as HTMLElement)
|
||||
: document.body
|
||||
},
|
||||
key.action
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,13 +17,7 @@ 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 {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState
|
||||
} from "react";
|
||||
import { useCallback, useLayoutEffect, useRef, useState } from "react";
|
||||
import { Flex, Text, Button } from "@theme-ui/components";
|
||||
import { Copy, Restore } from "../icons";
|
||||
import ContentToggle from "./content-toggle";
|
||||
@@ -31,7 +25,6 @@ import { store as notesStore } from "../../stores/note-store";
|
||||
import { db } from "../../common/db";
|
||||
import {
|
||||
ConflictedEditorSession,
|
||||
DiffEditorSession,
|
||||
useEditorStore
|
||||
} from "../../stores/editor-store";
|
||||
import { ScrollSync, ScrollSyncPane } from "react-scroll-sync";
|
||||
@@ -42,7 +35,7 @@ import { getFormattedDate } from "@notesnook/common";
|
||||
import { diff } from "diffblazer";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type DiffViewerProps = { session: ConflictedEditorSession | DiffEditorSession };
|
||||
type DiffViewerProps = { session: ConflictedEditorSession };
|
||||
function DiffViewer(props: DiffViewerProps) {
|
||||
const { session } = props;
|
||||
|
||||
@@ -51,11 +44,7 @@ function DiffViewer(props: DiffViewerProps) {
|
||||
const [conflictedContent, setConflictedContent] = useState(
|
||||
content?.conflicted
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setContent(session.content);
|
||||
setConflictedContent(session.content?.conflicted);
|
||||
}, [session.content]);
|
||||
const root = useRef<HTMLDivElement>(null);
|
||||
|
||||
const onResolveContent = useCallback(
|
||||
(saveCopy: boolean) => {
|
||||
@@ -82,13 +71,19 @@ function DiffViewer(props: DiffViewerProps) {
|
||||
});
|
||||
}, [session]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const element = root.current;
|
||||
element?.classList.add("active");
|
||||
return () => {
|
||||
element?.classList.remove("active");
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!conflictedContent || !content) return null;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
ref={(el) => {
|
||||
if (el) el.classList.add("active");
|
||||
}}
|
||||
ref={root}
|
||||
className="diffviewer"
|
||||
data-test-id="diff-viewer"
|
||||
sx={{
|
||||
@@ -117,15 +112,12 @@ function DiffViewer(props: DiffViewerProps) {
|
||||
variant="secondary"
|
||||
data-test-id="restore-session"
|
||||
onClick={async () => {
|
||||
const { closeTabs, openSession, getSessionsForNote } =
|
||||
const { closeSessions, openSession } =
|
||||
useEditorStore.getState();
|
||||
|
||||
await db.noteHistory.restore(session.historySessionId);
|
||||
await db.noteHistory.restore(session.id);
|
||||
|
||||
closeTabs(
|
||||
session.id,
|
||||
...getSessionsForNote(session.note.id).map((s) => s.id)
|
||||
);
|
||||
closeSessions(session.id, session.note.id);
|
||||
|
||||
await notesStore.refresh();
|
||||
await openSession(session.note.id, { force: true });
|
||||
@@ -143,11 +135,12 @@ function DiffViewer(props: DiffViewerProps) {
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={async () => {
|
||||
const { closeTabs, openSession } = useEditorStore.getState();
|
||||
const { closeSessions, openSession } =
|
||||
useEditorStore.getState();
|
||||
|
||||
const noteId = await createCopy(session.note, content);
|
||||
|
||||
closeTabs(session.id);
|
||||
closeSessions(session.id);
|
||||
|
||||
await notesStore.refresh();
|
||||
await openSession(noteId);
|
||||
|
||||
@@ -18,10 +18,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Button, Flex, Text } from "@theme-ui/components";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
Cross,
|
||||
EditorFullWidth,
|
||||
EditorNormalWidth,
|
||||
@@ -29,12 +28,10 @@ import {
|
||||
FocusMode,
|
||||
Fullscreen,
|
||||
Lock,
|
||||
NewTab,
|
||||
NormalMode,
|
||||
Note,
|
||||
NoteRemove,
|
||||
Pin,
|
||||
Plus,
|
||||
Properties,
|
||||
Publish,
|
||||
Published,
|
||||
@@ -89,10 +86,10 @@ import { isMac } from "../../utils/platform";
|
||||
export function EditorActionBar() {
|
||||
const { isMaximized, isFullscreen, hasNativeWindowControls } =
|
||||
useWindowControls();
|
||||
const editorMargins = useEditorStore((store) => store.editorMargins);
|
||||
const isFocusMode = useAppStore((store) => store.isFocusMode);
|
||||
const activeTab = useEditorStore((store) => store.getActiveTab());
|
||||
const activeSession = useEditorStore((store) =>
|
||||
activeTab ? store.getSession(activeTab.sessionId) : undefined
|
||||
store.activeSessionId ? store.getSession(store.activeSessionId) : undefined
|
||||
);
|
||||
const editorManager = useEditorManager((store) =>
|
||||
activeSession?.id ? store.editors[activeSession?.id] : undefined
|
||||
@@ -100,19 +97,11 @@ export function EditorActionBar() {
|
||||
const isLoggedIn = useUserStore((store) => store.isLoggedIn);
|
||||
const monographs = useMonographStore((store) => store.monographs);
|
||||
const isNotePublished =
|
||||
activeSession &&
|
||||
"note" in activeSession &&
|
||||
db.monographs.isPublished(activeSession.note.id);
|
||||
activeSession && db.monographs.isPublished(activeSession.id);
|
||||
const isMobile = useMobile();
|
||||
const isTablet = useTablet();
|
||||
|
||||
const tools = [
|
||||
{
|
||||
title: strings.newTab(),
|
||||
icon: NewTab,
|
||||
enabled: true,
|
||||
onClick: () => useEditorStore.getState().addTab()
|
||||
},
|
||||
{
|
||||
title: strings.undo(),
|
||||
icon: Undo,
|
||||
@@ -139,6 +128,44 @@ export function EditorActionBar() {
|
||||
activeSession.type === "readonly") &&
|
||||
showPublishView(activeSession.note, "top")
|
||||
},
|
||||
{
|
||||
title: editorMargins
|
||||
? strings.disableEditorMargins()
|
||||
: strings.enableEditorMargins(),
|
||||
icon: editorMargins ? EditorNormalWidth : EditorFullWidth,
|
||||
enabled: true,
|
||||
hideOnMobile: true,
|
||||
onClick: () => useEditorStore.getState().toggleEditorMargins()
|
||||
},
|
||||
{
|
||||
title: isFullscreen
|
||||
? strings.exitFullScreen()
|
||||
: strings.enterFullScreen(),
|
||||
icon: isFullscreen ? ExitFullscreen : Fullscreen,
|
||||
enabled: true,
|
||||
hidden: !isFocusMode,
|
||||
hideOnMobile: true,
|
||||
onClick: () => {
|
||||
if (isFullscreen) {
|
||||
exitFullscreen();
|
||||
} else {
|
||||
enterFullscreen(document.documentElement);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: isFocusMode ? strings.normalMode() : strings.focusMode(),
|
||||
icon: isFocusMode ? FocusMode : NormalMode,
|
||||
enabled: true,
|
||||
hideOnMobile: true,
|
||||
onClick: () => {
|
||||
useAppStore.getState().toggleFocusMode();
|
||||
if (document.fullscreenElement) exitFullscreen();
|
||||
const id = useEditorStore.getState().activeSessionId;
|
||||
const editor = id && useEditorManager.getState().getEditor(id);
|
||||
if (editor) editor.editor?.focus();
|
||||
}
|
||||
},
|
||||
{
|
||||
title: strings.toc(),
|
||||
icon: TableOfContents,
|
||||
@@ -211,10 +238,7 @@ export function EditorActionBar() {
|
||||
mr:
|
||||
hasNativeWindowControls && !isMac() && !isMobile && !isTablet
|
||||
? `calc(100vw - env(titlebar-area-width))`
|
||||
: 1,
|
||||
pl: 1,
|
||||
borderLeft: "1px solid var(--border)",
|
||||
flexShrink: 0
|
||||
: 0
|
||||
}}
|
||||
>
|
||||
{tools.map((tool) => (
|
||||
@@ -225,13 +249,14 @@ export function EditorActionBar() {
|
||||
title={tool.title}
|
||||
key={tool.title}
|
||||
sx={{
|
||||
p: 1,
|
||||
height: "100%",
|
||||
alignItems: "center",
|
||||
bg: "transparent",
|
||||
display: [
|
||||
"hideOnMobile" in tool && tool.hideOnMobile ? "none" : "flex",
|
||||
tool.hidden ? "none" : "flex"
|
||||
],
|
||||
borderRadius: 0,
|
||||
flexShrink: 0,
|
||||
"&:hover svg path": {
|
||||
fill:
|
||||
@@ -242,7 +267,7 @@ export function EditorActionBar() {
|
||||
}}
|
||||
onClick={tool.onClick}
|
||||
>
|
||||
<tool.icon size={16} />
|
||||
<tool.icon size={18} />
|
||||
</Button>
|
||||
))}
|
||||
</Flex>
|
||||
@@ -250,209 +275,183 @@ export function EditorActionBar() {
|
||||
);
|
||||
}
|
||||
|
||||
const TabStrip = React.memo(function TabStrip() {
|
||||
useEditorStore((store) => store.getActiveSession()); // otherwise the tab title won't update on opening a note
|
||||
const tabs = useEditorStore((store) => store.tabs);
|
||||
const currentTab = useEditorStore((store) => store.activeTabId);
|
||||
const canGoBack = useEditorStore((store) => store.canGoBack);
|
||||
const canGoForward = useEditorStore((store) => store.canGoForward);
|
||||
function TabStrip() {
|
||||
const sessions = useEditorStore((store) => store.sessions);
|
||||
const activeSessionId = useEditorStore((store) => store.activeSessionId);
|
||||
|
||||
return (
|
||||
<Flex sx={{ flex: 1 }}>
|
||||
<ScrollContainer
|
||||
className="tabsScroll"
|
||||
suppressScrollY
|
||||
style={{ flex: 1, height: TITLE_BAR_HEIGHT }}
|
||||
trackStyle={() => ({
|
||||
backgroundColor: "transparent",
|
||||
"--ms-track-size": "6px"
|
||||
})}
|
||||
thumbStyle={() => ({ height: 3 })}
|
||||
onWheel={(e) => {
|
||||
const scrollcontainer = document.querySelector(".tabsScroll");
|
||||
if (!scrollcontainer) return;
|
||||
if (e.deltaY > 0) scrollcontainer.scrollLeft += 100;
|
||||
else if (e.deltaY < 0) scrollcontainer.scrollLeft -= 100;
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
px: 1,
|
||||
borderRight: "1px solid var(--border)",
|
||||
alignItems: "center",
|
||||
flexShrink: 0
|
||||
flex: 1,
|
||||
height: "100%"
|
||||
}}
|
||||
onDoubleClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Button
|
||||
disabled={!canGoBack}
|
||||
onClick={() => useEditorStore.getState().goBack()}
|
||||
variant="secondary"
|
||||
sx={{ p: 1, bg: "transparent" }}
|
||||
data-test-id="go-back"
|
||||
>
|
||||
<ArrowLeft size={16} />
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!canGoForward}
|
||||
onClick={() => useEditorStore.getState().goForward()}
|
||||
variant="secondary"
|
||||
sx={{ p: 1, bg: "transparent" }}
|
||||
data-test-id="go-forward"
|
||||
>
|
||||
<ArrowRight size={16} />
|
||||
</Button>
|
||||
</Flex>
|
||||
<ScrollContainer
|
||||
className="tabsScroll"
|
||||
suppressScrollY
|
||||
style={{ flex: 1, height: TITLE_BAR_HEIGHT }}
|
||||
trackStyle={() => ({
|
||||
backgroundColor: "transparent",
|
||||
"--ms-track-size": "6px"
|
||||
})}
|
||||
thumbStyle={() => ({ height: 3 })}
|
||||
onWheel={(e) => {
|
||||
const scrollcontainer = document.querySelector(".tabsScroll");
|
||||
if (!scrollcontainer) return;
|
||||
if (e.deltaY > 0) scrollcontainer.scrollLeft += 100;
|
||||
else if (e.deltaY < 0) scrollcontainer.scrollLeft -= 100;
|
||||
onDoubleClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
useEditorStore.getState().newSession();
|
||||
}}
|
||||
data-test-id="tabs"
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
flex: 1,
|
||||
height: "100%"
|
||||
<ReorderableList
|
||||
items={sessions}
|
||||
moveItem={(from, to) => {
|
||||
if (from === to) return;
|
||||
const sessions = useEditorStore.getState().sessions.slice();
|
||||
const isToPinned = sessions[to].pinned;
|
||||
const [fromTab] = sessions.splice(from, 1);
|
||||
|
||||
// if the tab where this tab is being dropped is pinned,
|
||||
// let's pin our tab too.
|
||||
if (isToPinned) {
|
||||
fromTab.pinned = true;
|
||||
fromTab.preview = false;
|
||||
}
|
||||
// unpin the tab if it is moved.
|
||||
else if (fromTab.pinned) fromTab.pinned = false;
|
||||
|
||||
sessions.splice(to, 0, fromTab);
|
||||
useEditorStore.setState({ sessions });
|
||||
}}
|
||||
onDoubleClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
useEditorStore.getState().addTab();
|
||||
}}
|
||||
data-test-id="tabs"
|
||||
>
|
||||
<ReorderableList
|
||||
items={tabs}
|
||||
moveItem={(from, to) => {
|
||||
if (from === to) return;
|
||||
const tabs = useEditorStore.getState().tabs.slice();
|
||||
const isToPinned = tabs[to].pinned;
|
||||
const [fromTab] = tabs.splice(from, 1);
|
||||
|
||||
// if the tab where this tab is being dropped is pinned,
|
||||
// let's pin our tab too.
|
||||
if (isToPinned) {
|
||||
fromTab.pinned = true;
|
||||
}
|
||||
// unpin the tab if it is moved.
|
||||
else if (fromTab.pinned) fromTab.pinned = false;
|
||||
|
||||
tabs.splice(to, 0, fromTab);
|
||||
useEditorStore.setState({ tabs });
|
||||
}}
|
||||
renderItem={({ item: tab, index: i }) => {
|
||||
const session = useEditorStore
|
||||
.getState()
|
||||
.getSession(tab.sessionId);
|
||||
if (!session) return null;
|
||||
|
||||
const isUnsaved =
|
||||
session.type === "default" &&
|
||||
session.saveState === SaveState.NotSaved;
|
||||
|
||||
return (
|
||||
<Tab
|
||||
id={tab.id}
|
||||
key={tab.sessionId}
|
||||
title={
|
||||
session.title ||
|
||||
("note" in session
|
||||
? session.note.title
|
||||
: strings.untitled())
|
||||
renderItem={({ item: session, index: i }) => {
|
||||
const isUnsaved =
|
||||
session.type === "default" &&
|
||||
session.saveState === SaveState.NotSaved;
|
||||
return (
|
||||
<Tab
|
||||
id={session.id}
|
||||
key={session.id}
|
||||
title={
|
||||
session.title ||
|
||||
("note" in session ? session.note.title : "Untitled")
|
||||
}
|
||||
isUnsaved={isUnsaved}
|
||||
isTemporary={!!session.preview}
|
||||
isActive={session.id === activeSessionId}
|
||||
isPinned={!!session.pinned}
|
||||
isLocked={isLockedSession(session)}
|
||||
type={session.type}
|
||||
onKeepOpen={() =>
|
||||
useEditorStore
|
||||
.getState()
|
||||
.updateSession(
|
||||
session.id,
|
||||
[session.type],
|
||||
(s) => (s.preview = false)
|
||||
)
|
||||
}
|
||||
onFocus={() => {
|
||||
if (session.id !== activeSessionId) {
|
||||
useEditorStore.getState().openSession(session.id);
|
||||
}
|
||||
isUnsaved={isUnsaved}
|
||||
isActive={tab.id === currentTab}
|
||||
isPinned={!!tab.pinned}
|
||||
isLocked={isLockedSession(session)}
|
||||
type={session.type}
|
||||
onFocus={() => {
|
||||
if (tab.id !== currentTab) {
|
||||
useEditorStore.getState().activateSession(tab.sessionId);
|
||||
}
|
||||
}}
|
||||
onClose={() => useEditorStore.getState().closeTabs(tab.id)}
|
||||
onCloseAll={() =>
|
||||
useEditorStore
|
||||
.getState()
|
||||
.closeTabs(
|
||||
...tabs.filter((s) => !s.pinned).map((s) => s.id)
|
||||
)
|
||||
}
|
||||
onCloseOthers={() =>
|
||||
useEditorStore
|
||||
.getState()
|
||||
.closeTabs(
|
||||
...tabs
|
||||
.filter((s) => s.id !== tab.id && !s.pinned)
|
||||
.map((s) => s.id)
|
||||
)
|
||||
}
|
||||
onCloseToTheRight={() =>
|
||||
useEditorStore
|
||||
.getState()
|
||||
.closeTabs(
|
||||
...tabs
|
||||
.filter((s, index) => index > i && !s.pinned)
|
||||
.map((s) => s.id)
|
||||
)
|
||||
}
|
||||
onCloseToTheLeft={() =>
|
||||
useEditorStore
|
||||
.getState()
|
||||
.closeTabs(
|
||||
...tabs
|
||||
.filter((s, index) => index < i && !s.pinned)
|
||||
.map((s) => s.id)
|
||||
)
|
||||
}
|
||||
onRevealInList={
|
||||
"note" in session
|
||||
? () =>
|
||||
AppEventManager.publish(
|
||||
AppEvents.revealItemInList,
|
||||
session.note.id,
|
||||
true
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
onPin={() => {
|
||||
const tabs = useEditorStore.getState().tabs.slice();
|
||||
const index = tabs.findIndex((t) => t.id === tab.id);
|
||||
tabs[index].pinned = !tabs[index].pinned;
|
||||
tabs.sort((a, b) =>
|
||||
}}
|
||||
onClose={() =>
|
||||
useEditorStore.getState().closeSessions(session.id)
|
||||
}
|
||||
onCloseAll={() =>
|
||||
useEditorStore
|
||||
.getState()
|
||||
.closeSessions(
|
||||
...sessions.filter((s) => !s.pinned).map((s) => s.id)
|
||||
)
|
||||
}
|
||||
onCloseOthers={() =>
|
||||
useEditorStore
|
||||
.getState()
|
||||
.closeSessions(
|
||||
...sessions
|
||||
.filter((s) => s.id !== session.id && !s.pinned)
|
||||
.map((s) => s.id)
|
||||
)
|
||||
}
|
||||
onCloseToTheRight={() =>
|
||||
useEditorStore
|
||||
.getState()
|
||||
.closeSessions(
|
||||
...sessions
|
||||
.filter((s, index) => index > i && !s.pinned)
|
||||
.map((s) => s.id)
|
||||
)
|
||||
}
|
||||
onCloseToTheLeft={() =>
|
||||
useEditorStore
|
||||
.getState()
|
||||
.closeSessions(
|
||||
...sessions
|
||||
.filter((s, index) => index < i && !s.pinned)
|
||||
.map((s) => s.id)
|
||||
)
|
||||
}
|
||||
onRevealInList={() =>
|
||||
AppEventManager.publish(
|
||||
AppEvents.revealItemInList,
|
||||
"note" in session ? session.note.id : session.id,
|
||||
true
|
||||
)
|
||||
}
|
||||
onPin={() => {
|
||||
useEditorStore.setState((state) => {
|
||||
// preview tabs can never be pinned.
|
||||
if (!session.pinned) state.sessions[i].preview = false;
|
||||
state.sessions[i].pinned = !session.pinned;
|
||||
state.sessions.sort((a, b) =>
|
||||
a.pinned === b.pinned ? 0 : a.pinned ? -1 : 1
|
||||
);
|
||||
useEditorStore.setState({ tabs });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
</ScrollContainer>
|
||||
</Flex>
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
</ScrollContainer>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
type TabProps = {
|
||||
id: string;
|
||||
title: string;
|
||||
isActive: boolean;
|
||||
isTemporary: boolean;
|
||||
isPinned: boolean;
|
||||
isLocked: boolean;
|
||||
isUnsaved: boolean;
|
||||
type: SessionType;
|
||||
onKeepOpen: () => void;
|
||||
onFocus: () => void;
|
||||
onClose: () => void;
|
||||
onCloseOthers: () => void;
|
||||
onCloseToTheRight: () => void;
|
||||
onCloseToTheLeft: () => void;
|
||||
onCloseAll: () => void;
|
||||
onRevealInList: () => void;
|
||||
onPin: () => void;
|
||||
onRevealInList?: () => void;
|
||||
};
|
||||
function Tab(props: TabProps) {
|
||||
const {
|
||||
id,
|
||||
title,
|
||||
isActive,
|
||||
isTemporary,
|
||||
isPinned,
|
||||
isLocked,
|
||||
isUnsaved,
|
||||
type,
|
||||
onKeepOpen,
|
||||
onFocus,
|
||||
onClose,
|
||||
onCloseAll,
|
||||
@@ -494,14 +493,13 @@ function Tab(props: TabProps) {
|
||||
setNodeRef(el);
|
||||
activeTabRef.current = el;
|
||||
}}
|
||||
className={`tab${isActive || active?.id === id ? " active" : ""}`}
|
||||
className="tab"
|
||||
data-test-id={`tab-${id}`}
|
||||
sx={{
|
||||
height: "100%",
|
||||
cursor: "pointer",
|
||||
pl: 2,
|
||||
px: 2,
|
||||
borderRight: "1px solid var(--border)",
|
||||
":last-of-type": { borderRight: 0 },
|
||||
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
@@ -513,7 +511,7 @@ function Tab(props: TabProps) {
|
||||
flexShrink: 0,
|
||||
":hover": {
|
||||
"& .closeTabButton": {
|
||||
opacity: 1
|
||||
visibility: "visible"
|
||||
},
|
||||
bg: isActive ? "hover-selected" : "hover"
|
||||
}
|
||||
@@ -556,10 +554,16 @@ function Tab(props: TabProps) {
|
||||
type: "button",
|
||||
title: strings.revealInList(),
|
||||
key: "reveal-in-list",
|
||||
onClick: onRevealInList,
|
||||
isHidden: !onRevealInList
|
||||
onClick: onRevealInList
|
||||
},
|
||||
{ type: "separator", key: "sep" },
|
||||
{
|
||||
type: "button",
|
||||
key: "keep-open",
|
||||
title: strings.keepOpen(),
|
||||
onClick: onKeepOpen,
|
||||
isDisabled: !isTemporary
|
||||
},
|
||||
{ type: "separator", key: "sep2" },
|
||||
{
|
||||
type: "button",
|
||||
key: "pin",
|
||||
@@ -570,6 +574,10 @@ function Tab(props: TabProps) {
|
||||
}
|
||||
]);
|
||||
}}
|
||||
onDoubleClick={(e) => {
|
||||
e.stopPropagation();
|
||||
if (isTemporary) onKeepOpen();
|
||||
}}
|
||||
onAuxClick={(e) => {
|
||||
if (e.button == 1) onClose();
|
||||
}}
|
||||
@@ -577,25 +585,26 @@ function Tab(props: TabProps) {
|
||||
{...attributes}
|
||||
>
|
||||
<Flex
|
||||
mr={1}
|
||||
onMouseUp={(e) => {
|
||||
if (e.button == 0) onFocus();
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
data-test-id={`tab-icon${isUnsaved ? "-unsaved" : ""}`}
|
||||
size={14}
|
||||
size={16}
|
||||
color={
|
||||
isUnsaved ? "accent-error" : isActive ? "accent-selected" : "icon"
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
data-test-id="tab-title"
|
||||
variant="body"
|
||||
sx={{
|
||||
whiteSpace: "nowrap",
|
||||
textOverflow: "ellipsis",
|
||||
overflowX: "hidden",
|
||||
pointerEvents: "none",
|
||||
fontStyle: isTemporary ? "italic" : "normal",
|
||||
maxWidth: 120,
|
||||
color: isActive ? "paragraph-selected" : "paragraph"
|
||||
}}
|
||||
@@ -607,33 +616,34 @@ function Tab(props: TabProps) {
|
||||
{isPinned ? (
|
||||
<Pin
|
||||
sx={{
|
||||
":hover": { bg: "border" },
|
||||
borderRadius: "default",
|
||||
flexShrink: 0,
|
||||
ml: "small",
|
||||
mr: 1,
|
||||
"&:hover": {
|
||||
bg: "hover-secondary"
|
||||
}
|
||||
flexShrink: 0
|
||||
}}
|
||||
size={14}
|
||||
onClick={onPin}
|
||||
onMouseUp={(e) => {
|
||||
if (e.button == 0) {
|
||||
e.stopPropagation();
|
||||
onPin();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Cross
|
||||
sx={{
|
||||
visibility: isActive && active?.id !== id ? "visible" : "hidden",
|
||||
":hover": { bg: "border" },
|
||||
borderRadius: "default",
|
||||
flexShrink: 0,
|
||||
opacity: isActive || active?.id === id ? 1 : 0,
|
||||
ml: "small",
|
||||
mr: 1,
|
||||
"&:hover": {
|
||||
bg: "hover-secondary"
|
||||
flexShrink: 0
|
||||
}}
|
||||
onMouseUp={(e) => {
|
||||
if (e.button == 0) {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
onClick={onClose}
|
||||
className="closeTabButton"
|
||||
data-test-id={"tab-close-button"}
|
||||
size={14}
|
||||
size={16}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
@@ -642,7 +652,7 @@ function Tab(props: TabProps) {
|
||||
|
||||
type ReorderableListProps<T> = {
|
||||
items: T[];
|
||||
renderItem: (props: { item: T; index: number }) => JSX.Element | null;
|
||||
renderItem: (props: { item: T; index: number }) => JSX.Element;
|
||||
moveItem: (from: number, to: number) => void;
|
||||
};
|
||||
|
||||
@@ -704,3 +714,12 @@ function ReorderableList<T extends { id: string }>(
|
||||
</DndContext>
|
||||
);
|
||||
}
|
||||
|
||||
function enterFullscreen(elem: HTMLElement) {
|
||||
elem.requestFullscreen();
|
||||
}
|
||||
|
||||
function exitFullscreen() {
|
||||
if (!document.fullscreenElement) return;
|
||||
document.exitFullscreen();
|
||||
}
|
||||
|
||||
@@ -19,31 +19,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Button, Flex, Text } from "@theme-ui/components";
|
||||
import { SaveState, useEditorStore } from "../../stores/editor-store";
|
||||
import { useStore as useAppStore } from "../../stores/app-store";
|
||||
import {
|
||||
Loading,
|
||||
Saved,
|
||||
NotSaved,
|
||||
FocusMode,
|
||||
Plus,
|
||||
Minus,
|
||||
EditorNormalWidth,
|
||||
TableOfContents,
|
||||
ExitFullscreen,
|
||||
Fullscreen,
|
||||
EditorFullWidth,
|
||||
NormalMode
|
||||
} from "../icons";
|
||||
import {
|
||||
useEditorConfig,
|
||||
useNoteStatistics,
|
||||
useEditorManager
|
||||
} from "./manager";
|
||||
import { Loading, Saved, NotSaved } from "../icons";
|
||||
import { useEditorConfig, useNoteStatistics } from "./manager";
|
||||
import { getFormattedDate } from "@notesnook/common";
|
||||
import { MAX_AUTO_SAVEABLE_WORDS } from "./types";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { EDITOR_ZOOM } from "./common";
|
||||
import { useWindowControls } from "../../hooks/use-window-controls";
|
||||
|
||||
const SAVE_STATE_ICON_MAP = {
|
||||
"-1": NotSaved,
|
||||
@@ -52,59 +33,15 @@ const SAVE_STATE_ICON_MAP = {
|
||||
};
|
||||
|
||||
function EditorFooter() {
|
||||
const { isFullscreen } = useWindowControls();
|
||||
const { words } = useNoteStatistics();
|
||||
const session = useEditorStore((store) => store.getActiveSession());
|
||||
const { editorConfig, setEditorConfig } = useEditorConfig();
|
||||
const editorMargins = useEditorStore((store) => store.editorMargins);
|
||||
const isFocusMode = useAppStore((store) => store.isFocusMode);
|
||||
|
||||
if (!session) return null;
|
||||
|
||||
const saveState =
|
||||
session.type === "default" ? session.saveState : SaveState.NotSaved;
|
||||
const dateEdited = "note" in session ? session.note.dateEdited : 0;
|
||||
const SaveStateIcon = SAVE_STATE_ICON_MAP[saveState];
|
||||
const tools = [
|
||||
{
|
||||
title: editorMargins
|
||||
? strings.disableEditorMargins()
|
||||
: strings.enableEditorMargins(),
|
||||
icon: editorMargins ? EditorNormalWidth : EditorFullWidth,
|
||||
enabled: true,
|
||||
hideOnMobile: true,
|
||||
onClick: () => useEditorStore.getState().toggleEditorMargins()
|
||||
},
|
||||
{
|
||||
title: isFullscreen
|
||||
? strings.exitFullScreen()
|
||||
: strings.enterFullScreen(),
|
||||
icon: isFullscreen ? ExitFullscreen : Fullscreen,
|
||||
enabled: true,
|
||||
hidden: !isFocusMode,
|
||||
hideOnMobile: true,
|
||||
onClick: () => {
|
||||
if (isFullscreen) {
|
||||
exitFullscreen();
|
||||
} else {
|
||||
enterFullscreen(document.documentElement);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: isFocusMode ? strings.normalMode() : strings.focusMode(),
|
||||
icon: isFocusMode ? FocusMode : NormalMode,
|
||||
enabled: true,
|
||||
hideOnMobile: true,
|
||||
onClick: () => {
|
||||
useAppStore.getState().toggleFocusMode();
|
||||
if (document.fullscreenElement) exitFullscreen();
|
||||
const editor =
|
||||
session && useEditorManager.getState().getEditor(session.id);
|
||||
if (editor) editor.editor?.focus();
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<Flex sx={{ alignItems: "center", justifyContent: "center", gap: 2 }}>
|
||||
@@ -112,28 +49,9 @@ function EditorFooter() {
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
flex: 1,
|
||||
height: "100%"
|
||||
flex: 1
|
||||
}}
|
||||
>
|
||||
{tools.map((tool) => (
|
||||
<Button
|
||||
data-test-id={tool.title}
|
||||
disabled={!tool.enabled}
|
||||
title={tool.title}
|
||||
key={tool.title}
|
||||
onClick={tool.onClick}
|
||||
sx={{
|
||||
py: 0,
|
||||
px: 1,
|
||||
height: "100%",
|
||||
display: tool.hidden ? "none" : "block"
|
||||
}}
|
||||
variant="icon"
|
||||
>
|
||||
<tool.icon size={13} />
|
||||
</Button>
|
||||
))}
|
||||
<Button
|
||||
variant="icon"
|
||||
onClick={() =>
|
||||
@@ -145,11 +63,15 @@ function EditorFooter() {
|
||||
})
|
||||
}
|
||||
disabled={editorConfig.zoom <= EDITOR_ZOOM.MIN}
|
||||
sx={{ py: 0, px: 1, height: "100%" }}
|
||||
sx={{ py: 0, height: "100%" }}
|
||||
>
|
||||
<Minus size={13} />
|
||||
<b>-</b>
|
||||
</Button>
|
||||
<Text variant="subBody" sx={{ color: "paragraph" }}>
|
||||
<Text
|
||||
className="selectable"
|
||||
variant="subBody"
|
||||
sx={{ color: "paragraph" }}
|
||||
>
|
||||
{editorConfig.zoom}%
|
||||
</Text>
|
||||
<Button
|
||||
@@ -163,9 +85,9 @@ function EditorFooter() {
|
||||
})
|
||||
}
|
||||
disabled={editorConfig.zoom >= EDITOR_ZOOM.MAX}
|
||||
sx={{ py: 0, px: 1, height: "100%" }}
|
||||
sx={{ py: 0, height: "100%" }}
|
||||
>
|
||||
<Plus size={13} />
|
||||
<b>+</b>
|
||||
</Button>
|
||||
</Flex>
|
||||
{words.total > MAX_AUTO_SAVEABLE_WORDS ? (
|
||||
@@ -220,12 +142,3 @@ function EditorFooter() {
|
||||
);
|
||||
}
|
||||
export default EditorFooter;
|
||||
|
||||
function enterFullscreen(elem: HTMLElement) {
|
||||
elem.requestFullscreen();
|
||||
}
|
||||
|
||||
function exitFullscreen() {
|
||||
if (!document.fullscreenElement) return;
|
||||
document.exitFullscreen();
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ import { TITLE_BAR_HEIGHT } from "../title-bar";
|
||||
|
||||
const PDFPreview = React.lazy(() => import("../pdf-preview"));
|
||||
|
||||
const autoSaveToast = { show: true, hide: () => {} };
|
||||
const autoSaveToast = { show: true, hide: () => { } };
|
||||
|
||||
async function saveContent(
|
||||
noteId: string,
|
||||
@@ -116,15 +116,40 @@ async function saveContent(
|
||||
const deferredSave = debounceWithId(saveContent, 100);
|
||||
|
||||
export default function TabsView() {
|
||||
const tabs = useEditorStore((store) => store.tabs);
|
||||
const sessions = useEditorStore((store) => store.sessions);
|
||||
const documentPreview = useEditorStore((store) => store.documentPreview);
|
||||
const activeTab = useEditorStore((store) => store.getActiveTab());
|
||||
const activeSessionId = useEditorStore((store) => store.activeSessionId);
|
||||
const arePropertiesVisible = useEditorStore(
|
||||
(store) => store.arePropertiesVisible
|
||||
);
|
||||
const isTOCVisible = useEditorStore((store) => store.isTOCVisible);
|
||||
const [dropRef, overlayRef] = useDragOverlay();
|
||||
|
||||
useEffect(() => {
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.altKey &&
|
||||
event.key === "ArrowRight"
|
||||
) {
|
||||
event.preventDefault();
|
||||
useEditorStore.getState().openNextSession();
|
||||
}
|
||||
if (
|
||||
(event.ctrlKey || event.metaKey) &&
|
||||
event.altKey &&
|
||||
event.key === "ArrowLeft"
|
||||
) {
|
||||
event.preventDefault();
|
||||
useEditorStore.getState().openPreviousSession();
|
||||
}
|
||||
};
|
||||
document.body.addEventListener("keydown", onKeyDown);
|
||||
return () => {
|
||||
document.body.removeEventListener("keydown", onKeyDown);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
@@ -151,24 +176,17 @@ export default function TabsView() {
|
||||
>
|
||||
<SplitPane direction="vertical" autoSaveId={"editor-panels"}>
|
||||
<Pane id="editor-panel" className="editor-pane">
|
||||
{tabs.map((tab) => {
|
||||
const session = useEditorStore
|
||||
.getState()
|
||||
.getSession(tab.sessionId);
|
||||
if (!session) return null;
|
||||
return (
|
||||
<Freeze key={session.id} freeze={tab.id !== activeTab?.id}>
|
||||
{session.type === "locked" ? (
|
||||
<UnlockNoteView session={session} />
|
||||
) : session.type === "conflicted" ||
|
||||
session.type === "diff" ? (
|
||||
<DiffViewer session={session} />
|
||||
) : (
|
||||
<MemoizedEditorView session={session} />
|
||||
)}
|
||||
</Freeze>
|
||||
);
|
||||
})}
|
||||
{sessions.map((session) => (
|
||||
<Freeze key={session.id} freeze={session.id !== activeSessionId}>
|
||||
{session.type === "locked" ? (
|
||||
<UnlockNoteView session={session} />
|
||||
) : session.type === "conflicted" || session.type === "diff" ? (
|
||||
<DiffViewer session={session} />
|
||||
) : (
|
||||
<MemoizedEditorView session={session} />
|
||||
)}
|
||||
</Freeze>
|
||||
))}
|
||||
</Pane>
|
||||
|
||||
{documentPreview ? (
|
||||
@@ -208,15 +226,15 @@ export default function TabsView() {
|
||||
</Pane>
|
||||
) : null}
|
||||
|
||||
{isTOCVisible && activeTab ? (
|
||||
{isTOCVisible && activeSessionId ? (
|
||||
<Pane id="table-of-contents-pane" initialSize={300} minSize={300}>
|
||||
<TableOfContents sessionId={activeTab.sessionId} />
|
||||
<TableOfContents sessionId={activeSessionId} />
|
||||
</Pane>
|
||||
) : null}
|
||||
</SplitPane>
|
||||
<DropZone overlayRef={overlayRef} />
|
||||
{arePropertiesVisible && activeTab && (
|
||||
<Properties sessionId={activeTab.sessionId} />
|
||||
{arePropertiesVisible && activeSessionId && (
|
||||
<Properties sessionId={activeSessionId} />
|
||||
)}
|
||||
</ScopedThemeProvider>
|
||||
</>
|
||||
@@ -234,10 +252,10 @@ function EditorView({
|
||||
session
|
||||
}: {
|
||||
session:
|
||||
| DefaultEditorSession
|
||||
| NewEditorSession
|
||||
| ReadonlyEditorSession
|
||||
| DeletedEditorSession;
|
||||
| DefaultEditorSession
|
||||
| NewEditorSession
|
||||
| ReadonlyEditorSession
|
||||
| DeletedEditorSession;
|
||||
}) {
|
||||
const lastChangedTime = useRef<number>(0);
|
||||
const root = useRef<HTMLDivElement>(null);
|
||||
@@ -276,7 +294,6 @@ function EditorView({
|
||||
editor.updateContent(result.data);
|
||||
} else if (isNote && session.note.title !== item.title) {
|
||||
AppEventManager.publish(AppEvents.changeNoteTitle, {
|
||||
sessionId: session.id,
|
||||
title: item.title,
|
||||
preventSave: true
|
||||
});
|
||||
@@ -302,7 +319,7 @@ function EditorView({
|
||||
if (!session.needsHydration && session.content) {
|
||||
editor?.updateContent(session.content.data);
|
||||
}
|
||||
}, [editor, session]);
|
||||
}, [editor, session.needsHydration]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@@ -330,39 +347,19 @@ function EditorView({
|
||||
onSave={(content, ignoreEdit) => {
|
||||
const currentSession = useEditorStore
|
||||
.getState()
|
||||
.getSession(session.id);
|
||||
const noteId =
|
||||
currentSession && "note" in currentSession
|
||||
? currentSession.note.id
|
||||
: null;
|
||||
const sessions = noteId
|
||||
? useEditorStore.getState().getSessionsForNote(noteId)
|
||||
: [currentSession];
|
||||
.getSession(session.id, ["default", "readonly", "new"]);
|
||||
if (!currentSession) return;
|
||||
|
||||
const currentSessionId = session.id;
|
||||
const data = content();
|
||||
for (const session of sessions) {
|
||||
if (
|
||||
session?.type !== "default" &&
|
||||
session?.type !== "readonly" &&
|
||||
session?.type !== "new"
|
||||
)
|
||||
continue;
|
||||
if (!session.content) session.content = { type: "tiptap", data };
|
||||
else session.content.data = data;
|
||||
|
||||
// update content in other tabs
|
||||
if (session.id !== currentSessionId) {
|
||||
const editor = useEditorManager.getState().getEditor(session.id);
|
||||
editor?.editor?.updateContent(data);
|
||||
}
|
||||
}
|
||||
if (!currentSession.content)
|
||||
currentSession.content = { type: "tiptap", data };
|
||||
else currentSession.content.data = data;
|
||||
|
||||
logger.debug("scheduling save", {
|
||||
id: session.id,
|
||||
length: data.length
|
||||
});
|
||||
deferredSave(session.id, session.id, ignoreEdit, data);
|
||||
deferredSave(currentSession.id, currentSession.id, ignoreEdit, data);
|
||||
}}
|
||||
options={{
|
||||
readonly: session?.type === "readonly" || session?.type === "deleted",
|
||||
@@ -417,9 +414,9 @@ function DownloadAttachmentProgress(props: DownloadAttachmentProgressProps) {
|
||||
variant="secondary"
|
||||
mt={2}
|
||||
onClick={() => {
|
||||
const note = useEditorStore.getState().getActiveNote();
|
||||
const id = useEditorStore.getState().activeSessionId;
|
||||
useEditorStore.setState({ documentPreview: undefined });
|
||||
if (note) db.fs().cancel(note.id).catch(console.error);
|
||||
if (id) db.fs().cancel(id).catch(console.error);
|
||||
}}
|
||||
>
|
||||
{strings.cancel()}
|
||||
@@ -496,14 +493,6 @@ export function Editor(props: EditorProps) {
|
||||
return () => unsub();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
const editor = useEditorManager.getState().getEditor(id)?.editor;
|
||||
const selection = editor?.getSelection();
|
||||
if (selection) Config.set(`${id}:selection`, selection);
|
||||
};
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<EditorChrome {...props}>
|
||||
<Tiptap
|
||||
@@ -895,7 +884,7 @@ function UnlockNoteView(props: UnlockNoteViewProps) {
|
||||
subtitle={strings.enterPasswordToUnlockNote()}
|
||||
title={session.note.title}
|
||||
unlock={async (password) => {
|
||||
const note = await db.vault.open(session.note.id, password);
|
||||
const note = await db.vault.open(session.id, password);
|
||||
if (!note || !note.content)
|
||||
throw new Error("note with this id does not exist.");
|
||||
|
||||
@@ -908,7 +897,8 @@ function UnlockNoteView(props: UnlockNoteViewProps) {
|
||||
saveState: SaveState.Saved,
|
||||
sessionId: `${Date.now()}`,
|
||||
tags,
|
||||
tabId: session.tabId,
|
||||
pinned: session.pinned,
|
||||
preview: session.preview,
|
||||
content: note.content
|
||||
});
|
||||
}}
|
||||
|
||||
@@ -604,11 +604,7 @@ function toIEditor(editor: Editor): IEditor {
|
||||
),
|
||||
startSearch: () => editor.commands.startSearch(),
|
||||
getContent: () =>
|
||||
getHTMLFromFragment(editor.state.doc.content, editor.schema),
|
||||
getSelection: () => {
|
||||
const { from, to } = editor.state.selection;
|
||||
return { from, to };
|
||||
}
|
||||
getHTMLFromFragment(editor.state.doc.content, editor.schema)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -17,14 +17,7 @@ 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 React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from "react";
|
||||
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
||||
import { Input } from "@theme-ui/components";
|
||||
import { useEditorStore } from "../../stores/editor-store";
|
||||
import { debounceWithId } from "@notesnook/common";
|
||||
@@ -47,7 +40,7 @@ function TitleBox(props: TitleBoxProps) {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const pendingChanges = useRef(false);
|
||||
// const id = useStore((store) => store.session.id);
|
||||
const sessionType = useEditorStore((store) => store.getSession(id)?.type);
|
||||
const sessionType = useEditorStore((store) => store.getActiveSession()?.type);
|
||||
const isMobile = useMobile();
|
||||
const isTablet = useTablet();
|
||||
const { editorConfig } = useEditorConfig();
|
||||
@@ -71,15 +64,13 @@ function TitleBox(props: TitleBoxProps) {
|
||||
[isMobile, isTablet]
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
useEffect(() => {
|
||||
const session = useEditorStore.getState().getSession(id);
|
||||
if (!session || !("note" in session) || !session.note || !inputRef.current)
|
||||
return;
|
||||
if (pendingChanges.current) return;
|
||||
|
||||
const { title } = session.note;
|
||||
if (inputRef.current.value === title) return;
|
||||
|
||||
withSelectionPersist(
|
||||
inputRef.current,
|
||||
(input) => (input.value = title || "")
|
||||
@@ -95,24 +86,23 @@ function TitleBox(props: TitleBoxProps) {
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = AppEventManager.subscribe(
|
||||
AppEvents.changeNoteTitle,
|
||||
({
|
||||
preventSave,
|
||||
title,
|
||||
sessionId
|
||||
}: {
|
||||
title: string;
|
||||
preventSave: boolean;
|
||||
sessionId: string;
|
||||
}) => {
|
||||
if (!inputRef.current || sessionId !== id) return;
|
||||
({ preventSave, title }: { title: string; preventSave: boolean }) => {
|
||||
if (!inputRef.current) return;
|
||||
withSelectionPersist(
|
||||
inputRef.current,
|
||||
(input) => (input.value = title)
|
||||
);
|
||||
updateFontSize(title.length);
|
||||
if (!preventSave) {
|
||||
const { activeSessionId } = useEditorStore.getState();
|
||||
if (!activeSessionId) return;
|
||||
pendingChanges.current = true;
|
||||
debouncedOnTitleChange(sessionId, sessionId, title, pendingChanges);
|
||||
debouncedOnTitleChange(
|
||||
activeSessionId,
|
||||
activeSessionId,
|
||||
title,
|
||||
pendingChanges
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -120,7 +110,7 @@ function TitleBox(props: TitleBoxProps) {
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [updateFontSize, id]);
|
||||
}, [updateFontSize]);
|
||||
|
||||
return (
|
||||
<Input
|
||||
|
||||
@@ -40,5 +40,4 @@ export interface IEditor {
|
||||
sendAttachmentProgress: (hash: string, progress: number) => void;
|
||||
startSearch: () => void;
|
||||
getContent: () => string;
|
||||
getSelection: () => { from: number; to: number };
|
||||
}
|
||||
|
||||
@@ -219,8 +219,7 @@ import {
|
||||
mdiOpenInNew,
|
||||
mdiTagOutline,
|
||||
mdiChatQuestionOutline,
|
||||
mdiNoteRemoveOutline,
|
||||
mdiTabPlus
|
||||
mdiNoteRemoveOutline
|
||||
} from "@mdi/js";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Theme } from "@notesnook/theme";
|
||||
@@ -274,8 +273,7 @@ const MDIIconWrapper = memo(
|
||||
(prev, next) =>
|
||||
prev.rotate === next.rotate &&
|
||||
prev.color === next.color &&
|
||||
prev.title === next.title &&
|
||||
prev.size === next.size
|
||||
prev.title === next.title
|
||||
);
|
||||
|
||||
export type IconProps = FlexProps & Omit<MDIIconWrapperProps, "path">;
|
||||
@@ -562,4 +560,3 @@ export const ClearCache = createIcon(mdiBroom);
|
||||
export const OpenInNew = createIcon(mdiOpenInNew);
|
||||
export const Coupon = createIcon(mdiTagOutline);
|
||||
export const Support = createIcon(mdiChatQuestionOutline);
|
||||
export const NewTab = createIcon(mdiTabPlus);
|
||||
|
||||
@@ -118,7 +118,7 @@ function Note(props: NoteProps) {
|
||||
} = props;
|
||||
const note = item;
|
||||
|
||||
const isOpened = useEditorStore((store) => store.isNoteOpen(item.id));
|
||||
const isOpened = useEditorStore((store) => store.activeSessionId === item.id);
|
||||
const primary: SchemeColors = color ? color.colorCode : "accent-selected";
|
||||
|
||||
return (
|
||||
@@ -146,7 +146,7 @@ function Note(props: NoteProps) {
|
||||
menuItems={menuItems}
|
||||
onClick={() => useEditorStore.getState().openSession(note)}
|
||||
onMiddleClick={() =>
|
||||
useEditorStore.getState().openSession(note, { openInNewTab: true })
|
||||
useEditorStore.getState().openSession(note, { newSession: true })
|
||||
}
|
||||
header={
|
||||
<Flex
|
||||
@@ -331,14 +331,6 @@ const menuItems: (
|
||||
// const isSynced = db.notes.note(note.id)?.synced();
|
||||
|
||||
return [
|
||||
{
|
||||
type: "button",
|
||||
key: "openinnewtab",
|
||||
title: strings.openInNewTab(),
|
||||
icon: OpenInNew.path,
|
||||
onClick: () =>
|
||||
useEditorStore.getState().openSession(note.id, { openInNewTab: true })
|
||||
},
|
||||
{
|
||||
type: "button",
|
||||
key: "pin",
|
||||
|
||||
@@ -140,7 +140,7 @@ function Header(props: RouteContainerProps) {
|
||||
{buttons?.back ? (
|
||||
<Button
|
||||
{...buttons.back}
|
||||
data-test-id="route-go-back"
|
||||
data-test-id="go-back"
|
||||
sx={{ p: 0, flexShrink: 0 }}
|
||||
>
|
||||
<ArrowLeft size={20} />
|
||||
|
||||
@@ -96,15 +96,14 @@ export const SplitPane = React.forwardRef<
|
||||
const wrapSize = useRef(0);
|
||||
const childrenLength = childrenToArray(children).length;
|
||||
const autoSaveKey = autoSaveId ? `csp:${autoSaveId}` : undefined;
|
||||
const lastCollapsedPaneSize = useRef(0);
|
||||
|
||||
const { sizeName, splitPos, splitAxis } = useMemo(
|
||||
() =>
|
||||
({
|
||||
sizeName: direction === "vertical" ? "width" : "height",
|
||||
splitPos: direction === "vertical" ? "left" : "top",
|
||||
splitAxis: direction === "vertical" ? "x" : "y"
|
||||
} as const),
|
||||
({
|
||||
sizeName: direction === "vertical" ? "width" : "height",
|
||||
splitPos: direction === "vertical" ? "left" : "top",
|
||||
splitAxis: direction === "vertical" ? "x" : "y"
|
||||
} as const),
|
||||
[direction]
|
||||
);
|
||||
|
||||
@@ -257,14 +256,10 @@ export const SplitPane = React.forwardRef<
|
||||
return {
|
||||
collapse: (index: number) => {
|
||||
paneSizes.current[index].collapsed = true;
|
||||
lastCollapsedPaneSize.current = paneSizes.current[index].size;
|
||||
setSizes(paneSizes.current, wrapSize.current);
|
||||
},
|
||||
expand: (index: number) => {
|
||||
paneSizes.current[index].collapsed = false;
|
||||
paneSizes.current[index].size = lastCollapsedPaneSize.current
|
||||
? lastCollapsedPaneSize.current
|
||||
: paneSizes.current[index].initialSize;
|
||||
setSizes(paneSizes.current, wrapSize.current);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -34,7 +34,7 @@ import { strings } from "@notesnook/intl";
|
||||
type TrashItemProps = { item: TrashItemType; date: number };
|
||||
function TrashItem(props: TrashItemProps) {
|
||||
const { item, date } = props;
|
||||
const isOpened = useEditorStore((store) => store.isNoteOpen(item.id));
|
||||
const isOpened = useEditorStore((store) => store.activeSessionId === item.id);
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
|
||||
@@ -39,14 +39,11 @@ import {
|
||||
isTrashItem,
|
||||
NoteContent
|
||||
} from "@notesnook/core";
|
||||
import { Context } from "../components/list-container/types";
|
||||
import { showToast } from "../utils/toast";
|
||||
import { getId } from "@notesnook/core";
|
||||
import { PersistStorage } from "zustand/middleware";
|
||||
import {
|
||||
getFormattedHistorySessionDate,
|
||||
TabHistory,
|
||||
TabSessionHistory
|
||||
} from "@notesnook/common";
|
||||
import { getFormattedHistorySessionDate } from "@notesnook/common";
|
||||
import { isCipher } from "@notesnook/core";
|
||||
import { hashNavigate } from "../navigation";
|
||||
import { AppEventManager, AppEvents } from "../common/app-events";
|
||||
@@ -67,17 +64,15 @@ enum SESSION_STATES {
|
||||
conflicted
|
||||
}
|
||||
|
||||
type TabItem = {
|
||||
id: string;
|
||||
sessionId: string;
|
||||
pinned?: boolean;
|
||||
};
|
||||
// type ConflictedContentItem = Omit<ContentItem, "conflicted"> & {
|
||||
// conflicted: ContentItem;
|
||||
// };
|
||||
|
||||
export type BaseEditorSession = {
|
||||
tabId: string;
|
||||
|
||||
id: string;
|
||||
needsHydration?: boolean;
|
||||
pinned?: boolean;
|
||||
preview?: boolean;
|
||||
title?: string;
|
||||
|
||||
/**
|
||||
@@ -120,29 +115,22 @@ export type DefaultEditorSession = BaseEditorSession & {
|
||||
|
||||
export type NewEditorSession = BaseEditorSession & {
|
||||
type: "new";
|
||||
context?: Context;
|
||||
saveState: SaveState;
|
||||
content?: NoteContent<false>;
|
||||
};
|
||||
|
||||
export type ConflictedEditorSession = BaseEditorSession & {
|
||||
type: "conflicted";
|
||||
type: "conflicted" | "diff";
|
||||
note: Note;
|
||||
content?: ContentItem;
|
||||
};
|
||||
|
||||
export type DiffEditorSession = BaseEditorSession & {
|
||||
type: "diff";
|
||||
note: Note;
|
||||
content: ContentItem;
|
||||
historySessionId: string;
|
||||
};
|
||||
|
||||
export type EditorSession =
|
||||
| DefaultEditorSession
|
||||
| LockedEditorSession
|
||||
| NewEditorSession
|
||||
| ConflictedEditorSession
|
||||
| DiffEditorSession
|
||||
| ReadonlyEditorSession
|
||||
| DeletedEditorSession;
|
||||
|
||||
@@ -152,7 +140,7 @@ type SessionTypeMap = {
|
||||
locked: LockedEditorSession;
|
||||
new: NewEditorSession;
|
||||
conflicted: ConflictedEditorSession;
|
||||
diff: DiffEditorSession;
|
||||
diff: ConflictedEditorSession;
|
||||
readonly: ReadonlyEditorSession;
|
||||
deleted: DeletedEditorSession;
|
||||
};
|
||||
@@ -172,33 +160,10 @@ export function isLockedSession(session: EditorSession): boolean {
|
||||
session.content.locked)
|
||||
);
|
||||
}
|
||||
|
||||
const tabSessionHistory = new TabSessionHistory({
|
||||
get() {
|
||||
return {
|
||||
tabSessionHistory: useEditorStore.getState().tabHistory,
|
||||
canGoBack: useEditorStore.getState().canGoBack,
|
||||
canGoForward: useEditorStore.getState().canGoForward
|
||||
};
|
||||
},
|
||||
set(state) {
|
||||
useEditorStore.setState({
|
||||
tabHistory: state.tabSessionHistory,
|
||||
canGoBack: state.canGoBack,
|
||||
canGoForward: state.canGoForward
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const saveMutex = new Mutex();
|
||||
|
||||
class EditorStore extends BaseStore<EditorStore> {
|
||||
tabs: TabItem[] = [];
|
||||
tabHistory: TabHistory = {};
|
||||
activeTabId: string | undefined;
|
||||
canGoBack = false;
|
||||
canGoForward = false;
|
||||
sessions: EditorSession[] = [];
|
||||
activeSessionId?: string;
|
||||
|
||||
arePropertiesVisible = false;
|
||||
documentPreview?: DocumentPreview;
|
||||
@@ -213,25 +178,18 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
);
|
||||
};
|
||||
|
||||
getSessionsForNote = (noteId: string) => {
|
||||
return this.get().sessions.filter(
|
||||
(s) => "note" in s && s.note.id === noteId
|
||||
);
|
||||
};
|
||||
|
||||
getTabsForNote = (noteId: string) => {
|
||||
const { tabs, sessions } = this.get();
|
||||
return tabs.filter((t) =>
|
||||
sessions.some(
|
||||
(s) => t.sessionId === s.id && "note" in s && s.note.id === noteId
|
||||
)
|
||||
getActiveSession = <T extends SessionType[]>(types?: T) => {
|
||||
const { activeSessionId, sessions } = this.get();
|
||||
return sessions.find(
|
||||
(s): s is SessionTypeMap[T[number]] =>
|
||||
s.id === activeSessionId && (!types || types.includes(s.type))
|
||||
);
|
||||
};
|
||||
|
||||
init = () => {
|
||||
EV.subscribe(EVENTS.userLoggedOut, () => {
|
||||
const { closeTabs, tabs } = this.get();
|
||||
closeTabs(...tabs.map((s) => s.id));
|
||||
const { closeSessions, sessions } = this.get();
|
||||
closeSessions(...sessions.map((s) => s.id));
|
||||
});
|
||||
|
||||
EV.subscribe(EVENTS.vaultLocked, () => {
|
||||
@@ -241,7 +199,8 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
if (
|
||||
session.type === "diff" ||
|
||||
session.type === "deleted" ||
|
||||
session.type === "new"
|
||||
// TODO: what's this?
|
||||
!("note" in session)
|
||||
)
|
||||
return session;
|
||||
|
||||
@@ -249,7 +208,8 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
type: "locked",
|
||||
id: session.id,
|
||||
note: session.note,
|
||||
tabId: session.tabId
|
||||
pinned: session.pinned,
|
||||
preview: session.preview
|
||||
};
|
||||
}
|
||||
return session;
|
||||
@@ -261,7 +221,8 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
EVENTS.syncItemMerged,
|
||||
(item?: MaybeDeletedItem<Item>) => {
|
||||
if (!item) return;
|
||||
const { sessions, closeTabs, updateSession, openSession } = this.get();
|
||||
const { sessions, closeSessions, updateSession, openSession } =
|
||||
this.get();
|
||||
const clearIds: string[] = [];
|
||||
for (const session of sessions) {
|
||||
if (session.type === "new") continue;
|
||||
@@ -272,9 +233,9 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
: item.type === "tiptap"
|
||||
? item.noteId
|
||||
: null;
|
||||
if (noteId && session.note.id !== noteId) continue;
|
||||
if (isDeleted(item) || isTrashItem(item))
|
||||
clearIds.push(session.tabId);
|
||||
if (noteId && session.id !== noteId && session.note.id !== noteId)
|
||||
continue;
|
||||
if (isDeleted(item) || isTrashItem(item)) clearIds.push(session.id);
|
||||
// if a note becomes conflicted, reopen the session
|
||||
else if (
|
||||
session.type !== "conflicted" &&
|
||||
@@ -326,31 +287,33 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
);
|
||||
}
|
||||
}
|
||||
if (clearIds.length > 0) closeTabs(...clearIds);
|
||||
if (clearIds.length > 0) closeSessions(...clearIds);
|
||||
}
|
||||
);
|
||||
|
||||
db.eventManager.subscribe(
|
||||
EVENTS.databaseUpdated,
|
||||
async (event: DatabaseUpdatedEvent) => {
|
||||
const { sessions, openSession, closeTabs, updateSession } = this.get();
|
||||
const { sessions, openSession, closeSessions, updateSession } =
|
||||
this.get();
|
||||
const clearIds: string[] = [];
|
||||
if (event.collection === "notes") {
|
||||
// when a note is permanently deleted from trash
|
||||
if (event.type === "softDelete" || event.type === "delete") {
|
||||
clearIds.push(
|
||||
...sessions
|
||||
.filter(
|
||||
(session) =>
|
||||
"note" in session && event.ids.includes(session.note.id)
|
||||
)
|
||||
.map((s) => s.tabId)
|
||||
...event.ids.filter(
|
||||
(id) =>
|
||||
sessions.findIndex(
|
||||
(s) => s.id === id || ("note" in s && s.note.id === id)
|
||||
) > -1
|
||||
)
|
||||
);
|
||||
} else if (event.type === "update") {
|
||||
for (const session of sessions) {
|
||||
if (
|
||||
session.type === "new" ||
|
||||
!event.ids.includes(session.note.id)
|
||||
(!event.ids.includes(session.id) &&
|
||||
!event.ids.includes(session.note.id))
|
||||
)
|
||||
continue;
|
||||
|
||||
@@ -361,13 +324,13 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
// when a note is restored from trash
|
||||
(session.type === "deleted" && event.item.type !== "trash")
|
||||
) {
|
||||
openSession(session.note.id, { force: true, silent: true });
|
||||
openSession(session.id, { force: true, silent: true });
|
||||
} else if (
|
||||
// when a note is moved to trash
|
||||
session.type !== "deleted" &&
|
||||
event.item.type === "trash"
|
||||
) {
|
||||
clearIds.push(session.tabId);
|
||||
clearIds.push(session.id);
|
||||
} else {
|
||||
updateSession(session.id, [session.type], (session) => {
|
||||
session.note.pinned =
|
||||
@@ -402,7 +365,7 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
(session.type === "default" && session.locked)) &&
|
||||
!event.item.locked)
|
||||
) {
|
||||
openSession(session.note.id, { force: true, silent: true });
|
||||
openSession(session.id, { force: true, silent: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -431,6 +394,7 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
: event.reference.ids;
|
||||
if (!("color" in session) || !session.color) continue;
|
||||
if (
|
||||
!ids.includes(session.id) &&
|
||||
!ids.includes(session.note.id) &&
|
||||
!ids.includes(session.color)
|
||||
)
|
||||
@@ -451,6 +415,7 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
: event.reference.ids;
|
||||
if (!("tags" in session) || !session.tags) continue;
|
||||
if (
|
||||
!ids.includes(session.id) &&
|
||||
!ids.includes(session.note.id) &&
|
||||
session.tags.every((t) => !ids.includes(t.id))
|
||||
)
|
||||
@@ -479,43 +444,36 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
session.tags?.every((t) => !event.ids.includes(t.id))
|
||||
)
|
||||
continue;
|
||||
console.log("UDPATE");
|
||||
updateSession(session.id, undefined, {
|
||||
tags: await db.notes.tags(session.note.id)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (clearIds.length > 0) closeTabs(...clearIds);
|
||||
if (clearIds.length > 0) closeSessions(...clearIds);
|
||||
}
|
||||
);
|
||||
|
||||
const { rehydrateSession, activeTabId, newSession } = this.get();
|
||||
if (activeTabId) {
|
||||
const tab = this.get().tabs.find((t) => t.id === activeTabId);
|
||||
if (!tab) return;
|
||||
rehydrateSession(tab.sessionId);
|
||||
const {
|
||||
openSession,
|
||||
openDiffSession,
|
||||
activateSession,
|
||||
activeSessionId,
|
||||
getSession,
|
||||
newSession
|
||||
} = this.get();
|
||||
if (activeSessionId) {
|
||||
const session = getSession(activeSessionId);
|
||||
if (!session) return;
|
||||
|
||||
if (session.type === "diff" || session.type === "conflicted")
|
||||
openDiffSession(session.note.id, session.id);
|
||||
else if (session.type === "new") activateSession(session.id);
|
||||
else openSession(activeSessionId);
|
||||
} else newSession();
|
||||
};
|
||||
|
||||
private rehydrateSession = (sessionId: string) => {
|
||||
const { openSession, openDiffSession, getSession, activateSession } =
|
||||
this.get();
|
||||
|
||||
const session = getSession(sessionId);
|
||||
if (session?.type === "new") {
|
||||
activateSession(session.id);
|
||||
return;
|
||||
}
|
||||
if (!session || !session.needsHydration) return;
|
||||
|
||||
if (session.type === "diff")
|
||||
openDiffSession(session.note.id, session.historySessionId);
|
||||
else
|
||||
openSession(session.note.id, {
|
||||
force: true
|
||||
});
|
||||
};
|
||||
|
||||
updateSession = <T extends SessionType[] = SessionType[]>(
|
||||
id: string,
|
||||
types: T | undefined,
|
||||
@@ -538,13 +496,14 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
});
|
||||
};
|
||||
|
||||
activateSession = (id?: string, activeBlockId?: string, silent?: boolean) => {
|
||||
activateSession = (id?: string, activeBlockId?: string) => {
|
||||
if (!id) hashNavigate(`/`, { replace: true, notify: false });
|
||||
|
||||
const session = this.get().sessions.find((s) => s.id === id);
|
||||
if (!session) id = undefined;
|
||||
|
||||
const activeSession = this.getActiveSession();
|
||||
|
||||
if (activeSession) {
|
||||
this.saveSessionContentIfNotSaved(activeSession.id);
|
||||
}
|
||||
@@ -558,9 +517,13 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
setDocumentTitle(session.note.title);
|
||||
} else setDocumentTitle();
|
||||
|
||||
this.set({ activeSessionId: id });
|
||||
AppEventManager.publish(AppEvents.toggleEditor, true);
|
||||
|
||||
if (id) {
|
||||
const { history } = this.get();
|
||||
if (history.includes(id)) history.splice(history.indexOf(id), 1);
|
||||
history.push(id);
|
||||
if (session?.type === "new")
|
||||
hashNavigate(`/notes/${id}/create`, { replace: true, notify: false });
|
||||
else hashNavigate(`/notes/${id}/edit`, { replace: true, notify: false });
|
||||
@@ -570,19 +533,6 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
this.updateSession(session.id, [session.type], {
|
||||
activeBlockId
|
||||
});
|
||||
|
||||
if (session?.tabId) {
|
||||
const { tabs, activeTabId } = this.get();
|
||||
const index = tabs.findIndex((t) => t.id === session.tabId);
|
||||
// no need to focus tab if the same session is already open
|
||||
if (
|
||||
index === -1 ||
|
||||
(activeTabId === session.tabId && tabs[index].sessionId === session.id)
|
||||
)
|
||||
return;
|
||||
this.set((state) => (state.tabs[index].sessionId = session.id));
|
||||
if (!silent) this.focusTab(session.tabId, session.id);
|
||||
}
|
||||
};
|
||||
|
||||
openDiffSession = async (noteId: string, sessionId: string) => {
|
||||
@@ -595,30 +545,12 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
|
||||
if (!oldContent || !currentContent) return;
|
||||
|
||||
const { getSession, addSession, sessions, activeTabId, tabs } = this.get();
|
||||
|
||||
const tabId = activeTabId ?? this.addTab();
|
||||
const tab = tabs.find((t) => t.id === tabId);
|
||||
const activeSession = tab && getSession(tab.sessionId);
|
||||
const oldSession = sessions.find(
|
||||
(s) =>
|
||||
s.type === "diff" &&
|
||||
s.historySessionId === session.id &&
|
||||
s.tabId === tabId
|
||||
);
|
||||
const tabSessionId =
|
||||
activeSession?.needsHydration || activeSession?.type === "new"
|
||||
? activeSession.id
|
||||
: tabSessionHistory.add(tabId, oldSession?.id);
|
||||
|
||||
const label = getFormattedHistorySessionDate(session);
|
||||
addSession({
|
||||
this.get().addSession({
|
||||
type: "diff",
|
||||
id: tabSessionId,
|
||||
id: session.id,
|
||||
note,
|
||||
tabId,
|
||||
title: label,
|
||||
historySessionId: session.id,
|
||||
content: {
|
||||
type: oldContent.type,
|
||||
dateCreated: session.dateCreated,
|
||||
@@ -641,59 +573,31 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
force?: boolean;
|
||||
activeBlockId?: string;
|
||||
silent?: boolean;
|
||||
openInNewTab?: boolean;
|
||||
newSession?: boolean;
|
||||
} = {}
|
||||
): Promise<void> => {
|
||||
const {
|
||||
getSession,
|
||||
sessions,
|
||||
tabs,
|
||||
activateSession,
|
||||
activeTabId,
|
||||
rehydrateSession,
|
||||
getTabsForNote,
|
||||
addTab
|
||||
} = this.get();
|
||||
const { getSession, openDiffSession } = this.get();
|
||||
const noteId = typeof noteOrId === "string" ? noteOrId : noteOrId.id;
|
||||
const oldTabForNote = options.force ? null : getTabsForNote(noteId).at(0);
|
||||
const tabId = options.openInNewTab
|
||||
? addTab(getId())
|
||||
: oldTabForNote?.id || activeTabId || addTab(getId());
|
||||
const session = getSession(noteId);
|
||||
|
||||
const tab = tabs.find((t) => t.id === tabId);
|
||||
const activeSession = tab && getSession(tab.sessionId);
|
||||
const noteAlreadyOpened =
|
||||
activeSession &&
|
||||
"note" in activeSession &&
|
||||
activeSession.note.id === noteId &&
|
||||
// we should allow opening the same note again if a diff session of a note
|
||||
// is opened in the same tab. This allows for cases where a user opens a diff
|
||||
// and then wants to open the note in the same tab again.
|
||||
activeSession.type !== "diff";
|
||||
if (noteAlreadyOpened && !options.force) {
|
||||
return activeSession.needsHydration
|
||||
? rehydrateSession(activeSession.id)
|
||||
: activateSession(activeSession.id, options.activeBlockId);
|
||||
if (session && !options.force) {
|
||||
if (!session.needsHydration) {
|
||||
return this.activateSession(noteId, options.activeBlockId);
|
||||
}
|
||||
|
||||
if (session.type === "diff" || session.type === "conflicted") {
|
||||
return openDiffSession(session.note.id, session.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (activeSession && "note" in activeSession)
|
||||
await db.fs().cancel(activeSession.note.id);
|
||||
if (session && session.id) await db.fs().cancel(session.id);
|
||||
|
||||
const note =
|
||||
typeof noteOrId === "object" && !activeSession?.needsHydration
|
||||
typeof noteOrId === "object"
|
||||
? noteOrId
|
||||
: (await db.notes.note(noteId)) || (await db.notes.trashed(noteId));
|
||||
if (!note) return;
|
||||
|
||||
const oldSessionOfNote = sessions.find(
|
||||
(s) => "note" in s && s.note.id === noteId && s.tabId === tabId
|
||||
);
|
||||
const sessionId =
|
||||
activeSession?.needsHydration ||
|
||||
activeSession?.type === "new" ||
|
||||
noteAlreadyOpened
|
||||
? activeSession.id
|
||||
: tabSessionHistory.add(tabId, oldSessionOfNote?.id);
|
||||
const isPreview = session ? session.preview : !options?.newSession;
|
||||
const isLocked = await db.vaults.itemExists(note);
|
||||
|
||||
if (note.conflicted) {
|
||||
@@ -722,23 +626,25 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
{
|
||||
type: "conflicted",
|
||||
content: content,
|
||||
id: sessionId,
|
||||
id: note.id,
|
||||
pinned: session?.pinned,
|
||||
note,
|
||||
activeBlockId: options.activeBlockId,
|
||||
tabId
|
||||
preview: isPreview,
|
||||
activeBlockId: options.activeBlockId
|
||||
},
|
||||
options.silent
|
||||
!options.silent
|
||||
);
|
||||
} else if (isLocked && note.type !== "trash") {
|
||||
this.addSession(
|
||||
{
|
||||
type: "locked",
|
||||
id: sessionId,
|
||||
id: note.id,
|
||||
pinned: session?.pinned,
|
||||
note,
|
||||
activeBlockId: options.activeBlockId,
|
||||
tabId
|
||||
preview: isPreview,
|
||||
activeBlockId: options.activeBlockId
|
||||
},
|
||||
options.silent
|
||||
!options.silent
|
||||
);
|
||||
} else {
|
||||
const content = note.contentId
|
||||
@@ -755,12 +661,12 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
{
|
||||
type: "deleted",
|
||||
note,
|
||||
id: sessionId,
|
||||
id: note.id,
|
||||
pinned: session?.pinned,
|
||||
content,
|
||||
activeBlockId: options.activeBlockId,
|
||||
tabId
|
||||
activeBlockId: options.activeBlockId
|
||||
},
|
||||
options.silent
|
||||
!options.silent
|
||||
);
|
||||
} else {
|
||||
const attachmentsLength = await db.attachments
|
||||
@@ -773,113 +679,94 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
{
|
||||
type: "readonly",
|
||||
note,
|
||||
id: sessionId,
|
||||
id: note.id,
|
||||
pinned: session?.pinned,
|
||||
content,
|
||||
color: colors[0]?.fromId,
|
||||
tags,
|
||||
activeBlockId: options.activeBlockId,
|
||||
tabId
|
||||
activeBlockId: options.activeBlockId
|
||||
},
|
||||
options.silent
|
||||
!options.silent
|
||||
);
|
||||
} else {
|
||||
this.addSession(
|
||||
{
|
||||
type: "default",
|
||||
id: sessionId,
|
||||
id: note.id,
|
||||
note,
|
||||
saveState: SaveState.Saved,
|
||||
sessionId: `${Date.now()}`,
|
||||
attachmentsLength,
|
||||
pinned: session?.pinned,
|
||||
tags,
|
||||
color: colors[0]?.fromId,
|
||||
content,
|
||||
activeBlockId: options.activeBlockId,
|
||||
tabId
|
||||
preview: isPreview,
|
||||
activeBlockId: options.activeBlockId
|
||||
},
|
||||
options.silent
|
||||
!options.silent
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
focusNextTab = () => {
|
||||
const { tabs, activeTabId } = this.get();
|
||||
if (tabs.length <= 1) return;
|
||||
openNextSession = () => {
|
||||
const { sessions, activeSessionId } = this.get();
|
||||
if (sessions.length === 0 || sessions.length === 1) return;
|
||||
|
||||
const index = tabs.findIndex((s) => s.id === activeTabId);
|
||||
const index = sessions.findIndex((s) => s.id === activeSessionId);
|
||||
if (index === -1) return;
|
||||
return this.focusTab(tabs[index === tabs.length - 1 ? 0 : index + 1].id);
|
||||
|
||||
if (index === sessions.length - 1) {
|
||||
return this.openSession(sessions[0].id);
|
||||
}
|
||||
return this.openSession(sessions[index + 1].id);
|
||||
};
|
||||
|
||||
focusPreviousTab = () => {
|
||||
const { tabs, activeTabId } = this.get();
|
||||
if (tabs.length <= 1) return;
|
||||
openPreviousSession = () => {
|
||||
const { sessions, activeSessionId } = this.get();
|
||||
if (sessions.length === 0 || sessions.length === 1) return;
|
||||
|
||||
const index = tabs.findIndex((s) => s.id === activeTabId);
|
||||
const index = sessions.findIndex((s) => s.id === activeSessionId);
|
||||
if (index === -1) return;
|
||||
return this.focusTab(tabs[index === 0 ? tabs.length - 1 : index - 1].id);
|
||||
|
||||
if (index === 0) {
|
||||
return this.openSession(sessions[sessions.length - 1].id);
|
||||
}
|
||||
return this.openSession(sessions[index - 1].id);
|
||||
};
|
||||
|
||||
goBack = async () => {
|
||||
const activeTabId = this.get().activeTabId;
|
||||
if (!activeTabId || !tabSessionHistory.canGoBack(activeTabId)) return;
|
||||
const sessionId = tabSessionHistory.back(activeTabId);
|
||||
if (!sessionId) return;
|
||||
if (!(await this.goToSession(activeTabId, sessionId))) {
|
||||
await this.goBack();
|
||||
}
|
||||
};
|
||||
addSession = (session: EditorSession, activate = true) => {
|
||||
let oldSessionId: string | null = null;
|
||||
|
||||
goForward = async () => {
|
||||
const activeTabId = this.get().activeTabId;
|
||||
if (!activeTabId || !tabSessionHistory.canGoForward(activeTabId)) return;
|
||||
const sessionId = tabSessionHistory.forward(activeTabId);
|
||||
if (!sessionId) return;
|
||||
if (!(await this.goToSession(activeTabId, sessionId))) {
|
||||
await this.goForward();
|
||||
}
|
||||
};
|
||||
|
||||
goToSession = async (tabId: string, sessionId: string) => {
|
||||
const session = this.get().getSession(sessionId);
|
||||
if (!session) {
|
||||
tabSessionHistory.remove(tabId, sessionId);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (session.type === "new") {
|
||||
this.activateSession(session.id);
|
||||
return true;
|
||||
} else {
|
||||
if (!(await db.notes.exists(session.note.id))) {
|
||||
tabSessionHistory.remove(tabId, session.id);
|
||||
this.set((state) => {
|
||||
const index = state.sessions.findIndex((s) => s.id === session.id);
|
||||
state.sessions.splice(index, 1);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
// we must rehydrate the session as the note's content can be stale
|
||||
this.updateSession(session.id, undefined, {
|
||||
needsHydration: true
|
||||
});
|
||||
this.activateSession(session.id);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
addSession = (session: EditorSession, silent = false) => {
|
||||
this.set((state) => {
|
||||
const index = state.sessions.findIndex((s) => s.id === session.id);
|
||||
if (index > -1) {
|
||||
state.sessions[index] = session;
|
||||
} else state.sessions.push(session);
|
||||
const { activeSessionIndex, duplicateSessionIndex, previewSessionIndex } =
|
||||
findSessionIndices(state.sessions, session, state.activeSessionId);
|
||||
|
||||
if (duplicateSessionIndex > -1) {
|
||||
oldSessionId = state.sessions[duplicateSessionIndex].id;
|
||||
state.sessions[duplicateSessionIndex] = session;
|
||||
} else if (previewSessionIndex > -1) {
|
||||
oldSessionId = state.sessions[previewSessionIndex].id;
|
||||
state.sessions[previewSessionIndex] = session;
|
||||
} else if (activeSessionIndex > -1)
|
||||
state.sessions.splice(activeSessionIndex + 1, 0, session);
|
||||
else state.sessions.push(session);
|
||||
state.sessions.sort((a, b) =>
|
||||
a.pinned === b.pinned ? 0 : a.pinned ? -1 : 1
|
||||
);
|
||||
});
|
||||
|
||||
this.activateSession(session.id, undefined, silent);
|
||||
const { history } = this.get();
|
||||
if (
|
||||
oldSessionId &&
|
||||
oldSessionId !== session.id &&
|
||||
history.includes(oldSessionId)
|
||||
)
|
||||
history.splice(history.indexOf(oldSessionId), 1);
|
||||
|
||||
if (activate) this.activateSession(session.id);
|
||||
};
|
||||
|
||||
saveSession = async (
|
||||
@@ -899,10 +786,6 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
this.setSaveState(id, 0);
|
||||
try {
|
||||
const sessionId = getSessionId(currentSession);
|
||||
const noteId =
|
||||
("note" in currentSession
|
||||
? currentSession.note.id
|
||||
: partial.note?.id) || id;
|
||||
|
||||
if (isLockedSession(currentSession) && partial.content) {
|
||||
logger.debug("Saving locked content", { id });
|
||||
@@ -910,7 +793,7 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
await db.vault.save({
|
||||
content: partial.content,
|
||||
sessionId,
|
||||
id: noteId
|
||||
id
|
||||
});
|
||||
} else {
|
||||
if (partial.content)
|
||||
@@ -930,36 +813,36 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
: undefined,
|
||||
content: partial.content,
|
||||
sessionId,
|
||||
id: noteId
|
||||
id
|
||||
});
|
||||
}
|
||||
|
||||
const note = await db.notes.note(noteId);
|
||||
const note = await db.notes.note(id);
|
||||
if (!note) throw new Error("Note not saved.");
|
||||
|
||||
if (currentSession.type === "new") {
|
||||
const context = useNoteStore.getState().context;
|
||||
if (context) {
|
||||
const { type } = context;
|
||||
if (currentSession.context) {
|
||||
const { type } = currentSession.context;
|
||||
if (type === "notebook")
|
||||
await db.notes.addToNotebook(context.id, noteId);
|
||||
await db.notes.addToNotebook(currentSession.context.id, id);
|
||||
else if (type === "color" || type === "tag")
|
||||
await db.relations.add(
|
||||
{ type, id: context.id },
|
||||
{ type, id: currentSession.context.id },
|
||||
{ id, type: "note" }
|
||||
);
|
||||
} else {
|
||||
const defaultNotebook = db.settings.getDefaultNotebook();
|
||||
if (defaultNotebook)
|
||||
await db.notes.addToNotebook(defaultNotebook, noteId);
|
||||
await db.notes.addToNotebook(defaultNotebook, id);
|
||||
}
|
||||
}
|
||||
|
||||
const attachmentsLength = await db.attachments
|
||||
.ofNote(note.id, "all")
|
||||
.ofNote(id, "all")
|
||||
.count();
|
||||
const shouldRefreshNotes =
|
||||
currentSession.type === "new" ||
|
||||
!id ||
|
||||
note.title !== currentSession.note?.title ||
|
||||
note.headline !== currentSession.note?.headline ||
|
||||
attachmentsLength !== currentSession.attachmentsLength;
|
||||
@@ -986,6 +869,7 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
}
|
||||
|
||||
this.updateSession(id, ["default"], {
|
||||
preview: false,
|
||||
attachmentsLength: attachmentsLength,
|
||||
note,
|
||||
sessionId
|
||||
@@ -1005,8 +889,8 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
this.setSaveState(id, SaveState.NotSaved);
|
||||
console.error(err);
|
||||
if (err instanceof Error) logger.error(err);
|
||||
if (isLockedSession(currentSession) && "note" in currentSession) {
|
||||
this.get().openSession(currentSession.note, { force: true });
|
||||
if (isLockedSession(currentSession)) {
|
||||
this.get().openSession(id, { force: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1032,55 +916,43 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
};
|
||||
|
||||
newSession = () => {
|
||||
const { activeTabId, activateSession } = this.get();
|
||||
if (!activeTabId) {
|
||||
this.addTab();
|
||||
return;
|
||||
const state = useEditorStore.getState();
|
||||
const session = state.sessions.find((session) => session.type === "new");
|
||||
if (session) {
|
||||
session.context = useNoteStore.getState().context;
|
||||
this.activateSession(session.id);
|
||||
} else {
|
||||
this.addSession({
|
||||
type: "new",
|
||||
id: getId(),
|
||||
context: useNoteStore.getState().context,
|
||||
saveState: SaveState.NotSaved
|
||||
});
|
||||
}
|
||||
|
||||
const session = this.getActiveSession();
|
||||
if (session?.type === "new") return activateSession(session.id);
|
||||
|
||||
const sessionId = tabSessionHistory.add(activeTabId);
|
||||
this.addSession({
|
||||
type: "new",
|
||||
id: sessionId,
|
||||
tabId: activeTabId,
|
||||
saveState: SaveState.NotSaved
|
||||
});
|
||||
};
|
||||
|
||||
closeTabs = (...ids: string[]) => {
|
||||
closeSessions = (...ids: string[]) => {
|
||||
this.set((state) => {
|
||||
const tabs: TabItem[] = [];
|
||||
for (let i = 0; i < state.tabs.length; ++i) {
|
||||
const tab = state.tabs[i];
|
||||
if (!ids.includes(tab.id)) {
|
||||
tabs.push(tab);
|
||||
const sessions: EditorSession[] = [];
|
||||
for (let i = 0; i < state.sessions.length; ++i) {
|
||||
const session = state.sessions[i];
|
||||
if (!ids.includes(session.id)) {
|
||||
sessions.push(session);
|
||||
continue;
|
||||
}
|
||||
|
||||
this.saveSessionContentIfNotSaved(tab.sessionId);
|
||||
this.saveSessionContentIfNotSaved(session.id);
|
||||
|
||||
db.fs().cancel(tab.sessionId).catch(console.error);
|
||||
if (state.history.includes(tab.id))
|
||||
state.history.splice(state.history.indexOf(tab.id), 1);
|
||||
|
||||
const tabHistory = tabSessionHistory.getTabHistory(tab.id);
|
||||
state.sessions = state.sessions.filter((session) => {
|
||||
return (
|
||||
!tabHistory.back.includes(session.id) &&
|
||||
!tabHistory.forward.includes(session.id)
|
||||
);
|
||||
});
|
||||
tabSessionHistory.clearStackForTab(tab.id);
|
||||
db.fs().cancel(session.id).catch(console.error);
|
||||
if (state.history.includes(session.id))
|
||||
state.history.splice(state.history.indexOf(session.id), 1);
|
||||
}
|
||||
state.tabs = tabs;
|
||||
state.sessions = sessions;
|
||||
});
|
||||
|
||||
const { history, tabs } = this.get();
|
||||
this.focusTab(history.pop());
|
||||
if (tabs.length === 0) this.addTab();
|
||||
const { history, sessions } = this.get();
|
||||
this.activateSession(history.pop());
|
||||
if (sessions.length === 0) this.newSession();
|
||||
};
|
||||
|
||||
setTitle = (id: string, title: string) => {
|
||||
@@ -1132,90 +1004,23 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
this.set({ editorMargins: editorMarginsState });
|
||||
Config.set("editor:margins", editorMarginsState);
|
||||
};
|
||||
|
||||
getActiveTab = () => {
|
||||
const activeTabId = this.get().activeTabId;
|
||||
return this.get().tabs.find((t) => t.id === activeTabId);
|
||||
};
|
||||
|
||||
getActiveNote = () => {
|
||||
const session = this.getActiveSession();
|
||||
return session && "note" in session ? session.note : undefined;
|
||||
};
|
||||
|
||||
isNoteOpen = (noteId: string) => {
|
||||
return this.getActiveNote()?.id === noteId;
|
||||
};
|
||||
|
||||
getActiveSession = <T extends SessionType[]>(
|
||||
types?: T
|
||||
): SessionTypeMap[T[number]] | undefined => {
|
||||
const activeTab = this.getActiveTab();
|
||||
if (!activeTab) return;
|
||||
const session = this.getSession(activeTab.sessionId);
|
||||
if (session && (!types || types.includes(session.type)))
|
||||
return session as SessionTypeMap[T[number]];
|
||||
};
|
||||
|
||||
addTab = (sessionId?: string) => {
|
||||
const id = getId();
|
||||
const newSessionId = sessionId || tabSessionHistory.add(id);
|
||||
this.set((state) => {
|
||||
state.tabs.push({
|
||||
id,
|
||||
sessionId: newSessionId
|
||||
});
|
||||
});
|
||||
if (!sessionId)
|
||||
this.addSession({
|
||||
type: "new",
|
||||
tabId: id,
|
||||
id: newSessionId,
|
||||
saveState: SaveState.NotSaved
|
||||
});
|
||||
this.focusTab(id);
|
||||
return id;
|
||||
};
|
||||
|
||||
focusTab = (tabId: string | undefined, sessionId?: string) => {
|
||||
if (!tabId) return;
|
||||
|
||||
const { history } = this.get();
|
||||
if (history.includes(tabId)) history.splice(history.indexOf(tabId), 1);
|
||||
history.push(tabId);
|
||||
|
||||
this.set({
|
||||
activeTabId: tabId,
|
||||
canGoBack: tabSessionHistory.canGoBack(tabId),
|
||||
canGoForward: tabSessionHistory.canGoForward(tabId)
|
||||
});
|
||||
|
||||
sessionId =
|
||||
sessionId || this.get().tabs.find((t) => t.id === tabId)?.sessionId;
|
||||
if (sessionId) this.rehydrateSession(sessionId);
|
||||
};
|
||||
}
|
||||
|
||||
const useEditorStore = createPersistedStore(EditorStore, {
|
||||
name: "editor-sessions-v2",
|
||||
name: "editor-sessions",
|
||||
partialize: (state) => ({
|
||||
history: state.history,
|
||||
activeSessionId: state.activeSessionId,
|
||||
arePropertiesVisible: state.arePropertiesVisible,
|
||||
editorMargins: state.editorMargins,
|
||||
tabs: state.tabs,
|
||||
activeTabId: state.activeTabId,
|
||||
tabHistory: state.tabHistory,
|
||||
canGoBack: state.canGoBack,
|
||||
canGoForward: state.canGoForward,
|
||||
sessions: state.sessions.reduce((sessions, session) => {
|
||||
sessions.push({
|
||||
id: session.id,
|
||||
type: isLockedSession(session) ? "locked" : session.type,
|
||||
needsHydration: session.type === "new" ? false : true,
|
||||
preview: session.preview,
|
||||
pinned: session.pinned,
|
||||
title: session.title,
|
||||
historySessionId:
|
||||
session.type === "diff" ? session.historySessionId : undefined,
|
||||
tabId: session.tabId,
|
||||
note:
|
||||
"note" in session
|
||||
? {
|
||||
@@ -1232,6 +1037,28 @@ const useEditorStore = createPersistedStore(EditorStore, {
|
||||
});
|
||||
export { useEditorStore, SESSION_STATES };
|
||||
|
||||
function findSessionIndices(
|
||||
sessions: EditorSession[],
|
||||
session: EditorSession,
|
||||
activeSessionId?: string
|
||||
) {
|
||||
let activeSessionIndex = -1;
|
||||
let previewSessionIndex = -1;
|
||||
let duplicateSessionIndex = -1;
|
||||
for (let i = 0; i < sessions.length; ++i) {
|
||||
const { id, preview } = sessions[i];
|
||||
if (id === session.id) duplicateSessionIndex = i;
|
||||
else if (preview && session.preview) previewSessionIndex = i;
|
||||
else if (id === activeSessionId) activeSessionIndex = i;
|
||||
}
|
||||
|
||||
return {
|
||||
activeSessionIndex,
|
||||
previewSessionIndex,
|
||||
duplicateSessionIndex
|
||||
};
|
||||
}
|
||||
|
||||
const MILLISECONDS_IN_A_MINUTE = 60 * 1000;
|
||||
const SESSION_DURATION = MILLISECONDS_IN_A_MINUTE * 5;
|
||||
function getSessionId(session: DefaultEditorSession | NewEditorSession) {
|
||||
|
||||
@@ -72,9 +72,7 @@ export async function* exportNotes(
|
||||
const pathTree = new PathTree();
|
||||
const notePathMap: Map<string, string[]> = new Map();
|
||||
|
||||
for await (const note of notes
|
||||
.fields(["notes.id", "notes.title"])
|
||||
.iterate()) {
|
||||
for await (const note of notes.fields(["notes.id", "notes.title"])) {
|
||||
const filename = `${sanitizeFilename(note.title || "Untitled", {
|
||||
replacement: "-"
|
||||
})}.${FORMAT_TO_EXT[format]}`;
|
||||
|
||||
@@ -27,4 +27,3 @@ export * from "./resolve-items.js";
|
||||
export * from "./migrate-toolbar.js";
|
||||
export * from "./export-notes.js";
|
||||
export * from "./dataurl.js";
|
||||
export * from "./tab-session-history.js";
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
/*
|
||||
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 { getId } from "@notesnook/core";
|
||||
|
||||
export type TabHistory = Record<
|
||||
string,
|
||||
{ backStack: string[]; forwardStack: string[] }
|
||||
>;
|
||||
export type TabState = {
|
||||
tabSessionHistory: TabHistory;
|
||||
canGoBack?: boolean;
|
||||
canGoForward?: boolean;
|
||||
};
|
||||
|
||||
export class TabSessionHistory {
|
||||
constructor(
|
||||
public options: {
|
||||
set: (state: TabState) => void;
|
||||
get: () => TabState;
|
||||
}
|
||||
) {}
|
||||
|
||||
getBackStack(id: string) {
|
||||
const tabHistory = this.options.get().tabSessionHistory[id];
|
||||
if (!tabHistory) return [];
|
||||
return tabHistory.backStack.slice();
|
||||
}
|
||||
|
||||
getForwardStack(id: string) {
|
||||
const tabHistory = this.options.get().tabSessionHistory[id];
|
||||
if (!tabHistory) return [];
|
||||
return tabHistory.forwardStack.slice();
|
||||
}
|
||||
|
||||
setBackStack(id: string, value: string[]) {
|
||||
const tabHistory = this.options.get().tabSessionHistory;
|
||||
this.options.set({
|
||||
canGoBack: value.length > 1,
|
||||
tabSessionHistory: {
|
||||
...tabHistory,
|
||||
[id]: {
|
||||
...(tabHistory[id] || {}),
|
||||
backStack: value
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setForwardStack(id: string, value: string[]) {
|
||||
const tabHistory = this.options.get().tabSessionHistory;
|
||||
this.options.set({
|
||||
canGoForward: value.length > 0,
|
||||
tabSessionHistory: {
|
||||
...tabHistory,
|
||||
[id]: {
|
||||
...(tabHistory[id] || {}),
|
||||
forwardStack: value
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
add(id: string, sessionId?: string) {
|
||||
sessionId = sessionId || getId();
|
||||
const back_stack = this.getBackStack(id);
|
||||
back_stack.push(sessionId);
|
||||
this.setBackStack(id, back_stack);
|
||||
this.setForwardStack(id, []);
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
clearStackForTab(tabId: string) {
|
||||
this.options.set({
|
||||
tabSessionHistory: {
|
||||
...this.options.get().tabSessionHistory,
|
||||
[tabId]: {
|
||||
backStack: [],
|
||||
forwardStack: []
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
back(id: string): string | null {
|
||||
if (!this.canGoBack(id)) return null;
|
||||
|
||||
const backStack = this.getBackStack(id);
|
||||
const forwardStack = this.getForwardStack(id);
|
||||
|
||||
const currentItem = backStack.pop();
|
||||
const nextItem = backStack[backStack.length - 1];
|
||||
|
||||
currentItem && forwardStack.push(currentItem);
|
||||
|
||||
this.setForwardStack(id, forwardStack);
|
||||
this.setBackStack(id, backStack);
|
||||
|
||||
return nextItem;
|
||||
}
|
||||
|
||||
remove(tabId: string, sessionId: string) {
|
||||
const backStack = this.getBackStack(tabId);
|
||||
let index = backStack.findIndex((item) => item === sessionId);
|
||||
if (index === -1) {
|
||||
const forwardStack = this.getForwardStack(tabId);
|
||||
index = forwardStack.findIndex((item) => item === sessionId);
|
||||
forwardStack.splice(index, 1);
|
||||
this.setForwardStack(tabId, forwardStack);
|
||||
} else {
|
||||
backStack.splice(index, 1);
|
||||
this.setBackStack(tabId, backStack);
|
||||
}
|
||||
}
|
||||
|
||||
forward(id: string): string | null {
|
||||
if (!this.canGoForward(id)) return null;
|
||||
|
||||
const backStack = this.getBackStack(id);
|
||||
const forwardStack = this.getForwardStack(id);
|
||||
|
||||
const item = forwardStack.pop() as string;
|
||||
this.setForwardStack(id, forwardStack);
|
||||
backStack.push(item);
|
||||
this.setBackStack(id, backStack);
|
||||
return item;
|
||||
}
|
||||
|
||||
currentSessionId(id: string) {
|
||||
const { back } = this.getTabHistory(id);
|
||||
return back[back.length - 1];
|
||||
}
|
||||
|
||||
getTabHistory(id: string) {
|
||||
const tabHistory = this.options.get().tabSessionHistory[id];
|
||||
if (!tabHistory)
|
||||
return {
|
||||
back: [],
|
||||
forward: []
|
||||
};
|
||||
|
||||
return {
|
||||
back: tabHistory.backStack?.slice() || [],
|
||||
forward: tabHistory.forwardStack?.slice() || []
|
||||
};
|
||||
}
|
||||
|
||||
canGoBack(id: string) {
|
||||
const tabHistory = this.options.get().tabSessionHistory[id];
|
||||
if (!tabHistory) return false;
|
||||
return tabHistory.backStack.length > 1;
|
||||
}
|
||||
|
||||
canGoForward(id: string) {
|
||||
const tabHistory = this.options.get().tabSessionHistory[id];
|
||||
if (!tabHistory) return false;
|
||||
return tabHistory.forwardStack.length >= 1;
|
||||
}
|
||||
}
|
||||
@@ -184,7 +184,7 @@ export default class Lookup {
|
||||
) {
|
||||
const results: Map<string, number> = new Map();
|
||||
const columns = fields.map((f) => f.column);
|
||||
for await (const item of selector.fields(columns).iterate()) {
|
||||
for await (const item of selector.fields(columns)) {
|
||||
if (limit && results.size >= limit) break;
|
||||
|
||||
for (const field of fields) {
|
||||
|
||||
@@ -422,7 +422,7 @@ export class Attachments implements ICollection {
|
||||
async cleanup() {
|
||||
const now = dayjs().unix();
|
||||
const ids: string[] = [];
|
||||
for await (const attachment of this.deleted.iterate()) {
|
||||
for await (const attachment of this.deleted) {
|
||||
if (dayjs(attachment.dateDeleted).add(7, "days").unix() < now) continue;
|
||||
|
||||
const isDeleted = await this.db.fs().deleteFile(attachment.hash);
|
||||
|
||||
@@ -285,32 +285,31 @@ export class Notes implements ICollection {
|
||||
const { format, rawContent } = options;
|
||||
|
||||
const contentString =
|
||||
rawContent === undefined
|
||||
? await (async () => {
|
||||
let contentItem = options.contentItem;
|
||||
if (!contentItem) {
|
||||
const rawContent = await this.db.content.findByNoteId(note.id);
|
||||
if (rawContent && rawContent.locked) return false;
|
||||
contentItem = rawContent || EMPTY_CONTENT(note.id);
|
||||
}
|
||||
rawContent ||
|
||||
(await (async () => {
|
||||
let contentItem = options.contentItem;
|
||||
if (!contentItem) {
|
||||
const rawContent = await this.db.content.findByNoteId(note.id);
|
||||
if (rawContent && rawContent.locked) return false;
|
||||
contentItem = rawContent || EMPTY_CONTENT(note.id);
|
||||
}
|
||||
|
||||
const { data, type } =
|
||||
options?.embedMedia && format !== "txt"
|
||||
? await this.db.content.downloadMedia(
|
||||
`export-${note.id}`,
|
||||
contentItem,
|
||||
false
|
||||
)
|
||||
: contentItem;
|
||||
const content = await getContentFromData(type, data);
|
||||
return format === "html"
|
||||
? content.toHTML()
|
||||
: format === "md"
|
||||
? content.toMD()
|
||||
: content.toTXT();
|
||||
})()
|
||||
: rawContent;
|
||||
if (contentString === false) return false;
|
||||
const { data, type } =
|
||||
options?.embedMedia && format !== "txt"
|
||||
? await this.db.content.downloadMedia(
|
||||
`export-${note.id}`,
|
||||
contentItem,
|
||||
false
|
||||
)
|
||||
: contentItem;
|
||||
const content = await getContentFromData(type, data);
|
||||
return format === "html"
|
||||
? content.toHTML()
|
||||
: format === "md"
|
||||
? content.toMD()
|
||||
: content.toTXT();
|
||||
})());
|
||||
if (!contentString) return false;
|
||||
|
||||
const tags = (await this.db.relations.to(note, "tag").resolve()).map(
|
||||
(tag) => tag.title
|
||||
|
||||
@@ -361,7 +361,7 @@ export default class Backup {
|
||||
};
|
||||
|
||||
let current = 0;
|
||||
for await (const attachment of this.db.attachments.all.iterate()) {
|
||||
for await (const attachment of this.db.attachments.all) {
|
||||
current++;
|
||||
if (
|
||||
!(await this.db
|
||||
|
||||
@@ -476,7 +476,7 @@ export class FilteredSelector<T extends Item> {
|
||||
async *map<TReturnType>(
|
||||
fn: (item: T) => TReturnType
|
||||
): AsyncIterableIterator<TReturnType> {
|
||||
for await (const item of this.iterate()) {
|
||||
for await (const item of this) {
|
||||
yield fn(item);
|
||||
}
|
||||
}
|
||||
@@ -567,43 +567,37 @@ export class FilteredSelector<T extends Item> {
|
||||
);
|
||||
}
|
||||
|
||||
iterate() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
const thisArg = this;
|
||||
return {
|
||||
async *[Symbol.asyncIterator]() {
|
||||
let lastRow: any | null = null;
|
||||
const fields = thisArg._fields.slice();
|
||||
if (fields.length > 0) {
|
||||
if (!fields.find((f) => f.includes(".dateCreated")))
|
||||
fields.push("dateCreated");
|
||||
if (!fields.find((f) => f.includes(".id"))) fields.push("id");
|
||||
}
|
||||
async *[Symbol.asyncIterator]() {
|
||||
let lastRow: any | null = null;
|
||||
const fields = this._fields.slice();
|
||||
if (fields.length > 0) {
|
||||
if (!fields.find((f) => f.includes(".dateCreated")))
|
||||
fields.push("dateCreated");
|
||||
if (!fields.find((f) => f.includes(".id"))) fields.push("id");
|
||||
}
|
||||
|
||||
while (true) {
|
||||
const rows = await thisArg.filter
|
||||
.orderBy("dateCreated asc")
|
||||
.orderBy("id asc")
|
||||
.$if(lastRow !== null, (qb) =>
|
||||
qb.where(
|
||||
(eb) => eb.refTuple("dateCreated", "id"),
|
||||
">",
|
||||
(eb) => eb.tuple(lastRow.dateCreated, lastRow.id)
|
||||
)
|
||||
)
|
||||
.limit(thisArg.batchSize)
|
||||
.$if(fields.length === 0, (eb) => eb.selectAll())
|
||||
.$if(fields.length > 0, (eb) => eb.select(fields))
|
||||
.execute();
|
||||
if (rows.length === 0) break;
|
||||
for (const row of rows) {
|
||||
yield row as T;
|
||||
}
|
||||
|
||||
lastRow = rows[rows.length - 1];
|
||||
}
|
||||
while (true) {
|
||||
const rows = await this.filter
|
||||
.orderBy("dateCreated asc")
|
||||
.orderBy("id asc")
|
||||
.$if(lastRow !== null, (qb) =>
|
||||
qb.where(
|
||||
(eb) => eb.refTuple("dateCreated", "id"),
|
||||
">",
|
||||
(eb) => eb.tuple(lastRow.dateCreated, lastRow.id)
|
||||
)
|
||||
)
|
||||
.limit(this.batchSize)
|
||||
.$if(fields.length === 0, (eb) => eb.selectAll())
|
||||
.$if(fields.length > 0, (eb) => eb.select(fields))
|
||||
.execute();
|
||||
if (rows.length === 0) break;
|
||||
for (const row of rows) {
|
||||
yield row as T;
|
||||
}
|
||||
};
|
||||
|
||||
lastRow = rows[rows.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
private buildSortExpression(options: GroupOptions, hasDueDate?: boolean) {
|
||||
|
||||
33
packages/editor-mobile/package-lock.json
generated
33
packages/editor-mobile/package-lock.json
generated
@@ -14,7 +14,6 @@
|
||||
"@lingui/react": "5.1.2",
|
||||
"@mdi/js": "^7.2.96",
|
||||
"@mdi/react": "^1.6.0",
|
||||
"@notesnook/common": "file:../common",
|
||||
"@notesnook/editor": "file:../editor",
|
||||
"@notesnook/intl": "file:../intl",
|
||||
"@notesnook/theme": "file:../theme",
|
||||
@@ -25,7 +24,6 @@
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-freeze": "^1.0.3",
|
||||
"tinycolor2": "1.6.0",
|
||||
"zustand": "^4.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -35,28 +33,6 @@
|
||||
"react-scripts": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"../common": {
|
||||
"name": "@notesnook/common",
|
||||
"version": "2.1.3",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@notesnook/core": "file:../core",
|
||||
"@readme/data-urls": "^3.0.0",
|
||||
"dayjs": "1.11.13",
|
||||
"pathe": "^1.1.2",
|
||||
"timeago.js": "4.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@notesnook/core": "file:../core",
|
||||
"@types/react": "18.3.5",
|
||||
"react": "18.3.1",
|
||||
"vitest": "2.1.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"timeago.js": "4.0.2"
|
||||
}
|
||||
},
|
||||
"../editor": {
|
||||
"name": "@notesnook/editor",
|
||||
"version": "2.1.3",
|
||||
@@ -3790,10 +3766,6 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@notesnook/common": {
|
||||
"resolved": "../common",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@notesnook/editor": {
|
||||
"resolved": "../editor",
|
||||
"link": true
|
||||
@@ -17639,11 +17611,6 @@
|
||||
"integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/tinycolor2": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz",
|
||||
"integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="
|
||||
},
|
||||
"node_modules/tmpl": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
|
||||
|
||||
@@ -18,9 +18,7 @@
|
||||
"react-freeze": "^1.0.3",
|
||||
"zustand": "^4.4.7",
|
||||
"@lingui/core": "5.1.2",
|
||||
"@lingui/react": "5.1.2",
|
||||
"tinycolor2": "1.6.0",
|
||||
"@notesnook/common": "file:../common"
|
||||
"@lingui/react": "5.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.37.1",
|
||||
|
||||
@@ -22,9 +22,9 @@ import {
|
||||
getFontById,
|
||||
getTableOfContents,
|
||||
TiptapOptions,
|
||||
toBlobURL,
|
||||
usePermissionHandler
|
||||
} from "@notesnook/editor";
|
||||
import { toBlobURL } from "@notesnook/editor";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import FingerprintIcon from "mdi-react/FingerprintIcon";
|
||||
import {
|
||||
@@ -36,11 +36,15 @@ import {
|
||||
useState
|
||||
} from "react";
|
||||
import { useEditorController } from "../hooks/useEditorController";
|
||||
import { useSafeArea } from "../hooks/useSafeArea";
|
||||
import { useSettings } from "../hooks/useSettings";
|
||||
import { TabItem, useTabContext, useTabStore } from "../hooks/useTabStore";
|
||||
import { postAsyncWithTimeout, Settings } from "../utils";
|
||||
import { EditorEvents } from "../utils/editor-events";
|
||||
import {
|
||||
NoteState,
|
||||
TabItem,
|
||||
TabStore,
|
||||
useTabContext,
|
||||
useTabStore
|
||||
} from "../hooks/useTabStore";
|
||||
import { EventTypes, postAsyncWithTimeout, Settings } from "../utils";
|
||||
import { pendingSaveRequests } from "../utils/pending-saves";
|
||||
import Header from "./header";
|
||||
import StatusBar from "./statusbar";
|
||||
@@ -78,43 +82,49 @@ const Tiptap = ({
|
||||
undo,
|
||||
redo
|
||||
});
|
||||
const insets = useSafeArea();
|
||||
tabRef.current = tab;
|
||||
valueRef.current = {
|
||||
undo,
|
||||
redo
|
||||
};
|
||||
|
||||
logger("info", tabRef.current.id, "rendering");
|
||||
function restoreNoteSelection(state?: NoteState) {
|
||||
try {
|
||||
if (!tabRef.current.noteId) return;
|
||||
const noteState =
|
||||
state || useTabStore.getState().noteState[tabRef.current.noteId];
|
||||
|
||||
const restoreNoteSelection = useCallback(
|
||||
(scrollTop?: number, selection?: { to: number; from: number }) => {
|
||||
if (!tabRef.current.session?.noteId) return;
|
||||
const sel = selection || tabRef.current.session?.selection;
|
||||
if (sel && sel.to && sel.from) {
|
||||
if (noteState && (noteState.to || noteState.from)) {
|
||||
const size = editors[tabRef.current.id]?.state.doc.content.size || 0;
|
||||
if (sel.to > 0 && sel.to <= size && sel.from > 0 && sel.from <= size) {
|
||||
if (
|
||||
noteState.to > 0 &&
|
||||
noteState.to <= size &&
|
||||
noteState.from > 0 &&
|
||||
noteState.from <= size
|
||||
) {
|
||||
editors[tabRef.current.id]?.chain().setTextSelection({
|
||||
to: sel.to,
|
||||
from: sel.from
|
||||
to: noteState.to,
|
||||
from: noteState.from
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
containerRef.current?.scrollTo({
|
||||
left: 0,
|
||||
top: scrollTop || tabRef.current.session?.scrollTop || 0,
|
||||
top: noteState?.top || 0,
|
||||
behavior: "auto"
|
||||
});
|
||||
},
|
||||
[]
|
||||
);
|
||||
} catch (e) {
|
||||
logger("error", (e as Error).message, (e as Error).stack);
|
||||
}
|
||||
}
|
||||
|
||||
usePermissionHandler({
|
||||
claims: {
|
||||
premium: settings.premium
|
||||
},
|
||||
onPermissionDenied: () => {
|
||||
post(EditorEvents.pro, undefined, tabRef.current.id, tab.session?.noteId);
|
||||
post(EventTypes.pro, undefined, tabRef.current.id, tab.noteId);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -151,14 +161,14 @@ const Tiptap = ({
|
||||
) as Promise<string | undefined>;
|
||||
},
|
||||
createInternalLink(attributes) {
|
||||
return postAsyncWithTimeout(EditorEvents.createInternalLink, {
|
||||
return postAsyncWithTimeout(EventTypes.createInternalLink, {
|
||||
attributes
|
||||
});
|
||||
},
|
||||
element: getContentDiv(),
|
||||
editable: !tab.session?.readonly,
|
||||
editable: !tab.readonly,
|
||||
editorProps: {
|
||||
editable: () => !tab.session?.readonly,
|
||||
editable: () => !tab.readonly,
|
||||
handlePaste: (view, event) => {
|
||||
const hasFiles = event.clipboardData?.types?.some((type) =>
|
||||
type.startsWith("Files")
|
||||
@@ -201,12 +211,19 @@ const Tiptap = ({
|
||||
copyToClipboard: (text) => {
|
||||
globalThis.editorControllers[tab.id]?.copyToClipboard(text);
|
||||
},
|
||||
placeholder: strings.startWritingNote(),
|
||||
onSelectionUpdate: () => {
|
||||
if (tabRef.current.session?.noteId) {
|
||||
if (tabRef.current.noteId) {
|
||||
const noteId = tabRef.current.noteId;
|
||||
clearTimeout(noteStateUpdateTimer.current);
|
||||
noteStateUpdateTimer.current = setTimeout(() => {
|
||||
if (tabRef.current.noteId !== noteId) return;
|
||||
const { to, from } =
|
||||
editors[tabRef.current?.id]?.state.selection || {};
|
||||
useTabStore.getState().setNoteState(noteId, {
|
||||
to,
|
||||
from
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
},
|
||||
@@ -225,7 +242,7 @@ const Tiptap = ({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
getContentDiv,
|
||||
tab.session?.readonly,
|
||||
tab.readonly,
|
||||
settings.doubleSpacedLines,
|
||||
settings.corsProxy,
|
||||
settings.dateFormat,
|
||||
@@ -235,21 +252,15 @@ const Tiptap = ({
|
||||
tick
|
||||
]);
|
||||
|
||||
const update = useCallback(
|
||||
(scrollTop?: number, selection?: { to: number; from: number }) => {
|
||||
setTick((tick) => tick + 1);
|
||||
globalThis.editorControllers[tabRef.current.id]?.setTitlePlaceholder(
|
||||
strings.noteTitle()
|
||||
);
|
||||
setTimeout(() => {
|
||||
editorControllers[tabRef.current.id]?.setLoading(false);
|
||||
setTimeout(() => {
|
||||
restoreNoteSelection(scrollTop, selection);
|
||||
}, 300);
|
||||
}, 1);
|
||||
},
|
||||
[restoreNoteSelection]
|
||||
);
|
||||
const update = useCallback(() => {
|
||||
setTick((tick) => tick + 1);
|
||||
globalThis.editorControllers[tabRef.current.id]?.setTitlePlaceholder(
|
||||
strings.noteTitle()
|
||||
);
|
||||
setTimeout(() => {
|
||||
editorControllers[tabRef.current.id]?.setLoading(false);
|
||||
}, 300);
|
||||
}, []);
|
||||
|
||||
const controller = useEditorController({
|
||||
update,
|
||||
@@ -290,40 +301,60 @@ const Tiptap = ({
|
||||
});
|
||||
}
|
||||
|
||||
const updateFocusedTab = () => {
|
||||
const updateScrollPosition = (state: TabStore) => {
|
||||
if (isFocusedRef.current) return;
|
||||
isFocusedRef.current = true;
|
||||
const noteId = useTabStore
|
||||
.getState()
|
||||
.tabs.find((tab) => tab.id === useTabStore.getState().currentTab)
|
||||
?.session?.noteId;
|
||||
post(
|
||||
EditorEvents.tabFocused,
|
||||
undefined,
|
||||
useTabStore.getState().currentTab,
|
||||
noteId
|
||||
);
|
||||
editorControllers[tabRef.current.id]?.updateTab();
|
||||
if (state.currentTab === tabRef.current.id) {
|
||||
isFocusedRef.current = true;
|
||||
const noteState = tabRef.current.noteId
|
||||
? state.noteState[tabRef.current.noteId]
|
||||
: undefined;
|
||||
|
||||
restoreNoteSelection();
|
||||
post(
|
||||
EventTypes.tabFocused,
|
||||
!!globalThis.editorControllers[tabRef.current.id]?.content.current &&
|
||||
!editorControllers[tabRef.current.id]?.loading,
|
||||
tabRef.current.id,
|
||||
state.getCurrentNoteId()
|
||||
);
|
||||
editorControllers[tabRef.current.id]?.updateTab();
|
||||
|
||||
if (
|
||||
!globalThis.editorControllers[tabRef.current.id]?.content.current &&
|
||||
tabRef.current.session?.noteId
|
||||
) {
|
||||
editorControllers[tabRef.current.id]?.setLoading(true);
|
||||
if (noteState) {
|
||||
if (
|
||||
containerRef.current &&
|
||||
containerRef.current?.scrollHeight < noteState.top
|
||||
) {
|
||||
console.log("Container too small to scroll.");
|
||||
return;
|
||||
}
|
||||
|
||||
restoreNoteSelection(noteState);
|
||||
} else {
|
||||
containerRef.current?.scrollTo({
|
||||
left: 0,
|
||||
top: 0,
|
||||
behavior: "auto"
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
!globalThis.editorControllers[tabRef.current.id]?.content.current &&
|
||||
tabRef.current.noteId
|
||||
) {
|
||||
editorControllers[tabRef.current.id]?.setLoading(true);
|
||||
}
|
||||
} else {
|
||||
isFocusedRef.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
updateFocusedTab();
|
||||
updateScrollPosition(useTabStore.getState());
|
||||
|
||||
const unsub = useTabStore.subscribe((state, prevState) => {
|
||||
if (state.currentTab !== tabRef.current.id) {
|
||||
isFocusedRef.current = false;
|
||||
}
|
||||
if (state.currentTab === prevState.currentTab && isFocusedRef.current)
|
||||
return;
|
||||
updateFocusedTab();
|
||||
if (state.currentTab === prevState.currentTab) return;
|
||||
updateScrollPosition(state);
|
||||
logger("info", "updating scroll position");
|
||||
});
|
||||
logger("info", tabRef.current.id, "active");
|
||||
@@ -332,7 +363,7 @@ const Tiptap = ({
|
||||
logger("info", tabRef.current.id, "inactive");
|
||||
unsub();
|
||||
};
|
||||
}, [getContentDiv, restoreNoteSelection]);
|
||||
}, [getContentDiv]);
|
||||
|
||||
const onClickEmptyArea: React.MouseEventHandler<HTMLDivElement> = useCallback(
|
||||
(event) => {
|
||||
@@ -526,7 +557,7 @@ const Tiptap = ({
|
||||
position: "relative"
|
||||
}}
|
||||
>
|
||||
{settings.noHeader || tab.session?.locked ? null : (
|
||||
{settings.noHeader || tab.locked ? null : (
|
||||
<>
|
||||
<Tags settings={settings} loading={controller.loading} />
|
||||
<Title
|
||||
@@ -547,7 +578,7 @@ const Tiptap = ({
|
||||
</>
|
||||
)}
|
||||
|
||||
{controller.loading || tab.session?.locked ? (
|
||||
{controller.loading || tab.locked ? (
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
@@ -559,13 +590,13 @@ const Tiptap = ({
|
||||
paddingLeft: 12,
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: tab.session?.locked ? "center" : "flex-start",
|
||||
justifyContent: tab.session?.locked ? "center" : "flex-start",
|
||||
alignItems: tab.locked ? "center" : "flex-start",
|
||||
justifyContent: tab.locked ? "center" : "flex-start",
|
||||
boxSizing: "border-box",
|
||||
rowGap: 10
|
||||
}}
|
||||
>
|
||||
{tab.session?.locked ? (
|
||||
{tab.locked ? (
|
||||
<>
|
||||
<p
|
||||
style={{
|
||||
@@ -818,7 +849,7 @@ const Tiptap = ({
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: tab.session?.locked ? "none" : "block"
|
||||
display: tab.locked ? "none" : "block"
|
||||
}}
|
||||
ref={contentPlaceholderRef}
|
||||
className="theme-scope-editor"
|
||||
@@ -826,11 +857,11 @@ const Tiptap = ({
|
||||
|
||||
<div
|
||||
onClick={(e) => {
|
||||
if (tab.session?.locked) return;
|
||||
if (tab.locked) return;
|
||||
onClickBottomArea();
|
||||
}}
|
||||
onMouseDown={(e) => {
|
||||
if (tab.session?.locked) return;
|
||||
if (tab.locked) return;
|
||||
if (globalThis.keyboardShown) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
@@ -19,22 +19,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { ControlledMenu, MenuItem as MenuItemInner } from "@szhsin/react-menu";
|
||||
import ArrowBackIcon from "mdi-react/ArrowBackIcon";
|
||||
import ArrowForwardIcon from "mdi-react/ArrowForwardIcon";
|
||||
import ArrowULeftTopIcon from "mdi-react/ArrowULeftTopIcon";
|
||||
import ArrowURightTopIcon from "mdi-react/ArrowURightTopIcon";
|
||||
import DotsHorizontalIcon from "mdi-react/DotsHorizontalIcon";
|
||||
import DotsVerticalIcon from "mdi-react/DotsVerticalIcon";
|
||||
import FullscreenIcon from "mdi-react/FullscreenIcon";
|
||||
import MagnifyIcon from "mdi-react/MagnifyIcon";
|
||||
import PlusIcon from "mdi-react/PlusIcon";
|
||||
|
||||
import PencilLockIcon from "mdi-react/PencilLockIcon";
|
||||
import TableOfContentsIcon from "mdi-react/TableOfContentsIcon";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { useSafeArea } from "../hooks/useSafeArea";
|
||||
import { useTabContext, useTabStore } from "../hooks/useTabStore";
|
||||
import { Settings } from "../utils";
|
||||
import { EditorEvents } from "../utils/editor-events";
|
||||
import { EventTypes, Settings } from "../utils";
|
||||
import styles from "./styles.module.css";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
@@ -104,10 +100,6 @@ function Header({
|
||||
const openedTabsCount = useTabStore((state) => state.tabs.length);
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
const btnRef = useRef(null);
|
||||
const [canGoBack, canGoForward] = useTabStore((state) => [
|
||||
state.canGoBack,
|
||||
state.canGoForward
|
||||
]);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -139,7 +131,7 @@ function Header({
|
||||
) : (
|
||||
<Button
|
||||
onPress={() => {
|
||||
post(EditorEvents.back, undefined, tab.id, tab.session?.noteId);
|
||||
post(EventTypes.back, undefined, tab.id, tab.noteId);
|
||||
}}
|
||||
preventDefault={false}
|
||||
style={{
|
||||
@@ -173,15 +165,75 @@ function Header({
|
||||
flexDirection: "row"
|
||||
}}
|
||||
>
|
||||
{tab.locked ? null : (
|
||||
<>
|
||||
<Button
|
||||
onPress={() => {
|
||||
editor?.commands.undo();
|
||||
}}
|
||||
style={{
|
||||
borderWidth: 0,
|
||||
borderRadius: 100,
|
||||
color: "var(--nn_primary_icon)",
|
||||
marginRight: 10,
|
||||
width: 39,
|
||||
height: 39,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "relative"
|
||||
}}
|
||||
>
|
||||
<ArrowULeftTopIcon
|
||||
color={
|
||||
!hasUndo
|
||||
? "var(--nn_secondary_border)"
|
||||
: "var(--nn_primary_icon)"
|
||||
}
|
||||
size={25 * settings.fontScale}
|
||||
style={{
|
||||
position: "absolute"
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onPress={() => {
|
||||
if (tab.locked) return;
|
||||
editor?.commands.redo();
|
||||
}}
|
||||
style={{
|
||||
borderWidth: 0,
|
||||
borderRadius: 100,
|
||||
color: "var(--nn_primary_icon)",
|
||||
marginRight: 10,
|
||||
width: 39,
|
||||
height: 39,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "relative"
|
||||
}}
|
||||
>
|
||||
<ArrowURightTopIcon
|
||||
color={
|
||||
!hasRedo
|
||||
? "var(--nn_secondary_border)"
|
||||
: "var(--nn_primary_icon)"
|
||||
}
|
||||
size={25 * settings.fontScale}
|
||||
style={{
|
||||
position: "absolute"
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{settings.deviceMode !== "mobile" && !settings.fullscreen ? (
|
||||
<Button
|
||||
onPress={() => {
|
||||
post(
|
||||
EditorEvents.fullscreen,
|
||||
undefined,
|
||||
tab.id,
|
||||
tab.session?.noteId
|
||||
);
|
||||
post(EventTypes.fullscreen, undefined, tab.id, tab.noteId);
|
||||
}}
|
||||
preventDefault={false}
|
||||
style={{
|
||||
@@ -207,12 +259,14 @@ function Header({
|
||||
</Button>
|
||||
) : null}
|
||||
|
||||
{tab.session?.readonly ? (
|
||||
{tab.readonly ? (
|
||||
<Button
|
||||
onPress={() => {
|
||||
post(
|
||||
"editor-events:disable-readonly-mode",
|
||||
tab.session?.noteId
|
||||
useTabStore
|
||||
.getState()
|
||||
.getNoteIdForTab(useTabStore.getState().currentTab)
|
||||
);
|
||||
}}
|
||||
fwdRef={btnRef}
|
||||
@@ -242,72 +296,7 @@ function Header({
|
||||
|
||||
<Button
|
||||
onPress={() => {
|
||||
editor?.commands.undo();
|
||||
}}
|
||||
style={{
|
||||
borderWidth: 0,
|
||||
borderRadius: 100,
|
||||
color: "var(--nn_primary_icon)",
|
||||
marginRight: 10,
|
||||
width: 39,
|
||||
height: 39,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "relative"
|
||||
}}
|
||||
>
|
||||
<ArrowULeftTopIcon
|
||||
color={
|
||||
!hasUndo
|
||||
? "var(--nn_secondary_border)"
|
||||
: "var(--nn_primary_icon)"
|
||||
}
|
||||
size={25 * settings.fontScale}
|
||||
style={{
|
||||
position: "absolute"
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onPress={() => {
|
||||
editor?.commands.redo();
|
||||
}}
|
||||
style={{
|
||||
borderWidth: 0,
|
||||
borderRadius: 100,
|
||||
color: "var(--nn_primary_icon)",
|
||||
marginRight: 10,
|
||||
width: 39,
|
||||
height: 39,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "relative"
|
||||
}}
|
||||
>
|
||||
<ArrowURightTopIcon
|
||||
color={
|
||||
!hasRedo
|
||||
? "var(--nn_secondary_border)"
|
||||
: "var(--nn_primary_icon)"
|
||||
}
|
||||
size={25 * settings.fontScale}
|
||||
style={{
|
||||
position: "absolute"
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onPress={() => {
|
||||
post(
|
||||
EditorEvents.showTabs,
|
||||
undefined,
|
||||
tab.id,
|
||||
tab.session?.noteId
|
||||
);
|
||||
post(EventTypes.showTabs, undefined, tab.id, tab.noteId);
|
||||
}}
|
||||
preventDefault={false}
|
||||
style={{
|
||||
@@ -351,13 +340,8 @@ function Header({
|
||||
<Button
|
||||
fwdRef={btnRef}
|
||||
onPress={() => {
|
||||
if (tab.session?.locked) {
|
||||
post(
|
||||
EditorEvents.properties,
|
||||
undefined,
|
||||
tab.id,
|
||||
tab.session?.noteId
|
||||
);
|
||||
if (tab.locked) {
|
||||
post(EventTypes.properties, undefined, tab.id, tab.noteId);
|
||||
} else {
|
||||
setOpen(!isOpen);
|
||||
}
|
||||
@@ -376,7 +360,7 @@ function Header({
|
||||
position: "relative"
|
||||
}}
|
||||
>
|
||||
{tab.session?.locked ? (
|
||||
{tab.locked ? (
|
||||
<DotsHorizontalIcon
|
||||
size={25 * settings.fontScale}
|
||||
style={{
|
||||
@@ -411,153 +395,33 @@ function Header({
|
||||
switch (e.value) {
|
||||
case "toc":
|
||||
post(
|
||||
EditorEvents.toc,
|
||||
EventTypes.toc,
|
||||
editorControllers[tab.id]?.getTableOfContents(),
|
||||
tab.id,
|
||||
tab.session?.noteId
|
||||
tab.noteId
|
||||
);
|
||||
break;
|
||||
case "search":
|
||||
editor?.commands.startSearch();
|
||||
break;
|
||||
case "newNote":
|
||||
post(
|
||||
EditorEvents.newNote,
|
||||
undefined,
|
||||
tab.id,
|
||||
tab.session?.noteId
|
||||
);
|
||||
break;
|
||||
case "properties":
|
||||
post(
|
||||
EditorEvents.properties,
|
||||
undefined,
|
||||
tab.id,
|
||||
tab.session?.noteId
|
||||
);
|
||||
logger("info", "post properties...");
|
||||
post(EventTypes.properties, undefined, tab.id, tab.noteId);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: 10,
|
||||
alignItems: "center",
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
flex: 1,
|
||||
paddingTop: 5
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
onPress={() => {
|
||||
post(
|
||||
EditorEvents.goBack,
|
||||
undefined,
|
||||
tab.id,
|
||||
tab.session?.noteId
|
||||
);
|
||||
setOpen(false);
|
||||
}}
|
||||
style={{
|
||||
borderWidth: 0,
|
||||
borderRadius: 100,
|
||||
color: "var(--nn_primary_icon)",
|
||||
width: 39,
|
||||
height: 39,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "relative"
|
||||
}}
|
||||
>
|
||||
<ArrowBackIcon
|
||||
color={
|
||||
!canGoBack
|
||||
? "var(--nn_secondary_border)"
|
||||
: "var(--nn_primary_icon)"
|
||||
}
|
||||
size={25 * settings.fontScale}
|
||||
style={{
|
||||
position: "absolute"
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onPress={() => {
|
||||
post(
|
||||
EditorEvents.goForward,
|
||||
undefined,
|
||||
tab.id,
|
||||
tab.session?.noteId
|
||||
);
|
||||
setOpen(false);
|
||||
}}
|
||||
style={{
|
||||
borderWidth: 0,
|
||||
borderRadius: 100,
|
||||
color: "var(--nn_primary_icon)",
|
||||
width: 39,
|
||||
height: 39,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "relative"
|
||||
}}
|
||||
>
|
||||
<ArrowForwardIcon
|
||||
color={
|
||||
!canGoForward
|
||||
? "var(--nn_secondary_border)"
|
||||
: "var(--nn_primary_icon)"
|
||||
}
|
||||
size={25 * settings.fontScale}
|
||||
style={{
|
||||
position: "absolute"
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onPress={() => {
|
||||
editor?.commands.startSearch();
|
||||
setOpen(false);
|
||||
}}
|
||||
style={{
|
||||
borderWidth: 0,
|
||||
borderRadius: 100,
|
||||
color: "var(--nn_primary_icon)",
|
||||
width: 39,
|
||||
height: 39,
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "relative"
|
||||
}}
|
||||
>
|
||||
<MagnifyIcon
|
||||
size={28 * settings.fontScale}
|
||||
style={{
|
||||
position: "absolute"
|
||||
}}
|
||||
color="var(--nn_primary_icon)"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<MenuItem
|
||||
value="newNote"
|
||||
value="search"
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: 10,
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<PlusIcon
|
||||
<MagnifyIcon
|
||||
size={22 * settings.fontScale}
|
||||
color="var(--nn_primary_icon)"
|
||||
/>
|
||||
@@ -566,7 +430,7 @@ function Header({
|
||||
color: "var(--nn_primary_paragraph)"
|
||||
}}
|
||||
>
|
||||
New note
|
||||
{strings.search()}
|
||||
</span>
|
||||
</MenuItem>
|
||||
|
||||
|
||||
@@ -27,8 +27,7 @@ import {
|
||||
useState
|
||||
} from "react";
|
||||
import { useSettings } from "../hooks/useSettings";
|
||||
import { Settings, isReactNative, randId } from "../utils";
|
||||
import { EditorEvents } from "../utils/editor-events";
|
||||
import { EventTypes, Settings, isReactNative, randId } from "../utils";
|
||||
|
||||
export const ReadonlyEditorProvider = (): JSX.Element => {
|
||||
const settings = useSettings();
|
||||
@@ -96,7 +95,7 @@ const Tiptap = ({
|
||||
delete pendingResolvers[resolverId];
|
||||
resolve(data);
|
||||
};
|
||||
post(EditorEvents.getAttachmentData, {
|
||||
post(EventTypes.getAttachmentData, {
|
||||
attachment,
|
||||
resolverId: resolverId
|
||||
});
|
||||
@@ -143,7 +142,7 @@ const Tiptap = ({
|
||||
if (isSafari) {
|
||||
root = window;
|
||||
}
|
||||
post(EditorEvents.readonlyEditorLoaded);
|
||||
post(EventTypes.readonlyEditorLoaded);
|
||||
|
||||
const onMessage = (event: any) => {
|
||||
if (event?.data?.[0] !== "{") return;
|
||||
|
||||
@@ -18,8 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Settings } from "../utils";
|
||||
import { EditorEvents } from "../utils/editor-events";
|
||||
import { EventTypes, Settings } from "../utils";
|
||||
import styles from "./styles.module.css";
|
||||
import { useTabContext } from "../hooks/useTabStore";
|
||||
import { strings } from "@notesnook/intl";
|
||||
@@ -46,7 +45,7 @@ function Tags(props: { settings: Settings; loading?: boolean }): JSX.Element {
|
||||
editor.commands.blur();
|
||||
editorTitles[tab.id]?.current?.blur();
|
||||
}
|
||||
post(EditorEvents.newtag, undefined, tab.id, tab.session?.noteId);
|
||||
post(EventTypes.newtag, undefined, tab.id, tab.noteId);
|
||||
};
|
||||
const fontScale = props.settings?.fontScale || 1;
|
||||
|
||||
@@ -127,7 +126,7 @@ function Tags(props: { settings: Settings; loading?: boolean }): JSX.Element {
|
||||
}}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
post(EditorEvents.tag, tag, tab.id, tab.session?.noteId);
|
||||
post(EventTypes.tag, tag, tab.id, tab.noteId);
|
||||
}}
|
||||
>
|
||||
#{tag.alias}
|
||||
|
||||
@@ -36,7 +36,7 @@ export default function TiptapEditorWrapper(props: {
|
||||
|
||||
return (
|
||||
<>
|
||||
{tab.session?.locked ? null : (
|
||||
{tab.locked ? null : (
|
||||
<EmotionEditorToolbarTheme>
|
||||
<Toolbar
|
||||
className="theme-scope-editorToolbar"
|
||||
|
||||
@@ -17,7 +17,8 @@ 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 { getFontById, replaceDateTime } from "@notesnook/editor";
|
||||
import { getFontById } from "@notesnook/editor";
|
||||
import { replaceDateTime } from "@notesnook/editor";
|
||||
import React, { RefObject, useCallback, useEffect, useRef } from "react";
|
||||
import { EditorController } from "../hooks/useEditorController";
|
||||
import { useTabContext } from "../hooks/useTabStore";
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
useState
|
||||
} from "react";
|
||||
import {
|
||||
EventTypes,
|
||||
getRoot,
|
||||
isReactNative,
|
||||
post,
|
||||
@@ -39,7 +40,6 @@ import {
|
||||
saveTheme
|
||||
} from "../utils";
|
||||
import { injectCss, transform } from "../utils/css";
|
||||
import { EditorEvents } from "../utils/editor-events";
|
||||
import { pendingSaveRequests } from "../utils/pending-saves";
|
||||
import { useTabContext, useTabStore } from "./useTabStore";
|
||||
|
||||
@@ -133,10 +133,7 @@ export function useEditorController({
|
||||
scrollTo,
|
||||
scrollTop
|
||||
}: {
|
||||
update: (
|
||||
scrollTop?: number,
|
||||
selection?: { to: number; from: number }
|
||||
) => void;
|
||||
update: () => void;
|
||||
getTableOfContents: () => any[];
|
||||
scrollTo: (top: number) => void;
|
||||
scrollTop: () => number;
|
||||
@@ -146,7 +143,7 @@ export function useEditorController({
|
||||
const tabRef = useRef(tab);
|
||||
tabRef.current = tab;
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const setTheme = useThemeEngineStore((store) => store.setTheme);
|
||||
const { colors } = useThemeColors("editor");
|
||||
const [title, setTitle] = useState("");
|
||||
@@ -160,12 +157,8 @@ export function useEditorController({
|
||||
scroll: null
|
||||
});
|
||||
|
||||
if (!tabRef.current.session?.noteId && loading) {
|
||||
setTimeout(() => {
|
||||
if (!tabRef.current.session?.noteId && loading) {
|
||||
setLoading(false);
|
||||
}
|
||||
}, 3000);
|
||||
if (!tabRef.current.noteId && loading) {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
const selectionChange = useCallback((_editor: Editor) => {}, []);
|
||||
@@ -174,22 +167,21 @@ export function useEditorController({
|
||||
if (!isReactNative()) return;
|
||||
const currentSessionId = globalThis.sessionId;
|
||||
post(
|
||||
EditorEvents.contentchange,
|
||||
EventTypes.contentchange,
|
||||
undefined,
|
||||
tabRef.current.id,
|
||||
tabRef.current.session?.noteId
|
||||
tabRef.current.noteId
|
||||
);
|
||||
const params = [
|
||||
{
|
||||
title
|
||||
},
|
||||
tabRef.current.id,
|
||||
tabRef.current.session?.noteId,
|
||||
currentSessionId,
|
||||
1000
|
||||
tabRef.current.noteId,
|
||||
currentSessionId
|
||||
];
|
||||
const pendingTitleIds = await pendingSaveRequests.getPendingTitleIds();
|
||||
postAsyncWithTimeout(EditorEvents.title, ...params)
|
||||
postAsyncWithTimeout(EventTypes.title, ...params, 1000)
|
||||
.then(() => {
|
||||
if (pendingTitleIds.length) {
|
||||
dbLogger(
|
||||
@@ -238,32 +230,30 @@ export function useEditorController({
|
||||
}
|
||||
const currentSessionId = globalThis.sessionId;
|
||||
post(
|
||||
EditorEvents.contentchange,
|
||||
EventTypes.contentchange,
|
||||
undefined,
|
||||
tabRef.current.id,
|
||||
tabRef.current.session?.noteId
|
||||
tabRef.current.noteId
|
||||
);
|
||||
if (!editor) return;
|
||||
if (typeof timers.current.change === "number") {
|
||||
clearTimeout(timers.current?.change);
|
||||
}
|
||||
|
||||
timers.current.change = setTimeout(async () => {
|
||||
htmlContentRef.current = editor.getHTML();
|
||||
|
||||
const params = [
|
||||
{
|
||||
html: htmlContentRef.current,
|
||||
ignoreEdit: ignoreEdit
|
||||
},
|
||||
tabRef.current.id,
|
||||
tabRef.current.session?.noteId,
|
||||
currentSessionId,
|
||||
5000
|
||||
tabRef.current.noteId,
|
||||
currentSessionId
|
||||
];
|
||||
|
||||
const pendingContentIds =
|
||||
await pendingSaveRequests.getPendingContentIds();
|
||||
postAsyncWithTimeout(EditorEvents.content, ...params)
|
||||
postAsyncWithTimeout(EventTypes.content, ...params, 5000)
|
||||
.then(() => {
|
||||
if (pendingContentIds.length) {
|
||||
dbLogger(
|
||||
@@ -294,7 +284,12 @@ export function useEditorController({
|
||||
}
|
||||
});
|
||||
|
||||
logger("info", "Editor saving content", params[1], params[2]);
|
||||
logger(
|
||||
"info",
|
||||
"Editor saving content",
|
||||
tabRef.current.id,
|
||||
tabRef.current.noteId
|
||||
);
|
||||
}, 300);
|
||||
|
||||
countWords(5000);
|
||||
@@ -308,24 +303,14 @@ export function useEditorController({
|
||||
if (timers.current.scroll !== null) clearTimeout(timers.current.scroll);
|
||||
timers.current.scroll = setTimeout(() => {
|
||||
if (
|
||||
tabRef.current.session?.noteId &&
|
||||
tabRef.current.session?.noteId ===
|
||||
useTabStore.getState().getCurrentNoteId()
|
||||
tabRef.current.noteId &&
|
||||
tabRef.current.noteId === useTabStore.getState().getCurrentNoteId()
|
||||
) {
|
||||
post(
|
||||
EditorEvents.saveScroll,
|
||||
{
|
||||
scrollTop: value,
|
||||
selection: {
|
||||
to: editors[tabRef.current.id]?.state.selection.to,
|
||||
from: editors[tabRef.current.id]?.state.selection.from
|
||||
}
|
||||
},
|
||||
tabRef.current.id,
|
||||
tabRef.current.session?.noteId
|
||||
);
|
||||
useTabStore.getState().setNoteState(tabRef.current.noteId, {
|
||||
top: value
|
||||
});
|
||||
}
|
||||
}, 300);
|
||||
}, 16);
|
||||
},
|
||||
[]
|
||||
);
|
||||
@@ -336,12 +321,12 @@ export function useEditorController({
|
||||
}, [update]);
|
||||
|
||||
useEffect(() => {
|
||||
if (tab.session?.locked) {
|
||||
if (tab.locked) {
|
||||
htmlContentRef.current = "";
|
||||
setLoading(true);
|
||||
onUpdate();
|
||||
}
|
||||
}, [tab.session?.locked, onUpdate]);
|
||||
}, [tab.locked, onUpdate]);
|
||||
|
||||
const onMessage = useCallback(
|
||||
(event: Event & { data?: string }) => {
|
||||
@@ -357,35 +342,38 @@ export function useEditorController({
|
||||
const editor = editors[tabRef.current.id];
|
||||
switch (type) {
|
||||
case "native:updatehtml": {
|
||||
htmlContentRef.current = value.data;
|
||||
htmlContentRef.current = value;
|
||||
logger("info", "UPDATING NOTE HTML");
|
||||
if (tabRef.current.id !== useTabStore.getState().currentTab) {
|
||||
updateTabOnFocus.current = true;
|
||||
} else {
|
||||
if (!editor) break;
|
||||
const noteState = tabRef.current?.noteId
|
||||
? useTabStore.getState().noteState[tabRef.current?.noteId]
|
||||
: null;
|
||||
const top = scrollTop() || noteState?.top || 0;
|
||||
editor?.commands.setContent(htmlContentRef.current, false, {
|
||||
preserveWhitespace: true
|
||||
});
|
||||
|
||||
if (value.selection) {
|
||||
editor.commands.setTextSelection(value.selection);
|
||||
if (noteState && editor.isFocused) {
|
||||
editor.commands.setTextSelection({
|
||||
from: noteState.from,
|
||||
to: noteState.to
|
||||
});
|
||||
}
|
||||
|
||||
scrollTo?.(value.scrollTop || 0);
|
||||
setLoading(false);
|
||||
scrollTo?.(top || 0);
|
||||
countWords(0);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "native:html":
|
||||
if (htmlContentRef.current === value.data) {
|
||||
setLoading(false);
|
||||
break;
|
||||
}
|
||||
htmlContentRef.current = value.data;
|
||||
htmlContentRef.current = value;
|
||||
logger("info", "LOADING NOTE HTML");
|
||||
if (!editor) break;
|
||||
update(value.scrollTop, value.selection);
|
||||
update();
|
||||
setTimeout(() => {
|
||||
countWords(0);
|
||||
}, 300);
|
||||
@@ -419,7 +407,7 @@ export function useEditorController({
|
||||
}
|
||||
post(type); // Notify that message was delivered successfully.
|
||||
},
|
||||
[update, setTheme, scrollTo, countWords]
|
||||
[update, countWords, setTheme]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -430,46 +418,36 @@ export function useEditorController({
|
||||
}, [onMessage]);
|
||||
|
||||
const openFilePicker = useCallback((type: "image" | "file" | "camera") => {
|
||||
post(
|
||||
EditorEvents.filepicker,
|
||||
type,
|
||||
tabRef.current.id,
|
||||
tabRef.current.session?.noteId
|
||||
);
|
||||
post(EventTypes.filepicker, type, tabRef.current.id, tabRef.current.noteId);
|
||||
}, []);
|
||||
|
||||
const downloadAttachment = useCallback((attachment: Attachment) => {
|
||||
post(
|
||||
EditorEvents.download,
|
||||
EventTypes.download,
|
||||
attachment,
|
||||
tabRef.current.id,
|
||||
tabRef.current.session?.noteId
|
||||
tabRef.current.noteId
|
||||
);
|
||||
}, []);
|
||||
const previewAttachment = useCallback((attachment: Attachment) => {
|
||||
post(
|
||||
EditorEvents.previewAttachment,
|
||||
EventTypes.previewAttachment,
|
||||
attachment,
|
||||
tabRef.current.id,
|
||||
tabRef.current.session?.noteId
|
||||
tabRef.current.noteId
|
||||
);
|
||||
}, []);
|
||||
const openLink = useCallback((url: string) => {
|
||||
post(
|
||||
EditorEvents.link,
|
||||
url,
|
||||
tabRef.current.id,
|
||||
tabRef.current.session?.noteId
|
||||
);
|
||||
post(EventTypes.link, url, tabRef.current.id, tabRef.current.noteId);
|
||||
return true;
|
||||
}, []);
|
||||
|
||||
const copyToClipboard = (text: string) => {
|
||||
post(EditorEvents.copyToClipboard, text);
|
||||
post(EventTypes.copyToClipboard, text);
|
||||
};
|
||||
|
||||
const getAttachmentData = (attachment: Partial<Attachment>) => {
|
||||
return postAsyncWithTimeout(EditorEvents.getAttachmentData, {
|
||||
return postAsyncWithTimeout(EventTypes.getAttachmentData, {
|
||||
attachment
|
||||
});
|
||||
};
|
||||
|
||||
@@ -27,51 +27,187 @@ globalThis.editorTitles = {};
|
||||
globalThis.statusBars = {};
|
||||
|
||||
export type TabItem = {
|
||||
id: string;
|
||||
session?: {
|
||||
noteId?: string;
|
||||
readonly?: boolean;
|
||||
locked?: boolean;
|
||||
noteLocked?: boolean;
|
||||
scrollTop?: number;
|
||||
selection?: { to: number; from: number };
|
||||
};
|
||||
id: number;
|
||||
noteId?: string;
|
||||
previewTab?: boolean;
|
||||
readonly?: boolean;
|
||||
locked?: boolean;
|
||||
noteLocked?: boolean;
|
||||
pinned?: boolean;
|
||||
needsRefresh?: boolean;
|
||||
};
|
||||
|
||||
export type NoteState = {
|
||||
top: number;
|
||||
to: number;
|
||||
from: number;
|
||||
};
|
||||
|
||||
export type TabStore = {
|
||||
tabs: TabItem[];
|
||||
currentTab?: string;
|
||||
currentTab: number;
|
||||
scrollPosition: Record<number, number>;
|
||||
noteState: Record<string, NoteState>;
|
||||
updateTab: (id: number, options: Omit<Partial<TabItem>, "id">) => void;
|
||||
removeTab: (index: number) => void;
|
||||
moveTab: (index: number, toIndex: number) => void;
|
||||
newTab: (noteId?: string, previewTab?: boolean) => void;
|
||||
focusTab: (id: number) => void;
|
||||
setScrollPosition: (id: number, position: number) => void;
|
||||
getNoteIdForTab: (id: number) => string | undefined;
|
||||
getTabForNote: (noteId: string) => number | undefined;
|
||||
hasTabForNote: (noteId: string) => boolean;
|
||||
focusEmptyTab: () => void;
|
||||
focusPreviewTab: (
|
||||
noteId: string,
|
||||
options: Omit<Partial<TabItem>, "id">
|
||||
) => void;
|
||||
getCurrentNoteId: () => string | undefined;
|
||||
getTab: (tabId: number) => TabItem | undefined;
|
||||
setNoteState: (noteId: string, state: Partial<NoteState>) => void;
|
||||
biometryAvailable?: boolean;
|
||||
biometryEnrolled?: boolean;
|
||||
canGoBack?: boolean;
|
||||
canGoForward?: boolean;
|
||||
sessionId?: string;
|
||||
getCurrentNoteId: () => string | undefined;
|
||||
};
|
||||
|
||||
function getId(id: number, tabs: TabItem[]): number {
|
||||
const exists = tabs.find((t) => t.id === id);
|
||||
if (exists) {
|
||||
return getId(id + 1, tabs);
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
export const useTabStore = create(
|
||||
persist<TabStore>(
|
||||
(set, get) => ({
|
||||
noteState: {},
|
||||
tabs: [
|
||||
{
|
||||
id: "679da59a3924d4bd56d16d3f",
|
||||
session: {
|
||||
id: "679da5a5667a16db2353a062"
|
||||
}
|
||||
id: 0,
|
||||
previewTab: true
|
||||
}
|
||||
],
|
||||
currentTab: "679da59a3924d4bd56d16d3f",
|
||||
currentTab: 0,
|
||||
scrollPosition: {},
|
||||
setNoteState: (noteId: string, state: Partial<NoteState>) => {
|
||||
if (editorControllers[get().currentTab]?.loading) return;
|
||||
|
||||
const noteState = {
|
||||
...get().noteState
|
||||
};
|
||||
noteState[noteId] = {
|
||||
...get().noteState[noteId],
|
||||
...state
|
||||
};
|
||||
|
||||
set({
|
||||
noteState
|
||||
});
|
||||
},
|
||||
updateTab: (id: number, options: Omit<Partial<TabItem>, "id">) => {
|
||||
const index = get().tabs.findIndex((t) => t.id === id);
|
||||
if (index == -1) return;
|
||||
const tabs = [...get().tabs];
|
||||
tabs[index] = {
|
||||
...tabs[index],
|
||||
...options
|
||||
};
|
||||
set({
|
||||
tabs: tabs
|
||||
});
|
||||
},
|
||||
removeTab: (index: number) => {
|
||||
const scrollPosition = { ...get().scrollPosition };
|
||||
if (scrollPosition[index]) {
|
||||
delete scrollPosition[index];
|
||||
}
|
||||
globalThis.editorControllers[index] = undefined;
|
||||
globalThis.editors[index] = null;
|
||||
|
||||
set({
|
||||
scrollPosition
|
||||
});
|
||||
},
|
||||
focusPreviewTab: (noteId: string, options) => {
|
||||
const index = get().tabs.findIndex((t) => t.previewTab);
|
||||
if (index == -1) return get().newTab(noteId, true);
|
||||
const tabs = [...get().tabs];
|
||||
tabs[index] = {
|
||||
...tabs[index],
|
||||
noteId: noteId,
|
||||
previewTab: true,
|
||||
...options
|
||||
};
|
||||
|
||||
set({
|
||||
currentTab: tabs[index].id
|
||||
});
|
||||
},
|
||||
focusEmptyTab: () => {
|
||||
const index = get().tabs.findIndex((t) => !t.noteId);
|
||||
if (index == -1) return get().newTab();
|
||||
const tabs = [...get().tabs];
|
||||
tabs[index] = {
|
||||
...tabs[index]
|
||||
};
|
||||
set({
|
||||
currentTab: tabs[index].id
|
||||
});
|
||||
},
|
||||
newTab: (noteId?: string, previewTab?: boolean) => {
|
||||
const id = getId(get().tabs.length, get().tabs);
|
||||
const nextTabs = [
|
||||
...get().tabs,
|
||||
{
|
||||
id: id,
|
||||
noteId,
|
||||
previewTab: previewTab
|
||||
}
|
||||
];
|
||||
set({
|
||||
tabs: nextTabs,
|
||||
currentTab: id
|
||||
});
|
||||
},
|
||||
moveTab: (index: number, toIndex: number) => {
|
||||
const tabs = get().tabs.slice();
|
||||
tabs.splice(toIndex, 0, tabs.slice(index, 1)[0]);
|
||||
set({
|
||||
tabs: tabs
|
||||
});
|
||||
},
|
||||
focusTab: (id: number) => {
|
||||
set({
|
||||
currentTab: id
|
||||
});
|
||||
},
|
||||
setScrollPosition: (id: number, position: number) => {
|
||||
set({
|
||||
scrollPosition: {
|
||||
...get().scrollPosition,
|
||||
[id]: position
|
||||
}
|
||||
});
|
||||
},
|
||||
getNoteIdForTab: (id: number) => {
|
||||
return get().tabs.find((t) => t.id === id)?.noteId;
|
||||
},
|
||||
hasTabForNote: (noteId: string) => {
|
||||
return (
|
||||
typeof get().tabs.find((t) => t.noteId === noteId)?.id === "number"
|
||||
);
|
||||
},
|
||||
getTabForNote: (noteId: string) => {
|
||||
return get().tabs.find((t) => t.noteId === noteId)?.id;
|
||||
},
|
||||
getCurrentNoteId: () => {
|
||||
return get().tabs.find((t) => t.id === get().currentTab)?.session
|
||||
?.noteId;
|
||||
return get().tabs.find((t) => t.id === get().currentTab)?.noteId;
|
||||
},
|
||||
getTab: (tabId) => {
|
||||
return get().tabs.find((t) => t.id === tabId);
|
||||
}
|
||||
}),
|
||||
{
|
||||
name: "tab-storage-v3",
|
||||
name: "tab-storage",
|
||||
storage: createJSONStorage(() => localStorage)
|
||||
}
|
||||
)
|
||||
@@ -79,7 +215,9 @@ export const useTabStore = create(
|
||||
|
||||
globalThis.tabStore = useTabStore;
|
||||
|
||||
export const TabContext = createContext<TabItem>({} as TabItem);
|
||||
export const TabContext = createContext<TabItem>({
|
||||
id: 0
|
||||
});
|
||||
|
||||
export const useTabContext = () => {
|
||||
const tab = useContext(TabContext);
|
||||
|
||||
@@ -17,7 +17,6 @@ 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 "./utils/index";
|
||||
import "./utils/commands";
|
||||
global.Buffer = require("buffer").Buffer;
|
||||
import { i18n } from "@lingui/core";
|
||||
import "@notesnook/editor/styles/fonts.mobile.css";
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
/*
|
||||
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 { Attachment, ImageAttributes, LinkAttributes } from "@notesnook/editor";
|
||||
import { Settings } from ".";
|
||||
|
||||
globalThis.commands = {
|
||||
clearContent: (tabId: string) => {
|
||||
try {
|
||||
const editor = editors[tabId];
|
||||
const editorController = editorControllers[tabId];
|
||||
const editorTitle = editorTitles[tabId];
|
||||
const statusBar = statusBars[tabId];
|
||||
|
||||
if (editor) {
|
||||
editor?.commands.blur();
|
||||
editor?.commands.clearContent(false);
|
||||
}
|
||||
|
||||
if (editorController) {
|
||||
editorController.content.current = "";
|
||||
editorController.onUpdate();
|
||||
editorController.setTitle("");
|
||||
}
|
||||
|
||||
if (editorTitle?.current) {
|
||||
editorTitle.current?.blur();
|
||||
editorTitle.current.value = "";
|
||||
}
|
||||
|
||||
if (statusBar) {
|
||||
statusBar.current.resetWords();
|
||||
statusBar.current.set({ date: "", saved: "" });
|
||||
}
|
||||
} catch (error) {
|
||||
logger("error", "clearContent", error, (error as Error).stack);
|
||||
}
|
||||
},
|
||||
|
||||
focus: (tabId: string, locked: boolean) => {
|
||||
const editorController = editorControllers[tabId];
|
||||
if (locked) {
|
||||
editorController?.focusPassInput();
|
||||
} else {
|
||||
editors[tabId]?.commands.focus();
|
||||
}
|
||||
},
|
||||
|
||||
blur: (tabId: string) => {
|
||||
const editor = editors[tabId];
|
||||
const editorTitle = editorTitles[tabId];
|
||||
if (editor) editor.commands.blur();
|
||||
if (editorTitle?.current) editorTitle.current.blur();
|
||||
editorControllers[tabId]?.blurPassInput();
|
||||
},
|
||||
|
||||
setSessionId: (id: string | undefined) => {
|
||||
globalThis.sessionId = id;
|
||||
},
|
||||
|
||||
setStatus: (date: string | undefined, saved: string, tabId: string) => {
|
||||
const statusBar = statusBars[tabId];
|
||||
if (statusBar?.current) {
|
||||
statusBar.current.set({ date: date || "", saved });
|
||||
}
|
||||
},
|
||||
|
||||
setLoading: (loading?: boolean, tabId?: string) => {
|
||||
if (tabId) {
|
||||
const editorController = editorControllers[tabId];
|
||||
editorController?.setLoading(loading || false);
|
||||
logger("info", editorController?.setLoading);
|
||||
}
|
||||
},
|
||||
|
||||
setInsets: (insets: any) => {
|
||||
if (typeof safeAreaController !== "undefined") {
|
||||
safeAreaController.update(insets);
|
||||
}
|
||||
},
|
||||
|
||||
updateSettings: (settings?: Partial<Settings>) => {
|
||||
if (typeof globalThis.settingsController !== "undefined") {
|
||||
globalThis.settingsController.update(settings as Settings);
|
||||
}
|
||||
},
|
||||
|
||||
setSettings: (settings?: Partial<Settings>) => {
|
||||
if (typeof globalThis.settingsController !== "undefined") {
|
||||
globalThis.settingsController.update(settings as Settings);
|
||||
}
|
||||
},
|
||||
|
||||
setTags: async (tabId: string, tags: any) => {
|
||||
const current = globalThis.editorTags[tabId];
|
||||
if (current?.current) {
|
||||
current.current.setTags(
|
||||
tags.map((tag: any) => ({
|
||||
title: tag.title,
|
||||
alias: tag.title,
|
||||
id: tag.id,
|
||||
type: tag.type
|
||||
}))
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
clearTags: (tabId: string) => {
|
||||
const tags = editorTags[tabId];
|
||||
if (tags?.current) {
|
||||
tags.current.setTags([]);
|
||||
}
|
||||
},
|
||||
|
||||
insertAttachment: (attachment: Attachment, tabId: number) => {
|
||||
const editor = editors[tabId];
|
||||
if (editor) {
|
||||
editor.commands.insertAttachment(attachment);
|
||||
}
|
||||
},
|
||||
|
||||
setAttachmentProgress: (
|
||||
attachmentProgress: Partial<Attachment>,
|
||||
tabId: number
|
||||
) => {
|
||||
const editor = editors[tabId];
|
||||
if (editor) {
|
||||
editor.commands.updateAttachment(attachmentProgress, {
|
||||
preventUpdate: true,
|
||||
query: (attachment) => attachment.hash === attachmentProgress.hash
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
insertImage: (
|
||||
image: Omit<ImageAttributes, "bloburl"> & { dataurl: string },
|
||||
tabId: number
|
||||
) => {
|
||||
const editor = editors[tabId];
|
||||
if (editor) {
|
||||
editor.commands.insertImage({
|
||||
...image
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
handleBack: () => {
|
||||
return window.dispatchEvent(
|
||||
new Event("handleBackPress", { cancelable: true })
|
||||
);
|
||||
},
|
||||
|
||||
keyboardShown: (keyboardShown: boolean) => {
|
||||
globalThis["keyboardShown"] = keyboardShown;
|
||||
},
|
||||
|
||||
getTableOfContents: (tabId: string) => {
|
||||
return editorControllers[tabId]?.getTableOfContents() || [];
|
||||
},
|
||||
|
||||
focusPassInput: (tabId: string) => {
|
||||
return editorControllers[tabId]?.focusPassInput() || [];
|
||||
},
|
||||
|
||||
blurPassInput: (tabId: string) => {
|
||||
return editorControllers[tabId]?.blurPassInput() || [];
|
||||
},
|
||||
|
||||
createInternalLink: (attributes: LinkAttributes, resolverId: string) => {
|
||||
if (globalThis.pendingResolvers[resolverId]) {
|
||||
globalThis.pendingResolvers[resolverId](attributes);
|
||||
}
|
||||
},
|
||||
|
||||
dismissCreateInternalLinkRequest: (resolverId: string) => {
|
||||
if (globalThis.pendingResolvers[resolverId]) {
|
||||
globalThis.pendingResolvers[resolverId](undefined);
|
||||
}
|
||||
},
|
||||
|
||||
scrollIntoViewById: (id: string, tabId: string) => {
|
||||
return editorControllers[tabId]?.scrollIntoView(id) || [];
|
||||
}
|
||||
};
|
||||
@@ -22,8 +22,6 @@ import { ThemeDefinition } from "@notesnook/theme";
|
||||
import { Dispatch, MutableRefObject, RefObject, SetStateAction } from "react";
|
||||
import { EditorController } from "../hooks/useEditorController";
|
||||
|
||||
import { EditorEvents } from "./editor-events";
|
||||
|
||||
globalThis.sessionId = "notesnook-editor";
|
||||
globalThis.pendingResolvers = {};
|
||||
|
||||
@@ -64,7 +62,7 @@ declare global {
|
||||
|
||||
var readonlyEditor: boolean;
|
||||
var statusBars: Record<
|
||||
string,
|
||||
number,
|
||||
| React.MutableRefObject<{
|
||||
set: React.Dispatch<
|
||||
React.SetStateAction<{
|
||||
@@ -83,22 +81,20 @@ declare global {
|
||||
var noHeader: boolean;
|
||||
function toBlobURL(dataurl: string, id?: string): string | undefined;
|
||||
var pendingResolvers: { [name: string]: (value: any) => void };
|
||||
|
||||
var commands: any;
|
||||
/**
|
||||
* Id of current session
|
||||
*/
|
||||
var sessionId: string | undefined;
|
||||
var sessionId: string;
|
||||
|
||||
var tabStore: any;
|
||||
/**
|
||||
* Current tiptap editors
|
||||
*/
|
||||
var editors: Record<string, Editor | null>;
|
||||
var editors: Record<number, Editor | null>;
|
||||
/**
|
||||
* Current editor controllers
|
||||
*/
|
||||
var editorControllers: Record<string, EditorController | undefined>;
|
||||
var editorControllers: Record<number, EditorController | undefined>;
|
||||
|
||||
var settingsController: {
|
||||
update: (settings: Settings) => void;
|
||||
@@ -126,12 +122,12 @@ declare global {
|
||||
>;
|
||||
};
|
||||
|
||||
var editorTitles: Record<string, RefObject<HTMLTextAreaElement> | undefined>;
|
||||
var editorTitles: Record<number, RefObject<HTMLTextAreaElement> | undefined>;
|
||||
/**
|
||||
* Global ref to manage tags in editor.
|
||||
*/
|
||||
var editorTags: Record<
|
||||
string,
|
||||
number,
|
||||
| MutableRefObject<{
|
||||
setTags: React.Dispatch<
|
||||
React.SetStateAction<
|
||||
@@ -154,10 +150,10 @@ declare global {
|
||||
* @param value
|
||||
*/
|
||||
|
||||
function post<T extends keyof typeof EditorEvents>(
|
||||
type: (typeof EditorEvents)[T],
|
||||
function post<T extends keyof typeof EventTypes>(
|
||||
type: (typeof EventTypes)[T],
|
||||
value?: unknown,
|
||||
tabId?: string,
|
||||
tabId?: number,
|
||||
noteId?: string,
|
||||
sessionId?: string
|
||||
): void;
|
||||
@@ -188,6 +184,44 @@ export function getOnMessageListener(callback: () => void) {
|
||||
};
|
||||
}
|
||||
|
||||
/* eslint-enable no-var */
|
||||
|
||||
export const EventTypes = {
|
||||
selection: "editor-event:selection",
|
||||
content: "editor-event:content",
|
||||
title: "editor-event:title",
|
||||
scroll: "editor-event:scroll",
|
||||
history: "editor-event:history",
|
||||
newtag: "editor-event:newtag",
|
||||
tag: "editor-event:tag",
|
||||
filepicker: "editor-event:picker",
|
||||
download: "editor-event:download-attachment",
|
||||
logger: "native:logger",
|
||||
back: "editor-event:back",
|
||||
pro: "editor-event:pro",
|
||||
monograph: "editor-event:monograph",
|
||||
properties: "editor-event:properties",
|
||||
fullscreen: "editor-event:fullscreen",
|
||||
link: "editor-event:link",
|
||||
contentchange: "editor-event:content-change",
|
||||
reminders: "editor-event:reminders",
|
||||
previewAttachment: "editor-event:preview-attachment",
|
||||
copyToClipboard: "editor-events:copy-to-clipboard",
|
||||
getAttachmentData: "editor-events:get-attachment-data",
|
||||
tabsChanged: "editor-events:tabs-changed",
|
||||
showTabs: "editor-events:show-tabs",
|
||||
tabFocused: "editor-events:tab-focused",
|
||||
toc: "editor-events:toc",
|
||||
createInternalLink: "editor-events:create-internal-link",
|
||||
load: "editor-events:load",
|
||||
unlock: "editor-events:unlock",
|
||||
unlockWithBiometrics: "editor-events:unlock-biometrics",
|
||||
disableReadonlyMode: "editor-events:disable-readonly-mode",
|
||||
readonlyEditorLoaded: "readonlyEditorLoaded",
|
||||
error: "editorError",
|
||||
dbLogger: "editor-events:dbLogger"
|
||||
} as const;
|
||||
|
||||
export function randId(prefix: string) {
|
||||
return Math.random()
|
||||
.toString(36)
|
||||
@@ -210,7 +244,7 @@ export function logger(
|
||||
})
|
||||
.join(" ");
|
||||
|
||||
post(EditorEvents.logger, `[${type}]: ` + logString);
|
||||
post(EventTypes.logger, `[${type}]: ` + logString);
|
||||
}
|
||||
|
||||
export function dbLogger(type: "error" | "log", ...logs: unknown[]): void {
|
||||
@@ -220,7 +254,7 @@ export function dbLogger(type: "error" | "log", ...logs: unknown[]): void {
|
||||
})
|
||||
.join(" ");
|
||||
|
||||
post(EditorEvents.dbLogger, {
|
||||
post(EventTypes.dbLogger, {
|
||||
message: `[${type}]: ` + logString,
|
||||
error: logs[0] instanceof Error ? logs[0] : undefined
|
||||
});
|
||||
@@ -229,7 +263,7 @@ export function dbLogger(type: "error" | "log", ...logs: unknown[]): void {
|
||||
export function post(
|
||||
type: string,
|
||||
value?: unknown,
|
||||
tabId?: string,
|
||||
tabId?: number,
|
||||
noteId?: string,
|
||||
sessionId?: string,
|
||||
hasTimeout?: boolean
|
||||
@@ -256,7 +290,7 @@ export function post(
|
||||
export async function postAsyncWithTimeout<R = any>(
|
||||
type: string,
|
||||
value?: unknown,
|
||||
tabId?: string,
|
||||
tabId?: number,
|
||||
noteId?: string,
|
||||
sessionId?: string,
|
||||
waitFor?: number
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
export const NativeEvents = {
|
||||
html: "native:html",
|
||||
updatehtml: "native:updatehtml",
|
||||
title: "native:title",
|
||||
theme: "native:theme",
|
||||
titleplaceholder: "native:titleplaceholder",
|
||||
logger: "native:logger",
|
||||
status: "native:status",
|
||||
keyboardShown: "native:keyboardShown",
|
||||
attachmentData: "native:attachment-data",
|
||||
resolve: "native:resolve",
|
||||
session: "native:session"
|
||||
};
|
||||
@@ -16,8 +16,7 @@ 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 { postAsyncWithTimeout, randId } from ".";
|
||||
import { EditorEvents } from "./editor-events";
|
||||
import { EventTypes, postAsyncWithTimeout, randId } from ".";
|
||||
|
||||
class PendingSaveRequests {
|
||||
static TITLES = "pendingTitles";
|
||||
@@ -119,7 +118,7 @@ class PendingSaveRequests {
|
||||
this.remove(PendingSaveRequests.TITLES);
|
||||
for (const pending of pendingTitles) {
|
||||
if (pending.params[0]) pending.params[0].pendingChanges = true;
|
||||
await postAsyncWithTimeout(EditorEvents.title, ...pending.params);
|
||||
await postAsyncWithTimeout(EventTypes.title, ...pending.params, 5000);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -128,7 +127,7 @@ class PendingSaveRequests {
|
||||
this.remove(PendingSaveRequests.CONTENT);
|
||||
for (const pending of pendingContents) {
|
||||
if (pending.params[0]) pending.params[0].pendingChanges = true;
|
||||
await postAsyncWithTimeout(EditorEvents.content, ...pending.params);
|
||||
await postAsyncWithTimeout(EventTypes.content, ...pending.params, 5000);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,17 +1,7 @@
|
||||
diff --git a/node_modules/prosemirror-view/dist/index.cjs b/node_modules/prosemirror-view/dist/index.cjs
|
||||
index 8ea57c7..c289489 100644
|
||||
index 8ea57c7..aeda01d 100644
|
||||
--- a/node_modules/prosemirror-view/dist/index.cjs
|
||||
+++ b/node_modules/prosemirror-view/dist/index.cjs
|
||||
@@ -980,8 +980,8 @@ var ViewDesc = function () {
|
||||
if (!(force || brKludge && safari) && isEquivalentPosition(anchorDOM.node, anchorDOM.offset, domSel.anchorNode, domSel.anchorOffset) && isEquivalentPosition(headDOM.node, headDOM.offset, domSel.focusNode, domSel.focusOffset)) return;
|
||||
var domSelExtended = false;
|
||||
if ((domSel.extend || anchor == head) && !brKludge) {
|
||||
- domSel.collapse(anchorDOM.node, anchorDOM.offset);
|
||||
try {
|
||||
+ domSel.collapse(anchorDOM.node, anchorDOM.offset);
|
||||
if (anchor != head) domSel.extend(headDOM.node, headDOM.offset);
|
||||
domSelExtended = true;
|
||||
} catch (_) {}
|
||||
@@ -3456,7 +3456,7 @@ editHandlers.drop = function (view, _event) {
|
||||
});
|
||||
tr.setSelection(selectionBetween(view, $pos, tr.doc.resolve(end)));
|
||||
@@ -22,19 +12,9 @@ index 8ea57c7..c289489 100644
|
||||
};
|
||||
handlers.focus = function (view) {
|
||||
diff --git a/node_modules/prosemirror-view/dist/index.js b/node_modules/prosemirror-view/dist/index.js
|
||||
index 9583dc3..6899e62 100644
|
||||
index 9583dc3..991bf0a 100644
|
||||
--- a/node_modules/prosemirror-view/dist/index.js
|
||||
+++ b/node_modules/prosemirror-view/dist/index.js
|
||||
@@ -1052,8 +1052,8 @@ class ViewDesc {
|
||||
// browsers support it yet.
|
||||
let domSelExtended = false;
|
||||
if ((domSel.extend || anchor == head) && !brKludge) {
|
||||
- domSel.collapse(anchorDOM.node, anchorDOM.offset);
|
||||
try {
|
||||
+ domSel.collapse(anchorDOM.node, anchorDOM.offset);
|
||||
if (anchor != head)
|
||||
domSel.extend(headDOM.node, headDOM.offset);
|
||||
domSelExtended = true;
|
||||
@@ -3731,7 +3731,7 @@ editHandlers.drop = (view, _event) => {
|
||||
tr.mapping.maps[tr.mapping.maps.length - 1].forEach((_from, _to, _newFrom, newTo) => end = newTo);
|
||||
tr.setSelection(selectionBetween(view, $pos, tr.doc.resolve(end)));
|
||||
|
||||
@@ -17,7 +17,7 @@ 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 { Input, Text } from "@theme-ui/components";
|
||||
import { Input } from "@theme-ui/components";
|
||||
import { Flex } from "@theme-ui/components";
|
||||
import { useRefValue } from "../../hooks/use-ref-value.js";
|
||||
import { Popup } from "../components/popup.js";
|
||||
@@ -64,41 +64,19 @@ export function LinkPopup(props: LinkPopupProps) {
|
||||
}}
|
||||
>
|
||||
{!isImageActive && (
|
||||
<>
|
||||
<Text
|
||||
sx={{
|
||||
mb: 1,
|
||||
ml: 1,
|
||||
fontSize: "body",
|
||||
color: "paragraph"
|
||||
}}
|
||||
>
|
||||
{strings.linkText()}
|
||||
</Text>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={strings.linkText()}
|
||||
defaultValue={link.current?.title}
|
||||
sx={{ mb: 2 }}
|
||||
onChange={(e) =>
|
||||
(link.current = {
|
||||
...link.current,
|
||||
title: e.target.value
|
||||
})
|
||||
}
|
||||
/>
|
||||
</>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder={strings.linkText()}
|
||||
defaultValue={link.current?.title}
|
||||
sx={{ mb: 1 }}
|
||||
onChange={(e) =>
|
||||
(link.current = {
|
||||
...link.current,
|
||||
title: e.target.value
|
||||
})
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<Text
|
||||
sx={{
|
||||
mb: 1,
|
||||
ml: 1,
|
||||
fontSize: "body",
|
||||
color: "paragraph"
|
||||
}}
|
||||
>
|
||||
{strings.url()}
|
||||
</Text>
|
||||
<Input
|
||||
type="url"
|
||||
autoFocus
|
||||
|
||||
@@ -13,7 +13,7 @@ msgstr ""
|
||||
"Language-Team: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#: src/strings.ts:2390
|
||||
#: src/strings.ts:2389
|
||||
msgid " \"Notebook > Notes\""
|
||||
msgstr " \"Notebook > Notes\""
|
||||
|
||||
@@ -279,7 +279,7 @@ msgstr "{count, plural, one {Item restored} other {# items restored}}"
|
||||
msgid "{count, plural, one {Item unpublished} other {# items unpublished}}"
|
||||
msgstr "{count, plural, one {Item unpublished} other {# items unpublished}}"
|
||||
|
||||
#: src/strings.ts:2407
|
||||
#: src/strings.ts:2406
|
||||
msgid "{count, plural, one {Move all notes in this notebook to trash} other {Move all notes in these notebooks to trash}}"
|
||||
msgstr "{count, plural, one {Move all notes in this notebook to trash} other {Move all notes in these notebooks to trash}}"
|
||||
|
||||
@@ -724,7 +724,7 @@ msgstr "All"
|
||||
msgid "All attachments are end-to-end encrypted."
|
||||
msgstr "All attachments are end-to-end encrypted."
|
||||
|
||||
#: src/strings.ts:2384
|
||||
#: src/strings.ts:2383
|
||||
msgid "All cached attachments have been cleared."
|
||||
msgstr "All cached attachments have been cleared."
|
||||
|
||||
@@ -911,7 +911,7 @@ msgstr "Attachment"
|
||||
msgid "Attachment preview failed"
|
||||
msgstr "Attachment preview failed"
|
||||
|
||||
#: src/strings.ts:2377
|
||||
#: src/strings.ts:2376
|
||||
msgid "Attachment recheck cancelled"
|
||||
msgstr "Attachment recheck cancelled"
|
||||
|
||||
@@ -932,7 +932,7 @@ msgstr "Attachments"
|
||||
msgid "Attachments cache cleared!"
|
||||
msgstr "Attachments cache cleared!"
|
||||
|
||||
#: src/strings.ts:2379
|
||||
#: src/strings.ts:2378
|
||||
msgid "Attachments recheck complete"
|
||||
msgstr "Attachments recheck complete"
|
||||
|
||||
@@ -1149,7 +1149,7 @@ msgstr "Biometrics not enrolled"
|
||||
msgid "Bold"
|
||||
msgstr "Bold"
|
||||
|
||||
#: src/strings.ts:2389
|
||||
#: src/strings.ts:2388
|
||||
msgid "Boost your productivity with Notebooks and organize your notes."
|
||||
msgstr "Boost your productivity with Notebooks and organize your notes."
|
||||
|
||||
@@ -1310,7 +1310,7 @@ msgstr "Check roadmap"
|
||||
msgid "Check your spam folder if you haven't received an email yet."
|
||||
msgstr "Check your spam folder if you haven't received an email yet."
|
||||
|
||||
#: src/strings.ts:2381
|
||||
#: src/strings.ts:2380
|
||||
msgid "Checking all attachments"
|
||||
msgstr "Checking all attachments"
|
||||
|
||||
@@ -1322,7 +1322,7 @@ msgstr "Checking for new version"
|
||||
msgid "Checking for updates"
|
||||
msgstr "Checking for updates"
|
||||
|
||||
#: src/strings.ts:2380
|
||||
#: src/strings.ts:2379
|
||||
msgid "Checking note attachments"
|
||||
msgstr "Checking note attachments"
|
||||
|
||||
@@ -1468,7 +1468,7 @@ msgstr "Click to preview"
|
||||
msgid "Click to remove"
|
||||
msgstr "Click to remove"
|
||||
|
||||
#: src/strings.ts:2372
|
||||
#: src/strings.ts:2371
|
||||
msgid "Click to reset {title}"
|
||||
msgstr "Click to reset {title}"
|
||||
|
||||
@@ -1569,7 +1569,7 @@ msgstr "Compressed images are uploaded in Full HD resolution and usually are goo
|
||||
msgid "Configure"
|
||||
msgstr "Configure"
|
||||
|
||||
#: src/strings.ts:2386
|
||||
#: src/strings.ts:2385
|
||||
msgid "Configure server URLs for Notesnook"
|
||||
msgstr "Configure server URLs for Notesnook"
|
||||
|
||||
@@ -1892,7 +1892,7 @@ msgstr "Debug logs downloaded"
|
||||
msgid "Debugging"
|
||||
msgstr "Debugging"
|
||||
|
||||
#: src/strings.ts:2374
|
||||
#: src/strings.ts:2373
|
||||
msgid "Decrease {title}"
|
||||
msgstr "Decrease {title}"
|
||||
|
||||
@@ -2190,7 +2190,7 @@ msgstr "Duplicate"
|
||||
msgid "Earliest first"
|
||||
msgstr "Earliest first"
|
||||
|
||||
#: src/strings.ts:2398
|
||||
#: src/strings.ts:2397
|
||||
msgid "Easy access"
|
||||
msgstr "Easy access"
|
||||
|
||||
@@ -2224,7 +2224,7 @@ msgstr "Editor"
|
||||
msgid "Email"
|
||||
msgstr "Email"
|
||||
|
||||
#: src/strings.ts:2411
|
||||
#: src/strings.ts:2410
|
||||
msgid "Email copied"
|
||||
msgstr "Email copied"
|
||||
|
||||
@@ -2392,7 +2392,7 @@ msgstr "Enter the 6 digit code sent to your email to continue logging in"
|
||||
msgid "Enter the 6 digit code sent to your phone number to continue logging in"
|
||||
msgstr "Enter the 6 digit code sent to your phone number to continue logging in"
|
||||
|
||||
#: src/strings.ts:2413
|
||||
#: src/strings.ts:2412
|
||||
msgid "Enter the gift code to redeem your subscription."
|
||||
msgstr "Enter the gift code to redeem your subscription."
|
||||
|
||||
@@ -2412,7 +2412,7 @@ msgstr "Enter your new email"
|
||||
msgid "Enter your username"
|
||||
msgstr "Enter your username"
|
||||
|
||||
#: src/strings.ts:2405
|
||||
#: src/strings.ts:2404
|
||||
msgid "Error"
|
||||
msgstr "Error"
|
||||
|
||||
@@ -2452,15 +2452,15 @@ msgstr "Errors in {count} attachments"
|
||||
msgid "Events server"
|
||||
msgstr "Events server"
|
||||
|
||||
#: src/strings.ts:2391
|
||||
#: src/strings.ts:2390
|
||||
msgid "Every Notebook can have notes and sub notebooks."
|
||||
msgstr "Every Notebook can have notes and sub notebooks."
|
||||
|
||||
#: src/strings.ts:2393
|
||||
#: src/strings.ts:2392
|
||||
msgid "Everything related to my job in one place."
|
||||
msgstr "Everything related to my job in one place."
|
||||
|
||||
#: src/strings.ts:2402
|
||||
#: src/strings.ts:2401
|
||||
msgid "Everything related to my school in one place."
|
||||
msgstr "Everything related to my school in one place."
|
||||
|
||||
@@ -2517,7 +2517,7 @@ msgstr "EXTREMELY DANGEROUS! This action is irreversible. All your data includin
|
||||
msgid "Faced an issue or have a suggestion? Click here to create a bug report"
|
||||
msgstr "Faced an issue or have a suggestion? Click here to create a bug report"
|
||||
|
||||
#: src/strings.ts:2383
|
||||
#: src/strings.ts:2382
|
||||
msgid "Failed"
|
||||
msgstr "Failed"
|
||||
|
||||
@@ -2602,11 +2602,11 @@ msgstr "Favorite"
|
||||
msgid "Favorites"
|
||||
msgstr "Favorites"
|
||||
|
||||
#: src/strings.ts:2395
|
||||
#: src/strings.ts:2394
|
||||
msgid "February 2022 Week 2"
|
||||
msgstr "February 2022 Week 2"
|
||||
|
||||
#: src/strings.ts:2396
|
||||
#: src/strings.ts:2395
|
||||
msgid "February 2022 Week 3"
|
||||
msgstr "February 2022 Week 3"
|
||||
|
||||
@@ -2784,7 +2784,7 @@ msgstr "Get Notesnook Pro"
|
||||
msgid "Get Notesnook Pro to enable automatic backups"
|
||||
msgstr "Get Notesnook Pro to enable automatic backups"
|
||||
|
||||
#: src/strings.ts:2387
|
||||
#: src/strings.ts:2386
|
||||
msgid "Get Priority support"
|
||||
msgstr "Get Priority support"
|
||||
|
||||
@@ -2956,7 +2956,7 @@ msgstr "I have a recovery code"
|
||||
msgid "I have saved my key"
|
||||
msgstr "I have saved my key"
|
||||
|
||||
#: src/strings.ts:2404
|
||||
#: src/strings.ts:2403
|
||||
msgid "I love cooking and collecting recipes."
|
||||
msgstr "I love cooking and collecting recipes."
|
||||
|
||||
@@ -3064,7 +3064,7 @@ msgstr "Incoming note"
|
||||
msgid "Incorrect {type}"
|
||||
msgstr "Incorrect {type}"
|
||||
|
||||
#: src/strings.ts:2373
|
||||
#: src/strings.ts:2372
|
||||
msgid "Increase {title}"
|
||||
msgstr "Increase {title}"
|
||||
|
||||
@@ -3072,7 +3072,7 @@ msgstr "Increase {title}"
|
||||
msgid "Insert"
|
||||
msgstr "Insert"
|
||||
|
||||
#: src/strings.ts:2370
|
||||
#: src/strings.ts:2369
|
||||
msgid "Insert a {rows}x{columns} table"
|
||||
msgstr "Insert a {rows}x{columns} table"
|
||||
|
||||
@@ -3527,7 +3527,7 @@ msgstr "Maximize"
|
||||
msgid "Meet other privacy-minded people & talk to us directly about your concerns, issues and suggestions."
|
||||
msgstr "Meet other privacy-minded people & talk to us directly about your concerns, issues and suggestions."
|
||||
|
||||
#: src/strings.ts:2397
|
||||
#: src/strings.ts:2396
|
||||
msgid "Meetings"
|
||||
msgstr "Meetings"
|
||||
|
||||
@@ -4114,7 +4114,7 @@ msgstr "Partial backups contain all your data except attachments. They are creat
|
||||
msgid "Partially refunded"
|
||||
msgstr "Partially refunded"
|
||||
|
||||
#: src/strings.ts:2382
|
||||
#: src/strings.ts:2381
|
||||
msgid "Passed"
|
||||
msgstr "Passed"
|
||||
|
||||
@@ -4308,7 +4308,7 @@ msgstr "Please select the day to repeat the reminder on"
|
||||
msgid "please send us an email from your registered email address"
|
||||
msgstr "please send us an email from your registered email address"
|
||||
|
||||
#: src/strings.ts:2371
|
||||
#: src/strings.ts:2370
|
||||
msgid "Please set a table size"
|
||||
msgstr "Please set a table size"
|
||||
|
||||
@@ -4602,7 +4602,7 @@ msgstr "Receipt"
|
||||
msgid "RECENT BACKUPS"
|
||||
msgstr "RECENT BACKUPS"
|
||||
|
||||
#: src/strings.ts:2378
|
||||
#: src/strings.ts:2377
|
||||
msgid "Recheck all"
|
||||
msgstr "Recheck all"
|
||||
|
||||
@@ -4610,7 +4610,7 @@ msgstr "Recheck all"
|
||||
msgid "Rechecking failed"
|
||||
msgstr "Rechecking failed"
|
||||
|
||||
#: src/strings.ts:2403
|
||||
#: src/strings.ts:2402
|
||||
msgid "Recipes"
|
||||
msgstr "Recipes"
|
||||
|
||||
@@ -4654,15 +4654,15 @@ msgstr "Recovery key text file saved"
|
||||
msgid "Recovery successful!"
|
||||
msgstr "Recovery successful!"
|
||||
|
||||
#: src/strings.ts:2415
|
||||
#: src/strings.ts:2414
|
||||
msgid "Redeem"
|
||||
msgstr "Redeem"
|
||||
|
||||
#: src/strings.ts:2412
|
||||
#: src/strings.ts:2411
|
||||
msgid "Redeem gift code"
|
||||
msgstr "Redeem gift code"
|
||||
|
||||
#: src/strings.ts:2414
|
||||
#: src/strings.ts:2413
|
||||
msgid "Redeeming gift code"
|
||||
msgstr "Redeeming gift code"
|
||||
|
||||
@@ -4913,7 +4913,7 @@ msgstr "Restore"
|
||||
msgid "Restore backup"
|
||||
msgstr "Restore backup"
|
||||
|
||||
#: src/strings.ts:2385
|
||||
#: src/strings.ts:2384
|
||||
msgid "Restore backup?"
|
||||
msgstr "Restore backup?"
|
||||
|
||||
@@ -5073,11 +5073,11 @@ msgstr "Save your data recovery key in a safe place. You will need it to recover
|
||||
msgid "Save your recovery codes in a safe place. You will need them to recover your account in case you lose access to your two-factor authentication methods."
|
||||
msgstr "Save your recovery codes in a safe place. You will need them to recover your account in case you lose access to your two-factor authentication methods."
|
||||
|
||||
#: src/strings.ts:2375
|
||||
#: src/strings.ts:2374
|
||||
msgid "Saved"
|
||||
msgstr "Saved"
|
||||
|
||||
#: src/strings.ts:2376
|
||||
#: src/strings.ts:2375
|
||||
msgid "Saving"
|
||||
msgstr "Saving"
|
||||
|
||||
@@ -5097,7 +5097,7 @@ msgstr "Saving zip file. Please wait..."
|
||||
msgid "Scan the QR code with your authenticator app"
|
||||
msgstr "Scan the QR code with your authenticator app"
|
||||
|
||||
#: src/strings.ts:2401
|
||||
#: src/strings.ts:2400
|
||||
msgid "School work"
|
||||
msgstr "School work"
|
||||
|
||||
@@ -5857,7 +5857,7 @@ msgstr "Tap twice to confirm you have saved the recovery key."
|
||||
msgid "Task list"
|
||||
msgstr "Task list"
|
||||
|
||||
#: src/strings.ts:2394
|
||||
#: src/strings.ts:2393
|
||||
msgid "Tasks"
|
||||
msgstr "Tasks"
|
||||
|
||||
@@ -6322,10 +6322,6 @@ msgstr "Uploads"
|
||||
msgid "Urgent"
|
||||
msgstr "Urgent"
|
||||
|
||||
#: src/strings.ts:2367
|
||||
msgid "URL"
|
||||
msgstr "URL"
|
||||
|
||||
#: src/strings.ts:1770
|
||||
msgid "Use"
|
||||
msgstr "Use"
|
||||
@@ -6599,7 +6595,7 @@ msgstr "What went wrong?"
|
||||
msgid "Width"
|
||||
msgstr "Width"
|
||||
|
||||
#: src/strings.ts:2392
|
||||
#: src/strings.ts:2391
|
||||
msgid "Work & Office"
|
||||
msgstr "Work & Office"
|
||||
|
||||
@@ -6664,7 +6660,7 @@ msgstr "You can add as many tags as you want."
|
||||
msgid "You can change the theme at any time from Settings or the side menu."
|
||||
msgstr "You can change the theme at any time from Settings or the side menu."
|
||||
|
||||
#: src/strings.ts:2400
|
||||
#: src/strings.ts:2399
|
||||
msgid "You can create shortcuts of frequently accessed notebooks in the side menu"
|
||||
msgstr "You can create shortcuts of frequently accessed notebooks in the side menu"
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ msgstr ""
|
||||
"Language-Team: \n"
|
||||
"Plural-Forms: \n"
|
||||
|
||||
#: src/strings.ts:2390
|
||||
#: src/strings.ts:2389
|
||||
msgid " \"Notebook > Notes\""
|
||||
msgstr ""
|
||||
|
||||
@@ -279,7 +279,7 @@ msgstr ""
|
||||
msgid "{count, plural, one {Item unpublished} other {# items unpublished}}"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2407
|
||||
#: src/strings.ts:2406
|
||||
msgid "{count, plural, one {Move all notes in this notebook to trash} other {Move all notes in these notebooks to trash}}"
|
||||
msgstr ""
|
||||
|
||||
@@ -724,7 +724,7 @@ msgstr ""
|
||||
msgid "All attachments are end-to-end encrypted."
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2384
|
||||
#: src/strings.ts:2383
|
||||
msgid "All cached attachments have been cleared."
|
||||
msgstr ""
|
||||
|
||||
@@ -911,7 +911,7 @@ msgstr ""
|
||||
msgid "Attachment preview failed"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2377
|
||||
#: src/strings.ts:2376
|
||||
msgid "Attachment recheck cancelled"
|
||||
msgstr ""
|
||||
|
||||
@@ -932,7 +932,7 @@ msgstr ""
|
||||
msgid "Attachments cache cleared!"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2379
|
||||
#: src/strings.ts:2378
|
||||
msgid "Attachments recheck complete"
|
||||
msgstr ""
|
||||
|
||||
@@ -1149,7 +1149,7 @@ msgstr ""
|
||||
msgid "Bold"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2389
|
||||
#: src/strings.ts:2388
|
||||
msgid "Boost your productivity with Notebooks and organize your notes."
|
||||
msgstr ""
|
||||
|
||||
@@ -1310,7 +1310,7 @@ msgstr ""
|
||||
msgid "Check your spam folder if you haven't received an email yet."
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2381
|
||||
#: src/strings.ts:2380
|
||||
msgid "Checking all attachments"
|
||||
msgstr ""
|
||||
|
||||
@@ -1322,7 +1322,7 @@ msgstr ""
|
||||
msgid "Checking for updates"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2380
|
||||
#: src/strings.ts:2379
|
||||
msgid "Checking note attachments"
|
||||
msgstr ""
|
||||
|
||||
@@ -1457,7 +1457,7 @@ msgstr ""
|
||||
msgid "Click to remove"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2372
|
||||
#: src/strings.ts:2371
|
||||
msgid "Click to reset {title}"
|
||||
msgstr ""
|
||||
|
||||
@@ -1558,7 +1558,7 @@ msgstr ""
|
||||
msgid "Configure"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2386
|
||||
#: src/strings.ts:2385
|
||||
msgid "Configure server URLs for Notesnook"
|
||||
msgstr ""
|
||||
|
||||
@@ -1881,7 +1881,7 @@ msgstr ""
|
||||
msgid "Debugging"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2374
|
||||
#: src/strings.ts:2373
|
||||
msgid "Decrease {title}"
|
||||
msgstr ""
|
||||
|
||||
@@ -2179,7 +2179,7 @@ msgstr ""
|
||||
msgid "Earliest first"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2398
|
||||
#: src/strings.ts:2397
|
||||
msgid "Easy access"
|
||||
msgstr ""
|
||||
|
||||
@@ -2213,7 +2213,7 @@ msgstr ""
|
||||
msgid "Email"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2411
|
||||
#: src/strings.ts:2410
|
||||
msgid "Email copied"
|
||||
msgstr ""
|
||||
|
||||
@@ -2381,7 +2381,7 @@ msgstr ""
|
||||
msgid "Enter the 6 digit code sent to your phone number to continue logging in"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2413
|
||||
#: src/strings.ts:2412
|
||||
msgid "Enter the gift code to redeem your subscription."
|
||||
msgstr ""
|
||||
|
||||
@@ -2401,7 +2401,7 @@ msgstr ""
|
||||
msgid "Enter your username"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2405
|
||||
#: src/strings.ts:2404
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
@@ -2441,15 +2441,15 @@ msgstr ""
|
||||
msgid "Events server"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2391
|
||||
#: src/strings.ts:2390
|
||||
msgid "Every Notebook can have notes and sub notebooks."
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2393
|
||||
#: src/strings.ts:2392
|
||||
msgid "Everything related to my job in one place."
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2402
|
||||
#: src/strings.ts:2401
|
||||
msgid "Everything related to my school in one place."
|
||||
msgstr ""
|
||||
|
||||
@@ -2506,7 +2506,7 @@ msgstr ""
|
||||
msgid "Faced an issue or have a suggestion? Click here to create a bug report"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2383
|
||||
#: src/strings.ts:2382
|
||||
msgid "Failed"
|
||||
msgstr ""
|
||||
|
||||
@@ -2591,11 +2591,11 @@ msgstr ""
|
||||
msgid "Favorites"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2395
|
||||
#: src/strings.ts:2394
|
||||
msgid "February 2022 Week 2"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2396
|
||||
#: src/strings.ts:2395
|
||||
msgid "February 2022 Week 3"
|
||||
msgstr ""
|
||||
|
||||
@@ -2766,7 +2766,7 @@ msgstr ""
|
||||
msgid "Get Notesnook Pro to enable automatic backups"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2387
|
||||
#: src/strings.ts:2386
|
||||
msgid "Get Priority support"
|
||||
msgstr ""
|
||||
|
||||
@@ -2938,7 +2938,7 @@ msgstr ""
|
||||
msgid "I have saved my key"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2404
|
||||
#: src/strings.ts:2403
|
||||
msgid "I love cooking and collecting recipes."
|
||||
msgstr ""
|
||||
|
||||
@@ -3044,7 +3044,7 @@ msgstr ""
|
||||
msgid "Incorrect {type}"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2373
|
||||
#: src/strings.ts:2372
|
||||
msgid "Increase {title}"
|
||||
msgstr ""
|
||||
|
||||
@@ -3052,7 +3052,7 @@ msgstr ""
|
||||
msgid "Insert"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2370
|
||||
#: src/strings.ts:2369
|
||||
msgid "Insert a {rows}x{columns} table"
|
||||
msgstr ""
|
||||
|
||||
@@ -3507,7 +3507,7 @@ msgstr ""
|
||||
msgid "Meet other privacy-minded people & talk to us directly about your concerns, issues and suggestions."
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2397
|
||||
#: src/strings.ts:2396
|
||||
msgid "Meetings"
|
||||
msgstr ""
|
||||
|
||||
@@ -4088,7 +4088,7 @@ msgstr ""
|
||||
msgid "Partially refunded"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2382
|
||||
#: src/strings.ts:2381
|
||||
msgid "Passed"
|
||||
msgstr ""
|
||||
|
||||
@@ -4282,7 +4282,7 @@ msgstr ""
|
||||
msgid "please send us an email from your registered email address"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2371
|
||||
#: src/strings.ts:2370
|
||||
msgid "Please set a table size"
|
||||
msgstr ""
|
||||
|
||||
@@ -4576,7 +4576,7 @@ msgstr ""
|
||||
msgid "RECENT BACKUPS"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2378
|
||||
#: src/strings.ts:2377
|
||||
msgid "Recheck all"
|
||||
msgstr ""
|
||||
|
||||
@@ -4584,7 +4584,7 @@ msgstr ""
|
||||
msgid "Rechecking failed"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2403
|
||||
#: src/strings.ts:2402
|
||||
msgid "Recipes"
|
||||
msgstr ""
|
||||
|
||||
@@ -4628,15 +4628,15 @@ msgstr ""
|
||||
msgid "Recovery successful!"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2415
|
||||
#: src/strings.ts:2414
|
||||
msgid "Redeem"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2412
|
||||
#: src/strings.ts:2411
|
||||
msgid "Redeem gift code"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2414
|
||||
#: src/strings.ts:2413
|
||||
msgid "Redeeming gift code"
|
||||
msgstr ""
|
||||
|
||||
@@ -4887,7 +4887,7 @@ msgstr ""
|
||||
msgid "Restore backup"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2385
|
||||
#: src/strings.ts:2384
|
||||
msgid "Restore backup?"
|
||||
msgstr ""
|
||||
|
||||
@@ -5047,11 +5047,11 @@ msgstr ""
|
||||
msgid "Save your recovery codes in a safe place. You will need them to recover your account in case you lose access to your two-factor authentication methods."
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2375
|
||||
#: src/strings.ts:2374
|
||||
msgid "Saved"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2376
|
||||
#: src/strings.ts:2375
|
||||
msgid "Saving"
|
||||
msgstr ""
|
||||
|
||||
@@ -5071,7 +5071,7 @@ msgstr ""
|
||||
msgid "Scan the QR code with your authenticator app"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2401
|
||||
#: src/strings.ts:2400
|
||||
msgid "School work"
|
||||
msgstr ""
|
||||
|
||||
@@ -5823,7 +5823,7 @@ msgstr ""
|
||||
msgid "Task list"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2394
|
||||
#: src/strings.ts:2393
|
||||
msgid "Tasks"
|
||||
msgstr ""
|
||||
|
||||
@@ -6281,10 +6281,6 @@ msgstr ""
|
||||
msgid "Urgent"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2367
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:1770
|
||||
msgid "Use"
|
||||
msgstr ""
|
||||
@@ -6550,7 +6546,7 @@ msgstr ""
|
||||
msgid "Width"
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2392
|
||||
#: src/strings.ts:2391
|
||||
msgid "Work & Office"
|
||||
msgstr ""
|
||||
|
||||
@@ -6615,7 +6611,7 @@ msgstr ""
|
||||
msgid "You can change the theme at any time from Settings or the side menu."
|
||||
msgstr ""
|
||||
|
||||
#: src/strings.ts:2400
|
||||
#: src/strings.ts:2399
|
||||
msgid "You can create shortcuts of frequently accessed notebooks in the side menu"
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -2364,7 +2364,6 @@ Use this if changes from other devices are not appearing on this device. This wi
|
||||
height: () => t`Height`,
|
||||
pasteImageURL: () => t`Paste image URL here`,
|
||||
linkText: () => t`Link text`,
|
||||
url: () => t`URL`,
|
||||
|
||||
insertTableOfSize: (rows: number, columns: number) =>
|
||||
t`Insert a ${rows}x${columns} table`,
|
||||
|
||||
Reference in New Issue
Block a user