mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-09-01 03:29:21 +02:00
Compare commits
8 Commits
mobile-rel
...
importer/a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f2c9caee1 | ||
|
|
2bb3d43db6 | ||
|
|
87987c75b2 | ||
|
|
47140d691d | ||
|
|
98e35d93a3 | ||
|
|
ee3f08a725 | ||
|
|
54a9c131fa | ||
|
|
bcfdb5caf9 |
4
apps/desktop/package-lock.json
generated
4
apps/desktop/package-lock.json
generated
@@ -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": {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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!
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
}}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
10
apps/mobile/package-lock.json
generated
10
apps/mobile/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.4.10",
|
||||
"version": "3.4.8",
|
||||
"private": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"scripts": {
|
||||
|
||||
@@ -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")
|
||||
|
||||
1112
apps/web/package-lock.json
generated
1112
apps/web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
121
apps/web/src/common/sqlite/importer-sqlite-adapter.ts
Normal file
121
apps/web/src/common/sqlite/importer-sqlite-adapter.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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 || "");
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
})
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
- Fix drag/drop issues in task-lists and improved UX
|
||||
- Minor bug fixes and improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
@@ -1,3 +0,0 @@
|
||||
- Minor bug fixes and improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
33
inbox-public.asc
Normal file
33
inbox-public.asc
Normal 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-----
|
||||
@@ -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);
|
||||
|
||||
144
packages/editor/package-lock.json
generated
144
packages/editor/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 };
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
|
||||
@@ -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`
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}}
|
||||
|
||||
@@ -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
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user