mobile: update tabs

This commit is contained in:
Ammar Ahmed
2025-01-29 12:30:18 +05:00
committed by Abdullah Atta
parent c9a3f31e8f
commit 46583e12d9
13 changed files with 277 additions and 228 deletions

View File

@@ -25,7 +25,10 @@ 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 { useTabStore } from "../../../screens/editor/tiptap/use-tab-store";
import {
TabItem,
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";
@@ -37,23 +40,13 @@ 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;
isFocused: boolean;
close?: (ctx?: string | undefined) => void;
}) => {
const { colors } = useThemeColors();
const [item, update] = useDBItem(props.tab.noteId, "note");
const [item, update] = useDBItem(props.tab.session?.noteId, "note");
useEffect(() => {
const syncCompletedSubscription = db.eventManager?.subscribe(
@@ -85,11 +78,11 @@ const TabItemComponent = (props: {
onPress={() => {
if (!props.isFocused) {
useTabStore.getState().focusTab(props.tab.id);
if (props.tab.locked) {
if (props.tab.session?.locked) {
eSendEvent(eUnlockNote);
}
if (!props.tab.noteId) {
if (!props.tab.session?.noteId) {
setTimeout(() => {
editorController?.current?.commands?.focus(props.tab.id);
}, 300);
@@ -107,9 +100,9 @@ const TabItemComponent = (props: {
flexShrink: 1
}}
>
{props.tab.noteLocked ? (
{props.tab.session?.noteLocked ? (
<>
{props.tab.locked ? (
{props.tab.session?.locked ? (
<Icon size={SIZE.md} name="lock" />
) : (
<Icon size={SIZE.md} name="lock-open-outline" />
@@ -117,7 +110,9 @@ const TabItemComponent = (props: {
</>
) : null}
{props.tab.readonly ? <Icon size={SIZE.md} name="pencil-lock" /> : null}
{props.tab.session?.readonly ? (
<Icon size={SIZE.md} name="pencil-lock" />
) : null}
<Paragraph
color={
@@ -125,15 +120,10 @@ 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.noteId
{props.tab.session?.noteId
? item?.title || strings.untitledNote()
: strings.newNote()}
</Paragraph>
@@ -230,11 +220,6 @@ export default function EditorTabs({
<Button
onPress={() => {
useTabStore.getState().newTab();
setTimeout(() => {
editorController?.current?.commands?.focus(
useTabStore.getState().currentTab
);
}, 500);
close?.();
}}
title={strings.newTab()}

View File

@@ -28,5 +28,5 @@ const EditorMobileSourceUrl =
* The url should be something like this: http://192.168.100.126:3000/index.html
*/
export const EDITOR_URI = __DEV__
? "http://192.168.100.8:3000/index.html"
? EditorMobileSourceUrl
: EditorMobileSourceUrl;

View File

@@ -30,7 +30,6 @@ import { sleep } from "../../../utils/time";
import { Settings } from "./types";
import { useTabStore } from "./use-tab-store";
import { getResponse, randId, textInput } from "./utils";
import { EditorSessionItem } from "@notesnook/common";
type Action = { job: string; id: string };
@@ -76,7 +75,7 @@ class Commands {
return call(this.ref, fn(job, name)) as Promise<T>;
}
focus = async (tabId: number) => {
focus = async (tabId: string) => {
if (!this.ref.current) return;
const locked = useTabStore.getState().getTab(tabId)?.session?.locked;
@@ -87,8 +86,8 @@ class Commands {
textInput.current?.focus();
await this.doAsync(
locked
? `editorControllers[${tabId}]?.focusPassInput();`
: `editors[${tabId}]?.commands.focus()`,
? `editorControllers["${tabId}"]?.focusPassInput();`
: `editors["${tabId}"]?.commands.focus()`,
"focus"
);
@@ -98,35 +97,35 @@ class Commands {
await sleep(400);
await this.doAsync(
locked
? `editorControllers[${tabId}]?.focusPassInput();`
: `editors[${tabId}]?.commands.focus()`,
? `editorControllers["${tabId}"]?.focusPassInput();`
: `editors["${tabId}"]?.commands.focus()`,
"focus"
);
}
};
blur = async (tabId: number) =>
blur = async (tabId: string) =>
await this.doAsync(
`
const editor = editors[${tabId}];
const editorTitle = editorTitles[${tabId}];
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();
editorControllers["${tabId}"]?.blurPassInput();
`,
"blur"
);
clearContent = async (tabId: number) => {
clearContent = async (tabId: string) => {
this.previousSettings = null;
await this.doAsync(
`
const editor = editors[${tabId}];
const editorController = editorControllers[${tabId}];
const editorTitle = editorTitles[${tabId}];
const statusBar = statusBars[${tabId}];
const editor = editors["${tabId}"];
const editorController = editorControllers["${tabId}"];
const editorTitle = editorTitles["${tabId}"];
const statusBar = statusBars["${tabId}"];
if (typeof editor !== "undefined") {
editor.commands.blur();
@@ -151,11 +150,11 @@ if (typeof statusBar !== "undefined") {
setStatus = async (
date: string | undefined,
saved: string,
tabId: number
tabId: string
) => {
await this.doAsync(
`
const statusBar = statusBars[${tabId}];
const statusBar = statusBars["${tabId}"];
typeof statusBar !== "undefined" && statusBar.current.set({date:"${date}",saved:"${saved}"})`,
"setStatus"
);
@@ -170,11 +169,11 @@ if (typeof statusBar !== "undefined") {
// `);
};
setLoading = async (loading?: boolean, tabId?: number) => {
setLoading = async (loading?: boolean, tabId?: string) => {
await this.doAsync(`
const editorController = editorControllers[${
const editorController = editorControllers["${
tabId === undefined ? useTabStore.getState().currentTab : tabId
}];
}"];
editorController.setLoading(${loading})
logger("info", editorController.setLoading);
`);
@@ -227,7 +226,7 @@ if (typeof statusBar !== "undefined") {
const tags = await db.relations.to(note, "tag").resolve();
await this.doAsync(
`
const tags = editorTags[${tabId}];
const tags = editorTags["${tabId}"];
if (tags && tags.current) {
tags.current.setTags(${JSON.stringify(
tags.map((tag) => ({
@@ -244,10 +243,10 @@ if (typeof statusBar !== "undefined") {
});
};
clearTags = async (tabId: number) => {
clearTags = async (tabId: string) => {
await this.doAsync(
`
const tags = editorTags[${tabId}];
const tags = editorTags["${tabId}"];
logger("info", Object.keys(editorTags), typeof editorTags[0]);
if (tags && tags.current) {
tags.current.setTags([]);
@@ -259,7 +258,7 @@ if (typeof statusBar !== "undefined") {
insertAttachment = async (attachment: Attachment, tabId: number) => {
await this.doAsync(
`const editor = editors[${tabId}];
`const editor = editors["${tabId}"];
editor && editor.commands.insertAttachment(${JSON.stringify(attachment)})`
);
};
@@ -269,7 +268,7 @@ editor && editor.commands.insertAttachment(${JSON.stringify(attachment)})`
tabId: number
) => {
await this.doAsync(
`const editor = editors[${tabId}];
`const editor = editors["${tabId}"];
editor && editor.commands.updateAttachment(${JSON.stringify(
attachmentProgress
)}, {
@@ -288,7 +287,7 @@ editor && editor.commands.updateAttachment(${JSON.stringify(
tabId: number
) => {
await this.doAsync(
`const editor = editors[${tabId}];
`const editor = editors["${tabId}"];
const image = toBlobURL("${image.dataurl}", "${image.hash}");
@@ -315,21 +314,21 @@ editor && editor.commands.insertImage({
getTableOfContents = async () => {
const tabId = useTabStore.getState().currentTab;
return this.doAsync(`
response = editorControllers[${tabId}]?.getTableOfContents() || [];
response = editorControllers["${tabId}"]?.getTableOfContents() || [];
`);
};
focusPassInput = async () => {
const tabId = useTabStore.getState().currentTab;
return this.doAsync(`
response = editorControllers[${tabId}]?.focusPassInput() || [];
response = editorControllers["${tabId}"]?.focusPassInput() || [];
`);
};
blurPassInput = async () => {
const tabId = useTabStore.getState().currentTab;
return this.doAsync(`
response = editorControllers[${tabId}]?.blurPassInput() || [];
response = editorControllers["${tabId}"]?.blurPassInput() || [];
`);
};
@@ -358,47 +357,7 @@ editor && editor.commands.insertImage({
scrollIntoViewById = async (id: string) => {
const tabId = useTabStore.getState().currentTab;
return this.doAsync(`
response = editorControllers[${tabId}]?.scrollIntoView("${id}") || [];
`);
};
newSession = async (sessionId: string, tabId: number, noteId: string) => {
return this.doAsync(`
globalThis.sessions.newSession("${sessionId}", ${tabId}, "${noteId}");
`);
};
getSession = async (id: string): Promise<EditorSessionItem | false> => {
return this.doAsync(`
response = globalThis.sessions.get("${id}");
`);
};
deleteSession = async (id: string) => {
return this.doAsync(`
globalThis.sessions.delete("${id}");
`);
};
deleteSessionsForTabId = async (tabId: number) => {
return this.doAsync(`
globalThis.sessions.deleteForTabId(${tabId});
`);
};
updateSession = async (
id: string,
session: {
tabId: number;
noteId: string;
scrollTop: number;
from: number;
to: number;
sessionId: string;
}
) => {
return this.doAsync(`
globalThis.sessions.updateSession("${id}", ${JSON.stringify(session)});
response = editorControllers["${tabId}"]?.scrollIntoView("${id}") || [];
`);
};
}

View File

@@ -74,7 +74,7 @@ export type EditorMessage<T> = {
value: T;
type: string;
noteId: string;
tabId: number;
tabId: string;
resolverId?: string;
hasTimeout?: boolean;
};
@@ -86,7 +86,7 @@ export type SavePayload = {
type?: "tiptap";
sessionHistoryId?: number;
ignoreEdit: boolean;
tabId: number;
tabId: string;
pendingChanges?: boolean;
};

View File

@@ -102,7 +102,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 +125,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) {
@@ -248,7 +248,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) {
@@ -684,7 +684,7 @@ export const useEditorEvents = (
editor.note.current[noteId] = await db.notes?.note(noteId);
useTabStore
.getState()
.updateTab(useTabStore.getState().currentTab, {
.updateTab(useTabStore.getState().currentTab!, {
session: {
readonly: false
}

View File

@@ -84,6 +84,62 @@ 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,
@@ -121,14 +177,17 @@ export const useEditor = (
const lastContentChangeTime = useRef<Record<string, number>>({});
const lock = useRef(false);
const currentLoadingNoteId = useRef<string>();
const lastTabFocused = useRef(0);
const lastTabFocused = useRef<string>();
const localTabState = useRef<LocalTabState>(new LocalTabState());
const blockIdRef = useRef<string>();
const postMessage = useCallback(
async <T>(type: string, data: T, tabId?: number, waitFor = 300) =>
async <T>(type: string, data: T, tabId?: string, waitFor = 300) =>
await post(
editorRef,
sessionIdRef.current,
typeof tabId !== "number" ? useTabStore.getState().currentTab : tabId,
tabId || useTabStore.getState().currentTab!,
type,
data,
waitFor
@@ -153,9 +212,9 @@ export const useEditor = (
}, [commands, tags]);
useEffect(() => {
const event = eSubscribeEvent(eEditorTabFocused, (tabId) => {
const event = eSubscribeEvent(eEditorTabFocused, (tabId: string) => {
if (lastTabFocused.current !== tabId) lock.current = false;
lastTabFocused.current = tabId as number;
lastTabFocused.current = tabId;
});
return () => {
event?.unsubscribe();
@@ -189,7 +248,7 @@ export const useEditor = (
);
const reset = useCallback(
async (tabId: number, resetState = true, resetContent = true) => {
async (tabId: string, resetState = true, resetContent = true) => {
const noteId = useTabStore.getState().getNoteIdForTab(tabId);
if (noteId) {
currentNotes.current?.id && db.fs().cancel(noteId);
@@ -437,9 +496,10 @@ export const useEditor = (
(event: {
item?: Note;
newNote?: boolean;
tabId?: number;
tabId?: string;
blockId?: string;
session?: TabSessionItem;
newTab?: boolean;
}) => {
loadNoteMutex.runExclusive(async () => {
if (!event) return;
@@ -453,7 +513,7 @@ export const useEditor = (
(await isEditorLoaded(
editorRef,
sessionIdRef.current,
useTabStore.getState().currentTab
useTabStore.getState().currentTab!
))
) {
state.current.ready = true;
@@ -464,37 +524,72 @@ export const useEditor = (
tabId = useTabStore.getState().newTab();
} else {
tabId = useTabStore.getState().currentTab;
await reset(tabId, true, true);
await reset(tabId!, true, true);
if (
event.session?.noteId ||
useTabStore.getState().getTab(tabId)?.session?.noteId
useTabStore.getState().getTab(tabId!)?.session?.noteId
) {
useTabStore.getState().newTabSession(tabId, {});
useTabStore.getState().newTabSession(tabId!, {});
}
}
setTimeout(() => {
if (state.current?.ready && !state.current.movedAway)
commands.focus(tabId);
commands.focus(tabId!);
});
} else {
if (!event.item) {
overlay(false);
return;
}
const item = event.item;
currentLoadingNoteId.current = item.id;
const isLockedNote = await db.vaults.itemExists(
event.item as ItemReference
);
const tabLocked =
isLockedNote && !(event.item as NoteWithContent).content;
let tabId = event.tabId;
if (tabId === undefined) tabId = useTabStore.getState().currentTab;
await commands.setLoading(true, tabId);
if (
tabId &&
!localTabState.current?.needsRefresh(
tabId,
isLockedNote,
item.readonly
)
) {
console.log(
"Note is already updated, skipping refresh request on focus"
);
currentLoadingNoteId.current = undefined;
return;
} else {
console.log("updating the tab in focus...");
localTabState.current?.setEditTime(
item.id,
localTabState.current?.noteEditedTime[item.id] || item.dateEdited
);
localTabState.current?.set(tabId!, {
editedAt:
localTabState.current?.noteEditedTime[item.id] ||
item.dateEdited
});
}
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 || {
readonly: event.item.readonly,
@@ -503,9 +598,9 @@ export const useEditor = (
noteId: event.item.id
};
const tab = useTabStore.getState().getTab(tabId);
const tab = useTabStore.getState().getTab(tabId!);
if (useTabStore.getState().tabs.length === 0) {
if (useTabStore.getState().tabs.length === 0 || event.newTab) {
useTabStore.getState().newTab({
session: session
});
@@ -515,11 +610,11 @@ export const useEditor = (
event.item.id !== tab?.session?.noteId &&
tab?.session?.noteId
) {
useTabStore.getState().newTabSession(tabId, session);
useTabStore.getState().newTabSession(tabId!, session);
console.log("Creating a new tab session");
} else {
console.log("Updating tab session");
useTabStore.getState().updateTab(tabId, {
useTabStore.getState().updateTab(tabId!, {
session: session
});
}
@@ -538,7 +633,7 @@ export const useEditor = (
if (!tabLocked) {
await loadContent(item);
} else {
commands.focus(tabId);
commands.focus(tabId!);
}
lastContentChangeTime.current[item.id] = item.dateEdited;
@@ -551,7 +646,7 @@ export const useEditor = (
await commands.setStatus(
getFormattedDate(item.dateEdited, "date-time"),
"Saved",
tabId
tabId!
);
await postMessage(NativeEvents.title, item.title, tabId);
overlay(false);
@@ -593,7 +688,6 @@ export const useEditor = (
loadContent,
overlay,
postMessage,
readonly,
reset,
theme
]
@@ -624,7 +718,7 @@ export const useEditor = (
: data.id;
if (!useTabStore.getState().hasTabForNote(noteId)) return;
const tabId = useTabStore.getState().getTabForNote(noteId) as number;
const tabId = useTabStore.getState().getTabForNote(noteId) as string;
const tab = useTabStore.getState().getTab(tabId);
@@ -679,7 +773,7 @@ export const useEditor = (
commands.setStatus(
getFormattedDate(note.dateEdited, "date-time"),
strings.saved(),
tabId as number
tabId as string
);
}
@@ -765,7 +859,7 @@ export const useEditor = (
content?: string;
type: string;
ignoreEdit: boolean;
tabId: number;
tabId: string;
pendingChanges?: boolean;
}) => {
DatabaseLogger.log(
@@ -794,6 +888,10 @@ 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) {
@@ -879,7 +977,7 @@ export const useEditor = (
!(await isEditorLoaded(
editorRef,
sessionIdRef.current,
useTabStore.getState().currentTab
useTabStore.getState().currentTab!
))
) {
eSendEvent(eEditorReset, "onReady");

View File

@@ -16,7 +16,11 @@ 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 { TabSessionHistory } from "@notesnook/common";
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";
@@ -27,12 +31,12 @@ import { eOnLoadNote } from "../../../utils/events";
import { editorController } from "./utils";
class TabHistory {
history: number[];
history: string[];
constructor() {
this.history = [0];
this.history = [];
}
add(item: number) {
add(item: string) {
const index = this.history.findIndex((id) => item === id);
if (index !== -1) {
// Item already exists, move it to the top
@@ -41,19 +45,19 @@ class TabHistory {
this.history.unshift(item); // Add item to the beginning of the array
useTabStore.setState({
history: this.history.slice()
historyNew: this.history.slice()
});
return true; // Item added successfully
}
remove(id: number) {
remove(id: string) {
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({
history: this.history.slice()
historyNew: this.history.slice()
});
return null; // Invalid index
}
@@ -64,7 +68,7 @@ class TabHistory {
return restoredItem;
}
useTabStore.setState({
history: this.history.slice()
historyNew: this.history.slice()
});
return null; // History is empty
}
@@ -115,14 +119,6 @@ class TabSessionStorage {
}
}
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 syncTabs(
type: "tabs" | "history" | "biometry" | "all" = "all"
) {
@@ -152,15 +148,15 @@ export const tabSessionHistory = new TabSessionHistory({
return useTabStore.getState();
},
set(state) {
console.log("Set state", state.canGoBack, state.canGoForward);
useTabStore.setState({
...state
});
},
getCurrentTab: () => useTabStore.getState().currentTab
}
});
export type TabItem = {
id: number;
id: string;
pinned?: boolean;
needsRefresh?: boolean;
session?: Partial<TabSessionItem>;
@@ -170,35 +166,32 @@ const history = new TabHistory();
export type TabStore = {
tabs: TabItem[];
currentTab: number;
updateTab: (id: number, options: Omit<Partial<TabItem>, "id">) => void;
currentTab?: string;
updateTab: (id: string, options: Omit<Partial<TabItem>, "id">) => void;
focusPreviewTab: (
noteId: string,
options: Omit<Partial<TabItem>, "id">
) => void;
removeTab: (index: number) => void;
removeTab: (index: string) => void;
moveTab: (index: number, toIndex: number) => void;
newTab: (options?: Omit<Partial<TabItem>, "id">) => number;
focusTab: (id: number) => void;
getNoteIdForTab: (id: number) => string | undefined;
getTabForNote: (noteId: string) => number | undefined;
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;
hasTabForNote: (noteId: string) => boolean;
focusEmptyTab: () => void;
getCurrentNoteId: () => string | undefined;
getTab: (tabId: number) => TabItem | undefined;
getTab: (tabId: string) => TabItem | undefined;
newTabSession: (
id: number,
options: Omit<Partial<TabSessionItem>, "id">
id: string,
options?: Omit<Partial<TabSessionItem>, "id">
) => void;
history: number[];
historyNew: string[];
biometryAvailable?: boolean;
biometryEnrolled?: boolean;
tabSessionHistory: Record<
number,
{ back_stack: string[]; forward_stack: string[] }
>;
tabSessionHistory: TabHistoryType;
goBack(): void;
goForward(): void;
loadSession: (id: string) => Promise<boolean>;
@@ -210,21 +203,27 @@ export type TabStore = {
export const useTabStore = create<TabStore>(
persist(
(set, get) => ({
tabs: [],
tabs: [
{
id: "0"
}
],
tabSessionHistory: {},
history: [0],
currentTab: 0,
historyNew: ["0"],
currentTab: "0",
newTabSession: (
id: number,
options: Omit<Partial<TabSessionItem>, "id">
_id?: string,
options?: Omit<Partial<TabSessionItem>, "id">
) => {
const sessionId = tabSessionHistory.add();
const tabId = _id || (get().currentTab as string);
const sessionId = tabSessionHistory.add(tabId);
const session = {
id: sessionId,
...options
};
TabSessionStorage.set(sessionId, session);
const index = get().tabs.findIndex((t) => t.id === id);
const index = get().tabs.findIndex((t) => t.id === tabId);
if (index == -1) return;
const tabs = [...get().tabs];
tabs[index] = {
@@ -238,7 +237,7 @@ export const useTabStore = create<TabStore>(
});
syncTabs();
},
updateTab: (id: number, options: Omit<Partial<TabItem>, "id">) => {
updateTab: (id: string, options: Omit<Partial<TabItem>, "id">) => {
if (!options) return;
const index = get().tabs.findIndex((t) => t.id === id);
if (index == -1) return;
@@ -262,14 +261,16 @@ export const useTabStore = create<TabStore>(
syncTabs();
},
goBack: async () => {
if (!tabSessionHistory.canGoBack()) return;
const id = tabSessionHistory.back() as string;
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(id);
tabSessionHistory.remove(currentTab, id);
TabSessionStorage.remove(id);
if (!tabSessionHistory.canGoBack()) {
tabSessionHistory.forward();
if (!tabSessionHistory.canGoBack(currentTab)) {
tabSessionHistory.forward(currentTab);
syncTabs();
} else {
return get().goBack();
@@ -279,13 +280,16 @@ export const useTabStore = create<TabStore>(
}
},
goForward: async () => {
if (!tabSessionHistory.canGoForward()) return;
const id = tabSessionHistory.forward() as string;
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(id);
tabSessionHistory.remove(currentTab, id);
TabSessionStorage.remove(id);
if (!tabSessionHistory.canGoForward()) {
tabSessionHistory.back();
if (!tabSessionHistory.canGoForward(currentTab)) {
tabSessionHistory.back(currentTab);
syncTabs();
} else {
return get().goForward();
@@ -314,9 +318,13 @@ export const useTabStore = create<TabStore>(
return false;
}
get().updateTab(get().currentTab, {
session: session
});
const currentTab = get().currentTab;
if (currentTab) {
get().updateTab(currentTab, {
session: session
});
}
console.log("Loading session", session);
eSendEvent(eOnLoadNote, {
item: note,
@@ -332,7 +340,7 @@ export const useTabStore = create<TabStore>(
options: Omit<Partial<TabItem>, "id" | "noteId">
) => {},
removeTab: (id: number) => {
removeTab: (id: string) => {
const index = get().tabs.findIndex((t) => t.id === id);
if (index > -1) {
const isFocused = id === get().currentTab;
@@ -340,30 +348,32 @@ export const useTabStore = create<TabStore>(
nextTabs.splice(index, 1);
history.remove(id);
const tabSessions = tabSessionHistory.getHistory();
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: 0 }]
tabs: [{ id: id }]
});
get().newTabSession(0, {});
get().focusTab(0);
get().newTabSession(id, {});
get().focusTab(id);
} else {
set({
tabs: nextTabs
});
if (isFocused) {
get().focusTab(history.restoreLast() || 0);
const lastTab = history.restoreLast();
if (lastTab) get().focusTab(lastTab);
}
}
syncTabs();
}
},
newTab: (options) => {
const id = getId(get().tabs.length, get().tabs);
const id = getId();
set({
tabs: [
...get().tabs,
@@ -392,19 +402,19 @@ export const useTabStore = create<TabStore>(
syncTabs();
},
focusTab: (id: number) => {
focusTab: (id: string) => {
history.add(id);
set({
currentTab: id
});
set({
canGoBack: tabSessionHistory.canGoBack(),
canGoForward: tabSessionHistory.canGoForward(),
sessionId: tabSessionHistory.currentSessionId()
canGoBack: tabSessionHistory.canGoBack(id),
canGoForward: tabSessionHistory.canGoForward(id),
sessionId: tabSessionHistory.currentSessionId(id)
});
syncTabs();
},
getNoteIdForTab: (id: number) => {
getNoteIdForTab: (id: string) => {
return get().tabs.find((t) => t.id === id)?.session?.noteId;
},
hasTabForNote: (noteId: string) => {
@@ -436,7 +446,7 @@ export const useTabStore = create<TabStore>(
getStorage: () => MMKV as unknown as StateStorage,
onRehydrateStorage: () => {
return (state) => {
history.history = state?.history || [];
history.history = state?.historyNew || [];
};
}
}

View File

@@ -61,7 +61,7 @@ export function makeSessionId(id?: string) {
export async function isEditorLoaded(
ref: RefObject<WebView>,
sessionId: string,
tabId: number
tabId: string
) {
return await post(ref, sessionId, tabId, NativeEvents.status);
}
@@ -69,7 +69,7 @@ export async function isEditorLoaded(
export async function post<T>(
ref: RefObject<WebView>,
sessionId: string,
tabId: number,
tabId: string,
type: string,
value: T | null = null,
waitFor = 300
@@ -174,7 +174,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(() => {

View File

@@ -291,9 +291,10 @@ const Tiptap = ({
const updateFocusedTab = () => {
if (isFocusedRef.current) return;
isFocusedRef.current = true;
const noteId =
useTabStore.getState().tabs[useTabStore.getState().currentTab]?.session
?.noteId;
const noteId = useTabStore
.getState()
.tabs.find((tab) => tab.id === useTabStore.getState().currentTab)
?.session?.noteId;
post(
EditorEvents.tabFocused,
undefined,

View File

@@ -185,10 +185,11 @@ export function useEditorController({
},
tabRef.current.id,
tabRef.current.session?.noteId,
currentSessionId
currentSessionId,
1000
];
const pendingTitleIds = await pendingSaveRequests.getPendingTitleIds();
postAsyncWithTimeout(EditorEvents.title, ...params, 1000)
postAsyncWithTimeout(EditorEvents.title, ...params)
.then(() => {
if (pendingTitleIds.length) {
dbLogger(
@@ -256,12 +257,13 @@ export function useEditorController({
},
tabRef.current.id,
tabRef.current.session?.noteId,
currentSessionId
currentSessionId,
5000
];
const pendingContentIds =
await pendingSaveRequests.getPendingContentIds();
postAsyncWithTimeout(EditorEvents.content, ...params, 5000)
postAsyncWithTimeout(EditorEvents.content, ...params)
.then(() => {
if (pendingContentIds.length) {
dbLogger(

View File

@@ -27,7 +27,7 @@ globalThis.editorTitles = {};
globalThis.statusBars = {};
export type TabItem = {
id: number;
id: string;
session?: {
noteId?: string;
readonly?: boolean;
@@ -42,7 +42,7 @@ export type TabItem = {
export type TabStore = {
tabs: TabItem[];
currentTab: number;
currentTab?: string;
scrollPosition: Record<number, number>;
biometryAvailable?: boolean;
biometryEnrolled?: boolean;
@@ -57,10 +57,10 @@ export const useTabStore = create(
(set, get) => ({
tabs: [
{
id: 0
id: "0"
}
],
currentTab: 0,
currentTab: "0",
scrollPosition: {},
getCurrentNoteId: () => {
return get().tabs.find((t) => t.id === get().currentTab)?.session
@@ -76,9 +76,7 @@ export const useTabStore = create(
globalThis.tabStore = useTabStore;
export const TabContext = createContext<TabItem>({
id: 0
});
export const TabContext = createContext<TabItem>({} as TabItem);
export const useTabContext = () => {
const tab = useContext(TabContext);

View File

@@ -64,7 +64,7 @@ declare global {
var readonlyEditor: boolean;
var statusBars: Record<
number,
string,
| React.MutableRefObject<{
set: React.Dispatch<
React.SetStateAction<{
@@ -92,11 +92,11 @@ declare global {
/**
* Current tiptap editors
*/
var editors: Record<number, Editor | null>;
var editors: Record<string, Editor | null>;
/**
* Current editor controllers
*/
var editorControllers: Record<number, EditorController | undefined>;
var editorControllers: Record<string, EditorController | undefined>;
var settingsController: {
update: (settings: Settings) => void;
@@ -124,12 +124,12 @@ declare global {
>;
};
var editorTitles: Record<number, RefObject<HTMLTextAreaElement> | undefined>;
var editorTitles: Record<string, RefObject<HTMLTextAreaElement> | undefined>;
/**
* Global ref to manage tags in editor.
*/
var editorTags: Record<
number,
string,
| MutableRefObject<{
setTags: React.Dispatch<
React.SetStateAction<
@@ -155,7 +155,7 @@ declare global {
function post<T extends keyof typeof EditorEvents>(
type: (typeof EditorEvents)[T],
value?: unknown,
tabId?: number,
tabId?: string,
noteId?: string,
sessionId?: string
): void;
@@ -227,7 +227,7 @@ export function dbLogger(type: "error" | "log", ...logs: unknown[]): void {
export function post(
type: string,
value?: unknown,
tabId?: number,
tabId?: string,
noteId?: string,
sessionId?: string,
hasTimeout?: boolean
@@ -254,7 +254,7 @@ export function post(
export async function postAsyncWithTimeout<R = any>(
type: string,
value?: unknown,
tabId?: number,
tabId?: string,
noteId?: string,
sessionId?: string,
waitFor?: number

View File

@@ -119,7 +119,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, 5000);
await postAsyncWithTimeout(EditorEvents.title, ...pending.params);
}
};
@@ -128,11 +128,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,
5000
);
await postAsyncWithTimeout(EditorEvents.content, ...pending.params);
}
};