Compare commits

..

8 Commits

Author SHA1 Message Date
Abdullah Atta
9f2c9caee1 web: ask password again if wrong during import 2026-08-24 09:44:07 +05:00
Abdullah Atta
2bb3d43db6 web: @notesnook-importer/core dynamic import 2026-08-21 12:54:51 +05:00
Abdullah Atta
87987c75b2 web: fix notesnook-importer installed from wrong dir 2026-08-20 16:51:32 +05:00
Abdullah Atta
47140d691d web: fix bundle too large to precache error 2026-08-20 13:37:47 +05:00
Abdullah Atta
98e35d93a3 web: add missing zod library 2026-08-20 10:28:51 +05:00
Abdullah Atta
ee3f08a725 editor: improve SVG size handling
this is experimental and should be tested properly
2026-08-19 13:31:01 +05:00
Abdullah Atta
54a9c131fa web: update importer to 2.6.0 2026-08-19 13:29:54 +05:00
Abdullah Atta
bcfdb5caf9 web: add support for using imported sqlite dbs 2026-08-19 13:00:44 +05:00
48 changed files with 1954 additions and 644 deletions

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/desktop",
"version": "3.4.6",
"version": "3.4.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "3.4.6",
"version": "3.4.5",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "3.4.6",
"version": "3.4.5",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/cjs/index.js",

View File

@@ -140,7 +140,7 @@ android {
if (project.hasProperty("prBuildNumber")) {
versionCode Integer.parseInt(prBuildNumber())
} else {
versionCode 3116
versionCode 3114
}
versionName getNpmVersion()
testBuildType System.getProperty('testBuildType', 'debug')

View File

@@ -42,12 +42,3 @@ allprojects {
maven { url 'https://www.jitpack.io' }
}
}
// Pin the ndkVersion for all subprojects
subprojects { subproject ->
["com.android.application", "com.android.library"].each { pluginId ->
subproject.plugins.withId(pluginId) {
subproject.android.ndkVersion = rootProject.ext.ndkVersion
}
}
}

View File

@@ -1,3 +1,6 @@
- Added sync status icon in sidebar
- Added new reminder shortcut in app icon context menu
- Improved editor saving reliability
- Minor bug fixes and improvements
Thank you for using Notesnook!

View File

@@ -33,6 +33,7 @@ import {
setGroupOptionsById
} from "../../../hooks/use-group-options";
import { eSendEvent } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { RouteName } from "../../../stores/use-navigation-store";
import { useNotebookStore } from "../../../stores/use-notebook-store";
import { useTagStore } from "../../../stores/use-tag-store";
@@ -45,7 +46,6 @@ import { Button } from "../../ui/button";
import { Pressable } from "../../ui/pressable";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import Navigation from "../../../services/navigation";
const Sort = ({
dataType,
screen,
@@ -87,7 +87,8 @@ const Sort = ({
};
const updateGroupOptions = async (_groupOptions: GroupOptions) => {
await setGroupOptionsById(groupType, _groupOptions, groupId, type);
console.log(groupId, type);
setGroupOptionsById(groupType, _groupOptions, groupId, type);
setGroupOptions(_groupOptions);
setTimeout(() => {
if (screen) Navigation.queueRoutesForUpdate(screen);

View File

@@ -27,7 +27,7 @@ import { useUserStore } from "../../../stores/use-user-store";
import { getContainerBorder } from "../../../utils/colors";
import { NotesnookModule } from "../../../utils/notesnook-module";
import { Toast } from "../../toast";
import { useReduceMotion } from "../../../hooks/use-reduce-motion";
/**
*
* @param {any} param0
@@ -53,9 +53,6 @@ const SheetWrapper = ({
const sheetKeyboardHandler = useSettingStore(
(state) => state.sheetKeyboardHandler
);
const isReduceMotionEnabled = useReduceMotion();
const isAnimated = !isReduceMotionEnabled;
const largeTablet = deviceMode === "tablet";
const smallTablet = deviceMode === "smallTablet";
const dimensions = useSettingStore((state) => state.dimensions);
@@ -128,7 +125,6 @@ const SheetWrapper = ({
<ScopedThemeProvider value="sheet">
<ActionSheet
ref={fwdRef || localRef}
animated={isAnimated}
testIDs={{
backdrop: "sheet-backdrop"
}}

View File

@@ -54,13 +54,15 @@ export function useGroupOptions(
const [groupOptions, setGroupOptions] = useState(
getGroupOptions(groupingKey, id, type)
);
console.log(groupingKey, id, type, groupOptions, "options");
const groupOptionsRef = useRef(groupOptions);
groupOptionsRef.current = groupOptions;
useEffect(() => {
const onUpdate = (_groupingKey: string, _id?: string, _type?: string) => {
if (_groupingKey !== groupingKey || _type !== type) return;
if (_id && _type && _id !== id) return;
if (_groupingKey !== groupingKey) return;
if (_id && _type && _id !== id && _type !== type) return;
const options = getGroupOptions(groupingKey, id, type);
if (!options) return;
if (
@@ -68,7 +70,9 @@ export function useGroupOptions(
groupOptionsRef.current?.sortBy !== options.sortBy ||
groupOptionsRef.current?.sortDirection !== options?.sortDirection
) {
console.log("onUpdate", _id, _type);
setGroupOptions({ ...options });
Navigation.queueRoutesForUpdate();
}
};

View File

@@ -1,50 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useEffect, useState } from "react";
import { AccessibilityInfo } from "react-native";
export function useReduceMotion(): boolean {
const [isReduceMotionEnabled, setIsReduceMotionEnabled] =
useState<boolean>(false);
useEffect(() => {
AccessibilityInfo.isReduceMotionEnabled()
.then(setIsReduceMotionEnabled)
.catch(() => {});
const subscription = AccessibilityInfo.addEventListener(
"reduceMotionChanged",
setIsReduceMotionEnabled
);
return () => {
if (subscription?.remove) {
subscription.remove();
} else if ((AccessibilityInfo as any).removeEventListener) {
(AccessibilityInfo as any).removeEventListener(
"reduceMotionChanged",
setIsReduceMotionEnabled
);
}
};
}, []);
return isReduceMotionEnabled;
}

View File

@@ -749,13 +749,12 @@ export const useEditor = (
await postMessage(NativeEvents.title, item.title, tabId);
overlay(false);
const updatedTab = useTabStore.getState().getTab(tabId!);
await postMessage(
NativeEvents.html,
{
data: currentContents.current[item.id]?.data || "",
scrollTop: updatedTab?.session?.scrollTop,
selection: updatedTab?.session?.selection,
scrollTop: tab?.session?.scrollTop,
selection: tab?.session?.selection,
searchResultIndex: event.searchResultIndex
},
tabId,

View File

@@ -711,10 +711,9 @@ const ShareView = () => {
width: "100%",
marginTop: 6
}}
onPress={async () => {
const feature = await isFeatureAvailable("fullQualityImages");
if (feature?.isAllowed) {
setCompress((prev) => !prev);
onPress={() => {
if (fullQualityImages?.isAllowed) {
setCompress(!compress);
}
}}
>

View File

@@ -1,6 +1,6 @@
// Production iOS build identifiers
IOS_CURRENT_PROJECT_VERSION = 2194
IOS_MARKETING_VERSION = 3.4.10
IOS_CURRENT_PROJECT_VERSION = 2192
IOS_MARKETING_VERSION = 3.4.8
IOS_MAIN_BUNDLE_ID = org.streetwriters.notesnook
IOS_WIDGET_BUNDLE_ID = org.streetwriters.notesnook.notewidget
IOS_SHARE_BUNDLE_ID = org.streetwriters.notesnook.share

View File

@@ -1,6 +1,6 @@
// Production iOS build identifiers
IOS_CURRENT_PROJECT_VERSION = 2194
IOS_MARKETING_VERSION = 3.4.10
IOS_CURRENT_PROJECT_VERSION = 2192
IOS_MARKETING_VERSION = 3.4.8
IOS_MAIN_BUNDLE_ID = org.streetwriters.notesnook
IOS_WIDGET_BUNDLE_ID = org.streetwriters.notesnook.notewidget
IOS_SHARE_BUNDLE_ID = org.streetwriters.notesnook.share

View File

@@ -1,6 +1,6 @@
// Staging iOS build identifiers
IOS_CURRENT_PROJECT_VERSION = 2194
IOS_MARKETING_VERSION = 3.4.10
IOS_CURRENT_PROJECT_VERSION = 2192
IOS_MARKETING_VERSION = 3.4.8
IOS_MAIN_BUNDLE_ID = org.streetwriters.notesnook
IOS_WIDGET_BUNDLE_ID = org.streetwriters.notesnook.notewidget
IOS_SHARE_BUNDLE_ID = org.streetwriters.notesnook.share

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/mobile",
"version": "3.4.10",
"version": "3.4.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/mobile",
"version": "3.4.10",
"version": "3.4.5",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
@@ -191,7 +191,6 @@
"version": "2.1.3",
"license": "GPL-3.0-or-later",
"dependencies": {
"@notesnook/common": "^2.1.3",
"@notesnook/core": "file:../core",
"@readme/data-urls": "^3.0.0",
"dayjs": "1.11.13",
@@ -334,10 +333,7 @@
"papaparse": "^5.5.3",
"prism-themes": "^1.9.0",
"prosemirror-codemark": "^0.4.2",
"prosemirror-model": "1.25.11",
"prosemirror-state": "1.4.4",
"prosemirror-transform": "1.12.0",
"prosemirror-view": "1.42.2",
"prosemirror-view": "1.34.2",
"re-resizable": "^6.9.18",
"react-colorful": "^5.6.1",
"redent": "^4.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "3.4.10",
"version": "3.4.8",
"private": true,
"license": "GPL-3.0-or-later",
"scripts": {

View File

@@ -33,6 +33,7 @@ import {
} from "./utils";
import { NavigationMenuModel } from "./navigation-menu.model";
import { AppModel } from "./app.model";
import { getAppFromPage } from "../../../desktop/__tests__/electron-test/utils";
import { readFile } from "node:fs/promises";
export class SettingsViewModel {
@@ -134,10 +135,6 @@ export class SettingsViewModel {
};
if (IS_DESKTOP_TESTS) {
const { getAppFromPage } = await import(
"../../../desktop/__tests__/electron-test/utils"
);
await saveBackup();
const toast = new AppModel(this.page).toasts.toasts.locator(
getTestId("toast-message")

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "@notesnook/web",
"description": "Your private note taking space",
"version": "3.4.6",
"version": "3.4.5",
"private": true,
"main": "./src/app.js",
"homepage": "https://notesnook.com/",
@@ -18,7 +18,7 @@
"@lingui/react": "5.1.2",
"@mdi/js": "7.4.47",
"@mdi/react": "1.6.1",
"@notesnook-importer/core": "^2.4.5",
"@notesnook-importer/core": "^2.6.1",
"@notesnook/common": "file:../../packages/common",
"@notesnook/core": "file:../../packages/core",
"@notesnook/crypto": "file:../../packages/crypto",
@@ -82,6 +82,7 @@
"timeago.js": "4.0.2",
"w3c-keyname": "^2.2.6",
"wouter": "2.12.1",
"zod": "^4.4.3",
"zustand": "4.5.5",
"zustand-mutative": "^1.2.0"
},

View File

@@ -87,6 +87,67 @@ export class IDBBatchAtomicVFS extends VFS.Base {
this.#idb = null;
}
/**
* Import a file into the VFS so it can be opened by SQLite. Block 0 (offset
* 0) carries the total file size; a streamed file is written in chunks as
* blocks at negative offsets, matching the layout {@link xRead} expects.
*
* @param {string} path
* @param {Uint8Array | ReadableStream<Uint8Array>} source
*/
async importFile(path, source) {
if (source instanceof Uint8Array) {
await this.#idb.run("readwrite", ({ blocks }) => {
blocks.put({
path,
offset: 0,
version: 0,
data: source,
fileSize: source.byteLength
});
});
return;
}
const reader = source.getReader();
const batch = [];
let offset = 0;
let firstChunk;
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
firstChunk = firstChunk ?? value;
batch.push(
offset === 0
? { path, offset: 0, version: 0, data: value, fileSize: 0 }
: { path, offset: -offset, version: 0, data: value }
);
offset += value.byteLength;
if (batch.length >= 8) {
const blocks = batch.splice(0);
await this.#idb.run("readwrite", ({ blocks: store }) => {
for (const block of blocks) store.put(block);
});
}
}
} finally {
reader.releaseLock();
}
// Final block 0 with the total file size (replaces the placeholder).
await this.#idb.run("readwrite", ({ blocks }) => {
for (const block of batch) blocks.put(block);
blocks.put({
path,
offset: 0,
version: 0,
data: firstChunk ?? new Uint8Array(0),
fileSize: offset
});
});
}
/**
* @param {string?} name
* @param {number} fileId

View File

@@ -103,19 +103,22 @@ export class IDBContext {
// @ts-ignore
this.#tx = db.transaction(db.objectStoreNames, mode, this.#txOptions);
const timestamp = (this.#txTimestamp = performance.now());
const tx = this.#tx;
// Chain the result of every transaction. If any transaction is
// aborted then the next sync() call will throw.
// aborted then the next sync() call will throw. The transaction is
// captured so a later transaction (which replaces `#tx`) or a
// completed one (which nulls it) cannot break this handler.
this.#putChain = this.#putChain.then(() => {
return new Promise((resolve, reject) => {
this.#tx.addEventListener("complete", (event) => {
tx.addEventListener("complete", (event) => {
resolve();
if (this.#tx === event.target) {
this.#tx = null;
}
log(`transaction ${mapTxToId.get(event.target)} complete`);
});
this.#tx.addEventListener("abort", (event) => {
tx.addEventListener("abort", (event) => {
console.warn("tx abort", (performance.now() - timestamp) / 1000);
// @ts-ignore
const e = event.target.error;

View File

@@ -0,0 +1,121 @@
/*
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 { wrap } from "comlink";
import type { Remote } from "comlink";
import type {
SqlDatabase,
SqliteAdapter,
SqliteDatabaseFiles,
SqlParams,
SqlRow
} from "@notesnook-importer/core";
import SQLiteWorker from "./sqlite.worker?worker";
import { IDBBatchAtomicVFS } from "./IDBBatchAtomicVFS";
import SQLiteAsyncURI from "./wa-sqlite-async.wasm?url";
type ImporterWorkerAPI = {
open(
name: string,
options: { async: boolean; url?: string; encrypted: boolean; skipExtensions?: boolean }
): Promise<void>;
run(
mode: "query" | "exec" | "raw",
sql: string,
parameters?: unknown[]
): Promise<{ rows: SqlRow[] }>;
close(): Promise<void>;
};
function toParams(params?: SqlParams): unknown[] | undefined {
return Array.isArray(params) ? params : params ? Object.values(params) : undefined;
}
class WorkerSqlDatabase implements SqlDatabase {
constructor(
private readonly worker: InstanceType<typeof SQLiteWorker>,
private readonly api: Remote<ImporterWorkerAPI>,
private readonly idbName: string
) {}
async all<T = SqlRow>(sql: string, params?: SqlParams): Promise<T[]> {
const result = await this.api.run("query", sql, toParams(params));
return result.rows as T[];
}
async get<T = SqlRow>(sql: string, params?: SqlParams): Promise<T | undefined> {
const result = await this.api.run("query", sql, toParams(params));
return result.rows[0] as T | undefined;
}
close() {
void this.api
.close()
.catch(() => {})
.finally(() => {
this.worker.terminate();
try {
indexedDB.deleteDatabase(this.idbName);
} catch {
// ignore
}
});
}
}
/**
* An SQLite adapter for Apple Notes / Apple Journal. Database files are
* streamed into IndexedDB (via the `IDBBatchAtomicVFS` block format) on the
* main thread — so large imports never load the files fully into memory — and
* the existing `sqlite.worker.ts` is reused to open the database and run
* queries. WAL data is replayed with exclusive locking, so no manual
* checkpointing is needed.
*/
export class ImporterSqliteAdapter implements SqliteAdapter {
async open(files: SqliteDatabaseFiles): Promise<SqlDatabase> {
const name = `importer-${crypto.randomUUID()}`;
const path = `/${name}`;
const vfs = new IDBBatchAtomicVFS(name, { durability: "strict" });
try {
await vfs.importFile(path, files.main);
if (files.wal) await vfs.importFile(`${path}-wal`, files.wal);
// The -shm file is intentionally skipped: it is only a cache of the WAL
// index and its import breaks the Asyncify build. Exclusive locking makes
// SQLite rebuild it in memory.
} finally {
await vfs.close();
}
const worker = new SQLiteWorker();
const api = wrap<ImporterWorkerAPI>(worker);
await api.open(name, {
async: true,
encrypted: false,
url: SQLiteAsyncURI,
skipExtensions: true
});
// WAL + exclusive locking (the same combination the Notesnook database
// uses) replays the imported -wal file without needing a shared -shm.
await api.run("exec", "PRAGMA locking_mode=EXCLUSIVE");
return new WorkerSqlDatabase(worker, api, name);
}
}

View File

@@ -37,6 +37,11 @@ type SQLiteOptions = {
async: boolean;
url?: string;
encrypted: boolean;
/**
* Skip FTS5 extension registration. Used for imported (read-only) databases
* whose schema should not be touched before the importer queries them.
*/
skipExtensions?: boolean;
};
class _SQLiteWorker {
@@ -49,6 +54,7 @@ class _SQLiteWorker {
encrypted = false;
name = "";
async = false;
skipExtensions = false;
async open(name: string, options: SQLiteOptions) {
if (this.db) {
@@ -59,6 +65,7 @@ class _SQLiteWorker {
this.encrypted = options.encrypted;
this.name = name;
this.async = options.async;
this.skipExtensions = !!options.skipExtensions;
const option = options.url ? { locateFile: () => options.url } : {};
const sqliteModule = options.async
@@ -230,7 +237,7 @@ class _SQLiteWorker {
}
async initialize() {
if (typeof this.db === "number")
if (typeof this.db === "number" && !this.skipExtensions)
await this.sqlite.register_extensions(this.db);
self.dispatchEvent(

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useMemo } from "react";
import { Button, Flex, Image, Text } from "@theme-ui/components";
import { Box, Button, Flex, Image, Link, Text } from "@theme-ui/components";
import { getRandom, usePromise } from "@notesnook/common";
import Holenstein from "../../assets/testimonials/holenstein.jpg";
import Jason from "../../assets/testimonials/jason.jpg";
@@ -26,7 +26,6 @@ import Cameron from "../../assets/testimonials/cameron.jpg";
import { hosts } from "@notesnook/core";
import { SettingsDialog } from "../../dialogs/settings";
import { strings } from "@notesnook/intl";
import { FixedColorSchemeThemeProvider } from "../theme-provider";
const testimonials = [
{
@@ -81,18 +80,62 @@ function AuthContainer(props) {
bg: "background"
}}
>
<FixedColorSchemeThemeProvider
colorScheme="dark"
<Box
sx={{
position: "relative",
overflow: "hidden",
flexDirection: "column",
display: ["none", "none", "flex"],
flex: 1,
background:
"radial-gradient(1200px 700px at 82% 18%, color-mix(in srgb, var(--accent) 14%, transparent) 0%, transparent 62%), var(--background-secondary)"
flex: 1
}}
>
<Box
as="svg"
version="1.1"
viewBox="0 0 1920 1080"
preserveAspectRatio="xMinYMin slice"
sx={{
position: "absolute",
top: -100,
left: 0,
height: "100%"
// opacity: 0.7,
}}
>
<g mask='url("#SvgjsMask1017")' fill="none">
<path
d="M1184.21-85.14C1033.8-60.27 964.89 302.42 717.38 307.22 469.87 312.02 483.97 244.72 250.55 244.72 17.13 244.72-98.53 307.08-216.28 307.22"
stroke="var(--icon)"
strokeWidth="2"
></path>
<path
d="M641.38-10.43C534.57 43 590.55 387.5 384.53 392.38 178.52 397.26 2.17 282.99-129.16 282.38"
stroke="var(--icon)"
strokeWidth="2"
></path>
<path
d="M1136.18-29.24C957.53-5.77 852.26 404.49 561.01 405.07 269.76 405.65 142.54 160.4-14.16 155.07"
stroke="var(--icon)"
strokeWidth="2"
></path>
<path
d="M508.47-71.88C398.16-66.29 333.42 117.75 114.38 127.84-104.65 137.93-170.96 308.31-279.7 312.84"
stroke="var(--icon)"
strokeWidth="2"
></path>
<path
d="M1104.88-26.74C976.63-19.04 883.5 217.2 653.03 218.11 422.55 219.02 427.1 155.61 201.17 155.61-24.75 155.61-136.64 217.96-250.68 218.11"
stroke="var(--icon)"
strokeWidth="2"
></path>
</g>
<defs>
<mask id="SvgjsMask1017">
<rect width="1440" height="500" fill="#ffffff"></rect>
</mask>
</defs>
</Box>
<Flex
p={50}
sx={{
@@ -119,9 +162,17 @@ function AuthContainer(props) {
<Text
variant="body"
mt={10}
sx={{ fontSize: 16, color: "paragraph-secondary" }}
sx={{ fontSize: 14, color: "paragraph-secondary" }}
>
{testimonial.text}
{testimonial.text} {" "}
<Link
sx={{ fontStyle: "italic", color: "paragraph-secondary" }}
href={testimonial.link}
target="_blank"
rel="noopener noreferrer"
>
source
</Link>
</Text>
<Flex mt={2} sx={{ alignItems: "center", justifyContent: "center" }}>
<Image
@@ -129,12 +180,10 @@ function AuthContainer(props) {
sx={{ borderRadius: 50, width: 40 }}
/>
<Flex ml={2} sx={{ flexDirection: "column" }}>
<Text variant="body" sx={{ fontSize: 16, fontWeight: "bold" }}>
<Text variant="body" sx={{ fontSize: 14, fontWeight: "bold" }}>
{testimonial.name}
</Text>
<Text variant="subBody" sx={{ fontSize: 13 }}>
@{testimonial.username}
</Text>
<Text variant="subBody">@{testimonial.username}</Text>
</Flex>
</Flex>
@@ -169,18 +218,52 @@ function AuthContainer(props) {
</Button>
</Flex>
</Flex>
</FixedColorSchemeThemeProvider>
<FixedColorSchemeThemeProvider
colorScheme="light"
</Box>
<Flex
sx={{
display: "flex",
position: "relative",
flex: 1.5,
background: "var(--background-secondary)"
flexDirection: "column"
}}
>
<Box
as="svg"
version="1.1"
viewBox="0 0 1920 1080"
preserveAspectRatio="xMinYMin slice"
sx={{
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
width: "130%",
height: "100%"
}}
>
<path
d="M0 336L29.2 316.2C58.3 296.3 116.7 256.7 174.8 267.5C233 278.3 291 339.7 349.2 361.3C407.3 383 465.7 365 523.8 359.5C582 354 640 361 698.2 346.5C756.3 332 814.7 296 872.8 267.2C931 238.3 989 216.7 1047.2 202.3C1105.3 188 1163.7 181 1221.8 202.7C1280 224.3 1338 274.7 1396.2 298C1454.3 321.3 1512.7 317.7 1570.8 332C1629 346.3 1687 378.7 1745.2 366.2C1803.3 353.7 1861.7 296.3 1890.8 267.7L1920 239L1920 0L1890.8 0C1861.7 0 1803.3 0 1745.2 0C1687 0 1629 0 1570.8 0C1512.7 0 1454.3 0 1396.2 0C1338 0 1280 0 1221.8 0C1163.7 0 1105.3 0 1047.2 0C989 0 931 0 872.8 0C814.7 0 756.3 0 698.2 0C640 0 582 0 523.8 0C465.7 0 407.3 0 349.2 0C291 0 233 0 174.8 0C116.7 0 58.3 0 29.2 0L0 0Z"
fill="var(--background-secondary)"
></path>
<path
d="M0 627L29.2 607.3C58.3 587.7 116.7 548.3 174.8 564.7C233 581 291 653 349.2 683.5C407.3 714 465.7 703 523.8 703C582 703 640 714 698.2 724.8C756.3 735.7 814.7 746.3 872.8 742.7C931 739 989 721 1047.2 670.7C1105.3 620.3 1163.7 537.7 1221.8 528.7C1280 519.7 1338 584.3 1396.2 623.8C1454.3 663.3 1512.7 677.7 1570.8 666.8C1629 656 1687 620 1745.2 602C1803.3 584 1861.7 584 1890.8 584L1920 584L1920 237L1890.8 265.7C1861.7 294.3 1803.3 351.7 1745.2 364.2C1687 376.7 1629 344.3 1570.8 330C1512.7 315.7 1454.3 319.3 1396.2 296C1338 272.7 1280 222.3 1221.8 200.7C1163.7 179 1105.3 186 1047.2 200.3C989 214.7 931 236.3 872.8 265.2C814.7 294 756.3 330 698.2 344.5C640 359 582 352 523.8 357.5C465.7 363 407.3 381 349.2 359.3C291 337.7 233 276.3 174.8 265.5C116.7 254.7 58.3 294.3 29.2 314.2L0 334Z"
fill="var(--hover)"
></path>
<path
d="M0 735L29.2 731.5C58.3 728 116.7 721 174.8 739C233 757 291 800 349.2 832.3C407.3 864.7 465.7 886.3 523.8 886.3C582 886.3 640 864.7 698.2 859.3C756.3 854 814.7 865 872.8 870.5C931 876 989 876 1047.2 845.3C1105.3 814.7 1163.7 753.3 1221.8 729.8C1280 706.3 1338 720.7 1396.2 738.7C1454.3 756.7 1512.7 778.3 1570.8 789.2C1629 800 1687 800 1745.2 814.5C1803.3 829 1861.7 858 1890.8 872.5L1920 887L1920 582L1890.8 582C1861.7 582 1803.3 582 1745.2 600C1687 618 1629 654 1570.8 664.8C1512.7 675.7 1454.3 661.3 1396.2 621.8C1338 582.3 1280 517.7 1221.8 526.7C1163.7 535.7 1105.3 618.3 1047.2 668.7C989 719 931 737 872.8 740.7C814.7 744.3 756.3 733.7 698.2 722.8C640 712 582 701 523.8 701C465.7 701 407.3 712 349.2 681.5C291 651 233 579 174.8 562.7C116.7 546.3 58.3 585.7 29.2 605.3L0 625Z"
fill="var(--border)"
></path>
<path
d="M0 897L29.2 895.3C58.3 893.7 116.7 890.3 174.8 908.3C233 926.3 291 965.7 349.2 985.3C407.3 1005 465.7 1005 523.8 1003.3C582 1001.7 640 998.3 698.2 996.7C756.3 995 814.7 995 872.8 986C931 977 989 959 1047.2 939.2C1105.3 919.3 1163.7 897.7 1221.8 894C1280 890.3 1338 904.7 1396.2 911.8C1454.3 919 1512.7 919 1570.8 928C1629 937 1687 955 1745.2 960.3C1803.3 965.7 1861.7 958.3 1890.8 954.7L1920 951L1920 885L1890.8 870.5C1861.7 856 1803.3 827 1745.2 812.5C1687 798 1629 798 1570.8 787.2C1512.7 776.3 1454.3 754.7 1396.2 736.7C1338 718.7 1280 704.3 1221.8 727.8C1163.7 751.3 1105.3 812.7 1047.2 843.3C989 874 931 874 872.8 868.5C814.7 863 756.3 852 698.2 857.3C640 862.7 582 884.3 523.8 884.3C465.7 884.3 407.3 862.7 349.2 830.3C291 798 233 755 174.8 737C116.7 719 58.3 726 29.2 729.5L0 733Z"
fill="var(--hover)"
></path>
<path
d="M0 1081L29.2 1081C58.3 1081 116.7 1081 174.8 1081C233 1081 291 1081 349.2 1081C407.3 1081 465.7 1081 523.8 1081C582 1081 640 1081 698.2 1081C756.3 1081 814.7 1081 872.8 1081C931 1081 989 1081 1047.2 1081C1105.3 1081 1163.7 1081 1221.8 1081C1280 1081 1338 1081 1396.2 1081C1454.3 1081 1512.7 1081 1570.8 1081C1629 1081 1687 1081 1745.2 1081C1803.3 1081 1861.7 1081 1890.8 1081L1920 1081L1920 949L1890.8 952.7C1861.7 956.3 1803.3 963.7 1745.2 958.3C1687 953 1629 935 1570.8 926C1512.7 917 1454.3 917 1396.2 909.8C1338 902.7 1280 888.3 1221.8 892C1163.7 895.7 1105.3 917.3 1047.2 937.2C989 957 931 975 872.8 984C814.7 993 756.3 993 698.2 994.7C640 996.3 582 999.7 523.8 1001.3C465.7 1003 407.3 1003 349.2 983.3C291 963.7 233 924.3 174.8 906.3C116.7 888.3 58.3 891.7 29.2 893.3L0 895Z"
fill="var(--border)"
></path>
</Box>
{props.children}
</FixedColorSchemeThemeProvider>
</Flex>
</Flex>
);
}

View File

@@ -17,24 +17,20 @@ 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 {
IFile,
IFileProvider,
ProviderSettings,
transform
} from "@notesnook-importer/core";
import { IFileProvider, ProviderSettings } from "@notesnook-importer/core";
import { formatBytes, getFormattedDate } from "@notesnook/common";
import { ScrollContainer } from "@notesnook/ui";
import { Button, Flex, Input, Text } from "@theme-ui/components";
import { Button, Flex, Text } from "@theme-ui/components";
import { xxhash64 } from "hash-wasm";
import { useCallback, useEffect, useRef, useState } from "react";
import { useDropzone } from "react-dropzone";
import { useEffect, useRef, useState } from "react";
import { importNote } from "../../../utils/importer";
import { PromptDialog } from "../../../dialogs/prompt";
import { ImporterSqliteAdapter } from "../../../common/sqlite/importer-sqlite-adapter";
import Accordion from "../../accordion";
import { TransformResult } from "../types";
import { useStore as useAppStore } from "../../../stores/app-store";
import { strings } from "@notesnook/intl";
import { showFilePicker } from "../../../utils/file-picker";
type FileProviderHandlerProps = {
provider: IFileProvider;
@@ -63,20 +59,6 @@ export function FileProviderHandler(props: FileProviderHandlerProps) {
const [_, setCounter] = useState<number>(0);
const logs = useRef<LogMessage[]>([]);
const onDrop = useCallback((acceptedFiles: File[]) => {
setFiles((files) => {
const newFiles = [...acceptedFiles, ...files];
return newFiles;
});
}, []);
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
accept: {
file: provider?.supportedExtensions?.concat([".zip"])
}
});
useEffect(() => {
setFiles([]);
}, [provider]);
@@ -122,14 +104,15 @@ export function FileProviderHandler(props: FileProviderHandlerProps) {
setTotalNoteCount(++totalNotes);
},
options: {
onenote: {
getPassword
},
colornote: {
getPassword: async (filename: string) => {
const password = await PromptDialog.show({
title: strings.colorNotePasswordFor(filename),
description: strings.colorNotPasswordForDesc()
});
return password || undefined;
}
getPassword
},
applenotes: {
adapter: new ImporterSqliteAdapter(),
getPassword
}
}
};
@@ -140,20 +123,26 @@ export function FileProviderHandler(props: FileProviderHandlerProps) {
done: 0
});
for (const file of files) {
setFilesProgress((p) => ({
...p,
done: p.done + 1
}));
const { transform } = await import("@notesnook-importer/core");
errors.push(
...(await transform(
provider,
files.map((f) => ({
name: f.name,
modifiedAt: f.lastModified,
size: f.size,
data: f
})),
settings
))
);
setFilesProgress({
total: files.length,
done: files.length
});
const providerFile: IFile = {
name: file.name,
modifiedAt: file.lastModified,
size: file.size,
data: file
};
errors.push(...(await transform(provider, [providerFile], settings)));
}
await useAppStore.getState().refresh();
onTransformFinished({
totalNotes,
@@ -226,42 +215,53 @@ export function FileProviderHandler(props: FileProviderHandlerProps) {
how to import from {provider?.name}.
</a>
</Text>
<Flex
{...getRootProps()}
sx={{
justifyContent: "center",
alignItems: "center",
height: 100,
border: "2px dashed var(--border)",
borderRadius: "default",
mt: 2,
cursor: "pointer",
":hover": {
bg: "background-secondary"
<Flex sx={{ mt: 1, gap: 1 }}>
<Button
variant="secondary"
onClick={() =>
showFilePicker({
multiple: true,
acceptedFileTypes: provider?.supportedExtensions
?.concat([".zip"])
.join(",")
}).then((newFiles) => {
setFiles((files) => {
const _files = [...files, ...newFiles];
return _files;
});
})
}
}}
>
<Input {...getInputProps()} />
<Text variant="body" sx={{ textAlign: "center" }}>
{isDragActive
? "Drop the files here"
: "Drag & drop files here, or click to select files"}
<br />
<Text variant="subBody">
Only {provider?.supportedExtensions.join(", ")} files are supported.{" "}
{provider?.supportedExtensions.includes(".zip") ? null : (
<>
You can also select .zip files containing{" "}
{provider?.supportedExtensions.join(", ")} files.
</>
)}
<br />
{provider.examples ? (
<>For example, {provider.examples.join(", ")}</>
) : null}
</Text>
</Text>
>
{strings.selectFiles()}
</Button>
<Button
variant="secondary"
onClick={() => {
showFilePicker({
directory: true
}).then((newFiles) => {
setFiles((files) => {
const _files = [...files, ...newFiles];
return _files;
});
});
}}
>
{strings.selectFolder()}
</Button>
</Flex>
<Text variant="subBody" sx={{ mt: 1 }}>
Only {provider?.supportedExtensions.join(", ")} files are supported.{" "}
{provider?.supportedExtensions.includes(".zip") ? null : (
<>
You can also select .zip files containing{" "}
{provider?.supportedExtensions.join(", ")} files.
</>
)}
{provider.examples ? (
<> For example, {provider.examples.join(", ")}</>
) : null}
</Text>
{files.length > 0 ? (
<Accordion
@@ -280,7 +280,13 @@ export function FileProviderHandler(props: FileProviderHandlerProps) {
>
{files.map((file, index) => (
<Flex
key={file.name}
key={
file.name +
file.size +
file.lastModified +
file.webkitRelativePath +
index
}
sx={{
p: 2,
bg: index % 2 ? "transparent" : "background-secondary",
@@ -352,3 +358,11 @@ export function FileProviderHandler(props: FileProviderHandlerProps) {
</Flex>
);
}
async function getPassword(filename: string) {
const password = await PromptDialog.show({
title: strings.passwordFor(filename),
type: "password"
});
return password || undefined;
}

View File

@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import {
EmotionThemeProvider,
FixedThemeProvider,
ThemeScopes,
themeToCSS,
useThemeEngineStore
@@ -95,25 +94,4 @@ export function BaseThemeProvider(
);
}
export function FixedColorSchemeThemeProvider(
props: PropsWithChildren<
{
injectCssVars?: boolean;
scope?: keyof ThemeScopes;
colorScheme: "light" | "dark";
} & Omit<BoxProps, "variant">
>
) {
const { children, scope = "base", ...restProps } = props;
const theme = useThemeStore((store) =>
props.colorScheme === "dark" ? store.darkTheme : store.lightTheme
);
return (
<FixedThemeProvider {...restProps} scope={scope} theme={theme}>
{children}
</FixedThemeProvider>
);
}
export { EmotionThemeProvider as ScopedThemeProvider };

View File

@@ -27,6 +27,7 @@ export type PromptDialogProps = BaseDialogProps<undefined | string> & {
title: string;
description?: string;
defaultValue?: string;
type?: "text" | "password" | "email" | "number";
};
export const PromptDialog = DialogManager.register(function PromptDialog(
@@ -51,6 +52,7 @@ export const PromptDialog = DialogManager.register(function PromptDialog(
<Field
inputRef={inputRef}
defaultValue={props.defaultValue}
type={props.type}
autoFocus
onKeyUp={(e) => {
if (e.key == "Enter") props.onClose(inputRef.current?.value || "");

View File

@@ -124,7 +124,7 @@ export const PrivacySettings: SettingsGroup[] = [
if (!result) return;
try {
const url = new URL(result);
Config.set("corsProxy", url.href.replace(/\/$/, ""));
Config.set("corsProxy", `${url.protocol}//${url.hostname}`);
} catch (e) {
console.error(e);
showToast("error", strings.invalidCors());

View File

@@ -21,17 +21,26 @@ import { PAGE_VISIBILITY_CHANGE } from "./page-visibility";
import { strings } from "@notesnook/intl";
import { TaskManager } from "../common/task-manager";
type FilePickerOptions = { acceptedFileTypes: string; multiple?: boolean };
type FilePickerOptions = {
acceptedFileTypes?: string;
multiple?: boolean;
directory?: boolean;
};
export async function showFilePicker({
acceptedFileTypes,
multiple
multiple,
directory
}: FilePickerOptions): Promise<File[]> {
PAGE_VISIBILITY_CHANGE.ignore = true;
const input = document.createElement("input");
input.setAttribute("type", "file");
input.setAttribute("multiple", `${multiple || false}`);
input.setAttribute("accept", acceptedFileTypes);
if (acceptedFileTypes) input.setAttribute("accept", acceptedFileTypes);
if (directory) {
input.setAttribute("webkitdirectory", "true");
input.setAttribute("directory", "true");
}
input.dispatchEvent(new MouseEvent("click"));
const result = await TaskManager.startTask<File[]>({
type: "modal",

View File

@@ -389,7 +389,7 @@ function Signup(props: BaseAuthComponentProps<"signup">) {
<Text
mt={4}
variant="subBody"
sx={{ fontSize: "subBody", textAlign: "center" }}
sx={{ fontSize: 13, textAlign: "center" }}
>
{strings.signupAgreement[0]()}{" "}
<Link
@@ -843,6 +843,9 @@ export function AuthForm<T extends AuthRoutes>(props: AuthFormProps<T>) {
const formRef = useRef<HTMLFormElement>(null);
const [form, setForm] = useState<AuthFormData[T] | undefined>();
if (isSubmitting)
return <Loader title={props.loading.title} text={props.loading.subtitle} />;
return (
<Flex
ref={formRef}
@@ -873,74 +876,57 @@ export function AuthForm<T extends AuthRoutes>(props: AuthFormProps<T>) {
}
}}
sx={{
flex: 1,
flexDirection: "column",
size: "100%",
alignItems: "center",
justifyContent: "center"
justifyContent: "center",
width: ["95%", "95%", "45%"],
alignSelf: "center"
}}
>
<Flex
<Text variant={"heading"} sx={{ fontSize: 32, textAlign: "center" }}>
{title}
</Text>
<Text
variant="body"
mt={2}
mb={35}
sx={{
flexDirection: "column",
width: ["95%", "95%", "550px"],
background: "var(--background)",
p: 6,
my: 10,
borderRadius: "15px",
border: "1px solid var(--border)",
boxShadow: "0px 0px 10px 0px #00000019"
fontSize: "title",
textAlign: "center",
color: "var(--paragraph-secondary)"
}}
>
<Text variant={"heading"} sx={{ fontSize: 32 }}>
{title}
</Text>
<Text
variant="body"
mt={2}
mb={2}
{subtitle}
</Text>
{typeof children === "function" ? children(form) : children}
{canSkip && (
<Button
type="button"
variant="anchor"
sx={{
fontSize: "title",
color: "var(--paragraph-secondary)"
mt: 5,
color: "paragraph",
textDecoration: "none",
position: "absolute",
top: 0,
right: 5
}}
onClick={async () => {
const result = await ConfirmDialog.show({
title: strings.offlineMode(),
message: strings.offlineModeDesc(),
negativeButtonText: strings.cancel(),
positiveButtonText: strings.understand()
});
if (result) openURL("/notes/", { authenticated: false });
}}
>
{subtitle}
</Text>
{canSkip && (
<Button
type="button"
variant="secondary"
sx={{
position: "absolute",
top: 4,
right: 4,
bg: "transparent",
border: "2px solid var(--border)",
borderRadius: "default",
px: 2
}}
onClick={async () => {
const result = await ConfirmDialog.show({
title: strings.offlineMode(),
message: strings.offlineModeDesc(),
negativeButtonText: strings.cancel(),
positiveButtonText: strings.understand()
});
if (result) openURL("/notes/", { authenticated: false });
}}
>
{strings.skipAndGoToApp()}
</Button>
)}
{isSubmitting ? (
<Loader title={props.loading.title} text={props.loading.subtitle} />
) : typeof children === "function" ? (
children(form)
) : (
children
)}
{strings.skipAndGoToApp()}
</Button>
)}
<ErrorText error={error} mt={5} />
</Flex>
<ErrorText error={error} mt={5} />
</Flex>
);
}
@@ -962,7 +948,8 @@ function SubtitleWithAction(props: SubtitleWithActionProps) {
sx={{
textDecoration: "underline",
fontWeight: "bold",
fontSize: "title",
fontSize: "subtitle",
color: "paragraph",
cursor: "pointer"
}}
onClick={props.action.onClick}
@@ -982,11 +969,7 @@ export function AuthField(props: FieldProps) {
data-test-id={props["data-test-id"] || props.id}
sx={{ mt: 2, width: "100%" }}
styles={{
label: { fontWeight: "normal", fontSize: "subtitle" },
helpText: {
fontSize: "body",
my: "2px"
},
// label: { fontWeight: "normal" },
input: {
p: "12px",
borderRadius: "default",
@@ -1009,20 +992,21 @@ type SubmitButtonProps = {
text: string;
disabled?: boolean;
loading?: boolean;
sx?: Record<string, unknown>;
};
export function SubmitButton(props: SubmitButtonProps) {
return (
<Button
data-test-id="submitButton"
type="submit"
mt={50}
variant="accent"
px={50}
sx={{
alignSelf: "stretch",
py: 2,
mt: 3,
fontSize: "subtitle",
...props.sx
borderRadius: 50,
alignItems: "center",
justifyContent: "center",
alignSelf: "center",
display: "flex"
}}
disabled={props.disabled}
>

View File

@@ -356,16 +356,16 @@ function RecoveryKeyMethod(props: BaseRecoveryComponentProps<"method:key">) {
autoFocus
defaultValue={formData?.recoveryKey || ""}
/>
<Flex sx={{ gap: 1, mt: 3 }}>
<Flex sx={{ gap: 1 }}>
<Button
variant="secondary"
type="button"
sx={{ flex: 1, py: 2, fontSize: "subtitle" }}
sx={{ mt: 50, borderRadius: 50 }}
onClick={() => navigate("methods")}
>
{strings.back()}
</Button>
<SubmitButton text={strings.startAccountRecovery()} sx={{ flex: 1, mt: 0 }} />
<SubmitButton text={strings.startAccountRecovery()} />
</Flex>
<Button
@@ -447,11 +447,11 @@ function NewPassword(props: BaseRecoveryComponentProps<"new">) {
label={strings.confirmPassword()}
defaultValue={form?.confirmPassword}
/>
<Flex sx={{ gap: 1, mt: 3 }}>
<Flex sx={{ gap: 1 }}>
<Button
variant="secondary"
type="button"
sx={{ flex: 1, py: 2, fontSize: "subtitle" }}
sx={{ mt: 50, borderRadius: 50 }}
onClick={() =>
navigate(
formData?.userResetRequired ? "methods" : "method:key",
@@ -461,7 +461,7 @@ function NewPassword(props: BaseRecoveryComponentProps<"new">) {
>
{strings.back()}
</Button>
<SubmitButton text={strings.continue()} sx={{ flex: 1, mt: 0 }} />
<SubmitButton text={strings.continue()} />
</Flex>
</>
)}
@@ -539,41 +539,31 @@ export function RecoveryForm<T extends RecoveryRoutes>(
}
}}
sx={{
flex: 1,
flexDirection: "column",
size: "100%",
alignItems: "center",
justifyContent: "center"
justifyContent: "center",
width: ["95%", 420],
alignSelf: "center"
}}
>
<Flex
<Text variant={"heading"} sx={{ fontSize: 32, textAlign: "center" }}>
{title}
</Text>
<Text
variant="body"
mt={2}
mb={35}
sx={{
flexDirection: "column",
width: ["95%", "95%", "550px"],
background: "var(--background)",
p: 6,
my: 10,
borderRadius: "15px",
border: "1px solid var(--border)",
boxShadow: "0px 0px 10px 0px #00000019"
fontSize: "title",
textAlign: "center",
color: "var(--paragraph-secondary)"
}}
>
<Text variant={"heading"} sx={{ fontSize: 32 }}>
{title}
</Text>
<Text
variant="body"
mt={2}
mb={2}
sx={{
fontSize: "title",
color: "var(--paragraph-secondary)"
}}
>
{subtitle}
</Text>
{typeof children === "function" ? children(form) : children}
<ErrorText error={error} sx={{ mt: 2 }} />
</Flex>
{subtitle}
</Text>
{typeof children === "function" ? children(form) : children}
<ErrorText error={error} sx={{ mt: 2 }} />
</Flex>
);
}

View File

@@ -61,7 +61,7 @@ export default defineConfig({
plugins: [emitEditorStyles()],
assetFileNames: "assets/[name]-[hash:12][extname]",
chunkFileNames: "assets/[name]-[hash:12].js",
manualChunks: (id: string) => {
manualChunks: (id) => {
if (
(id.includes("/editor/languages/") ||
id.includes("/html/languages/") ||
@@ -190,6 +190,7 @@ export default defineConfig({
: [
prefetchPlugin({
excludeFn: (assetName) =>
assetName.includes("notesnook-importer") ||
assetName.includes("wa-sqlite-async") ||
!assetName.includes("wa-sqlite")
})

View File

@@ -7,7 +7,6 @@
* built from, so it can never drift from the navigation.
*/
import { sidebar } from "../../sidebar.mjs";
import { withBase } from "vitepress";
type Item = { text: string; link?: string; items?: Item[] };
@@ -25,19 +24,13 @@ const pageCount = groups.reduce(
<template>
<div class="nn-index">
<p class="nn-index__count">
{{ pageCount }} pages, grouped by what you're trying to do.
</p>
<p class="nn-index__count">{{ pageCount }} pages, grouped by what you're trying to do.</p>
<div class="nn-index__grid">
<section
v-for="group in groups"
:key="group.text"
class="nn-index__group"
>
<section v-for="group in groups" :key="group.text" class="nn-index__group">
<h2 class="nn-index__heading">{{ group.text }}</h2>
<ul class="nn-index__list">
<li v-for="item in group.items" :key="item.link || item.text">
<a v-if="item.link" :href="withBase(item.link)">{{ item.text }}</a>
<a v-if="item.link" :href="item.link">{{ item.text }}</a>
<span v-else>{{ item.text }}</span>
</li>
</ul>

View File

@@ -1,4 +0,0 @@
- Fix drag/drop issues in task-lists and improved UX
- Minor bug fixes and improvements
Thank you for using Notesnook!

View File

@@ -1,3 +0,0 @@
- Minor bug fixes and improvements
Thank you for using Notesnook!

33
inbox-public.asc Normal file
View File

@@ -0,0 +1,33 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: openpgp-mobile
xsBNBGoNaK8BCAD0B33KK4LRAvN1lZLpJhQMyk/+Srss56PjFphMH1MmqMgIRRBP
3RykX+7+ibha+5WIFYpgBEaPM9osZz22XUGhVrUzUxJMScjhLWR6xyuv0qs0Dctg
ePdcupPbJND3j9W4OnOBXwv+Ko/fX5K+enJfPp6fxyWbf3X/1BnAYlHyLngBLz8P
mt5qj0qax5V4ujUVU8ByNFvQkjcA+ip0vol2xQlmKa5UXJ1KYfM7LQWa4gQqdaY8
8FOl6CaaWsXO/vCnIF47JClWGJfJ0rAaREI/Kj+MV+i98BPAcZG8Kj523QmgIHo6
Puahi1q9wqX6Vwe6hjhiTUJSWvNjZXGi9A4PABEBAAHNDU5OIDxOTkBOTi5OTj7C
wLsEEwEIAG8FgmoNaK8CCwcJkC34qZJdxA5jNRQAAAAAABwAEHNhbHRAbm90YXRp
b25zLm9wZW5wZ3Bqcy5vcmfLP2eEUAk99foAqkNQSyW9AhUIAhYAAhkBApsDAh4B
FiEEjXaqZcVUExRCuEhoLfipkl3EDmMAAKW7CACxh26CVTjRLjeq/GNueceWRJoT
qF/OMQY/3mnuLMbaEMuYSc03ml6jiMdqZy6qeKgjH36qvpfu68HxPYEOn3WQ/k9V
2Iug4xDFlqFw0IN2Gqkgur+FYbFCjG1mkqsrlKsN2QHqG8sf+5EXegpvNibI+43Q
HZp+Q5B8HftnBANvZngFJz3t0hCCpUgVOTK4vwK8IKDK7zbMqfnT4k5vNbfpxd30
5xfZkQXrltJLaHb5pzgVMeoM00TaHd8WBDFYn8BF1OI/TbcChYpRPkW7WoKKuZ/1
tIBfv/O5h3ZxgSc4NdL1FuLZ0nTscPoN+uBE29XQat8IOiYiA4DvKLbIQe89zsBN
BGoNaK8BCACppIfZHotgC8zKvCkj1sWAny5Qq/AkYdIJh/b//7NhnWyUgiSDnoEV
1yik33CiQgoORR42YfVCZCMH2ZydzeKdaNKd/fFLeMsog0ddp4cW68drDDVvkGso
vSMwvpSS/J4JJ2KXqIbvscJXGzAaFZ61BoY3kvRKHymHGe2oLs2bPscNug7mm8pm
18+IhNeOtuS6MZzdXr9rmfuTtY9zUIbIaOgY3EiiaRkvcQLPcBTwsoM9b9zNMK+1
ivA65KrtgN+T7axcIRT+QFFoNOw7mjHNQybb6qRABWS8AWouot4B2g9tNHndlhNV
aimI+P1RHaP9VOxJFneWkmvviXqznv4TABEBAAHCwKwEGAEIAGAFgmoNaK8JkC34
qZJdxA5jNRQAAAAAABwAEHNhbHRAbm90YXRpb25zLm9wZW5wZ3Bqcy5vcmeywJ1O
IyGqDaRT2EGH4ZZpApsMFiEEjXaqZcVUExRCuEhoLfipkl3EDmMAABm8B/9rKv4l
PNwYXLVQlhyGlF/MvdYvT4Fj2CxtO32Fo++dUlEYJqX++GihXr0HyjdE200Ttb1Q
IpZto9rx5X0QHKGEqVGM+kJ6STOWK5jRlADES6GKE3dZde4Z+QS+BBdEdveZtGwR
GsLArPKjhnbkiBXTrMUkoQa4eGanuXA452io5NsiIeNlMUsC6IAXxuZp8+iBtN9K
65iSrvmasdKgwttbqp0qiI5VudiEAQjrkHDMlGftMqSllZWkavlbpYPIN/Omzh8G
kVbBMAvQtDsxSFACnZCGQ1l3r9qDJWUQyn57qQgrQwYXx+sHIlOcdAMGiPbE49KP
kUYOKopIqix0i2/F
=rfLM
-----END PGP PUBLIC KEY BLOCK-----

View File

@@ -139,7 +139,6 @@ export function useEditorController({
wordCounter: null,
scroll: null
});
const hasRestored = useRef(false);
if (!tabRef.current.session?.noteId && loading) {
setTimeout(() => {
@@ -301,7 +300,6 @@ export function useEditorController({
const scroll = useCallback(
(_event: React.UIEvent<HTMLDivElement, UIEvent>) => {
if (!hasRestored.current) return;
const value = _event.currentTarget.scrollTop;
if (timers.current.scroll !== null) clearTimeout(timers.current.scroll);
timers.current.scroll = setTimeout(() => {
@@ -369,7 +367,6 @@ export function useEditorController({
}
scrollTo?.(value.scrollTop || 0);
hasRestored.current = true;
setLoading(false);
countWords(0);
}
@@ -385,7 +382,6 @@ export function useEditorController({
logger("info", "LOADING NOTE HTML");
if (!editor) break;
update(value.scrollTop, value.selection, value.searchResultIndex);
hasRestored.current = true;
setTimeout(() => {
countWords(0);
}, 300);

View File

@@ -10,7 +10,7 @@
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
"@notesnook-importer/core": "^2.4.5",
"@notesnook-importer/core": "^2.6.0",
"@notesnook/common": "file:../common",
"@notesnook/intl": "file:../intl",
"@notesnook/theme": "file:../theme",
@@ -105,6 +105,7 @@
"version": "2.1.3",
"license": "GPL-3.0-or-later",
"dependencies": {
"@notesnook/common": "^2.1.3",
"@notesnook/core": "file:../core",
"@readme/data-urls": "^3.0.0",
"dayjs": "1.11.13",
@@ -982,15 +983,43 @@
"integrity": "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==",
"dev": true
},
"node_modules/@notesnook-importer/core": {
"version": "2.4.5",
"resolved": "https://registry.npmjs.org/@notesnook-importer/core/-/core-2.4.5.tgz",
"integrity": "sha512-7Oqkw07CykwC/xzGrC6St4C/87QmXqK8UtmSxwE05fpz8DWoKEviJSWFa5fKY52umTphGvmtY23252dZ9J9EJg==",
"node_modules/@notesnook-importer/applenotes": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@notesnook-importer/applenotes/-/applenotes-1.1.0.tgz",
"integrity": "sha512-6mV6QtYqNqomyHJsWA926I1X8BsoM/6R3X8cdF7d4sg5cfkjENt2hoayBiW1ONK92MM9rU6mbJlqDSDEDinXPg==",
"license": "GPL-3.0-or-later",
"dependencies": {
"@notesnook-importer/enex": "^2.3.5",
"@notesnook-importer/storage": "^2.3.5",
"@notesnook-importer/znel": "^2.3.5",
"@notesnook-importer/types": "^1.1.0",
"entities": "^4.4.0",
"fflate": "^0.7.4",
"protobufjs": "^8.7.2"
}
},
"node_modules/@notesnook-importer/applenotes/node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/@notesnook-importer/core": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/@notesnook-importer/core/-/core-2.6.0.tgz",
"integrity": "sha512-HjVvNgH6hFDMM/m3i+7GNDLMfcqAvNessDMS+JBWp70k8w8m1KltkVL2E6bng/zRRG6hisJJMSQzFOwpTBS2Ag==",
"license": "GPL-3.0-or-later",
"dependencies": {
"@notesnook-importer/applenotes": "^1.1.0",
"@notesnook-importer/enex": "^2.5.0",
"@notesnook-importer/onenote": "^1.1.0",
"@notesnook-importer/samsung-notes": "^1.1.0",
"@notesnook-importer/storage": "^2.5.0",
"@notesnook-importer/types": "^1.1.0",
"@notesnook-importer/znel": "^2.5.0",
"@stablelib/chacha20poly1305": "^1.0.1",
"@streamparser/json": "^0.0.10",
"@zip.js/zip.js": "^2.7.32",
@@ -1004,6 +1033,7 @@
"hast-util-is-element": "^2.1.3",
"htmlparser2": "^8.0.1",
"magic-bytes.js": "^1.8.0",
"plist": "^5.0.0",
"rehype-stringify": "^9.0.3",
"remark": "^14.0.3",
"remark-comments": "^1.2.9",
@@ -1012,6 +1042,7 @@
"remark-rehype": "^10.1.0",
"remark-supersub": "^1.0.0",
"spark-md5": "^3.0.2",
"sql.js": "^1.14.2",
"unified": "^10.1.2",
"unist-util-visit": "^5.0.0",
"varint": "^6.0.0",
@@ -1031,9 +1062,9 @@
}
},
"node_modules/@notesnook-importer/enex": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@notesnook-importer/enex/-/enex-2.3.5.tgz",
"integrity": "sha512-0+08XVhsQQFoEUrar3k07DuTluNho/FHxJ25n/DT+ug8kVraHHVqI40a2P9h8bW8iWxYs02Zq1n+N2uTv5qb+g==",
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@notesnook-importer/enex/-/enex-2.5.0.tgz",
"integrity": "sha512-oqlg0SCqwTDmqGdi/p6m5FpcaKspIhDG08g/2fm2xIAYH95WJlyS4nb4pygyJGsywrq1USgIQ/Bup9hZ4jJKeA==",
"license": "GPL-3.0-or-later",
"dependencies": {
"base64-js": "^1.5.1",
@@ -1045,16 +1076,40 @@
"spark-md5": "^3.0.2"
}
},
"node_modules/@notesnook-importer/onenote": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@notesnook-importer/onenote/-/onenote-1.1.0.tgz",
"integrity": "sha512-V0cFx4QsC873wJsh5ONNG/pTAdTN+9LBYIAEIi+U6ZubTfm8Y2zIn/eBabVpFO62OvdD551EcFIbto5CTGJOrg==",
"license": "GPL-3.0-or-later",
"dependencies": {
"fflate": "^0.7.4"
}
},
"node_modules/@notesnook-importer/samsung-notes": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@notesnook-importer/samsung-notes/-/samsung-notes-1.1.0.tgz",
"integrity": "sha512-2/2WbDqBBYQ46qkpPf4M5mdgHgSsjBlyTxWQsr0jv5l2j9TedxKE6NwmMxlrRJFkLxNBnNw3Yj1E437h7AJpqA==",
"license": "GPL-3.0-or-later",
"dependencies": {
"fflate": "^0.7.4"
}
},
"node_modules/@notesnook-importer/storage": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@notesnook-importer/storage/-/storage-2.3.5.tgz",
"integrity": "sha512-orvSK0XIcqlmiWmjQ3RzS3qZp7viBdCpAloX557Ml200LMSMGDsOtofYNv5IYllBETnqApERLPQaUzBSuviuvA==",
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@notesnook-importer/storage/-/storage-2.5.0.tgz",
"integrity": "sha512-7HpvJkafCs3Ba0DdzZA8QecLfdN7D7V3g4773/iwTsRxa07YTRcavmV2jBGY6DuMaTkJio/Q/M/kSI0m5yiU6A==",
"license": "GPL-3.0-or-later"
},
"node_modules/@notesnook-importer/types": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@notesnook-importer/types/-/types-1.1.0.tgz",
"integrity": "sha512-hoS4jHoaXiFnbUTElxYdIY85BOqf1wke0B+Oe5ExbHuFdUDOI0AtZWyCwArG9t0vfUlYmhJ1v27QHOfeE1kxQQ==",
"license": "GPL-3.0-or-later"
},
"node_modules/@notesnook-importer/znel": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@notesnook-importer/znel/-/znel-2.3.5.tgz",
"integrity": "sha512-s1kTA3EQ5OswBc0RvWnWgIm6/mIWvnh3iWmcOJcApIqhDwQziEkYiS+UMfuY9nOUyIWtepMe/WJ0Odk15uah9w==",
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@notesnook-importer/znel/-/znel-2.5.0.tgz",
"integrity": "sha512-8Tx+fcAPJQeyjJpk9+mrak4c+v186IExgpy16I25fRJwl35k5B77NS0gWNOCFX80levInoX8FoXNp8IanVb8Rg==",
"license": "GPL-3.0-or-later",
"dependencies": {
"base64-js": "^1.5.1",
@@ -2299,6 +2354,15 @@
"url": "https://opencollective.com/vitest"
}
},
"node_modules/@xmldom/xmldom": {
"version": "0.9.11",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.11.tgz",
"integrity": "sha512-tW8bcK3hsG0/uqSnNz6TK4BkcuZSezoU7DlnYssILmZDktPnSHHuDJJFM0AJv+13gz2r0iGdrj6qqKeUnxXEDg==",
"license": "MIT",
"engines": {
"node": ">=14.6"
}
},
"node_modules/@zip.js/zip.js": {
"version": "2.7.57",
"resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.57.tgz",
@@ -3528,6 +3592,12 @@
"resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.1.3.tgz",
"integrity": "sha512-auMesunaJ8yfkHvK4gfg1K0SaKX/6Wn9g2Aac/NwX+l5VdmFZzo/hdPGxEOETj+ryRa4/fiOPjeeKURSAJx1sg=="
},
"node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
"license": "Apache-2.0"
},
"node_modules/longest-streak": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz",
@@ -4764,6 +4834,19 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/plist": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/plist/-/plist-5.0.0.tgz",
"integrity": "sha512-20N+g1DvMm/DFRbsvER7tT4wDryq0WunK7VMkDaiJcKNapAnUMkTsAnacFYf8n420F4Hf6/hefgmJRkMb1M0fg==",
"license": "MIT",
"dependencies": {
"@xmldom/xmldom": "^0.9.10",
"xmlbuilder": "^15.1.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/postcss": {
"version": "8.5.1",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz",
@@ -5047,6 +5130,18 @@
"prosemirror-transform": "^1.1.0"
}
},
"node_modules/protobufjs": {
"version": "8.7.2",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-8.7.2.tgz",
"integrity": "sha512-oTVHV+oelUBtiu5iTuTNNZ0eLYsXSMxry4cgr30mayNkgIZL6qZ0IOQVPuSWGcyAaXKl/XgqwWHIC3a0khYVBA==",
"license": "BSD-3-Clause",
"dependencies": {
"long": "^5.3.2"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/pstree.remy": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
@@ -5500,6 +5595,12 @@
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"license": "BSD-3-Clause"
},
"node_modules/sql.js": {
"version": "1.14.2",
"resolved": "https://registry.npmjs.org/sql.js/-/sql.js-1.14.2.tgz",
"integrity": "sha512-3ZGPovObMFrdw79zrUHbfdE/DLIsy8jdNdssmMSQuRAymedU6q84asPt0kgiqrdMYlPegDItiIMfmIXzZnYFcw==",
"license": "MIT"
},
"node_modules/stackback": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
@@ -6208,6 +6309,15 @@
"node": ">=8"
}
},
"node_modules/xmlbuilder": {
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
"integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==",
"license": "MIT",
"engines": {
"node": ">=8.0"
}
},
"node_modules/yaml": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",

View File

@@ -30,7 +30,7 @@
},
"license": "GPL-3.0-or-later",
"dependencies": {
"@notesnook-importer/core": "^2.4.5",
"@notesnook-importer/core": "^2.6.0",
"@notesnook/common": "file:../common",
"@notesnook/intl": "file:../intl",
"@notesnook/theme": "file:../theme",

View File

@@ -60,19 +60,18 @@ export function ImageComponent(
});
const dom = editor.view.dom;
const downloadOptions = useToolbarStore((store) => store.downloadOptions);
const isReadonly = !editor.isEditable;
const isSVG = !!mime && mime.includes("/svg");
const size =
editor.view.dom.clientWidth === 0
? node.attrs
: clampSize(node.attrs, dom.clientWidth, aspectRatio);
: clampSize(node.attrs, dom.clientWidth, aspectRatio, isSVG);
let align = node.attrs.align;
if (!align) align = textDirection ? "right" : "left";
const downloadOptions = useToolbarStore((store) => store.downloadOptions);
const isReadonly = !editor.isEditable;
const isSVG = !!mime && mime.includes("/svg");
useEffect(() => {
if (!inView) return;
if (src || !hash || bloburl) return;
@@ -113,12 +112,114 @@ export function ImageComponent(
}
}}
>
<DesktopOnly>
{selected && (
<Flex
sx={{
position: "absolute",
top: -40,
right: 0,
mb: 2,
alignItems: "end",
zIndex: 999
}}
>
<ToolbarGroup
editor={editor}
groupId="imageTools"
tools={
isReadonly
? [
hash ? "previewAttachment" : "none",
hash ? "downloadAttachment" : "none"
]
: [
hash ? "previewAttachment" : "none",
hash ? "downloadAttachment" : "none",
"imageAlignLeft",
"imageAlignCenter",
"imageAlignRight",
"imageProperties"
]
}
sx={{
boxShadow: "menu",
borderRadius: "default",
bg: "background"
}}
/>
</Flex>
)}
{Boolean(resizing) && (
<Box
sx={{
position: "absolute",
top: -30,
left: 0,
zIndex: 9999,
background: "var(--background-secondary)",
px: 2,
py: 1,
borderRadius: "default"
}}
>
<Text variant="subBody" sx={{ fontWeight: "bold" }}>
{resizing?.width}
{" × "}
{resizing?.height}
</Text>
</Box>
)}
</DesktopOnly>
{isSVG ? (
<Box
sx={{
width: "100%",
display: editor.isEditable ? "flex" : "none",
position: "absolute",
top: -24,
height: 24,
justifyContent: "end",
p: "small",
bg: editor.isEditable
? "var(--background-secondary)"
: "transparent",
borderTopLeftRadius: "default",
borderTopRightRadius: "default",
borderColor: selected ? "border" : "var(--border-secondary)",
cursor: "pointer",
":hover": {
borderColor: "border"
}
}}
></Box>
) : null}
<Resizer
style={{ marginTop: 5 }}
style={{
marginTop: 5,
...(isSVG
? { overflow: "auto", maxWidth: "100%", maxHeight: "80vh" }
: {})
}}
enabled={editor.isEditable}
selected={selected}
width={size.width}
height={bloburl || src ? undefined : size.height}
height={
isSVG
? Math.min(
aspectRatio
? Math.min(
size.width || dom.clientWidth,
dom.clientWidth
) / aspectRatio
: size.height || dom.clientWidth,
window.innerHeight
)
: bloburl || src
? undefined
: size.height
}
lockAspectRation={isSVG ? false : undefined}
onResize={(width, height) => {
setResizing({ width, height });
}}
@@ -127,65 +228,6 @@ export function ImageComponent(
editor.commands.setImageSize({ width, height });
}}
>
<DesktopOnly>
{selected && (
<Flex
sx={{
position: "absolute",
top: -40,
right: 0,
mb: 2,
alignItems: "end",
zIndex: 999
}}
>
<ToolbarGroup
editor={editor}
groupId="imageTools"
tools={
isReadonly
? [
hash ? "previewAttachment" : "none",
hash ? "downloadAttachment" : "none"
]
: [
hash ? "previewAttachment" : "none",
hash ? "downloadAttachment" : "none",
"imageAlignLeft",
"imageAlignCenter",
"imageAlignRight",
"imageProperties"
]
}
sx={{
boxShadow: "menu",
borderRadius: "default",
bg: "background"
}}
/>
</Flex>
)}
{Boolean(resizing) && (
<Box
sx={{
position: "absolute",
top: -30,
left: 0,
zIndex: 9999,
background: "var(--background-secondary)",
px: 2,
py: 1,
borderRadius: "default"
}}
>
<Text variant="subBody" sx={{ fontWeight: "bold" }}>
{resizing?.width}
{" × "}
{resizing?.height}
</Text>
</Box>
)}
</DesktopOnly>
{progress ? (
<Flex
sx={{
@@ -281,7 +323,10 @@ export function ImageComponent(
? {
src: bloburl || corsify(src, downloadOptions?.corsHost),
type: mime,
sandbox: ""
// allow-same-origin is needed to read the SVG's viewBox
// from contentDocument for correct aspect ratio detection.
// The SVG content is from the user's own storage (trusted).
sandbox: "allow-same-origin"
}
: {
src: bloburl || corsify(src, downloadOptions?.corsHost)
@@ -296,7 +341,14 @@ export function ImageComponent(
? "2px solid var(--accent) !important"
: "2px solid transparent !important",
borderRadius: "default",
...(isSVG ? { bg: "transparent" } : {})
...(isSVG
? {
bg: "transparent",
display: "block",
minWidth: 0,
minHeight: 0
}
: {})
}}
onDoubleClick={() => {
const { hash, filename, mime, size } = node.attrs;
@@ -312,6 +364,47 @@ export function ImageComponent(
onLoad={async function onLoad() {
if (!imageRef.current) return;
// For SVGs rendered as iframes, naturalWidth/naturalHeight are 0.
// Read the viewBox from the SVG contentDocument instead.
if (isSVG) {
try {
const iframe = imageRef.current as unknown as HTMLIFrameElement;
const svgEl =
iframe.contentDocument?.querySelector("svg");
const viewBox = svgEl?.getAttribute("viewBox");
if (viewBox) {
const parts = viewBox.split(/[\s,]+/).map(Number);
if (parts.length === 4 && parts[2] > 0 && parts[3] > 0) {
const svgAspectRatio = parts[2] / parts[3];
if (
!aspectRatio ||
Math.abs(aspectRatio - svgAspectRatio) > 0.01
) {
const fixedDimensions = fixAspectRatio(
size.width ?? 0,
svgAspectRatio
);
await editor.threadsafe((editor) =>
editor.commands.updateAttachment(
{
...fixedDimensions,
aspectRatio: svgAspectRatio
},
{
query: makeImageQuery(src, hash),
ignoreEdit: true
}
)
);
}
}
}
} catch {
// cross-origin or missing contentDocument — skip
}
return;
}
const { naturalWidth, naturalHeight, clientHeight, clientWidth } =
imageRef.current;
const originalWidth = naturalWidth || clientWidth;
@@ -389,7 +482,8 @@ function canParse(src: string) {
function clampSize(
size: { width?: number; height?: number },
maxWidth: number,
aspectRatio?: number
aspectRatio?: number,
isSVG?: boolean
): { width: number; height: number } {
if (typeof aspectRatio === "string" && isNaN(aspectRatio)) aspectRatio = 1;
@@ -398,6 +492,10 @@ function clampSize(
if (!aspectRatio) aspectRatio = size.width / size.height;
// SVGs (especially infinite canvas exports from OneNote) should preserve
// their original dimensions. The container handles overflow via scrolling.
if (isSVG) return { width: size.width, height: size.height };
if (size.width > maxWidth)
return { width: maxWidth, height: maxWidth / aspectRatio };

View File

@@ -70,12 +70,16 @@ export function HoverPopupHandler(props: FloatingMenuProps) {
const element = e.target;
if (activePopup.current) {
const isOutsideEditor = !element.closest(".ProseMirror");
const isInsidePopup = element.closest(".popup-presenter-portal");
const isActiveElement = activePopup.current.element === element;
if (isInsidePopup || isActiveElement) return;
if (isInsidePopup) return;
activePopup.current.hide();
activePopup.current = undefined;
if (isOutsideEditor || !isActiveElement) {
activePopup.current.hide();
activePopup.current = undefined;
return;
}
}
clearTimeout(hoverTimeoutId.current);

View File

@@ -812,7 +812,7 @@ msgstr "Align left"
msgid "Align right"
msgstr "Align right"
#: src/strings.ts:2813
#: src/strings.ts:2809
msgid "Alignment"
msgstr "Alignment"
@@ -1358,6 +1358,10 @@ msgstr "Boost your productivity with Notebooks and organize your notes."
msgid "Browse"
msgstr "Browse"
#: src/strings.ts:2811
msgid "Browser storage quota reached. Please delete some attachments from the attachment manager or clear some local data to free up space."
msgstr "Browser storage quota reached. Please delete some attachments from the attachment manager or clear some local data to free up space."
#: src/strings.ts:2286
msgid "Bullet list"
msgstr "Bullet list"
@@ -1662,7 +1666,7 @@ msgstr "Clear data & reset account"
msgid "Clear default notebook"
msgstr "Clear default notebook"
#: src/strings.ts:2803
#: src/strings.ts:2799
msgid "Clear history"
msgstr "Clear history"
@@ -1821,10 +1825,6 @@ msgstr "Color scheme"
msgid "Color title"
msgstr "Color title"
#: src/strings.ts:2796
msgid "Colornote password for {filename}"
msgstr "Colornote password for {filename}"
#: src/strings.ts:309
msgid "colors"
msgstr "colors"
@@ -2314,7 +2314,7 @@ msgstr "Delete account"
msgid "Delete all"
msgstr "Delete all"
#: src/strings.ts:2805
#: src/strings.ts:2801
msgid "Delete all version history for this note?"
msgstr "Delete all version history for this note?"
@@ -2338,7 +2338,7 @@ msgstr "Delete data"
msgid "Delete group"
msgstr "Delete group"
#: src/strings.ts:2799
#: src/strings.ts:2795
msgid "Delete item"
msgstr "Delete item"
@@ -2809,6 +2809,10 @@ msgstr "Enter notebook title"
msgid "Enter password"
msgstr "Enter password"
#: src/strings.ts:2812
msgid "Enter password for \"{filename}\""
msgstr "Enter password for \"{filename}\""
#: src/strings.ts:2100
msgid "Enter pin or password to enable app lock."
msgstr "Enter pin or password to enable app lock."
@@ -3844,7 +3848,7 @@ msgstr "Keep"
msgid "Keep open"
msgstr "Keep open"
#: src/strings.ts:2800
#: src/strings.ts:2796
msgid "Keep screen on"
msgstr "Keep screen on"
@@ -4737,7 +4741,7 @@ msgstr "Off"
msgid "Offline"
msgstr "Offline"
#: src/strings.ts:2810
#: src/strings.ts:2806
msgid "Offline mode"
msgstr "Offline mode"
@@ -5223,7 +5227,7 @@ msgstr "Pressing \"X\" will hide the app in your system tray."
msgid "Prevent note title from appearing in tab/window title."
msgstr "Prevent note title from appearing in tab/window title."
#: src/strings.ts:2802
#: src/strings.ts:2798
msgid "Prevent the screen from turning off while the editor is focused."
msgstr "Prevent the screen from turning off while the editor is focused."
@@ -6152,8 +6156,12 @@ msgid "Select day of the week to repeat the reminder."
msgstr "Select day of the week to repeat the reminder."
#: src/strings.ts:1765
msgid "Select files to import"
msgstr "Select files to import"
msgid "Select files"
msgstr "Select files"
#: src/strings.ts:2813
msgid "Select folder"
msgstr "Select folder"
#: src/strings.ts:1608
msgid "Select folder where Notesnook backup files are stored to view and restore them from the app"
@@ -6924,10 +6932,6 @@ msgstr "The information above will be publically available at"
msgid "The Notesnook Circle is exclusive to subscribers. Please consider subscribing to gain access to Notesnook Circle and enjoy additional benefits."
msgstr "The Notesnook Circle is exclusive to subscribers. Please consider subscribing to gain access to Notesnook Circle and enjoy additional benefits."
#: src/strings.ts:2798
msgid "The password for decrypting the Colornote backup file."
msgstr "The password for decrypting the Colornote backup file."
#: src/strings.ts:2094
msgid "The password/pin for unlocking the app."
msgstr "The password/pin for unlocking the app."
@@ -7493,7 +7497,7 @@ msgstr "User verification failed"
msgid "Using {instance} (v{version})"
msgstr "Using {instance} (v{version})"
#: src/strings.ts:2812
#: src/strings.ts:2808
msgid "Using Notesnook without an account will **NOT** sync your notes across devices and could result in data loss if you lose access to your device or uninstall the app. Make sure to backup your notes regularly."
msgstr "Using Notesnook without an account will **NOT** sync your notes across devices and could result in data loss if you lose access to your device or uninstall the app. Make sure to backup your notes regularly."
@@ -7565,7 +7569,7 @@ msgstr "Verifying your email"
msgid "Version"
msgstr "Version"
#: src/strings.ts:2804
#: src/strings.ts:2800
msgid "Version history cleared"
msgstr "Version history cleared"

View File

@@ -812,7 +812,7 @@ msgstr ""
msgid "Align right"
msgstr ""
#: src/strings.ts:2813
#: src/strings.ts:2809
msgid "Alignment"
msgstr ""
@@ -1358,6 +1358,10 @@ msgstr ""
msgid "Browse"
msgstr ""
#: src/strings.ts:2811
msgid "Browser storage quota reached. Please delete some attachments from the attachment manager or clear some local data to free up space."
msgstr ""
#: src/strings.ts:2286
msgid "Bullet list"
msgstr ""
@@ -1662,7 +1666,7 @@ msgstr ""
msgid "Clear default notebook"
msgstr ""
#: src/strings.ts:2803
#: src/strings.ts:2799
msgid "Clear history"
msgstr ""
@@ -1810,10 +1814,6 @@ msgstr ""
msgid "Color title"
msgstr ""
#: src/strings.ts:2796
msgid "Colornote password for {filename}"
msgstr ""
#: src/strings.ts:309
msgid "colors"
msgstr ""
@@ -2303,7 +2303,7 @@ msgstr ""
msgid "Delete all"
msgstr ""
#: src/strings.ts:2805
#: src/strings.ts:2801
msgid "Delete all version history for this note?"
msgstr ""
@@ -2327,7 +2327,7 @@ msgstr ""
msgid "Delete group"
msgstr ""
#: src/strings.ts:2799
#: src/strings.ts:2795
msgid "Delete item"
msgstr ""
@@ -2798,6 +2798,10 @@ msgstr ""
msgid "Enter password"
msgstr ""
#: src/strings.ts:2812
msgid "Enter password for \"{filename}\""
msgstr ""
#: src/strings.ts:2100
msgid "Enter pin or password to enable app lock."
msgstr ""
@@ -3824,7 +3828,7 @@ msgstr ""
msgid "Keep open"
msgstr ""
#: src/strings.ts:2800
#: src/strings.ts:2796
msgid "Keep screen on"
msgstr ""
@@ -4711,7 +4715,7 @@ msgstr ""
msgid "Offline"
msgstr ""
#: src/strings.ts:2810
#: src/strings.ts:2806
msgid "Offline mode"
msgstr ""
@@ -5197,7 +5201,7 @@ msgstr ""
msgid "Prevent note title from appearing in tab/window title."
msgstr ""
#: src/strings.ts:2802
#: src/strings.ts:2798
msgid "Prevent the screen from turning off while the editor is focused."
msgstr ""
@@ -6126,7 +6130,11 @@ msgid "Select day of the week to repeat the reminder."
msgstr ""
#: src/strings.ts:1765
msgid "Select files to import"
msgid "Select files"
msgstr ""
#: src/strings.ts:2813
msgid "Select folder"
msgstr ""
#: src/strings.ts:1608
@@ -6883,10 +6891,6 @@ msgstr ""
msgid "The Notesnook Circle is exclusive to subscribers. Please consider subscribing to gain access to Notesnook Circle and enjoy additional benefits."
msgstr "<<<<<<< HEAD"
#: src/strings.ts:2798
msgid "The password for decrypting the Colornote backup file."
msgstr ""
#: src/strings.ts:2094
msgid "The password/pin for unlocking the app."
msgstr ""
@@ -7443,7 +7447,7 @@ msgstr ""
msgid "Using {instance} (v{version})"
msgstr ""
#: src/strings.ts:2812
#: src/strings.ts:2808
msgid "Using Notesnook without an account will **NOT** sync your notes across devices and could result in data loss if you lose access to your device or uninstall the app. Make sure to backup your notes regularly."
msgstr ""
@@ -7515,7 +7519,7 @@ msgstr ""
msgid "Version"
msgstr ""
#: src/strings.ts:2804
#: src/strings.ts:2800
msgid "Version history cleared"
msgstr ""

View File

@@ -1762,7 +1762,7 @@ For example:
one: "# file ready for import",
other: "# files ready for import"
}),
selectFilesToImport: () => t`Select files to import`,
selectFiles: () => t`Select files`,
importerHelpText: () => [
t`Please refer to the`,
t`import guide`,
@@ -2792,10 +2792,6 @@ Continue without attachments?`,
enterPgpPublicKey: () => t`Enter your PGP public key`,
enterPgpPrivateKey: () => t`Enter your PGP private key`,
expiryDateRemoved: () => t`Expiry date removed`,
colorNotePasswordFor: (filename: string) =>
t`Colornote password for ${filename}`,
colorNotPasswordForDesc: () =>
t`The password for decrypting the Colornote backup file.`,
deleteItem: () => t`Delete item`,
keepScreenOn: () => t`Keep screen on`,
keepScreenOnDesc: () =>
@@ -2810,5 +2806,9 @@ Continue without attachments?`,
offlineMode: () => t`Offline mode`,
offlineModeDesc: () =>
t`Using Notesnook without an account will **NOT** sync your notes across devices and could result in data loss if you lose access to your device or uninstall the app. Make sure to backup your notes regularly.`,
alignment: () => t`Alignment`
alignment: () => t`Alignment`,
browserStorageQuotaReached: () =>
t`Browser storage quota reached. Please delete some attachments from the attachment manager or clear some local data to free up space.`,
passwordFor: (filename: string) => t`Enter password for "${filename}"`,
selectFolder: () => t`Select folder`
};

View File

@@ -21,15 +21,13 @@ import { ThemeProvider } from "@theme-ui/core";
import React, { ForwardedRef, PropsWithChildren, useMemo } from "react";
import { Box, BoxProps } from "@theme-ui/components";
import { useTheme } from "@emotion/react";
import { ThemeDefinition, ThemeScopes } from "../theme-engine/types.js";
import { ThemeScopes } from "../theme-engine/types.js";
import { Theme, ThemeFactory } from "../theme/index.js";
import {
getThemeScope,
ScopedThemeProvider,
useThemeColors,
useThemeEngineStore
} from "../theme-engine/index.js";
import { colorsToCSSVariables } from "../theme-engine/utils.js";
export type EmotionThemeProviderProps = {
scope?: keyof ThemeScopes;
@@ -92,65 +90,6 @@ function _EmotionThemeProvider(
);
}
type FixedThemeProviderProps = {
scope: keyof ThemeScopes;
injectCssVars?: boolean;
theme: ThemeDefinition;
} & Omit<BoxProps, "variant" | "ref">;
function _FixedThemeProvider(
props: PropsWithChildren<FixedThemeProviderProps>,
forwardedRef: ForwardedRef<HTMLDivElement>
) {
const {
children,
scope = "base",
injectCssVars = true,
theme,
...restProps
} = props;
const themeScope = getThemeScope(scope, theme);
const { colors } = themeScope;
const themeProperties = useMemo(
() =>
ThemeFactory.construct({
scope: colors,
colorScheme: theme.colorScheme
}),
[colors, theme.colorScheme]
);
return (
<ThemeProvider
theme={{
...themeProperties,
colors: themeProperties.colors
}}
>
<ScopedThemeProvider value={scope}>
{injectCssVars ? (
<Box
{...restProps}
ref={forwardedRef}
css={colorsToCSSVariables(themeProperties.colors)}
>
{children}
</Box>
) : (
children
)}
</ScopedThemeProvider>
</ThemeProvider>
);
}
export const FixedThemeProvider = React.forwardRef<
HTMLDivElement,
FixedThemeProviderProps
>(_FixedThemeProvider);
export const EmotionThemeProvider = React.forwardRef<
HTMLDivElement,
EmotionThemeProviderProps

View File

@@ -69,19 +69,6 @@ export function useThemeColors(scope?: keyof ThemeScopes): ThemeScope {
return currentTheme;
}
export function getThemeScope(
scope: keyof ThemeScopes,
theme: ThemeDefinition
): ThemeScope {
const themeScope = theme.scopes[scope] || theme.scopes.base;
const currentTheme = {
colors: buildVariants(scope, theme, themeScope),
isDark: theme.colorScheme === "dark",
scope
};
return currentTheme;
}
export const useCurrentThemeScope = () => useContext(ThemeScopeContext);
export const ScopedThemeProvider = ThemeScopeContext.Provider;
export const THEME_COMPATIBILITY_VERSION: ThemeCompatibilityVersion = 1;

View File

@@ -204,7 +204,6 @@ function MenuContainer(props: PropsWithChildren<MenuContainerProps>) {
boxShadow: "menu",
border: "1px solid var(--border)",
minWidth: 220,
maxWidth: "min(95vw, 350px)",
maxHeight: "80vh",
...sx
}}

View File

@@ -62,7 +62,7 @@ export function MenuButton(props: MenuButtonProps) {
ref={itemRef}
tabIndex={-1}
variant="menuitem"
title={tooltip || title}
title={tooltip}
disabled={isDisabled}
onClick={(e) => onClick?.(e.nativeEvent)}
sx={{
@@ -73,12 +73,7 @@ export function MenuButton(props: MenuButtonProps) {
}}
>
<Flex
sx={{
fontSize: "inherit",
fontFamily: "inherit",
minWidth: 0,
overflow: "hidden"
}}
sx={{ fontSize: "inherit", fontFamily: "inherit", flexShrink: 0 }}
>
<Icon
path={icon || ""}
@@ -97,10 +92,7 @@ export function MenuButton(props: MenuButtonProps) {
fontFamily: "inherit",
color: variant === "dangerous" ? "paragraph-error" : "paragraph",
textAlign: "left",
minWidth: 0,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
flexShrink: 0,
...styles?.title
}}
>