Compare commits

...

77 Commits

Author SHA1 Message Date
Ammar Ahmed
f42489ecb5 mobile: fix package version is null 2025-12-17 19:06:48 +05:00
Ammar Ahmed
abd5fa00ed mobile: fix ci workflow no space left 2025-12-17 16:28:50 +05:00
Ammar Ahmed
d66bda7adc mobile: release 3.3.10 2025-12-17 15:38:37 +05:00
Abdullah Atta
a45b54dd85 Merge pull request #9041 from streetwriters/fix-subscription-type 2025-12-17 14:22:31 +05:00
01zulfi
a4d29f3e7a editor: hide expand/collapse icon for empty headings (#9063)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-17 14:18:30 +05:00
Ammar Ahmed
2a8ec8245f mobile: fix e2e tests 2025-12-17 13:01:33 +05:00
Ammar Ahmed
d85f32e1f2 mobile: fix babel config for reanimated when using metro bundler 2025-12-17 09:02:27 +05:00
Ammar Ahmed
adc7bb1d9e mobile: fix months ordering bug in wrapped 2025-12-17 09:01:58 +05:00
Ammar Ahmed
2dafde0117 mobile: improve navigation performance 2025-12-13 12:39:38 +05:00
Ammar Ahmed
7582a01b3f mobile: remove polkadot background 2025-12-13 12:38:52 +05:00
Ammar Ahmed
9081012673 mobile: fix drawer open animation velocity 2025-12-13 10:31:41 +05:00
Ammar Ahmed
bf83dfbd28 mobile: do not cache wrapped data 2025-12-13 10:20:12 +05:00
01zulfi
903b96e629 web: show spaces in note,notebook,tag,reminder list item titles (#9027)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-12 19:19:36 +05:00
Ammar Ahmed
3e9007806e mobile: release 3.3.10-beta.6 2025-12-12 12:00:58 +05:00
Abdullah Atta
b6ec0091f0 web: update lockfile (again) 2025-12-12 11:57:54 +05:00
Ammar Ahmed
726b4f5c0d Merge pull request #9037 from streetwriters/fix-clipper-images
Fix loading images in clipped pages
2025-12-12 11:54:11 +05:00
Abdullah Atta
2fdd3a2a85 web: update lockfile 2025-12-12 11:45:36 +05:00
Ammar Ahmed
88ecb58153 mobile: fix wrapped ui on tablets 2025-12-12 10:38:05 +05:00
Abdullah Atta
d084da2914 global: update package lockfiles 2025-12-12 10:33:02 +05:00
Abdullah Atta
716de6f9eb web: bump version to 3.3.6-beta.3 2025-12-12 10:06:04 +05:00
Abdullah Atta
dfc4cbe01f Merge pull request #9042 from streetwriters/editor/fix-callout-heading
editor: fix callout collapse/expand on clicking right after its heading
2025-12-12 09:57:56 +05:00
Abdullah Atta
3a5b4c48f5 Merge branch 'master' into beta 2025-12-12 09:47:50 +05:00
Abdullah Atta
08b0fd498e Merge pull request #9049 from streetwriters/chore/disable-rebase-when-sync
config: disable rebase when sync vscode setting
2025-12-12 09:47:05 +05:00
Abdullah Atta
3f365e7ee1 config: disable rebase when sync vscode setting 2025-12-12 09:46:31 +05:00
Abdullah Atta
ebb5c078c9 Merge pull request #9048 from streetwriters/wrapped-2025
Notesnook Wrapped
2025-12-12 09:37:52 +05:00
Ammar Ahmed
f536def5fa mobile: wrapped 2025 2025-12-12 09:37:20 +05:00
01zulfi
2918b8f04b web: wrapped 2025
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>

web: refined wrapped ui

web: make wrapped work automatically for future years

web: fix emoji for colors

web: format word count
2025-12-12 09:37:11 +05:00
01zulfi
bb3b05aab0 web: allow closing file drag overlay by click or esc key (#9044)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-11 11:29:28 +05:00
01zulfi
2cdfe2c158 editor: add shortcut to open search and replace (#9043)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-11 11:28:15 +05:00
01zulfi
d1dfafe1da editor: fix callout collapse/expand on clicking right after its heading
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-10 14:36:50 +05:00
01zulfi
144febb3cc web: handle null productId
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-10 13:01:55 +05:00
Ammar Ahmed
22b2d50139 mobile: fix productId usage 2025-12-10 10:11:21 +05:00
01zulfi
121e521eb7 editor: hide horizontal rule if its under a collapsed heading (#9038)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-12-10 10:01:14 +05:00
Ammar Ahmed
7ada9cacda core: fix productId can be null 2025-12-10 09:40:56 +05:00
Ammar Ahmed
5585d69dcf mobile: fix clipper build path 2025-12-09 12:49:37 +05:00
Ammar Ahmed
f5d73a0c8f clipper: set full src url on image.src 2025-12-09 12:49:02 +05:00
Ammar Ahmed
4563739f6e Merge branch 'mobile-release-3.3.10-beta.4' into beta 2025-12-09 10:49:52 +05:00
Abdullah Atta
79ed081c99 desktop: update sqlite-better-trigram to 0.0.5
this should fix the `The specified module could not be found` error on Windows
2025-12-08 15:14:08 +05:00
Ammar Ahmed
dc1c9a9d37 mobile: fix PrivacyInfo.xcprivacy file 2025-12-06 21:15:51 +05:00
Ammar Ahmed
25c90537c8 mobile: fix version code for iOS 2025-12-06 19:54:57 +05:00
Ammar Ahmed
b7aadf4b17 ci: Update iOS Workflow 2025-12-06 18:51:50 +05:00
Ammar Ahmed
b9587e0fd5 mobile: bump version to v3.3.10-beta.5 2025-12-06 18:46:19 +05:00
Ammar Ahmed
38e529f5bf mobile: update libs 2025-12-06 18:42:06 +05:00
Ammar Ahmed
81e2d7738f mobile: enable fade animation for splash screen 2025-12-06 18:41:53 +05:00
Ammar Ahmed
380a681cc1 mobile: fix splash screen in dark mode 2025-12-06 13:28:18 +05:00
Ammar Ahmed
8c2a557618 mobile: fix switching device mode while in fullscreen mode 2025-12-06 12:36:55 +05:00
Ammar Ahmed
7b98d2ede2 mobile: Update lockfile 2025-12-06 11:21:13 +05:00
Ammar Ahmed
64c544a86e mobile: fix notifee lib version 2025-12-06 11:10:31 +05:00
Ammar Ahmed
a048125a14 mobile: release 3.3.10-beta.4 2025-12-06 11:10:31 +05:00
Ammar Ahmed
4c37e16e92 mobile: remove delay when opening settings 2025-12-06 11:10:31 +05:00
Ammar Ahmed
c448bc151b mobile: remove unused hook 2025-12-06 11:10:31 +05:00
Ammar Ahmed
efcbefe1c8 mobile: fix ui shift on focus search 2025-12-06 11:10:31 +05:00
Ammar Ahmed
339a15803c mobile: fix app exits editor on focus keyboard 2025-12-06 11:10:31 +05:00
Ammar Ahmed
582a06c05a mobile: fix biometric unlock not working on iPad and tablet devices 2025-12-06 11:09:39 +05:00
Ammar Ahmed
852f1ec31a Merge pull request #8949 from streetwriters/mobile-fix-ipad-biometric-unlock
mobile: Fix biometric unlock not working on iPad and tablet devices
2025-12-05 14:43:24 +05:00
Ammar Ahmed
2ca142566c Merge branch 'mobile-release-3.3.10-beta.3' into beta 2025-12-04 14:37:27 +05:00
Ammar Ahmed
9a13d112cb mobile: Update Podfile 2025-12-04 14:36:52 +05:00
Abdullah Atta
bc9cbbc1f6 crypto: lazily import sodium 2025-12-03 19:15:31 +05:00
Ammar Ahmed
129bf49204 mobile: release v3.3.10-beta.3 2025-12-03 14:26:57 +05:00
Ammar Ahmed
a29acb6f30 mobile: update notifee to support new architecture 2025-12-03 14:26:57 +05:00
Ammar Ahmed
c957094b1e mobile: fix orientation changing and fullscreen mode on tablets 2025-12-03 14:26:57 +05:00
Ammar Ahmed
8e5743f417 mobile: fix orientation listening events 2025-12-03 14:26:57 +05:00
Ammar Ahmed
4ff42f1ae4 mobile: fix crash on configuration changed on android 2025-12-03 14:26:57 +05:00
Ammar Ahmed
06c8f5367e mobile: fix picking themes from file 2025-12-03 14:26:57 +05:00
Abdullah Atta
f74d7cf37e web: bump version to 3.3.6-beta.2 2025-12-03 14:06:45 +05:00
Abdullah Atta
b971b4d07a Merge pull request #9002 from streetwriters/web/new-note-properties-pane 2025-12-03 14:05:27 +05:00
Abdullah Atta
09fd5d8642 Merge pull request #9002 from streetwriters/web/new-note-properties-pane
web: release space taken by properties pane for new note/tab
2025-12-03 14:01:34 +05:00
Abdullah Atta
97447e8d6c Merge pull request #9016 from NeedsChloesure/patch/erroneous-filesize-check
Fix file size calculation
2025-12-03 14:01:05 +05:00
Abdullah Atta
c388045c68 web: fix file uploading not working in safari 2025-12-03 13:59:55 +05:00
Abdullah Atta
db4d998705 editor: fix TouchEvent is not defined 2025-12-03 13:59:36 +05:00
Chloe Oletto
011cc992e8 mobile: Incorrect math for determining total number of chunks causes erroneous assertation that there is one more chunk in certain conditions.
Signed-off-by: Chloe Oletto <needschloesure@riseup.net>
2025-12-01 02:45:21 -08:00
Chloe Oletto
2c1d8b0eea web: Incorrect math for determining total number of chunks causes erroneous assertation that there is one more chunk in certain conditions.
Signed-off-by: Chloe Oletto <needschloesure@riseup.net>
2025-12-01 02:43:33 -08:00
Ammar Ahmed
65ae22e70e Merge branch 'mobile-release-3.3.10-beta.2' into beta 2025-12-01 10:05:46 +05:00
01zulfi
81da60333d editor: fade all nodes inside a checklist item except for a nested checklist (#9008)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-11-29 12:37:36 +05:00
01zulfi
aa572febd4 editor: add toggle text color keybinding (#9010)
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-11-29 12:36:09 +05:00
01zulfi
f7914c57c7 web: release space taken by properties pane for new note/tab
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
2025-11-26 16:49:17 +05:00
Ammar Ahmed
6dd16f6791 mobile: fix biometric unlock not working on iPad and tablet devices 2025-11-12 12:19:57 +05:00
98 changed files with 63710 additions and 568 deletions

View File

@@ -24,6 +24,22 @@ jobs:
java-version: "17"
architecture: "x64"
- name: Free Disk Space (Ubuntu)
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed,
# if set to "true" but frees about 6 GB
tool-cache: false
# all of these default to true, but feel free to set to
# "false" if necessary for your workflow
android: false
dotnet: true
haskell: true
large-packages: true
docker-images: true
swap-storage: true
- name: Install node modules
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
@@ -143,8 +159,8 @@ jobs:
uses: softprops/action-gh-release@v1
with:
draft: true
tag_name: ${{ steps.package-version.outputs.current-version}}-android
name: Notesnook Android v${{ steps.package-version.outputs.current-version}}
tag_name: ${{ steps.package-version.outputs.version}}-android
name: Notesnook Android v${{ steps.package-version.outputs.version}}
repository: streetwriters/notesnook
token: ${{ secrets.GITHUB_TOKEN }}
files: |

View File

@@ -4,7 +4,7 @@ on: workflow_dispatch
jobs:
build:
runs-on: macos-14
runs-on: macos-26
timeout-minutes: 60
steps:
@@ -17,7 +17,7 @@ jobs:
- name: Setup Xcode
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: "16.1"
xcode-version: "26.1.1"
- name: Install node modules
run: |
@@ -30,7 +30,7 @@ jobs:
- name: ccache
uses: hendrikmuhs/ccache-action@v1.2.11
with:
key: ${{ runner.os }}-ccache-${{ hashFiles(format('apps/mobile/native/ios/Podfile.lock')) }}
key: ${{ runner.os }}-ccache-${{ hashFiles(format('apps/mobile/ios/Podfile.lock')) }}
max-size: 1500M
restore-keys: |
${{ runner.os }}-ccache-
@@ -55,14 +55,13 @@ jobs:
uses: actions/cache@v3
id: pods-cache
with:
path: apps/mobile/native/ios/Pods
key: ${{ runner.os }}-pods-${{ hashFiles('apps/mobile/native/ios/Podfile.lock') }}
path: apps/mobile/ios/Pods
key: ${{ runner.os }}-pods-${{ hashFiles('apps/mobile/ios/Podfile.lock') }}
- name: Install Pods
run: |
cd apps/mobile/native/ios
bundle install
RCT_NEW_ARCH_ENABLED=0 bundle exec pod install
cd apps/mobile/ios
pod install
- name: Check for typescript errors
run: |
@@ -79,9 +78,9 @@ jobs:
bundle-identifier: org.streetwriters.notesnook
scheme: Notesnook
configuration: "Release"
export-options: apps/mobile/native/ios/ExportOptions.plist
project-path: apps/mobile/native/ios/Notesnook.xcodeproj
workspace-path: apps/mobile/native/ios/Notesnook.xcworkspace
export-options: apps/mobile/ios/ExportOptions.plist
project-path: apps/mobile/ios/Notesnook.xcodeproj
workspace-path: apps/mobile/ios/Notesnook.xcworkspace
update-targets: |
Notesnook
Make Note
@@ -117,5 +116,5 @@ jobs:
name: Notesnook.zip
path: |
Notesnook.ipa
apps/mobile/native/ios/**/*.map
apps/mobile/ios/**/*.map
packages/editor-mobile/sourcemaps/*.map

View File

@@ -11,5 +11,5 @@
"cache": true,
"cacheStrategy": "content"
},
"git.rebaseWhenSync": true
"git.rebaseWhenSync": false
}

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/desktop",
"version": "3.3.6-beta.1",
"version": "3.3.6-beta.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "3.3.6-beta.1",
"version": "3.3.6-beta.3",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
@@ -19,7 +19,7 @@
"electron-trpc": "0.7.1",
"electron-updater": "^6.6.2",
"icojs": "^0.19.5",
"sqlite-better-trigram": "0.0.3",
"sqlite-better-trigram": "0.0.5",
"sqlite3-fts5-html": "^0.0.4",
"typed-emitter": "^2.1.0",
"yargs": "^17.7.2",
@@ -6316,23 +6316,23 @@
"optional": true
},
"node_modules/sqlite-better-trigram": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/sqlite-better-trigram/-/sqlite-better-trigram-0.0.3.tgz",
"integrity": "sha512-r+SDeNk+K1VotrUx/w0TMu4FdjoDDPCqxhtfU50dSeTzfCFtkMBq1/E3gvsaiHi3UYtOa6siA8CK/gYlj75eCA==",
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/sqlite-better-trigram/-/sqlite-better-trigram-0.0.5.tgz",
"integrity": "sha512-LDAuMzU2u9rhJyNmSxZyJiL3lWb8FjUqX/JZ3dwmo16Cl0zmkzFvGS94yfjLhwEce1yg1UE851Mz5UplWNhhpg==",
"license": "Public Domain",
"optionalDependencies": {
"sqlite-better-trigram-darwin-arm64": "0.0.3",
"sqlite-better-trigram-darwin-x64": "0.0.3",
"sqlite-better-trigram-linux-arm64": "0.0.3",
"sqlite-better-trigram-linux-x64": "0.0.3",
"sqlite-better-trigram-windows-arm64": "0.0.3",
"sqlite-better-trigram-windows-x64": "0.0.3"
"sqlite-better-trigram-darwin-arm64": "0.0.5",
"sqlite-better-trigram-darwin-x64": "0.0.5",
"sqlite-better-trigram-linux-arm64": "0.0.5",
"sqlite-better-trigram-linux-x64": "0.0.5",
"sqlite-better-trigram-windows-arm64": "0.0.5",
"sqlite-better-trigram-windows-x64": "0.0.5"
}
},
"node_modules/sqlite-better-trigram-darwin-arm64": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/sqlite-better-trigram-darwin-arm64/-/sqlite-better-trigram-darwin-arm64-0.0.3.tgz",
"integrity": "sha512-ddL0K3v10bvqjuUgPSRfP1qFZAhO/43ruaWtWS5wu4DgikldGz1tBY+nQtdc+Xt0arrEMUyiEvOz7MuKHQNA0Q==",
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/sqlite-better-trigram-darwin-arm64/-/sqlite-better-trigram-darwin-arm64-0.0.5.tgz",
"integrity": "sha512-AVSfFHLDAuttkKtlXCMHbhtV6Dv8M48e4ucTfjMOpGenJTEWmXx3Goj/ZCHe3uLEW57qaely15AAl/xFOsGSLA==",
"cpu": [
"arm64"
],
@@ -6343,9 +6343,9 @@
]
},
"node_modules/sqlite-better-trigram-darwin-x64": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/sqlite-better-trigram-darwin-x64/-/sqlite-better-trigram-darwin-x64-0.0.3.tgz",
"integrity": "sha512-IQzFzXt0ib1MZ/UQm71X7RKv8G0vwepNqcmncWbK1R7LAWwpS/qaJ1NyrMOhL2AnfeQ33x1IkQE0GlamxFVC9A==",
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/sqlite-better-trigram-darwin-x64/-/sqlite-better-trigram-darwin-x64-0.0.5.tgz",
"integrity": "sha512-SxrQxI8r42+WN8DZxKuTQtSPmY7TLwtk5VoKbRN6HuNkb4rx8ZmmKeVmVA8u+hLkO4bzh2GtydOrlTdIgg5g5Q==",
"cpu": [
"x64"
],
@@ -6356,9 +6356,9 @@
]
},
"node_modules/sqlite-better-trigram-linux-arm64": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/sqlite-better-trigram-linux-arm64/-/sqlite-better-trigram-linux-arm64-0.0.3.tgz",
"integrity": "sha512-NTykERCSxAutAtJ9ITwOQBitC9zDg61tTDoLOGdMoDO2iAQNLuGApNS8qFvnCNl1wZy8yowI24rHYMCWLDIEIA==",
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/sqlite-better-trigram-linux-arm64/-/sqlite-better-trigram-linux-arm64-0.0.5.tgz",
"integrity": "sha512-jrm5jDUfozFEU3ErN2NosQHTi53XouXBrrhzoigAYWYhfk+oeMbN7MWdzv5y5eZNH5uUSRc8/Kj6XO8rx147mw==",
"cpu": [
"arm64"
],
@@ -6369,9 +6369,9 @@
]
},
"node_modules/sqlite-better-trigram-linux-x64": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/sqlite-better-trigram-linux-x64/-/sqlite-better-trigram-linux-x64-0.0.3.tgz",
"integrity": "sha512-WoB2qMeFX6XGOWdTYCrF4imEBL5/a/UKqZpzWvPhtV92Ys47z4AIuwiWvekO8/5FRLYCyT9rJLXLSY2eUwOqLg==",
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/sqlite-better-trigram-linux-x64/-/sqlite-better-trigram-linux-x64-0.0.5.tgz",
"integrity": "sha512-AqiEOF7fK8MPcJKxWkkrF9DWSs5Ma/AlJfuWDrdxKzlLPaV4OWucL2zm/ShdEMGZ+Uz5EgTShGxnvLRRMmZYsQ==",
"cpu": [
"x64"
],
@@ -6382,9 +6382,9 @@
]
},
"node_modules/sqlite-better-trigram-windows-arm64": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/sqlite-better-trigram-windows-arm64/-/sqlite-better-trigram-windows-arm64-0.0.3.tgz",
"integrity": "sha512-hXIT+PkkJQ3vJ0+E8Zh2/BoOCQ2dRj6LmchjP7T5afpbKXI/lcI77Cgk3tPwe8s/Ywci6OyeJwcaFuBtf1CLhw==",
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/sqlite-better-trigram-windows-arm64/-/sqlite-better-trigram-windows-arm64-0.0.5.tgz",
"integrity": "sha512-HEY3GbhtINKyM2ztcBJ7GqpgHNDA0aBUFNnKsvAkorF08+0v0sRs3oZsoDBqDQY0tm90iUZVLoTqvtN3sLhaGw==",
"cpu": [
"arm64"
],
@@ -6395,9 +6395,9 @@
]
},
"node_modules/sqlite-better-trigram-windows-x64": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/sqlite-better-trigram-windows-x64/-/sqlite-better-trigram-windows-x64-0.0.3.tgz",
"integrity": "sha512-dli7MaDld9k02Vx74Mrovxxy+SQ72GwaObvIOcbI7ATiKUvhRvjHZgmuIvgxF2DM8aRejFHgPIC4/GUJcjauEg==",
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/sqlite-better-trigram-windows-x64/-/sqlite-better-trigram-windows-x64-0.0.5.tgz",
"integrity": "sha512-JeLPlGc5yL7KL/uqAgcRb9wwS51xN1aezP18k2dvuxpXWmfGDc8e8/bpKYVVzUbnMF63MdpSa9PBch2NhT6CPA==",
"cpu": [
"x64"
],

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "3.3.6-beta.1",
"version": "3.3.6-beta.3",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/cjs/index.js",
@@ -36,7 +36,7 @@
"electron-trpc": "0.7.1",
"electron-updater": "^6.6.2",
"icojs": "^0.19.5",
"sqlite-better-trigram": "0.0.3",
"sqlite-better-trigram": "0.0.5",
"sqlite3-fts5-html": "^0.0.4",
"typed-emitter": "^2.1.0",
"yargs": "^17.7.2",

View File

@@ -47,7 +47,7 @@ module.exports = {
simulator: {
type: "ios.simulator",
device: {
type: "iPhone 12"
type: "iPhone 17 Pro Max"
}
},
attached: {
@@ -59,7 +59,7 @@ module.exports = {
emulator: {
type: "android.emulator",
device: {
avdName: "Pixel_5_API_34"
avdName: "Pixel_5_API_36"
}
}
},

View File

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

View File

@@ -20,7 +20,7 @@ import com.zoontek.rnbootsplash.RNBootSplash;
public class MainActivity extends ReactActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
protected void onCreate(Bundle savedInstanceState) {
RNBootSplash.init(this, R.style.BootTheme);
super.onCreate(null);
@@ -32,19 +32,6 @@ public class MainActivity extends ReactActivity {
startService(new Intent(getBaseContext(), OnClearFromRecentService.class));
} catch (Exception ignored) {}
if (Build.VERSION.SDK_INT >= 35) {
final View rootView = findViewById(android.R.id.content);
ViewCompat.setOnApplyWindowInsetsListener(rootView, (v, insets) -> {
Insets innerPadding = insets.getInsets(WindowInsetsCompat.Type.ime());
rootView.setPadding(
innerPadding.left,
innerPadding.top,
innerPadding.right,
innerPadding.bottom
);
return insets;
});
}
}
/**
@@ -60,20 +47,20 @@ public class MainActivity extends ReactActivity {
DefaultNewArchitectureEntryPoint.getFabricEnabled());
}
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
@Override
public void onNewIntent(Intent intent) {
super.onNewIntent(intent);
setIntent(intent);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getReactInstanceManager().onConfigurationChanged(this, newConfig);
Intent intent = new Intent("onConfigurationChanged");
intent.putExtra("newConfig", newConfig);
this.sendBroadcast(intent);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getReactHost().onConfigurationChanged(this);
Intent intent = new Intent("onConfigurationChanged");
intent.putExtra("newConfig", newConfig);
this.sendBroadcast(intent);
}
@Override
protected String getMainComponentName() {

View File

@@ -6,7 +6,7 @@ import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
import org.wonday.orientation.OrientationActivityLifecycle;
class MainApplication : Application(), ReactApplication {
override val reactHost: ReactHost by lazy {
getDefaultReactHost(
@@ -22,6 +22,7 @@ class MainApplication : Application(), ReactApplication {
override fun onCreate() {
super.onCreate()
registerActivityLifecycleCallbacks(OrientationActivityLifecycle.getInstance());
loadReactNative(this)
}
}

View File

@@ -17,7 +17,7 @@
</style>
<!-- BootTheme should inherit from Theme.SplashScreen -->
<style name="BootTheme" parent="Theme.SplashScreen">
<style name="BootTheme" parent="Theme.BootSplash.EdgeToEdge">
<item name="bootSplashBackground">@color/bootsplash_background</item>
<item name="bootSplashLogo">@mipmap/bootsplash_logo_dark</item>
<item name="android:navigationBarColor">@color/bootsplash_background</item>

View File

@@ -1,3 +1,6 @@
- Get an overview of your year with Notesnook with Wrapped 2025
- Cell selection and column resizing in tables
- Added support for uploading large files in background
- Bug fixes and minor improvements
Thank you for using Notesnook!
Thank you for using Notesnook!

View File

@@ -51,8 +51,9 @@ const { appLockEnabled, appLockMode } = SettingsService.get();
if (appLockEnabled || appLockMode !== "none") {
useUserStore.getState().lockApp(true);
}
RNBootSplash.hide();
RNBootSplash.hide({
fade: true
});
const App = (props: { configureMode: "note-preview" }) => {
useAppEvents();
//@ts-ignore

View File

@@ -70,7 +70,7 @@ export async function downloadFile(
throw new Error(error);
}
const totalChunks = Math.ceil(size / chunkSize);
const totalChunks = Math.ceil(size / (chunkSize + ABYTES));
const decryptedLength = size - totalChunks * ABYTES;
if (attachment && attachment.size !== decryptedLength) {

View File

@@ -155,7 +155,7 @@ export async function checkUpload(
expectedSize: number
) {
const size = await getUploadedFileSize(filename);
const totalChunks = Math.ceil(size / chunkSize);
const totalChunks = Math.ceil(size / (chunkSize + ABYTES));
const decryptedLength = size - totalChunks * ABYTES;
const error =
size === 0

View File

@@ -324,7 +324,9 @@ export const Signup = ({
<View
style={{
paddingHorizontal: DefaultAppStyles.GAP
paddingHorizontal: DefaultAppStyles.GAP,
width: DDS.isTab ? "50%" : "100%",
alignSelf: "center"
}}
>
<Paragraph

View File

@@ -42,9 +42,7 @@ export default function DelayLayout({
...props
}: IDelayLayoutProps) {
const { colors } = useThemeColors();
const loading = useDelayLayout(
!props.delay || props.delay < 300 ? 0 : props.delay
);
const loading = useDelayLayout(props.delay === undefined ? 200 : props.delay);
const Placeholder = placeholder[props.type || "default"];
return loading || props.wait ? (

View File

@@ -136,12 +136,12 @@ const BaseDialog = ({
backgroundColor: background
? background
: transparent
? "transparent"
: "rgba(0,0,0,0.3)"
? "transparent"
: "rgba(0,0,0,0.3)"
}}
>
<KeyboardAvoidingView
enabled={!floating && Platform.OS === "ios" && !avoidKeyboardResize}
enabled={!floating && !avoidKeyboardResize}
behavior="padding"
>
<BouncingView
@@ -155,8 +155,8 @@ const BaseDialog = ({
justifyContent: centered
? "center"
: bottom
? "flex-end"
: "flex-start"
? "flex-end"
: "flex-start"
}
]}
>

View File

@@ -96,22 +96,22 @@ export class VaultDialog extends Component {
this.title = !this.state.novault
? strings.createVault()
: this.state.fingerprintAccess
? strings.vaultFingerprintUnlock()
: this.state.revokeFingerprintAccess
? strings.revokeVaultFingerprintUnlock()
: this.state.changePassword
? strings.changeVaultPassword()
: this.state.noteLocked
? this.state.deleteNote
? strings.deleteNote()
: this.state.share
? strings.shareNote()
: this.state.copyNote
? strings.copyNote()
: this.state.goToEditor
? strings.goToEditor()
: strings.goToEditor()
: strings.lockNote();
? strings.vaultFingerprintUnlock()
: this.state.revokeFingerprintAccess
? strings.revokeVaultFingerprintUnlock()
: this.state.changePassword
? strings.changeVaultPassword()
: this.state.noteLocked
? this.state.deleteNote
? strings.deleteNote()
: this.state.share
? strings.shareNote()
: this.state.copyNote
? strings.copyNote()
: this.state.goToEditor
? strings.goToEditor()
: strings.goToEditor()
: strings.lockNote();
}
componentDidMount() {
@@ -337,12 +337,13 @@ export class VaultDialog extends Component {
);
});
}
eSendEvent("vaultUpdated");
this.setState({
loading: false
});
this.close();
setTimeout(() => {
this.close();
}, 100);
} else {
ToastManager.show({
heading: strings.passwordIncorrect(),
@@ -353,9 +354,6 @@ export class VaultDialog extends Component {
} catch (e) {
console.error(e);
}
this.setState({
loading: false
});
};
clearVault = async () => {
@@ -874,24 +872,24 @@ export class VaultDialog extends Component {
deleteVault
? strings.delete()
: clearVault
? strings.clear()
: fingerprintAccess
? strings.enable()
: this.state.revokeFingerprintAccess
? strings.revoke()
: changePassword
? strings.change()
: this.state.noteLocked
? deleteNote
? strings.delete()
: share
? strings.share()
: goToEditor
? strings.open()
: strings.unlock()
: !note.id
? strings.create()
: strings.lock()
? strings.clear()
: fingerprintAccess
? strings.enable()
: this.state.revokeFingerprintAccess
? strings.revoke()
: changePassword
? strings.change()
: this.state.noteLocked
? deleteNote
? strings.delete()
: share
? strings.share()
: goToEditor
? strings.open()
: strings.unlock()
: !note.id
? strings.create()
: strings.lock()
}
/>
</View>

View File

@@ -111,7 +111,6 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
const containerWidth = widths
? widths.sidebar + widths.list + widths.editor
: dimensions.width;
const drawerPosition = 0;
const homePosition = widths.sidebar;
const editorPosition = widths.sidebar + widths.list;
@@ -355,8 +354,9 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
const finalValue = isSwipeLeft
? translateX.value - velocityX / 40.0
: translateX.value + velocityX / 40.0;
const velocity = velocityX / 5000;
const animationConfig: WithSpringConfig = {
velocity: velocityX / 10,
velocity: velocity,
...SnappySpringConfig
};
@@ -440,15 +440,7 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
width: containerWidth,
flexDirection: "row"
},
deviceMode === "tablet"
? {
transform: [
{
translateX: 0
}
]
}
: animatedStyles
animatedStyles
]}
>
{children}

View File

@@ -994,7 +994,7 @@ const PricingPlanCard = ({
PremiumService.get() &&
(pricingPlans?.user?.subscription?.productId ===
(product as RNIap.Subscription)?.productId ||
pricingPlans?.user?.subscription?.productId.startsWith(
pricingPlans?.user?.subscription?.productId?.startsWith(
(product as RNIap.Subscription)?.productId
));
pricingPlans?.selectPlan(

View File

@@ -352,7 +352,7 @@ const ProductItem = (props: {
props.pricingPlans.isSubscribed() &&
(props.pricingPlans.user?.subscription?.productId ===
(product as RNIap.Subscription)?.productId ||
props.pricingPlans.user?.subscription?.productId.startsWith(
props.pricingPlans.user?.subscription?.productId?.startsWith(
(product as RNIap.Subscription)?.productId
) ||
props.pricingPlans.user?.subscription?.productId ===

View File

@@ -103,7 +103,7 @@ export function PlanLimits() {
if (
user?.subscription.plan !== SubscriptionPlan.FREE &&
user?.subscription.productId.includes("5year")
user?.subscription.productId?.includes("5year")
) {
ToastManager.show({
message:

View File

@@ -58,6 +58,7 @@ import { Button } from "../ui/button";
import SettingsService from "../../services/settings";
import { isFeatureAvailable } from "@notesnook/common";
import PaywallSheet from "../sheets/paywall";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
const renderScene = SceneMap({
home: SideMenuHome,
notebooks: SideMenuNotebooks,
@@ -68,6 +69,7 @@ const renderScene = SceneMap({
export const SideMenu = React.memo(
function SideMenu() {
const { colors } = useThemeColors();
const insets = useGlobalSafeAreaInsets();
const [index, setIndex] = React.useState(
SettingsService.getProperty("defaultSidebarTab")
);
@@ -87,10 +89,13 @@ export const SideMenu = React.memo(
]);
return (
<SafeAreaView
<View
style={{
flex: 1,
backgroundColor: colors.primary.background
backgroundColor: colors.primary.background,
paddingTop: insets.top,
paddingBottom: insets.bottom,
paddingLeft: insets.left
}}
>
<TabView
@@ -103,7 +108,7 @@ export const SideMenu = React.memo(
animationEnabled={false}
lazy
/>
</SafeAreaView>
</View>
);
},
() => true
@@ -351,9 +356,8 @@ const TabBar = (
color={colors.primary.icon}
onPress={async () => {
if (props.navigationState.index === 1) {
const notebooksFeature = await isFeatureAvailable(
"notebooks"
);
const notebooksFeature =
await isFeatureAvailable("notebooks");
if (!notebooksFeature.isAllowed) {
PaywallSheet.present(notebooksFeature);
return;

View File

@@ -104,15 +104,12 @@ function _MenuItem({
const _onPress = () => {
if (useSideBarDraggingStore.getState().dragging) return;
if (item.onPress) return item.onPress(item);
Navigation.closeDrawer();
if (useNavigationStore.getState().currentRoute !== item.id) {
Navigation.navigate(item.id as keyof RouteParams, {
canGoBack: false
});
}
setTimeout(() => {
Navigation.closeDrawer();
}, 32);
};
return (

View File

@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { SubscriptionPlan } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import dayjs from "dayjs";
import React from "react";
import { FlatList, View } from "react-native";
import { DraxProvider, DraxScrollView } from "react-native-drax";
@@ -140,19 +141,35 @@ export function SideMenuHome() {
paddingVertical: DefaultAppStyles.GAP_VERTICAL
}}
>
{(subscriptionType === SubscriptionPlan.FREE ||
!subscriptionType ||
!user) &&
!SettingsService.getProperty("serverUrls") ? (
{dayjs().month() !== 11 ? (
<>
{(subscriptionType === SubscriptionPlan.FREE ||
!subscriptionType ||
!user) &&
!SettingsService.getProperty("serverUrls") ? (
<Button
title={pro.title}
style={{
width: "100%"
}}
type="accent"
onPress={pro.onPress}
/>
) : null}
</>
) : (
<Button
title={pro.title}
title={`Wrapped ${dayjs().year()} 🎉`}
style={{
width: "100%"
}}
type="accent"
onPress={pro.onPress}
bold
type="secondaryAccented"
onPress={() => {
Navigation.navigate("Wrapped");
}}
/>
) : null}
)}
</View>
</View>
);

View File

@@ -660,9 +660,25 @@ const usePricingPlans = (options?: PricingPlansOptions) => {
}
}
function isSubscribedToPlan(planId: string) {
if (!PremiumService.get()) return false;
return user?.subscription?.productId?.includes(planId);
}
function isSubscribedToProduct(productId: string) {
if (!PremiumService.get()) return false;
return (
user?.subscription?.productId &&
(user?.subscription?.productId === productId ||
user?.subscription?.productId?.startsWith(productId))
);
}
return {
currentPlan: pricingPlans.find((p) => p.id === currentPlan),
pricingPlans: plans,
isSubscribedToPlan,
isSubscribedToProduct,
getStandardPrice: getLocalizedPrice,
loadingPlans,
loading,

View File

@@ -29,7 +29,7 @@ import React, {
useRef,
useState
} from "react";
import { Dimensions, LayoutChangeEvent, Platform, View } from "react-native";
import { LayoutChangeEvent, View } from "react-native";
import Orientation, {
OrientationType,
useDeviceOrientationChange
@@ -39,7 +39,6 @@ import Animated, {
useSharedValue,
withTiming
} from "react-native-reanimated";
import { SafeAreaView } from "react-native-safe-area-context";
import { notesnook } from "../../e2e/test.ids";
import { db } from "../common/database";
import { FluidPanels } from "../components/fluid-panels";
@@ -69,15 +68,13 @@ import {
eOpenFullscreenEditor,
eUnlockNote
} from "../utils/events";
import { editorRef, fluidTabsRef } from "../utils/global-refs";
import { valueLimiter } from "../utils/functions";
import { fluidTabsRef } from "../utils/global-refs";
import { AppNavigationStack } from "./navigation-stack";
import type { PaneWidths } from "../screens/editor/wrapper";
const MOBILE_SIDEBAR_SIZE = 0.85;
const valueLimiter = (value: number, min: number, max: number) => {
return value < min ? min : value > max ? max : value;
};
let SideMenu: any = null;
let EditorWrapper: any = null;
@@ -93,7 +90,6 @@ export const FluidPanelsView = React.memo(
const insets = useGlobalSafeAreaInsets();
const animatedOpacity = useSharedValue(0);
const animatedTranslateY = useSharedValue(-9999);
const overlayRef = useRef<Animated.View>(null);
const [orientation, setOrientation] = useState<OrientationType>(
Orientation.getInitialOrientation()
@@ -102,16 +98,21 @@ export const FluidPanelsView = React.memo(
const [isLoading, setIsLoading] = useState(false);
useDeviceOrientationChange((o) => {
setOrientation(o);
if (
o !== OrientationType.UNKNOWN &&
o !== OrientationType["FACE-UP"] &&
o !== OrientationType["FACE-DOWN"] &&
o !== OrientationType["PORTRAIT-UPSIDEDOWN"]
) {
setOrientation(o);
}
});
const isLandscape = orientation.includes("LANDSCAPE");
useEffect(() => {
if (!appLoading) {
setTimeout(() => {
setIsLoading(false);
}, 500);
}, 200);
}
}, [appLoading]);
@@ -147,16 +148,6 @@ export const FluidPanelsView = React.memo(
if (deviceMode === "smallTablet") {
fluidTabsRef.current?.openDrawer(false);
}
editorRef.current?.setNativeProps({
style: {
width: dimensions.width,
zIndex: 999,
paddingHorizontal:
deviceMode === "smallTablet"
? dimensions.width * 0
: dimensions.width * 0.15
}
});
}, [deviceMode, dimensions.width, setFullscreen]);
const closeFullScreenEditor = useCallback(
@@ -169,17 +160,6 @@ export const FluidPanelsView = React.memo(
editorController.current?.commands.updateSettings({
fullscreen: false
});
editorRef.current?.setNativeProps({
style: {
width:
_deviceMode === "smallTablet"
? dimensions.width -
valueLimiter(dimensions.width * 0.4, 300, 450)
: dimensions.width * 0.48,
zIndex: null,
paddingHorizontal: 0
}
});
if (_deviceMode === "smallTablet") {
fluidTabsRef.current?.goToIndex(1, false);
}
@@ -221,38 +201,8 @@ export const FluidPanelsView = React.memo(
(current: string | null, size: { width: number; height: number }) => {
setDeviceModeState(current);
const needsLayout = current !== deviceMode;
if (fullscreen && current !== "mobile") {
// Runs after size is set via state.
setTimeout(() => {
editorRef.current?.setNativeProps({
style: {
width: size.width,
zIndex: 999,
paddingHorizontal:
current === "smallTablet" ? size.width * 0 : size.width * 0.15
}
});
}, 1);
} else {
if (fullscreen) eSendEvent(eCloseFullscreenEditor, current);
editorRef.current?.setNativeProps({
style: {
position: "relative",
width:
current === "tablet"
? size.width * 0.48
: current === "smallTablet"
? size.width - valueLimiter(size.width * 0.4, 300, 450)
: size.width,
zIndex: null,
paddingHorizontal: 0
}
});
}
if (!needsLayout) {
return;
if (fullscreen && current === "mobile") {
eSendEvent(eCloseFullscreenEditor, current);
}
const state = getAppState();
@@ -264,8 +214,6 @@ export const FluidPanelsView = React.memo(
case "smallTablet":
if (!fullscreen) {
fluidTabsRef.current?.closeDrawer(false);
} else {
fluidTabsRef.current?.openDrawer(false);
}
break;
case "mobile":
@@ -274,46 +222,52 @@ export const FluidPanelsView = React.memo(
editorState().movedAway === false &&
useTabStore.getState().getCurrentNoteId()
) {
fluidTabsRef.current?.goToIndex(2, false);
fluidTabsRef.current?.goToPage("editor", false);
} else {
fluidTabsRef.current?.goToIndex(1, false);
fluidTabsRef.current?.goToPage(
fluidTabsRef.current?.page(),
false
);
}
break;
}
}, 32);
}, 0);
},
[deviceMode, fullscreen, setDeviceModeState]
);
const checkDeviceType = React.useCallback(
(size: { width: number; height: number }) => {
setDimensions({
width: size.width,
height: size.height
});
if (DDS.width === size.width && orientation === DDS.orientation) return;
DDS.setSize(size, orientation);
const nextDeviceMode = DDS.isLargeTablet()
? "tablet"
: DDS.isSmallTab
? "smallTablet"
: "mobile";
setDeviceMode(nextDeviceMode, size);
},
[orientation, setDeviceMode, setDimensions]
);
useEffect(() => {
if (orientation !== "UNKNOWN") {
checkDeviceType(dimensions);
}
}, [orientation, dimensions]);
const _onLayout = React.useCallback(
(event: LayoutChangeEvent) => {
const size = event?.nativeEvent?.layout;
if (!size || (size.width === dimensions.width && deviceMode !== null)) {
DDS.setSize(size, orientation);
setDeviceMode(deviceMode, size);
checkDeviceType(size);
return;
setDimensions({
width: size.width,
height: size.height
});
if (size.width > size.height) {
setOrientation(OrientationType["LANDSCAPE-RIGHT"]);
} else {
setOrientation(OrientationType["PORTRAIT"]);
}
checkDeviceType(size);
},
[
checkDeviceType,
@@ -324,11 +278,6 @@ export const FluidPanelsView = React.memo(
]
);
if (!deviceMode) {
const size = Dimensions.get("window");
checkDeviceType(size);
}
const PANE_OFFSET = useMemo(
() => ({
mobile: {
@@ -356,7 +305,7 @@ export const FluidPanelsView = React.memo(
[dimensions.width, fullscreen]
);
const PANE_WIDTHS = useMemo(
const PANE_WIDTHS: PaneWidths = useMemo(
() => ({
mobile: {
sidebar: dimensions.width * MOBILE_SIDEBAR_SIZE,
@@ -382,6 +331,7 @@ export const FluidPanelsView = React.memo(
(scrollOffset: number) => {
if (!deviceMode) return;
hideAllTooltips();
if (
scrollOffset >
PANE_OFFSET[deviceMode as keyof typeof PANE_OFFSET].sidebar - 10
@@ -409,7 +359,7 @@ export const FluidPanelsView = React.memo(
};
}, []);
if (!isLoading) {
if (!isLoading && !SideMenu && !EditorWrapper) {
SideMenu = require("../components/side-menu").SideMenu;
EditorWrapper = require("../screens/editor/wrapper").EditorWrapper;
}
@@ -421,15 +371,7 @@ export const FluidPanelsView = React.memo(
style={{
height: "100%",
width: "100%",
backgroundColor: colors.primary.background,
marginRight:
orientation === "LANDSCAPE-RIGHT" && Platform.OS === "ios"
? insets.right
: 0,
marginLeft:
orientation === "LANDSCAPE-LEFT" && Platform.OS === "ios"
? insets.left
: 0
backgroundColor: colors.primary.background
}}
>
{deviceMode && PANE_WIDTHS[deviceMode as keyof typeof PANE_WIDTHS] ? (
@@ -503,9 +445,7 @@ export const FluidPanelsView = React.memo(
style={{
flex: 1,
paddingTop: insets.top,
paddingBottom: insets.bottom,
paddingLeft: insets.left,
paddingRight: insets.right
paddingBottom: insets.bottom
}}
>
<AppNavigationStack />

View File

@@ -273,6 +273,7 @@ let Settings: any = null;
let ManageTags: any = null;
let AddReminder: any = null;
let PayWall: any = null;
let Wrapped: any = null;
export const RootNavigation = () => {
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
@@ -384,6 +385,14 @@ export const RootNavigation = () => {
return PayWall;
}}
/>
<RootStack.Screen
name="Wrapped"
getComponent={() => {
Wrapped = Wrapped || require("../screens/wrapped").default;
return Wrapped;
}}
/>
</RootStack.Navigator>
</NavigationContainer>
);

View File

@@ -62,6 +62,7 @@ import { fluidTabsRef } from "../../utils/global-refs";
import { strings } from "@notesnook/intl";
import { i18n } from "@lingui/core";
import { useVaultStatus } from "../../hooks/use-vault-status";
import { useSettingStore } from "../../stores/use-setting-store";
const style: ViewStyle = {
height: "100%",
@@ -231,7 +232,6 @@ const useLockedNoteHandler = () => {
const unlockWithBiometrics = async () => {
try {
if (!tabRef.current?.session?.noteLocked || !tabRef.current) return;
console.log("Trying to unlock with biometrics...");
const credentials = await BiometricService.getCredentials(
"Unlock note",
"Unlock note to open it in editor."
@@ -320,12 +320,16 @@ const useLockedNoteHandler = () => {
}
};
const unlock = () => {
const unlock = (forced?: boolean) => {
const isMovedAway =
useSettingStore.getState().deviceMode !== "mobile"
? false
: editorState().movedAway;
if (
(tabRef.current?.session?.locked,
tabRef.current?.session?.locked &&
useTabStore.getState().biometryAvailable &&
useTabStore.getState().biometryEnrolled &&
!editorState().movedAway)
useTabStore.getState().biometryEnrolled &&
(!isMovedAway || forced)
) {
setTimeout(() => {
unlockWithBiometrics();
@@ -344,13 +348,15 @@ const useLockedNoteHandler = () => {
const subs = [
eSubscribeEvent(eUnlockNote, unlock),
eSubscribeEvent(eUnlockWithBiometrics, () => {
unlock();
unlock(true);
}),
eSubscribeEvent(eUnlockWithPassword, onSubmit)
];
if (
tabRef.current?.session?.locked &&
fluidTabsRef.current?.page() === "editor"
(fluidTabsRef.current?.page() === "editor" ||
useSettingStore.getState().deviceMode !== "mobile")
) {
unlock();
}

View File

@@ -23,21 +23,36 @@ import {
AppState,
AppStateStatus,
KeyboardAvoidingView,
Platform,
TextInput,
View
} from "react-native";
import Editor from ".";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
import useKeyboard from "../../hooks/use-keyboard";
import { DDS } from "../../services/device-detection";
import { useSettingStore } from "../../stores/use-setting-store";
import { editorRef } from "../../utils/global-refs";
import { editorController, textInput } from "./tiptap/utils";
import deviceInfo from "react-native-device-info";
export const EditorWrapper = ({ widths }: { widths: any }) => {
export type PaneWidths = {
mobile: {
sidebar: number;
list: number;
editor: number;
};
smallTablet: {
sidebar: number;
list: number;
editor: number;
};
tablet: {
sidebar: number;
list: number;
editor: number;
};
};
export const EditorWrapper = ({ widths }: { widths: PaneWidths }) => {
const { colors } = useThemeColors();
const { colors: toolBarColors } = useThemeColors("editorToolbar");
const deviceMode = useSettingStore((state) => state.deviceMode);
@@ -47,8 +62,9 @@ export const EditorWrapper = ({ widths }: { widths: any }) => {
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const keyboard = useKeyboard();
const prevState = useRef<AppStateStatus>(undefined);
const isFullscreen = useSettingStore((state) => state.fullscreen);
const dimensions = useSettingStore((state) => state.dimensions);
const onAppStateChanged = async (state: AppStateStatus) => {
if (!prevState.current) {
@@ -72,32 +88,37 @@ export const EditorWrapper = ({ widths }: { widths: any }) => {
};
}, [loading]);
const getMarginBottom = () => {
const bottomInsets =
Platform.OS === "android" ? 12 : insets.bottom + 16 || 14;
if (!keyboard.keyboardShown) return bottomInsets / 1.5;
if (deviceInfo.isTablet() && Platform.OS === "ios" && !floating)
return bottomInsets;
if (Platform.OS === "ios") return bottomInsets / 1.5;
return 0;
};
return (
<View
testID="editor-wrapper"
ref={editorRef}
style={{
width: widths[!introCompleted ? "mobile" : (deviceMode as any)]?.editor,
height: "100%",
minHeight: "100%",
backgroundColor: toolBarColors.primary.background,
borderLeftWidth: DDS.isTab ? 1 : 0,
borderLeftColor: DDS.isTab
? colors.secondary.background
: "transparent",
paddingBottom:
Platform.OS === "android" ? insets.bottom + 10 : insets.bottom
}}
style={[
{
width: isFullscreen
? dimensions.width
: widths[
!introCompleted ? "mobile" : (deviceMode as keyof PaneWidths)
]?.editor,
height: "100%",
minHeight: "100%",
backgroundColor: toolBarColors.primary.background,
paddingLeft: isFullscreen
? deviceMode === "smallTablet"
? 0
: dimensions.width * 0.15
: null,
paddingRight: isFullscreen
? deviceMode === "smallTablet"
? 0
: dimensions.width * 0.15
: insets.right,
borderLeftWidth: DDS.isTab ? 1 : 0,
borderLeftColor: DDS.isTab
? colors.secondary.background
: "transparent",
paddingBottom: insets.bottom
}
]}
>
{loading || !introCompleted ? null : (
<KeyboardAvoidingView

View File

@@ -217,12 +217,12 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
position: "absolute",
bottom: 20,
right: 20,
gap: DefaultAppStyles.GAP_VERTICAL,
alignItems: "center"
}}
>
<FloatingButton
icon="file-tree"
testID="notebookTreeSheet"
size="small"
onPress={() => {
Notebooks.present(params.current.item);
@@ -230,7 +230,8 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
style={{
position: "relative",
right: 0,
bottom: 5
bottom: 0,
paddingBottom: 10
}}
/>
<FloatingButton
@@ -238,6 +239,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
alwaysVisible
style={{
position: "relative",
paddingTop: 10,
right: 0,
bottom: 0
}}

View File

@@ -23,7 +23,6 @@ import React, { useRef, useState } from "react";
import { View } from "react-native";
import { TextInput } from "react-native-gesture-handler";
import { IconButton } from "../../components/ui/icon-button";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import Navigation from "../../services/navigation";
import useNavigationStore from "../../stores/use-navigation-store";
import { useSelectionStore } from "../../stores/use-selection-store";
@@ -41,7 +40,6 @@ export const SearchBar = ({
const isFocused = useNavigationStore(
(state) => state.focusedRouteId === "Search"
);
const insets = useGlobalSafeAreaInsets();
const { colors } = useThemeColors();
const inputRef = useRef<TextInput>(null);
const _onChangeText = (value: string) => {
@@ -58,7 +56,6 @@ export const SearchBar = ({
>
<View
style={{
marginTop: DefaultAppStyles.GAP_SMALL,
flexDirection: "row",
alignItems: "center",
width: "100%",

View File

@@ -59,7 +59,7 @@ const Group = ({
id="Settings"
/>
)}
<DelayLayout type="settings" delay={0}>
<DelayLayout type="settings">
<View
style={{
flex: 1

View File

@@ -59,7 +59,7 @@ const Home = ({
hasSearch={false}
id="Settings"
/>
<DelayLayout delay={300} type="settings">
<DelayLayout type="settings">
<LegendList
data={settingsGroups}
keyExtractor={keyExtractor}

View File

@@ -17,23 +17,29 @@ 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 { ScopedThemeProvider, useThemeColors } from "@notesnook/theme";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import React from "react";
import { View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import useNavigationStore from "../../stores/use-navigation-store";
import { ScopedThemeProvider, useThemeColors } from "@notesnook/theme";
import Group from "./group";
import Home from "./home";
import { RouteParams } from "./types";
import { SafeAreaView } from "react-native-safe-area-context";
const SettingsStack = createNativeStackNavigator<RouteParams>();
export const Settings = () => {
const { colors } = useThemeColors();
const insets = useSafeAreaInsets();
return (
<SafeAreaView
<View
style={{
flex: 1,
backgroundColor: colors.primary.background
backgroundColor: colors.primary.background,
paddingTop: insets.top,
paddingBottom: insets.bottom,
paddingLeft: insets.left,
paddingRight: insets.right
}}
>
<ScopedThemeProvider value="list">
@@ -58,7 +64,7 @@ export const Settings = () => {
<SettingsStack.Screen name="SettingsGroup" component={Group} />
</SettingsStack.Navigator>
</ScopedThemeProvider>
</SafeAreaView>
</View>
);
};

View File

@@ -118,7 +118,8 @@ export const settingsGroups: SettingSection[] = [
if (
(user.subscription?.provider === SubscriptionProvider.GOOGLE ||
user.subscription?.provider === SubscriptionProvider.APPLE) &&
isCurrentPlatform
isCurrentPlatform &&
user?.subscription?.productId
) {
RNIap.deepLinkToSubscriptions({
sku: user?.subscription.productId
@@ -142,13 +143,18 @@ export const settingsGroups: SettingSection[] = [
"dddd, MMMM D, YYYY h:mm A"
);
if (user.subscription?.plan !== SubscriptionPlan.FREE) {
if (
user.subscription?.plan !== SubscriptionPlan.FREE &&
user.subscription?.productId
) {
const status = user.subscription?.status;
return status === SubscriptionStatus.TRIAL
? strings.trialEndsOn(
dayjs(user?.subscription?.start)
.add(
user?.subscription?.productId.includes("monthly") ? 7 : 14
user?.subscription?.productId?.includes("monthly")
? 7
: 14
)
.format("dddd, MMMM D, YYYY h:mm A")
)

View File

@@ -28,7 +28,7 @@ import type {
ThemeMetadata,
ThemesRouter
} from "@notesnook/themes-server";
import { pick } from "@react-native-documents/picker";
import { keepLocalCopy, pick } from "@react-native-documents/picker";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
@@ -57,6 +57,7 @@ import { getColorLinearShade } from "../../utils/colors";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../utils/styles";
import { LegendList } from "@legendapp/list";
import ReactNativeBlobUtil from "react-native-blob-util";
const THEME_SERVER_URL = "https://themes-api.notesnook.com";
//@ts-ignore
@@ -412,20 +413,39 @@ function ThemeSelector() {
type={"secondaryAccented"}
icon="folder"
fontSize={AppFontSize.xs}
onPress={() => {
pick({
allowMultiSelection: false
}).then((r) => {
fetch(r[0].uri).then(async (response) => {
const json = await response.json();
const result = validateTheme(json);
if (result.error) {
ToastManager.error(new Error(result.error));
return;
}
select(json, true);
onPress={async () => {
try {
const pickResponse = await pick({
allowMultiSelection: false
});
});
const copiedFile = await keepLocalCopy({
destination: "cachesDirectory",
files: [
{
uri: pickResponse[0].uri,
fileName: pickResponse[0].name || "theme.json"
}
]
});
if (copiedFile[0].status !== "success") return;
const themeJson = await ReactNativeBlobUtil.fs.readFile(
copiedFile[0].localUri,
"utf8"
);
ReactNativeBlobUtil.fs
.unlink(copiedFile[0].localUri)
.catch(() => {});
const json = JSON.parse(themeJson);
const result = validateTheme(json);
if (result.error) {
ToastManager.error(new Error(result.error));
return;
}
select(json, true);
} catch (e) {
ToastManager.error(e as Error);
}
}}
/>
</View>

File diff suppressed because it is too large Load Diff

View File

@@ -50,6 +50,7 @@ export class DeviceDetectionService {
windowSize = size || windowSize;
this.width = windowSize.width;
this.height = windowSize.height;
this.orientation = orientation;
this.adjustedWidth = this.width * this.pixelDensity;
this.adjustedHeight = this.height * this.pixelDensity;
screenSize = Dimensions.get("screen");

View File

@@ -71,7 +71,8 @@ const routeNames = {
Archive: "Archive",
ManageTags: "ManageTags",
AddReminder: "AddReminder",
PayWall: "PayWall"
PayWall: "PayWall",
Wrapped: "Wrapped"
};
export type NavigationProps<T extends RouteName> = NativeStackScreenProps<

View File

@@ -112,6 +112,7 @@ export interface RouteParams extends ParamListBase {
context: "signup" | "logged-in" | "logged-out" | "subscribed";
state?: BillingState;
};
Wrapped: GenericRouteParam;
}
export type RouteName = keyof RouteParams;

View File

@@ -35,6 +35,10 @@ import { useTagStore } from "../stores/use-tag-store";
import { eUpdateNoteInEditor } from "./events";
import { unlockVault } from "./unlock-vault";
export const valueLimiter = (value: number, min: number, max: number) => {
return value < min ? min : value > max ? max : value;
};
export function getObfuscatedEmail(email: string) {
if (!email) return "";
const [username, provider] = email.split("@");

View File

@@ -72,10 +72,8 @@ export const MenuItemsList: SideMenuItem[] = [
title: "Monographs",
icon: "text-box-multiple-outline",
onPress: () => {
Navigation.closeDrawer();
Monographs.navigate();
setTimeout(() => {
Navigation.closeDrawer();
}, 32);
},
type: "side-menu-item"
},

View File

@@ -23,7 +23,7 @@ module.exports = function (api, opts) {
});
}
env.plugins.push("react-native-reanimated/plugin");
env.plugins.push("react-native-worklets/plugin");
api.cache(true);
return env;

View File

@@ -134,7 +134,7 @@ describe("NOTE TESTS", () => {
.run();
});
it("Assign colors to a note", async () => {
it.only("Assign colors to a note", async () => {
await TestBuilder.create()
.prepare()
.createNote()

View File

@@ -121,7 +121,7 @@ describe("NOTEBOOKS", () => {
.run();
});
it.only("Remove note from notebook", async () => {
it("Remove note from notebook", async () => {
await TestBuilder.create()
.prepare()
.openSideMenu()
@@ -138,7 +138,8 @@ describe("NOTEBOOKS", () => {
.longPressByText(note.body)
.wait(500)
.waitAndTapById("select-minus")
.isNotVisibleById(note.title)
.wait(500)
.isNotVisibleByText(note.body)
.run();
})
.run();
@@ -199,6 +200,7 @@ describe("NOTEBOOKS", () => {
.longPressByText("Notebook 1")
.wait(500)
.waitAndTapByText("Move to trash")
.wait(500)
.waitAndTapByText("Delete")
.waitAndTapById("tab-home")
.waitAndTapByText("Trash")

View File

@@ -34,7 +34,7 @@ describe("Sort & filter", () => {
.element(by.web.className("ProseMirror"))
.typeText("Edited ", true);
})
.pressBack()
.pressBack(2)
.waitAndTapById("icon-sort")
.wait(500)
.waitAndTapByText("Date created")

View File

@@ -32,7 +32,10 @@ const testvars = {
class Element {
element: Detox.NativeElement;
constructor(public type: "id" | "text", public value: string) {
constructor(
public type: "id" | "text",
public value: string
) {
if (type == "id") {
this.element = element(by.id(value)).atIndex(0);
} else {
@@ -135,6 +138,7 @@ const Tests = {
);
await titleInput.isVisible();
await titleInput.element.typeText(title);
await Tests.sleep(1000);
if (description) {
await Tests.fromId(
notesnook.ids.dialogs.notebook.inputs.description

View File

@@ -84,7 +84,7 @@ describe("VAULT", () => {
.run();
});
it.only("Change vault password", async () => {
it("Change vault password", async () => {
await TestBuilder.create()
.prepare()
.createNote()
@@ -94,7 +94,7 @@ describe("VAULT", () => {
.typeTextById(notesnook.ids.dialogs.vault.pwd, "1234")
.typeTextById(notesnook.ids.dialogs.vault.changePwd, "2362")
.waitAndTapByText("Change")
.pressBack(3)
.pressBack(4)
.addStep(async () => await openLockedNote("2362"))
.run();
});

View File

@@ -1029,7 +1029,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2160;
CURRENT_PROJECT_VERSION = 2166;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1104,7 +1104,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = "3.3.10-beta.2";
MARKETING_VERSION = 3.3.10;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1135,7 +1135,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2160;
CURRENT_PROJECT_VERSION = 2166;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1210,7 +1210,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = "3.3.10-beta.2";
MARKETING_VERSION = 3.3.10;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1367,7 +1367,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2160;
CURRENT_PROJECT_VERSION = 2166;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1379,7 +1379,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = "3.3.10-beta.2";
MARKETING_VERSION = 3.3.10;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
@@ -1410,7 +1410,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2160;
CURRENT_PROJECT_VERSION = 2166;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1423,7 +1423,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = "3.3.10-beta.2";
MARKETING_VERSION = 3.3.10;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1453,7 +1453,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2160;
CURRENT_PROJECT_VERSION = 2166;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1534,7 +1534,7 @@
"@executable_path/../../Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
MARKETING_VERSION = "3.3.10-beta.2";
MARKETING_VERSION = 3.3.10;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
@@ -1565,7 +1565,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2160;
CURRENT_PROJECT_VERSION = 2166;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1647,7 +1647,7 @@
"@executable_path/../../Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
MARKETING_VERSION = "3.3.10-beta.2";
MARKETING_VERSION = 3.3.10;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -10,8 +10,6 @@
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
<string>3B52.1</string>
<string>C617.1</string>
</array>
</dict>
<dict>

View File

@@ -1969,7 +1969,7 @@ PODS:
- React-Core
- React-RCTFabric
- ReactCommon/turbomodule/core
- react-native-mmkv-storage (12.0.0):
- react-native-mmkv-storage (12.0.1):
- boost
- DoubleConversion
- fast_float
@@ -2244,8 +2244,36 @@ PODS:
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- react-native-upload (6.27.0):
- react-native-upload (6.29.0):
- React
- react-native-view-shot (4.0.3):
- boost
- DoubleConversion
- fast_float
- fmt
- glog
- hermes-engine
- RCT-Folly
- RCT-Folly/Fabric
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-jsi
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- SocketRocket
- Yoga
- react-native-webview (13.16.0):
- boost
- DoubleConversion
@@ -3033,14 +3061,14 @@ PODS:
- Yoga
- RNKeychain (4.0.5):
- React
- RNNotifee (7.4.9):
- RNNotifee (7.4.10):
- React-Core
- RNNotifee/NotifeeCore (= 7.4.9)
- RNNotifee/NotifeeCore (7.4.9):
- RNNotifee/NotifeeCore (= 7.4.10)
- RNNotifee/NotifeeCore (7.4.10):
- React-Core
- RNPrivacySnapshot (1.0.0):
- React-Core
- RNReanimated (4.1.3):
- RNReanimated (4.2.0):
- boost
- DoubleConversion
- fast_float
@@ -3067,11 +3095,11 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- RNReanimated/reanimated (= 4.1.3)
- RNReanimated/reanimated (= 4.2.0)
- RNWorklets
- SocketRocket
- Yoga
- RNReanimated/reanimated (4.1.3):
- RNReanimated/reanimated (4.2.0):
- boost
- DoubleConversion
- fast_float
@@ -3098,11 +3126,11 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- RNReanimated/reanimated/apple (= 4.1.3)
- RNReanimated/reanimated/apple (= 4.2.0)
- RNWorklets
- SocketRocket
- Yoga
- RNReanimated/reanimated/apple (4.1.3):
- RNReanimated/reanimated/apple (4.2.0):
- boost
- DoubleConversion
- fast_float
@@ -3282,7 +3310,7 @@ PODS:
- pop (~> 1.0)
- React
- SexyTooltip
- RNWorklets (0.6.1):
- RNWorklets (0.7.1):
- boost
- DoubleConversion
- fast_float
@@ -3309,10 +3337,10 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- RNWorklets/worklets (= 0.6.1)
- RNWorklets/worklets (= 0.7.1)
- SocketRocket
- Yoga
- RNWorklets/worklets (0.6.1):
- RNWorklets/worklets (0.7.1):
- boost
- DoubleConversion
- fast_float
@@ -3339,10 +3367,10 @@ PODS:
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- RNWorklets/worklets/apple (= 0.6.1)
- RNWorklets/worklets/apple (= 0.7.1)
- SocketRocket
- Yoga
- RNWorklets/worklets/apple (0.6.1):
- RNWorklets/worklets/apple (0.7.1):
- boost
- DoubleConversion
- fast_float
@@ -3465,6 +3493,7 @@ DEPENDENCIES:
- "react-native-sodium (from `../node_modules/@ammarahmed/react-native-sodium`)"
- react-native-theme-switch-animation (from `../node_modules/react-native-theme-switch-animation`)
- "react-native-upload (from `../node_modules/@ammarahmed/react-native-upload`)"
- react-native-view-shot (from `../node_modules/react-native-view-shot`)
- react-native-webview (from `../node_modules/react-native-webview`)
- React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)
- React-oscompat (from `../node_modules/react-native/ReactCommon/oscompat`)
@@ -3680,6 +3709,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-theme-switch-animation"
react-native-upload:
:path: "../node_modules/@ammarahmed/react-native-upload"
react-native-view-shot:
:path: "../node_modules/react-native-view-shot"
react-native-webview:
:path: "../node_modules/react-native-webview"
React-NativeModulesApple:
@@ -3868,7 +3899,7 @@ SPEC CHECKSUMS:
react-native-image-resizer: 290b045c34c69db7574e4d08aadfc4abe1ff5a99
react-native-in-app-review: 1516ba69d60d58053b7eb3aaaf8d2a5a74af8b57
react-native-keep-awake: a351e6f67006b47f316ae2b17ee8ee69386167f4
react-native-mmkv-storage: 31b7b155e690339f5a25cfe56d5e2f68dee4f066
react-native-mmkv-storage: e84980084e371a9c2230bbde071536a7e1539406
react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187
react-native-notification-sounds: ce106d58df0dd384bccbd2e84fb53accab7cc068
react-native-orientation-locker: cc6f357b289a2e0dd2210fea0c52cb8e0727fdaa
@@ -3880,7 +3911,8 @@ SPEC CHECKSUMS:
react-native-share-extension: fdc6aaab51591a2d445df239c446aaa3a99658ec
react-native-sodium: 066f76e46c9be13e9260521e3fa994937c4cdab4
react-native-theme-switch-animation: 449d6db7a760f55740505e7403ae8061debc9a7e
react-native-upload: f0198bb4db00ff7dae14904ad59da470550be075
react-native-upload: 1459a4be625dda39e4364d9e7d859477a3000f2a
react-native-view-shot: 6c008e58f4720de58370848201c5d4a082c6d4ca
react-native-webview: 654f794a7686b47491cf43aa67f7f428bea00eed
React-NativeModulesApple: 46690a0fe94ec28fc6fc686ec797b911d251ded0
React-oscompat: 95875e81f5d4b3c7b2c888d5bd2c9d83450d8bdb
@@ -3927,15 +3959,15 @@ SPEC CHECKSUMS:
RNIap: 61f183ac917792fae42b0326b1bef33598c1adf6
RNImageCropPicker: 5fd4ceaead64d8c53c787e4e559004f97bc76df7
RNKeychain: ffd0513e676445c637410b47249460cbf56bc9cb
RNNotifee: dabf3cdd7bfd9340bb84358cc78f635af4bc80e2
RNNotifee: 3840cc81add9954a5dec040f96442498f3f95d7b
RNPrivacySnapshot: ccad3a548338c2f526bb7b1789af3fb0618b7d1d
RNReanimated: 732e7d1662f8cc0e533fa32791800de5b5934726
RNReanimated: f1868b36f4b2b52a0ed00062cfda69506f75eaee
RNScreens: d821082c6dd1cb397cc0c98b026eeafaa68be479
RNSecureRandom: b64d263529492a6897e236a22a2c4249aa1b53dc
RNShare: 5d39a36f2d3d9a6c9344bfb3232c8ed607924a35
RNSVG: 8c0bbfa480a24b24468f1c76bd852a4aac3178e6
RNTooltips: 50ff94bd2bff84ef74279c9532d008f5fdcddb95
RNWorklets: ab618bf7d1c7fd2cb793b9f0f39c3e29274b3ebf
RNWorklets: d9c050940f140af5d8b611d937eab1cbfce5e9a5
RNZipArchive: 71befbdc20e7fbd4b64552a0b324ad0e8d0efefb
SDWebImage: a7f831e1a65eb5e285e3fb046a23fcfbf08e696d
SexyTooltip: 5c9b4dec52bfb317938cb0488efd9da3717bb6fd

File diff suppressed because one or more lines are too long

View File

@@ -1,22 +1,22 @@
{
"name": "@notesnook/mobile",
"version": "3.3.10-beta.2",
"version": "3.3.10-beta.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/mobile",
"version": "3.3.10-beta.2",
"version": "3.3.10-beta.6",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
"@ammarahmed/notifee-react-native": "7.4.9",
"@ammarahmed/notifee-react-native": "^7.4.10",
"@ammarahmed/react-native-background-fetch": "^4.2.2",
"@ammarahmed/react-native-eventsource": "1.1.0",
"@ammarahmed/react-native-fingerprint-scanner": "^5.0.1",
"@ammarahmed/react-native-share-extension": "^2.9.5",
"@ammarahmed/react-native-sodium": "^1.6.8",
"@ammarahmed/react-native-upload": "^6.28.0",
"@ammarahmed/react-native-upload": "^6.29.0",
"@azure/core-asynciterator-polyfill": "^1.0.2",
"@bam.tech/react-native-image-resizer": "3.0.11",
"@callstack/repack": "~5.2.1",
@@ -72,7 +72,7 @@
"react": "19.1.1",
"react-async-hook": "^4.0.0",
"react-native": "0.82.1",
"react-native-actions-sheet": "^10.0.0-alpha.13",
"react-native-actions-sheet": "^10.1.0",
"react-native-actions-shortcuts": "^1.0.1",
"react-native-background-actions": "^2.6.6",
"react-native-begin-background-task": "github:blockfirm/react-native-begin-background-task",
@@ -97,7 +97,7 @@
"react-native-keyboard-aware-scroll-view": "^0.9.5",
"react-native-keychain": "4.0.5",
"react-native-material-menu": "^2.0.0",
"react-native-mmkv-storage": "^12.0.0",
"react-native-mmkv-storage": "^12.0.1",
"react-native-modal-datetime-picker": "14.0.0",
"react-native-navigation-bar-color": "2.0.2",
"react-native-notification-sounds": "0.5.5",
@@ -108,7 +108,7 @@
"react-native-progress": "5.0.0",
"react-native-qrcode-svg": "^6.0.6",
"react-native-quick-sqlite": "^8.2.7",
"react-native-reanimated": "^4.1.3",
"react-native-reanimated": "^4.2.0",
"react-native-reanimated-progress-bar": "1.0.1",
"react-native-safe-area-context": "^5.6.1",
"react-native-scoped-storage": "^1.9.5",
@@ -123,9 +123,10 @@
"react-native-tooltips": "^1.0.3",
"react-native-url-polyfill": "^2.0.0",
"react-native-vector-icons": "10.3.0",
"react-native-view-shot": "^4.0.3",
"react-native-webview": "^13.13.5",
"react-native-wheel-color-picker": "^1.3.1",
"react-native-worklets": "^0.6.1",
"react-native-worklets": "^0.7.1",
"react-native-zip-archive": "6.0.9",
"timeago.js": "4.0.2",
"toggle-switch-react-native": "3.2.0",
@@ -228,6 +229,7 @@
"@streetwriters/kysely": "^0.27.4",
"@streetwriters/showdown": "^3.0.9-alpha",
"@types/mime-db": "^1.43.5",
"alfaaz": "^1.1.0",
"async-mutex": "0.5.0",
"dayjs": "1.11.13",
"dom-serializer": "^2.0.0",
@@ -483,9 +485,9 @@
}
},
"node_modules/@ammarahmed/notifee-react-native": {
"version": "7.4.9",
"resolved": "https://registry.npmjs.org/@ammarahmed/notifee-react-native/-/notifee-react-native-7.4.9.tgz",
"integrity": "sha512-DK4ogXPBnoM8UsDQQLK6AQZUbP9TqOcL3YDEKtXmsWug1hzvcP/AfJ42KUCni/XR5NMI1VWW7a7tEwdbdzmS8g==",
"version": "7.4.10",
"resolved": "https://registry.npmjs.org/@ammarahmed/notifee-react-native/-/notifee-react-native-7.4.10.tgz",
"integrity": "sha512-19t1vhIddFneOI7JVmXghunEt5mEmDBY/G1YVMuRNNT/HFQ1EcArJWuAaFAJrhrl/2CBS7xoPGLusKbkbT/JjA==",
"license": "Apache-2.0",
"peerDependencies": {
"react-native": "*"
@@ -531,9 +533,9 @@
"license": "ISC"
},
"node_modules/@ammarahmed/react-native-upload": {
"version": "6.28.0",
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-upload/-/react-native-upload-6.28.0.tgz",
"integrity": "sha512-0cU6ulbJB2s9KgV2QlQ91//YZ+K5USlKjM6CW9fDHFcGn+Td3tFWB0atnplP35Ih+HeOnYL9t1MuNG4vNzjoFw==",
"version": "6.29.0",
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-upload/-/react-native-upload-6.29.0.tgz",
"integrity": "sha512-k2FJAVR4Nohx4VhfgHuXzLXQpgEnco/PAv9R080X7oY3O12DTgY0ruDvOFwvQGFW0PDIHlXfLfUiMq2kZc/k1A==",
"license": "BSD-3-Clause",
"peerDependencies": {
"react": "*",
@@ -2001,6 +2003,7 @@
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz",
"integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
@@ -2483,16 +2486,16 @@
}
},
"node_modules/@babel/preset-typescript": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz",
"integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==",
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz",
"integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-validator-option": "^7.27.1",
"@babel/plugin-syntax-jsx": "^7.27.1",
"@babel/plugin-transform-modules-commonjs": "^7.27.1",
"@babel/plugin-transform-typescript": "^7.28.5"
"@babel/plugin-transform-typescript": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@@ -7497,6 +7500,15 @@
"resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz",
"integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA=="
},
"node_modules/base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
"license": "MIT",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -8553,6 +8565,15 @@
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
"license": "MIT"
},
"node_modules/css-line-break": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
"license": "MIT",
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/css-select": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
@@ -11633,6 +11654,19 @@
"entities": "^4.4.0"
}
},
"node_modules/html2canvas": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
"license": "MIT",
"dependencies": {
"css-line-break": "^2.1.0",
"text-segmentation": "^1.0.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/htmlparser2": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz",
@@ -17130,9 +17164,9 @@
}
},
"node_modules/react-native-actions-sheet": {
"version": "10.0.0-alpha.13",
"resolved": "https://registry.npmjs.org/react-native-actions-sheet/-/react-native-actions-sheet-10.0.0-alpha.13.tgz",
"integrity": "sha512-jy2PruTrz5Hp4NGFk6ykOLoTYcUaSYQ9ylTBi+JM32Lh8UQl1SkKm9kp7VcEMpwXw/gf+/eYpDtPffi3CAzfIA==",
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/react-native-actions-sheet/-/react-native-actions-sheet-10.1.0.tgz",
"integrity": "sha512-pfbfWs2YqdF3Vo7Cx6LUsDQ/uS3iw8H64iFxZvtLXhZ3cVHWundJ903sZtadzJC+TpQIRV7NQsdnhErhf+wBlg==",
"license": "MIT",
"peerDependencies": {
"react-native": "*",
@@ -17655,13 +17689,10 @@
}
},
"node_modules/react-native-mmkv-storage": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/react-native-mmkv-storage/-/react-native-mmkv-storage-12.0.0.tgz",
"integrity": "sha512-sssZInILQBquytDDfjosjEdvevwMPk3fqQQjdjfnH362IcsBsApvtT8yJS406Mz9aJ6VhqVsZw/awltsUuPSYg==",
"version": "12.0.1",
"resolved": "https://registry.npmjs.org/react-native-mmkv-storage/-/react-native-mmkv-storage-12.0.1.tgz",
"integrity": "sha512-4I4NzjStuCqkeXGfJ7pcACNQ2FlZKlAQqeRvI80iZL7mQcaO5NMO26ILUk2f4LPEvxaT2ns+NcqPMZSj7L83ng==",
"license": "MIT",
"bin": {
"mmkv-link": "autolink/postlink/run.js"
},
"peerDependencies": {
"react-native": "*"
}
@@ -17799,19 +17830,18 @@
}
},
"node_modules/react-native-reanimated": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.3.tgz",
"integrity": "sha512-GP8wsi1u3nqvC1fMab/m8gfFwFyldawElCcUSBJQgfrXeLmsPPUOpDw44lbLeCpcwUuLa05WTVePdTEwCLTUZg==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.2.0.tgz",
"integrity": "sha512-frhu5b8/m/VvaMWz48V8RxcsXnE3hrlErQ5chr21MzAeDCpY4X14sQjvm+jvu3aOI+7Cz2atdRpyhhIuqxVaXg==",
"license": "MIT",
"dependencies": {
"react-native-is-edge-to-edge": "^1.2.1",
"semver": "7.7.2"
"react-native-is-edge-to-edge": "1.2.1",
"semver": "7.7.3"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0",
"react": "*",
"react-native": "*",
"react-native-worklets": ">=0.5.0"
"react-native-worklets": ">=0.7.0"
}
},
"node_modules/react-native-reanimated-progress-bar": {
@@ -17826,9 +17856,9 @@
}
},
"node_modules/react-native-reanimated/node_modules/semver": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -18069,6 +18099,19 @@
"node": ">=10"
}
},
"node_modules/react-native-view-shot": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/react-native-view-shot/-/react-native-view-shot-4.0.3.tgz",
"integrity": "sha512-USNjYmED7C0me02c1DxKA0074Hw+y/nxo+xJKlffMvfUWWzL5ELh/TJA/pTnVqFurIrzthZDPtDM7aBFJuhrHQ==",
"license": "MIT",
"dependencies": {
"html2canvas": "^1.4.1"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-webview": {
"version": "13.16.0",
"resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.16.0.tgz",
@@ -18093,33 +18136,49 @@
}
},
"node_modules/react-native-worklets": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.6.1.tgz",
"integrity": "sha512-URca8l7c7Uog7gv4mcg9KILdJlnbvwdS5yfXQYf5TDkD2W1VY1sduEKrD+sA3lUPXH/TG1vmXAvNxCNwPMYgGg==",
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.7.1.tgz",
"integrity": "sha512-KNsvR48ULg73QhTlmwPbdJLPsWcyBotrGPsrDRDswb5FYpQaJEThUKc2ncXE4UM5dn/ewLoQHjSjLaKUVPxPhA==",
"license": "MIT",
"dependencies": {
"@babel/plugin-transform-arrow-functions": "^7.0.0-0",
"@babel/plugin-transform-class-properties": "^7.0.0-0",
"@babel/plugin-transform-classes": "^7.0.0-0",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0",
"@babel/plugin-transform-optional-chaining": "^7.0.0-0",
"@babel/plugin-transform-shorthand-properties": "^7.0.0-0",
"@babel/plugin-transform-template-literals": "^7.0.0-0",
"@babel/plugin-transform-unicode-regex": "^7.0.0-0",
"@babel/preset-typescript": "^7.16.7",
"convert-source-map": "^2.0.0",
"semver": "7.7.2"
"@babel/plugin-transform-arrow-functions": "7.27.1",
"@babel/plugin-transform-class-properties": "7.27.1",
"@babel/plugin-transform-classes": "7.28.4",
"@babel/plugin-transform-nullish-coalescing-operator": "7.27.1",
"@babel/plugin-transform-optional-chaining": "7.27.1",
"@babel/plugin-transform-shorthand-properties": "7.27.1",
"@babel/plugin-transform-template-literals": "7.27.1",
"@babel/plugin-transform-unicode-regex": "7.27.1",
"@babel/preset-typescript": "7.27.1",
"convert-source-map": "2.0.0",
"semver": "7.7.3"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0",
"@babel/core": "*",
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-worklets/node_modules/@babel/plugin-transform-optional-chaining": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz",
"integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/react-native-worklets/node_modules/semver": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -20266,6 +20325,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/text-segmentation": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
"license": "MIT",
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/thingies": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/thingies/-/thingies-2.5.0.tgz",
@@ -21077,6 +21145,15 @@
"node": ">= 0.4.0"
}
},
"node_modules/utrie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
"license": "MIT",
"dependencies": {
"base64-arraybuffer": "^1.0.2"
}
},
"node_modules/uuid": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "3.3.10-beta.2",
"version": "3.3.10",
"private": true,
"license": "GPL-3.0-or-later",
"scripts": {
@@ -26,13 +26,13 @@
"react": "19.1.1"
},
"dependencies": {
"@ammarahmed/notifee-react-native": "7.4.9",
"@ammarahmed/notifee-react-native": "^7.4.10",
"@ammarahmed/react-native-background-fetch": "^4.2.2",
"@ammarahmed/react-native-eventsource": "1.1.0",
"@ammarahmed/react-native-fingerprint-scanner": "^5.0.1",
"@ammarahmed/react-native-share-extension": "^2.9.5",
"@ammarahmed/react-native-sodium": "^1.6.8",
"@ammarahmed/react-native-upload": "^6.28.0",
"@ammarahmed/react-native-upload": "^6.29.0",
"@azure/core-asynciterator-polyfill": "^1.0.2",
"@bam.tech/react-native-image-resizer": "3.0.11",
"@callstack/repack": "~5.2.1",
@@ -88,7 +88,7 @@
"react": "19.1.1",
"react-async-hook": "^4.0.0",
"react-native": "0.82.1",
"react-native-actions-sheet": "^10.0.0-alpha.13",
"react-native-actions-sheet": "^10.1.0",
"react-native-actions-shortcuts": "^1.0.1",
"react-native-background-actions": "^2.6.6",
"react-native-begin-background-task": "github:blockfirm/react-native-begin-background-task",
@@ -113,7 +113,7 @@
"react-native-keyboard-aware-scroll-view": "^0.9.5",
"react-native-keychain": "4.0.5",
"react-native-material-menu": "^2.0.0",
"react-native-mmkv-storage": "^12.0.0",
"react-native-mmkv-storage": "^12.0.1",
"react-native-modal-datetime-picker": "14.0.0",
"react-native-navigation-bar-color": "2.0.2",
"react-native-notification-sounds": "0.5.5",
@@ -124,7 +124,7 @@
"react-native-progress": "5.0.0",
"react-native-qrcode-svg": "^6.0.6",
"react-native-quick-sqlite": "^8.2.7",
"react-native-reanimated": "^4.1.3",
"react-native-reanimated": "^4.2.0",
"react-native-reanimated-progress-bar": "1.0.1",
"react-native-safe-area-context": "^5.6.1",
"react-native-scoped-storage": "^1.9.5",
@@ -139,9 +139,10 @@
"react-native-tooltips": "^1.0.3",
"react-native-url-polyfill": "^2.0.0",
"react-native-vector-icons": "10.3.0",
"react-native-view-shot": "^4.0.3",
"react-native-webview": "^13.13.5",
"react-native-wheel-color-picker": "^1.3.1",
"react-native-worklets": "^0.6.1",
"react-native-worklets": "^0.7.1",
"react-native-zip-archive": "6.0.9",
"timeago.js": "4.0.2",
"toggle-switch-react-native": "3.2.0",

View File

@@ -0,0 +1,12 @@
diff --git a/node_modules/react-native-gesture-handler/android/build.gradle b/node_modules/react-native-gesture-handler/android/build.gradle
index ac149a0..37f620c 100644
--- a/node_modules/react-native-gesture-handler/android/build.gradle
+++ b/node_modules/react-native-gesture-handler/android/build.gradle
@@ -187,6 +187,7 @@ android {
exclude "**/libreactnative.so"
exclude "**/libjsi.so"
exclude "**/libc++_shared.so"
+ exclude "**/libfbjni.so"
}
sourceSets.main {

File diff suppressed because one or more lines are too long

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/web",
"version": "3.3.6-beta.1",
"version": "3.3.6-beta.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/web",
"version": "3.3.6-beta.1",
"version": "3.3.6-beta.3",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
@@ -59,6 +59,7 @@
"file-saver": "^2.0.5",
"hash-wasm": "4.12.0",
"hotkeys-js": "^3.8.3",
"htmlparser2": "^10.0.0",
"katex": "0.16.11",
"mac-scrollbar": "0.13.6",
"mutative": "^1.1.0",
@@ -234,6 +235,7 @@
"@streetwriters/kysely": "^0.27.4",
"@streetwriters/showdown": "^3.0.9-alpha",
"@types/mime-db": "^1.43.5",
"alfaaz": "^1.1.0",
"async-mutex": "0.5.0",
"dayjs": "1.11.13",
"dom-serializer": "^2.0.0",
@@ -321,9 +323,6 @@
"@tiptap/extension-placeholder": "2.6.6",
"@tiptap/extension-subscript": "2.6.6",
"@tiptap/extension-superscript": "2.6.6",
"@tiptap/extension-table": "2.6.6",
"@tiptap/extension-table-cell": "2.6.6",
"@tiptap/extension-table-header": "2.6.6",
"@tiptap/extension-table-row": "2.6.6",
"@tiptap/extension-task-item": "2.6.6",
"@tiptap/extension-task-list": "2.6.6",
@@ -504,7 +503,7 @@
},
"../desktop": {
"name": "@notesnook/desktop",
"version": "3.3.4",
"version": "3.3.6-beta.3",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {
@@ -517,7 +516,7 @@
"electron-trpc": "0.7.1",
"electron-updater": "^6.6.2",
"icojs": "^0.19.5",
"sqlite-better-trigram": "0.0.3",
"sqlite-better-trigram": "0.0.5",
"sqlite3-fts5-html": "^0.0.4",
"typed-emitter": "^2.1.0",
"yargs": "^17.7.2",
@@ -2896,6 +2895,25 @@
"integrity": "sha512-5u2V/CDW15QM1XbbgS+0DfPxVB+jUKhWEKuuFuHncbk3tEEqzmoXL+2KyOFuKGqOnmdIy0/davWF1CkuwtibCw==",
"license": "MIT"
},
"node_modules/@notesnook-importer/core/node_modules/htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"entities": "^4.4.0"
}
},
"node_modules/@notesnook-importer/enex": {
"version": "2.2.3",
"license": "GPL-3.0-or-later",
@@ -2909,6 +2927,25 @@
"spark-md5": "^3.0.2"
}
},
"node_modules/@notesnook-importer/enex/node_modules/htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"entities": "^4.4.0"
}
},
"node_modules/@notesnook-importer/storage": {
"version": "2.2.3",
"license": "GPL-3.0-or-later"
@@ -2926,6 +2963,25 @@
"spark-md5": "^3.0.2"
}
},
"node_modules/@notesnook-importer/znel/node_modules/htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"entities": "^4.4.0"
}
},
"node_modules/@notesnook/common": {
"resolved": "../../packages/common",
"link": true
@@ -7438,9 +7494,9 @@
}
},
"node_modules/htmlparser2": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.0.0.tgz",
"integrity": "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
@@ -7452,8 +7508,20 @@
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.0.1",
"entities": "^4.4.0"
"domutils": "^3.2.1",
"entities": "^6.0.0"
}
},
"node_modules/htmlparser2/node_modules/entities": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/https-proxy-agent": {

View File

@@ -1,7 +1,7 @@
{
"name": "@notesnook/web",
"description": "Your private note taking space",
"version": "3.3.6-beta.1",
"version": "3.3.6-beta.3",
"private": true,
"main": "./src/app.js",
"homepage": "https://notesnook.com/",
@@ -57,6 +57,7 @@
"file-saver": "^2.0.5",
"hash-wasm": "4.12.0",
"hotkeys-js": "^3.8.3",
"htmlparser2": "^10.0.0",
"katex": "0.16.11",
"mac-scrollbar": "0.13.6",
"mutative": "^1.1.0",

View File

@@ -48,6 +48,7 @@ import { getFontSizes } from "@notesnook/theme/theme/font/fontsize.js";
import { useWindowControls } from "./hooks/use-window-controls";
import { STATUS_BAR_HEIGHT } from "./common/constants";
import { NavigationEvents } from "./navigation";
import { db } from "./common/db";
new WebExtensionRelay();
@@ -143,6 +144,10 @@ function DesktopAppContents() {
const isTablet = useTablet();
const navPane = useRef<SplitPaneImperativeHandle>(null);
useEffect(() => {
(async () => {})();
}, []);
useEffect(() => {
if (isTablet) navPane.current?.collapse(0);
else if (navPane.current?.isCollapsed(0)) navPane.current?.expand(0);

View File

@@ -25,6 +25,7 @@ import {
isFeatureSupported
} from "./utils/feature-check";
import { initializeLogger } from "./utils/logger";
import { shouldShowWrapped } from "./utils/should-show-wrapped";
type Route<TProps = null> = {
component: () => Promise<{
@@ -55,6 +56,9 @@ const routes = {
"/plans": {
component: () => import("./views/plans")
},
"/wrapped": {
component: () => import("./views/wrapped")
},
"/checkout": {
component: () => import("./views/checkout")
},
@@ -122,6 +126,12 @@ function getRoute(): RouteWithPath<AuthProps> | RouteWithPath {
routes[path] ? { route: routes[path], path } : null
) as RouteWithPath<AuthProps> | null;
if (route?.path === "/wrapped" && !shouldShowWrapped())
return {
route: routes.default,
path: "default"
};
return signup || sessionExpired || route || fallback;
}

View File

@@ -226,11 +226,13 @@ export default function TabsView() {
<TableOfContents sessionId={activeSession.id} />
</Pane>
) : null}
{arePropertiesVisible && activeSession && (
<Pane id="properties-pane" initialSize={250} minSize={250}>
<Properties sessionId={activeSession.id} />
</Pane>
)}
{arePropertiesVisible &&
activeSession &&
activeSession.type !== "new" && (
<Pane id="properties-pane" initialSize={250} minSize={250}>
<Properties sessionId={activeSession.id} />
</Pane>
)}
</SplitPane>
<DropZone overlayRef={overlayRef} />
</ScopedThemeProvider>
@@ -848,17 +850,27 @@ function useDragOverlay() {
e.preventDefault();
}
function handleKeyDown(e: KeyboardEvent) {
if (e.key === "Escape") {
hideOverlay();
}
}
dropElement.addEventListener("dragenter", showOverlay);
overlay.addEventListener("drop", hideOverlay);
overlay.addEventListener("dragenter", allowDrag);
overlay.addEventListener("dragover", allowDrag);
overlay.addEventListener("dragleave", hideOverlay);
overlay.addEventListener("click", hideOverlay);
document.addEventListener("keydown", handleKeyDown);
return () => {
dropElement.removeEventListener("dragenter", showOverlay);
overlay.removeEventListener("drop", hideOverlay);
overlay.removeEventListener("dragenter", allowDrag);
overlay.removeEventListener("dragover", allowDrag);
overlay.removeEventListener("dragleave", hideOverlay);
overlay.removeEventListener("click", hideOverlay);
document.removeEventListener("keydown", handleKeyDown);
};
}, []);

View File

@@ -202,7 +202,7 @@ function ListItem<TItem extends Item, TContext>(
data-test-id={`title`}
variant={"body"}
sx={{
whiteSpace: "nowrap",
whiteSpace: "pre",
overflow: "hidden",
textOverflow: "ellipsis",
fontWeight: isCompact ? "body" : "medium",

View File

@@ -0,0 +1,143 @@
/*
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 { Box, Flex, Text } from "@theme-ui/components";
import { SxProp } from "@theme-ui/core";
type MonthlyActivityHeatmapProps = {
monthlyStats: Record<string, number>;
year?: number;
} & SxProp;
const MONTHS = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
];
const MONTHS_SHORT = [
"Jan",
"Feb",
"Mar",
"Apr",
"May",
"Jun",
"Jul",
"Aug",
"Sep",
"Oct",
"Nov",
"Dec"
];
export function MonthlyActivityHeatmap({
monthlyStats
}: MonthlyActivityHeatmapProps) {
// Find max count for bar height calculation
const maxCount = Math.max(...Object.values(monthlyStats), 1);
// Create month data with counts
const monthData = MONTHS.map((month) => ({
month,
count: monthlyStats[month] || 0
}));
return (
<>
{/* Bar chart */}
<Flex
sx={{
alignItems: "flex-end",
justifyContent: "space-between",
gap: 2,
height: "100%",
width: "100%",
position: "relative",
pb: 2
}}
>
{monthData.map((data, index) => {
const barHeight = maxCount > 0 ? (data.count / maxCount) * 100 : 0;
return (
<Flex
key={data.month}
sx={{
flexDirection: "column",
alignItems: "center",
justifyContent: "flex-end",
flex: 1,
gap: 2,
cursor: "pointer",
"&:hover .tooltip": { opacity: 1 },
"&:hover .bar": { bg: "accent" },
height: "100%"
}}
>
<Flex
className="tooltip"
sx={{ opacity: 0, bg: "background", zIndex: 1000 }}
>
<Text variant="body" sx={{ fontWeight: "bold" }}>
{data.count}
</Text>
</Flex>
<Box
className="bar"
sx={{
width: "100%",
height: `${barHeight}%`,
minHeight: data.count > 0 ? "10px" : "5px",
bg: maxCount === data.count ? "accent" : "paragraph",
borderRadius: "4px 4px 0 0",
transition: "all 0.3s ease",
position: "relative",
"&:hover": {
transform: "scaleY(1.05)",
transformOrigin: "bottom"
}
}}
></Box>
<Text
sx={{
fontSize: "11px",
color: "paragraph-secondary",
fontWeight: "normal",
transition: "all 0.3s ease"
}}
>
{MONTHS_SHORT[index]}
</Text>
</Flex>
);
})}
</Flex>
</>
);
}

View File

@@ -97,7 +97,6 @@ import { strings } from "@notesnook/intl";
import Tags from "../../views/tags";
import { Notebooks } from "../../views/notebooks";
import { UserProfile } from "../../dialogs/settings/components/user-profile";
import { SUBSCRIPTION_STATUS } from "../../common/constants";
import {
checkFeature,
createSetDefaultHomepageMenuItem,
@@ -105,7 +104,6 @@ import {
withFeatureCheck
} from "../../common";
import { TabItem } from "./tab-item";
import Notice from "../notice";
import { Freeze } from "react-freeze";
import { CREATE_BUTTON_MAP } from "../../common";
import { useStore as useNotebookStore } from "../../stores/notebook-store";
@@ -118,6 +116,7 @@ import {
useIsFeatureAvailable
} from "@notesnook/common";
import { isUserSubscribed } from "../../hooks/use-is-user-premium";
import { shouldShowWrapped } from "../../utils/should-show-wrapped";
type Route = {
id: "notes" | "favorites" | "reminders" | "monographs" | "trash" | "archive";
@@ -449,7 +448,17 @@ function NavigationMenu({ onExpand }: { onExpand?: () => void }) {
</FlexScrollContainer>
</Freeze>
</Flex>
{currentTab.id === "home" && !isCollapsed ? <Notice /> : null}
{currentTab.id === "home" && !isCollapsed && shouldShowWrapped() ? (
<Button
variant="accent"
sx={{ m: 2 }}
onClick={() => {
hardNavigate("/wrapped");
}}
>
🎉 Wrapped {new Date().getFullYear()}
</Button>
) : null}
</ScopedThemeProvider>
);
}

View File

@@ -153,7 +153,7 @@ export function Notebook(props: NotebookProps) {
variant={"body"}
color={isOpened ? "paragraph-selected" : "paragraph"}
sx={{
whiteSpace: "nowrap",
whiteSpace: "pre",
overflow: "hidden",
textOverflow: "ellipsis",
fontWeight: "body",

View File

@@ -67,7 +67,7 @@ function Tag(props: TagProps) {
variant={"body"}
color={isSelected ? "paragraph-selected" : "paragraph"}
sx={{
whiteSpace: "nowrap",
whiteSpace: "pre",
overflow: "hidden",
textOverflow: "ellipsis",
fontWeight: "body",

View File

@@ -147,7 +147,7 @@ export const BuyDialog = DialogManager.register(function BuyDialog(
</Text>
</Flex>
<PlansList
selectedPlan={user?.subscription.productId}
selectedPlan={user?.subscription?.productId}
recommendedPlan={SubscriptionPlan.PRO}
onPlanSelected={(plan) => selectPlan(plan)}
/>

View File

@@ -42,7 +42,7 @@ import { useStore as useUserStore } from "../../stores/user-store";
import { getCurrencySymbol } from "../../common/currencies";
type PlansListProps = {
selectedPlan?: string;
selectedPlan?: string | null;
loadAllPlans?: boolean;
ignoreTrial?: boolean;
recommendedPlan?: SubscriptionPlan;

View File

@@ -454,7 +454,7 @@ export async function checkUpload(
expectedSize: number
) {
const size = await getUploadedFileSize(filename);
const totalChunks = Math.ceil(size / chunkSize);
const totalChunks = Math.ceil(size / (chunkSize + ABYTES));
const decryptedLength = size - totalChunks * ABYTES;
const error =
size === 0
@@ -520,7 +520,7 @@ async function downloadFile(
throw new Error(error);
}
const totalChunks = Math.ceil(size / chunkSize);
const totalChunks = Math.ceil(size / (chunkSize + ABYTES));
const decryptedLength = size - totalChunks * ABYTES;
if (attachment && attachment.size !== decryptedLength) {
const error = `File length mismatch. Expected ${attachment.size} but got ${decryptedLength} bytes. Please upload this file again from the attachment manager. (File hash: ${filename})`;

View File

@@ -20,8 +20,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { INNCrypto } from "@notesnook/crypto";
import CryptoWorker from "./nncrypto.worker?worker";
import { wrap } from "comlink";
import { NNCrypto as NNCryptoSync } from "@notesnook/crypto";
import { isTransferableStreamsSupported } from "../utils/feature-check";
export const NNCrypto = wrap<INNCrypto>(new CryptoWorker()) as INNCrypto;
export const NNCrypto = isTransferableStreamsSupported()
? (wrap<INNCrypto>(new CryptoWorker()) as INNCrypto)
: new NNCryptoSync();
// TODO: disable until we fix the `pull failed` errors for good.
// IS_DESKTOP_APP && window.NativeNNCrypto
// ? new window.NativeNNCrypto()

View File

@@ -22,9 +22,28 @@ const FEATURE_CHECKS = {
cache: false,
indexedDB: false,
clonableCryptoKey: false,
applePaySupported: false
applePaySupported: false,
transferableStreams: false
};
export function isTransferableStreamsSupported() {
try {
const readable = new ReadableStream({
pull(controller) {
controller.enqueue(new Uint8Array([1, 2, 3]));
controller.close();
}
});
window.postMessage(readable, [readable]);
FEATURE_CHECKS.transferableStreams = true;
return true;
} catch {
console.log("Transferable streams not supported");
FEATURE_CHECKS.transferableStreams = false;
return false;
}
}
async function isApplePaySupported() {
try {
FEATURE_CHECKS.applePaySupported =
@@ -99,6 +118,7 @@ async function isCryptoKeyClonable() {
export async function initializeFeatureChecks() {
await Promise.allSettled([
isTransferableStreamsSupported(),
isOPFSSupported(),
isCacheSupported(),
isIndexedDBSupported(),

View File

@@ -0,0 +1,23 @@
/*
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 const shouldShowWrapped = () => {
const now = new Date();
return now.getMonth() === 11;
};

View File

@@ -0,0 +1,776 @@
/*
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 { Button, Flex, Text, Box, FlexProps } from "@theme-ui/components";
import { db } from "../common/db";
import { useState, useEffect, useRef } from "react";
import { NoteStats, WrappedStats } from "@notesnook/core";
import { formatBytes } from "@notesnook/common";
import { ArrowDown, ArrowLeft, Loading } from "../components/icons";
import { hardNavigate } from "../navigation";
import { MonthlyActivityHeatmap } from "../components/monthly-activity-heatmap";
function formatNumber(num: number) {
return num.toLocaleString();
}
function formatCount(num: number) {
return num > 1000 ? `${(num / 1000).toFixed(0)}k` : num.toString();
}
interface SlideProps {
children: React.ReactNode;
pattern?: "dots" | "grid" | "diagonal" | "none";
}
function Slide({
children,
pattern = "none",
sx,
...flexProps
}: SlideProps & FlexProps) {
const slideRef = useRef<HTMLDivElement>(null);
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
}
},
{ threshold: 0.5 }
);
if (slideRef.current) {
observer.observe(slideRef.current);
}
return () => {
if (slideRef.current) {
observer.unobserve(slideRef.current);
}
};
}, []);
const getBackgroundPattern = () => {
switch (pattern) {
case "dots":
return "radial-gradient(circle, var(--border) 1px, transparent 1px)";
case "grid":
return "linear-gradient(var(--border) 1px, transparent 1px), linear-gradient(90deg, var(--border) 1px, transparent 1px)";
case "diagonal":
return "repeating-linear-gradient(-45deg, transparent, transparent 20px, var(--border) 20px, var(--border) 21px)";
default:
return "none";
}
};
const getBackgroundSize = () => {
switch (pattern) {
case "dots":
return "20px 20px";
case "grid":
return "30px 30px";
case "diagonal":
default:
return "auto";
}
};
return (
<Flex
ref={slideRef}
sx={{
minHeight: "100vh",
minWidth: "100%",
scrollSnapAlign: "start",
scrollSnapStop: "always",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
p: 4,
position: "relative",
backgroundImage: getBackgroundPattern(),
backgroundSize: getBackgroundSize(),
backgroundPosition: "center"
}}
>
<Flex
{...flexProps}
sx={{
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
opacity: isVisible ? 1 : 0,
transform: isVisible ? "translateY(0)" : "translateY(50px)",
transition: "opacity 0.8s ease-out, transform 0.8s ease-out",
...sx
}}
>
{children}
</Flex>
</Flex>
);
}
function WelcomeSlide({ loading }: { loading: boolean }) {
return (
<Slide pattern="dots">
<Text
sx={{
fontSize: "6rem",
fontWeight: "bold",
mb: 3,
textAlign: "center",
background: "linear-gradient(135deg, #667eea 0%, #764ba2 100%)",
WebkitBackgroundClip: "text",
WebkitTextFillColor: "transparent",
animation: "slideUp 1s ease-out"
}}
>
🎉
</Text>
<Text
variant="heading"
sx={{
fontSize: ["2rem", "3rem", "4rem"],
fontWeight: "bold",
mb: 2,
textAlign: "center",
animation: "slideUp 1s ease-out 0.2s both"
}}
>
Your {new Date().getFullYear()} Wrapped
</Text>
{loading ? (
<Loading
sx={{
animation: "fadeIn 1s ease-out 0.4s both"
}}
size={30}
/>
) : (
<>
<Text
sx={{
fontSize: ["1rem", "1.2rem"],
textAlign: "center",
animation: "fadeIn 1s ease-out 0.4s both"
}}
>
Let&apos;s look back at your year in Notesnook
</Text>
<Text
variant="body"
sx={{
mt: 4,
fontSize: "title",
color: "paragraph-secondary",
textAlign: "center",
animation: "fadeIn 1s ease-out 0.4s both"
}}
>
Scroll down to explore
</Text>
<ArrowDown sx={{ mt: 2 }} color="paragraph-secondary" />
</>
)}
</Slide>
);
}
function TotalNotesSlide({ count }: { count: number }) {
return (
<Slide pattern="dots">
<Flex>
<Flex sx={{ flexDirection: "column" }}>
<Text
variant="body"
sx={{
fontSize: ["1.2rem", "1.5rem"],
textAlign: "center",
animation: "fadeIn 0.8s ease-out"
}}
>
You created
</Text>
<Text
variant="heading"
sx={{
fontSize: ["5rem", "7rem", "8rem"],
textAlign: "center",
color: "accent",
animation: "scaleIn 0.8s ease-out 0.2s both"
}}
>
{formatNumber(count)}
</Text>
<Text
variant="body"
sx={{
fontSize: ["1.2rem", "1.5rem"],
textAlign: "center",
animation: "fadeIn 0.8s ease-out 0.4s both"
}}
>
notes this year
</Text>
</Flex>
<Flex
sx={{
flexDirection: "column",
ml: [0, 5],
mt: [4, 0],
borderLeft: ["none", "1px solid var(--border)"],
pl: [0, 5],
borderTop: ["1px solid var(--border)", "none"]
}}
>
<Text
variant="body"
color="paragraph-secondary"
sx={{
fontSize: ["1.2rem", "1.2rem"],
animation: "fadeIn 0.8s ease-out",
lineHeight: 1.8
}}
>
That&apos;s <strong>{formatNumber(count)}</strong>
<br />
ideas
<br />
thoughts
<br />
memories.
<br />
100% encrypted.
<br />
100% yours.
</Text>
</Flex>
</Flex>
</Slide>
);
}
const TOTAL_WORDS_TAGLINES = {
1000: "That's longer than the average blog post on Medium!",
10000: "That's real commitment to your mindspace.",
25000: "That's almost the length of a short novel!",
50000:
"That's the length of The Great Gatsby if you were F. Scott Fitzgerald.",
100000: "You wrote more than many published authors this year.",
250000: "Woah! Your notes could fill a small library.",
500000: "Your vault is becoming a chronicle.",
1000000: "That's longer than the entire Harry Potter series!"
};
function TotalWordsSlide({ count }: { count: number }) {
const tagline = Object.entries(TOTAL_WORDS_TAGLINES)
.sort((a, b) => Number(b[0]) - Number(a[0]))
.find(([threshold]) => count >= Number(threshold))?.[1];
return (
<Slide pattern="dots">
<Text
variant="body"
sx={{
fontSize: ["1.2rem", "1.5rem"],
textAlign: "center",
animation: "fadeIn 0.8s ease-out"
}}
>
You wrote a total of
</Text>
<Text
variant="heading"
sx={{
fontSize: ["5rem", "7rem"],
textAlign: "center",
color: "accent",
animation: "scaleIn 0.8s ease-out 0.2s both"
}}
>
{formatNumber(count)}
</Text>
<Text
variant="body"
sx={{
fontSize: ["1.2rem", "1.5rem"],
textAlign: "center",
animation: "fadeIn 0.8s ease-out 0.4s both"
}}
>
words this year
</Text>
{tagline ? (
<Text
variant="body"
color="paragraph-secondary"
sx={{
fontSize: ["1.2rem", "1.2rem"],
animation: "fadeIn 0.8s ease-out",
borderTop: "1px solid var(--border)",
mt: 3,
pt: 3
}}
>
{tagline}
</Text>
) : null}
</Slide>
);
}
type ActivityStatsSlideProps = {
mostNotesCreatedInMonth: NoteStats["mostNotesCreatedInMonth"];
mostNotesCreatedInDay: NoteStats["mostNotesCreatedInDay"];
};
function ActivityStatsSlide({
mostNotesCreatedInMonth,
mostNotesCreatedInDay
}: ActivityStatsSlideProps) {
if (!mostNotesCreatedInMonth && !mostNotesCreatedInDay) return null;
return (
<Slide pattern="diagonal" sx={{ alignItems: "start" }}>
{mostNotesCreatedInMonth && (
<>
<Text
variant="body"
sx={{
fontSize: ["1.2rem", "1.5rem"]
}}
>
Your most productive month was
</Text>
<Flex
sx={{
alignItems: "center",
justifyContent: "space-between",
width: "100%"
}}
>
<Text
variant="heading"
sx={{
fontSize: ["5rem", "7rem", "3rem"]
}}
>
{mostNotesCreatedInMonth.month}
</Text>
<Text
variant="body"
sx={{ fontSize: "1rem" }}
color="paragraph-secondary"
>
{formatNumber(mostNotesCreatedInMonth.count)} notes
</Text>
</Flex>
</>
)}
{mostNotesCreatedInDay && (
<>
<Text
variant="body"
sx={{
fontSize: ["1.2rem", "1.5rem"],
mt: 5
}}
>
Your favorite day to write was
</Text>
<Flex
sx={{
alignItems: "center",
justifyContent: "space-between",
width: "100%"
}}
>
<Text
variant="heading"
sx={{
fontSize: ["5rem", "7rem", "3rem"]
}}
>
{mostNotesCreatedInDay.day}
</Text>
<Text
variant="body"
sx={{ fontSize: "1rem" }}
color="paragraph-secondary"
>
{formatNumber(mostNotesCreatedInDay.count)} notes
</Text>
</Flex>
</>
)}
</Slide>
);
}
function SummarySlide({ stats }: { stats: WrappedStats }) {
return (
<Slide
pattern="dots"
sx={{
bg: "background-secondary",
border: "1px solid var(--accent)",
borderRadius: "dialog",
boxShadow: "-15px 15px 0px 0px var(--accent)",
p: 5
}}
>
<svg
style={{
height: 20,
width: 20,
position: "absolute",
bottom: "20px",
right: "20px"
}}
>
<use href="#themed-logo" />
</svg>
<Text
variant="heading"
sx={{
fontSize: "1.2rem",
fontWeight: 800,
textAlign: "center",
transform: "skew(-10deg) scaleX(1.5)",
letterSpacing: "-1px",
mb: "25px",
textDecorationLine: "underline",
textDecorationColor: "border"
}}
>
NOTESNOOK WRAPPED {new Date().getFullYear()}
</Text>
<Flex
sx={{
flexDirection: ["column", "row"],
gap: "30px",
justifyContent: "center",
alignItems: "center"
}}
>
<Flex
sx={{
flexDirection: "column",
flex: 1
}}
>
<Box
sx={{
display: "grid",
gridTemplateColumns: "1fr 1fr 1fr",
gridTemplateRows: "1fr 1fr",
gap: 4,
height: "100%"
}}
>
{[
{
icon: "📝",
count: stats.totalNotes,
label: "Notes"
},
{
icon: "🎨",
count: stats.totalColors,
label: "Colors"
},
{
icon: "📚",
count: stats.totalNotebooks,
label: "Notebooks"
},
{
icon: "🏷️",
count: stats.totalTags,
label: "Tags"
},
{
icon: "📂",
count: stats.totalAttachments,
label: "Files"
},
{
icon: "☁️",
count: stats.totalMonographs,
label: "Monographs"
}
].map(({ icon, count, label }) => (
<Flex
key={label}
sx={{
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: 1
}}
>
<Text sx={{ fontSize: "1.5rem" }}>{icon}</Text>
<Text
sx={{
fontSize: "2rem",
fontWeight: "bold"
}}
>
{formatCount(count)}
</Text>
<Text sx={{ fontSize: "0.85rem", color: "fontTertiary" }}>
{label}
</Text>
</Flex>
))}
</Box>
<Flex
sx={{
flexDirection: "column",
gap: 2,
borderTop: "1px solid var(--border)",
mt: 6,
pt: 3,
"& strong": {
color: "accent"
}
}}
>
<Text sx={{ fontSize: "1rem", fontWeight: "bold" }}>
Fun facts of the year
</Text>
{stats.mostNotesCreatedInMonth && (
<Text sx={{ fontSize: "0.9rem", color: "fontTertiary" }}>
📅 Your most productive month was{" "}
<Text as="strong">{stats.mostNotesCreatedInMonth.month}</Text>
</Text>
)}
{stats.mostNotesCreatedInDay && (
<Text sx={{ fontSize: "0.9rem", color: "fontTertiary" }}>
🗓 Your favorite day to write was{" "}
<Text as="strong">{stats.mostNotesCreatedInDay.day}</Text>
</Text>
)}
{stats.largestNote && (
<Text
sx={{
fontSize: "0.9rem",
color: "fontTertiary",
maxWidth: 340
}}
>
📝 Your longest note was{" "}
<Text as="strong">
{formatNumber(stats.largestNote.length)}
{" words"}
</Text>
</Text>
)}
{stats.largestAttachment && (
<Text
sx={{
fontSize: "0.9rem",
color: "fontTertiary",
maxWidth: 340
}}
>
🔗 Your largest attachment was{" "}
<strong>{formatBytes(stats.largestAttachment.size)}</strong>
</Text>
)}
</Flex>
</Flex>
<Flex
sx={{
flexDirection: "column",
justifyContent: "stretch",
height: "100%",
flex: 1
}}
>
<Flex
sx={{
flexDirection: "column",
justifyContent: "center",
alignItems: "center"
}}
>
<Text sx={{ fontSize: "2.5rem" }}></Text>
<Text
sx={{
fontSize: "3rem",
fontWeight: "bold",
color: "accent"
}}
>
{formatNumber(stats.totalWords)}
</Text>
<Text sx={{ fontSize: "1rem", color: "fontTertiary" }}>
Words Written
</Text>
</Flex>
<Flex
sx={{
flexDirection: "column",
borderTop: "1px solid var(--border)",
mt: 6,
pt: 2,
flex: 1
}}
>
<MonthlyActivityHeatmap monthlyStats={stats.monthlyStats} />
<Text
sx={{
fontSize: "1rem",
color: "fontTertiary",
textAlign: "center"
}}
>
Notes per month
</Text>
</Flex>
</Flex>
</Flex>
<Text
variant="body"
color="paragraph-secondary"
sx={{ mt: 2, borderTop: "1px solid var(--border)", pt: 2 }}
>
Generated 100% locally on your device.
</Text>
</Slide>
);
}
export default function Wrapped() {
const [loading, setLoading] = useState(false);
const [stats, setStats] = useState<WrappedStats>();
useEffect(() => {
async function loadWrapped() {
setLoading(true);
try {
console.time("wrapped - getting from core");
const wrapped = await db.wrapped.get();
console.timeEnd("wrapped - getting from core");
setStats(wrapped);
} finally {
setLoading(false);
}
}
loadWrapped();
}, []);
return (
<>
<style>
{`
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
`}
</style>
<Flex
sx={{
position: "fixed",
top: 0,
left: 0,
right: 0,
bottom: 0,
flexDirection: "column",
overflowY: "scroll",
overflowX: "hidden",
scrollSnapType: "y mandatory",
scrollBehavior: "smooth",
"&::-webkit-scrollbar": {
display: "none"
},
msOverflowStyle: "none",
scrollbarWidth: "none"
}}
>
<WelcomeSlide loading={loading} />
{stats ? (
<>
<TotalNotesSlide count={stats.totalNotes} />
{stats.totalWords > 0 && (
<TotalWordsSlide count={stats.totalWords} />
)}
{(stats.mostNotesCreatedInMonth || stats.mostNotesCreatedInDay) && (
<ActivityStatsSlide
mostNotesCreatedInMonth={stats.mostNotesCreatedInMonth}
mostNotesCreatedInDay={stats.mostNotesCreatedInDay}
/>
)}
<SummarySlide stats={stats} />
</>
) : null}
</Flex>
<Button
onClick={() => hardNavigate("/")}
variant="secondary"
sx={{
position: "fixed",
top: 3,
left: 3,
zIndex: 1000
}}
>
<Flex sx={{ alignItems: "center", gap: 1, justifyContent: "center" }}>
<ArrowLeft size={16} />
<Text variant="body">Go back to app</Text>
</Flex>
</Button>
</>
);
}

View File

@@ -9,71 +9,73 @@ The following keyboard shortcuts will help you navigate Notesnook faster.
### General
| Description | Web | Windows/Linux | Mac |
| -------------------------------------------------- | ------ | ------------- | --- |
| Search in notes list view if editor is not focused | Ctrl F | Ctrl F | ⌘ F |
| Settings | Ctrl , | Ctrl , | ⌘ , |
| Keyboard shortcuts | Ctrl / | Ctrl / | ⌘ / |
| New note | - | Ctrl N | ⌘ N |
| Description | Web | Windows/Linux | Mac |
| --- | --- | --- | --- |
| Search in notes list view if editor is not focused | Ctrl F | Ctrl F | ⌘ F |
| Settings | Ctrl , | Ctrl , | ⌘ , |
| Keyboard shortcuts | Ctrl / | Ctrl / | ⌘ / |
| New note | - | Ctrl N | ⌘ N |
### Navigation
| Description | Web | Windows/Linux | Mac |
| ---------------- | ------------------------- | ------------- | ------- |
| Next tab | Ctrl Alt → / Ctrl Alt ⇧ → | Ctrl tab | ⌘ tab |
| Previous tab | Ctrl Alt ← / Ctrl Alt ⇧ ← | Ctrl ⇧ tab | ⌘ ⇧ tab |
| Command palette | Ctrl ⇧ P | Ctrl ⇧ P | ⌘ ⇧ P |
| Quick open | Ctrl P | Ctrl P | ⌘ P |
| New tab | - | Ctrl T | ⌘ T |
| Close active tab | - | Ctrl W | ⌘ W |
| Close all tabs | - | Ctrl ⇧ W | ⌘ ⇧ W |
| Description | Web | Windows/Linux | Mac |
| --- | --- | --- | --- |
| Next tab | Ctrl Alt → / Ctrl Alt ⇧ → | Ctrl tab | ⌘ tab |
| Previous tab | Ctrl Alt ← / Ctrl Alt ⇧ ← | Ctrl ⇧ tab | ⌘ ⇧ tab |
| Command palette | Ctrl ⇧ P | Ctrl ⇧ P | ⌘ ⇧ P |
| Quick open | Ctrl P | Ctrl P | ⌘ P |
| New tab | - | Ctrl T | ⌘ T |
| Close active tab | - | Ctrl W | ⌘ W |
| Close all tabs | - | Ctrl ⇧ W | ⌘ ⇧ W |
### Editor
| Description | Web | Windows/Linux | Mac |
| ---------------------------------- | ----------------- | ----------------- | ----------- |
| Add attachment | Ctrl ⇧ A | Ctrl ⇧ A | ⌘ ⇧ A |
| Insert blockquote | Ctrl ⇧ B | Ctrl ⇧ B | ⌘ ⇧ B |
| Toggle bold | Ctrl B | Ctrl B | ⌘ B |
| Toggle bullet list | Ctrl ⇧ 8 | Ctrl ⇧ 8 | ⌘ ⇧ 8 |
| Toggle check list | Ctrl ⇧ 9 | Ctrl ⇧ 9 | ⌘ ⇧ 9 |
| Split list item | ↵ | ↵ | ↵ |
| Lift list item | ⇧ Tab | ⇧ Tab | ⇧ Tab |
| Sink list item | Ctrl ⇧ Down | Ctrl ⇧ Down | ⌘ ⇧ Down |
| Toggle code | Ctrl E | Ctrl E | ⌘ E |
| Toggle code block | Ctrl ⇧ C | Ctrl ⇧ C | ⌘ ⇧ C |
| Insert date | Alt D | Alt D | ⌥ D |
| Insert time | Alt T | Alt T | ⌥ T |
| Insert date and time | Ctrl Alt D | Ctrl Alt D | ⌘ ⌥ D |
| Insert date and time with timezone | Ctrl Alt Z | Ctrl Alt Z | ⌘ ⌥ Z |
| Increase font size | Ctrl [ | Ctrl [ | ⌘ [ |
| Decrease font size | Ctrl ] | Ctrl ] | ⌘ ] |
| Insert paragraph | Ctrl ⇧ 0 | Ctrl ⇧ 0 | ⌘ 0 |
| Insert heading 1 | Ctrl Alt 1 | Ctrl Alt 1 | ⌘ ⌥ 1 |
| Insert heading 2 | Ctrl Alt 2 | Ctrl Alt 2 | ⌘ ⌥ 2 |
| Insert heading 3 | Ctrl Alt 3 | Ctrl Alt 3 | ⌘ ⌥ 3 |
| Insert heading 4 | Ctrl Alt 4 | Ctrl Alt 4 | ⌘ ⌥ 4 |
| Insert heading 5 | Ctrl Alt 5 | Ctrl Alt 5 | ⌘ ⌥ 5 |
| Insert heading 6 | Ctrl Alt 6 | Ctrl Alt 6 | ⌘ ⌥ 6 |
| Undo | Ctrl Z | Ctrl Z | ⌘ Z |
| Redo | Ctrl ⇧ Z / Ctrl Y | Ctrl ⇧ Z / Ctrl Y | ⌘ ⇧ Z / ⌘ Y |
| Add image | Ctrl ⇧ I | Ctrl ⇧ I | ⌘ ⇧ I |
| Toggle italic | Ctrl I | Ctrl I | ⌘ I |
| Remove formatting in selection | Ctrl \ | Ctrl \ | ⌘ \ |
| Insert internal link | Ctrl ⇧ K | Ctrl ⇧ K | ⌘ ⇧ K |
| Insert link | Ctrl K | Ctrl K | ⌘ K |
| Insert math block | Ctrl ⇧ M | Ctrl ⇧ M | ⌘ ⇧ M |
| Toggle ordered list | Ctrl ⇧ 7 | Ctrl ⇧ 7 | ⌘ ⇧ 7 |
| Toggle outline list | Ctrl ⇧ O | Ctrl ⇧ O | ⌘ ⇧ O |
| Toggle outline list expand | Ctrl Space | Ctrl Space | ⌘ Space |
| Open search | Ctrl F | Ctrl F | ⌘ F |
| Toggle strike | Ctrl ⇧ S | Ctrl ⇧ S | ⌘ ⇧ S |
| Toggle subscript | Ctrl , | Ctrl , | ⌘ , |
| Toggle superscript | Ctrl . | Ctrl . | ⌘ . |
| Toggle task list | Ctrl ⇧ T | Ctrl ⇧ T | ⌘ ⇧ T |
| Text align center | Ctrl ⇧ E | Ctrl ⇧ E | ⌘ ⇧ E |
| Text align justify | Ctrl ⇧ J | Ctrl ⇧ J | ⌘ ⇧ J |
| Text align left | Ctrl ⇧ L | Ctrl ⇧ L | ⌘ ⇧ L |
| Text align right | Ctrl ⇧ R | Ctrl ⇧ R | ⌘ ⇧ R |
| Underline | Ctrl U | Ctrl U | ⌘ U |
| Toggle highlight | Ctrl Alt H | Ctrl Alt H | ⌘ ⌥ H |
| Description | Web | Windows/Linux | Mac |
| --- | --- | --- | --- |
| Add attachment | Ctrl ⇧ A | Ctrl ⇧ A | ⌘ ⇧ A |
| Insert blockquote | Ctrl ⇧ B | Ctrl ⇧ B | ⌘ ⇧ B |
| Toggle bold | Ctrl B | Ctrl B | ⌘ B |
| Toggle bullet list | Ctrl ⇧ 8 | Ctrl ⇧ 8 | ⌘ ⇧ 8 |
| Toggle check list | Ctrl ⇧ 9 | Ctrl ⇧ 9 | ⌘ ⇧ 9 |
| Split list item | ↵ | ↵ | ↵ |
| Lift list item | ⇧ Tab | ⇧ Tab | ⇧ Tab |
| Sink list item | Ctrl ⇧ Down | Ctrl ⇧ Down | ⌘ ⇧ Down |
| Toggle code | Ctrl E | Ctrl E | ⌘ E |
| Toggle code block | Ctrl ⇧ C | Ctrl ⇧ C | ⌘ ⇧ C |
| Insert date | Alt D | Alt D | ⌥ D |
| Insert time | Alt T | Alt T | ⌥ T |
| Insert date and time | Ctrl Alt D | Ctrl Alt D | ⌘ ⌥ D |
| Insert date and time with timezone | Ctrl Alt Z | Ctrl Alt Z | ⌘ ⌥ Z |
| Increase font size | Ctrl [ | Ctrl [ | ⌘ [ |
| Decrease font size | Ctrl ] | Ctrl ] | ⌘ ] |
| Insert paragraph | Ctrl Alt 0 | Ctrl Alt 0 | ⌘ 0 |
| Insert heading 1 | Ctrl Alt 1 | Ctrl Alt 1 | ⌘ ⌥ 1 |
| Insert heading 2 | Ctrl Alt 2 | Ctrl Alt 2 | ⌘ ⌥ 2 |
| Insert heading 3 | Ctrl Alt 3 | Ctrl Alt 3 | ⌘ ⌥ 3 |
| Insert heading 4 | Ctrl Alt 4 | Ctrl Alt 4 | ⌘ ⌥ 4 |
| Insert heading 5 | Ctrl Alt 5 | Ctrl Alt 5 | ⌘ ⌥ 5 |
| Insert heading 6 | Ctrl Alt 6 | Ctrl Alt 6 | ⌘ ⌥ 6 |
| Undo | Ctrl Z | Ctrl Z | ⌘ Z |
| Redo | Ctrl ⇧ Z / Ctrl Y | Ctrl ⇧ Z / Ctrl Y | ⌘ ⇧ Z / ⌘ Y |
| Add image | Ctrl ⇧ I | Ctrl ⇧ I | ⌘ ⇧ I |
| Toggle italic | Ctrl I | Ctrl I | ⌘ I |
| Remove formatting in selection | Ctrl \ | Ctrl \ | ⌘ \ |
| Insert internal link | Ctrl ⇧ K | Ctrl ⇧ K | ⌘ ⇧ K |
| Insert link | Ctrl K | Ctrl K | ⌘ K |
| Insert math block | Ctrl ⇧ M | Ctrl ⇧ M | ⌘ ⇧ M |
| Toggle ordered list | Ctrl ⇧ 7 | Ctrl ⇧ 7 | ⌘ ⇧ 7 |
| Toggle outline list | Ctrl ⇧ O | Ctrl ⇧ O | ⌘ ⇧ O |
| Toggle outline list expand | Ctrl Space | Ctrl Space | ⌘ Space |
| Open search | Ctrl F | Ctrl F | ⌘ F |
| Open search and replace | Ctrl Alt F | Ctrl Alt F | ⌘ ⌥ F |
| Toggle strike | Ctrl ⇧ S | Ctrl ⇧ S | ⌘ ⇧ S |
| Toggle subscript | Ctrl , | Ctrl , | ⌘ , |
| Toggle superscript | Ctrl . | Ctrl . | ⌘ . |
| Toggle task list | Ctrl ⇧ T | Ctrl ⇧ T | ⌘ ⇧ T |
| Text align center | Ctrl ⇧ E | Ctrl ⇧ E | ⌘ ⇧ E |
| Text align justify | Ctrl ⇧ J | Ctrl ⇧ J | ⌘ ⇧ J |
| Text align left | Ctrl ⇧ L | Ctrl ⇧ L | ⌘ ⇧ L |
| Text align right | Ctrl ⇧ R | Ctrl ⇧ R | ⌘ ⇧ R |
| Underline | Ctrl U | Ctrl U | ⌘ U |
| Toggle highlight | Ctrl Alt H | Ctrl Alt H | ⌘ ⌥ H |
| Toggle text color | Ctrl Alt C | Ctrl Alt C | ⌘ ⌥ C |

View File

@@ -0,0 +1,5 @@
- Get an overview of your year with Notesnook with Wrapped 2025
- Cell selection and column resizing in tables
- Bug fixes and minor improvements
Thank you for using Notesnook!

View File

@@ -34,10 +34,7 @@ export default defineConfig({
js: "clipper.bundle.js"
},
distPath: {
root: path.resolve(
__dirname,
"../../apps/mobile/native/ios/extension.bundle"
)
root: path.resolve(__dirname, "../../apps/mobile/ios/extension.bundle")
}
}
});

View File

@@ -26,9 +26,13 @@ type CloneNodeOptions = {
export function cloneNode(node: HTMLElement, options: CloneNodeOptions) {
node = node.cloneNode(true) as HTMLElement;
const images = node.querySelectorAll("img");
if (!options.images) {
const images = node.getElementsByTagName("img");
for (const image of images) image.remove();
} else {
for (const image of images) {
image.src = image.currentSrc;
}
}
if (!options.styles) {

View File

@@ -41,6 +41,7 @@
"@streetwriters/kysely": "^0.27.4",
"@streetwriters/showdown": "^3.0.9-alpha",
"@types/mime-db": "^1.43.5",
"alfaaz": "^1.1.0",
"async-mutex": "0.5.0",
"dayjs": "1.11.13",
"dom-serializer": "^2.0.0",

View File

@@ -335,6 +335,12 @@ export const tiptapKeys = {
category: "Editor",
type: "tiptap"
},
openSearchAndReplace: {
keys: "Mod-Alt-f",
description: "Open search and replace",
category: "Editor",
type: "tiptap"
},
toggleStrike: {
keys: "Mod-Shift-S",
description: "Toggle strike",
@@ -394,6 +400,12 @@ export const tiptapKeys = {
description: "Toggle highlight",
category: "Editor",
type: "tiptap"
},
toggleTextColor: {
keys: "Mod-Alt-c",
description: "Toggle text color",
category: "Editor",
type: "tiptap"
}
} satisfies Record<string, TipTapKey>;

View File

@@ -18,6 +18,7 @@
"@streetwriters/kysely": "^0.27.4",
"@streetwriters/showdown": "^3.0.9-alpha",
"@types/mime-db": "^1.43.5",
"alfaaz": "^1.1.0",
"async-mutex": "0.5.0",
"dayjs": "1.11.13",
"dom-serializer": "^2.0.0",
@@ -1391,6 +1392,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/alfaaz": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/alfaaz/-/alfaaz-1.1.0.tgz",
"integrity": "sha512-J/P07R41APslK7NmD5303bwStN8jpRA4DdvtLeAr1Jhfj6XWGrASUWI0G6jbWjJAZyw3Lu1Pb4J8rsM/cb+xDQ==",
"license": "MIT"
},
"node_modules/ansi-regex": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",

View File

@@ -73,6 +73,7 @@
"@streetwriters/kysely": "^0.27.4",
"@streetwriters/showdown": "^3.0.9-alpha",
"@types/mime-db": "^1.43.5",
"alfaaz": "^1.1.0",
"async-mutex": "0.5.0",
"dayjs": "1.11.13",
"dom-serializer": "^2.0.0",

View File

@@ -83,6 +83,7 @@ import { ConfigStorage } from "../database/config.js";
import { LazyPromise } from "../utils/lazy-promise.js";
import { InboxApiKeys } from "./inbox-api-keys.js";
import { Circle } from "./circle.js";
import { Wrapped } from "./wrapped.js";
type EventSourceConstructor = new (
uri: string,
@@ -224,6 +225,8 @@ class Database {
inboxApiKeys = new InboxApiKeys(this, this.tokenManager);
wrapped = new Wrapped(this);
/**
* @deprecated only kept here for migration purposes
*/

View File

@@ -0,0 +1,342 @@
/*
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 { Parser } from "htmlparser2";
import Database from "./index.js";
import { countWords } from "alfaaz";
import { DatabaseSchema, isFalse } from "../database/index.js";
import { SelectQueryBuilder } from "@streetwriters/kysely";
const dayNames = [
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
];
const monthNames = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
];
export type NoteStats = {
totalNotes: number;
totalWords: number;
totalMonographs: number;
mostNotesCreatedInMonth: { month: string; count: number } | null;
mostNotesCreatedInDay: { day: string; count: number } | null;
monthlyStats: Record<string, number>;
dayOfWeekStats: Record<string, number>;
largestNote: { title: string; length: number } | null;
};
export type OrganizationStats = {
totalNotebooks: number;
totalTags: number;
mostUsedTags: { id: string; title: string; noteCount: number }[];
mostActiveNotebooks: { id: string; title: string; noteCount: number }[];
totalColors: number;
};
export type AttachmentStats = {
totalAttachments: number;
totalStorageUsed: number;
largestAttachment: { id: string; filename: string; size: number } | null;
mostCommonFileType: string | null;
};
export type WrappedStats = NoteStats & OrganizationStats & AttachmentStats;
export class Wrapped {
constructor(private readonly db: Database) {}
async get(): Promise<WrappedStats> {
const { startDate, endDate } = this.getYearRange();
const [noteStats, organizationStats, attachmentStats] = await Promise.all([
this.getNoteStats(startDate, endDate),
this.getOrganizationStats(startDate, endDate),
this.getAttachmentStats(startDate, endDate)
]);
return {
...noteStats,
...organizationStats,
...attachmentStats
};
}
private getYearRange(): { startDate: number; endDate: number } {
const year = new Date().getFullYear();
const startDate = new Date(year, 0, 1, 0, 0, 0, 0).getTime();
const endDate = new Date(year + 1, 0, 1, 0, 0, 0, 0).getTime();
return { startDate, endDate };
}
private async getNoteStats(
startDate: number,
endDate: number
): Promise<NoteStats> {
const notesSelector = this.db
.sql()
.selectFrom("notes")
.where((eb) => eb("dateCreated", ">=", startDate))
.where((eb) => eb("dateCreated", "<", endDate))
.where(isFalse("deleted"))
.where(isFalse("dateDeleted"));
const notes = await notesSelector.select(["notes.dateCreated"]).execute();
const monthlyStats: Map<string, number> = new Map();
const dayOfWeekStats: Map<string, number> = new Map();
let totalNotes = 0;
for (const note of notes) {
if (!note.dateCreated) continue;
totalNotes++;
const month = new Date(note.dateCreated).getMonth();
const monthName = monthNames[month];
monthlyStats.set(monthName, (monthlyStats.get(monthName) || 0) + 1);
const dayOfWeek = new Date(note.dateCreated).getDay();
const dayName = dayNames[dayOfWeek];
dayOfWeekStats.set(dayName, (dayOfWeekStats.get(dayName) || 0) + 1);
}
let mostNotesCreatedInMonth: NoteStats["mostNotesCreatedInMonth"] = null;
for (const [month, count] of monthlyStats.entries()) {
if (!mostNotesCreatedInMonth || count > mostNotesCreatedInMonth.count) {
mostNotesCreatedInMonth = { month, count };
}
}
let mostNotesCreatedInDay: NoteStats["mostNotesCreatedInDay"] = null;
let maxDayCount = 0;
for (const [day, count] of dayOfWeekStats.entries()) {
if (count > maxDayCount) {
maxDayCount = count;
mostNotesCreatedInDay = { day, count };
}
}
const totalMonographs = await this.db.monographs.all
.where((eb) =>
eb.and([
eb("dateCreated", ">=", startDate),
eb("dateCreated", "<", endDate)
])
)
.count();
const { largestNote, totalWords } = await this.countTotalWords(
notesSelector
);
return {
totalNotes,
totalWords,
totalMonographs,
largestNote: largestNote
? {
title: (await this.db.notes.note(largestNote.id))?.title || "",
length: largestNote.wordCount
}
: null,
monthlyStats: Object.fromEntries(monthlyStats),
dayOfWeekStats: Object.fromEntries(dayOfWeekStats),
mostNotesCreatedInMonth,
mostNotesCreatedInDay
};
}
private async countItemNotes<T extends { id: string; title: string }>(
items: T[],
itemType: "tag" | "notebook"
): Promise<Array<T & { noteCount: number }>> {
const allRelations = await this.db.relations
.from({ ids: items.map((item) => item.id), type: itemType }, "note")
.get();
const noteCounts: Map<string, number> = new Map();
for (const relation of allRelations) {
const itemId = relation.fromId;
noteCounts.set(itemId, (noteCounts.get(itemId) || 0) + 1);
}
return items
.map((item) => ({
...item,
noteCount: noteCounts.get(item.id) || 0
}))
.filter((item) => item.noteCount > 0)
.sort((a, b) => b.noteCount - a.noteCount);
}
private async getOrganizationStats(
startDate: number,
endDate: number
): Promise<OrganizationStats> {
const notebookSelector = this.db.notebooks.all
.where((eb) => eb("dateCreated", ">=", startDate))
.where((eb) => eb("dateCreated", "<", endDate));
const tagSelector = this.db.tags.all
.where((eb) => eb("dateCreated", ">=", startDate))
.where((eb) => eb("dateCreated", "<", endDate));
const [totalNotebooks, totalTags, tags, notebooks, totalColors] =
await Promise.all([
notebookSelector.count(),
tagSelector.count(),
tagSelector.fields(["tags.id", "tags.title"]).items(),
notebookSelector.fields(["notebooks.id", "notebooks.title"]).items(),
this.db.colors.all
.where((eb) => eb("dateCreated", ">=", startDate))
.where((eb) => eb("dateCreated", "<", endDate))
.count()
]);
const tagNotes = await this.countItemNotes(tags, "tag");
const mostUsedTags = tagNotes.slice(0, 3);
const notebookNotes = await this.countItemNotes(notebooks, "notebook");
const mostActiveNotebooks = notebookNotes.slice(0, 3);
return {
totalNotebooks,
totalTags,
mostUsedTags:
mostUsedTags.length > 0
? mostUsedTags
: tags.slice(0, 3).map((tag) => ({ ...tag, noteCount: 0 })),
mostActiveNotebooks:
mostActiveNotebooks.length > 0
? mostActiveNotebooks
: notebooks.slice(0, 3).map((n) => ({ ...n, noteCount: 0 })),
totalColors
};
}
private async getAttachmentStats(
startDate: number,
endDate: number
): Promise<AttachmentStats> {
const attachmentsSelector = this.db.attachments.all
.where((eb) => eb("dateCreated", ">=", startDate))
.where((eb) => eb("dateCreated", "<", endDate));
const totalAttachments = await attachmentsSelector.count();
if (totalAttachments === 0) {
return {
totalAttachments: 0,
totalStorageUsed: 0,
largestAttachment: null,
mostCommonFileType: null
};
}
const totalStorageUsed =
(await this.db.attachments.totalSize(attachmentsSelector)) || 0;
const attachments = await attachmentsSelector.items();
let largestAttachment: AttachmentStats["largestAttachment"] = null;
const mimeTypeCounts: Map<string, number> = new Map();
for (const attachment of attachments) {
if (!largestAttachment || attachment.size > largestAttachment.size) {
largestAttachment = {
id: attachment.id,
filename: attachment.filename,
size: attachment.size
};
}
const mimeType = attachment.mimeType.split("/")[0] || attachment.mimeType;
mimeTypeCounts.set(mimeType, (mimeTypeCounts.get(mimeType) || 0) + 1);
}
let mostCommonFileType: string | null = null;
let maxCount = 0;
for (const [mimeType, count] of mimeTypeCounts.entries()) {
if (count > maxCount) {
maxCount = count;
mostCommonFileType = mimeType;
}
}
return {
totalAttachments,
totalStorageUsed,
largestAttachment,
mostCommonFileType
};
}
private async countTotalWords(
selector: SelectQueryBuilder<DatabaseSchema, "notes", unknown>
) {
let words = 0;
let largestNote = { id: "", wordCount: 0 };
const contents = await this.db
.sql()
.selectFrom("content")
.where("noteId", "in", selector.select("id"))
.where(isFalse("locked"))
.where(isFalse("deleted"))
.select(["content.data", "content.noteId"])
.execute();
for (const content of contents) {
if (typeof content?.data !== "string") continue;
const counted = countWords(toTextContent(content.data));
words += counted;
if (content.noteId && counted > largestNote.wordCount) {
largestNote = { id: content.noteId, wordCount: counted };
}
}
return { totalWords: words, largestNote };
}
}
function toTextContent(html: string) {
let text = "";
const parser = new Parser({
ontext: (data) => {
text += data;
},
onclosetag() {
text += " ";
}
});
parser.write(html);
parser.end();
return text;
}

View File

@@ -45,3 +45,4 @@ export type { SyncOptions } from "./api/sync/index.js";
export { sanitizeTag } from "./collections/tags.js";
export { default as DataURL } from "./utils/dataurl.js";
export { type ResolveInternalLink } from "./content-types/tiptap.js";
export type * from "./api/wrapped.js";

View File

@@ -604,7 +604,7 @@ export type User = {
cancelURL: string | null;
expiry: number;
trialExpiry?: number;
productId: string;
productId: string | null;
provider: SubscriptionProvider;
start: number;
plan: SubscriptionPlan;

View File

@@ -17,8 +17,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { base64_variants, ISodium } from "@notesnook/sodium";
import KeyUtils from "./keyutils.js";
import { ISodium } from "@notesnook/sodium";
import KeyUtils, { base64_variants } from "./keyutils.js";
import {
Cipher,
Output,

View File

@@ -17,8 +17,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { ISodium, base64_variants } from "@notesnook/sodium";
import KeyUtils from "./keyutils.js";
import { ISodium } from "@notesnook/sodium";
import KeyUtils, { base64_variants } from "./keyutils.js";
import { Chunk, Cipher, Input, DataFormat, SerializedKey } from "./types.js";
const encoder = new TextEncoder();

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { ISodium, Sodium } from "@notesnook/sodium";
import { ISodium } from "@notesnook/sodium";
import Decryption from "./decryption.js";
import Encryption from "./encryption.js";
import { INNCrypto } from "./interfaces.js";
@@ -37,10 +37,11 @@ import {
export class NNCrypto implements INNCrypto {
private isReady = false;
private sodium: ISodium = new Sodium();
private sodium!: ISodium;
private async init() {
if (this.isReady) return;
this.sodium = new (await import("@notesnook/sodium")).Sodium();
await this.sodium.initialize();
this.isReady = true;
}

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { base64_variants, ISodium } from "@notesnook/sodium";
import { ISodium } from "@notesnook/sodium";
import {
EncryptionKey,
EncryptionKeyPair,
@@ -25,6 +25,13 @@ import {
SerializedKeyPair
} from "./types.js";
export enum base64_variants {
ORIGINAL = 1,
ORIGINAL_NO_PADDING = 3,
URLSAFE = 5,
URLSAFE_NO_PADDING = 7
}
export default class KeyUtils {
static deriveKey(
sodium: ISodium,

View File

@@ -109,3 +109,19 @@ for (const { name, setNode } of nodes) {
expect(editor.getHTML()).toMatchSnapshot();
});
}
test("empty heading should have empty class", () => {
const { editor } = createEditor({
extensions: {
heading: Heading.configure({ levels: [1, 2, 3, 4, 5, 6] })
},
initialContent: "<h1></h1>"
});
const headingElement = editor.view.dom.querySelector("h1");
expect(headingElement?.classList.contains("empty")).toBe(true);
editor.commands.setTextSelection(0);
editor.commands.insertContent("Some content");
expect(headingElement?.classList.contains("empty")).toBe(false);
});

View File

@@ -23,11 +23,9 @@ import {
textblockTypeInputRule
} from "@tiptap/core";
import { Heading as TiptapHeading } from "@tiptap/extension-heading";
import { isClickWithinBounds } from "../../utils/prosemirror.js";
import { Plugin, PluginKey, Selection, Transaction } from "@tiptap/pm/state";
import { Node } from "@tiptap/pm/model";
import { useToolbarStore } from "../../toolbar/stores/toolbar-store.js";
import { Decoration, DecorationSet } from "prosemirror-view";
import { Plugin, PluginKey, Selection, Transaction } from "@tiptap/pm/state";
import { Callout } from "../callout/callout.js";
const COLLAPSIBLE_BLOCK_TYPES = [
"paragraph",
@@ -44,7 +42,8 @@ const COLLAPSIBLE_BLOCK_TYPES = [
"outlineList",
"mathBlock",
"webclip",
"embed"
"embed",
"horizontalRule"
];
const HEADING_REGEX = /^(#{1,6})\s$/;
@@ -174,6 +173,10 @@ export const Heading = TiptapHeading.extend({
heading.setAttribute(attr, HTMLAttributes[attr]);
}
if (node.textContent === "") {
heading.classList.add("empty");
}
if (node.attrs.collapsed) heading.dataset.collapsed = "true";
else delete heading.dataset.collapsed;
@@ -187,6 +190,17 @@ export const Heading = TiptapHeading.extend({
if (typeof getPos === "boolean") return;
const pos = getPos();
const resolvedPos = editor.state.doc.resolve(pos);
const callout = findParentNodeClosestToPos(
resolvedPos,
(node) => node.type.name === Callout.name
);
// the first callout heading's collapsibility is handled by callout itself
if (callout?.node.firstChild === node) {
return;
}
const clientX =
e instanceof MouseEvent ? e.clientX : e.touches[0].clientX;
const clientY =
@@ -194,7 +208,7 @@ export const Heading = TiptapHeading.extend({
const isRtl =
e.target.dir === "rtl" ||
findParentNodeClosestToPos(
editor.state.doc.resolve(pos),
resolvedPos,
(node) => !!node.attrs.textDirection
)?.node.attrs.textDirection === "rtl";
@@ -254,6 +268,12 @@ export const Heading = TiptapHeading.extend({
return false;
}
if (updatedNode.textContent === "") {
heading.classList.add("empty");
} else {
heading.classList.remove("empty");
}
if (updatedNode.attrs.collapsed) heading.dataset.collapsed = "true";
else delete heading.dataset.collapsed;

View File

@@ -23,6 +23,8 @@ import { showLinkPopup } from "../../toolbar/popups/link-popup.js";
import { isListActive } from "../../utils/list.js";
import { tiptapKeys } from "@notesnook/common";
import { isInTable } from "../table/prosemirror-tables/util.js";
import { config } from "../../utils/config.js";
import { DEFAULT_COLORS } from "../../toolbar/tools/colors.js";
export const KeyMap = Extension.create({
name: "key-map",
@@ -71,6 +73,11 @@ export const KeyMap = Extension.create({
[tiptapKeys.insertLink.keys]: ({ editor }) => {
showLinkPopup(editor);
return true;
},
[tiptapKeys.toggleTextColor.keys]: ({ editor }) => {
const color =
config.get<"string">("textColor") || DEFAULT_COLORS.text[0];
return editor.commands.toggleMark("textStyle", { color });
}
};
}

View File

@@ -33,7 +33,7 @@ type DispatchFn = (tr: Transaction) => void;
declare module "@tiptap/core" {
interface Commands<ReturnType> {
searchreplace: {
startSearch: () => ReturnType;
startSearch: (isReplacing?: boolean) => ReturnType;
endSearch: () => ReturnType;
search: (term: string, options?: SearchSettings) => ReturnType;
moveToNextResult: () => ReturnType;
@@ -51,7 +51,7 @@ interface Result {
interface SearchOptions {
searchResultClass: string;
onStartSearch: (term?: string) => boolean;
onStartSearch: (term?: string, isReplacing?: boolean) => boolean;
onEndSearch: () => boolean;
}
@@ -242,7 +242,7 @@ export const SearchReplace = Extension.create<SearchOptions, SearchStorage>({
addCommands() {
return {
startSearch:
() =>
(isReplacing) =>
({ state, commands }) => {
const term = !state.selection.empty
? state.doc.textBetween(
@@ -252,7 +252,7 @@ export const SearchReplace = Extension.create<SearchOptions, SearchStorage>({
: undefined;
if (term) commands.search(term);
return this.options.onStartSearch(term);
return this.options.onStartSearch(term, isReplacing);
},
endSearch:
() =>
@@ -357,6 +357,8 @@ export const SearchReplace = Extension.create<SearchOptions, SearchStorage>({
return {
[tiptapKeys.openSearch.keys]: ({ editor }) =>
editor.commands.startSearch(),
[tiptapKeys.openSearchAndReplace.keys]: ({ editor }) =>
editor.commands.startSearch(true),
Escape: ({ editor }) => editor.commands.endSearch()
};
},

View File

@@ -198,7 +198,7 @@ export function handleMouseDown(
// cell selection to be created).
setCellSelection($anchor, startEvent);
startEvent.preventDefault();
} else if (startEvent instanceof TouchEvent) {
} else if (isTouchEvent(startEvent)) {
const selectedCell = view.domAtPos(view.state.selection.from).node;
if (startDOMCell?.contains(selectedCell)) {
$anchor = cellUnderMouse(view, startEvent);
@@ -313,3 +313,7 @@ function cellUnderMouse(
if (!mousePos) return null;
return mousePos ? cellAround(view.state.doc.resolve(mousePos.pos)) : null;
}
function isTouchEvent(event: Event): event is TouchEvent {
return "TouchEvent" in window && event instanceof TouchEvent;
}

View File

@@ -191,11 +191,12 @@ const useTiptap = (
extensions: [
...CoreExtensions,
SearchReplace.configure({
onStartSearch: (term) => {
onStartSearch: (term, isReplacing) => {
useEditorSearchStore.setState({
isSearching: true,
searchTerm: term,
focusNonce: Math.random()
focusNonce: Math.random(),
isReplacing: isReplacing
});
return true;
},

View File

@@ -41,7 +41,7 @@ type ColorToolProps = ToolProps & {
type: ColorType;
};
const DEFAULT_COLORS: Record<ColorType, string[]> = {
export const DEFAULT_COLORS: Record<ColorType, string[]> = {
background: [
"#b0bec5", // Neutralized pink
"#a1887f", // Neutralized purple

View File

@@ -761,10 +761,11 @@ p > *::selection {
mask-size: cover;
}
.simple-checklist > li.checked p {
.simple-checklist > li.checked > :not(.simple-checklist) {
opacity: 0.8;
}
/* Callout */
.ProseMirror div.callout {
padding: 15px;
@@ -955,12 +956,12 @@ del.diffdel {
opacity: 1;
}
.ProseMirror h1.is-empty::after,
.ProseMirror h2.is-empty::after,
.ProseMirror h3.is-empty::after,
.ProseMirror h4.is-empty::after,
.ProseMirror h5.is-empty::after,
.ProseMirror h6.is-empty::after {
.ProseMirror h1.empty::after,
.ProseMirror h2.empty::after,
.ProseMirror h3.empty::after,
.ProseMirror h4.empty::after,
.ProseMirror h5.empty::after,
.ProseMirror h6.empty::after {
display: none !important;
}