mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-29 18:19:38 +02:00
Compare commits
131 Commits
fix-ts-err
...
fix-ios-ed
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8abab1457 | ||
|
|
b618386e6b | ||
|
|
2e79b2c7a2 | ||
|
|
4e4990bd98 | ||
|
|
4c8a44353a | ||
|
|
7dd8b13bc8 | ||
|
|
e1aa3b59be | ||
|
|
d79888793a | ||
|
|
b56e381022 | ||
|
|
15b32b312d | ||
|
|
6a0a00f126 | ||
|
|
9876c2ef6d | ||
|
|
fba29e3c80 | ||
|
|
b8275f5a3f | ||
|
|
b7e8365117 | ||
|
|
8df7b5c56b | ||
|
|
04519d1db7 | ||
|
|
7b808bea0b | ||
|
|
2878f1d9cf | ||
|
|
651f6011b8 | ||
|
|
f17cbb5aa3 | ||
|
|
a2a4a165a5 | ||
|
|
9c7ff38f99 | ||
|
|
76f27adbea | ||
|
|
85a5f38590 | ||
|
|
dcdd876a4c | ||
|
|
d055908809 | ||
|
|
89806148b2 | ||
|
|
433be12b2b | ||
|
|
9ff235e767 | ||
|
|
9b6f7ff225 | ||
|
|
99f97ac565 | ||
|
|
7e598098fe | ||
|
|
02afbf6c90 | ||
|
|
b464fafc18 | ||
|
|
e06320108b | ||
|
|
a377b13e2b | ||
|
|
4728212531 | ||
|
|
71fc2a3cac | ||
|
|
303282624e | ||
|
|
2ca02937df | ||
|
|
00b62df2e8 | ||
|
|
6d377ded9a | ||
|
|
087c404225 | ||
|
|
4932763468 | ||
|
|
abcc19d5da | ||
|
|
f1ce1e9b60 | ||
|
|
5a6f99a41b | ||
|
|
1862802052 | ||
|
|
1adddbdb4c | ||
|
|
15f4f2accb | ||
|
|
725f3c3522 | ||
|
|
efa73a1c32 | ||
|
|
00250c6669 | ||
|
|
d11afda1a8 | ||
|
|
1350784c33 | ||
|
|
1852a216a2 | ||
|
|
422c2492c4 | ||
|
|
c9c72741b3 | ||
|
|
99dbcaff7d | ||
|
|
8d83f5474d | ||
|
|
a912a1a22e | ||
|
|
ea7afbe93e | ||
|
|
7b2a8c3e20 | ||
|
|
911adfc151 | ||
|
|
23f710856e | ||
|
|
7f6274821c | ||
|
|
4700843517 | ||
|
|
8823054227 | ||
|
|
7c63a3e004 | ||
|
|
2a59154268 | ||
|
|
ed15d53484 | ||
|
|
fc8a4bb55d | ||
|
|
dd1c1bb11d | ||
|
|
3ffbad5727 | ||
|
|
207ae39d13 | ||
|
|
ae141ac9c5 | ||
|
|
40f058b14b | ||
|
|
865c9f3e32 | ||
|
|
26df9bfdd8 | ||
|
|
1e52e01fb8 | ||
|
|
5fece5faaa | ||
|
|
d7fdaf6c46 | ||
|
|
8e1faf9f60 | ||
|
|
9dce39b32d | ||
|
|
80a0b75887 | ||
|
|
c9704e1031 | ||
|
|
31e32cd154 | ||
|
|
61c296fc37 | ||
|
|
c8d38235b2 | ||
|
|
b717a64607 | ||
|
|
0f2b1d3e14 | ||
|
|
4abefe63db | ||
|
|
fc31b2a8d7 | ||
|
|
2708bac4fb | ||
|
|
a47967dd53 | ||
|
|
53ba5562d3 | ||
|
|
347df32943 | ||
|
|
ac8c63526b | ||
|
|
a48bd360ca | ||
|
|
f476969e9e | ||
|
|
c45539dea6 | ||
|
|
d8f5815b84 | ||
|
|
2eaf07118a | ||
|
|
b636b09a96 | ||
|
|
c16bbcaad5 | ||
|
|
49ffcbea03 | ||
|
|
f772cf0f14 | ||
|
|
3f1406a05b | ||
|
|
21760b2b74 | ||
|
|
d8011f0239 | ||
|
|
b9a8b054e2 | ||
|
|
2ab321869e | ||
|
|
fcb45a24d6 | ||
|
|
7cdb13def6 | ||
|
|
2ab58f9203 | ||
|
|
df74448e17 | ||
|
|
ada98fca93 | ||
|
|
1d3650659f | ||
|
|
9b43af475b | ||
|
|
d9c592c7fb | ||
|
|
f10ac9fbb5 | ||
|
|
b7334c09d4 | ||
|
|
0cd3bd5739 | ||
|
|
33d69480eb | ||
|
|
5cae4eb0d9 | ||
|
|
840c7fda5e | ||
|
|
4ce24ac8fd | ||
|
|
ae9e80e12e | ||
|
|
43356e5b6a | ||
|
|
7b13f35424 |
36
.github/workflows/desktop.publish.yml
vendored
36
.github/workflows/desktop.publish.yml
vendored
@@ -33,11 +33,19 @@ on:
|
||||
required: true
|
||||
default: true
|
||||
description: "Build for macOS?"
|
||||
release-track:
|
||||
type: choice
|
||||
required: true
|
||||
default: stable
|
||||
description: "Select the release track"
|
||||
options:
|
||||
- stable
|
||||
- beta
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -61,9 +69,14 @@ jobs:
|
||||
run: |
|
||||
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate desktop build
|
||||
- name: Generate desktop build (stable)
|
||||
if: ${{ inputs.release-track == 'stable' }}
|
||||
run: npx nx build:desktop @notesnook/web
|
||||
|
||||
- name: Generate desktop build (beta)
|
||||
if: ${{ inputs.release-track == 'beta' }}
|
||||
run: BETA=true npx nx build:desktop @notesnook/web
|
||||
|
||||
- name: Build desktop bundle
|
||||
working-directory: ./apps/desktop
|
||||
run: npm run bundle
|
||||
@@ -99,7 +112,8 @@ jobs:
|
||||
- name: Generate flatpak sources
|
||||
if: inputs.publish-github && inputs.build-linux
|
||||
run: |
|
||||
sudo apt-get install -y flatpak flatpak-builder pipx
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --quiet flatpak flatpak-builder pipx
|
||||
git clone https://github.com/flatpak/flatpak-builder-tools.git /tmp/flatpak-builder-tools
|
||||
cd /tmp/flatpak-builder-tools/node
|
||||
pipx install .
|
||||
@@ -185,7 +199,7 @@ jobs:
|
||||
CSC_KEY_PASSWORD: ${{ secrets.mac_certs_password }}
|
||||
run: |
|
||||
npx nx run release --project @notesnook/desktop -- --variant=mas
|
||||
yarn electron-builder --mac mas --universal -p never
|
||||
yarn electron-builder --config=electron-builder.config.js --mac mas --universal -p never
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Build zip and dmg
|
||||
@@ -199,9 +213,9 @@ jobs:
|
||||
run: |
|
||||
npx nx run release --project @notesnook/desktop
|
||||
if [ ${{ inputs.publish-github }} == true ]; then
|
||||
yarn electron-builder --mac zip dmg --arm64 --x64 -p always
|
||||
yarn electron-builder --config=electron-builder.config.js --mac zip dmg --arm64 --x64 -p always
|
||||
else
|
||||
yarn electron-builder --mac zip dmg --arm64 --x64 -p never
|
||||
yarn electron-builder --config=electron-builder.config.js --mac zip dmg --arm64 --x64 -p never
|
||||
fi
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
@@ -263,7 +277,7 @@ jobs:
|
||||
- name: Build snap
|
||||
if: inputs.publish-snap
|
||||
run: |
|
||||
yarn electron-builder --linux snap:x64 -p never
|
||||
yarn electron-builder --config=electron-builder.config.js --linux snap:x64 -p never
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Build AppImage
|
||||
@@ -271,9 +285,9 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
if [ ${{ inputs.publish-github }} == true ]; then
|
||||
yarn electron-builder --linux AppImage:x64 AppImage:arm64 -p always
|
||||
yarn electron-builder --config=electron-builder.config.js --linux AppImage:x64 AppImage:arm64 -p always
|
||||
else
|
||||
yarn electron-builder --linux AppImage:x64 AppImage:arm64 -p never
|
||||
yarn electron-builder --config=electron-builder.config.js --linux AppImage:x64 AppImage:arm64 -p never
|
||||
fi
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
@@ -333,8 +347,8 @@ jobs:
|
||||
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
|
||||
run: |
|
||||
if ($${{ inputs.publish-github }} -eq $true) {
|
||||
yarn electron-builder --win --publish always
|
||||
yarn electron-builder --config=electron-builder.config.js --win --publish always
|
||||
} else {
|
||||
yarn electron-builder --win --publish never
|
||||
yarn electron-builder --config=electron-builder.config.js --win --publish never
|
||||
}
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
20
.github/workflows/desktop.tests.yml
vendored
20
.github/workflows/desktop.tests.yml
vendored
@@ -69,12 +69,11 @@ jobs:
|
||||
|
||||
- name: Build app
|
||||
run: |
|
||||
yarn electron-builder --mac --dir --x64
|
||||
yarn electron-builder --config=electron-builder.config.js --mac --dir --x64
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Run tests x64
|
||||
run: |
|
||||
EXECUTABLE_PATH=output/mac/Notesnook.app/Contents/MacOS/Notesnook npm run test
|
||||
run: npm run test
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Upload test results
|
||||
@@ -114,12 +113,11 @@ jobs:
|
||||
|
||||
- name: Build app
|
||||
run: |
|
||||
yarn electron-builder --mac --dir --arm64
|
||||
yarn electron-builder --config=electron-builder.config.js --mac --dir --arm64
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Run tests arm64
|
||||
run: |
|
||||
EXECUTABLE_PATH=output/mac-arm64/Notesnook.app/Contents/MacOS/Notesnook npm run test
|
||||
run: npm run test
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Upload test results
|
||||
@@ -165,12 +163,11 @@ jobs:
|
||||
|
||||
- name: Build app
|
||||
run: |
|
||||
yarn electron-builder --linux --dir --arm64 --x64
|
||||
yarn electron-builder --config=electron-builder.config.js --linux --dir --arm64 --x64
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
EXECUTABLE_PATH=output/linux-unpacked/notesnook xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24" -- npm run test
|
||||
run: xvfb-run --auto-servernum --server-args="-screen 0 1920x1080x24" -- npm run test
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Upload test results
|
||||
@@ -216,12 +213,11 @@ jobs:
|
||||
|
||||
- name: Build app
|
||||
run: |
|
||||
npx cross-env NOTESNOOK_STAGING=true yarn electron-builder --win --dir --arm64 --x64
|
||||
npx cross-env NOTESNOOK_STAGING=true yarn electron-builder --config=electron-builder.config.js --win --dir --arm64 --x64
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
npx cross-env EXECUTABLE_PATH=output/win-unpacked/Notesnook.exe npm run test
|
||||
run: npm run test
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Upload test results
|
||||
|
||||
4
.github/workflows/ios.publish.yml
vendored
4
.github/workflows/ios.publish.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
ccache -p
|
||||
|
||||
- name: Cache Pods
|
||||
uses: actions/cache@v2
|
||||
uses: actions/cache@v3
|
||||
id: pods-cache
|
||||
with:
|
||||
path: apps/mobile/native/ios/Pods
|
||||
|
||||
30
.github/workflows/web.publish.yml
vendored
30
.github/workflows/web.publish.yml
vendored
@@ -1,6 +1,16 @@
|
||||
name: Publish @notesnook/web
|
||||
|
||||
on: workflow_dispatch
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
release-track:
|
||||
type: choice
|
||||
required: true
|
||||
default: stable
|
||||
description: "Select the release track"
|
||||
options:
|
||||
- stable
|
||||
- beta
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -24,8 +34,20 @@ jobs:
|
||||
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
|
||||
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Generate build
|
||||
- name: Generate build (stable)
|
||||
if: ${{ inputs.release-track == 'stable' }}
|
||||
run: npm run build:web
|
||||
|
||||
- name: Publish to Cloudflare Pages
|
||||
run: npx --yes wrangler pages deploy --project-name=notesnook-app ./apps/web/build/
|
||||
- name: Publish to Cloudflare Pages (stable)
|
||||
if: ${{ inputs.release-track == 'stable' }}
|
||||
working-directory: ./apps/web
|
||||
run: npx --yes wrangler pages deploy --project-name=notesnook-app ./build/
|
||||
|
||||
- name: Generate build (beta)
|
||||
if: ${{ inputs.release-track == 'beta' }}
|
||||
run: npm run build:beta:web
|
||||
|
||||
- name: Publish to Cloudflare Pages (beta)
|
||||
if: ${{ inputs.release-track == 'beta' }}
|
||||
working-directory: ./apps/web
|
||||
run: npx --yes wrangler pages deploy --branch=beta --project-name=notesnook-app-beta ./build/
|
||||
|
||||
2
.github/workflows/web.tests.yml
vendored
2
.github/workflows/web.tests.yml
vendored
@@ -49,7 +49,7 @@ jobs:
|
||||
name: 🧪 Test (${{ matrix.shard }}/${{ strategy.job-total }})
|
||||
strategy:
|
||||
matrix:
|
||||
shard: [1, 2, 3, 4]
|
||||
shard: [1, 2, 3, 4, 5]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
4
apps/desktop/.gitignore
vendored
4
apps/desktop/.gitignore
vendored
@@ -2,4 +2,6 @@ node_modules
|
||||
build
|
||||
output
|
||||
dist
|
||||
_catalog
|
||||
_catalog
|
||||
test-results
|
||||
test-artifacts
|
||||
|
||||
@@ -58,13 +58,13 @@ This will compile and run the app in production mode but it won't generate any p
|
||||
npm run release -- --rebuild
|
||||
|
||||
# For macOS
|
||||
npx electron-builder --mac dmg --arm64 --x64 -publish never
|
||||
npx electron-builder --config=electron-builder.config.js --mac dmg --arm64 --x64 -publish never
|
||||
|
||||
# For Linux (AppImage)
|
||||
npx electron-builder --linux AppImage:x64 AppImage:arm64 -publish never
|
||||
npx electron-builder --config=electron-builder.config.js --linux AppImage:x64 AppImage:arm64 -publish never
|
||||
|
||||
# For Windows
|
||||
npx electron-builder --win --publish never
|
||||
npx electron-builder --config=electron-builder.config.js --win --publish never
|
||||
```
|
||||
|
||||
Feel free to play around with the `electron-builder` command to get the packages you need. `npx electron-builder --help` is a great resource to learn different commands & platforms supported by `electron-builder`.
|
||||
|
||||
246
apps/desktop/__tests__/auto-updates.test.ts
Normal file
246
apps/desktop/__tests__/auto-updates.test.ts
Normal file
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { test } from "vitest";
|
||||
import { harness } from "./utils.js";
|
||||
import { writeFile } from "fs/promises";
|
||||
import { Page } from "playwright";
|
||||
import { gt, lt } from "semver";
|
||||
|
||||
test("update starts downloading if version is outdated", async (t) => {
|
||||
await harness(
|
||||
t,
|
||||
async ({ page }) => {
|
||||
await page.waitForSelector("#authForm");
|
||||
|
||||
t.expect(
|
||||
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||
).toBe(true);
|
||||
|
||||
await page
|
||||
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||
.click();
|
||||
|
||||
await skipDialog(page);
|
||||
|
||||
await page.waitForSelector(".ProseMirror");
|
||||
|
||||
await page
|
||||
.locator(".theme-scope-statusBar")
|
||||
.getByRole("button", { name: /updating/i })
|
||||
.waitFor({ state: "attached" });
|
||||
},
|
||||
{ version: "3.0.0" }
|
||||
);
|
||||
});
|
||||
|
||||
test("update is only shown if version is outdated and auto updates are disabled", async (t) => {
|
||||
await harness(
|
||||
t,
|
||||
async (ctx) => {
|
||||
await ctx.app.close();
|
||||
await writeFile(
|
||||
ctx.configPath,
|
||||
JSON.stringify({
|
||||
automaticUpdates: false
|
||||
})
|
||||
);
|
||||
|
||||
await ctx.relaunch();
|
||||
|
||||
const { page } = ctx;
|
||||
|
||||
await page.waitForSelector("#authForm");
|
||||
|
||||
t.expect(
|
||||
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||
).toBe(true);
|
||||
|
||||
await page
|
||||
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||
.click();
|
||||
|
||||
await skipDialog(page);
|
||||
|
||||
await page.waitForSelector(".ProseMirror");
|
||||
|
||||
await page
|
||||
.locator(".theme-scope-statusBar")
|
||||
.getByRole("button", { name: /available/i })
|
||||
.waitFor({ state: "attached" });
|
||||
},
|
||||
{ version: "3.0.0" }
|
||||
);
|
||||
});
|
||||
|
||||
test("update to stable if it is newer", async (t) => {
|
||||
await harness(
|
||||
t,
|
||||
async (ctx) => {
|
||||
await ctx.app.close();
|
||||
await writeFile(
|
||||
ctx.configPath,
|
||||
JSON.stringify({
|
||||
automaticUpdates: false,
|
||||
releaseTrack: "beta"
|
||||
})
|
||||
);
|
||||
|
||||
await ctx.relaunch();
|
||||
|
||||
const { page } = ctx;
|
||||
|
||||
await page.waitForSelector("#authForm");
|
||||
|
||||
t.expect(
|
||||
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||
).toBe(true);
|
||||
|
||||
await page
|
||||
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||
.click();
|
||||
|
||||
await skipDialog(page);
|
||||
|
||||
await page.waitForSelector(".ProseMirror");
|
||||
|
||||
const updateButton = page
|
||||
.locator(".theme-scope-statusBar")
|
||||
.getByRole("button", { name: /available/i });
|
||||
await updateButton.waitFor({ state: "visible" });
|
||||
const content = await updateButton.textContent();
|
||||
const version = content?.split(" ")?.[0] || "";
|
||||
t.expect(gt(version, "3.0.0-beta.0")).toBe(true);
|
||||
},
|
||||
{ version: "3.0.0-beta.0" }
|
||||
);
|
||||
});
|
||||
|
||||
test("update is not available if it latest stable version is older", async (t) => {
|
||||
await harness(
|
||||
t,
|
||||
async (ctx) => {
|
||||
await ctx.app.close();
|
||||
await writeFile(
|
||||
ctx.configPath,
|
||||
JSON.stringify({
|
||||
automaticUpdates: false,
|
||||
releaseTrack: "beta"
|
||||
})
|
||||
);
|
||||
|
||||
await ctx.relaunch();
|
||||
|
||||
const { page } = ctx;
|
||||
|
||||
await page.waitForSelector("#authForm");
|
||||
|
||||
t.expect(
|
||||
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||
).toBe(true);
|
||||
|
||||
await page
|
||||
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||
.click();
|
||||
|
||||
await skipDialog(page);
|
||||
|
||||
await page.waitForSelector(".ProseMirror");
|
||||
|
||||
await page
|
||||
.locator(".theme-scope-statusBar")
|
||||
.getByRole("button", { name: /checking for updates/i })
|
||||
.waitFor({ state: "hidden" });
|
||||
|
||||
t.expect(
|
||||
await page
|
||||
.locator(".theme-scope-statusBar")
|
||||
.getByRole("button", { name: /available/i })
|
||||
.isHidden()
|
||||
).toBe(true);
|
||||
},
|
||||
{ version: "99.0.0-beta.0" }
|
||||
);
|
||||
});
|
||||
|
||||
test("downgrade to stable on switching to stable release track", async (t) => {
|
||||
await harness(
|
||||
t,
|
||||
async (ctx) => {
|
||||
await ctx.app.close();
|
||||
await writeFile(
|
||||
ctx.configPath,
|
||||
JSON.stringify({
|
||||
automaticUpdates: false,
|
||||
releaseTrack: "stable"
|
||||
})
|
||||
);
|
||||
|
||||
await ctx.relaunch();
|
||||
|
||||
const { page } = ctx;
|
||||
|
||||
await page.waitForSelector("#authForm");
|
||||
|
||||
t.expect(
|
||||
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||
).toBe(true);
|
||||
|
||||
await page
|
||||
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||
.click();
|
||||
|
||||
await skipDialog(page);
|
||||
|
||||
await page.waitForSelector(".ProseMirror");
|
||||
|
||||
await page
|
||||
.locator(".theme-scope-statusBar")
|
||||
.getByRole("button", { name: /checking for updates/i })
|
||||
.waitFor({ state: "hidden" });
|
||||
|
||||
const updateButton = page
|
||||
.locator(".theme-scope-statusBar")
|
||||
.getByRole("button", { name: /available/i });
|
||||
await updateButton.waitFor({ state: "visible" });
|
||||
const content = await updateButton.textContent();
|
||||
const version = content?.split(" ")?.[0] || "";
|
||||
t.expect(lt(version, "99.0.0-beta.0")).toBe(true);
|
||||
},
|
||||
{ version: "99.0.0-beta.0" }
|
||||
);
|
||||
});
|
||||
|
||||
async function skipDialog(page: Page) {
|
||||
await page
|
||||
.waitForSelector(".ReactModal__Content", {
|
||||
timeout: 1000
|
||||
})
|
||||
.catch(() => {})
|
||||
.then(async () => {
|
||||
const positiveButton = page.locator(
|
||||
"button[data-role='positive-button']"
|
||||
);
|
||||
const negativeButton = page.locator(
|
||||
"button[data-role='negative-button']"
|
||||
);
|
||||
if (await positiveButton.isVisible()) await positiveButton.click();
|
||||
else if (await negativeButton.isVisible()) await negativeButton.click();
|
||||
});
|
||||
}
|
||||
@@ -17,16 +17,12 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import test from "node:test";
|
||||
import { launchApp } from "./utils.mjs";
|
||||
import { test } from "vitest";
|
||||
import { harness } from "./utils.js";
|
||||
import assert from "assert";
|
||||
import slugify from "slugify";
|
||||
import path from "path";
|
||||
import { mkdir } from "fs/promises";
|
||||
|
||||
test("make sure app loads", async (t) => {
|
||||
const { app, page } = await launchApp();
|
||||
try {
|
||||
await harness(t, async ({ page }) => {
|
||||
await page.waitForSelector("#authForm");
|
||||
|
||||
assert.ok(
|
||||
@@ -38,16 +34,5 @@ test("make sure app loads", async (t) => {
|
||||
.click();
|
||||
|
||||
await page.waitForSelector(".ProseMirror");
|
||||
} catch (e) {
|
||||
await mkdir("test-results", { recursive: true });
|
||||
await page.screenshot({
|
||||
path: path.join(
|
||||
"test-results",
|
||||
`${slugify(t.name)}-${process.platform}-${process.arch}-error.png`
|
||||
)
|
||||
});
|
||||
throw e;
|
||||
} finally {
|
||||
await app.close();
|
||||
}
|
||||
});
|
||||
});
|
||||
172
apps/desktop/__tests__/utils.ts
Normal file
172
apps/desktop/__tests__/utils.ts
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
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 { execSync } from "child_process";
|
||||
import { mkdir } from "fs/promises";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import path from "path";
|
||||
import { _electron as electron } from "playwright";
|
||||
import slugify from "slugify";
|
||||
import { TaskContext } from "vitest";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const IS_DEBUG = process.env.NN_DEBUG === "true" || process.env.CI === "true";
|
||||
|
||||
interface AppContext {
|
||||
app: import("playwright").ElectronApplication;
|
||||
page: import("playwright").Page;
|
||||
configPath: string;
|
||||
relaunch: () => Promise<void>;
|
||||
}
|
||||
|
||||
interface TestOptions {
|
||||
version: string;
|
||||
}
|
||||
|
||||
export async function harness(
|
||||
t: TaskContext,
|
||||
cb: (ctx: AppContext) => Promise<void>,
|
||||
options?: TestOptions
|
||||
) {
|
||||
const ctx = await buildAndLaunchApp(options);
|
||||
|
||||
t.onTestFinished(async (result) => {
|
||||
if (result.state === "fail") {
|
||||
await mkdir("test-results", { recursive: true });
|
||||
await ctx.page.screenshot({
|
||||
path: path.join(
|
||||
"test-results",
|
||||
`${slugify(t.task.name)}-${process.platform}-${
|
||||
process.arch
|
||||
}-error.png`
|
||||
)
|
||||
});
|
||||
}
|
||||
await ctx.app.close();
|
||||
});
|
||||
|
||||
await cb(ctx);
|
||||
}
|
||||
|
||||
async function buildAndLaunchApp(options?: TestOptions): Promise<AppContext> {
|
||||
const productName = makeid(10);
|
||||
const executablePath = await buildApp({ ...options, productName });
|
||||
const { app, page, configPath } = await launchApp(executablePath);
|
||||
const ctx: AppContext = {
|
||||
app,
|
||||
page,
|
||||
configPath,
|
||||
relaunch: async () => {
|
||||
const { app, page, configPath } = await launchApp(executablePath);
|
||||
ctx.app = app;
|
||||
ctx.page = page;
|
||||
ctx.configPath = configPath;
|
||||
}
|
||||
};
|
||||
return ctx;
|
||||
}
|
||||
|
||||
async function launchApp(executablePath: string) {
|
||||
const app = await electron.launch({
|
||||
executablePath,
|
||||
args: IS_DEBUG ? [] : ["--hidden"],
|
||||
env:
|
||||
process.platform === "linux"
|
||||
? {
|
||||
...(process.env as Record<string, string>),
|
||||
APPIMAGE: "true"
|
||||
}
|
||||
: (process.env as Record<string, string>)
|
||||
});
|
||||
|
||||
const page = await app.firstWindow();
|
||||
|
||||
const userDataDirectory = await app.evaluate((a) => {
|
||||
return a.app.getPath("userData");
|
||||
});
|
||||
const configPath = path.join(userDataDirectory, "config.json");
|
||||
return {
|
||||
app,
|
||||
page,
|
||||
configPath
|
||||
};
|
||||
}
|
||||
|
||||
async function buildApp({
|
||||
version,
|
||||
productName
|
||||
}: {
|
||||
version?: string;
|
||||
productName: string;
|
||||
}) {
|
||||
const buildRoot = path.join("test-artifacts", `${productName}-build`);
|
||||
const output = path.join("test-artifacts", `${productName}-output`);
|
||||
execSync(`npm run release -- --root ${buildRoot} --skip-tsc-build`, {
|
||||
stdio: IS_DEBUG ? "inherit" : "ignore"
|
||||
});
|
||||
|
||||
const args = [
|
||||
`--config electron-builder.config.js`,
|
||||
`--c.extraMetadata.productName=${productName}`,
|
||||
"--publish=never"
|
||||
];
|
||||
if (version) args.push(`--c.extraMetadata.version=${version}`);
|
||||
|
||||
execSync(`npx electron-builder --dir --${process.arch} ${args.join(" ")}`, {
|
||||
stdio: IS_DEBUG ? "inherit" : "ignore",
|
||||
env: {
|
||||
...process.env,
|
||||
NOTESNOOK_STAGING: "true",
|
||||
NN_BUILD_ROOT: buildRoot,
|
||||
NN_PRODUCT_NAME: productName,
|
||||
NN_APP_ID: `com.notesnook.test.${productName}`,
|
||||
NN_OUTPUT_DIR: output
|
||||
}
|
||||
});
|
||||
|
||||
return path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
output,
|
||||
process.platform === "linux"
|
||||
? process.arch === "arm64"
|
||||
? "linux-arm64-unpacked"
|
||||
: "linux-unpacked"
|
||||
: process.platform === "darwin"
|
||||
? process.arch === "arm64"
|
||||
? `mac-arm64/${productName}.app/Contents/MacOS/`
|
||||
: `mac/${productName}.app/Contents/MacOS/`
|
||||
: "win-unpacked",
|
||||
process.platform === "win32" ? `${productName}.exe` : productName
|
||||
);
|
||||
}
|
||||
|
||||
function makeid(length: number) {
|
||||
let result = "";
|
||||
const characters =
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
const charactersLength = characters.length;
|
||||
let counter = 0;
|
||||
while (counter < length) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
counter += 1;
|
||||
}
|
||||
return result.toLowerCase();
|
||||
}
|
||||
Binary file not shown.
200
apps/desktop/electron-builder.config.js
Normal file
200
apps/desktop/electron-builder.config.js
Normal file
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
|
||||
const path = require("path");
|
||||
|
||||
const buildRoot = process.env.NN_BUILD_ROOT || ".";
|
||||
const buildFiles = [
|
||||
`${buildRoot}/build/`,
|
||||
`!${buildRoot}/build/screenshots\${/*}`,
|
||||
`!${buildRoot}/build/banner.jpg`,
|
||||
`!${buildRoot}/build/*.ico`,
|
||||
`!${buildRoot}/build/*.png`
|
||||
];
|
||||
|
||||
const productName = process.env.NN_PRODUCT_NAME || "Notesnook";
|
||||
const appId = process.env.NN_APP_ID || "org.streetwriters.notesnook";
|
||||
const outputDir = process.env.NN_OUTPUT_DIR || "output";
|
||||
const linuxExecutableName = process.env.NN_PRODUCT_NAME
|
||||
? process.env.NN_PRODUCT_NAME.toLowerCase().replace(/\s+/g, "-")
|
||||
: "notesnook";
|
||||
const year = new Date().getFullYear();
|
||||
|
||||
module.exports = {
|
||||
appId: appId,
|
||||
productName: productName,
|
||||
copyright: `Copyright © ${year} Streetwriters (Private) Limited`,
|
||||
artifactName: "notesnook_${os}_${arch}.${ext}",
|
||||
generateUpdatesFilesForAllChannels: true,
|
||||
asar: false,
|
||||
files: [
|
||||
"!*.chunk.js.map",
|
||||
"!*.chunk.js.LICENSE.txt",
|
||||
...buildFiles,
|
||||
"!node_modules${/*}",
|
||||
"node_modules/better-sqlite3-multiple-ciphers/build/Release/better_sqlite3.node",
|
||||
"node_modules/better-sqlite3-multiple-ciphers/lib",
|
||||
"node_modules/better-sqlite3-multiple-ciphers/package.json",
|
||||
"node_modules/file-uri-to-path",
|
||||
"node_modules/bindings",
|
||||
"node_modules/node-gyp-build",
|
||||
"node_modules/sqlite-better-trigram",
|
||||
"node_modules/sodium-native/prebuilds/${platform}-${arch}",
|
||||
{
|
||||
from: "node_modules/sqlite-better-trigram-linux-${arch}",
|
||||
to: "node_modules/sqlite-better-trigram-linux-${arch}"
|
||||
},
|
||||
{
|
||||
from: "node_modules/sqlite-better-trigram-darwin-${arch}",
|
||||
to: "node_modules/sqlite-better-trigram-darwin-${arch}"
|
||||
},
|
||||
{
|
||||
from: "node_modules/sqlite-better-trigram-windows-${arch}",
|
||||
to: "node_modules/sqlite-better-trigram-windows-${arch}"
|
||||
},
|
||||
"node_modules/sodium-native/index.js",
|
||||
"node_modules/sodium-native/package.json"
|
||||
],
|
||||
afterPack: "./scripts/removeLocales.js",
|
||||
mac: {
|
||||
bundleVersion: "240",
|
||||
minimumSystemVersion: "10.12.0",
|
||||
target: [
|
||||
{
|
||||
target: "dmg",
|
||||
arch: ["arm64", "x64"]
|
||||
},
|
||||
{
|
||||
target: "zip",
|
||||
arch: ["arm64", "x64"]
|
||||
}
|
||||
],
|
||||
category: "public.app-category.productivity",
|
||||
darkModeSupport: true,
|
||||
type: "distribution",
|
||||
hardenedRuntime: true,
|
||||
entitlements: "assets/entitlements.mac.plist",
|
||||
entitlementsInherit: "assets/entitlements.mac.plist",
|
||||
gatekeeperAssess: false,
|
||||
icon: "assets/icons/app.icns",
|
||||
notarize: true
|
||||
},
|
||||
dmg: {
|
||||
contents: [
|
||||
{
|
||||
x: 130,
|
||||
y: 220
|
||||
},
|
||||
{
|
||||
x: 410,
|
||||
y: 220,
|
||||
type: "link",
|
||||
path: "/Applications"
|
||||
}
|
||||
],
|
||||
icon: "assets/icons/app.icns",
|
||||
title: "Install Notesnook"
|
||||
},
|
||||
mas: {
|
||||
entitlements: "assets/entitlements.mas.plist",
|
||||
entitlementsInherit: "assets/entitlements.mas.inherit.plist",
|
||||
entitlementsLoginHelper: "assets/entitlements.mas.loginhelper.plist",
|
||||
hardenedRuntime: true
|
||||
},
|
||||
win: {
|
||||
target: [
|
||||
{
|
||||
target: "nsis",
|
||||
arch: ["x64", "arm64"]
|
||||
},
|
||||
{
|
||||
target: "portable",
|
||||
arch: ["x64", "arm64"]
|
||||
}
|
||||
],
|
||||
signtoolOptions: {
|
||||
signingHashAlgorithms: ["sha256"],
|
||||
sign: "./scripts/sign.js"
|
||||
},
|
||||
icon: "assets/icons/app.ico"
|
||||
},
|
||||
portable: {
|
||||
artifactName: "notesnook_${os}_${arch}_portable.${ext}"
|
||||
},
|
||||
nsis: {
|
||||
oneClick: true,
|
||||
createDesktopShortcut: "always",
|
||||
deleteAppDataOnUninstall: true
|
||||
},
|
||||
linux: {
|
||||
target: [
|
||||
{
|
||||
target: "AppImage",
|
||||
arch: ["x64", "arm64"]
|
||||
},
|
||||
{
|
||||
target: "snap",
|
||||
arch: ["x64", "arm64"]
|
||||
}
|
||||
],
|
||||
category: "Office",
|
||||
icon: "assets/icons/app.icns",
|
||||
description: "Your private note taking space",
|
||||
executableName: linuxExecutableName,
|
||||
desktop: {
|
||||
actions: [
|
||||
{
|
||||
id: "new-note",
|
||||
name: "New note",
|
||||
args: "new note"
|
||||
},
|
||||
{
|
||||
id: "new-notebook",
|
||||
name: "New notebook",
|
||||
args: "new notebook"
|
||||
},
|
||||
{
|
||||
id: "new-reminder",
|
||||
name: "New reminder",
|
||||
args: "new reminder"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
snap: {
|
||||
autoStart: false,
|
||||
confinement: "strict",
|
||||
allowNativeWayland: true
|
||||
},
|
||||
extraResources: ["app-update.yml", "./assets/**"],
|
||||
extraMetadata: {
|
||||
main: path.join(buildRoot, "build", "electron.js")
|
||||
},
|
||||
directories: {
|
||||
buildResources: "assets",
|
||||
output: outputDir
|
||||
},
|
||||
publish: [
|
||||
{
|
||||
provider: "github",
|
||||
repo: "notesnook",
|
||||
owner: "streetwriters"
|
||||
}
|
||||
]
|
||||
};
|
||||
757
apps/desktop/package-lock.json
generated
757
apps/desktop/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
"name": "@notesnook/desktop",
|
||||
"productName": "Notesnook",
|
||||
"description": "Your private note taking space",
|
||||
"version": "3.0.25",
|
||||
"version": "3.0.31",
|
||||
"appAppleId": "1544027013",
|
||||
"private": true,
|
||||
"main": "./dist/cjs/index.js",
|
||||
@@ -30,7 +30,7 @@
|
||||
"@trpc/client": "10.45.2",
|
||||
"@trpc/server": "10.45.2",
|
||||
"better-sqlite3-multiple-ciphers": "11.5.0",
|
||||
"electron-trpc": "0.6.1",
|
||||
"electron-trpc": "0.7.1",
|
||||
"electron-updater": "^6.3.4",
|
||||
"icojs": "^0.19.4",
|
||||
"sqlite-better-trigram": "0.0.2",
|
||||
@@ -46,14 +46,14 @@
|
||||
"electron": "^31.7.4",
|
||||
"electron-builder": "^25.1.8",
|
||||
"esbuild": "0.21.5",
|
||||
"vitest": "2.1.8",
|
||||
"node-abi": "^3.68.0",
|
||||
"node-gyp-build": "^4.8.2",
|
||||
"playwright": "^1.48.2",
|
||||
"prebuildify": "^6.0.1",
|
||||
"slugify": "1.6.6",
|
||||
"tree-kill": "^1.2.2",
|
||||
"undici": "^6.19.8"
|
||||
"undici": "^6.19.8",
|
||||
"vitest": "^2.1.8"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"dmg-license": "^1.0.11"
|
||||
@@ -63,203 +63,14 @@
|
||||
"staging": "node scripts/build.mjs --run",
|
||||
"release": "node scripts/build.mjs",
|
||||
"build": "node ../../scripts/build.mjs",
|
||||
"bundle": "esbuild electron=./src/main.ts ./src/preload.ts --external:electron --external:fsevents --external:better-sqlite3-multiple-ciphers --external:sodium-native --minify --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=false --define:RELEASE=true",
|
||||
"bundle": "esbuild electron=./src/main.ts ./src/preload.ts --external:electron --external:fsevents --external:better-sqlite3-multiple-ciphers --external:sodium-native --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=false --define:RELEASE=true",
|
||||
"bundle:mas": "esbuild electron=./src/main.ts ./src/preload.ts --minify --external:electron --external:fsevents --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=true --define:RELEASE=true",
|
||||
"postinstall": "patch-package",
|
||||
"test": "node --test --test-timeout 30000 --test-force-exit"
|
||||
"test": "vitest run"
|
||||
},
|
||||
"author": {
|
||||
"name": "Streetwriters (Private) Limited",
|
||||
"email": "support@streetwriters.co",
|
||||
"url": "https://streetwriters.co"
|
||||
},
|
||||
"build": {
|
||||
"appId": "org.streetwriters.notesnook",
|
||||
"productName": "Notesnook",
|
||||
"copyright": "Copyright © 2023 Streetwriters (Private) Limited",
|
||||
"artifactName": "notesnook_${os}_${arch}.${ext}",
|
||||
"generateUpdatesFilesForAllChannels": true,
|
||||
"asar": false,
|
||||
"files": [
|
||||
"!*.chunk.js.map",
|
||||
"!*.chunk.js.LICENSE.txt",
|
||||
"build/",
|
||||
"!build/screenshots${/*}",
|
||||
"!build/banner.jpg",
|
||||
"!build/*.ico",
|
||||
"!build/*.png",
|
||||
"!node_modules${/*}",
|
||||
"node_modules/better-sqlite3-multiple-ciphers/build/Release/better_sqlite3.node",
|
||||
"node_modules/better-sqlite3-multiple-ciphers/lib",
|
||||
"node_modules/better-sqlite3-multiple-ciphers/package.json",
|
||||
"node_modules/file-uri-to-path",
|
||||
"node_modules/bindings",
|
||||
"node_modules/node-gyp-build",
|
||||
"node_modules/sqlite-better-trigram",
|
||||
"node_modules/sodium-native/prebuilds/${platform}-${arch}",
|
||||
{
|
||||
"from": "node_modules/sqlite-better-trigram-linux-${arch}",
|
||||
"to": "node_modules/sqlite-better-trigram-linux-${arch}"
|
||||
},
|
||||
{
|
||||
"from": "node_modules/sqlite-better-trigram-darwin-${arch}",
|
||||
"to": "node_modules/sqlite-better-trigram-darwin-${arch}"
|
||||
},
|
||||
{
|
||||
"from": "node_modules/sqlite-better-trigram-windows-${arch}",
|
||||
"to": "node_modules/sqlite-better-trigram-windows-${arch}"
|
||||
},
|
||||
"node_modules/sodium-native/index.js",
|
||||
"node_modules/sodium-native/package.json"
|
||||
],
|
||||
"afterPack": "./scripts/removeLocales.js",
|
||||
"mac": {
|
||||
"bundleVersion": "240",
|
||||
"minimumSystemVersion": "10.12.0",
|
||||
"target": [
|
||||
{
|
||||
"target": "dmg",
|
||||
"arch": [
|
||||
"arm64",
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "zip",
|
||||
"arch": [
|
||||
"arm64",
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": "public.app-category.productivity",
|
||||
"darkModeSupport": true,
|
||||
"type": "distribution",
|
||||
"hardenedRuntime": true,
|
||||
"entitlements": "assets/entitlements.mac.plist",
|
||||
"entitlementsInherit": "assets/entitlements.mac.plist",
|
||||
"gatekeeperAssess": false,
|
||||
"icon": "assets/icons/app.icns",
|
||||
"notarize": true
|
||||
},
|
||||
"dmg": {
|
||||
"contents": [
|
||||
{
|
||||
"x": 130,
|
||||
"y": 220
|
||||
},
|
||||
{
|
||||
"x": 410,
|
||||
"y": 220,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
}
|
||||
],
|
||||
"icon": "assets/icons/app.icns",
|
||||
"title": "Install Notesnook"
|
||||
},
|
||||
"mas": {
|
||||
"entitlements": "assets/entitlements.mas.plist",
|
||||
"entitlementsInherit": "assets/entitlements.mas.inherit.plist",
|
||||
"entitlementsLoginHelper": "assets/entitlements.mas.loginhelper.plist",
|
||||
"hardenedRuntime": true
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "portable",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"signtoolOptions": {
|
||||
"signingHashAlgorithms": [
|
||||
"sha256"
|
||||
],
|
||||
"sign": "./scripts/sign.js"
|
||||
},
|
||||
"icon": "assets/icons/app.ico"
|
||||
},
|
||||
"portable": {
|
||||
"artifactName": "notesnook_${os}_${arch}_portable.${ext}"
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": true,
|
||||
"createDesktopShortcut": "always",
|
||||
"deleteAppDataOnUninstall": true
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
{
|
||||
"target": "AppImage",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "snap",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": "Office",
|
||||
"icon": "assets/icons/app.icns",
|
||||
"description": "Your private note taking space",
|
||||
"executableName": "notesnook",
|
||||
"desktop": {
|
||||
"actions": [
|
||||
{
|
||||
"id": "new-note",
|
||||
"name": "New note",
|
||||
"args": "new note"
|
||||
},
|
||||
{
|
||||
"id": "new-notebook",
|
||||
"name": "New notebook",
|
||||
"args": "new notebook"
|
||||
},
|
||||
{
|
||||
"id": "new-reminder",
|
||||
"name": "New reminder",
|
||||
"args": "new reminder"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"snap": {
|
||||
"autoStart": false,
|
||||
"confinement": "strict",
|
||||
"allowNativeWayland": true
|
||||
},
|
||||
"extraResources": [
|
||||
"app-update.yml",
|
||||
"./assets/**"
|
||||
],
|
||||
"extraMetadata": {
|
||||
"main": "./build/electron.js"
|
||||
},
|
||||
"directories": {
|
||||
"buildResources": "assets",
|
||||
"output": "./output/"
|
||||
},
|
||||
"publish": [
|
||||
{
|
||||
"provider": "github",
|
||||
"repo": "notesnook",
|
||||
"owner": "streetwriters"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,10 +29,12 @@ import { patchBetterSQLite3 } from "./patch-better-sqlite3.mjs";
|
||||
const args = yargs(process.argv);
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const root = args.root || path.join(__dirname, "..");
|
||||
const skipTscBuild = args.skipTscBuild || false;
|
||||
|
||||
const webAppPath = path.resolve(path.join(__dirname, "..", "..", "web"));
|
||||
|
||||
await fs.rm("./build/", { force: true, recursive: true });
|
||||
await fs.rm(path.join(root, "build"), { force: true, recursive: true });
|
||||
|
||||
if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
|
||||
await exec(
|
||||
@@ -44,21 +46,25 @@ if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
|
||||
// temporary until there's support for prebuilt binaries for linux ARM
|
||||
if (os.platform() === "linux") await patchBetterSQLite3();
|
||||
|
||||
await fs.cp(path.join(webAppPath, "build"), "build", {
|
||||
await fs.cp(path.join(webAppPath, "build"), path.join(root, "build"), {
|
||||
recursive: true,
|
||||
force: true
|
||||
});
|
||||
|
||||
if (args.variant === "mas") {
|
||||
await exec(`yarn run bundle:mas`);
|
||||
await exec(`yarn run bundle:mas --outdir=${path.join(root, "build")}`);
|
||||
} else {
|
||||
await exec(`yarn run bundle`);
|
||||
await exec(`yarn run bundle --outdir=${path.join(root, "build")}`);
|
||||
}
|
||||
|
||||
await exec(`yarn run build`);
|
||||
if (!skipTscBuild) {
|
||||
await exec(`yarn run build`);
|
||||
}
|
||||
|
||||
if (args.run) {
|
||||
await exec(`yarn electron-builder --dir --${process.arch}`);
|
||||
await exec(
|
||||
`yarn electron-builder --dir --${process.arch} --config=electron-builder.config.js`
|
||||
);
|
||||
if (process.platform === "win32") {
|
||||
await exec(`.\\output\\win-unpacked\\Notesnook.exe`);
|
||||
} else if (process.platform === "darwin") {
|
||||
|
||||
@@ -29,7 +29,7 @@ import { dirname } from "path";
|
||||
import { resolvePath } from "../utils/resolve-path";
|
||||
import { observable } from "@trpc/server/observable";
|
||||
import { AssetManager } from "../utils/asset-manager";
|
||||
import { isFlatpak } from "../utils";
|
||||
import { isFlatpak, isSnap } from "../utils";
|
||||
import { setupDesktopIntegration } from "../utils/desktop-integration";
|
||||
import { rm } from "fs/promises";
|
||||
import { disableCustomDns, enableCustomDns } from "../utils/custom-dns";
|
||||
@@ -49,6 +49,7 @@ const NotificationOptions = z.object({
|
||||
|
||||
export const osIntegrationRouter = t.router({
|
||||
isFlatpak: t.procedure.query(() => isFlatpak()),
|
||||
isSnap: t.procedure.query(() => isSnap()),
|
||||
|
||||
zoomFactor: t.procedure.query(() => config.zoomFactor),
|
||||
setZoomFactor: t.procedure.input(z.number()).mutation(({ input: factor }) => {
|
||||
|
||||
@@ -23,6 +23,7 @@ import { CancellationToken, autoUpdater } from "electron-updater";
|
||||
import type { AppUpdaterEvents } from "electron-updater/out/AppUpdater";
|
||||
import { z } from "zod";
|
||||
import { config } from "../utils/config";
|
||||
import { app } from "electron";
|
||||
|
||||
type UpdateInfo = { version: string };
|
||||
type Progress = { percent: number };
|
||||
@@ -33,6 +34,7 @@ let downloadTimeout: NodeJS.Timeout | undefined = undefined;
|
||||
|
||||
export const updaterRouter = t.router({
|
||||
autoUpdates: t.procedure.query(() => config.automaticUpdates),
|
||||
releaseTrack: t.procedure.query(() => config.releaseTrack),
|
||||
install: t.procedure.query(() => autoUpdater.quitAndInstall()),
|
||||
download: t.procedure.query(async () => {
|
||||
if (cancellationToken) return;
|
||||
@@ -67,7 +69,13 @@ export const updaterRouter = t.router({
|
||||
.mutation(({ input: { enabled } }) => {
|
||||
config.automaticUpdates = enabled;
|
||||
}),
|
||||
|
||||
changeReleaseTrack: t.procedure
|
||||
.input(z.object({ track: z.string() }))
|
||||
.mutation(({ input: { track } }) => {
|
||||
config.releaseTrack = track;
|
||||
app.relaunch();
|
||||
app.exit();
|
||||
}),
|
||||
onChecking: createSubscription("checking-for-update"),
|
||||
onDownloaded: createSubscription<"update-downloaded", UpdateInfo>(
|
||||
"update-downloaded"
|
||||
|
||||
@@ -85,6 +85,8 @@ async function createWindow() {
|
||||
const mainWindowState = new WindowState({});
|
||||
const mainWindow = new BrowserWindow({
|
||||
show: !cliOptions.hidden,
|
||||
paintWhenInitiallyHidden: cliOptions.hidden,
|
||||
skipTaskbar: cliOptions.hidden,
|
||||
x: mainWindowState.x,
|
||||
y: mainWindowState.y,
|
||||
width: mainWindowState.width,
|
||||
@@ -101,7 +103,10 @@ async function createWindow() {
|
||||
...(config.desktopSettings.nativeTitlebar
|
||||
? {}
|
||||
: {
|
||||
titleBarStyle: process.platform === "win32" || process.platform === "darwin" ? "hidden" : "default",
|
||||
titleBarStyle:
|
||||
process.platform === "win32" || process.platform === "darwin"
|
||||
? "hidden"
|
||||
: "default",
|
||||
frame: process.platform === "win32" || process.platform === "darwin",
|
||||
titleBarOverlay: {
|
||||
height: 37,
|
||||
|
||||
@@ -20,19 +20,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { autoUpdater } from "electron-updater";
|
||||
import { config } from "./config";
|
||||
|
||||
const CHANNEL = autoUpdater.currentVersion.raw.endsWith("-beta")
|
||||
? "beta"
|
||||
: "latest";
|
||||
async function configureAutoUpdater() {
|
||||
const releaseTrack =
|
||||
config.releaseTrack === "stable" ? "latest" : config.releaseTrack;
|
||||
autoUpdater.setFeedURL({
|
||||
provider: "generic",
|
||||
url: `https://notesnook.com/api/v1/releases/${process.platform}/${CHANNEL}`,
|
||||
url: `https://notesnook.com/api/v1/releases/${process.platform}/${releaseTrack}`,
|
||||
useMultipleRangeRequest: false,
|
||||
channel: CHANNEL
|
||||
channel: releaseTrack
|
||||
});
|
||||
|
||||
autoUpdater.autoDownload = config.automaticUpdates;
|
||||
autoUpdater.allowDowngrade = false;
|
||||
autoUpdater.allowDowngrade =
|
||||
// only allow downgrade if the current version is a prerelease
|
||||
// and the user has changed the release track to stable
|
||||
config.releaseTrack === "stable" &&
|
||||
autoUpdater.currentVersion.prerelease.length > 0;
|
||||
autoUpdater.allowPrerelease = false;
|
||||
autoUpdater.autoInstallOnAppQuit = true;
|
||||
autoUpdater.disableWebInstaller = true;
|
||||
|
||||
@@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { nativeTheme } from "electron";
|
||||
import { JSONStorage } from "./json-storage";
|
||||
import { z } from "zod";
|
||||
import { autoUpdater } from "electron-updater";
|
||||
|
||||
export const DesktopIntegration = z.object({
|
||||
autoStart: z.boolean().optional(),
|
||||
@@ -46,6 +47,9 @@ export const config = {
|
||||
automaticUpdates: true,
|
||||
proxyRules: "",
|
||||
customDns: true,
|
||||
releaseTrack: autoUpdater.currentVersion.raw.includes("-beta")
|
||||
? "beta"
|
||||
: "stable",
|
||||
|
||||
backgroundColor: nativeTheme.themeSource === "dark" ? "#0f0f0f" : "#ffffff",
|
||||
windowControlsIconColor:
|
||||
|
||||
@@ -29,3 +29,7 @@ export function isDevelopment() {
|
||||
export function isFlatpak() {
|
||||
return existsSync("/.flatpak-info");
|
||||
}
|
||||
|
||||
export function isSnap() {
|
||||
return process.env.SNAP !== undefined;
|
||||
}
|
||||
|
||||
40
apps/desktop/vitest.config.ts
Normal file
40
apps/desktop/vitest.config.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
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 { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
testTimeout: process.env.CI ? 120 * 1000 : 120 * 1000,
|
||||
sequence: {
|
||||
concurrent: true,
|
||||
shuffle: true
|
||||
},
|
||||
dir: "./__tests__/",
|
||||
exclude: [
|
||||
"**/node_modules/**",
|
||||
"**/dist/**",
|
||||
"**/cypress/**",
|
||||
"**/.{idea,git,cache,output,temp}/**",
|
||||
"**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier}.config.*",
|
||||
"**/test-results/**",
|
||||
"**/test-artifacts/**"
|
||||
]
|
||||
}
|
||||
});
|
||||
@@ -231,6 +231,26 @@ export async function deleteDCacheFiles() {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getCachePathForFile(filename: string) {
|
||||
const path = `${cacheDir}/${filename}`;
|
||||
|
||||
const iosAppGroup =
|
||||
Platform.OS === "ios"
|
||||
? await (RNFetchBlob.fs as any).pathForAppGroup(IOS_APPGROUPID)
|
||||
: null;
|
||||
const appGroupPath = `${iosAppGroup}/${filename}`;
|
||||
|
||||
const exists = await RNFetchBlob.fs.exists(path);
|
||||
|
||||
// Check if file is present in app group path.
|
||||
let existsInAppGroup = false;
|
||||
if (!exists && Platform.OS === "ios") {
|
||||
existsInAppGroup = await RNFetchBlob.fs.exists(appGroupPath);
|
||||
}
|
||||
|
||||
return existsInAppGroup ? appGroupPath : path;
|
||||
}
|
||||
|
||||
export async function exists(filename: string) {
|
||||
const path = `${cacheDir}/${filename}`;
|
||||
|
||||
|
||||
@@ -80,8 +80,9 @@ export const Dialog = ({ context = "global" }) => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
hide();
|
||||
setChecked(false);
|
||||
values.current.inputValue = undefined;
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
const show = useCallback(
|
||||
@@ -104,14 +105,14 @@ export const Dialog = ({ context = "global" }) => {
|
||||
eUnSubscribeEvent(eOpenSimpleDialog, show);
|
||||
eUnSubscribeEvent(eCloseSimpleDialog, hide);
|
||||
};
|
||||
}, [show]);
|
||||
}, [hide, show]);
|
||||
|
||||
const hide = () => {
|
||||
const hide = React.useCallback(() => {
|
||||
setChecked(false);
|
||||
values.current.inputValue = undefined;
|
||||
setVisible(false);
|
||||
dialogInfo.onClose();
|
||||
};
|
||||
dialogInfo.onClose?.();
|
||||
}, [dialogInfo]);
|
||||
|
||||
const onNegativePress = async () => {
|
||||
if (dialogInfo.onClose) {
|
||||
|
||||
@@ -76,7 +76,8 @@ const NoteItem = ({
|
||||
}: NoteItemProps) => {
|
||||
const isEditingNote = useTabStore(
|
||||
(state) =>
|
||||
state.tabs.find((t) => t.id === state.currentTab)?.noteId === item.id
|
||||
state.tabs.find((t) => t.id === state.currentTab)?.session?.noteId ===
|
||||
item.id
|
||||
);
|
||||
const { colors } = useThemeColors();
|
||||
const compactMode = useIsCompactModeEnabled(
|
||||
|
||||
@@ -28,7 +28,8 @@ export const Filler = ({ item, color }: { item: Item; color?: string }) => {
|
||||
const { colors } = useThemeColors();
|
||||
const isEditingNote = useTabStore(
|
||||
(state) =>
|
||||
state.tabs.find((t) => t.id === state.currentTab)?.noteId === item.id
|
||||
state.tabs.find((t) => t.id === state.currentTab)?.session?.noteId ===
|
||||
item.id
|
||||
);
|
||||
|
||||
const [selected] = useIsSelected(item);
|
||||
|
||||
@@ -509,7 +509,8 @@ const onChangeTab = async (event) => {
|
||||
});
|
||||
} else {
|
||||
if (
|
||||
useTabStore.getState().getTab(useTabStore.getState().currentTab).locked
|
||||
useTabStore.getState().getTab(useTabStore.getState().currentTab).session
|
||||
?.locked
|
||||
) {
|
||||
eSendEvent(eUnlockNote);
|
||||
}
|
||||
@@ -523,7 +524,7 @@ const onChangeTab = async (event) => {
|
||||
|
||||
// Lock all tabs with locked notes...
|
||||
for (const tab of useTabStore.getState().tabs) {
|
||||
const noteId = useTabStore.getState().getTab(tab.id)?.noteId;
|
||||
const noteId = useTabStore.getState().getTab(tab.id)?.session?.noteId;
|
||||
if (!noteId) continue;
|
||||
const note = await db.notes.note(noteId);
|
||||
const locked = note && (await db.vaults.itemExists(note));
|
||||
|
||||
@@ -176,13 +176,13 @@ class Commands {
|
||||
await this.sendCommand("clearTags", tabId);
|
||||
};
|
||||
|
||||
insertAttachment = async (attachment: Attachment, tabId: number) => {
|
||||
insertAttachment = async (attachment: Attachment, tabId: string) => {
|
||||
await this.sendCommand("insertAttachment", attachment, tabId);
|
||||
};
|
||||
|
||||
setAttachmentProgress = async (
|
||||
attachmentProgress: Partial<Attachment>,
|
||||
tabId: number
|
||||
tabId: string
|
||||
) => {
|
||||
await this.sendCommand("setAttachmentProgress", attachmentProgress, tabId);
|
||||
};
|
||||
@@ -191,7 +191,7 @@ class Commands {
|
||||
image: Omit<ImageAttributes, "bloburl"> & {
|
||||
dataurl: string;
|
||||
},
|
||||
tabId: number
|
||||
tabId: string
|
||||
) => {
|
||||
await this.sendCommand("insertImage", image, tabId);
|
||||
};
|
||||
|
||||
@@ -61,7 +61,7 @@ const santizeUri = (uri: string) => {
|
||||
|
||||
type PickerOptions = {
|
||||
noteId?: string;
|
||||
tabId?: number;
|
||||
tabId?: string;
|
||||
type: "image" | "camera" | "file";
|
||||
reupload: boolean;
|
||||
hash?: string;
|
||||
|
||||
@@ -127,15 +127,8 @@ class LocalTabState {
|
||||
tabSession?.readonly !== readonly ||
|
||||
!noteId
|
||||
) {
|
||||
console.log("tab is refreshing...");
|
||||
return true;
|
||||
}
|
||||
console.log(
|
||||
"Tab refreshing...",
|
||||
state.editedAt < this.noteEditedTime[noteId]
|
||||
);
|
||||
|
||||
console.log(state.editedAt, this.noteEditedTime[noteId]);
|
||||
|
||||
return !state.editedAt || state.editedAt < this.noteEditedTime[noteId];
|
||||
}
|
||||
@@ -522,7 +515,12 @@ export const useEditor = (
|
||||
}
|
||||
if (event.newNote && !currentLoadingNoteId.current) {
|
||||
let tabId;
|
||||
if (useTabStore.getState().tabs.length === 0) {
|
||||
|
||||
const currentTab = useTabStore
|
||||
.getState()
|
||||
.getTab(useTabStore.getState().currentTab as string);
|
||||
|
||||
if (useTabStore.getState().tabs.length === 0 || currentTab?.pinned) {
|
||||
tabId = useTabStore.getState().newTab();
|
||||
} else {
|
||||
tabId = useTabStore.getState().currentTab;
|
||||
@@ -547,14 +545,14 @@ export const useEditor = (
|
||||
|
||||
const item = event.item;
|
||||
|
||||
const currentTab = useTabStore
|
||||
.getState()
|
||||
.getTab(useTabStore.getState().currentTab as string);
|
||||
|
||||
// If note is already open in a tab, focus that tab.
|
||||
if (useTabStore.getState().hasTabForNote(item.id) && !event.newTab) {
|
||||
const tabId = useTabStore.getState().getTabForNote(item.id);
|
||||
|
||||
const currentTab = useTabStore
|
||||
.getState()
|
||||
.getTab(useTabStore.getState().currentTab as string);
|
||||
|
||||
if (
|
||||
currentTab?.session?.noteId !== item.id &&
|
||||
tabId !== useTabStore.getState().currentTab
|
||||
@@ -624,10 +622,16 @@ export const useEditor = (
|
||||
|
||||
const tab = useTabStore.getState().getTab(tabId!);
|
||||
|
||||
if (useTabStore.getState().tabs.length === 0 || event.newTab) {
|
||||
if (
|
||||
useTabStore.getState().tabs.length === 0 ||
|
||||
event.newTab ||
|
||||
(currentTab?.pinned &&
|
||||
event.item.id !== currentTab?.session?.noteId)
|
||||
) {
|
||||
useTabStore.getState().newTab({
|
||||
session: session
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
// A new session is created if the note is changed.
|
||||
// If the note is already opened, the session is updated.
|
||||
@@ -1040,6 +1044,17 @@ export const useEditor = (
|
||||
}, [isDefaultEditor, restoreEditorState]);
|
||||
|
||||
const onLoad = useCallback(async () => {
|
||||
const isAppLoading = useSettingStore.getState().isAppLoading;
|
||||
if (isAppLoading) {
|
||||
const sub = useSettingStore.subscribe((state) => {
|
||||
if (!state.isAppLoading) {
|
||||
sub();
|
||||
onLoad();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
postMessage(NativeEvents.theme, theme);
|
||||
});
|
||||
@@ -1082,9 +1097,8 @@ export const useEditor = (
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
state.current.initialLoadCalled = true;
|
||||
}
|
||||
state.current.initialLoadCalled = true;
|
||||
overlay(false);
|
||||
}
|
||||
}, [
|
||||
|
||||
@@ -97,15 +97,16 @@ class TabSessionStorage {
|
||||
static storage: typeof TabSessionStorageKV = TabSessionStorageKV;
|
||||
|
||||
static get(id: string): TabSessionItem | null {
|
||||
if (!id) return null;
|
||||
return TabSessionStorage.storage.getMap(id);
|
||||
}
|
||||
|
||||
static set(id: string, session: TabSessionItem): void {
|
||||
if (!id) return;
|
||||
TabSessionStorage.storage.setMap(id, session);
|
||||
}
|
||||
|
||||
static update(id: string, session: Partial<TabSessionItem>) {
|
||||
if (!id) throw new Error("Session ID is required");
|
||||
const currentSession = TabSessionStorage.get(id);
|
||||
const newSession = {
|
||||
...currentSession,
|
||||
@@ -118,6 +119,7 @@ class TabSessionStorage {
|
||||
}
|
||||
|
||||
static remove(id: string) {
|
||||
if (!id) return;
|
||||
TabSessionStorageKV.removeItem(id);
|
||||
}
|
||||
}
|
||||
@@ -267,6 +269,7 @@ export const useTabStore = create<TabStore>(
|
||||
TabSessionStorage.set(sessionId, session as TabSessionItem);
|
||||
} else {
|
||||
session = {
|
||||
id: sessionId,
|
||||
...TabSessionStorage.get(oldSessionId),
|
||||
...options
|
||||
};
|
||||
@@ -317,13 +320,15 @@ export const useTabStore = create<TabStore>(
|
||||
const sessionLoaded = await get().loadSession(id);
|
||||
|
||||
if (!sessionLoaded) {
|
||||
tabSessionHistory.remove(currentTab, id);
|
||||
TabSessionStorage.remove(id);
|
||||
if (!tabSessionHistory.canGoBack(currentTab)) {
|
||||
const canGoBack = tabSessionHistory.canGoBack(currentTab);
|
||||
if (!canGoBack) {
|
||||
tabSessionHistory.forward(currentTab);
|
||||
syncTabs();
|
||||
} else {
|
||||
return get().goBack();
|
||||
get().goBack();
|
||||
tabSessionHistory.remove(currentTab, id);
|
||||
TabSessionStorage.remove(id);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
syncTabs();
|
||||
@@ -336,13 +341,15 @@ export const useTabStore = create<TabStore>(
|
||||
if (!tabSessionHistory.canGoForward(currentTab)) return;
|
||||
const id = tabSessionHistory.forward(currentTab) as string;
|
||||
if (!(await get().loadSession(id))) {
|
||||
tabSessionHistory.remove(currentTab, id);
|
||||
TabSessionStorage.remove(id);
|
||||
if (!tabSessionHistory.canGoForward(currentTab)) {
|
||||
const canGoForward = tabSessionHistory.canGoForward(currentTab);
|
||||
if (!canGoForward) {
|
||||
tabSessionHistory.back(currentTab);
|
||||
syncTabs();
|
||||
} else {
|
||||
return get().goForward();
|
||||
get().goForward();
|
||||
tabSessionHistory.remove(currentTab, id);
|
||||
TabSessionStorage.remove(id);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
syncTabs();
|
||||
@@ -399,7 +406,7 @@ export const useTabStore = create<TabStore>(
|
||||
set({
|
||||
tabs: [{ id: id }]
|
||||
});
|
||||
get().newTabSession(id, {});
|
||||
get().newTabSession(id);
|
||||
get().focusTab(id);
|
||||
} else {
|
||||
set({
|
||||
|
||||
@@ -41,7 +41,10 @@ import { RouteParams, SettingSection } from "./types";
|
||||
|
||||
const _SectionItem = ({ item }: { item: SettingSection }) => {
|
||||
const { colors } = useThemeColors();
|
||||
const settings = useSettingStore((state) => state.settings);
|
||||
const [settings, itemProperty] = useSettingStore((state) => [
|
||||
state.settings,
|
||||
item.property ? state.settings[item.property] : null
|
||||
]);
|
||||
const navigation = useNavigation<NavigationProp<RouteParams>>();
|
||||
const current = item.useHook && item.useHook(item);
|
||||
const [isHidden, setIsHidden] = useState(
|
||||
@@ -106,7 +109,7 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
|
||||
useEffect(() => {
|
||||
setIsHidden(item.hidden && item.hidden(item.property || current));
|
||||
setIsDisabled(item.disabled && item.disabled(item.property || current));
|
||||
}, [current, item]);
|
||||
}, [current, item, itemProperty]);
|
||||
|
||||
return isHidden ? null : (
|
||||
<Pressable
|
||||
|
||||
@@ -39,6 +39,7 @@ import { eCloseSheet } from "../utils/events";
|
||||
import { sleep } from "../utils/time";
|
||||
import { ToastManager, eSendEvent, presentSheet } from "./event-manager";
|
||||
import SettingsService from "./settings";
|
||||
import { getCachePathForFile } from "../common/filesystem/io";
|
||||
|
||||
const MS_DAY = 86400000;
|
||||
const MS_WEEK = MS_DAY * 7;
|
||||
@@ -169,7 +170,11 @@ async function run(
|
||||
progress = false,
|
||||
context?: string,
|
||||
backupType: "full" | "partial" = "partial"
|
||||
) {
|
||||
): Promise<{
|
||||
path?: string;
|
||||
error?: Error;
|
||||
report?: boolean;
|
||||
}> {
|
||||
if (backupRunning) {
|
||||
if (progress) {
|
||||
startProgress({
|
||||
@@ -179,7 +184,7 @@ async function run(
|
||||
canHideProgress: true
|
||||
});
|
||||
}
|
||||
return;
|
||||
return {};
|
||||
}
|
||||
backupRunning = true;
|
||||
const androidBackupDirectory = (await checkBackupDirExists(
|
||||
@@ -255,10 +260,14 @@ async function run(
|
||||
progress: `Saving attachments in backup... ${file.hash}`
|
||||
});
|
||||
if (await FileStorage.exists(file.hash)) {
|
||||
await RNFetchBlob.fs.cp(
|
||||
`${cacheDir}/${file.hash}`,
|
||||
`${attachmentsDir}/${file.hash}`
|
||||
);
|
||||
await RNFetchBlob.fs
|
||||
.cp(
|
||||
await getCachePathForFile(file.hash),
|
||||
`${attachmentsDir}/${file.hash}`
|
||||
)
|
||||
.catch((e) =>
|
||||
DatabaseLogger.error(e, "Error saving attachment to backup file")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -343,7 +352,7 @@ async function run(
|
||||
endProgress();
|
||||
}
|
||||
return {
|
||||
error: e,
|
||||
error: e as Error,
|
||||
report: true
|
||||
};
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
multiDexEnabled true
|
||||
versionCode 3040
|
||||
versionCode 3045
|
||||
versionName getNpmVersion()
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
- New and improved editor tabs with history
|
||||
- Fixed a crash on app launch faced by some users
|
||||
- Fixed homescreen note widget does not open correct note
|
||||
- Many small bug fixes and improvements
|
||||
- Small bug fixes and improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
@@ -1063,7 +1063,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2127;
|
||||
CURRENT_PROJECT_VERSION = 2130;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
ENABLE_BITCODE = NO;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1137,7 +1137,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.30;
|
||||
MARKETING_VERSION = 3.0.33;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -1168,7 +1168,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 2127;
|
||||
CURRENT_PROJECT_VERSION = 2130;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1242,7 +1242,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.30;
|
||||
MARKETING_VERSION = 3.0.33;
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
@@ -1401,7 +1401,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2127;
|
||||
CURRENT_PROJECT_VERSION = 2130;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1413,7 +1413,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.30;
|
||||
MARKETING_VERSION = 3.0.33;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
@@ -1444,7 +1444,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2127;
|
||||
CURRENT_PROJECT_VERSION = 2130;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1457,7 +1457,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.30;
|
||||
MARKETING_VERSION = 3.0.33;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1487,7 +1487,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2127;
|
||||
CURRENT_PROJECT_VERSION = 2130;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1561,7 +1561,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.30;
|
||||
MARKETING_VERSION = 3.0.33;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
@@ -1592,7 +1592,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2127;
|
||||
CURRENT_PROJECT_VERSION = 2130;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1667,7 +1667,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.0.30;
|
||||
MARKETING_VERSION = 3.0.33;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
||||
@@ -82,6 +82,7 @@ target 'Make Note' do
|
||||
pod "RNDeviceInfo", :path => '../../node_modules/react-native-device-info'
|
||||
pod "RNPrivacySnapshot", :path => '../../node_modules/react-native-privacy-snapshot'
|
||||
pod "react-native-theme-switch-animation", :path => '../../node_modules/react-native-theme-switch-animation'
|
||||
pod "react-native-image-resizer", :path => '../../node_modules/@bam.tech/react-native-image-resizer'
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -1019,7 +1019,7 @@ PODS:
|
||||
- react-native-screenguard (1.0.0):
|
||||
- React-Core
|
||||
- SDWebImage (~> 5.11.1)
|
||||
- react-native-share-extension (2.8.0):
|
||||
- react-native-share-extension (2.9.0):
|
||||
- React
|
||||
- react-native-sodium (1.6.1):
|
||||
- React
|
||||
@@ -1831,7 +1831,7 @@ SPEC CHECKSUMS:
|
||||
MMKV: 817ba1eea17421547e01e087285606eb270a8dcb
|
||||
MMKVCore: af055b00e27d88cd92fad301c5fecd1ff9b26dd9
|
||||
pop: d582054913807fd11fd50bfe6a539d91c7e1a55a
|
||||
RCT-Folly: 02617c592a293bd6d418e0a88ff4ee1f88329b47
|
||||
RCT-Folly: 5dc73daec3476616d19e8a53f0156176f7b55461
|
||||
RCTDeprecation: 3afceddffa65aee666dafd6f0116f1d975db1584
|
||||
RCTRequired: ec1239bc9d8bf63e10fb92bd8b26171a9258e0c1
|
||||
RCTTypeSafety: f5ecbc86c5c5fa163c05acb7a1c5012e15b5f994
|
||||
@@ -1877,7 +1877,7 @@ SPEC CHECKSUMS:
|
||||
react-native-quick-sqlite: 80b5b3405bfc92e97b6041d2c2a446800bea6ad7
|
||||
react-native-safe-area-context: b72c4611af2e86d80a59ac76279043d8f75f454c
|
||||
react-native-screenguard: 82437eeb0086a90b5e5d7e54130bb04fb406373e
|
||||
react-native-share-extension: 7528322fe621b683e6b48ab3418207ed45a70983
|
||||
react-native-share-extension: bcb7e466390a9e50c742f4b1019d6f181aedd7ad
|
||||
react-native-sodium: ed020d5f50ab1886f7bf23e32732d0d10e9373ac
|
||||
react-native-theme-switch-animation: 64f2688c2e3489ababc7b35f436fd3b5ed7c0a98
|
||||
react-native-webview: a71525b1ab760230fbf37303d8371fbe72051c7d
|
||||
@@ -1936,6 +1936,6 @@ SPEC CHECKSUMS:
|
||||
toolbar-android: c426ed5bd3dcccfed20fd79533efc0d1ae0ef018
|
||||
Yoga: 950bbfd7e6f04790fdb51149ed51df41f329fcc8
|
||||
|
||||
PODFILE CHECKSUM: aa07aca16c1d92394d8ce12f2c5fcaa8a3ae4997
|
||||
PODFILE CHECKSUM: 95f23a5abb88f81f8ca90e24a0a8ceae0a2d1028
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
"react-native-begin-background-task": "github:blockfirm/react-native-begin-background-task",
|
||||
"react-native-privacy-snapshot": "github:standardnotes/react-native-privacy-snapshot",
|
||||
"@ammarahmed/react-native-fingerprint-scanner": "^5.0.0",
|
||||
"@ammarahmed/react-native-share-extension": "^2.8.0"
|
||||
"@ammarahmed/react-native-share-extension": "^2.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"detox": "^20.27.6",
|
||||
|
||||
163
apps/mobile/package-lock.json
generated
163
apps/mobile/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.0.28",
|
||||
"version": "3.0.31",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.0.28",
|
||||
"version": "3.0.31",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
@@ -14,6 +14,7 @@
|
||||
"app/"
|
||||
],
|
||||
"dependencies": {
|
||||
"@ammarahmed/react-native-share-extension": "^2.9.0",
|
||||
"@notesnook/common": "file:../../packages/common",
|
||||
"@notesnook/core": "file:../../packages/core",
|
||||
"@notesnook/crypto": "file:../../packages/crypto",
|
||||
@@ -7742,7 +7743,7 @@
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/@types/prop-types": {
|
||||
"version": "15.7.11",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/@types/q": {
|
||||
@@ -7762,7 +7763,7 @@
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/@types/react": {
|
||||
"version": "18.2.39",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
@@ -7793,7 +7794,7 @@
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/@types/scheduler": {
|
||||
"version": "0.16.8",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/@types/semver": {
|
||||
@@ -12618,7 +12619,7 @@
|
||||
},
|
||||
"../../packages/editor-mobile/node_modules/immer": {
|
||||
"version": "9.0.21",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@@ -23090,7 +23091,6 @@
|
||||
},
|
||||
"../../packages/editor/node_modules/js-tokens": {
|
||||
"version": "4.0.0",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"../../packages/editor/node_modules/jsesc": {
|
||||
@@ -23141,7 +23141,6 @@
|
||||
},
|
||||
"../../packages/editor/node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
@@ -23653,7 +23652,6 @@
|
||||
},
|
||||
"../../packages/editor/node_modules/react": {
|
||||
"version": "18.3.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
@@ -23672,7 +23670,6 @@
|
||||
},
|
||||
"../../packages/editor/node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
@@ -23811,7 +23808,6 @@
|
||||
},
|
||||
"../../packages/editor/node_modules/scheduler": {
|
||||
"version": "0.23.2",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
@@ -28319,7 +28315,6 @@
|
||||
"@ammarahmed/react-native-background-fetch": "^4.2.2",
|
||||
"@ammarahmed/react-native-eventsource": "1.1.0",
|
||||
"@ammarahmed/react-native-fingerprint-scanner": "^5.0.0",
|
||||
"@ammarahmed/react-native-share-extension": "^2.8.0",
|
||||
"@ammarahmed/react-native-sodium": "^1.6.1",
|
||||
"@bam.tech/react-native-image-resizer": "3.0.5",
|
||||
"@callstack/repack": "^4.1.1",
|
||||
@@ -28457,9 +28452,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ammarahmed/react-native-share-extension": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-share-extension/-/react-native-share-extension-2.8.0.tgz",
|
||||
"integrity": "sha512-eDFVKiJJxjnIORveMUKVJGIhKfRNjqJWIoKS+JM88q8I8e3jHys9moyJAr9QM5oW/tZx4yyItl08yRXGnr7Grg==",
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-share-extension/-/react-native-share-extension-2.9.0.tgz",
|
||||
"integrity": "sha512-iEkpJQFZL5/WDPh7iRCLZREANgaaAHthDsPaAYT6sI4wieQcHZMBAnfRMwBEnC0BZ0WVxbQZdzJYez+dmwpr2g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-native": "^0.63.1"
|
||||
}
|
||||
@@ -28594,7 +28590,6 @@
|
||||
},
|
||||
"node_modules/@babel/helper-builder-binary-assignment-operator-visitor": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.22.5"
|
||||
@@ -28714,7 +28709,6 @@
|
||||
},
|
||||
"node_modules/@babel/helper-hoist-variables": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.22.5"
|
||||
@@ -28974,7 +28968,6 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz",
|
||||
"integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
},
|
||||
@@ -28989,7 +28982,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz",
|
||||
"integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
"@babel/helper-skip-transparent-expression-wrappers": "^7.22.5",
|
||||
@@ -29138,7 +29130,6 @@
|
||||
"version": "7.21.0-placeholder-for-preset-env.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz",
|
||||
"integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
},
|
||||
@@ -29151,7 +29142,6 @@
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz",
|
||||
"integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==",
|
||||
"deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-unicode-property-regex instead.",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-create-regexp-features-plugin": "^7.18.6",
|
||||
"@babel/helper-plugin-utils": "^7.18.6"
|
||||
@@ -29186,7 +29176,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-syntax-class-properties": {
|
||||
"version": "7.12.13",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.12.13"
|
||||
@@ -29199,7 +29188,6 @@
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
|
||||
"integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.14.5"
|
||||
},
|
||||
@@ -29237,7 +29225,6 @@
|
||||
"version": "7.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz",
|
||||
"integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.8.3"
|
||||
},
|
||||
@@ -29262,7 +29249,6 @@
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.25.6.tgz",
|
||||
"integrity": "sha512-aABl0jHw9bZ2karQ/uUD6XP4u0SG22SJrOHFoL6XB1R7dTovOP4TzTlsxOYC5yQ1pdscVK2JTUnF6QL3ARoAiQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
},
|
||||
@@ -29277,7 +29263,6 @@
|
||||
"version": "7.25.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.6.tgz",
|
||||
"integrity": "sha512-sXaDXaJN9SNLymBdlWFA+bjzBhFD617ZaFiY13dGt7TVslVvVgA6fkZOP7Ki3IGElC45lwHdOTrCtKZGVAWeLQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
},
|
||||
@@ -29292,7 +29277,6 @@
|
||||
"version": "7.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
|
||||
"integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.10.4"
|
||||
},
|
||||
@@ -29304,7 +29288,6 @@
|
||||
"version": "7.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
|
||||
"integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.8.0"
|
||||
},
|
||||
@@ -29404,7 +29387,6 @@
|
||||
"version": "7.14.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
|
||||
"integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.14.5"
|
||||
},
|
||||
@@ -29432,7 +29414,6 @@
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz",
|
||||
"integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-create-regexp-features-plugin": "^7.18.6",
|
||||
"@babel/helper-plugin-utils": "^7.18.6"
|
||||
@@ -29461,7 +29442,6 @@
|
||||
"version": "7.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.4.tgz",
|
||||
"integrity": "sha512-jz8cV2XDDTqjKPwVPJBIjORVEmSGYhdRa8e5k5+vN+uwcjSrSxUaebBRa4ko1jqNF2uxyg8G6XYk30Jv285xzg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-environment-visitor": "^7.22.5",
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
@@ -29492,7 +29472,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-block-scoped-functions": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -29521,7 +29500,6 @@
|
||||
"version": "7.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.4.tgz",
|
||||
"integrity": "sha512-nZeZHyCWPfjkdU5pA/uHiTaDAFUEqkpzf1YoQT2NeSynCGYq9rxfyI3XpQbfx/a0hSnFH6TGlEXvae5Vi7GD8g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-create-class-features-plugin": "^7.22.5",
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -29537,7 +29515,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz",
|
||||
"integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-create-class-features-plugin": "^7.22.5",
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
@@ -29602,7 +29579,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz",
|
||||
"integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-create-regexp-features-plugin": "^7.22.5",
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -29618,7 +29594,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz",
|
||||
"integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
},
|
||||
@@ -29633,7 +29608,6 @@
|
||||
"version": "7.25.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz",
|
||||
"integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.25.9"
|
||||
},
|
||||
@@ -29646,7 +29620,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-exponentiation-operator": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.5",
|
||||
@@ -29663,7 +29636,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz",
|
||||
"integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
"@babel/plugin-syntax-export-namespace-from": "^7.8.3"
|
||||
@@ -29691,7 +29663,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-for-of": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -29722,7 +29693,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz",
|
||||
"integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
"@babel/plugin-syntax-json-strings": "^7.8.3"
|
||||
@@ -29751,7 +29721,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz",
|
||||
"integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
"@babel/plugin-syntax-logical-assignment-operators": "^7.10.4"
|
||||
@@ -29765,7 +29734,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-member-expression-literals": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -29781,7 +29749,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz",
|
||||
"integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-module-transforms": "^7.22.5",
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -29812,7 +29779,6 @@
|
||||
"version": "7.25.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz",
|
||||
"integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-hoist-variables": "^7.22.5",
|
||||
"@babel/helper-module-transforms": "^7.22.5",
|
||||
@@ -29830,7 +29796,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz",
|
||||
"integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-module-transforms": "^7.22.5",
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -29860,7 +29825,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz",
|
||||
"integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
},
|
||||
@@ -29890,7 +29854,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz",
|
||||
"integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
"@babel/plugin-syntax-numeric-separator": "^7.10.4"
|
||||
@@ -29906,7 +29869,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz",
|
||||
"integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.22.5",
|
||||
"@babel/helper-compilation-targets": "^7.22.5",
|
||||
@@ -29923,7 +29885,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-object-super": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
@@ -29940,7 +29901,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz",
|
||||
"integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
"@babel/plugin-syntax-optional-catch-binding": "^7.8.3"
|
||||
@@ -30014,7 +29974,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-property-literals": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -30084,7 +30043,6 @@
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-regenerator": {
|
||||
"version": "7.22.5",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5",
|
||||
@@ -30101,7 +30059,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz",
|
||||
"integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
},
|
||||
@@ -30194,7 +30151,6 @@
|
||||
"version": "7.24.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz",
|
||||
"integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
},
|
||||
@@ -30225,7 +30181,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz",
|
||||
"integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
},
|
||||
@@ -30240,7 +30195,6 @@
|
||||
"version": "7.24.7",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz",
|
||||
"integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-create-regexp-features-plugin": "^7.22.5",
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -30270,7 +30224,6 @@
|
||||
"version": "7.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.4.tgz",
|
||||
"integrity": "sha512-qesBxiWkgN1Q+31xUE9RcMk79eOXXDCv6tfyGMRSs4RGlioSg2WVyQAm07k726cSE56pa+Kb0y9epX2qaXzTvA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-create-regexp-features-plugin": "^7.22.5",
|
||||
"@babel/helper-plugin-utils": "^7.22.5"
|
||||
@@ -30286,7 +30239,6 @@
|
||||
"version": "7.25.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.4.tgz",
|
||||
"integrity": "sha512-W9Gyo+KmcxjGahtt3t9fb14vFRWvPpu5pT6GBlovAK6BTBcxgjfVMSQCfJl4oi35ODrxP6xx2Wr8LNST57Mraw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/compat-data": "^7.22.5",
|
||||
"@babel/helper-compilation-targets": "^7.22.5",
|
||||
@@ -30380,7 +30332,6 @@
|
||||
"version": "0.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6.tgz",
|
||||
"integrity": "sha512-ID2yj6K/4lKfhuU3+EX4UvNbIt7eACFbHmNUjzA+ep+B5971CknnA/9DEWKbRokfbbtblxxxXFJJrH47UEAMVg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.0.0",
|
||||
"@babel/plugin-proposal-unicode-property-regex": "^7.4.4",
|
||||
@@ -30396,7 +30347,6 @@
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
@@ -33700,8 +33650,7 @@
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
|
||||
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
|
||||
},
|
||||
"node_modules/@types/graceful-fs": {
|
||||
"version": "4.1.6",
|
||||
@@ -33807,14 +33756,14 @@
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.5",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz",
|
||||
"integrity": "sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
@@ -34170,7 +34119,6 @@
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz",
|
||||
"integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/helper-numbers": "1.11.6",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.6"
|
||||
@@ -34179,26 +34127,22 @@
|
||||
"node_modules/@webassemblyjs/floating-point-hex-parser": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz",
|
||||
"integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw=="
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-api-error": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz",
|
||||
"integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==",
|
||||
"dev": true
|
||||
"integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q=="
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-buffer": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz",
|
||||
"integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw=="
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-numbers": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz",
|
||||
"integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/floating-point-hex-parser": "1.11.6",
|
||||
"@webassemblyjs/helper-api-error": "1.11.6",
|
||||
@@ -34208,14 +34152,12 @@
|
||||
"node_modules/@webassemblyjs/helper-wasm-bytecode": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz",
|
||||
"integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA=="
|
||||
},
|
||||
"node_modules/@webassemblyjs/helper-wasm-section": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz",
|
||||
"integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.11.6",
|
||||
"@webassemblyjs/helper-buffer": "1.11.6",
|
||||
@@ -34227,7 +34169,6 @@
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz",
|
||||
"integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@xtuc/ieee754": "^1.2.0"
|
||||
}
|
||||
@@ -34236,7 +34177,6 @@
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz",
|
||||
"integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@xtuc/long": "4.2.2"
|
||||
}
|
||||
@@ -34244,14 +34184,12 @@
|
||||
"node_modules/@webassemblyjs/utf8": {
|
||||
"version": "1.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz",
|
||||
"integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA=="
|
||||
},
|
||||
"node_modules/@webassemblyjs/wasm-edit": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz",
|
||||
"integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.11.6",
|
||||
"@webassemblyjs/helper-buffer": "1.11.6",
|
||||
@@ -34267,7 +34205,6 @@
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz",
|
||||
"integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.11.6",
|
||||
"@webassemblyjs/helper-wasm-bytecode": "1.11.6",
|
||||
@@ -34280,7 +34217,6 @@
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz",
|
||||
"integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.11.6",
|
||||
"@webassemblyjs/helper-buffer": "1.11.6",
|
||||
@@ -34292,7 +34228,6 @@
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz",
|
||||
"integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.11.6",
|
||||
"@webassemblyjs/helper-api-error": "1.11.6",
|
||||
@@ -34306,7 +34241,6 @@
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz",
|
||||
"integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@webassemblyjs/ast": "1.11.6",
|
||||
"@xtuc/long": "4.2.2"
|
||||
@@ -34364,14 +34298,12 @@
|
||||
"node_modules/@xtuc/ieee754": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
|
||||
"integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="
|
||||
},
|
||||
"node_modules/@xtuc/long": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
|
||||
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="
|
||||
},
|
||||
"node_modules/@yarnpkg/lockfile": {
|
||||
"version": "1.1.0",
|
||||
@@ -35370,7 +35302,6 @@
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz",
|
||||
"integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
}
|
||||
@@ -35801,7 +35732,7 @@
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.2",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/date-fns": {
|
||||
@@ -36401,7 +36332,6 @@
|
||||
"version": "5.17.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
|
||||
"integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
"tapable": "^2.2.0"
|
||||
@@ -36521,8 +36451,7 @@
|
||||
"node_modules/es-module-lexer": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
|
||||
"integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw=="
|
||||
},
|
||||
"node_modules/es-object-atoms": {
|
||||
"version": "1.0.0",
|
||||
@@ -36867,7 +36796,6 @@
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esrecurse": "^4.3.0",
|
||||
"estraverse": "^4.1.1"
|
||||
@@ -36880,7 +36808,6 @@
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
|
||||
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
@@ -37044,7 +36971,6 @@
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
|
||||
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"estraverse": "^5.2.0"
|
||||
},
|
||||
@@ -37056,7 +36982,6 @@
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
|
||||
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
@@ -37065,7 +36990,6 @@
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
|
||||
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -37825,8 +37749,7 @@
|
||||
"node_modules/glob-to-regexp": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
|
||||
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
|
||||
"dev": true
|
||||
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="
|
||||
},
|
||||
"node_modules/global": {
|
||||
"version": "4.4.0",
|
||||
@@ -39915,8 +39838,7 @@
|
||||
"node_modules/json-parse-even-better-errors": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||
"dev": true
|
||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="
|
||||
},
|
||||
"node_modules/json-schema-ref-resolver": {
|
||||
"version": "1.0.1",
|
||||
@@ -40392,7 +40314,6 @@
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
|
||||
"integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.11.5"
|
||||
}
|
||||
@@ -42622,7 +42543,6 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.1.0"
|
||||
}
|
||||
@@ -42644,6 +42564,30 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
|
||||
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"scheduler": "^0.23.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom/node_modules/scheduler": {
|
||||
"version": "0.23.2",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
|
||||
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"loose-envify": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-freeze": {
|
||||
"version": "1.0.3",
|
||||
"license": "MIT",
|
||||
@@ -43563,7 +43507,6 @@
|
||||
},
|
||||
"node_modules/regenerator-transform": {
|
||||
"version": "0.15.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.8.4"
|
||||
@@ -43916,7 +43859,6 @@
|
||||
},
|
||||
"node_modules/serialize-javascript": {
|
||||
"version": "6.0.1",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"randombytes": "^2.1.0"
|
||||
@@ -44574,7 +44516,6 @@
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.2.1",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -44652,7 +44593,6 @@
|
||||
"version": "5.3.10",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz",
|
||||
"integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.17",
|
||||
"jest-worker": "^27.4.5",
|
||||
@@ -44686,7 +44626,6 @@
|
||||
"version": "27.5.1",
|
||||
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
|
||||
"integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"merge-stream": "^2.0.0",
|
||||
@@ -44700,7 +44639,6 @@
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
@@ -45277,7 +45215,6 @@
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
|
||||
"integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
"graceful-fs": "^4.1.2"
|
||||
@@ -45301,7 +45238,6 @@
|
||||
"version": "5.94.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz",
|
||||
"integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/eslint-scope": "^3.7.3",
|
||||
"@types/estree": "^1.0.0",
|
||||
@@ -45417,7 +45353,6 @@
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
|
||||
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.0.30",
|
||||
"version": "3.0.33",
|
||||
"private": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
|
||||
@@ -21,13 +21,11 @@ import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from "react";
|
||||
import { Linking, Platform, TextInput, View } from "react-native";
|
||||
import { WebView } from "react-native-webview";
|
||||
import Commands from "../app/screens/editor/tiptap/commands";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
@@ -57,7 +55,6 @@ export async function post(ref, type, value = null) {
|
||||
const useEditor = () => {
|
||||
const ref = useRef();
|
||||
const { colors } = useThemeColors("editor");
|
||||
const commands = useMemo(() => new Commands(ref), [ref]);
|
||||
const currentNote = useRef();
|
||||
|
||||
const postMessage = useCallback(
|
||||
@@ -106,7 +103,7 @@ const useEditor = () => {
|
||||
}, 1);
|
||||
};
|
||||
|
||||
return { ref, onLoad, currentNote, commands };
|
||||
return { ref, onLoad, currentNote };
|
||||
};
|
||||
|
||||
const useEditorEvents = (editor, onChange) => {
|
||||
|
||||
@@ -546,7 +546,7 @@ const ShareView = () => {
|
||||
</Paragraph>
|
||||
<ScrollView horizontal>
|
||||
{rawFiles.map((item) =>
|
||||
isImage(item.type) ? (
|
||||
isImage(item.type) || item.value?.endsWith(".png") ? (
|
||||
<TouchableOpacity
|
||||
onPress={() => onRemoveFile(item)}
|
||||
key={item.name}
|
||||
@@ -604,7 +604,8 @@ const ShareView = () => {
|
||||
paddingRight: 8
|
||||
}}
|
||||
>
|
||||
{item.name} ({formatBytes(item.size)})
|
||||
{item.name || item.value.split("/").pop()}
|
||||
{item.size ? `(${formatBytes(item.size)})` : ""}
|
||||
</Paragraph>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
|
||||
FROM --platform=$BUILDPLATFORM oven/bun:1.1.36-alpine
|
||||
FROM oven/bun:1.2.2-alpine
|
||||
|
||||
RUN mkdir -p /home/bun/app && chown -R bun:bun /home/bun/app
|
||||
|
||||
@@ -11,4 +11,4 @@ COPY --chown=bun:bun output .
|
||||
|
||||
RUN bun install
|
||||
|
||||
CMD [ "bun", "run", "start" ]
|
||||
CMD [ "bun", "run", "start" ]
|
||||
|
||||
@@ -62,8 +62,42 @@ export default function handleRequest(
|
||||
|
||||
responseHeaders.set("Content-Type", "text/html; charset=utf-8");
|
||||
|
||||
const nonce =
|
||||
remixContext.staticHandlerContext?.loaderData?.root?.cspScriptNonce;
|
||||
if (nonce)
|
||||
responseHeaders.set(
|
||||
"Content-Security-Policy",
|
||||
getContentSecurityPolicy(nonce)
|
||||
);
|
||||
|
||||
return new Response(html, {
|
||||
status: responseStatusCode,
|
||||
headers: responseHeaders
|
||||
});
|
||||
}
|
||||
|
||||
function getContentSecurityPolicy(nonce?: string) {
|
||||
let script_src: string;
|
||||
if (typeof nonce === "string") {
|
||||
script_src = `'self' 'report-sample' 'nonce-${nonce}'`;
|
||||
} else if (process.env.NODE_ENV === "development") {
|
||||
// Allow the <LiveReload /> component to load without a nonce in the error pages
|
||||
script_src = "'self' 'report-sample'";
|
||||
} else {
|
||||
script_src = "'self' 'report-sample'";
|
||||
}
|
||||
|
||||
const connect_src =
|
||||
process.env.NODE_ENV === "development"
|
||||
? "'self' ws://localhost:*"
|
||||
: "'self'";
|
||||
|
||||
return (
|
||||
`script-src ${script_src} 'strict-dynamic'; ` +
|
||||
`connect-src ${connect_src}; ` +
|
||||
"form-action 'self'; " +
|
||||
"object-src 'none'; " +
|
||||
"block-all-mixed-content; " +
|
||||
"base-uri 'self'; manifest-src 'self'"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -22,14 +22,22 @@ import {
|
||||
Meta,
|
||||
Outlet,
|
||||
Scripts,
|
||||
ScrollRestoration
|
||||
ScrollRestoration,
|
||||
useLoaderData
|
||||
} from "@remix-run/react";
|
||||
import { BaseThemeProvider } from "./components/theme-provider";
|
||||
import { Buffer } from "buffer";
|
||||
import { ThemeDark, themeToCSS } from "@notesnook/theme";
|
||||
import { LoaderFunction } from "@remix-run/node";
|
||||
|
||||
globalThis.Buffer = Buffer;
|
||||
|
||||
type RootLoaderData = { cspScriptNonce: string };
|
||||
export const loader: LoaderFunction = async () => {
|
||||
const crypto = await import("node:crypto");
|
||||
return { cspScriptNonce: crypto.randomBytes(16).toString("hex") };
|
||||
};
|
||||
|
||||
export function Head() {
|
||||
return (
|
||||
<>
|
||||
@@ -40,13 +48,17 @@ export function Head() {
|
||||
<Links />
|
||||
<style
|
||||
id="theme-colors"
|
||||
dangerouslySetInnerHTML={{ __html: colorsToCSS() }}
|
||||
dangerouslySetInnerHTML={{ __html: themeToCSS(ThemeDark) }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
const data = useLoaderData<RootLoaderData>();
|
||||
const cspScriptNonce =
|
||||
typeof document === "undefined" ? data.cspScriptNonce : "";
|
||||
|
||||
return (
|
||||
<>
|
||||
<BaseThemeProvider
|
||||
@@ -56,29 +68,8 @@ export default function App() {
|
||||
>
|
||||
<Outlet />
|
||||
</BaseThemeProvider>
|
||||
<ScrollRestoration />
|
||||
<Scripts />
|
||||
<ScrollRestoration nonce={cspScriptNonce} />
|
||||
<Scripts nonce={cspScriptNonce} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function colorsToCSS() {
|
||||
// const colorScheme = JSON.parse(
|
||||
// window.localStorage.getItem("colorScheme") || '"light"'
|
||||
// );
|
||||
// const root = document.querySelector("html");
|
||||
// if (root) root.setAttribute("data-theme", colorScheme);
|
||||
|
||||
// const theme =
|
||||
// colorScheme === "dark"
|
||||
// ? JSON.parse(window.localStorage.getItem("theme:dark") || "false") ||
|
||||
// ThemeDark
|
||||
// : JSON.parse(window.localStorage.getItem("theme:light") || "false") ||
|
||||
// ThemeLight;
|
||||
// const stylesheet = document.getElementById("theme-colors");
|
||||
// if (theme) {
|
||||
// const css = ;
|
||||
// if (stylesheet) stylesheet.innerHTML = css;
|
||||
// } else stylesheet?.remove();
|
||||
return themeToCSS(ThemeDark);
|
||||
}
|
||||
|
||||
45
apps/monograph/package-lock.json
generated
45
apps/monograph/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/monograph",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/monograph",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.4",
|
||||
"dependencies": {
|
||||
"@emotion/cache": "11.11.0",
|
||||
"@emotion/react": "11.11.1",
|
||||
@@ -60,9 +60,6 @@
|
||||
"vite-plugin-static-copy": "^2.1.0",
|
||||
"vite-tsconfig-paths": "^5.0.1",
|
||||
"wrangler": "3.78.11"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"../../packages/core": {
|
||||
@@ -2945,6 +2942,22 @@
|
||||
"@styled-system/css": "^5.1.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@theme-ui/color-modes": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@theme-ui/color-modes/-/color-modes-0.16.2.tgz",
|
||||
"integrity": "sha512-jWEWx53lxNgWCT38i/kwLV2rsvJz8lVZgi5oImnVwYba9VejXD23q1ckbNFJHosQ8KKXY87ht0KPC6BQFIiHtQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@theme-ui/core": "^0.16.2",
|
||||
"@theme-ui/css": "^0.16.2",
|
||||
"deepmerge": "^4.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@theme-ui/components": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@theme-ui/components/-/components-0.16.2.tgz",
|
||||
@@ -2990,6 +3003,22 @@
|
||||
"@emotion/react": "^11.11.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@theme-ui/theme-provider": {
|
||||
"version": "0.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@theme-ui/theme-provider/-/theme-provider-0.16.2.tgz",
|
||||
"integrity": "sha512-LRnVevODcGqO0JyLJ3wht+PV3ZoZcJ7XXLJAJWDoGeII4vZcPQKwVy4Lpz/juHsZppQxKcB3U+sQDGBnP25irQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@theme-ui/color-modes": "^0.16.2",
|
||||
"@theme-ui/core": "^0.16.2",
|
||||
"@theme-ui/css": "^0.16.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/acorn": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.6.tgz",
|
||||
@@ -3119,14 +3148,14 @@
|
||||
"version": "15.7.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
|
||||
"integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "18.3.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.10.tgz",
|
||||
"integrity": "sha512-02sAAlBnP39JgXwkAq3PeU9DVaaGpZyF3MGcC0MKgQVkZor5IiiDAipVaxQHtDJAmO4GIy/rVBy/LzVj76Cyqg==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/prop-types": "*",
|
||||
@@ -12212,7 +12241,7 @@
|
||||
"version": "3.23.8",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
|
||||
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@notesnook/monograph",
|
||||
"version": "1.2.2",
|
||||
"version": "1.2.4",
|
||||
"private": true,
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
@@ -64,8 +64,5 @@
|
||||
"vite-plugin-static-copy": "^2.1.0",
|
||||
"vite-tsconfig-paths": "^5.0.1",
|
||||
"wrangler": "3.78.11"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
3481
apps/theme-builder/package-lock.json
generated
3481
apps/theme-builder/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@notesnook/theme-builder",
|
||||
"description": "Your private note taking space",
|
||||
"version": "1.2.0",
|
||||
"version": "1.3.0",
|
||||
"private": true,
|
||||
"main": "./src/app.js",
|
||||
"homepage": "https://notesnook.com/",
|
||||
@@ -12,35 +12,25 @@
|
||||
"@mdi/js": "^7.2.96",
|
||||
"@mdi/react": "^1.6.1",
|
||||
"@notesnook/common": "file:../../packages/common",
|
||||
"@notesnook/intl": "file:../../packages/intl",
|
||||
"@notesnook/theme": "file:../../packages/theme",
|
||||
"@notesnook/web": "file:../web",
|
||||
"@streetwriters/kysely": "^0.27.4",
|
||||
"@theme-ui/components": "^0.14.7",
|
||||
"@theme-ui/core": "^0.14.7",
|
||||
"@trpc/client": "10.45.2",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"clipboard-polyfill": "^4.0.0",
|
||||
"dayjs": "1.11.13",
|
||||
"file-saver": "^2.0.5",
|
||||
"katex": "^0.16.2",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
"react-dom": "18.2.0",
|
||||
"react-modal": "3.16.3",
|
||||
"@lingui/core": "5.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.22.5",
|
||||
"@types/babel__core": "^7.20.1",
|
||||
"@types/file-saver": "^2.0.5",
|
||||
"@types/react": "^18.2.39",
|
||||
"@vitejs/plugin-react-swc": "^3.3.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"buffer": "^6.0.3",
|
||||
"chalk": "^4.1.0",
|
||||
"happy-dom": "^8.9.0",
|
||||
"rollup": "^3.27.0",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"swc-plugin-react-remove-properties": "^0.1.4",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-env-compatible": "^1.1.1",
|
||||
"vite-plugin-svgr": "^3.2.0",
|
||||
"vitest": "^0.32.0"
|
||||
"vite": "5.4.11"
|
||||
},
|
||||
"overrides": {
|
||||
"@theme-ui/components@>0": "0.14.7",
|
||||
|
||||
13
apps/theme-builder/src/index.css
Normal file
13
apps/theme-builder/src/index.css
Normal file
@@ -0,0 +1,13 @@
|
||||
#root > .theme-scope-base {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#app {
|
||||
flex: 1;
|
||||
height: unset;
|
||||
}
|
||||
|
||||
#root > .theme-scope-base > :first-child:not(#menu-wrapper) {
|
||||
flex: 1;
|
||||
}
|
||||
@@ -17,60 +17,44 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { init } from "@notesnook/web/src/bootstrap";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import {
|
||||
ErrorBoundary,
|
||||
ErrorComponent
|
||||
} from "@notesnook/web/src/components/error-boundary";
|
||||
import { BaseThemeProvider } from "@notesnook/web/src/components/theme-provider";
|
||||
import "./index.css";
|
||||
import "@notesnook/web/src/polyfills";
|
||||
import "@notesnook/web/src/app.css";
|
||||
import { ThemeDark, ThemeLight, themeToCSS } from "@notesnook/theme";
|
||||
import Config from "@notesnook/web/src/utils/config";
|
||||
import { setI18nGlobal, Messages } from "@notesnook/intl";
|
||||
import { i18n } from "@lingui/core";
|
||||
import { App } from "./app";
|
||||
|
||||
renderApp();
|
||||
const colorScheme = JSON.parse(
|
||||
window.localStorage.getItem("colorScheme") || '"light"'
|
||||
);
|
||||
const root = document.querySelector("html");
|
||||
if (root) root.setAttribute("data-theme", colorScheme);
|
||||
|
||||
async function renderApp() {
|
||||
const rootElement = document.getElementById("root");
|
||||
if (!rootElement) return;
|
||||
const root = createRoot(rootElement);
|
||||
try {
|
||||
const { component, props } = await init();
|
||||
const theme =
|
||||
colorScheme === "dark"
|
||||
? Config.get("theme:dark", ThemeDark)
|
||||
: Config.get("theme:light", ThemeLight);
|
||||
const stylesheet = document.getElementById("theme-colors");
|
||||
if (theme) {
|
||||
const css = themeToCSS(theme);
|
||||
if (stylesheet) stylesheet.innerHTML = css;
|
||||
} else stylesheet?.remove();
|
||||
|
||||
const { useKeyStore } = await import(
|
||||
"@notesnook/web/src/interfaces/key-store"
|
||||
);
|
||||
await useKeyStore.getState().init();
|
||||
const locale = import.meta.env.DEV
|
||||
? import("@notesnook/intl/locales/$pseudo-LOCALE.json")
|
||||
: import("@notesnook/intl/locales/$en.json");
|
||||
locale.then(({ default: locale }) => {
|
||||
i18n.load({
|
||||
en: locale.messages as unknown as Messages
|
||||
});
|
||||
i18n.activate("en");
|
||||
|
||||
const { default: Component } = await component();
|
||||
const { default: AppLock } = await import(
|
||||
"@notesnook/web/src/views/app-lock"
|
||||
);
|
||||
|
||||
root.render(
|
||||
<ErrorBoundary>
|
||||
<BaseThemeProvider
|
||||
onRender={() => document.getElementById("splash")?.remove()}
|
||||
sx={{
|
||||
display: "flex",
|
||||
"#app": { flex: 1, height: "unset" },
|
||||
"& > :first-child:not(#menu-wrapper)": { flex: 1 },
|
||||
height: "100%"
|
||||
}}
|
||||
>
|
||||
<AppLock>
|
||||
<Component route={props?.route || "login:email"} />
|
||||
</AppLock>
|
||||
<App />
|
||||
</BaseThemeProvider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
} catch (e) {
|
||||
root.render(
|
||||
<>
|
||||
<ErrorComponent
|
||||
error={e}
|
||||
resetErrorBoundary={() => window.location.reload()}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
performance.mark("import:root");
|
||||
import("@notesnook/web/src/root.js").then(({ startApp }) => {
|
||||
performance.mark("start:app");
|
||||
startApp(<App />);
|
||||
});
|
||||
});
|
||||
setI18nGlobal(i18n);
|
||||
|
||||
3
apps/web/.gitignore
vendored
3
apps/web/.gitignore
vendored
@@ -30,4 +30,5 @@ dist
|
||||
public/workbox
|
||||
scripts/secrets
|
||||
test-results
|
||||
.swc
|
||||
.swc
|
||||
.wrangler
|
||||
|
||||
@@ -258,6 +258,21 @@ test("creating a new note and toggling read-only mode should not empty editor co
|
||||
expect(await notes.editor.getContent("text")).toBe(NOTE.content);
|
||||
});
|
||||
|
||||
test("toggling read-only should hide undo & redo buttons", async ({ page }) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const note = await notes.createNote(NOTE);
|
||||
|
||||
await notes.editor.undoButton.waitFor({ state: "visible" });
|
||||
await notes.editor.redoButton.waitFor({ state: "visible" });
|
||||
await note?.properties.readonly();
|
||||
|
||||
expect(await note?.properties.isReadonly()).toBeTruthy();
|
||||
expect(notes.editor.undoButton).not.toBeVisible();
|
||||
expect(notes.editor.redoButton).not.toBeVisible();
|
||||
});
|
||||
|
||||
test("#1468 count words separated by newlines", async ({ page }) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
@@ -347,7 +362,7 @@ test("when autosave is disabled, creating a new note should save the note", asyn
|
||||
await notes.newNote();
|
||||
|
||||
await note?.openNote();
|
||||
await expect(notes.editor.savedIcon).toBeVisible();
|
||||
// await expect(notes.editor.savedIcon).toBeVisible();
|
||||
expect(await notes.editor.getContent("text")).toBe(content.trim());
|
||||
});
|
||||
|
||||
@@ -385,7 +400,7 @@ test("control + alt + right arrow should go to next note", async ({ page }) => {
|
||||
});
|
||||
|
||||
await note1?.openNote();
|
||||
await note2?.openNote();
|
||||
await note2?.openNote(true);
|
||||
await page.keyboard.press("Control+Alt+ArrowRight");
|
||||
|
||||
expect(await notes.editor.getTitle()).toBe("Note 1");
|
||||
@@ -413,7 +428,7 @@ test("control + alt + left arrow should go to previous note", async ({
|
||||
});
|
||||
|
||||
await note1?.openNote();
|
||||
await note2?.openNote();
|
||||
await note2?.openNote(true);
|
||||
await page.keyboard.press("Control+Alt+ArrowLeft");
|
||||
|
||||
expect(await notes.editor.getTitle()).toBe("Note 1");
|
||||
|
||||
@@ -1 +1 @@
|
||||
This is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1An edit I made
|
||||
An edit I madeThis is Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1Test 1
|
||||
@@ -41,6 +41,8 @@ export class EditorModel {
|
||||
private readonly newTabButton: Locator;
|
||||
readonly savedIcon: Locator;
|
||||
readonly notSavedIcon: Locator;
|
||||
readonly undoButton: Locator;
|
||||
readonly redoButton: Locator;
|
||||
|
||||
constructor(page: Page) {
|
||||
this.page = page;
|
||||
@@ -65,6 +67,8 @@ export class EditorModel {
|
||||
this.goBackButton = page.locator(getTestId("go-back"));
|
||||
this.goForwardButton = page.locator(getTestId("go-forward"));
|
||||
this.newTabButton = page.locator(getTestId("New tab"));
|
||||
this.undoButton = page.locator(getTestId("Undo"));
|
||||
this.redoButton = page.locator(getTestId("Redo"));
|
||||
}
|
||||
|
||||
async waitForLoading(title?: string, content?: string) {
|
||||
@@ -93,8 +97,8 @@ export class EditorModel {
|
||||
await this.searchButton.isDisabled();
|
||||
await this.page
|
||||
.locator(".active")
|
||||
.locator(getTestId("tags"))
|
||||
.waitFor({ state: "hidden" });
|
||||
.locator(getTestId("editor-tag-input"))
|
||||
.isDisabled();
|
||||
await this.dateEditedText.waitFor({ state: "hidden" });
|
||||
await this.wordCountText.waitFor();
|
||||
await this.waitForLoading("", "");
|
||||
@@ -109,7 +113,7 @@ export class EditorModel {
|
||||
|
||||
async isUnloaded() {
|
||||
return (
|
||||
(await this.tagInput.isHidden()) &&
|
||||
(await this.tagInput.isDisabled()) &&
|
||||
(await this.enterFullscreenButton.isHidden()) &&
|
||||
(await this.dateEditedText.isHidden())
|
||||
);
|
||||
@@ -124,7 +128,8 @@ export class EditorModel {
|
||||
async typeTitle(text: string, delay = 0) {
|
||||
await this.editAndWait(async () => {
|
||||
await this.title.focus();
|
||||
await this.title.press("End");
|
||||
await this.title.selectText();
|
||||
await this.title.press("ArrowRight");
|
||||
await this.title.type(text, { delay });
|
||||
});
|
||||
}
|
||||
@@ -196,6 +201,16 @@ export class EditorModel {
|
||||
return tags;
|
||||
}
|
||||
|
||||
async removeTags(tags: string[]) {
|
||||
for (const tag of tags) {
|
||||
const item = this.tags
|
||||
.filter({ hasText: tag })
|
||||
.getByTitle("Remove")
|
||||
.first();
|
||||
await item.click();
|
||||
}
|
||||
}
|
||||
|
||||
async getTitle() {
|
||||
return this.title.inputValue();
|
||||
}
|
||||
|
||||
@@ -98,4 +98,9 @@ export class NotebookItemModel extends BaseItemModel {
|
||||
await this.contextMenu.close();
|
||||
return state;
|
||||
}
|
||||
|
||||
async setAsDefault() {
|
||||
await this.contextMenu.open(this.locator);
|
||||
await new ToggleModel(this.page, "menu-button-set-as-default").on();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,11 +19,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Locator, Page } from "@playwright/test";
|
||||
import { getTestId } from "../utils";
|
||||
import { ContextMenuModel } from "./context-menu.model";
|
||||
|
||||
export class TabItemModel {
|
||||
private readonly closeButton: Locator;
|
||||
constructor(private readonly locator: Locator, page: Page) {
|
||||
readonly contextMenu: TabContextMenuModel;
|
||||
readonly titleElement: Locator;
|
||||
|
||||
constructor(readonly locator: Locator, page: Page) {
|
||||
this.closeButton = locator.locator(getTestId("tab-close-button"));
|
||||
this.contextMenu = new TabContextMenuModel(page, locator);
|
||||
this.titleElement = locator.locator(getTestId("tab-title"));
|
||||
}
|
||||
|
||||
async getId() {
|
||||
@@ -36,7 +42,7 @@ export class TabItemModel {
|
||||
}
|
||||
|
||||
async title() {
|
||||
return this.locator.locator(getTestId("tab-title")).textContent();
|
||||
return this.titleElement.textContent();
|
||||
}
|
||||
|
||||
async isActive() {
|
||||
@@ -48,3 +54,19 @@ export class TabItemModel {
|
||||
return this.closeButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
class TabContextMenuModel {
|
||||
private readonly menu: ContextMenuModel;
|
||||
constructor(page: Page, private readonly tabLocator: Locator) {
|
||||
this.menu = new ContextMenuModel(page);
|
||||
}
|
||||
|
||||
async pin() {
|
||||
await this.open();
|
||||
await this.menu.clickOnItem("pin");
|
||||
}
|
||||
|
||||
async open() {
|
||||
await this.menu.open(this.tabLocator);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,3 +271,84 @@ test(`sort notebooks`, async ({ page }, info) => {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("when default notebook is set, created note in notes context should go to default notebook", async ({
|
||||
page
|
||||
}) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
let notebooks = await app.goToNotebooks();
|
||||
let notebook = await notebooks.createNotebook(NOTEBOOK);
|
||||
await notebook?.setAsDefault();
|
||||
|
||||
const notes = await app.goToNotes();
|
||||
await notes?.createNote(NOTE);
|
||||
notebooks = await app.goToNotebooks();
|
||||
notebook = await notebooks.findNotebook(NOTEBOOK);
|
||||
const openedNotebook = await notebook?.openNotebook();
|
||||
|
||||
expect(await openedNotebook?.notes.findNote(NOTE)).toBeDefined();
|
||||
});
|
||||
|
||||
test("when default notebook is set, created note in other notebook's context should not go to default notebook", async ({
|
||||
page
|
||||
}) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
let notebooks = await app.goToNotebooks();
|
||||
let notebook = await notebooks.createNotebook(NOTEBOOK);
|
||||
await notebook?.setAsDefault();
|
||||
|
||||
const otherNotebook = await notebooks.createNotebook({
|
||||
title: "Other Notebook"
|
||||
});
|
||||
const openedOtherNotebook = await otherNotebook?.openNotebook();
|
||||
await openedOtherNotebook?.notes.createNote(NOTE);
|
||||
notebooks = await app.goToNotebooks();
|
||||
notebook = await notebooks.findNotebook(NOTEBOOK);
|
||||
const openedNotebook = await notebook?.openNotebook();
|
||||
|
||||
expect(await openedNotebook?.notes.findNote(NOTE)).toBeUndefined();
|
||||
});
|
||||
|
||||
test("when default notebook is set, created note in tags context should go to default notebook", async ({
|
||||
page
|
||||
}) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
let notebooks = await app.goToNotebooks();
|
||||
let notebook = await notebooks.createNotebook(NOTEBOOK);
|
||||
await notebook?.setAsDefault();
|
||||
|
||||
const tags = await app.goToTags();
|
||||
const tag = await tags.createItem({ title: "TestTag" });
|
||||
const openedTag = await tag?.open();
|
||||
await openedTag?.createNote(NOTE);
|
||||
notebooks = await app.goToNotebooks();
|
||||
notebook = await notebooks.findNotebook(NOTEBOOK);
|
||||
const openedNotebook = await notebook?.openNotebook();
|
||||
|
||||
expect(await openedNotebook?.notes.findNote(NOTE)).toBeDefined();
|
||||
});
|
||||
|
||||
test("when default notebook is set, created note in colors context should go to default notebook", async ({
|
||||
page
|
||||
}) => {
|
||||
const coloredNote = { title: "Red note", content: NOTE.content };
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
let notebooks = await app.goToNotebooks();
|
||||
let notebook = await notebooks.createNotebook(NOTEBOOK);
|
||||
await notebook?.setAsDefault();
|
||||
|
||||
const notes = await app.goToNotes();
|
||||
const note = await notes.createNote(NOTE);
|
||||
await note?.contextMenu.newColor({ color: "#ff0000", title: "red" });
|
||||
const color = await app.goToColor("red");
|
||||
await color?.createNote(coloredNote);
|
||||
notebooks = await app.goToNotebooks();
|
||||
notebook = await notebooks.findNotebook(NOTEBOOK);
|
||||
const openedNotebook = await notebook?.openNotebook();
|
||||
|
||||
expect(await openedNotebook?.notes.findNote(coloredNote)).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { AppModel } from "./models/app.model";
|
||||
import { createHistorySession } from "./utils";
|
||||
import { createHistorySession, NOTE } from "./utils";
|
||||
|
||||
test("notes should open in the same tab", async ({ page }) => {
|
||||
const app = new AppModel(page);
|
||||
@@ -63,7 +63,7 @@ test("open note in new tab (using context menu)", async ({ page }) => {
|
||||
|
||||
const tabs = await notes.editor.getTabs();
|
||||
expect(tabs.length).toBe(2);
|
||||
expect(await tabs[1].title()).toBe("Note 2");
|
||||
await expect(tabs[1].titleElement).toHaveText("Note 2");
|
||||
});
|
||||
|
||||
test("open note in new tab (using middle click)", async ({ page }) => {
|
||||
@@ -80,7 +80,7 @@ test("open note in new tab (using middle click)", async ({ page }) => {
|
||||
|
||||
const tabs = await notes.editor.getTabs();
|
||||
expect(tabs.length).toBe(2);
|
||||
expect(await tabs[1].title()).toBe("Note 2");
|
||||
await expect(tabs[1].titleElement).toHaveText("Note 2");
|
||||
});
|
||||
|
||||
test("go back should open previous note", async ({ page }) => {
|
||||
@@ -93,9 +93,9 @@ test("go back should open previous note", async ({ page }) => {
|
||||
await notes.createNote({ title: "Note 3" });
|
||||
|
||||
await notes.editor.goBack();
|
||||
expect(await tabs[0].title()).toBe("Note 2");
|
||||
await expect(tabs[0].titleElement).toHaveText("Note 2");
|
||||
await notes.editor.goBack();
|
||||
expect(await tabs[0].title()).toBe("Note 1");
|
||||
await expect(tabs[0].titleElement).toHaveText("Note 1");
|
||||
});
|
||||
|
||||
test("go forward should open next note", async ({ page }) => {
|
||||
@@ -110,9 +110,9 @@ test("go forward should open next note", async ({ page }) => {
|
||||
await notes.editor.goBack();
|
||||
await notes.editor.goBack();
|
||||
await notes.editor.goForward();
|
||||
expect(await tabs[0].title()).toBe("Note 2");
|
||||
await expect(tabs[0].titleElement).toHaveText("Note 2");
|
||||
await notes.editor.goForward();
|
||||
expect(await tabs[0].title()).toBe("Note 3");
|
||||
await expect(tabs[0].titleElement).toHaveText("Note 3");
|
||||
});
|
||||
|
||||
test("new tab button should open a new tab", async ({ page }) => {
|
||||
@@ -124,11 +124,11 @@ test("new tab button should open a new tab", async ({ page }) => {
|
||||
await notes.editor.newTab();
|
||||
|
||||
const tabs = await notes.editor.getTabs();
|
||||
expect(await tabs[0].title()).toBe("Note 1");
|
||||
expect(await tabs[1].title()).toBe("Untitled");
|
||||
await expect(tabs[0].titleElement).toHaveText("Note 1");
|
||||
await expect(tabs[1].titleElement).toHaveText("Untitled");
|
||||
});
|
||||
|
||||
test("changes in a note opened in multiple tabs should sync", async ({
|
||||
test("content changes in a note opened in multiple tabs should sync", async ({
|
||||
page
|
||||
}) => {
|
||||
const app = new AppModel(page);
|
||||
@@ -173,6 +173,7 @@ test("reloading with a note diff open in a tab", async ({ page }) => {
|
||||
const history = await note?.properties.getSessionHistory();
|
||||
const preview = await history?.[0].open();
|
||||
await preview!.firstEditor.waitFor({ state: "visible" });
|
||||
await page.waitForTimeout(1000);
|
||||
|
||||
await page.reload();
|
||||
await preview!.firstEditor.waitFor({ state: "visible" });
|
||||
@@ -227,7 +228,7 @@ test("clicking on a note that's already opened in another tab should focus the t
|
||||
await note?.openNote();
|
||||
|
||||
const tabs = await notes.editor.getTabs();
|
||||
expect(await tabs[0].isActive()).toBe(true);
|
||||
await expect(tabs[0].locator).toHaveClass(/active/);
|
||||
});
|
||||
|
||||
test("open a note in 2 tabs then open another note and navigate back", async ({
|
||||
@@ -248,8 +249,134 @@ test("open a note in 2 tabs then open another note and navigate back", async ({
|
||||
await notes.editor.goBack();
|
||||
|
||||
const tabs = await notes.editor.getTabs();
|
||||
expect(await tabs[1].isActive()).toBe(true);
|
||||
expect(await tabs[1].title()).toBe("Note 1");
|
||||
await expect(tabs[1].locator).toHaveClass(/active/);
|
||||
await expect(tabs[1].titleElement).toHaveText("Note 1");
|
||||
});
|
||||
|
||||
test("shouldn't be possible to open a note in a pinned tab", async ({
|
||||
page
|
||||
}) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const note = await notes.createNote({
|
||||
title: "Note 1"
|
||||
});
|
||||
await notes.createNote({
|
||||
title: "Note 2"
|
||||
});
|
||||
let tabs = await notes.editor.getTabs();
|
||||
await tabs[0].contextMenu.pin();
|
||||
|
||||
await note?.click();
|
||||
await notes.editor.waitForLoading();
|
||||
|
||||
tabs = await notes.editor.getTabs();
|
||||
expect(tabs.length).toBe(2);
|
||||
await expect(tabs[1].locator).toHaveClass(/active/);
|
||||
await expect(tabs[1].titleElement).toHaveText("Note 1");
|
||||
});
|
||||
|
||||
test("shouldn't be possible to create a new note in a pinned tab", async ({
|
||||
page
|
||||
}) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
await notes.createNote({
|
||||
title: "Note 1"
|
||||
});
|
||||
let tabs = await notes.editor.getTabs();
|
||||
await tabs[0].contextMenu.pin();
|
||||
|
||||
await notes.newNote();
|
||||
|
||||
tabs = await notes.editor.getTabs();
|
||||
expect(tabs.length).toBe(2);
|
||||
await expect(tabs[1].locator).toHaveClass(/active/);
|
||||
});
|
||||
|
||||
test("notes open in multiple tabs should sync tags when tags are added", async ({
|
||||
page
|
||||
}) => {
|
||||
const tags = ["toAdd", "todo"];
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const note = await notes.createNote(NOTE);
|
||||
await note?.contextMenu.openInNewTab();
|
||||
|
||||
await notes.editor.setTags(tags);
|
||||
const tabs = await notes.editor.getTabs();
|
||||
await tabs[0].click();
|
||||
|
||||
const noteTags = await notes.editor.getTags();
|
||||
expect(noteTags).toHaveLength(tags.length);
|
||||
expect(noteTags.every((t, i) => t === tags[i])).toBe(true);
|
||||
});
|
||||
|
||||
test("notes open in multiple tabs should sync tags when tags are removed", async ({
|
||||
page
|
||||
}) => {
|
||||
const tags = ["removeme", "dontremoveme", "removemex2", "todo"];
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const note = await notes.createNote(NOTE);
|
||||
await notes.editor.setTags(tags);
|
||||
await note?.contextMenu.openInNewTab();
|
||||
|
||||
await notes.editor.removeTags(["removeme", "removemex2"]);
|
||||
const tabs = await notes.editor.getTabs();
|
||||
await tabs[0].click();
|
||||
|
||||
const noteTags = await notes.editor.getTags();
|
||||
expect(noteTags).toHaveLength(2);
|
||||
expect(noteTags.every((t, i) => t === ["dontremoveme", "todo"][i])).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
test("notes open in multiple tabs should sync color in note properties when color is changed", async ({
|
||||
page
|
||||
}) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const note = await notes.createNote(NOTE);
|
||||
await note?.contextMenu.newColor({ title: "red", color: "#ff0000" });
|
||||
await note?.contextMenu.newColor({ title: "blue", color: "#0000ff" });
|
||||
await note?.contextMenu.openInNewTab();
|
||||
|
||||
await notes.editor.waitForLoading();
|
||||
const tabs = await notes.editor.getTabs();
|
||||
await tabs[0].click();
|
||||
|
||||
expect(await note?.properties.isColored("blue")).toBe(true);
|
||||
|
||||
await note?.properties.color("red");
|
||||
await tabs[1].click();
|
||||
|
||||
expect(await note?.properties.isColored("red")).toBe(true);
|
||||
});
|
||||
|
||||
test("notes open in multiple tabs should sync title when title is changed", async ({
|
||||
page
|
||||
}) => {
|
||||
const app = new AppModel(page);
|
||||
await app.goto();
|
||||
const notes = await app.goToNotes();
|
||||
const note = await notes.createNote(NOTE);
|
||||
await notes.editor.setTitle("Todo");
|
||||
await note?.contextMenu.openInNewTab();
|
||||
|
||||
expect(await notes.editor.getTitle()).toBe("Todo");
|
||||
|
||||
await notes.editor.setTitle("Todo - Today");
|
||||
const tabs = await notes.editor.getTabs();
|
||||
await tabs[0].click();
|
||||
|
||||
expect(await notes.editor.getTitle()).toBe("Todo - Today");
|
||||
});
|
||||
|
||||
test.skip("TODO: open a locked note, switch to another note and navigate back", () => {});
|
||||
|
||||
57
apps/web/functions/_middleware.ts
Normal file
57
apps/web/functions/_middleware.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
function isBeta(cookie: string) {
|
||||
console.log("Checking cookie:", cookie);
|
||||
const releaseTrack = cookie
|
||||
.split("; ")
|
||||
.find((row) => row.startsWith("release-track="))
|
||||
?.split("=")[1];
|
||||
return releaseTrack === "beta";
|
||||
}
|
||||
|
||||
interface Env {
|
||||
BETA_BASE_URL: string;
|
||||
}
|
||||
|
||||
export const onRequest: PagesFunction<Env> = async ({ request, env, next }) => {
|
||||
try {
|
||||
const url = new URL(request.url);
|
||||
const response = await (async () => {
|
||||
if (isBeta(request.headers.get("Cookie") || "") && env.BETA_BASE_URL) {
|
||||
const betaUrl = new URL(env.BETA_BASE_URL);
|
||||
betaUrl.pathname = url.pathname;
|
||||
betaUrl.search = url.search;
|
||||
console.log("Fetching asset from beta URL:", betaUrl.toString());
|
||||
const asset = await fetch(betaUrl, {
|
||||
headers: request.headers,
|
||||
method: request.method,
|
||||
body: request.body
|
||||
});
|
||||
return new Response(asset.body, asset);
|
||||
} else {
|
||||
return await next();
|
||||
}
|
||||
})();
|
||||
return response;
|
||||
} catch (thrown) {
|
||||
console.error("Error occurred:", thrown);
|
||||
return new Response(thrown);
|
||||
}
|
||||
};
|
||||
10
apps/web/functions/tsconfig.json
Normal file
10
apps/web/functions/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "CommonJS",
|
||||
"lib": ["ES2020"],
|
||||
"types": ["@cloudflare/workers-types"],
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
||||
1387
apps/web/package-lock.json
generated
1387
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.0.25",
|
||||
"version": "3.0.31",
|
||||
"private": true,
|
||||
"main": "./src/app.js",
|
||||
"homepage": "https://notesnook.com/",
|
||||
@@ -85,6 +85,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "7.22.5",
|
||||
"@cloudflare/workers-types": "^4.20250224.0",
|
||||
"@playwright/test": "1.48.2",
|
||||
"@swc/plugin-react-remove-properties": "^6.0.2",
|
||||
"@trpc/server": "10.45.2",
|
||||
@@ -131,6 +132,7 @@
|
||||
"start:test": "serve -s build/ -p 3000",
|
||||
"build": "cross-env PLATFORM=web vite build",
|
||||
"build:test": "cross-env PLATFORM=web TEST=true vite build",
|
||||
"build:beta": "cross-env PLATFORM=web BETA=true vite build",
|
||||
"build:desktop": "cross-env PLATFORM=desktop vite build",
|
||||
"analyze": "cross-env ANALYZING=true PLATFORM=web vite build",
|
||||
"test": "playwright test -u",
|
||||
|
||||
@@ -249,3 +249,26 @@ textarea,
|
||||
background-color: color-mix(in srgb, var(--accent) 5%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
kbd {
|
||||
background: var(--background);
|
||||
border-radius: 3px;
|
||||
padding: 2px 5px;
|
||||
color: var(--paragraph-secondary);
|
||||
}
|
||||
|
||||
.ping {
|
||||
animation: ping 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes ping {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ import { AppEventManager, AppEvents } from "./common/app-events";
|
||||
import { TITLE_BAR_HEIGHT } from "./components/title-bar";
|
||||
import { getFontSizes } from "@notesnook/theme/theme/font/fontsize.js";
|
||||
import { useWindowControls } from "./hooks/use-window-controls";
|
||||
import { STATUS_BAR_HEIGHT } from "./common/constants";
|
||||
|
||||
new WebExtensionRelay();
|
||||
|
||||
@@ -130,7 +131,10 @@ function App() {
|
||||
) : (
|
||||
<DesktopAppContents setShow={setShow} show={show} />
|
||||
)}
|
||||
<Toaster containerClassName="toasts-container" />
|
||||
<Toaster
|
||||
containerClassName="toasts-container"
|
||||
containerStyle={{ bottom: STATUS_BAR_HEIGHT + 10 }}
|
||||
/>
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -25,3 +25,5 @@ export const SUBSCRIPTION_STATUS = {
|
||||
PREMIUM_EXPIRED: 6,
|
||||
PREMIUM_CANCELED: 7
|
||||
} as const;
|
||||
|
||||
export const STATUS_BAR_HEIGHT = 24 as const;
|
||||
|
||||
@@ -70,6 +70,7 @@ function attachListeners() {
|
||||
function attachListener(event: string) {
|
||||
return {
|
||||
onData(...args: any[]) {
|
||||
console.log("Received data:", args);
|
||||
AppEventManager.publish(event, ...args);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -25,7 +25,8 @@ export interface BaseDialogProps<T = unknown> {
|
||||
}
|
||||
|
||||
class _DialogManager {
|
||||
private openedDialogs: Set<React.JSXElementConstructor<any>> = new Set();
|
||||
private openedDialogs: Map<React.JSXElementConstructor<any>, () => void> =
|
||||
new Map();
|
||||
|
||||
open<
|
||||
Props extends BaseDialogProps<any>,
|
||||
@@ -39,19 +40,21 @@ class _DialogManager {
|
||||
const container = document.createElement("div");
|
||||
const root = createRoot(container);
|
||||
|
||||
const close = () => {
|
||||
this.openedDialogs.delete(component);
|
||||
root.unmount();
|
||||
container.remove();
|
||||
};
|
||||
|
||||
const Dialog = component as React.FunctionComponent<
|
||||
Omit<Props, "onClose"> & {
|
||||
onClose: (result: any) => void;
|
||||
}
|
||||
>;
|
||||
this.openedDialogs.add(component);
|
||||
return new Promise<Result>((resolve, reject) =>
|
||||
return new Promise<Result>((resolve, reject) => {
|
||||
const close = () => {
|
||||
this.openedDialogs.delete(component);
|
||||
root.unmount();
|
||||
container.remove();
|
||||
};
|
||||
this.openedDialogs.set(component, () => {
|
||||
close();
|
||||
reject(new Error("Dialog force closed."));
|
||||
});
|
||||
root.render(
|
||||
<Dialog
|
||||
{...props}
|
||||
@@ -64,8 +67,8 @@ class _DialogManager {
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)
|
||||
);
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
closeAll() {
|
||||
@@ -80,7 +83,12 @@ class _DialogManager {
|
||||
component: React.ComponentType<Props>
|
||||
) {
|
||||
return {
|
||||
show: (props: Omit<Props, "onClose">) => this.open(component, props)
|
||||
show: (props: Omit<Props, "onClose">) => this.open(component, props),
|
||||
close: () => {
|
||||
const dialog = this.openedDialogs.get(component);
|
||||
if (!dialog) return;
|
||||
dialog();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import hotkeys from "hotkeys-js";
|
||||
import { useEditorStore } from "../stores/editor-store";
|
||||
import { useStore as useSearchStore } from "../stores/search-store";
|
||||
import { useEditorManager } from "../components/editor/manager";
|
||||
import { CommandPaletteDialog } from "../dialogs/command-palette";
|
||||
import { hashNavigate } from "../navigation";
|
||||
|
||||
function isInEditor(e: KeyboardEvent) {
|
||||
return (
|
||||
@@ -123,7 +125,7 @@ const KEYMAP = [
|
||||
|
||||
useSearchStore.setState({ isSearching: true, searchType: "notes" });
|
||||
}
|
||||
}
|
||||
},
|
||||
// {
|
||||
// keys: ["alt+n"],
|
||||
// description: "Go to Notes",
|
||||
@@ -187,6 +189,22 @@ const KEYMAP = [
|
||||
// themestore.get().toggleNightMode();
|
||||
// },
|
||||
// },
|
||||
{
|
||||
keys: ["ctrl+k", "cmd+k", "ctrl+p", "cmd+p"],
|
||||
description: "Open command palette",
|
||||
action: (e: KeyboardEvent) => {
|
||||
e.preventDefault();
|
||||
CommandPaletteDialog.close();
|
||||
CommandPaletteDialog.show({
|
||||
isCommandMode: e.key === "k"
|
||||
}).catch(() => {});
|
||||
}
|
||||
},
|
||||
{
|
||||
keys: ["ctrl+,", "command+,"],
|
||||
description: "Open settings",
|
||||
action: () => hashNavigate("/settings", { replace: true })
|
||||
}
|
||||
];
|
||||
|
||||
export function registerKeyMap() {
|
||||
|
||||
@@ -33,6 +33,7 @@ type DialogButtonProps = ButtonProps & {
|
||||
disabled?: boolean;
|
||||
text: JSX.Element | string;
|
||||
loading?: boolean;
|
||||
role?: string;
|
||||
};
|
||||
|
||||
type DialogProps = SxProp & {
|
||||
@@ -113,6 +114,13 @@ function BaseDialog(props: React.PropsWithChildren<DialogProps>) {
|
||||
|
||||
...props.sx
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Escape") {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
props.onClose?.();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{props.showCloseButton && (
|
||||
<Close
|
||||
@@ -183,6 +191,7 @@ function BaseDialog(props: React.PropsWithChildren<DialogProps>) {
|
||||
{...props.negativeButton}
|
||||
color="paragraph"
|
||||
data-test-id="dialog-no"
|
||||
role="negative-button"
|
||||
/>
|
||||
)}
|
||||
{props.positiveButton && (
|
||||
@@ -190,6 +199,7 @@ function BaseDialog(props: React.PropsWithChildren<DialogProps>) {
|
||||
{...props.positiveButton}
|
||||
color="accent"
|
||||
data-test-id="dialog-yes"
|
||||
role="positive-button"
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
@@ -202,13 +212,21 @@ function BaseDialog(props: React.PropsWithChildren<DialogProps>) {
|
||||
|
||||
export default BaseDialog;
|
||||
|
||||
export function DialogButton(props: DialogButtonProps) {
|
||||
export function DialogButton({
|
||||
disabled,
|
||||
onClick,
|
||||
loading,
|
||||
text,
|
||||
role,
|
||||
...props
|
||||
}: DialogButtonProps) {
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
variant="dialog"
|
||||
disabled={props.disabled}
|
||||
onClick={props.disabled ? undefined : props.onClick}
|
||||
disabled={disabled}
|
||||
onClick={disabled ? undefined : onClick}
|
||||
data-role={role}
|
||||
sx={{
|
||||
maxWidth: "100%",
|
||||
textOverflow: "ellipsis",
|
||||
@@ -216,7 +234,7 @@ export function DialogButton(props: DialogButtonProps) {
|
||||
whiteSpace: "nowrap"
|
||||
}}
|
||||
>
|
||||
{props.loading ? <Loading size={16} color="accent" /> : props.text}
|
||||
{loading ? <Loading size={16} color="accent" /> : text}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -225,6 +243,7 @@ function onAfterOpen(
|
||||
e: ReactModal.OnAfterOpenCallbackOptions | undefined,
|
||||
props: DialogProps
|
||||
) {
|
||||
if (props.onOpen) props.onOpen();
|
||||
if (!props.onClose || !e) return;
|
||||
// we need this work around because ReactModal content spreads over the overlay
|
||||
const child = e.contentEl.firstElementChild as HTMLElement;
|
||||
@@ -241,5 +260,4 @@ function onAfterOpen(
|
||||
if (props.onClose) props.onClose();
|
||||
}
|
||||
};
|
||||
if (props.onOpen) props.onOpen();
|
||||
}
|
||||
|
||||
@@ -117,13 +117,15 @@ export function EditorActionBar() {
|
||||
title: strings.undo(),
|
||||
icon: Undo,
|
||||
enabled: editorManager?.canUndo,
|
||||
onClick: () => editorManager?.editor?.undo()
|
||||
onClick: () => editorManager?.editor?.undo(),
|
||||
hidden: activeSession?.type === "readonly"
|
||||
},
|
||||
{
|
||||
title: strings.redo(),
|
||||
icon: Redo,
|
||||
enabled: editorManager?.canRedo,
|
||||
onClick: () => editorManager?.editor?.redo()
|
||||
onClick: () => editorManager?.editor?.redo(),
|
||||
hidden: activeSession?.type === "readonly"
|
||||
},
|
||||
{
|
||||
title: isNotePublished ? strings.published() : strings.publish(),
|
||||
@@ -408,15 +410,7 @@ const TabStrip = React.memo(function TabStrip() {
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
onPin={() => {
|
||||
const tabs = useEditorStore.getState().tabs.slice();
|
||||
const index = tabs.findIndex((t) => t.id === tab.id);
|
||||
tabs[index].pinned = !tabs[index].pinned;
|
||||
tabs.sort((a, b) =>
|
||||
a.pinned === b.pinned ? 0 : a.pinned ? -1 : 1
|
||||
);
|
||||
useEditorStore.setState({ tabs });
|
||||
}}
|
||||
onPin={() => useEditorStore.getState().pinTab(tab.id)}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
|
||||
@@ -44,6 +44,7 @@ import { MAX_AUTO_SAVEABLE_WORDS } from "./types";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { EDITOR_ZOOM } from "./common";
|
||||
import { useWindowControls } from "../../hooks/use-window-controls";
|
||||
import { exitFullscreen } from "../../utils/fullscreen";
|
||||
|
||||
const SAVE_STATE_ICON_MAP = {
|
||||
"-1": NotSaved,
|
||||
@@ -96,13 +97,7 @@ function EditorFooter() {
|
||||
icon: isFocusMode ? FocusMode : NormalMode,
|
||||
enabled: true,
|
||||
hideOnMobile: true,
|
||||
onClick: () => {
|
||||
useAppStore.getState().toggleFocusMode();
|
||||
if (document.fullscreenElement) exitFullscreen();
|
||||
const editor =
|
||||
session && useEditorManager.getState().getEditor(session.id);
|
||||
if (editor) editor.editor?.focus();
|
||||
}
|
||||
onClick: () => useAppStore.getState().toggleFocusMode()
|
||||
}
|
||||
];
|
||||
|
||||
@@ -149,9 +144,15 @@ function EditorFooter() {
|
||||
>
|
||||
<Minus size={13} />
|
||||
</Button>
|
||||
<Text variant="subBody" sx={{ color: "paragraph" }}>
|
||||
{editorConfig.zoom}%
|
||||
</Text>
|
||||
<Button
|
||||
title={strings.clickToReset(strings.zoom().toLocaleLowerCase())}
|
||||
variant="statusitem"
|
||||
onClick={() => setEditorConfig({ zoom: EDITOR_ZOOM.DEFAULT })}
|
||||
>
|
||||
<Text variant="subBody" sx={{ color: "paragraph" }}>
|
||||
{editorConfig.zoom}%{" "}
|
||||
</Text>
|
||||
</Button>
|
||||
<Button
|
||||
variant="icon"
|
||||
onClick={() =>
|
||||
@@ -224,8 +225,3 @@ export default EditorFooter;
|
||||
function enterFullscreen(elem: HTMLElement) {
|
||||
elem.requestFullscreen();
|
||||
}
|
||||
|
||||
function exitFullscreen() {
|
||||
if (!document.fullscreenElement) return;
|
||||
document.exitFullscreen();
|
||||
}
|
||||
|
||||
@@ -35,9 +35,11 @@ import { strings } from "@notesnook/intl";
|
||||
type HeaderProps = { readonly: boolean; id: string };
|
||||
function Header(props: HeaderProps) {
|
||||
const { readonly, id } = props;
|
||||
const tags = useEditorStore(
|
||||
(store) => store.getActiveSession(["default", "readonly"])?.tags
|
||||
const activeSession = useEditorStore((store) =>
|
||||
store.getActiveSession(["default", "readonly"])
|
||||
);
|
||||
const tags = activeSession?.tags || [];
|
||||
const noteId = activeSession?.note.id;
|
||||
|
||||
const setTag = useCallback(async function (
|
||||
noteId: string,
|
||||
@@ -62,7 +64,12 @@ function Header(props: HeaderProps) {
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
sx={{ lineHeight: 2.5, alignItems: "center", flexWrap: "wrap" }}
|
||||
sx={{
|
||||
lineHeight: 2.5,
|
||||
alignItems: "center",
|
||||
flexWrap: "wrap",
|
||||
opacity: !noteId ? 0.5 : 1
|
||||
}}
|
||||
data-test-id="tags"
|
||||
>
|
||||
{tags?.map((tag) => (
|
||||
@@ -72,7 +79,11 @@ function Header(props: HeaderProps) {
|
||||
text={tag.title}
|
||||
icon={TagIcon}
|
||||
onClick={() => navigate(`/tags/${tag.id}`)}
|
||||
onDismiss={readonly ? undefined : () => setTag(id, tags, tag.title)}
|
||||
onDismiss={
|
||||
readonly || !noteId
|
||||
? undefined
|
||||
: () => setTag(noteId, tags, tag.title)
|
||||
}
|
||||
styles={{ container: { mr: 1 }, text: { fontSize: "body" } }}
|
||||
/>
|
||||
))}
|
||||
@@ -80,8 +91,10 @@ function Header(props: HeaderProps) {
|
||||
<Autosuggest
|
||||
sessionId={id}
|
||||
filter={(query) => db.lookup.tags(query).items(10)}
|
||||
disabled={!noteId}
|
||||
toMenuItems={(filtered, reset, query) => {
|
||||
const items: MenuItem[] = [];
|
||||
if (!noteId) return items;
|
||||
const isExactMatch =
|
||||
!!query && filtered.some((item) => item.title === query);
|
||||
if (query && !isExactMatch) {
|
||||
@@ -90,7 +103,7 @@ function Header(props: HeaderProps) {
|
||||
key: "new",
|
||||
title: `Create "${query}" tag`,
|
||||
icon: Plus.path,
|
||||
onClick: () => setTag(id, tags, query).finally(reset)
|
||||
onClick: () => setTag(noteId, tags, query).finally(reset)
|
||||
});
|
||||
}
|
||||
|
||||
@@ -101,17 +114,22 @@ function Header(props: HeaderProps) {
|
||||
key: item.id,
|
||||
title: item.title,
|
||||
icon: TagIcon.path,
|
||||
onClick: () => setTag(id, tags, item.title).finally(reset)
|
||||
onClick: () =>
|
||||
setTag(noteId, tags, item.title).finally(reset)
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
return items;
|
||||
}}
|
||||
onAdd={(value) => setTag(id, tags, value)}
|
||||
onAdd={(value) => {
|
||||
if (!noteId) return;
|
||||
setTag(noteId, tags, value);
|
||||
}}
|
||||
onRemove={() => {
|
||||
if (!noteId) return;
|
||||
if (tags.length <= 0) return;
|
||||
setTag(id, tags, tags[tags.length - 1].title);
|
||||
setTag(noteId, tags, tags[tags.length - 1].title);
|
||||
}}
|
||||
defaultItems={() =>
|
||||
db.tags.all.limit(10).items(undefined, {
|
||||
@@ -134,6 +152,7 @@ type AutosuggestProps<T> = {
|
||||
onAdd: (text: string) => void;
|
||||
toMenuItems: (filtered: T[], reset: () => void, query?: string) => MenuItem[];
|
||||
defaultItems: () => Promise<T[]>;
|
||||
disabled?: boolean;
|
||||
};
|
||||
export function Autosuggest<T>(props: AutosuggestProps<T>) {
|
||||
const { sessionId, filter, onRemove, onAdd, defaultItems, toMenuItems } =
|
||||
@@ -188,6 +207,7 @@ export function Autosuggest<T>(props: AutosuggestProps<T>) {
|
||||
ref={inputRef}
|
||||
tabIndex={-1}
|
||||
variant="clean"
|
||||
disabled={props.disabled}
|
||||
sx={{
|
||||
width: "auto",
|
||||
border: "none",
|
||||
|
||||
@@ -23,7 +23,8 @@ import React, {
|
||||
useRef,
|
||||
PropsWithChildren,
|
||||
Suspense,
|
||||
useLayoutEffect
|
||||
useLayoutEffect,
|
||||
useCallback
|
||||
} from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Box, Button, Flex, Progress, Text } from "@theme-ui/components";
|
||||
@@ -223,13 +224,13 @@ export default function TabsView() {
|
||||
);
|
||||
}
|
||||
|
||||
const MemoizedEditorView = React.memo(
|
||||
EditorView,
|
||||
(prev, next) =>
|
||||
const MemoizedEditorView = React.memo(EditorView, (prev, next) => {
|
||||
return (
|
||||
prev.session.id === next.session.id &&
|
||||
prev.session.type === next.session.type &&
|
||||
prev.session.needsHydration === next.session.needsHydration
|
||||
);
|
||||
);
|
||||
});
|
||||
function EditorView({
|
||||
session
|
||||
}: {
|
||||
@@ -298,11 +299,11 @@ function EditorView({
|
||||
};
|
||||
}, [editor]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!session.needsHydration && session.content) {
|
||||
editor?.updateContent(session.content.data);
|
||||
}
|
||||
}, [editor, session]);
|
||||
const getContent = useCallback(() => {
|
||||
return session.content?.data;
|
||||
}, [session.content?.data]);
|
||||
|
||||
if (session.needsHydration) return null;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@@ -321,7 +322,7 @@ function EditorView({
|
||||
<Editor
|
||||
id={session.id}
|
||||
nonce={1}
|
||||
content={() => session.content?.data}
|
||||
content={getContent}
|
||||
session={session}
|
||||
onPreviewDocument={(preview) =>
|
||||
useEditorStore.setState({ documentPreview: preview })
|
||||
@@ -665,10 +666,12 @@ function EditorChrome(props: PropsWithChildren<EditorProps>) {
|
||||
parent.left - child.left - CONTAINER_MARGIN
|
||||
);
|
||||
|
||||
editor.style.marginLeft = `-${negativeSpace}px`;
|
||||
editor.style.marginRight = `-${negativeSpace}px`;
|
||||
editor.style.paddingLeft = `${negativeSpace}px`;
|
||||
editor.style.paddingRight = `${negativeSpace}px`;
|
||||
requestAnimationFrame(() => {
|
||||
editor.style.marginLeft = `-${negativeSpace}px`;
|
||||
editor.style.marginRight = `-${negativeSpace}px`;
|
||||
editor.style.paddingLeft = `${negativeSpace}px`;
|
||||
editor.style.paddingRight = `${negativeSpace}px`;
|
||||
});
|
||||
}
|
||||
const observer = new ResizeObserver(debounce(onResize, 500));
|
||||
observer.observe(editorScrollRef.current);
|
||||
@@ -862,7 +865,7 @@ function restoreScrollPosition(session: EditorSession) {
|
||||
function restoreSelection(editor: IEditor, id: string) {
|
||||
setTimeout(() => {
|
||||
editor.focus({
|
||||
position: Config.get(`${id}:selection`, { from: 0, to: 0 })
|
||||
position: Config.get(`${id}:selection`)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -148,7 +148,9 @@ function TableOfContents(props: TableOfContentsProps) {
|
||||
? "accent-selected"
|
||||
: "paragraph"
|
||||
}}
|
||||
onClick={() => scrollIntoViewById(t.id)}
|
||||
onClick={() =>
|
||||
scrollIntoViewById(t.id, "scroll-margin-top: 35px;")
|
||||
}
|
||||
>
|
||||
{t.title}
|
||||
</Button>
|
||||
|
||||
@@ -297,18 +297,19 @@ function TipTap(props: TipTapProps) {
|
||||
previewAttachment: onPreviewAttachment,
|
||||
createInternalLink: onInsertInternalLink,
|
||||
getAttachmentData: onGetAttachmentData,
|
||||
openLink: (url) => {
|
||||
openLink: (url, openInNewTab) => {
|
||||
const link = parseInternalLink(url);
|
||||
if (link && link.type === "note") {
|
||||
useEditorStore.getState().openSession(link.id, {
|
||||
activeBlockId: link.params?.blockId || undefined
|
||||
activeBlockId: link.params?.blockId || undefined,
|
||||
openInNewTab: openInNewTab
|
||||
});
|
||||
} else window.open(url, "_blank");
|
||||
}
|
||||
};
|
||||
}, [
|
||||
content,
|
||||
readonly,
|
||||
nonce,
|
||||
doubleSpacedLines,
|
||||
dateFormat,
|
||||
timeFormat,
|
||||
|
||||
@@ -17,25 +17,17 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from "react";
|
||||
import { Input } from "@theme-ui/components";
|
||||
import React, { useEffect, useLayoutEffect, useMemo, useRef } from "react";
|
||||
import { Textarea } from "@theme-ui/components";
|
||||
import { useEditorStore } from "../../stores/editor-store";
|
||||
import { debounceWithId } from "@notesnook/common";
|
||||
import useMobile from "../../hooks/use-mobile";
|
||||
import useTablet from "../../hooks/use-tablet";
|
||||
import { useEditorConfig, useEditorManager } from "./manager";
|
||||
import { getFontById } from "@notesnook/editor";
|
||||
import { replaceDateTime } from "@notesnook/editor";
|
||||
import { useStore as useSettingsStore } from "../../stores/setting-store";
|
||||
import { AppEventManager, AppEvents } from "../../common/app-events";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { NEWLINE_STRIP_REGEX } from "@notesnook/core";
|
||||
|
||||
type TitleBoxProps = {
|
||||
id: string;
|
||||
@@ -44,12 +36,10 @@ type TitleBoxProps = {
|
||||
|
||||
function TitleBox(props: TitleBoxProps) {
|
||||
const { readonly, id } = props;
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
const pendingChanges = useRef(false);
|
||||
// const id = useStore((store) => store.session.id);
|
||||
const sessionType = useEditorStore((store) => store.getSession(id)?.type);
|
||||
const isMobile = useMobile();
|
||||
const isTablet = useTablet();
|
||||
const { editorConfig } = useEditorConfig();
|
||||
const dateFormat = useSettingsStore((store) => store.dateFormat);
|
||||
const timeFormat = useSettingsStore((store) => store.timeFormat);
|
||||
@@ -59,18 +49,6 @@ function TitleBox(props: TitleBoxProps) {
|
||||
[editorConfig.fontFamily]
|
||||
);
|
||||
|
||||
const updateFontSize = useCallback(
|
||||
(length: number) => {
|
||||
if (!inputRef.current) return;
|
||||
const fontSize = textLengthToFontSize(
|
||||
length,
|
||||
isMobile || isTablet ? 1.625 : 2.625
|
||||
);
|
||||
inputRef.current.style.fontSize = `${fontSize}em`;
|
||||
},
|
||||
[isMobile, isTablet]
|
||||
);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const session = useEditorStore.getState().getSession(id);
|
||||
if (!session || !("note" in session) || !session.note || !inputRef.current)
|
||||
@@ -80,17 +58,11 @@ function TitleBox(props: TitleBoxProps) {
|
||||
const { title } = session.note;
|
||||
if (inputRef.current.value === title) return;
|
||||
|
||||
withSelectionPersist(
|
||||
inputRef.current,
|
||||
(input) => (input.value = title || "")
|
||||
);
|
||||
updateFontSize(title?.length || 0);
|
||||
}, [sessionType, id, updateFontSize]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!inputRef.current) return;
|
||||
updateFontSize(inputRef.current.value.length);
|
||||
}, [isTablet, isMobile, updateFontSize]);
|
||||
withSelectionPersist(inputRef.current, (input) => {
|
||||
input.value = title || "";
|
||||
resizeTextarea(input);
|
||||
});
|
||||
}, [sessionType, id]);
|
||||
|
||||
useEffect(() => {
|
||||
const { unsubscribe } = AppEventManager.subscribe(
|
||||
@@ -105,11 +77,10 @@ function TitleBox(props: TitleBoxProps) {
|
||||
sessionId: string;
|
||||
}) => {
|
||||
if (!inputRef.current || sessionId !== id) return;
|
||||
withSelectionPersist(
|
||||
inputRef.current,
|
||||
(input) => (input.value = title)
|
||||
);
|
||||
updateFontSize(title.length);
|
||||
withSelectionPersist(inputRef.current, (input) => {
|
||||
input.value = title;
|
||||
resizeTextarea(input);
|
||||
});
|
||||
if (!preventSave) {
|
||||
pendingChanges.current = true;
|
||||
debouncedOnTitleChange(sessionId, sessionId, title, pendingChanges);
|
||||
@@ -120,10 +91,10 @@ function TitleBox(props: TitleBoxProps) {
|
||||
return () => {
|
||||
unsubscribe();
|
||||
};
|
||||
}, [updateFontSize, id]);
|
||||
}, [id]);
|
||||
|
||||
return (
|
||||
<Input
|
||||
<Textarea
|
||||
ref={inputRef}
|
||||
variant="clean"
|
||||
id="editor-title"
|
||||
@@ -132,32 +103,46 @@ function TitleBox(props: TitleBoxProps) {
|
||||
placeholder={strings.noteTitle()}
|
||||
readOnly={readonly}
|
||||
dir="auto"
|
||||
wrap="soft"
|
||||
rows={1}
|
||||
sx={{
|
||||
m: 0,
|
||||
p: 0,
|
||||
fontFamily,
|
||||
fontSize: ["1.625em", "1.625em", "2.625em"],
|
||||
fontWeight: "heading",
|
||||
width: "100%",
|
||||
fieldSizing: "content",
|
||||
whiteSpace: "pre-wrap",
|
||||
resize: "none",
|
||||
overflow: "hidden",
|
||||
"::placeholder": {
|
||||
color: "placeholder"
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
}
|
||||
}}
|
||||
onKeyUp={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
e.preventDefault();
|
||||
const context = useEditorManager.getState().getEditor(id);
|
||||
if (!context) return;
|
||||
context.editor?.focus({ scrollIntoView: true });
|
||||
}
|
||||
}}
|
||||
onChange={(e) => {
|
||||
if (!(e.target instanceof HTMLTextAreaElement)) return;
|
||||
pendingChanges.current = true;
|
||||
e.target.value = replaceDateTime(
|
||||
e.target.value,
|
||||
dateFormat,
|
||||
timeFormat
|
||||
);
|
||||
).replace(NEWLINE_STRIP_REGEX, " ");
|
||||
debouncedOnTitleChange(id, id, e.target.value, pendingChanges);
|
||||
updateFontSize(e.target.value.length);
|
||||
resizeTextarea(e.target);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -167,6 +152,15 @@ export default React.memo(TitleBox, (prevProps, nextProps) => {
|
||||
return prevProps.readonly === nextProps.readonly;
|
||||
});
|
||||
|
||||
function resizeTextarea(input: HTMLTextAreaElement) {
|
||||
requestAnimationFrame(() => {
|
||||
input.style.height = "auto";
|
||||
requestAnimationFrame(() => {
|
||||
input.style.height = input.scrollHeight + "px";
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function onTitleChange(
|
||||
noteId: string,
|
||||
title: string,
|
||||
@@ -178,16 +172,9 @@ function onTitleChange(
|
||||
|
||||
const debouncedOnTitleChange = debounceWithId(onTitleChange, 100);
|
||||
|
||||
function textLengthToFontSize(length: number, max: number) {
|
||||
const stepLength = 35;
|
||||
const decreaseStep = 0.5;
|
||||
const steps = length / stepLength;
|
||||
return Math.max(1.2, Math.min(max, max - steps * decreaseStep));
|
||||
}
|
||||
|
||||
function withSelectionPersist(
|
||||
input: HTMLInputElement,
|
||||
action: (input: HTMLInputElement) => void
|
||||
input: HTMLTextAreaElement,
|
||||
action: (input: HTMLTextAreaElement) => void
|
||||
) {
|
||||
const selection = {
|
||||
start: input.selectionStart,
|
||||
|
||||
@@ -220,7 +220,8 @@ import {
|
||||
mdiTagOutline,
|
||||
mdiChatQuestionOutline,
|
||||
mdiNoteRemoveOutline,
|
||||
mdiTabPlus
|
||||
mdiTabPlus,
|
||||
mdiRadar
|
||||
} from "@mdi/js";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Theme } from "@notesnook/theme";
|
||||
@@ -286,7 +287,7 @@ export type Icon = {
|
||||
path: string;
|
||||
};
|
||||
|
||||
function createIcon(path: string, rotate = false) {
|
||||
export function createIcon(path: string, rotate = false) {
|
||||
const NNIcon: Icon = function Icon(props) {
|
||||
const { sx, rotate: _rotate = rotate, size, ...restProps } = props;
|
||||
return (
|
||||
@@ -563,3 +564,4 @@ export const OpenInNew = createIcon(mdiOpenInNew);
|
||||
export const Coupon = createIcon(mdiTagOutline);
|
||||
export const Support = createIcon(mdiChatQuestionOutline);
|
||||
export const NewTab = createIcon(mdiTabPlus);
|
||||
export const Radar = createIcon(mdiRadar);
|
||||
|
||||
@@ -26,7 +26,12 @@ export type NotebookContext = {
|
||||
};
|
||||
export type Context =
|
||||
| {
|
||||
type: "tag" | "color";
|
||||
type: "tag";
|
||||
id: string;
|
||||
item?: Tag;
|
||||
}
|
||||
| {
|
||||
type: "color";
|
||||
id: string;
|
||||
}
|
||||
| NotebookContext
|
||||
|
||||
@@ -143,7 +143,7 @@ function Note(props: NoteProps) {
|
||||
background: "background"
|
||||
}}
|
||||
context={{ color, locked }}
|
||||
menuItems={menuItems}
|
||||
menuItems={noteMenuItems}
|
||||
onClick={() => useEditorStore.getState().openSession(note)}
|
||||
onMiddleClick={() =>
|
||||
useEditorStore.getState().openSession(note, { openInNewTab: true })
|
||||
@@ -320,9 +320,7 @@ const formats = [
|
||||
}
|
||||
] as const;
|
||||
|
||||
const notFullySyncedText =
|
||||
"Cannot perform this action because note is not fully synced.";
|
||||
const menuItems: (
|
||||
export const noteMenuItems: (
|
||||
note: NoteType,
|
||||
ids?: string[],
|
||||
context?: { color?: Color; locked?: boolean }
|
||||
|
||||
@@ -54,6 +54,7 @@ type PaneOptions = {
|
||||
nextSize?: number;
|
||||
initialSize: number;
|
||||
collapsed: boolean;
|
||||
expandedSize?: number;
|
||||
};
|
||||
|
||||
export type SplitPaneImperativeHandle = {
|
||||
@@ -97,7 +98,6 @@ export const SplitPane = React.forwardRef<
|
||||
const wrapSize = useRef(0);
|
||||
const childrenLength = childrenToArray(children).length;
|
||||
const autoSaveKey = autoSaveId ? `csp:${autoSaveId}` : undefined;
|
||||
const lastCollapsedPaneSize = useRef(0);
|
||||
|
||||
const { sizeName, splitPos, splitAxis } = useMemo(
|
||||
() =>
|
||||
@@ -151,6 +151,19 @@ export const SplitPane = React.forwardRef<
|
||||
else Config.set(`${autoSaveKey}-${id}`, v);
|
||||
}
|
||||
});
|
||||
Object.defineProperty(limits, "expandedSize", {
|
||||
get() {
|
||||
return Config.get(
|
||||
`${autoSaveKey}-${id}:expandedSize`,
|
||||
assertsSize(initialSize, wrapSize.current)
|
||||
);
|
||||
},
|
||||
set(v) {
|
||||
if (v === null || v === undefined || v === Infinity)
|
||||
Config.remove(`${autoSaveKey}-${id}:expandedSize`);
|
||||
else Config.set(`${autoSaveKey}-${id}:expandedSize`, v);
|
||||
}
|
||||
});
|
||||
}
|
||||
return limits;
|
||||
}) || [];
|
||||
@@ -257,15 +270,19 @@ export const SplitPane = React.forwardRef<
|
||||
() => {
|
||||
return {
|
||||
collapse: (index: number) => {
|
||||
paneSizes.current[index].collapsed = true;
|
||||
lastCollapsedPaneSize.current = paneSizes.current[index].size;
|
||||
const currentPane = paneSizes.current[index];
|
||||
if (currentPane.collapsed) return;
|
||||
currentPane.collapsed = true;
|
||||
currentPane.expandedSize = paneSizes.current[index].size;
|
||||
setSizes(paneSizes.current, wrapSize.current);
|
||||
},
|
||||
expand: (index: number) => {
|
||||
paneSizes.current[index].collapsed = false;
|
||||
paneSizes.current[index].size = lastCollapsedPaneSize.current
|
||||
? lastCollapsedPaneSize.current
|
||||
: paneSizes.current[index].initialSize;
|
||||
const currentPane = paneSizes.current[index];
|
||||
if (!currentPane.collapsed) return;
|
||||
currentPane.collapsed = false;
|
||||
currentPane.size =
|
||||
currentPane.expandedSize ?? currentPane.initialSize;
|
||||
currentPane.expandedSize = undefined;
|
||||
setSizes(paneSizes.current, wrapSize.current);
|
||||
},
|
||||
isCollapsed: (index: number) => {
|
||||
@@ -308,6 +325,8 @@ export const SplitPane = React.forwardRef<
|
||||
const nextPane = paneSizes.current[i + 1];
|
||||
const rightBorder = sashPosSizes.current[i + 2];
|
||||
|
||||
if (currentPane.collapsed) currentPane.collapsed = false;
|
||||
|
||||
if (currentPane.size + distanceX >= rightBorder)
|
||||
distanceX = rightBorder - currentPane.size;
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ import { showUpdateAvailableNotice } from "../../dialogs/confirm";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useVault } from "../../hooks/use-vault";
|
||||
import { useKeyStore } from "../../interfaces/key-store";
|
||||
import { STATUS_BAR_HEIGHT } from "../../common/constants";
|
||||
|
||||
function StatusBar() {
|
||||
const user = useUserStore((state) => state.user);
|
||||
@@ -64,7 +65,7 @@ function StatusBar() {
|
||||
justifyContent: "space-between",
|
||||
display: ["none", "flex", "flex"],
|
||||
flexShrink: 0,
|
||||
height: 24
|
||||
height: STATUS_BAR_HEIGHT
|
||||
}}
|
||||
px={2}
|
||||
>
|
||||
|
||||
@@ -60,7 +60,7 @@ function Tag(props: TagProps) {
|
||||
);
|
||||
}
|
||||
}}
|
||||
menuItems={menuItems}
|
||||
menuItems={tagMenuItems}
|
||||
onClick={() => {
|
||||
navigate(`/tags/${id}`);
|
||||
}}
|
||||
@@ -73,7 +73,7 @@ function Tag(props: TagProps) {
|
||||
}
|
||||
export default Tag;
|
||||
|
||||
const menuItems: (tag: TagType, ids?: string[]) => MenuItem[] = (
|
||||
export const tagMenuItems: (tag: TagType, ids?: string[]) => MenuItem[] = (
|
||||
tag,
|
||||
ids = []
|
||||
) => {
|
||||
|
||||
@@ -61,7 +61,7 @@ function TrashItem(props: TrashItemProps) {
|
||||
</Text>
|
||||
</Flex>
|
||||
}
|
||||
menuItems={menuItems}
|
||||
menuItems={trashMenuItems}
|
||||
onClick={async () => {
|
||||
if (item.itemType === "note")
|
||||
useEditorStore.getState().openSession(item);
|
||||
@@ -71,10 +71,10 @@ function TrashItem(props: TrashItemProps) {
|
||||
}
|
||||
export default TrashItem;
|
||||
|
||||
const menuItems: (item: TrashItemType, ids?: string[]) => MenuItem[] = (
|
||||
item,
|
||||
ids = []
|
||||
) => {
|
||||
export const trashMenuItems: (
|
||||
item: TrashItemType,
|
||||
ids?: string[]
|
||||
) => MenuItem[] = (item, ids = []) => {
|
||||
return [
|
||||
{
|
||||
type: "button",
|
||||
@@ -100,7 +100,7 @@ const menuItems: (item: TrashItemType, ids?: string[]) => MenuItem[] = (
|
||||
];
|
||||
};
|
||||
|
||||
async function deleteTrash(ids: string[]) {
|
||||
export async function deleteTrash(ids: string[]) {
|
||||
if (!(await showMultiPermanentDeleteConfirmation(ids.length))) return;
|
||||
await store.delete(...ids);
|
||||
showToast("success", `${pluralize(ids.length, "item")} permanently deleted`);
|
||||
|
||||
@@ -156,6 +156,7 @@ export const AddReminderDialog = DialogManager.register(
|
||||
testId="add-reminder-dialog"
|
||||
onClose={() => props.onClose(false)}
|
||||
sx={{ fontFamily: "body" }}
|
||||
width={600}
|
||||
positiveButton={{
|
||||
text: reminder ? strings.save() : strings.add(),
|
||||
disabled:
|
||||
|
||||
@@ -189,7 +189,7 @@ export const AttachmentsDialog = DialogManager.register(
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
<FlexScrollContainer>
|
||||
<FlexScrollContainer style={{ minHeight: 300 }}>
|
||||
<Flex sx={{ justifyContent: "space-between" }}>
|
||||
<Flex sx={{ gap: 1 }}>
|
||||
{TOOLBAR_ACTIONS.map((tool) => (
|
||||
|
||||
559
apps/web/src/dialogs/command-palette/command-palette-dialog.tsx
Normal file
559
apps/web/src/dialogs/command-palette/command-palette-dialog.tsx
Normal file
@@ -0,0 +1,559 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { debounce, usePromise } from "@notesnook/common";
|
||||
import { EVENTS, fuzzy, Note, Notebook, Reminder, Tag } from "@notesnook/core";
|
||||
import { Box, Button, Flex, Input, Text } from "@theme-ui/components";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
GroupedVirtuoso,
|
||||
GroupedVirtuosoHandle,
|
||||
CalculateViewLocation
|
||||
} from "react-virtuoso";
|
||||
import { db } from "../../common/db";
|
||||
import { BaseDialogProps, DialogManager } from "../../common/dialog-manager";
|
||||
import Dialog from "../../components/dialog";
|
||||
import { Cross } from "../../components/icons";
|
||||
import { CustomScrollbarsVirtualList } from "../../components/list-container";
|
||||
import { useEditorStore } from "../../stores/editor-store";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { isMac } from "../../utils/platform";
|
||||
import {
|
||||
getDefaultCommands,
|
||||
Command,
|
||||
getRecentCommands,
|
||||
resolveRecentCommand,
|
||||
commandActions,
|
||||
commandIcons,
|
||||
removeRecentCommand,
|
||||
addRecentCommand
|
||||
} from "./commands";
|
||||
|
||||
type CommandPaletteDialogProps = BaseDialogProps<boolean> & {
|
||||
isCommandMode: boolean;
|
||||
};
|
||||
|
||||
const COMMAND_PALETTE_STICKY_HEADER_HEIGHT = 32;
|
||||
|
||||
export const CommandPaletteDialog = DialogManager.register(
|
||||
function CommandPaletteDialog(props: CommandPaletteDialogProps) {
|
||||
const [selected, setSelected] = useState<number>(0);
|
||||
const [query, setQuery] = useState<string>("");
|
||||
const virtuosoRef = useRef<GroupedVirtuosoHandle>(null);
|
||||
const { search: dbSearch } = useDatabaseFuzzySearch();
|
||||
const defaultCommands = useRef<Command[]>();
|
||||
|
||||
const select = useCallback((index: number) => {
|
||||
setSelected(index);
|
||||
virtuosoRef.current?.scrollIntoView({
|
||||
index: index,
|
||||
calculateViewLocation: calculateCommandItemLocation
|
||||
});
|
||||
}, []);
|
||||
|
||||
const commands = usePromise(async () => {
|
||||
select(0);
|
||||
if (!defaultCommands.current)
|
||||
defaultCommands.current = await getDefaultCommands();
|
||||
const commands = props.isCommandMode
|
||||
? sortCommands(commandSearch(query, defaultCommands.current))
|
||||
: await dbSearch(query);
|
||||
const groups = commands.reduce(
|
||||
(acc, command) => {
|
||||
const index = acc.keys.indexOf(command.group);
|
||||
if (index === -1) {
|
||||
acc.keys.push(command.group);
|
||||
acc.counts.push(1);
|
||||
} else {
|
||||
acc.counts[index]++;
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
{ counts: [], keys: [] } as {
|
||||
counts: number[];
|
||||
keys: string[];
|
||||
}
|
||||
);
|
||||
return { commands, groups };
|
||||
}, [dbSearch, query]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={true}
|
||||
width={650}
|
||||
onClose={() => {
|
||||
props.onClose(false);
|
||||
}}
|
||||
noScroll
|
||||
sx={{
|
||||
fontFamily: "body"
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
variant="columnFill"
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
height: 400,
|
||||
'[data-viewport-type="element"]': {
|
||||
width: "calc(100% - 10px) !important",
|
||||
px: 1
|
||||
}
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (commands.status !== "fulfilled") return;
|
||||
if (e.key == "Enter") {
|
||||
e.preventDefault();
|
||||
const command = commands.value.commands[selected];
|
||||
if (!command) return;
|
||||
command.action?.(command, {
|
||||
openInNewTab: e.ctrlKey || e.metaKey
|
||||
});
|
||||
addRecentCommand(command);
|
||||
props.onClose(false);
|
||||
}
|
||||
if (e.key === "ArrowDown") {
|
||||
e.preventDefault();
|
||||
select(getNextCommandIndex(selected, commands.value.commands));
|
||||
}
|
||||
if (e.key === "ArrowUp") {
|
||||
e.preventDefault();
|
||||
select(
|
||||
getPreviousCommandIndex(selected, commands.value.commands)
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
autoFocus
|
||||
variant="clean"
|
||||
placeholder={
|
||||
props.isCommandMode
|
||||
? strings.executeACommand()
|
||||
: strings.searchForNotesNotebooksAndTags()
|
||||
}
|
||||
sx={{
|
||||
m: 0,
|
||||
marginTop: 0,
|
||||
mr: 0,
|
||||
px: 2,
|
||||
borderRadius: 0,
|
||||
borderBottom: "1px solid var(--border)"
|
||||
}}
|
||||
onChange={debounce((e) => {
|
||||
setQuery(e.target.value);
|
||||
}, 100)}
|
||||
/>
|
||||
<GroupedVirtuoso
|
||||
ref={virtuosoRef}
|
||||
style={{ overflow: "hidden" }}
|
||||
components={{
|
||||
Scroller: CustomScrollbarsVirtualList,
|
||||
Footer: () => (
|
||||
<div
|
||||
className="footer"
|
||||
style={{
|
||||
height: "5px"
|
||||
}}
|
||||
/>
|
||||
),
|
||||
EmptyPlaceholder: () => (
|
||||
<Text
|
||||
sx={{
|
||||
px: 1
|
||||
}}
|
||||
variant="subBody"
|
||||
>
|
||||
{query ? strings.noResultsFound(query) : ""}
|
||||
</Text>
|
||||
),
|
||||
TopItemList: ({ style, ...props }) => (
|
||||
<div
|
||||
className="top-item-list"
|
||||
{...props}
|
||||
style={{
|
||||
...style,
|
||||
width: "calc(100% - 10px) !important",
|
||||
padding: "0px 5px"
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
groupCounts={
|
||||
commands.status === "fulfilled"
|
||||
? commands.value.groups.counts
|
||||
: []
|
||||
}
|
||||
groupContent={(groupIndex) => {
|
||||
if (commands.status !== "fulfilled") return null;
|
||||
const label =
|
||||
commands.value.groups.keys[groupIndex] === "recent"
|
||||
? strings.recents()
|
||||
: commands.value.groups.keys[groupIndex];
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
py: 1,
|
||||
bg: "background",
|
||||
px: 1
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
variant="subBody"
|
||||
dangerouslySetInnerHTML={{ __html: label }}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}}
|
||||
itemContent={(index) => {
|
||||
if (commands.status !== "fulfilled") return null;
|
||||
|
||||
const command = commands.value.commands[index];
|
||||
if (!command) return null;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
key={index}
|
||||
onClick={(e) => {
|
||||
command.action?.(command, {
|
||||
openInNewTab: e.ctrlKey || e.metaKey
|
||||
});
|
||||
addRecentCommand(command);
|
||||
props.onClose(false);
|
||||
}}
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
display: "flex",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
py: 1,
|
||||
px: 1,
|
||||
borderRadius: "default",
|
||||
bg:
|
||||
index === selected
|
||||
? "background-selected"
|
||||
: "transparent",
|
||||
":hover:not(:disabled):not(:active)": {
|
||||
bg: index === selected ? "hover-selected" : "hover"
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
gap: 1
|
||||
}}
|
||||
>
|
||||
{command.icon && (
|
||||
<command.icon
|
||||
size={14}
|
||||
color={index === selected ? "icon-selected" : "icon"}
|
||||
/>
|
||||
)}
|
||||
<Text
|
||||
variant="body"
|
||||
sx={{
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "hidden"
|
||||
}}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: command.title
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
{command.group === "recent" && (
|
||||
<Button
|
||||
title={strings.removeFromRecents()}
|
||||
onClick={async (e) => {
|
||||
e.stopPropagation();
|
||||
removeRecentCommand(command.id);
|
||||
defaultCommands.current = await getDefaultCommands();
|
||||
commands.refresh();
|
||||
}}
|
||||
variant="secondary"
|
||||
sx={{
|
||||
bg: "transparent",
|
||||
p: "small",
|
||||
borderRadius: 100
|
||||
}}
|
||||
>
|
||||
<Cross size={12} />
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: "row",
|
||||
bg: "background-secondary",
|
||||
px: 2,
|
||||
py: 2,
|
||||
gap: 3,
|
||||
borderTop: "1px solid var(--border)",
|
||||
justifyContent: "end",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{getCommandPaletteHelp(props.isCommandMode).map((key) => {
|
||||
return (
|
||||
<Flex
|
||||
key={key.key}
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
gap: 1
|
||||
}}
|
||||
>
|
||||
<Text variant="body">{key.description}</Text>
|
||||
<Text variant="subBody">{key.key}</Text>
|
||||
</Flex>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
function getNextCommandIndex(selected: number, commands: Command[]) {
|
||||
return (selected + 1) % commands.length;
|
||||
}
|
||||
|
||||
function getPreviousCommandIndex(selected: number, commands: Command[]) {
|
||||
return (selected - 1 + commands.length) % commands.length;
|
||||
}
|
||||
|
||||
const RESULT_PREFIX_SUFFIX = {
|
||||
prefix: "<b style='color: var(--accent-foreground)'>",
|
||||
suffix: "</b>"
|
||||
};
|
||||
function commandSearch(query: string, commands: Command[]) {
|
||||
if (!query) return commands;
|
||||
return fuzzy(
|
||||
query,
|
||||
commands,
|
||||
(command) => command.id + command.group,
|
||||
{
|
||||
title: 10,
|
||||
group: 5
|
||||
},
|
||||
RESULT_PREFIX_SUFFIX
|
||||
);
|
||||
}
|
||||
|
||||
function useDatabaseFuzzySearch() {
|
||||
const notes = useRef<Note[]>();
|
||||
const notebooks = useRef<Notebook[]>();
|
||||
const tags = useRef<Tag[]>();
|
||||
const reminders = useRef<Reminder[]>();
|
||||
|
||||
const updateCollections = useCallback(async (force = false) => {
|
||||
if (force || !notes.current)
|
||||
notes.current = await db.notes.all
|
||||
.fields(["notes.id", "notes.title"])
|
||||
.items();
|
||||
if (force || !notebooks.current)
|
||||
notebooks.current = await db.notebooks.all
|
||||
.fields(["notebooks.id", "notebooks.title"])
|
||||
.items();
|
||||
if (force || !tags.current)
|
||||
tags.current = await db.tags.all
|
||||
.fields(["tags.id", "tags.title"])
|
||||
.items();
|
||||
if (force || !reminders.current)
|
||||
reminders.current = await db.reminders.all
|
||||
.fields(["reminders.id", "reminders.title"])
|
||||
.items();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const event = db.eventManager.subscribe(EVENTS.syncCompleted, async () => {
|
||||
await updateCollections(true);
|
||||
});
|
||||
return () => {
|
||||
event.unsubscribe();
|
||||
reminders.current = undefined;
|
||||
notebooks.current = undefined;
|
||||
tags.current = undefined;
|
||||
notes.current = undefined;
|
||||
};
|
||||
}, [updateCollections]);
|
||||
|
||||
const search = useCallback(
|
||||
async (query: string) => {
|
||||
if (!query) return await getSessionsAsCommands();
|
||||
|
||||
await updateCollections();
|
||||
|
||||
const list: Command[] = [];
|
||||
const collections: Record<
|
||||
"note" | "notebook" | "tag" | "reminder",
|
||||
{ id: string; title: string }[] | undefined
|
||||
> = {
|
||||
note: notes.current,
|
||||
notebook: notebooks.current,
|
||||
tag: tags.current,
|
||||
reminder: reminders.current
|
||||
};
|
||||
for (const _type in collections) {
|
||||
const type = _type as keyof typeof collections;
|
||||
const items = collections[type];
|
||||
if (!items) continue;
|
||||
for (const item of fuzzy(
|
||||
query,
|
||||
items,
|
||||
(item) => item.id,
|
||||
{
|
||||
title: 10
|
||||
},
|
||||
RESULT_PREFIX_SUFFIX
|
||||
)) {
|
||||
list.push({
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
group: strings.dataTypesPluralCamelCase[type](),
|
||||
type: type,
|
||||
action: commandActions[type],
|
||||
icon: commandIcons[type]
|
||||
});
|
||||
}
|
||||
}
|
||||
return list;
|
||||
},
|
||||
[updateCollections]
|
||||
);
|
||||
|
||||
return { search };
|
||||
}
|
||||
|
||||
async function getSessionsAsCommands() {
|
||||
const commands: Command[] = [];
|
||||
for (const recentCommand of getRecentCommands()) {
|
||||
if (recentCommand.type === "command") continue;
|
||||
|
||||
const resolvedCommand = await resolveRecentCommand(recentCommand);
|
||||
if (resolvedCommand) commands.push(resolvedCommand);
|
||||
}
|
||||
|
||||
const sessions = useEditorStore.getState().get().sessions;
|
||||
for (const session of sessions) {
|
||||
if (
|
||||
session.type === "new" ||
|
||||
commands.find((c) => c.id === session.note.id)
|
||||
)
|
||||
continue;
|
||||
|
||||
commands.push({
|
||||
id: session.note.id,
|
||||
title: session.note.title,
|
||||
group: strings.dataTypesPluralCamelCase.note(),
|
||||
type: "note" as const,
|
||||
action: commandActions.note,
|
||||
icon: commandIcons.note
|
||||
});
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* This override is required to ensure smooth scrolling when moving between
|
||||
* command palette items using the keyboard. Without this the selected item
|
||||
* is not visible when moving up/down in the command palette.
|
||||
*/
|
||||
const calculateCommandItemLocation: CalculateViewLocation = ({
|
||||
itemTop,
|
||||
itemBottom,
|
||||
viewportTop,
|
||||
viewportBottom,
|
||||
locationParams: { behavior, align, ...rest }
|
||||
}) => {
|
||||
const topOffset = viewportTop + COMMAND_PALETTE_STICKY_HEADER_HEIGHT;
|
||||
const itemInView = itemTop >= topOffset && itemBottom <= viewportBottom;
|
||||
if (itemInView) return null;
|
||||
return {
|
||||
...rest,
|
||||
offset:
|
||||
itemBottom > viewportBottom
|
||||
? itemBottom - viewportBottom
|
||||
: itemTop - topOffset,
|
||||
behavior,
|
||||
align:
|
||||
align ??
|
||||
(itemBottom > viewportBottom
|
||||
? "end"
|
||||
: itemTop < topOffset
|
||||
? "start"
|
||||
: "center")
|
||||
};
|
||||
};
|
||||
|
||||
function getCommandPaletteHelp(isCommandMode: boolean) {
|
||||
return [
|
||||
{
|
||||
key: "⏎",
|
||||
description: isCommandMode ? strings.execute() : strings.open()
|
||||
},
|
||||
...(isCommandMode
|
||||
? [
|
||||
{
|
||||
key: isMac() ? "⌘P" : "Ctrl+P",
|
||||
description: strings.quickOpen()
|
||||
}
|
||||
]
|
||||
: [
|
||||
{
|
||||
key: isMac() ? "⌘⏎" : "Ctrl+⏎",
|
||||
description: strings.openInNewTab()
|
||||
},
|
||||
{
|
||||
key: isMac() ? "⌘K" : "Ctrl+K",
|
||||
description: strings.commandPalette()
|
||||
}
|
||||
])
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* commands need to be sorted wrt groups,
|
||||
* meaning commands of same group should be next to each other,
|
||||
* and recent commands should be at the top
|
||||
*/
|
||||
function sortCommands(commands: Command[]) {
|
||||
const recent: Command[] = [];
|
||||
const sortedWrtGroups: Command[][] = [];
|
||||
for (const command of commands) {
|
||||
const group = command.group;
|
||||
if (group === "recent") {
|
||||
recent.push(command);
|
||||
continue;
|
||||
}
|
||||
const index = sortedWrtGroups.findIndex((c) => c[0].group === group);
|
||||
if (index === -1) {
|
||||
sortedWrtGroups.push([command]);
|
||||
} else {
|
||||
sortedWrtGroups[index].push(command);
|
||||
}
|
||||
}
|
||||
return recent.concat(sortedWrtGroups.flat());
|
||||
}
|
||||
529
apps/web/src/dialogs/command-palette/commands.ts
Normal file
529
apps/web/src/dialogs/command-palette/commands.ts
Normal file
@@ -0,0 +1,529 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { db } from "../../common/db";
|
||||
import {
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
ArrowTopRight,
|
||||
createIcon,
|
||||
Icon,
|
||||
Plus,
|
||||
Radar,
|
||||
Redo,
|
||||
Undo,
|
||||
Notebook as NotebookIcon,
|
||||
Note as NoteIcon,
|
||||
Reminder as ReminderIcon,
|
||||
Tag as TagIcon,
|
||||
FocusMode,
|
||||
NormalMode
|
||||
} from "../../components/icons";
|
||||
import { trashMenuItems } from "../../components/trash-item";
|
||||
import { hashNavigate, navigate } from "../../navigation";
|
||||
import { useEditorStore } from "../../stores/editor-store";
|
||||
import { useStore as useNoteStore } from "../../stores/note-store";
|
||||
import { useStore as useAppStore } from "../../stores/app-store";
|
||||
import { useStore as useThemeStore } from "../../stores/theme-store";
|
||||
import { AttachmentsDialog } from "../attachments-dialog";
|
||||
import { CreateColorDialog } from "../create-color-dialog";
|
||||
import { noteMenuItems } from "../../components/note";
|
||||
import { MenuItem } from "@notesnook/ui";
|
||||
import { notebookMenuItems } from "../../components/notebook";
|
||||
import { tagMenuItems } from "../../components/tag";
|
||||
import { useEditorManager } from "../../components/editor/manager";
|
||||
import Config from "../../utils/config";
|
||||
|
||||
export interface BaseCommand {
|
||||
id: string;
|
||||
type: "command" | "note" | "notebook" | "tag" | "reminder";
|
||||
}
|
||||
export interface Command extends BaseCommand {
|
||||
title: string;
|
||||
group: string;
|
||||
icon?: Icon;
|
||||
action: (command: Command, options?: { openInNewTab?: boolean }) => void;
|
||||
excludeFromRecents?: boolean;
|
||||
}
|
||||
|
||||
export interface RecentCommand extends BaseCommand {
|
||||
id: string;
|
||||
group: "recent";
|
||||
}
|
||||
|
||||
export const commandActions = {
|
||||
command: (command: Command) => getCommandById(command.id)?.action(command),
|
||||
note: (command: Command, options?: { openInNewTab?: boolean }) =>
|
||||
useEditorStore.getState().openSession(command.id, options),
|
||||
notebook: (command: Command) => navigate(`/notebooks/${command.id}`),
|
||||
tag: (command: Command) => navigate(`/tags/${command.id}`),
|
||||
reminder: (command: Command) => hashNavigate(`/reminders/${command.id}/edit`)
|
||||
};
|
||||
|
||||
export const commandIcons = {
|
||||
command: (command: BaseCommand) => getCommandById(command.id)?.icon,
|
||||
note: NoteIcon,
|
||||
notebook: NotebookIcon,
|
||||
tag: TagIcon,
|
||||
reminder: ReminderIcon
|
||||
};
|
||||
|
||||
const RECENT_COMMANDS_SAVE_KEY = "commandPalette:recent";
|
||||
|
||||
const staticCommands: Command[] = [
|
||||
{
|
||||
id: "notes",
|
||||
title: strings.dataTypesPluralCamelCase.note(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => navigate("/"),
|
||||
group: strings.navigate(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "notebooks",
|
||||
title: strings.dataTypesPluralCamelCase.notebook(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => navigate("/notebooks"),
|
||||
group: strings.navigate(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "tags",
|
||||
title: strings.dataTypesPluralCamelCase.tag(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => navigate("/tags"),
|
||||
group: strings.navigate(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "favorites",
|
||||
title: strings.dataTypesPluralCamelCase.favorite(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => navigate("/favorites"),
|
||||
group: strings.navigate(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "reminders",
|
||||
title: strings.dataTypesPluralCamelCase.reminder(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => navigate("/reminders"),
|
||||
group: strings.navigate(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "monographs",
|
||||
title: strings.dataTypesPluralCamelCase.monograph(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => navigate("/monographs"),
|
||||
group: strings.navigate(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "trash",
|
||||
title: strings.trash(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => navigate("/trash"),
|
||||
group: strings.navigate(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "settings",
|
||||
title: strings.settings(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => hashNavigate("/settings", { replace: true }),
|
||||
group: strings.navigate(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "help",
|
||||
title: strings.helpAndSupport(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => (window.location.href = "https://help.notesnook.com"),
|
||||
group: strings.navigate(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "attachment-manager",
|
||||
title: strings.attachmentManager(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => AttachmentsDialog.show({}),
|
||||
group: strings.navigate(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "new-note",
|
||||
title: strings.newNote(),
|
||||
icon: Plus,
|
||||
action: () => useEditorStore.getState().newSession(),
|
||||
group: strings.create(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "new-notebook",
|
||||
title: strings.newNotebook(),
|
||||
icon: Plus,
|
||||
action: () => hashNavigate("/notebooks/create", { replace: true }),
|
||||
group: strings.create(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "new-tag",
|
||||
title: strings.newTag(),
|
||||
icon: Plus,
|
||||
action: () => hashNavigate("/tags/create", { replace: true }),
|
||||
group: strings.create(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "new-reminder",
|
||||
title: strings.newReminder(),
|
||||
icon: Plus,
|
||||
action: () => hashNavigate(`/reminders/create`, { replace: true }),
|
||||
group: strings.create(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "new-color",
|
||||
title: strings.newColor(),
|
||||
icon: Plus,
|
||||
action: () => CreateColorDialog.show(true),
|
||||
group: strings.create(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "toggle-theme",
|
||||
title: strings.toggleTheme(),
|
||||
icon: Radar,
|
||||
action: () => useThemeStore.getState().toggleColorScheme(),
|
||||
group: strings.general(),
|
||||
type: "command"
|
||||
}
|
||||
];
|
||||
|
||||
export async function getDefaultCommands(): Promise<Command[]> {
|
||||
const recentCommands: Command[] = [];
|
||||
for (const recentCommand of getRecentCommands()) {
|
||||
if (recentCommand.type !== "command") continue;
|
||||
const resolvedCommand = await resolveRecentCommand(recentCommand);
|
||||
if (resolvedCommand) recentCommands.push(resolvedCommand);
|
||||
}
|
||||
|
||||
return [
|
||||
...recentCommands,
|
||||
...(await getActiveNoteCommands()),
|
||||
...(await getActiveNotebookCommands()),
|
||||
...(await getActiveTagCommands()),
|
||||
...getEditorCommands(),
|
||||
...staticCommands
|
||||
];
|
||||
}
|
||||
|
||||
export function getCommandById(id: string): Command | undefined {
|
||||
return (
|
||||
staticCommands.find((command) => command.id === id) ||
|
||||
getEditorCommands().find((command) => command.id === id)
|
||||
);
|
||||
}
|
||||
|
||||
export async function resolveRecentCommand(
|
||||
recentCommand: RecentCommand
|
||||
): Promise<Command | undefined> {
|
||||
const title = await getCommandTitle(recentCommand);
|
||||
const action = commandActions[recentCommand.type];
|
||||
if (!title || !action) return;
|
||||
|
||||
return {
|
||||
id: recentCommand.id,
|
||||
group: "recent",
|
||||
title: title,
|
||||
type: recentCommand.type,
|
||||
icon:
|
||||
recentCommand.type === "command"
|
||||
? commandIcons.command(recentCommand)
|
||||
: commandIcons[recentCommand.type],
|
||||
action: action
|
||||
};
|
||||
}
|
||||
|
||||
export function getRecentCommands() {
|
||||
return Config.get<RecentCommand[]>(RECENT_COMMANDS_SAVE_KEY, []);
|
||||
}
|
||||
|
||||
export function addRecentCommand(command: Command) {
|
||||
if (command.excludeFromRecents) return;
|
||||
const commands = getRecentCommands();
|
||||
const index = commands.findIndex((c) => c.id === command.id);
|
||||
if (index > -1) commands.splice(index, 1);
|
||||
commands.unshift({
|
||||
group: "recent",
|
||||
id: command.id,
|
||||
type: command.type
|
||||
});
|
||||
Config.set(RECENT_COMMANDS_SAVE_KEY, commands);
|
||||
}
|
||||
|
||||
export function removeRecentCommand(id: string) {
|
||||
const commands = getRecentCommands();
|
||||
const index = commands.findIndex((c) => c.id === id);
|
||||
if (index > -1) {
|
||||
commands.splice(index, 1);
|
||||
Config.set(RECENT_COMMANDS_SAVE_KEY, commands);
|
||||
}
|
||||
}
|
||||
|
||||
async function getActiveNoteCommands(): Promise<Command[]> {
|
||||
const note = useEditorStore.getState().getActiveNote();
|
||||
if (!note) return [];
|
||||
const group = strings.actionsForNote(note.title);
|
||||
|
||||
const commands: Command[] = [];
|
||||
|
||||
const menuItems =
|
||||
note.type !== "trash"
|
||||
? noteMenuItems(note, [note.id], {
|
||||
locked: !!(
|
||||
await db
|
||||
.sql()
|
||||
.selectFrom("content")
|
||||
.where("noteId", "in", [note.id])
|
||||
.select(["noteId", "locked"])
|
||||
.executeTakeFirst()
|
||||
)?.locked
|
||||
})
|
||||
: trashMenuItems(note);
|
||||
|
||||
for (const menuItem of menuItems) {
|
||||
commands.push(...menuItemToCommands(menuItem, group, "active-note"));
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
async function getActiveNotebookCommands() {
|
||||
const context = useNoteStore.getState().context;
|
||||
if (context?.type !== "notebook") return [];
|
||||
const notebook = await db.notebooks.notebook(context.id);
|
||||
if (!notebook) return [];
|
||||
const group = strings.actionsForNotebook(notebook.title);
|
||||
|
||||
const commands: Command[] = [];
|
||||
|
||||
const menuItems = notebookMenuItems(notebook, [notebook.id]);
|
||||
for (const menuItem of menuItems) {
|
||||
commands.push(...menuItemToCommands(menuItem, group, "active-notebook"));
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
async function getActiveTagCommands() {
|
||||
const context = useNoteStore.getState().context;
|
||||
if (context?.type !== "tag") return [];
|
||||
const tag = await db.tags.tag(context.id);
|
||||
if (!tag) return [];
|
||||
const group = strings.actionsForTag(tag.title);
|
||||
const commands: Command[] = [];
|
||||
|
||||
const menuItems = tagMenuItems(tag, [tag.id]);
|
||||
for (const menuItem of menuItems) {
|
||||
commands.push(...menuItemToCommands(menuItem, group, "active-tag"));
|
||||
}
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
function getEditorCommands(): Command[] {
|
||||
const session = useEditorStore.getState().getActiveSession();
|
||||
if (!session) return [];
|
||||
const editor = useEditorManager.getState().editors[session.id];
|
||||
const isFocusMode = useAppStore.getState().isFocusMode;
|
||||
|
||||
const commands: Command[] = [
|
||||
{
|
||||
id: "new-tab",
|
||||
title: strings.newTab(),
|
||||
icon: Plus,
|
||||
action: () => useEditorStore.getState().addTab(),
|
||||
group: strings.editor(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "next-tab",
|
||||
title: strings.nextTab(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => useEditorStore.getState().focusNextTab(),
|
||||
group: strings.editor(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "previous-tab",
|
||||
title: strings.previousTab(),
|
||||
icon: ArrowTopRight,
|
||||
action: () => useEditorStore.getState().focusPreviousTab(),
|
||||
group: strings.editor(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "close-tab",
|
||||
title: strings.closeCurrentTab(),
|
||||
icon: Radar,
|
||||
action: () => useEditorStore.getState().closeActiveTab(),
|
||||
group: strings.editor(),
|
||||
type: "command"
|
||||
},
|
||||
{
|
||||
id: "close-all-tabs",
|
||||
title: strings.closeAllTabs(),
|
||||
icon: Radar,
|
||||
action: () => useEditorStore.getState().closeAllTabs(),
|
||||
group: strings.editor(),
|
||||
type: "command"
|
||||
}
|
||||
];
|
||||
|
||||
if (session.type !== "readonly" && (editor.canUndo || editor.canRedo)) {
|
||||
commands.push(
|
||||
{
|
||||
id: "undo",
|
||||
title: strings.undo(),
|
||||
icon: Undo,
|
||||
action: () => editor.editor?.undo(),
|
||||
group: strings.editor(),
|
||||
type: "command",
|
||||
excludeFromRecents: true
|
||||
},
|
||||
{
|
||||
id: "redo",
|
||||
title: strings.redo(),
|
||||
icon: Redo,
|
||||
action: () => editor.editor?.redo(),
|
||||
group: strings.editor(),
|
||||
type: "command",
|
||||
excludeFromRecents: true
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
if (useEditorStore.getState().canGoBack) {
|
||||
commands.push({
|
||||
id: "go-back-in-tab",
|
||||
title: strings.goBackInTab(),
|
||||
icon: ArrowLeft,
|
||||
action: () => useEditorStore.getState().goBack(),
|
||||
group: strings.editor(),
|
||||
type: "command"
|
||||
});
|
||||
}
|
||||
|
||||
if (useEditorStore.getState().canGoForward) {
|
||||
commands.push({
|
||||
id: "go-forward-in-tab",
|
||||
title: strings.goForwardInTab(),
|
||||
icon: ArrowRight,
|
||||
action: () => useEditorStore.getState().goForward(),
|
||||
group: strings.editor(),
|
||||
type: "command"
|
||||
});
|
||||
}
|
||||
|
||||
commands.push({
|
||||
id: "toggle-focus-mode",
|
||||
title: strings.toggleFocusMode(),
|
||||
icon: isFocusMode ? FocusMode : NormalMode,
|
||||
action: () => useAppStore.getState().toggleFocusMode(),
|
||||
group: strings.editor(),
|
||||
type: "command"
|
||||
});
|
||||
|
||||
return commands;
|
||||
}
|
||||
|
||||
function menuItemToCommands(
|
||||
menuItem: MenuItem,
|
||||
group: string,
|
||||
idSuffix?: string
|
||||
): Command[] {
|
||||
if (menuItem.type !== "button" || menuItem.isHidden || menuItem.isDisabled)
|
||||
return [];
|
||||
|
||||
const commands: Command[] = [];
|
||||
if (menuItem.menu) {
|
||||
for (const item of menuItem.menu.items) {
|
||||
commands.push(
|
||||
...menuItemToCommands(item, group, idSuffix).map((c) => {
|
||||
c.title = `${menuItem.title}: ${c.title}`;
|
||||
c.id = `${menuItem.key}-${c.id}`;
|
||||
return c;
|
||||
})
|
||||
);
|
||||
}
|
||||
return commands;
|
||||
}
|
||||
|
||||
if (!menuItem.onClick) return commands;
|
||||
const _Icon = menuItem.icon ? createIcon(menuItem.icon) : undefined;
|
||||
commands.push({
|
||||
id: idSuffix ? `${menuItem.key}-${idSuffix}` : menuItem.key,
|
||||
title: menuItem.title,
|
||||
action: menuItem.onClick,
|
||||
group,
|
||||
type: "command",
|
||||
icon: _Icon,
|
||||
excludeFromRecents: true
|
||||
});
|
||||
return commands;
|
||||
}
|
||||
|
||||
async function getCommandTitle({ id, type }: RecentCommand) {
|
||||
switch (type) {
|
||||
case "command": {
|
||||
const command = getCommandById(id);
|
||||
if (!command || typeof command.title !== "string") return;
|
||||
return command.title;
|
||||
}
|
||||
case "note": {
|
||||
const note = (await db.notes.all.fields(["notes.title"]).items([id])).at(
|
||||
0
|
||||
);
|
||||
if (!note) return;
|
||||
return note.title;
|
||||
}
|
||||
case "notebook": {
|
||||
const notebook = (
|
||||
await db.notebooks.all.fields(["notebooks.title"]).items([id])
|
||||
).at(0);
|
||||
if (!notebook) return;
|
||||
return notebook.title;
|
||||
}
|
||||
case "tag": {
|
||||
const tag = (await db.tags.all.fields(["tags.title"]).items([id])).at(0);
|
||||
if (!tag) return;
|
||||
return tag.title;
|
||||
}
|
||||
case "reminder": {
|
||||
const reminder = (
|
||||
await db.reminders.all.fields(["reminders.title"]).items([id])
|
||||
).at(0);
|
||||
if (!reminder) return;
|
||||
return reminder.title;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
apps/web/src/dialogs/command-palette/index.ts
Normal file
20
apps/web/src/dialogs/command-palette/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export * from "./command-palette-dialog";
|
||||
@@ -93,9 +93,15 @@ const features: Record<FeatureKeys, Feature> = {
|
||||
]
|
||||
: [
|
||||
{
|
||||
title: "Better tabs",
|
||||
subtitle:
|
||||
"Tabs have been overhauled to be more predictable and easier to use. Give it a try! We are sure you'll love it."
|
||||
title: "Release tracks",
|
||||
subtitle: (
|
||||
<>
|
||||
Switch to the beta version to try out upcoming new features and
|
||||
provide us with feedback. Switch back to stable anytime without
|
||||
losing any data. To change release tracks, go to Settings {">"}
|
||||
About and select the desired release track.
|
||||
</>
|
||||
)
|
||||
}
|
||||
],
|
||||
cta: {
|
||||
@@ -128,12 +134,7 @@ export const FeatureDialog = DialogManager.register(function FeatureDialog(
|
||||
) {
|
||||
const { featureName, onClose } = props;
|
||||
const feature = features[featureName];
|
||||
|
||||
useEffect(() => {
|
||||
if (!feature || (feature.shouldShow && !feature.shouldShow())) {
|
||||
onClose(false);
|
||||
}
|
||||
}, [feature, onClose]);
|
||||
const isShown = feature && feature.shouldShow && feature.shouldShow();
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
@@ -141,6 +142,9 @@ export const FeatureDialog = DialogManager.register(function FeatureDialog(
|
||||
title={feature.title}
|
||||
description={feature.subtitle}
|
||||
textAlignment="center"
|
||||
onOpen={() => {
|
||||
if (!isShown) onClose(false);
|
||||
}}
|
||||
positiveButton={{
|
||||
text: (
|
||||
<Flex>
|
||||
|
||||
@@ -186,7 +186,9 @@ export const BehaviourSettings: SettingsGroup[] = [
|
||||
description: strings.automaticUpdatesDesc(),
|
||||
onStateChange: (listener) =>
|
||||
useSettingStore.subscribe((s) => s.autoUpdates, listener),
|
||||
isHidden: () => useSettingStore.getState().isFlatpak,
|
||||
isHidden: () =>
|
||||
useSettingStore.getState().isFlatpak ||
|
||||
useSettingStore.getState().isSnap,
|
||||
components: [
|
||||
{
|
||||
type: "toggle",
|
||||
|
||||
@@ -275,6 +275,7 @@ function SettingsSideBar(props: SettingsSideBarProps) {
|
||||
<Input
|
||||
id="search"
|
||||
name="search"
|
||||
autoFocus
|
||||
placeholder={strings.search()}
|
||||
data-test-id="settings-search"
|
||||
sx={{
|
||||
|
||||
@@ -27,6 +27,9 @@ import { clearLogs, downloadLogs } from "../../utils/logger";
|
||||
import { useAutoUpdateStore } from "../../hooks/use-auto-updater";
|
||||
import { IssueDialog } from "../issue-dialog";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { desktop } from "../../common/desktop-bridge";
|
||||
import { TaskManager } from "../../common/task-manager";
|
||||
import { useStore as useSettingStore } from "../../stores/setting-store";
|
||||
|
||||
export const AboutSettings: SettingsGroup[] = [
|
||||
{
|
||||
@@ -73,6 +76,76 @@ export const AboutSettings: SettingsGroup[] = [
|
||||
];
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "release-track",
|
||||
title: strings.releaseTrack(),
|
||||
description: strings.releaseTrackDesc(),
|
||||
isHidden: () =>
|
||||
useSettingStore.getState().isFlatpak ||
|
||||
useSettingStore.getState().isSnap,
|
||||
components: [
|
||||
{
|
||||
type: "dropdown",
|
||||
options: [
|
||||
{
|
||||
title: strings.stable(),
|
||||
value: "stable"
|
||||
},
|
||||
{
|
||||
title: strings.beta(),
|
||||
value: "beta"
|
||||
}
|
||||
],
|
||||
selectedOption: async () => {
|
||||
if (IS_DESKTOP_APP)
|
||||
return (
|
||||
(await desktop?.updater.releaseTrack.query()) || "stable"
|
||||
);
|
||||
|
||||
return (
|
||||
document.cookie
|
||||
.split("; ")
|
||||
.find((row) => row.startsWith("release-track="))
|
||||
?.split("=")[1] || "stable"
|
||||
);
|
||||
},
|
||||
async onSelectionChanged(value) {
|
||||
if (IS_DESKTOP_APP) {
|
||||
return await desktop?.updater.changeReleaseTrack.mutate({
|
||||
track: value
|
||||
});
|
||||
}
|
||||
const registration =
|
||||
await navigator.serviceWorker.getRegistration();
|
||||
if (!registration) return;
|
||||
const worker =
|
||||
registration.active ||
|
||||
registration.waiting ||
|
||||
registration.installing;
|
||||
if (!worker) return;
|
||||
if (worker.state === "activated") {
|
||||
await switchReleaseTrack(value);
|
||||
} else {
|
||||
await TaskManager.startTask({
|
||||
type: "modal",
|
||||
title: "Changing release track",
|
||||
subtitle:
|
||||
"Please wait while we switch to the new release track...",
|
||||
action: () =>
|
||||
new Promise<void>((resolve) => {
|
||||
worker.onstatechange = async function () {
|
||||
if (this.state === "activated") {
|
||||
await switchReleaseTrack(value);
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "source-code",
|
||||
title: strings.sourceCode(),
|
||||
@@ -338,3 +411,12 @@ export const SupportSettings: SettingsGroup[] = [
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
async function switchReleaseTrack(track: string) {
|
||||
const registration = await navigator.serviceWorker.getRegistration();
|
||||
if (!registration) return;
|
||||
await registration.unregister();
|
||||
for (const key of await caches.keys()) await caches.delete(key);
|
||||
document.cookie = `release-track=${track}; Secure; Path=/`;
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
@@ -168,6 +168,11 @@ const routes = defineRoutes({
|
||||
title: async () => {
|
||||
const tag = await db.tags.tag(tagId);
|
||||
if (!tag) return;
|
||||
notestore.setContext({
|
||||
type: "tag",
|
||||
id: tagId,
|
||||
item: tag
|
||||
});
|
||||
return `#${tag.title}`;
|
||||
},
|
||||
component: Notes,
|
||||
|
||||
@@ -36,7 +36,7 @@ import { Text } from "@theme-ui/components";
|
||||
import { EV, EVENTS } from "@notesnook/core";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export async function startApp() {
|
||||
export async function startApp(children?: React.ReactNode) {
|
||||
const rootElement = document.getElementById("root");
|
||||
if (!rootElement) return;
|
||||
const root = createRoot(rootElement);
|
||||
@@ -72,6 +72,7 @@ export async function startApp() {
|
||||
routeProps={props}
|
||||
/>
|
||||
</AppLock>
|
||||
{children}
|
||||
</BaseThemeProvider>
|
||||
</GlobalErrorHandler>
|
||||
</ErrorBoundary>
|
||||
|
||||
@@ -99,7 +99,8 @@ self.addEventListener("message", (event) => {
|
||||
event.source.postMessage({
|
||||
type: data.type,
|
||||
version: APP_VERSION,
|
||||
hash: GIT_HASH
|
||||
hash: GIT_HASH,
|
||||
isBeta: IS_BETA
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -41,6 +41,9 @@ import {
|
||||
import { NetworkCheck } from "../utils/network-check";
|
||||
import { Color, Notebook, Tag } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useEditorStore } from "./editor-store";
|
||||
import { useEditorManager } from "../components/editor/manager";
|
||||
import { exitFullscreen } from "../utils/fullscreen";
|
||||
|
||||
type SyncState =
|
||||
| "synced"
|
||||
@@ -171,6 +174,10 @@ class AppStore extends BaseStore<AppStore> {
|
||||
|
||||
toggleFocusMode = () => {
|
||||
this.set((state) => (state.isFocusMode = !state.isFocusMode));
|
||||
if (document.fullscreenElement) exitFullscreen();
|
||||
const session = useEditorStore.getState().getActiveSession();
|
||||
const editor = session && useEditorManager.getState().getEditor(session.id);
|
||||
if (editor) editor.editor?.focus();
|
||||
};
|
||||
|
||||
toggleAutoSync = () => {
|
||||
|
||||
@@ -135,7 +135,10 @@ class AttachmentStore extends BaseStore<AttachmentStore> {
|
||||
if (await db.attachments.remove(attachment.hash, false)) {
|
||||
await this.get().refresh();
|
||||
await useNoteStore.getState().refresh();
|
||||
useEditorStore.getState().closeSessions(...linkedNotes);
|
||||
const sessions = linkedNotes
|
||||
.map((id) => useEditorStore.getState().getSessionsForNote(id))
|
||||
.flat();
|
||||
useEditorStore.getState().closeTabs(...sessions.map((s) => s.id));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
@@ -414,9 +414,13 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
event.item.fromType === "color" &&
|
||||
event.item.toType === "note"
|
||||
) {
|
||||
updateSession(event.item.toId, undefined, {
|
||||
color: event.item.fromId
|
||||
});
|
||||
for (const session of sessions) {
|
||||
if ("color" in session && session.note.id === event.item.toId) {
|
||||
updateSession(session.id, undefined, {
|
||||
color: event.item.fromId
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
event.type === "unlink" &&
|
||||
((event.reference.type === "color" &&
|
||||
@@ -467,9 +471,13 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
event.item.fromType === "tag" &&
|
||||
event.item.toType === "note"
|
||||
) {
|
||||
updateSession(event.item.toId, undefined, {
|
||||
tags: await db.notes.tags(event.item.toId)
|
||||
});
|
||||
for (const session of sessions) {
|
||||
if ("note" in session && session.note.id === event.item.toId) {
|
||||
updateSession(session.id, undefined, {
|
||||
tags: await db.notes.tags(event.item.toId)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (event.collection === "tags") {
|
||||
if (event.type === "update") {
|
||||
@@ -595,9 +603,18 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
|
||||
if (!oldContent || !currentContent) return;
|
||||
|
||||
const { getSession, addSession, sessions, activeTabId, tabs } = this.get();
|
||||
const {
|
||||
getSession,
|
||||
addSession,
|
||||
sessions,
|
||||
activeTabId,
|
||||
getActiveTab,
|
||||
tabs
|
||||
} = this.get();
|
||||
|
||||
const tabId = activeTabId ?? this.addTab();
|
||||
const tabId = getActiveTab()?.pinned
|
||||
? this.addTab(getId())
|
||||
: activeTabId || this.addTab(getId());
|
||||
const tab = tabs.find((t) => t.id === tabId);
|
||||
const activeSession = tab && getSession(tab.sessionId);
|
||||
const oldSession = sessions.find(
|
||||
@@ -650,15 +667,19 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
tabs,
|
||||
activateSession,
|
||||
activeTabId,
|
||||
getActiveTab,
|
||||
rehydrateSession,
|
||||
getTabsForNote,
|
||||
addTab
|
||||
} = this.get();
|
||||
const noteId = typeof noteOrId === "string" ? noteOrId : noteOrId.id;
|
||||
const oldTabForNote = options.force ? null : getTabsForNote(noteId).at(0);
|
||||
const tabId = options.openInNewTab
|
||||
? addTab(getId())
|
||||
: oldTabForNote?.id || activeTabId || addTab(getId());
|
||||
const activeTab = getActiveTab();
|
||||
const tabId =
|
||||
// if a tab is pinned then always open a new tab.
|
||||
options.openInNewTab || activeTab?.pinned
|
||||
? addTab(getId())
|
||||
: oldTabForNote?.id || activeTabId || addTab(getId());
|
||||
|
||||
const tab = tabs.find((t) => t.id === tabId);
|
||||
const activeSession = tab && getSession(tab.sessionId);
|
||||
@@ -889,8 +910,8 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
ignoreEdit?: boolean;
|
||||
}
|
||||
) => {
|
||||
const currentSession = this.getSession(id, ["new", "default"]);
|
||||
if (!currentSession) return;
|
||||
const session = this.getSession(id, ["new", "default"]);
|
||||
if (!session) return;
|
||||
|
||||
// do not allow saving of readonly session
|
||||
if (partial.note?.readonly) return;
|
||||
@@ -898,16 +919,22 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
await saveMutex.runExclusive(async () => {
|
||||
this.setSaveState(id, 0);
|
||||
try {
|
||||
// Get session again as it might have changed to default.
|
||||
const currentSession =
|
||||
session.type === "new"
|
||||
? this.getSession(id, ["new", "default"])
|
||||
: session;
|
||||
|
||||
if (!currentSession) return;
|
||||
const sessionId = getSessionId(currentSession);
|
||||
const noteId =
|
||||
("note" in currentSession
|
||||
? currentSession.note.id
|
||||
: partial.note?.id) || id;
|
||||
let noteId =
|
||||
"note" in currentSession ? currentSession.note.id : partial.note?.id;
|
||||
|
||||
if (isLockedSession(currentSession) && partial.content) {
|
||||
logger.debug("Saving locked content", { id });
|
||||
if (!noteId) return;
|
||||
logger.debug("Saving locked content", { noteId });
|
||||
|
||||
await db.vault.save({
|
||||
noteId = await db.vault.save({
|
||||
content: partial.content,
|
||||
sessionId,
|
||||
id: noteId
|
||||
@@ -915,10 +942,10 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
} else {
|
||||
if (partial.content)
|
||||
logger.debug("Saving content", {
|
||||
id,
|
||||
noteId,
|
||||
length: partial.content.data.length
|
||||
});
|
||||
await db.notes.add({
|
||||
noteId = await db.notes.add({
|
||||
...partial.note,
|
||||
dateEdited:
|
||||
partial.ignoreEdit && currentSession.type === "default"
|
||||
@@ -934,24 +961,27 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
});
|
||||
}
|
||||
|
||||
const note = await db.notes.note(noteId);
|
||||
const note = noteId && (await db.notes.note(noteId));
|
||||
if (!note) throw new Error("Note not saved.");
|
||||
|
||||
if (currentSession.type === "new") {
|
||||
const context = useNoteStore.getState().context;
|
||||
const defaultNotebook = db.settings.getDefaultNotebook();
|
||||
if (context) {
|
||||
const { type } = context;
|
||||
if (type === "notebook")
|
||||
await db.notes.addToNotebook(context.id, noteId);
|
||||
else if (type === "color" || type === "tag")
|
||||
await db.notes.addToNotebook(context.id, note.id);
|
||||
else if (type === "color" || type === "tag") {
|
||||
await db.relations.add(
|
||||
{ type, id: context.id },
|
||||
{ id, type: "note" }
|
||||
{ id: note.id, type: "note" }
|
||||
);
|
||||
if (defaultNotebook)
|
||||
await db.notes.addToNotebook(defaultNotebook, note.id);
|
||||
}
|
||||
} else {
|
||||
const defaultNotebook = db.settings.getDefaultNotebook();
|
||||
if (defaultNotebook)
|
||||
await db.notes.addToNotebook(defaultNotebook, noteId);
|
||||
await db.notes.addToNotebook(defaultNotebook, note.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -991,6 +1021,19 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
sessionId
|
||||
});
|
||||
}
|
||||
|
||||
if (partial.note?.title !== undefined) {
|
||||
const { sessions } = this.get();
|
||||
for (const session of sessions) {
|
||||
if ("note" in session && session.note.id === note.id) {
|
||||
this.updateSession(session.id, undefined, {
|
||||
note,
|
||||
title: note.title
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setDocumentTitle(
|
||||
settingStore.get().hideNoteTitle ? undefined : note.title
|
||||
);
|
||||
@@ -1005,8 +1048,8 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
this.setSaveState(id, SaveState.NotSaved);
|
||||
console.error(err);
|
||||
if (err instanceof Error) logger.error(err);
|
||||
if (isLockedSession(currentSession) && "note" in currentSession) {
|
||||
this.get().openSession(currentSession.note, { force: true });
|
||||
if (isLockedSession(session) && "note" in session) {
|
||||
this.get().openSession(session.note, { force: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1032,8 +1075,8 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
};
|
||||
|
||||
newSession = () => {
|
||||
const { activeTabId, activateSession } = this.get();
|
||||
if (!activeTabId) {
|
||||
const { activeTabId, activateSession, getActiveTab } = this.get();
|
||||
if (!activeTabId || getActiveTab()?.pinned) {
|
||||
this.addTab();
|
||||
return;
|
||||
}
|
||||
@@ -1188,6 +1231,19 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
return id;
|
||||
};
|
||||
|
||||
pinTab = (tabId: string) => {
|
||||
const { tabs: _tabs, activeTabId } = this.get();
|
||||
const tabs = _tabs.slice();
|
||||
const index = tabs.findIndex((t) => t.id === tabId);
|
||||
if (index === -1) return;
|
||||
|
||||
tabs[index].pinned = !tabs[index].pinned;
|
||||
tabs.sort((a, b) => (a.pinned === b.pinned ? 0 : a.pinned ? -1 : 1));
|
||||
useEditorStore.setState({
|
||||
tabs
|
||||
});
|
||||
};
|
||||
|
||||
focusTab = (tabId: string | undefined, sessionId?: string) => {
|
||||
if (!tabId) return;
|
||||
|
||||
@@ -1195,14 +1251,14 @@ class EditorStore extends BaseStore<EditorStore> {
|
||||
if (history.includes(tabId)) history.splice(history.indexOf(tabId), 1);
|
||||
history.push(tabId);
|
||||
|
||||
const tab = this.get().tabs.find((t) => t.id === tabId);
|
||||
this.set({
|
||||
activeTabId: tabId,
|
||||
canGoBack: tabSessionHistory.canGoBack(tabId),
|
||||
canGoForward: tabSessionHistory.canGoForward(tabId)
|
||||
canGoBack: !tab?.pinned && tabSessionHistory.canGoBack(tabId),
|
||||
canGoForward: !tab?.pinned && tabSessionHistory.canGoForward(tabId)
|
||||
});
|
||||
|
||||
sessionId =
|
||||
sessionId || this.get().tabs.find((t) => t.id === tabId)?.sessionId;
|
||||
sessionId = sessionId || tab?.sessionId;
|
||||
if (sessionId) this.rehydrateSession(sessionId);
|
||||
};
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user