mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-31 11:08:47 +02:00
Compare commits
101 Commits
fix-widget
...
fix-image-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5292f39b97 | ||
|
|
2447acca03 | ||
|
|
efa73a1c32 | ||
|
|
00250c6669 | ||
|
|
d11afda1a8 | ||
|
|
1350784c33 | ||
|
|
1852a216a2 | ||
|
|
422c2492c4 | ||
|
|
c9c72741b3 | ||
|
|
99dbcaff7d | ||
|
|
8d83f5474d | ||
|
|
a912a1a22e | ||
|
|
ea7afbe93e | ||
|
|
7b2a8c3e20 | ||
|
|
911adfc151 | ||
|
|
23f710856e | ||
|
|
7f6274821c | ||
|
|
4700843517 | ||
|
|
8823054227 | ||
|
|
7c63a3e004 | ||
|
|
2a59154268 | ||
|
|
ed15d53484 | ||
|
|
fc8a4bb55d | ||
|
|
dd1c1bb11d | ||
|
|
3ffbad5727 | ||
|
|
207ae39d13 | ||
|
|
ae141ac9c5 | ||
|
|
40f058b14b | ||
|
|
865c9f3e32 | ||
|
|
26df9bfdd8 | ||
|
|
1e52e01fb8 | ||
|
|
5fece5faaa | ||
|
|
d7fdaf6c46 | ||
|
|
8e1faf9f60 | ||
|
|
9dce39b32d | ||
|
|
80a0b75887 | ||
|
|
c9704e1031 | ||
|
|
31e32cd154 | ||
|
|
61c296fc37 | ||
|
|
c8d38235b2 | ||
|
|
b717a64607 | ||
|
|
0f2b1d3e14 | ||
|
|
4abefe63db | ||
|
|
fc31b2a8d7 | ||
|
|
2708bac4fb | ||
|
|
a47967dd53 | ||
|
|
53ba5562d3 | ||
|
|
347df32943 | ||
|
|
ac8c63526b | ||
|
|
a48bd360ca | ||
|
|
f476969e9e | ||
|
|
c45539dea6 | ||
|
|
d8f5815b84 | ||
|
|
2eaf07118a | ||
|
|
b636b09a96 | ||
|
|
c16bbcaad5 | ||
|
|
49ffcbea03 | ||
|
|
f772cf0f14 | ||
|
|
3f1406a05b | ||
|
|
21760b2b74 | ||
|
|
d8011f0239 | ||
|
|
b9a8b054e2 | ||
|
|
2ab321869e | ||
|
|
fcb45a24d6 | ||
|
|
7cdb13def6 | ||
|
|
2ab58f9203 | ||
|
|
df74448e17 | ||
|
|
ada98fca93 | ||
|
|
1d3650659f | ||
|
|
9b43af475b | ||
|
|
d9c592c7fb | ||
|
|
f10ac9fbb5 | ||
|
|
b7334c09d4 | ||
|
|
0cd3bd5739 | ||
|
|
33d69480eb | ||
|
|
5cae4eb0d9 | ||
|
|
840c7fda5e | ||
|
|
4ce24ac8fd | ||
|
|
ae9e80e12e | ||
|
|
43356e5b6a | ||
|
|
7b13f35424 | ||
|
|
0517c6ce41 | ||
|
|
54d06328c1 | ||
|
|
c74677acd4 | ||
|
|
7689e1a7c5 | ||
|
|
5ffae2a08b | ||
|
|
5fa80abdc8 | ||
|
|
a30598fedf | ||
|
|
48ffecfb5f | ||
|
|
89fe712104 | ||
|
|
5dd298ee86 | ||
|
|
275b285a8b | ||
|
|
96310a46e9 | ||
|
|
42f9bf1593 | ||
|
|
25aab6913a | ||
|
|
3bd7da68be | ||
|
|
a218738eff | ||
|
|
8bf283b3bc | ||
|
|
cb09430ff4 | ||
|
|
66d75492bb | ||
|
|
d772d38dc7 |
4
.github/workflows/desktop.publish.yml
vendored
4
.github/workflows/desktop.publish.yml
vendored
@@ -37,7 +37,7 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -99,7 +99,7 @@ jobs:
|
||||
- name: Generate flatpak sources
|
||||
if: inputs.publish-github && inputs.build-linux
|
||||
run: |
|
||||
sudo apt-get install -y flatpak flatpak-builder pipx
|
||||
sudo apt-get install -y --quiet flatpak flatpak-builder pipx
|
||||
git clone https://github.com/flatpak/flatpak-builder-tools.git /tmp/flatpak-builder-tools
|
||||
cd /tmp/flatpak-builder-tools/node
|
||||
pipx install .
|
||||
|
||||
2
.github/workflows/web.tests.yml
vendored
2
.github/workflows/web.tests.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
name: 🧪 Test (${{ matrix.shard }}/${{ strategy.job-total }})
|
||||
strategy:
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4]
|
||||
shard: [1, 2, 3, 4, 5]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ nx-cloud.env
|
||||
site
|
||||
node_modules.backup
|
||||
.nx
|
||||
*.patch
|
||||
|
||||
37
7421.patch
37
7421.patch
@@ -1,37 +0,0 @@
|
||||
From af88ee05e1620afb65c76148d347cb7efc32bded Mon Sep 17 00:00:00 2001
|
||||
From: 01zulfi <85733202+01zulfi@users.noreply.github.com>
|
||||
Date: Wed, 29 Jan 2025 12:22:32 +0500
|
||||
Subject: [PATCH] editor: fix hover styling in toolbar color buttons
|
||||
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
|
||||
|
||||
---
|
||||
packages/editor/src/toolbar/popups/color-picker.tsx | 2 +-
|
||||
packages/editor/src/toolbar/tools/colors.tsx | 2 +-
|
||||
2 files changed, 2 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/packages/editor/src/toolbar/popups/color-picker.tsx b/packages/editor/src/toolbar/popups/color-picker.tsx
|
||||
index 0abca19268..34209bd550 100644
|
||||
--- a/packages/editor/src/toolbar/popups/color-picker.tsx
|
||||
+++ b/packages/editor/src/toolbar/popups/color-picker.tsx
|
||||
@@ -284,7 +284,7 @@ function PaletteButton(props: PaletteButtonProps) {
|
||||
ml: [2, 2, 1],
|
||||
bg,
|
||||
":hover:not(:disabled):not(:active)": {
|
||||
- bg: bg?.startsWith("#") ? colord(bg).darken(5).toRgbString() : "hover"
|
||||
+ bg: bg?.startsWith("#") ? colord(bg).darken().toRgbString() : "hover"
|
||||
},
|
||||
...sx
|
||||
}}
|
||||
diff --git a/packages/editor/src/toolbar/tools/colors.tsx b/packages/editor/src/toolbar/tools/colors.tsx
|
||||
index 08c6b41d8a..77ea660e75 100644
|
||||
--- a/packages/editor/src/toolbar/tools/colors.tsx
|
||||
+++ b/packages/editor/src/toolbar/tools/colors.tsx
|
||||
@@ -137,7 +137,7 @@ export function ColorTool(props: ColorToolProps) {
|
||||
mr: 0,
|
||||
bg: activeColor || "transparent",
|
||||
":hover:not(:disabled):not(:active)": {
|
||||
- bg: tColor ? tColor.darken(5).toRgbString() : "transparent"
|
||||
+ bg: tColor ? tColor.darken().toRgbString() : "transparent"
|
||||
}
|
||||
}}
|
||||
onOpen={toggle}
|
||||
Binary file not shown.
4
apps/desktop/package-lock.json
generated
4
apps/desktop/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "3.0.25",
|
||||
"version": "3.0.28",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "3.0.25",
|
||||
"version": "3.0.28",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@notesnook/desktop",
|
||||
"productName": "Notesnook",
|
||||
"description": "Your private note taking space",
|
||||
"version": "3.0.25",
|
||||
"version": "3.0.28",
|
||||
"appAppleId": "1544027013",
|
||||
"private": true,
|
||||
"main": "./dist/cjs/index.js",
|
||||
|
||||
@@ -39,7 +39,6 @@ import SettingsService from "./services/settings";
|
||||
import { TipManager } from "./services/tip-manager";
|
||||
import { useThemeStore } from "./stores/use-theme-store";
|
||||
import { useUserStore } from "./stores/use-user-store";
|
||||
import { IntentService } from "./services/intent";
|
||||
|
||||
I18nManager.allowRTL(false);
|
||||
I18nManager.forceRTL(false);
|
||||
@@ -49,7 +48,6 @@ if (appLockEnabled || appLockMode !== "none") {
|
||||
useUserStore.getState().lockApp(true);
|
||||
}
|
||||
|
||||
IntentService.onLaunch();
|
||||
const App = (props: { configureMode: "note-preview" }) => {
|
||||
useAppEvents();
|
||||
//@ts-ignore
|
||||
|
||||
@@ -115,7 +115,7 @@ export const FileSizeResult = {
|
||||
Error: -1
|
||||
};
|
||||
|
||||
export async function getUploadedFileSize(hash: string) {
|
||||
export async function getUploadedFileSize(hash: string, retry = 0) {
|
||||
try {
|
||||
const url = `${hosts.API_HOST}/s3?name=${hash}`;
|
||||
const token = await db.tokenManager.getAccessToken();
|
||||
@@ -123,8 +123,24 @@ export async function getUploadedFileSize(hash: string) {
|
||||
method: "HEAD",
|
||||
headers: { Authorization: `Bearer ${token}` }
|
||||
});
|
||||
|
||||
if (
|
||||
!attachmentInfo.ok ||
|
||||
attachmentInfo.headers?.get("content-length") === null
|
||||
) {
|
||||
if (retry < 3) {
|
||||
DatabaseLogger.log(`Retrying file size check: ${hash}, ${retry}`);
|
||||
return getUploadedFileSize(hash, retry + 1);
|
||||
}
|
||||
throw new Error(
|
||||
`File size check failed: ${hash}, ${
|
||||
attachmentInfo.status
|
||||
}, ${attachmentInfo.headers?.get("content-length")}`
|
||||
);
|
||||
}
|
||||
|
||||
const contentLength = parseInt(
|
||||
attachmentInfo.headers?.get("content-length") || "0"
|
||||
attachmentInfo.headers?.get("content-length") as string
|
||||
);
|
||||
return isNaN(contentLength) ? FileSizeResult.Empty : contentLength;
|
||||
} catch (e) {
|
||||
|
||||
@@ -112,14 +112,19 @@ const AppLockedOverlay = () => {
|
||||
if (
|
||||
!biometricsAuthEnabled ||
|
||||
!(await BiometricService.isBiometryAvailable())
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
biometricUnlockAwaitingUserInput.current = true;
|
||||
|
||||
if (Platform.OS === "android") {
|
||||
const activityName = await NotesnookModule.getActivityName();
|
||||
if (activityName !== "MainActivity") return;
|
||||
if (
|
||||
activityName !== "MainActivity" &&
|
||||
activityName !== "NotePreviewConfigureActivity"
|
||||
)
|
||||
return;
|
||||
}
|
||||
useSettingStore.getState().setRequestBiometrics(true);
|
||||
|
||||
@@ -180,7 +185,6 @@ const AppLockedOverlay = () => {
|
||||
DatabaseLogger.info("Locking app on entering foreground");
|
||||
useUserStore.getState().lockApp(true);
|
||||
}
|
||||
|
||||
if (
|
||||
!(
|
||||
biometricUnlockAwaitingUserInput.current ||
|
||||
|
||||
@@ -80,8 +80,9 @@ export const Dialog = ({ context = "global" }) => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
hide();
|
||||
setChecked(false);
|
||||
values.current.inputValue = undefined;
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
const show = useCallback(
|
||||
@@ -104,14 +105,14 @@ export const Dialog = ({ context = "global" }) => {
|
||||
eUnSubscribeEvent(eOpenSimpleDialog, show);
|
||||
eUnSubscribeEvent(eCloseSimpleDialog, hide);
|
||||
};
|
||||
}, [show]);
|
||||
}, [hide, show]);
|
||||
|
||||
const hide = () => {
|
||||
const hide = React.useCallback(() => {
|
||||
setChecked(false);
|
||||
values.current.inputValue = undefined;
|
||||
setVisible(false);
|
||||
dialogInfo.onClose();
|
||||
};
|
||||
dialogInfo.onClose?.();
|
||||
}, [dialogInfo]);
|
||||
|
||||
const onNegativePress = async () => {
|
||||
if (dialogInfo.onClose) {
|
||||
|
||||
@@ -76,7 +76,8 @@ const NoteItem = ({
|
||||
}: NoteItemProps) => {
|
||||
const isEditingNote = useTabStore(
|
||||
(state) =>
|
||||
state.tabs.find((t) => t.id === state.currentTab)?.noteId === item.id
|
||||
state.tabs.find((t) => t.id === state.currentTab)?.session?.noteId ===
|
||||
item.id
|
||||
);
|
||||
const { colors } = useThemeColors();
|
||||
const compactMode = useIsCompactModeEnabled(
|
||||
|
||||
@@ -28,7 +28,8 @@ export const Filler = ({ item, color }: { item: Item; color?: string }) => {
|
||||
const { colors } = useThemeColors();
|
||||
const isEditingNote = useTabStore(
|
||||
(state) =>
|
||||
state.tabs.find((t) => t.id === state.currentTab)?.noteId === item.id
|
||||
state.tabs.find((t) => t.id === state.currentTab)?.session?.noteId ===
|
||||
item.id
|
||||
);
|
||||
|
||||
const [selected] = useIsSelected(item);
|
||||
|
||||
@@ -50,6 +50,7 @@ import { MMKV } from "../common/database/mmkv";
|
||||
import { endProgress, startProgress } from "../components/dialogs/progress";
|
||||
import Migrate from "../components/sheets/migrate";
|
||||
import NewFeature from "../components/sheets/new-feature";
|
||||
import ReminderSheet from "../components/sheets/reminder";
|
||||
import { Walkthrough } from "../components/walkthroughs";
|
||||
import {
|
||||
resetTabStore,
|
||||
@@ -58,7 +59,8 @@ import {
|
||||
import {
|
||||
clearAppState,
|
||||
editorController,
|
||||
editorState
|
||||
editorState,
|
||||
setAppState
|
||||
} from "../screens/editor/tiptap/utils";
|
||||
import { useDragState } from "../screens/settings/editor/state";
|
||||
import BackupService from "../services/backup";
|
||||
@@ -69,7 +71,6 @@ import {
|
||||
eSubscribeEvent,
|
||||
presentSheet
|
||||
} from "../services/event-manager";
|
||||
import { IntentService } from "../services/intent";
|
||||
import {
|
||||
clearMessage,
|
||||
setEmailVerifyMessage,
|
||||
@@ -98,7 +99,6 @@ import {
|
||||
eLoginSessionExpired,
|
||||
eOnLoadNote,
|
||||
eOpenAnnouncementDialog,
|
||||
eUnlockNote,
|
||||
eUserLoggedIn,
|
||||
refreshNotesPage
|
||||
} from "../utils/events";
|
||||
@@ -106,7 +106,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();
|
||||
@@ -162,7 +161,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();
|
||||
@@ -751,13 +749,15 @@ export const useAppEvents = () => {
|
||||
setTimeout(() => {
|
||||
sub = AppState.addEventListener("change", onAppStateChanged);
|
||||
if (
|
||||
refValues.current.initialUrl?.startsWith(
|
||||
"https://app.notesnook.com/account/verified"
|
||||
)
|
||||
refValues.current.initialUrl &&
|
||||
!refValues.current.initialUrl?.includes("open_note")
|
||||
) {
|
||||
onUserEmailVerified();
|
||||
onAppOpenedFromURL({
|
||||
url: refValues.current.initialUrl!
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
refValues.current.removeInternetStateListener = NetInfo.addEventListener(
|
||||
onInternetStateChanged
|
||||
);
|
||||
|
||||
@@ -17,6 +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 { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { NavigationContainer } from "@react-navigation/native";
|
||||
import { createNativeStackNavigator } from "@react-navigation/native-stack";
|
||||
@@ -44,10 +45,6 @@ import useNavigationStore from "../stores/use-navigation-store";
|
||||
import { useSelectionStore } from "../stores/use-selection-store";
|
||||
import { useSettingStore } from "../stores/use-setting-store";
|
||||
import { rootNavigatorRef } from "../utils/global-refs";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { IntentService } from "../services/intent";
|
||||
import ReminderSheet from "../components/sheets/reminder";
|
||||
import { db } from "../common/database";
|
||||
|
||||
const NativeStack = createNativeStackNavigator();
|
||||
const IntroStack = createNativeStackNavigator();
|
||||
@@ -86,17 +83,6 @@ const _Tabs = () => {
|
||||
React.useEffect(() => {
|
||||
setTimeout(async () => {
|
||||
useNavigationStore.getState().update(homepage);
|
||||
const intent = IntentService.getLaunchIntent();
|
||||
if (intent && intent["com.streetwriters.notesnook.OpenReminderId"]) {
|
||||
const reminder = await db.reminders.reminder(
|
||||
intent["com.streetwriters.notesnook.OpenReminderId"]
|
||||
);
|
||||
if (reminder) {
|
||||
ReminderSheet.present(reminder);
|
||||
}
|
||||
} else if (intent["com.streetwriters.notesnook.NewReminder"]) {
|
||||
ReminderSheet.present();
|
||||
}
|
||||
}, 1000);
|
||||
}, [homepage]);
|
||||
|
||||
|
||||
@@ -509,7 +509,8 @@ const onChangeTab = async (event) => {
|
||||
});
|
||||
} else {
|
||||
if (
|
||||
useTabStore.getState().getTab(useTabStore.getState().currentTab).locked
|
||||
useTabStore.getState().getTab(useTabStore.getState().currentTab).session
|
||||
?.locked
|
||||
) {
|
||||
eSendEvent(eUnlockNote);
|
||||
}
|
||||
@@ -523,7 +524,7 @@ const onChangeTab = async (event) => {
|
||||
|
||||
// Lock all tabs with locked notes...
|
||||
for (const tab of useTabStore.getState().tabs) {
|
||||
const noteId = useTabStore.getState().getTab(tab.id)?.noteId;
|
||||
const noteId = useTabStore.getState().getTab(tab.id)?.session?.noteId;
|
||||
if (!noteId) continue;
|
||||
const note = await db.notes.note(noteId);
|
||||
const locked = note && (await db.vaults.itemExists(note));
|
||||
|
||||
@@ -248,14 +248,13 @@ const useLockedNoteHandler = () => {
|
||||
);
|
||||
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note
|
||||
});
|
||||
|
||||
useTabStore.getState().updateTab(tabRef.current.id, {
|
||||
session: {
|
||||
locked: false
|
||||
}
|
||||
item: note,
|
||||
refresh: true
|
||||
});
|
||||
} else {
|
||||
if (tabRef.current && tabRef.current.session?.locked) {
|
||||
editorController.current?.commands.focusPassInput();
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@@ -310,12 +309,8 @@ const useLockedNoteHandler = () => {
|
||||
}
|
||||
}
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note
|
||||
});
|
||||
useTabStore.getState().updateTab(tabRef.current.id, {
|
||||
session: {
|
||||
locked: false
|
||||
}
|
||||
item: note,
|
||||
refresh: true
|
||||
});
|
||||
} catch (e) {
|
||||
ToastManager.show({
|
||||
|
||||
@@ -90,8 +90,14 @@ class Commands {
|
||||
|
||||
const locked = useTabStore.getState().getTab(tabId)?.session?.locked;
|
||||
if (Platform.OS === "android") {
|
||||
//this.ref.current?.requestFocus();
|
||||
setTimeout(async () => {
|
||||
if (
|
||||
locked &&
|
||||
useTabStore.getState().biometryAvailable &&
|
||||
useTabStore.getState().biometryEnrolled
|
||||
)
|
||||
return;
|
||||
|
||||
if (!this.ref) return;
|
||||
textInput.current?.focus();
|
||||
await this.sendCommand("focus", tabId, locked);
|
||||
@@ -170,13 +176,13 @@ class Commands {
|
||||
await this.sendCommand("clearTags", tabId);
|
||||
};
|
||||
|
||||
insertAttachment = async (attachment: Attachment, tabId: number) => {
|
||||
insertAttachment = async (attachment: Attachment, tabId: string) => {
|
||||
await this.sendCommand("insertAttachment", attachment, tabId);
|
||||
};
|
||||
|
||||
setAttachmentProgress = async (
|
||||
attachmentProgress: Partial<Attachment>,
|
||||
tabId: number
|
||||
tabId: string
|
||||
) => {
|
||||
await this.sendCommand("setAttachmentProgress", attachmentProgress, tabId);
|
||||
};
|
||||
@@ -185,7 +191,7 @@ class Commands {
|
||||
image: Omit<ImageAttributes, "bloburl"> & {
|
||||
dataurl: string;
|
||||
},
|
||||
tabId: number
|
||||
tabId: string
|
||||
) => {
|
||||
await this.sendCommand("insertImage", image, tabId);
|
||||
};
|
||||
|
||||
@@ -61,7 +61,7 @@ const santizeUri = (uri: string) => {
|
||||
|
||||
type PickerOptions = {
|
||||
noteId?: string;
|
||||
tabId?: number;
|
||||
tabId?: string;
|
||||
type: "image" | "camera" | "file";
|
||||
reupload: boolean;
|
||||
hash?: string;
|
||||
|
||||
@@ -613,7 +613,7 @@ export const useEditorEvents = (
|
||||
}
|
||||
case EditorEvents.tabFocused: {
|
||||
eSendEvent(eEditorTabFocused, editorMessage.tabId);
|
||||
|
||||
if (!editor.state.current.initialLoadCalled) break;
|
||||
if (editorMessage.noteId) {
|
||||
if (!useSettingStore.getState().isAppLoading) {
|
||||
const note = await db.notes.note(editorMessage.noteId);
|
||||
|
||||
@@ -77,6 +77,7 @@ import {
|
||||
isEditorLoaded,
|
||||
post
|
||||
} from "./utils";
|
||||
import { Linking } from "react-native";
|
||||
|
||||
const loadNoteMutex = new Mutex();
|
||||
|
||||
@@ -126,15 +127,8 @@ class LocalTabState {
|
||||
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];
|
||||
}
|
||||
@@ -500,6 +494,7 @@ export const useEditor = (
|
||||
blockId?: string;
|
||||
session?: TabSessionItem;
|
||||
newTab?: boolean;
|
||||
refresh?: boolean;
|
||||
}) => {
|
||||
loadNoteMutex.runExclusive(async () => {
|
||||
if (!event) return;
|
||||
@@ -520,7 +515,12 @@ export const useEditor = (
|
||||
}
|
||||
if (event.newNote && !currentLoadingNoteId.current) {
|
||||
let tabId;
|
||||
if (useTabStore.getState().tabs.length === 0) {
|
||||
|
||||
const currentTab = useTabStore
|
||||
.getState()
|
||||
.getTab(useTabStore.getState().currentTab as string);
|
||||
|
||||
if (useTabStore.getState().tabs.length === 0 || currentTab?.pinned) {
|
||||
tabId = useTabStore.getState().newTab();
|
||||
} else {
|
||||
tabId = useTabStore.getState().currentTab;
|
||||
@@ -545,14 +545,14 @@ export const useEditor = (
|
||||
|
||||
const item = event.item;
|
||||
|
||||
const currentTab = useTabStore
|
||||
.getState()
|
||||
.getTab(useTabStore.getState().currentTab as string);
|
||||
|
||||
// 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
|
||||
@@ -573,11 +573,10 @@ export const useEditor = (
|
||||
? event.tabId
|
||||
: useTabStore.getState().currentTab;
|
||||
|
||||
console.log(tabId === useTabStore.getState().currentTab);
|
||||
|
||||
// Check if tab needs to be refreshed.
|
||||
if (!event.newTab) {
|
||||
if (
|
||||
!event.refresh &&
|
||||
tabId &&
|
||||
event.item.id === useTabStore.getState().getNoteIdForTab(tabId) &&
|
||||
!localTabState.current?.needsRefresh(
|
||||
@@ -623,10 +622,16 @@ export const useEditor = (
|
||||
|
||||
const tab = useTabStore.getState().getTab(tabId!);
|
||||
|
||||
if (useTabStore.getState().tabs.length === 0 || event.newTab) {
|
||||
if (
|
||||
useTabStore.getState().tabs.length === 0 ||
|
||||
event.newTab ||
|
||||
(currentTab?.pinned &&
|
||||
event.item.id !== currentTab?.session?.noteId)
|
||||
) {
|
||||
useTabStore.getState().newTab({
|
||||
session: session
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
// A new session is created if the note is changed.
|
||||
// If the note is already opened, the session is updated.
|
||||
@@ -1039,6 +1044,17 @@ export const useEditor = (
|
||||
}, [isDefaultEditor, restoreEditorState]);
|
||||
|
||||
const onLoad = useCallback(async () => {
|
||||
const isAppLoading = useSettingStore.getState().isAppLoading;
|
||||
if (isAppLoading) {
|
||||
const sub = useSettingStore.subscribe((state) => {
|
||||
if (!state.isAppLoading) {
|
||||
sub();
|
||||
onLoad();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
postMessage(NativeEvents.theme, theme);
|
||||
});
|
||||
@@ -1055,32 +1071,34 @@ export const useEditor = (
|
||||
state.current.ready = true;
|
||||
}
|
||||
|
||||
const appState = getAppState();
|
||||
if (appState?.noteId) {
|
||||
const note = await db.notes?.note(appState.noteId);
|
||||
if (note) {
|
||||
loadNote({
|
||||
item: note
|
||||
});
|
||||
}
|
||||
} else {
|
||||
const noteId = useTabStore.getState().getCurrentNoteId();
|
||||
if (!noteId) {
|
||||
loadNote({ newNote: true });
|
||||
if (tabBarRef.current?.page() === 1) {
|
||||
state.current.currentlyEditing = false;
|
||||
}
|
||||
} else if (state.current?.initialLoadCalled) {
|
||||
const note = currentNotes.current[noteId];
|
||||
if (!state.current?.initialLoadCalled) {
|
||||
const url = await Linking.getInitialURL();
|
||||
let noteId = url && new URL(url).searchParams.get("id");
|
||||
if (noteId) {
|
||||
const note = await db.notes?.note(noteId);
|
||||
if (note) {
|
||||
loadNote({
|
||||
item: note
|
||||
});
|
||||
}
|
||||
tabBarRef.current?.goToPage(1);
|
||||
} else {
|
||||
noteId = useTabStore.getState().getCurrentNoteId() || null;
|
||||
if (!noteId) {
|
||||
loadNote({ newNote: true });
|
||||
if (tabBarRef.current?.page() === 1) {
|
||||
state.current.currentlyEditing = false;
|
||||
}
|
||||
} else {
|
||||
const note = await db.notes.note(noteId);
|
||||
if (note) {
|
||||
loadNote({
|
||||
item: note
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!state.current?.initialLoadCalled) {
|
||||
state.current.initialLoadCalled = true;
|
||||
}
|
||||
state.current.initialLoadCalled = true;
|
||||
overlay(false);
|
||||
}
|
||||
}, [
|
||||
|
||||
@@ -97,10 +97,12 @@ class TabSessionStorage {
|
||||
static storage: typeof TabSessionStorageKV = TabSessionStorageKV;
|
||||
|
||||
static get(id: string): TabSessionItem | null {
|
||||
if (!id) return null;
|
||||
return TabSessionStorage.storage.getMap(id);
|
||||
}
|
||||
|
||||
static set(id: string, session: TabSessionItem): void {
|
||||
if (!id) return;
|
||||
TabSessionStorage.storage.setMap(id, session);
|
||||
}
|
||||
|
||||
@@ -108,13 +110,16 @@ class TabSessionStorage {
|
||||
const currentSession = TabSessionStorage.get(id);
|
||||
const newSession = {
|
||||
...currentSession,
|
||||
...session
|
||||
...session,
|
||||
id: currentSession?.id || session.id || id
|
||||
};
|
||||
|
||||
TabSessionStorage.set(id, newSession as TabSessionItem);
|
||||
return newSession;
|
||||
}
|
||||
|
||||
static remove(id: string) {
|
||||
if (!id) return;
|
||||
TabSessionStorageKV.removeItem(id);
|
||||
}
|
||||
}
|
||||
@@ -164,7 +169,7 @@ const history = new TabHistory();
|
||||
|
||||
export type TabStore = {
|
||||
tabs: TabItem[];
|
||||
currentTab?: string;
|
||||
currentTab: string;
|
||||
updateTab: (id: string, options: Omit<Partial<TabItem>, "id">) => void;
|
||||
focusPreviewTab: (
|
||||
noteId: string,
|
||||
@@ -264,6 +269,7 @@ export const useTabStore = create<TabStore>(
|
||||
TabSessionStorage.set(sessionId, session as TabSessionItem);
|
||||
} else {
|
||||
session = {
|
||||
id: sessionId,
|
||||
...TabSessionStorage.get(oldSessionId),
|
||||
...options
|
||||
};
|
||||
@@ -289,6 +295,7 @@ export const useTabStore = create<TabStore>(
|
||||
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);
|
||||
@@ -313,13 +320,15 @@ export const useTabStore = create<TabStore>(
|
||||
const sessionLoaded = await get().loadSession(id);
|
||||
|
||||
if (!sessionLoaded) {
|
||||
tabSessionHistory.remove(currentTab, id);
|
||||
TabSessionStorage.remove(id);
|
||||
if (!tabSessionHistory.canGoBack(currentTab)) {
|
||||
const canGoBack = tabSessionHistory.canGoBack(currentTab);
|
||||
if (!canGoBack) {
|
||||
tabSessionHistory.forward(currentTab);
|
||||
syncTabs();
|
||||
} else {
|
||||
return get().goBack();
|
||||
get().goBack();
|
||||
tabSessionHistory.remove(currentTab, id);
|
||||
TabSessionStorage.remove(id);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
syncTabs();
|
||||
@@ -332,13 +341,15 @@ export const useTabStore = create<TabStore>(
|
||||
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)) {
|
||||
const canGoForward = tabSessionHistory.canGoForward(currentTab);
|
||||
if (!canGoForward) {
|
||||
tabSessionHistory.back(currentTab);
|
||||
syncTabs();
|
||||
} else {
|
||||
return get().goForward();
|
||||
get().goForward();
|
||||
tabSessionHistory.remove(currentTab, id);
|
||||
TabSessionStorage.remove(id);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
syncTabs();
|
||||
@@ -354,8 +365,8 @@ export const useTabStore = create<TabStore>(
|
||||
|
||||
if (note) {
|
||||
const isLocked = await db.vaults.itemExists(note);
|
||||
session.noteLocked = isLocked;
|
||||
session.locked = isLocked;
|
||||
session.noteLocked = isLocked && !session?.noteLocked;
|
||||
|
||||
session.readonly = note.readonly;
|
||||
} else if (session.noteId) {
|
||||
@@ -395,7 +406,7 @@ export const useTabStore = create<TabStore>(
|
||||
set({
|
||||
tabs: [{ id: id }]
|
||||
});
|
||||
get().newTabSession(id, {});
|
||||
get().newTabSession(id);
|
||||
get().focusTab(id);
|
||||
} else {
|
||||
set({
|
||||
@@ -474,7 +485,7 @@ export const useTabStore = create<TabStore>(
|
||||
}
|
||||
}),
|
||||
{
|
||||
name: "tabs-storage-v3",
|
||||
name: "tabs-storage-v5",
|
||||
getStorage: () => MMKV as unknown as StateStorage,
|
||||
onRehydrateStorage: () => {
|
||||
return (state) => {
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
StackActions,
|
||||
useNavigation
|
||||
} from "@react-navigation/native";
|
||||
import React, { useRef, useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { ActivityIndicator, TextInput, View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import ToggleSwitch from "toggle-switch-react-native";
|
||||
@@ -41,7 +41,10 @@ import { RouteParams, SettingSection } from "./types";
|
||||
|
||||
const _SectionItem = ({ item }: { item: SettingSection }) => {
|
||||
const { colors } = useThemeColors();
|
||||
const settings = useSettingStore((state) => state.settings);
|
||||
const [settings, itemProperty] = useSettingStore((state) => [
|
||||
state.settings,
|
||||
item.property ? state.settings[item.property] : null
|
||||
]);
|
||||
const navigation = useNavigation<NavigationProp<RouteParams>>();
|
||||
const current = item.useHook && item.useHook(item);
|
||||
const [isHidden, setIsHidden] = useState(
|
||||
@@ -103,6 +106,11 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setIsHidden(item.hidden && item.hidden(item.property || current));
|
||||
setIsDisabled(item.disabled && item.disabled(item.property || current));
|
||||
}, [current, item, itemProperty]);
|
||||
|
||||
return isHidden ? null : (
|
||||
<Pressable
|
||||
disabled={item.type === "component" || isDisabled}
|
||||
|
||||
@@ -451,7 +451,7 @@ export const settingsGroups: SettingSection[] = [
|
||||
"local",
|
||||
"partial"
|
||||
);
|
||||
if (result.error) throw result.error as Error;
|
||||
if (result?.error) throw result.error as Error;
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e);
|
||||
const error = e;
|
||||
|
||||
@@ -30,12 +30,15 @@ import { DatabaseLogger, db } from "../common/database";
|
||||
import filesystem, { FileStorage } from "../common/filesystem";
|
||||
import { cacheDir, copyFileAsync } from "../common/filesystem/utils";
|
||||
import { presentDialog } from "../components/dialog/functions";
|
||||
import { endProgress, updateProgress } from "../components/dialogs/progress";
|
||||
import {
|
||||
endProgress,
|
||||
startProgress,
|
||||
updateProgress
|
||||
} from "../components/dialogs/progress";
|
||||
import { eCloseSheet } from "../utils/events";
|
||||
import { sleep } from "../utils/time";
|
||||
import { ToastManager, eSendEvent, presentSheet } from "./event-manager";
|
||||
import SettingsService from "./settings";
|
||||
import { e } from "@lingui/react/dist/shared/react.b2b749a9";
|
||||
|
||||
const MS_DAY = 86400000;
|
||||
const MS_WEEK = MS_DAY * 7;
|
||||
@@ -156,6 +159,7 @@ async function updateNextBackupTime(type: "full" | "partial") {
|
||||
[type === "full" ? "lastFullBackupDate" : "lastBackupDate"]: Date.now()
|
||||
});
|
||||
}
|
||||
let backupRunning = false;
|
||||
/**
|
||||
* @param {boolean=} progress
|
||||
* @param {string=} context
|
||||
@@ -165,7 +169,23 @@ async function run(
|
||||
progress = false,
|
||||
context?: string,
|
||||
backupType: "full" | "partial" = "partial"
|
||||
) {
|
||||
): Promise<{
|
||||
path?: string;
|
||||
error?: Error;
|
||||
report?: boolean;
|
||||
}> {
|
||||
if (backupRunning) {
|
||||
if (progress) {
|
||||
startProgress({
|
||||
title: strings.backingUpData(backupType),
|
||||
paragraph: strings.backupDataDesc(),
|
||||
progress: "Backup in progress...",
|
||||
canHideProgress: true
|
||||
});
|
||||
}
|
||||
return {};
|
||||
}
|
||||
backupRunning = true;
|
||||
const androidBackupDirectory = (await checkBackupDirExists(
|
||||
false,
|
||||
context
|
||||
@@ -193,10 +213,11 @@ async function run(
|
||||
);
|
||||
|
||||
if (progress) {
|
||||
presentSheet({
|
||||
startProgress({
|
||||
title: strings.backingUpData(backupType),
|
||||
paragraph: strings.backupDataDesc(),
|
||||
progress: true
|
||||
progress: "Preparing backup...",
|
||||
canHideProgress: true
|
||||
});
|
||||
}
|
||||
|
||||
@@ -274,14 +295,7 @@ async function run(
|
||||
path = zipOutputFile;
|
||||
}
|
||||
|
||||
RNFetchBlob.fs.unlink(zipSourceFolder).catch(() => {
|
||||
/* empty */
|
||||
});
|
||||
if (Platform.OS === "android") {
|
||||
RNFetchBlob.fs.unlink(zipOutputFile).catch(() => {
|
||||
/* empty */
|
||||
});
|
||||
}
|
||||
cleanupAfterBackup(zipSourceFolder, zipOutputFile);
|
||||
|
||||
updateNextBackupTime(backupType || "partial");
|
||||
|
||||
@@ -292,6 +306,8 @@ async function run(
|
||||
const canShowCompletionStatus =
|
||||
progress && SettingsService.get().showBackupCompleteSheet;
|
||||
|
||||
backupRunning = false;
|
||||
|
||||
if (context) {
|
||||
return {
|
||||
path: path
|
||||
@@ -302,8 +318,6 @@ async function run(
|
||||
|
||||
if (canShowCompletionStatus) {
|
||||
presentBackupCompleteSheet(path);
|
||||
} else {
|
||||
eSendEvent(eCloseSheet);
|
||||
}
|
||||
|
||||
ToastManager.show({
|
||||
@@ -311,11 +325,11 @@ async function run(
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
|
||||
return {
|
||||
path: path
|
||||
};
|
||||
} catch (e) {
|
||||
backupRunning = false;
|
||||
ToastManager.error(e as Error, strings.backupFailed(), context || "global");
|
||||
|
||||
if (
|
||||
@@ -326,27 +340,30 @@ async function run(
|
||||
return run(progress, context, backupType);
|
||||
}
|
||||
|
||||
RNFetchBlob.fs.unlink(zipSourceFolder).catch(() => {
|
||||
/* empty */
|
||||
});
|
||||
if (Platform.OS === "android") {
|
||||
RNFetchBlob.fs.unlink(zipOutputFile).catch(() => {
|
||||
/* empty */
|
||||
});
|
||||
}
|
||||
cleanupAfterBackup(zipSourceFolder, zipOutputFile);
|
||||
|
||||
DatabaseLogger.error(e);
|
||||
await sleep(300);
|
||||
if (progress) {
|
||||
endProgress();
|
||||
}
|
||||
return {
|
||||
error: e,
|
||||
error: e as Error,
|
||||
report: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function cleanupAfterBackup(zipSourceFolder: string, zipOutputFile: string) {
|
||||
RNFetchBlob.fs.unlink(zipSourceFolder).catch(() => {
|
||||
/* empty */
|
||||
});
|
||||
if (Platform.OS === "android") {
|
||||
RNFetchBlob.fs.unlink(zipOutputFile).catch(() => {
|
||||
/* empty */
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function checkBackupRequired(
|
||||
type: "daily" | "off" | "useroff" | "weekly" | "monthly" | "never",
|
||||
lastBackupDateType: "lastBackupDate" | "lastFullBackupDate" = "lastBackupDate"
|
||||
|
||||
@@ -45,7 +45,17 @@ function resetSettings() {
|
||||
...defaultSettings,
|
||||
introCompleted: true,
|
||||
serverUrls: get().serverUrls,
|
||||
backupDirectoryAndroid: undefined
|
||||
backupDirectoryAndroid: get().backupDirectoryAndroid,
|
||||
darkTheme: get().darkTheme,
|
||||
lighTheme: get().lighTheme,
|
||||
useSystemTheme: get().useSystemTheme,
|
||||
colorScheme: get().colorScheme,
|
||||
defaultSnoozeTime: get().defaultSnoozeTime,
|
||||
defaultFontFamily: get().defaultFontFamily,
|
||||
defaultFontSize: get().defaultFontSize,
|
||||
privacyScreen: get().privacyScreen,
|
||||
corsProxy: get().corsProxy,
|
||||
showBackupCompleteSheet: get().showBackupCompleteSheet
|
||||
};
|
||||
|
||||
MMKV.setString("appSettings", JSON.stringify(settings));
|
||||
|
||||
@@ -116,7 +116,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
multiDexEnabled true
|
||||
versionCode 3038
|
||||
versionCode 3042
|
||||
versionName getNpmVersion()
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
- New and improved editor tabs with history
|
||||
- Fixed homescreen widget bugs
|
||||
- Many small bug fixes and improvements
|
||||
- Small bug fixes and improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
@@ -1063,7 +1063,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2125;
|
||||
CURRENT_PROJECT_VERSION = 2129;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
ENABLE_BITCODE = NO;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1137,7 +1137,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.28;
|
||||
MARKETING_VERSION = 3.0.32;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -1168,7 +1168,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 2125;
|
||||
CURRENT_PROJECT_VERSION = 2129;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1242,7 +1242,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.28;
|
||||
MARKETING_VERSION = 3.0.32;
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
@@ -1401,7 +1401,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2125;
|
||||
CURRENT_PROJECT_VERSION = 2129;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1413,7 +1413,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.28;
|
||||
MARKETING_VERSION = 3.0.32;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
@@ -1444,7 +1444,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2125;
|
||||
CURRENT_PROJECT_VERSION = 2129;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1457,7 +1457,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.28;
|
||||
MARKETING_VERSION = 3.0.32;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1487,7 +1487,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2125;
|
||||
CURRENT_PROJECT_VERSION = 2129;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1561,7 +1561,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.28;
|
||||
MARKETING_VERSION = 3.0.32;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
@@ -1592,7 +1592,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2125;
|
||||
CURRENT_PROJECT_VERSION = 2129;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1667,7 +1667,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.28;
|
||||
MARKETING_VERSION = 3.0.32;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
||||
@@ -1820,7 +1820,7 @@ SPEC CHECKSUMS:
|
||||
Base64: cecfb41a004124895a7bcee567a89bae5a89d49b
|
||||
BEMCheckBox: 5ba6e37ade3d3657b36caecc35c8b75c6c2b1a4e
|
||||
boost: d3f49c53809116a5d38da093a8aa78bf551aed09
|
||||
callstack-repack: 2db7cbeced7cd5da360d01965e0b983f6dacfd54
|
||||
callstack-repack: 721b034d0662f47388719bf26c00f838da6880d5
|
||||
DoubleConversion: 76ab83afb40bddeeee456813d9c04f67f78771b5
|
||||
FBLazyVector: ac12dc084d1c8ec4cc4d7b3cf1b0ebda6dab85af
|
||||
fmt: 4c2741a687cc09f0634a2e2c72a838b99f1ff120
|
||||
@@ -1837,103 +1837,103 @@ SPEC CHECKSUMS:
|
||||
RCTTypeSafety: f5ecbc86c5c5fa163c05acb7a1c5012e15b5f994
|
||||
React: fc9fa7258eff606f44d58c5b233a82dc9cf09018
|
||||
React-callinvoker: e3fab14d69607fb7e8e3a57e5a415aed863d3599
|
||||
React-Codegen: 49efb5917c904b5b01ef708311a2f1773f4fe73e
|
||||
React-Core: 3a5fd9e781cecf87803e5b091496a606a3df774a
|
||||
React-CoreModules: cbf4707dafab8f9f826ac0c63a07d0bf5d01e256
|
||||
React-cxxreact: 7b188556271e3c7fdf22a04819f6a6225045b9dd
|
||||
React-Codegen: ff6c425afab9288a1573337111741d30363bf962
|
||||
React-Core: c3f589f104983dec3c3eeec5e70d61aa811bc236
|
||||
React-CoreModules: 864932ddae3ead5af5bfb05f9bbc2cedcb958b39
|
||||
React-cxxreact: bd9146108c44e6dbb99bba4568ce7af0304a2419
|
||||
React-debug: d30893c49ae1bce4037ea5cd8bb2511d2a38d057
|
||||
React-Fabric: 826729dd2304fda9b89ff0a579f60ba2a470bc26
|
||||
React-FabricImage: 2ad1fb8ffa5778eda9ed204a7b3cdd70bc333ce7
|
||||
React-Fabric: a171830e52baf8ec2b175c6a3791e01bbb92f1fb
|
||||
React-FabricImage: ad154af0067f4b5dc5a41f607e48ee343641e903
|
||||
React-featureflags: 4ae83e72d9a92452793601ac9ac7d2280e486089
|
||||
React-graphics: 61a026e1c1e7e20d20ac9fec6f6de631732b233d
|
||||
React-hermes: a7054fbcbda3957e3c5eaad06ef9bf79998d535a
|
||||
React-ImageManager: 2bbd6eb2e696bc680f76f84563e4b87d241614e1
|
||||
React-jserrorhandler: 56fa04d49bfbe54ddfece7916673a73ebfea286b
|
||||
React-jsi: f3ce1dd2e950b6ad12b65ea3ef89168f1b94c584
|
||||
React-jsiexecutor: b4df3a27973d82f9abf3c4bd0f88e042cda25f16
|
||||
React-jsinspector: 97ea746c023687de7313ee289817d6991d596c7d
|
||||
React-jsitracing: 3b6060bbf5317663667e1dd93560c7943ab86ccc
|
||||
React-logger: 257858bd55f3a4e1bc0cf07ddc8fb9faba6f8c7c
|
||||
React-Mapbuffer: 6c1cacdbf40b531f549eba249e531a7d0bfd8e7f
|
||||
react-native-actions-shortcuts: 5d9cf0c9c308333dfcc1e05c3f9afa8c428e2533
|
||||
react-native-background-actions: 2c251c986f23347f9c1722f05fd296938f60edb1
|
||||
react-native-begin-background-task: 3b889e07458afc5822a7277cf9cbc7cd077e39ee
|
||||
react-native-blob-util: 2d36383bb52c15c5451be81cb7ddf22bc34a12a6
|
||||
react-native-config: 86038147314e2e6d10ea9972022aa171e6b1d4d8
|
||||
react-native-date-picker: 93e43b3084cea595b4d68b1405d6d99849663bd6
|
||||
react-native-document-picker: ec07866a30707f23660c0f3ae591d669d3e89096
|
||||
react-native-fingerprint-scanner: be63e626b31fb951780a5fac5328b065a61a3d6e
|
||||
react-native-get-random-values: dee677497c6a740b71e5612e8dbd83e7539ed5bb
|
||||
react-native-gzip: c5e87ee9e359f02350e3a2ee52eb35eddc398868
|
||||
react-native-html-to-pdf-lite: 21bfb169bf4cbcd7bec9f736975ee1b3f5292d4a
|
||||
react-native-image-resizer: 00ceb0e05586c7aadf061eea676957a6c2ec60fa
|
||||
react-native-in-app-review: db8bb167a5f238e7ceca5c242d6b36ce8c4404a4
|
||||
react-native-keep-awake: caee3ff89eaa21dfe29010f0d143566874a04441
|
||||
react-native-mmkv-storage: 3892853c11ab8a0c3a3f9ef3b0657aa9a5f616c9
|
||||
react-native-netinfo: ccbe1085dffd16592791d550189772e13bf479e2
|
||||
react-native-notification-sounds: da78c828fe1bcbb92d8b505d5261890ed315ff39
|
||||
react-native-orientation: f1caf84d65f1a4fd4511a18f2b924e634ad7a628
|
||||
react-native-pdf: 33c622cbdf776a649929e8b9d1ce2d313347c4fa
|
||||
react-native-quick-sqlite: 18e1367c34faac90e37f6eb3e78c196e9b674b5d
|
||||
react-native-safe-area-context: b7daa1a8df36095a032dff095a1ea8963cb48371
|
||||
react-native-screenguard: 8b36a3df84c76cd2b82c477f71c26fa1c8cc14a0
|
||||
react-native-share-extension: 01b25fc15faf2510cc2529e98ca444f64e46238d
|
||||
react-native-sodium: 4cb76086943a7f60c42b40ebca866695b360a196
|
||||
react-native-theme-switch-animation: d3eb50365a3829ce5572628888fa514752703f61
|
||||
react-native-webview: 553abd09f58e340fdc7746c9e2ae096839e99911
|
||||
React-graphics: ed7d57965140168de86835946e8f1210c72c65dc
|
||||
React-hermes: 177b1efdf3b8f10f4ca12b624b83fb4d4ccb2884
|
||||
React-ImageManager: 3a50d0ee0bf81b1a6f23a0c5b30388293bcd6004
|
||||
React-jserrorhandler: dcd62f5ca1c724c19637595ef7f45b78018e758f
|
||||
React-jsi: 0abe1b0881b67caf8d8df6a57778dd0d3bb9d9a5
|
||||
React-jsiexecutor: f6ca8c04f19f6a3acaa9610f7fb728f39d6e3248
|
||||
React-jsinspector: db98771eae84e6f86f0ca5d9dcc572baadbfefc0
|
||||
React-jsitracing: f8367edacc50bb3f9f056a5aeafb8cee5849fafb
|
||||
React-logger: 780b9ee9cec7d44eabc4093de90107c379078cb6
|
||||
React-Mapbuffer: f544f00b98dbdd8cbae96dd2bdb8b47f719976e0
|
||||
react-native-actions-shortcuts: 65fb9cb4a58c71af7382663c1822dda061dbf1b0
|
||||
react-native-background-actions: 40e09df6ea8c7d2753f6c1d75f6f1eee4a9bc35f
|
||||
react-native-begin-background-task: 2191f2a84b0328932a3d44db4361e666fee781bb
|
||||
react-native-blob-util: 4be49d669870f01645b63d33342718e95d0d48b1
|
||||
react-native-config: 136f9755ccc991cc6438053a44363259ad4c7813
|
||||
react-native-date-picker: bbf77c50dce9da2beb11e2f67f3300195ba27d81
|
||||
react-native-document-picker: f4ccd6f6f369da1409e609788181eac66cc1a2d8
|
||||
react-native-fingerprint-scanner: 562585260768cad51ae48e4b505d28c8731aecfa
|
||||
react-native-get-random-values: 419569b6ed3d15bfb9b6781b2f2e058f8e8d2698
|
||||
react-native-gzip: 8d602277c2564591f04dd1cec4043acc8350dcc3
|
||||
react-native-html-to-pdf-lite: d606d3b72d9696c7a6d1e5f5b7536783faacef14
|
||||
react-native-image-resizer: ff3c9ab0ce235c4c00f49d0a373043cb4b8781be
|
||||
react-native-in-app-review: b3d1eed3d1596ebf6539804778272c4c65e4a400
|
||||
react-native-keep-awake: bda639e941ce138506c18f1b999dcbf16d4c77b8
|
||||
react-native-mmkv-storage: 8dddc9d16248bc9a4d9566ac5c97c318692d299c
|
||||
react-native-netinfo: 66c9ac0d0ae92a57a9ed99ab8a75429740700c73
|
||||
react-native-notification-sounds: ce106d58df0dd384bccbd2e84fb53accab7cc068
|
||||
react-native-orientation: db2655b95b4123f17e75dcb04ee8b8b1294ce7ee
|
||||
react-native-pdf: da10473ae54555c373c62af671da8fe0eef846e2
|
||||
react-native-quick-sqlite: 80b5b3405bfc92e97b6041d2c2a446800bea6ad7
|
||||
react-native-safe-area-context: b72c4611af2e86d80a59ac76279043d8f75f454c
|
||||
react-native-screenguard: 82437eeb0086a90b5e5d7e54130bb04fb406373e
|
||||
react-native-share-extension: 7528322fe621b683e6b48ab3418207ed45a70983
|
||||
react-native-sodium: ed020d5f50ab1886f7bf23e32732d0d10e9373ac
|
||||
react-native-theme-switch-animation: 64f2688c2e3489ababc7b35f436fd3b5ed7c0a98
|
||||
react-native-webview: a71525b1ab760230fbf37303d8371fbe72051c7d
|
||||
React-nativeconfig: ba9a2e54e2f0882cf7882698825052793ed4c851
|
||||
React-NativeModulesApple: 8d11ff8955181540585c944cf48e9e7236952697
|
||||
React-NativeModulesApple: 84aaad2b0e546d7b839837ca537f6e72804a4cad
|
||||
React-perflogger: ed4e0c65781521e0424f2e5e40b40cc7879d737e
|
||||
React-RCTActionSheet: 49d53ff03bb5688ca4606c55859053a0cd129ea5
|
||||
React-RCTAnimation: 07b4923885c52c397c4ec103924bf6e53b42c73e
|
||||
React-RCTAppDelegate: 316e295076734baf9bdf1bfac7d92ab647aed930
|
||||
React-RCTBlob: 85c57b0d5e667ff8a472163ba3af0628171a64bb
|
||||
React-RCTFabric: 97c1465ded4dc92841f5376a39e43e1b2c455f40
|
||||
React-RCTImage: b965c85bec820e2a9c154b1fb00a2ecdd59a9c92
|
||||
React-RCTLinking: 75f04a5f27c26c4e73a39c50df470820d219df79
|
||||
React-RCTNetwork: c1a9143f4d5778efc92da40d83969d03912ccc24
|
||||
React-RCTSettings: c6800f91c0ecd48868cd5db754b0b0a7f5ffe039
|
||||
React-RCTText: b923e24f9b7250bc4f7ab154c4168ad9f8d8fc9d
|
||||
React-RCTVibration: 08c4f0c917c435b3619386c25a94ee5d64c250f0
|
||||
React-rendererdebug: 3cda04217d9df67b94397ee0ead8ef3d8b7e427b
|
||||
React-RCTAnimation: 3075449f26cb98a52bcbf51cccd0c7954e2a71db
|
||||
React-RCTAppDelegate: 9a419c4dda9dd039ad851411546dd297b930c454
|
||||
React-RCTBlob: e81ab773a8fc1e9dceed953e889f936a7b7b3aa6
|
||||
React-RCTFabric: 47a87a3e3fa751674f7e64d0bcd58976b8c57db9
|
||||
React-RCTImage: d570531201c6dce7b5b63878fa8ecec0cc311c4c
|
||||
React-RCTLinking: af888972b925d2811633d47853c479e88c35eb4d
|
||||
React-RCTNetwork: 5728a06ff595003eca628f43f112a804f4a9a970
|
||||
React-RCTSettings: ba3665b0569714a8aaceee5c7d23b943e333fa55
|
||||
React-RCTText: b733fa984f0336b072e47512898ba91214f66ddb
|
||||
React-RCTVibration: 0cbcbbd8781b6f6123671bae9ee5dd20d621af6c
|
||||
React-rendererdebug: 9fc8f7d0bd19f2a3fe3791982af550b5e1535ff7
|
||||
React-rncore: 4013508a2f3fcf46c961919bbbd4bfdda198977e
|
||||
React-RuntimeApple: 447844a2bdb0a03ffd24e5b4a4b96cfc50325b88
|
||||
React-RuntimeCore: 9b5bffdaccee9b707b1c2694c9044e13ff0bb087
|
||||
React-RuntimeApple: a852a6e06ab20711658873f39cb10b0033bea19d
|
||||
React-RuntimeCore: 12e5e176c0cb09926f3e6f37403a84d2e0f203a7
|
||||
React-runtimeexecutor: 0e688aefc14c6bc8601f4968d8d01c3fb6446844
|
||||
React-RuntimeHermes: 4d6ef6bb0f2b0b40d59143317f6b99c82764c959
|
||||
React-runtimescheduler: cfbe85c3510c541ec6dc815c7729b41304b67961
|
||||
React-utils: f242eb7e7889419d979ca0e1c02ccc0ea6e43b29
|
||||
ReactCommon: f7da14a8827b72704169a48c929bcde802698361
|
||||
RNBackgroundFetch: 35c7183f6eeff69396d42c4b206e1ae2a21e9327
|
||||
RNBootSplash: de2c568373a9c79a66e9918b8929eb6c9a35246f
|
||||
RNCCheckbox: 43bcc6493611468af0e19f19f029dab3da8561c4
|
||||
RNCClipboard: 3f0451a8100393908bea5c5c5b16f96d45f30bfc
|
||||
RNCMaskedView: 949696f25ec596bfc697fc88e6f95cf0c79669b6
|
||||
RNDateTimePicker: 40ffda97d071a98a10fdca4fa97e3977102ccd14
|
||||
RNDeviceInfo: aad3c663b25752a52bf8fce93f2354001dd185aa
|
||||
RNExitApp: c4e052df2568b43bec8a37c7cd61194d4cfee2c3
|
||||
RNFileViewer: ce7ca3ac370e18554d35d6355cffd7c30437c592
|
||||
RNFlashList: e9b57a5553639f9b528cc50ab53f25831722ed62
|
||||
RNGestureHandler: 4fd316f1bdb8d7f6a54efc58da5923cb83014b19
|
||||
RNIap: 17583f958d35788a87948e94072ff6edd3580497
|
||||
RNImageCropPicker: d9616a0cb9b72e8551ff94a7a5021fbd29050aa5
|
||||
RNKeychain: 840f8e6f13be0576202aefcdffd26a4f54bfe7b5
|
||||
RNNotifee: 1448fd16c2a6f3e8abbc219801bf5be2435183e5
|
||||
RNPrivacySnapshot: 8eaf571478a353f2e5184f5c803164f22428b023
|
||||
RNReanimated: 7212582b487bceed64b2dbc6c15777b97922be21
|
||||
RNScreens: d2a12528d1abe4922e7ee5c9a00dd40c827c2ab5
|
||||
RNSecureRandom: 07efbdf2cd99efe13497433668e54acd7df49fef
|
||||
RNShare: 22717e910836a66cb7255b3b8c4ab06cbe346e27
|
||||
RNSVG: d7d7bc8229af3842c9cfc3a723c815a52cdd1105
|
||||
RNTooltips: 5424d4bf0b3d441104127943b1115cc7f0616b1f
|
||||
RNZipArchive: 9d90367b1573903b5561a8c0b911e5cc12626e74
|
||||
React-RuntimeHermes: 80c03a5215520c9733764ba11cbe535053c9746d
|
||||
React-runtimescheduler: 2cbd0f3625b30bba08e8768776107f6f0203159b
|
||||
React-utils: 9fa4e5d0b5e6c6c85c958f19d6ef854337886417
|
||||
ReactCommon: 9f285823dbe955099978d9bff65a7653ca029256
|
||||
RNBackgroundFetch: 0c49d5892a0e2f9c139e24ba498985cf0bf070c6
|
||||
RNBootSplash: 3bc12ecb62f69c5af6e6ddf0e6bef88f572ccdb5
|
||||
RNCCheckbox: df88c1c78a17869da84bb031c9d8185d417398a6
|
||||
RNCClipboard: efe1b27ad1ea378c60c8c8aabfd130961bbeb474
|
||||
RNCMaskedView: 4b5b12efdee55966f1d68f32d4b3401c2acf05c2
|
||||
RNDateTimePicker: 818460dc31b0dc5ec58289003e27dd8d022fb79c
|
||||
RNDeviceInfo: d3e91ffb33ee97a7982108476edb68cb3672efa6
|
||||
RNExitApp: 890cce29b4e01372c84b3b775f9a63f90d77de19
|
||||
RNFileViewer: 4b5d83358214347e4ab2d4ca8d5c1c90d869e251
|
||||
RNFlashList: fe872a2d1b4c859c64a7d7421628a1308ebb9e4f
|
||||
RNGestureHandler: 61302deb50c92cba160f5a11f00f69da48116724
|
||||
RNIap: aa38c2cc8a2a75ba8112d161ca084a0b611955df
|
||||
RNImageCropPicker: 30d770b383d84e1067d82ea7b5ed4fff851bbbb2
|
||||
RNKeychain: ffd0513e676445c637410b47249460cbf56bc9cb
|
||||
RNNotifee: b98fb71f678f6644fa48ec6d515ec6fcfc5214ba
|
||||
RNPrivacySnapshot: ccad3a548338c2f526bb7b1789af3fb0618b7d1d
|
||||
RNReanimated: bee9626cf4920f2910bf7c4f8a3dc569161a9475
|
||||
RNScreens: d0db5e9f28afd284a5193474d6be949f4cba888e
|
||||
RNSecureRandom: b64d263529492a6897e236a22a2c4249aa1b53dc
|
||||
RNShare: 5d7762ded21e1fff97dda6d41e963e22dce2d025
|
||||
RNSVG: d7cb8bd34550cf4c4fc7edd7ac627905e2b71f3f
|
||||
RNTooltips: 50ff94bd2bff84ef74279c9532d008f5fdcddb95
|
||||
RNZipArchive: 71befbdc20e7fbd4b64552a0b324ad0e8d0efefb
|
||||
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
|
||||
SexyTooltip: 5c9b4dec52bfb317938cb0488efd9da3717bb6fd
|
||||
SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d
|
||||
SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef
|
||||
SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6
|
||||
TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654
|
||||
toolbar-android: 2a73856e98b750d7e71ce4644d3f41cc98211719
|
||||
toolbar-android: c426ed5bd3dcccfed20fd79533efc0d1ae0ef018
|
||||
Yoga: 950bbfd7e6f04790fdb51149ed51df41f329fcc8
|
||||
|
||||
PODFILE CHECKSUM: aa07aca16c1d92394d8ce12f2c5fcaa8a3ae4997
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
"react-native-begin-background-task": "github:blockfirm/react-native-begin-background-task",
|
||||
"react-native-privacy-snapshot": "github:standardnotes/react-native-privacy-snapshot",
|
||||
"@ammarahmed/react-native-fingerprint-scanner": "^5.0.0",
|
||||
"@ammarahmed/react-native-share-extension": "^2.8.0"
|
||||
"@ammarahmed/react-native-share-extension": "^2.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"detox": "^20.27.6",
|
||||
|
||||
163
apps/mobile/package-lock.json
generated
163
apps/mobile/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.0.27",
|
||||
"version": "3.0.31",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.0.27",
|
||||
"version": "3.0.31",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
@@ -14,6 +14,7 @@
|
||||
"app/"
|
||||
],
|
||||
"dependencies": {
|
||||
"@ammarahmed/react-native-share-extension": "^2.9.0",
|
||||
"@notesnook/common": "file:../../packages/common",
|
||||
"@notesnook/core": "file:../../packages/core",
|
||||
"@notesnook/crypto": "file:../../packages/crypto",
|
||||
@@ -7742,7 +7743,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 +7763,7 @@
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/@types/react": {
|
||||
"version": "18.2.39",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
@@ -7793,7 +7794,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 +12619,7 @@
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/immer": {
|
||||
"version": "9.0.21",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@@ -23090,7 +23091,6 @@
|
||||
},
|
||||
"../../packages/editor/node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"../../packages/editor/node_modules/jsesc": {
|
||||
@@ -23141,7 +23141,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 +23652,6 @@
|
||||
},
|
||||
"../../packages/editor/node_modules/react": {
|
||||
"version": "18.3.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
@@ -23672,7 +23670,6 @@
|
||||
},
|
||||
"../../packages/editor/node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
@@ -23811,7 +23808,6 @@
|
||||
},
|
||||
"../../packages/editor/node_modules/scheduler": {
|
||||
"version": "0.23.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
@@ -28319,7 +28315,6 @@
|
||||
"@ammarahmed/react-native-background-fetch": "^4.2.2",
|
||||
"@ammarahmed/react-native-eventsource": "1.1.0",
|
||||
"@ammarahmed/react-native-fingerprint-scanner": "^5.0.0",
|
||||
"@ammarahmed/react-native-share-extension": "^2.8.0",
|
||||
"@ammarahmed/react-native-sodium": "^1.6.1",
|
||||
"@bam.tech/react-native-image-resizer": "3.0.5",
|
||||
"@callstack/repack": "^4.1.1",
|
||||
@@ -28457,9 +28452,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ammarahmed/react-native-share-extension": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-share-extension/-/react-native-share-extension-2.8.0.tgz",
|
||||
"integrity": "sha512-eDFVKiJJxjnIORveMUKVJGIhKfRNjqJWIoKS+JM88q8I8e3jHys9moyJAr9QM5oW/tZx4yyItl08yRXGnr7Grg==",
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-share-extension/-/react-native-share-extension-2.9.0.tgz",
|
||||
"integrity": "sha512-iEkpJQFZL5/WDPh7iRCLZREANgaaAHthDsPaAYT6sI4wieQcHZMBAnfRMwBEnC0BZ0WVxbQZdzJYez+dmwpr2g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-native": "^0.63.1"
|
||||
}
|
||||
@@ -28594,7 +28590,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 +28709,6 @@
|
||||
},
|
||||
"node_modules/@babel/helper-hoist-variables": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.22.5"
|
||||
@@ -28974,7 +28968,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 +28982,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 +29130,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 +29142,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 +29176,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 +29188,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 +29225,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 +29249,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 +29263,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 +29277,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 +29288,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 +29387,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 +29414,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 +29442,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 +29472,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 +29500,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 +29515,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 +29579,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 +29594,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 +29608,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 +29620,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 +29636,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 +29663,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 +29693,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 +29721,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 +29734,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 +29749,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 +29779,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 +29796,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 +29825,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 +29854,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 +29869,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 +29885,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 +29901,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 +29974,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 +30043,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 +30059,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 +30151,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 +30181,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 +30195,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 +30224,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 +30239,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 +30332,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 +30347,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 +33650,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 +33756,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 +34119,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 +34127,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 +34152,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 +34169,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 +34177,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 +34184,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 +34205,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 +34217,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 +34228,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 +34241,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 +34298,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",
|
||||
@@ -35370,7 +35302,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 +35732,7 @@
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.2",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
@@ -36401,7 +36332,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 +36451,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 +36796,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 +36808,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 +36971,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 +36982,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 +36990,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 +37749,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 +39838,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 +40314,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 +42543,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 +42564,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",
|
||||
@@ -43563,7 +43507,6 @@
|
||||
},
|
||||
"node_modules/regenerator-transform": {
|
||||
"version": "0.15.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.8.4"
|
||||
@@ -43916,7 +43859,6 @@
|
||||
},
|
||||
"node_modules/serialize-javascript": {
|
||||
"version": "6.0.1",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"randombytes": "^2.1.0"
|
||||
@@ -44574,7 +44516,6 @@
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.2.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -44652,7 +44593,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 +44626,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 +44639,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 +45215,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 +45238,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 +45353,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"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.0.28",
|
||||
"version": "3.0.32",
|
||||
"private": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
@@ -54,4 +54,4 @@
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.74.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,7 +546,7 @@ const ShareView = () => {
|
||||
</Paragraph>
|
||||
<ScrollView horizontal>
|
||||
{rawFiles.map((item) =>
|
||||
isImage(item.type) ? (
|
||||
isImage(item.type) || item.value?.endsWith(".png") ? (
|
||||
<TouchableOpacity
|
||||
onPress={() => onRemoveFile(item)}
|
||||
key={item.name}
|
||||
@@ -604,7 +604,8 @@ const ShareView = () => {
|
||||
paddingRight: 8
|
||||
}}
|
||||
>
|
||||
{item.name} ({formatBytes(item.size)})
|
||||
{item.name || item.value.split("/").pop()}
|
||||
{item.size ? `(${formatBytes(item.size)})` : ""}
|
||||
</Paragraph>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
FROM --platform=$BUILDPLATFORM oven/bun:1.1.36-alpine
|
||||
FROM oven/bun:1.2.2-alpine
|
||||
|
||||
RUN mkdir -p /home/bun/app && chown -R bun:bun /home/bun/app
|
||||
|
||||
@@ -11,4 +11,4 @@ COPY --chown=bun:bun output .
|
||||
|
||||
RUN bun install
|
||||
|
||||
CMD [ "bun", "run", "start" ]
|
||||
CMD [ "bun", "run", "start" ]
|
||||
|
||||
@@ -62,8 +62,42 @@ export default function handleRequest(
|
||||
|
||||
responseHeaders.set("Content-Type", "text/html; charset=utf-8");
|
||||
|
||||
const nonce =
|
||||
remixContext.staticHandlerContext?.loaderData?.root?.cspScriptNonce;
|
||||
if (nonce)
|
||||
responseHeaders.set(
|
||||
"Content-Security-Policy",
|
||||
getContentSecurityPolicy(nonce)
|
||||
);
|
||||
|
||||
return new Response(html, {
|
||||
status: responseStatusCode,
|
||||
headers: responseHeaders
|
||||
});
|
||||
}
|
||||
|
||||
function getContentSecurityPolicy(nonce?: string) {
|
||||
let script_src: string;
|
||||
if (typeof nonce === "string") {
|
||||
script_src = `'self' 'report-sample' 'nonce-${nonce}'`;
|
||||
} else if (process.env.NODE_ENV === "development") {
|
||||
// Allow the <LiveReload /> component to load without a nonce in the error pages
|
||||
script_src = "'self' 'report-sample'";
|
||||
} else {
|
||||
script_src = "'self' 'report-sample'";
|
||||
}
|
||||
|
||||
const connect_src =
|
||||
process.env.NODE_ENV === "development"
|
||||
? "'self' ws://localhost:*"
|
||||
: "'self'";
|
||||
|
||||
return (
|
||||
`script-src ${script_src} 'strict-dynamic'; ` +
|
||||
`connect-src ${connect_src}; ` +
|
||||
"form-action 'self'; " +
|
||||
"object-src 'none'; " +
|
||||
"block-all-mixed-content; " +
|
||||
"base-uri 'self'; manifest-src 'self'"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,14 +22,22 @@ import {
|
||||
Meta,
|
||||
Outlet,
|
||||
Scripts,
|
||||
ScrollRestoration
|
||||
ScrollRestoration,
|
||||
useLoaderData
|
||||
} from "@remix-run/react";
|
||||
import { BaseThemeProvider } from "./components/theme-provider";
|
||||
import { Buffer } from "buffer";
|
||||
import { ThemeDark, themeToCSS } from "@notesnook/theme";
|
||||
import { LoaderFunction } from "@remix-run/node";
|
||||
|
||||
globalThis.Buffer = Buffer;
|
||||
|
||||
type RootLoaderData = { cspScriptNonce: string };
|
||||
export const loader: LoaderFunction = async () => {
|
||||
const crypto = await import("node:crypto");
|
||||
return { cspScriptNonce: crypto.randomBytes(16).toString("hex") };
|
||||
};
|
||||
|
||||
export function Head() {
|
||||
return (
|
||||
<>
|
||||
@@ -40,13 +48,17 @@ export function Head() {
|
||||
<Links />
|
||||
<style
|
||||
id="theme-colors"
|
||||
dangerouslySetInnerHTML={{ __html: colorsToCSS() }}
|
||||
dangerouslySetInnerHTML={{ __html: themeToCSS(ThemeDark) }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const data = useLoaderData<RootLoaderData>();
|
||||
const cspScriptNonce =
|
||||
typeof document === "undefined" ? data.cspScriptNonce : "";
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseThemeProvider
|
||||
@@ -56,29 +68,8 @@ export default function App() {
|
||||
>
|
||||
<Outlet />
|
||||
</BaseThemeProvider>
|
||||
<ScrollRestoration />
|
||||
<Scripts />
|
||||
<ScrollRestoration nonce={cspScriptNonce} />
|
||||
<Scripts nonce={cspScriptNonce} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function colorsToCSS() {
|
||||
// const colorScheme = JSON.parse(
|
||||
// window.localStorage.getItem("colorScheme") || '"light"'
|
||||
// );
|
||||
// const root = document.querySelector("html");
|
||||
// if (root) root.setAttribute("data-theme", colorScheme);
|
||||
|
||||
// const theme =
|
||||
// colorScheme === "dark"
|
||||
// ? JSON.parse(window.localStorage.getItem("theme:dark") || "false") ||
|
||||
// ThemeDark
|
||||
// : JSON.parse(window.localStorage.getItem("theme:light") || "false") ||
|
||||
// ThemeLight;
|
||||
// const stylesheet = document.getElementById("theme-colors");
|
||||
// if (theme) {
|
||||
// const css = ;
|
||||
// if (stylesheet) stylesheet.innerHTML = css;
|
||||
// } else stylesheet?.remove();
|
||||
return themeToCSS(ThemeDark);
|
||||
}
|
||||
|
||||
45
apps/monograph/package-lock.json
generated
45
apps/monograph/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/monograph",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/monograph",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.4",
|
||||
"dependencies": {
|
||||
"@emotion/cache": "11.11.0",
|
||||
"@emotion/react": "11.11.1",
|
||||
@@ -60,9 +60,6 @@
|
||||
"vite-plugin-static-copy": "^2.1.0",
|
||||
"vite-tsconfig-paths": "^5.0.1",
|
||||
"wrangler": "3.78.11"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"../../packages/core": {
|
||||
@@ -2945,6 +2942,22 @@
|
||||
"@styled-system/css": "^5.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@theme-ui/color-modes": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@theme-ui/color-modes/-/color-modes-0.16.2.tgz",
|
||||
"integrity": "sha512-jWEWx53lxNgWCT38i/kwLV2rsvJz8lVZgi5oImnVwYba9VejXD23q1ckbNFJHosQ8KKXY87ht0KPC6BQFIiHtQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@theme-ui/core": "^0.16.2",
|
||||
"@theme-ui/css": "^0.16.2",
|
||||
"deepmerge": "^4.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@theme-ui/components": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@theme-ui/components/-/components-0.16.2.tgz",
|
||||
@@ -2990,6 +3003,22 @@
|
||||
"@emotion/react": "^11.11.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@theme-ui/theme-provider": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@theme-ui/theme-provider/-/theme-provider-0.16.2.tgz",
|
||||
"integrity": "sha512-LRnVevODcGqO0JyLJ3wht+PV3ZoZcJ7XXLJAJWDoGeII4vZcPQKwVy4Lpz/juHsZppQxKcB3U+sQDGBnP25irQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@theme-ui/color-modes": "^0.16.2",
|
||||
"@theme-ui/core": "^0.16.2",
|
||||
"@theme-ui/css": "^0.16.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/acorn": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz",
|
||||
@@ -3119,14 +3148,14 @@
|
||||
"version": "15.7.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
|
||||
"integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.10.tgz",
|
||||
"integrity": "sha512-02sAAlBnP39JgXwkAq3PeU9DVaaGpZyF3MGcC0MKgQVkZor5IiiDAipVaxQHtDJAmO4GIy/rVBy/LzVj76Cyqg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
@@ -12212,7 +12241,7 @@
|
||||
"version": "3.23.8",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
|
||||
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@notesnook/monograph",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.4",
|
||||
"private": true,
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
@@ -64,8 +64,5 @@
|
||||
"vite-plugin-static-copy": "^2.1.0",
|
||||
"vite-tsconfig-paths": "^5.0.1",
|
||||
"wrangler": "3.78.11"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
3481
apps/theme-builder/package-lock.json
generated
3481
apps/theme-builder/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@notesnook/theme-builder",
|
||||
"description": "Your private note taking space",
|
||||
"version": "1.2.0",
|
||||
"version": "1.3.0",
|
||||
"private": true,
|
||||
"main": "./src/app.js",
|
||||
"homepage": "https://notesnook.com/",
|
||||
@@ -12,35 +12,25 @@
|
||||
"@mdi/js": "^7.2.96",
|
||||
"@mdi/react": "^1.6.1",
|
||||
"@notesnook/common": "file:../../packages/common",
|
||||
"@notesnook/intl": "file:../../packages/intl",
|
||||
"@notesnook/theme": "file:../../packages/theme",
|
||||
"@notesnook/web": "file:../web",
|
||||
"@streetwriters/kysely": "^0.27.4",
|
||||
"@theme-ui/components": "^0.14.7",
|
||||
"@theme-ui/core": "^0.14.7",
|
||||
"@trpc/client": "10.45.2",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"clipboard-polyfill": "^4.0.0",
|
||||
"dayjs": "1.11.13",
|
||||
"file-saver": "^2.0.5",
|
||||
"katex": "^0.16.2",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react-dom": "18.2.0",
|
||||
"react-modal": "3.16.3",
|
||||
"@lingui/core": "5.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.5",
|
||||
"@types/babel__core": "^7.20.1",
|
||||
"@types/file-saver": "^2.0.5",
|
||||
"@types/react": "^18.2.39",
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"buffer": "^6.0.3",
|
||||
"chalk": "^4.1.0",
|
||||
"happy-dom": "^8.9.0",
|
||||
"rollup": "^3.27.0",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"swc-plugin-react-remove-properties": "^0.1.4",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-env-compatible": "^1.1.1",
|
||||
"vite-plugin-svgr": "^3.2.0",
|
||||
"vitest": "^0.32.0"
|
||||
"vite": "5.4.11"
|
||||
},
|
||||
"overrides": {
|
||||
"@theme-ui/components@>0": "0.14.7",
|
||||
|
||||
13
apps/theme-builder/src/index.css
Normal file
13
apps/theme-builder/src/index.css
Normal file
@@ -0,0 +1,13 @@
|
||||
#root > .theme-scope-base {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#app {
|
||||
flex: 1;
|
||||
height: unset;
|
||||
}
|
||||
|
||||
#root > .theme-scope-base > :first-child:not(#menu-wrapper) {
|
||||
flex: 1;
|
||||
}
|
||||
@@ -17,60 +17,44 @@ 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 { init } from "@notesnook/web/src/bootstrap";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import {
|
||||
ErrorBoundary,
|
||||
ErrorComponent
|
||||
} from "@notesnook/web/src/components/error-boundary";
|
||||
import { BaseThemeProvider } from "@notesnook/web/src/components/theme-provider";
|
||||
import "./index.css";
|
||||
import "@notesnook/web/src/polyfills";
|
||||
import "@notesnook/web/src/app.css";
|
||||
import { ThemeDark, ThemeLight, themeToCSS } from "@notesnook/theme";
|
||||
import Config from "@notesnook/web/src/utils/config";
|
||||
import { setI18nGlobal, Messages } from "@notesnook/intl";
|
||||
import { i18n } from "@lingui/core";
|
||||
import { App } from "./app";
|
||||
|
||||
renderApp();
|
||||
const colorScheme = JSON.parse(
|
||||
window.localStorage.getItem("colorScheme") || '"light"'
|
||||
);
|
||||
const root = document.querySelector("html");
|
||||
if (root) root.setAttribute("data-theme", colorScheme);
|
||||
|
||||
async function renderApp() {
|
||||
const rootElement = document.getElementById("root");
|
||||
if (!rootElement) return;
|
||||
const root = createRoot(rootElement);
|
||||
try {
|
||||
const { component, props } = await init();
|
||||
const theme =
|
||||
colorScheme === "dark"
|
||||
? Config.get("theme:dark", ThemeDark)
|
||||
: Config.get("theme:light", ThemeLight);
|
||||
const stylesheet = document.getElementById("theme-colors");
|
||||
if (theme) {
|
||||
const css = themeToCSS(theme);
|
||||
if (stylesheet) stylesheet.innerHTML = css;
|
||||
} else stylesheet?.remove();
|
||||
|
||||
const { useKeyStore } = await import(
|
||||
"@notesnook/web/src/interfaces/key-store"
|
||||
);
|
||||
await useKeyStore.getState().init();
|
||||
const locale = import.meta.env.DEV
|
||||
? import("@notesnook/intl/locales/$pseudo-LOCALE.json")
|
||||
: import("@notesnook/intl/locales/$en.json");
|
||||
locale.then(({ default: locale }) => {
|
||||
i18n.load({
|
||||
en: locale.messages as unknown as Messages
|
||||
});
|
||||
i18n.activate("en");
|
||||
|
||||
const { default: Component } = await component();
|
||||
const { default: AppLock } = await import(
|
||||
"@notesnook/web/src/views/app-lock"
|
||||
);
|
||||
|
||||
root.render(
|
||||
<ErrorBoundary>
|
||||
<BaseThemeProvider
|
||||
onRender={() => document.getElementById("splash")?.remove()}
|
||||
sx={{
|
||||
display: "flex",
|
||||
"#app": { flex: 1, height: "unset" },
|
||||
"& > :first-child:not(#menu-wrapper)": { flex: 1 },
|
||||
height: "100%"
|
||||
}}
|
||||
>
|
||||
<AppLock>
|
||||
<Component route={props?.route || "login:email"} />
|
||||
</AppLock>
|
||||
<App />
|
||||
</BaseThemeProvider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
} catch (e) {
|
||||
root.render(
|
||||
<>
|
||||
<ErrorComponent
|
||||
error={e}
|
||||
resetErrorBoundary={() => window.location.reload()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
performance.mark("import:root");
|
||||
import("@notesnook/web/src/root.js").then(({ startApp }) => {
|
||||
performance.mark("start:app");
|
||||
startApp(<App />);
|
||||
});
|
||||
});
|
||||
setI18nGlobal(i18n);
|
||||
|
||||
@@ -26,7 +26,7 @@ type ErrorsListProps = {
|
||||
export function ErrorsList(props: ErrorsListProps) {
|
||||
return (
|
||||
<Accordion
|
||||
title={`${props.errors.length} errors occured`}
|
||||
title={`${props.errors.length} errors occurred`}
|
||||
sx={{ bg: "errorBg", borderRadius: "default", mt: 2 }}
|
||||
color="#E53935"
|
||||
>
|
||||
|
||||
@@ -258,6 +258,21 @@ test("creating a new note and toggling read-only mode should not empty editor co
|
||||
expect(await notes.editor.getContent("text")).toBe(NOTE.content);
|
||||
});
|
||||
|
||||
test("toggling read-only should hide undo & redo buttons", async ({ page }) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const note = await notes.createNote(NOTE);
|
||||
|
||||
await notes.editor.undoButton.waitFor({ state: "visible" });
|
||||
await notes.editor.redoButton.waitFor({ state: "visible" });
|
||||
await note?.properties.readonly();
|
||||
|
||||
expect(await note?.properties.isReadonly()).toBeTruthy();
|
||||
expect(notes.editor.undoButton).not.toBeVisible();
|
||||
expect(notes.editor.redoButton).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("#1468 count words separated by newlines", async ({ page }) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
@@ -347,7 +362,7 @@ test("when autosave is disabled, creating a new note should save the note", asyn
|
||||
await notes.newNote();
|
||||
|
||||
await note?.openNote();
|
||||
await expect(notes.editor.savedIcon).toBeVisible();
|
||||
// await expect(notes.editor.savedIcon).toBeVisible();
|
||||
expect(await notes.editor.getContent("text")).toBe(content.trim());
|
||||
});
|
||||
|
||||
@@ -385,7 +400,7 @@ test("control + alt + right arrow should go to next note", async ({ page }) => {
|
||||
});
|
||||
|
||||
await note1?.openNote();
|
||||
await note2?.openNote();
|
||||
await note2?.openNote(true);
|
||||
await page.keyboard.press("Control+Alt+ArrowRight");
|
||||
|
||||
expect(await notes.editor.getTitle()).toBe("Note 1");
|
||||
@@ -413,7 +428,7 @@ test("control + alt + left arrow should go to previous note", async ({
|
||||
});
|
||||
|
||||
await note1?.openNote();
|
||||
await note2?.openNote();
|
||||
await note2?.openNote(true);
|
||||
await page.keyboard.press("Control+Alt+ArrowLeft");
|
||||
|
||||
expect(await notes.editor.getTitle()).toBe("Note 1");
|
||||
|
||||
@@ -1 +1 @@
|
||||
This is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1An edit I made
|
||||
An edit I madeThis is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1
|
||||
@@ -41,6 +41,8 @@ export class EditorModel {
|
||||
private readonly newTabButton: Locator;
|
||||
readonly savedIcon: Locator;
|
||||
readonly notSavedIcon: Locator;
|
||||
readonly undoButton: Locator;
|
||||
readonly redoButton: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
@@ -65,6 +67,8 @@ export class EditorModel {
|
||||
this.goBackButton = page.locator(getTestId("go-back"));
|
||||
this.goForwardButton = page.locator(getTestId("go-forward"));
|
||||
this.newTabButton = page.locator(getTestId("New tab"));
|
||||
this.undoButton = page.locator(getTestId("Undo"));
|
||||
this.redoButton = page.locator(getTestId("Redo"));
|
||||
}
|
||||
|
||||
async waitForLoading(title?: string, content?: string) {
|
||||
@@ -124,7 +128,8 @@ export class EditorModel {
|
||||
async typeTitle(text: string, delay = 0) {
|
||||
await this.editAndWait(async () => {
|
||||
await this.title.focus();
|
||||
await this.title.press("End");
|
||||
await this.title.selectText();
|
||||
await this.title.press("ArrowRight");
|
||||
await this.title.type(text, { delay });
|
||||
});
|
||||
}
|
||||
@@ -196,6 +201,16 @@ export class EditorModel {
|
||||
return tags;
|
||||
}
|
||||
|
||||
async removeTags(tags: string[]) {
|
||||
for (const tag of tags) {
|
||||
const item = this.tags
|
||||
.filter({ hasText: tag })
|
||||
.getByTitle("Remove")
|
||||
.first();
|
||||
await item.click();
|
||||
}
|
||||
}
|
||||
|
||||
async getTitle() {
|
||||
return this.title.inputValue();
|
||||
}
|
||||
|
||||
@@ -19,11 +19,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Locator, Page } from "@playwright/test";
|
||||
import { getTestId } from "../utils";
|
||||
import { ContextMenuModel } from "./context-menu.model";
|
||||
|
||||
export class TabItemModel {
|
||||
private readonly closeButton: Locator;
|
||||
constructor(private readonly locator: Locator, page: Page) {
|
||||
readonly contextMenu: TabContextMenuModel;
|
||||
readonly titleElement: Locator;
|
||||
|
||||
constructor(readonly locator: Locator, page: Page) {
|
||||
this.closeButton = locator.locator(getTestId("tab-close-button"));
|
||||
this.contextMenu = new TabContextMenuModel(page, locator);
|
||||
this.titleElement = locator.locator(getTestId("tab-title"));
|
||||
}
|
||||
|
||||
async getId() {
|
||||
@@ -36,7 +42,7 @@ export class TabItemModel {
|
||||
}
|
||||
|
||||
async title() {
|
||||
return this.locator.locator(getTestId("tab-title")).textContent();
|
||||
return this.titleElement.textContent();
|
||||
}
|
||||
|
||||
async isActive() {
|
||||
@@ -48,3 +54,19 @@ export class TabItemModel {
|
||||
return this.closeButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
class TabContextMenuModel {
|
||||
private readonly menu: ContextMenuModel;
|
||||
constructor(page: Page, private readonly tabLocator: Locator) {
|
||||
this.menu = new ContextMenuModel(page);
|
||||
}
|
||||
|
||||
async pin() {
|
||||
await this.open();
|
||||
await this.menu.clickOnItem("pin");
|
||||
}
|
||||
|
||||
async open() {
|
||||
await this.menu.open(this.tabLocator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ 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";
|
||||
import { createHistorySession, NOTE } from "./utils";
|
||||
|
||||
test("notes should open in the same tab", async ({ page }) => {
|
||||
const app = new AppModel(page);
|
||||
@@ -63,7 +63,7 @@ test("open note in new tab (using context menu)", async ({ page }) => {
|
||||
|
||||
const tabs = await notes.editor.getTabs();
|
||||
expect(tabs.length).toBe(2);
|
||||
expect(await tabs[1].title()).toBe("Note 2");
|
||||
await expect(tabs[1].titleElement).toHaveText("Note 2");
|
||||
});
|
||||
|
||||
test("open note in new tab (using middle click)", async ({ page }) => {
|
||||
@@ -80,7 +80,7 @@ test("open note in new tab (using middle click)", async ({ page }) => {
|
||||
|
||||
const tabs = await notes.editor.getTabs();
|
||||
expect(tabs.length).toBe(2);
|
||||
expect(await tabs[1].title()).toBe("Note 2");
|
||||
await expect(tabs[1].titleElement).toHaveText("Note 2");
|
||||
});
|
||||
|
||||
test("go back should open previous note", async ({ page }) => {
|
||||
@@ -93,9 +93,9 @@ test("go back should open previous note", async ({ page }) => {
|
||||
await notes.createNote({ title: "Note 3" });
|
||||
|
||||
await notes.editor.goBack();
|
||||
expect(await tabs[0].title()).toBe("Note 2");
|
||||
await expect(tabs[0].titleElement).toHaveText("Note 2");
|
||||
await notes.editor.goBack();
|
||||
expect(await tabs[0].title()).toBe("Note 1");
|
||||
await expect(tabs[0].titleElement).toHaveText("Note 1");
|
||||
});
|
||||
|
||||
test("go forward should open next note", async ({ page }) => {
|
||||
@@ -110,9 +110,9 @@ test("go forward should open next note", async ({ page }) => {
|
||||
await notes.editor.goBack();
|
||||
await notes.editor.goBack();
|
||||
await notes.editor.goForward();
|
||||
expect(await tabs[0].title()).toBe("Note 2");
|
||||
await expect(tabs[0].titleElement).toHaveText("Note 2");
|
||||
await notes.editor.goForward();
|
||||
expect(await tabs[0].title()).toBe("Note 3");
|
||||
await expect(tabs[0].titleElement).toHaveText("Note 3");
|
||||
});
|
||||
|
||||
test("new tab button should open a new tab", async ({ page }) => {
|
||||
@@ -124,11 +124,11 @@ test("new tab button should open a new tab", async ({ page }) => {
|
||||
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");
|
||||
await expect(tabs[0].titleElement).toHaveText("Note 1");
|
||||
await expect(tabs[1].titleElement).toHaveText("Untitled");
|
||||
});
|
||||
|
||||
test("changes in a note opened in multiple tabs should sync", async ({
|
||||
test("content changes in a note opened in multiple tabs should sync", async ({
|
||||
page
|
||||
}) => {
|
||||
const app = new AppModel(page);
|
||||
@@ -173,6 +173,7 @@ test("reloading with a note diff open in a tab", async ({ page }) => {
|
||||
const history = await note?.properties.getSessionHistory();
|
||||
const preview = await history?.[0].open();
|
||||
await preview!.firstEditor.waitFor({ state: "visible" });
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page.reload();
|
||||
await preview!.firstEditor.waitFor({ state: "visible" });
|
||||
@@ -227,7 +228,7 @@ test("clicking on a note that's already opened in another tab should focus the t
|
||||
await note?.openNote();
|
||||
|
||||
const tabs = await notes.editor.getTabs();
|
||||
expect(await tabs[0].isActive()).toBe(true);
|
||||
await expect(tabs[0].locator).toHaveClass(/active/);
|
||||
});
|
||||
|
||||
test("open a note in 2 tabs then open another note and navigate back", async ({
|
||||
@@ -248,8 +249,134 @@ test("open a note in 2 tabs then open another note and navigate back", async ({
|
||||
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");
|
||||
await expect(tabs[1].locator).toHaveClass(/active/);
|
||||
await expect(tabs[1].titleElement).toHaveText("Note 1");
|
||||
});
|
||||
|
||||
test("shouldn't be possible to open a note in a pinned 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 notes.createNote({
|
||||
title: "Note 2"
|
||||
});
|
||||
let tabs = await notes.editor.getTabs();
|
||||
await tabs[0].contextMenu.pin();
|
||||
|
||||
await note?.click();
|
||||
await notes.editor.waitForLoading();
|
||||
|
||||
tabs = await notes.editor.getTabs();
|
||||
expect(tabs.length).toBe(2);
|
||||
await expect(tabs[1].locator).toHaveClass(/active/);
|
||||
await expect(tabs[1].titleElement).toHaveText("Note 1");
|
||||
});
|
||||
|
||||
test("shouldn't be possible to create a new note in a pinned tab", async ({
|
||||
page
|
||||
}) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
await notes.createNote({
|
||||
title: "Note 1"
|
||||
});
|
||||
let tabs = await notes.editor.getTabs();
|
||||
await tabs[0].contextMenu.pin();
|
||||
|
||||
await notes.newNote();
|
||||
|
||||
tabs = await notes.editor.getTabs();
|
||||
expect(tabs.length).toBe(2);
|
||||
await expect(tabs[1].locator).toHaveClass(/active/);
|
||||
});
|
||||
|
||||
test("notes open in multiple tabs should sync tags when tags are added", async ({
|
||||
page
|
||||
}) => {
|
||||
const tags = ["toAdd", "todo"];
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const note = await notes.createNote(NOTE);
|
||||
await note?.contextMenu.openInNewTab();
|
||||
|
||||
await notes.editor.setTags(tags);
|
||||
const tabs = await notes.editor.getTabs();
|
||||
await tabs[0].click();
|
||||
|
||||
const noteTags = await notes.editor.getTags();
|
||||
expect(noteTags).toHaveLength(tags.length);
|
||||
expect(noteTags.every((t, i) => t === tags[i])).toBe(true);
|
||||
});
|
||||
|
||||
test("notes open in multiple tabs should sync tags when tags are removed", async ({
|
||||
page
|
||||
}) => {
|
||||
const tags = ["removeme", "dontremoveme", "removemex2", "todo"];
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const note = await notes.createNote(NOTE);
|
||||
await notes.editor.setTags(tags);
|
||||
await note?.contextMenu.openInNewTab();
|
||||
|
||||
await notes.editor.removeTags(["removeme", "removemex2"]);
|
||||
const tabs = await notes.editor.getTabs();
|
||||
await tabs[0].click();
|
||||
|
||||
const noteTags = await notes.editor.getTags();
|
||||
expect(noteTags).toHaveLength(2);
|
||||
expect(noteTags.every((t, i) => t === ["dontremoveme", "todo"][i])).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
test("notes open in multiple tabs should sync color in note properties when color is changed", async ({
|
||||
page
|
||||
}) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const note = await notes.createNote(NOTE);
|
||||
await note?.contextMenu.newColor({ title: "red", color: "#ff0000" });
|
||||
await note?.contextMenu.newColor({ title: "blue", color: "#0000ff" });
|
||||
await note?.contextMenu.openInNewTab();
|
||||
|
||||
await notes.editor.waitForLoading();
|
||||
const tabs = await notes.editor.getTabs();
|
||||
await tabs[0].click();
|
||||
|
||||
expect(await note?.properties.isColored("blue")).toBe(true);
|
||||
|
||||
await note?.properties.color("red");
|
||||
await tabs[1].click();
|
||||
|
||||
expect(await note?.properties.isColored("red")).toBe(true);
|
||||
});
|
||||
|
||||
test("notes open in multiple tabs should sync title when title is changed", async ({
|
||||
page
|
||||
}) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const note = await notes.createNote(NOTE);
|
||||
await notes.editor.setTitle("Todo");
|
||||
await note?.contextMenu.openInNewTab();
|
||||
|
||||
expect(await notes.editor.getTitle()).toBe("Todo");
|
||||
|
||||
await notes.editor.setTitle("Todo - Today");
|
||||
const tabs = await notes.editor.getTabs();
|
||||
await tabs[0].click();
|
||||
|
||||
expect(await notes.editor.getTitle()).toBe("Todo - Today");
|
||||
});
|
||||
|
||||
test.skip("TODO: open a locked note, switch to another note and navigate back", () => {});
|
||||
|
||||
@@ -36,7 +36,7 @@ The flow will be as follows:
|
||||
5. Use the database password to init the db & keep the user's encryption key in memory. (we have 2 choices here: either keep the encryption key in memory or keep the pin in memory)
|
||||
6. If not enabled, we will ask the keystore to give us the database password + user's encryption key
|
||||
|
||||
We should store both the database password and the user's encryption key in a keychain. If user sets a pin, we can just double encrypt these values. This will keep things simple and seemless.
|
||||
We should store both the database password and the user's encryption key in a keychain. If user sets a pin, we can just double encrypt these values. This will keep things simple and seamless.
|
||||
|
||||
Q: How to find out if app lock is enabled?
|
||||
|
||||
|
||||
6
apps/web/package-lock.json
generated
6
apps/web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/web",
|
||||
"version": "3.0.25",
|
||||
"version": "3.0.28",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/web",
|
||||
"version": "3.0.25",
|
||||
"version": "3.0.28",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
@@ -497,7 +497,7 @@
|
||||
},
|
||||
"../desktop": {
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "3.0.24",
|
||||
"version": "3.0.25",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@notesnook/web",
|
||||
"description": "Your private note taking space",
|
||||
"version": "3.0.25",
|
||||
"version": "3.0.28",
|
||||
"private": true,
|
||||
"main": "./src/app.js",
|
||||
"homepage": "https://notesnook.com/",
|
||||
|
||||
@@ -249,3 +249,26 @@ textarea,
|
||||
background-color: color-mix(in srgb, var(--accent) 5%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
kbd {
|
||||
background: var(--background);
|
||||
border-radius: 3px;
|
||||
padding: 2px 5px;
|
||||
color: var(--paragraph-secondary);
|
||||
}
|
||||
|
||||
.ping {
|
||||
animation: ping 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes ping {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ import { AppEventManager, AppEvents } from "./common/app-events";
|
||||
import { TITLE_BAR_HEIGHT } from "./components/title-bar";
|
||||
import { getFontSizes } from "@notesnook/theme/theme/font/fontsize.js";
|
||||
import { useWindowControls } from "./hooks/use-window-controls";
|
||||
import { STATUS_BAR_HEIGHT } from "./common/constants";
|
||||
|
||||
new WebExtensionRelay();
|
||||
|
||||
@@ -64,8 +65,8 @@ function App() {
|
||||
{isFocused ? null : (
|
||||
<Global
|
||||
styles={`
|
||||
body {
|
||||
filter: brightness(0.95) grayscale(1);
|
||||
.nav-pane {
|
||||
opacity: 0.7;
|
||||
}
|
||||
.titlebar {
|
||||
background: var(--background-secondary) !important;
|
||||
@@ -130,7 +131,10 @@ function App() {
|
||||
) : (
|
||||
<DesktopAppContents setShow={setShow} show={show} />
|
||||
)}
|
||||
<Toaster containerClassName="toasts-container" />
|
||||
<Toaster
|
||||
containerClassName="toasts-container"
|
||||
containerStyle={{ bottom: STATUS_BAR_HEIGHT + 10 }}
|
||||
/>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
@@ -150,23 +154,9 @@ function DesktopAppContents({ show, setShow }: DesktopAppContentsProps) {
|
||||
|
||||
useEffect(() => {
|
||||
if (isTablet) navPane.current?.collapse(0);
|
||||
else navPane.current?.expand(0);
|
||||
else if (navPane.current?.isCollapsed(0)) navPane.current?.expand(0);
|
||||
}, [isTablet]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (show) middlePane.current?.expand();
|
||||
// else middlePane.current?.collapse();
|
||||
// }, [show]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (isFocusMode) {
|
||||
// const middlePaneSize = middlePane.current?.getSize() || 20;
|
||||
// navPane.current?.collapse();
|
||||
// // the middle pane has to be resized because collapsing the nav
|
||||
// // pane increases the middle pane's size every time.
|
||||
// middlePane.current?.resize(middlePaneSize);
|
||||
// } else navPane.current?.expand();
|
||||
// }, [isFocusMode]);
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
|
||||
@@ -25,3 +25,5 @@ export const SUBSCRIPTION_STATUS = {
|
||||
PREMIUM_EXPIRED: 6,
|
||||
PREMIUM_CANCELED: 7
|
||||
} as const;
|
||||
|
||||
export const STATUS_BAR_HEIGHT = 24 as const;
|
||||
|
||||
@@ -25,7 +25,8 @@ export interface BaseDialogProps<T = unknown> {
|
||||
}
|
||||
|
||||
class _DialogManager {
|
||||
private openedDialogs: Set<React.JSXElementConstructor<any>> = new Set();
|
||||
private openedDialogs: Map<React.JSXElementConstructor<any>, () => void> =
|
||||
new Map();
|
||||
|
||||
open<
|
||||
Props extends BaseDialogProps<any>,
|
||||
@@ -39,19 +40,21 @@ class _DialogManager {
|
||||
const container = document.createElement("div");
|
||||
const root = createRoot(container);
|
||||
|
||||
const close = () => {
|
||||
this.openedDialogs.delete(component);
|
||||
root.unmount();
|
||||
container.remove();
|
||||
};
|
||||
|
||||
const Dialog = component as React.FunctionComponent<
|
||||
Omit<Props, "onClose"> & {
|
||||
onClose: (result: any) => void;
|
||||
}
|
||||
>;
|
||||
this.openedDialogs.add(component);
|
||||
return new Promise<Result>((resolve, reject) =>
|
||||
return new Promise<Result>((resolve, reject) => {
|
||||
const close = () => {
|
||||
this.openedDialogs.delete(component);
|
||||
root.unmount();
|
||||
container.remove();
|
||||
};
|
||||
this.openedDialogs.set(component, () => {
|
||||
close();
|
||||
reject(new Error("Dialog force closed."));
|
||||
});
|
||||
root.render(
|
||||
<Dialog
|
||||
{...props}
|
||||
@@ -64,8 +67,8 @@ class _DialogManager {
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
);
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
closeAll() {
|
||||
@@ -80,7 +83,12 @@ class _DialogManager {
|
||||
component: React.ComponentType<Props>
|
||||
) {
|
||||
return {
|
||||
show: (props: Omit<Props, "onClose">) => this.open(component, props)
|
||||
show: (props: Omit<Props, "onClose">) => this.open(component, props),
|
||||
close: () => {
|
||||
const dialog = this.openedDialogs.get(component);
|
||||
if (!dialog) return;
|
||||
dialog();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import hotkeys from "hotkeys-js";
|
||||
import { useEditorStore } from "../stores/editor-store";
|
||||
import { useStore as useSearchStore } from "../stores/search-store";
|
||||
import { useEditorManager } from "../components/editor/manager";
|
||||
import { CommandPaletteDialog } from "../dialogs/command-palette";
|
||||
|
||||
function isInEditor(e: KeyboardEvent) {
|
||||
return (
|
||||
@@ -64,10 +65,11 @@ const KEYMAP = [
|
||||
"command+option+right",
|
||||
"ctrl+alt+right",
|
||||
"command+option+shift+right",
|
||||
"ctrl+alt+shift+right"
|
||||
"ctrl+alt+shift+right",
|
||||
"ctrl+tab",
|
||||
"command+tab"
|
||||
],
|
||||
description: "Go to next tab",
|
||||
global: false,
|
||||
action: () => useEditorStore.getState().focusNextTab()
|
||||
},
|
||||
{
|
||||
@@ -75,12 +77,33 @@ const KEYMAP = [
|
||||
"command+option+left",
|
||||
"ctrl+alt+left",
|
||||
"command+option+shift+left",
|
||||
"ctrl+alt+shift+left"
|
||||
"ctrl+alt+shift+left",
|
||||
"ctrl+shift+tab",
|
||||
"command+shift+tab"
|
||||
],
|
||||
description: "Go to next tab",
|
||||
global: false,
|
||||
description: "Go to previous tab",
|
||||
action: () => useEditorStore.getState().focusPreviousTab()
|
||||
},
|
||||
{
|
||||
keys: ["ctrl+t", "command+t"],
|
||||
description: "Create a new tab",
|
||||
action: () => useEditorStore.getState().addTab()
|
||||
},
|
||||
{
|
||||
keys: ["ctrl+n", "command+n"],
|
||||
description: "Create a new note",
|
||||
action: () => useEditorStore.getState().newSession()
|
||||
},
|
||||
{
|
||||
keys: ["ctrl+w", "command+w"],
|
||||
description: "Close active tab",
|
||||
action: () => useEditorStore.getState().closeActiveTab()
|
||||
},
|
||||
{
|
||||
keys: ["ctrl+shift+w", "command+shift+w"],
|
||||
description: "Close all tabs",
|
||||
action: () => useEditorStore.getState().closeAllTabs()
|
||||
},
|
||||
{
|
||||
keys: ["command+f", "ctrl+f"],
|
||||
description: "Search all notes",
|
||||
@@ -101,7 +124,7 @@ const KEYMAP = [
|
||||
|
||||
useSearchStore.setState({ isSearching: true, searchType: "notes" });
|
||||
}
|
||||
}
|
||||
},
|
||||
// {
|
||||
// keys: ["alt+n"],
|
||||
// description: "Go to Notes",
|
||||
@@ -165,6 +188,17 @@ const KEYMAP = [
|
||||
// themestore.get().toggleNightMode();
|
||||
// },
|
||||
// },
|
||||
{
|
||||
keys: ["ctrl+k", "cmd+k", "ctrl+p", "cmd+p"],
|
||||
description: "Open command palette",
|
||||
action: (e: KeyboardEvent) => {
|
||||
e.preventDefault();
|
||||
CommandPaletteDialog.close();
|
||||
CommandPaletteDialog.show({
|
||||
isCommandMode: e.key === "k"
|
||||
}).catch(() => {});
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
export function registerKeyMap() {
|
||||
|
||||
@@ -165,7 +165,7 @@ export class Base {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle asynchronous operation. This implementation will be overriden on
|
||||
* Handle asynchronous operation. This implementation will be overridden on
|
||||
* registration by an Asyncify build.
|
||||
* @param {function(): Promise<number>} f
|
||||
* @returns {number}
|
||||
|
||||
@@ -269,7 +269,7 @@ export interface SQLiteModule {
|
||||
* the corresponding Javascript wrapper will throw an exception with a
|
||||
* `code` property on an error.
|
||||
*
|
||||
* Note that a few functions return a Promise in order to accomodate
|
||||
* Note that a few functions return a Promise in order to accommodate
|
||||
* either a synchronous or asynchronous SQLite build, generally those
|
||||
* involved with opening/closing a database or executing a statement.
|
||||
*
|
||||
@@ -493,7 +493,7 @@ export interface SQLiteAPI {
|
||||
column(stmt: number, i: number): SQLiteCompatibleType;
|
||||
|
||||
/**
|
||||
* Extract a column value from a row after a prepared statment {@link step}
|
||||
* Extract a column value from a row after a prepared statement {@link step}
|
||||
*
|
||||
* The contents of the returned buffer may be invalid after the
|
||||
* next SQLite call. Make a copy of the data (e.g. with `.slice()`)
|
||||
@@ -523,7 +523,7 @@ export interface SQLiteAPI {
|
||||
column_count(stmt: number): number;
|
||||
|
||||
/**
|
||||
* Extract a column value from a row after a prepared statment {@link step}
|
||||
* Extract a column value from a row after a prepared statement {@link step}
|
||||
* @see https://www.sqlite.org/c3ref/column_blob.html
|
||||
* @param stmt prepared statement pointer
|
||||
* @param i column index
|
||||
@@ -532,7 +532,7 @@ export interface SQLiteAPI {
|
||||
column_double(stmt: number, i: number): number;
|
||||
|
||||
/**
|
||||
* Extract a column value from a row after a prepared statment {@link step}
|
||||
* Extract a column value from a row after a prepared statement {@link step}
|
||||
* @see https://www.sqlite.org/c3ref/column_blob.html
|
||||
* @param stmt prepared statement pointer
|
||||
* @param i column index
|
||||
@@ -541,7 +541,7 @@ export interface SQLiteAPI {
|
||||
column_int(stmt: number, i: number): number;
|
||||
|
||||
/**
|
||||
* Extract a column value from a row after a prepared statment {@link step}
|
||||
* Extract a column value from a row after a prepared statement {@link step}
|
||||
* @see https://www.sqlite.org/c3ref/column_blob.html
|
||||
* @param stmt prepared statement pointer
|
||||
* @param i column index
|
||||
@@ -569,7 +569,7 @@ export interface SQLiteAPI {
|
||||
column_names(stmt: number): Array<string>;
|
||||
|
||||
/**
|
||||
* Extract a column value from a row after a prepared statment {@link step}
|
||||
* Extract a column value from a row after a prepared statement {@link step}
|
||||
* @see https://www.sqlite.org/c3ref/column_blob.html
|
||||
* @param stmt prepared statement pointer
|
||||
* @param i column index
|
||||
|
||||
@@ -113,6 +113,13 @@ function BaseDialog(props: React.PropsWithChildren<DialogProps>) {
|
||||
|
||||
...props.sx
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
props.onClose?.();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{props.showCloseButton && (
|
||||
<Close
|
||||
@@ -225,6 +232,7 @@ function onAfterOpen(
|
||||
e: ReactModal.OnAfterOpenCallbackOptions | undefined,
|
||||
props: DialogProps
|
||||
) {
|
||||
if (props.onOpen) props.onOpen();
|
||||
if (!props.onClose || !e) return;
|
||||
// we need this work around because ReactModal content spreads over the overlay
|
||||
const child = e.contentEl.firstElementChild as HTMLElement;
|
||||
@@ -241,5 +249,4 @@ function onAfterOpen(
|
||||
if (props.onClose) props.onClose();
|
||||
}
|
||||
};
|
||||
if (props.onOpen) props.onOpen();
|
||||
}
|
||||
|
||||
@@ -117,13 +117,15 @@ export function EditorActionBar() {
|
||||
title: strings.undo(),
|
||||
icon: Undo,
|
||||
enabled: editorManager?.canUndo,
|
||||
onClick: () => editorManager?.editor?.undo()
|
||||
onClick: () => editorManager?.editor?.undo(),
|
||||
hidden: activeSession?.type === "readonly"
|
||||
},
|
||||
{
|
||||
title: strings.redo(),
|
||||
icon: Redo,
|
||||
enabled: editorManager?.canRedo,
|
||||
onClick: () => editorManager?.editor?.redo()
|
||||
onClick: () => editorManager?.editor?.redo(),
|
||||
hidden: activeSession?.type === "readonly"
|
||||
},
|
||||
{
|
||||
title: isNotePublished ? strings.published() : strings.publish(),
|
||||
@@ -408,15 +410,7 @@ const TabStrip = React.memo(function TabStrip() {
|
||||
)
|
||||
: 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) =>
|
||||
a.pinned === b.pinned ? 0 : a.pinned ? -1 : 1
|
||||
);
|
||||
useEditorStore.setState({ tabs });
|
||||
}}
|
||||
onPin={() => useEditorStore.getState().pinTab(tab.id)}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
|
||||
@@ -35,9 +35,11 @@ import { strings } from "@notesnook/intl";
|
||||
type HeaderProps = { readonly: boolean; id: string };
|
||||
function Header(props: HeaderProps) {
|
||||
const { readonly, id } = props;
|
||||
const tags = useEditorStore(
|
||||
(store) => store.getActiveSession(["default", "readonly"])?.tags
|
||||
const activeSession = useEditorStore((store) =>
|
||||
store.getActiveSession(["default", "readonly"])
|
||||
);
|
||||
const tags = activeSession?.tags;
|
||||
const noteId = activeSession?.note.id;
|
||||
|
||||
const setTag = useCallback(async function (
|
||||
noteId: string,
|
||||
@@ -59,6 +61,8 @@ function Header(props: HeaderProps) {
|
||||
},
|
||||
[]);
|
||||
|
||||
if (!noteId) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
@@ -72,7 +76,9 @@ function Header(props: HeaderProps) {
|
||||
text={tag.title}
|
||||
icon={TagIcon}
|
||||
onClick={() => navigate(`/tags/${tag.id}`)}
|
||||
onDismiss={readonly ? undefined : () => setTag(id, tags, tag.title)}
|
||||
onDismiss={
|
||||
readonly ? undefined : () => setTag(noteId, tags, tag.title)
|
||||
}
|
||||
styles={{ container: { mr: 1 }, text: { fontSize: "body" } }}
|
||||
/>
|
||||
))}
|
||||
@@ -90,7 +96,7 @@ function Header(props: HeaderProps) {
|
||||
key: "new",
|
||||
title: `Create "${query}" tag`,
|
||||
icon: Plus.path,
|
||||
onClick: () => setTag(id, tags, query).finally(reset)
|
||||
onClick: () => setTag(noteId, tags, query).finally(reset)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -101,17 +107,18 @@ function Header(props: HeaderProps) {
|
||||
key: item.id,
|
||||
title: item.title,
|
||||
icon: TagIcon.path,
|
||||
onClick: () => setTag(id, tags, item.title).finally(reset)
|
||||
onClick: () =>
|
||||
setTag(noteId, tags, item.title).finally(reset)
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
return items;
|
||||
}}
|
||||
onAdd={(value) => setTag(id, tags, value)}
|
||||
onAdd={(value) => setTag(noteId, tags, value)}
|
||||
onRemove={() => {
|
||||
if (tags.length <= 0) return;
|
||||
setTag(id, tags, tags[tags.length - 1].title);
|
||||
setTag(noteId, tags, tags[tags.length - 1].title);
|
||||
}}
|
||||
defaultItems={() =>
|
||||
db.tags.all.limit(10).items(undefined, {
|
||||
|
||||
@@ -23,7 +23,8 @@ import React, {
|
||||
useRef,
|
||||
PropsWithChildren,
|
||||
Suspense,
|
||||
useLayoutEffect
|
||||
useLayoutEffect,
|
||||
useCallback
|
||||
} from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Box, Button, Flex, Progress, Text } from "@theme-ui/components";
|
||||
@@ -298,11 +299,11 @@ function EditorView({
|
||||
};
|
||||
}, [editor]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!session.needsHydration && session.content) {
|
||||
editor?.updateContent(session.content.data);
|
||||
}
|
||||
}, [editor, session]);
|
||||
const getContent = useCallback(() => {
|
||||
return session.content?.data;
|
||||
}, [session.content?.data]);
|
||||
|
||||
if (session.needsHydration) return null;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@@ -321,7 +322,7 @@ function EditorView({
|
||||
<Editor
|
||||
id={session.id}
|
||||
nonce={1}
|
||||
content={() => session.content?.data}
|
||||
content={getContent}
|
||||
session={session}
|
||||
onPreviewDocument={(preview) =>
|
||||
useEditorStore.setState({ documentPreview: preview })
|
||||
@@ -665,10 +666,12 @@ function EditorChrome(props: PropsWithChildren<EditorProps>) {
|
||||
parent.left - child.left - CONTAINER_MARGIN
|
||||
);
|
||||
|
||||
editor.style.marginLeft = `-${negativeSpace}px`;
|
||||
editor.style.marginRight = `-${negativeSpace}px`;
|
||||
editor.style.paddingLeft = `${negativeSpace}px`;
|
||||
editor.style.paddingRight = `${negativeSpace}px`;
|
||||
requestAnimationFrame(() => {
|
||||
editor.style.marginLeft = `-${negativeSpace}px`;
|
||||
editor.style.marginRight = `-${negativeSpace}px`;
|
||||
editor.style.paddingLeft = `${negativeSpace}px`;
|
||||
editor.style.paddingRight = `${negativeSpace}px`;
|
||||
});
|
||||
}
|
||||
const observer = new ResizeObserver(debounce(onResize, 500));
|
||||
observer.observe(editorScrollRef.current);
|
||||
|
||||
@@ -307,8 +307,8 @@ function TipTap(props: TipTapProps) {
|
||||
}
|
||||
};
|
||||
}, [
|
||||
content,
|
||||
readonly,
|
||||
nonce,
|
||||
doubleSpacedLines,
|
||||
dateFormat,
|
||||
timeFormat,
|
||||
|
||||
@@ -17,25 +17,17 @@ 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 { Input } from "@theme-ui/components";
|
||||
import React, { useEffect, useLayoutEffect, useMemo, useRef } from "react";
|
||||
import { Textarea } from "@theme-ui/components";
|
||||
import { useEditorStore } from "../../stores/editor-store";
|
||||
import { debounceWithId } from "@notesnook/common";
|
||||
import useMobile from "../../hooks/use-mobile";
|
||||
import useTablet from "../../hooks/use-tablet";
|
||||
import { useEditorConfig, useEditorManager } from "./manager";
|
||||
import { getFontById } from "@notesnook/editor";
|
||||
import { replaceDateTime } from "@notesnook/editor";
|
||||
import { useStore as useSettingsStore } from "../../stores/setting-store";
|
||||
import { AppEventManager, AppEvents } from "../../common/app-events";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { NEWLINE_STRIP_REGEX } from "@notesnook/core";
|
||||
|
||||
type TitleBoxProps = {
|
||||
id: string;
|
||||
@@ -44,12 +36,10 @@ type TitleBoxProps = {
|
||||
|
||||
function TitleBox(props: TitleBoxProps) {
|
||||
const { readonly, id } = props;
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
const pendingChanges = useRef(false);
|
||||
// const id = useStore((store) => store.session.id);
|
||||
const sessionType = useEditorStore((store) => store.getSession(id)?.type);
|
||||
const isMobile = useMobile();
|
||||
const isTablet = useTablet();
|
||||
const { editorConfig } = useEditorConfig();
|
||||
const dateFormat = useSettingsStore((store) => store.dateFormat);
|
||||
const timeFormat = useSettingsStore((store) => store.timeFormat);
|
||||
@@ -59,18 +49,6 @@ function TitleBox(props: TitleBoxProps) {
|
||||
[editorConfig.fontFamily]
|
||||
);
|
||||
|
||||
const updateFontSize = useCallback(
|
||||
(length: number) => {
|
||||
if (!inputRef.current) return;
|
||||
const fontSize = textLengthToFontSize(
|
||||
length,
|
||||
isMobile || isTablet ? 1.625 : 2.625
|
||||
);
|
||||
inputRef.current.style.fontSize = `${fontSize}em`;
|
||||
},
|
||||
[isMobile, isTablet]
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const session = useEditorStore.getState().getSession(id);
|
||||
if (!session || !("note" in session) || !session.note || !inputRef.current)
|
||||
@@ -80,17 +58,11 @@ function TitleBox(props: TitleBoxProps) {
|
||||
const { title } = session.note;
|
||||
if (inputRef.current.value === title) return;
|
||||
|
||||
withSelectionPersist(
|
||||
inputRef.current,
|
||||
(input) => (input.value = title || "")
|
||||
);
|
||||
updateFontSize(title?.length || 0);
|
||||
}, [sessionType, id, updateFontSize]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!inputRef.current) return;
|
||||
updateFontSize(inputRef.current.value.length);
|
||||
}, [isTablet, isMobile, updateFontSize]);
|
||||
withSelectionPersist(inputRef.current, (input) => {
|
||||
input.value = title || "";
|
||||
resizeTextarea(input);
|
||||
});
|
||||
}, [sessionType, id]);
|
||||
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = AppEventManager.subscribe(
|
||||
@@ -105,11 +77,10 @@ function TitleBox(props: TitleBoxProps) {
|
||||
sessionId: string;
|
||||
}) => {
|
||||
if (!inputRef.current || sessionId !== id) return;
|
||||
withSelectionPersist(
|
||||
inputRef.current,
|
||||
(input) => (input.value = title)
|
||||
);
|
||||
updateFontSize(title.length);
|
||||
withSelectionPersist(inputRef.current, (input) => {
|
||||
input.value = title;
|
||||
resizeTextarea(input);
|
||||
});
|
||||
if (!preventSave) {
|
||||
pendingChanges.current = true;
|
||||
debouncedOnTitleChange(sessionId, sessionId, title, pendingChanges);
|
||||
@@ -120,10 +91,10 @@ function TitleBox(props: TitleBoxProps) {
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [updateFontSize, id]);
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<Input
|
||||
<Textarea
|
||||
ref={inputRef}
|
||||
variant="clean"
|
||||
id="editor-title"
|
||||
@@ -132,32 +103,45 @@ function TitleBox(props: TitleBoxProps) {
|
||||
placeholder={strings.noteTitle()}
|
||||
readOnly={readonly}
|
||||
dir="auto"
|
||||
wrap="soft"
|
||||
rows={1}
|
||||
sx={{
|
||||
p: 0,
|
||||
fontFamily,
|
||||
fontSize: ["1.625em", "1.625em", "2.625em"],
|
||||
fontWeight: "heading",
|
||||
width: "100%",
|
||||
fieldSizing: "content",
|
||||
whiteSpace: "pre-wrap",
|
||||
resize: "none",
|
||||
overflow: "hidden",
|
||||
"::placeholder": {
|
||||
color: "placeholder"
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
onKeyUp={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
const context = useEditorManager.getState().getEditor(id);
|
||||
if (!context) return;
|
||||
context.editor?.focus({ scrollIntoView: true });
|
||||
}
|
||||
}}
|
||||
onChange={(e) => {
|
||||
if (!(e.target instanceof HTMLTextAreaElement)) return;
|
||||
pendingChanges.current = true;
|
||||
e.target.value = replaceDateTime(
|
||||
e.target.value,
|
||||
dateFormat,
|
||||
timeFormat
|
||||
);
|
||||
).replace(NEWLINE_STRIP_REGEX, " ");
|
||||
debouncedOnTitleChange(id, id, e.target.value, pendingChanges);
|
||||
updateFontSize(e.target.value.length);
|
||||
resizeTextarea(e.target);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -167,6 +151,11 @@ export default React.memo(TitleBox, (prevProps, nextProps) => {
|
||||
return prevProps.readonly === nextProps.readonly;
|
||||
});
|
||||
|
||||
function resizeTextarea(input: HTMLTextAreaElement) {
|
||||
input.style.height = "auto";
|
||||
input.style.height = input.scrollHeight + "px";
|
||||
}
|
||||
|
||||
function onTitleChange(
|
||||
noteId: string,
|
||||
title: string,
|
||||
@@ -178,16 +167,9 @@ function onTitleChange(
|
||||
|
||||
const debouncedOnTitleChange = debounceWithId(onTitleChange, 100);
|
||||
|
||||
function textLengthToFontSize(length: number, max: number) {
|
||||
const stepLength = 35;
|
||||
const decreaseStep = 0.5;
|
||||
const steps = length / stepLength;
|
||||
return Math.max(1.2, Math.min(max, max - steps * decreaseStep));
|
||||
}
|
||||
|
||||
function withSelectionPersist(
|
||||
input: HTMLInputElement,
|
||||
action: (input: HTMLInputElement) => void
|
||||
input: HTMLTextAreaElement,
|
||||
action: (input: HTMLTextAreaElement) => void
|
||||
) {
|
||||
const selection = {
|
||||
start: input.selectionStart,
|
||||
|
||||
@@ -220,7 +220,8 @@ import {
|
||||
mdiTagOutline,
|
||||
mdiChatQuestionOutline,
|
||||
mdiNoteRemoveOutline,
|
||||
mdiTabPlus
|
||||
mdiTabPlus,
|
||||
mdiRadar
|
||||
} from "@mdi/js";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Theme } from "@notesnook/theme";
|
||||
@@ -286,7 +287,7 @@ export type Icon = {
|
||||
path: string;
|
||||
};
|
||||
|
||||
function createIcon(path: string, rotate = false) {
|
||||
export function createIcon(path: string, rotate = false) {
|
||||
const NNIcon: Icon = function Icon(props) {
|
||||
const { sx, rotate: _rotate = rotate, size, ...restProps } = props;
|
||||
return (
|
||||
@@ -563,3 +564,4 @@ export const OpenInNew = createIcon(mdiOpenInNew);
|
||||
export const Coupon = createIcon(mdiTagOutline);
|
||||
export const Support = createIcon(mdiChatQuestionOutline);
|
||||
export const NewTab = createIcon(mdiTabPlus);
|
||||
export const Radar = createIcon(mdiRadar);
|
||||
|
||||
@@ -26,7 +26,12 @@ export type NotebookContext = {
|
||||
};
|
||||
export type Context =
|
||||
| {
|
||||
type: "tag" | "color";
|
||||
type: "tag";
|
||||
id: string;
|
||||
item?: Tag;
|
||||
}
|
||||
| {
|
||||
type: "color";
|
||||
id: string;
|
||||
}
|
||||
| NotebookContext
|
||||
|
||||
@@ -143,7 +143,7 @@ function Note(props: NoteProps) {
|
||||
background: "background"
|
||||
}}
|
||||
context={{ color, locked }}
|
||||
menuItems={menuItems}
|
||||
menuItems={noteMenuItems}
|
||||
onClick={() => useEditorStore.getState().openSession(note)}
|
||||
onMiddleClick={() =>
|
||||
useEditorStore.getState().openSession(note, { openInNewTab: true })
|
||||
@@ -320,9 +320,7 @@ const formats = [
|
||||
}
|
||||
] as const;
|
||||
|
||||
const notFullySyncedText =
|
||||
"Cannot perform this action because note is not fully synced.";
|
||||
const menuItems: (
|
||||
export const noteMenuItems: (
|
||||
note: NoteType,
|
||||
ids?: string[],
|
||||
context?: { color?: Color; locked?: boolean }
|
||||
|
||||
@@ -54,11 +54,13 @@ type PaneOptions = {
|
||||
nextSize?: number;
|
||||
initialSize: number;
|
||||
collapsed: boolean;
|
||||
expandedSize?: number;
|
||||
};
|
||||
|
||||
export type SplitPaneImperativeHandle = {
|
||||
collapse: (index: number) => void;
|
||||
expand: (index: number) => void;
|
||||
isCollapsed: (index: number) => boolean;
|
||||
};
|
||||
export const SplitPane = React.forwardRef<
|
||||
SplitPaneImperativeHandle,
|
||||
@@ -96,7 +98,6 @@ 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(
|
||||
() =>
|
||||
@@ -150,6 +151,19 @@ export const SplitPane = React.forwardRef<
|
||||
else Config.set(`${autoSaveKey}-${id}`, v);
|
||||
}
|
||||
});
|
||||
Object.defineProperty(limits, "expandedSize", {
|
||||
get() {
|
||||
return Config.get(
|
||||
`${autoSaveKey}-${id}:expandedSize`,
|
||||
assertsSize(initialSize, wrapSize.current)
|
||||
);
|
||||
},
|
||||
set(v) {
|
||||
if (v === null || v === undefined || v === Infinity)
|
||||
Config.remove(`${autoSaveKey}-${id}:expandedSize`);
|
||||
else Config.set(`${autoSaveKey}-${id}:expandedSize`, v);
|
||||
}
|
||||
});
|
||||
}
|
||||
return limits;
|
||||
}) || [];
|
||||
@@ -165,7 +179,7 @@ export const SplitPane = React.forwardRef<
|
||||
|
||||
if (wrapSize.current === 0) return;
|
||||
updatePaneLimitSizes(children);
|
||||
setSizes(paneSizes.current, wrapSize.current, false);
|
||||
setSizes(paneSizes.current, wrapSize.current, true);
|
||||
}, [children, childrenLength]);
|
||||
|
||||
const setSizes = useCallback(
|
||||
@@ -256,16 +270,23 @@ export const SplitPane = React.forwardRef<
|
||||
() => {
|
||||
return {
|
||||
collapse: (index: number) => {
|
||||
paneSizes.current[index].collapsed = true;
|
||||
lastCollapsedPaneSize.current = paneSizes.current[index].size;
|
||||
const currentPane = paneSizes.current[index];
|
||||
if (currentPane.collapsed) return;
|
||||
currentPane.collapsed = true;
|
||||
currentPane.expandedSize = 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;
|
||||
const currentPane = paneSizes.current[index];
|
||||
if (!currentPane.collapsed) return;
|
||||
currentPane.collapsed = false;
|
||||
currentPane.size =
|
||||
currentPane.expandedSize ?? currentPane.initialSize;
|
||||
currentPane.expandedSize = undefined;
|
||||
setSizes(paneSizes.current, wrapSize.current);
|
||||
},
|
||||
isCollapsed: (index: number) => {
|
||||
return paneSizes.current[index].collapsed;
|
||||
}
|
||||
};
|
||||
},
|
||||
@@ -304,6 +325,8 @@ export const SplitPane = React.forwardRef<
|
||||
const nextPane = paneSizes.current[i + 1];
|
||||
const rightBorder = sashPosSizes.current[i + 2];
|
||||
|
||||
if (currentPane.collapsed) currentPane.collapsed = false;
|
||||
|
||||
if (currentPane.size + distanceX >= rightBorder)
|
||||
distanceX = rightBorder - currentPane.size;
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ import { showUpdateAvailableNotice } from "../../dialogs/confirm";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useVault } from "../../hooks/use-vault";
|
||||
import { useKeyStore } from "../../interfaces/key-store";
|
||||
import { STATUS_BAR_HEIGHT } from "../../common/constants";
|
||||
|
||||
function StatusBar() {
|
||||
const user = useUserStore((state) => state.user);
|
||||
@@ -62,9 +63,9 @@ function StatusBar() {
|
||||
borderTop: "1px solid",
|
||||
borderTopColor: "separator",
|
||||
justifyContent: "space-between",
|
||||
display: ["none", "none", "flex"],
|
||||
display: ["none", "flex", "flex"],
|
||||
flexShrink: 0,
|
||||
height: 24
|
||||
height: STATUS_BAR_HEIGHT
|
||||
}}
|
||||
px={2}
|
||||
>
|
||||
|
||||
@@ -60,7 +60,7 @@ function Tag(props: TagProps) {
|
||||
);
|
||||
}
|
||||
}}
|
||||
menuItems={menuItems}
|
||||
menuItems={tagMenuItems}
|
||||
onClick={() => {
|
||||
navigate(`/tags/${id}`);
|
||||
}}
|
||||
@@ -73,7 +73,7 @@ function Tag(props: TagProps) {
|
||||
}
|
||||
export default Tag;
|
||||
|
||||
const menuItems: (tag: TagType, ids?: string[]) => MenuItem[] = (
|
||||
export const tagMenuItems: (tag: TagType, ids?: string[]) => MenuItem[] = (
|
||||
tag,
|
||||
ids = []
|
||||
) => {
|
||||
|
||||
@@ -61,7 +61,7 @@ function TrashItem(props: TrashItemProps) {
|
||||
</Text>
|
||||
</Flex>
|
||||
}
|
||||
menuItems={menuItems}
|
||||
menuItems={trashMenuItems}
|
||||
onClick={async () => {
|
||||
if (item.itemType === "note")
|
||||
useEditorStore.getState().openSession(item);
|
||||
@@ -71,10 +71,10 @@ function TrashItem(props: TrashItemProps) {
|
||||
}
|
||||
export default TrashItem;
|
||||
|
||||
const menuItems: (item: TrashItemType, ids?: string[]) => MenuItem[] = (
|
||||
item,
|
||||
ids = []
|
||||
) => {
|
||||
export const trashMenuItems: (
|
||||
item: TrashItemType,
|
||||
ids?: string[]
|
||||
) => MenuItem[] = (item, ids = []) => {
|
||||
return [
|
||||
{
|
||||
type: "button",
|
||||
@@ -100,7 +100,7 @@ const menuItems: (item: TrashItemType, ids?: string[]) => MenuItem[] = (
|
||||
];
|
||||
};
|
||||
|
||||
async function deleteTrash(ids: string[]) {
|
||||
export async function deleteTrash(ids: string[]) {
|
||||
if (!(await showMultiPermanentDeleteConfirmation(ids.length))) return;
|
||||
await store.delete(...ids);
|
||||
showToast("success", `${pluralize(ids.length, "item")} permanently deleted`);
|
||||
|
||||
@@ -189,7 +189,7 @@ export const AttachmentsDialog = DialogManager.register(
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<FlexScrollContainer>
|
||||
<FlexScrollContainer style={{ minHeight: 300 }}>
|
||||
<Flex sx={{ justifyContent: "space-between" }}>
|
||||
<Flex sx={{ gap: 1 }}>
|
||||
{TOOLBAR_ACTIONS.map((tool) => (
|
||||
|
||||
531
apps/web/src/dialogs/command-palette/command-palette-dialog.tsx
Normal file
531
apps/web/src/dialogs/command-palette/command-palette-dialog.tsx
Normal file
@@ -0,0 +1,531 @@
|
||||
/*
|
||||
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 { debounce, usePromise } from "@notesnook/common";
|
||||
import { EVENTS, fuzzy, Note, Notebook, Reminder, Tag } from "@notesnook/core";
|
||||
import { Box, Button, Flex, Input, Text } from "@theme-ui/components";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
GroupedVirtuoso,
|
||||
GroupedVirtuosoHandle,
|
||||
CalculateViewLocation
|
||||
} from "react-virtuoso";
|
||||
import { db } from "../../common/db";
|
||||
import { BaseDialogProps, DialogManager } from "../../common/dialog-manager";
|
||||
import Dialog from "../../components/dialog";
|
||||
import { Cross } from "../../components/icons";
|
||||
import { CustomScrollbarsVirtualList } from "../../components/list-container";
|
||||
import { useEditorStore } from "../../stores/editor-store";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { isMac } from "../../utils/platform";
|
||||
import {
|
||||
getDefaultCommands,
|
||||
Command,
|
||||
getRecentCommands,
|
||||
resolveRecentCommand,
|
||||
commandActions,
|
||||
commandIcons,
|
||||
removeRecentCommand,
|
||||
addRecentCommand
|
||||
} from "./commands";
|
||||
|
||||
type CommandPaletteDialogProps = BaseDialogProps<boolean> & {
|
||||
isCommandMode: boolean;
|
||||
};
|
||||
|
||||
const COMMAND_PALETTE_STICKY_HEADER_HEIGHT = 32;
|
||||
|
||||
export const CommandPaletteDialog = DialogManager.register(
|
||||
function CommandPaletteDialog(props: CommandPaletteDialogProps) {
|
||||
const [selected, setSelected] = useState<number>(0);
|
||||
const [query, setQuery] = useState<string>("");
|
||||
const virtuosoRef = useRef<GroupedVirtuosoHandle>(null);
|
||||
const { search: dbSearch } = useDatabaseFuzzySearch();
|
||||
const defaultCommands = useRef<Command[]>();
|
||||
|
||||
const select = useCallback((index: number) => {
|
||||
setSelected(index);
|
||||
virtuosoRef.current?.scrollIntoView({
|
||||
index: index,
|
||||
calculateViewLocation: calculateCommandItemLocation
|
||||
});
|
||||
}, []);
|
||||
|
||||
const commands = usePromise(async () => {
|
||||
select(0);
|
||||
if (!defaultCommands.current)
|
||||
defaultCommands.current = await getDefaultCommands();
|
||||
const commands = props.isCommandMode
|
||||
? commandSearch(query, defaultCommands.current)
|
||||
: await dbSearch(query);
|
||||
const groups = commands.reduce(
|
||||
(acc, command) => {
|
||||
const index = acc.keys.indexOf(command.group);
|
||||
if (index === -1) {
|
||||
acc.keys.push(command.group);
|
||||
acc.counts.push(1);
|
||||
} else {
|
||||
acc.counts[index]++;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ counts: [], keys: [] } as {
|
||||
counts: number[];
|
||||
keys: string[];
|
||||
}
|
||||
);
|
||||
return { commands, groups };
|
||||
}, [dbSearch, query]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={true}
|
||||
width={650}
|
||||
onClose={() => {
|
||||
props.onClose(false);
|
||||
}}
|
||||
noScroll
|
||||
sx={{
|
||||
fontFamily: "body"
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
variant="columnFill"
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
height: 400,
|
||||
'[data-viewport-type="element"]': {
|
||||
width: "calc(100% - 10px) !important",
|
||||
px: 1
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (commands.status !== "fulfilled") return;
|
||||
if (e.key == "Enter") {
|
||||
e.preventDefault();
|
||||
const command = commands.value.commands[selected];
|
||||
command.action?.(command, {
|
||||
openInNewTab: e.ctrlKey || e.metaKey
|
||||
});
|
||||
addRecentCommand(command);
|
||||
props.onClose(false);
|
||||
}
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
select(getNextCommandIndex(selected, commands.value.commands));
|
||||
}
|
||||
if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
select(
|
||||
getPreviousCommandIndex(selected, commands.value.commands)
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
autoFocus
|
||||
variant="clean"
|
||||
placeholder={
|
||||
props.isCommandMode
|
||||
? strings.executeACommand()
|
||||
: strings.searchForNotesNotebooksAndTags()
|
||||
}
|
||||
sx={{
|
||||
m: 0,
|
||||
marginTop: 0,
|
||||
mr: 0,
|
||||
px: 2,
|
||||
borderRadius: 0,
|
||||
borderBottom: "1px solid var(--border)"
|
||||
}}
|
||||
onChange={debounce((e) => {
|
||||
setQuery(e.target.value);
|
||||
}, 100)}
|
||||
/>
|
||||
<GroupedVirtuoso
|
||||
ref={virtuosoRef}
|
||||
style={{ overflow: "hidden" }}
|
||||
components={{
|
||||
Scroller: CustomScrollbarsVirtualList,
|
||||
Footer: () => (
|
||||
<div
|
||||
className="footer"
|
||||
style={{
|
||||
height: "5px"
|
||||
}}
|
||||
/>
|
||||
),
|
||||
EmptyPlaceholder: () => (
|
||||
<Text
|
||||
sx={{
|
||||
px: 1
|
||||
}}
|
||||
variant="subBody"
|
||||
>
|
||||
{query ? strings.noResultsFound(query) : ""}
|
||||
</Text>
|
||||
),
|
||||
TopItemList: ({ style, ...props }) => (
|
||||
<div
|
||||
className="top-item-list"
|
||||
{...props}
|
||||
style={{
|
||||
...style,
|
||||
width: "calc(100% - 10px) !important",
|
||||
padding: "0px 5px"
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
groupCounts={
|
||||
commands.status === "fulfilled"
|
||||
? commands.value.groups.counts
|
||||
: []
|
||||
}
|
||||
groupContent={(groupIndex) => {
|
||||
if (commands.status !== "fulfilled") return null;
|
||||
const label =
|
||||
commands.value.groups.keys[groupIndex] === "recent"
|
||||
? strings.recents()
|
||||
: commands.value.groups.keys[groupIndex];
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
py: 1,
|
||||
bg: "background",
|
||||
px: 1
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
variant="subBody"
|
||||
dangerouslySetInnerHTML={{ __html: label }}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}}
|
||||
itemContent={(index) => {
|
||||
if (commands.status !== "fulfilled") return null;
|
||||
|
||||
const command = commands.value.commands[index];
|
||||
if (!command) return null;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
key={index}
|
||||
onClick={(e) => {
|
||||
command.action?.(command, {
|
||||
openInNewTab: e.ctrlKey || e.metaKey
|
||||
});
|
||||
addRecentCommand(command);
|
||||
props.onClose(false);
|
||||
}}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
py: 1,
|
||||
px: 1,
|
||||
borderRadius: "default",
|
||||
bg:
|
||||
index === selected
|
||||
? "background-selected"
|
||||
: "transparent",
|
||||
":hover:not(:disabled):not(:active)": {
|
||||
bg: index === selected ? "hover-selected" : "hover"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
gap: 1
|
||||
}}
|
||||
>
|
||||
{command.icon && (
|
||||
<command.icon
|
||||
size={14}
|
||||
color={index === selected ? "icon-selected" : "icon"}
|
||||
/>
|
||||
)}
|
||||
<Text
|
||||
variant="body"
|
||||
sx={{
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden"
|
||||
}}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: command.title
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
{command.group === "recent" && (
|
||||
<Button
|
||||
title={strings.removeFromRecents()}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
removeRecentCommand(command.id);
|
||||
commands.refresh();
|
||||
}}
|
||||
variant="secondary"
|
||||
sx={{
|
||||
bg: "transparent",
|
||||
p: "small",
|
||||
borderRadius: 100
|
||||
}}
|
||||
>
|
||||
<Cross size={12} />
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: "row",
|
||||
bg: "background-secondary",
|
||||
px: 2,
|
||||
py: 2,
|
||||
gap: 3,
|
||||
borderTop: "1px solid var(--border)",
|
||||
justifyContent: "end",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{getCommandPaletteHelp(props.isCommandMode).map((key) => {
|
||||
return (
|
||||
<Flex
|
||||
key={key.key}
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 1
|
||||
}}
|
||||
>
|
||||
<Text variant="body">{key.description}</Text>
|
||||
<Text variant="subBody">{key.key}</Text>
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
function getNextCommandIndex(selected: number, commands: Command[]) {
|
||||
return (selected + 1) % commands.length;
|
||||
}
|
||||
|
||||
function getPreviousCommandIndex(selected: number, commands: Command[]) {
|
||||
return (selected - 1 + commands.length) % commands.length;
|
||||
}
|
||||
|
||||
const RESULT_PREFIX_SUFFIX = {
|
||||
prefix: "<b style='color: var(--accent-foreground)'>",
|
||||
suffix: "</b>"
|
||||
};
|
||||
function commandSearch(query: string, commands: Command[]) {
|
||||
if (!query) return commands;
|
||||
return fuzzy(
|
||||
query,
|
||||
commands,
|
||||
{
|
||||
title: 10,
|
||||
group: 5
|
||||
},
|
||||
RESULT_PREFIX_SUFFIX
|
||||
);
|
||||
}
|
||||
|
||||
function useDatabaseFuzzySearch() {
|
||||
const notes = useRef<Note[]>();
|
||||
const notebooks = useRef<Notebook[]>();
|
||||
const tags = useRef<Tag[]>();
|
||||
const reminders = useRef<Reminder[]>();
|
||||
|
||||
const updateCollections = useCallback(async (force = false) => {
|
||||
if (force || !notes.current)
|
||||
notes.current = await db.notes.all
|
||||
.fields(["notes.id", "notes.title"])
|
||||
.items();
|
||||
if (force || !notebooks.current)
|
||||
notebooks.current = await db.notebooks.all
|
||||
.fields(["notebooks.id", "notebooks.title"])
|
||||
.items();
|
||||
if (force || !tags.current)
|
||||
tags.current = await db.tags.all
|
||||
.fields(["tags.id", "tags.title"])
|
||||
.items();
|
||||
if (force || !reminders.current)
|
||||
reminders.current = await db.reminders.all
|
||||
.fields(["reminders.id", "reminders.title"])
|
||||
.items();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const event = db.eventManager.subscribe(EVENTS.syncCompleted, async () => {
|
||||
await updateCollections(true);
|
||||
});
|
||||
return () => {
|
||||
event.unsubscribe();
|
||||
reminders.current = undefined;
|
||||
notebooks.current = undefined;
|
||||
tags.current = undefined;
|
||||
notes.current = undefined;
|
||||
};
|
||||
}, [updateCollections]);
|
||||
|
||||
const search = useCallback(
|
||||
async (query: string) => {
|
||||
if (!query) return await getSessionsAsCommands();
|
||||
|
||||
await updateCollections();
|
||||
|
||||
const list: Command[] = [];
|
||||
const collections: Record<
|
||||
"note" | "notebook" | "tag" | "reminder",
|
||||
{ id: string; title: string }[] | undefined
|
||||
> = {
|
||||
note: notes.current,
|
||||
notebook: notebooks.current,
|
||||
tag: tags.current,
|
||||
reminder: reminders.current
|
||||
};
|
||||
for (const _type in collections) {
|
||||
const type = _type as keyof typeof collections;
|
||||
const items = collections[type];
|
||||
if (!items) continue;
|
||||
for (const item of fuzzy(
|
||||
query,
|
||||
items,
|
||||
{
|
||||
title: 10
|
||||
},
|
||||
RESULT_PREFIX_SUFFIX
|
||||
)) {
|
||||
list.push({
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
group: strings.dataTypesPluralCamelCase[type](),
|
||||
type: type,
|
||||
action: commandActions[type],
|
||||
icon: commandIcons[type]
|
||||
});
|
||||
}
|
||||
}
|
||||
return list;
|
||||
},
|
||||
[updateCollections]
|
||||
);
|
||||
|
||||
return { search };
|
||||
}
|
||||
|
||||
async function getSessionsAsCommands() {
|
||||
const commands: Command[] = [];
|
||||
for (const recentCommand of getRecentCommands()) {
|
||||
if (recentCommand.type === "command") continue;
|
||||
|
||||
const resolvedCommand = await resolveRecentCommand(recentCommand);
|
||||
if (resolvedCommand) commands.push(resolvedCommand);
|
||||
}
|
||||
|
||||
const sessions = useEditorStore.getState().get().sessions;
|
||||
for (const session of sessions) {
|
||||
if (
|
||||
session.type === "new" ||
|
||||
commands.find((c) => c.id === session.note.id)
|
||||
)
|
||||
continue;
|
||||
|
||||
commands.push({
|
||||
id: session.note.id,
|
||||
title: session.note.title,
|
||||
group: strings.dataTypesPluralCamelCase.note(),
|
||||
type: "note" as const,
|
||||
action: commandActions.note,
|
||||
icon: commandIcons.note
|
||||
});
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* This override is required to ensure smooth scrolling when moving between
|
||||
* command palette items using the keyboard. Without this the selected item
|
||||
* is not visible when moving up/down in the command palette.
|
||||
*/
|
||||
const calculateCommandItemLocation: CalculateViewLocation = ({
|
||||
itemTop,
|
||||
itemBottom,
|
||||
viewportTop,
|
||||
viewportBottom,
|
||||
locationParams: { behavior, align, ...rest }
|
||||
}) => {
|
||||
const topOffset = viewportTop + COMMAND_PALETTE_STICKY_HEADER_HEIGHT;
|
||||
const itemInView = itemTop >= topOffset && itemBottom <= viewportBottom;
|
||||
if (itemInView) return null;
|
||||
return {
|
||||
...rest,
|
||||
offset:
|
||||
itemBottom > viewportBottom
|
||||
? itemBottom - viewportBottom
|
||||
: itemTop - topOffset,
|
||||
behavior,
|
||||
align:
|
||||
align ??
|
||||
(itemBottom > viewportBottom
|
||||
? "end"
|
||||
: itemTop < topOffset
|
||||
? "start"
|
||||
: "center")
|
||||
};
|
||||
};
|
||||
|
||||
function getCommandPaletteHelp(isCommandMode: boolean) {
|
||||
return [
|
||||
{
|
||||
key: "⏎",
|
||||
description: isCommandMode ? strings.execute() : strings.open()
|
||||
},
|
||||
...(isCommandMode
|
||||
? [
|
||||
{
|
||||
key: isMac() ? "⌘P" : "Ctrl+P",
|
||||
description: strings.quickOpen()
|
||||
}
|
||||
]
|
||||
: [
|
||||
{
|
||||
key: isMac() ? "⌘⏎" : "Ctrl+⏎",
|
||||
description: strings.openInNewTab()
|
||||
},
|
||||
{
|
||||
key: isMac() ? "⌘K" : "Ctrl+K",
|
||||
description: strings.commandPalette()
|
||||
}
|
||||
])
|
||||
];
|
||||
}
|
||||
513
apps/web/src/dialogs/command-palette/commands.ts
Normal file
513
apps/web/src/dialogs/command-palette/commands.ts
Normal file
@@ -0,0 +1,513 @@
|
||||
/*
|
||||
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 { strings } from "@notesnook/intl";
|
||||
import { db } from "../../common/db";
|
||||
import {
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
ArrowTopRight,
|
||||
createIcon,
|
||||
Icon,
|
||||
Plus,
|
||||
Radar,
|
||||
Redo,
|
||||
Undo,
|
||||
Notebook as NotebookIcon,
|
||||
Note as NoteIcon,
|
||||
Reminder as ReminderIcon,
|
||||
Tag as TagIcon
|
||||
} from "../../components/icons";
|
||||
import { trashMenuItems } from "../../components/trash-item";
|
||||
import { hashNavigate, navigate } from "../../navigation";
|
||||
import { useEditorStore } from "../../stores/editor-store";
|
||||
import { useStore as useNoteStore } from "../../stores/note-store";
|
||||
import { useStore as useThemeStore } from "../../stores/theme-store";
|
||||
import { AttachmentsDialog } from "../attachments-dialog";
|
||||
import { CreateColorDialog } from "../create-color-dialog";
|
||||
import { noteMenuItems } from "../../components/note";
|
||||
import { MenuItem } from "@notesnook/ui";
|
||||
import { notebookMenuItems } from "../../components/notebook";
|
||||
import { tagMenuItems } from "../../components/tag";
|
||||
import { useEditorManager } from "../../components/editor/manager";
|
||||
import Config from "../../utils/config";
|
||||
|
||||
export interface BaseCommand {
|
||||
id: string;
|
||||
type: "command" | "note" | "notebook" | "tag" | "reminder";
|
||||
}
|
||||
export interface Command extends BaseCommand {
|
||||
title: string;
|
||||
group: string;
|
||||
icon?: Icon;
|
||||
action: (command: Command, options?: { openInNewTab?: boolean }) => void;
|
||||
excludeFromRecents?: boolean;
|
||||
}
|
||||
|
||||
export interface RecentCommand extends BaseCommand {
|
||||
id: string;
|
||||
group: "recent";
|
||||
}
|
||||
|
||||
export const commandActions = {
|
||||
command: (command: Command) => getCommandById(command.id)?.action(command),
|
||||
note: (command: Command, options?: { openInNewTab?: boolean }) =>
|
||||
useEditorStore.getState().openSession(command.id, options),
|
||||
notebook: (command: Command) => navigate(`/notebooks/${command.id}`),
|
||||
tag: (command: Command) => navigate(`/tags/${command.id}`),
|
||||
reminder: (command: Command) => hashNavigate(`/reminders/${command.id}/edit`)
|
||||
};
|
||||
|
||||
export const commandIcons = {
|
||||
command: (command: BaseCommand) => getCommandById(command.id)?.icon,
|
||||
note: NoteIcon,
|
||||
notebook: NotebookIcon,
|
||||
tag: TagIcon,
|
||||
reminder: ReminderIcon
|
||||
};
|
||||
|
||||
const RECENT_COMMANDS_SAVE_KEY = "commandPalette:recent";
|
||||
|
||||
const staticCommands: Command[] = [
|
||||
{
|
||||
id: "notes",
|
||||
title: strings.dataTypesPluralCamelCase.note(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => navigate("/"),
|
||||
group: strings.navigate(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "notebooks",
|
||||
title: strings.dataTypesPluralCamelCase.notebook(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => navigate("/notebooks"),
|
||||
group: strings.navigate(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "tags",
|
||||
title: strings.dataTypesPluralCamelCase.tag(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => navigate("/tags"),
|
||||
group: strings.navigate(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "favorites",
|
||||
title: strings.dataTypesPluralCamelCase.favorite(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => navigate("/favorites"),
|
||||
group: strings.navigate(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "reminders",
|
||||
title: strings.dataTypesPluralCamelCase.reminder(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => navigate("/reminders"),
|
||||
group: strings.navigate(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "monographs",
|
||||
title: strings.dataTypesPluralCamelCase.monograph(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => navigate("/monographs"),
|
||||
group: strings.navigate(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "trash",
|
||||
title: strings.trash(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => navigate("/trash"),
|
||||
group: strings.navigate(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "settings",
|
||||
title: strings.settings(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => hashNavigate("/settings", { replace: true }),
|
||||
group: strings.navigate(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "help",
|
||||
title: strings.helpAndSupport(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => (window.location.href = "https://help.notesnook.com"),
|
||||
group: strings.navigate(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "attachment-manager",
|
||||
title: strings.attachmentManager(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => AttachmentsDialog.show({}),
|
||||
group: strings.navigate(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "new-note",
|
||||
title: strings.newNote(),
|
||||
icon: Plus,
|
||||
action: () => useEditorStore.getState().newSession(),
|
||||
group: strings.create(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "new-notebook",
|
||||
title: strings.newNotebook(),
|
||||
icon: Plus,
|
||||
action: () => hashNavigate("/notebooks/create", { replace: true }),
|
||||
group: strings.create(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "new-tag",
|
||||
title: strings.newTag(),
|
||||
icon: Plus,
|
||||
action: () => hashNavigate("/tags/create", { replace: true }),
|
||||
group: strings.create(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "new-reminder",
|
||||
title: strings.newReminder(),
|
||||
icon: Plus,
|
||||
action: () => hashNavigate(`/reminders/create`, { replace: true }),
|
||||
group: strings.create(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "new-color",
|
||||
title: strings.newColor(),
|
||||
icon: Plus,
|
||||
action: () => CreateColorDialog.show(true),
|
||||
group: strings.create(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "toggle-theme",
|
||||
title: strings.toggleTheme(),
|
||||
icon: Radar,
|
||||
action: () => useThemeStore.getState().toggleColorScheme(),
|
||||
group: strings.general(),
|
||||
type: "command"
|
||||
}
|
||||
];
|
||||
|
||||
export async function getDefaultCommands(): Promise<Command[]> {
|
||||
const recentCommands: Command[] = [];
|
||||
for (const recentCommand of getRecentCommands()) {
|
||||
if (recentCommand.type !== "command") continue;
|
||||
const resolvedCommand = await resolveRecentCommand(recentCommand);
|
||||
if (resolvedCommand) recentCommands.push(resolvedCommand);
|
||||
}
|
||||
|
||||
return [
|
||||
...recentCommands,
|
||||
...(await getActiveNoteCommands()),
|
||||
...(await getActiveNotebookCommands()),
|
||||
...(await getActiveTagCommands()),
|
||||
...getEditorCommands(),
|
||||
...staticCommands
|
||||
];
|
||||
}
|
||||
|
||||
export function getCommandById(id: string): Command | undefined {
|
||||
return staticCommands.find((command) => command.id === id);
|
||||
}
|
||||
|
||||
export async function resolveRecentCommand(
|
||||
recentCommand: RecentCommand
|
||||
): Promise<Command | undefined> {
|
||||
const title = await getCommandTitle(recentCommand);
|
||||
const action = commandActions[recentCommand.type];
|
||||
if (!title || !action) return;
|
||||
|
||||
return {
|
||||
id: recentCommand.id,
|
||||
group: "recent",
|
||||
title: title,
|
||||
type: recentCommand.type,
|
||||
icon:
|
||||
recentCommand.type === "command"
|
||||
? commandIcons.command(recentCommand)
|
||||
: commandIcons[recentCommand.type],
|
||||
action: action
|
||||
};
|
||||
}
|
||||
|
||||
export function getRecentCommands() {
|
||||
return Config.get<RecentCommand[]>(RECENT_COMMANDS_SAVE_KEY, []);
|
||||
}
|
||||
|
||||
export function addRecentCommand(command: Command) {
|
||||
if (command.excludeFromRecents) return;
|
||||
const commands = getRecentCommands();
|
||||
const index = commands.findIndex((c) => c.id === command.id);
|
||||
if (index > -1) commands.splice(index, 1);
|
||||
commands.unshift({
|
||||
group: "recent",
|
||||
id: command.id,
|
||||
type: command.type
|
||||
});
|
||||
Config.set(RECENT_COMMANDS_SAVE_KEY, commands);
|
||||
}
|
||||
|
||||
export function removeRecentCommand(id: string) {
|
||||
const commands = getRecentCommands();
|
||||
const index = commands.findIndex((c) => c.id === id);
|
||||
if (index > -1) {
|
||||
commands.splice(index, 1);
|
||||
Config.set(RECENT_COMMANDS_SAVE_KEY, commands);
|
||||
}
|
||||
}
|
||||
|
||||
async function getActiveNoteCommands(): Promise<Command[]> {
|
||||
const note = useEditorStore.getState().getActiveNote();
|
||||
if (!note) return [];
|
||||
const group = strings.actionsForNote(note.title);
|
||||
|
||||
const commands: Command[] = [];
|
||||
|
||||
const menuItems =
|
||||
note.type !== "trash"
|
||||
? noteMenuItems(note, [note.id], {
|
||||
locked: !!(
|
||||
await db
|
||||
.sql()
|
||||
.selectFrom("content")
|
||||
.where("noteId", "in", [note.id])
|
||||
.select(["noteId", "locked"])
|
||||
.executeTakeFirst()
|
||||
)?.locked
|
||||
})
|
||||
: trashMenuItems(note);
|
||||
|
||||
for (const menuItem of menuItems) {
|
||||
commands.push(...menuItemToCommands(menuItem, group, "active-note"));
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
async function getActiveNotebookCommands() {
|
||||
const context = useNoteStore.getState().context;
|
||||
if (context?.type !== "notebook") return [];
|
||||
const notebook = await db.notebooks.notebook(context.id);
|
||||
if (!notebook) return [];
|
||||
const group = strings.actionsForNotebook(notebook.title);
|
||||
|
||||
const commands: Command[] = [];
|
||||
|
||||
const menuItems = notebookMenuItems(notebook, [notebook.id]);
|
||||
for (const menuItem of menuItems) {
|
||||
commands.push(...menuItemToCommands(menuItem, group, "active-notebook"));
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
async function getActiveTagCommands() {
|
||||
const context = useNoteStore.getState().context;
|
||||
if (context?.type !== "tag") return [];
|
||||
const tag = await db.tags.tag(context.id);
|
||||
if (!tag) return [];
|
||||
const group = strings.actionsForTag(tag.title);
|
||||
const commands: Command[] = [];
|
||||
|
||||
const menuItems = tagMenuItems(tag, [tag.id]);
|
||||
for (const menuItem of menuItems) {
|
||||
commands.push(...menuItemToCommands(menuItem, group, "active-tag"));
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
function getEditorCommands(): Command[] {
|
||||
const session = useEditorStore.getState().getActiveSession();
|
||||
if (!session) return [];
|
||||
const editor = useEditorManager.getState().editors[session.id];
|
||||
|
||||
const commands: Command[] = [
|
||||
{
|
||||
id: "new-tab",
|
||||
title: strings.newTab(),
|
||||
icon: Plus,
|
||||
action: () => useEditorStore.getState().addTab(),
|
||||
group: strings.editor(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "next-tab",
|
||||
title: strings.nextTab(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => useEditorStore.getState().focusNextTab(),
|
||||
group: strings.editor(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "previous-tab",
|
||||
title: strings.previousTab(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => useEditorStore.getState().focusPreviousTab(),
|
||||
group: strings.editor(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "close-tab",
|
||||
title: strings.closeCurrentTab(),
|
||||
icon: Radar,
|
||||
action: () => useEditorStore.getState().closeActiveTab(),
|
||||
group: strings.editor(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "close-all-tabs",
|
||||
title: strings.closeAllTabs(),
|
||||
icon: Radar,
|
||||
action: () => useEditorStore.getState().closeAllTabs(),
|
||||
group: strings.editor(),
|
||||
type: "command"
|
||||
}
|
||||
];
|
||||
|
||||
if (session.type !== "readonly" && (editor.canUndo || editor.canRedo)) {
|
||||
commands.push(
|
||||
{
|
||||
id: "undo",
|
||||
title: strings.undo(),
|
||||
icon: Undo,
|
||||
action: () => editor.editor?.undo(),
|
||||
group: strings.editor(),
|
||||
type: "command",
|
||||
excludeFromRecents: true
|
||||
},
|
||||
{
|
||||
id: "redo",
|
||||
title: strings.redo(),
|
||||
icon: Redo,
|
||||
action: () => editor.editor?.redo(),
|
||||
group: strings.editor(),
|
||||
type: "command",
|
||||
excludeFromRecents: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (useEditorStore.getState().canGoBack) {
|
||||
commands.push({
|
||||
id: "go-back-in-tab",
|
||||
title: strings.goBackInTab(),
|
||||
icon: ArrowLeft,
|
||||
action: () => useEditorStore.getState().goBack(),
|
||||
group: strings.editor(),
|
||||
type: "command"
|
||||
});
|
||||
}
|
||||
|
||||
if (useEditorStore.getState().canGoForward) {
|
||||
commands.push({
|
||||
id: "go-forward-in-tab",
|
||||
title: strings.goForwardInTab(),
|
||||
icon: ArrowRight,
|
||||
action: () => useEditorStore.getState().goForward(),
|
||||
group: strings.editor(),
|
||||
type: "command"
|
||||
});
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
function menuItemToCommands(
|
||||
menuItem: MenuItem,
|
||||
group: string,
|
||||
idSuffix?: string
|
||||
): Command[] {
|
||||
if (menuItem.type !== "button" || menuItem.isHidden || menuItem.isDisabled)
|
||||
return [];
|
||||
|
||||
const commands: Command[] = [];
|
||||
if (menuItem.menu) {
|
||||
for (const item of menuItem.menu.items) {
|
||||
commands.push(
|
||||
...menuItemToCommands(item, group, idSuffix).map((c) => {
|
||||
c.title = `${menuItem.title}: ${c.title}`;
|
||||
c.id = `${menuItem.key}-${c.id}`;
|
||||
return c;
|
||||
})
|
||||
);
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
|
||||
if (!menuItem.onClick) return commands;
|
||||
const _Icon = menuItem.icon ? createIcon(menuItem.icon) : undefined;
|
||||
commands.push({
|
||||
id: idSuffix ? `${menuItem.key}-${idSuffix}` : menuItem.key,
|
||||
title: menuItem.title,
|
||||
action: menuItem.onClick,
|
||||
group,
|
||||
type: "command",
|
||||
icon: _Icon,
|
||||
excludeFromRecents: true
|
||||
});
|
||||
return commands;
|
||||
}
|
||||
|
||||
async function getCommandTitle({ id, type }: RecentCommand) {
|
||||
switch (type) {
|
||||
case "command": {
|
||||
const command = getCommandById(id);
|
||||
if (!command || typeof command.title !== "string") return;
|
||||
return command.title;
|
||||
}
|
||||
case "note": {
|
||||
const note = (await db.notes.all.fields(["notes.title"]).items([id])).at(
|
||||
0
|
||||
);
|
||||
if (!note) return;
|
||||
return note.title;
|
||||
}
|
||||
case "notebook": {
|
||||
const notebook = (
|
||||
await db.notebooks.all.fields(["notebooks.title"]).items([id])
|
||||
).at(0);
|
||||
if (!notebook) return;
|
||||
return notebook.title;
|
||||
}
|
||||
case "tag": {
|
||||
const tag = (await db.tags.all.fields(["tags.title"]).items([id])).at(0);
|
||||
if (!tag) return;
|
||||
return tag.title;
|
||||
}
|
||||
case "reminder": {
|
||||
const reminder = (
|
||||
await db.reminders.all.fields(["reminders.title"]).items([id])
|
||||
).at(0);
|
||||
if (!reminder) return;
|
||||
return reminder.title;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
apps/web/src/dialogs/command-palette/index.ts
Normal file
20
apps/web/src/dialogs/command-palette/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
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 * from "./command-palette-dialog";
|
||||
@@ -93,9 +93,28 @@ const features: Record<FeatureKeys, Feature> = {
|
||||
]
|
||||
: [
|
||||
{
|
||||
title: "Better tabs",
|
||||
subtitle:
|
||||
"Tabs have been overhauled to be more predictable and easier to use. Give it a try! We are sure you'll love it."
|
||||
title: "Command palette",
|
||||
subtitle: (
|
||||
<>
|
||||
You can now quickly execute commands like navigation, actions on
|
||||
notes, notebooks etc. just by pressing{" "}
|
||||
<Code text="Ctrl/Cmd+K" />. This will open a command palette
|
||||
where you can fuzzy search for commands and execute them.
|
||||
</>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: "Quick open",
|
||||
subtitle: (
|
||||
<>
|
||||
You can also open the command palette in quick open mode by
|
||||
pressing <Code text="Ctrl/Cmd+P" />. This will open a command
|
||||
palette where you can fuzzy search for your notes, notebooks,
|
||||
tags, etc. and quickly open them by pressing{" "}
|
||||
<Code text="Enter" />. Try it out and let us know what you
|
||||
think!
|
||||
</>
|
||||
)
|
||||
}
|
||||
],
|
||||
cta: {
|
||||
@@ -128,12 +147,7 @@ export const FeatureDialog = DialogManager.register(function FeatureDialog(
|
||||
) {
|
||||
const { featureName, onClose } = props;
|
||||
const feature = features[featureName];
|
||||
|
||||
useEffect(() => {
|
||||
if (!feature || (feature.shouldShow && !feature.shouldShow())) {
|
||||
onClose(false);
|
||||
}
|
||||
}, [feature, onClose]);
|
||||
const isShown = feature && feature.shouldShow && feature.shouldShow();
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
@@ -141,6 +155,9 @@ export const FeatureDialog = DialogManager.register(function FeatureDialog(
|
||||
title={feature.title}
|
||||
description={feature.subtitle}
|
||||
textAlignment="center"
|
||||
onOpen={() => {
|
||||
if (!isShown) onClose(false);
|
||||
}}
|
||||
positiveButton={{
|
||||
text: (
|
||||
<Flex>
|
||||
|
||||
@@ -187,7 +187,7 @@ const SettingsGroups = [
|
||||
// 2. Settings will be synced so their state must be serializable
|
||||
// 3. Settings will be grouped
|
||||
// - Where group header is customizable
|
||||
// 4. Sections/groups must be able to accomodate tips & tutorials for future.
|
||||
// 4. Sections/groups must be able to accommodate tips & tutorials for future.
|
||||
// 5. Settings will be stateful but independent such that any one setting
|
||||
// can appear independent of others (e.g. as a search result)
|
||||
|
||||
|
||||
@@ -168,6 +168,11 @@ const routes = defineRoutes({
|
||||
title: async () => {
|
||||
const tag = await db.tags.tag(tagId);
|
||||
if (!tag) return;
|
||||
notestore.setContext({
|
||||
type: "tag",
|
||||
id: tagId,
|
||||
item: tag
|
||||
});
|
||||
return `#${tag.title}`;
|
||||
},
|
||||
component: Notes,
|
||||
|
||||
@@ -36,7 +36,7 @@ import { Text } from "@theme-ui/components";
|
||||
import { EV, EVENTS } from "@notesnook/core";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export async function startApp() {
|
||||
export async function startApp(children?: React.ReactNode) {
|
||||
const rootElement = document.getElementById("root");
|
||||
if (!rootElement) return;
|
||||
const root = createRoot(rootElement);
|
||||
@@ -72,6 +72,7 @@ export async function startApp() {
|
||||
routeProps={props}
|
||||
/>
|
||||
</AppLock>
|
||||
{children}
|
||||
</BaseThemeProvider>
|
||||
</GlobalErrorHandler>
|
||||
</ErrorBoundary>
|
||||
|
||||
@@ -135,7 +135,10 @@ class AttachmentStore extends BaseStore<AttachmentStore> {
|
||||
if (await db.attachments.remove(attachment.hash, false)) {
|
||||
await this.get().refresh();
|
||||
await useNoteStore.getState().refresh();
|
||||
useEditorStore.getState().closeSessions(...linkedNotes);
|
||||
const sessions = linkedNotes
|
||||
.map((id) => useEditorStore.getState().getSessionsForNote(id))
|
||||
.flat();
|
||||
useEditorStore.getState().closeTabs(...sessions.map((s) => s.id));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
@@ -414,9 +414,13 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
event.item.fromType === "color" &&
|
||||
event.item.toType === "note"
|
||||
) {
|
||||
updateSession(event.item.toId, undefined, {
|
||||
color: event.item.fromId
|
||||
});
|
||||
for (const session of sessions) {
|
||||
if ("color" in session && session.note.id === event.item.toId) {
|
||||
updateSession(session.id, undefined, {
|
||||
color: event.item.fromId
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
event.type === "unlink" &&
|
||||
((event.reference.type === "color" &&
|
||||
@@ -467,9 +471,13 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
event.item.fromType === "tag" &&
|
||||
event.item.toType === "note"
|
||||
) {
|
||||
updateSession(event.item.toId, undefined, {
|
||||
tags: await db.notes.tags(event.item.toId)
|
||||
});
|
||||
for (const session of sessions) {
|
||||
if ("note" in session && session.note.id === event.item.toId) {
|
||||
updateSession(session.id, undefined, {
|
||||
tags: await db.notes.tags(event.item.toId)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (event.collection === "tags") {
|
||||
if (event.type === "update") {
|
||||
@@ -595,9 +603,18 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
|
||||
if (!oldContent || !currentContent) return;
|
||||
|
||||
const { getSession, addSession, sessions, activeTabId, tabs } = this.get();
|
||||
const {
|
||||
getSession,
|
||||
addSession,
|
||||
sessions,
|
||||
activeTabId,
|
||||
getActiveTab,
|
||||
tabs
|
||||
} = this.get();
|
||||
|
||||
const tabId = activeTabId ?? this.addTab();
|
||||
const tabId = getActiveTab()?.pinned
|
||||
? this.addTab(getId())
|
||||
: activeTabId || this.addTab(getId());
|
||||
const tab = tabs.find((t) => t.id === tabId);
|
||||
const activeSession = tab && getSession(tab.sessionId);
|
||||
const oldSession = sessions.find(
|
||||
@@ -650,15 +667,19 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
tabs,
|
||||
activateSession,
|
||||
activeTabId,
|
||||
getActiveTab,
|
||||
rehydrateSession,
|
||||
getTabsForNote,
|
||||
addTab
|
||||
} = 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 activeTab = getActiveTab();
|
||||
const tabId =
|
||||
// if a tab is pinned then always open a new tab.
|
||||
options.openInNewTab || activeTab?.pinned
|
||||
? addTab(getId())
|
||||
: oldTabForNote?.id || activeTabId || addTab(getId());
|
||||
|
||||
const tab = tabs.find((t) => t.id === tabId);
|
||||
const activeSession = tab && getSession(tab.sessionId);
|
||||
@@ -899,15 +920,14 @@ 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;
|
||||
let noteId =
|
||||
"note" in currentSession ? currentSession.note.id : partial.note?.id;
|
||||
|
||||
if (isLockedSession(currentSession) && partial.content) {
|
||||
logger.debug("Saving locked content", { id });
|
||||
if (!noteId) return;
|
||||
logger.debug("Saving locked content", { noteId });
|
||||
|
||||
await db.vault.save({
|
||||
noteId = await db.vault.save({
|
||||
content: partial.content,
|
||||
sessionId,
|
||||
id: noteId
|
||||
@@ -915,10 +935,10 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
} else {
|
||||
if (partial.content)
|
||||
logger.debug("Saving content", {
|
||||
id,
|
||||
noteId,
|
||||
length: partial.content.data.length
|
||||
});
|
||||
await db.notes.add({
|
||||
noteId = await db.notes.add({
|
||||
...partial.note,
|
||||
dateEdited:
|
||||
partial.ignoreEdit && currentSession.type === "default"
|
||||
@@ -934,7 +954,7 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
});
|
||||
}
|
||||
|
||||
const note = await db.notes.note(noteId);
|
||||
const note = noteId && (await db.notes.note(noteId));
|
||||
if (!note) throw new Error("Note not saved.");
|
||||
|
||||
if (currentSession.type === "new") {
|
||||
@@ -942,16 +962,16 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
if (context) {
|
||||
const { type } = context;
|
||||
if (type === "notebook")
|
||||
await db.notes.addToNotebook(context.id, noteId);
|
||||
await db.notes.addToNotebook(context.id, note.id);
|
||||
else if (type === "color" || type === "tag")
|
||||
await db.relations.add(
|
||||
{ type, id: context.id },
|
||||
{ id, type: "note" }
|
||||
{ id: note.id, type: "note" }
|
||||
);
|
||||
} else {
|
||||
const defaultNotebook = db.settings.getDefaultNotebook();
|
||||
if (defaultNotebook)
|
||||
await db.notes.addToNotebook(defaultNotebook, noteId);
|
||||
await db.notes.addToNotebook(defaultNotebook, note.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -991,6 +1011,19 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
sessionId
|
||||
});
|
||||
}
|
||||
|
||||
if (partial.note?.title !== undefined) {
|
||||
const { sessions } = this.get();
|
||||
for (const session of sessions) {
|
||||
if ("note" in session && session.note.id === note.id) {
|
||||
this.updateSession(session.id, undefined, {
|
||||
note,
|
||||
title: note.title
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setDocumentTitle(
|
||||
settingStore.get().hideNoteTitle ? undefined : note.title
|
||||
);
|
||||
@@ -1032,8 +1065,8 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
};
|
||||
|
||||
newSession = () => {
|
||||
const { activeTabId, activateSession } = this.get();
|
||||
if (!activeTabId) {
|
||||
const { activeTabId, activateSession, getActiveTab } = this.get();
|
||||
if (!activeTabId || getActiveTab()?.pinned) {
|
||||
this.addTab();
|
||||
return;
|
||||
}
|
||||
@@ -1050,6 +1083,17 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
});
|
||||
};
|
||||
|
||||
closeActiveTab = () => {
|
||||
const { activeTabId } = this.get();
|
||||
if (!activeTabId) return;
|
||||
this.closeTabs(activeTabId);
|
||||
};
|
||||
|
||||
closeAllTabs = () => {
|
||||
const { tabs } = this.get();
|
||||
this.closeTabs(...tabs.map((t) => t.id));
|
||||
};
|
||||
|
||||
closeTabs = (...ids: string[]) => {
|
||||
this.set((state) => {
|
||||
const tabs: TabItem[] = [];
|
||||
@@ -1177,6 +1221,19 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
return id;
|
||||
};
|
||||
|
||||
pinTab = (tabId: string) => {
|
||||
const { tabs: _tabs, activeTabId } = this.get();
|
||||
const tabs = _tabs.slice();
|
||||
const index = tabs.findIndex((t) => t.id === tabId);
|
||||
if (index === -1) return;
|
||||
|
||||
tabs[index].pinned = !tabs[index].pinned;
|
||||
tabs.sort((a, b) => (a.pinned === b.pinned ? 0 : a.pinned ? -1 : 1));
|
||||
useEditorStore.setState({
|
||||
tabs
|
||||
});
|
||||
};
|
||||
|
||||
focusTab = (tabId: string | undefined, sessionId?: string) => {
|
||||
if (!tabId) return;
|
||||
|
||||
@@ -1184,14 +1241,14 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
if (history.includes(tabId)) history.splice(history.indexOf(tabId), 1);
|
||||
history.push(tabId);
|
||||
|
||||
const tab = this.get().tabs.find((t) => t.id === tabId);
|
||||
this.set({
|
||||
activeTabId: tabId,
|
||||
canGoBack: tabSessionHistory.canGoBack(tabId),
|
||||
canGoForward: tabSessionHistory.canGoForward(tabId)
|
||||
canGoBack: !tab?.pinned && tabSessionHistory.canGoBack(tabId),
|
||||
canGoForward: !tab?.pinned && tabSessionHistory.canGoForward(tabId)
|
||||
});
|
||||
|
||||
sessionId =
|
||||
sessionId || this.get().tabs.find((t) => t.id === tabId)?.sessionId;
|
||||
sessionId = sessionId || tab?.sessionId;
|
||||
if (sessionId) this.rehydrateSession(sessionId);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import createStore from "../common/store";
|
||||
import { db } from "../common/db";
|
||||
import BaseStore from "./index";
|
||||
import config from "../utils/config";
|
||||
import { EV, EVENTS } from "@notesnook/core";
|
||||
import Config from "../utils/config";
|
||||
import { hashNavigate } from "../navigation";
|
||||
@@ -81,7 +80,7 @@ class UserStore extends BaseStore<UserStore> {
|
||||
state.user = undefined;
|
||||
state.isLoggedIn = false;
|
||||
});
|
||||
config.clear();
|
||||
Config.logout();
|
||||
if (reason) {
|
||||
await ConfirmDialog.show({
|
||||
title: strings.loggedOut(),
|
||||
|
||||
@@ -57,5 +57,30 @@ function has(predicate: (key: string) => boolean) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Config = { set, get, clear, all, has, remove };
|
||||
function logout() {
|
||||
const toKeep = [
|
||||
"editorConfig",
|
||||
"backupStorageLocation",
|
||||
"serverUrls",
|
||||
"corsProxy",
|
||||
"theme:light",
|
||||
"theme:dark",
|
||||
"colorScheme",
|
||||
"followSystemTheme"
|
||||
];
|
||||
const vals = {} as Record<string, any>;
|
||||
|
||||
for (const keep of toKeep) {
|
||||
const val = get(keep);
|
||||
if (val !== undefined) vals[keep] = val;
|
||||
}
|
||||
|
||||
clear();
|
||||
|
||||
for (const key in vals) {
|
||||
set(key, vals[key]);
|
||||
}
|
||||
}
|
||||
|
||||
const Config = { set, get, clear, all, has, remove, logout };
|
||||
export default Config;
|
||||
|
||||
@@ -63,7 +63,7 @@ export async function postMessage(
|
||||
// so all download links needs to be prefixed to avoid any other conflict
|
||||
data.origin = window.location.origin;
|
||||
|
||||
// if we ever (in some feature versoin of streamsaver) would like to
|
||||
// if we ever (in some feature version of streamsaver) would like to
|
||||
// redirect back to the page of who initiated a http request
|
||||
data.referrer = data.referrer || document.referrer || origin;
|
||||
|
||||
|
||||
@@ -81,12 +81,17 @@ function Notebook(props: NotebookProps) {
|
||||
Promise.all([
|
||||
!!notebookId && db.notebooks.exists(notebookId),
|
||||
db.notebooks.exists(rootId)
|
||||
]).then((exists) => {
|
||||
]).then(async (exists) => {
|
||||
if (exists.every((e) => !e)) {
|
||||
navigate(`/notebooks`, { replace: true });
|
||||
return;
|
||||
}
|
||||
setContext({ type: "notebook", id: notebookId || rootId });
|
||||
const notebook = await db.notebooks.notebook(notebookId || rootId);
|
||||
setContext({
|
||||
type: "notebook",
|
||||
id: notebookId || rootId,
|
||||
item: notebook
|
||||
});
|
||||
});
|
||||
}, [rootId, notebookId]);
|
||||
|
||||
@@ -148,7 +153,10 @@ function SubNotebooks({ rootId, isCollapsed, onClick }: SubNotebooksProps) {
|
||||
const toggleSelection = useSelectionStore(
|
||||
(store) => store.toggleSelectionMode
|
||||
);
|
||||
const rootNotebook = usePromise(() => db.notebooks.notebook(rootId));
|
||||
const rootNotebook = usePromise(
|
||||
() => db.notebooks.notebook(rootId),
|
||||
[rootId]
|
||||
);
|
||||
const notebooks = useNotebookStore((store) => store.notebooks);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
3
fastlane/metadata/android/en-US/changelogs/15184.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/15184.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
- Small bug fixes and improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
6
fastlane/metadata/android/en-US/changelogs/15189.txt
Normal file
6
fastlane/metadata/android/en-US/changelogs/15189.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
- New and improved editor tabs with history
|
||||
- Fixed a crash on app launch faced by some users
|
||||
- Fixed homescreen note widget does not open correct note
|
||||
- Many small bug fixes and improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
6
fastlane/metadata/android/en-US/changelogs/15209.txt
Normal file
6
fastlane/metadata/android/en-US/changelogs/15209.txt
Normal file
@@ -0,0 +1,6 @@
|
||||
- New and improved editor tabs with history
|
||||
- Fixed a crash on app launch faced by some users
|
||||
- Fixed homescreen note widget does not open correct note
|
||||
- Many small bug fixes and improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
@@ -71,5 +71,6 @@
|
||||
"license": "GPL-3.0-or-later",
|
||||
"volta": {
|
||||
"node": "20.12.0"
|
||||
}
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
}
|
||||
|
||||
@@ -117,10 +117,10 @@ export class TabSessionHistory {
|
||||
|
||||
remove(tabId: string, sessionId: string) {
|
||||
const backStack = this.getBackStack(tabId);
|
||||
let index = backStack.findIndex((item) => item === sessionId);
|
||||
let index = backStack.lastIndexOf(sessionId);
|
||||
if (index === -1) {
|
||||
const forwardStack = this.getForwardStack(tabId);
|
||||
index = forwardStack.findIndex((item) => item === sessionId);
|
||||
index = forwardStack.lastIndexOf(sessionId);
|
||||
forwardStack.splice(index, 1);
|
||||
this.setForwardStack(tabId, forwardStack);
|
||||
} else {
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
TEST_NOTEBOOK2,
|
||||
databaseTest
|
||||
} from "./utils/index.ts";
|
||||
import { test, expect } from "vitest";
|
||||
import { test, expect, describe } from "vitest";
|
||||
|
||||
const content = {
|
||||
...TEST_NOTE.content,
|
||||
@@ -112,3 +112,17 @@ test("search should return restored notes", () =>
|
||||
|
||||
expect(filtered).toHaveLength(1);
|
||||
}));
|
||||
|
||||
test("search reminders", () =>
|
||||
databaseTest().then(async (db) => {
|
||||
await db.reminders.add({
|
||||
title: "remind me",
|
||||
description: "please do",
|
||||
date: Date.now()
|
||||
});
|
||||
|
||||
const titleSearch = await db.lookup.reminders("remind me").ids();
|
||||
expect(titleSearch).toHaveLength(1);
|
||||
const descriptionSearch = await db.lookup.reminders("please do").ids();
|
||||
expect(descriptionSearch).toHaveLength(1);
|
||||
}));
|
||||
|
||||
@@ -17,9 +17,16 @@ 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 { match } from "fuzzyjs";
|
||||
import { match, surround } from "fuzzyjs";
|
||||
import Database from "./index.js";
|
||||
import { Item, Note, SortOptions, TrashItem } from "../types.js";
|
||||
import {
|
||||
Item,
|
||||
Note,
|
||||
Notebook,
|
||||
Reminder,
|
||||
SortOptions,
|
||||
TrashItem
|
||||
} from "../types.js";
|
||||
import { DatabaseSchema, RawDatabaseSchema } from "../database/index.js";
|
||||
import { AnyColumnWithTable, Kysely, sql } from "@streetwriters/kysely";
|
||||
import { FilteredSelector } from "../database/sql-collection.js";
|
||||
@@ -28,6 +35,7 @@ import { logger } from "../logger.js";
|
||||
import { rebuildSearchIndex } from "../database/fts.js";
|
||||
import { transformQuery } from "../utils/query-transformer.js";
|
||||
import { getSortSelectors } from "../utils/grouping.js";
|
||||
import { fuzzy } from "../utils/fuzzy.js";
|
||||
|
||||
type SearchResults<T> = {
|
||||
sorted: (limit?: number) => Promise<VirtualizedGrouping<T>>;
|
||||
@@ -39,11 +47,16 @@ type FuzzySearchField<T> = {
|
||||
weight?: number;
|
||||
name: keyof T;
|
||||
column: AnyColumnWithTable<DatabaseSchema, keyof DatabaseSchema>;
|
||||
ignore?: boolean;
|
||||
};
|
||||
export default class Lookup {
|
||||
constructor(private readonly db: Database) {}
|
||||
|
||||
notes(query: string, notes?: FilteredSelector<Note>): SearchResults<Note> {
|
||||
notes(
|
||||
query: string,
|
||||
notes?: FilteredSelector<Note>,
|
||||
opts?: { titleOnly?: boolean }
|
||||
): SearchResults<Note> {
|
||||
return this.toSearchResults(async (limit, sortOptions) => {
|
||||
const db = this.db.sql() as unknown as Kysely<RawDatabaseSchema>;
|
||||
const excludedIds = this.db.trash.cache.notes;
|
||||
@@ -61,21 +74,23 @@ export default class Lookup {
|
||||
)
|
||||
.where("title", "match", query)
|
||||
.select(["id", sql<number>`rank * 10`.as("rank")])
|
||||
.unionAll((eb) =>
|
||||
eb
|
||||
.selectFrom("content_fts")
|
||||
.$if(!!notes, (eb) =>
|
||||
eb.where("noteId", "in", notes!.filter.select("id"))
|
||||
)
|
||||
.$if(excludedIds.length > 0, (eb) =>
|
||||
eb.where("id", "not in", excludedIds)
|
||||
)
|
||||
.where("data", "match", query)
|
||||
.select(["noteId as id", "rank"])
|
||||
.$castTo<{
|
||||
id: string;
|
||||
rank: number;
|
||||
}>()
|
||||
.$if(!opts?.titleOnly, (eb) =>
|
||||
eb.unionAll((eb) =>
|
||||
eb
|
||||
.selectFrom("content_fts")
|
||||
.$if(!!notes, (eb) =>
|
||||
eb.where("noteId", "in", notes!.filter.select("id"))
|
||||
)
|
||||
.$if(excludedIds.length > 0, (eb) =>
|
||||
eb.where("id", "not in", excludedIds)
|
||||
)
|
||||
.where("data", "match", query)
|
||||
.select(["noteId as id", "rank"])
|
||||
.$castTo<{
|
||||
id: string;
|
||||
rank: number;
|
||||
}>()
|
||||
)
|
||||
)
|
||||
.as("results")
|
||||
)
|
||||
@@ -100,26 +115,34 @@ export default class Lookup {
|
||||
}
|
||||
|
||||
notebooks(query: string) {
|
||||
return this.search(this.db.notebooks.all, query, [
|
||||
{ name: "id", column: "notebooks.id", weight: -100 },
|
||||
const fields: FuzzySearchField<Notebook>[] = [
|
||||
{ name: "id", column: "notebooks.id", weight: -100, ignore: true },
|
||||
{ name: "title", column: "notebooks.title", weight: 10 },
|
||||
{ name: "description", column: "notebooks.description" }
|
||||
]);
|
||||
{
|
||||
name: "description",
|
||||
column: "notebooks.description"
|
||||
}
|
||||
];
|
||||
return this.search(this.db.notebooks.all, query, fields);
|
||||
}
|
||||
|
||||
tags(query: string) {
|
||||
return this.search(this.db.tags.all, query, [
|
||||
{ name: "id", column: "tags.id", weight: -100 },
|
||||
{ name: "id", column: "tags.id", weight: -100, ignore: true },
|
||||
{ name: "title", column: "tags.title" }
|
||||
]);
|
||||
}
|
||||
|
||||
reminders(query: string) {
|
||||
return this.search(this.db.reminders.all, query, [
|
||||
{ name: "id", column: "reminders.id", weight: -100 },
|
||||
const fields: FuzzySearchField<Reminder>[] = [
|
||||
{ name: "id", column: "reminders.id", weight: -100, ignore: true },
|
||||
{ name: "title", column: "reminders.title", weight: 10 },
|
||||
{ name: "description", column: "reminders.description" }
|
||||
]);
|
||||
{
|
||||
name: "description",
|
||||
column: "reminders.description"
|
||||
}
|
||||
];
|
||||
return this.search(this.db.reminders.all, query, fields);
|
||||
}
|
||||
|
||||
trash(query: string): SearchResults<TrashItem> {
|
||||
@@ -168,47 +191,37 @@ export default class Lookup {
|
||||
query: string,
|
||||
fields: FuzzySearchField<T>[]
|
||||
) {
|
||||
return this.toSearchResults(
|
||||
(limit, sortOptions) =>
|
||||
this.filter(selector, query, fields, limit, sortOptions),
|
||||
selector
|
||||
);
|
||||
return this.toSearchResults(async (limit, sortOptions) => {
|
||||
const results = await this.filter(selector, query, fields, {
|
||||
limit,
|
||||
sortOptions
|
||||
});
|
||||
return results.map((item) => item.id);
|
||||
}, selector);
|
||||
}
|
||||
|
||||
private async filter<T extends Item>(
|
||||
selector: FilteredSelector<T>,
|
||||
query: string,
|
||||
fields: FuzzySearchField<T>[],
|
||||
limit?: number,
|
||||
sortOptions?: SortOptions
|
||||
fields: readonly FuzzySearchField<T>[],
|
||||
options: {
|
||||
limit?: number;
|
||||
sortOptions?: SortOptions;
|
||||
prefix?: string;
|
||||
suffix?: string;
|
||||
} = {}
|
||||
) {
|
||||
const results: Map<string, number> = new Map();
|
||||
const columns = fields.map((f) => f.column);
|
||||
for await (const item of selector.fields(columns).iterate()) {
|
||||
if (limit && results.size >= limit) break;
|
||||
const items = await selector.fields(columns).items();
|
||||
|
||||
for (const field of fields) {
|
||||
const result = match(query, `${item[field.name]}`);
|
||||
if (result.match) {
|
||||
const oldScore = results.get(item.id) || 0;
|
||||
results.set(item.id, oldScore + result.score * (field.weight || 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
selector.fields([]);
|
||||
|
||||
const sorted = Array.from(results.entries());
|
||||
|
||||
if (!sortOptions)
|
||||
// || sortOptions.sortBy === "relevance")
|
||||
sorted.sort(
|
||||
// sortOptions?.sortDirection === "desc"
|
||||
// ? (a, b) => a[1] - b[1]
|
||||
// :
|
||||
(a, b) => b[1] - a[1]
|
||||
);
|
||||
|
||||
return sorted.map((a) => a[0]);
|
||||
return fuzzy(
|
||||
query,
|
||||
items,
|
||||
Object.fromEntries(
|
||||
fields.filter((f) => !f.ignore).map((f) => [f.name, f.weight || 1])
|
||||
) as Record<keyof T, number>,
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
private toSearchResults<T extends Item>(
|
||||
|
||||
92
packages/core/src/utils/__tests__/fuzzy.test.ts
Normal file
92
packages/core/src/utils/__tests__/fuzzy.test.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
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 { fuzzy } from "../fuzzy.js";
|
||||
import { test, expect, describe } from "vitest";
|
||||
|
||||
describe("lookup.fuzzy", () => {
|
||||
test("should sort items by score", () => {
|
||||
const items = [
|
||||
{
|
||||
id: "1",
|
||||
title: "system"
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
title: "hello"
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
title: "items"
|
||||
}
|
||||
];
|
||||
const query = "ems";
|
||||
expect(fuzzy(query, items, { title: 1 })).toStrictEqual([items[2]]);
|
||||
});
|
||||
describe("opts.prefix", () => {
|
||||
test("should prefix matched field with provided value when given", () => {
|
||||
const items = [
|
||||
{
|
||||
id: "1",
|
||||
title: "hello"
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
title: "world"
|
||||
}
|
||||
];
|
||||
const query = "d";
|
||||
expect(
|
||||
fuzzy(
|
||||
query,
|
||||
items,
|
||||
{ title: 1 },
|
||||
{
|
||||
prefix: "prefix-"
|
||||
}
|
||||
)
|
||||
).toStrictEqual([{ id: "2", title: "worlprefix-d" }]);
|
||||
});
|
||||
});
|
||||
describe("opt.suffix", () => {
|
||||
test("should suffix matched field with provided value when given", () => {
|
||||
const items = [
|
||||
{
|
||||
id: "1",
|
||||
title: "hello"
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
title: "world"
|
||||
}
|
||||
];
|
||||
const query = "llo";
|
||||
expect(
|
||||
fuzzy(
|
||||
query,
|
||||
items,
|
||||
{ title: 1 },
|
||||
{
|
||||
suffix: "-suffix"
|
||||
}
|
||||
)
|
||||
).toStrictEqual([{ id: "1", title: "hello-suffix" }]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -21,7 +21,7 @@ import { expect, test } from "vitest";
|
||||
import { transformQuery } from "../query-transformer.js";
|
||||
|
||||
function lt2(str: string) {
|
||||
return `(">${str}" OR ${str} OR "${str}<")`;
|
||||
return `(">${str}" OR "${str}" OR "${str}<")`;
|
||||
}
|
||||
|
||||
const TRANSFORM_QUERY_TESTS = [
|
||||
@@ -65,14 +65,14 @@ const TRANSFORM_QUERY_TESTS = [
|
||||
["foo + OR", `foo AND ${lt2("+")}`],
|
||||
['"special -phrase*"', '"special -phrase*"'],
|
||||
["foo* + bar*", `"foo*" AND ${lt2("+")} AND "bar*"`],
|
||||
["(foo + bar) -baz", `(foo AND ${lt2("+")} AND bar) AND "-baz"`],
|
||||
["(foo + bar) -baz", `"(foo" AND ${lt2("+")} AND "bar)" AND "-baz"`],
|
||||
['"phrase with "quotes""', '"phrase with ""quotes"""'],
|
||||
[
|
||||
'foo + "bar -baz" OR "qux*"',
|
||||
`foo AND ${lt2("+")} AND "bar -baz" OR "qux*"`
|
||||
],
|
||||
["foo + bar + ", `foo AND ${lt2("+")} AND bar AND ${lt2("+")}`],
|
||||
["+foo bar", `+foo AND bar`],
|
||||
["+foo bar", `"+foo" AND bar`],
|
||||
["foo*bar*", `"foo*bar*"`],
|
||||
['"escaped "quotes""', '"escaped ""quotes"""'],
|
||||
["-hello-world", `"-hello-world"`],
|
||||
|
||||
75
packages/core/src/utils/fuzzy.ts
Normal file
75
packages/core/src/utils/fuzzy.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
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 { match, surround } from "fuzzyjs";
|
||||
import { clone } from "./clone";
|
||||
|
||||
export function fuzzy<T extends { id: string }>(
|
||||
query: string,
|
||||
items: T[],
|
||||
fields: Partial<Record<keyof T, number>>,
|
||||
options: {
|
||||
limit?: number;
|
||||
prefix?: string;
|
||||
suffix?: string;
|
||||
} = {}
|
||||
): T[] {
|
||||
const results: Map<
|
||||
string,
|
||||
{
|
||||
item: T;
|
||||
score: number;
|
||||
}
|
||||
> = new Map();
|
||||
|
||||
for (const item of items) {
|
||||
if (options.limit && results.size >= options.limit) break;
|
||||
|
||||
for (const field in fields) {
|
||||
const result = match(query, `${item[field]}`);
|
||||
if (!result.match) continue;
|
||||
|
||||
const oldMatch = results.get(item.id);
|
||||
const clonedItem = oldMatch?.item || clone(item);
|
||||
|
||||
if (options.suffix || options.prefix) {
|
||||
clonedItem[field] = surround(`${clonedItem[field]}`, {
|
||||
suffix: options.suffix,
|
||||
prefix: options.prefix,
|
||||
result
|
||||
}) as T[Extract<keyof T, string>];
|
||||
}
|
||||
if (oldMatch) {
|
||||
oldMatch.score += result.score * (fields[field] || 1);
|
||||
} else {
|
||||
results.set(item.id, {
|
||||
item: clonedItem,
|
||||
score: result.score * (fields[field] || 1)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (results.size === 0) return [];
|
||||
|
||||
const sorted = Array.from(results.entries());
|
||||
sorted.sort((a, b) => b[1].score - a[1].score);
|
||||
|
||||
return sorted.map((item) => item[1].item);
|
||||
}
|
||||
@@ -41,3 +41,4 @@ export * from "./set.js";
|
||||
export * from "./title-format.js";
|
||||
export * from "./virtualized-grouping.js";
|
||||
export * from "./crypto.js";
|
||||
export * from "./fuzzy.js";
|
||||
|
||||
@@ -33,23 +33,20 @@ type OperatorNode = {
|
||||
type: "AND" | "OR" | "NOT";
|
||||
};
|
||||
|
||||
const INVALID_CHARS = /[:<>./\\()$&=#!\-+~§@^?,;'"[\]{}|]/;
|
||||
function escapeSQLString(str: string): string {
|
||||
if (str.startsWith('"') && str.endsWith('"')) {
|
||||
const innerStr = str.slice(1, -1).replace(/"/g, '""');
|
||||
return `"${innerStr}"`;
|
||||
}
|
||||
|
||||
const maybeColspec =
|
||||
str.includes(":") ||
|
||||
str.includes(">") ||
|
||||
str.includes("<") ||
|
||||
str.includes("-");
|
||||
const isInvalidChar = INVALID_CHARS.test(str);
|
||||
const isWildcard =
|
||||
str.startsWith("*") ||
|
||||
str.endsWith("*") ||
|
||||
str.startsWith("%") ||
|
||||
str.endsWith("%");
|
||||
if (maybeColspec || isWildcard) {
|
||||
if (isInvalidChar || isWildcard) {
|
||||
return `"${str}"`;
|
||||
}
|
||||
|
||||
@@ -159,6 +156,21 @@ function generateSQL(ast: QueryNode): string {
|
||||
result.push(`(">${value}"`, "OR", value, "OR", `"${value}<")`);
|
||||
result.push("AND");
|
||||
continue;
|
||||
} else if (
|
||||
value.length === 3 &&
|
||||
value[0] === '"' &&
|
||||
value[2] === '"' &&
|
||||
!["<", ">"].includes(value[1])
|
||||
) {
|
||||
result.push(
|
||||
`(">${value[1]}"`,
|
||||
"OR",
|
||||
value,
|
||||
"OR",
|
||||
`"${value[1]}<")`
|
||||
);
|
||||
result.push("AND");
|
||||
continue;
|
||||
}
|
||||
|
||||
result.push(value);
|
||||
|
||||
@@ -38,13 +38,6 @@ if (currentTheme) {
|
||||
useThemeEngineStore.getState().setTheme(currentTheme);
|
||||
}
|
||||
|
||||
window.addEventListener("error", (event) => {
|
||||
post("editorError", {
|
||||
message: event.error.message,
|
||||
stack: event.error?.stack || ""
|
||||
});
|
||||
});
|
||||
|
||||
class ExceptionHandler extends React.Component<{
|
||||
children: React.ReactNode;
|
||||
component: string;
|
||||
|
||||
@@ -556,7 +556,7 @@ const Tiptap = ({
|
||||
</>
|
||||
)}
|
||||
|
||||
{controller.loading || tab.session?.noteLocked ? (
|
||||
{controller.loading || tab.session?.locked ? (
|
||||
<div
|
||||
style={{
|
||||
width: "100%",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user