Compare commits

...

43 Commits

Author SHA1 Message Date
Ammar Ahmed
41605c308a mobile: always load note content in editor 2025-01-14 12:04:58 +05:00
Ammar Ahmed
77a2f86119 mobile: fix trash interval picker text showing NaN 2025-01-14 12:02:16 +05:00
01zulfi
71db85458d web: add go to next/previous tab shortcut (#7109)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-01-02 15:13:49 +05:00
01zulfi
0be59c71de web: save image compression upload setting (#7089)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-01-02 15:10:58 +05:00
Abdullah Atta
2357e3de0b web: title bar underlay not showing on mac 2025-01-02 14:15:09 +05:00
Abdullah Atta
53b89a59ed web: fix integrated titlebar on windows 2025-01-02 14:15:09 +05:00
Abdullah Atta
202c9135c2 web: show exclusive titlebar in tablet/mobile mode 2025-01-02 14:15:09 +05:00
Abdullah Atta
69e6263d34 web: better integrated titlebar that looks same across platforms 2025-01-02 14:15:09 +05:00
Abdullah Atta
ebe830bbcc theme: add new borderless input variant 2025-01-02 14:15:09 +05:00
Abdullah Atta
44bb9752d2 web: improve slider ux on mobile & small screens 2025-01-02 14:15:09 +05:00
Abdullah Atta
2f6af50fe2 web: split pane now supports adding/removing panes in place 2025-01-02 14:15:09 +05:00
Ammar Ahmed
1d80ca7d3c mobile: release v3.0.26 2024-12-31 14:03:49 +05:00
Ammar Ahmed
d33baebac8 editor: fix frame around image on selection 2024-12-31 13:21:47 +05:00
Ammar Ahmed
25aaef361a mobile: throw error if db key not found in keychain 2024-12-31 12:50:38 +05:00
Abdullah Atta
f3af39a093 web: fix action bar ui on web 2024-12-30 16:12:19 +05:00
Abdullah Atta
149c2013a2 editor: fix multiple editor views being created 2024-12-30 16:05:28 +05:00
Ammar Ahmed
8fb0b6f94a mobile: fix build errors in editor-mobile 2024-12-30 15:44:57 +05:00
Ammar Ahmed
f09e5cadcf mobile: fix image and file uploads 2024-12-30 15:44:57 +05:00
Ammar Ahmed
724c69655b mobile: fix scroll jumping in realtime sync 2024-12-30 15:44:41 +05:00
Abdullah Atta
800f3ca5d3 editor: update tiptap to v2.10.4 (#6872)
* editor: update tiptap to v2.9.1

* editor: update tiptap & pkgs to 2.10.4

* editor: update test snapshots
2024-12-30 14:51:50 +05:00
Abdullah Atta
193007370a web: add experimental support for responsive pane resizing 2024-12-30 11:56:42 +05:00
Abdullah Atta
c8aff3764b web: lots of improvements 2024-12-30 11:56:42 +05:00
Abdullah Atta
d72b502977 web: fix snapping logic 2024-12-30 11:56:42 +05:00
Abdullah Atta
263c970320 web: add support for macOS like pane resizing logic 2024-12-30 11:56:42 +05:00
Abdullah Atta
aeebc9878d web: only reset size of doubled clicked pane 2024-12-30 11:56:42 +05:00
Abdullah Atta
4f37c92ecd web: add snapping support 2024-12-30 11:56:42 +05:00
Abdullah Atta
3180a88ef0 web: replace react-resizable-panels with custom implementation 2024-12-30 11:56:42 +05:00
01zulfi
6672db6ac2 editor: add markdown shortcut for checklist (#7087)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-12-30 11:17:18 +05:00
01zulfi
b6da62469e core: fix vault unlock event not sent when a note is unlocked (#7172)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-12-30 10:43:33 +05:00
Ammar Ahmed
e67e6374c3 mobile: set default widget size 2024-12-30 10:32:15 +05:00
Ammar Ahmed
ff1fc5b391 mobile: fix formatting 2024-12-30 10:32:15 +05:00
Ammar Ahmed
2f8bf191aa mobile: resizable and themeable android widget 2024-12-30 10:32:15 +05:00
Abdullah Atta
c4af2b2159 web: fix build on macos (by updating packages) 2024-12-30 10:13:40 +05:00
Abdullah Atta
690b157387 desktop: b/w when window is unfocused 2024-12-30 10:13:12 +05:00
Abdullah Atta
5a276a43a4 web: improve tab ui 2024-12-30 10:12:02 +05:00
Abdullah Atta
a364ae2e9d core: fix notes search in tags, colors & notebooks not working 2024-12-27 16:20:53 +05:00
Abdullah Atta
1e2055c81b web: fix Cannot start service: Host version "0.20.2" does not match binary version "0.23.1" on macos 2024-12-27 15:30:37 +05:00
01zulfi
31d110252c web: fix status bar size changing (#7143)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-12-27 11:50:40 +05:00
Ammar Ahmed
702976c6cf mobile: do not show progress when doing full auto backup 2024-12-26 12:44:54 +05:00
Ammar Ahmed
3ec8500515 mobile: add fdroid release notes 2024-12-26 12:32:27 +05:00
Ammar Ahmed
2c03420f33 mobile: release v3.0.25 2024-12-26 12:25:12 +05:00
Ammar Ahmed
868ae597ae mobile: fix ts errors 2024-12-26 12:25:12 +05:00
01zulfi
59d3e06935 intl: fix casing in legal & notebooks strings (#7142)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2024-12-26 11:29:57 +05:00
102 changed files with 18343 additions and 7405 deletions

View File

@@ -36,7 +36,7 @@ await fs.rm("./build/", { force: true, recursive: true });
if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
await exec(
"yarn nx build:desktop @notesnook/web",
"npx nx build:desktop @notesnook/web",
path.join(__dirname, "..", "..", "..")
);
}
@@ -58,11 +58,13 @@ if (args.variant === "mas") {
await exec(`yarn run build`);
if (args.run) {
await exec(`yarn electron-builder --dir --x64`);
await exec(`yarn electron-builder --dir --${process.arch}`);
if (process.platform === "win32") {
await exec(`.\\output\\win-unpacked\\Notesnook.exe`);
} else if (process.platform === "darwin") {
await exec(`./output/mac/Notesnook.app/Contents/MacOS/Notesnook`);
if (process.arch === "arm64")
await exec(`./output/mac-arm64/Notesnook.app/Contents/MacOS/Notesnook`);
else await exec(`./output/mac/Notesnook.app/Contents/MacOS/Notesnook`);
} else {
await exec(`./output/linux-unpacked/Notesnook`);
}

View File

@@ -70,8 +70,8 @@ async function onChange(first) {
if (first) {
await spawnAndWaitUntil(
["yarn", "nx", "start:desktop", "@notesnook/web"],
path.join(__dirname, "..", "..", ".."),
["npm", "run", "start:desktop"],
path.join(__dirname, "..", "..", "web"),
(data) => data.includes("Network: use --host to expose")
);
}

View File

@@ -159,79 +159,77 @@ export function clearDatabaseKey() {
export async function getDatabaseKey(appLockPassword?: string) {
if (DB_KEY) return DB_KEY;
try {
if (appLockPassword) {
const databaseKeyCipher: Cipher =
CipherStorage.getMap("databaseKeyCipher");
const databaseKey = await decrypt(
{
password: appLockPassword
},
databaseKeyCipher
);
DatabaseLogger.info("Getting database key from cipher");
DB_KEY = databaseKey;
}
if (appLockPassword) {
const databaseKeyCipher: Cipher = CipherStorage.getMap("databaseKeyCipher");
const databaseKey = await decrypt(
{
password: appLockPassword
},
databaseKeyCipher
);
DatabaseLogger.info("Getting database key from cipher");
DB_KEY = databaseKey;
}
if (!DB_KEY) {
const hasKey = await Keychain.hasInternetCredentials(
if (!DB_KEY) {
const hasKey = await Keychain.hasInternetCredentials(KEYCHAIN_SERVER_DBKEY);
if (hasKey) {
const credentials = await Keychain.getInternetCredentials(
KEYCHAIN_SERVER_DBKEY
);
if (hasKey) {
const credentials = await Keychain.getInternetCredentials(
KEYCHAIN_SERVER_DBKEY
);
DatabaseLogger.info("Getting database key from Keychain");
DB_KEY = (credentials as Keychain.UserCredentials).password;
}
DatabaseLogger.info("Getting database key from Keychain");
DB_KEY = (credentials as Keychain.UserCredentials).password;
}
if (!DB_KEY) {
DatabaseLogger.info("Generating new database key");
const password = generatePassword();
const derivedDatabaseKey = await Sodium.deriveKey(
password,
NOTESNOOK_DB_KEY_SALT
);
DB_KEY = derivedDatabaseKey.key as string;
await Keychain.setInternetCredentials(
KEYCHAIN_SERVER_DBKEY,
"notesnook",
DB_KEY,
KEYSTORE_CONFIG
);
}
if (await Keychain.hasInternetCredentials("notesnook")) {
const userKeyCredentials = await Keychain.getInternetCredentials(
"notesnook"
);
if (userKeyCredentials) {
const userKeyCipher: Cipher = (await encrypt(
{
key: DB_KEY,
salt: NOTESNOOK_DB_KEY_SALT
},
userKeyCredentials.password
)) as Cipher;
// Store encrypted user key in MMKV
MMKV.setMap(USER_KEY_CIPHER, userKeyCipher);
await Keychain.resetInternetCredentials("notesnook");
}
DatabaseLogger.info("Migrated user credentials to cipher storage");
}
return DB_KEY;
} catch (e) {
ToastManager.error(e as Error, "Error getting database key");
console.log(e, "error");
DatabaseLogger.error(e);
return null;
}
if (!DB_KEY) {
DatabaseLogger.info("Generating new database key");
const password = generatePassword();
const derivedDatabaseKey = await Sodium.deriveKey(
password,
NOTESNOOK_DB_KEY_SALT
);
DB_KEY = derivedDatabaseKey.key as string;
await Keychain.setInternetCredentials(
KEYCHAIN_SERVER_DBKEY,
"notesnook",
DB_KEY,
KEYSTORE_CONFIG
);
}
if (await Keychain.hasInternetCredentials("notesnook")) {
const userKeyCredentials = await Keychain.getInternetCredentials(
"notesnook"
);
if (userKeyCredentials) {
const userKeyCipher: Cipher = (await encrypt(
{
key: DB_KEY,
salt: NOTESNOOK_DB_KEY_SALT
},
userKeyCredentials.password
)) as Cipher;
// Store encrypted user key in MMKV
MMKV.setMap(USER_KEY_CIPHER, userKeyCipher);
await Keychain.resetInternetCredentials("notesnook");
}
DatabaseLogger.info("Migrated user credentials to cipher storage");
}
if (!DB_KEY) {
throw new Error(
`Failed to get database key, ${await Keychain.hasInternetCredentials(
KEYCHAIN_SERVER_DBKEY
)}`
);
}
return DB_KEY;
}
export async function deriveCryptoKeyFallback(data: SerializedKey) {

View File

@@ -47,7 +47,8 @@ export default {
deleteCacheFileByPath,
getCacheSize,
requestPermission,
checkAndCreateDir
checkAndCreateDir,
getUploadedFileSize
};
export const FileStorage: IFileStorage = {

View File

@@ -38,7 +38,7 @@ import { useAppState } from "../../hooks/use-app-state";
export interface BaseDialogProps extends PropsWithChildren {
animation?: "fade" | "none" | "slide" | undefined;
visible: boolean;
visible?: boolean;
onRequestClose?: () => void;
onShow?: () => void;
premium?: boolean;

View File

@@ -179,6 +179,7 @@ const ColorPicker = ({
title: title.current,
colorCode: selectedColor
});
if (!id) return;
useRelationStore.getState().update();
useMenuStore.getState().setColorNotes();
setVisible(false);

View File

@@ -297,7 +297,6 @@ function getDate(item: Item, groupType?: GroupingKey): number {
groupType
? db.settings.getGroupOptions(groupType)
: {
groupBy: "default",
sortBy: "dateEdited",
sortDirection: "desc"
},

View File

@@ -129,7 +129,10 @@ export const PricingPlans = ({
if (code.startsWith("com.streetwriters.notesnook")) {
skuId = code;
} else {
skuId = await db.offers?.getCode(code.split(":")[0], Platform.OS);
skuId = await db.offers?.getCode(
code.split(":")[0],
Platform.OS as "ios" | "android"
);
}
const products = await PremiumService.getProducts();

View File

@@ -150,7 +150,7 @@ const PublishNoteSheet = ({
</View>
) : (
<>
{isPublished && (
{isPublished && publishUrl ? (
<View
style={{
flexDirection: "row",
@@ -204,7 +204,7 @@ const PublishNoteSheet = ({
name="content-copy"
/>
</View>
)}
) : null}
<TouchableOpacity
onPress={() => {

View File

@@ -556,9 +556,9 @@ export default function ReminderSheet({
{Object.keys(ReminderNotificationModes).map((mode) => (
<Button
key={mode}
title={strings.reminderNotificationModes[
title={strings.reminderNotificationModes(
mode as keyof typeof ReminderNotificationModes
]()}
)}
style={{
marginRight: 12,
borderRadius: 100

View File

@@ -1,158 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { ActivityIndicator, DimensionValue, ViewStyle } from "react-native";
import Animated, {
FadeIn,
FadeOut,
Layout,
LightSpeedInLeft
} from "react-native-reanimated";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { SIZE } from "../../../utils/size";
import NativeTooltip from "../../../utils/tooltip";
import { ButtonProps } from "../button";
import { Pressable, useButton } from "../pressable";
import Heading from "../typography/heading";
import Paragraph from "../typography/paragraph";
const AnimatedIcon = Animated.createAnimatedComponent(Icon);
export const AnimatedButton = ({
height = 45,
width = null,
onPress,
loading = false,
title = null,
icon,
fontSize = SIZE.sm,
type = "transparent",
iconSize = SIZE.md,
style = {},
accentColor = "accent",
accentText = "light",
onLongPress,
tooltipText,
textStyle,
iconPosition = "left",
buttonType,
bold,
iconColor,
fwdRef,
...restProps
}: ButtonProps) => {
const { text } = useButton({
type,
accent: accentColor,
text: accentText
});
const textColor = buttonType?.text ? buttonType.text : text;
const Component = bold ? Heading : Paragraph;
return (
<Animated.View
entering={FadeIn}
exiting={FadeOut}
layout={Layout.springify()}
>
<Pressable
{...restProps}
fwdRef={fwdRef}
onPress={onPress}
onLongPress={(event) => {
if (onLongPress) {
onLongPress(event);
return;
}
if (tooltipText) {
NativeTooltip.show(event, tooltipText, NativeTooltip.POSITIONS.TOP);
}
}}
disabled={loading}
type={type}
accentColor={accentColor}
accentText={accentText}
customColor={buttonType?.color}
customSelectedColor={buttonType?.selected}
customOpacity={buttonType?.opacity}
customAlpha={buttonType?.alpha}
style={{
height: height,
width: (width as DimensionValue) || undefined,
paddingHorizontal: 12,
borderRadius: 5,
alignSelf: "center",
justifyContent: "center",
alignItems: "center",
flexDirection: "row",
...(style as ViewStyle)
}}
>
{loading ? (
<ActivityIndicator color={textColor} size={fontSize + 4} />
) : null}
{icon && !loading && iconPosition === "left" ? (
<AnimatedIcon
exiting={FadeOut.duration(100)}
entering={LightSpeedInLeft}
layout={Layout.springify()}
name={icon}
style={{
marginRight: 0
}}
color={iconColor || buttonType?.text || textColor}
size={iconSize}
/>
) : null}
{!title ? null : (
<Component
layout={Layout.springify()}
animated={true}
color={textColor as string}
size={fontSize}
numberOfLines={1}
style={[
{
marginLeft:
icon || (loading && iconPosition === "left") ? 5 : 0,
marginRight:
icon || (loading && iconPosition === "right") ? 5 : 0
},
textStyle
]}
>
{title}
</Component>
)}
{icon && !loading && iconPosition === "right" ? (
<Icon
name={icon}
style={{
marginLeft: 0
}}
color={iconColor || buttonType?.text || textColor}
size={iconSize}
/>
) : null}
</Pressable>
</Animated.View>
);
};

View File

@@ -18,15 +18,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { View } from "react-native";
import { DimensionValue, View } from "react-native";
import { SvgXml } from "./lazy";
export const SvgView = ({
width = 250,
height = 250,
src
}: {
width?: number | string;
height?: number | number;
width?: DimensionValue;
height?: DimensionValue;
src?: string;
}) => {
if (!src) return null;

View File

@@ -497,7 +497,7 @@ export const useAppEvents = () => {
}
if (fullBackup) {
await BackupService.run(true, undefined, "full");
await BackupService.run(false, undefined, "full");
}
}

View File

@@ -32,18 +32,11 @@ export function useAppState() {
const subscription = AppState.addEventListener("change", onChange);
return () => {
// @ts-expect-error - React Native >= 0.65
if (typeof subscription?.remove === "function") {
// @ts-expect-error - need update @types/react-native@0.65.x
subscription.remove();
} else {
// React Native < 0.65
AppState.removeEventListener("change", onChange);
}
subscription.remove();
};
}, []);
return appState;
}
export { AppStateStatus };
export type { AppStateStatus };

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useEffect, useState } from "react";
import { Keyboard, KeyboardEventListener, ScreenRect } from "react-native";
import { Keyboard, KeyboardEventListener, KeyboardMetrics } from "react-native";
const emptyCoordinates = Object.freeze({
screenX: 0,
@@ -34,8 +34,8 @@ const initialValue = {
export default function useKeyboard() {
const [shown, setShown] = useState(false);
const [coordinates, setCoordinates] = useState<{
start: undefined | ScreenRect;
end: ScreenRect;
start: undefined | KeyboardMetrics;
end: KeyboardMetrics;
}>(initialValue);
const [keyboardHeight, setKeyboardHeight] = useState<number>(0);

View File

@@ -388,7 +388,6 @@ export async function attachFile(
encryptionInfo.mimeType = type;
encryptionInfo.filename = filename;
encryptionInfo.alg = "xcha-stream";
encryptionInfo.size = encryptionInfo.length;
encryptionInfo.key = key;
if (options?.reupload && exists) {
const attachment = await db.attachments.attachment(hash);
@@ -397,6 +396,7 @@ export async function attachFile(
} else {
encryptionInfo = { hash: hash };
}
console.log("FILE ENCRYPTED....", encryptionInfo);
await db.attachments.add(encryptionInfo);
return true;
} catch (e) {

View File

@@ -59,15 +59,38 @@ export const Search = ({ route, navigation }: NavigationProps<"Search">) => {
}
try {
setLoading(true);
const type =
route.params.type === "trash"
? "trash"
: ((route.params?.type + "s") as keyof typeof db.lookup);
console.log(`Searching in ${type} for ${query}`);
const results = await db.lookup[type](
query,
route.params.items as FilteredSelector<Note>
).sorted();
let results: VirtualizedGrouping<Item> | undefined;
switch (route.params.type) {
case "note":
results = await db.lookup
.notes(query, route.params.items as FilteredSelector<Note>)
.sorted();
break;
case "notebook":
results = await db.lookup.notebooks(query).sorted();
break;
case "tag":
results = await db.lookup.tags(query).sorted();
break;
case "reminder":
results = await db.lookup.reminders(query).sorted();
break;
case "trash":
results = await db.lookup.trash(query).sorted();
break;
case "attachment":
results = await db.lookup.attachments(query).sorted();
break;
default:
results = undefined;
}
if (!results) {
setSearchStatus(strings.noResultsFound(query));
setLoading(false);
return;
}
console.log(
`Found ${results.placeholders?.length} results for ${query}`

View File

@@ -75,7 +75,7 @@ export const TrashIntervalPicker = createSettingsPicker({
? strings.never()
: item === 1
? strings.reminderRecurringMode.day()
: item + " " + strings.days();
: strings.days(item);
},
getItemKey: (item) => item.toString(),
options: [-1, 1, 7, 30, 365],

View File

@@ -48,7 +48,6 @@ export async function attachFile(uri, hash, type, filename, options) {
encryptionInfo.mimeType = type;
encryptionInfo.filename = filename;
encryptionInfo.alg = "xcha-stream";
encryptionInfo.size = encryptionInfo.length;
encryptionInfo.key = key;
console.log(encryptionInfo);
if (options?.reupload && exists) await db.attachments.reset(hash);

View File

@@ -116,7 +116,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled true
versionCode 3034
versionCode 3036
versionName getNpmVersion()
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'

View File

@@ -70,7 +70,7 @@
<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/note_widget_info" />
android:resource="@xml/new_note_widget_info" />
</receiver>
<activity

View File

@@ -5,6 +5,7 @@ import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.RemoteViews;
/**
@@ -24,21 +25,29 @@ public class NoteWidget extends AppWidgetProvider {
appWidgetManager.updateAppWidget(appWidgetId, views);
}
@Override
public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle newOptions) {
super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions);
updateAppWidget(context, appWidgetManager, appWidgetId, newOptions);
}
private void updateAppWidget(Context context, AppWidgetManager appWidgetManager, int appWidgetId, Bundle options) {
int minWidth = options != null ? options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH) : 0;
// int minHeight = options != null ? options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT) : 0;
int layoutId = (minWidth < 100) ? R.layout.note_widget_icon : R.layout.note_widget;
RemoteViews views = new RemoteViews(context.getPackageName(), layoutId);
appWidgetManager.updateAppWidget(appWidgetId, views);
}
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
// There may be multiple widgets active, so update all of them
for (int appWidgetId : appWidgetIds) {
updateAppWidget(context, appWidgetManager, appWidgetId);
updateAppWidget(context, appWidgetManager, appWidgetId,null);
}
}
@Override
public void onEnabled(Context context) {
// Enter relevant functionality for when the first widget is created
}
@Override
public void onDisabled(Context context) {
// Enter relevant functionality for when the last widget is disabled
}
}

View File

@@ -1,4 +1,4 @@
<vector android:height="24dp" android:tint="#000000"
<vector android:height="24dp" android:tint="@color/text"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M14,2L6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6zM16,16h-3v3h-2v-3L8,16v-2h3v-3h2v3h3v2zM13,9L13,3.5L18.5,9L13,9z"/>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#CCFFFFFF"/>
<solid android:color="@color/background"/>
<stroke android:width="0dp" android:color="#B1BCBE" />
<corners android:radius="10dp"/>
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />

View File

@@ -6,7 +6,6 @@
android:padding="@dimen/widget_margin"
android:theme="@style/ThemeOverlay.Notesnook.AppWidgetContainer">
<LinearLayout
android:layout_width="match_parent"
android:id="@+id/widget_button"
@@ -18,21 +17,21 @@
android:elevation="5dp"
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerVertical="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="8dp"
android:textSize="16sp"
android:text="Take a quick note." />
</LinearLayout>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:src="@drawable/add_note"
android:contentDescription="New note icon" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="8dp"
android:textColor="@color/text"
android:textSize="16sp"
android:text="@string/take_a_quick_note" />
</LinearLayout>
</RelativeLayout>

View File

@@ -0,0 +1,13 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="@drawable/layout_bg"
android:padding="@dimen/widget_margin">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="16dp"
android:src="@drawable/add_note" />
</FrameLayout>

View File

@@ -5,4 +5,6 @@
<color name="light_blue_600">#FF039BE5</color>
<color name="light_blue_900">#FF01579B</color>
<color name="bootsplash_background">#1f1f1f</color>
<color name="background">#1D1D1D</color>
<color name="text">#B4B4B4</color>
</resources>

View File

@@ -5,4 +5,6 @@
<color name="light_blue_600">#FF039BE5</color>
<color name="light_blue_900">#FF01579B</color>
<color name="bootsplash_background">#FFFFFF</color>
<color name="background">#FFFFFF</color>
<color name="text">#000000</color>
</resources>

View File

@@ -3,4 +3,5 @@
<string name="title_activity_share">NotesnookShare</string>
<string name="appwidget_text">EXAMPLE</string>
<string name="add_widget">Add widget</string>
<string name="take_a_quick_note">Take a quick note.</string>
</resources>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialKeyguardLayout="@layout/note_widget"
android:initialLayout="@layout/note_widget"
android:minWidth="50dp"
android:minHeight="50dp"
android:targetCellWidth="5"
android:targetCellHeight="1"
android:previewImage="@drawable/widget_preview"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="86400000"
android:widgetCategory="home_screen"/>

View File

@@ -2,9 +2,9 @@
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
android:initialKeyguardLayout="@layout/note_widget"
android:initialLayout="@layout/note_widget"
android:minWidth="300dp"
android:minWidth="50dp"
android:minHeight="50dp"
android:previewImage="@drawable/widget_preview"
android:resizeMode="horizontal"
android:resizeMode="horizontal|vertical"
android:updatePeriodMillis="86400000"
android:widgetCategory="home_screen"></appwidget-provider>
android:widgetCategory="home_screen"/>

View File

@@ -1,5 +1,3 @@
- You can now share multiple files to Notesnook
- Fix file and image sharing not working
- Many other bug fixes and small improvements
- Bug fixes and small improvements
Thank you for using Notesnook!

View File

@@ -1063,7 +1063,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2121;
CURRENT_PROJECT_VERSION = 2123;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1137,7 +1137,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.0.24;
MARKETING_VERSION = 3.0.26;
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 = 2121;
CURRENT_PROJECT_VERSION = 2123;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1242,7 +1242,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.0.24;
MARKETING_VERSION = 3.0.26;
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 = 2121;
CURRENT_PROJECT_VERSION = 2123;
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.24;
MARKETING_VERSION = 3.0.26;
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 = 2121;
CURRENT_PROJECT_VERSION = 2123;
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.24;
MARKETING_VERSION = 3.0.26;
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 = 2121;
CURRENT_PROJECT_VERSION = 2123;
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.24;
MARKETING_VERSION = 3.0.26;
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 = 2121;
CURRENT_PROJECT_VERSION = 2123;
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.24;
MARKETING_VERSION = 3.0.26;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -1019,7 +1019,7 @@ PODS:
- react-native-screenguard (1.0.0):
- React-Core
- SDWebImage (~> 5.11.1)
- react-native-share-extension (2.7.0):
- react-native-share-extension (2.8.0):
- React
- react-native-sodium (1.6.1):
- React
@@ -1858,7 +1858,7 @@ SPEC CHECKSUMS:
react-native-quick-sqlite: 18e1367c34faac90e37f6eb3e78c196e9b674b5d
react-native-safe-area-context: b7daa1a8df36095a032dff095a1ea8963cb48371
react-native-screenguard: 8b36a3df84c76cd2b82c477f71c26fa1c8cc14a0
react-native-share-extension: 17e42444d0d9fbfeb0a7392899def70f9534c9c4
react-native-share-extension: 01b25fc15faf2510cc2529e98ca444f64e46238d
react-native-sodium: 4cb76086943a7f60c42b40ebca866695b360a196
react-native-theme-switch-animation: d3eb50365a3829ce5572628888fa514752703f61
react-native-webview: 553abd09f58e340fdc7746c9e2ae096839e99911
@@ -1915,8 +1915,8 @@ SPEC CHECKSUMS:
SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6
TOCropViewController: 80b8985ad794298fb69d3341de183f33d1853654
toolbar-android: 2a73856e98b750d7e71ce4644d3f41cc98211719
Yoga: 2246eea72aaf1b816a68a35e6e4b74563653ae09
Yoga: 950bbfd7e6f04790fdb51149ed51df41f329fcc8
PODFILE CHECKSUM: aa07aca16c1d92394d8ce12f2c5fcaa8a3ae4997
COCOAPODS: 1.15.2
COCOAPODS: 1.16.2

View File

@@ -88,7 +88,6 @@
"@tsconfig/react-native": "^3.0.2",
"@types/html-to-text": "^8.0.1",
"@types/metro-config": "^0.76.3",
"@types/react": "^18.2.6",
"@types/react-native": "^0.69.1",
"@types/react-native-vector-icons": "^6.4.10",
"@types/react-test-renderer": "^18.0.0",

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/mobile",
"version": "3.0.23",
"version": "3.0.25",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/mobile",
"version": "3.0.23",
"version": "3.0.25",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"workspaces": [
@@ -27,11 +27,13 @@
"@trpc/client": "^10.45.2",
"@trpc/react-query": "^10.45.2",
"@trpc/server": "^10.45.2",
"@types/validator": "^13.12.2",
"diffblazer": "^1.0.1",
"react": "18.2.0",
"react-native": "0.74.5"
},
"devDependencies": {
"@types/react": "^18.2.39",
"fonteditor-core": "^2.1.11",
"listr": "^0.14.3",
"otplib": "12.0.1",
@@ -3709,7 +3711,6 @@
"../../packages/crypto": {
"name": "@notesnook/crypto",
"version": "2.1.3",
"dev": true,
"license": "GPL-3.0-or-later",
"dependencies": {
"@notesnook/sodium": "file:../sodium"
@@ -7739,7 +7740,7 @@
},
"../../packages/editor-mobile/node_modules/@types/prop-types": {
"version": "15.7.11",
"devOptional": true,
"dev": true,
"license": "MIT"
},
"../../packages/editor-mobile/node_modules/@types/q": {
@@ -7759,7 +7760,7 @@
},
"../../packages/editor-mobile/node_modules/@types/react": {
"version": "18.2.39",
"devOptional": true,
"dev": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
@@ -7790,7 +7791,7 @@
},
"../../packages/editor-mobile/node_modules/@types/scheduler": {
"version": "0.16.8",
"devOptional": true,
"dev": true,
"license": "MIT"
},
"../../packages/editor-mobile/node_modules/@types/semver": {
@@ -12615,7 +12616,7 @@
},
"../../packages/editor-mobile/node_modules/immer": {
"version": "9.0.21",
"devOptional": true,
"dev": true,
"license": "MIT",
"funding": {
"type": "opencollective",
@@ -23087,6 +23088,7 @@
},
"../../packages/editor/node_modules/js-tokens": {
"version": "4.0.0",
"dev": true,
"license": "MIT"
},
"../../packages/editor/node_modules/jsesc": {
@@ -23137,6 +23139,7 @@
},
"../../packages/editor/node_modules/loose-envify": {
"version": "1.4.0",
"dev": true,
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
@@ -23648,6 +23651,7 @@
},
"../../packages/editor/node_modules/react": {
"version": "18.3.1",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
@@ -23666,6 +23670,7 @@
},
"../../packages/editor/node_modules/react-dom": {
"version": "18.3.1",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0",
@@ -23804,6 +23809,7 @@
},
"../../packages/editor/node_modules/scheduler": {
"version": "0.23.2",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
@@ -29007,7 +29013,6 @@
"@types/html-to-text": "^8.0.1",
"@types/jest": "^29.5.14",
"@types/metro-config": "^0.76.3",
"@types/react": "^18.2.6",
"@types/react-native": "^0.69.1",
"@types/react-native-vector-icons": "^6.4.10",
"@types/react-test-renderer": "^18.0.0",
@@ -29207,6 +29212,7 @@
},
"node_modules/@babel/helper-builder-binary-assignment-operator-visitor": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.22.5"
@@ -29326,6 +29332,7 @@
},
"node_modules/@babel/helper-hoist-variables": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.22.5"
@@ -29585,6 +29592,7 @@
"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"
},
@@ -29599,6 +29607,7 @@
"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",
@@ -29747,6 +29756,7 @@
"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"
},
@@ -29759,6 +29769,7 @@
"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"
@@ -29793,6 +29804,7 @@
},
"node_modules/@babel/plugin-syntax-class-properties": {
"version": "7.12.13",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.12.13"
@@ -29805,6 +29817,7 @@
"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"
},
@@ -29842,6 +29855,7 @@
"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"
},
@@ -29866,6 +29880,7 @@
"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"
},
@@ -29880,6 +29895,7 @@
"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"
},
@@ -29894,6 +29910,7 @@
"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"
},
@@ -29905,6 +29922,7 @@
"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"
},
@@ -30004,6 +30022,7 @@
"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"
},
@@ -30031,6 +30050,7 @@
"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"
@@ -30059,6 +30079,7 @@
"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",
@@ -30089,6 +30110,7 @@
},
"node_modules/@babel/plugin-transform-block-scoped-functions": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -30117,6 +30139,7 @@
"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"
@@ -30132,6 +30155,7 @@
"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",
@@ -30196,6 +30220,7 @@
"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"
@@ -30211,6 +30236,7 @@
"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"
},
@@ -30225,6 +30251,7 @@
"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"
},
@@ -30237,6 +30264,7 @@
},
"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",
@@ -30253,6 +30281,7 @@
"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"
@@ -30280,6 +30309,7 @@
},
"node_modules/@babel/plugin-transform-for-of": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -30310,6 +30340,7 @@
"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"
@@ -30338,6 +30369,7 @@
"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"
@@ -30351,6 +30383,7 @@
},
"node_modules/@babel/plugin-transform-member-expression-literals": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -30366,6 +30399,7 @@
"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"
@@ -30396,6 +30430,7 @@
"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",
@@ -30413,6 +30448,7 @@
"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"
@@ -30442,6 +30478,7 @@
"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"
},
@@ -30471,6 +30508,7 @@
"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"
@@ -30486,6 +30524,7 @@
"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",
@@ -30502,6 +30541,7 @@
},
"node_modules/@babel/plugin-transform-object-super": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -30518,6 +30558,7 @@
"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"
@@ -30591,6 +30632,7 @@
},
"node_modules/@babel/plugin-transform-property-literals": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -30660,6 +30702,7 @@
},
"node_modules/@babel/plugin-transform-regenerator": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -30676,6 +30719,7 @@
"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"
},
@@ -30768,6 +30812,7 @@
"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"
},
@@ -30798,6 +30843,7 @@
"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"
},
@@ -30812,6 +30858,7 @@
"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"
@@ -30841,6 +30888,7 @@
"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"
@@ -30856,6 +30904,7 @@
"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",
@@ -30949,6 +30998,7 @@
"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",
@@ -30964,6 +31014,7 @@
"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"
}
@@ -34245,7 +34296,8 @@
"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=="
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true
},
"node_modules/@types/graceful-fs": {
"version": "4.1.6",
@@ -34351,16 +34403,17 @@
},
"node_modules/@types/prop-types": {
"version": "15.7.5",
"devOptional": true,
"dev": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.2.13",
"devOptional": true,
"version": "18.3.18",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
"integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
"csstype": "^3.0.2"
}
},
@@ -34397,11 +34450,6 @@
"@types/react": "*"
}
},
"node_modules/@types/scheduler": {
"version": "0.16.3",
"devOptional": true,
"license": "MIT"
},
"node_modules/@types/semver": {
"version": "7.5.0",
"dev": true,
@@ -34411,6 +34459,12 @@
"version": "2.0.1",
"license": "MIT"
},
"node_modules/@types/validator": {
"version": "13.12.2",
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.12.2.tgz",
"integrity": "sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==",
"license": "MIT"
},
"node_modules/@types/yargs": {
"version": "17.0.24",
"license": "MIT",
@@ -34712,6 +34766,7 @@
"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"
@@ -34720,22 +34775,26 @@
"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=="
"integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==",
"dev": true
},
"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=="
"integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==",
"dev": true
},
"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=="
"integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==",
"dev": true
},
"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",
@@ -34745,12 +34804,14 @@
"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=="
"integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==",
"dev": true
},
"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",
@@ -34762,6 +34823,7 @@
"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"
}
@@ -34770,6 +34832,7 @@
"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"
}
@@ -34777,12 +34840,14 @@
"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=="
"integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==",
"dev": true
},
"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",
@@ -34798,6 +34863,7 @@
"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",
@@ -34810,6 +34876,7 @@
"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",
@@ -34821,6 +34888,7 @@
"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",
@@ -34834,6 +34902,7 @@
"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"
@@ -34891,12 +34960,14 @@
"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=="
"integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
"dev": true
},
"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=="
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
"dev": true
},
"node_modules/@yarnpkg/lockfile": {
"version": "1.1.0",
@@ -35886,6 +35957,7 @@
"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"
}
@@ -36316,7 +36388,7 @@
},
"node_modules/csstype": {
"version": "3.1.2",
"devOptional": true,
"dev": true,
"license": "MIT"
},
"node_modules/date-fns": {
@@ -36915,6 +36987,7 @@
"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"
@@ -37034,7 +37107,8 @@
"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=="
"integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
"dev": true
},
"node_modules/es-object-atoms": {
"version": "1.0.0",
@@ -37379,6 +37453,7 @@
"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"
@@ -37391,6 +37466,7 @@
"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"
}
@@ -37554,6 +37630,7 @@
"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"
},
@@ -37565,6 +37642,7 @@
"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"
}
@@ -37573,6 +37651,7 @@
"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"
}
@@ -38331,7 +38410,8 @@
"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=="
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
"dev": true
},
"node_modules/global": {
"version": "4.4.0",
@@ -40420,7 +40500,8 @@
"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=="
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"dev": true
},
"node_modules/json-schema-ref-resolver": {
"version": "1.0.1",
@@ -40896,6 +40977,7 @@
"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"
}
@@ -43124,6 +43206,7 @@
"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"
}
@@ -43145,28 +43228,6 @@
"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==",
"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==",
"peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
}
},
"node_modules/react-freeze": {
"version": "1.0.3",
"license": "MIT",
@@ -44080,6 +44141,7 @@
},
"node_modules/regenerator-transform": {
"version": "0.15.1",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.8.4"
@@ -44432,6 +44494,7 @@
},
"node_modules/serialize-javascript": {
"version": "6.0.1",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"randombytes": "^2.1.0"
@@ -45089,6 +45152,7 @@
},
"node_modules/tapable": {
"version": "2.2.1",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -45166,6 +45230,7 @@
"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",
@@ -45199,6 +45264,7 @@
"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",
@@ -45212,6 +45278,7 @@
"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"
},
@@ -45787,6 +45854,7 @@
"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"
@@ -45810,6 +45878,7 @@
"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",
@@ -45925,6 +45994,7 @@
"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"
}

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "3.0.24",
"version": "3.0.26",
"private": true,
"license": "GPL-3.0-or-later",
"workspaces": [
@@ -24,6 +24,7 @@
"release-android-bundle": "cd native/android && ./gradlew bundleRelease --no-daemon"
},
"devDependencies": {
"@types/react": "^18.2.39",
"fonteditor-core": "^2.1.11",
"listr": "^0.14.3",
"otplib": "12.0.1",
@@ -37,19 +38,20 @@
"dependencies": {
"@notesnook/common": "file:../../packages/common",
"@notesnook/core": "file:../../packages/core",
"@notesnook/crypto": "file:../../packages/crypto",
"@notesnook/editor": "file:../../packages/editor",
"@notesnook/editor-mobile": "file:../../packages/editor-mobile",
"@notesnook/intl": "file:../../packages/intl",
"@notesnook/logger": "file:../../packages/logger",
"@notesnook/theme": "file:../../packages/theme",
"@notesnook/themes-server": "file:../../servers/themes",
"@notesnook/crypto": "file:../../packages/crypto",
"diffblazer": "^1.0.1",
"react": "18.2.0",
"react-native": "0.74.5",
"@tanstack/react-query": "^4.36.1",
"@trpc/client": "^10.45.2",
"@trpc/react-query": "^10.45.2",
"@trpc/server": "^10.45.2",
"@tanstack/react-query": "^4.36.1"
"@types/validator": "^13.12.2",
"diffblazer": "^1.0.1",
"react": "18.2.0",
"react-native": "0.74.5"
}
}

View File

@@ -496,6 +496,7 @@ export const Search = ({
const tagId = await db.tags.add({
title: searchKeyword
});
if (!tagId) return;
SearchSetters.selectTags(tagId);
onSearch();
checkQueryExists(searchKeyword);

View File

@@ -20,5 +20,5 @@
"maxNodeModuleJsDepth": 5,
"downlevelIteration": true
},
"exclude": ["native"]
"exclude": ["native", "e2e"]
}

View File

@@ -370,3 +370,57 @@ test("when autosave is disabled, closing the note should save it", async ({
await expect(notes.editor.savedIcon).toBeVisible();
expect(await notes.editor.getContent("text")).toBe(content.trim());
});
test("control + alt + right arrow should go to next note", async ({ page }) => {
const app = new AppModel(page);
await app.goto();
const notes = await app.goToNotes();
const note1 = await notes.createNote({
title: "Note 1",
content: "Note 1 content"
});
const note2 = await notes.createNote({
title: "Note 2",
content: "Note 2 content"
});
await note1?.openNote();
await note2?.openNote();
await page.keyboard.press("Control+Alt+ArrowRight");
expect(await notes.editor.getTitle()).toBe("Note 1");
expect(await notes.editor.getContent("text")).toBe("Note 1 content");
await page.keyboard.press("Control+Alt+ArrowRight");
expect(await notes.editor.getTitle()).toBe("Note 2");
expect(await notes.editor.getContent("text")).toBe("Note 2 content");
});
test("control + alt + left arrow should go to previous note", async ({
page
}) => {
const app = new AppModel(page);
await app.goto();
const notes = await app.goToNotes();
const note1 = await notes.createNote({
title: "Note 1",
content: "Note 1 content"
});
const note2 = await notes.createNote({
title: "Note 2",
content: "Note 2 content"
});
await note1?.openNote();
await note2?.openNote();
await page.keyboard.press("Control+Alt+ArrowLeft");
expect(await notes.editor.getTitle()).toBe("Note 1");
expect(await notes.editor.getContent("text")).toBe("Note 1 content");
await page.keyboard.press("Control+Alt+ArrowLeft");
expect(await notes.editor.getTitle()).toBe("Note 2");
expect(await notes.editor.getContent("text")).toBe("Note 2 content");
});

View File

@@ -242,4 +242,23 @@ export class EditorModel {
if ((await tabModel.getId()) === id) return tabModel;
}
}
async attachImage() {
await this.page
.context()
.grantPermissions(["clipboard-read", "clipboard-write"]);
await this.page.evaluate(async () => {
const resp = await fetch("https://dummyjson.com/image/150");
const blob = await resp.blob();
window.navigator.clipboard.write([
new ClipboardItem({
"image/png": new Blob([blob], { type: "image/png" })
})
]);
});
await this.page.keyboard.down("Control");
await this.page.keyboard.press("KeyV");
await this.page.keyboard.up("Control");
}
}

View File

@@ -139,4 +139,15 @@ export class SettingsViewModel {
await waitForDialog(this.page, "Restoring backup");
}
async selectImageCompression(option: { value: string; label: string }) {
const item = await this.navigation.findItem("Behaviour");
await item?.click();
const imageCompressionDropdown = this.page
.locator(getTestId("setting-image-compression"))
.locator("select");
await imageCompressionDropdown.selectOption(option);
}
}

View File

@@ -0,0 +1,79 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { test, expect } from "@playwright/test";
import { AppModel } from "./models/app.model";
import { NOTE } from "./utils";
test("ask for image compression during image upload when 'Image Compression' setting is 'Ask every time'", async ({
page
}) => {
const app = new AppModel(page);
await app.goto();
const settings = await app.goToSettings();
await settings.selectImageCompression({
value: "0",
label: "Ask every time"
});
await settings.close();
const notes = await app.goToNotes();
await notes.createNote(NOTE);
await notes.editor.attachImage();
await expect(page.getByText("Enable compression")).toBeVisible();
});
test("do not ask for image compression during image upload when 'Image Compression' setting is 'Enable (Recommended)'", async ({
page
}) => {
const app = new AppModel(page);
await app.goto();
const settings = await app.goToSettings();
await settings.selectImageCompression({
value: "1",
label: "Enable (Recommended)"
});
await settings.close();
const notes = await app.goToNotes();
await notes.createNote(NOTE);
await notes.editor.attachImage();
await expect(page.getByText("Enable compression")).toBeHidden();
});
test("do not ask for image compression during image upload when 'Image Compression' setting is 'Disable'", async ({
page
}) => {
const app = new AppModel(page);
await app.goto();
const settings = await app.goToSettings();
await settings.selectImageCompression({
value: "2",
label: "Disable"
});
await settings.close();
const notes = await app.goToNotes();
await notes.createNote(NOTE);
await notes.editor.attachImage();
await expect(page.getByText("Enable compression")).toBeHidden();
});

View File

@@ -0,0 +1,88 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { test, expect } from "@playwright/test";
import { AppModel } from "./models/app.model";
import { getTestId, NOTE, PASSWORD } from "./utils";
test("locking a note should show vault unlocked status", async ({ page }) => {
const app = new AppModel(page);
await app.goto();
const notes = await app.goToNotes();
const note = await notes.createNote(NOTE);
const vaultUnlockedStatus = page.locator(getTestId("vault-unlocked"));
await note?.contextMenu.lock(PASSWORD);
await expect(vaultUnlockedStatus).toBeVisible();
});
test("clicking on vault unlocked status should lock the vault", async ({
page
}) => {
const app = new AppModel(page);
await app.goto();
const notes = await app.goToNotes();
const note = await notes.createNote(NOTE);
const vaultUnlockedStatus = page.locator(getTestId("vault-unlocked"));
await note?.contextMenu.lock(PASSWORD);
await note?.openLockedNote(PASSWORD);
await vaultUnlockedStatus.waitFor({ state: "visible" });
await vaultUnlockedStatus.click();
await expect(vaultUnlockedStatus).toBeHidden();
expect(await note?.contextMenu.isLocked()).toBe(true);
});
test("opening a locked note should show vault unlocked status", async ({
page
}) => {
const app = new AppModel(page);
await app.goto();
const notes = await app.goToNotes();
const note = await notes.createNote(NOTE);
const vaultUnlockedStatus = page.locator(getTestId("vault-unlocked"));
await note?.contextMenu.lock(PASSWORD);
await vaultUnlockedStatus.waitFor({ state: "visible" });
await vaultUnlockedStatus.click();
await vaultUnlockedStatus.waitFor({ state: "hidden" });
await note?.openLockedNote(PASSWORD);
await expect(vaultUnlockedStatus).toBeVisible();
});
test("unlocking a note permanently should not show vault unlocked status", async ({
page
}) => {
const app = new AppModel(page);
await app.goto();
const notes = await app.goToNotes();
const note = await notes.createNote(NOTE);
const vaultUnlockedStatus = page.locator(getTestId("vault-unlocked"));
await note?.contextMenu.lock(PASSWORD);
await vaultUnlockedStatus.waitFor({ state: "visible" });
await vaultUnlockedStatus.click();
await vaultUnlockedStatus.waitFor({ state: "hidden" });
await note?.contextMenu.unlock(PASSWORD);
await expect(vaultUnlockedStatus).toBeHidden();
});

16545
apps/web/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -43,9 +43,9 @@
"@theme-ui/core": "^0.16.1",
"@trpc/client": "10.45.2",
"@trpc/react-query": "10.45.2",
"@zip.js/zip.js": "^2.7.32",
"@zip.js/zip.js": "^2.7.54",
"async-mutex": "^0.4.0",
"axios": "^1.3.4",
"axios": "^1.7.9",
"clipboard-polyfill": "4.0.0",
"comlink": "^4.3.1",
"cronosjs": "^1.7.1",
@@ -55,11 +55,11 @@
"event-source-polyfill": "^1.0.25",
"fflate": "^0.8.0",
"file-saver": "^2.0.5",
"hash-wasm": "^4.9.0",
"hash-wasm": "^4.12.0",
"hotkeys-js": "^3.8.3",
"katex": "0.16.2",
"mac-scrollbar": "^0.13.5",
"mutative": "^1.0.6",
"mac-scrollbar": "^0.13.6",
"mutative": "^1.1.0",
"pdfjs-dist": "3.6.172",
"phone": "^3.1.14",
"platform": "^1.3.6",
@@ -69,12 +69,11 @@
"react-dom": "18.2.0",
"react-dropzone": "^14.2.3",
"react-error-boundary": "^4.0.12",
"react-freeze": "^1.0.3",
"react-freeze": "^1.0.4",
"react-hot-toast": "^2.4.1",
"react-loading-skeleton": "^3.3.1",
"react-modal": "3.16.1",
"react-modal": "3.16.3",
"react-qrcode-logo": "^2.9.0",
"react-resizable-panels": "^2.0.17",
"react-scroll-sync": "^0.11.2",
"react-virtuoso": "^4.6.2",
"snarkdown": "^2.0.0",
@@ -82,50 +81,49 @@
"w3c-keyname": "^2.2.6",
"wouter": "2.12.1",
"zustand": "4.4.7",
"zustand-mutative": "^1.0.1"
"zustand-mutative": "^1.2.0"
},
"devDependencies": {
"@babel/core": "^7.22.5",
"@playwright/test": "^1.48.2",
"@swc/core": "^1.5.24",
"@swc/plugin-react-remove-properties": "^2.0.4",
"@swc/plugin-react-remove-properties": "^6.0.2",
"@trpc/server": "10.45.2",
"@types/babel__core": "^7.20.1",
"@types/event-source-polyfill": "^1.0.1",
"@types/file-saver": "^2.0.5",
"@types/marked": "^4.0.7",
"@types/node-fetch": "^2.5.10",
"@types/platform": "^1.3.4",
"@types/event-source-polyfill": "^1.0.5",
"@types/file-saver": "^2.0.7",
"@types/marked": "^6.0.0",
"@types/node-fetch": "^2.6.12",
"@types/platform": "^1.3.6",
"@types/react": "^18.2.39",
"@types/react-avatar-editor": "^13.0.2",
"@types/react-avatar-editor": "^13.0.3",
"@types/react-dom": "^18.2.17",
"@types/react-modal": "3.16.3",
"@types/react-scroll-sync": "^0.9.0",
"@types/wicg-file-system-access": "^2020.9.6",
"@vitejs/plugin-react-swc": "^3.7.0",
"@types/wicg-file-system-access": "^2023.10.5",
"@vitejs/plugin-react-swc": "^3.7.2",
"autoprefixer": "^10.4.19",
"better-sqlite3-multiple-ciphers": "^9.4.0",
"buffer": "^6.0.3",
"chalk": "^4.1.0",
"dotenv": "^10.0.0",
"dotenv": "^16.4.7",
"esbuild": "^0.24.2",
"file-loader": "^6.2.0",
"find-process": "^1.4.4",
"happy-dom": "^8.9.0",
"ip": "^1.1.8",
"happy-dom": "^16.0.1",
"ip": "^2.0.1",
"lorem-ipsum": "^2.0.4",
"otplib": "^12.0.1",
"rollup": "^4.18.0",
"rollup-plugin-visualizer": "^5.12.0",
"vite": "^5.2.12",
"rollup-plugin-visualizer": "^5.13.1",
"vite": "^5.4.11",
"vite-plugin-env-compatible": "^2.0.1",
"vite-plugin-pwa": "^0.20.0",
"vite-plugin-svgr": "^4.2.0",
"vitest": "^1.6.0",
"workbox-core": "^7.0.0",
"workbox-expiration": "^7.0.0",
"workbox-precaching": "^7.0.0",
"workbox-routing": "^7.0.0",
"workbox-strategies": "^7.0.0"
"vite-plugin-pwa": "^0.21.1",
"vite-plugin-svgr": "^4.3.0",
"vitest": "^2.1.8",
"workbox-core": "^7.3.0",
"workbox-expiration": "^7.3.0",
"workbox-precaching": "^7.3.0",
"workbox-routing": "^7.3.0",
"workbox-strategies": "^7.3.0"
},
"scripts": {
"start": "cross-env PLATFORM=web vite",

View File

@@ -1,94 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useEffect } from "react";
import { useStore } from "./stores/app-store";
import useSlider from "./hooks/use-slider";
import useMobile from "./hooks/use-mobile";
import useTablet from "./hooks/use-tablet";
type MobileAppEffectsProps = {
sliderId: string;
overlayId: string;
setShow: (show: boolean) => void;
};
export default function MobileAppEffects({
sliderId,
overlayId,
setShow
}: MobileAppEffectsProps) {
const isMobile = useMobile();
const isTablet = useTablet();
const toggleSideMenu = useStore((store) => store.toggleSideMenu);
const setIsEditorOpen = useStore((store) => store.setIsEditorOpen);
const isEditorOpen = useStore((store) => store.isEditorOpen);
const isSideMenuOpen = useStore((store) => store.isSideMenuOpen);
const isFocusMode = useStore((store) => store.isFocusMode);
const [slideToIndex] = useSlider(sliderId, {
onSliding: (_e, { position }) => {
if (!isMobile) return;
const offset = 70;
const width = 300;
const percent = offset - (position / width) * offset;
const overlay = document.getElementById("overlay");
if (!overlay) return;
if (percent > 0) {
overlay.style.opacity = `${percent}%`;
overlay.style.pointerEvents = "all";
} else {
overlay.style.opacity = "0%";
overlay.style.pointerEvents = "none";
}
},
onChange: (e, { slide }) => {
toggleSideMenu(slide?.index === 0 ? true : false);
setIsEditorOpen(slide?.index === 3 ? true : false);
}
});
useEffect(() => {
if (!isMobile) return;
slideToIndex(isSideMenuOpen ? 1 : 2);
}, [isMobile, slideToIndex, isSideMenuOpen]);
useEffect(() => {
if (!isMobile) return;
slideToIndex(isEditorOpen ? 3 : 2);
}, [isMobile, slideToIndex, isEditorOpen]);
useEffect(() => {
toggleSideMenu(!isMobile);
if (!isMobile && !isTablet && !isFocusMode) setShow(true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isMobile, isTablet, isFocusMode, toggleSideMenu]);
useEffect(() => {
if (!overlayId) return;
const overlay = document.getElementById(overlayId);
if (!overlay) return;
overlay.onclick = () => toggleSideMenu(false);
return () => {
overlay.onclick = null;
};
}, [overlayId, toggleSideMenu]);
return null;
}

View File

@@ -5,40 +5,19 @@
.tabsScroll,
.titlebarLogo,
.theme-scope-titleBar {
.theme-scope-titleBar,
.route-container-header {
-webkit-app-region: drag;
}
.theme-scope-titleBar button,
.tabsScroll .tab {
.tabsScroll .tab,
.editor-action-bar button,
.route-container-header button,
.search-container {
-webkit-app-region: no-drag;
}
.panel-resize-handle {
position: relative;
}
.panel-resize-handle::before {
content: " ";
position: absolute;
transition: background-color 300ms ease-out;
z-index: 999;
}
.panel-resize-handle[data-panel-group-direction="horizontal"]::before {
width: 5px;
height: 100%;
}
.panel-resize-handle[data-panel-group-direction="vertical"]::before {
width: 100%;
height: 5px;
}
.panel-resize-handle[data-resize-handle-state="hover"]::before {
background-color: var(--accent);
}
/* open-sans-regular - vietnamese_latin-ext_latin_hebrew_greek-ext_greek_cyrillic-ext_cyrillic */
@font-face {
font-family: "Open Sans";

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useState, Suspense, useRef, useEffect } from "react";
import { useState, Suspense, useEffect, useRef } from "react";
import { Box, Flex } from "@theme-ui/components";
import { ScopedThemeProvider } from "./components/theme-provider";
import useMobile from "./hooks/use-mobile";
@@ -30,38 +30,84 @@ import { FlexScrollContainer } from "./components/scroll-container";
import CachedRouter from "./components/cached-router";
import { WebExtensionRelay } from "./utils/web-extension-relay";
import {
PanelGroup,
Panel,
PanelResizeHandle,
ImperativePanelHandle
} from "react-resizable-panels";
Pane,
SplitPane,
SplitPaneImperativeHandle
} from "./components/split-pane";
import GlobalMenuWrapper from "./components/global-menu-wrapper";
import AppEffects from "./app-effects";
import HashRouter from "./components/hash-router";
import { useWindowFocus } from "./hooks/use-window-focus";
import { Global } from "@emotion/react";
import { isMac } from "./utils/platform";
import useSlider from "./hooks/use-slider";
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";
new WebExtensionRelay();
const MobileAppEffects = React.lazy(() => import("./app-effects.mobile"));
function App() {
const isMobile = useMobile();
const [show, setShow] = useState(true);
const isFocusMode = useStore((store) => store.isFocusMode);
const { isFocused } = useWindowFocus();
const { isFullscreen } = useWindowControls();
console.timeEnd("loading app");
return (
<>
{isFocused ? null : (
<Global
styles={`
body {
filter: brightness(0.95) grayscale(1);
}
.titlebar {
background: var(--background-secondary) !important;
}
`}
/>
)}
{IS_DESKTOP_APP && isMac() && !isFullscreen ? (
<Global
// These styles to make sure the app content doesn't overlap with the traffic lights.
styles={`
.nav-pane,
.mobile-nav-pane {
margin-top: env(titlebar-area-height) !important;
}
.nav-pane.collapsed + .list-pane .route-container-header,
.nav-pane.collapsed + .list-pane.collapsed + .editor-pane .editor-action-bar,
.nav-pane.collapsed + .editor-pane .editor-action-bar {
padding-left: 25px;
}
.editor-pane:first-of-type .editor-action-bar,
.mobile-editor-pane.pane-active .editor-action-bar,
.mobile-list-pane.pane-active .route-container-header {
padding-left: 80px;
}
.route-container-header, .editor-action-bar {
transition: padding-left 0.4s ease-out;
}
.editor-action-bar {
border-bottom: none;
}
.route-container-header .routeHeader {
font-size: ${getFontSizes().title};
}
.global-split-pane .react-split__sash {
height: calc(100% - ${TITLE_BAR_HEIGHT}px);
}
`}
/>
) : null}
<Suspense fallback={<div style={{ display: "none" }} />}>
<div id="menu-wrapper">
<GlobalMenuWrapper />
</div>
{isMobile && (
<MobileAppEffects
sliderId="slider"
overlayId="overlay"
setShow={setShow}
/>
)}
</Suspense>
<AppEffects setShow={setShow} />
@@ -69,7 +115,11 @@ function App() {
id="app"
bg="background"
className={isFocusMode ? "app-focus-mode" : ""}
sx={{ overflow: "hidden", flexDirection: "column", height: "100%" }}
sx={{
overflow: "hidden",
flexDirection: "column",
height: "100%"
}}
>
{isMobile ? (
<MobileAppContents />
@@ -92,13 +142,11 @@ function DesktopAppContents({ show, setShow }: DesktopAppContentsProps) {
const isFocusMode = useStore((store) => store.isFocusMode);
const isTablet = useTablet();
const [isNarrow, setIsNarrow] = useState(isTablet || false);
const navPane = useRef<ImperativePanelHandle>(null);
const middlePane = useRef<ImperativePanelHandle>(null);
const navPane = useRef<SplitPaneImperativeHandle>(null);
useEffect(() => {
const size = navPane.current?.getSize();
// Toggle `isNarrow` to true if panel size isn't set to narrow by user
setIsNarrow((size && size <= 5) || isTablet);
if (isTablet) navPane.current?.collapse(0);
else navPane.current?.expand(0);
}, [isTablet]);
// useEffect(() => {
@@ -123,81 +171,71 @@ function DesktopAppContents({ show, setShow }: DesktopAppContentsProps) {
overflow: "hidden"
}}
>
<PanelGroup autoSaveId="global-panel-group" direction="horizontal">
{!isFocusMode && isTablet ? (
<Flex sx={{ width: 50 }}>
<SplitPane
className="global-split-pane"
ref={navPane}
autoSaveId="global-panel-group"
direction="vertical"
onChange={(sizes) => {
setIsNarrow(sizes[0] <= 70);
}}
>
{isFocusMode ? null : (
<Pane
id="nav-pane"
initialSize={180}
className={`nav-pane`}
minSize={50}
snapSize={120}
maxSize={300}
>
<NavigationMenu
toggleNavigationContainer={(state) => {
setShow(state || !show);
}}
isTablet={isNarrow}
/>
</Flex>
) : (
!isFocusMode && (
<>
<Panel
ref={navPane}
order={1}
className="nav-pane"
defaultSize={10}
minSize={3.5}
// maxSize={isNarrow ? 5 : undefined}
onResize={(size) => setIsNarrow(size <= 5)}
collapsible
collapsedSize={3.5}
>
<NavigationMenu
toggleNavigationContainer={(state) => {
setShow(state || !show);
}}
isTablet={isNarrow}
/>
</Panel>
<PanelResizeHandle className="panel-resize-handle" />
</>
)
</Pane>
)}
{!isFocusMode && show && (
<>
<Panel
ref={middlePane}
className="middle-pane"
order={2}
collapsible
defaultSize={20}
>
<ScopedThemeProvider
className="listMenu"
scope="list"
sx={{
display: "flex",
flexDirection: "column",
flex: 1,
bg: "background",
borderRight: "1px solid var(--separator)"
}}
>
<CachedRouter />
</ScopedThemeProvider>
</Panel>
<PanelResizeHandle className="panel-resize-handle" />
</>
)}
<Panel className="editor-pane" order={3} defaultSize={70}>
<Flex
sx={{
display: "flex",
overflow: "hidden",
flex: 1,
flexDirection: "column",
bg: "background"
}}
{!isFocusMode && show ? (
<Pane
id="list-pane"
initialSize={380}
style={{ flex: 1, display: "flex" }}
snapSize={200}
maxSize={500}
className="list-pane"
>
<HashRouter />
</Flex>
</Panel>
</PanelGroup>
<ScopedThemeProvider
className="listMenu"
scope="list"
sx={{
display: "flex",
flexDirection: "column",
flex: 1,
bg: "background",
borderRight: "1px solid var(--separator)"
}}
>
<CachedRouter />
</ScopedThemeProvider>
</Pane>
) : null}
<Pane
id="editor-pane"
className="editor-pane"
style={{
flex: 1,
display: "flex",
backgroundColor: "var(--background)",
overflow: "hidden",
flexDirection: "column"
}}
>
{<HashRouter />}
</Pane>
</SplitPane>
</Flex>
<StatusBar />
</>
@@ -205,8 +243,46 @@ function DesktopAppContents({ show, setShow }: DesktopAppContentsProps) {
}
function MobileAppContents() {
const { ref, slideToIndex } = useSlider({
onSliding: (_e, { position }) => {
const offset = 70;
const width = 300;
const percent = offset - (position / width) * offset;
const overlay = document.getElementById("overlay");
if (!overlay) return;
if (percent > 0) {
overlay.style.opacity = `${percent}%`;
overlay.style.pointerEvents = "all";
} else {
overlay.style.opacity = "0%";
overlay.style.pointerEvents = "none";
}
},
onChange: (e, { slide, lastSlide }) => {
slide.node.classList.add("pane-active");
lastSlide?.node.classList.remove("pane-active");
}
});
useEffect(() => {
const toggleSideMenuEvent = AppEventManager.subscribe(
AppEvents.toggleSideMenu,
(state) => slideToIndex(state ? 0 : 1)
);
const toggleEditorEvent = AppEventManager.subscribe(
AppEvents.toggleEditor,
(state) => slideToIndex(state ? 2 : 1)
);
return () => {
toggleSideMenuEvent.unsubscribe();
toggleEditorEvent.unsubscribe();
};
}, [slideToIndex]);
return (
<FlexScrollContainer
scrollRef={ref}
id="slider"
suppressScrollX
style={{
@@ -223,17 +299,18 @@ function MobileAppContents() {
}}
>
<Flex
className="mobile-nav-pane"
sx={{
scrollSnapAlign: "start",
scrollSnapStop: "always",
width: [300, 60],
width: 300,
flexShrink: 0
}}
>
<NavigationMenu toggleNavigationContainer={() => {}} isTablet={false} />
<NavigationMenu toggleNavigationContainer={() => { }} isTablet={false} />
</Flex>
<Flex
className="listMenu"
className="mobile-list-pane"
variant="columnFill"
sx={{
position: "relative",
@@ -246,6 +323,7 @@ function MobileAppContents() {
<CachedRouter />
<Box
id="overlay"
onClick={() => slideToIndex(1)}
sx={{
position: "absolute",
width: "100%",
@@ -261,6 +339,7 @@ function MobileAppContents() {
/>
</Flex>
<Flex
className="mobile-editor-pane"
sx={{
scrollSnapAlign: "start",
scrollSnapStop: "always",

View File

@@ -38,5 +38,8 @@ export const AppEvents = {
changeNoteTitle: "changeNoteTitle",
revealItemInList: "list:revealItem"
revealItemInList: "list:revealItem",
toggleSideMenu: "app:openSideMenu",
toggleEditor: "app:toggleEditor"
};

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Button, Flex, Text } from "@theme-ui/components";
import { useState } from "react";
import { useEffect, useRef, useState } from "react";
import {
ArrowLeft,
Cross,
@@ -79,11 +79,15 @@ import { showPublishView } from "../publish-view";
import { restrictToHorizontalAxis } from "@dnd-kit/modifiers";
import useMobile from "../../hooks/use-mobile";
import { strings } from "@notesnook/intl";
import { TITLE_BAR_HEIGHT, getWindowControls } from "../title-bar";
import useTablet from "../../hooks/use-tablet";
import { isMac } from "../../utils/platform";
export function EditorActionBar() {
const { isMaximized, isFullscreen, hasNativeWindowControls } =
useWindowControls();
const editorMargins = useEditorStore((store) => store.editorMargins);
const isFocusMode = useAppStore((store) => store.isFocusMode);
const { isFullscreen } = useWindowControls();
const activeSession = useEditorStore((store) =>
store.activeSessionId ? store.getSession(store.activeSessionId) : undefined
);
@@ -95,7 +99,7 @@ export function EditorActionBar() {
const isNotePublished =
activeSession && db.monographs.isPublished(activeSession.id);
const isMobile = useMobile();
const setIsEditorOpen = useAppStore((store) => store.setIsEditorOpen);
const isTablet = useTablet();
const tools = [
{
@@ -195,7 +199,14 @@ export function EditorActionBar() {
activeSession.type !== "conflicted" &&
!isFocusMode,
onClick: () => useEditorStore.getState().toggleProperties()
}
},
...getWindowControls(
hasNativeWindowControls,
isFullscreen,
isMaximized,
isTablet,
isMobile
)
];
return (
@@ -210,7 +221,9 @@ export function EditorActionBar() {
borderRadius: 0,
flexShrink: 0
}}
onClick={() => setIsEditorOpen(false)}
onClick={() =>
AppEventManager.publish(AppEvents.toggleEditor, false)
}
>
<ArrowLeft size={18} />
</Button>
@@ -218,35 +231,46 @@ export function EditorActionBar() {
) : (
<TabStrip />
)}
{tools.map((tool) => (
<Button
data-test-id={tool.title}
disabled={!tool.enabled}
variant={tool.title === "Close" ? "error" : "secondary"}
title={tool.title}
key={tool.title}
sx={{
height: "100%",
alignItems: "center",
bg: "transparent",
display: [
tool.hideOnMobile ? "none" : "flex",
tool.hidden ? "none" : "flex"
],
borderRadius: 0,
flexShrink: 0,
"&:hover svg path": {
fill:
tool.title === "Close"
? "var(--accentForeground-error) !important"
: "var(--icon)"
}
}}
onClick={tool.onClick}
>
<tool.icon size={18} />
</Button>
))}
<Flex
sx={{
alignItems: "center",
justifyContent: "center",
mr:
hasNativeWindowControls && !isMac() && !isMobile && !isTablet
? `calc(100vw - env(titlebar-area-width))`
: 0
}}
>
{tools.map((tool) => (
<Button
data-test-id={tool.title}
disabled={!tool.enabled}
variant={tool.title === "Close" ? "error" : "secondary"}
title={tool.title}
key={tool.title}
sx={{
height: "100%",
alignItems: "center",
bg: "transparent",
display: [
"hideOnMobile" in tool && tool.hideOnMobile ? "none" : "flex",
tool.hidden ? "none" : "flex"
],
borderRadius: 0,
flexShrink: 0,
"&:hover svg path": {
fill:
tool.title === "Close"
? "var(--accentForeground-error) !important"
: "var(--icon)"
}
}}
onClick={tool.onClick}
>
<tool.icon size={18} />
</Button>
))}
</Flex>
</>
);
}
@@ -259,7 +283,7 @@ function TabStrip() {
<ScrollContainer
className="tabsScroll"
suppressScrollY
style={{ flex: 1 }}
style={{ flex: 1, height: TITLE_BAR_HEIGHT }}
trackStyle={() => ({
backgroundColor: "transparent",
"--ms-track-size": "6px"
@@ -275,9 +299,7 @@ function TabStrip() {
<Flex
sx={{
flex: 1,
my: "2.5px",
gap: 1,
height: 32
height: "100%"
}}
onDoubleClick={async (e) => {
e.stopPropagation();
@@ -452,28 +474,38 @@ function Tab(props: TabProps) {
: Note;
const { attributes, listeners, setNodeRef, transform, transition, active } =
useSortable({ id });
const activeTabRef = useRef<HTMLElement | null>(null);
useEffect(() => {
if (activeTabRef.current && isActive) {
const tab = activeTabRef.current;
tab.scrollIntoView({
behavior: "smooth",
block: "nearest",
inline: "nearest"
});
}
}, [isActive]);
return (
<Flex
ref={setNodeRef}
ref={(el) => {
setNodeRef(el);
activeTabRef.current = el;
}}
className="tab"
data-test-id={`tab-${id}`}
sx={{
borderRadius: "default",
height: "100%",
cursor: "pointer",
px: 2,
py: "7px",
borderRight: "1px solid var(--border)",
transform: CSS.Transform.toString(transform),
transition,
visibility: active?.id === id ? "hidden" : "visible",
bg: isActive ? "background-selected" : "background-secondary",
// borderTopLeftRadius: "default",
// borderTopRightRadius: "default",
// borderBottom: isActive ? "none" : "1px solid var(--border)",
border: "1px solid",
borderColor: isActive ? "border-selected" : "transparent",
bg: isActive ? "background-selected" : "transparent",
justifyContent: "space-between",
alignItems: "center",
flexShrink: 0,

View File

@@ -68,14 +68,15 @@ import { scrollIntoViewById } from "@notesnook/editor";
import { IEditor } from "./types";
import { EditorActionBar } from "./action-bar";
import { logger } from "../../utils/logger";
import { PanelGroup, Panel, PanelResizeHandle } from "react-resizable-panels";
import { NoteLinkingDialog } from "../../dialogs/note-linking-dialog";
import { strings } from "@notesnook/intl";
import { onPageVisibilityChanged } from "../../utils/page-visibility";
import { Pane, SplitPane } from "../split-pane";
import { TITLE_BAR_HEIGHT } from "../title-bar";
const PDFPreview = React.lazy(() => import("../pdf-preview"));
const autoSaveToast = { show: true, hide: () => {} };
const autoSaveToast = { show: true, hide: () => { } };
async function saveContent(
noteId: string,
@@ -124,30 +125,57 @@ export default function TabsView() {
const isTOCVisible = useEditorStore((store) => store.isTOCVisible);
const [dropRef, overlayRef] = useDragOverlay();
useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
if (
(event.ctrlKey || event.metaKey) &&
event.altKey &&
event.key === "ArrowRight"
) {
event.preventDefault();
useEditorStore.getState().openNextSession();
}
if (
(event.ctrlKey || event.metaKey) &&
event.altKey &&
event.key === "ArrowLeft"
) {
event.preventDefault();
useEditorStore.getState().openPreviousSession();
}
};
document.body.addEventListener("keydown", onKeyDown);
return () => {
document.body.removeEventListener("keydown", onKeyDown);
};
}, []);
return (
<>
{!hasNativeTitlebar ? (
<EditorActionBarPortal />
) : (
<Flex sx={{ px: 1 }}>
<EditorActionBar />
</Flex>
)}
<Flex
className="editor-action-bar"
sx={{
zIndex: 2,
height: TITLE_BAR_HEIGHT,
borderBottom: "1px solid var(--border)"
}}
>
<EditorActionBar />
</Flex>
<ScopedThemeProvider
scope="editor"
ref={dropRef}
sx={{
bg: "background",
pt: 1,
flex: 1,
overflow: "hidden",
display: "flex",
flexDirection: "column"
}}
>
<PanelGroup direction="horizontal" autoSaveId={"editor-panels"}>
<Panel id="editor-panel" className="editor-pane" order={1}>
<SplitPane direction="vertical" autoSaveId={"editor-panels"}>
<Pane id="editor-panel" className="editor-pane">
{sessions.map((session) => (
<Freeze key={session.id} freeze={session.id !== activeSessionId}>
{session.type === "locked" ? (
@@ -159,64 +187,51 @@ export default function TabsView() {
)}
</Freeze>
))}
</Panel>
</Pane>
{documentPreview && (
<>
<PanelResizeHandle className="panel-resize-handle" />
<Panel
id="pdf-preview-panel"
order={2}
minSize={35}
defaultSize={35}
{documentPreview ? (
<Pane id="pdf-preview-panel" initialSize={435} minSize={435}>
<ScopedThemeProvider
scope="editorSidebar"
id="editorSidebar"
sx={{
display: "flex",
flexDirection: "column",
overflow: "hidden",
borderLeft: "1px solid var(--border)",
height: "100%",
bg: "background"
}}
>
<ScopedThemeProvider
scope="editorSidebar"
id="editorSidebar"
sx={{
display: "flex",
flexDirection: "column",
overflow: "hidden",
borderLeft: "1px solid var(--border)",
height: "100%",
bg: "background"
}}
>
{documentPreview.url ? (
<Suspense
fallback={
<DownloadAttachmentProgress
hash={documentPreview.hash}
/>
{documentPreview.url ? (
<Suspense
fallback={
<DownloadAttachmentProgress hash={documentPreview.hash} />
}
>
<PDFPreview
fileUrl={documentPreview.url}
hash={documentPreview.hash}
onClose={() =>
useEditorStore.setState({
documentPreview: undefined
})
}
>
<PDFPreview
fileUrl={documentPreview.url}
hash={documentPreview.hash}
onClose={() =>
useEditorStore.setState({
documentPreview: undefined
})
}
/>
</Suspense>
) : (
<DownloadAttachmentProgress hash={documentPreview.hash} />
)}
</ScopedThemeProvider>
</Panel>
</>
)}
/>
</Suspense>
) : (
<DownloadAttachmentProgress hash={documentPreview.hash} />
)}
</ScopedThemeProvider>
</Pane>
) : null}
{isTOCVisible && activeSessionId && (
<>
<PanelResizeHandle className="panel-resize-handle" />
<Panel id="toc-panel" order={3} defaultSize={25} minSize={15}>
<TableOfContents sessionId={activeSessionId} />
</Panel>
</>
)}
</PanelGroup>
{isTOCVisible && activeSessionId ? (
<Pane id="table-of-contents-pane" initialSize={300} minSize={300}>
<TableOfContents sessionId={activeSessionId} />
</Pane>
) : null}
</SplitPane>
<DropZone overlayRef={overlayRef} />
{arePropertiesVisible && activeSessionId && (
<Properties sessionId={activeSessionId} />
@@ -237,10 +252,10 @@ function EditorView({
session
}: {
session:
| DefaultEditorSession
| NewEditorSession
| ReadonlyEditorSession
| DeletedEditorSession;
| DefaultEditorSession
| NewEditorSession
| ReadonlyEditorSession
| DeletedEditorSession;
}) {
const lastChangedTime = useRef<number>(0);
const root = useRef<HTMLDivElement>(null);
@@ -891,9 +906,3 @@ function UnlockNoteView(props: UnlockNoteViewProps) {
</div>
);
}
function EditorActionBarPortal() {
const container = document.getElementById("titlebar-portal-container");
if (!container) return null;
return ReactDOM.createPortal(<EditorActionBar />, container);
}

View File

@@ -33,6 +33,9 @@ import {
hashStream,
writeEncryptedFile
} from "../../interfaces/fs";
import Config from "../../utils/config";
import { compressImage, FileWithURI } from "../../utils/image-compressor";
import { ImageCompressionOptions } from "../../stores/setting-store";
const FILE_SIZE_LIMIT = 500 * 1024 * 1024;
const IMAGE_SIZE_LIMIT = 50 * 1024 * 1024;
@@ -58,12 +61,43 @@ export async function attachFiles(files: File[]) {
}
let images = files.filter((f) => f.type.startsWith("image/"));
images =
images.length > 0
? (await ImagePickerDialog.show({
images
})) || []
: [];
const imageCompressionConfig = Config.get<ImageCompressionOptions>(
"imageCompression",
ImageCompressionOptions.ASK_EVERY_TIME
);
switch (imageCompressionConfig) {
case ImageCompressionOptions.ENABLE: {
let compressedImages: FileWithURI[] = [];
for (const image of images) {
const compressed = await compressImage(image, {
maxWidth: (naturalWidth) => Math.min(1920, naturalWidth * 0.7),
width: (naturalWidth) => naturalWidth,
height: (_, naturalHeight) => naturalHeight,
resize: "contain",
quality: 0.7
});
compressedImages.push(
new FileWithURI([compressed], image.name, {
lastModified: image.lastModified,
type: image.type
})
);
}
images = compressedImages;
break;
}
case ImageCompressionOptions.DISABLE:
break;
default:
images =
images.length > 0
? (await ImagePickerDialog.show({
images
})) || []
: [];
}
const documents = files.filter((f) => !f.type.startsWith("image/"));
const attachments: Attachment[] = [];
for (const file of [...images, ...documents]) {

View File

@@ -166,7 +166,7 @@ function TipTap(props: TipTapProps) {
const tiptapOptions = useMemo<Partial<TiptapOptions>>(() => {
return {
editorProps: {
handleKeyDown(view, event) {
handleKeyDown(_, event) {
if ((event.ctrlKey || event.metaKey) && event.key === "s") {
event.preventDefault();
onChange?.(

View File

@@ -24,6 +24,13 @@ import { ThemeUIStyleObject } from "@theme-ui/css";
import { PasswordVisible, PasswordInvisible, Icon } from "../icons";
import { useStore as useThemeStore } from "../../stores/theme-store";
type Action = {
testId?: string;
onClick?: React.MouseEventHandler<HTMLButtonElement>;
disabled?: boolean;
icon?: Icon;
component?: JSX.Element;
};
export type FieldProps = InputProps & {
label?: string;
helpText?: string;
@@ -35,13 +42,9 @@ export type FieldProps = InputProps & {
label?: ThemeUIStyleObject;
helpText?: ThemeUIStyleObject;
};
action?: {
testId?: string;
onClick?: React.MouseEventHandler<HTMLButtonElement>;
disabled?: boolean;
icon?: Icon;
component?: JSX.Element;
};
action?: Action;
rightActions?: Action[];
leftActions?: Action[];
};
function Field(props: FieldProps) {
@@ -61,6 +64,8 @@ function Field(props: FieldProps) {
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
const colorScheme = useThemeStore((state) => state.colorScheme);
const [isValid, setIsValid] = useState(true);
const rightActions = props.rightActions || (action ? [action] : []);
const leftActions = props.leftActions || [];
return (
<Flex
@@ -68,6 +73,7 @@ function Field(props: FieldProps) {
m: "2px",
mr: "2px",
opacity: disabled ? 0.7 : 1,
gap: 1,
...sx,
flexDirection: "column"
}}
@@ -98,7 +104,7 @@ function Field(props: FieldProps) {
)}
</Label>
<Flex mt={1} sx={{ position: "relative" }}>
<Flex sx={{ position: "relative", flex: 1 }}>
<Input
{...inputProps}
variant={isValid ? inputProps.variant || "input" : "error"}
@@ -139,32 +145,89 @@ function Field(props: FieldProps) {
{isPasswordVisible ? <PasswordVisible /> : <PasswordInvisible />}
</Flex>
)}
{action && (
<Button
type="button"
variant={"secondary"}
data-test-id={action.testId}
onClick={action.onClick}
{rightActions.length > 0 ? (
<Flex
sx={{
bg: "transparent",
position: "absolute",
margin: 0,
top: 0,
right: "2px",
left: 0,
height: "100%",
px: 1,
borderRadius: "default",
":hover": { bg: "border" }
borderTopLeftRadius: "default",
borderBottomLeftRadius: "default",
overflow: "hidden"
}}
disabled={action.disabled}
>
{action.component ? (
action.component
) : action.icon ? (
<action.icon size={20} />
) : null}
</Button>
)}
{leftActions.map((action) => (
<Button
key={action.testId}
type="button"
variant={"secondary"}
data-test-id={action.testId}
onClick={action.onClick}
sx={{
p: 0,
px: 1,
height: "100%",
bg: "transparent",
borderRadius: 0,
margin: 0,
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
disabled={action.disabled}
>
{action.component ? (
action.component
) : action.icon ? (
<action.icon size={20} />
) : null}
</Button>
))}
</Flex>
) : null}
{rightActions.length > 0 ? (
<Flex
sx={{
position: "absolute",
top: 0,
right: 0,
height: "100%",
borderTopRightRadius: "default",
borderBottomRightRadius: "default",
overflow: "hidden"
}}
>
{rightActions.map((action) => (
<Button
key={action.testId}
type="button"
variant={"secondary"}
data-test-id={action.testId}
onClick={action.onClick}
sx={{
p: 0,
px: 1,
height: "100%",
bg: "transparent",
borderRadius: 0,
margin: 0,
display: "flex",
alignItems: "center",
justifyContent: "center"
}}
disabled={action.disabled}
>
{action.component ? (
action.component
) : action.icon ? (
<action.icon size={20} />
) : null}
</Button>
))}
</Flex>
) : null}
</Flex>
</Flex>
);

View File

@@ -18,7 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Button, Flex, FlexProps, Image, Text } from "@theme-ui/components";
import { useStore as useAppStore } from "../../stores/app-store";
import { Menu } from "../../hooks/use-menu";
import useMobile from "../../hooks/use-mobile";
import { PropsWithChildren } from "react";
@@ -27,6 +26,7 @@ import { SchemeColors, createButtonVariant } from "@notesnook/theme";
import { MenuItem } from "@notesnook/ui";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { AppEventManager, AppEvents } from "../../common/app-events";
type NavigationItemProps = {
icon?: Icon;
@@ -66,7 +66,6 @@ function NavigationItem(
containerRef,
...restProps
} = props;
const toggleSideMenu = useAppStore((store) => store.toggleSideMenu);
const isMobile = useMobile();
return (
@@ -120,7 +119,7 @@ function NavigationItem(
Menu.openMenu(menuItems);
}}
onClick={() => {
if (isMobile) toggleSideMenu(false);
AppEventManager.publish(AppEvents.toggleSideMenu, false);
if (onClick) onClick();
}}
>

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { PropsWithChildren } from "react";
import { Flex, Text } from "@theme-ui/components";
import { Button, Flex, Text } from "@theme-ui/components";
import { ArrowLeft, Menu, Search, Plus, Close } from "../icons";
import { useStore } from "../../stores/app-store";
import { useStore as useSearchStore } from "../../stores/search-store";
@@ -26,6 +26,8 @@ import useMobile from "../../hooks/use-mobile";
import { debounce, usePromise } from "@notesnook/common";
import Field from "../field";
import { strings } from "@notesnook/intl";
import { TITLE_BAR_HEIGHT } from "../title-bar";
import { AppEventManager, AppEvents } from "../../common/app-events";
export type RouteContainerButtons = {
search?: {
@@ -64,7 +66,6 @@ function Header(props: RouteContainerProps) {
() => (typeof props.title === "string" ? props.title : props.title?.()),
[props.title]
);
const toggleSideMenu = useStore((store) => store.toggleSideMenu);
const isMobile = useMobile();
const isSearching = useSearchStore((store) => store.isSearching);
const query = useSearchStore((store) => store.query);
@@ -72,16 +73,24 @@ function Header(props: RouteContainerProps) {
if (isSearching)
return (
<Flex
sx={{ alignItems: "center", justifyContent: "center", mx: 1, my: 1 }}
sx={{
alignItems: "center",
justifyContent: "center",
height: TITLE_BAR_HEIGHT,
zIndex: 2,
px: 1
}}
className="route-container-header search-container"
>
<Field
data-test-id="search-input"
autoFocus
id="search"
name="search"
variant="borderless"
type="text"
sx={{ m: 0, flex: 1 }}
styles={{ input: { p: "7px" } }}
sx={{ m: 0, flex: 1, gap: 0 }}
styles={{ input: { p: "5px", m: 0 } }}
defaultValue={query}
placeholder={strings.typeAKeyword()}
onChange={debounce(
@@ -111,11 +120,12 @@ function Header(props: RouteContainerProps) {
return (
<Flex
className="route-container-header"
mx={2}
sx={{
px: 1,
alignItems: "center",
justifyContent: "space-between",
height: 42.8
height: TITLE_BAR_HEIGHT,
zIndex: 2
}}
>
<Flex
@@ -123,65 +133,80 @@ function Header(props: RouteContainerProps) {
sx={{
alignItems: "center",
justifyContent: "center",
overflow: "hidden"
overflow: "hidden",
gap: 1
}}
>
{buttons?.back ? (
<ArrowLeft
size={24}
<Button
{...buttons.back}
sx={{ flexShrink: 0, mr: 2, cursor: "pointer" }}
data-test-id="go-back"
/>
sx={{ p: 0, flexShrink: 0 }}
>
<ArrowLeft size={20} />
</Button>
) : (
<Menu
onClick={() => toggleSideMenu(true)}
sx={{
flexShrink: 0,
ml: 0,
mr: 4,
mt: 1,
display: ["block", "none", "none"]
}}
size={30}
/>
<Button
onClick={() =>
AppEventManager.publish(AppEvents.toggleSideMenu, true)
}
sx={{ p: 0, flexShrink: 0 }}
>
<Menu
sx={{
display: ["block", "none", "none"],
size: 23
}}
size={24}
/>
</Button>
)}
{titlePromise.status === "fulfilled" && titlePromise.value && (
<Text variant="heading" data-test-id="routeHeader" color="heading">
<Text
className="routeHeader"
variant="heading"
data-test-id="routeHeader"
color="heading"
>
{titlePromise.value}
</Text>
)}
</Flex>
<Flex sx={{ flexShrink: 0 }}>
<Flex sx={{ flexShrink: 0, gap: 2 }}>
{buttons?.search && (
<Search
data-test-id={"open-search"}
size={24}
<Button
title={buttons.search.title}
onClick={() =>
useSearchStore.setState({ isSearching: true, searchType: type })
}
sx={{
size: 24,
cursor: "pointer"
}}
/>
data-test-id={"open-search"}
sx={{ p: 0 }}
>
<Search
size={24}
sx={{
size: 24
}}
/>
</Button>
)}
{!isMobile && buttons?.create && (
<Plus
data-test-id={`${type}-action-button`}
color="accentForeground"
size={18}
sx={{
bg: "accent",
ml: 2,
borderRadius: 100,
size: 28,
cursor: "pointer",
":hover": { boxShadow: "0px 0px 5px 0px var(--accent)" }
}}
<Button
{...buttons.create}
/>
data-test-id={`${type}-action-button`}
sx={{ p: 0 }}
>
<Plus
color="accentForeground"
size={18}
sx={{
height: 24,
width: 24,
bg: "accent",
borderRadius: 100
}}
/>
</Button>
)}
</Flex>
</Flex>

View File

@@ -0,0 +1,104 @@
/*
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/>.
*/
function getBEMElement(block: string, element: string) {
return `${block}__${element}`;
}
/**
* CSS class is formed as blocks or elements name plus two dashes:
* .block--mod or .block__elem--mod and .block--color-black with .block--color-red.
* Spaces in complicated modifiers are replaced by dash.
* @param blockOrElement
* @param modifier
*/
function getBEMModifier<TBlock extends string, TModifier extends string>(
blockOrElement: TBlock,
modifier: TModifier
): `${TBlock}--${TModifier}` {
return `${blockOrElement}--${modifier}`;
}
export const splitClassName = "react-split";
export const splitDragClassName = getBEMModifier(splitClassName, "dragging");
export const splitVerticalClassName = getBEMModifier(
splitClassName,
"vertical"
);
export const splitHorizontalClassName = getBEMModifier(
splitClassName,
"horizontal"
);
export const bodyDisableUserSelect = getBEMModifier(splitClassName, "disabled");
export const paneClassName = getBEMElement(splitClassName, "pane");
export const sashClassName = getBEMElement(splitClassName, "sash");
export const sashVerticalClassName = getBEMModifier(sashClassName, "vertical");
export const sashHorizontalClassName = getBEMModifier(
sashClassName,
"horizontal"
);
export const sashDisabledClassName = getBEMModifier(sashClassName, "disabled");
export const sashHoverClassName = getBEMModifier(sashClassName, "hover");
export function classNames(...args: any[]) {
const classList: string[] = [];
for (const arg of args) {
if (!arg) continue;
const argType = typeof arg;
if (argType === "string" || argType === "number") {
classList.push(`${arg}`);
continue;
}
if (argType === "object") {
if (arg.toString !== Object.prototype.toString) {
classList.push(arg.toString());
continue;
}
for (const key in arg) {
if (Object.hasOwnProperty.call(arg, key) && arg[key]) {
classList.push(key);
}
}
}
}
return classList.join(" ");
}
/**
* Convert size to absolute number or Infinity
* SplitPane allows sizes in string and number, but the state sizes only support number,
* so convert string and number to number in here
* 'auto' -> divide the remaining space equally
* 'xxxpx' -> xxx
* 'xxx%' -> wrapper.size * xxx/100
* xxx -> xxx
*/
export function assertsSize(
size: string | number | undefined,
sum: number,
defaultValue = Infinity
) {
if (typeof size === "undefined") return defaultValue;
if (typeof size === "number") return size;
if (size.endsWith("%")) return sum * (+size.replace("%", "") / 100);
if (size.endsWith("px")) return +size.replace("px", "");
return defaultValue;
}

View File

@@ -0,0 +1,432 @@
/*
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 "./styles.css";
import React, {
useEffect,
useMemo,
useCallback,
useRef,
PropsWithChildren,
useLayoutEffect,
useImperativeHandle
} from "react";
import Pane from "./pane";
import Sash from "./sash";
import {
classNames,
bodyDisableUserSelect,
paneClassName,
splitClassName,
splitDragClassName,
splitVerticalClassName,
splitHorizontalClassName,
sashDisabledClassName,
sashHorizontalClassName,
sashVerticalClassName,
assertsSize
} from "./base";
import { IAxis, ISplitProps, IPaneConfigs } from "./types";
import Config from "../../utils/config";
export { Pane };
type PaneOptions = {
min: number;
max: number;
snap: number;
size: number;
nextSize?: number;
initialSize: number;
collapsed: boolean;
};
export type SplitPaneImperativeHandle = {
collapse: (index: number) => void;
expand: (index: number) => void;
};
export const SplitPane = React.forwardRef<
SplitPaneImperativeHandle,
PropsWithChildren<ISplitProps>
>(function SplitPane(
{
children,
allowResize = true,
direction = "vertical",
className: wrapClassName,
sashStyle,
sashRender = (_, active) => (
<div
className={classNames(
"split-sash-content",
active && "split-sash-content-active"
)}
/>
),
sashSize: resizerSize = 5,
onChange = () => null,
onDragStart = () => null,
onDragEnd = () => null,
autoSaveId,
...others
},
ref
) {
const axis = useRef<IAxis>({ x: 0, y: 0 });
const wrapper = useRef<HTMLDivElement>(null);
const sashPosSizes = useRef<number[]>([]);
const panes = useRef<(HTMLDivElement | null)[]>([]);
const sashes = useRef<(HTMLDivElement | null)[]>([]);
const paneSizes = useRef<PaneOptions[]>([]);
const wrapSize = useRef(0);
const childrenLength = childrenToArray(children).length;
const autoSaveKey = autoSaveId ? `csp:${autoSaveId}` : undefined;
const { sizeName, splitPos, splitAxis } = useMemo(
() =>
({
sizeName: direction === "vertical" ? "width" : "height",
splitPos: direction === "vertical" ? "left" : "top",
splitAxis: direction === "vertical" ? "x" : "y"
} as const),
[direction]
);
const updatePaneLimitSizes = useCallback(
(children: React.ReactNode) => {
paneSizes.current =
childrenToArray(children).map((childNode) => {
const limits: PaneOptions = {
min: 0,
max: Infinity,
snap: 0,
size: Infinity,
initialSize: Infinity,
collapsed: false
};
if (React.isValidElement(childNode) && childNode.type === Pane) {
const { minSize, maxSize, snapSize, initialSize, id, collapsed } =
childNode.props as IPaneConfigs;
limits.min = assertsSize(minSize, wrapSize.current, 0);
limits.max = assertsSize(maxSize, wrapSize.current);
limits.snap = assertsSize(snapSize, wrapSize.current, 0);
limits.initialSize = assertsSize(initialSize, wrapSize.current);
Object.defineProperty(limits, "collapsed", {
get() {
return Config.get(`${autoSaveKey}-${id}:collapsed`, collapsed);
},
set(v) {
if (v == null) Config.remove(`${autoSaveKey}-${id}:collapsed`);
else Config.set(`${autoSaveKey}-${id}:collapsed`, v);
}
});
Object.defineProperty(limits, "size", {
get() {
return Config.get(
`${autoSaveKey}-${id}`,
assertsSize(initialSize, wrapSize.current)
);
},
set(v) {
if (v === null || v === undefined || v === Infinity)
Config.remove(`${autoSaveKey}-${id}`);
else Config.set(`${autoSaveKey}-${id}`, v);
}
});
}
return limits;
}) || [];
},
[autoSaveKey]
);
useLayoutEffect(() => {
if (wrapSize.current === 0) {
wrapSize.current =
wrapper.current?.getBoundingClientRect()[sizeName] || 0;
}
if (wrapSize.current === 0) return;
updatePaneLimitSizes(children);
setSizes(paneSizes.current, wrapSize.current, false);
}, [children, childrenLength]);
const setSizes = useCallback(
function setSizes(
paneLimits: PaneOptions[],
wrapSize: number,
notify = true
) {
const normalized = normalizeSizes(children, paneLimits, wrapSize);
sashPosSizes.current = normalized.reduce(
(a, b) => [...a, a[a.length - 1] + b],
[0]
);
for (let i = 0; i < panes.current.length; ++i) {
const pane = panes.current[i];
if (!pane) continue;
const size = normalized[i];
const sashPos = sashPosSizes.current[i];
const limits = paneSizes.current[i];
pane.style[sizeName] = `${size}px`;
pane.style[splitPos] = `${sashPos}px`;
if (limits.collapsed || size === limits.min)
pane.classList.add("collapsed");
else pane.classList.remove("collapsed");
}
for (let i = 0; i < sashes.current.length; ++i) {
const sash = sashes.current[i];
const sashPos = sashPosSizes.current[i + 1];
if (sash) {
sash.style[splitPos] = `${sashPos - resizerSize / 2}px`;
}
}
paneSizes.current.forEach((limits, index) => {
limits.size = normalized[index];
});
if (notify) onChange(normalized);
},
[children, onChange, sizeName, splitPos, resizerSize]
);
useEffect(() => {
if (!wrapper.current) return;
const resizeObserver = new ResizeObserver((entries) => {
if (!paneSizes.current.length) return;
const [entry] = entries;
const newSize = entry.contentRect ? entry.contentRect[sizeName] : 0;
const delta = newSize - wrapSize.current;
if (delta === 0) return;
// TODO: responsiveness
// const nextSizes = [...sizes.current];
// const i = sizes.current.length - 1;
// const currentSize = sizes.current[i];
// const currentPaneLimits = paneLimitSizes.current[i];
// const prevPaneLimits = paneLimitSizes.current[i - 1];
// if (
// delta > 0 &&
// prevPaneLimits &&
// prevPaneLimits.max < Infinity &&
// nextSizes[i - 1] + delta <= prevPaneLimits.max
// ) {
// nextSizes[i - 1] += delta;
// } else if (delta < 0 && currentSize + delta <= currentPaneLimits.min) {
// nextSizes[i - 1] += delta;
// } else {
// nextSizes[i] += delta;
// }
wrapSize.current = newSize;
setSizes(paneSizes.current, wrapSize.current);
});
resizeObserver.observe(wrapper.current);
return () => {
resizeObserver.disconnect();
};
}, [sizeName, setSizes]);
useImperativeHandle(
ref,
() => {
return {
collapse: (index: number) => {
paneSizes.current[index].collapsed = true;
setSizes(paneSizes.current, wrapSize.current);
},
expand: (index: number) => {
paneSizes.current[index].collapsed = false;
setSizes(paneSizes.current, wrapSize.current);
}
};
},
[setSizes]
);
const dragStart = useCallback(
function (e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
document?.body?.classList?.add(bodyDisableUserSelect);
axis.current = { x: e.pageX, y: e.pageY };
wrapper.current?.classList.toggle(splitDragClassName, true);
onDragStart(e);
},
[onDragStart]
);
const dragEnd = useCallback(
function (e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
document?.body?.classList?.remove(bodyDisableUserSelect);
wrapper.current?.classList.toggle(splitDragClassName);
onDragEnd(e);
},
[onDragEnd]
);
const onDragging = useCallback(
function onDragging(
e: React.MouseEvent<HTMLDivElement, MouseEvent>,
i: number
) {
const curAxis = { x: e.pageX, y: e.pageY };
let distanceX = curAxis[splitAxis] - axis.current[splitAxis];
axis.current = { x: e.pageX, y: e.pageY };
const currentPane = paneSizes.current[i];
const nextPane = paneSizes.current[i + 1];
const rightBorder = sashPosSizes.current[i + 2];
if (currentPane.size + distanceX >= rightBorder)
distanceX = rightBorder - currentPane.size;
// if current pane size is out of limit, adjust the previous pane
if (
currentPane.size + distanceX >= currentPane.max ||
currentPane.size + distanceX <= currentPane.min
) {
if (i > 0) {
// reset axis
axis.current[splitAxis] += -distanceX;
onDragging(e, i - 1);
}
return;
}
currentPane.nextSize =
(currentPane.nextSize || currentPane.size) + distanceX;
// keep the next pane size in the min-max range
nextPane.nextSize = Math.min(
nextPane.max,
Math.max(nextPane.min, (nextPane.nextSize || nextPane.size) - distanceX)
);
// snapping logic
if (currentPane.snap > 0) {
if (distanceX < 0 && currentPane.nextSize <= currentPane.snap / 2) {
currentPane.nextSize = currentPane.min;
} else if (currentPane.nextSize < currentPane.snap) {
// reset axis
axis.current[splitAxis] += -distanceX;
return;
}
}
nextPane.size = nextPane.nextSize;
currentPane.size = currentPane.nextSize;
nextPane.nextSize = undefined;
currentPane.nextSize = undefined;
setSizes(paneSizes.current, wrapSize.current);
},
[paneSizes, setSizes, splitAxis]
);
return (
<div
className={classNames(
splitClassName,
direction === "vertical" && splitVerticalClassName,
direction === "horizontal" && splitHorizontalClassName,
wrapClassName
)}
ref={wrapper}
{...others}
>
{childrenToArray(children).map((childNode, childIndex) => {
const isPane = React.isValidElement(childNode)
? childNode.type === Pane
: false;
const paneProps =
isPane && React.isValidElement(childNode) ? childNode.props : {};
return (
<Pane
id={paneProps.id}
key={childIndex}
paneRef={(e) => (panes.current[childIndex] = e)}
className={classNames(paneClassName, paneProps.className)}
style={{ ...paneProps.style }}
>
{isPane ? paneProps.children : childNode}
</Pane>
);
})}
{new Array(childrenLength - 1).fill(0).map((_, index) => (
<Sash
key={index}
sashRef={(e) => (sashes.current[index] = e)}
className={classNames(
!allowResize && sashDisabledClassName,
direction === "vertical"
? sashVerticalClassName
: sashHorizontalClassName
)}
style={{
[sizeName]: resizerSize,
...sashStyle
}}
render={sashRender.bind(null, index)}
onDragStart={dragStart}
onDragging={(e) => onDragging(e, index)}
onDragEnd={dragEnd}
onDoubleClick={() => {
paneSizes.current[index].size =
paneSizes.current[index].initialSize;
setSizes(paneSizes.current, wrapSize.current);
}}
/>
))}
</div>
);
});
function childrenToArray(children: React.ReactNode) {
return React.Children.toArray(children);
}
function normalizeSizes(
children: React.ReactNode,
panes: PaneOptions[],
wrapSize: number
): number[] {
let count = 0;
let curSum = 0;
const res = childrenToArray(children).map((_, index) => {
const initialSize = panes[index].initialSize;
const size = panes[index].collapsed ? panes[index].min : panes[index].size;
initialSize === Infinity ? count++ : (curSum += size);
return size;
});
if (count > 0 || curSum > wrapSize) {
const average = (wrapSize - curSum) / count;
return res.map((size, index) => {
const initialSize = panes[index].initialSize;
return initialSize === Infinity ? average : size;
});
}
return res;
}

View File

@@ -0,0 +1,44 @@
/*
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 { PropsWithChildren } from "react";
import { HTMLElementProps, IPaneConfigs } from "./types";
export default function Pane({
paneRef,
children,
style,
className,
role,
title,
id
}: PropsWithChildren<HTMLElementProps & IPaneConfigs>) {
return (
<div
id={id}
ref={paneRef}
role={role}
title={title}
className={className}
style={style}
>
{children}
</div>
);
}

View 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 { useRef, useState } from "react";
import { classNames, sashClassName } from "./base";
import { ISashProps } from "./types";
export default function Sash({
className,
render,
onDragStart,
onDragging,
onDragEnd,
sashRef,
...others
}: ISashProps) {
const timeout = useRef<number | null>(null);
const [active, setActive] = useState(false);
const [draging, setDrag] = useState(false);
const handleMouseMove = function (e: MouseEvent) {
onDragging(e as any);
};
const handleMouseUp = function (e: MouseEvent) {
setDrag(false);
onDragEnd(e as any);
window.removeEventListener("mousemove", handleMouseMove);
window.removeEventListener("mouseup", handleMouseUp);
};
return (
<div
ref={sashRef}
role="Resizer"
className={classNames(sashClassName, className)}
onMouseEnter={() => {
timeout.current = setTimeout(() => {
setActive(true);
}, 150) as unknown as number;
}}
onMouseLeave={() => {
if (timeout.current) {
setActive(false);
clearTimeout(timeout.current);
}
}}
onMouseDown={(e) => {
setDrag(true);
onDragStart(e);
window.addEventListener("mousemove", handleMouseMove);
window.addEventListener("mouseup", handleMouseUp);
}}
{...others}
>
{render(draging || active)}
</div>
);
}

View File

@@ -0,0 +1,59 @@
.react-split {
flex: 1;
height: 100%;
position: relative;
width: 100%;
}
.react-split__pane {
height: 100%;
position: absolute;
white-space: normal;
width: 100%;
overflow: hidden;
}
.react-split__sash {
height: 100%;
position: absolute;
bottom: 0;
transition: background-color 0.1s;
width: 100%;
z-index: 2;
}
.react-split__sash--disabled {
pointer-events: none;
}
.react-split__sash--vertical {
cursor: col-resize;
}
.react-split__sash--horizontal {
cursor: row-resize;
}
.react-split__sash-content {
width: 100%;
height: 100%;
}
.react-split--dragging.react-split--vertical {
cursor: col-resize;
}
.react-split--dragging.react-split--horizontal {
cursor: row-resize;
}
body.react-split--disabled {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.split-sash-content {
width: 100%;
height: 100%;
}
.split-sash-content.split-sash-content-active {
background-color: var(--accent);
}

View File

@@ -0,0 +1,78 @@
/*
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 React from "react";
export interface HTMLElementProps {
title?: string;
style?: React.CSSProperties;
className?: string;
role?: string;
}
export interface IAxis {
x: number;
y: number;
}
export interface ICacheSizes {
sizes: (string | number)[];
sashPosSizes: (string | number)[];
}
export interface ISplitProps extends HTMLElementProps {
autoSaveId?: string;
allowResize?: boolean;
direction: "vertical" | "horizontal";
sashRender?: (index: number, active: boolean) => React.ReactNode;
onChange?: (sizes: number[]) => void;
onDragStart?: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
onDragEnd?: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
className?: string;
sashClassName?: string;
sashStyle?: React.CSSProperties;
sashSize?: number;
}
export interface ISashProps {
sashRef?: React.LegacyRef<HTMLDivElement>;
className?: string;
style: React.CSSProperties;
render: (active: boolean) => React.ReactNode;
onDragStart: React.MouseEventHandler<HTMLDivElement>;
onDragging: React.MouseEventHandler<HTMLDivElement>;
onDragEnd: React.MouseEventHandler<HTMLDivElement>;
onDoubleClick: React.MouseEventHandler<HTMLDivElement>;
}
export interface ISashContentProps {
className?: string;
active?: boolean;
children?: JSX.Element[];
}
export interface IPaneConfigs {
id: string;
paneRef?: React.LegacyRef<HTMLDivElement>;
maxSize?: number | string;
minSize?: number | string;
snapSize?: number | string;
initialSize?: number | string;
collapsed?: boolean;
}

View File

@@ -61,6 +61,7 @@ function StatusBar() {
borderTopColor: "separator",
justifyContent: "space-between",
display: ["none", "none", "flex"],
flexShrink: 0,
height: 24
}}
px={2}
@@ -174,6 +175,7 @@ function StatusBar() {
justifyContent: "center",
display: "flex"
}}
data-test-id="vault-unlocked"
>
<Unlock size={10} />
<Text variant="subBody" ml={1} sx={{ color: "paragraph" }}>

View File

@@ -17,25 +17,30 @@ 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 { Button } from "@theme-ui/components";
import { desktop } from "../../common/desktop-bridge";
import { useWindowControls } from "../../hooks/use-window-controls";
import { getPlatform } from "../../utils/platform";
import { isMac } from "../../utils/platform";
import { BaseThemeProvider } from "../theme-provider";
import { strings } from "@notesnook/intl";
import { desktop } from "../../common/desktop-bridge";
import {
WindowClose,
WindowMaximize,
WindowMinimize,
WindowRestore
} from "../icons";
import { BaseThemeProvider } from "../theme-provider";
import { strings } from "@notesnook/intl";
import { Button, Flex } from "@theme-ui/components";
import useMobile from "../../hooks/use-mobile";
import useTablet from "../../hooks/use-tablet";
export const TITLE_BAR_HEIGHT = IS_DESKTOP_APP ? 37.8 : 0;
export function TitleBar() {
const { isMaximized, isFullscreen, hasNativeWindowControls } =
useWindowControls();
const tools = [
export function getWindowControls(
hasNativeWindowControls: boolean,
isFullscreen?: boolean,
isMaximized?: boolean,
isTablet?: boolean,
isMobile?: boolean
) {
if (isMobile || isTablet) return [];
return [
{
title: strings.minimize(),
icon: WindowMinimize,
@@ -61,27 +66,46 @@ export function TitleBar() {
onClick: () => window.close()
}
];
}
export const TITLE_BAR_HEIGHT = 37;
export function TitleBar({ isUnderlay = isMac() }: { isUnderlay?: boolean }) {
const { isFullscreen, hasNativeWindowControls, isMaximized } =
useWindowControls();
const isTablet = useTablet();
const isMobile = useMobile();
if ((!isMac() && !isMobile && !isTablet) || (isFullscreen && isMac()))
return null;
const tools = getWindowControls(
hasNativeWindowControls,
isFullscreen,
isMaximized
);
return (
<BaseThemeProvider
scope="titleBar"
className="titlebar"
sx={{
background: "background",
height: TITLE_BAR_HEIGHT,
minHeight: TITLE_BAR_HEIGHT,
maxHeight: TITLE_BAR_HEIGHT,
display: "flex",
justifyContent: "space-between",
flexShrink: 0,
width: "100%",
zIndex: 1,
borderBottom: "1px solid var(--border)",
...(!isFullscreen && hasNativeWindowControls
? getPlatform() === "darwin"
? { pl: "calc(100vw - env(titlebar-area-width))" }
: { pr: "calc(100vw - env(titlebar-area-width))" }
: { pr: 0 })
...(isUnderlay
? {
position: "absolute",
top: 0
}
: {})
}}
injectCssVars
>
{getPlatform() !== "darwin" || isFullscreen ? (
{tools.filter((t) => !t.hidden).length > 0 ? (
<svg
className="titlebarLogo"
style={{
@@ -95,40 +119,34 @@ export function TitleBar() {
<use href="#themed-logo" />
</svg>
) : null}
<div
id="titlebar-portal-container"
style={{
flex: 1,
display: "flex",
overflow: "hidden"
}}
/>
{tools.map((tool) => (
<Button
data-test-id={tool.title}
disabled={!tool.enabled}
variant={tool.title === "Close" ? "error" : "secondary"}
title={tool.title}
key={tool.title}
sx={{
height: "100%",
alignItems: "center",
bg: "transparent",
display: tool.hidden ? "none" : "flex",
borderRadius: 0,
flexShrink: 0,
"&:hover svg path": {
fill:
tool.title === "Close"
? "var(--accentForeground-error) !important"
: "var(--icon)"
}
}}
onClick={tool.onClick}
>
<tool.icon size={18} />
</Button>
))}
<Flex sx={{ alignItems: "center" }}>
{tools.map((tool) => (
<Button
data-test-id={tool.title}
disabled={!tool.enabled}
variant={tool.title === "Close" ? "error" : "secondary"}
title={tool.title}
key={tool.title}
sx={{
height: "100%",
alignItems: "center",
bg: "transparent",
display: tool.hidden ? "none" : "flex",
borderRadius: 0,
flexShrink: 0,
"&:hover svg path": {
fill:
tool.title === "Close"
? "var(--accentForeground-error) !important"
: "var(--icon)"
}
}}
onClick={tool.onClick}
>
<tool.icon size={18} />
</Button>
))}
</Flex>
</BaseThemeProvider>
);
}

View File

@@ -22,24 +22,13 @@ import Dialog from "../components/dialog";
import { ScrollContainer } from "@notesnook/ui";
import { Flex, Image, Label, Text } from "@theme-ui/components";
import { formatBytes } from "@notesnook/common";
import { compressImage } from "../utils/image-compressor";
import { compressImage, FileWithURI } from "../utils/image-compressor";
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
import { strings } from "@notesnook/intl";
export type ImagePickerDialogProps = BaseDialogProps<false | File[]> & {
images: File[];
};
class FileWithURI extends File {
uri: string;
constructor(
fileBits: BlobPart[],
fileName: string,
options?: FilePropertyBag
) {
super(fileBits, fileName, options);
this.uri = URL.createObjectURL(this);
}
}
export const ImagePickerDialog = DialogManager.register(
function ImagePickerDialog(props: ImagePickerDialogProps) {

View File

@@ -19,7 +19,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { DATE_FORMATS } from "@notesnook/core";
import { SettingsGroup } from "./types";
import { useStore as useSettingStore } from "../../stores/setting-store";
import {
ImageCompressionOptions,
useStore as useSettingStore
} from "../../stores/setting-store";
import dayjs from "dayjs";
import { isUserPremium } from "../../hooks/use-is-user-premium";
import { TimeFormat } from "@notesnook/core";
@@ -55,6 +58,37 @@ export const BehaviourSettings: SettingsGroup[] = [
]
}
]
},
{
key: "image-compression",
title: strings.imageCompression(),
description: strings.imageCompressionDesc(),
keywords: ["compress images", "image quality"],
onStateChange: (listener) =>
useSettingStore.subscribe((s) => s.imageCompression, listener),
components: [
{
type: "dropdown",
onSelectionChanged: (value) =>
useSettingStore.getState().setImageCompression(parseInt(value)),
selectedOption: () =>
useSettingStore.getState().imageCompression.toString(),
options: [
{
value: ImageCompressionOptions.ASK_EVERY_TIME.toString(),
title: strings.askEveryTime()
},
{
value: ImageCompressionOptions.ENABLE.toString(),
title: strings.enableRecommended()
},
{
value: ImageCompressionOptions.DISABLE.toString(),
title: strings.disable()
}
]
}
]
}
]
},

View File

@@ -26,27 +26,24 @@ type Slide = {
width: number;
};
export default function useSlider(
sliderId: string,
{
onSliding,
onChange
}: {
onSliding?: (
e: Event,
options: {
lastSlide: Slide | null;
lastPosition: number;
position: number;
}
) => void;
onChange?: (
e: Event,
options: { position: number; slide: Slide; lastSlide: Slide | null }
) => void;
} = {}
) {
const ref = useRef(document.getElementById(sliderId));
export default function useSlider({
onSliding,
onChange
}: {
onSliding?: (
e: Event,
options: {
lastSlide: Slide | null;
lastPosition: number;
position: number;
}
) => void;
onChange?: (
e: Event,
options: { position: number; slide: Slide; lastSlide: Slide | null }
) => void;
} = {}) {
const ref = useRef<HTMLDivElement>(null);
const slides: Slide[] = useMemo(() => [], []);
useEffect(() => {
@@ -57,7 +54,11 @@ export default function useSlider(
let last = 0;
if (!slides.length) {
for (const node of slider.childNodes) {
if (!(node instanceof HTMLElement)) continue;
if (
!(node instanceof HTMLElement) ||
node.classList.contains("ms-track-box")
)
continue;
slides.push({
index: slides.length,
node,
@@ -102,5 +103,5 @@ export default function useSlider(
[ref, slides]
);
return [slideToIndex];
return { ref, slideToIndex };
}

View File

@@ -37,7 +37,6 @@ export function useWindowControls() {
function onFullscreenChange() {
setIsFullscreen(!!document.fullscreenElement);
console.log(document.fullscreenElement);
}
document.addEventListener("fullscreenchange", onFullscreenChange);
return () => {

View File

@@ -0,0 +1,43 @@
/*
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 { useEffect, useState } from "react";
export function useWindowFocus() {
const [isFocused, setIsFocused] = useState<boolean>(true);
useEffect(() => {
function onFocus() {
setIsFocused(true);
}
function onBlur() {
setIsFocused(false);
}
window.addEventListener("focus", onFocus);
window.addEventListener("blur", onBlur);
return () => {
document.removeEventListener("focus", onFocus);
document.removeEventListener("blur", onBlur);
};
}, []);
return {
isFocused
};
}

View File

@@ -81,7 +81,7 @@ export async function startApp() {
console.error(e);
root.render(
<>
<TitleBar />
<TitleBar isUnderlay={false} />
<ErrorComponent
error={e}
resetErrorBoundary={() => window.location.reload()}

View File

@@ -61,10 +61,7 @@ let syncTimeout = 0;
let pendingSync: SyncOptions | undefined = undefined;
class AppStore extends BaseStore<AppStore> {
// default state
isSideMenuOpen = false;
isFocusMode = false;
isEditorOpen = false;
isVaultCreated = false;
isAutoSyncEnabled = Config.get("autoSyncEnabled", true);
isSyncEnabled = Config.get("syncEnabled", true);
@@ -199,17 +196,6 @@ class AppStore extends BaseStore<AppStore> {
);
};
toggleSideMenu = (toggleState: boolean) => {
console.log("toggling side menu");
this.set(
(state) => (state.isSideMenuOpen = toggleState ?? !state.isSideMenuOpen)
);
};
setIsEditorOpen = (toggleState: boolean) => {
this.set((state) => (state.isEditorOpen = toggleState));
};
setIsVaultCreated = (toggleState: boolean) => {
this.set((state) => (state.isVaultCreated = toggleState));
};

View File

@@ -518,7 +518,7 @@ class EditorStore extends BaseStore<EditorStore> {
} else setDocumentTitle();
this.set({ activeSessionId: id });
appStore.setIsEditorOpen(!!id);
AppEventManager.publish(AppEvents.toggleEditor, true);
if (id) {
const { history } = this.get();
@@ -717,6 +717,32 @@ class EditorStore extends BaseStore<EditorStore> {
}
};
openNextSession = () => {
const { sessions, activeSessionId } = this.get();
if (sessions.length === 0 || sessions.length === 1) return;
const index = sessions.findIndex((s) => s.id === activeSessionId);
if (index === -1) return;
if (index === sessions.length - 1) {
return this.openSession(sessions[0].id);
}
return this.openSession(sessions[index + 1].id);
};
openPreviousSession = () => {
const { sessions, activeSessionId } = this.get();
if (sessions.length === 0 || sessions.length === 1) return;
const index = sessions.findIndex((s) => s.id === activeSessionId);
if (index === -1) return;
if (index === 0) {
return this.openSession(sessions[sessions.length - 1].id);
}
return this.openSession(sessions[index - 1].id);
};
addSession = (session: EditorSession, activate = true) => {
let oldSessionId: string | null = null;

View File

@@ -35,6 +35,13 @@ export const HostIds = [
"MONOGRAPH_HOST"
] as const;
export type HostId = (typeof HostIds)[number];
export enum ImageCompressionOptions {
ASK_EVERY_TIME,
ENABLE,
DISABLE
}
class SettingStore extends BaseStore<SettingStore> {
encryptBackups = Config.get("encryptBackups", false);
backupReminderOffset = Config.get("backupReminderOffset", 0);
@@ -60,6 +67,10 @@ class SettingStore extends BaseStore<SettingStore> {
trashCleanupInterval: TrashCleanupInterval = 7;
homepage = Config.get("homepage", 0);
imageCompression = Config.get(
"imageCompression",
ImageCompressionOptions.ASK_EVERY_TIME
);
desktopIntegrationSettings?: DesktopIntegration;
autoUpdates = true;
isFlatpak = false;
@@ -125,6 +136,11 @@ class SettingStore extends BaseStore<SettingStore> {
Config.set("homepage", homepage);
};
setImageCompression = (imageCompression: ImageCompressionOptions) => {
this.set({ imageCompression });
Config.set("imageCompression", imageCompression);
};
setDesktopIntegration = async (settings: DesktopIntegration) => {
const { desktopIntegrationSettings } = this.get();

View File

@@ -17,6 +17,18 @@ 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 class FileWithURI extends File {
uri: string;
constructor(
fileBits: BlobPart[],
fileName: string,
options?: FilePropertyBag
) {
super(fileBits, fileName, options);
this.uri = URL.createObjectURL(this);
}
}
type DeriveDimension = (naturalWidth: number, naturalHeight: number) => number;
interface CompressorOptions {

View File

@@ -39,12 +39,6 @@ import SubNotebook from "../components/sub-notebook";
import { NotebookContext } from "../components/list-container/types";
import { Menu } from "../hooks/use-menu";
import Notes from "./notes";
import {
PanelGroup,
Panel,
PanelResizeHandle,
ImperativePanelHandle
} from "react-resizable-panels";
import { AddNotebookDialog } from "../dialogs/add-notebook-dialog";
import { strings } from "@notesnook/intl";
import { Notebook as NotebookType } from "@notesnook/core";
@@ -54,6 +48,11 @@ import {
VirtualizedTree,
VirtualizedTreeHandle
} from "../components/virtualized-tree";
import {
Pane,
SplitPane,
SplitPaneImperativeHandle
} from "../components/split-pane";
type NotebookProps = {
rootId: string;
@@ -63,7 +62,7 @@ function Notebook(props: NotebookProps) {
const { rootId, notebookId } = props;
const [isCollapsed, setIsCollapsed] = useState(false);
const subNotebooksPane = useRef<ImperativePanelHandle>(null);
const pane = useRef<SplitPaneImperativeHandle>(null);
const context = useNotesStore((store) => store.context);
const notes = useNotesStore((store) => store.contextNotes);
@@ -93,11 +92,19 @@ function Notebook(props: NotebookProps) {
if (!context || !notes || context.type !== "notebook") return null;
return (
<PanelGroup
direction="vertical"
<SplitPane
ref={pane}
direction="horizontal"
autoSaveId={`notebook-panel-sizes:${rootId}`}
onChange={([_, subnotebooksPane]) => {
setIsCollapsed((isCollapsed) => {
if (subnotebooksPane <= 34 && !isCollapsed) return true;
else if (subnotebooksPane >= 35 && isCollapsed) return false;
return isCollapsed;
});
}}
>
<Panel style={{ display: "flex" }}>
<Pane id="notes-pane" style={{ display: "flex" }}>
<Notes
header={
<NotebookHeader
@@ -107,26 +114,18 @@ function Notebook(props: NotebookProps) {
/>
}
/>
</Panel>
<PanelResizeHandle className="panel-resize-handle" />
<Panel
ref={subNotebooksPane}
defaultSize={25}
collapsedSize={7}
collapsible
minSize={7}
onResize={(size) => setIsCollapsed(size <= 7)}
>
</Pane>
<Pane id="subnotebooks-pane" initialSize={250} minSize={30}>
<SubNotebooks
isCollapsed={isCollapsed}
rootId={rootId}
onClick={() => {
if (isCollapsed) subNotebooksPane.current?.expand();
else subNotebooksPane.current?.collapse();
if (isCollapsed) pane.current?.expand(1);
else pane.current?.collapse(1);
}}
/>
</Panel>
</PanelGroup>
</Pane>
</SplitPane>
);
}
export default Notebook;

View File

@@ -0,0 +1,5 @@
- You can now share multiple files to Notesnook
- Fix file and image sharing not working
- Many other bug fixes and small improvements
Thank you for using Notesnook!

View File

@@ -0,0 +1,5 @@
- You can now share multiple files to Notesnook
- Fix file and image sharing not working
- Many other bug fixes and small improvements
Thank you for using Notesnook!

View File

@@ -189,7 +189,7 @@ class Database {
tokenManager = new TokenManager(this.kv);
mfa = new MFAManager(this.tokenManager);
subscriptions = new Subscriptions(this.tokenManager);
offers = new Offers();
offers = Offers;
debug = new Debug();
pricing = Pricing;

View File

@@ -65,7 +65,7 @@ export default class Lookup {
eb
.selectFrom("content_fts")
.$if(!!notes, (eb) =>
eb.where("id", "in", notes!.filter.select("id"))
eb.where("noteId", "in", notes!.filter.select("id"))
)
.$if(excludedIds.length > 0, (eb) =>
eb.where("id", "not in", excludedIds)

View File

@@ -48,6 +48,7 @@ export default class Vault {
}
private startEraser() {
EV.publish(EVENTS.vaultUnlocked);
clearTimeout(this.erasureTimeout);
this.erasureTimeout = setTimeout(() => {
this.lock();
@@ -95,7 +96,6 @@ export default class Vault {
throw new Error(VAULT_ERRORS.wrongPassword);
}
this.password = password;
EV.publish(EVENTS.vaultUnlocked);
return true;
}

View File

@@ -4441,7 +4441,7 @@
"version": "15.7.11",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.11.tgz",
"integrity": "sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==",
"dev": true
"devOptional": true
},
"node_modules/@types/q": {
"version": "1.5.8",
@@ -4465,7 +4465,7 @@
"version": "18.2.39",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.39.tgz",
"integrity": "sha512-Oiw+ppED6IremMInLV4HXGbfbG6GyziY3kqAwJYOR0PNbkYDmLWQA3a95EhdSmamsvbkJN96ZNN+YD+fGjzSBA==",
"dev": true,
"devOptional": true,
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -4500,7 +4500,7 @@
"version": "0.16.8",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
"dev": true
"devOptional": true
},
"node_modules/@types/semver": {
"version": "7.5.6",
@@ -9762,7 +9762,7 @@
"version": "9.0.21",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
"integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
"dev": true,
"devOptional": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
@@ -17856,6 +17856,21 @@
"is-typedarray": "^1.0.0"
}
},
"node_modules/typescript": {
"version": "4.9.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
},
"node_modules/unbox-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",

View File

@@ -269,6 +269,7 @@ const Tiptap = ({
? []
: getTableOfContents(containerRef.current);
},
scrollTop: () => containerRef.current?.scrollTop || 0,
scrollTo: (top) => {
containerRef.current?.scrollTo({ top, behavior: "auto" });
}

View File

@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Editor, scrollIntoViewById } from "@notesnook/editor";
import { strings } from "@notesnook/intl";
import {
ThemeDefinition,
useThemeColors,
@@ -41,7 +42,6 @@ import {
import { injectCss, transform } from "../utils/css";
import { pendingSaveRequests } from "../utils/pending-saves";
import { useTabContext, useTabStore } from "./useTabStore";
import { strings } from "@notesnook/intl";
type Attachment = {
hash: string;
@@ -126,11 +126,13 @@ export type EditorController = {
export function useEditorController({
update,
getTableOfContents,
scrollTo
scrollTo,
scrollTop
}: {
update: () => void;
getTableOfContents: () => any[];
scrollTo: (top: number) => void;
scrollTop: () => number;
}): EditorController {
const passwordInputRef = useRef<HTMLInputElement | null>(null);
const tab = useTabContext();
@@ -342,30 +344,28 @@ export function useEditorController({
updateTabOnFocus.current = true;
} else {
if (!editor) break;
const noteState = tabRef.current?.noteId
? useTabStore.getState().noteState[tabRef.current?.noteId]
: null;
const top = scrollTop() || noteState?.top || 0;
editor?.commands.setContent(htmlContentRef.current, false, {
preserveWhitespace: true
});
if (noteState) {
if (noteState && editor.isFocused) {
editor.commands.setTextSelection({
from: noteState.from,
to: noteState.to
});
}
scrollTo?.(noteState?.top || 0);
scrollTo?.(top || 0);
countWords(0);
}
break;
}
case "native:html":
if (htmlContentRef.current === value) break;
htmlContentRef.current = value;
logger("info", "LOADING NOTE HTML");
if (!editor) break;

File diff suppressed because it is too large Load Diff

View File

@@ -35,33 +35,33 @@
"@notesnook/theme": "file:../theme",
"@notesnook/ui": "file:../ui",
"@social-embed/lib": "^0.1.0-next.7",
"@tiptap/core": "2.6.6",
"@tiptap/extension-blockquote": "^2.6.6",
"@tiptap/extension-bullet-list": "^2.6.6",
"@tiptap/extension-character-count": "2.6.6",
"@tiptap/extension-code": "^2.6.6",
"@tiptap/extension-color": "2.6.6",
"@tiptap/extension-font-family": "2.6.6",
"@tiptap/extension-heading": "^2.6.6",
"@tiptap/extension-history": "2.6.6",
"@tiptap/extension-horizontal-rule": "2.6.6",
"@tiptap/extension-list-item": "^2.6.6",
"@tiptap/extension-list-keymap": "2.6.6",
"@tiptap/extension-ordered-list": "^2.6.6",
"@tiptap/extension-placeholder": "2.6.6",
"@tiptap/extension-subscript": "2.6.6",
"@tiptap/extension-superscript": "2.6.6",
"@tiptap/extension-table": "2.6.6",
"@tiptap/extension-table-cell": "2.6.6",
"@tiptap/extension-table-header": "2.6.6",
"@tiptap/extension-table-row": "2.6.6",
"@tiptap/extension-task-item": "2.6.6",
"@tiptap/extension-task-list": "2.6.6",
"@tiptap/extension-text-align": "2.6.6",
"@tiptap/extension-text-style": "2.6.6",
"@tiptap/extension-underline": "2.6.6",
"@tiptap/pm": "2.6.6",
"@tiptap/starter-kit": "2.6.6",
"@tiptap/core": "^2.10.4",
"@tiptap/extension-blockquote": "2.10.4",
"@tiptap/extension-bullet-list": "2.10.4",
"@tiptap/extension-character-count": "2.10.4",
"@tiptap/extension-code": "2.10.4",
"@tiptap/extension-color": "2.10.4",
"@tiptap/extension-font-family": "2.10.4",
"@tiptap/extension-heading": "2.10.4",
"@tiptap/extension-history": "2.10.4",
"@tiptap/extension-horizontal-rule": "2.10.4",
"@tiptap/extension-list-item": "2.10.4",
"@tiptap/extension-list-keymap": "2.10.4",
"@tiptap/extension-ordered-list": "2.10.4",
"@tiptap/extension-placeholder": "2.10.4",
"@tiptap/extension-subscript": "2.10.4",
"@tiptap/extension-superscript": "2.10.4",
"@tiptap/extension-table": "2.10.4",
"@tiptap/extension-table-cell": "2.10.4",
"@tiptap/extension-table-header": "2.10.4",
"@tiptap/extension-table-row": "2.10.4",
"@tiptap/extension-task-item": "2.10.4",
"@tiptap/extension-task-list": "2.10.4",
"@tiptap/extension-text-align": "2.10.4",
"@tiptap/extension-text-style": "2.10.4",
"@tiptap/extension-underline": "2.10.4",
"@tiptap/pm": "2.10.4",
"@tiptap/starter-kit": "2.10.4",
"alfaaz": "^1.1.0",
"async-mutex": "^0.5.0",
"clipboard-polyfill": "4.1.0",
@@ -73,7 +73,7 @@
"nanoid": "^5.0.7",
"prism-themes": "^1.9.0",
"prosemirror-codemark": "^0.4.2",
"prosemirror-view": "1.34.2",
"prosemirror-view": "1.37.1",
"re-resizable": "^6.9.18",
"react-colorful": "^5.6.1",
"redent": "^4.0.0",
@@ -128,4 +128,4 @@
"url": "git://github.com/streetwriters/notesnook.git",
"directory": "packages/editor"
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,54 +0,0 @@
diff --git a/node_modules/@tiptap/extension-list-keymap/dist/index.cjs b/node_modules/@tiptap/extension-list-keymap/dist/index.cjs
index 0f8ae36..adeee92 100644
--- a/node_modules/@tiptap/extension-list-keymap/dist/index.cjs
+++ b/node_modules/@tiptap/extension-list-keymap/dist/index.cjs
@@ -82,7 +82,9 @@ const handleBackspace = (editor, name, parentListTypes) => {
// the previous item is a list (orderedList or bulletList)
// move the cursor into the list and delete the current item
if (!core.isNodeActive(editor.state, name) && hasListBefore(editor.state, name, parentListTypes)) {
- const { $anchor } = editor.state.selection;
+ const { $anchor, empty } = editor.state.selection;
+ if (!empty) return false;
+
const $listPos = editor.state.doc.resolve($anchor.before() - 1);
const listDescendants = [];
$listPos.node().descendants((node, pos) => {
@@ -111,6 +113,11 @@ const handleBackspace = (editor, name, parentListTypes) => {
if (!listItemPos) {
return false;
}
+ // if the current position is not at the start of the list item
+ // then join backward i.e. join within the list item
+ if (listItemPos.$pos.parentOffset !== 0) {
+ return editor.commands.joinBackward();
+ }
const $prev = editor.state.doc.resolve(listItemPos.$pos.pos - 2);
const prevNode = $prev.node(listItemPos.depth);
const previousListItemHasSubList = listItemHasSubList(name, editor.state, prevNode);
diff --git a/node_modules/@tiptap/extension-list-keymap/dist/index.js b/node_modules/@tiptap/extension-list-keymap/dist/index.js
index f7ab1e4..6ea03d5 100644
--- a/node_modules/@tiptap/extension-list-keymap/dist/index.js
+++ b/node_modules/@tiptap/extension-list-keymap/dist/index.js
@@ -78,7 +78,9 @@ const handleBackspace = (editor, name, parentListTypes) => {
// the previous item is a list (orderedList or bulletList)
// move the cursor into the list and delete the current item
if (!isNodeActive(editor.state, name) && hasListBefore(editor.state, name, parentListTypes)) {
- const { $anchor } = editor.state.selection;
+ const { $anchor, empty } = editor.state.selection;
+ if (!empty) return false;
+
const $listPos = editor.state.doc.resolve($anchor.before() - 1);
const listDescendants = [];
$listPos.node().descendants((node, pos) => {
@@ -107,6 +109,11 @@ const handleBackspace = (editor, name, parentListTypes) => {
if (!listItemPos) {
return false;
}
+ // if the current position is not at the start of the list item
+ // then join backward i.e. join within the list item
+ if (listItemPos.$pos.parentOffset !== 0) {
+ return editor.commands.joinBackward();
+ }
const $prev = editor.state.doc.resolve(listItemPos.$pos.pos - 2);
const prevNode = $prev.node(listItemPos.depth);
const previousListItemHasSubList = listItemHasSubList(name, editor.state, prevNode);

View File

@@ -1,8 +1,8 @@
diff --git a/node_modules/prosemirror-view/dist/index.cjs b/node_modules/prosemirror-view/dist/index.cjs
index 8ea57c7..aeda01d 100644
index 5903e89..6a85c3d 100644
--- a/node_modules/prosemirror-view/dist/index.cjs
+++ b/node_modules/prosemirror-view/dist/index.cjs
@@ -3456,7 +3456,7 @@ editHandlers.drop = function (view, _event) {
@@ -3482,7 +3482,7 @@ editHandlers.drop = function (view, _event) {
});
tr.setSelection(selectionBetween(view, $pos, tr.doc.resolve(end)));
}
@@ -12,10 +12,10 @@ index 8ea57c7..aeda01d 100644
};
handlers.focus = function (view) {
diff --git a/node_modules/prosemirror-view/dist/index.js b/node_modules/prosemirror-view/dist/index.js
index 9583dc3..991bf0a 100644
index d0c1f9d..f5b3f53 100644
--- a/node_modules/prosemirror-view/dist/index.js
+++ b/node_modules/prosemirror-view/dist/index.js
@@ -3731,7 +3731,7 @@ editHandlers.drop = (view, _event) => {
@@ -3754,7 +3754,7 @@ editHandlers.drop = (view, _event) => {
tr.mapping.maps[tr.mapping.maps.length - 1].forEach((_from, _to, _newFrom, newTo) => end = newTo);
tr.setSelection(selectionBetween(view, $pos, tr.doc.resolve(end)));
}

View File

@@ -16,7 +16,10 @@ 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 { mergeAttributes, Node } from "@tiptap/core";
import { mergeAttributes, Node, wrappingInputRule } from "@tiptap/core";
import { inputRegex } from "@tiptap/extension-task-item";
import { getParentAttributes } from "../../utils/prosemirror.js";
import { ListItem } from "../list-item/index.js";
export interface CheckListOptions {
itemTypeName: string;
@@ -79,6 +82,45 @@ export const CheckList = Node.create<CheckListOptions>({
};
},
addInputRules() {
const inputRule = wrappingInputRule({
find: inputRegex,
type: this.type,
getAttributes: () => {
return getParentAttributes(this.editor, true, true);
}
});
const oldHandler = inputRule.handler;
inputRule.handler = ({ state, range, match, chain, can, commands }) => {
const $from = state.selection.$from;
const parentNode = $from.node($from.depth - 1);
if (parentNode.type.name !== ListItem.name) {
return;
}
const tr = state.tr;
// reset nodes before converting them to a check list.
commands.clearNodes();
oldHandler({
state,
range: {
from: tr.mapping.map(range.from),
to: tr.mapping.map(range.to)
},
match,
chain,
can,
commands
});
tr.setNodeMarkup(state.tr.selection.to - 2, undefined, {
checked: match[match.length - 1] === "x"
});
};
return [inputRule];
},
addKeyboardShortcuts() {
return {
"Mod-Shift-9": () => this.editor.commands.toggleCheckList()

File diff suppressed because one or more lines are too long

View File

@@ -60,6 +60,7 @@ export function ImageComponent(
});
const dom = editor.view.dom.parentElement || editor.view.dom;
const size =
editor.view.dom.clientWidth === 0
? node.attrs
@@ -266,7 +267,7 @@ export function ImageComponent(
title={title}
sx={{
animation: bloburl || src ? "0.2s ease-in 0s 1 fadeIn" : "none",
objectFit: "contain",
objectFit: "scale-down",
width: editor.isEditable ? "100%" : size.width,
height: editor.isEditable ? "100%" : size.height,
border: selected

View File

@@ -1,5 +1,5 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`paste text > with markdown link 1`] = `"<div><div contenteditable="true" translate="no" class="tiptap ProseMirror" tabindex="0"><p><a target="_blank" rel="noopener noreferrer nofollow" href="example.com">test</a></p></div></div>"`;
exports[`paste text > with markdown link 1`] = `"<div><div contenteditable="true" role="textbox" translate="no" class="tiptap ProseMirror" tabindex="0"><p><a target="_blank" rel="noopener noreferrer nofollow" href="example.com">test</a></p></div></div>"`;
exports[`paste text > with multiple markdown links 1`] = `"<div><div contenteditable="true" translate="no" class="tiptap ProseMirror" tabindex="0"><p><a target="_blank" rel="noopener noreferrer nofollow" href="example.com">test</a> some text <a target="_blank" rel="noopener noreferrer nofollow" href="example2.com">test2</a></p></div></div>"`;
exports[`paste text > with multiple markdown links 1`] = `"<div><div contenteditable="true" role="textbox" translate="no" class="tiptap ProseMirror" tabindex="0"><p><a target="_blank" rel="noopener noreferrer nofollow" href="example.com">test</a> some text <a target="_blank" rel="noopener noreferrer nofollow" href="example2.com">test2</a></p></div></div>"`;

View File

@@ -1,7 +1,7 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`hitting backspace at the start of first list item 1`] = `"<div><div contenteditable="true" translate="no" class="tiptap ProseMirror" tabindex="0"><p>item1</p><ul><li><p>item2</p></li></ul></div></div>"`;
exports[`hitting backspace at the start of first list item 1`] = `"<div><div contenteditable="true" role="textbox" translate="no" class="tiptap ProseMirror" tabindex="0"><p>item1</p><ul><li><p>item2</p></li></ul></div></div>"`;
exports[`hitting backspace at the start of the second (or next) list item 1`] = `"<div><div contenteditable="true" translate="no" class="tiptap ProseMirror" tabindex="0"><ul><li><p>item1item2</p></li></ul></div></div>"`;
exports[`hitting backspace at the start of the second (or next) list item 1`] = `"<div><div contenteditable="true" role="textbox" translate="no" class="tiptap ProseMirror" tabindex="0"><ul><li><p>item1item2</p></li></ul></div></div>"`;
exports[`hitting backspace at the start of the second (or next) paragraph inside the list item 1`] = `"<div><div contenteditable="true" translate="no" class="tiptap ProseMirror" tabindex="0"><ul><li><p>item 1item 2</p></li></ul></div></div>"`;
exports[`hitting backspace at the start of the second (or next) paragraph inside the list item 1`] = `"<div><div contenteditable="true" role="textbox" translate="no" class="tiptap ProseMirror" tabindex="0"><ul><li><p>item 1item 2</p></li></ul></div></div>"`;

View File

@@ -39,6 +39,7 @@ import {
} from "./utils.js";
import { Node as ProsemirrorNode } from "@tiptap/pm/model";
import { TaskItemNode } from "../task-item/index.js";
import { ListItem } from "../list-item/list-item.js";
type TaskListStats = { checked: number; total: number };
export type TaskListAttributes = {
@@ -335,6 +336,12 @@ export const TaskListNode = TaskList.extend({
});
const oldHandler = inputRule.handler;
inputRule.handler = ({ state, range, match, chain, can, commands }) => {
const $from = state.selection.$from;
const parentNode = $from.node($from.depth - 1);
if (parentNode.type.name === ListItem.name) {
return;
}
const tr = state.tr;
// reset nodes before converting them to a task list.
commands.clearNodes();

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More