Compare commits

..

5 Commits

Author SHA1 Message Date
Ammar Ahmed
a1658b6de2 mobile: fix showing toast at bottom always 2024-09-23 13:47:25 +05:00
Ammar Ahmed
fab0f6f834 mobile: make sure toast moves with keyboard 2024-09-23 12:23:26 +05:00
Ammar Ahmed
8d10dd040f mobile: fix size of text in toast 2024-09-18 16:18:47 +05:00
Ammar Ahmed
56078a8ef3 mobile: fix toast ui in diff cond 2024-09-18 14:18:14 +05:00
Ammar Ahmed
df4fac7005 mobile: new toast 2024-09-18 13:00:20 +05:00
88 changed files with 30657 additions and 3451 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -11,25 +11,24 @@
"repository": "https://github.com/streetwriters/notesnook",
"license": "GPL-3.0-or-later",
"dependencies": {
"@trpc/client": "10.45.2",
"@trpc/server": "10.45.2",
"better-sqlite3-multiple-ciphers": "^11.2.1",
"electron-trpc": "0.6.1",
"@trpc/client": "10.38.3",
"@trpc/server": "10.38.3",
"better-sqlite3-multiple-ciphers": "^9.5.0",
"electron-trpc": "0.5.2",
"electron-updater": "^6.2.1",
"icojs": "^0.19.4",
"icojs": "^0.17.1",
"typed-emitter": "^2.1.0",
"yargs": "^17.7.2",
"yargs": "^17.6.2",
"zod": "^3.21.4"
},
"devDependencies": {
"@streetwriters/kysely": "^0.27.4",
"@types/node": "20.14.8",
"@types/yargs": "^17.0.33",
"@types/node": "18.16.1",
"@types/yargs": "^17.0.24",
"chokidar": "^3.5.3",
"electron": "^30.4.0",
"electron": "^29.3.1",
"electron-builder": "^24.13.3",
"esbuild": "^0.23.0",
"node-abi": "^3.67.0",
"kysely": "^0.27.3",
"node-gyp-build": "^4.8.0",
"prebuildify": "^6.0.1",
"tree-kill": "^1.2.2",
@@ -158,8 +157,7 @@
},
"nsis": {
"oneClick": true,
"createDesktopShortcut": "always",
"deleteAppDataOnUninstall": true
"createDesktopShortcut": "always"
},
"linux": {
"target": [

View File

@@ -94,7 +94,7 @@ async function patchBetterSQLite3() {
);
const json = JSON.parse(await readFile(jsonPath, "utf-8"));
json.version = "11.2.2";
json.version = "9.5.1";
json.homepage = "https://github.com/thecodrr/better-sqlite3-multiple-ciphers";
json.repository.url =
"git://github.com/thecodrr/better-sqlite3-multiple-ciphers.git";

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import type { Database, Statement } from "better-sqlite3-multiple-ciphers";
import type { QueryResult } from "@streetwriters/kysely";
import type { QueryResult } from "kysely";
type SQLiteCompatibleType =
| number

View File

@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { NativeImage, nativeImage } from "electron";
import path from "path";
import { isDevelopment } from "./index";
import { ParsedImage, parseICO } from "icojs";
import { parse, ParsedImage } from "icojs";
import { getSystemTheme } from "./theme";
import { readFile } from "fs/promises";
@@ -71,7 +71,7 @@ export class AssetManager {
`${icon}${prefix}.ico`
);
const icoBuffer = await readFile(icoPath);
const images = await parseICO(icoBuffer, "image/png");
const images = await parse(icoBuffer, "image/png");
ALL_ICONS.push({ id: icon, images, prefix });
}
}

View File

@@ -23,11 +23,7 @@ import { Platform } from "react-native";
import * as Gzip from "react-native-gzip";
import EventSource from "../../utils/sse/even-source-ios";
import AndroidEventSource from "../../utils/sse/event-source";
import {
SqliteAdapter,
SqliteIntrospector,
SqliteQueryCompiler
} from "@streetwriters/kysely";
import { SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler } from "kysely";
import filesystem from "../filesystem";
import Storage from "./storage";
import { RNSqliteDriver } from "./sqlite.kysely";

View File

@@ -18,11 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { initialize } from "@notesnook/core/dist/logger";
import {
SqliteAdapter,
SqliteIntrospector,
SqliteQueryCompiler
} from "@streetwriters/kysely";
import { SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler } from "kysely";
import { Platform } from "react-native";
import { setLogger } from ".";
import { RNSqliteDriver } from "./sqlite.kysely";

View File

@@ -17,12 +17,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import type {
DatabaseConnection,
Driver,
QueryResult
} from "@streetwriters/kysely";
import { CompiledQuery } from "@streetwriters/kysely";
import type { DatabaseConnection, Driver, QueryResult } from "kysely";
import { CompiledQuery } from "kysely";
import { QuickSQLiteConnection, open } from "react-native-quick-sqlite";
type Config = { dbName: string; async: boolean; location: string };

View File

@@ -43,7 +43,6 @@ export async function downloadFile(filename, requestOptions, cancelToken) {
DatabaseLogger.log(`File Exists already: ${filename}`);
return true;
}
const attachment = await db.attachments.attachment(filename);
const size = await getUploadedFileSize(filename);
@@ -91,8 +90,7 @@ export async function downloadFile(filename, requestOptions, cancelToken) {
DatabaseLogger.log(`Download starting: ${filename}`);
let request = RNFetchBlob.config({
path: tempFilePath,
IOSBackgroundTask: true,
overwrite: true
IOSBackgroundTask: true
})
.fetch("GET", downloadUrl, null)
.progress(async (recieved, total) => {
@@ -124,11 +122,6 @@ export async function downloadFile(filename, requestOptions, cancelToken) {
let status = response.info().status;
useAttachmentStore.getState().remove(filename);
if (exists(originalFilePath)) {
await RNFetchBlob.fs.unlink(originalFilePath).catch(console.log);
}
await RNFetchBlob.fs.mv(tempFilePath, originalFilePath);
if (!(await exists(filename))) {

View File

@@ -1,235 +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, { useCallback, useEffect, useRef, useState } from "react";
import { TouchableOpacity, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../e2e/test.ids";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { DDS } from "../../services/device-detection";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import { useThemeColors } from "@notesnook/theme";
import { getElevationStyle } from "../../utils/elevation";
import { eHideToast, eShowToast } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
export const Toast = ({ context = "global" }) => {
const { colors } = useThemeColors();
const [data, setData] = useState({});
const insets = useGlobalSafeAreaInsets();
const hideTimeout = useRef();
const [visible, setVisible] = useState(false);
const toastMessages = useRef([]);
const show = useCallback(
async (data) => {
if (!data) return;
if (data.context !== context) return;
if (
toastMessages.current.findIndex((m) => m.message === data.message) >= 0
) {
return;
}
toastMessages.current.push(data);
if (toastMessages.current?.length > 1) return;
setData(data);
setVisible(true);
if (hideTimeout.current) {
clearTimeout(hideTimeout.current);
}
hideTimeout.current = setTimeout(() => {
hide();
}, data.duration);
},
[context, hide]
);
const next = useCallback(
(data) => {
if (!data) {
hide();
return;
}
setData(data);
if (hideTimeout.current) {
clearTimeout(hideTimeout.current);
}
hideTimeout.current = setTimeout(() => {
hide();
}, data?.duration);
},
[hide]
);
const hide = useCallback(() => {
if (hideTimeout.current) {
clearTimeout(hideTimeout.current);
}
let msg =
toastMessages.current.length > 1 ? toastMessages.current.shift() : null;
if (msg) {
setVisible(false);
next(msg);
setTimeout(() => {
setVisible(true);
}, 300);
} else {
setVisible(false);
toastMessages.current.shift();
setTimeout(() => {
setData({});
if (hideTimeout.current) {
clearTimeout(hideTimeout.current);
}
}, 100);
}
}, [next]);
useEffect(() => {
eSubscribeEvent(eShowToast, show);
eSubscribeEvent(eHideToast, hide);
return () => {
toastMessages.current = [];
eUnSubscribeEvent(eShowToast, show);
eUnSubscribeEvent(eHideToast, hide);
};
}, [hide, show]);
return (
visible && (
<TouchableOpacity
onPress={hide}
activeOpacity={1}
style={{
width: DDS.isTab ? 400 : "100%",
alignItems: "center",
alignSelf: "center",
minHeight: 30,
top: insets.top + 10,
position: "absolute",
zIndex: 999,
elevation: 15
}}
>
<View
style={{
...getElevationStyle(5),
maxWidth: "95%",
backgroundColor: colors.secondary.background,
minWidth: data?.func ? "95%" : "50%",
alignSelf: "center",
borderRadius: 5,
minHeight: 30,
paddingVertical: 10,
paddingLeft: 12,
paddingRight: 5,
justifyContent: "space-between",
flexDirection: "row",
alignItems: "center",
width: "95%"
}}
>
<View
style={{
flexDirection: "row",
alignItems: "center",
flexGrow: 1,
flex: 1
}}
>
<View
style={{
height: 30,
borderRadius: 100,
justifyContent: "center",
alignItems: "center",
marginRight: 10
}}
>
<Icon
name={data?.type === "success" ? "check" : "close"}
size={SIZE.lg}
color={
data?.type === "error"
? colors.error.icon
: colors.primary.accent
}
/>
</View>
<View
style={{
flexGrow: 1,
paddingRight: 25
}}
>
{data?.heading ? (
<Heading
color={colors.primary.paragraph}
size={SIZE.md}
onPress={() => {
hide();
}}
>
{data.heading}
</Heading>
) : null}
{data?.message ? (
<Paragraph
color={colors.primary.paragraph}
style={{
maxWidth: "100%",
paddingRight: 10
}}
onPress={() => {
hide();
}}
>
{data.message}
</Paragraph>
) : null}
</View>
</View>
{data.func ? (
<Button
testID={notesnook.toast.button}
fontSize={SIZE.md}
type={data.type === "error" ? "errorShade" : "transparent"}
onPress={data.func}
title={data.actionText}
height={30}
style={{
zIndex: 10
}}
/>
) : null}
</View>
</TouchableOpacity>
)
);
};

View File

@@ -0,0 +1,219 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useRef, useState } from "react";
import {
Platform,
TouchableOpacity,
useWindowDimensions,
View
} from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../e2e/test.ids";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import useKeyboard from "../../hooks/use-keyboard";
import { DDS } from "../../services/device-detection";
import {
eSubscribeEvent,
eUnSubscribeEvent,
ToastOptions
} from "../../services/event-manager";
import { getElevationStyle } from "../../utils/elevation";
import { eHideToast, eShowToast } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
export const Toast = ({ context = "global" }) => {
const { colors, isDark } = useThemeColors();
const [toastOptions, setToastOptions] = useState<ToastOptions | undefined>();
const hideTimeout = useRef<NodeJS.Timeout | undefined>();
const insets = useGlobalSafeAreaInsets();
const [visible, setVisible] = useState(false);
const toastMessages = useRef<ToastOptions[]>([]);
const dimensions = useWindowDimensions();
const keyboard = useKeyboard();
const hideToast = useCallback(() => {
const nextToastMessage = toastMessages.current.shift();
if (nextToastMessage) {
if (hideTimeout.current) {
clearTimeout(hideTimeout.current);
}
setVisible(true);
setToastOptions(nextToastMessage);
hideTimeout.current = setTimeout(() => {
hideToast();
}, nextToastMessage?.duration);
} else {
setVisible(false);
setToastOptions(undefined);
if (hideTimeout.current) {
clearTimeout(hideTimeout.current);
}
}
}, []);
const showToast = useCallback(
(data?: ToastOptions) => {
if (
!data ||
data.context !== context ||
toastMessages.current.findIndex((m) => m.message === data.message) != -1
)
return;
toastMessages.current.push(data);
if (toastMessages.current?.length > 1) return;
if (hideTimeout.current) {
clearTimeout(hideTimeout.current);
}
setVisible(true);
const nextToastMessage = toastMessages.current.shift();
setToastOptions(nextToastMessage);
hideTimeout.current = setTimeout(() => {
hideToast();
}, nextToastMessage?.duration);
},
[context, hideToast]
);
useEffect(() => {
eSubscribeEvent(eShowToast, showToast);
eSubscribeEvent(eHideToast, hideToast);
return () => {
eUnSubscribeEvent(eShowToast, showToast);
eUnSubscribeEvent(eHideToast, hideToast);
};
}, [hideToast, showToast]);
const isFullToastMessage = toastOptions?.heading && toastOptions?.message;
return visible && toastOptions ? (
<TouchableOpacity
onPress={() => {
hideToast();
}}
activeOpacity={1}
style={{
width: DDS.isTab ? dimensions.width / 2 : "100%",
alignItems: "center",
alignSelf: "center",
bottom:
Platform.OS === "android"
? Math.max(insets.bottom, 40)
: Math.max(insets.bottom, 40) +
(keyboard.keyboardShown ? keyboard.keyboardHeight : 0),
position: "absolute",
zIndex: 999,
elevation: 15
}}
>
<View
style={{
...getElevationStyle(5),
backgroundColor: isDark ? colors.static.black : colors.static.white,
alignSelf: "center",
borderRadius: 100,
paddingVertical: 12,
paddingHorizontal: 12,
justifyContent: "space-between",
flexDirection: "row",
alignItems: "center",
maxWidth: "95%",
gap: 10,
flexShrink: 1
}}
>
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: 10,
flexShrink: 1
}}
>
<Icon
name={
toastOptions.type === "success"
? "check"
: toastOptions.type === "info"
? "information"
: "close"
}
size={isFullToastMessage ? SIZE.xxxl : SIZE.xl}
color={
toastOptions?.icon
? toastOptions?.icon
: toastOptions.type === "error"
? colors.error.icon
: toastOptions.type === "info"
? isDark
? colors.static.white
: colors.static.black
: colors.success.icon
}
/>
<View
style={{
paddingRight: toastOptions?.func ? 0 : 12,
flexShrink: 1
}}
>
{isFullToastMessage ? (
<Heading
color={!isDark ? colors.static.black : colors.static.white}
size={SIZE.sm}
>
{toastOptions.heading}
</Heading>
) : null}
{toastOptions.message || toastOptions.heading ? (
<Paragraph
color={!isDark ? colors.static.black : colors.static.white}
size={SIZE.xs}
>
{toastOptions.message || toastOptions.heading}
</Paragraph>
) : null}
</View>
</View>
{toastOptions.func ? (
<Button
testID={notesnook.toast.button}
fontSize={SIZE.md}
type={toastOptions.type === "error" ? "errorShade" : "transparent"}
onPress={toastOptions.func}
title={toastOptions.actionText}
height={35}
style={{
zIndex: 10
}}
/>
) : null}
</View>
</TouchableOpacity>
) : null;
};

View File

@@ -36,11 +36,11 @@
"react-native-wheel-color-picker": "^1.3.1",
"tinycolor2": "1.6.0",
"@azure/core-asynciterator-polyfill": "^1.0.2",
"@tanstack/react-query": "^4.36.1",
"@trpc/client": "10.45.2",
"@trpc/react-query": "10.45.2",
"@trpc/server": "10.45.2",
"@streetwriters/kysely": "^0.27.4",
"@tanstack/react-query": "^4.29.19",
"@trpc/client": "10.38.3",
"@trpc/react-query": "10.38.3",
"@trpc/server": "10.38.3",
"kysely": "^0.26.3",
"pathe": "1.1.2"
},
"sideEffects": false

View File

@@ -139,6 +139,7 @@ export type ToastOptions = {
duration?: number;
func?: () => void;
actionText?: string;
icon?: string;
};
export const ToastManager = {

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,7 @@
"@notesnook/web": "file:../web",
"@theme-ui/components": "^0.14.7",
"@theme-ui/core": "^0.14.7",
"@trpc/client": "10.45.2",
"@trpc/client": "^10.38.3",
"@types/react-dom": "^18.3.0",
"clipboard-polyfill": "^4.0.0",
"file-saver": "^2.0.5",
@@ -979,7 +979,7 @@
},
"../web": {
"name": "@notesnook/web",
"version": "3.0.17",
"version": "3.0.13",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
@@ -993,7 +993,7 @@
"@henrygd/queue": "^1.0.6",
"@mdi/js": "^7.2.96",
"@mdi/react": "^1.6.1",
"@notesnook-importer/core": "^2.1.1",
"@notesnook-importer/core": "^2.0.0",
"@notesnook/common": "file:../../packages/common",
"@notesnook/core": "file:../../packages/core",
"@notesnook/crypto": "file:../../packages/crypto",
@@ -1007,14 +1007,13 @@
"@notesnook/web-clipper": "file:../../extensions/web-clipper",
"@react-pdf-viewer/core": "^3.12.0",
"@react-pdf-viewer/toolbar": "^3.12.0",
"@streetwriters/kysely": "^0.27.4",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-query": "^4.29.19",
"@tanstack/react-virtual": "^3.0.1",
"@theme-ui/color": "^0.16.1",
"@theme-ui/components": "^0.16.1",
"@theme-ui/core": "^0.16.1",
"@trpc/client": "10.45.2",
"@trpc/react-query": "10.45.2",
"@trpc/client": "10.38.3",
"@trpc/react-query": "10.38.3",
"@zip.js/zip.js": "^2.7.32",
"async-mutex": "^0.4.0",
"axios": "^1.3.4",
@@ -1032,6 +1031,8 @@
"hash-wasm": "^4.9.0",
"hotkeys-js": "^3.8.3",
"katex": "0.16.2",
"kysely": "^0.26.3",
"libsodium-wrappers": "^0.7.13",
"mac-scrollbar": "^0.13.5",
"marked": "^4.1.0",
"mutative": "^1.0.6",
@@ -1067,7 +1068,7 @@
"@playwright/test": "^1.43.1",
"@swc/core": "^1.5.24",
"@swc/plugin-react-remove-properties": "^2.0.4",
"@trpc/server": "10.45.2",
"@trpc/server": "10.38.3",
"@types/babel__core": "^7.20.1",
"@types/event-source-polyfill": "^1.0.1",
"@types/file-saver": "^2.0.5",
@@ -2018,14 +2019,14 @@
}
},
"node_modules/@trpc/client": {
"version": "10.45.2",
"resolved": "https://registry.npmjs.org/@trpc/client/-/client-10.45.2.tgz",
"integrity": "sha512-ykALM5kYWTLn1zYuUOZ2cPWlVfrXhc18HzBDyRhoPYN0jey4iQHEFSEowfnhg1RvYnrAVjNBgHNeSAXjrDbGwg==",
"version": "10.38.3",
"resolved": "https://registry.npmjs.org/@trpc/client/-/client-10.38.3.tgz",
"integrity": "sha512-hHPsElTL4sB+UGzuAZ4iwRVHjELrk/Nsl2owsYw9ITJz3EY7VWRsQ6vK48kJvlHPYPhP+efID5UOgyZMs4bFXA==",
"funding": [
"https://trpc.io/sponsor"
],
"peerDependencies": {
"@trpc/server": "10.45.2"
"@trpc/server": "10.38.3"
}
},
"node_modules/@types/babel__core": {

View File

@@ -16,7 +16,7 @@
"@notesnook/web": "file:../web",
"@theme-ui/components": "^0.14.7",
"@theme-ui/core": "^0.14.7",
"@trpc/client": "10.45.2",
"@trpc/client": "^10.38.3",
"@types/react-dom": "^18.3.0",
"clipboard-polyfill": "^4.0.0",
"file-saver": "^2.0.5",

File diff suppressed because it is too large Load Diff

View File

@@ -32,13 +32,13 @@
"@notesnook/web-clipper": "file:../../extensions/web-clipper",
"@react-pdf-viewer/core": "^3.12.0",
"@react-pdf-viewer/toolbar": "^3.12.0",
"@tanstack/react-query": "^4.36.1",
"@tanstack/react-query": "^4.29.19",
"@tanstack/react-virtual": "^3.0.1",
"@theme-ui/color": "^0.16.1",
"@theme-ui/components": "^0.16.1",
"@theme-ui/core": "^0.16.1",
"@trpc/client": "10.45.2",
"@trpc/react-query": "10.45.2",
"@trpc/client": "10.38.3",
"@trpc/react-query": "10.38.3",
"@zip.js/zip.js": "^2.7.32",
"async-mutex": "^0.4.0",
"axios": "^1.3.4",
@@ -56,7 +56,7 @@
"hash-wasm": "^4.9.0",
"hotkeys-js": "^3.8.3",
"katex": "0.16.2",
"@streetwriters/kysely": "^0.27.4",
"kysely": "^0.26.3",
"mac-scrollbar": "^0.13.5",
"marked": "^4.1.0",
"mutative": "^1.0.6",
@@ -92,7 +92,7 @@
"@playwright/test": "^1.43.1",
"@swc/core": "^1.5.24",
"@swc/plugin-react-remove-properties": "^2.0.4",
"@trpc/server": "10.45.2",
"@trpc/server": "10.38.3",
"@types/babel__core": "^7.20.1",
"@types/event-source-polyfill": "^1.0.1",
"@types/file-saver": "^2.0.5",

File diff suppressed because it is too large Load Diff

View File

@@ -21,8 +21,6 @@ import { store as noteStore } from "../stores/note-store";
import { store as notebookStore } from "../stores/notebook-store";
import { store as attachmentStore } from "../stores/attachment-store";
import { store as reminderStore } from "../stores/reminder-store";
import { store as tagStore } from "../stores/tag-store";
import { store as appStore } from "../stores/app-store";
import { db } from "./db";
import { showToast } from "../utils/toast";
import Vault from "./vault";
@@ -61,8 +59,6 @@ async function moveNotesToTrash(ids: string[], confirm = true) {
}
async function moveNotebooksToTrash(ids: string[]) {
if (!ids.length) return;
const isMultiselect = ids.length > 1;
if (isMultiselect) {
if (!(await showMultiDeleteConfirmation(ids.length))) return;
@@ -138,42 +134,9 @@ async function moveRemindersToTrash(ids: string[]) {
showToast("success", `${pluralize(ids.length, "reminder")} deleted.`);
}
async function deleteTags(ids: string[]) {
if (
!(await ConfirmDialog.show({
title: "Are you sure?",
message:
"Are you sure you want to permanently delete these tags? This action is IRREVERSIBLE.",
negativeButtonText: "No",
positiveButtonText: "Yes"
}))
)
return;
await TaskManager.startTask({
type: "status",
id: "deleteTags",
title: "Deleting tags",
action: async (report) => {
report({
text: `Deleting ${pluralize(ids.length, "tag")}...`
});
for (const id of ids) {
await db.tags.remove(id);
}
await appStore.refreshNavItems();
await tagStore.refresh();
await noteStore.refresh();
}
});
showToast("success", `${pluralize(ids.length, "tag")} deleted.`);
}
export const Multiselect = {
moveRemindersToTrash,
moveNotebooksToTrash,
moveNotesToTrash,
deleteAttachments,
deleteTags
deleteAttachments
};

View File

@@ -26,7 +26,7 @@ import {
QueryResult,
CompiledQuery,
Driver
} from "@streetwriters/kysely";
} from "kysely";
import { desktop } from "../desktop-bridge";
import Worker from "./sqlite.worker.desktop.ts?worker";
import type { SQLiteWorker } from "./sqlite.worker.desktop";

View File

@@ -22,7 +22,7 @@ import {
SqliteQueryCompiler,
SqliteIntrospector,
Dialect
} from "@streetwriters/kysely";
} from "kysely";
import {
WaSqliteWorkerMultipleTabDriver,
WaSqliteWorkerSingleTabDriver

View File

@@ -21,7 +21,7 @@ import type { SQLiteAPI, SQLiteCompatibleType } from "./sqlite-types";
import { Factory, SQLITE_ROW, SQLiteError } from "./sqlite-api";
import { expose, transfer } from "comlink";
import type { RunMode } from "./type";
import { QueryResult } from "@streetwriters/kysely";
import { QueryResult } from "kysely";
import { DatabaseSource } from "./sqlite-export";
import { createSharedServicePort } from "./shared-service";
import type { IDBBatchAtomicVFS } from "./IDBBatchAtomicVFS";

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 type { QueryResult } from "@streetwriters/kysely";
import type { QueryResult } from "kysely";
export type Promisable<T> = T | Promise<T>;

View File

@@ -17,12 +17,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import type {
DatabaseConnection,
Driver,
QueryResult
} from "@streetwriters/kysely";
import { CompiledQuery } from "@streetwriters/kysely";
import type { DatabaseConnection, Driver, QueryResult } from "kysely";
import { CompiledQuery } from "kysely";
import Worker from "./sqlite.worker.ts?worker";
import type { SQLiteWorker } from "./sqlite.worker";
import SQLiteSyncURI from "./wa-sqlite.wasm?url";

View File

@@ -57,7 +57,6 @@ import { Attachment as AttachmentType } from "@notesnook/core";
import { useEditorStore } from "../../stores/editor-store";
import { PromptDialog } from "../../dialogs/prompt";
import { DialogManager } from "../../common/dialog-manager";
import { useStore as useSelectionStore } from "../../stores/selection-store";
const FILE_ICONS: Record<string, Icon> = {
"image/": FileImage,
@@ -136,13 +135,6 @@ export function Attachment({
Menu.openMenu(AttachmentMenuItems(item, status));
}}
onClick={onSelected}
onKeyPress={async (e) => {
if (e.key === "Delete") {
await Multiselect.deleteAttachments(
useSelectionStore.getState().selectedItems
);
}
}}
style={style}
ref={rowRef}
>

View File

@@ -133,9 +133,8 @@ function Note(props: NoteProps) {
onKeyPress={async (e) => {
if (e.key === "Delete") {
// @ts-expect-error write tests for this
await Multiselect.moveNotesToTrash(
selectionStore.get().selectedItems
);
const selectedItems = selectionStore.get().selectedItems;
await Multiselect.moveNotesToTrash([item.id, ...selectedItems]);
}
}}
colors={{

View File

@@ -44,7 +44,6 @@ import { Notebook as NotebookType } from "@notesnook/core";
import { handleDrop } from "../../common/drop-handler";
import { useDragHandler } from "../../hooks/use-drag-handler";
import { ConfirmDialog } from "../../dialogs/confirm";
import { useStore as useSelectionStore } from "../../stores/selection-store";
type NotebookProps = {
item: NotebookType;
@@ -81,13 +80,6 @@ function Notebook(props: NotebookProps) {
handleDrop(e.dataTransfer, notebook);
}}
onKeyPress={async (e) => {
if (e.key === "Delete") {
await Multiselect.moveNotebooksToTrash(
useSelectionStore.getState().selectedItems
);
}
}}
title={notebook.title}
body={notebook.description as string}
menuItems={notebookMenuItems}

View File

@@ -43,7 +43,6 @@ import { MenuItem } from "@notesnook/ui";
import { Reminder as ReminderType } from "@notesnook/core/dist/types";
import { ConfirmDialog } from "../../dialogs/confirm";
import { EditReminderDialog } from "../../dialogs/add-reminder-dialog";
import { useStore as useSelectionStore } from "../../stores/selection-store";
const RECURRING_MODE_MAP = {
week: "Weekly",
@@ -75,13 +74,6 @@ function Reminder(props: ReminderProps) {
isDisabled={reminder.disabled}
isCompact={compact}
onClick={() => EditReminderDialog.show({ reminderId: reminder.id })}
onKeyPress={async (e) => {
if (e.key === "Delete") {
await Multiselect.moveRemindersToTrash(
useSelectionStore.getState().selectedItems
);
}
}}
footer={
<Flex
sx={{

View File

@@ -29,8 +29,6 @@ import { useCallback, useRef } from "react";
import { handleDrop } from "../../common/drop-handler";
import { useDragHandler } from "../../hooks/use-drag-handler";
import { AddNotebookDialog } from "../../dialogs/add-notebook-dialog";
import { useStore as useSelectionStore } from "../../stores/selection-store";
import { Multiselect } from "../../common/multi-select";
type SubNotebookProps = {
item: Notebook;
@@ -114,11 +112,6 @@ function SubNotebook(props: SubNotebookProps) {
});
navigate(`/notebooks/${rootId}/${item.id}`);
}
} else if (e.code === "Delete") {
console.log("Delete", useSelectionStore.getState().selectedItems);
await Multiselect.moveNotebooksToTrash(
useSelectionStore.getState().selectedItems
);
}
}}
title={

View File

@@ -21,14 +21,16 @@ import ListItem from "../list-item";
import { navigate } from "../../navigation";
import { Text } from "@theme-ui/components";
import { store as appStore } from "../../stores/app-store";
import { store as tagStore } from "../../stores/tag-store";
import { store as noteStore } from "../../stores/note-store";
import { db } from "../../common/db";
import { Edit, Shortcut, DeleteForver } from "../icons";
import { showToast } from "../../utils/toast";
import { pluralize } from "@notesnook/common";
import { MenuItem } from "@notesnook/ui";
import { Tag as TagType } from "@notesnook/core";
import { handleDrop } from "../../common/drop-handler";
import { EditTagDialog } from "../../dialogs/item-dialog";
import { useStore as useSelectionStore } from "../../stores/selection-store";
import { Multiselect } from "../../common/multi-select";
type TagProps = { item: TagType; totalNotes: number };
function Tag(props: TagProps) {
@@ -52,13 +54,6 @@ function Tag(props: TagProps) {
{totalNotes}
</Text>
}
onKeyPress={async (e) => {
if (e.key === "Delete") {
await Multiselect.deleteTags(
useSelectionStore.getState().selectedItems
);
}
}}
menuItems={menuItems}
onClick={() => {
navigate(`/tags/${id}`);
@@ -101,7 +96,11 @@ const menuItems: (tag: TagType, ids?: string[]) => MenuItem[] = (
title: "Delete",
icon: DeleteForver.path,
onClick: async () => {
await Multiselect.deleteTags(ids);
await db.tags.remove(...ids);
showToast("success", `${pluralize(ids.length, "tag")} deleted`);
await appStore.refreshNavItems();
await tagStore.refresh();
await noteStore.refresh();
},
multiSelect: true
}

View File

@@ -28,7 +28,6 @@ import { MenuItem } from "@notesnook/ui";
import { TrashItem as TrashItemType } from "@notesnook/core/dist/types";
import { useEditorStore } from "../../stores/editor-store";
import { showMultiPermanentDeleteConfirmation } from "../../dialogs/confirm";
import { useStore as useSelectionStore } from "../../stores/selection-store";
type TrashItemProps = { item: TrashItemType; date: number };
function TrashItem(props: TrashItemProps) {
@@ -41,11 +40,6 @@ function TrashItem(props: TrashItemProps) {
item={item}
title={item.title}
body={item.itemType === "note" ? item.headline : item.description}
onKeyPress={async (e) => {
if (e.key === "Delete") {
await deleteTrash(useSelectionStore.getState().selectedItems);
}
}}
footer={
<Flex
mt={1}
@@ -93,15 +87,14 @@ const menuItems: (item: TrashItemType, ids?: string[]) => MenuItem[] = (
icon: DeleteForver.path,
variant: "dangerous",
onClick: async () => {
await deleteTrash(ids);
if (!(await showMultiPermanentDeleteConfirmation(ids.length))) return;
await store.delete(...ids);
showToast(
"success",
`${pluralize(ids.length, "item")} permanently deleted`
);
},
multiSelect: true
}
];
};
async function deleteTrash(ids: string[]) {
if (!(await showMultiPermanentDeleteConfirmation(ids.length))) return;
await store.delete(...ids);
showToast("success", `${pluralize(ids.length, "item")} permanently deleted`);
}

View File

@@ -26,7 +26,6 @@ import { db } from "../common/db";
import { getChangelog } from "../utils/version";
import { downloadUpdate } from "../utils/updater";
import { ErrorText } from "../components/error-text";
import { pluralize } from "@notesnook/common";
type Check = { text: string; default?: boolean };
export type ConfirmDialogProps<TCheckId extends string> = BaseDialogProps<
@@ -144,7 +143,7 @@ export const ConfirmDialog = DialogManager.register(function ConfirmDialog<
export function showMultiDeleteConfirmation(length: number) {
return ConfirmDialog.show({
title: `Delete ${pluralize(length, "item")}?`,
title: `Delete ${length} items?`,
message: `These items will be **kept in your Trash for ${
db.settings.getTrashCleanupInterval() || 7
} days** after which they will be permanently deleted.`,
@@ -155,7 +154,7 @@ export function showMultiDeleteConfirmation(length: number) {
export function showMultiPermanentDeleteConfirmation(length: number) {
return ConfirmDialog.show({
title: `Permanently delete ${pluralize(length, "item")}?`,
title: `Permanently delete ${length} items?`,
message:
"These items will be **permanently deleted**. This is IRREVERSIBLE.",
positiveButtonText: "Yes",

View File

@@ -802,11 +802,10 @@ class EditorStore extends BaseStore<EditorStore> {
{ type, id: currentSession.context.id },
{ id, type: "note" }
);
} else {
const defaultNotebook = db.settings.getDefaultNotebook();
if (defaultNotebook)
await db.notes.addToNotebook(defaultNotebook, id);
}
const defaultNotebook = db.settings.getDefaultNotebook();
if (defaultNotebook)
await db.notes.addToNotebook(defaultNotebook, id);
}
const attachmentsLength = await db.attachments

View File

@@ -35,8 +35,7 @@
"@microsoft/signalr": "^8.0.0",
"@notesnook/logger": "file:../logger",
"@readme/data-urls": "^3.0.0",
"@streetwriters/kysely": "^0.27.4",
"@streetwriters/showdown": "^3.0.8-alpha",
"@streetwriters/showdown": "^3.0.7-alpha",
"async-mutex": "^0.3.2",
"dayjs": "1.11.9",
"dom-serializer": "^2.0.0",
@@ -47,6 +46,7 @@
"html-to-text": "^9.0.5",
"htmlparser2": "^8.0.1",
"katex": "0.16.2",
"kysely": "^0.26.3",
"linkedom": "^0.14.17",
"liqe": "^1.13.0",
"mime-db": "1.52.0",
@@ -1828,8 +1828,7 @@
"@notesnook/crypto": "file:../crypto",
"@notesnook/logger": "file:../logger",
"@readme/data-urls": "^3.0.0",
"@streetwriters/kysely": "^0.27.4",
"@streetwriters/showdown": "^3.0.8-alpha",
"@streetwriters/showdown": "^3.0.7-alpha",
"@types/better-sqlite3": "^7.6.5",
"@types/event-source-polyfill": "^1.0.1",
"@types/html-to-text": "^9.0.0",
@@ -1859,6 +1858,7 @@
"isomorphic-fetch": "^3.0.0",
"jsdom": "^22.1.0",
"katex": "0.16.2",
"kysely": "^0.26.3",
"linkedom": "^0.14.17",
"liqe": "^1.13.0",
"mime-db": "1.52.0",

View File

@@ -25,7 +25,7 @@ import { CHECK_IDS, EV, EVENTS } from "../src/common";
import { EventSource } from "event-source-polyfill";
import { test, expect, vitest } from "vitest";
import { login } from "./utils";
import { SqliteDialect } from "@streetwriters/kysely";
import { SqliteDialect } from "kysely";
import BetterSQLite3 from "better-sqlite3-multiple-ciphers";
const TEST_TIMEOUT = 60 * 1000;

View File

@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { describe, it, expect } from "vitest";
import { initialize, logManager, logger } from "../src/logger";
import { SqliteDialect } from "@streetwriters/kysely";
import { SqliteDialect } from "kysely";
import BetterSQLite3 from "better-sqlite3-multiple-ciphers";
import { NoopLogger } from "@notesnook/logger";
import { delay } from "./utils";

View File

@@ -25,7 +25,7 @@ import { EventSourcePolyfill as EventSource } from "event-source-polyfill";
import { randomBytes } from "../../src/utils/random";
import { Note, Notebook } from "../../src/types";
import { NoteContent } from "../../src/collections/session-content";
import { SqliteDialect } from "@streetwriters/kysely";
import { SqliteDialect } from "kysely";
import BetterSQLite3 from "better-sqlite3-multiple-ciphers";
import path from "path";
import { tmpdir } from "os";

View File

@@ -115,23 +115,6 @@ test("unlock a note permanently", () =>
expect(await db.relations.from(vault, "note").has(id)).toBe(false);
}));
test("lock an empty note", () =>
noteTest({ title: "I am a note" }).then(async ({ db, id }) => {
await db.vault.create("password");
await db.vault.add(id);
const note = await db.notes.note(id);
const content = await db.content.get(note.contentId);
const vault = await db.vaults.default();
expect(note.headline).toBe("");
expect(content.locked).toBeTruthy();
expect(content.noteId).toBeDefined();
expect(content.data.iv).toBeDefined();
expect(content.data.cipher).toBeDefined();
expect(await db.relations.from(vault, "note").has(id)).toBe(true);
}));
test("save a locked note", () =>
noteTest().then(async ({ db, id }) => {
await db.vault.create("password");

View File

@@ -14,7 +14,6 @@
"@microsoft/signalr": "^8.0.0",
"@notesnook/logger": "file:../logger",
"@readme/data-urls": "^3.0.0",
"@streetwriters/kysely": "^0.27.4",
"@streetwriters/showdown": "^3.0.8-alpha",
"async-mutex": "^0.3.2",
"dayjs": "1.11.9",
@@ -26,6 +25,7 @@
"html-to-text": "^9.0.5",
"htmlparser2": "^8.0.1",
"katex": "0.16.2",
"kysely": "^0.26.3",
"linkedom": "^0.14.17",
"liqe": "^1.13.0",
"mime-db": "1.52.0",
@@ -2216,14 +2216,6 @@
"integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==",
"dev": true
},
"node_modules/@streetwriters/kysely": {
"version": "0.27.4",
"resolved": "https://registry.npmjs.org/@streetwriters/kysely/-/kysely-0.27.4.tgz",
"integrity": "sha512-c2p+wtX8WESSVjJAc16j4lT44w/g1DvJFEIeKj/CFy36vmi0uq9FOAzxc50VtRc7rp0UlECCfl3Sxd+BTZOHFg==",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@streetwriters/showdown": {
"version": "3.0.8-alpha",
"resolved": "https://registry.npmjs.org/@streetwriters/showdown/-/showdown-3.0.8-alpha.tgz",
@@ -3567,6 +3559,14 @@
"node": ">= 12"
}
},
"node_modules/kysely": {
"version": "0.26.3",
"resolved": "https://registry.npmjs.org/kysely/-/kysely-0.26.3.tgz",
"integrity": "sha512-yWSgGi9bY13b/W06DD2OCDDHQmq1kwTGYlQ4wpZkMOJqMGCstVCFIvxCCVG4KfY1/3G0MhDAcZsip/Lw8/vJWw==",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/leac": {
"version": "0.6.0",
"license": "MIT",

View File

@@ -62,7 +62,7 @@
"html-to-text": "^9.0.5",
"htmlparser2": "^8.0.1",
"katex": "0.16.2",
"@streetwriters/kysely": "^0.27.4",
"kysely": "^0.26.3",
"linkedom": "^0.14.17",
"liqe": "^1.13.0",
"mime-db": "1.52.0",

File diff suppressed because it is too large Load Diff

View File

@@ -70,7 +70,7 @@ import {
createDatabase,
initializeDatabase
} from "../database";
import { Kysely, Transaction, sql } from "@streetwriters/kysely";
import { Kysely, Transaction, sql } from "kysely";
import { CachedCollection } from "../database/cached-collection";
import { Vaults } from "../collections/vaults";
import { KVStorage } from "../database/kv";

View File

@@ -21,7 +21,7 @@ import { match } from "fuzzyjs";
import Database from ".";
import { Item, Note, TrashItem } from "../types";
import { DatabaseSchema, RawDatabaseSchema } from "../database";
import { AnyColumnWithTable, Kysely, sql } from "@streetwriters/kysely";
import { AnyColumnWithTable, Kysely, sql } from "kysely";
import { FilteredSelector } from "../database/sql-collection";
import { VirtualizedGrouping } from "../utils/virtualized-grouping";
import { logger } from "../logger";
@@ -79,12 +79,6 @@ export default class Lookup {
.select(["results.id"])
.groupBy("results.id")
.$if(!!limit, (eb) => eb.limit(limit!))
// filter out ids that have no note against them
.where(
"results.id",
"in",
(notes || this.db.notes.all).filter.select("id")
)
.execute()
.catch((e) => {
logger.error(e, `Error while searching`, { query });

View File

@@ -251,7 +251,7 @@ export default class Vault {
.storage()
.encrypt({ password }, JSON.stringify(content.data));
return await this.db.content.add({
await this.db.content.add({
noteId,
sessionId,
data: encryptedContent,
@@ -295,7 +295,7 @@ export default class Vault {
// Case: when note is being newly locked
if (!locked && (!data || !type)) {
const rawContent = await this.db.content.findByNoteId(id);
if (rawContent?.locked) {
if (!rawContent || rawContent.locked) {
await this.db.relations.add(vault, {
id,
type: "note"
@@ -305,8 +305,8 @@ export default class Vault {
// NOTE:
// At this point, the note already has all the attachments extracted
// so we should just encrypt it as normal.
data = rawContent?.data || "<p></p>";
type = rawContent?.type || "tiptap";
data = rawContent.data;
type = rawContent.type;
} else if (data && type) {
data = await this.db.content.postProcess({
data,
@@ -315,14 +315,13 @@ export default class Vault {
});
}
if (data && type)
await this.encryptContent({ data, type }, id, password, sessionId);
await this.db.notes.add({
id,
headline: "",
dateEdited: Date.now(),
contentId:
data && type
? await this.encryptContent({ data, type }, id, password, sessionId)
: undefined
dateEdited: Date.now()
});
await this.db.relations.add(vault, {

View File

@@ -29,7 +29,7 @@ import { Attachment } from "../types";
import Database from "../api";
import { FilteredSelector, SQLCollection } from "../database/sql-collection";
import { isFalse } from "../database";
import { sql } from "@streetwriters/kysely";
import { sql } from "kysely";
import { logger } from "../logger";
export class Attachments implements ICollection {

View File

@@ -23,7 +23,7 @@ import { Notebook, TrashOrItem, isTrashItem } from "../types";
import { ICollection } from "./collection";
import { SQLCollection } from "../database/sql-collection";
import { isFalse } from "../database";
import { sql } from "@streetwriters/kysely";
import { sql } from "kysely";
import { deleteItems } from "../utils/array";
import { CHECK_IDS, checkIsUserPremium } from "../common";

View File

@@ -29,7 +29,7 @@ import {
import Database from "../api";
import { FilteredSelector, SQLCollection } from "../database/sql-collection";
import { DatabaseSchema, UnlinkEvent, isFalse } from "../database";
import { SelectQueryBuilder } from "@streetwriters/kysely";
import { SelectQueryBuilder } from "kysely";
import { EVENTS } from "../common";
export class Relations implements ICollection {

View File

@@ -29,7 +29,7 @@ import { Reminder } from "../types";
import Database from "../api";
import { SQLCollection } from "../database/sql-collection";
import { isFalse } from "../database";
import { sql } from "@streetwriters/kysely";
import { sql } from "kysely";
dayjs.extend(isTomorrow);
dayjs.extend(isSameOrBefore);

View File

@@ -23,7 +23,7 @@ import Database from "../api";
import { ICollection } from "./collection";
import { SQLCollection } from "../database/sql-collection";
import { isFalse } from "../database";
import { sql } from "@streetwriters/kysely";
import { sql } from "kysely";
import { CHECK_IDS, checkIsUserPremium } from "../common";
export class Tags implements ICollection {

View File

@@ -27,7 +27,7 @@ import {
getSortSelectors,
groupArray
} from "../utils/grouping";
import { sql } from "@streetwriters/kysely";
import { sql } from "kysely";
import { MAX_SQL_PARAMETERS } from "../database/sql-collection";
export default class Trash {

View File

@@ -123,21 +123,28 @@ export const EVENTS = {
systemTimeInvalid: "system:invalidTime"
};
const separators = ["-", "/", "."];
const separators = ["-", "/"];
const DD = "DD";
const MM = "MM";
const YYYY = "YYYY";
export const DATE_FORMATS = [
...[
[DD, MM, YYYY],
[MM, DD, YYYY],
[YYYY, MM, DD]
]
.map((item) => separators.map((sep) => item.join(sep)))
...separators
.map((sep) => [
[DD, MM, YYYY].join(sep),
[MM, DD, YYYY].join(sep),
[YYYY, MM, DD].join(sep)
])
.flat(),
"MMM D, YYYY"
];
export const DATE_FORMATS_WITHOUT_YEAR = [
...separators
.map((sep) => [[DD, MM].join(sep), [MM, DD].join(sep), [MM, DD].join(sep)])
.flat(),
"MMM D"
];
export const TIME_FORMATS = ["12-hour", "24-hour"];
export const CURRENT_DATABASE_VERSION = 6.1;

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 { Kysely, sql } from "@streetwriters/kysely";
import { Kysely, sql } from "kysely";
import { RawDatabaseSchema } from ".";
export async function rebuildSearchIndex(db: Kysely<RawDatabaseSchema>) {

View File

@@ -36,7 +36,7 @@ import {
ReferenceExpression,
Dialect,
MigrationProvider
} from "@streetwriters/kysely";
} from "kysely";
import {
Attachment,
Color,

View File

@@ -20,11 +20,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import {
ColumnBuilderCallback,
CreateTableBuilder,
ExpressionBuilder,
Migration,
MigrationProvider,
sql
} from "@streetwriters/kysely";
} from "kysely";
import { rebuildSearchIndex } from "./fts";
const COLLATE_NOCASE: ColumnBuilderCallback = (col) =>
@@ -295,7 +294,7 @@ export class NNMigrationProvider implements MigrationProvider {
async up(db) {
await db
.updateTable("notes")
.where("id", "in", (eb: ExpressionBuilder<any, string>) =>
.where("id", "in", (eb) =>
eb
.selectFrom("content")
.select("noteId as id")

View File

@@ -21,7 +21,7 @@ import { MaybeDeletedItem, isDeleted } from "../types";
import EventManager from "../utils/event-manager";
import { DatabaseAccessor, DatabaseCollection, DatabaseSchema } from ".";
import { SQLCollection } from "./sql-collection";
import { Kysely } from "@streetwriters/kysely";
import { Kysely } from "kysely";
import { Sanitizer } from "./sanitizer";
export class SQLCachedCollection<

View File

@@ -45,7 +45,7 @@ import {
SelectQueryBuilder,
SqlBool,
sql
} from "@streetwriters/kysely";
} from "kysely";
import { VirtualizedGrouping } from "../utils/virtualized-grouping";
import { createKeySelector, groupArray } from "../utils/grouping";
import { toChunks } from "../utils/array";

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 { Kysely, sql } from "@streetwriters/kysely";
import { Kysely, sql } from "kysely";
import { DatabaseSchema, RawDatabaseSchema } from ".";
export async function createTriggers(db: Kysely<RawDatabaseSchema>) {

View File

@@ -28,7 +28,7 @@ import {
format,
ILogger
} from "@notesnook/logger";
import { Kysely, Migration, MigrationProvider } from "@streetwriters/kysely";
import { Kysely, Migration, MigrationProvider } from "kysely";
import { SQLiteOptions, createDatabase } from "./database";
import { toChunks } from "./utils/array";

View File

@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import dayjs from "dayjs";
import { DATE_FORMATS, DATE_FORMATS_WITHOUT_YEAR } from "../common";
export type TimeFormat = "12-hour" | "24-hour";
@@ -69,7 +70,7 @@ export type TimeOptions = {
timeFormat: TimeFormat;
};
export type DateOptions = {
type: "date";
type: "date" | "date-without-year";
dateFormat: string;
};
export type DateTimeOptions = {
@@ -96,6 +97,12 @@ export function formatDate(
return dayjs(date).format(getTimeFormat(options.timeFormat));
case "date":
return dayjs(date).format(options.dateFormat);
case "date-without-year": {
const format =
DATE_FORMATS_WITHOUT_YEAR[DATE_FORMATS.indexOf(options.dateFormat)];
if (!format) return dayjs(date).format("MM-DD");
return dayjs(date).format(format);
}
}
}

View File

@@ -42,76 +42,70 @@
"@notesnook/core": "file:../core",
"@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",
"@social-embed/lib": "^0.0.2-next.1",
"@tiptap/core": "2.2.4",
"@tiptap/extension-character-count": "2.2.4",
"@tiptap/extension-color": "2.2.4",
"@tiptap/extension-font-family": "2.2.4",
"@tiptap/extension-history": "2.2.4",
"@tiptap/extension-horizontal-rule": "2.2.4",
"@tiptap/extension-list-keymap": "2.2.4",
"@tiptap/extension-placeholder": "2.2.4",
"@tiptap/extension-subscript": "2.2.4",
"@tiptap/extension-superscript": "2.2.4",
"@tiptap/extension-table": "2.2.4",
"@tiptap/extension-table-cell": "2.2.4",
"@tiptap/extension-table-header": "2.2.4",
"@tiptap/extension-table-row": "2.2.4",
"@tiptap/extension-task-item": "2.2.4",
"@tiptap/extension-task-list": "2.2.4",
"@tiptap/extension-text-align": "2.2.4",
"@tiptap/extension-text-style": "2.2.4",
"@tiptap/extension-underline": "2.2.4",
"@tiptap/pm": "2.2.4",
"@tiptap/starter-kit": "2.2.4",
"alfaaz": "^1.1.0",
"async-mutex": "^0.5.0",
"clipboard-polyfill": "4.1.0",
"detect-indent": "^7.0.1",
"entities": "^5.0.0",
"katex": "0.16.11",
"async-mutex": "^0.4.0",
"clipboard-polyfill": "4.0.0",
"detect-indent": "^7.0.0",
"entities": "^4.5.0",
"katex": "0.16.9",
"linkifyjs": "^4.1.3",
"nanoid": "^5.0.7",
"nanoid": "^4.0.1",
"prism-themes": "^1.9.0",
"prosemirror-codemark": "^0.4.2",
"prosemirror-view": "1.34.2",
"re-resizable": "^6.9.18",
"prosemirror-view": "1.33.1",
"re-resizable": "^6.9.11",
"react-colorful": "^5.6.1",
"redent": "^4.0.0",
"refractor": "^4.8.1",
"strip-indent": "^4.0.0",
"unfurl.js": "^6.4.0"
"unfurl.js": "^6.3.2"
},
"devDependencies": {
"@emotion/react": "11.13.3",
"@mdi/js": "^7.4.47",
"@emotion/react": "11.11.1",
"@mdi/js": "^7.2.96",
"@mdi/react": "^1.6.1",
"@theme-ui/components": "^0.16.2",
"@theme-ui/core": "^0.16.2",
"@types/katex": "^0.16.7",
"@types/prismjs": "^1.26.4",
"@types/react": "^18.3.5",
"@types/react-color": "^3.0.12",
"@types/react-dom": "^18.3.0",
"@theme-ui/components": "^0.16.1",
"@theme-ui/core": "^0.16.1",
"@types/katex": "^0.14.0",
"@types/prismjs": "^1.26.0",
"@types/react": "^18.2.39",
"@types/react-color": "^3.0.6",
"@types/react-dom": "^18.2.17",
"@types/react-modal": "^3.16.3 ",
"@types/tinycolor2": "^1.4.6",
"framer-motion": "^11.5.4",
"happy-dom": "^15.7.4",
"@types/tinycolor2": "^1.4.3",
"framer-motion": "^10.16.8",
"happy-dom": "^8.9.0",
"isomorphic-fetch": "^3.0.0",
"prosemirror-test-builder": "^1.1.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"prosemirror-test-builder": "^1.1.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-modal": "3.16.1",
"tinycolor2": "^1.6.0",
"vitest": "^2.0.5",
"web-vitals": "^4.2.3",
"zustand": "4.5.5"
"vitest": "^0.29.2",
"web-vitals": "^2.1.4",
"zustand": "4.4.7"
},
"peerDependencies": {
"@emotion/react": ">=11",

File diff suppressed because it is too large Load Diff

View File

@@ -8,76 +8,70 @@
"@notesnook/core": "file:../core",
"@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",
"@social-embed/lib": "^0.0.2-next.1",
"@tiptap/core": "2.2.4",
"@tiptap/extension-character-count": "2.2.4",
"@tiptap/extension-color": "2.2.4",
"@tiptap/extension-font-family": "2.2.4",
"@tiptap/extension-history": "2.2.4",
"@tiptap/extension-horizontal-rule": "2.2.4",
"@tiptap/extension-list-keymap": "2.2.4",
"@tiptap/extension-placeholder": "2.2.4",
"@tiptap/extension-subscript": "2.2.4",
"@tiptap/extension-superscript": "2.2.4",
"@tiptap/extension-table": "2.2.4",
"@tiptap/extension-table-cell": "2.2.4",
"@tiptap/extension-table-header": "2.2.4",
"@tiptap/extension-table-row": "2.2.4",
"@tiptap/extension-task-item": "2.2.4",
"@tiptap/extension-task-list": "2.2.4",
"@tiptap/extension-text-align": "2.2.4",
"@tiptap/extension-text-style": "2.2.4",
"@tiptap/extension-underline": "2.2.4",
"@tiptap/pm": "2.2.4",
"@tiptap/starter-kit": "2.2.4",
"alfaaz": "^1.1.0",
"async-mutex": "^0.5.0",
"clipboard-polyfill": "4.1.0",
"detect-indent": "^7.0.1",
"entities": "^5.0.0",
"katex": "0.16.11",
"async-mutex": "^0.4.0",
"clipboard-polyfill": "4.0.0",
"detect-indent": "^7.0.0",
"entities": "^4.5.0",
"katex": "0.16.9",
"linkifyjs": "^4.1.3",
"nanoid": "^5.0.7",
"nanoid": "^4.0.1",
"prism-themes": "^1.9.0",
"prosemirror-codemark": "^0.4.2",
"prosemirror-view": "1.34.2",
"re-resizable": "^6.9.18",
"prosemirror-view": "1.33.1",
"re-resizable": "^6.9.11",
"react-colorful": "^5.6.1",
"redent": "^4.0.0",
"refractor": "^4.8.1",
"strip-indent": "^4.0.0",
"unfurl.js": "^6.4.0"
"unfurl.js": "^6.3.2"
},
"devDependencies": {
"@emotion/react": "11.13.3",
"@mdi/js": "^7.4.47",
"@emotion/react": "11.11.1",
"@mdi/js": "^7.2.96",
"@mdi/react": "^1.6.1",
"@theme-ui/components": "^0.16.2",
"@theme-ui/core": "^0.16.2",
"@types/katex": "^0.16.7",
"@types/prismjs": "^1.26.4",
"@types/react": "^18.3.5",
"@types/react-color": "^3.0.12",
"@types/react-dom": "^18.3.0",
"@theme-ui/components": "^0.16.1",
"@theme-ui/core": "^0.16.1",
"@types/katex": "^0.14.0",
"@types/prismjs": "^1.26.0",
"@types/react": "^18.2.39",
"@types/react-color": "^3.0.6",
"@types/react-dom": "^18.2.17",
"@types/react-modal": "^3.16.3 ",
"@types/tinycolor2": "^1.4.6",
"framer-motion": "^11.5.4",
"happy-dom": "^15.7.4",
"@types/tinycolor2": "^1.4.3",
"framer-motion": "^10.16.8",
"happy-dom": "^8.9.0",
"isomorphic-fetch": "^3.0.0",
"prosemirror-test-builder": "^1.1.1",
"react": "18.3.1",
"react-dom": "18.3.1",
"prosemirror-test-builder": "^1.1.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-modal": "3.16.1",
"tinycolor2": "^1.6.0",
"vitest": "^2.0.5",
"web-vitals": "^4.2.3",
"zustand": "4.5.5"
"vitest": "^0.29.2",
"web-vitals": "^2.1.4",
"zustand": "4.4.7"
},
"peerDependencies": {
"@emotion/react": ">=11",

View File

@@ -0,0 +1,334 @@
diff --git a/node_modules/@tiptap/core/dist/index.cjs b/node_modules/@tiptap/core/dist/index.cjs
index 1ea30e0..2291615 100644
--- a/node_modules/@tiptap/core/dist/index.cjs
+++ b/node_modules/@tiptap/core/dist/index.cjs
@@ -383,7 +383,7 @@ function fromString(value) {
* @param extensionAttributes List of attributes to inject
*/
function injectExtensionAttributesToParseRule(parseRule, extensionAttributes) {
- if (parseRule.style) {
+ if (parseRule.style || (!parseRule.attrs && !parseRule.getAttrs && extensionAttributes.length === 0)) {
return parseRule;
}
return {
@@ -400,12 +400,10 @@ function injectExtensionAttributesToParseRule(parseRule, extensionAttributes) {
if (value === null || value === undefined) {
return items;
}
- return {
- ...items,
- [item.name]: value,
- };
- }, {});
- return { ...oldAttributes, ...newAttributes };
+ items[item.name] = value;
+ return items;
+ }, oldAttributes || {});
+ return newAttributes;
},
};
}
@@ -1327,7 +1325,7 @@ const cut = (originRange, targetPos) => ({ editor, tr }) => {
tr.deleteRange(originRange.from, originRange.to);
const newPos = tr.mapping.map(targetPos);
tr.insert(newPos, contentSlice.content);
- tr.setSelection(new state.TextSelection(tr.doc.resolve(newPos - 1)));
+ tr.setSelection(new state.TextSelection(tr.doc.resolve(Math.max(0, newPos - 1))));
return true;
};
@@ -1587,24 +1585,11 @@ const insertContent = (value, options) => ({ tr, commands }) => {
return commands.insertContentAt({ from: tr.selection.from, to: tr.selection.to }, value, options);
};
-const removeWhitespaces = (node) => {
- const children = node.childNodes;
- for (let i = children.length - 1; i >= 0; i -= 1) {
- const child = children[i];
- if (child.nodeType === 3 && child.nodeValue && /^(\n\s\s|\n)$/.test(child.nodeValue)) {
- node.removeChild(child);
- }
- else if (child.nodeType === 1) {
- removeWhitespaces(child);
- }
- }
- return node;
-};
function elementFromString(value) {
// add a wrapper to preserve leading and trailing whitespace
const wrappedValue = `<body>${value}</body>`;
const html = new window.DOMParser().parseFromString(wrappedValue, 'text/html').body;
- return removeWhitespaces(html);
+ return html;
}
function createNodeFromContent(content, schema, options) {
@@ -1901,9 +1886,24 @@ const lift = (typeOrName, attributes = {}) => ({ state, dispatch }) => {
return commands$1.lift(state, dispatch);
};
-const liftEmptyBlock = () => ({ state, dispatch }) => {
- return commands$1.liftEmptyBlock(state, dispatch);
-};
+const liftEmptyBlock = () => ({ state, dispatch, editor }) => {
+ return commands$1.liftEmptyBlock(state, (tr) => {
+ if (!dispatch) return true;
+
+ const { selection, storedMarks } = state;
+ const marks = storedMarks || (selection.$to.parentOffset && selection.$from.marks());
+
+ if (!marks) return dispatch(tr);
+
+ const { splittableMarks } = editor.extensionManager;
+ const filteredMarks = marks.filter((mark) =>
+ splittableMarks.includes(mark.type.name)
+ );
+ tr.ensureMarks(filteredMarks)
+
+ return dispatch(tr);
+ });
+}
const liftListItem = typeOrName => ({ state, dispatch }) => {
const type = getNodeType(typeOrName, state.schema);
@@ -3279,16 +3279,20 @@ const Keymap = Extension.create({
new state.Plugin({
key: new state.PluginKey('clearDocument'),
appendTransaction: (transactions, oldState, newState) => {
+ const { empty, from, to } = oldState.selection;
+ if (empty) {
+ return;
+ }
+
const docChanges = transactions.some(transaction => transaction.docChanged)
- && !oldState.doc.eq(newState.doc);
+ && oldState.doc.nodeSize !== newState.doc.nodeSize;
if (!docChanges) {
return;
}
- const { empty, from, to } = oldState.selection;
const allFrom = state.Selection.atStart(oldState.doc).from;
const allEnd = state.Selection.atEnd(oldState.doc).to;
const allWasSelected = from === allFrom && to === allEnd;
- if (empty || !allWasSelected) {
+ if (!allWasSelected) {
return;
}
const isEmpty = newState.doc.textBetween(0, newState.doc.content.size, ' ', ' ').length === 0;
@@ -3788,23 +3792,28 @@ class Editor extends EventEmitter {
* Creates a ProseMirror view.
*/
createView() {
- const doc = createDocument(this.options.content, this.schema, this.options.parseOptions);
- const selection = resolveFocusPosition(doc, this.options.autofocus);
- this.view = new view.EditorView(this.options.element, {
+ this.view = new EditorView(this.options.element, {
...this.options.editorProps,
dispatchTransaction: this.dispatchTransaction.bind(this),
- state: state.EditorState.create({
- doc,
- selection: selection || undefined,
- }),
+ state: EditorState.create({
+ doc: this.schema.topNodeType.create(),
+ })
});
+
+ const doc = createDocument(this.options.content, this.schema, this.options.parseOptions);
+ const selection = resolveFocusPosition(doc, this.options.autofocus);
// `editor.view` is not yet available at this time.
// Therefore we will add all plugins and node views directly afterwards.
- const newState = this.state.reconfigure({
+ const newState = EditorState.create({
plugins: this.extensionManager.plugins,
+ doc,
+ selection
+ });
+
+ this.view.setProps({
+ state: newState,
+ nodeViews: this.extensionManager.nodeViews
});
- this.view.updateState(newState);
- this.createNodeViews();
this.prependClass();
// Lets store the editor instance in the DOM element.
// So well have access to it for tests.
diff --git a/node_modules/@tiptap/core/dist/index.js b/node_modules/@tiptap/core/dist/index.js
index 554e919..28fc1c1 100644
--- a/node_modules/@tiptap/core/dist/index.js
+++ b/node_modules/@tiptap/core/dist/index.js
@@ -379,9 +379,10 @@ function fromString(value) {
* @param extensionAttributes List of attributes to inject
*/
function injectExtensionAttributesToParseRule(parseRule, extensionAttributes) {
- if (parseRule.style) {
+ if (parseRule.style || (!parseRule.attrs && !parseRule.getAttrs && extensionAttributes.length === 0)) {
return parseRule;
}
+
return {
...parseRule,
getAttrs: node => {
@@ -396,12 +397,10 @@ function injectExtensionAttributesToParseRule(parseRule, extensionAttributes) {
if (value === null || value === undefined) {
return items;
}
- return {
- ...items,
- [item.name]: value,
- };
- }, {});
- return { ...oldAttributes, ...newAttributes };
+ items[item.name] = value;
+ return items;
+ }, oldAttributes || {});
+ return newAttributes;
},
};
}
@@ -1323,7 +1322,7 @@ const cut = (originRange, targetPos) => ({ editor, tr }) => {
tr.deleteRange(originRange.from, originRange.to);
const newPos = tr.mapping.map(targetPos);
tr.insert(newPos, contentSlice.content);
- tr.setSelection(new TextSelection(tr.doc.resolve(newPos - 1)));
+ tr.setSelection(new TextSelection(tr.doc.resolve(Math.max(0, newPos - 1))));
return true;
};
@@ -1583,24 +1582,11 @@ const insertContent = (value, options) => ({ tr, commands }) => {
return commands.insertContentAt({ from: tr.selection.from, to: tr.selection.to }, value, options);
};
-const removeWhitespaces = (node) => {
- const children = node.childNodes;
- for (let i = children.length - 1; i >= 0; i -= 1) {
- const child = children[i];
- if (child.nodeType === 3 && child.nodeValue && /^(\n\s\s|\n)$/.test(child.nodeValue)) {
- node.removeChild(child);
- }
- else if (child.nodeType === 1) {
- removeWhitespaces(child);
- }
- }
- return node;
-};
function elementFromString(value) {
// add a wrapper to preserve leading and trailing whitespace
const wrappedValue = `<body>${value}</body>`;
const html = new window.DOMParser().parseFromString(wrappedValue, 'text/html').body;
- return removeWhitespaces(html);
+ return html;
}
function createNodeFromContent(content, schema, options) {
@@ -1897,9 +1883,24 @@ const lift = (typeOrName, attributes = {}) => ({ state, dispatch }) => {
return lift$1(state, dispatch);
};
-const liftEmptyBlock = () => ({ state, dispatch }) => {
- return liftEmptyBlock$1(state, dispatch);
-};
+const liftEmptyBlock = () => ({ state, dispatch, editor }) => {
+ return liftEmptyBlock$1(state, (tr) => {
+ if (!dispatch) return true;
+
+ const { selection, storedMarks } = state;
+ const marks = storedMarks || (selection.$to.parentOffset && selection.$from.marks());
+
+ if (!marks) return dispatch(tr);
+
+ const { splittableMarks } = editor.extensionManager;
+ const filteredMarks = marks.filter((mark) =>
+ splittableMarks.includes(mark.type.name)
+ );
+ tr.ensureMarks(filteredMarks)
+
+ return dispatch(tr);
+ });
+}
const liftListItem = typeOrName => ({ state, dispatch }) => {
const type = getNodeType(typeOrName, state.schema);
@@ -3275,16 +3276,20 @@ const Keymap = Extension.create({
new Plugin({
key: new PluginKey('clearDocument'),
appendTransaction: (transactions, oldState, newState) => {
+ const { empty, from, to } = oldState.selection;
+ if (empty) {
+ return;
+ }
+
const docChanges = transactions.some(transaction => transaction.docChanged)
- && !oldState.doc.eq(newState.doc);
+ && oldState.doc.nodeSize !== newState.doc.nodeSize;
if (!docChanges) {
return;
}
- const { empty, from, to } = oldState.selection;
const allFrom = Selection.atStart(oldState.doc).from;
const allEnd = Selection.atEnd(oldState.doc).to;
const allWasSelected = from === allFrom && to === allEnd;
- if (empty || !allWasSelected) {
+ if (!allWasSelected) {
return;
}
const isEmpty = newState.doc.textBetween(0, newState.doc.content.size, ' ', ' ').length === 0;
@@ -3784,23 +3789,28 @@ class Editor extends EventEmitter {
* Creates a ProseMirror view.
*/
createView() {
- const doc = createDocument(this.options.content, this.schema, this.options.parseOptions);
- const selection = resolveFocusPosition(doc, this.options.autofocus);
this.view = new EditorView(this.options.element, {
...this.options.editorProps,
dispatchTransaction: this.dispatchTransaction.bind(this),
state: EditorState.create({
- doc,
- selection: selection || undefined,
- }),
+ doc: this.schema.topNodeType.create(),
+ })
});
+
+ const doc = createDocument(this.options.content, this.schema, this.options.parseOptions);
+ const selection = resolveFocusPosition(doc, this.options.autofocus);
// `editor.view` is not yet available at this time.
// Therefore we will add all plugins and node views directly afterwards.
- const newState = this.state.reconfigure({
+ const newState = EditorState.create({
plugins: this.extensionManager.plugins,
+ doc,
+ selection
+ });
+
+ this.view.setProps({
+ state: newState,
+ nodeViews: this.extensionManager.nodeViews
});
- this.view.updateState(newState);
- this.createNodeViews();
this.prependClass();
// Lets store the editor instance in the DOM element.
// So well have access to it for tests.
diff --git a/node_modules/@tiptap/core/dist/packages/core/src/Editor.d.ts b/node_modules/@tiptap/core/dist/packages/core/src/Editor.d.ts
index 55d710f..e9698f5 100644
--- a/node_modules/@tiptap/core/dist/packages/core/src/Editor.d.ts
+++ b/node_modules/@tiptap/core/dist/packages/core/src/Editor.d.ts
@@ -7,6 +7,7 @@ import * as extensions from './extensions/index.js';
import { NodePos } from './NodePos.js';
import { CanCommands, ChainedCommands, EditorEvents, EditorOptions, JSONContent, SingleCommands, TextSerializer } from './types.js';
export { extensions };
+export interface EditorStorage extends Record<string, any> { }
export interface HTMLElement {
editor?: Editor;
}
@@ -23,7 +24,7 @@ export declare class Editor extends EventEmitter<EditorEvents> {
/**
* Returns the editor storage.
*/
- get storage(): Record<string, any>;
+ get storage(): EditorStorage;
/**
* An object of all registered commands.
*/

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
diff --git a/node_modules/@tiptap/extension-list-keymap/dist/index.cjs b/node_modules/@tiptap/extension-list-keymap/dist/index.cjs
index 0f8ae36..2f715b5 100644
index 9cd822f..1539b13 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) => {
@@ -14,7 +14,7 @@ index 0f8ae36..2f715b5 100644
const listDescendants = [];
$listPos.node().descendants((node, pos) => {
diff --git a/node_modules/@tiptap/extension-list-keymap/dist/index.js b/node_modules/@tiptap/extension-list-keymap/dist/index.js
index f7ab1e4..8645c44 100644
index e759826..11a385f 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) => {

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,8 +1,8 @@
diff --git a/node_modules/prosemirror-model/dist/index.cjs b/node_modules/prosemirror-model/dist/index.cjs
index aa31355..dcb4a75 100644
index 1c45f1c..c92ec8d 100644
--- a/node_modules/prosemirror-model/dist/index.cjs
+++ b/node_modules/prosemirror-model/dist/index.cjs
@@ -95,6 +95,7 @@ var Fragment = function () {
@@ -98,6 +98,7 @@ var Fragment = function () {
var nodeStart = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
var parent = arguments.length > 4 ? arguments[4] : undefined;
for (var i = 0, pos = 0; pos < to; i++) {
@@ -11,7 +11,7 @@ index aa31355..dcb4a75 100644
end = pos + child.nodeSize;
if (end > from && f(child, nodeStart + pos, parent || null, i) !== false && child.content.size) {
diff --git a/node_modules/prosemirror-model/dist/index.js b/node_modules/prosemirror-model/dist/index.js
index 0097d9f..44f7d4a 100644
index 9c37a40..a215c60 100644
--- a/node_modules/prosemirror-model/dist/index.js
+++ b/node_modules/prosemirror-model/dist/index.js
@@ -84,6 +84,7 @@ class Fragment {

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 81a331c..6e60d39 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) {
@@ -3380,7 +3380,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 e076a13..2a0fea5 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) => {
@@ -3647,7 +3647,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

@@ -25,7 +25,7 @@ import React, {
useEffect
} from "react";
import { MenuItem, Icon, MenuButton, MenuSeparator } from "@notesnook/ui";
import { Box, Button, Flex, Text } from "@theme-ui/components";
import { Box, Button, Flex, Text, FlexProps } from "@theme-ui/components";
import { Icons } from "../../toolbar/icons";
import Modal from "react-modal";
import {
@@ -38,7 +38,9 @@ import {
import { useTheme } from "@emotion/react";
import { EmotionThemeProvider, Theme } from "@notesnook/theme";
const AnimatedFlex = motion(Flex);
const AnimatedFlex = motion(
Flex as React.FunctionComponent<Omit<FlexProps, "onDrag" | "onDragEnd">>
);
type ActionSheetHistoryItem = {
title?: string;

View File

@@ -22,8 +22,8 @@ import { Node } from "@tiptap/pm/model";
import { Plugin } from "@tiptap/pm/state";
import { nanoid } from "nanoid";
const NESTED_BLOCK_ID_TYPES = ["callout"];
const BLOCK_ID_TYPES = [
const NESTED_BLOCK_ID_TYPES = new Set(["callout"]);
const BLOCK_ID_TYPES = new Set([
"paragraph",
"heading",
"blockquote",
@@ -39,7 +39,7 @@ const BLOCK_ID_TYPES = [
"mathBlock",
"webclip",
"embed"
];
]);
export const BlockId = Extension.create<any, { seen: WeakSet<Node> }>({
name: "blockId",
@@ -86,11 +86,11 @@ export const BlockId = Extension.create<any, { seen: WeakSet<Node> }>({
if (seen.has(n)) return;
seen.add(n);
if (!n.isBlock || !BLOCK_ID_TYPES.includes(n.type.name)) return;
if (!n.isBlock || !BLOCK_ID_TYPES.has(n.type.name)) return;
if (!n.attrs.blockId)
blocksWithoutBlockId.push({ node: n, pos: offset });
if (NESTED_BLOCK_ID_TYPES.includes(n.type.name))
if (NESTED_BLOCK_ID_TYPES.has(n.type.name))
n.forEach((n, pos) => addBlocks(n, offset + pos + 1));
});
if (blocksWithoutBlockId.length > 0) {

View File

@@ -1,38 +1,38 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`convert br tags to paragraphs 1`] = `"<p>line 1</p><p data-spacing="single">line 2</p>"`;
exports[`convert br tags to paragraphs 1`] = `"<p>line 1</p><p data-spacing=\\"single\\">line 2</p>"`;
exports[`convert br tags to paragraphs 2`] = `"<p>line <em>1</em></p><p data-spacing="single"><em>line</em> 2</p>"`;
exports[`convert br tags to paragraphs 2`] = `"<p>line <em>1</em></p><p data-spacing=\\"single\\"><em>line</em> 2</p>"`;
exports[`convert br tags to paragraphs 3`] = `"<p>line <span><em>1</em></span></p><p data-spacing="single"><span><em>line</em></span> 2</p>"`;
exports[`convert br tags to paragraphs 3`] = `"<p>line <span><em>1</em></span></p><p data-spacing=\\"single\\"><span><em>line</em></span> 2</p>"`;
exports[`convert br tags to paragraphs 4`] = `"<p>line <span><em>1</em></span></p><p data-spacing="single"><span><em>line</em></span> 2</p>"`;
exports[`convert br tags to paragraphs 4`] = `"<p>line <span><em>1</em></span></p><p data-spacing=\\"single\\"><span><em>line</em></span> 2</p>"`;
exports[`convert br tags to paragraphs 5`] = `"<p></p>"`;
exports[`convert br tags to paragraphs 6`] = `
"<p data-spacing="single">A troll, they call me, but I have no wish<!--StartFragment-->
</p><p data-spacing="single">
to be associated with those dolls</p><p data-spacing="single">
</p><p data-spacing="single">
We lack religion, purpose, politics,</p><p data-spacing="single">
"<p data-spacing=\\"single\\">A troll, they call me, but I have no wish<!--StartFragment-->
</p><p data-spacing=\\"single\\">
to be associated with those dolls</p><p data-spacing=\\"single\\">
</p><p data-spacing=\\"single\\">
We lack religion, purpose, politics,</p><p data-spacing=\\"single\\">
and yet, we somehow manage to get by.</p>"
`;
exports[`convert br tags to paragraphs 7`] = `
"<!--StartFragment--><p dir="auto">When I try to paste something (e.g. email content) to a note, the styling is kept, which is good, but the newlines are removed.</p><p data-spacing="single">
"<!--StartFragment--><p dir=\\"auto\\">When I try to paste something (e.g. email content) to a note, the styling is kept, which is good, but the newlines are removed.</p><p data-spacing=\\"single\\">
Also when I share the selection to Notesnook via the share functionality from Android, I have the same issue.</p>
<hr>
<p dir="auto"><strong>Device information:</strong></p><p data-spacing="single">
App version: 2.3.0</p><p data-spacing="single">
Platform: android</p><p data-spacing="single">
Model: OnePlus-CPH2409-31</p><p data-spacing="single">
Pro: true</p><p data-spacing="single">
<p dir=\\"auto\\"><strong>Device information:</strong></p><p data-spacing=\\"single\\">
App version: 2.3.0</p><p data-spacing=\\"single\\">
Platform: android</p><p data-spacing=\\"single\\">
Model: OnePlus-CPH2409-31</p><p data-spacing=\\"single\\">
Pro: true</p><p data-spacing=\\"single\\">
Logged in: yes</p><!--EndFragment-->"
`;
exports[`convert br tags to paragraphs 8`] = `
"<!--StartFragment--><span class="css-901oao css-16my406 r-poiln3 r-bcqeeo r-qvutc0">Why switch from Gmail?
"<!--StartFragment--><span class=\\"css-901oao css-16my406 r-poiln3 r-bcqeeo r-qvutc0\\">Why switch from Gmail?
Not sacrificing features for more privacy, prefer using one app, in many public groups and channels (Telegram)
@@ -43,10 +43,10 @@ exports[`convert br tags to paragraphs 8`] = `
exports[`convert br tags to paragraphs 9`] = `
"<div>
<p style="line-height: 100%; margin-bottom: 0in">
<p style=\\"line-height: 100%; margin-bottom: 0in\\">
I am not talk9ing to you</p>
<p style="line-height: 100%; margin-bottom: 0in"></p>
<p style="line-height: 100%; margin-bottom: 0in">I am not talk9ing to
<p style=\\"line-height: 100%; margin-bottom: 0in\\"></p>
<p style=\\"line-height: 100%; margin-bottom: 0in\\">I am not talk9ing to
you</p>
</div>"
@@ -54,8 +54,8 @@ exports[`convert br tags to paragraphs 9`] = `
exports[`convert br tags to paragraphs 10`] = `
"<div>
<p style="line-height: 100%; margin-bottom: 0in">
I am not talking to you</p><p data-spacing="single">
<p style=\\"line-height: 100%; margin-bottom: 0in\\">
I am not talking to you</p><p data-spacing=\\"single\\">
</p>
<p>I am talking to you</p>
</div>"
@@ -63,32 +63,32 @@ exports[`convert br tags to paragraphs 10`] = `
exports[`convert br tags to paragraphs 11`] = `
"<div>
<!--StartFragment--><p><span>Hello</span></p><p data-spacing="single"></p><p data-spacing="single"></p><p data-spacing="single"></p><p data-spacing="single"></p><p data-spacing="single"></p><p data-spacing="single"><span>world</span></p><!--EndFragment-->
<!--StartFragment--><p><span>Hello</span></p><p data-spacing=\\"single\\"></p><p data-spacing=\\"single\\"></p><p data-spacing=\\"single\\"></p><p data-spacing=\\"single\\"></p><p data-spacing=\\"single\\"></p><p data-spacing=\\"single\\"><span>world</span></p><!--EndFragment-->
</div>"
`;
exports[`convert br tags to paragraphs 12`] = `
"<div>
<!--StartFragment--><meta charset="utf-8"><h1><span>Write notes</span></h1><p data-spacing="single"></p><p dir="ltr" data-spacing="single"><span>Welcome of </span><a><span>Notesnook</span></a><span>, an syncing.</span></p><p data-spacing="single"></p><p dir="ltr" data-spacing="single"><span>Enjoy the read!</span></p><p data-spacing="single"></p><p dir="ltr" data-spacing="single"><span><span><img></span></span></p><p data-spacing="single"></p><p dir="ltr" data-spacing="single"><span>vision?</span></p><p data-spacing="single"></p><p dir="ltr" data-spacing="single"><span>everyone means</span></p><!--EndFragment-->
<!--StartFragment--><meta charset=\\"utf-8\\"><h1><span>Write notes</span></h1><p data-spacing=\\"single\\"></p><p dir=\\"ltr\\" data-spacing=\\"single\\"><span>Welcome of </span><a><span>Notesnook</span></a><span>, an syncing.</span></p><p data-spacing=\\"single\\"></p><p dir=\\"ltr\\" data-spacing=\\"single\\"><span>Enjoy the read!</span></p><p data-spacing=\\"single\\"></p><p dir=\\"ltr\\" data-spacing=\\"single\\"><span><span><img></span></span></p><p data-spacing=\\"single\\"></p><p dir=\\"ltr\\" data-spacing=\\"single\\"><span>vision?</span></p><p data-spacing=\\"single\\"></p><p dir=\\"ltr\\" data-spacing=\\"single\\"><span>everyone means</span></p><!--EndFragment-->
</div>"
`;
exports[`convert br tags to paragraphs 13`] = `"<ul xmlns="http://www.w3.org/1999/xhtml" data-block-id="A9jEKaAm" class="checklist"><li class="checklist--item"><p data-spacing="single"><a target="_blank" rel="noopener noreferrer nofollow" href="https://google.com">hello</a>hello <a target="_blank" rel="noopener noreferrer nofollow" href="https://google.com">hello</a></p><p data-spacing="single"><a target="_blank" rel="noopener noreferrer nofollow" href="https://google.com">hello</a></p><p data-spacing="single"><a target="_blank" rel="noopener noreferrer nofollow" href="https://google.com">hello</a></p><a target="_blank" rel="noopener noreferrer nofollow" href="https://google.com">hello</a></li><li class="checklist--item"><p data-spacing="single"><a target="_blank" rel="noopener noreferrer nofollow" href="https://google.com">hello</a></p><p data-spacing="single"><a target="_blank" rel="noopener noreferrer nofollow" href="https://google.com">hello</a></p><p data-spacing="single"><a target="_blank" rel="noopener noreferrer nofollow" href="https://google.com">hello</a></p><a target="_blank" rel="noopener noreferrer nofollow" href="https://google.com">hello</a></li><li class="checklist--item"><p data-spacing="single"><a target="_blank" rel="noopener noreferrer nofollow" href="https://google.com">hellohello</a></p><p data-spacing="single"><a target="_blank" rel="noopener noreferrer nofollow" href="https://google.com">hello hello hello</a></p><p data-spacing="single"><a target="_blank" rel="noopener noreferrer nofollow" href="https://google.com">hello</a></p><a target="_blank" rel="noopener noreferrer nofollow" href="https://google.com">hello</a></li><li class="checklist--item">vsvdsavdsa</li><li class="checklist--item">vadsvasdv</li></ul><p xmlns="http://www.w3.org/1999/xhtml" data-block-id="CuscJzDm" data-spacing="double"></p>"`;
exports[`convert br tags to paragraphs 13`] = `"<ul xmlns=\\"http://www.w3.org/1999/xhtml\\" data-block-id=\\"A9jEKaAm\\" class=\\"checklist\\"><li class=\\"checklist--item\\"><p data-spacing=\\"single\\"><a target=\\"_blank\\" rel=\\"noopener noreferrer nofollow\\" href=\\"https://google.com\\">hello</a>hello <a target=\\"_blank\\" rel=\\"noopener noreferrer nofollow\\" href=\\"https://google.com\\">hello</a></p><p data-spacing=\\"single\\"><a target=\\"_blank\\" rel=\\"noopener noreferrer nofollow\\" href=\\"https://google.com\\">hello</a></p><p data-spacing=\\"single\\"><a target=\\"_blank\\" rel=\\"noopener noreferrer nofollow\\" href=\\"https://google.com\\">hello</a></p><a target=\\"_blank\\" rel=\\"noopener noreferrer nofollow\\" href=\\"https://google.com\\">hello</a></li><li class=\\"checklist--item\\"><p data-spacing=\\"single\\"><a target=\\"_blank\\" rel=\\"noopener noreferrer nofollow\\" href=\\"https://google.com\\">hello</a></p><p data-spacing=\\"single\\"><a target=\\"_blank\\" rel=\\"noopener noreferrer nofollow\\" href=\\"https://google.com\\">hello</a></p><p data-spacing=\\"single\\"><a target=\\"_blank\\" rel=\\"noopener noreferrer nofollow\\" href=\\"https://google.com\\">hello</a></p><a target=\\"_blank\\" rel=\\"noopener noreferrer nofollow\\" href=\\"https://google.com\\">hello</a></li><li class=\\"checklist--item\\"><p data-spacing=\\"single\\"><a target=\\"_blank\\" rel=\\"noopener noreferrer nofollow\\" href=\\"https://google.com\\">hellohello</a></p><p data-spacing=\\"single\\"><a target=\\"_blank\\" rel=\\"noopener noreferrer nofollow\\" href=\\"https://google.com\\">hello hello hello</a></p><p data-spacing=\\"single\\"><a target=\\"_blank\\" rel=\\"noopener noreferrer nofollow\\" href=\\"https://google.com\\">hello</a></p><a target=\\"_blank\\" rel=\\"noopener noreferrer nofollow\\" href=\\"https://google.com\\">hello</a></li><li class=\\"checklist--item\\">vsvdsavdsa</li><li class=\\"checklist--item\\">vadsvasdv</li></ul><p xmlns=\\"http://www.w3.org/1999/xhtml\\" data-block-id=\\"CuscJzDm\\" data-spacing=\\"double\\"></p>"`;
exports[`convert google docs checklist 1`] = `
"<div>
<!--StartFragment--><meta charset="utf-8"><ul id="docs-internal-guid-0d9a5db3-7fff-ab55-e7ca-b178e1031970" class="checklist"><li dir="ltr" role="checkbox" aria-checked="false" aria-level="1" class="checklist--item"><p dir="ltr" role="presentation"><span>Adsjkfhasdf</span></p></li><li dir="ltr" role="checkbox" aria-checked="false" aria-level="1" class="checklist--item"><p dir="ltr" role="presentation"><span>Asdfsadf</span></p></li><li dir="ltr" role="checkbox" aria-checked="true" aria-level="1" class="checklist--item checked"><p dir="ltr" role="presentation"><span>Asdfsda</span></p></li><li dir="ltr" role="checkbox" aria-checked="true" aria-level="1" class="checklist--item checked"><p dir="ltr" role="presentation"><span>Fasd</span></p></li><li dir="ltr" role="checkbox" aria-checked="true" aria-level="1" class="checklist--item checked"><p dir="ltr" role="presentation"><span>Fasd</span></p></li><li dir="ltr" role="checkbox" aria-checked="false" aria-level="1" class="checklist--item"><p dir="ltr" role="presentation"><span>F</span></p></li><li dir="ltr" role="checkbox" aria-checked="true" aria-level="1" class="checklist--item checked"><p dir="ltr" role="presentation"><span>akcasb</span></p></li><ul class="checklist"><li dir="ltr" role="checkbox" aria-checked="true" aria-level="2" class="checklist--item checked"><p dir="ltr" role="presentation"><span>Asdf</span></p></li><li dir="ltr" role="checkbox" aria-checked="false" aria-level="2" class="checklist--item"><p dir="ltr" role="presentation"><span>Asdcasdc</span></p></li><li dir="ltr" role="checkbox" aria-checked="false" aria-level="2" class="checklist--item"><p dir="ltr" role="presentation"><span>sdac</span></p></li></ul><li dir="ltr" role="checkbox" aria-checked="false" aria-level="1" class="checklist--item"><p dir="ltr" role="presentation"><span>Asdfsda</span></p></li></ul><!--EndFragment-->
<!--StartFragment--><meta charset=\\"utf-8\\"><ul id=\\"docs-internal-guid-0d9a5db3-7fff-ab55-e7ca-b178e1031970\\" class=\\"checklist\\"><li dir=\\"ltr\\" role=\\"checkbox\\" aria-checked=\\"false\\" aria-level=\\"1\\" class=\\"checklist--item\\"><p dir=\\"ltr\\" role=\\"presentation\\"><span>Adsjkfhasdf</span></p></li><li dir=\\"ltr\\" role=\\"checkbox\\" aria-checked=\\"false\\" aria-level=\\"1\\" class=\\"checklist--item\\"><p dir=\\"ltr\\" role=\\"presentation\\"><span>Asdfsadf</span></p></li><li dir=\\"ltr\\" role=\\"checkbox\\" aria-checked=\\"true\\" aria-level=\\"1\\" class=\\"checklist--item checked\\"><p dir=\\"ltr\\" role=\\"presentation\\"><span>Asdfsda</span></p></li><li dir=\\"ltr\\" role=\\"checkbox\\" aria-checked=\\"true\\" aria-level=\\"1\\" class=\\"checklist--item checked\\"><p dir=\\"ltr\\" role=\\"presentation\\"><span>Fasd</span></p></li><li dir=\\"ltr\\" role=\\"checkbox\\" aria-checked=\\"true\\" aria-level=\\"1\\" class=\\"checklist--item checked\\"><p dir=\\"ltr\\" role=\\"presentation\\"><span>Fasd</span></p></li><li dir=\\"ltr\\" role=\\"checkbox\\" aria-checked=\\"false\\" aria-level=\\"1\\" class=\\"checklist--item\\"><p dir=\\"ltr\\" role=\\"presentation\\"><span>F</span></p></li><li dir=\\"ltr\\" role=\\"checkbox\\" aria-checked=\\"true\\" aria-level=\\"1\\" class=\\"checklist--item checked\\"><p dir=\\"ltr\\" role=\\"presentation\\"><span>akcasb</span></p></li><ul class=\\"checklist\\"><li dir=\\"ltr\\" role=\\"checkbox\\" aria-checked=\\"true\\" aria-level=\\"2\\" class=\\"checklist--item checked\\"><p dir=\\"ltr\\" role=\\"presentation\\"><span>Asdf</span></p></li><li dir=\\"ltr\\" role=\\"checkbox\\" aria-checked=\\"false\\" aria-level=\\"2\\" class=\\"checklist--item\\"><p dir=\\"ltr\\" role=\\"presentation\\"><span>Asdcasdc</span></p></li><li dir=\\"ltr\\" role=\\"checkbox\\" aria-checked=\\"false\\" aria-level=\\"2\\" class=\\"checklist--item\\"><p dir=\\"ltr\\" role=\\"presentation\\"><span>sdac</span></p></li></ul><li dir=\\"ltr\\" role=\\"checkbox\\" aria-checked=\\"false\\" aria-level=\\"1\\" class=\\"checklist--item\\"><p dir=\\"ltr\\" role=\\"presentation\\"><span>Asdfsda</span></p></li></ul><!--EndFragment-->
</div>"
`;
exports[`properly format codeblocks 1`] = `
"<div>
<!--StartFragment--><p>Sure! Here's an implementation of a word counter for Thai that considers each syllable consisting of a consonant sound followed by a vowel sound as a word:</p><pre><code>javascript</code></pre><pre class="language-javascript"><code>function countThaiWords(text) {
<!--StartFragment--><p>Sure! Here's an implementation of a word counter for Thai that considers each syllable consisting of a consonant sound followed by a vowel sound as a word:</p><pre><code>javascript</code></pre><pre class=\\"language-javascript\\"><code>function countThaiWords(text) {
// define a regular expression to match Thai syllables
const thaiSyllableRegex = /[ก-ฺเ-๛]+[฀-๿]?/g;
const thaiSyllableRegex = /[&#xe01;-&#xe3a;&#xe40;-&#xe5b;]+[&#xe00;-&#xe7f;]?/g;
// count the number of matches of the Thai syllable regex in the text
const matches = text.match(thaiSyllableRegex) || [];
@@ -99,9 +99,9 @@ exports[`properly format codeblocks 1`] = `
}
// example usage
const text = "สวัสดีค่ะยินดีต้อนรับเข้าสู่โลกของฉัน";
const text = &quot;&#xe2a;&#xe27;&#xe31;&#xe2a;&#xe14;&#xe35;&#xe04;&#xe48;&#xe30;&#xe22;&#xe34;&#xe19;&#xe14;&#xe35;&#xe15;&#xe49;&#xe2d;&#xe19;&#xe23;&#xe31;&#xe1a;&#xe40;&#xe02;&#xe49;&#xe32;&#xe2a;&#xe39;&#xe48;&#xe42;&#xe25;&#xe01;&#xe02;&#xe2d;&#xe07;&#xe09;&#xe31;&#xe19;&quot;;
const wordCount = countThaiWords(text);
console.log(\`Word count: \${wordCount}\`);
console.log(\`Word count: &dollar;{wordCount}\`);
// output: Word count: 9
</code></pre><p>This implementation defines a regular expression to match Thai syllables, which are composed of one or more Thai characters. It then uses the <code>match</code> function of the <code>String</code> object to count the number of matches of the Thai syllable regex in the input text.</p><p>Note that this implementation assumes that each syllable consisting of a consonant sound followed by a vowel sound is considered a word in Thai. This may not always be accurate, as some words in Thai may consist of multiple syllables, and some syllables may be used as prefixes or suffixes to modify other words. However, this approach should work well for many cases where each syllable is considered a separate word.</p><!--EndFragment-->

View File

@@ -1,7 +1,7 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`convert text to html 1`] = `"<p data-spacing="single">Hello</p><p data-spacing="single">world</p>"`;
exports[`convert text to html 1`] = `"<p data-spacing=\\"single\\">Hello</p><p data-spacing=\\"single\\">world</p>"`;
exports[`convert text to html 2`] = `"<p data-spacing="single">What</p><p data-spacing="single"></p><p data-spacing="single"></p><p data-spacing="single">NO!</p>"`;
exports[`convert text to html 2`] = `"<p data-spacing=\\"single\\">What</p><p data-spacing=\\"single\\"></p><p data-spacing=\\"single\\"></p><p data-spacing=\\"single\\">NO!</p>"`;
exports[`convert text to html 3`] = `"<p data-spacing="single">Hello</p><p data-spacing="single">World</p><p data-spacing="single">what?</p><p data-spacing="single">Nay!</p>"`;
exports[`convert text to html 3`] = `"<p data-spacing=\\"single\\">Hello</p><p data-spacing=\\"single\\">World</p><p data-spacing=\\"single\\">what?</p><p data-spacing=\\"single\\">Nay!</p>"`;

View File

@@ -43,7 +43,6 @@ exports[`copying multiple lists shouldn't copy only the first list 1`] = `
"attrs": {
"listType": "",
"start": 1,
"type": undefined,
},
"content": [
{
@@ -67,7 +66,6 @@ exports[`copying multiple lists shouldn't copy only the first list 1`] = `
"attrs": {
"listType": "",
"start": 1,
"type": undefined,
},
"content": [
{
@@ -91,7 +89,6 @@ exports[`copying multiple lists shouldn't copy only the first list 1`] = `
"attrs": {
"listType": "",
"start": 1,
"type": undefined,
},
"content": [
{

View File

@@ -234,20 +234,11 @@ export const CodeBlock = Node.create<CodeBlockOptions>({
},
toggleCodeBlock:
(attributes) =>
({ commands, state, tr }) => {
const isInsideCodeBlock = this.editor.isActive(this.type.name);
if (!isInsideCodeBlock) {
const { from, to } = state.selection;
const text = state.doc.textBetween(from, to, "\n");
tr.replaceSelectionWith(
this.type.create(
{ ...attributes, id: createCodeblockId() },
state.schema.text(text)
)
);
return commands.setTextSelection({ from, to: tr.mapping.map(to) });
}
return commands.clearNodes();
({ commands }) => {
return commands.toggleNode(this.name, "paragraph", {
...attributes,
id: createCodeblockId()
});
},
changeCodeBlockIndentation:
(options) =>

File diff suppressed because one or more lines are too long

View File

@@ -33,17 +33,6 @@ const CODEBLOCKS_HTML = h("div", [
})
]).innerHTML;
const CODE_PARAGRAPH = `function helloWorld() {
console.log('Hello, World!');
}`;
interface DocumentNode {
type: string;
attrs?: { [key: string]: any };
content?: DocumentNode[];
text?: string;
}
test("codeblocks should get highlighted on init", async () => {
const editorElement = h("div");
createEditor({
@@ -254,73 +243,3 @@ test("Switching codeblock language should register the new language", async () =
await new Promise((resolve) => setTimeout(resolve, 100));
expect(refractor.registered("java")).toBe(true);
});
test("Toggling multiple paragraphs into a codeblock should make one codeblock", async () => {
const editorElement = h("div");
const { editor } = createEditor({
element: editorElement,
initialContent: CODE_PARAGRAPH,
extensions: {
codeblock: CodeBlock,
codeBlock: false,
code: false
}
});
const docLength = editor.state.doc.nodeSize - 2;
editor.commands.setTextSelection({ from: 0, to: docLength });
editor.commands.toggleCodeBlock();
editor.commands.updateAttributes(CodeBlock.name, { language: "javascript" });
await new Promise((resolve) => setTimeout(resolve, 100));
expect(editorElement.outerHTML).toMatchSnapshot();
const docJSON = editor.state.doc.toJSON();
const content = docJSON.content;
const codeBlockNodes = content.filter(
(node: DocumentNode) => node.type === "codeblock"
);
const otherNodes = content.filter(
(node: DocumentNode) => node.type !== "codeblock"
);
expect(codeBlockNodes).toHaveLength(1);
expect(otherNodes).toHaveLength(0);
});
test("Toggling from a codeblock should make codeblock disappear", async () => {
const editorElement = document.createElement("div");
const { editor } = createEditor({
element: editorElement,
initialContent: CODEBLOCKS_HTML,
extensions: {
codeblock: CodeBlock,
codeBlock: false,
code: false
}
});
const docLength = editor.state.doc.nodeSize - 2;
editor.commands.setTextSelection({ from: 0, to: docLength });
editor.commands.toggleCodeBlock();
await new Promise((resolve) => setTimeout(resolve, 100));
expect(editorElement.outerHTML).toMatchSnapshot();
const docJSON = editor.state.doc.toJSON();
const content = docJSON.content;
const codeBlockNodes = content.filter(
(node: DocumentNode) => node.type === "codeblock"
);
const paragraphNodes = content.filter(
(node: DocumentNode) => node.type === "paragraph"
);
expect(codeBlockNodes).toHaveLength(0);
expect(paragraphNodes).toHaveLength(2);
});

View File

@@ -21,7 +21,6 @@ import { Extension } from "@tiptap/core";
import { isInTable } from "@tiptap/pm/tables";
import { isListActive } from "../../utils/prosemirror";
import { CodeBlock } from "../code-block";
import { showLinkPopup } from "../../toolbar/popups/link-popup";
export const KeyMap = Extension.create({
name: "key-map",
@@ -66,10 +65,6 @@ export const KeyMap = Extension.create({
});
});
return true;
},
"Mod-k": ({ editor }) => {
showLinkPopup(editor);
return true;
}
};
}

View File

@@ -29,6 +29,7 @@ import {
} from "@tiptap/core";
import { Plugin, TextSelection } from "@tiptap/pm/state";
import { find, registerCustomProtocol, reset } from "linkifyjs";
import { autolink } from "./helpers/autolink";
import { clickHandler } from "./helpers/clickHandler";
import { pasteHandler } from "./helpers/pasteHandler";
@@ -289,22 +290,6 @@ export const Link = Mark.create<LinkOptions>({
}
return plugins;
},
addKeyboardShortcuts() {
return {
Space: ({ editor }) => {
const { from, to } = editor.state.selection;
if (!editor.state.doc.rangeHasMark(from, to + 1, this.type)) {
const { tr } = editor.state;
tr.removeStoredMark(editor.schema.marks.link);
editor.view.dispatch(tr);
}
return false;
}
};
}
});

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 { createEditor, h, ul, li } from "../../../../test-utils";
import { createEditor, h, ul, li } from "@/test-utils";
import { test, expect } from "vitest";
import { TaskListNode } from "../index";
import { TaskItemNode } from "../../task-item";

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 { NodeWithOffset } from "../../../utils/prosemirror";
import { NodeWithOffset } from "@/src/utils/prosemirror";
import { createContext, useContext } from "react";
const HoverPopupContext = createContext<{

View File

@@ -21,12 +21,7 @@ import { Input } from "@theme-ui/components";
import { Flex } from "@theme-ui/components";
import { useRefValue } from "../../hooks/use-ref-value";
import { Popup } from "../components/popup";
import { isInternalLink, LinkDefinition } from "../tools/link";
import { showPopup } from "../../components/popup-presenter";
import Link, { LinkAttributes } from "../../extensions/link";
import { ImageNode } from "../../extensions/image";
import { findMark, selectionToOffset } from "../../utils/prosemirror";
import { Editor, getMarkAttributes } from "@tiptap/core";
import { LinkDefinition } from "../tools/link";
export type LinkPopupProps = {
link?: LinkDefinition;
@@ -51,17 +46,13 @@ export function LinkPopup(props: LinkPopupProps) {
onClose={onClose}
action={{
title: isEditing ? "Save edits" : "Insert link",
onClick: () => onDone(link.current)
onClick: () => {
if (!link.current) return;
onDone(link.current);
}
}}
>
<Flex
sx={{ p: 1, flexDirection: "column", width: ["auto", 250] }}
onKeyUp={(e) => {
if (e.key === "Enter") {
onDone(link.current);
}
}}
>
<Flex sx={{ p: 1, flexDirection: "column", width: ["auto", 250] }}>
{!isImageActive && (
<Input
type="text"
@@ -89,60 +80,3 @@ export function LinkPopup(props: LinkPopupProps) {
</Popup>
);
}
export async function showLinkPopup(editor: Editor) {
const isActive = editor.isActive(Link.name);
const isImageActive = editor.isActive(ImageNode.name);
const selectedNode = selectionToOffset(editor.state);
const link = selectedNode?.node
? findMark(selectedNode.node, Link.name)
: null;
const attrs = link?.attrs || getMarkAttributes(editor.state, Link.name);
const selectedText = editor.state.doc.textBetween(
editor.state.selection.from,
editor.state.selection.to
);
if (isInternalLink(attrs.href)) {
const link = await editor.storage.createInternalLink?.(
attrs as LinkAttributes
);
if (!link) return;
if (isActive) {
const { from, to } = editor.state.selection;
if (selectedNode) editor.commands.setTextSelection(selectedNode);
editor.commands.setLink(link);
if (selectedNode) editor.commands.setTextSelection({ from, to });
} else {
editor.commands.setLink({ ...link, title: selectedText || link.title });
}
return;
}
showPopup({
popup: (close) => (
<LinkPopup
link={
selectedNode && selectedNode.node
? {
title: isActive ? selectedNode.node.textContent : selectedText,
href: link?.attrs.href || ""
}
: undefined
}
onClose={close}
onDone={(link) => {
if (isActive) {
if (selectedNode)
editor.chain().focus().setTextSelection(selectedNode).run();
editor.commands.setLink(link);
} else editor.commands.toggleLink(link);
close();
}}
isEditing={isActive}
isImageActive={isImageActive}
/>
),
mobile: "sheet",
desktop: "popup"
});
}

View File

@@ -25,7 +25,7 @@ import { MoreTools } from "../components/more-tools";
import { useToolbarLocation } from "../stores/toolbar-store";
import { ImageProperties as ImagePropertiesPopup } from "../popups/image-properties";
import { findSelectedNode } from "../../utils/prosemirror";
import { ImageAttributes } from "../../extensions/image";
import { ImageAttributes } from "@/src/extensions/image";
export function ImageSettings(props: ToolProps) {
const { editor } = props;

View File

@@ -315,6 +315,6 @@ function LinkTool(props: LinkToolProps) {
);
}
export function isInternalLink(href?: string | null) {
function isInternalLink(href?: string | null) {
return typeof href === "string" ? href.startsWith("nn://") : false;
}

File diff suppressed because one or more lines are too long

View File

@@ -4,6 +4,10 @@
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"jsx": "react-jsx",
"outDir": "./dist/",
"baseUrl": "./",
"paths": {
"@/*": ["*"]
},
"incremental": true
},
"exclude": ["src/**/*.test.ts"],

View File

@@ -0,0 +1,19 @@
{
"extends": "../../tsconfig",
"compilerOptions": {
"module": "commonjs",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"jsx": "react-jsx",
"outDir": "./dist/",
"baseUrl": "./",
"paths": {
"@/*": ["*"]
}
},
"ts-node": {
"transpileOnly": true,
"swc": true,
"require": ["tsconfig-paths/register"]
},
"include": ["src/"]
}