Compare commits

..

116 Commits

Author SHA1 Message Date
ammarahm-ed
9ef9180894 web: refactor theme builder 2023-07-31 11:20:54 +05:00
ammarahm-ed
d1a2ec580d theme: use validateTheme in theme builder 2023-07-31 11:20:54 +05:00
ammarahm-ed
0a057e2b32 theme: update 2023-07-31 11:20:54 +05:00
ammarahm-ed
29611ffdef theme: add theme builder to web app 2023-07-31 11:20:54 +05:00
ammarahm-ed
85fa49f53e mobile: update theme 2023-07-31 11:14:25 +05:00
ammarahm-ed
703853bd8a mobile: fix floating button color 2023-07-31 10:14:13 +05:00
Abdullah Atta
9b046c6b6f web: improve button hover interaction 2023-07-31 10:04:56 +05:00
Abdullah Atta
d10e08a158 web: add support for loading theme from file 2023-07-29 16:04:45 +05:00
Abdullah Atta
ad67947ad7 theme: refactor theme-engine 2023-07-29 15:34:04 +05:00
Abdullah Atta
1574ae7a28 theme: add sourceURL property 2023-07-29 15:33:32 +05:00
ammarahm-ed
56201cb408 mobile: rename state 2023-07-29 11:44:05 +05:00
ammarahm-ed
b3fc476352 mobile: fix share extension crash 2023-07-29 11:36:22 +05:00
ammarahm-ed
fdd24a90d5 mobile: load theme from file 2023-07-28 18:41:22 +05:00
ammarahm-ed
5140fd7108 theme: fix theme validation 2023-07-28 18:11:31 +05:00
ammarahm-ed
becd852165 theme: add getPreviewColors & validateTheme 2023-07-28 16:40:16 +05:00
Abdullah Atta
700f5e4205 web: fix Loader errors on build 2023-07-27 12:52:53 +05:00
ammarahm-ed
4245ac1451 mobile: fix ui issues 2023-07-27 12:44:39 +05:00
ammarahm-ed
26e44798ec mobile: fix warning 2023-07-27 11:19:20 +05:00
Abdullah Atta
9648913a02 web: add loading indicator for themes loading 2023-07-26 22:15:48 +05:00
Abdullah Atta
7091f64de8 web: fix settings sidebar is not scrollable 2023-07-26 20:29:51 +05:00
Abdullah Atta
e5ec15fd9c editor: fix toolbar group padding everywhere 2023-07-26 20:20:45 +05:00
ammarahm-ed
c067c73b33 mobile: refactor 2023-07-26 20:10:43 +05:00
Abdullah Atta
d304f708c7 editor: adjust theming and styling of editor toolbar 2023-07-26 19:11:55 +05:00
ammarahm-ed
71cf9955cd misc: remove ThemeProvider usage 2023-07-26 18:43:07 +05:00
ammarahm-ed
c5fcfd6654 mobile: fix webpack build 2023-07-26 18:20:23 +05:00
Abdullah Atta
67a1c55420 web: fix login/signup theming 2023-07-26 17:19:55 +05:00
Abdullah Atta
6c54f28c0f theme: use zustand to manage theme engine state 2023-07-26 17:00:12 +05:00
Abdullah Atta
c576671302 editor: bring back icons in table menus 2023-07-26 15:56:33 +05:00
ammarahm-ed
6e663bf913 theme: remove caching 2023-07-26 13:55:35 +05:00
ammarahm-ed
0f999947ee mobile: fix color usage in message card 2023-07-25 12:21:37 +05:00
ammarahm-ed
ab458486a2 theme: use scope name to get current scope 2023-07-25 12:21:15 +05:00
ammarahm-ed
5e627b567f editor: set theme provider at app level 2023-07-25 12:19:43 +05:00
ammarahm-ed
2ef7baab0a editor: fix toolbar group alignments 2023-07-25 12:17:46 +05:00
ammarahm-ed
56ede7654a mobile: update theme 2023-07-24 14:52:22 +05:00
Abdullah Atta
18ccbc0ba3 web: fix navigation item hover & other colors 2023-07-24 13:38:40 +05:00
Abdullah Atta
73f50a34f9 web: fix only 1 theme showing in theme selector 2023-07-24 13:38:23 +05:00
Abdullah Atta
c0805b1915 theme: export button variant creation fn 2023-07-24 13:37:59 +05:00
Abdullah Atta
3c9a62b8ed config: add ui scope for commits 2023-07-24 12:39:06 +05:00
Abdullah Atta
b61c094a5d ui: use unique id for all menu items 2023-07-24 12:38:50 +05:00
Abdullah Atta
7f7390810f web: apply selected variant where appropriate 2023-07-24 12:31:26 +05:00
Abdullah Atta
09ea2f577b web: fix sort & group options not having submenus 2023-07-24 12:29:32 +05:00
Abdullah Atta
cbcd7a9e5a global: use single typescript version across the projects 2023-07-24 11:18:08 +05:00
Abdullah Atta
bca774dfd0 theme: add selected variant 2023-07-24 10:16:30 +05:00
Abdullah Atta
0b56bdb0c4 web: fix colors in places 2023-07-24 10:13:01 +05:00
Abdullah Atta
cdf481d581 web: bring back dark/light mode option in settings 2023-07-24 10:12:37 +05:00
Abdullah Atta
c644f606f3 web: add support for endless scrolling in themes 2023-07-24 10:12:17 +05:00
Abdullah Atta
c45e029780 mobile: remove react-native-blob-util 2023-07-22 06:58:16 +05:00
Abdullah Atta
59422d3e9c web: update lockfile 2023-07-22 06:56:04 +05:00
Abdullah Atta
f0df45bd82 Merge branch 'master' into theme-engine 2023-07-22 06:53:31 +05:00
Abdullah Atta
c5ce153c00 web: update lockfile 2023-07-22 06:49:43 +05:00
Abdullah Atta
3e7d68f524 web: add production server url 2023-07-22 06:46:15 +05:00
Abdullah Atta
e4eea50319 setup: add scope for themes server in bootstrap script 2023-07-22 06:25:24 +05:00
Abdullah Atta
47e0c54ab0 web: add theme details dialog 2023-07-19 13:11:06 +05:00
Abdullah Atta
1ff1319e8a global: update lockfiles 2023-07-18 15:31:40 +05:00
Abdullah Atta
16c46d3fc7 theme: add isomorphic-fetch package 2023-07-18 15:27:59 +05:00
ammarahm-ed
8362c39277 mobile: update theme if new verison available 2023-07-18 12:57:00 +05:00
ammarahm-ed
eedf347708 mobile: update theme selector 2023-07-18 12:48:42 +05:00
Abdullah Atta
403b2809b7 web: add support for auto updating themes 2023-07-17 17:46:30 +05:00
Abdullah Atta
6c551eef0a theme: use vite-plugin-react to start theme server 2023-07-17 13:51:41 +05:00
ammarahm-ed
d34b354420 mobile: fix error 2023-07-17 09:55:46 +05:00
Abdullah Atta
38a4652c29 global: update lockfiles 2023-07-17 09:44:48 +05:00
Abdullah Atta
228c1a4d3a web: add support for theme search and theme switching 2023-07-14 22:54:47 +05:00
Abdullah Atta
7bca693c44 global: a huge set of changes
1. get rid of ThemeVariant. All variants can now be accessed anywhere.
2. remove unnecessary button variants
3. make buttons more responsive
4. implement themes server
2023-07-14 22:18:03 +05:00
Abdullah Atta
583fa3da49 global: fix @trpc/* versions 2023-07-13 10:11:07 +05:00
Abdullah Atta
2ff73e241d web: fix imports 2023-07-11 15:54:47 +05:00
Abdullah Atta
fe2c753068 editor: update katex patch 2023-07-11 15:38:34 +05:00
Abdullah Atta
19201186c5 global: fix @emotion/react version to 11.11.1 2023-07-11 15:36:26 +05:00
ammarahm-ed
b2dd6b750d editor: fix build 2023-07-11 15:30:02 +05:00
Abdullah Atta
25380ec052 web: finalize new theme engine on web 2023-07-11 15:11:52 +05:00
ammarahm-ed
81cc687e1e server: theme updates 2023-07-11 10:08:42 +05:00
ammarahm-ed
1dab6be32d server: show ip & port on start server 2023-07-10 18:22:55 +05:00
ammarahm-ed
b9bfeee126 server: fix types 2023-07-10 18:15:21 +05:00
ammarahm-ed
a06fa9f00e server: reset cache on sync repo 2023-07-10 15:59:55 +05:00
ammarahm-ed
b147afbefe mobile: add client from theme server 2023-07-10 15:58:54 +05:00
ammarahm-ed
7646f35e58 server: support themes query pagination 2023-07-10 15:58:28 +05:00
ammarahm-ed
beb782b9ad theme: add previewColors 2023-07-10 13:29:11 +05:00
ammarahm-ed
6e3f706b3a server: add theme server 2023-07-10 10:40:39 +05:00
ammarahm-ed
f01d415415 mobile: fix bottom padding 2023-07-08 13:25:18 +05:00
ammarahm-ed
b6417d0016 mobile: fix theme colors 2023-07-08 12:45:10 +05:00
ammarahm-ed
f1ab8e92bd misc: fix colors 2023-07-08 11:58:46 +05:00
ammarahm-ed
80ffbc511d mobile: fix build 2023-07-08 11:50:26 +05:00
ammarahm-ed
745bf65641 misc: fix build 2023-07-08 11:13:55 +05:00
ammarahm-ed
a0015af169 Merge branch 'master' into theme-engine 2023-07-08 11:00:21 +05:00
ammarahm-ed
e6e775dbeb mobile: fix statusbar color on switch from bg 2023-05-19 20:31:56 +05:00
Abdullah Atta
e082007a49 clipper: add support for the new theme engine 2023-05-08 14:42:29 +05:00
ammarahm-ed
deced99339 mobile: fix editor theme provider 2023-05-08 08:13:13 +05:00
ammarahm-ed
b0acce7b4f mobile: migrate share ext to theme engine 2023-05-08 08:12:42 +05:00
Abdullah Atta
e081e7ea09 web: adjust hover & focus colors on list item 2023-05-06 15:29:10 +05:00
Abdullah Atta
35db89aa11 editor: add support for theme enging 2023-05-06 15:29:10 +05:00
Abdullah Atta
679a912e2b web: move emotion theme provider to @notesnook/theme 2023-05-06 15:28:33 +05:00
ammarahm-ed
627259e129 mobile: fix dark color scheme 2023-05-06 13:17:44 +05:00
ammarahm-ed
70c8201355 mobile: fix status bar color incorrect on entering foreground 2023-05-06 11:24:09 +05:00
ammarahm-ed
064059278a mobile: cleanup 2023-05-06 11:10:17 +05:00
ammarahm-ed
959e7466a6 mobile: fix colors 2023-05-06 10:27:43 +05:00
ammarahm-ed
69bd277d87 mobile: fix colors 2023-05-06 10:09:49 +05:00
ammarahm-ed
5833832123 mobile: fix theme-engine usage in editor & app 2023-05-05 16:11:13 +05:00
Abdullah Atta
37187403d4 global: sync with master branch and make everything work again 2023-05-04 15:16:05 +05:00
ammarahm-ed
b24d52bbbd theme: add dialog & sheet scopes 2023-05-04 14:48:19 +05:00
Abdullah Atta
82142d11aa web: add partial support for custom themes
only context menus & popups are left.
2023-05-04 14:48:19 +05:00
ammarahm-ed
19b4d84c40 mobile: fix theme definition 2023-05-04 13:26:36 +05:00
ammarahm-ed
fd98b89900 mobile: fix typings 2023-05-04 13:26:36 +05:00
ammarahm-ed
1870406b5e mobile: fix button width on applock screen 2023-05-04 13:26:36 +05:00
ammarahm-ed
64e5f63f70 mobile: commit 2023-05-04 13:26:36 +05:00
ammarahm-ed
02f75ac1cd mobile: add theme scopes 2023-05-04 13:26:36 +05:00
ammarahm-ed
52849a5bf3 mobile: manage theme state via theme-engine 2023-05-04 13:25:35 +05:00
ammarahm-ed
f64c91faec theme: add default pitch-black theme 2023-05-04 13:24:40 +05:00
ammarahm-ed
c7ae14f239 mobile: const colors -> `const {colors} 2023-05-04 13:24:40 +05:00
Abdullah Atta
9ab084d793 theme: add backdrop color 2023-05-04 13:23:04 +05:00
Abdullah Atta
6514b98ce8 theme: use explicit return type for useThemeColors 2023-05-04 13:23:04 +05:00
Abdullah Atta
5a5cacee4f theme: ColorsType -> Variants 2023-05-04 13:23:04 +05:00
ammarahm-ed
04451eee5d mobile: store theme info in store 2023-05-04 13:23:04 +05:00
ammarahm-ed
6e7558b364 mobile: fix colors 2023-05-04 13:22:28 +05:00
ammarahm-ed
9c02fed3af mobile: fixed some colors 2023-05-04 13:22:28 +05:00
ammarahm-ed
5d7195fb41 mobile: migrate app colors to new theme engine 2023-05-04 13:21:56 +05:00
ammarahm-ed
6cd1178347 theme: add theme engine 2023-05-04 12:52:38 +05:00
ammarahm-ed
b7b4a18624 mobile: theme 2023-05-04 12:52:38 +05:00
937 changed files with 83599 additions and 62078 deletions

View File

@@ -14,7 +14,6 @@ const SCOPES = [
"mobile",
"web",
"vericrypt",
"desktop",
"crypto",
"editor",
@@ -33,8 +32,7 @@ const SCOPES = [
"misc",
"common",
"global",
"docs",
"themebuilder"
"docs"
];
module.exports = {

View File

@@ -19,6 +19,7 @@ runs:
packages/crypto/package-lock.json
packages/sodium/package-lock.json
packages/clipper/package-lock.json
packages/crypto-worker/package-lock.json
packages/editor-mobile/package-lock.json
packages/editor/package-lock.json
packages/logger/package-lock.json

View File

@@ -97,7 +97,6 @@ jobs:
- name: Get App Store Version
id: appstore
uses: streetwriters/appstore-connect-app-version@develop
if: inputs.publish-apple
with:
app-id: ${{ steps.app_metadata.outputs.apple_app_id }}
key-id: ${{ secrets.api_key_id }}

View File

@@ -1,31 +0,0 @@
name: Publish @notesnook/theme-builder
on: workflow_dispatch
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=themebuilder
- name: Setup environment
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
- name: Generate build
run: npm run build:theme-builder
- name: Publish to Cloudflare Pages
run: npx --yes wrangler pages deploy --project-name=notesnook-theme-builder ./apps/theme-builder/build/

View File

@@ -1,31 +0,0 @@
name: Publish @notesnook/vericrypt
on: workflow_dispatch
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=vericrypt
- name: Setup environment
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
- name: Generate build
run: npm run build:vericrypt
- name: Publish on Cloudflare Pages
run: npx --yes wrangler pages deploy --project-name=vericrypt ./apps/vericrypt/build/

View File

@@ -1,67 +0,0 @@
name: Publish @notesnook/web Beta
on:
workflow_dispatch:
push:
branches:
- master
paths-ignore:
- "apps/mobile/**"
- "packages/editor-mobile/**"
- "apps/desktop/**"
- "apps/vericrypt/**"
- "servers/**"
- "packages/clipper/**"
- "extensions/web-clipper/**"
pull_request:
types:
- closed
- ready_for_review
- reopened
- opened
- synchronize
branches:
- master
paths-ignore:
- "apps/mobile/**"
- "packages/editor-mobile/**"
- "apps/desktop/**"
- "apps/vericrypt/**"
- "servers/**"
- "packages/clipper/**"
- "extensions/web-clipper/**"
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=web
- name: Setup environment
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
- name: Generate beta build
run: npm run build:beta:web
- name: Publish to Cloudflare Pages
uses: unlike-ltd/github-actions-cloudflare-pages@v0.1.1
id: pages
with:
cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
cloudflare-project-name: notesnook-beta-app
directory: ./apps/web/build
github-token: ${{ secrets.GITHUB_TOKEN }}
github-environment: ${{ (github.ref == 'refs/heads/master' && 'beta') || 'preview' }}

View File

@@ -24,8 +24,8 @@ jobs:
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
- name: Generate build
- name: Generate desktop build
run: npm run build:web
- name: Publish to Cloudflare Pages
run: npx --yes wrangler pages deploy --project-name=notesnook-app ./apps/web/build/
- name: Publish on Cloudflare Pages
run: npx --yes wrangler pages publish --project-name=notesnook-app ./apps/web/build/

View File

@@ -103,7 +103,7 @@ All commits must include valid scopes in the commit message. **Valid commit scop
**Packages:**
1. `crypto`: changes related to the cryptographic core
1. `crypto`: changes related to the cryptographic core (including `@notesnook/crypto-worker`)
2. `editor`: changes related to the editor (including `@notesnook/editor-mobile`)
3. `logger`: changes related to the logger
4. `theme`: changes related to the theme

View File

@@ -46,6 +46,7 @@ Notesnook is built using the following technologies:
| `@notesnook/editor-mobile` | [/packages/editor-mobile](/packages/editor-mobile) | A very thin wrapper around `@notesnook/editor` for mobile clients |
| `@notesnook/logger` | [/packages/logger](/packages/logger) | Simple & pluggable logger |
| `@notesnook/sodium` | [/packages/sodium](/packages/sodium) | Wrapper around libsodium to support Node.js & Browser |
| `@notesnook/crypto-worker` | [/packages/crypto-worker](/packages/crypto-worker) | Helpers to use `@notesnook/crypto` from a Worker |
| `@notesnook/streamable-fs` | [/packages/streamable-fs](/packages/streamable-fs) | Streaming interface around an IndexedDB based file system |
| `@notesnook/theme` | [/packages/theme](/packages/theme) | The core theme used in web & desktop clients |

View File

@@ -1,3 +0,0 @@
owner: streetwriters
repo: notesnook
provider: github

View File

@@ -19,40 +19,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* eslint-disable no-var */
import { BrowserWindow } from "electron";
import {
type FormData as FormDataType,
type Headers as HeadersType,
type Request as RequestType,
type Response as ResponseType
} from "undici";
declare global {
var window: BrowserWindow | null;
var RELEASE: boolean;
var MAC_APP_STORE: boolean;
// Re-export undici fetch function and various classes to global scope.
// These are classes and functions expected to be at global scope according to Node.js v18 API
// documentation.
// See: https://nodejs.org/dist/latest-v18.x/docs/api/globals.html
// eslint-disable-next-line no-var
export var {
FormData,
Headers,
Request,
Response,
fetch
}: typeof import("undici");
type FormData = FormDataType;
type Headers = HeadersType;
type Request = RequestType;
type Response = ResponseType;
}
// NOTE: the import in the global block above needs to be a var for this to work properly.
globalThis.fetch = fetch;
globalThis.FormData = FormData;
globalThis.Headers = Headers;
globalThis.Request = Request;
globalThis.Response = Response;

File diff suppressed because it is too large Load Diff

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "2.6.8",
"version": "2.5.8",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/index.js",
@@ -12,25 +12,25 @@
"license": "GPL-3.0-or-later",
"dependencies": {
"@notesnook/crypto": "file:../../packages/crypto",
"@trpc/client": "10.38.3",
"@trpc/server": "10.38.3",
"@trpc/client": "10.31.0",
"@trpc/server": "10.31.0",
"electron-trpc": "0.5.2",
"electron-updater": "6.1.4",
"electron-updater": "6.1.1",
"icojs": "^0.17.1",
"typed-emitter": "^2.1.0",
"yargs": "^17.6.2",
"zod": "^3.21.4"
},
"devDependencies": {
"@types/node": "18.16.1",
"@types/node": "^18.15.0",
"@types/yargs": "^17.0.24",
"chokidar": "^3.5.3",
"electron": "25.9.3",
"electron-builder": "^24.6.4",
"electron": "24.5.1",
"electron-builder": "24.4.0",
"electron-builder-notarize": "^1.5.1",
"esbuild": "^0.17.19",
"tree-kill": "^1.2.2",
"undici": "^5.23.0"
"node-fetch": "^3.3.1",
"tree-kill": "^1.2.2"
},
"optionalDependencies": {
"dmg-license": "^1.0.11"
@@ -191,7 +191,6 @@
"allowNativeWayland": true
},
"extraResources": [
"app-update.yml",
"./assets/**"
],
"extraMetadata": {

View File

@@ -1,8 +1,8 @@
diff --git a/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js b/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js
index fcb7f54..cb1c7f7 100644
index 3ba5d6a..cf8e147 100644
--- a/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js
+++ b/node_modules/app-builder-lib/out/targets/LinuxTargetHelper.js
@@ -116,6 +116,7 @@ class LinuxTargetHelper {
@@ -105,6 +105,7 @@ class LinuxTargetHelper {
StartupWMClass: appInfo.productName,
...extra,
...targetSpecificOptions.desktop,
@@ -10,7 +10,7 @@ index fcb7f54..cb1c7f7 100644
};
const description = this.getDescription(targetSpecificOptions);
if (!(0, builder_util_1.isEmptyOrSpaces)(description)) {
@@ -159,6 +160,23 @@ class LinuxTargetHelper {
@@ -148,6 +149,23 @@ class LinuxTargetHelper {
data += `\n${name}=${desktopMeta[name]}`;
}
data += "\n";

View File

@@ -29,8 +29,6 @@ import { dirname } from "path";
import { resolvePath } from "../utils/resolve-path";
import { observable } from "@trpc/server/observable";
import { AssetManager } from "../utils/asset-manager";
import { isFlatpak } from "../utils";
import { setupDesktopIntegration } from "../utils/desktop-integration";
const t = initTRPC.create();
@@ -46,40 +44,29 @@ const NotificationOptions = z.object({
});
export const osIntegrationRouter = t.router({
isFlatpak: t.procedure.query(() => isFlatpak()),
zoomFactor: t.procedure.query(() => config.zoomFactor),
setZoomFactor: t.procedure.input(z.number()).mutation(({ input: factor }) => {
globalThis.window?.webContents.setZoomFactor(factor);
config.zoomFactor = factor;
}),
menuBar: t.procedure.query(() => config.menuBar),
setMenuBar: t.procedure
.input(z.object({ enabled: z.boolean() }))
.mutation(({ input: { enabled } }) => {
config.menuBar = enabled;
if (!enabled) globalThis.window?.setMenuBarVisibility(false);
globalThis.window?.setAutoHideMenuBar(enabled);
}),
privacyMode: t.procedure.query(() => config.privacyMode),
setPrivacyMode: t.procedure
.input(z.object({ enabled: z.boolean() }))
.mutation(({ input: { enabled } }) => {
.input(z.boolean().optional())
.mutation(({ input: privacyMode }) => {
if (!globalThis.window || !["win32", "darwin"].includes(process.platform))
return;
globalThis.window.setContentProtection(enabled);
globalThis.window.setContentProtection(!!privacyMode);
if (process.platform === "win32") {
globalThis.window.setThumbnailClip(
enabled
privacyMode
? { x: 0, y: 0, width: 1, height: 1 }
: { x: 0, y: 0, width: 0, height: 0 }
);
}
config.privacyMode = enabled;
config.privacyMode = !!privacyMode;
}),
desktopIntegration: t.procedure.query(() => config.desktopSettings),
@@ -92,7 +79,6 @@ export const osIntegrationRouter = t.router({
AutoLaunch.disable();
}
config.desktopSettings = settings;
setupDesktopIntegration();
}),
selectDirectory: t.procedure
@@ -131,14 +117,6 @@ export const osIntegrationRouter = t.router({
writeFileSync(resolvedPath, data);
}),
resolvePath: t.procedure
.input(z.object({ filePath: z.string() }))
.query(({ input }) => {
const { filePath } = input;
if (!filePath) return;
return resolvePath(filePath);
}),
showNotification: t.procedure
.input(NotificationOptions)
.query(({ input }) => {

View File

@@ -114,9 +114,9 @@ export const spellCheckerRouter = t.router({
globalThis.window?.webContents.session.setSpellCheckerLanguages(languages)
),
toggle: t.procedure
.input(z.object({ enabled: z.boolean() }))
.mutation(({ input: { enabled } }) => {
globalThis.window?.webContents.session.setSpellCheckerEnabled(enabled);
config.isSpellCheckerEnabled = enabled;
.input(z.boolean().optional())
.mutation(({ input: enabled }) => {
globalThis.window?.webContents.session.setSpellCheckerEnabled(!!enabled);
config.isSpellCheckerEnabled = !!enabled;
})
});

View File

@@ -41,9 +41,10 @@ export const updaterRouter = t.router({
}),
toggleAutoUpdates: t.procedure
.input(z.object({ enabled: z.boolean() }))
.mutation(({ input: { enabled } }) => {
config.automaticUpdates = enabled;
.input(z.boolean().optional())
.mutation(({ input }) => {
config.automaticUpdates =
input === undefined ? !config.automaticUpdates : input;
}),
onChecking: createSubscription("checking-for-update"),

View File

@@ -27,23 +27,19 @@ export type CLIOptions = {
hidden: boolean;
};
export async function parseArguments(argv: string[]): Promise<CLIOptions> {
export async function parseArguments(): Promise<CLIOptions> {
const result: CLIOptions = {
note: false,
notebook: false,
reminder: false,
hidden: false
};
const { hidden } = await yargs(hideBin(argv))
const { hidden } = await yargs(hideBin(process.argv))
.boolean("hidden")
// have to account for this flag added on Windows when launching
// via Jumplist
.boolean("allow-file-access-from-files")
.command("new", "Create a new item", (yargs) => {
return yargs
.command("note", "Create a new note", {}, () => {
result.note = true;
console.log("HERE!");
})
.command("notebook", "Create a new notebook", {}, () => {
result.notebook = true;

View File

@@ -17,13 +17,14 @@ 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 { app, BrowserWindow, nativeTheme, shell } from "electron";
import { app, BrowserWindow, nativeTheme, session, shell } from "electron";
import { isDevelopment } from "./utils";
import { registerProtocol, PROTOCOL_URL } from "./utils/protocol";
import { configureAutoUpdater } from "./utils/autoupdater";
import { getBackgroundColor, getTheme, setTheme } from "./utils/theme";
import { setupMenu } from "./utils/menu";
import { WindowState } from "./utils/window-state";
import { AutoLaunch } from "./utils/autolaunch";
import { setupJumplist } from "./utils/jumplist";
import { setupTray } from "./utils/tray";
import { CLIOptions, parseArguments } from "./cli";
@@ -32,15 +33,6 @@ import { createIPCHandler } from "electron-trpc/main";
import { router, api } from "./api";
import { config } from "./utils/config";
import path from "path";
import { bringToFront } from "./utils/bring-to-front";
import { bridge } from "./api/bridge";
import { setupDesktopIntegration } from "./utils/desktop-integration";
// only run a single instance
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
console.log("Another instance is already running!");
app.exit();
}
if (process.platform == "win32" && process.env.PORTABLE_EXECUTABLE_DIR) {
console.log("Portable app: true");
@@ -50,21 +42,19 @@ if (process.platform == "win32" && process.env.PORTABLE_EXECUTABLE_DIR) {
app.setPath("userData", path.join(root, "UserData"));
}
// only run a single instance
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
app.exit();
}
if (process.platform === "win32") {
app.setAppUserModelId(app.name);
}
process.on("uncaughtException", (error) => {
console.error("uncaughtException:", error);
});
process.on("unhandledRejection", (reason) => {
console.error("unhandledRejection:", reason);
});
app.commandLine.appendSwitch("lang", "en-US");
async function createWindow() {
const cliOptions = await parseArguments(process.argv);
const cliOptions = await parseArguments();
setTheme(getTheme());
const mainWindowState = new WindowState({});
@@ -77,7 +67,7 @@ async function createWindow() {
darkTheme: getTheme() === "dark",
backgroundColor: getBackgroundColor(),
opacity: 0,
autoHideMenuBar: config.menuBar,
autoHideMenuBar: true,
icon: AssetManager.appIcon({
size: 512,
format: process.platform === "win32" ? "ico" : "png"
@@ -91,9 +81,7 @@ async function createWindow() {
}
});
createIPCHandler({ router, windows: [mainWindow] });
globalThis.window = mainWindow;
mainWindow.setMenuBarVisibility(false);
mainWindowState.manage(mainWindow);
if (cliOptions.hidden && !config.desktopSettings.minimizeToSystemTray)
@@ -103,11 +91,12 @@ async function createWindow() {
mainWindow.setOpacity(1);
if (config.privacyMode) {
await api.integration.setPrivacyMode({ enabled: config.privacyMode });
await api.integration.setPrivacyMode(config.privacyMode);
}
await AssetManager.loadIcons();
setupDesktopIntegration();
createIPCHandler({ router, windows: [mainWindow] });
mainWindow.webContents.session.setSpellCheckerDictionaryDownloadURL(
"http://dictionaries.notesnook.com/"
@@ -148,15 +137,6 @@ app.once("window-all-closed", () => {
}
});
app.on("second-instance", async (_ev, argv) => {
if (!globalThis.window) return;
const cliOptions = await parseArguments(argv);
if (cliOptions.note) bridge.onCreateItem("note");
if (cliOptions.notebook) bridge.onCreateItem("notebook");
if (cliOptions.reminder) bridge.onCreateItem("reminder");
bringToFront();
});
app.on("activate", () => {
if (globalThis.window === null) {
createWindow();
@@ -177,3 +157,49 @@ function createURL(options: CLIOptions, path = "/") {
return url;
}
function setupDesktopIntegration() {
const desktopIntegration = config.desktopSettings;
if (
desktopIntegration.closeToSystemTray ||
desktopIntegration.minimizeToSystemTray
) {
setupTray();
}
// when close to system tray is enabled, it becomes nigh impossible
// to "quit" the app. This is necessary in order to fix that.
if (desktopIntegration.closeToSystemTray) {
app.on("before-quit", () => app.exit(0));
}
globalThis.window?.once("close", (e) => {
if (config.desktopSettings.closeToSystemTray) {
e.preventDefault();
if (process.platform == "darwin") {
// on macOS window cannot be minimized/hidden if it is already fullscreen
// so we just close it.
if (globalThis.window?.isFullScreen()) app.exit(0);
else app.hide();
} else {
globalThis.window?.minimize();
globalThis.window?.hide();
}
}
});
globalThis.window?.on("minimize", () => {
if (config.desktopSettings.minimizeToSystemTray) {
if (process.platform == "darwin") {
app.hide();
} else {
globalThis.window?.hide();
}
}
});
if (desktopIntegration.autoStart) {
AutoLaunch.enable(!!desktopIntegration.startMinimized);
}
}

View File

@@ -31,6 +31,7 @@ declare global {
}
process.once("loaded", async () => {
console.log("HELLO!");
const electronTRPC: RendererGlobalElectronTRPC = {
sendMessage: (operation) =>
ipcRenderer.send(ELECTRON_TRPC_CHANNEL, operation),

View File

@@ -35,8 +35,6 @@ const LINUX_AUTOSTART_DIRECTORY_PATH = path.join(
"autostart"
);
const STARTUP_ARGS = ["--hidden"];
export class AutoLaunch {
static enable(hidden: boolean) {
if (process.platform === "linux") {
@@ -49,14 +47,10 @@ export class AutoLaunch {
LINUX_DESKTOP_ENTRY(hidden)
);
} else {
const loginItemSettings = app.getLoginItemSettings({
args: STARTUP_ARGS
});
if (loginItemSettings.openAtLogin) return;
app.setLoginItemSettings({
openAtLogin: true,
openAsHidden: hidden,
args: STARTUP_ARGS
args: ["--hidden"]
});
}
}

View File

@@ -38,11 +38,10 @@ export const config = {
closeToSystemTray: false
},
privacyMode: false,
isSpellCheckerEnabled: true,
zoomFactor: 1,
isSpellCheckerEnabled: false,
zoomFactor: 0,
theme: nativeTheme.themeSource,
automaticUpdates: true,
menuBar: true
automaticUpdates: true
};
type ConfigKey = keyof typeof config;

View File

@@ -1,70 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { app } from "electron";
import { config } from "./config";
import { setupTray } from "./tray";
import { AutoLaunch } from "./autolaunch";
export function setupDesktopIntegration() {
const desktopIntegration = config.desktopSettings;
if (
desktopIntegration.closeToSystemTray ||
desktopIntegration.minimizeToSystemTray
) {
setupTray();
}
// when close to system tray is enabled, it becomes nigh impossible
// to "quit" the app. This is necessary in order to fix that.
if (desktopIntegration.closeToSystemTray) {
app.on("before-quit", () => app.exit(0));
}
globalThis.window?.on("close", (e) => {
if (config.desktopSettings.closeToSystemTray) {
e.preventDefault();
if (process.platform == "darwin") {
// on macOS window cannot be minimized/hidden if it is already fullscreen
// so we just close it.
if (globalThis.window?.isFullScreen()) app.exit(0);
else app.hide();
} else {
try {
globalThis.window?.minimize();
globalThis.window?.hide();
} catch (error) {}
}
}
});
globalThis.window?.on("minimize", () => {
if (config.desktopSettings.minimizeToSystemTray) {
if (process.platform == "darwin") {
app.hide();
} else {
globalThis.window?.hide();
}
}
});
if (desktopIntegration.autoStart) {
AutoLaunch.enable(!!desktopIntegration.startMinimized);
}
}

View File

@@ -17,14 +17,16 @@ 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 { protocol, net } from "electron";
import { protocol, ProtocolRequest } from "electron";
import { isDevelopment } from "./index";
import { createReadStream } from "fs";
import { extname, normalize } from "path";
import { URL } from "url";
import fetch, { Response } from "node-fetch";
const BASE_PATH = isDevelopment() ? "../public" : "";
const HOSTNAME = `app.notesnook.com`;
const FILE_NOT_FOUND = -6;
const SCHEME = "https";
const extensionToMimeType: Record<string, string> = {
html: "text/html",
@@ -32,42 +34,101 @@ const extensionToMimeType: Record<string, string> = {
js: "application/javascript",
css: "text/css",
svg: "image/svg+xml",
png: "image/png",
jpg: "image/jpg",
ttf: "font/ttf",
woff: "font/woff",
woff2: "font/woff2"
png: "image/png"
};
function registerProtocol() {
protocol.handle(SCHEME, async (request) => {
const url = new URL(request.url);
if (shouldInterceptRequest(url)) {
console.info("Intercepting request:", request.url);
const loadIndex = !extname(url.pathname);
const filePath = normalize(
`${__dirname}${
loadIndex ? `${BASE_PATH}/index.html` : `${BASE_PATH}/${url.pathname}`
}`
);
if (!filePath) {
console.error("Local asset file not found at", filePath);
return new Response(undefined, {
status: 404,
statusText: "FILE_NOT_FOUND"
const protocolInterceptionResult = protocol.interceptStreamProtocol(
SCHEME,
async (request, callback) => {
const url = new URL(request.url);
if (shouldInterceptRequest(url)) {
console.info("Intercepting request:", request.url);
const loadIndex = !extname(url.pathname);
const filePath = normalize(
`${__dirname}${
loadIndex
? `${BASE_PATH}/index.html`
: `${BASE_PATH}/${url.pathname}`
}`
);
if (!filePath) {
console.error("Local asset file not found at", filePath);
callback({ error: FILE_NOT_FOUND });
return;
}
const fileExtension = extname(filePath).replace(".", "");
const data = createReadStream(filePath);
callback({
data,
mimeType: extensionToMimeType[fileExtension]
});
} else {
let response: Response;
try {
const body = await getBody(request);
response = await fetch(request.url, {
...request,
body,
headers: {
...request.headers
// origin: `${PROTOCOL}://${HOSTNAME}/`
},
referrer: request.referrer,
redirect: "manual"
});
} catch (e) {
console.error(e);
console.error(`Error sending request to `, request.url, "Error: ", e);
callback({ statusCode: 400 });
return;
}
callback({
statusCode: response.status,
data: response.body || undefined,
headers: Object.fromEntries(response.headers.entries()),
mimeType: response.headers.get("Content-Type") || undefined
});
}
const fileExtension = extname(filePath).replace(".", "");
return new Response(createReadStream(filePath), {
headers: { "Content-Type": extensionToMimeType[fileExtension] }
});
} else {
return net.fetch(request, {
bypassCustomProtocolHandlers: true
});
}
});
console.info(`${SCHEME} protocol inteception "successful"`);
);
console.info(
`${SCHEME} protocol inteception ${
protocolInterceptionResult ? "successful" : "failed"
}.`
);
// protocol.handle(SCHEME, (request) => {
// const url = new URL(request.url);
// if (shouldInterceptRequest(url)) {
// console.info("Intercepting request:", request.url);
// const loadIndex = !extname(url.pathname);
// const absoluteFilePath = normalize(
// `${__dirname}${
// loadIndex ? `${BASE_PATH}/index.html` : `${BASE_PATH}/${url.pathname}`
// }`
// );
// const filePath = getPath(absoluteFilePath);
// if (!filePath) {
// console.error("Local asset file not found at", filePath);
// return new Response(undefined, {
// status: 404,
// statusText: "FILE_NOT_FOUND"
// });
// }
// const fileExtension = extname(filePath).replace(".", "");
// const data = createReadStream(filePath);
// return new Response(data, {
// headers: { "Content-Type": extensionToMimeType[fileExtension] }
// });
// } else {
// return net.fetch(request);
// }
// });
// console.info(`${SCHEME} protocol inteception "successful"`);
}
const bypassedRoutes: string[] = [];
@@ -78,3 +139,22 @@ function shouldInterceptRequest(url: URL) {
const PROTOCOL_URL = `${SCHEME}://${HOSTNAME}/`;
export { registerProtocol, PROTOCOL_URL };
async function getBody(request: ProtocolRequest) {
const session = globalThis?.window?.webContents?.session;
const blobParts = [];
if (!request.uploadData || !request.uploadData.length) return null;
for (const data of request.uploadData) {
if (data.bytes) {
blobParts.push(new Uint8Array(data.bytes));
} else if (session && data.blobUUID) {
const buffer = await session.getBlobData(data.blobUUID);
if (!buffer) continue;
blobParts.push(new Uint8Array(buffer));
}
}
const blob = new Blob(blobParts);
return blob;
}

View File

@@ -5,6 +5,7 @@ artifacts/
.DS_Store
native/android/app/src/main/assets/
*Issues.md
build_cache/
#

View File

@@ -16,8 +16,6 @@ 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 "@azure/core-asynciterator-polyfill";
import SettingsService from "./services/settings";
import {
THEME_COMPATIBILITY_VERSION,
useThemeEngineStore
@@ -33,11 +31,13 @@ import GlobalSafeAreaProvider from "./components/globalsafearea";
import { useAppEvents } from "./hooks/use-app-events";
import { ApplicationHolder } from "./navigation";
import Notifications from "./services/notifications";
import SettingsService from "./services/settings";
import { TipManager } from "./services/tip-manager";
import { useThemeStore } from "./stores/use-theme-store";
import { useUserStore } from "./stores/use-user-store";
import { themeTrpcClient } from "./screens/settings/theme-selector";
SettingsService.init();
SettingsService.checkOrientation();
const App = () => {
const init = useAppEvents();
@@ -46,7 +46,6 @@ const App = () => {
if (appLockMode && appLockMode !== "none") {
useUserStore.getState().lockApp(true);
}
globalThis["IS_MAIN_APP_RUNNING"] = true;
init();
setTimeout(async () => {
SettingsService.onFirstLaunch();
@@ -94,12 +93,6 @@ const App = () => {
);
};
let currTheme =
useThemeStore.getState().colorScheme === "dark"
? SettingsService.getProperty("darkTheme")
: SettingsService.getProperty("lightTheme");
useThemeEngineStore.getState().setTheme(currTheme);
export const withTheme = (Element) => {
return function AppWithThemeProvider() {
const [colorScheme, darkTheme, lightTheme] = useThemeStore((state) => [
@@ -109,35 +102,31 @@ export const withTheme = (Element) => {
]);
useEffect(() => {
setTimeout(() => {
const currentTheme = colorScheme === "dark" ? darkTheme : lightTheme;
if (!currentTheme) return;
themeTrpcClient.updateTheme
.query({
version: currentTheme.version,
compatibilityVersion: THEME_COMPATIBILITY_VERSION,
id: currentTheme.id
})
.then((theme) => {
if (theme) {
console.log(theme.version, "theme updated");
theme.colorScheme === "dark"
? useThemeStore.getState().setDarkTheme(theme)
: useThemeStore.getState().setLightTheme(theme);
}
})
.catch(console.log);
}, 1000);
const currentTheme = colorScheme === "dark" ? darkTheme : lightTheme;
if (!currentTheme) return;
themeTrpcClient.updateTheme
.query({
version: currentTheme.version,
compatibilityVersion: THEME_COMPATIBILITY_VERSION,
id: currentTheme.id
})
.then((theme) => {
if (theme) {
console.log(theme.version, "theme updated");
theme.colorScheme === "dark"
? useThemeStore.getState().setDarkTheme(theme)
: useThemeStore.getState().setLightTheme(theme);
}
})
.catch(console.log);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const nextTheme = colorScheme === "dark" ? darkTheme : lightTheme;
if (JSON.stringify(nextTheme) !== JSON.stringify(currTheme)) {
useEffect(() => {
useThemeEngineStore
.getState()
.setTheme(colorScheme === "dark" ? darkTheme : lightTheme);
currTheme = nextTheme;
}
}, [colorScheme, darkTheme, lightTheme]);
return <Element />;
};

View File

@@ -126,18 +126,6 @@ export async function decrypt(password, data) {
return await Sodium.decrypt(password, _data);
}
export async function decryptMulti(password, data) {
if (!password.password && !password.key) return undefined;
if (password.password && password.password === "" && !password.key)
return undefined;
data = data.map((d) => {
d.output = "plain";
return d;
});
return await Sodium.decryptMulti(password, data);
}
export function parseAlgorithm(alg) {
if (!alg) return {};
const [enc, kdf, compressed, compressionAlg, base64variant] = alg.split("-");
@@ -166,24 +154,3 @@ export async function encrypt(password, data) {
alg: getAlgorithm(7)
};
}
export async function encryptMulti(password, data) {
if (!password.password && !password.key) return undefined;
if (password.password && password.password === "" && !password.key)
return undefined;
let results = await Sodium.encryptMulti(
password,
data.map((item) => ({
type: "plain",
data: item
}))
);
return !results
? []
: results.map((result) => ({
...result,
alg: getAlgorithm(7)
}));
}

View File

@@ -17,47 +17,56 @@ 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 { database } from "@notesnook/common";
import { logger as dbLogger } from "@notesnook/core/dist/logger";
import { initalize, logger as dbLogger } from "@notesnook/core/logger";
import { Platform } from "react-native";
import * as Gzip from "react-native-gzip";
import { MMKVLoader } from "react-native-mmkv-storage";
import filesystem from "../filesystem";
import EventSource from "../../utils/sse/even-source-ios";
import AndroidEventSource from "../../utils/sse/event-source";
import filesystem from "../filesystem";
import "./logger";
import Storage from "./storage";
import Storage, { KV } from "./storage";
import * as Gzip from "react-native-gzip";
database.host(
__DEV__
? {
API_HOST: "https://api.notesnook.com",
AUTH_HOST: "https://auth.streetwriters.co",
SSE_HOST: "https://events.streetwriters.co",
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
ISSUES_HOST: "https://issues.streetwriters.co"
// API_HOST: "http://192.168.43.108:5264",
// AUTH_HOST: "http://192.168.43.108:8264",
// SSE_HOST: "http://192.168.43.108:7264",
// SUBSCRIPTIONS_HOST: "http://192.168.43.108:9264",
// ISSUES_HOST: "http://192.168.43.108:2624"
}
: {
API_HOST: "https://api.notesnook.com",
AUTH_HOST: "https://auth.streetwriters.co",
SSE_HOST: "https://events.streetwriters.co",
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
ISSUES_HOST: "https://issues.streetwriters.co"
}
);
const LoggerStorage = new MMKVLoader()
.withInstanceID("notesnook_logs")
.initialize();
database.setup(
Storage,
Platform.OS === "ios" ? EventSource : AndroidEventSource,
filesystem,
{
compress: Gzip.deflate,
decompress: Gzip.inflate
}
);
database.host(
__DEV__
? {
API_HOST: "https://api.notesnook.com",
AUTH_HOST: "https://auth.streetwriters.co",
SSE_HOST: "https://events.streetwriters.co",
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
ISSUES_HOST: "https://issues.streetwriters.co"
// API_HOST: "http://192.168.8.101:5264",
// AUTH_HOST: "http://192.168.8.101:8264",
// SSE_HOST: "http://192.168.8.101:7264",
// SUBSCRIPTIONS_HOST: "http://192.168.8.101:9264",
// ISSUES_HOST: "http://192.168.8.101:2624"
}
: {
API_HOST: "https://api.notesnook.com",
AUTH_HOST: "https://auth.streetwriters.co",
SSE_HOST: "https://events.streetwriters.co",
SUBSCRIPTIONS_HOST: "https://subscriptions.streetwriters.co",
ISSUES_HOST: "https://issues.streetwriters.co"
}
);
database.setup(
Storage,
Platform.OS === "ios" ? EventSource : AndroidEventSource,
filesystem,
{
compress: Gzip.deflate,
decompress: Gzip.inflate
}
);
initalize(new KV(LoggerStorage), true);
export const db = database;
export const DatabaseLogger = dbLogger;

View File

@@ -1,29 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { MMKVLoader } from "react-native-mmkv-storage";
import { initalize } from "@notesnook/core/dist/logger";
import { KV } from "./storage";
const LoggerStorage = new MMKVLoader()
.withInstanceID("notesnook_logs")
.initialize();
initalize(new KV(LoggerStorage));
export {};

View File

@@ -18,13 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Platform } from "react-native";
import { ProcessingModes, MMKVLoader } from "react-native-mmkv-storage";
import MMKVStorage, { ProcessingModes } from "react-native-mmkv-storage";
export const MMKV = new MMKVLoader()
export const MMKV = new MMKVStorage.Loader()
.setProcessingMode(
Platform.OS === "ios"
? ProcessingModes.MULTI_PROCESS
: ProcessingModes.SINGLE_PROCESS
)
.disableIndexing()
.initialize();

View File

@@ -27,16 +27,11 @@ import {
getCryptoKey,
getRandomBytes,
hash,
removeCryptoKey,
decryptMulti,
encryptMulti
removeCryptoKey
} from "./encryption";
import { MMKV } from "./mmkv";
export class KV {
/**
* @type {typeof MMKV}
*/
storage = null;
constructor(storage) {
this.storage = storage;
@@ -58,7 +53,6 @@ export class KV {
key,
typeof data === "string" ? data : JSON.stringify(data)
);
return true;
}
@@ -66,32 +60,27 @@ export class KV {
if (keys.length <= 0) {
return [];
} else {
try {
let data = await this.storage.getMultipleItemsAsync(
keys.slice(),
"string"
);
return data.map(([key, value]) => {
let obj;
try {
obj = JSON.parse(value);
} catch (e) {
obj = value;
}
return [key, obj];
});
} catch (e) {
console.log(e);
}
let data = await this.storage.getMultipleItemsAsync(keys.slice());
return data.map(([key, value]) => {
let obj;
try {
obj = JSON.parse(value);
} catch (e) {
obj = value;
}
return [key, obj];
});
}
}
async remove(key) {
return this.storage.removeItem(key);
return await this.storage.removeItem(key);
}
async clear() {
return this.storage.clearStore();
return await this.storage.clearStore();
}
async getAllKeys() {
@@ -107,10 +96,6 @@ export class KV {
);
return keys;
}
async writeMulti(items) {
return this.storage.setMultipleItemsAsync(items, "object");
}
}
const DefaultStorage = new KV(MMKV);
@@ -144,10 +129,8 @@ export default {
remove: (key) => DefaultStorage.remove(key),
clear: () => DefaultStorage.clear(),
getAllKeys: () => DefaultStorage.getAllKeys(),
writeMulti: (items) => DefaultStorage.writeMulti(items),
encrypt,
decrypt,
decryptMulti,
getRandomBytes,
checkAndCreateDir,
requestPermission,
@@ -155,6 +138,5 @@ export default {
getCryptoKey,
removeCryptoKey,
hash,
generateCryptoKey,
encryptMulti
generateCryptoKey
};

View File

@@ -22,7 +22,7 @@ import {
getFileNameWithExtension,
isImage,
isDocument
} from "@notesnook/core/dist/utils/filename";
} from "@notesnook/core/utils/filename";
import React from "react";
import { Platform } from "react-native";
import * as ScopedStorage from "react-native-scoped-storage";
@@ -34,7 +34,6 @@ import { useAttachmentStore } from "../../stores/use-attachment-store";
import { db } from "../database";
import Storage from "../database/storage";
import { cacheDir, copyFileAsync, releasePermissions } from "./utils";
import { createCacheDir } from "./io";
export const FileDownloadStatus = {
Success: 1,
@@ -63,7 +62,6 @@ export async function downloadAttachments(
canceled,
groupId
) {
await createCacheDir();
if (!attachments || !attachments.length) return;
const result = new Map();
@@ -184,8 +182,6 @@ export default async function downloadAttachment(
groupId: undefined
}
) {
await createCacheDir();
let attachment = db.attachments.attachment(hash);
if (!attachment) {
console.log("attachment not found");

View File

@@ -17,25 +17,22 @@ 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 hosts from "@notesnook/core/dist/utils/constants";
import hosts from "@notesnook/core/utils/constants";
import NetInfo from "@react-native-community/netinfo";
import RNFetchBlob from "react-native-blob-util";
import { ToastEvent } from "../../services/event-manager";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { db } from "../database";
import { cacheDir, fileCheck } from "./utils";
import { createCacheDir, exists } from "./io";
export async function downloadFile(filename, data, cancelToken) {
if (!data) return false;
await createCacheDir();
let { url, headers } = data;
let path = `${cacheDir}/${filename}`;
let path = `${cacheDir}/${filename}`;
try {
if (await exists(filename)) {
let exists = await RNFetchBlob.fs.exists(path);
if (exists) {
return true;
}
@@ -63,55 +60,44 @@ export async function downloadFile(filename, data, cancelToken) {
console.log("downloading: ", recieved, total);
});
cancelToken.cancel = () => {
useAttachmentStore.getState().remove(filename);
request.cancel();
};
cancelToken.cancel = request.cancel;
let response = await request;
await fileCheck(response, totalSize);
let status = response.info().status;
useAttachmentStore.getState().remove(filename);
return status >= 200 && status < 300;
} catch (e) {
if (e.message !== "canceled") {
ToastEvent.show({
heading: "Error downloading file",
message: e.message,
type: "error",
context: "global"
});
ToastEvent.show({
heading: "Error downloading file",
message: e.message,
type: "error",
context: "local"
});
}
ToastEvent.show({
heading: "Error downloading file",
message: e.message,
type: "error",
context: "global"
});
ToastEvent.show({
heading: "Error downloading file",
message: e.message,
type: "error",
context: "local"
});
useAttachmentStore.getState().remove(filename);
RNFetchBlob.fs.unlink(path).catch(console.log);
console.log("Download file error:", e, url, headers);
console.log("download file error: ", e, url, headers);
return false;
}
}
export async function getUploadedFileSize(hash) {
try {
const url = `${hosts.API_HOST}/s3?name=${hash}`;
const token = await db.user.tokenManager.getAccessToken();
const url = `${hosts.API_HOST}/s3?name=${hash}`;
const token = await db.user.tokenManager.getAccessToken();
const attachmentInfo = await fetch(url, {
method: "HEAD",
headers: { Authorization: `Bearer ${token}` }
});
const attachmentInfo = await fetch(url, {
method: "HEAD",
headers: { Authorization: `Bearer ${token}` }
});
const contentLength = parseInt(
attachmentInfo.headers?.get("content-length")
);
return isNaN(contentLength) ? 0 : contentLength;
} catch (e) {
return 0;
}
const contentLength = parseInt(attachmentInfo.headers?.get("content-length"));
return isNaN(contentLength) ? 0 : contentLength;
}
export async function checkAttachment(hash) {

View File

@@ -20,25 +20,32 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Platform } from "react-native";
import Sodium from "@ammarahmed/react-native-sodium";
import RNFetchBlob from "react-native-blob-util";
import { cacheDir, cacheDirOld, getRandomId } from "./utils";
import { cacheDir, getRandomId } from "./utils";
import { db } from "../database";
import { compressToBase64 } from "./compress";
import { IOS_APPGROUPID } from "../../utils/constants";
export async function readEncrypted(filename, key, cipherData) {
await migrateFilesFromCache();
console.log("Read encrypted file...");
let path = `${cacheDir}/${filename}`;
try {
if (!(await exists(filename))) {
const iosAppGroup =
Platform.OS === "ios"
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
: null;
const appGroupPath = `${iosAppGroup}/${filename}`;
let exists =
(await RNFetchBlob.fs.exists(path)) ||
(Platform.OS === "ios" && (await RNFetchBlob.fs.exists(appGroupPath)));
if (!exists) {
return false;
}
const attachment = db.attachments.attachment(filename);
const isPng = /(png)/g.test(attachment?.metadata.type);
const isJpeg = /(jpeg|jpg)/g.test(attachment?.metadata.type);
console.log("decrypting....");
let output = await Sodium.decryptFile(
key,
{
@@ -52,7 +59,6 @@ export async function readEncrypted(filename, key, cipherData) {
: "base64"
: "text"
);
console.log("file decrypted...");
if (cipherData.outputType === "base64" && (isPng || isJpeg)) {
const dCachePath = `${cacheDir}/${output}`;
output = await compressToBase64(
@@ -64,7 +70,7 @@ export async function readEncrypted(filename, key, cipherData) {
return output;
} catch (e) {
RNFetchBlob.fs.unlink(path).catch(console.log);
console.log("readEncrypted", e);
console.log(e);
return false;
}
}
@@ -82,7 +88,6 @@ export async function hashBase64(data) {
}
export async function writeEncryptedBase64({ data, key }) {
await createCacheDir();
let filepath = cacheDir + `/${getRandomId("imagecache_")}`;
await RNFetchBlob.fs.writeFile(filepath, data, "base64");
let output = await Sodium.encryptFile(key, {
@@ -98,7 +103,6 @@ export async function writeEncryptedBase64({ data, key }) {
}
export async function deleteFile(filename, data) {
await createCacheDir();
let delFilePath = cacheDir + `/${filename}`;
if (!data) {
if (!filename) return;
@@ -124,89 +128,19 @@ export async function deleteFile(filename, data) {
export async function clearFileStorage() {
try {
let files = await RNFetchBlob.fs.ls(cacheDir);
let oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
for (let file of files) {
await RNFetchBlob.fs.unlink(cacheDir + `/${file}`).catch(console.log);
}
for (let file of oldCache) {
await RNFetchBlob.fs.unlink(cacheDirOld + `/${file}`).catch(console.log);
try {
await RNFetchBlob.fs.unlink(cacheDir + `/${file}`);
} catch (e) {
console.log(e);
}
}
} catch (e) {
console.log("clearFileStorage", e);
console.log(e);
}
}
export async function createCacheDir() {
if (!(await RNFetchBlob.fs.exists(cacheDir))) {
await RNFetchBlob.fs.mkdir(cacheDir);
console.log("Cache directory created");
}
}
export async function migrateFilesFromCache() {
try {
await createCacheDir();
const migratedFilesPath = cacheDir + "/.migrated_1";
const migrated = await RNFetchBlob.fs.exists(migratedFilesPath);
if (migrated) {
console.log("Files migrated already");
return;
}
let files = await RNFetchBlob.fs.ls(cacheDir);
console.log("Files to migrate:", files.join(","));
let oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
for (let file of oldCache) {
if (file.startsWith("org.") || file.startsWith("com.")) continue;
RNFetchBlob.fs
.mv(cacheDirOld + `/${file}`, cacheDir + `/${file}`)
.catch(console.log);
console.log("Moved", file);
}
await RNFetchBlob.fs.createFile(migratedFilesPath, "1", "utf8");
} catch (e) {
console.log("migrateFilesFromCache", e);
}
}
const ABYTES = 17;
export async function exists(filename) {
let path = `${cacheDir}/${filename}`;
const iosAppGroup =
Platform.OS === "ios"
? await RNFetchBlob.fs.pathForAppGroup(IOS_APPGROUPID)
: null;
const appGroupPath = `${iosAppGroup}/${filename}`;
let exists = await RNFetchBlob.fs.exists(path);
// Check if file is present in app group path.
let existsInAppGroup = false;
if (!exists && Platform.OS === "ios") {
existsInAppGroup = await RNFetchBlob.fs.exists(appGroupPath);
}
if (exists || existsInAppGroup) {
const attachment = db.attachments.attachment(filename);
const totalChunks = Math.ceil(attachment.length / attachment.chunkSize);
const totalAbytes = totalChunks * ABYTES;
const expectedFileSize = attachment.length + totalAbytes;
const stat = await RNFetchBlob.fs.stat(
existsInAppGroup ? appGroupPath : path
);
if (stat.size !== expectedFileSize) {
RNFetchBlob.fs
.unlink(existsInAppGroup ? appGroupPath : path)
.catch(console.log);
return false;
}
exists = true;
}
let exists = await RNFetchBlob.fs.exists(`${cacheDir}/${filename}`);
return exists;
}

View File

@@ -21,15 +21,14 @@ import RNFetchBlob from "react-native-blob-util";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { DatabaseLogger, db } from "../database";
import { cacheDir } from "./utils";
import { isImage, isDocument } from "@notesnook/core/dist/utils/filename";
import { isImage, isDocument } from "@notesnook/core/utils/filename";
import { Platform } from "react-native";
import { IOS_APPGROUPID } from "../../utils/constants";
import { createCacheDir } from "./io";
export async function uploadFile(filename, data, cancelToken) {
if (!data) return false;
let { url, headers } = data;
await createCacheDir();
DatabaseLogger.info(`Preparing to upload file: ${filename}`);
try {
@@ -73,10 +72,7 @@ export async function uploadFile(filename, data, cancelToken) {
);
});
cancelToken.cancel = () => {
useAttachmentStore.getState().remove(filename);
request.cancel();
};
cancelToken.cancel = request.cancel;
let response = await request;
let status = response.info().status;

View File

@@ -21,12 +21,7 @@ import * as ScopedStorage from "react-native-scoped-storage";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
export const cacheDirOld = RNFetchBlob.fs.dirs.CacheDir;
export const cacheDir =
Platform.OS == "ios"
? RNFetchBlob.fs.dirs.LibraryDir + "/.cache"
: RNFetchBlob.fs.dirs.DocumentDir + "/.cache";
export const cacheDir = RNFetchBlob.fs.dirs.CacheDir;
export function getRandomId(prefix) {
return Math.random()
@@ -71,12 +66,9 @@ export function cancelable(operation) {
}
export function copyFileAsync(source, dest) {
return new Promise((resolve, reject) => {
return new Promise((resolve) => {
ScopedStorage.copyFile(source, dest, (e, r) => {
if (e) {
reject(e);
return;
}
console.log(e, r);
resolve();
});
});

View File

@@ -128,9 +128,7 @@ export const Cta = ({ actions, style = {}, color, inline }) => {
fontSize={SIZE.md}
buttonType={{
color: color ? color : colors.primary.accent,
text: color
? colors.static.white
: colors.primary.accentForeground,
text: colors.static.white,
selected: color ? color : colors.primary.accent,
opacity: 1
}}

View File

@@ -100,7 +100,7 @@ const AppLockedOverlay = () => {
appLocked && (
<View
style={{
backgroundColor: colors.primary.background,
backgroundColor: colors.bg,
width: "100%",
height: "100%",
position: "absolute",
@@ -160,7 +160,7 @@ const AppLockedOverlay = () => {
style={{
width: "100%",
padding: 12,
backgroundColor: colors.primary.background
backgroundColor: colors.bg
}}
>
{user ? (

View File

@@ -132,7 +132,7 @@ export const AttachmentItem = ({
activeOpacity={0.9}
onPress={() => {
if (encryption || !pressable) return;
db.fs.cancel(attachment.metadata.hash, currentProgress.type);
db.fs.cancel(attachment.metadata.hash);
setCurrentProgress(null);
}}
style={{

View File

@@ -40,7 +40,7 @@ import {
isDocument,
isImage,
isVideo
} from "@notesnook/core/dist/utils/filename";
} from "@notesnook/core/utils/filename";
import { useSettingStore } from "../../stores/use-setting-store";
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
@@ -241,7 +241,7 @@ export const AttachmentDialog = ({ note }) => {
width: "100%",
height: 50,
flexDirection: "row",
backgroundColor: colors.primary.background
backgroundColor: colors.bg
}}
contentContainerStyle={{
minWidth: "100%",

View File

@@ -85,13 +85,11 @@ const AuthModal = () => {
background={colors.primary.background}
transparent={false}
animated={false}
centered={false}
>
<KeyboardAwareScrollView
style={{
width: "100%"
}}
enableAutomaticScroll={false}
keyboardShouldPersistTaps="handled"
>
{currentAuthMode !== AuthMode.login ? (

View File

@@ -17,22 +17,18 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useState } from "react";
import { Modal, View } from "react-native";
import { db } from "../../common/database";
import { MMKV } from "../../common/database/mmkv";
import BiometricService from "../../services/biometrics";
import {
ToastEvent,
eSendEvent,
eSubscribeEvent
} from "../../services/event-manager";
import { setLoginMessage } from "../../services/message";
import SettingsService from "../../services/settings";
import Sync from "../../services/sync";
import { clearAllStores } from "../../stores";
import { useUserStore } from "../../stores/use-user-store";
import { useThemeColors } from "@notesnook/theme";
import { eLoginSessionExpired, eUserLoggedIn } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
@@ -46,7 +42,6 @@ import Input from "../ui/input";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { LoginSteps, useLogin } from "./use-login";
import BaseDialog from "../dialog/base-dialog";
function getObfuscatedEmail(email) {
if (!email) return "";
@@ -69,20 +64,16 @@ export const SessionExpired = () => {
eSendEvent(eUserLoggedIn, true);
setVisible(false);
setFocused(false);
},
true
}
);
const logout = async () => {
try {
await db.user.logout();
await BiometricService.resetCredentials();
setLoginMessage();
SettingsService.resetSettings();
useUserStore.getState().setUser(null);
useUserStore.getState().setSyncing(false);
MMKV.clearStore();
clearAllStores();
SettingsService.set({
introCompleted: true
});
setVisible(false);
} catch (e) {
ToastEvent.show({
@@ -132,20 +123,15 @@ export const SessionExpired = () => {
return (
visible && (
<BaseDialog
transparent={false}
background={colors.primary.background}
bounce={false}
animated={false}
centered={false}
<Modal
onShow={async () => {
await sleep(300);
passwordInputRef.current?.focus();
setFocused(true);
}}
enableSheetKeyboardHandler={true}
visible={true}
>
<SheetProvider context="two_factor_verify" />
<View
style={{
width: focused ? "100%" : "99.9%",
@@ -238,9 +224,7 @@ export const SessionExpired = () => {
</View>
<Toast context="local" />
<Dialog context="session_expiry" />
<SheetProvider context="two_factor_verify" />
</BaseDialog>
</Modal>
)
);
};

View File

@@ -67,7 +67,6 @@ export const Signup = ({ changeMode, trial }) => {
const signup = async () => {
if (!validateInfo() || error) return;
if (loading) return;
setLoading(true);
try {
await db.user.signup(email.current.toLowerCase(), password.current);

View File

@@ -214,7 +214,6 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo }) => {
code.current = value;
//onNext();
}}
caretHidden
inputStyle={{
fontSize: SIZE.lg,
height: 60,

View File

@@ -33,7 +33,7 @@ export const LoginSteps = {
passwordAuth: 3
};
export const useLogin = (onFinishLogin, sessionExpired = false) => {
export const useLogin = (onFinishLogin) => {
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const setUser = useUserStore((state) => state.setUser);
@@ -64,12 +64,10 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
const login = async () => {
if (!validateInfo() || error) return;
try {
if (loading) return;
setLoading(true);
switch (step) {
case LoginSteps.emailAuth: {
const mfaInfo = await db.user.authenticateEmail(email.current);
console.log("email auth", mfaInfo);
if (mfaInfo) {
TwoFactorVerification.present(async (mfa, callback) => {
try {
@@ -102,12 +100,7 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
break;
}
case LoginSteps.passwordAuth: {
await db.user.authenticatePassword(
email.current,
password.current,
null,
sessionExpired
);
await db.user.authenticatePassword(email.current, password.current);
finishLogin();
break;
}

View File

@@ -17,12 +17,12 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { PropsWithChildren } from "react";
import React from "react";
import { View } from "react-native";
import { useSelectionStore } from "../../stores/use-selection-store";
import { useThemeColors } from "@notesnook/theme";
export const ContainerHeader = (props: PropsWithChildren) => {
export const ContainerHeader = ({ children }) => {
const { colors } = useThemeColors();
const selectionMode = useSelectionStore((state) => state.selectionMode);
@@ -34,7 +34,7 @@ export const ContainerHeader = (props: PropsWithChildren) => {
overflow: "hidden"
}}
>
{props.children}
{children}
</View>
) : null;
};

View File

@@ -17,9 +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 { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect } from "react";
import { Keyboard, View } from "react-native";
import { Keyboard, Platform, View } from "react-native";
import Animated, {
Easing,
useAnimatedStyle,
@@ -35,20 +34,14 @@ import { getElevationStyle } from "../../utils/elevation";
import { SIZE, normalize } from "../../utils/size";
import NativeTooltip from "../../utils/tooltip";
import { PressableButton } from "../ui/pressable";
import { useThemeColors } from "@notesnook/theme";
interface FloatingButton {
title?: string;
onPress: () => void;
color?: string;
alwaysVisible?: boolean;
}
const FloatingButton = ({
export const FloatingButton = ({
title,
onPress,
color,
alwaysVisible = false
}: FloatingButton) => {
shouldShow = false
}) => {
const { colors } = useThemeColors();
const deviceMode = useSettingStore((state) => state.deviceMode);
const selectionMode = useSelectionStore((state) => state.selectionMode);
@@ -67,8 +60,12 @@ const FloatingButton = ({
};
});
useEffect(() => {
animate(selectionMode ? 150 : 0);
}, [animate, selectionMode]);
const animate = useCallback(
(toValue: number) => {
(toValue) => {
translate.value = withTiming(toValue, {
duration: 250,
easing: Easing.elastic(1)
@@ -77,39 +74,39 @@ const FloatingButton = ({
[translate]
);
useEffect(() => {
animate(selectionMode ? 150 : 0);
}, [animate, selectionMode]);
const onKeyboardHide = useCallback(async () => {
editorState().keyboardState = false;
if (deviceMode !== "mobile") return;
animate(0);
}, [animate, deviceMode]);
const onKeyboardShow = useCallback(async () => {
editorState().keyboardState = true;
if (deviceMode !== "mobile") return;
animate(150);
}, [animate, deviceMode]);
useEffect(() => {
const onKeyboardHide = () => {
editorState().keyboardState = false;
if (deviceMode !== "mobile") return;
animate(0);
};
const onKeyboardShow = () => {
editorState().keyboardState = true;
if (deviceMode !== "mobile") return;
animate(150);
};
const sub = [
Keyboard.addListener("keyboardDidShow", onKeyboardShow),
Keyboard.addListener("keyboardDidHide", onKeyboardHide)
];
let sub1 = Keyboard.addListener("keyboardDidShow", onKeyboardShow);
let sub2 = Keyboard.addListener("keyboardDidHide", onKeyboardHide);
return () => {
sub.forEach((sub) => sub?.remove?.());
sub1?.remove();
sub2?.remove();
};
}, [deviceMode, animate]);
}, [deviceMode, onKeyboardHide, onKeyboardShow]);
const paddings = {
ios: 20,
android: 20,
iPad: 20
};
return deviceMode !== "mobile" && !alwaysVisible ? null : (
return deviceMode !== "mobile" && !shouldShow ? null : (
<Animated.View
style={[
{
position: "absolute",
right: 12,
bottom: 20,
bottom: paddings[Platform.isPad ? "iPad" : Platform.OS],
zIndex: 10
},
animatedStyle
@@ -118,15 +115,16 @@ const FloatingButton = ({
<PressableButton
testID={notesnook.buttons.add}
type="accent"
accentColor={colors.static[color as keyof typeof colors.static]}
buttonType={{
color: colors.static[color],
text: colors.static.white
}}
customStyle={{
...getElevationStyle(5),
borderRadius: 100
}}
onLongPress={(event) => {
if (title) {
NativeTooltip.show(event, title, NativeTooltip.POSITIONS.LEFT);
}
NativeTooltip.show(event, title, NativeTooltip.POSITIONS.LEFT);
}}
onPress={onPress}
>
@@ -140,7 +138,7 @@ const FloatingButton = ({
>
<Icon
name={title === "Clear all trash" ? "delete" : "plus"}
color={colors.primary.accentForeground}
color="white"
size={SIZE.xxl}
/>
</View>
@@ -148,5 +146,3 @@ const FloatingButton = ({
</Animated.View>
);
};
export { FloatingButton };

View File

@@ -17,40 +17,44 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { PropsWithChildren } from "react";
import { KeyboardAvoidingView, Platform } from "react-native";
import React from "react";
import { KeyboardAvoidingView, Platform, View } from "react-native";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
import { useSettingStore } from "../../stores/use-setting-store";
import { Header } from "../header";
import SelectionHeader from "../selection-header";
export const Container = ({ children }: PropsWithChildren) => {
export const Container = ({ children }) => {
const floating = useIsFloatingKeyboard();
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const insets = useGlobalSafeAreaInsets();
return (
<KeyboardAvoidingView
behavior="padding"
enabled={Platform.OS === "ios" && !floating}
style={{
flex: 1,
overflow: "hidden",
paddingTop: Platform.OS === "android" ? 0 : insets.top,
paddingBottom: Platform.OS === "android" ? 0 : insets.bottom
flex: 1
}}
>
{!introCompleted ? null : (
<>
<SelectionHeader />
<Header />
</>
)}
<View
style={{
flex: 1,
overflow: "hidden",
paddingTop: Platform.OS === "android" ? 0 : insets.top,
paddingBottom: Platform.OS === "android" ? 0 : insets.bottom
}}
>
{!introCompleted ? null : (
<>
<SelectionHeader />
<Header title="Header" screen="Header" />
</>
)}
{children}
{children}
</View>
</KeyboardAvoidingView>
);
};

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 React, { useEffect, useRef, useState } from "react";
import React, { useEffect } from "react";
import {
KeyboardAvoidingView,
Modal,
@@ -31,9 +31,6 @@ import useIsFloatingKeyboard from "../../hooks/use-is-floating-keyboard";
import { useSettingStore } from "../../stores/use-setting-store";
import { BouncingView } from "../ui/transitions/bouncing-view";
import { ScopedThemeProvider } from "@notesnook/theme";
import SettingsService from "../../services/settings";
import { useUserStore } from "../../stores/use-user-store";
import { useAppState } from "../../hooks/use-app-state";
const BaseDialog = ({
visible,
@@ -51,13 +48,9 @@ const BaseDialog = ({
bounce = true,
closeOnTouch = true,
useSafeArea = true,
avoidKeyboardResize = false,
enableSheetKeyboardHandler = false
avoidKeyboardResize = false
}) => {
const floating = useIsFloatingKeyboard();
const appState = useAppState();
const lockEvents = useRef(false);
const [internalVisible, setIntervalVisible] = useState(true);
useEffect(() => {
return () => {
@@ -65,33 +58,14 @@ const BaseDialog = ({
};
}, []);
useEffect(() => {
if (SettingsService.get().appLockMode === "background") {
if (appState === "background") {
setIntervalVisible(false);
if (useUserStore.getState().appLocked) {
lockEvents.current = true;
const unsub = useUserStore.subscribe((state) => {
if (!state.appLocked) {
setIntervalVisible(true);
unsub();
setTimeout(() => {
lockEvents.current = false;
});
}
});
}
}
}
}, [appState]);
const Wrapper = useSafeArea ? SafeAreaView : View;
return (
<ScopedThemeProvider value="dialog">
<Modal
visible={visible && internalVisible}
visible={visible}
transparent={true}
animated
statusBarTranslucent={statusBarTranslucent}
supportedOrientations={[
"portrait",
@@ -101,17 +75,13 @@ const BaseDialog = ({
"landscape-right"
]}
onShow={() => {
if (lockEvents.current) return;
if (onShow) {
onShow();
if (!enableSheetKeyboardHandler) {
useSettingStore.getState().setSheetKeyboardHandler(false);
}
useSettingStore.getState().setSheetKeyboardHandler(false);
}
}}
animationType={animation}
onRequestClose={() => {
if (lockEvents.current) return;
if (!closeOnTouch) return null;
useSettingStore.getState().setSheetKeyboardHandler(true);
onRequestClose && onRequestClose();
@@ -137,7 +107,6 @@ const BaseDialog = ({
style={[
styles.backdrop,
{
alignItems: centered ? "center" : undefined,
justifyContent: centered
? "center"
: bottom
@@ -164,7 +133,8 @@ const styles = StyleSheet.create({
backdrop: {
width: "100%",
height: "100%",
justifyContent: "center"
justifyContent: "center",
alignItems: "center"
},
overlayButton: {
width: "100%",

View File

@@ -48,7 +48,7 @@ export const ProFeatures = ({ count = 6 }) => {
content: "Instantly sync to unlimited devices"
},
{
content: "A private vault to keep everything important always locked"
content: "A private vault to keep everything imporant always locked"
},
{
content:

View File

@@ -442,7 +442,6 @@ export class VaultDialog extends Component {
if (this.state.biometricUnlock && !this.state.isBiometryEnrolled) {
await this._enrollFingerprint(this.password);
}
if (this.state.goToEditor) {
this._openInEditor(note);
} else if (this.state.share) {
@@ -453,6 +452,7 @@ export class VaultDialog extends Component {
await this._copyNote(note);
}
} catch (e) {
console.log(e);
this._takeErrorAction(e);
}
};
@@ -467,40 +467,44 @@ export class VaultDialog extends Component {
}
async _enrollFingerprint(password) {
this.setState(
{
loading: true
},
async () => {
try {
await db.vault.unlock(password);
await BiometricService.storeCredentials(password);
this.setState({
loading: false
});
eSendEvent("vaultUpdated");
ToastEvent.show({
heading: "Biometric unlocking enabled!",
message: "Now you can unlock notes in vault with biometrics.",
type: "success",
context: "global"
});
this.close();
} catch (e) {
this.close();
ToastEvent.show({
heading: "Incorrect password",
message:
"Please enter the correct vault password to enable biometrics.",
type: "error",
context: "local"
});
this.setState({
loading: false
});
try {
this.setState(
{
loading: true
},
async () => {
try {
await db.vault.unlock(password);
await BiometricService.storeCredentials(password);
this.setState({
loading: false
});
eSendEvent("vaultUpdated");
ToastEvent.show({
heading: "Biometric unlocking enabled!",
message: "Now you can unlock notes in vault with biometrics.",
type: "success",
context: "global"
});
this.close();
} catch (e) {
ToastEvent.show({
heading: "Incorrect password",
message:
"Please enter the correct vault password to enable biometrics.",
type: "error",
context: "local"
});
this.setState({
loading: false
});
return;
}
}
}
);
);
} catch (e) {
this._takeErrorAction(e);
}
}
async _createVault() {
@@ -539,7 +543,7 @@ export class VaultDialog extends Component {
.remove(this.state.note.id, this.password)
.then(() => {
ToastEvent.show({
heading: "Note permanently unlocked.",
heading: "Note permanantly unlocked.",
type: "success",
context: "global"
});

View File

@@ -39,9 +39,9 @@ class ExceptionHandler extends React.Component<{
error: Error | null;
hasError: boolean;
} = {
hasError: false,
error: null
};
hasError: false,
error: null
};
static getDerivedStateFromError(error: Error) {
return { hasError: true, error: error };
}
@@ -69,7 +69,7 @@ class ExceptionHandler extends React.Component<{
this.props.component
)}
defaultTitle={this.state.error?.message}
issueTitle="An exception occurred"
issueTitle="An exception occured"
/>
<Dialog />
</SafeAreaView>

View File

@@ -43,7 +43,7 @@ const _Header = () => {
);
const onScroll = useCallback(
(data: { x: number; y: number }) => {
(data) => {
if (data.y > 150) {
if (!hide) return;
setHide(false);

View File

@@ -43,6 +43,12 @@ export const LeftMenus = () => {
return;
}
Navigation.goBack();
if (
useNavigationStore.getState().currentScreen.name === "Signup" ||
useNavigationStore.getState().currentScreen.name === "Login"
) {
tabBarRef.current.unlock();
}
};
return isTablet && !canGoBack ? null : (

View File

@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useRef } from "react";
import { Platform, StyleSheet, View } from "react-native";
//@ts-ignore
import Menu from "react-native-reanimated-material-menu";
import { notesnook } from "../../../e2e/test.ids";
import Navigation from "../../services/navigation";
@@ -34,15 +33,12 @@ import { IconButton } from "../ui/icon-button";
export const RightMenus = () => {
const { colors } = useThemeColors();
const { colors: contextMenuColors } = useThemeColors("contextMenu");
const { contextMenuColors } = useThemeColors("contextMenu");
const deviceMode = useSettingStore((state) => state.deviceMode);
const buttons = useNavigationStore((state) => state.headerRightButtons);
const rightButtons = useNavigationStore((state) => state.headerRightButtons);
const currentScreen = useNavigationStore((state) => state.currentScreen.name);
const buttonAction = useNavigationStore((state) => state.buttonAction);
const menuRef = useRef<{
show: () => void;
hide: () => void;
}>();
const menuRef = useRef();
return (
<View style={styles.rightBtnContainer}>
@@ -50,12 +46,9 @@ export const RightMenus = () => {
<IconButton
onPress={async () => {
SearchService.prepareSearch();
Navigation.navigate(
{
name: "Search"
},
{}
);
Navigation.navigate({
name: "Search"
});
}}
testID="icon-search"
name="magnify"
@@ -89,7 +82,7 @@ export const RightMenus = () => {
/>
) : null}
{buttons && buttons.length > 0 ? (
{rightButtons && rightButtons.length > 0 ? (
<Menu
ref={menuRef}
animationDuration={200}
@@ -100,7 +93,7 @@ export const RightMenus = () => {
onRequestClose={() => {
menuRef.current?.hide();
}}
anchor={
button={
<IconButton
onPress={() => {
menuRef.current?.show();
@@ -111,7 +104,7 @@ export const RightMenus = () => {
/>
}
>
{buttons.map((item) => (
{rightButtons.map((item) => (
<Button
style={{
width: 150,
@@ -120,7 +113,7 @@ export const RightMenus = () => {
}}
type="gray"
buttonType={{
text: contextMenuColors.primary.paragraph
text: colors.primary.paragraph
}}
key={item.title}
title={item.title}

View File

@@ -17,9 +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 { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useState } from "react";
import { Platform } from "react-native";
import React, { useEffect, useState } from "react";
import { Platform, View } from "react-native";
import { db } from "../../common/database";
import Notebook from "../../screens/notebook";
import {
@@ -27,12 +26,14 @@ import {
eUnSubscribeEvent
} from "../../services/event-manager";
import useNavigationStore from "../../stores/use-navigation-store";
import { useThemeColors } from "@notesnook/theme";
import { eScrollEvent } from "../../utils/events";
import { SIZE } from "../../utils/size";
import Tag from "../ui/tag";
import Heading from "../ui/typography/heading";
import { useCallback } from "react";
import Tag from "../ui/tag";
const titleState: { [name: string]: boolean } = {};
const titleState = {};
export const Title = () => {
const { colors } = useThemeColors();
@@ -41,12 +42,12 @@ export const Title = () => {
const isTopic = currentScreen?.name === "TopicNotes";
const [hide, setHide] = useState(
isNotebook
? typeof titleState[currentScreen.id as string] === "boolean"
? titleState[currentScreen.id as string]
? typeof titleState[currentScreen.id] === "boolean"
? titleState[currentScreen.id]
: true
: false
);
const isHidden = titleState[currentScreen.id as string];
const isHidden = titleState[currentScreen.id];
const notebook =
isTopic && currentScreen.notebookId
? db.notebooks?.notebook(currentScreen.notebookId)?.data
@@ -55,18 +56,18 @@ export const Title = () => {
const isTag = currentScreen?.name === "TaggedNotes";
const onScroll = useCallback(
(data: { x: number; y: number }) => {
(data) => {
if (currentScreen.name !== "Notebook") {
setHide(false);
return;
}
if (data.y > 150) {
if (!hide) return;
titleState[currentScreen.id as string] = false;
titleState[currentScreen.id] = false;
setHide(false);
} else {
if (hide) return;
titleState[currentScreen.id as string] = true;
titleState[currentScreen.id] = true;
setHide(true);
}
},
@@ -75,13 +76,13 @@ export const Title = () => {
useEffect(() => {
if (currentScreen.name === "Notebook") {
const value =
typeof titleState[currentScreen.id as string] === "boolean"
? titleState[currentScreen.id as string]
let value =
typeof titleState[currentScreen.id] === "boolean"
? titleState[currentScreen.id]
: true;
setHide(value);
} else {
setHide(titleState[currentScreen.id as string]);
setHide(titleState[currentScreen.id]);
}
}, [currentScreen.id, currentScreen.name]);
@@ -97,7 +98,14 @@ export const Title = () => {
Notebook.navigate(notebook, true);
}
return (
<>
<View
style={{
opacity: 1,
flexShrink: 1,
flexDirection: "row",
alignItems: "center"
}}
>
{!hide && !isHidden ? (
<Heading
onPress={navigateToNotebook}
@@ -114,17 +122,10 @@ export const Title = () => {
#
</Heading>
) : null}
{title}{" "}
<Tag
visible={currentScreen.beta}
text="BETA"
style={{
backgroundColor: "transparent"
}}
textColor={colors.primary.accent}
/>
{title}
</Heading>
) : null}
</>
<Tag visible={currentScreen.beta} text="BETA" />
</View>
);
};

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Sodium from "@ammarahmed/react-native-sodium";
import dataurl from "@notesnook/core/dist/utils/dataurl";
import dataurl from "@notesnook/core/utils/dataurl";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useState } from "react";
import { Platform, View } from "react-native";

View File

@@ -17,25 +17,35 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useRef, useState } from "react";
import { TouchableOpacity, useWindowDimensions, View } from "react-native";
import { useThemeColors } from "@notesnook/theme";
import React, { useRef } from "react";
import { TouchableOpacity, View, useWindowDimensions } from "react-native";
import { eSendEvent, presentSheet } from "../../../services/event-manager";
import SettingsService from "../../../services/settings";
import { useSettingStore } from "../../../stores/use-setting-store";
import { ColorValues } from "../../../utils/colors";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent,
presentSheet
} from "../../../services/event-manager";
import SettingsService from "../../../services/settings";
import { GROUP } from "../../../utils/constants";
import { ColorValues } from "../../../utils/colors";
import { db } from "../../../common/database";
import { eOpenJumpToDialog } from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import Sort from "../../sheets/sort";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import { Button } from "../../ui/button";
import Sort from "../../sheets/sort";
import Heading from "../../ui/typography/heading";
import { useCallback } from "react";
export const SectionHeader = React.memo(
function SectionHeader({ item, index, type, color, screen, groupOptions }) {
function SectionHeader({ item, index, type, color, screen }) {
const { colors } = useThemeColors();
const { fontScale } = useWindowDimensions();
const [groupOptions, setGroupOptions] = useState(
db.settings?.getGroupOptions(type)
);
let groupBy = Object.keys(GROUP).find(
(key) => GROUP[key] === groupOptions.groupBy
);
@@ -55,6 +65,17 @@ export const SectionHeader = React.memo(
? "Default"
: groupBy.slice(0, 1).toUpperCase() + groupBy.slice(1, groupBy.length);
const onUpdate = useCallback(() => {
setGroupOptions({ ...db.settings?.getGroupOptions(type) });
}, [type]);
useEffect(() => {
eSubscribeEvent("groupOptionsUpdate", onUpdate);
return () => {
eUnSubscribeEvent("groupOptionsUpdate", onUpdate);
};
}, [onUpdate]);
return (
<View
style={{

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 { getUpcomingReminder } from "@notesnook/core/dist/collections/reminders";
import { getUpcomingReminder } from "@notesnook/core/collections/reminders";
import { decode, EntityLevel } from "entities";
import React from "react";
import { View } from "react-native";
@@ -40,7 +40,6 @@ import { TimeSince } from "../../ui/time-since";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { useIsCompactModeEnabled } from "../../../hooks/use-is-compact-mode-enabled";
import { useEditorStore } from "../../../stores/use-editor-store";
function navigateToTag(item) {
const tag = db.tags.tag(item.id);
@@ -99,9 +98,6 @@ const NoteItem = ({
dateBy = "dateCreated",
noOpen = false
}) => {
const currentEditingNote = useEditorStore(
(state) => state.currentEditingNote
);
const { colors } = useThemeColors();
const compactMode = useIsCompactModeEnabled(item);
const attachmentCount = db.attachments?.ofNote(item.id, "all")?.length || 0;
@@ -112,9 +108,6 @@ const NoteItem = ({
const reminder = getUpcomingReminder(reminders);
const noteColor = ColorValues[item.color?.toLowerCase()];
const tags = getTags(item);
const primaryColors =
currentEditingNote === item.id ? colors.selected : colors.primary;
return (
<>
<View
@@ -155,7 +148,7 @@ const NoteItem = ({
borderRadius: 5,
marginRight: 5,
borderWidth: 0.5,
borderColor: primaryColors.border,
borderColor: colors.primary.border,
paddingHorizontal: 6,
marginBottom: 5
}}
@@ -186,7 +179,7 @@ const NoteItem = ({
<Paragraph
numberOfLines={1}
color={
ColorValues[item.color?.toLowerCase()] || primaryColors.heading
ColorValues[item.color?.toLowerCase()] || colors.primary.heading
}
style={{
flexWrap: "wrap"
@@ -199,7 +192,7 @@ const NoteItem = ({
<Heading
numberOfLines={1}
color={
ColorValues[item.color?.toLowerCase()] || primaryColors.heading
ColorValues[item.color?.toLowerCase()] || colors.primary.heading
}
style={{
flexWrap: "wrap"
@@ -215,7 +208,6 @@ const NoteItem = ({
style={{
flexWrap: "wrap"
}}
color={primaryColors.paragraph}
numberOfLines={2}
>
{decode(item.headline, {
@@ -270,7 +262,7 @@ const NoteItem = ({
<Icon
name="attachment"
size={SIZE.md}
color={primaryColors.icon}
color={colors.primary.icon}
/>
<Paragraph
color={colors.secondary.paragraph}
@@ -291,7 +283,7 @@ const NoteItem = ({
}}
color={
ColorValues[item.color?.toLowerCase()] ||
primaryColors.accent
colors.primary.accent
}
/>
) : null}
@@ -304,7 +296,7 @@ const NoteItem = ({
style={{
marginRight: 6
}}
color={primaryColors.icon}
color={colors.primary.icon}
/>
) : null}
@@ -362,7 +354,7 @@ const NoteItem = ({
</Paragraph>
<Paragraph
color={primaryColors.accent}
color={colors.primary.accent}
size={SIZE.xs}
style={{
marginRight: 6
@@ -432,7 +424,7 @@ const NoteItem = ({
<IconButton
testID={notesnook.listitem.menu}
color={primaryColors.paragraph}
color={colors.primary.paragraph}
name="dots-horizontal"
size={SIZE.xl}
onPress={() => !noOpen && showActionSheet(item, isTrash)}

View File

@@ -77,7 +77,7 @@ export const openNotebookTopic = (item) => {
useTrashStore.getState().setTrash();
useSelectionStore.getState().setSelectionMode(false);
ToastEvent.show({
heading: "Permanently deleted items",
heading: "Permanantly deleted items",
type: "success",
context: "local"
});

View File

@@ -43,9 +43,6 @@ const SelectionWrapper = ({
const onLongPress = () => {
if (!useSelectionStore.getState().selectionMode) {
useSelectionStore.setState({
selectedItemsList: []
});
useSelectionStore.getState().setSelectionMode(true);
}
useSelectionStore.getState().setSelectedItem(item);

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { Dimensions, View } from "react-native";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useMessageStore } from "../../stores/use-message-store";
import { useThemeColors } from "@notesnook/theme";
@@ -32,7 +32,6 @@ export const Card = ({ color, warning }) => {
color = color ? color : colors.primary.accent;
const messageBoardState = useMessageStore((state) => state.message);
const announcement = useMessageStore((state) => state.announcement);
const fontScale = Dimensions.get("window").fontScale;
return !messageBoardState.visible || announcement || warning ? null : (
<View
@@ -45,6 +44,7 @@ export const Card = ({ color, warning }) => {
type="gray"
customStyle={{
paddingVertical: 12,
width: "95%",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
@@ -54,18 +54,17 @@ export const Card = ({ color, warning }) => {
<View
style={{
flexDirection: "row",
alignItems: "center",
flexShrink: 1
alignItems: "center"
}}
>
<View
style={{
width: 40 * fontScale,
width: 40,
backgroundColor:
messageBoardState.type === "error"
? hexToRGBA(colors.static.red, 0.15)
: hexToRGBA(color, 0.15),
height: 40 * fontScale,
height: 40,
borderRadius: 100,
alignItems: "center",
justifyContent: "center"
@@ -76,7 +75,6 @@ export const Card = ({ color, warning }) => {
color={
messageBoardState.type === "error" ? colors.error.icon : color
}
allowFontScaling
name={messageBoardState.icon}
/>
</View>
@@ -93,7 +91,7 @@ export const Card = ({ color, warning }) => {
</Paragraph>
<Paragraph
style={{
flexWrap: "nowrap",
flexWrap: "wrap",
flexShrink: 1
}}
color={colors.primary.heading}
@@ -103,24 +101,22 @@ export const Card = ({ color, warning }) => {
</View>
</View>
{fontScale > 1 ? null : (
<View
style={{
width: 40,
height: 40,
justifyContent: "center",
alignItems: "center"
}}
>
<Icon
name="chevron-right"
color={
messageBoardState.type === "error" ? colors.error.icon : color
}
size={SIZE.lg}
/>
</View>
)}
<View
style={{
width: 40,
height: 40,
justifyContent: "center",
alignItems: "center"
}}
>
<Icon
name="chevron-right"
color={
messageBoardState.type === "error" ? colors.error.icon : color
}
size={SIZE.lg}
/>
</View>
</PressableButton>
</View>
);

View File

@@ -39,7 +39,6 @@ import { Empty } from "./empty";
import { getTotalNotes } from "@notesnook/common";
import { useSettingStore } from "../../stores/use-setting-store";
import ReminderItem from "../list-items/reminder";
import { useGroupOptions } from "../../hooks/use-group-options";
const renderItems = {
note: NoteWrapper,
@@ -54,10 +53,15 @@ const renderItems = {
const RenderItem = ({ item, index, type, ...restArgs }) => {
if (!item) return <View />;
const Item = renderItems[item.itemType || item.type] || View;
const groupOptions = db.settings?.getGroupOptions(type);
const dateBy =
groupOptions.sortBy !== "title" ? groupOptions.sortBy : "dateEdited";
const totalNotes = getTotalNotes(item);
return (
<Item
item={item}
dateBy={dateBy}
index={index}
type={type}
totalNotes={totalNotes}
@@ -99,13 +103,6 @@ const List = ({
(type === "notes" && notesListMode === "compact") ||
type === "notebooks" ||
notebooksListMode === "compact";
const groupType =
screen === "Home" ? "home" : screen === "Favorites" ? "favorites" : type;
const groupOptions = useGroupOptions(groupType);
const dateBy =
groupOptions.sortBy !== "title" ? groupOptions.sortBy : "dateEdited";
const renderItem = React.useCallback(
({ item, index }) => (
@@ -114,22 +111,18 @@ const List = ({
index={index}
color={headerProps?.color}
title={headerProps?.heading}
dateBy={dateBy}
type={groupType}
type={
screen === "Notes"
? "home"
: screen === "Favorites"
? "favorites"
: type
}
screen={screen}
isSheet={isSheet}
groupOptions={groupOptions}
/>
),
[
headerProps?.color,
headerProps?.heading,
screen,
isSheet,
dateBy,
groupType,
groupOptions
]
[headerProps?.color, headerProps?.heading, screen, type, isSheet]
);
const _onRefresh = async () => {
@@ -235,7 +228,7 @@ const List = ({
<JumpToSectionDialog
screen={screen}
data={listData}
type={screen === "Home" ? "home" : type}
type={screen === "Notes" ? "home" : type}
scrollRef={scrollRef}
/>
) : null}

View File

@@ -245,17 +245,13 @@ const MergeConflicts = () => {
};
return !visible ? null : (
<BaseDialog
<Modal
statusBarTranslucent
transparent={false}
animationType="slide"
animated={false}
bounce={false}
onRequestClose={() => {
close();
}}
centered={false}
background={colors?.primary.background}
supportedOrientations={[
"portrait",
"portrait-upside-down",
@@ -367,7 +363,7 @@ const MergeConflicts = () => {
</Animated.View>
</View>
</SafeAreaView>
</BaseDialog>
</Modal>
);
};

View File

@@ -84,7 +84,7 @@ export default function NotePreview({ session, content, note }) {
useTrashStore.getState().setTrash();
useSelectionStore.getState().setSelectionMode(false);
ToastEvent.show({
heading: "Permanently deleted items",
heading: "Permanantly deleted items",
type: "success",
context: "local"
});
@@ -109,6 +109,7 @@ export default function NotePreview({ session, content, note }) {
flex: 1
}}
>
<EditorOverlay editorId={editorId} />
<Editor
noHeader
noToolbar
@@ -116,6 +117,7 @@ export default function NotePreview({ session, content, note }) {
editorId={editorId}
onLoad={async () => {
const _note = note || db.notes.note(session?.noteId)?.data;
await sleep(1000);
eSendEvent(eOnLoadNote + editorId, {
..._note,
content: {

View File

@@ -56,7 +56,7 @@ export const CompactFeatures = ({
icon: "shield"
},
{
highlight: "Daily, weekly & monthly",
highlight: "Daily, weekly & montly",
content: "recurring reminders",
icon: "bell"
},

View File

@@ -165,12 +165,7 @@ export const Component = ({ close, promo }) => {
}}
size={SIZE.md}
>
(
{Platform.OS === "android"
? pricing.product?.subscriptionOfferDetails[0].pricingPhases
.pricingPhaseList?.[0].formattedPrice
: pricing.product?.localizedPrice}{" "}
/ mo)
({pricing?.product?.localizedPrice} / mo)
</Paragraph>
)}

View File

@@ -98,7 +98,7 @@ export const features = [
icon: "bell"
},
{
highlight: "Daily, weekly & monthly",
highlight: "Daily, weekly & montly",
content: "reminders",
icon: "refresh",
pro: true
@@ -194,7 +194,7 @@ export const features = [
"Having the right tool at the right time is crucial for note taking. Lists, tables, codeblocks — you name it, we have it.",
features: [
{
highlight: "Basic formatting",
highlight: "Basic formating",
content: "and lists",
icon: "format-bold"
},

View File

@@ -18,27 +18,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { Platform, View } from "react-native";
import { View } from "react-native";
import { SIZE } from "../../utils/size";
import { PressableButton } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import RNIap from "react-native-iap";
export const PricingItem = ({
product,
onPress,
compact
}: {
product: {
type: "yearly" | "monthly";
data?: RNIap.Subscription;
info: string;
offerType?: "yearly" | "monthly";
};
onPress?: () => void;
compact?: boolean;
}) => {
export const PricingItem = ({ product, onPress, compact }) => {
return (
<PressableButton
onPress={onPress}
@@ -68,13 +54,7 @@ export const PricingItem = ({
<View>
<Paragraph size={SIZE.sm}>
<Heading size={SIZE.lg - 2}>
{Platform.OS === "android"
? (product.data as RNIap.SubscriptionAndroid)
?.subscriptionOfferDetails[0].pricingPhases
.pricingPhaseList?.[0].formattedPrice
: (product.data as RNIap.SubscriptionIOS)?.localizedPrice}
</Heading>
<Heading size={SIZE.lg - 2}>{product?.data?.localizedPrice}/</Heading>
{product?.type === "yearly" || product?.offerType === "yearly"
? "/year"
: "/month"}

View File

@@ -47,15 +47,11 @@ import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { Walkthrough } from "../walkthroughs";
import { PricingItem } from "./pricing-item";
import { useSettingStore } from "../../stores/use-setting-store";
const promoCyclesMonthly = {
1: "first month",
2: "first 2 months",
3: "first 3 months",
4: "first 4 months",
5: "first 5 months",
6: "first 3 months"
3: "first 3 months"
};
const promoCyclesYearly = {
@@ -69,24 +65,10 @@ export const PricingPlans = ({
marginTop,
heading = true,
compact = false
}: {
promo?: {
promoCode: string;
};
marginTop?: any;
heading?: boolean;
compact?: boolean;
}) => {
const { colors } = useThemeColors();
const user = useUserStore((state) => state.user);
const [product, setProduct] = useState<{
type: string;
offerType: "monthly" | "yearly";
data: RNIap.Subscription;
cycleText: string;
info: string;
}>();
const [product, setProduct] = useState(null);
const [buying, setBuying] = useState(false);
const [loading, setLoading] = useState(false);
const userCanRequestTrial =
@@ -108,40 +90,27 @@ export const PricingPlans = ({
}
}, [promo?.promoCode]);
const getPromo = async (code: string) => {
const getPromo = async (code) => {
try {
let skuId: string;
let productId;
if (code.startsWith("com.streetwriters.notesnook")) {
skuId = code;
productId = code;
} else {
skuId = await db.offers?.getCode(code.split(":")[0], Platform.OS);
productId = await db.offers.getCode(code.split(":")[0], Platform.OS);
}
const products = await PremiumService.getProducts();
const product = products.find((p) => p.productId === skuId);
let products = await PremiumService.getProducts();
let product = products.find((p) => p.productId === productId);
if (!product) return false;
const isMonthly = product.productId.indexOf(".mo") > -1;
const cycleText = isMonthly
let isMonthly = product.productId.indexOf(".mo") > -1;
let cycleText = isMonthly
? promoCyclesMonthly[
(Platform.OS === "android"
? (product as RNIap.SubscriptionAndroid)
.subscriptionOfferDetails[0]?.pricingPhases
.pricingPhaseList?.[0].billingCycleCount
: parseInt(
(product as RNIap.SubscriptionIOS)
.introductoryPriceNumberOfPeriodsIOS as string
)) as keyof typeof promoCyclesMonthly
product.introductoryPriceCyclesAndroid ||
product.introductoryPriceNumberOfPeriodsIOS
]
: promoCyclesYearly[
(Platform.OS === "android"
? (product as RNIap.SubscriptionAndroid)
.subscriptionOfferDetails[0]?.pricingPhases
.pricingPhaseList?.[0].billingCycleCount
: parseInt(
(product as RNIap.SubscriptionIOS)
.introductoryPriceNumberOfPeriodsIOS as string
)) as keyof typeof promoCyclesYearly
product.introductoryPriceCyclesAndroid ||
product.introductoryPriceNumberOfPeriodsIOS
];
setProduct({
@@ -149,7 +118,7 @@ export const PricingPlans = ({
offerType: isMonthly ? "monthly" : "yearly",
data: product,
cycleText: cycleText,
info: `Pay ${isMonthly ? "monthly" : "yearly"}, cancel anytime`
info: "Pay monthly, cancel anytime"
});
return true;
} catch (e) {
@@ -162,37 +131,22 @@ export const PricingPlans = ({
getSkus();
}, [getSkus]);
const buySubscription = async (product: RNIap.Subscription) => {
if (buying || !product) return;
const buySubscription = async (product) => {
if (buying) return;
setBuying(true);
try {
if (!user) {
setBuying(false);
return;
}
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
const androidOfferToken =
Platform.OS === "android"
? (product as RNIap.SubscriptionAndroid).subscriptionOfferDetails[0]
.offerToken
: null;
await RNIap.requestSubscription({
sku: product?.productId,
obfuscatedAccountIdAndroid: user.id,
obfuscatedProfileIdAndroid: user.id,
appAccountToken: user.id,
andDangerouslyFinishTransactionAutomaticallyIOS: false,
subscriptionOffers: androidOfferToken
? [
{
offerToken: androidOfferToken,
sku: product?.productId
}
]
: undefined
});
useSettingStore.getState().setAppDidEnterBackgroundForAction(false);
await RNIap.requestSubscription(
product?.productId,
false,
null,
-1,
user.id,
user.id
);
setBuying(false);
eSendEvent(eCloseSheet);
eSendEvent(eClosePremiumDialog);
@@ -244,14 +198,7 @@ export const PricingPlans = ({
}}
size={SIZE.lg}
>
{(Platform.OS === "android"
? (monthlyPlan?.product as RNIap.SubscriptionAndroid)
?.subscriptionOfferDetails[0].pricingPhases
.pricingPhaseList?.[0].formattedPrice
: (monthlyPlan?.product as RNIap.SubscriptionIOS)
.localizedPrice) ||
(PremiumService.getMontlySub() as any).localizedPrice}
/ mo
{PremiumService.getMontlySub().localizedPrice} / mo
</Paragraph>
<Button
onPress={() => {
@@ -271,7 +218,7 @@ export const PricingPlans = ({
<Button
onPress={async () => {
try {
await db.user?.activateTrial();
await db.user.activateTrial();
eSendEvent(eClosePremiumDialog);
eSendEvent(eCloseSheet);
await sleep(300);
@@ -300,11 +247,7 @@ export const PricingPlans = ({
}}
size={SIZE.lg - 4}
>
{Platform.OS === "android"
? (product.data as RNIap.SubscriptionAndroid)
?.subscriptionOfferDetails[0].pricingPhases
.pricingPhaseList?.[0].formattedPrice
: (product.data as RNIap.SubscriptionIOS)?.introductoryPrice}
{product.data.introductoryPrice}
<Paragraph
style={{
textDecorationLine: "line-through",
@@ -312,11 +255,7 @@ export const PricingPlans = ({
}}
size={SIZE.sm}
>
{Platform.OS === "android"
? (product.data as RNIap.SubscriptionAndroid)
?.subscriptionOfferDetails[1].pricingPhases
.pricingPhaseList?.[1].formattedPrice
: (product.data as RNIap.SubscriptionIOS)?.localizedPrice}
({product.data.localizedPrice})
</Paragraph>{" "}
for {product.cycleText}
</Heading>
@@ -324,9 +263,9 @@ export const PricingPlans = ({
{user && !product ? (
<>
{heading || (monthlyPlan?.info?.discount || 0) > 0 ? (
{heading || monthlyPlan?.info?.discount > 0 ? (
<>
{monthlyPlan && (monthlyPlan?.info?.discount || 0) > 0 ? (
{monthlyPlan && monthlyPlan?.info?.discount > 0 ? (
<View
style={{
alignSelf: "center",
@@ -361,10 +300,7 @@ export const PricingPlans = ({
}}
>
<PricingItem
onPress={() => {
if (!monthlyPlan?.product) return;
buySubscription(monthlyPlan?.product);
}}
onPress={() => buySubscription(monthlyPlan?.product)}
compact={compact}
product={{
type: "monthly",
@@ -383,10 +319,7 @@ export const PricingPlans = ({
)}
<PricingItem
onPress={() => {
if (!yearlyPlan?.product) return;
buySubscription(yearlyPlan?.product);
}}
onPress={() => buySubscription(yearlyPlan?.product)}
compact={compact}
product={{
type: "yearly",
@@ -413,7 +346,7 @@ export const PricingPlans = ({
eSendEvent(eCloseSimpleDialog);
setBuying(true);
try {
if (!(await getPromo(value as string)))
if (!(await getPromo(value)))
throw new Error("Error applying promo code");
ToastEvent.show({
heading: "Discount applied!",
@@ -425,7 +358,7 @@ export const PricingPlans = ({
setBuying(false);
ToastEvent.show({
heading: "Promo code invalid or expired",
message: (e as Error).message,
message: e.message,
type: "error",
context: "local"
});
@@ -494,10 +427,7 @@ export const PricingPlans = ({
) : (
<>
<Button
onPress={() => {
if (!product?.data) return;
buySubscription(product.data);
}}
onPress={() => buySubscription(product.data)}
height={40}
width="50%"
type="accent"
@@ -506,7 +436,7 @@ export const PricingPlans = ({
<Button
onPress={() => {
setProduct(undefined);
setProduct(null);
}}
style={{
marginTop: 5
@@ -570,7 +500,7 @@ export const PricingPlans = ({
textAlign: "center"
}}
>
By subscribing, you will be charged on your Google Account, and
By subscribing, your will be charged on your Google Account, and
your subscription will automatically renew until you cancel prior
to the end of the then current period.
</Paragraph>
@@ -593,7 +523,7 @@ export const PricingPlans = ({
<Paragraph
size={SIZE.xs}
onPress={() => {
openLinkInBrowser("https://notesnook.com/tos")
openLinkInBrowser("https://notesnook.com/tos", colors)
.catch(() => {})
.then(() => {});
}}
@@ -608,7 +538,7 @@ export const PricingPlans = ({
<Paragraph
size={SIZE.xs}
onPress={() => {
openLinkInBrowser("https://notesnook.com/privacy")
openLinkInBrowser("https://notesnook.com/privacy", colors)
.catch(() => {})
.then(() => {});
}}

View File

@@ -16,8 +16,8 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useRef } from "react";
import { Dimensions, Platform, View, useWindowDimensions } from "react-native";
import React from "react";
import { Platform, View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import { db } from "../../common/database";
import { DDS } from "../../services/device-detection";
@@ -54,6 +54,7 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
const { colors } = useThemeColors();
const alias = item.alias || item.title;
const isColor = !!ColorValues[item.title];
if (!item || !item.id) {
return (
<Paragraph style={{ marginVertical: 10, alignSelf: "center" }}>
@@ -61,7 +62,6 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
</Paragraph>
);
}
return (
<FlatList
keyboardShouldPersistTaps="always"
@@ -135,7 +135,6 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
}, 1000);
}}
/>
<Synced item={item} close={close} />
{DDS.isTab ? (

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { Dimensions, FlatList, ScrollView, View } from "react-native";
import { FlatList, ScrollView, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useActions } from "../../hooks/use-actions";
import { DDS } from "../../services/device-detection";
@@ -33,11 +33,9 @@ export const Items = ({ item, buttons, close }) => {
const dimensions = useSettingStore((state) => state.dimensions);
const actions = useActions({ item, close });
const data = actions.filter((i) => buttons.indexOf(i.id) > -1 && !i.hidden);
let width = dimensions.width > 600 ? 600 : dimensions.width;
const shouldShrink =
Dimensions.get("window").fontScale > 1 &&
Dimensions.get("window").width < 450;
let columnItemsCount = DDS.isLargeTablet() ? 7 : shouldShrink ? 4 : 5;
let columnItemsCount = DDS.isLargeTablet() ? 7 : 5;
let columnItemWidth = DDS.isTab
? (width - 12) / columnItemsCount
: (width - 12) / columnItemsCount;
@@ -68,7 +66,6 @@ export const Items = ({ item, buttons, close }) => {
}}
>
<Icon
allowFontScaling
name={item.icon}
size={DDS.isTab ? SIZE.xxl : SIZE.lg}
color={
@@ -81,11 +78,7 @@ export const Items = ({ item, buttons, close }) => {
/>
</PressableButton>
<Paragraph
size={SIZE.xs}
textBreakStrategy="simple"
style={{ textAlign: "center" }}
>
<Paragraph size={SIZE.xs} style={{ textAlign: "center" }}>
{item.title}
</Paragraph>
</View>
@@ -146,7 +139,6 @@ export const Items = ({ item, buttons, close }) => {
>
<Icon
name={item.icon}
allowFontScaling
size={DDS.isTab ? SIZE.xxl : SIZE.md + 4}
color={
item.on
@@ -158,11 +150,7 @@ export const Items = ({ item, buttons, close }) => {
/>
</PressableButton>
<Paragraph
textBreakStrategy="simple"
size={SIZE.xxs + 1}
style={{ textAlign: "center" }}
>
<Paragraph size={SIZE.xxs + 1} style={{ textAlign: "center" }}>
{item.title}
</Paragraph>
</PressableButton>
@@ -175,12 +163,9 @@ export const Items = ({ item, buttons, close }) => {
"copy",
"share",
"export",
"lock-unlock"
"lock-unlock",
"publish"
];
if (!shouldShrink) {
topBarItemsList.push("publish");
}
const topBarItems = data.filter(
(item) => topBarItemsList.indexOf(item.id) > -1
);

View File

@@ -17,10 +17,10 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { View, useWindowDimensions } from "react-native";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useThemeColors } from "@notesnook/theme";
import { useUserStore } from "../../stores/use-user-store";
import { openLinkInBrowser } from "../../utils/functions";
import { SIZE } from "../../utils/size";
@@ -33,8 +33,6 @@ export const Synced = ({ item, close }) => {
const user = useUserStore((state) => state.user);
const lastSynced = useUserStore((state) => state.lastSynced);
const dimensions = useWindowDimensions();
const shouldShrink = dimensions.fontScale > 1 && dimensions.width < 450;
return user && lastSynced >= item.dateModified ? (
<View
style={{
@@ -51,11 +49,7 @@ export const Synced = ({ item, close }) => {
borderTopColor: colors.secondary.background
}}
>
<Icon
name="shield-key-outline"
color={colors.primary.accent}
size={shouldShrink ? SIZE.xxl : SIZE.xxxl}
/>
<Icon name="shield-key-outline" color={colors.primary.accent} size={SIZE.xxxl} />
<View
style={{
@@ -73,17 +67,15 @@ export const Synced = ({ item, close }) => {
>
Encrypted and synced
</Heading>
{shouldShrink ? null : (
<Paragraph
style={{
flexWrap: "wrap"
}}
size={SIZE.xs}
color={colors.primary.paragraph}
>
No one can view this {item.itemType || item.type} except you.
</Paragraph>
)}
<Paragraph
style={{
flexWrap: "wrap"
}}
size={SIZE.xs}
color={colors.primary.paragraph}
>
No one can view this {item.itemType || item.type} except you.
</Paragraph>
</View>
<Button
@@ -99,8 +91,8 @@ export const Synced = ({ item, close }) => {
console.error(e);
}
}}
title="Learn more"
fontSize={SIZE.xs}
title="Learn more"
height={30}
type="grayAccent"
/>

View File

@@ -207,6 +207,7 @@ export const SelectionHeader = React.memo(() => {
{screen === "Trash" ||
screen === "Notebooks" ||
screen === "Notebook" ||
screen === "Reminders" ? null : (
<>
<IconButton
@@ -223,21 +224,6 @@ export const SelectionHeader = React.memo(() => {
name="pound"
size={SIZE.xl}
/>
<IconButton
onPress={async () => {
ExportNotesSheet.present(selectedItemsList);
}}
tooltipText="Export"
tooltipPosition={4}
customStyle={{
marginLeft: 10
}}
color={colors.primary.paragraph}
name="export"
size={SIZE.xl}
/>
<IconButton
onPress={async () => {
//setSelectionMode(false);
@@ -253,6 +239,20 @@ export const SelectionHeader = React.memo(() => {
name="plus"
size={SIZE.xl}
/>
<IconButton
onPress={async () => {
ExportNotesSheet.present(selectedItemsList);
}}
tooltipText="Export"
tooltipPosition={4}
customStyle={{
marginLeft: 10
}}
color={colors.primary.paragraph}
name="export"
size={SIZE.xl}
/>
</>
)}
@@ -317,8 +317,23 @@ export const SelectionHeader = React.memo(() => {
customStyle={{
marginLeft: 10
}}
onPress={() => {
deleteItems();
onPress={async () => {
presentDialog({
title: `Delete ${
selectedItemsList.length > 1 ? "items" : "item"
}`,
paragraph: `Are you sure you want to delete ${
selectedItemsList.length > 1 ? "these items?" : "this item?"
}`,
positiveText: "Delete",
negativeText: "Cancel",
positivePress: () => {
deleteItems();
},
positiveType: "errorShade"
});
return;
}}
tooltipText="Move to trash"
tooltipPosition={1}

View File

@@ -102,11 +102,7 @@ const SheetProvider = ({ context = "global" }) => {
setData(null);
}}
bottomPadding={!data.noBottomPadding}
enableGesturesInScrollView={
typeof data.enableGesturesInScrollView === "undefined"
? true
: data.enableGesturesInScrollView
}
enableGesturesInScrollView={data.enableGesturesInScrollView}
>
<View
style={{
@@ -131,7 +127,7 @@ const SheetProvider = ({ context = "global" }) => {
{data?.icon ? (
<Icon
color={colors[data.iconColor] || colors.primary.accent}
color={colors[dialogData.iconColor] || colors.primary.accent}
name={data.icon}
size={50}
/>
@@ -198,6 +194,7 @@ const SheetProvider = ({ context = "global" }) => {
key={data.actionText}
title={data.actionText}
accentColor={data.iconColor}
accentText="light"
type="accent"
height={40}
width={250}

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useCallback, useEffect, useRef, useState } from "react";
import { FlashList } from "react-native-actions-sheet";
import { FlatList } from "react-native-actions-sheet";
import { db } from "../../../common/database";
import { ListHeaderInputItem } from "./list-header-item.js";
@@ -54,7 +54,7 @@ export const FilteredList = ({
}, [data, onChangeText]);
return (
<FlashList
<FlatList
{...restProps}
data={filtered}
ref={listRef}

View File

@@ -161,6 +161,9 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
: "deselected";
if (itemState[notebook.id] === "selected") {
count++;
contextValue.select(notebook);
} else {
contextValue.deselect(notebook);
}
for (let topic of notebook.topics) {
itemState[topic.id] = state
@@ -173,6 +176,9 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
: "deselected";
if (itemState[topic.id] === "selected") {
count++;
contextValue.select(topic);
} else {
contextValue.deselect(topic);
}
}
}
@@ -183,7 +189,7 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
}
useItemSelectionStore.getState().setItemState(itemState);
},
[getSelectedNotesCountInItem, selectedItemsList]
[contextValue, getSelectedNotesCountInItem, selectedItemsList]
);
const getItemsForItem = (item) => {
@@ -208,7 +214,7 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
}
const updateItemState = useCallback(function (item, state) {
const itemState = { ...useItemSelectionStore.getState().itemState };
const itemState = useItemSelectionStore.getState().itemState;
const mergeState = {
[item.id]: state
};
@@ -364,93 +370,56 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
</View>
<SelectionProvider value={contextValue}>
<View
<FilteredList
style={{
paddingHorizontal: 12,
maxHeight: dimensions.height * 0.85,
height: 50 * (notebooks.length + 2)
maxHeight: dimensions.height * 0.85
}}
>
<FilteredList
ListEmptyComponent={
notebooks.length > 0 ? null : (
<View
style={{
width: "100%",
height: "100%",
justifyContent: "center",
alignItems: "center"
}}
>
<Icon
name="book-outline"
color={colors.primary.icon}
size={100}
/>
<Paragraph style={{ marginBottom: 10 }}>
You do not have any notebooks.
</Paragraph>
</View>
)
}
estimatedItemSize={50}
data={notebooks}
hasHeaderSearch={true}
renderItem={({ item, index }) => (
<ListItem
item={item}
key={item.id}
index={index}
hasNotes={getSelectedNotesCountInItem(item) > 0}
sheetRef={actionSheetRef}
infoText={
<>
{item.topics.length === 1
? item.topics.length + " topic"
: item.topics.length + " topics"}
</>
}
getListItems={getItemsForItem}
getSublistItemProps={(topic) => ({
hasNotes: getSelectedNotesCountInItem(topic) > 0,
style: {
marginBottom: 0,
height: 40
},
onPress: (item) => {
const itemState =
useItemSelectionStore.getState().itemState;
const currentState = itemState[item.id];
if (currentState !== "selected") {
resetItemState("deselected");
contextValue.select(item);
} else {
contextValue.deselect(item);
}
},
key: item.id,
type: "transparent"
})}
icon={(expanded) => ({
name: expanded ? "chevron-up" : "chevron-down",
color: expanded
? colors.primary.accent
: colors.primary.paragraph
})}
onScrollEnd={() => {
actionSheetRef.current?.handleChildScrollEnd();
ListEmptyComponent={
notebooks.length > 0 ? null : (
<View
style={{
width: "100%",
height: "100%",
justifyContent: "center",
alignItems: "center"
}}
hasSubList={true}
hasHeaderSearch={false}
type="grayBg"
sublistItemType="topic"
onAddItem={(title) => {
return onAddTopic(title, item);
}}
onAddSublistItem={(item) => {
openAddTopicDialog(item);
}}
onPress={(item) => {
>
<Icon
name="book-outline"
color={colors.primary.icon}
size={100}
/>
<Paragraph style={{ marginBottom: 10 }}>
You do not have any notebooks.
</Paragraph>
</View>
)
}
data={notebooks}
hasHeaderSearch={true}
renderItem={({ item, index }) => (
<ListItem
item={item}
key={item.id}
index={index}
hasNotes={getSelectedNotesCountInItem(item) > 0}
sheetRef={actionSheetRef}
infoText={
<>
{item.topics.length === 1
? item.topics.length + " topic"
: item.topics.length + " topics"}
</>
}
getListItems={getItemsForItem}
getSublistItemProps={(topic) => ({
hasNotes: getSelectedNotesCountInItem(topic) > 0,
style: {
marginBottom: 0,
height: 40
},
onPress: (item) => {
const itemState =
useItemSelectionStore.getState().itemState;
const currentState = itemState[item.id];
@@ -460,16 +429,47 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
} else {
contextValue.deselect(item);
}
}}
/>
)}
itemType="notebook"
onAddItem={async (title) => {
return await onAddNotebook(title);
}}
ListFooterComponent={<View style={{ height: 20 }} />}
/>
</View>
},
key: item.id,
type: "transparent"
})}
icon={(expanded) => ({
name: expanded ? "chevron-up" : "chevron-down",
color: expanded
? colors.primary.accent
: colors.primary.paragraph
})}
onScrollEnd={() => {
actionSheetRef.current?.handleChildScrollEnd();
}}
hasSubList={true}
hasHeaderSearch={false}
type="grayBg"
sublistItemType="topic"
onAddItem={(title) => {
return onAddTopic(title, item);
}}
onAddSublistItem={(item) => {
openAddTopicDialog(item);
}}
onPress={(item) => {
const itemState = useItemSelectionStore.getState().itemState;
const currentState = itemState[item.id];
if (currentState !== "selected") {
resetItemState("deselected");
contextValue.select(item);
} else {
contextValue.deselect(item);
}
}}
/>
)}
itemType="notebook"
onAddItem={async (title) => {
return await onAddNotebook(title);
}}
ListFooterComponent={<View style={{ height: 20 }} />}
/>
</SelectionProvider>
</View>
</>

View File

@@ -273,7 +273,6 @@ export const ListItem = ({
alignSelf: "flex-end",
maxHeight: 250
}}
estimatedItemSize={40}
itemType={sublistItemType}
hasHeaderSearch={hasHeaderSearch}
renderItem={({ item, index }) => (

View File

@@ -35,14 +35,10 @@ import { presentDialog } from "../../dialog/functions";
import { Button } from "../../ui/button";
import Seperator from "../../ui/seperator";
import Paragraph from "../../ui/typography/paragraph";
import { useStoredRef } from "../../../hooks/use-stored-ref";
export const Issue = ({ defaultTitle, defaultBody, issueTitle }) => {
const { colors } = useThemeColors();
const body = useStoredRef("issueBody", defaultBody);
const title = useStoredRef("issueTitle", defaultTitle);
const body = useRef(defaultBody);
const title = useRef(defaultTitle);
const user = useUserStore((state) => state.user);
const [loading, setLoading] = useState(false);
const bodyRef = useRef();
@@ -74,8 +70,6 @@ Logged in: ${user ? "yes" : "no"}`,
});
setLoading(false);
eSendEvent(eCloseSheet);
body.reset();
title.reset();
await sleep(300);
presentDialog({
title: "Issue reported",
@@ -111,7 +105,7 @@ Logged in: ${user ? "yes" : "no"}`,
} catch (e) {
setLoading(false);
ToastEvent.show({
heading: "An error occurred",
heading: "An error occured",
message: e.message,
type: "error"
});

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 { EVENTS } from "@notesnook/core/dist/common";
import { EVENTS } from "@notesnook/core/common";
import React, { useCallback, useEffect, useState } from "react";
import { Platform, View } from "react-native";
import { db } from "../../../common/database";
@@ -177,7 +177,7 @@ export default function Migrate() {
textAlign: "center"
}}
>
An error occurred while migrating your data. You can logout of your
An error occured while migrating your data. You can logout of your
account and try to relogin. However this is not recommended as it
may result in some data loss if your data was not synced.
</Paragraph>

View File

@@ -64,7 +64,7 @@ export const Progress = () => {
width={null}
animated={true}
useNativeDriver
indeterminate
progress={currentProgress || 0.1}
unfilledColor={colors.secondary.background}
color={colors.primary.accent}
borderWidth={0}
@@ -74,7 +74,7 @@ export const Progress = () => {
{progress ? (
<Paragraph color={colors.secondary.paragraph}>
{progress.type?.slice(0, 1).toUpperCase() + progress.type?.slice(1)}
ing {progress?.current}
ing {progress?.current}/{progress?.total}
</Paragraph>
) : null}
</View>

View File

@@ -40,9 +40,7 @@ import { requestInAppReview } from "../../../services/app-review";
const PublishNoteSheet = ({ note: item, update }) => {
const { colors } = useThemeColors();
const actionSheetRef = useRef();
const attachmentDownloads = useAttachmentStore((state) => state.downloading);
const downloading = attachmentDownloads[`monograph-${item.id}`];
const loading = useAttachmentStore((state) => state.loading);
const [selfDestruct, setSelfDestruct] = useState(false);
const [isLocked, setIsLocked] = useState(false);
const [note, setNote] = useState(item);
@@ -139,9 +137,9 @@ const PublishNoteSheet = ({ note: item, update }) => {
}}
>
Please wait...
{downloading && downloading.current && downloading.total
{loading && loading.current && loading.total
? `\nDownloading attachments (${
downloading?.current / downloading?.total
loading?.current / loading?.total
})`
: ""}
</Paragraph>

View File

@@ -16,7 +16,6 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React, { RefObject, useRef, useState } from "react";
import { Platform, TextInput, View } from "react-native";
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
@@ -26,6 +25,7 @@ import {
ToastEvent,
presentSheet
} from "../../../services/event-manager";
import { useThemeColors } from "@notesnook/theme";
import { SIZE } from "../../../utils/size";
import { Button } from "../../ui/button";
import Input from "../../ui/input";
@@ -223,7 +223,7 @@ export default function ReminderSheet({
<View
style={{
paddingHorizontal: 12,
maxHeight: DDS.isTab ? "99.99%" : "99.99%"
maxHeight: DDS.isTab ? "90%" : "99.99%"
}}
>
<View
@@ -247,63 +247,7 @@ export default function ReminderSheet({
/>
</View>
<Dialog context="local" />
<ScrollView
bounces={false}
style={{
marginBottom: DDS.isTab ? 25 : undefined
}}
>
{reminderMode === ReminderModes.Permanent ? null : (
<ScrollView
style={{
flexDirection: "row",
borderWidth: 1,
marginTop: 12,
borderRadius: 5,
borderColor: colors.primary.border,
paddingLeft: 12,
height: 50
}}
horizontal
>
{Object.keys(ReminderNotificationModes).map((mode) => (
<Button
key={mode}
title={mode}
style={{
marginRight: 12,
borderRadius: 100
}}
icon={
mode === "Silent"
? "minus-circle"
: mode === "Vibrate"
? "vibrate"
: "volume-high"
}
height={35}
type={
reminderNotificationMode ===
ReminderNotificationModes[
mode as keyof typeof ReminderNotificationModes
]
? "grayAccent"
: "gray"
}
onPress={() => {
const _mode = ReminderNotificationModes[
mode as keyof typeof ReminderNotificationModes
] as Reminder["priority"];
SettingsService.set({
reminderNotificationMode: _mode
});
setReminderNotificatioMode(_mode);
}}
/>
))}
</ScrollView>
)}
<ScrollView>
<Input
fwdRef={titleRef}
defaultValue={reminder?.title || referencedItem?.title}
@@ -331,21 +275,15 @@ export default function ReminderSheet({
}}
height={80}
wrapperStyle={{
marginBottom: 12
marginBottom: 20
}}
/>
<ScrollView
<View
style={{
flexDirection: "row",
marginBottom: 12,
height: 50,
borderWidth: 1,
borderRadius: 5,
borderColor: colors.primary.border,
paddingLeft: 12
marginBottom: 12
}}
horizontal
>
{Object.keys(ReminderModes).map((mode) => (
<Button
@@ -384,7 +322,7 @@ export default function ReminderSheet({
}}
/>
))}
</ScrollView>
</View>
{reminderMode === ReminderModes.Repeat ? (
<View
@@ -531,7 +469,9 @@ export default function ReminderSheet({
androidVariant="nativeAndroid"
is24hourSource="locale"
locale={
db.settings?.getTimeFormat() === "24-hour" ? "en_GB" : "en_US"
db.settings?.getTimeFormat() === "24-hour"
? "en_GB.UTF8"
: "en_US.UTF8"
}
mode={reminderMode === ReminderModes.Repeat ? "time" : "datetime"}
/>
@@ -553,6 +493,53 @@ export default function ReminderSheet({
</View>
)}
{reminderMode === ReminderModes.Permanent ? null : (
<View
style={{
flexDirection: "row",
marginBottom: 12,
paddingTop: 12,
borderTopWidth: 1,
borderTopColor: colors.secondary.background
}}
>
{Object.keys(ReminderNotificationModes).map((mode) => (
<Button
key={mode}
title={mode}
style={{
marginRight: 12,
borderRadius: 100
}}
icon={
mode === "Silent"
? "minus-circle"
: mode === "Vibrate"
? "vibrate"
: "volume-high"
}
height={35}
type={
reminderNotificationMode ===
ReminderNotificationModes[
mode as keyof typeof ReminderNotificationModes
]
? "grayAccent"
: "gray"
}
onPress={() => {
const _mode = ReminderNotificationModes[
mode as keyof typeof ReminderNotificationModes
] as Reminder["priority"];
SettingsService.set({
reminderNotificationMode: _mode
});
setReminderNotificatioMode(_mode);
}}
/>
))}
</View>
)}
{reminderMode === ReminderModes.Once ||
reminderMode === ReminderModes.Permanent ? null : (
<View
@@ -603,11 +590,6 @@ export default function ReminderSheet({
}}
/>
</ScrollView>
<View
style={{
height: 10
}}
/>
</View>
);
}
@@ -619,7 +601,7 @@ ReminderSheet.present = (
) => {
presentSheet({
context: isSheet ? "local" : undefined,
enableGesturesInScrollView: false,
enableGesturesInScrollView: true,
noBottomPadding: true,
component: (ref, close, update) => (
<ReminderSheet

View File

@@ -17,14 +17,12 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { EVENTS } from "@notesnook/core/dist/common";
import { EVENTS } from "@notesnook/core/common";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { ActivityIndicator, Platform, View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import RNFetchBlob from "react-native-blob-util";
import DocumentPicker, {
DocumentPickerResponse
} from "react-native-document-picker";
import DocumentPicker from "react-native-document-picker";
import * as ScopedStorage from "react-native-scoped-storage";
import { db } from "../../../common/database";
import storage from "../../../common/database/storage";
@@ -47,8 +45,6 @@ import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet";
import Paragraph from "../../ui/typography/paragraph";
import { getFormattedDate } from "@notesnook/common";
import { unzip } from "react-native-zip-archive";
import { cacheDir, copyFileAsync } from "../../../common/filesystem/utils";
const RestoreDataSheet = () => {
const [visible, setVisible] = useState(false);
@@ -133,71 +129,75 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
useEffect(() => {
setTimeout(() => {
checkBackups();
}, 1000);
}, 300);
}, []);
const restore = async (item) => {
if (restoring) {
return;
}
try {
const file = Platform.OS === "ios" ? item.path : item.uri;
console.log(file);
if (file.endsWith(".nnbackupz")) {
setRestoring(true);
setRestoring(true);
let prefix = Platform.OS === "ios" ? "" : "file:/";
let backup;
if (Platform.OS === "android") {
backup = await ScopedStorage.readFile(item.uri, "utf8");
} else {
backup = await RNFetchBlob.fs.readFile(prefix + item.path, "utf8");
}
backup = JSON.parse(backup);
if (Platform.OS === "android") {
const cacheFile = `file://${RNFetchBlob.fs.dirs.CacheDir}/backup.zip`;
if (await RNFetchBlob.fs.exists(cacheFile)) {
await RNFetchBlob.fs.unlink(cacheFile);
if (backup.data.iv && backup.data.salt) {
withPassword(
async (value) => {
try {
await restoreBackup(backup, value);
close();
setRestoring(false);
return true;
} catch (e) {
backupError(e);
return false;
}
},
() => {
setRestoring(false);
}
await RNFetchBlob.fs.createFile(cacheFile, "", "utf8");
console.log("copying");
await copyFileAsync(file, cacheFile);
console.log("copied");
await restoreFromZip(cacheFile);
} else {
await restoreFromZip(file, false);
}
} else if (file.endsWith(".nnbackup")) {
let backup;
if (Platform.OS === "android") {
backup = await ScopedStorage.readFile(file, "utf8");
} else {
backup = await RNFetchBlob.fs.readFile(file, "utf8");
}
await restoreFromNNBackup(JSON.parse(backup));
);
} else {
await restoreBackup(backup);
close();
}
} catch (e) {
console.log("error", e);
setRestoring(false);
backupError(e);
}
};
const withPassword = () => {
return new Promise((resolve) => {
let resolved = false;
presentDialog({
context: "local",
title: "Encrypted backup",
input: true,
inputPlaceholder: "Password",
paragraph: "Please enter password of this backup file to restore it",
positiveText: "Restore",
secureTextEntry: true,
onClose: () => {
if (resolved) return;
resolve(undefined);
},
negativeText: "Cancel",
positivePress: async (password) => {
resolve(password);
resolved = true;
return true;
const withPassword = (onsubmit, onclose = () => {}) => {
presentDialog({
context: "local",
title: "Encrypted backup",
input: true,
inputPlaceholder: "Password",
paragraph: "Please enter password of this backup file to restore it",
positiveText: "Restore",
secureTextEntry: true,
onClose: onclose,
negativeText: "Cancel",
positivePress: async (password) => {
try {
return await onsubmit(password);
} catch (e) {
ToastEvent.show({
heading: "Failed to backup data",
message: e.message,
type: "error",
context: "global"
});
return false;
}
});
}
});
};
@@ -217,164 +217,21 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
let path = await storage.checkAndCreateDir("/backups/");
files = await RNFetchBlob.fs.lstat(path);
}
files = files
.filter((file) => {
const name = Platform.OS === "android" ? file.name : file.filename;
return name.endsWith(".nnbackup") || name.endsWith(".nnbackupz");
})
.sort(function (a, b) {
let timeA = a.lastModified;
let timeB = b.lastModified;
return timeB - timeA;
});
files = files.sort(function (a, b) {
let timeA = a.lastModified;
let timeB = b.lastModified;
return timeB - timeA;
});
setFiles(files);
setLoading(false);
setTimeout(() => {
setLoading(false);
}, 1000);
} catch (e) {
console.log(e);
setLoading(false);
}
};
const restoreBackup = async (backup, password) => {
await db.backup.import(backup, password);
await db.initCollections();
initialize();
ToastEvent.show({
heading: "Backup restored successfully.",
type: "success",
context: "global"
});
return true;
};
const backupError = (e) => {
ToastEvent.show({
heading: "Restore failed",
message:
e.message ||
"The selected backup data file is invalid. You must select a *.nnbackup file to restore.",
type: "error",
context: "local"
});
};
/**
*
* @param {string} file
*/
async function restoreFromZip(file, remove) {
try {
const zipOutputFolder = `${cacheDir}/backup_extracted`;
if (await RNFetchBlob.fs.exists(zipOutputFolder)) {
await RNFetchBlob.fs.unlink(zipOutputFolder);
await RNFetchBlob.fs.mkdir(zipOutputFolder);
}
await unzip(file, zipOutputFolder);
console.log("Unzipped files successfully to", zipOutputFolder);
const backupFiles = await RNFetchBlob.fs.ls(zipOutputFolder);
if (backupFiles.findIndex((file) => file === ".nnbackup") === -1) {
throw new Error("Backup file is invalid");
}
let password;
console.log(`Found ${backupFiles?.length} files to restore from backup`);
for (const path of backupFiles) {
if (path === ".nnbackup") continue;
const filePath = `${zipOutputFolder}/${path}`;
const data = await RNFetchBlob.fs.readFile(filePath, "utf8");
const parsed = JSON.parse(data);
if (parsed.encrypted && !password) {
console.log("Backup is encrypted...", "requesting password");
password = await withPassword();
if (!password) throw new Error("Failed to decrypt backup");
}
await db.backup.import(parsed, password);
console.log("Imported", path);
}
// Remove files from cache
RNFetchBlob.fs.unlink(zipOutputFolder).catch(console.log);
if (remove) {
RNFetchBlob.fs.unlink(file).catch(console.log);
}
await db.initCollections();
initialize();
setRestoring(false);
close();
ToastEvent.show({
heading: "Backup restored successfully.",
type: "success",
context: "global"
});
} catch (e) {
backupError(e);
setRestoring(false);
}
}
/**
*
* @param {string} file
*/
async function restoreFromNNBackup(backup) {
try {
if (backup.data.iv && backup.data.salt) {
const password = await withPassword();
if (password) {
try {
await restoreBackup(backup, password);
close();
setRestoring(false);
} catch (e) {
setRestoring(false);
backupError(e);
}
} else {
setRestoring(false);
}
} else {
await restoreBackup(backup);
setRestoring(false);
close();
}
} catch (e) {
setRestoring(false);
backupError(e);
}
}
const button = {
title: "Restore from files",
onPress: async () => {
if (restoring) {
return;
}
try {
const file = await DocumentPicker.pickSingle({
copyTo: "cachesDirectory"
});
if (file.name.endsWith(".nnbackupz")) {
setRestoring(true);
await restoreFromZip(file.fileCopyUri, true);
} else if (file.name.endsWith(".nnbackup")) {
RNFetchBlob.fs.unlink(file.fileCopyUri).catch(console.log);
setRestoring(true);
const data = await fetch(file.uri);
await restoreFromNNBackup(await data.json());
}
} catch (e) {
console.log("error", e.stack);
setRestoring(false);
backupError(e);
}
}
};
const renderItem = ({ item, index }) => (
<View
style={{
@@ -414,6 +271,76 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
</View>
);
const restoreBackup = async (backup, password) => {
await db.backup.import(backup, password);
setRestoring(false);
initialize();
ToastEvent.show({
heading: "Backup restored successfully.",
type: "success",
context: "global"
});
};
const backupError = (e) => {
ToastEvent.show({
heading: "Restore failed",
message:
e.message ||
"The selected backup data file is invalid. You must select a *.nnbackup file to restore.",
type: "error",
context: "local"
});
};
const button = {
title: "Restore from files",
onPress: () => {
if (restoring) {
return;
}
DocumentPicker.pickSingle()
.then((r) => {
setRestoring(true);
fetch(r.uri)
.then(async (r) => {
try {
let backup = await r.json();
if (backup.data.iv && backup.data.salt) {
withPassword(
async (value) => {
try {
restoreBackup(backup, value).then(() => {
close();
setRestoring(false);
});
return true;
} catch (e) {
backupError(e);
setRestoring(false);
return false;
}
},
() => {
setRestoring(false);
}
);
} else {
await restoreBackup(backup);
close();
}
} catch (e) {
setRestoring(false);
backupError(e);
}
})
.catch(console.log);
})
.catch(console.log);
}
};
return (
<>
<View>
@@ -450,10 +377,7 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
height: 100
}}
>
<ActivityIndicator
color={colors.primary.accent}
size={SIZE.lg}
/>
<ActivityIndicator color={colors.primary.accent} size={SIZE.lg} />
</View>
) : (
<View
@@ -510,9 +434,7 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
</Paragraph>
</>
) : (
<Paragraph color={colors.secondary.paragraph}>
No backups found
</Paragraph>
<Paragraph color={colors.secondary.paragraph}>No backups found</Paragraph>
)}
</View>
)

View File

@@ -16,7 +16,6 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import qclone from "qclone";
import React, {
createContext,
@@ -27,7 +26,7 @@ import React, {
useRef,
useState
} from "react";
import { RefreshControl, useWindowDimensions, View } from "react-native";
import { RefreshControl, View } from "react-native";
import ActionSheet, {
ActionSheetRef,
FlatList
@@ -46,6 +45,7 @@ import {
import useNavigationStore, {
NotebookScreenParams
} from "../../../stores/use-navigation-store";
import { useThemeColors } from "@notesnook/theme";
import {
eOnNewTopicAdded,
eOnTopicSheetUpdate,
@@ -54,17 +54,18 @@ import {
import { normalize, SIZE } from "../../../utils/size";
import { GroupHeader, NotebookType, TopicType } from "../../../utils/types";
import { getTotalNotes } from "@notesnook/common";
import { groupArray } from "@notesnook/core/dist/utils/grouping";
import { groupArray } from "@notesnook/core/utils/grouping";
import Config from "react-native-config";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../../e2e/test.ids";
import { MMKV } from "../../../common/database/mmkv";
import { openEditor } from "../../../screens/notes/common";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { getTotalNotes } from "@notesnook/common";
import { deleteItems } from "../../../utils/functions";
import { presentDialog } from "../../dialog/functions";
import { Properties } from "../../properties";
import Sort from "../sort";
import { useSelectionStore } from "../../../stores/use-selection-store";
type ConfigItem = { id: string; type: string };
class TopicSheetConfig {
@@ -107,8 +108,7 @@ export const TopicsSheet = () => {
)
: []
);
const currentItem = useRef<string>();
const { fontScale } = useWindowDimensions();
const [groupOptions, setGroupOptions] = useState(
db.settings?.getGroupOptions("topics")
);
@@ -206,11 +206,6 @@ export const TopicsSheet = () => {
if (canShow) {
setTimeout(() => {
const id = isTopic ? currentScreen?.notebookId : currentScreen?.id;
if (currentItem.current !== id) {
setSelection([]);
setEnabled(false);
}
currentItem.current = id;
const notebook = db.notebooks?.notebook(id as string)?.data;
const snapPoint = isTopic
? 0
@@ -231,8 +226,6 @@ export const TopicsSheet = () => {
}
}, 300);
} else {
setSelection([]);
setEnabled(false);
ref.current?.hide();
}
}, [
@@ -270,7 +263,6 @@ export const TopicsSheet = () => {
index
);
}}
overlayColor={colors.primary.backdrop}
closable={!canShow}
elevation={10}
indicatorStyle={{
@@ -278,7 +270,7 @@ export const TopicsSheet = () => {
backgroundColor: colors.secondary.background
}}
keyboardHandlerEnabled={false}
snapPoints={Config.isTesting === "true" ? [100] : [20, 100]}
snapPoints={Config.isTesting === "true" ? [100] : [25, 100]}
initialSnapIndex={1}
backgroundInteractionEnabled
gestureEnabled
@@ -312,8 +304,8 @@ export const TopicsSheet = () => {
</View>
<View
style={{
maxHeight: 450,
height: 450,
maxHeight: 300,
height: 300,
width: "100%"
}}
>
@@ -336,19 +328,30 @@ export const TopicsSheet = () => {
{enabled ? (
<IconButton
customStyle={{
marginLeft: 10,
width: 40 * fontScale,
height: 40 * fontScale
marginLeft: 10
}}
onPress={async () => {
//@ts-ignore
useSelectionStore.setState({
selectedItemsList: selection
});
await deleteItems();
useSelectionStore.getState().clearSelection();
setEnabled(false);
setSelection([]);
presentDialog({
title: `Delete ${
selection.length > 1 ? "topics" : "topics"
}`,
paragraph: `Are you sure you want to delete ${
selection.length > 1 ? "these topics?" : "this topic?"
}`,
positiveText: "Delete",
negativeText: "Cancel",
positivePress: async () => {
await deleteItems();
useSelectionStore.getState().clearSelection();
setEnabled(false);
setSelection([]);
},
positiveType: "errorShade"
});
return;
}}
color={colors.primary.icon}
@@ -374,8 +377,8 @@ export const TopicsSheet = () => {
color={colors.primary.icon}
size={22}
customStyle={{
width: 40 * fontScale,
height: 40 * fontScale
width: 40,
height: 40
}}
/>
<IconButton
@@ -385,8 +388,8 @@ export const TopicsSheet = () => {
color={colors.primary.icon}
size={22}
customStyle={{
width: 40 * fontScale,
height: 40 * fontScale
width: 40,
height: 40
}}
/>
@@ -404,8 +407,8 @@ export const TopicsSheet = () => {
color={colors.primary.icon}
size={22}
customStyle={{
width: 40 * fontScale,
height: 40 * fontScale
width: 40,
height: 40
}}
/>
</>
@@ -479,7 +482,6 @@ const TopicItem = ({
selection.selection.findIndex((selected) => selected.id === item.id) > -1;
const isFocused = screen.id === item.id;
const notesCount = getTotalNotes(item);
const { fontScale } = useWindowDimensions();
return (
<PressableButton
@@ -516,10 +518,6 @@ const TopicItem = ({
<IconButton
size={SIZE.lg}
color={isSelected ? colors.selected.icon : colors.primary.icon}
top={0}
left={0}
bottom={0}
right={0}
name={
isSelected
? "check-circle-outline"
@@ -539,8 +537,8 @@ const TopicItem = ({
<IconButton
name="dots-horizontal"
customStyle={{
width: 40 * fontScale,
height: 40 * fontScale
width: 40,
height: 40
}}
testID={notesnook.ids.notebook.menu}
onPress={() => {

View File

@@ -28,8 +28,7 @@ import { useNoteStore } from "../../stores/use-notes-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeColors } from "@notesnook/theme";
import { useUserStore } from "../../stores/use-user-store";
import { MenuItemsList } from "../../utils/menu-items";
import { SUBSCRIPTION_STATUS } from "../../utils/constants";
import { MenuItemsList, SUBSCRIPTION_STATUS } from "../../utils/constants";
import { eOpenPremiumDialog } from "../../utils/events";
import { ColorSection } from "./color-section";
import { MenuItem } from "./menu-item";

View File

@@ -17,18 +17,20 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useState } from "react";
import { Dimensions, View } from "react-native";
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import ToggleSwitch from "toggle-switch-react-native";
import Navigation from "../../services/navigation";
import useNavigationStore from "../../stores/use-navigation-store";
import { SIZE, normalize } from "../../utils/size";
import { useThemeColors } from "@notesnook/theme";
import { normalize, SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import { PressableButton } from "../ui/pressable";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { useCallback } from "react";
import Tag from "../ui/tag";
export const MenuItem = React.memo(
function MenuItem({ item, index, testID, rightBtn }) {
@@ -38,7 +40,6 @@ export const MenuItem = React.memo(
);
const screenId = item.name.toLowerCase() + "_navigation";
let isFocused = headerTextState?.id === screenId;
const primaryColors = isFocused ? colors.selected : colors.primary;
const _onPress = () => {
if (item.func) {
@@ -109,7 +110,6 @@ export const MenuItem = React.memo(
textAlignVertical: "center",
textAlign: "left"
}}
allowFontScaling
name={item.icon}
color={
item.icon === "crown"
@@ -128,28 +128,14 @@ export const MenuItem = React.memo(
<Paragraph size={SIZE.md}>{item.name}</Paragraph>
)}
{item.isBeta ? (
<View
style={{
borderRadius: 100,
backgroundColor: primaryColors.accent,
paddingHorizontal: 4,
marginLeft: 5,
paddingVertical: 2
}}
>
<Paragraph color={primaryColors.accentForeground} size={SIZE.xxs}>
BETA
</Paragraph>
</View>
) : null}
<Tag visible={item.isBeta} text="BETA" />
</View>
{item.switch ? (
<ToggleSwitch
isOn={item.on}
onColor={primaryColors.accent}
offColor={primaryColors.icon}
onColor={colors.primary.accent}
offColor={colors.primary.icon}
size="small"
animationSpeed={150}
onToggle={_onPress}

View File

@@ -108,12 +108,9 @@ export const PinItem = React.memo(
alias = item?.alias || item?.title;
const [visible, setVisible] = useState(false);
const [headerTextState, setHeaderTextState] = useState(null);
const primaryColors =
headerTextState?.id === item.id ? colors.selected : colors.primary;
const color =
headerTextState?.id === item.id
? colors.selected.accent
? colors.primary.accent
: colors.primary.icon;
const fwdRef = useRef();
@@ -209,19 +206,13 @@ export const PinItem = React.memo(
justifyContent: "center"
}}
>
<Icon
allowFontScaling
color={color}
size={SIZE.lg - 2}
name={icons[item.type]}
/>
<Icon color={color} size={SIZE.lg - 2} name={icons[item.type]} />
<Icon
style={{
position: "absolute",
bottom: -6,
left: -6
}}
allowFontScaling
color={color}
size={SIZE.xs}
name="arrow-top-right-thick"
@@ -239,7 +230,7 @@ export const PinItem = React.memo(
style={{
flexWrap: "wrap"
}}
color={primaryColors.heading}
color={colors.primary.heading}
size={SIZE.md}
>
{alias}
@@ -247,7 +238,7 @@ export const PinItem = React.memo(
) : (
<Paragraph
numberOfLines={1}
color={primaryColors.paragraph}
color={colors.primary.paragraph}
size={SIZE.md}
>
{alias}

View File

@@ -113,7 +113,6 @@ export const UserStatus = () => {
<Icon
name="checkbox-blank-circle"
size={11}
allowFontScaling
color={
!user || lastSyncStatus === SyncStatus.Failed
? colors.error.icon
@@ -136,7 +135,7 @@ export const UserStatus = () => {
? "Last sync failed, tap to try again"
: syncing
? `Syncing your notes${
progress ? ` (${progress.current})` : ""
progress ? ` (${progress.current}/${progress.total})` : ""
}`
: "Tap here to sync your notes."}
</Paragraph>
@@ -150,15 +149,9 @@ export const UserStatus = () => {
color={colors.error.icon}
name="sync-alert"
size={SIZE.lg}
allowFontScaling
/>
) : (
<Icon
allowFontScaling
color={colors.primary.accent}
name="sync"
size={SIZE.lg}
/>
<Icon color={colors.primary.accent} name="sync" size={SIZE.lg} />
)
) : null}
</PressableButton>

View File

@@ -214,10 +214,6 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
return;
}
translateX.value = animated ? withTiming(homePosition) : homePosition;
if (!animated) {
translateX.value = 299;
translateX.value = 300;
}
onDrawerStateChange(false);
isDrawerOpen.value = false;
},

View File

@@ -77,9 +77,7 @@ export const Tip = ({
alignSelf: "flex-start",
borderRadius: 100,
borderWidth: 1,
borderColor:
colors.static[color as never] ||
(colors.primary[color as never] as string)
borderColor: colors.static[color as never] || colors.primary[color as never] as string
}}
/>
@@ -141,7 +139,7 @@ export const Tip = ({
icon={tip.button.icon}
buttonType={{
color: colors.static[color as never],
text: colors.primary.accentForeground
text: colors.static.white
}}
style={{
marginTop: 10

View File

@@ -134,7 +134,6 @@ export const Button = ({
{icon && !loading && iconPosition === "left" ? (
<Icon
name={icon}
allowFontScaling
style={[{ marginRight: 0 }, iconStyle as any]}
color={iconColor || buttonType?.text || textColor}
size={iconSize}
@@ -171,7 +170,6 @@ export const Button = ({
{icon && !loading && iconPosition === "right" ? (
<Icon
name={icon}
allowFontScaling
style={[{ marginLeft: 0 }, iconStyle as any]}
color={iconColor || buttonType?.text || textColor}
size={iconSize}

View File

@@ -40,6 +40,7 @@ interface IconButtonProps extends PressableButtonProps {
iconStyle?: TextStyle;
}
const AnimatedIcon = Animated.createAnimatedComponent(Icon);
export const IconButton = ({
onPress,
name,
@@ -87,10 +88,10 @@ export const IconButton = ({
...customStyle
}}
>
<Icon
<AnimatedIcon
layout={Layout}
name={name}
style={iconStyle as any}
allowFontScaling
color={
restProps.disabled
? RGB_Linear_Shade(-0.05, hexToRGBA(colors.secondary.background))

View File

@@ -108,7 +108,7 @@ export const useButton = ({
},
accent: {
primary: accent || colors.primary.accent,
text: text || colors.primary.accentForeground,
text: text || colors.primary.paragraph,
selected: accent || colors.primary.accent
},
inverted: {

View File

@@ -16,7 +16,7 @@ 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 { isReminderActive } from "@notesnook/core/dist/collections/reminders";
import { isReminderActive } from "@notesnook/core/collections/reminders";
import React from "react";
import { ViewStyle } from "react-native";

View File

@@ -17,17 +17,15 @@ 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 React, { useEffect, useRef } from "react";
import React from "react";
import { Platform, View } from "react-native";
import ActionSheet from "react-native-actions-sheet";
import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets";
import { useSettingStore } from "../../../stores/use-setting-store";
import { ScopedThemeProvider, useThemeColors } from "@notesnook/theme";
import { PremiumToast } from "../../premium/premium-toast";
import { Toast } from "../../toast";
import { useAppState } from "../../../hooks/use-app-state";
import SettingsService from "../../../services/settings";
import { useUserStore } from "../../../stores/use-user-store";
import { BouncingView } from "../transitions/bouncing-view";
const SheetWrapper = ({
children,
@@ -43,7 +41,6 @@ const SheetWrapper = ({
enableGesturesInScrollView = false,
bottomPadding = true
}) => {
const localRef = useRef(null);
const { colors } = useThemeColors("sheet");
const deviceMode = useSettingStore((state) => state.deviceMode);
const sheetKeyboardHandler = useSettingStore(
@@ -52,9 +49,9 @@ const SheetWrapper = ({
const largeTablet = deviceMode === "tablet";
const smallTablet = deviceMode === "smallTablet";
const dimensions = useSettingStore((state) => state.dimensions);
const pitchBlack = useSettingStore((state) => state.settings.pitchBlack);
const insets = useGlobalSafeAreaInsets();
const appState = useAppState();
const lockEvents = useRef(false);
console.log(colors.primary.background);
let width = dimensions.width > 600 ? 600 : 500;
const style = React.useMemo(() => {
@@ -73,40 +70,19 @@ const SheetWrapper = ({
}, [colors.primary.background, largeTablet, smallTablet, width]);
const _onOpen = () => {
if (lockEvents.current) return;
onOpen && onOpen();
};
const _onClose = async () => {
if (lockEvents.current) return;
if (onClose) {
onClose();
}
};
useEffect(() => {
if (SettingsService.get().appLockMode === "background") {
if (appState === "background") {
const ref = fwdRef || localRef;
ref?.current?.hide();
if (useUserStore.getState().appLocked) {
lockEvents.current = true;
const unsub = useUserStore.subscribe((state) => {
if (!state.appLocked) {
ref?.current?.show();
unsub();
lockEvents.current = false;
}
});
}
}
}
}, [appState, fwdRef]);
return (
<ScopedThemeProvider value="sheet">
<ActionSheet
ref={fwdRef || localRef}
ref={fwdRef}
testIDs={{
backdrop: "sheet-backdrop"
}}
@@ -128,7 +104,7 @@ const SheetWrapper = ({
keyboardDismissMode="none"
enableGesturesInScrollView={enableGesturesInScrollView}
defaultOverlayOpacity={overlayOpacity}
overlayColor={colors.primary.backdrop}
overlayColor={pitchBlack ? "#585858" : "#2b2b2b"}
keyboardShouldPersistTaps="always"
openAnimationConfig={{
friction: 9
@@ -146,17 +122,19 @@ const SheetWrapper = ({
}
onClose={_onClose}
>
{children}
{bottomPadding ? (
<View
style={{
height:
Platform.OS === "ios" && insets.bottom !== 0
? insets.bottom + 5
: 20
}}
/>
) : null}
<BouncingView>
{children}
{bottomPadding ? (
<View
style={{
height:
Platform.OS === "ios" && insets.bottom !== 0
? insets.bottom + 5
: 20
}}
/>
) : null}
</BouncingView>
</ActionSheet>
</ScopedThemeProvider>
);

View File

@@ -16,9 +16,9 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { ViewStyle } from "react-native";
import { View, ViewStyle } from "react-native";
import { useThemeColors } from "@notesnook/theme";
import { SIZE } from "../../../utils/size";
import Paragraph from "../typography/paragraph";
@@ -37,7 +37,7 @@ export default function Tag({
}) {
const { colors } = useThemeColors();
return !visible ? null : (
<Paragraph
<View
style={{
backgroundColor: background || colors.primary.accent,
borderRadius: 100,
@@ -46,14 +46,13 @@ export default function Tag({
marginLeft: 2,
marginTop: -10,
height: 20,
textAlignVertical: "center",
textAlign: "center",
justifyContent: "center",
...style
}}
color={textColor || colors.primary.accentForeground}
size={SIZE.xxs}
>
{text}
</Paragraph>
<Paragraph color={textColor || colors.static.white} size={SIZE.xxs}>
{text}
</Paragraph>
</View>
);
}

View File

@@ -59,6 +59,7 @@ const Heading = ({
<Component
layout={restProps.layout || Layout}
allowFontScaling={true}
maxFontSizeMultiplier={1}
{...restProps}
style={[
{

View File

@@ -45,6 +45,8 @@ const Paragraph = ({
return (
<Component
layout={restProps.layout || Layout}
allowFontScaling
maxFontSizeMultiplier={1}
{...restProps}
style={[
{

View File

@@ -70,8 +70,8 @@ const NotebookWelcome = () => {
count: 5
},
{
title: "Recipes",
description: "I love cooking and collecting recipes",
title: "Recipies",
description: "I love cooking and collecting recipies",
count: 10
}
]);
@@ -195,7 +195,7 @@ const notebooks: { id: string; steps: TStep[] } = {
>
<Paragraph size={SIZE.xs}>
<Icon color={colors.primary.icon} size={SIZE.sm} name="note" />{" "}
February 2022 Week 2
Feburary 2022 Week 2
</Paragraph>
</View>
<View
@@ -211,7 +211,7 @@ const notebooks: { id: string; steps: TStep[] } = {
>
<Paragraph size={SIZE.xs}>
<Icon color={colors.primary.icon} size={SIZE.sm} name="note" />{" "}
February 2022 Week 1
Feburary 2022 Week 1
</Paragraph>
</View>
<View
@@ -257,7 +257,7 @@ const notebooks: { id: string; steps: TStep[] } = {
title: "Tasks",
type: "topic"
}}
onPress={() => { }}
onPress={() => {}}
/>
<PinItem
@@ -267,7 +267,7 @@ const notebooks: { id: string; steps: TStep[] } = {
title: "Work and office",
type: "notebook"
}}
onPress={() => { }}
onPress={() => {}}
/>
</View>
),

Some files were not shown because too many files have changed in this diff Show More