Compare commits

..

116 Commits

Author SHA1 Message Date
Abdullah Atta
53f4e14ab7 Merge branch 'master' into theme-engine 2023-08-01 10:59:48 +05:00
ammarahm-ed
936274bd6d mobile: set sidemenu backdrop to theme backdrop 2023-07-31 13:21:37 +05:00
ammarahm-ed
c67b243049 mobile: set sheet overlay color to theme backdrop 2023-07-31 13:15:33 +05:00
ammarahm-ed
fcaefe9b55 mobile: fix border radius of context menu 2023-07-31 12:56:26 +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
733 changed files with 79920 additions and 78526 deletions

View File

@@ -33,8 +33,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

@@ -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:vericrypt
- name: Publish on Cloudflare Pages
run: npx --yes wrangler pages deploy --project-name=vericrypt ./apps/vericrypt/build/
run: npx --yes wrangler pages publish --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

@@ -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.5",
"version": "2.5.8",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/index.js",
@@ -15,22 +15,22 @@
"@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.8.1",
"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"

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

@@ -52,21 +52,21 @@ export const osIntegrationRouter = t.router({
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),
@@ -117,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

@@ -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 { 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";
@@ -33,13 +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";
// 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");
@@ -49,17 +42,15 @@ 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() {
@@ -100,7 +91,7 @@ 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();
@@ -146,11 +137,6 @@ app.once("window-all-closed", () => {
}
});
app.on("second-instance", () => {
if (!globalThis.window) return;
bringToFront();
});
app.on("activate", () => {
if (globalThis.window === null) {
createWindow();

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

@@ -38,8 +38,8 @@ export const config = {
closeToSystemTray: false
},
privacyMode: false,
isSpellCheckerEnabled: true,
zoomFactor: 1,
isSpellCheckerEnabled: false,
zoomFactor: 0,
theme: nativeTheme.themeSource,
automaticUpdates: true
};

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";

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 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";

View File

@@ -21,7 +21,7 @@ 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";

View File

@@ -66,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

@@ -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

@@ -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";
@@ -75,12 +71,9 @@ export const SessionExpired = () => {
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({

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

@@ -64,7 +64,6 @@ export const useLogin = (onFinishLogin) => {
const login = async () => {
if (!validateInfo() || error) return;
try {
if (loading) return;
setLoading(true);
switch (step) {
case LoginSteps.emailAuth: {

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

@@ -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,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

@@ -53,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}
@@ -98,12 +103,6 @@ const List = ({
(type === "notes" && notesListMode === "compact") ||
type === "notebooks" ||
notebooksListMode === "compact";
const groupType =
screen === "Notes" ? "home" : screen === "Favorites" ? "favorites" : type;
const groupOptions = db.settings?.getGroupOptions(groupType);
const dateBy =
groupOptions.sortBy !== "title" ? groupOptions.sortBy : "dateEdited";
const renderItem = React.useCallback(
({ item, index }) => (
@@ -112,7 +111,6 @@ const List = ({
index={index}
color={headerProps?.color}
title={headerProps?.heading}
dateBy={dateBy}
type={
screen === "Notes"
? "home"
@@ -124,7 +122,7 @@ const List = ({
isSheet={isSheet}
/>
),
[headerProps?.color, headerProps?.heading, screen, type, isSheet, dateBy]
[headerProps?.color, headerProps?.heading, screen, type, isSheet]
);
const _onRefresh = async () => {

View File

@@ -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

@@ -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

@@ -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",

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";

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

@@ -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,12 +247,7 @@ export default function ReminderSheet({
/>
</View>
<Dialog context="local" />
<ScrollView
bounces={false}
style={{
marginBottom: DDS.isTab ? 25 : undefined
}}
>
<ScrollView>
<Input
fwdRef={titleRef}
defaultValue={reminder?.title || referencedItem?.title}
@@ -606,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

@@ -54,7 +54,7 @@ import {
import { normalize, SIZE } from "../../../utils/size";
import { GroupHeader, NotebookType, TopicType } from "../../../utils/types";
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";
@@ -336,10 +336,23 @@ export const TopicsSheet = () => {
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}

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 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) {
@@ -127,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();
@@ -233,7 +230,7 @@ export const PinItem = React.memo(
style={{
flexWrap: "wrap"
}}
color={primaryColors.heading}
color={colors.primary.heading}
size={SIZE.md}
>
{alias}
@@ -241,7 +238,7 @@ export const PinItem = React.memo(
) : (
<Paragraph
numberOfLines={1}
color={primaryColors.paragraph}
color={colors.primary.paragraph}
size={SIZE.md}
>
{alias}

View File

@@ -135,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>

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

@@ -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

@@ -49,6 +49,7 @@ 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();
let width = dimensions.width > 600 ? 600 : 500;
@@ -120,17 +121,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

@@ -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 { EV, EVENTS, SYNC_CHECK_IDS } from "@notesnook/core/dist/common";
import { EV, EVENTS, SYNC_CHECK_IDS } from "@notesnook/core/common";
import notifee from "@notifee/react-native";
import NetInfo from "@react-native-community/netinfo";
import React, { useCallback, useEffect, useRef } from "react";
@@ -101,6 +101,13 @@ const onSyncAborted = () => {
useUserStore.getState().setSyncing(false, SyncStatus.Failed);
};
const onSyncProgress = ({ type, total, current }) => {
if (type !== "download") return;
if (total < 10 || current % 10 === 0) {
initAfterSync();
}
};
const onFileEncryptionProgress = ({ total, progress }) => {
useAttachmentStore
.getState()
@@ -160,15 +167,13 @@ const onUserSubscriptionStatusChanged = async (userStatus) => {
useMessageStore.getState().setAnnouncement();
};
const onRequestPartialSync = async (full, force, lastSyncTime) => {
const onRequestPartialSync = async (full, force) => {
if (SettingsService.get().disableAutoSync) return;
DatabaseLogger.info(
`onRequestPartialSync full:${full}, force:${force}, lastSyncTime:${lastSyncTime}`
);
DatabaseLogger.info(`onRequestPartialSync full:${full}, force:${force}`);
if (full || force) {
await Sync.run("global", force, full, undefined, lastSyncTime);
await Sync.run("global", force, full);
} else {
await Sync.run("global", false, false, undefined, lastSyncTime);
await Sync.run("global", false, false);
}
};
@@ -248,6 +253,7 @@ export const useAppEvents = () => {
]);
const syncedOnLaunch = useRef(false);
const { isDark } = useThemeColors();
const refValues = useRef({
subsriptionSuccessListener: null,
subsriptionErrorListener: null,
@@ -272,6 +278,7 @@ export const useAppEvents = () => {
const eventManager = db?.eventManager;
eventSubscriptions = [
eventManager?.subscribe(EVENTS.syncCompleted, onSyncComplete),
eventManager?.subscribe(EVENTS.syncProgress, onSyncProgress),
eventManager?.subscribe(
EVENTS.databaseSyncRequested,
onRequestPartialSync
@@ -526,12 +533,6 @@ export const useAppEvents = () => {
if (refValues.current?.isReconnecting || !refValues.current?.isUserReady)
return;
if (useSettingStore.getState().appDidEnterBackgroundForAction) {
useSettingStore.getState().setAppDidEnterBackgroundForAction(false);
console.log("AppDidEnterForegroundAfterAction");
return;
}
if (SettingsService.get().sessionExpired) {
refValues.current.isReconnecting = false;
return;
@@ -630,7 +631,6 @@ const doAppLoadActions = async () => {
await useMessageStore.getState().setAnnouncement();
if (NewFeature.present()) return;
if (await checkAppUpdateAvailable()) return;
if (!(await db.user.getUser())) {
setLoginMessage();
return;

View File

@@ -1,52 +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 { useRef } from "react";
import { MMKV } from "../common/database/mmkv";
export function useStoredRef<T>(
key: string,
initialValue: T
): {
current: T;
reset(): void;
} {
const refKey = `storedRef:${key}`;
const value = useRef(
MMKV.getMap<{ current: T }>(refKey)?.current || initialValue
);
const frameRef = useRef(0);
return {
get current() {
return value.current;
},
set current(next: T) {
value.current = next;
cancelAnimationFrame(frameRef.current);
frameRef.current = requestAnimationFrame(() => {
MMKV.setMap(refKey, {
current: value.current
});
});
},
reset() {
MMKV.removeItem(refKey);
}
};
}

View File

@@ -1,49 +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 { useRef, useState } from "react";
import { MMKV } from "../common/database/mmkv";
export function useStoredValue<T>(
key: string,
initialValue: T
): { value: T; reset(): void } {
const refKey = `storedState:${key}`;
const [value, setValue] = useState<T>(
MMKV.getMap<{ value: T }>(refKey)?.value || initialValue
);
const frameRef = useRef(0);
return {
get value() {
return value;
},
set value(next: T) {
setValue(next);
cancelAnimationFrame(frameRef.current);
frameRef.current = requestAnimationFrame(() => {
MMKV.setMap(refKey, {
value: value
});
});
},
reset() {
MMKV.removeItem(refKey);
}
};
}

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 { useCallback, useEffect, useState } from "react";
import { db } from "../common/database";

View File

@@ -16,7 +16,7 @@
"html-to-text": "9.0.5",
"phone": "^3.1.14",
"qclone": "^1.2.0",
"react-native-actions-sheet": "0.9.0-alpha.23",
"react-native-actions-sheet": "0.9.0-alpha.21",
"react-native-check-version": "https://github.com/flexible-agency/react-native-check-version",
"react-native-drax": "^0.10.2",
"react-native-image-zoom-viewer": "^3.0.1",
@@ -30,8 +30,7 @@
"toggle-switch-react-native": "3.2.0",
"url": "^0.11.0",
"validator": "^13.5.2",
"zustand": "^3.6.0",
"katex": "0.16.2"
"zustand": "^3.6.0"
},
"sideEffects": false
}

View File

@@ -18,33 +18,35 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* eslint-disable @typescript-eslint/no-var-requires */
import { EV, EVENTS } from "@notesnook/core/dist/common";
import { EV, EVENTS } from "@notesnook/core/common";
import React, {
forwardRef,
useCallback,
useEffect,
useImperativeHandle,
useLayoutEffect,
useRef
useRef,
useState
} from "react";
import { Platform, ViewStyle } from "react-native";
import WebView from "react-native-webview";
import { ShouldStartLoadRequest } from "react-native-webview/lib/WebViewTypes";
import { notesnook } from "../../../e2e/test.ids";
import { db } from "../../common/database";
import { IconButton } from "../../components/ui/icon-button";
import useKeyboard from "../../hooks/use-keyboard";
import { eSubscribeEvent } from "../../services/event-manager";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import { useEditorStore } from "../../stores/use-editor-store";
import { getElevationStyle } from "../../utils/elevation";
import { openLinkInBrowser } from "../../utils/functions";
import { NoteType } from "../../utils/types";
import EditorOverlay from "./loading";
import { EDITOR_URI } from "./source";
import { EditorProps, useEditorType } from "./tiptap/types";
import { useEditor } from "./tiptap/use-editor";
import { useEditorEvents } from "./tiptap/use-editor-events";
import { editorController } from "./tiptap/utils";
import { useLayoutEffect } from "react";
import useKeyboard from "../../hooks/use-keyboard";
const style: ViewStyle = {
height: "100%",
@@ -125,26 +127,21 @@ const Editor = React.memo(
const onError = useCallback(() => {
renderKey.current =
renderKey.current === `editor-0` ? `editor-1` : `editor-0`;
editor.state.current.ready = false;
editor.setLoading(true);
// editor.state.current.ready = false;
//editor.setLoading(true);
}, [editor]);
useEffect(() => {
const sub = [
eSubscribeEvent("webview_reset", onError),
EV.subscribe(EVENTS.mediaAttachmentDownloaded, onMediaDownloaded)
];
eSubscribeEvent("webview_reset", onError);
EV.subscribe(EVENTS.mediaAttachmentDownloaded, onMediaDownloaded);
return () => {
sub.forEach((s) => s.unsubscribe());
eUnSubscribeEvent("webview_reset", onError);
EV.unsubscribe(EVENTS.mediaAttachmentDownloaded, onMediaDownloaded);
};
}, [onError, onMediaDownloaded]);
useLayoutEffect(() => {
setImmediate(() => {
onLoad && onLoad();
});
onLoad && onLoad();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [onLoad]);
@@ -196,8 +193,9 @@ const Editor = React.memo(
autoManageStatusBarEnabled={false}
onMessage={onMessage || undefined}
/>
<EditorOverlay editorId={editorId || ""} editor={editor} />
<ReadonlyButton editor={editor} />
{editorId === "shareEditor" ? null : (
<AppSection editor={editor} editorId={editorId} />
)}
</>
);
}
@@ -207,6 +205,32 @@ const Editor = React.memo(
export default Editor;
let EditorOverlay: React.ElementType;
let IconButton: React.ElementType;
const AppSection = ({
editor,
editorId
}: {
editor: useEditorType;
editorId: string;
}) => {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
EditorOverlay = require("./loading.js").default;
IconButton =
require("../../components/ui/icon-button/index.tsx").IconButton;
setLoaded(true);
}, []);
return loaded ? (
<>
{EditorOverlay ? (
<EditorOverlay editorId={editorId || ""} editor={editor} />
) : null}
{editorId === "" ? <ReadonlyButton editor={editor} /> : null}
</>
) : null;
};
const ReadonlyButton = ({ editor }: { editor: useEditorType }) => {
const readonly = useEditorStore((state) => state.readonly);
const keyboard = useKeyboard();
@@ -220,7 +244,7 @@ const ReadonlyButton = ({ editor }: { editor: useEditorType }) => {
}
};
return readonly && !keyboard.keyboardShown ? (
return readonly && IconButton && !keyboard.keyboardShown ? (
<IconButton
name="pencil-lock"
type="grayBg"

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 { isImage } from "@notesnook/core/dist/utils/filename";
import { isImage } from "@notesnook/core/utils/filename";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import DocumentPicker from "react-native-document-picker";
@@ -34,7 +34,6 @@ import PremiumService from "../../../services/premium";
import { FILE_SIZE_LIMIT, IMAGE_SIZE_LIMIT } from "../../../utils/constants";
import { eCloseSheet } from "../../../utils/events";
import { editorController, editorState } from "./utils";
import { useSettingStore } from "../../../stores/use-setting-store";
const showEncryptionSheet = (file) => {
presentSheet({
@@ -63,7 +62,6 @@ const file = async (fileOptions) => {
let file;
try {
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
file = await DocumentPicker.pick(options);
} catch (e) {
return;
@@ -137,7 +135,6 @@ const file = async (fileOptions) => {
const camera = async (options) => {
try {
await db.attachments.generateKey();
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
launchCamera(
{
includeBase64: true,
@@ -159,7 +156,6 @@ const camera = async (options) => {
const gallery = async (options) => {
try {
await db.attachments.generateKey();
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
launchImageLibrary(
{
includeBase64: true,

View File

@@ -48,7 +48,7 @@ export type Settings = {
keyboardShown?: boolean;
doubleSpacedLines?: boolean;
corsProxy: string;
fontSize: number;
fontSize: string;
fontFamily: string;
dateFormat: string;
timeFormat: string;

View File

@@ -189,10 +189,7 @@ export const useEditorEvents = (
noToolbar: readonly || editorPropReadonly || noToolbar,
doubleSpacedLines: doubleSpacedLines,
corsProxy: corsProxy,
fontSize:
typeof defaultFontSize === "string"
? parseInt(defaultFontSize)
: defaultFontSize,
fontSize: defaultFontSize,
fontFamily: SettingsService.get().defaultFontFamily,
dateFormat: db.settings?.getDateFormat(),
timeFormat: db.settings?.getTimeFormat()

View File

@@ -17,8 +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 { getFormattedDate } from "@notesnook/common";
import { EVENTS } from "@notesnook/core/dist/common";
import { EVENTS } from "@notesnook/core/common";
import { useThemeEngineStore } from "@notesnook/theme";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import WebView from "react-native-webview";
@@ -32,7 +31,6 @@ import {
openVault
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import Notifications from "../../../services/notifications";
import SettingsService from "../../../services/settings";
import { TipManager } from "../../../services/tip-manager";
import { useEditorStore } from "../../../stores/use-editor-store";
@@ -54,6 +52,7 @@ import {
makeSessionId,
post
} from "./utils";
import { getFormattedDate } from "@notesnook/common";
export const useEditor = (
editorId = "",
@@ -81,7 +80,6 @@ export const useEditor = (
const lock = useRef(false);
const loadedImages = useRef<{ [name: string]: boolean }>({});
const lockedSessionId = useRef<string>();
const loadingState = useRef<string>();
const postMessage = useCallback(
async <T>(type: string, data: T, waitFor = 300) =>
@@ -119,8 +117,9 @@ export const useEditor = (
useEffect(() => {
if (loading) {
state.current.ready = false;
setLoading(false);
} else {
state.current.ready = false;
}
}, [loading]);
@@ -134,14 +133,13 @@ export const useEditor = (
const reset = useCallback(
async (resetState = true, resetContent = true) => {
currentNote.current?.id && db.fs?.cancel(currentNote.current.id, null);
currentNote.current?.id && db.fs?.cancel(currentNote.current.id);
currentNote.current = null;
loadedImages.current = {};
currentContent.current = null;
clearTimeout(timers.current["loading-images"]);
sessionHistoryId.current = undefined;
saveCount.current = 0;
lock.current = false;
useEditorStore.getState().setReadonly(false);
resetContent && postMessage(EditorEvents.title, "");
lastContentChangeTime.current = 0;
@@ -236,10 +234,6 @@ export const useEditor = (
id && useEditorStore.getState().setCurrentlyEditingNote(id);
});
}
if (Notifications.isNotePinned(id as string)) {
Notifications.pinNote(id as string);
}
} else {
noteData.contentId = note?.contentId;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -369,9 +363,6 @@ export const useEditor = (
isDefaultEditor && editorState.setCurrentlyEditingNote(item.id);
currentNote.current && (await reset(false, false));
await loadContent(item as NoteType);
if (loadingState.current === currentContent.current?.data) {
return;
}
if (
!currentContent.current?.data ||
currentContent.current?.data.length < 50000
@@ -394,13 +385,11 @@ export const useEditor = (
currentNote.current = item as NoteType;
await commands.setStatus(getFormattedDate(item.dateEdited), "Saved");
await postMessage(EditorEvents.title, item.title);
loadingState.current = currentContent.current?.data;
await postMessage(
EditorEvents.html,
currentContent.current?.data,
10000
);
loadingState.current = undefined;
useEditorStore.getState().setReadonly(item.readonly);
await commands.setTags(currentNote.current);
commands.setSettings();
@@ -612,11 +601,9 @@ export const useEditor = (
const onReady = useCallback(async () => {
if (!(await isEditorLoaded(editorRef, sessionIdRef.current))) {
eSendEvent("webview_reset", "onReady");
return false;
eSendEvent("webview_reset");
} else {
isDefaultEditor && restoreEditorState();
return true;
}
}, [isDefaultEditor, restoreEditorState]);
@@ -629,18 +616,15 @@ export const useEditor = (
isDefaultEditor ? insets : { top: 0, left: 0, right: 0, bottom: 0 }
);
await commands.setSessionId(sessionIdRef.current);
await onReady();
state.current.ready = true;
await commands.setSettings();
timers.current["editor:loaded"] = setTimeout(async () => {
if (!state.current.ready && (await onReady())) {
state.current.ready = true;
}
if (currentNote.current) {
loadNote({ ...currentNote.current, forced: true });
} else {
await commands.setPlaceholder(placeholderTip.current);
}
});
});
if (currentNote.current) {
loadNote({ ...currentNote.current, forced: true });
} else {
await commands.setPlaceholder(placeholderTip.current);
}
}, 1000);
}, [
onReady,
postMessage,

View File

@@ -55,7 +55,6 @@ export const EditorWrapper = ({ width }) => {
prevState.current = state;
return;
}
if (useSettingStore.getState().appDidEnterBackgroundForAction) return;
if (state === "active") {
editorController.current.onReady();
editorController.current.overlay(false);

View File

@@ -29,7 +29,6 @@ import SettingsService from "../../services/settings";
import useNavigationStore from "../../stores/use-navigation-store";
import { useNoteStore } from "../../stores/use-notes-store";
import { openEditor } from "../notes/common";
const prepareSearch = () => {
SearchService.update({
placeholder: "Type a keyword to search in notes",

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 { groupArray } from "@notesnook/core/dist/utils/grouping";
import { groupArray } from "@notesnook/core/utils/grouping";
import React, { useEffect, useRef, useState } from "react";
import { db } from "../../common/database";
import DelayLayout from "../../components/delay-layout";

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 { groupArray } from "@notesnook/core/dist/utils/grouping";
import { groupArray } from "@notesnook/core/utils/grouping";
import React from "react";
import NotesPage, { PLACEHOLDER_DATA } from ".";
import { db } from "../../common/database";

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 { groupArray } from "@notesnook/core/dist/utils/grouping";
import { groupArray } from "@notesnook/core/utils/grouping";
import React from "react";
import NotesPage, { PLACEHOLDER_DATA } from ".";
import { db } from "../../common/database";

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 { groupArray } from "@notesnook/core/dist/utils/grouping";
import { groupArray } from "@notesnook/core/utils/grouping";
import React from "react";
import NotesPage, { PLACEHOLDER_DATA } from ".";
import { db } from "../../common/database";

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 { groupArray } from "@notesnook/core/dist/utils/grouping";
import { groupArray } from "@notesnook/core/utils/grouping";
import React from "react";
import NotesPage, { PLACEHOLDER_DATA } from ".";
import { db } from "../../common/database";

View File

@@ -66,7 +66,6 @@ export const Reminders = ({
});
SearchService.prepareSearch = prepareSearch;
useNavigationStore.getState().setButtonAction(PLACEHOLDER_DATA.action);
return !prev?.current;
},
onBlur: () => false,

View File

@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import Clipboard from "@react-native-clipboard/clipboard";
import { LogMessage } from "@notesnook/logger";
import { format, LogLevel, logManager } from "@notesnook/core/dist/logger";
import { format, LogLevel, logManager } from "@notesnook/core/logger";
import React, { useEffect, useRef, useState } from "react";
import { FlatList, Platform, TouchableOpacity, View } from "react-native";
import * as ScopedStorage from "react-native-scoped-storage";

View File

@@ -42,8 +42,7 @@ export async function verifyUser(
negativeText: closeText || "Cancel",
positivePress: async (value) => {
try {
const user = await db.user.getUser();
let verified = !user ? true : await db.user.verifyPassword(value);
let verified = await db.user.verifyPassword(value);
if (verified) {
sleep(300).then(async () => {
await onsuccess();
@@ -59,7 +58,7 @@ export async function verifyUser(
}
} catch (e) {
ToastEvent.show({
heading: "Failed to verify",
heading: "Failed to backup data",
message: e.message,
type: "error",
context: "global"

View File

@@ -87,6 +87,12 @@ const Home = ({
alignItems: "center"
}}
>
<Heading color={colors.primary.heading} size={SIZE.lg}>
Logging out
</Heading>
<Paragraph color={colors.secondary.icon}>
Please wait while we log out and clear app data.
</Paragraph>
<View
style={{
width: "100%",

View File

@@ -27,8 +27,6 @@ import PremiumService from "../../../services/premium";
import { useThemeColors } from "@notesnook/theme";
import { SIZE } from "../../../utils/size";
import { sleep } from "../../../utils/time";
import { verifyUser } from "../functions";
import { Dialog } from "../../../components/dialog";
interface PickerOptions<T> {
getValue: () => T;
@@ -39,7 +37,6 @@ interface PickerOptions<T> {
options: T[];
premium?: boolean;
onCheckOptionIsPremium?: (item: T) => boolean;
requiresVerification?: () => boolean;
}
export function SettingsPicker<T>({
@@ -50,8 +47,7 @@ export function SettingsPicker<T>({
options,
getItemKey,
premium,
onCheckOptionIsPremium = () => true,
requiresVerification = () => false
onCheckOptionIsPremium = () => true
}: PickerOptions<T>) {
const { colors } = useThemeColors("contextMenu");
const menuRef = useRef<any>();
@@ -122,18 +118,11 @@ export function SettingsPicker<T>({
</PressableButton>
}
>
<Dialog context="local" />
{options.map((item) => (
<MenuItem
key={getItemKey(item)}
onPress={async () => {
if (requiresVerification?.()) {
verifyUser("local", () => {
onChange(item);
});
} else {
onChange(item);
}
onChange(item);
}}
style={{
backgroundColor: compareValue(currentValue, item)

View File

@@ -21,12 +21,11 @@ import { db } from "../../../common/database";
import { ToastEvent } from "../../../services/event-manager";
import SettingsService from "../../../services/settings";
import { useSettingStore } from "../../../stores/use-setting-store";
import { MenuItemsList } from "../../../utils/menu-items";
import { MenuItemsList } from "../../../utils/constants";
import { createSettingsPicker } from ".";
import { getFontById, getFonts } from "@notesnook/editor/dist/utils/font";
import { DATE_FORMATS, TIME_FORMATS } from "@notesnook/core/dist/common";
import { DATE_FORMATS, TIME_FORMATS } from "@notesnook/core/common";
import dayjs from "dayjs";
import { useUserStore } from "../../../stores/use-user-store";
export const FontPicker = createSettingsPicker({
getValue: () => useSettingStore.getState().settings.defaultFontFamily,
@@ -128,12 +127,6 @@ export const BackupReminderPicker = createSettingsPicker({
options: ["useroff", "daily", "weekly", "monthly"],
compareValue: (current, item) => current === item,
premium: true,
requiresVerification: () => {
return (
!useSettingStore.getState().settings.encryptedBackup &&
useUserStore.getState().user
);
},
onCheckOptionIsPremium: (item) => {
return item !== "useroff";
}

View File

@@ -17,6 +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 { ThemeDark, ThemePitchBlack } from "@notesnook/theme";
import notifee from "@notifee/react-native";
import dayjs from "dayjs";
import React from "react";
@@ -287,9 +288,9 @@ export const settingsGroups: SettingSection[] = [
positiveText: "Logout",
positivePress: async () => {
try {
eSendEvent(eCloseSimpleDialog);
setTimeout(async () => {
eSendEvent("settings-loading", true);
eSendEvent("settings-loading", true);
setImmediate(async () => {
eSendEvent(eCloseSimpleDialog);
Navigation.popToTop();
await db.user?.logout();
setLoginMessage();
@@ -305,7 +306,7 @@ export const settingsGroups: SettingSection[] = [
setTimeout(() => {
eSendEvent("settings-loading", false);
}, 2000);
}, 300);
});
} catch (e) {
ToastEvent.error(e as Error, "Error logging out");
eSendEvent("settings-loading", false);
@@ -782,11 +783,10 @@ export const settingsGroups: SettingSection[] = [
description: "Create a backup of your data",
modifer: async () => {
const user = useUserStore.getState().user;
if (!user || SettingsService.getProperty("encryptedBackup")) {
if (!user) {
await BackupService.run(true);
return;
}
verifyUser(null, () => BackupService.run(true));
}
},
@@ -870,17 +870,9 @@ export const settingsGroups: SettingSection[] = [
});
return;
}
if (settings.encryptedBackup) {
await verifyUser(null, () => {
SettingsService.set({
encryptedBackup: false
});
});
} else {
SettingsService.set({
encryptedBackup: true
});
}
SettingsService.set({
encryptedBackup: !settings.encryptedBackup
});
}
}
]
@@ -910,9 +902,7 @@ export const settingsGroups: SettingSection[] = [
description:
"Export all notes as pdf, markdown, html or text in a single zip file",
modifer: () => {
verifyUser(null, () => {
ExportNotesSheet.present(undefined, true);
});
ExportNotesSheet.present(undefined, true);
}
}
]
@@ -982,7 +972,7 @@ export const settingsGroups: SettingSection[] = [
}
},
{
id: "reminder-sound-ios",
id: "reminder-sound",
type: "screen",
name: "Change notification sound",
description:
@@ -994,7 +984,7 @@ export const settingsGroups: SettingSection[] = [
(Platform.OS === "android" && Platform.Version > 25)
},
{
id: "reminder-sound-android",
id: "reminder-sound",
name: "Change notification sound",
description:
"Set the notification sound for reminder notifications",

View File

@@ -51,11 +51,11 @@ import { ToastEvent, presentSheet } from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import { getElevationStyle } from "../../utils/elevation";
import { MenuItemsList } from "../../utils/menu-items";
import { MenuItemsList } from "../../utils/constants";
import { IconButton } from "../../components/ui/icon-button";
import { PressableButton } from "../../components/ui/pressable";
const THEME_SERVER_URL = "https://themes-api.notesnook.com";
const THEME_SERVER_URL = "https://themes.notesnook.com";
//@ts-ignore
export const themeTrpcClient = createTRPCProxyClient<ThemesRouter>({
links: [
@@ -103,12 +103,10 @@ function ThemeSelector() {
}
);
const select = (item: Partial<ThemeMetadata>, fromFile?: boolean) => {
const select = (item: Partial<ThemeMetadata>) => {
presentSheet({
context: "theme-details",
component: (ref, close) => (
<ThemeSetter close={close} theme={item} fromFile={fromFile} />
)
component: (ref, close) => <ThemeSetter close={close} theme={item} />
});
};
@@ -329,6 +327,7 @@ function ThemeSelector() {
) || []
);
}
return (
<>
<SheetProvider context="theme-details" />
@@ -398,17 +397,16 @@ function ThemeSelector() {
icon="folder"
fontSize={SIZE.xs}
onPress={() => {
DocumentPicker.pickSingle({
allowMultiSelection: false
}).then((r) => {
DocumentPicker.pickSingle().then((r) => {
fetch(r.uri).then(async (response) => {
const json = await response.json();
console.log(json);
const result = validateTheme(json);
if (result.error) {
ToastEvent.error(new Error(result.error));
return;
}
select(json, true);
select(json);
});
});
}}
@@ -447,22 +445,6 @@ function ThemeSelector() {
)}
</View>
}
ListFooterComponent={
themes.isError ? (
<View
style={{
height: 100,
width: "100%",
justifyContent: "center",
alignItems: "center"
}}
>
<Paragraph color={colors.error.paragraph}>
Error loading themes. {themes.error.message}.
</Paragraph>
</View>
) : null
}
estimatedItemSize={200}
renderItem={renderItem}
onEndReachedThreshold={0.1}
@@ -501,12 +483,10 @@ export default function ThemeSelectorWithQueryClient() {
const ThemeSetter = ({
theme,
close,
fromFile
close
}: {
theme: Partial<CompiledThemeDefinition>;
close?: (ctx?: string) => void;
fromFile?: boolean;
}) => {
const [darkTheme, lightTheme] = useThemeStore((state) => [
state.darkTheme,
@@ -522,13 +502,14 @@ const ThemeSetter = ({
if (!theme.id) return;
try {
const user = await db.user?.getUser();
const fullTheme = fromFile
const fullTheme = theme.scopes
? (theme as ThemeDefinition)
: await themeTrpcClient.installTheme.query({
compatibilityVersion: THEME_COMPATIBILITY_VERSION,
id: theme.id,
userId: user?.id
});
if (!fullTheme) return;
theme.colorScheme === "dark"
? useThemeStore.getState().setDarkTheme(fullTheme)
@@ -748,7 +729,7 @@ const ThemeSetter = ({
paddingVertical: 12
}}
>
<Heading color={colors.accentForeground} size={SIZE.md}>
<Heading color={colors.accent} size={SIZE.md}>
{darkTheme.id === theme.id
? "Applied as dark theme"
: "Applied as light theme"}

View File

@@ -114,7 +114,7 @@ export const Trash = ({ navigation, route }: NavigationProps<"Trash">) => {
<FloatingButton
title="Clear all trash"
onPress={onPressFloatingButton}
alwaysVisible={true}
shouldShow={true}
/>
) : null}
</DelayLayout>

View File

@@ -17,6 +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 { sanitizeFilename } from "@notesnook/common";
import { Platform } from "react-native";
import RNFetchBlob from "react-native-blob-util";
import FileViewer from "react-native-file-viewer";
@@ -29,8 +30,6 @@ import { eCloseSheet } from "../utils/events";
import { sleep } from "../utils/time";
import { ToastEvent, eSendEvent, presentSheet } from "./event-manager";
import SettingsService from "./settings";
import { cacheDir, copyFileAsync } from "../common/filesystem/utils";
import { zip } from "react-native-zip-archive";
const MS_DAY = 86400000;
const MS_WEEK = MS_DAY * 7;
@@ -153,6 +152,8 @@ async function run(progress, context) {
let androidBackupDirectory = await checkBackupDirExists(false, context);
if (!androidBackupDirectory) return;
let backup;
if (progress) {
presentSheet({
title: "Backing up your data",
@@ -162,67 +163,43 @@ async function run(progress, context) {
});
}
let path;
let backupFilePath;
let backupFileName = "notesnook_backup_" + Date.now();
if (Platform.OS === "ios") {
path = await storage.checkAndCreateDir("/backups/");
}
const zipSourceFolder = `${cacheDir}/${backupFileName}`;
const zipOutputFile =
Platform.OS === "ios"
? `${path}/${backupFileName}.nnbackupz`
: `${cacheDir}/${backupFileName}.nnbackupz`;
if (await RNFetchBlob.fs.exists(zipSourceFolder))
await RNFetchBlob.fs.unlink(zipSourceFolder);
await RNFetchBlob.fs.mkdir(zipSourceFolder);
try {
for await (const file of db.backup.export(
backup = await db.backup.export(
"mobile",
SettingsService.get().encryptedBackup
)) {
console.log("Writing backup chunk of size...", file?.data?.length);
await RNFetchBlob.fs.writeFile(
`${zipSourceFolder}/${file.path}`,
file.data,
"utf8"
);
}
await zip(zipSourceFolder, zipOutputFile);
console.log("Final zip:", await RNFetchBlob.fs.stat(zipOutputFile));
if (Platform.OS === "android") {
// Move the zip to user selected directory.
const file = await ScopedStorage.createFile(
androidBackupDirectory.uri,
`${backupFileName}.nnbackupz`,
"application/nnbackupz"
);
await copyFileAsync(`file://${zipOutputFile}`, file.uri);
path = file.uri;
} else {
path = zipOutputFile;
}
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
updateNextBackupTime();
let showBackupCompleteSheet = SettingsService.get().showBackupCompleteSheet;
if (context) return path;
);
if (!backup) throw new Error("Backup returned empty.");
} catch (e) {
await sleep(300);
if (showBackupCompleteSheet) {
presentBackupCompleteSheet(backupFilePath);
eSendEvent(eCloseSheet);
ToastEvent.error(e, "Backup failed!");
return null;
}
try {
let backupName = "notesnook_backup_" + Date.now();
backupName =
sanitizeFilename(backupName, { replacement: "_" }) + ".nnbackup";
let path;
let backupFilePath;
if (Platform.OS === "ios") {
path = await storage.checkAndCreateDir("/backups/");
await RNFetchBlob.fs.writeFile(path + backupName, backup, "utf8");
backupFilePath = path + backupName;
} else {
progress && eSendEvent(eCloseSheet);
backupFilePath = await ScopedStorage.writeFile(
androidBackupDirectory.uri,
backup,
backupName,
"nnbackup/json",
"utf8",
false
);
}
updateNextBackupTime();
ToastEvent.show({
heading: "Backup successful",
message: "Your backup is stored in Notesnook folder on your phone.",
@@ -230,10 +207,18 @@ async function run(progress, context) {
context: "global"
});
return path;
} catch (e) {
let showBackupCompleteSheet = SettingsService.get().showBackupCompleteSheet;
if (context) return backupFilePath;
await sleep(300);
eSendEvent(eCloseSheet);
if (showBackupCompleteSheet) {
presentBackupCompleteSheet(backupFilePath);
} else {
progress && eSendEvent(eCloseSheet);
}
return backupFilePath;
} catch (e) {
progress && eSendEvent(eCloseSheet);
ToastEvent.error(e, "Backup failed!");
return null;
}

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 EventManager from "@notesnook/core/dist/utils/event-manager";
import EventManager from "@notesnook/core/utils/event-manager";
import Clipboard from "@react-native-clipboard/clipboard";
import { RefObject } from "react";
import { ActionSheetRef } from "react-native-actions-sheet";

View File

@@ -46,8 +46,6 @@ import { useRelationStore } from "../stores/use-relation-store";
import { useReminderStore } from "../stores/use-reminder-store";
import { presentDialog } from "../components/dialog/functions";
import NetInfo from "@react-native-community/netinfo";
import { encodeNonAsciiHTML } from "entities";
import { convertNoteToText } from "../utils/note-to-text";
export type Reminder = {
id: string;
@@ -88,28 +86,6 @@ async function getNextMonthlyReminderDate(
return await getNextMonthlyReminderDate(reminder, dayjs().year() + 1);
}
export function textToHTML(src: string) {
return src
.split(/[\r\n]/)
.map((line) =>
line
? `<p data-spacing="single">${encodeLine(line)}</p>`
: `<p data-spacing="single"></p>`
)
.join("");
}
function encodeLine(line: string) {
line = encodeNonAsciiHTML(line);
line = line.replace(/(^ +)|( {2,})/g, (sub, ...args) => {
const [starting, inline] = args;
if (starting) return "&nbsp;".repeat(starting.length);
if (inline) return "&nbsp;".repeat(inline.length);
return sub;
});
return line;
}
async function initDatabase(notes = true) {
if (!db.isInitialized) {
await db.initCollections();
@@ -141,7 +117,6 @@ const onEvent = async ({ type, detail }: Event) => {
const ReminderNotify =
require("../components/sheets/reminder-notify").default;
ReminderNotify.present(reminder);
useRelationStore.getState().update();
return;
}
editorState().movedAway = false;
@@ -231,41 +206,16 @@ const onEvent = async ({ type, detail }: Event) => {
});
if (!db.isInitialized) await db.init();
await db.notes?.init();
const id = await db.notes?.add({
await db.notes?.add({
content: {
type: "tiptap",
data: textToHTML(input as string)
data: `<p>${input} </p>`
}
});
const defaultNotebook = db.settings?.getDefaultNotebook();
if (defaultNotebook) {
if (!defaultNotebook.topic) {
await db.relations?.add(
{ type: "notebook", id: defaultNotebook.id },
{ type: "note", id: id }
);
} else {
await db.notes?.addToNotebook(
{
topic: defaultNotebook.topic,
id: defaultNotebook?.id
},
id
);
}
}
const status = await NetInfo.fetch();
if (status.isInternetReachable) {
try {
if (!globalThis["IS_MAIN_APP_RUNNING" as never]) {
await db.sync(false, false);
} else {
console.log("main app running, skipping sync");
}
await db.sync(false, false);
} catch (e) {
console.log(e, (e as Error).stack);
}
@@ -807,11 +757,6 @@ function getPinnedNotes(): DisplayedNotification[] {
return pinned;
}
function isNotePinned(id: string) {
if (Platform.OS !== "android") return false;
return pinned.findIndex((notification) => notification.id === id) > -1;
}
function get(): Promise<DisplayedNotification[]> {
return new Promise((resolve) => {
if (Platform.OS === "ios") resolve([]);
@@ -899,26 +844,6 @@ async function setupReminders(checkNeedsScheduling = false) {
);
}
async function pinNote(id: string) {
try {
const note = db.notes?.note(id as string) as any;
let text = await convertNoteToText(note as any, false);
if (!text) text = "";
let html = text.replace(/\n/g, "<br />");
Notifications.displayNotification({
title: note.title,
message: note.headline || text,
subtitle: note.headline || text,
bigText: html,
ongoing: true,
actions: ["UNPIN"],
id: note.id
});
} catch (e) {
console.log(e);
}
}
const Notifications = {
init,
displayNotification,
@@ -934,9 +859,7 @@ const Notifications = {
checkAndRequestPermissions,
clearAllTriggers,
setupReminders,
getChannelId,
isNotePinned,
pinNote
getChannelId
};
export default Notifications;

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 { CHECK_IDS } from "@notesnook/core/dist/common";
import { CHECK_IDS } from "@notesnook/core/common";
import React from "react";
import { Platform } from "react-native";
import Config from "react-native-config";

View File

@@ -166,5 +166,4 @@ export const SettingsService = {
resetSettings
};
init();
export default SettingsService;

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 NetInfo from "@react-native-community/netinfo";
import { db } from "../common/database";
import { DatabaseLogger } from "../common/database/index";
@@ -39,14 +39,8 @@ const run = async (
context = "global",
forced = false,
full = true,
onCompleted,
lastSyncTime
onCompleted
) => {
if (useUserStore.getState().syncing) {
DatabaseLogger.log("Sync in progress");
console.log("Sync in progress");
return;
}
clearTimeout(syncTimer);
syncTimer = setTimeout(async () => {
const status = await NetInfo.fetch();
@@ -69,7 +63,7 @@ const run = async (
try {
let res = await BackgroundSync.doInBackground(async () => {
try {
await db.sync(full, forced, lastSyncTime);
await db.sync(full, forced);
return true;
} catch (e) {
error = e;

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 { groupArray } from "@notesnook/core/dist/utils/grouping";
import { groupArray } from "@notesnook/core/utils/grouping";
import create, { State } from "zustand";
import { db } from "../common/database";
import { NoteType } from "../utils/types";

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