mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-29 18:19:38 +02:00
Compare commits
216 Commits
fix-input-
...
fix-server
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9470d2b61f | ||
|
|
5e05d96487 | ||
|
|
18c9912068 | ||
|
|
bdb135036c | ||
|
|
5d70f58a0d | ||
|
|
b7d2d66e82 | ||
|
|
6b028ef850 | ||
|
|
8f85795e53 | ||
|
|
a0b7dff5fd | ||
|
|
352018c042 | ||
|
|
fd48684396 | ||
|
|
9b25ab8708 | ||
|
|
c725ba8530 | ||
|
|
98287fbfc4 | ||
|
|
9f24bd2250 | ||
|
|
94f2a216ad | ||
|
|
78e3ee5fd2 | ||
|
|
1ddcf44eb0 | ||
|
|
5c5fb2ba2c | ||
|
|
aa4995d871 | ||
|
|
f818c41a02 | ||
|
|
3f9d954e2f | ||
|
|
f1fcd227e7 | ||
|
|
44d910121c | ||
|
|
1258a95aad | ||
|
|
1379193065 | ||
|
|
a7fa42e944 | ||
|
|
02ab8e3938 | ||
|
|
5c120676e1 | ||
|
|
3821dc29ef | ||
|
|
3c51f73427 | ||
|
|
8db373ee34 | ||
|
|
adcb73a2fc | ||
|
|
4c97b3481d | ||
|
|
4785544e18 | ||
|
|
7812c15b48 | ||
|
|
b841c6e0eb | ||
|
|
55b6be0f15 | ||
|
|
dfe6a73ac1 | ||
|
|
afcb8835d2 | ||
|
|
9dc9f186e1 | ||
|
|
875125cfe4 | ||
|
|
32d9cdd265 | ||
|
|
09deae9dbf | ||
|
|
337582ac70 | ||
|
|
820c78ce81 | ||
|
|
565e692af8 | ||
|
|
94e4721663 | ||
|
|
8157c0812a | ||
|
|
bc36837cd0 | ||
|
|
33540b000c | ||
|
|
5a63a45d00 | ||
|
|
8fce6b5eb6 | ||
|
|
4e44661e1b | ||
|
|
81fd1c097e | ||
|
|
aaf82f359b | ||
|
|
aa7f14ae79 | ||
|
|
0ad63da4a2 | ||
|
|
2885b9ac3e | ||
|
|
a15ab99056 | ||
|
|
222cdbfa63 | ||
|
|
a1d8b34fb4 | ||
|
|
874352e753 | ||
|
|
49d1ab4be5 | ||
|
|
65a80dbd46 | ||
|
|
e55456f492 | ||
|
|
3f9fce0d34 | ||
|
|
2ae094cf1f | ||
|
|
bc224f1674 | ||
|
|
8bb1582cca | ||
|
|
d02125a93c | ||
|
|
d91ce4e936 | ||
|
|
deb902549b | ||
|
|
e41ed07805 | ||
|
|
f6e8dc0a88 | ||
|
|
044dd5ce59 | ||
|
|
b24830666c | ||
|
|
470802c3e2 | ||
|
|
7e0e1eac93 | ||
|
|
58cbf653b2 | ||
|
|
4b35f2ecd2 | ||
|
|
48d9a863ec | ||
|
|
1d616cf68f | ||
|
|
cdf6a4bb5b | ||
|
|
1b3735e2b2 | ||
|
|
850fc6b066 | ||
|
|
8a62070c8e | ||
|
|
8aa31b8380 | ||
|
|
f445a963f9 | ||
|
|
d6e3f27612 | ||
|
|
4688d74edb | ||
|
|
1985007039 | ||
|
|
039c8faabe | ||
|
|
fb1eb234b6 | ||
|
|
7e70cd96b9 | ||
|
|
f9a89783ed | ||
|
|
7f3c74b55b | ||
|
|
301ac781d6 | ||
|
|
ce3c98ae9b | ||
|
|
2447b3bea7 | ||
|
|
63888ff70e | ||
|
|
c6e3d1bd47 | ||
|
|
e03e02d702 | ||
|
|
1abf51ae2b | ||
|
|
c41dfa3185 | ||
|
|
52a07da230 | ||
|
|
f572bb1637 | ||
|
|
773ede4b6c | ||
|
|
e4d42c8bac | ||
|
|
5b85067e15 | ||
|
|
0d111f3a79 | ||
|
|
f6b6f2b4b7 | ||
|
|
d55a2aa04d | ||
|
|
0185eea740 | ||
|
|
d44e8874b8 | ||
|
|
e34d33dd61 | ||
|
|
4b2fb80c23 | ||
|
|
3c908ac079 | ||
|
|
563f068948 | ||
|
|
1344b958cb | ||
|
|
32d7cab70c | ||
|
|
1293fa2dec | ||
|
|
8542cbf37e | ||
|
|
62c6fba9a0 | ||
|
|
2d84154226 | ||
|
|
c4e306d569 | ||
|
|
e86f7491ef | ||
|
|
5151d83af9 | ||
|
|
e462cb0596 | ||
|
|
a30c0e4608 | ||
|
|
f90e7982f7 | ||
|
|
40c69d7cba | ||
|
|
23ee50f688 | ||
|
|
a7c857fb71 | ||
|
|
0f6622360c | ||
|
|
578bc6e546 | ||
|
|
70fd038207 | ||
|
|
b30110a48a | ||
|
|
21978c4c1d | ||
|
|
ec48f2350d | ||
|
|
fe1e3bb847 | ||
|
|
69776f26d8 | ||
|
|
ab4092c088 | ||
|
|
4948a8d7de | ||
|
|
0a607bce18 | ||
|
|
e59820e3d6 | ||
|
|
85a2d25c1b | ||
|
|
3bc7fc84f9 | ||
|
|
249ce1ecbb | ||
|
|
be7f8d9916 | ||
|
|
0a0495f1a7 | ||
|
|
2f7afa8556 | ||
|
|
a6d0177153 | ||
|
|
b9e24888e7 | ||
|
|
9017dae664 | ||
|
|
94acc241d9 | ||
|
|
765f3a476d | ||
|
|
2cdb916233 | ||
|
|
5f14261aba | ||
|
|
20138505c4 | ||
|
|
49c09bfd85 | ||
|
|
475edf6732 | ||
|
|
76ea89b668 | ||
|
|
56b3a8df6b | ||
|
|
e02de0c0f6 | ||
|
|
84bfab2a72 | ||
|
|
a6825a2df2 | ||
|
|
9ba58d1a46 | ||
|
|
29a99d3f97 | ||
|
|
a8b6fe78ba | ||
|
|
4abd69b460 | ||
|
|
5c55b49976 | ||
|
|
c932961dd4 | ||
|
|
47357c3e25 | ||
|
|
3ec5a1cf2c | ||
|
|
13d8a7baf7 | ||
|
|
19b1e74d06 | ||
|
|
82de9b9b94 | ||
|
|
867af58695 | ||
|
|
cdd50f0258 | ||
|
|
8c8b92cc4e | ||
|
|
31dd1619f6 | ||
|
|
afb010e3ae | ||
|
|
ddebdcda43 | ||
|
|
29c69418e9 | ||
|
|
3e565e5195 | ||
|
|
1b352d6162 | ||
|
|
258aa77cdd | ||
|
|
7789bd145c | ||
|
|
c1457cc83f | ||
|
|
8b316d0abf | ||
|
|
def5059ff1 | ||
|
|
e1febfae31 | ||
|
|
82aa506902 | ||
|
|
de1aa40ecf | ||
|
|
53724b437e | ||
|
|
1592620141 | ||
|
|
28ef87a95b | ||
|
|
d4019eea99 | ||
|
|
d13b46cbba | ||
|
|
f1eb215bb9 | ||
|
|
92c7e1a0c1 | ||
|
|
b81cc178b5 | ||
|
|
dd42b82dbf | ||
|
|
ca6b50081a | ||
|
|
4fa27046bb | ||
|
|
8cadc6eaa4 | ||
|
|
ccae629833 | ||
|
|
4a1cbe632c | ||
|
|
579813d93a | ||
|
|
00d00c02ea | ||
|
|
765949cc40 | ||
|
|
87cea6a120 | ||
|
|
83526d4d46 | ||
|
|
fca4faa114 | ||
|
|
171628c7b9 |
22
.github/workflows/core.tests.yml
vendored
22
.github/workflows/core.tests.yml
vendored
@@ -9,7 +9,13 @@ on:
|
||||
- "packages/core/**"
|
||||
# re-run workflow if workflow file changes
|
||||
- ".github/workflows/core.tests.yml"
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
branches:
|
||||
- "master"
|
||||
paths:
|
||||
- "packages/core/**"
|
||||
# re-run workflow if workflow file changes
|
||||
- ".github/workflows/core.tests.yml"
|
||||
types:
|
||||
- "ready_for_review"
|
||||
- "opened"
|
||||
@@ -17,11 +23,21 @@ on:
|
||||
- "reopened"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-22.04
|
||||
authorize:
|
||||
environment: ${{ github.event_name == 'pull_request_target' &&
|
||||
github.event.pull_request.head.repo.full_name != github.repository &&
|
||||
'external' || 'internal' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo true
|
||||
|
||||
test:
|
||||
needs: authorize
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.ref }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
|
||||
8
.github/workflows/desktop.tests.yml
vendored
8
.github/workflows/desktop.tests.yml
vendored
@@ -6,10 +6,16 @@ on:
|
||||
branches:
|
||||
- "master"
|
||||
paths:
|
||||
- "app/desktop/**"
|
||||
- "apps/desktop/**"
|
||||
# re-run workflow if workflow file changes
|
||||
- ".github/workflows/desktop.tests.yml"
|
||||
pull_request:
|
||||
branches:
|
||||
- "master"
|
||||
paths:
|
||||
- "apps/desktop/**"
|
||||
# re-run workflow if workflow file changes
|
||||
- ".github/workflows/desktop.tests.yml"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
6
.github/workflows/editor.tests.yml
vendored
6
.github/workflows/editor.tests.yml
vendored
@@ -10,6 +10,12 @@ on:
|
||||
# re-run workflow if workflow file changes
|
||||
- ".github/workflows/editor.tests.yml"
|
||||
pull_request:
|
||||
branches:
|
||||
- "master"
|
||||
paths:
|
||||
- "packages/editor/**"
|
||||
# re-run workflow if workflow file changes
|
||||
- ".github/workflows/editor.tests.yml"
|
||||
types:
|
||||
- "ready_for_review"
|
||||
- "opened"
|
||||
|
||||
4
.github/workflows/monograph.publish.yml
vendored
4
.github/workflows/monograph.publish.yml
vendored
@@ -45,7 +45,7 @@ jobs:
|
||||
- name: Docker Setup Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
context: apps/monograph
|
||||
file: apps/monograph/Dockerfile
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v8
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: streetwriters/monograph:${{ steps.package_metadata.outputs.app_version }},streetwriters/monograph:latest
|
||||
cache-from: streetwriters/monograph:latest
|
||||
|
||||
|
||||
21
.github/workflows/web.tests.yml
vendored
21
.github/workflows/web.tests.yml
vendored
@@ -9,7 +9,13 @@ on:
|
||||
- "apps/web/**"
|
||||
# re-run workflow if workflow file changes
|
||||
- ".github/workflows/web.tests.yml"
|
||||
pull_request:
|
||||
pull_request_target:
|
||||
branches:
|
||||
- "master"
|
||||
paths:
|
||||
- "apps/web/**"
|
||||
# re-run workflow if workflow file changes
|
||||
- ".github/workflows/web.tests.yml"
|
||||
types:
|
||||
- "ready_for_review"
|
||||
- "opened"
|
||||
@@ -17,12 +23,23 @@ on:
|
||||
- "reopened"
|
||||
|
||||
jobs:
|
||||
authorize:
|
||||
environment: ${{ github.event_name == 'pull_request_target' &&
|
||||
github.event.pull_request.head.repo.full_name != github.repository &&
|
||||
'external' || 'internal' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: echo true
|
||||
|
||||
build:
|
||||
needs: authorize
|
||||
name: Build
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.ref }}
|
||||
|
||||
- name: Setup Node
|
||||
uses: ./.github/actions/setup-node-with-cache
|
||||
@@ -54,6 +71,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha || github.ref }}
|
||||
|
||||
- name: Download build
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
@@ -24,7 +24,7 @@ Requirements:
|
||||
|
||||
Before you can do anything, you'll need to [install Node.js](https://nodejs.org/en/download/) v16 or later on your system.
|
||||
|
||||
Once you have completed the setup, the first step is to `clone` the monorepo:
|
||||
1. `clone` the monorepo:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/streetwriters/notesnook.git
|
||||
@@ -33,19 +33,28 @@ git clone https://github.com/streetwriters/notesnook.git
|
||||
cd notesnook
|
||||
```
|
||||
|
||||
Once you are inside the `./notesnook` directory, run the preparation step:
|
||||
2. Install dependencies:
|
||||
|
||||
```bash
|
||||
# this might take a while to complete
|
||||
npm install
|
||||
```
|
||||
|
||||
Now you can finally start the desktop app for development:
|
||||
3. Run the webapp for desktop environment:
|
||||
|
||||
```bash
|
||||
cd apps/web
|
||||
npm run start:desktop
|
||||
```
|
||||
|
||||
4. In a separate terminal session, run the desktop app from the root of the project:
|
||||
|
||||
```bash
|
||||
npm run start:desktop
|
||||
```
|
||||
|
||||
### Release mode
|
||||
|
||||
To run the app in release mode:
|
||||
|
||||
```bash
|
||||
|
||||
@@ -17,230 +17,226 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { test } from "vitest";
|
||||
import { harness } from "./utils.js";
|
||||
import { testCleanup, test } from "./test-override.js";
|
||||
import { writeFile } from "fs/promises";
|
||||
import { Page } from "playwright";
|
||||
import { gt, lt } from "semver";
|
||||
import { describe } from "vitest";
|
||||
|
||||
test("update starts downloading if version is outdated", async (t) => {
|
||||
await harness(
|
||||
t,
|
||||
async ({ page }) => {
|
||||
await page.waitForSelector("#authForm");
|
||||
test("update starts downloading if version is outdated", async ({
|
||||
ctx: { page },
|
||||
expect,
|
||||
onTestFinished
|
||||
}) => {
|
||||
onTestFinished(testCleanup);
|
||||
|
||||
t.expect(
|
||||
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||
).toBe(true);
|
||||
await page.waitForSelector("#authForm");
|
||||
expect(
|
||||
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||
).toBe(true);
|
||||
|
||||
await page
|
||||
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||
.click();
|
||||
await page
|
||||
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||
.click();
|
||||
|
||||
await skipDialog(page);
|
||||
await skipDialog(page);
|
||||
|
||||
await page.waitForSelector(".ProseMirror");
|
||||
await page.waitForSelector(".ProseMirror");
|
||||
|
||||
await page
|
||||
.locator(".theme-scope-statusBar")
|
||||
.getByRole("button", { name: /updating/i })
|
||||
.waitFor({ state: "attached" });
|
||||
},
|
||||
{ version: "3.0.0" }
|
||||
);
|
||||
await page
|
||||
.locator(".theme-scope-statusBar")
|
||||
.getByRole("button", { name: /updating/i })
|
||||
.waitFor({ state: "attached" });
|
||||
});
|
||||
|
||||
test("update is only shown if version is outdated and auto updates are disabled", async (t) => {
|
||||
await harness(
|
||||
t,
|
||||
async (ctx) => {
|
||||
await ctx.app.close();
|
||||
await writeFile(
|
||||
ctx.configPath,
|
||||
JSON.stringify({
|
||||
automaticUpdates: false
|
||||
})
|
||||
);
|
||||
test("update is only shown if version is outdated and auto updates are disabled", async ({
|
||||
ctx,
|
||||
expect,
|
||||
onTestFinished
|
||||
}) => {
|
||||
onTestFinished(testCleanup);
|
||||
|
||||
await ctx.relaunch();
|
||||
await ctx.app.close();
|
||||
await writeFile(
|
||||
ctx.configPath,
|
||||
JSON.stringify({
|
||||
automaticUpdates: false
|
||||
})
|
||||
);
|
||||
|
||||
const { page } = ctx;
|
||||
await ctx.relaunch();
|
||||
|
||||
await page.waitForSelector("#authForm");
|
||||
const { page } = ctx;
|
||||
|
||||
t.expect(
|
||||
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||
).toBe(true);
|
||||
await page.waitForSelector("#authForm");
|
||||
|
||||
await page
|
||||
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||
.click();
|
||||
expect(
|
||||
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||
).toBe(true);
|
||||
|
||||
await skipDialog(page);
|
||||
await page
|
||||
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||
.click();
|
||||
|
||||
await page.waitForSelector(".ProseMirror");
|
||||
await skipDialog(page);
|
||||
|
||||
await page.waitForSelector(".ProseMirror");
|
||||
|
||||
await page
|
||||
.locator(".theme-scope-statusBar")
|
||||
.getByRole("button", { name: /available/i })
|
||||
.waitFor({ state: "attached" });
|
||||
});
|
||||
|
||||
describe("update to stable if it is newer", () => {
|
||||
test.scoped({ options: { version: "3.0.0-beta.0" } });
|
||||
test("test", async ({ ctx, expect, onTestFinished }) => {
|
||||
onTestFinished(testCleanup);
|
||||
|
||||
await ctx.app.close();
|
||||
await writeFile(
|
||||
ctx.configPath,
|
||||
JSON.stringify({
|
||||
automaticUpdates: false,
|
||||
releaseTrack: "beta"
|
||||
})
|
||||
);
|
||||
|
||||
await ctx.relaunch();
|
||||
|
||||
const { page } = ctx;
|
||||
|
||||
await page.waitForSelector("#authForm");
|
||||
|
||||
expect(
|
||||
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||
).toBe(true);
|
||||
|
||||
await page
|
||||
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||
.click();
|
||||
|
||||
await skipDialog(page);
|
||||
|
||||
await page.waitForSelector(".ProseMirror");
|
||||
|
||||
const updateButton = page
|
||||
.locator(".theme-scope-statusBar")
|
||||
.getByRole("button", { name: /available/i });
|
||||
await updateButton.waitFor({ state: "visible" });
|
||||
const content = await updateButton.textContent();
|
||||
const version = content?.split(" ")?.[0] || "";
|
||||
expect(gt(version, "3.0.0-beta.0")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("update is not available if it latest stable version is older", () => {
|
||||
test.scoped({ options: { version: "99.0.0-beta.0" } });
|
||||
test("test", async ({ ctx, expect, onTestFinished }) => {
|
||||
onTestFinished(testCleanup);
|
||||
|
||||
await ctx.app.close();
|
||||
await writeFile(
|
||||
ctx.configPath,
|
||||
JSON.stringify({
|
||||
automaticUpdates: false,
|
||||
releaseTrack: "beta"
|
||||
})
|
||||
);
|
||||
|
||||
await ctx.relaunch();
|
||||
|
||||
const { page } = ctx;
|
||||
|
||||
await page.waitForSelector("#authForm");
|
||||
|
||||
expect(
|
||||
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||
).toBe(true);
|
||||
|
||||
await page
|
||||
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||
.click();
|
||||
|
||||
await skipDialog(page);
|
||||
|
||||
await page.waitForSelector(".ProseMirror");
|
||||
|
||||
await page
|
||||
.locator(".theme-scope-statusBar")
|
||||
.getByRole("button", { name: /checking for updates/i })
|
||||
.waitFor({ state: "hidden" });
|
||||
|
||||
expect(
|
||||
await page
|
||||
.locator(".theme-scope-statusBar")
|
||||
.getByRole("button", { name: /available/i })
|
||||
.waitFor({ state: "attached" });
|
||||
},
|
||||
{ version: "3.0.0" }
|
||||
);
|
||||
.isHidden()
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test("update to stable if it is newer", async (t) => {
|
||||
await harness(
|
||||
t,
|
||||
async (ctx) => {
|
||||
await ctx.app.close();
|
||||
await writeFile(
|
||||
ctx.configPath,
|
||||
JSON.stringify({
|
||||
automaticUpdates: false,
|
||||
releaseTrack: "beta"
|
||||
})
|
||||
);
|
||||
describe("downgrade to stable on switching to stable release track", () => {
|
||||
test.scoped({ options: { version: "99.0.0-beta.0" } });
|
||||
test("test", async ({ ctx, expect, onTestFinished }) => {
|
||||
onTestFinished(testCleanup);
|
||||
|
||||
await ctx.relaunch();
|
||||
await ctx.app.close();
|
||||
await writeFile(
|
||||
ctx.configPath,
|
||||
JSON.stringify({
|
||||
automaticUpdates: false,
|
||||
releaseTrack: "stable"
|
||||
})
|
||||
);
|
||||
|
||||
const { page } = ctx;
|
||||
await ctx.relaunch();
|
||||
|
||||
await page.waitForSelector("#authForm");
|
||||
const { page } = ctx;
|
||||
|
||||
t.expect(
|
||||
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||
).toBe(true);
|
||||
await page.waitForSelector("#authForm");
|
||||
|
||||
await page
|
||||
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||
.click();
|
||||
expect(
|
||||
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||
).toBe(true);
|
||||
|
||||
await skipDialog(page);
|
||||
await page
|
||||
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||
.click();
|
||||
|
||||
await page.waitForSelector(".ProseMirror");
|
||||
await skipDialog(page);
|
||||
|
||||
const updateButton = page
|
||||
.locator(".theme-scope-statusBar")
|
||||
.getByRole("button", { name: /available/i });
|
||||
await updateButton.waitFor({ state: "visible" });
|
||||
const content = await updateButton.textContent();
|
||||
const version = content?.split(" ")?.[0] || "";
|
||||
t.expect(gt(version, "3.0.0-beta.0")).toBe(true);
|
||||
},
|
||||
{ version: "3.0.0-beta.0" }
|
||||
);
|
||||
});
|
||||
await page.waitForSelector(".ProseMirror");
|
||||
|
||||
test("update is not available if it latest stable version is older", async (t) => {
|
||||
await harness(
|
||||
t,
|
||||
async (ctx) => {
|
||||
await ctx.app.close();
|
||||
await writeFile(
|
||||
ctx.configPath,
|
||||
JSON.stringify({
|
||||
automaticUpdates: false,
|
||||
releaseTrack: "beta"
|
||||
})
|
||||
);
|
||||
await page
|
||||
.locator(".theme-scope-statusBar")
|
||||
.getByRole("button", { name: /checking for updates/i })
|
||||
.waitFor({ state: "hidden" });
|
||||
|
||||
await ctx.relaunch();
|
||||
|
||||
const { page } = ctx;
|
||||
|
||||
await page.waitForSelector("#authForm");
|
||||
|
||||
t.expect(
|
||||
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||
).toBe(true);
|
||||
|
||||
await page
|
||||
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||
.click();
|
||||
|
||||
await skipDialog(page);
|
||||
|
||||
await page.waitForSelector(".ProseMirror");
|
||||
|
||||
await page
|
||||
.locator(".theme-scope-statusBar")
|
||||
.getByRole("button", { name: /checking for updates/i })
|
||||
.waitFor({ state: "hidden" });
|
||||
|
||||
t.expect(
|
||||
await page
|
||||
.locator(".theme-scope-statusBar")
|
||||
.getByRole("button", { name: /available/i })
|
||||
.isHidden()
|
||||
).toBe(true);
|
||||
},
|
||||
{ version: "99.0.0-beta.0" }
|
||||
);
|
||||
});
|
||||
|
||||
test("downgrade to stable on switching to stable release track", async (t) => {
|
||||
await harness(
|
||||
t,
|
||||
async (ctx) => {
|
||||
await ctx.app.close();
|
||||
await writeFile(
|
||||
ctx.configPath,
|
||||
JSON.stringify({
|
||||
automaticUpdates: false,
|
||||
releaseTrack: "stable"
|
||||
})
|
||||
);
|
||||
|
||||
await ctx.relaunch();
|
||||
|
||||
const { page } = ctx;
|
||||
|
||||
await page.waitForSelector("#authForm");
|
||||
|
||||
t.expect(
|
||||
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||
).toBe(true);
|
||||
|
||||
await page
|
||||
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||
.click();
|
||||
|
||||
await skipDialog(page);
|
||||
|
||||
await page.waitForSelector(".ProseMirror");
|
||||
|
||||
await page
|
||||
.locator(".theme-scope-statusBar")
|
||||
.getByRole("button", { name: /checking for updates/i })
|
||||
.waitFor({ state: "hidden" });
|
||||
|
||||
const updateButton = page
|
||||
.locator(".theme-scope-statusBar")
|
||||
.getByRole("button", { name: /available/i });
|
||||
await updateButton.waitFor({ state: "visible" });
|
||||
const content = await updateButton.textContent();
|
||||
const version = content?.split(" ")?.[0] || "";
|
||||
t.expect(lt(version, "99.0.0-beta.0")).toBe(true);
|
||||
},
|
||||
{ version: "99.0.0-beta.0" }
|
||||
);
|
||||
const updateButton = page
|
||||
.locator(".theme-scope-statusBar")
|
||||
.getByRole("button", { name: /available/i });
|
||||
await updateButton.waitFor({ state: "visible" });
|
||||
const content = await updateButton.textContent();
|
||||
const version = content?.split(" ")?.[0] || "";
|
||||
expect(lt(version, "99.0.0-beta.0")).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
async function skipDialog(page: Page) {
|
||||
await page
|
||||
.waitForSelector(".ReactModal__Content", {
|
||||
timeout: 1000
|
||||
})
|
||||
.catch(() => {})
|
||||
.then(async () => {
|
||||
const positiveButton = page.locator(
|
||||
"button[data-role='positive-button']"
|
||||
);
|
||||
const negativeButton = page.locator(
|
||||
"button[data-role='negative-button']"
|
||||
);
|
||||
if (await positiveButton.isVisible()) await positiveButton.click();
|
||||
else if (await negativeButton.isVisible()) await negativeButton.click();
|
||||
});
|
||||
try {
|
||||
const dialog = page.locator(".ReactModal__Content");
|
||||
const positiveButton = dialog.locator(
|
||||
"button[data-role='positive-button']"
|
||||
);
|
||||
const negativeButton = dialog.locator(
|
||||
"button[data-role='negative-button']"
|
||||
);
|
||||
if (await positiveButton.isVisible())
|
||||
await positiveButton.click({ timeout: 1000 });
|
||||
else if (await negativeButton.isVisible())
|
||||
await negativeButton.click({ timeout: 1000 });
|
||||
} catch (e) {
|
||||
// ignore error
|
||||
}
|
||||
}
|
||||
|
||||
24
apps/desktop/__tests__/global-setup.ts
Normal file
24
apps/desktop/__tests__/global-setup.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
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 { buildApp } from "./utils";
|
||||
|
||||
export default async function setup() {
|
||||
await buildApp();
|
||||
}
|
||||
@@ -17,22 +17,24 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { test } from "vitest";
|
||||
import { harness } from "./utils.js";
|
||||
import assert from "assert";
|
||||
import { testCleanup, test } from "./test-override.js";
|
||||
|
||||
test("make sure app loads", async (t) => {
|
||||
await harness(t, async ({ page }) => {
|
||||
await page.waitForSelector("#authForm");
|
||||
test("make sure app loads", async ({
|
||||
ctx: { page },
|
||||
expect,
|
||||
onTestFinished
|
||||
}) => {
|
||||
onTestFinished(testCleanup);
|
||||
|
||||
assert.ok(
|
||||
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||
);
|
||||
await page.waitForSelector("#authForm");
|
||||
|
||||
await page
|
||||
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||
.click();
|
||||
expect(
|
||||
await page.getByRole("button", { name: "Create account" }).isVisible()
|
||||
).toBe(true);
|
||||
|
||||
await page.waitForSelector(".ProseMirror");
|
||||
});
|
||||
await page
|
||||
.getByRole("button", { name: "Skip & go directly to the app" })
|
||||
.click();
|
||||
|
||||
await page.waitForSelector(".ProseMirror");
|
||||
});
|
||||
|
||||
64
apps/desktop/__tests__/test-override.ts
Normal file
64
apps/desktop/__tests__/test-override.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { test as vitestTest, TestContext } from "vitest";
|
||||
import { buildAndLaunchApp, Fixtures, TestOptions } from "./utils";
|
||||
import { mkdir, rm } from "fs/promises";
|
||||
import path from "path";
|
||||
import slugify from "slugify";
|
||||
|
||||
export const test = vitestTest.extend<Fixtures>({
|
||||
options: { version: "3.0.0" } as TestOptions,
|
||||
ctx: async ({ options }, use) => {
|
||||
const ctx = await buildAndLaunchApp(options);
|
||||
await use(ctx);
|
||||
}
|
||||
});
|
||||
|
||||
export async function testCleanup(context: TestContext) {
|
||||
const ctx = (context.task.context as unknown as Fixtures).ctx;
|
||||
if (context.task.result?.state === "fail") {
|
||||
await mkdir("test-results", { recursive: true });
|
||||
await ctx.page.screenshot({
|
||||
path: path.join(
|
||||
"test-results",
|
||||
`${slugify(context.task.name)}-${process.platform}-${
|
||||
process.arch
|
||||
}-error.png`
|
||||
)
|
||||
});
|
||||
}
|
||||
await ctx.app.close();
|
||||
await rm(ctx.userDataDir, {
|
||||
force: true,
|
||||
recursive: true,
|
||||
maxRetries: 3,
|
||||
retryDelay: 5000
|
||||
}).catch(() => {
|
||||
/*ignore */
|
||||
});
|
||||
await rm(ctx.outputDir, {
|
||||
force: true,
|
||||
recursive: true,
|
||||
maxRetries: 3,
|
||||
retryDelay: 5000
|
||||
}).catch(() => {
|
||||
/*ignore */
|
||||
});
|
||||
}
|
||||
@@ -18,146 +18,235 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { execSync } from "child_process";
|
||||
import { mkdir } from "fs/promises";
|
||||
import { cp, readFile, writeFile } from "fs/promises";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import path from "path";
|
||||
import path, { join, resolve } from "path";
|
||||
import { _electron as electron } from "playwright";
|
||||
import slugify from "slugify";
|
||||
import { TaskContext } from "vitest";
|
||||
import { existsSync } from "fs";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const IS_DEBUG = process.env.NN_DEBUG === "true" || process.env.CI === "true";
|
||||
const productName = `NotesnookTestHarness`;
|
||||
const SOURCE_DIR = resolve("output", productName);
|
||||
|
||||
interface AppContext {
|
||||
export interface AppContext {
|
||||
app: import("playwright").ElectronApplication;
|
||||
page: import("playwright").Page;
|
||||
configPath: string;
|
||||
userDataDir: string;
|
||||
outputDir: string;
|
||||
relaunch: () => Promise<void>;
|
||||
}
|
||||
|
||||
interface TestOptions {
|
||||
export interface TestOptions {
|
||||
version: string;
|
||||
}
|
||||
|
||||
export async function harness(
|
||||
t: TaskContext,
|
||||
cb: (ctx: AppContext) => Promise<void>,
|
||||
options?: TestOptions
|
||||
) {
|
||||
const ctx = await buildAndLaunchApp(options);
|
||||
|
||||
t.onTestFinished(async (result) => {
|
||||
if (result.state === "fail") {
|
||||
await mkdir("test-results", { recursive: true });
|
||||
await ctx.page.screenshot({
|
||||
path: path.join(
|
||||
"test-results",
|
||||
`${slugify(t.task.name)}-${process.platform}-${
|
||||
process.arch
|
||||
}-error.png`
|
||||
)
|
||||
});
|
||||
}
|
||||
await ctx.app.close();
|
||||
});
|
||||
|
||||
await cb(ctx);
|
||||
export interface Fixtures {
|
||||
options: TestOptions;
|
||||
ctx: AppContext;
|
||||
}
|
||||
|
||||
async function buildAndLaunchApp(options?: TestOptions): Promise<AppContext> {
|
||||
const productName = makeid(10);
|
||||
const executablePath = await buildApp({ ...options, productName });
|
||||
const { app, page, configPath } = await launchApp(executablePath);
|
||||
export async function buildAndLaunchApp(
|
||||
options?: TestOptions
|
||||
): Promise<AppContext> {
|
||||
const productName = `notesnooktest${makeid(10)}`;
|
||||
const outputDir = path.join("test-artifacts", `${productName}-output`);
|
||||
const executablePath = await copyBuild({
|
||||
...options,
|
||||
outputDir
|
||||
});
|
||||
const { app, page, configPath, userDataDir } = await launchApp(
|
||||
executablePath,
|
||||
productName
|
||||
);
|
||||
const ctx: AppContext = {
|
||||
app,
|
||||
page,
|
||||
configPath,
|
||||
userDataDir,
|
||||
outputDir,
|
||||
relaunch: async () => {
|
||||
const { app, page, configPath } = await launchApp(executablePath);
|
||||
const { app, page, configPath, userDataDir } = await launchApp(
|
||||
executablePath,
|
||||
productName
|
||||
);
|
||||
ctx.app = app;
|
||||
ctx.page = page;
|
||||
ctx.userDataDir = userDataDir;
|
||||
ctx.configPath = configPath;
|
||||
}
|
||||
};
|
||||
return ctx;
|
||||
}
|
||||
|
||||
async function launchApp(executablePath: string) {
|
||||
async function launchApp(executablePath: string, packageName: string) {
|
||||
const userDataDir = resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
"test-artifacts",
|
||||
"user_data_dirs",
|
||||
packageName
|
||||
);
|
||||
const app = await electron.launch({
|
||||
executablePath,
|
||||
args: IS_DEBUG ? [] : ["--hidden"],
|
||||
env:
|
||||
process.platform === "linux"
|
||||
env: {
|
||||
...(process.platform === "linux"
|
||||
? {
|
||||
...(process.env as Record<string, string>),
|
||||
APPIMAGE: "true"
|
||||
}
|
||||
: (process.env as Record<string, string>)
|
||||
: (process.env as Record<string, string>)),
|
||||
CUSTOM_USER_DATA_DIR: userDataDir
|
||||
}
|
||||
});
|
||||
|
||||
const page = await app.firstWindow();
|
||||
|
||||
const userDataDirectory = await app.evaluate((a) => {
|
||||
return a.app.getPath("userData");
|
||||
});
|
||||
const configPath = path.join(userDataDirectory, "config.json");
|
||||
const configPath = path.join(userDataDir, "UserData", "config.json");
|
||||
return {
|
||||
app,
|
||||
page,
|
||||
configPath
|
||||
configPath,
|
||||
userDataDir
|
||||
};
|
||||
}
|
||||
|
||||
async function buildApp({
|
||||
let MAX_RETRIES = 3;
|
||||
export async function buildApp(version?: string) {
|
||||
if (!existsSync(SOURCE_DIR)) {
|
||||
const args = [
|
||||
"electron-builder",
|
||||
"--dir",
|
||||
`--${process.arch}`,
|
||||
`--config electron-builder.config.js`,
|
||||
`--c.extraMetadata.productName=${productName}`,
|
||||
`--c.compression=store`,
|
||||
"--publish=never"
|
||||
];
|
||||
if (version) args.push(`--c.extraMetadata.version=${version}`);
|
||||
try {
|
||||
execSync(`npx ${args.join(" ")}`, {
|
||||
stdio: IS_DEBUG ? "inherit" : "ignore",
|
||||
env: {
|
||||
...process.env,
|
||||
NOTESNOOK_STAGING: "true",
|
||||
NN_PRODUCT_NAME: productName,
|
||||
NN_APP_ID: `com.notesnook.test.${productName}`,
|
||||
NN_OUTPUT_DIR: SOURCE_DIR
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
if (--MAX_RETRIES) {
|
||||
console.log("retrying...");
|
||||
return await buildApp(version);
|
||||
} else throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function copyBuild({
|
||||
version,
|
||||
productName
|
||||
outputDir
|
||||
}: {
|
||||
version?: string;
|
||||
productName: string;
|
||||
outputDir: string;
|
||||
}) {
|
||||
const buildRoot = path.join("test-artifacts", `${productName}-build`);
|
||||
const output = path.join("test-artifacts", `${productName}-output`);
|
||||
execSync(`npm run release -- --root ${buildRoot} --skip-tsc-build`, {
|
||||
stdio: IS_DEBUG ? "inherit" : "ignore"
|
||||
});
|
||||
return process.platform === "win32"
|
||||
? await makeBuildCopyWindows(outputDir, productName, version)
|
||||
: process.platform === "darwin"
|
||||
? await makeBuildCopyMacOS(outputDir, productName, version)
|
||||
: await makeBuildCopyLinux(outputDir, productName, version);
|
||||
}
|
||||
|
||||
const args = [
|
||||
`--config electron-builder.config.js`,
|
||||
`--c.extraMetadata.productName=${productName}`,
|
||||
"--publish=never"
|
||||
];
|
||||
if (version) args.push(`--c.extraMetadata.version=${version}`);
|
||||
|
||||
execSync(`npx electron-builder --dir --${process.arch} ${args.join(" ")}`, {
|
||||
stdio: IS_DEBUG ? "inherit" : "ignore",
|
||||
env: {
|
||||
...process.env,
|
||||
NOTESNOOK_STAGING: "true",
|
||||
NN_BUILD_ROOT: buildRoot,
|
||||
NN_PRODUCT_NAME: productName,
|
||||
NN_APP_ID: `com.notesnook.test.${productName}`,
|
||||
NN_OUTPUT_DIR: output
|
||||
}
|
||||
});
|
||||
|
||||
return path.join(
|
||||
async function makeBuildCopyLinux(
|
||||
outputDir: string,
|
||||
productName: string,
|
||||
version?: string
|
||||
) {
|
||||
const platformDir =
|
||||
process.arch === "arm64" ? "linux-arm64-unpacked" : "linux-unpacked";
|
||||
const appDir = await makeBuildCopy(
|
||||
outputDir,
|
||||
platformDir,
|
||||
"resources",
|
||||
version
|
||||
);
|
||||
return resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
output,
|
||||
process.platform === "linux"
|
||||
? process.arch === "arm64"
|
||||
? "linux-arm64-unpacked"
|
||||
: "linux-unpacked"
|
||||
: process.platform === "darwin"
|
||||
? process.arch === "arm64"
|
||||
? `mac-arm64/${productName}.app/Contents/MacOS/`
|
||||
: `mac/${productName}.app/Contents/MacOS/`
|
||||
: "win-unpacked",
|
||||
process.platform === "win32" ? `${productName}.exe` : productName
|
||||
appDir,
|
||||
productName.toLowerCase().replace(/\s+/g, "-")
|
||||
);
|
||||
}
|
||||
|
||||
async function makeBuildCopyWindows(
|
||||
outputDir: string,
|
||||
productName: string,
|
||||
version?: string
|
||||
) {
|
||||
const platformDir =
|
||||
process.arch === "arm64" ? "win-arm64-unpacked" : "win-unpacked";
|
||||
const appDir = await makeBuildCopy(
|
||||
outputDir,
|
||||
platformDir,
|
||||
"resources",
|
||||
version
|
||||
);
|
||||
return resolve(__dirname, "..", appDir, `${productName}.exe`);
|
||||
}
|
||||
|
||||
async function makeBuildCopyMacOS(
|
||||
outputDir: string,
|
||||
productName: string,
|
||||
version?: string
|
||||
) {
|
||||
const platformDir = process.arch === "arm64" ? "mac-arm64" : "mac";
|
||||
const appDir = await makeBuildCopy(
|
||||
outputDir,
|
||||
platformDir,
|
||||
join(`${productName}.app`, "Contents", "Resources"),
|
||||
version
|
||||
);
|
||||
return resolve(
|
||||
__dirname,
|
||||
"..",
|
||||
appDir,
|
||||
`${productName}.app`,
|
||||
"Contents",
|
||||
"MacOS",
|
||||
productName
|
||||
);
|
||||
}
|
||||
|
||||
async function makeBuildCopy(
|
||||
outputDir: string,
|
||||
platformDir: string,
|
||||
resourcesDir: string,
|
||||
version?: string
|
||||
) {
|
||||
const appDir = outputDir;
|
||||
await cp(join(SOURCE_DIR, platformDir), outputDir, {
|
||||
recursive: true,
|
||||
preserveTimestamps: true,
|
||||
verbatimSymlinks: true,
|
||||
dereference: false,
|
||||
force: true
|
||||
});
|
||||
|
||||
const packageJsonPath = join(appDir, resourcesDir, "app", "package.json");
|
||||
|
||||
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf-8"));
|
||||
if (version) {
|
||||
packageJson.version = version;
|
||||
await writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
||||
}
|
||||
|
||||
return appDir;
|
||||
}
|
||||
|
||||
function makeid(length: number) {
|
||||
let result = "";
|
||||
const characters =
|
||||
|
||||
1656
apps/desktop/package-lock.json
generated
1656
apps/desktop/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,13 +2,15 @@
|
||||
"name": "@notesnook/desktop",
|
||||
"productName": "Notesnook",
|
||||
"description": "Your private note taking space",
|
||||
"version": "3.2.2",
|
||||
"version": "3.2.4",
|
||||
"appAppleId": "1544027013",
|
||||
"private": true,
|
||||
"main": "./dist/cjs/index.js",
|
||||
"module": "./dist/esm/index.js",
|
||||
"types": "./dist/types/index.d.ts",
|
||||
"sideEffects": false,
|
||||
"sideEffects": [
|
||||
"src/overrides.ts"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"require": {
|
||||
@@ -35,7 +37,7 @@
|
||||
"electron-updater": "^6.6.2",
|
||||
"icojs": "^0.19.5",
|
||||
"sqlite-better-trigram": "0.0.3",
|
||||
"sqlite3-fts5-html": "^0.0.3",
|
||||
"sqlite3-fts5-html": "^0.0.4",
|
||||
"typed-emitter": "^2.1.0",
|
||||
"yargs": "^17.7.2",
|
||||
"zod": "3.24.3"
|
||||
@@ -55,7 +57,7 @@
|
||||
"slugify": "1.6.6",
|
||||
"tree-kill": "^1.2.2",
|
||||
"undici": "^7.8.0",
|
||||
"vitest": "2.1.8"
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"dmg-license": "^1.0.11"
|
||||
|
||||
@@ -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 "./overrides";
|
||||
import { app, BrowserWindow, nativeTheme, shell } from "electron";
|
||||
import { isDevelopment } from "./utils";
|
||||
import { registerProtocol, PROTOCOL_URL } from "./utils/protocol";
|
||||
|
||||
35
apps/desktop/src/overrides.ts
Normal file
35
apps/desktop/src/overrides.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { app } from "electron";
|
||||
import path from "path";
|
||||
|
||||
if (process.env.CUSTOM_USER_DATA_DIR) {
|
||||
app.setPath(
|
||||
"appData",
|
||||
path.join(process.env.CUSTOM_USER_DATA_DIR, "AppData")
|
||||
);
|
||||
app.setPath(
|
||||
"userData",
|
||||
path.join(process.env.CUSTOM_USER_DATA_DIR, "UserData")
|
||||
);
|
||||
app.setPath(
|
||||
"documents",
|
||||
path.join(process.env.CUSTOM_USER_DATA_DIR, "Documents")
|
||||
);
|
||||
}
|
||||
@@ -21,11 +21,13 @@ import { defineConfig } from "vitest/config";
|
||||
|
||||
export default defineConfig({
|
||||
test: {
|
||||
testTimeout: process.env.CI ? 120 * 1000 : 120 * 1000,
|
||||
testTimeout: 120 * 1000,
|
||||
hookTimeout: 120 * 1000,
|
||||
sequence: {
|
||||
concurrent: true,
|
||||
shuffle: true
|
||||
},
|
||||
globalSetup: "./__tests__/global-setup.ts",
|
||||
dir: "./__tests__/",
|
||||
exclude: [
|
||||
"**/node_modules/**",
|
||||
|
||||
@@ -137,6 +137,7 @@ export async function deleteFile(
|
||||
|
||||
export async function clearFileStorage() {
|
||||
try {
|
||||
await createCacheDir();
|
||||
const files = await RNFetchBlob.fs.ls(cacheDir);
|
||||
const oldCache = await RNFetchBlob.fs.ls(cacheDirOld);
|
||||
|
||||
@@ -221,6 +222,7 @@ export async function deleteCacheFileByName(name: string) {
|
||||
}
|
||||
|
||||
export async function deleteDCacheFiles() {
|
||||
await createCacheDir();
|
||||
const files = await RNFetchBlob.fs.ls(cacheDir);
|
||||
for (const file of files) {
|
||||
if (file.includes("_dcache") || file.startsWith("NN_")) {
|
||||
|
||||
@@ -17,26 +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 { strings } from "@notesnook/intl";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { db } from "../../common/database";
|
||||
import {
|
||||
eSendEvent,
|
||||
presentSheet,
|
||||
ToastManager
|
||||
} from "../../services/event-manager";
|
||||
import BackupService from "../../services/backup";
|
||||
import { eSendEvent, ToastManager } from "../../services/event-manager";
|
||||
import Navigation from "../../services/navigation";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { eCloseSheet, eOpenRecoveryKeyDialog } from "../../utils/events";
|
||||
import DialogHeader from "../dialog/dialog-header";
|
||||
import { eOpenRecoveryKeyDialog } from "../../utils/events";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { Dialog } from "../dialog";
|
||||
import { Button } from "../ui/button";
|
||||
import Input from "../ui/input";
|
||||
import { Notice } from "../ui/notice";
|
||||
import Seperator from "../ui/seperator";
|
||||
import { Dialog } from "../dialog";
|
||||
import BackupService from "../../services/backup";
|
||||
import { sleep } from "../../utils/time";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
|
||||
export const ChangePassword = () => {
|
||||
const passwordInputRef = useRef();
|
||||
@@ -85,8 +79,7 @@ export const ChangePassword = () => {
|
||||
context: "global"
|
||||
});
|
||||
setLoading(false);
|
||||
eSendEvent(eCloseSheet);
|
||||
await sleep(300);
|
||||
Navigation.goBack();
|
||||
eSendEvent(eOpenRecoveryKeyDialog);
|
||||
} catch (e) {
|
||||
setLoading(false);
|
||||
@@ -108,9 +101,6 @@ export const ChangePassword = () => {
|
||||
}}
|
||||
>
|
||||
<Dialog context="change-password-dialog" />
|
||||
<DialogHeader title={strings.changePassword()} />
|
||||
<Seperator />
|
||||
|
||||
<Input
|
||||
fwdRef={oldPasswordInputRef}
|
||||
onChangeText={(value) => {
|
||||
@@ -160,9 +150,3 @@ export const ChangePassword = () => {
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
ChangePassword.present = () => {
|
||||
presentSheet({
|
||||
component: <ChangePassword />
|
||||
});
|
||||
};
|
||||
|
||||
@@ -33,6 +33,9 @@ export function hideAuth() {
|
||||
eSendEvent(eCloseLoginDialog);
|
||||
if (initialAuthMode.current === AuthMode.welcomeSignup) {
|
||||
Navigation.replace("FluidPanelsView");
|
||||
setTimeout(() => {
|
||||
Navigation.resetRootState();
|
||||
}, 1000);
|
||||
} else {
|
||||
Navigation.goBack();
|
||||
}
|
||||
|
||||
@@ -89,6 +89,9 @@ const Auth = ({ navigation, route }) => {
|
||||
onPress={() => {
|
||||
if (initialAuthMode.current === 2) {
|
||||
Navigation.replace("FluidPanelsView");
|
||||
setTimeout(() => {
|
||||
Navigation.resetRootState();
|
||||
}, 1000);
|
||||
} else {
|
||||
Navigation.goBack();
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import { hideAuth } from "./common";
|
||||
import { ForgotPassword } from "./forgot-password";
|
||||
import { useLogin } from "./use-login";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { Dialog } from "../dialog";
|
||||
|
||||
const LoginSteps = {
|
||||
emailAuth: 1,
|
||||
@@ -89,7 +90,7 @@ export const Login = ({ changeMode }) => {
|
||||
return (
|
||||
<>
|
||||
<ForgotPassword />
|
||||
<SheetProvider context="two_factor_verify" />
|
||||
<Dialog context="two_factor_verify" />
|
||||
<View
|
||||
style={{
|
||||
borderRadius: DDS.isTab ? 5 : 0,
|
||||
|
||||
@@ -24,12 +24,8 @@ import { View } from "react-native";
|
||||
import { ScrollView } from "react-native-actions-sheet";
|
||||
import { db } from "../../common/database/index";
|
||||
import useTimer from "../../hooks/use-timer";
|
||||
import {
|
||||
eSendEvent,
|
||||
presentSheet,
|
||||
ToastManager
|
||||
} from "../../services/event-manager";
|
||||
import { eCloseSheet } from "../../utils/events";
|
||||
import { eSendEvent, ToastManager } from "../../services/event-manager";
|
||||
import { eCloseSimpleDialog } from "../../utils/events";
|
||||
import { AppFontSize } from "../../utils/size";
|
||||
import { Button } from "../ui/button";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
@@ -38,6 +34,7 @@ import { Pressable } from "../ui/pressable";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { presentDialog } from "../dialog/functions";
|
||||
|
||||
const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
|
||||
const { colors } = useThemeColors();
|
||||
@@ -46,7 +43,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
|
||||
method: mfaInfo?.primaryMethod,
|
||||
isPrimary: true
|
||||
});
|
||||
const { seconds, start } = useTimer(currentMethod.method);
|
||||
const { seconds, start, reset } = useTimer(currentMethod.method);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const inputRef = useRef();
|
||||
const [sending, setSending] = useState(false);
|
||||
@@ -62,7 +59,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
|
||||
},
|
||||
(result) => {
|
||||
if (result) {
|
||||
eSendEvent(eCloseSheet, "two_factor_verify");
|
||||
eSendEvent(eCloseSimpleDialog, "two_factor_verify");
|
||||
}
|
||||
setLoading(false);
|
||||
}
|
||||
@@ -133,6 +130,12 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
|
||||
<ScrollView
|
||||
keyboardShouldPersistTaps="handled"
|
||||
keyboardDismissMode="interactive"
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: colors.primary.background,
|
||||
paddingTop: 60
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
@@ -236,7 +239,10 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
|
||||
<Button
|
||||
title={strings.cancel()}
|
||||
type="secondaryAccented"
|
||||
onPress={onCancel}
|
||||
onPress={() => {
|
||||
reset();
|
||||
onCancel();
|
||||
}}
|
||||
width={250}
|
||||
/>
|
||||
|
||||
@@ -297,7 +303,7 @@ const TwoFactorVerification = ({ onMfaLogin, mfaInfo, onCancel }) => {
|
||||
};
|
||||
|
||||
TwoFactorVerification.present = (onMfaLogin, data, onCancel, context) => {
|
||||
presentSheet({
|
||||
presentDialog({
|
||||
component: () => (
|
||||
<TwoFactorVerification
|
||||
onMfaLogin={onMfaLogin}
|
||||
@@ -306,7 +312,9 @@ TwoFactorVerification.present = (onMfaLogin, data, onCancel, context) => {
|
||||
/>
|
||||
),
|
||||
context: context || "two_factor_verify",
|
||||
disableClosing: true
|
||||
disableClosing: true,
|
||||
transparent: false,
|
||||
statusBarTranslucent: true
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ import { clearMessage } from "../../services/message";
|
||||
import PremiumService from "../../services/premium";
|
||||
import SettingsService from "../../services/settings";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
import { eCloseSheet } from "../../utils/events";
|
||||
import { eCloseSimpleDialog } from "../../utils/events";
|
||||
import TwoFactorVerification from "./two-factor";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
@@ -92,7 +92,7 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
|
||||
} catch (e) {
|
||||
callback && callback(false);
|
||||
if (e.message === "invalid_grant") {
|
||||
eSendEvent(eCloseSheet, "two_factor_verify");
|
||||
eSendEvent(eCloseSimpleDialog, "two_factor_verify");
|
||||
setLoading(false);
|
||||
setStep(LoginSteps.emailAuth);
|
||||
}
|
||||
@@ -100,7 +100,7 @@ export const useLogin = (onFinishLogin, sessionExpired = false) => {
|
||||
},
|
||||
mfaInfo,
|
||||
() => {
|
||||
eSendEvent(eCloseSheet, "two_factor_verify");
|
||||
eSendEvent(eCloseSimpleDialog, "two_factor_verify");
|
||||
setLoading(false);
|
||||
setStep(LoginSteps.emailAuth);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ import { getElevationStyle } from "../../utils/elevation";
|
||||
import { AppFontSize, normalize } from "../../utils/size";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { hexToRGBA, RGB_Linear_Shade } from "../../utils/colors";
|
||||
import useKeyboard from "../../hooks/use-keyboard";
|
||||
|
||||
interface FloatingButtonProps {
|
||||
onPress: () => void;
|
||||
|
||||
@@ -21,7 +21,7 @@ import React from "react";
|
||||
import { Text, View, ViewStyle } from "react-native";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { AppFontSize } from "../../utils/size";
|
||||
import { Button } from "../ui/button";
|
||||
import { Button, ButtonProps } from "../ui/button";
|
||||
import { PressableProps } from "../ui/pressable";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
@@ -31,13 +31,7 @@ type DialogHeaderProps = {
|
||||
icon?: string;
|
||||
title?: string;
|
||||
paragraph?: string;
|
||||
button?: {
|
||||
onPress?: () => void;
|
||||
loading?: boolean;
|
||||
title?: string;
|
||||
type?: PressableProps["type"];
|
||||
icon?: string;
|
||||
};
|
||||
button?: ButtonProps;
|
||||
paragraphColor?: string;
|
||||
padding?: number;
|
||||
centered?: boolean;
|
||||
@@ -95,17 +89,14 @@ const DialogHeader = ({
|
||||
|
||||
{button ? (
|
||||
<Button
|
||||
onPress={button.onPress}
|
||||
style={{
|
||||
borderRadius: 100,
|
||||
paddingHorizontal: DefaultAppStyles.GAP
|
||||
}}
|
||||
loading={button.loading}
|
||||
fontSize={13}
|
||||
title={button.title}
|
||||
icon={button.icon}
|
||||
type={button.type || "secondary"}
|
||||
height={30}
|
||||
{...button}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
@@ -27,6 +27,9 @@ export type DialogInfo = {
|
||||
paragraph?: string;
|
||||
positiveText: string;
|
||||
negativeText: string;
|
||||
background?: string;
|
||||
transparent?: boolean;
|
||||
statusBarTranslucent?: boolean;
|
||||
positivePress?: (...args: any[]) => Promise<any>;
|
||||
onClose?: () => void;
|
||||
positiveType?:
|
||||
|
||||
@@ -130,9 +130,17 @@ export const Dialog = ({ context = "global" }: { context?: string }) => {
|
||||
|
||||
return visible && dialogInfo ? (
|
||||
<BaseDialog
|
||||
statusBarTranslucent={false}
|
||||
statusBarTranslucent={
|
||||
dialogInfo.statusBarTranslucent === undefined
|
||||
? false
|
||||
: dialogInfo.statusBarTranslucent
|
||||
}
|
||||
bounce={!dialogInfo.input}
|
||||
closeOnTouch={!dialogInfo.disableBackdropClosing}
|
||||
background={dialogInfo.background}
|
||||
transparent={
|
||||
dialogInfo.transparent === undefined ? true : dialogInfo.transparent
|
||||
}
|
||||
onShow={async () => {
|
||||
if (dialogInfo.input) {
|
||||
inputRef.current?.setNativeProps({
|
||||
|
||||
@@ -17,12 +17,15 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Dimensions, TextInput, View } from "react-native";
|
||||
import Orientation from "react-native-orientation-locker";
|
||||
import Pdf from "react-native-pdf";
|
||||
import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
|
||||
import { MMKV } from "../../../common/database/mmkv";
|
||||
import downloadAttachment from "../../../common/filesystem/download-attachment";
|
||||
import { deleteCacheFileByPath, exists } from "../../../common/filesystem/io";
|
||||
import { cacheDir } from "../../../common/filesystem/utils";
|
||||
import { useAttachmentProgress } from "../../../hooks/use-attachment-progress";
|
||||
import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets";
|
||||
@@ -30,8 +33,9 @@ import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../../services/event-manager";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { AppFontSize } from "../../../utils/size";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
import { sleep } from "../../../utils/time";
|
||||
import { Dialog } from "../../dialog";
|
||||
import BaseDialog from "../../dialog/base-dialog";
|
||||
import { presentDialog } from "../../dialog/functions";
|
||||
@@ -39,11 +43,6 @@ import SheetProvider from "../../sheet-provider";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import { ProgressBarComponent } from "../../ui/svg/lazy";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { sleep } from "../../../utils/time";
|
||||
import { MMKV } from "../../../common/database/mmkv";
|
||||
import { deleteCacheFileByPath } from "../../../common/filesystem/io";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
|
||||
const WIN_WIDTH = Dimensions.get("window").width;
|
||||
const WIN_HEIGHT = Dimensions.get("window").height;
|
||||
@@ -105,15 +104,19 @@ const PDFPreview = () => {
|
||||
const open = useCallback(
|
||||
async (attachment) => {
|
||||
setVisible(true);
|
||||
setLoading(true);
|
||||
setTimeout(async () => {
|
||||
setAttachment(attachment);
|
||||
let hash = attachment.hash;
|
||||
if (!hash) return;
|
||||
if (!(await exists(hash))) setLoading(true);
|
||||
const uri = await downloadAttachment(hash, false, {
|
||||
silent: true,
|
||||
cache: true
|
||||
});
|
||||
if (!(await exists(hash))) {
|
||||
setVisible(false);
|
||||
return;
|
||||
}
|
||||
const path = `${cacheDir}/${uri}`;
|
||||
snapshotValue.current = snapshot.current;
|
||||
setPDFSource("file://" + path);
|
||||
@@ -166,8 +169,7 @@ const PDFPreview = () => {
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<Animated.View
|
||||
exiting={FadeOut}
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
@@ -188,7 +190,7 @@ const PDFPreview = () => {
|
||||
>
|
||||
{strings.loadingWithProgress(progress?.percent)}
|
||||
</Paragraph>
|
||||
</Animated.View>
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
<View
|
||||
@@ -270,49 +272,42 @@ const PDFPreview = () => {
|
||||
</View>
|
||||
</View>
|
||||
{pdfSource ? (
|
||||
<Animated.View
|
||||
style={{
|
||||
flex: 1
|
||||
<Pdf
|
||||
source={{
|
||||
uri: pdfSource
|
||||
}}
|
||||
entering={FadeIn}
|
||||
>
|
||||
<Pdf
|
||||
source={{
|
||||
uri: pdfSource
|
||||
}}
|
||||
ref={pdfRef}
|
||||
onLoadComplete={(numberOfPages) => {
|
||||
setNumPages(numberOfPages);
|
||||
}}
|
||||
onPageChanged={(page) => {
|
||||
setCurrentPage(page);
|
||||
inputRef.current?.setNativeProps({
|
||||
text: page + ""
|
||||
});
|
||||
saveSnapshot({
|
||||
currentPage: page,
|
||||
scale: snapshot?.current?.scale
|
||||
});
|
||||
}}
|
||||
// scale={snapshotValue.current?.scale}
|
||||
// onScaleChanged={(scale) => {
|
||||
// saveSnapshot({
|
||||
// currentPage: snapshot?.current?.currentPage,
|
||||
// scale: scale
|
||||
// });
|
||||
// }}
|
||||
page={snapshotValue?.current?.currentPage}
|
||||
password={password}
|
||||
maxScale={6}
|
||||
onError={onError}
|
||||
onPressLink={(uri) => {}}
|
||||
style={{
|
||||
flex: 1,
|
||||
width: width,
|
||||
height: Dimensions.get("window").height
|
||||
}}
|
||||
/>
|
||||
</Animated.View>
|
||||
ref={pdfRef}
|
||||
onLoadComplete={(numberOfPages) => {
|
||||
setNumPages(numberOfPages);
|
||||
}}
|
||||
onPageChanged={(page) => {
|
||||
setCurrentPage(page);
|
||||
inputRef.current?.setNativeProps({
|
||||
text: page + ""
|
||||
});
|
||||
saveSnapshot({
|
||||
currentPage: page,
|
||||
scale: snapshot?.current?.scale
|
||||
});
|
||||
}}
|
||||
// scale={snapshotValue.current?.scale}
|
||||
// onScaleChanged={(scale) => {
|
||||
// saveSnapshot({
|
||||
// currentPage: snapshot?.current?.currentPage,
|
||||
// scale: scale
|
||||
// });
|
||||
// }}
|
||||
page={snapshotValue?.current?.currentPage}
|
||||
password={password}
|
||||
maxScale={6}
|
||||
onError={onError}
|
||||
onPressLink={(uri) => {}}
|
||||
style={{
|
||||
flex: 1,
|
||||
width: width,
|
||||
height: Dimensions.get("window").height
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -30,6 +30,7 @@ import { BackHandler, Platform, ViewProps } from "react-native";
|
||||
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
||||
import Animated, {
|
||||
runOnJS,
|
||||
SharedValue,
|
||||
useAnimatedReaction,
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
@@ -67,6 +68,7 @@ export interface TabsRef {
|
||||
setScrollEnabled: () => true;
|
||||
isDrawerOpen: () => boolean;
|
||||
node: RefObject<Animated.View>;
|
||||
tabChangedFromSwipeAction: SharedValue<boolean>;
|
||||
}
|
||||
|
||||
export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
@@ -116,6 +118,7 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
const isLoaded = useRef(false);
|
||||
const prevWidths = useRef(widths);
|
||||
const isIPhone = Platform.OS === "ios";
|
||||
const tabChangedFromSwipeAction = useSharedValue(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (deviceMode === "tablet" || fullscreen) {
|
||||
@@ -191,6 +194,7 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
: withTiming(editorPosition);
|
||||
currentTab.value = 2;
|
||||
}
|
||||
tabChangedFromSwipeAction.value = false;
|
||||
},
|
||||
goToIndex: (index: number, animated = true) => {
|
||||
if (deviceMode === "tablet") {
|
||||
@@ -210,6 +214,7 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
: editorPosition;
|
||||
currentTab.value = 2;
|
||||
}
|
||||
tabChangedFromSwipeAction.value = false;
|
||||
},
|
||||
unlock: () => {
|
||||
forcedLock.value = false;
|
||||
@@ -252,7 +257,8 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
},
|
||||
page: () => (currentTab.value === 1 ? "home" : "editor"),
|
||||
setScrollEnabled: () => true,
|
||||
node: node
|
||||
node: node,
|
||||
tabChangedFromSwipeAction: tabChangedFromSwipeAction
|
||||
}),
|
||||
[
|
||||
currentTab,
|
||||
@@ -262,7 +268,8 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
homePosition,
|
||||
editorPosition,
|
||||
forcedLock,
|
||||
isDrawerOpen
|
||||
isDrawerOpen,
|
||||
tabChangedFromSwipeAction
|
||||
]
|
||||
);
|
||||
|
||||
@@ -362,18 +369,20 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
isDrawerOpen.value = true;
|
||||
currentTab.value = 1;
|
||||
runOnJS(onDrawerStateChange)(true);
|
||||
tabChangedFromSwipeAction.value = true;
|
||||
return;
|
||||
} else if (!isSwipeLeft && finalValue > 100) {
|
||||
translateX.value = withSpring(homePosition, animationConfig);
|
||||
isDrawerOpen.value = false;
|
||||
currentTab.value = 1;
|
||||
runOnJS(onDrawerStateChange)(false);
|
||||
|
||||
tabChangedFromSwipeAction.value = true;
|
||||
return;
|
||||
} else if (!isSwipeLeft && finalValue < 100) {
|
||||
translateX.value = withSpring(0, animationConfig);
|
||||
isDrawerOpen.value = true;
|
||||
currentTab.value = 1;
|
||||
tabChangedFromSwipeAction.value = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -384,6 +393,8 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
translateX.value = withSpring(editorPosition, animationConfig);
|
||||
currentTab.value = 2;
|
||||
isDrawerOpen.value = false;
|
||||
|
||||
tabChangedFromSwipeAction.value = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -398,6 +409,8 @@ export const FluidPanels = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
translateX.value = withSpring(editorPosition, animationConfig);
|
||||
currentTab.value = 2;
|
||||
}
|
||||
|
||||
tabChangedFromSwipeAction.value = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,24 +17,26 @@ 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 { Reminder } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { notesnook } from "../../../../e2e/test.ids";
|
||||
import { defaultBorderRadius, AppFontSize } from "../../../utils/size";
|
||||
import useIsSelected from "../../../hooks/use-selected";
|
||||
import AddReminder from "../../../screens/add-reminder";
|
||||
import { eSendEvent } from "../../../services/event-manager";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import { eCloseSheet } from "../../../utils/events";
|
||||
import { AppFontSize, defaultBorderRadius } from "../../../utils/size";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
import { Properties } from "../../properties";
|
||||
import ReminderSheet from "../../sheets/reminder";
|
||||
import AppIcon from "../../ui/AppIcon";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import { ReminderTime } from "../../ui/reminder-time";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import SelectionWrapper, { selectItem } from "../selection-wrapper";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import useIsSelected from "../../../hooks/use-selected";
|
||||
import AppIcon from "../../ui/AppIcon";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
|
||||
const ReminderItem = React.memo(
|
||||
({
|
||||
@@ -49,8 +51,10 @@ const ReminderItem = React.memo(
|
||||
const { colors } = useThemeColors();
|
||||
const openReminder = () => {
|
||||
if (selectItem(item)) return;
|
||||
|
||||
ReminderSheet.present(item, undefined, isSheet);
|
||||
AddReminder.present(item, undefined);
|
||||
if (isSheet) {
|
||||
eSendEvent(eCloseSheet);
|
||||
}
|
||||
};
|
||||
const selectionMode = useSelectionStore((state) => state.selectionMode);
|
||||
const [selected] = useIsSelected(item);
|
||||
|
||||
@@ -153,7 +153,6 @@ export const SearchResult = (props: SearchResultProps) => {
|
||||
for (let i = 0; i <= index; i++) {
|
||||
activeIndex += props.item.content[i].length;
|
||||
}
|
||||
console.log(activeIndex);
|
||||
openNote(activeIndex);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -73,9 +73,6 @@ const ColorItem = ({ item, note }: { item: Color; note: Note }) => {
|
||||
testID={notesnook.ids.dialogs.actionsheet.color(item.colorCode)}
|
||||
key={item.id}
|
||||
onPress={toggleColor}
|
||||
onLongPress={(event) => {
|
||||
NativeTooltip.show(event, item.title, NativeTooltip.POSITIONS.TOP);
|
||||
}}
|
||||
style={{
|
||||
width: 35,
|
||||
height: 35,
|
||||
|
||||
@@ -21,13 +21,14 @@ import { useThemeColors } from "@notesnook/theme";
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { FlatList } from "react-native-actions-sheet";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../common/database";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import { eSendEvent, presentSheet } from "../../services/event-manager";
|
||||
import { ColorValues } from "../../utils/colors";
|
||||
import { eOnLoadNote } from "../../utils/events";
|
||||
import { fluidTabsRef } from "../../utils/global-refs";
|
||||
import { AppFontSize } from "../../utils/size";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import SheetProvider from "../sheet-provider";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import { Pressable } from "../ui/pressable";
|
||||
@@ -38,8 +39,6 @@ import { DateMeta } from "./date-meta";
|
||||
import { Items } from "./items";
|
||||
import Notebooks from "./notebooks";
|
||||
import { TagStrip, Tags } from "./tags";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
|
||||
const Line = ({ top = 6, bottom = 6 }) => {
|
||||
const { colors } = useThemeColors();
|
||||
@@ -151,7 +150,8 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
{item.type === "notebook" && item.description ? (
|
||||
{(item.type === "notebook" || item.type === "reminder") &&
|
||||
item.description ? (
|
||||
<Paragraph>{item.description}</Paragraph>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -65,7 +65,9 @@ const COLUMN_BAR_ITEMS: ActionId[] = [
|
||||
"edit-notebook",
|
||||
"move-notes",
|
||||
"move-notebook",
|
||||
"edit-reminder",
|
||||
"pin",
|
||||
"disable-reminder",
|
||||
"default-notebook",
|
||||
"default-tag",
|
||||
"default-homepage",
|
||||
|
||||
@@ -29,6 +29,10 @@ import { ColorTags } from "./color-tags";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import ManageTags from "../../screens/manage-tags";
|
||||
import {
|
||||
paddingTop,
|
||||
paddingVertical
|
||||
} from "deprecated-react-native-prop-types/DeprecatedLayoutPropTypes";
|
||||
|
||||
export const Tags = ({ item, close }) => {
|
||||
const { colors } = useThemeColors();
|
||||
@@ -86,7 +90,6 @@ export const TagStrip = ({ item, close }) => {
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
alignItems: "center",
|
||||
marginTop: DefaultAppStyles.GAP_VERTICAL,
|
||||
gap: 5
|
||||
}}
|
||||
>
|
||||
@@ -107,16 +110,13 @@ const TagItem = ({ tag, close }) => {
|
||||
|
||||
const style = {
|
||||
paddingHorizontal: 0,
|
||||
borderRadius: 100,
|
||||
marginTop: 0,
|
||||
backgroundColor: "transparent"
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
|
||||
};
|
||||
return (
|
||||
<Button
|
||||
onPress={onPress}
|
||||
title={"#" + tag.title}
|
||||
type="plain"
|
||||
height={20}
|
||||
fontSize={AppFontSize.xs}
|
||||
style={style}
|
||||
textStyle={{
|
||||
|
||||
@@ -197,16 +197,6 @@ export const SelectionHeader = React.memo(
|
||||
}
|
||||
]
|
||||
: [
|
||||
{
|
||||
title: strings.move(),
|
||||
onPress: async () => {
|
||||
const ids = selectedItemsList;
|
||||
const notebooks = await db.notebooks.all.items(ids);
|
||||
MoveNotebook.present(notebooks);
|
||||
},
|
||||
visible: renderedInRoute === "Notebooks",
|
||||
icon: "arrow-right-bold-box-outline"
|
||||
},
|
||||
{
|
||||
title: strings.manageTags(),
|
||||
onPress: async () => {
|
||||
|
||||
@@ -95,8 +95,6 @@ const SheetProvider = ({ context = "global" }) => {
|
||||
[context]
|
||||
);
|
||||
|
||||
console.log(data?.keyboardHandlerDisabled);
|
||||
|
||||
return !visible || !data ? null : (
|
||||
<SheetWrapper
|
||||
fwdRef={actionSheetRef}
|
||||
|
||||
@@ -310,6 +310,7 @@ const ExportNotesSheet = ({
|
||||
ToastManager.error(e as Error);
|
||||
});
|
||||
} else {
|
||||
await sleep(500);
|
||||
FileViewer.open(result?.filePath, {
|
||||
showOpenWithDialog: true,
|
||||
showAppsSuggestions: true
|
||||
@@ -338,6 +339,7 @@ const ExportNotesSheet = ({
|
||||
.getState()
|
||||
.setAppDidEnterBackgroundForAction(true);
|
||||
if (Platform.OS === "ios") {
|
||||
await sleep(500);
|
||||
Share.open({
|
||||
url: result?.fileDir + result.fileName
|
||||
}).catch(() => {
|
||||
|
||||
@@ -38,10 +38,9 @@ import { eCloseSheet, eOnNotebookUpdated } from "../../../utils/events";
|
||||
import { AppFontSize } from "../../../utils/size";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
import { sleep } from "../../../utils/time";
|
||||
import { Dialog } from "../../dialog";
|
||||
import { Properties } from "../../properties";
|
||||
import SheetProvider from "../../sheet-provider";
|
||||
import { NotebookItem } from "../../side-menu/notebook-item";
|
||||
import { useSideMenuNotebookTreeStore } from "../../side-menu/stores";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { AddNotebookSheet } from "../add-notebook";
|
||||
@@ -60,13 +59,8 @@ export const Notebooks = (props: {
|
||||
close?: (ctx?: string) => void;
|
||||
}) => {
|
||||
const tree = useNotebookTreeStore((state) => state.tree);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const { colors } = useThemeColors();
|
||||
const [notebooks, setNotebooks] = useState<Notebook[]>();
|
||||
const [filteredNotebooks, setFilteredNotebooks] =
|
||||
React.useState<VirtualizedGrouping<Notebook>>();
|
||||
const searchTimer = React.useRef<NodeJS.Timeout>();
|
||||
const lastQuery = React.useRef<string>();
|
||||
const loadRootNotebooks = React.useCallback(async () => {
|
||||
const notebooks = await db.relations
|
||||
.from(
|
||||
@@ -95,7 +89,6 @@ export const Notebooks = (props: {
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
loadRootNotebooks();
|
||||
setIsLoading(false);
|
||||
})();
|
||||
}, [loadRootNotebooks]);
|
||||
|
||||
@@ -151,7 +144,7 @@ export const Notebooks = (props: {
|
||||
height: 400
|
||||
}}
|
||||
>
|
||||
<SheetProvider context="local" />
|
||||
<Dialog context="local" />
|
||||
|
||||
<View
|
||||
style={{
|
||||
@@ -175,7 +168,7 @@ export const Notebooks = (props: {
|
||||
}}
|
||||
name="plus"
|
||||
onPress={() => {
|
||||
AddNotebookSheet.present(props.rootNotebook, undefined, "local");
|
||||
AddNotebookSheet.present(undefined, props.rootNotebook, "local");
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
@@ -244,20 +237,18 @@ const NotebookItemWrapper = React.memo(
|
||||
const onItemUpdate = React.useCallback(async () => {
|
||||
const notebook = await db.notebooks.notebook(item.notebook.id);
|
||||
if (notebook) {
|
||||
useSideMenuNotebookTreeStore
|
||||
.getState()
|
||||
.updateItem(item.notebook.id, notebook);
|
||||
useNotebookTreeStore.getState().updateItem(item.notebook.id, notebook);
|
||||
if (expanded) {
|
||||
useSideMenuNotebookTreeStore
|
||||
useNotebookTreeStore
|
||||
.getState()
|
||||
.setTree(
|
||||
await useSideMenuNotebookTreeStore
|
||||
await useNotebookTreeStore
|
||||
.getState()
|
||||
.fetchAndAdd(item.notebook.id, item.depth + 1)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
useSideMenuNotebookTreeStore.getState().removeItem(item.notebook.id);
|
||||
useNotebookTreeStore.getState().removeItem(item.notebook.id);
|
||||
}
|
||||
}, [expanded, item.depth, item.notebook.id]);
|
||||
|
||||
@@ -274,17 +265,15 @@ const NotebookItemWrapper = React.memo(
|
||||
onToggleExpanded={async () => {
|
||||
useNotebookExpandedStore.getState().setExpanded(item.notebook.id);
|
||||
if (!expanded) {
|
||||
useSideMenuNotebookTreeStore
|
||||
useNotebookTreeStore
|
||||
.getState()
|
||||
.setTree(
|
||||
await useSideMenuNotebookTreeStore
|
||||
await useNotebookTreeStore
|
||||
.getState()
|
||||
.fetchAndAdd(item.notebook.id, item.depth + 1)
|
||||
);
|
||||
} else {
|
||||
useSideMenuNotebookTreeStore
|
||||
.getState()
|
||||
.removeChildren(item.notebook.id);
|
||||
useNotebookTreeStore.getState().removeChildren(item.notebook.id);
|
||||
}
|
||||
}}
|
||||
selected={selected}
|
||||
|
||||
@@ -34,7 +34,7 @@ import { AppFontSize } from "../../../utils/size";
|
||||
import DialogHeader from "../../dialog/dialog-header";
|
||||
import List from "../../list";
|
||||
import SheetProvider from "../../sheet-provider";
|
||||
import { Button } from "../../ui/button";
|
||||
import { Button, ButtonProps } from "../../ui/button";
|
||||
import { PressableProps } from "../../ui/pressable";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
@@ -47,18 +47,10 @@ type RelationsListProps = {
|
||||
referenceType: string;
|
||||
relationType: "to" | "from";
|
||||
title: string;
|
||||
button?: Button;
|
||||
button?: ButtonProps;
|
||||
onAdd: () => void;
|
||||
};
|
||||
|
||||
type Button = {
|
||||
onPress?: (() => void) | undefined;
|
||||
loading?: boolean | undefined;
|
||||
title?: string | undefined;
|
||||
type?: PressableProps["type"];
|
||||
icon?: string;
|
||||
};
|
||||
|
||||
const IconsByType = {
|
||||
reminder: "bell"
|
||||
};
|
||||
@@ -154,7 +146,7 @@ RelationsList.present = ({
|
||||
referenceType: string;
|
||||
relationType: "to" | "from";
|
||||
title: string;
|
||||
button?: Button;
|
||||
button?: ButtonProps;
|
||||
onAdd: () => void;
|
||||
}) => {
|
||||
presentSheet({
|
||||
|
||||
@@ -1,630 +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 { useThemeColors } from "@notesnook/theme";
|
||||
import React, { RefObject, useRef, useState } from "react";
|
||||
import {
|
||||
Platform,
|
||||
TextInput,
|
||||
View,
|
||||
ScrollView as RNScrollView
|
||||
} from "react-native";
|
||||
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
|
||||
import DateTimePickerModal from "react-native-modal-datetime-picker";
|
||||
import {
|
||||
PresentSheetOptions,
|
||||
ToastManager,
|
||||
presentSheet
|
||||
} from "../../../services/event-manager";
|
||||
import { defaultBorderRadius, AppFontSize } from "../../../utils/size";
|
||||
import { Button } from "../../ui/button";
|
||||
import Input from "../../ui/input";
|
||||
|
||||
import dayjs from "dayjs";
|
||||
import DatePicker from "react-native-date-picker";
|
||||
import { db } from "../../../common/database";
|
||||
import { DDS } from "../../../services/device-detection";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import Notifications from "../../../services/notifications";
|
||||
import PremiumService from "../../../services/premium";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import { useRelationStore } from "../../../stores/use-relation-store";
|
||||
import { Dialog } from "../../dialog";
|
||||
import { ReminderTime } from "../../ui/reminder-time";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { Note, Reminder } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
|
||||
type ReminderSheetProps = {
|
||||
actionSheetRef: RefObject<ActionSheetRef>;
|
||||
close?: (ctx?: string) => void;
|
||||
update?: (options: PresentSheetOptions) => void;
|
||||
reminder?: Reminder;
|
||||
reference?: Note;
|
||||
};
|
||||
|
||||
const ReminderModes =
|
||||
Platform.OS === "ios"
|
||||
? {
|
||||
Once: "once",
|
||||
Repeat: "repeat"
|
||||
}
|
||||
: {
|
||||
Once: "once",
|
||||
Repeat: "repeat",
|
||||
Permanent: "permanent"
|
||||
};
|
||||
|
||||
const RecurringModes = {
|
||||
Daily: "day",
|
||||
Week: "week",
|
||||
Month: "month",
|
||||
Year: "year"
|
||||
};
|
||||
|
||||
const WeekDays = new Array(7).fill(true);
|
||||
const MonthDays = new Array(31).fill(true);
|
||||
const WeekDayNames = {
|
||||
0: "Sunday",
|
||||
1: "Monday",
|
||||
2: "Tuesday",
|
||||
3: "Wednesday",
|
||||
4: "Thursday",
|
||||
5: "Friday",
|
||||
6: "Saturday"
|
||||
};
|
||||
|
||||
const ReminderNotificationModes = {
|
||||
Silent: "silent",
|
||||
Vibrate: "vibrate",
|
||||
Urgent: "urgent"
|
||||
};
|
||||
|
||||
export default function ReminderSheet({
|
||||
actionSheetRef,
|
||||
close,
|
||||
update,
|
||||
reminder,
|
||||
reference
|
||||
}: ReminderSheetProps) {
|
||||
const { colors, isDark } = useThemeColors();
|
||||
const [reminderMode, setReminderMode] = useState<Reminder["mode"]>(
|
||||
reminder?.mode || "once"
|
||||
);
|
||||
const [recurringMode, setRecurringMode] = useState<Reminder["recurringMode"]>(
|
||||
reminder?.recurringMode || "week"
|
||||
);
|
||||
const [selectedDays, setSelectedDays] = useState<number[]>(
|
||||
reminder?.selectedDays || []
|
||||
);
|
||||
const [date, setDate] = useState<Date>(
|
||||
new Date(reminder?.date || Date.now())
|
||||
);
|
||||
const [reminderNotificationMode, setReminderNotificatioMode] = useState<
|
||||
Reminder["priority"]
|
||||
>(reminder?.priority || SettingsService.get().reminderNotificationMode);
|
||||
const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
|
||||
const [repeatFrequency, setRepeatFrequency] = useState(1);
|
||||
const referencedItem = reference ? (reference as Note) : null;
|
||||
|
||||
const title = useRef<string | undefined>(
|
||||
!reminder ? referencedItem?.title : reminder?.title
|
||||
);
|
||||
const details = useRef<string | undefined>(
|
||||
!reminder ? referencedItem?.headline : reminder?.description
|
||||
);
|
||||
const titleRef = useRef<TextInput>(null);
|
||||
const timer = useRef<NodeJS.Timeout>();
|
||||
|
||||
const showDatePicker = () => {
|
||||
setDatePickerVisibility(true);
|
||||
};
|
||||
|
||||
const hideDatePicker = () => {
|
||||
setDatePickerVisibility(false);
|
||||
};
|
||||
|
||||
const handleConfirm = (date: Date) => {
|
||||
timer.current = setTimeout(() => {
|
||||
hideDatePicker();
|
||||
setDate(date);
|
||||
}, 50);
|
||||
};
|
||||
function nth(n: number) {
|
||||
return (
|
||||
["st", "nd", "rd"][(((((n < 0 ? -n : n) + 90) % 100) - 10) % 10) - 1] ||
|
||||
"th"
|
||||
);
|
||||
}
|
||||
|
||||
function getSelectedDaysText(selectedDays: number[]) {
|
||||
const text = selectedDays
|
||||
.sort((a, b) => a - b)
|
||||
.map((day, index) => {
|
||||
const isLast = index === selectedDays.length - 1;
|
||||
const isSecondLast = index === selectedDays.length - 2;
|
||||
const joinWith = isSecondLast ? " & " : isLast ? "" : ", ";
|
||||
return recurringMode === RecurringModes.Week
|
||||
? WeekDayNames[day as keyof typeof WeekDayNames] + joinWith
|
||||
: `${day}${nth(day)} ${joinWith}`;
|
||||
})
|
||||
.join("");
|
||||
return text;
|
||||
}
|
||||
|
||||
async function saveReminder() {
|
||||
try {
|
||||
if (!(await Notifications.checkAndRequestPermissions(true)))
|
||||
throw new Error(strings.noNotificationPermission());
|
||||
if (!date && reminderMode !== ReminderModes.Permanent) return;
|
||||
if (
|
||||
reminderMode === ReminderModes.Repeat &&
|
||||
recurringMode !== "day" &&
|
||||
recurringMode !== "year" &&
|
||||
selectedDays.length === 0
|
||||
)
|
||||
throw new Error(strings.selectDayError());
|
||||
|
||||
if (!title.current) throw new Error(strings.setTitleError());
|
||||
if (date.getTime() < Date.now() && reminderMode === "once") {
|
||||
titleRef?.current?.focus();
|
||||
throw new Error(strings.dateError());
|
||||
}
|
||||
|
||||
date.setSeconds(0, 0);
|
||||
|
||||
const reminderId = await db.reminders?.add({
|
||||
id: reminder?.id,
|
||||
date: date?.getTime(),
|
||||
priority: reminderNotificationMode,
|
||||
title: title.current,
|
||||
description: details.current,
|
||||
recurringMode: recurringMode,
|
||||
selectedDays: selectedDays,
|
||||
mode: reminderMode,
|
||||
localOnly: reminderMode === "permanent",
|
||||
snoozeUntil:
|
||||
date?.getTime() > Date.now() ? undefined : reminder?.snoozeUntil,
|
||||
disabled: false
|
||||
});
|
||||
if (!reminderId) return;
|
||||
const _reminder = await db.reminders?.reminder(reminderId);
|
||||
|
||||
if (reference && _reminder) {
|
||||
await db.relations?.add(reference, {
|
||||
id: _reminder?.id as string,
|
||||
type: _reminder?.type
|
||||
});
|
||||
}
|
||||
Notifications.scheduleNotification(_reminder as Reminder);
|
||||
Navigation.queueRoutesForUpdate();
|
||||
useRelationStore.getState().update();
|
||||
close?.();
|
||||
} catch (e) {
|
||||
ToastManager.error(e as Error, undefined, "local");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: DefaultAppStyles.GAP,
|
||||
maxHeight: "100%"
|
||||
}}
|
||||
>
|
||||
<Heading size={AppFontSize.lg}>
|
||||
{reminder ? strings.editReminder() : strings.newReminder()}
|
||||
</Heading>
|
||||
|
||||
<Dialog context="local" />
|
||||
<ScrollView
|
||||
bounces={false}
|
||||
style={{
|
||||
marginBottom: DDS.isTab ? 25 : undefined
|
||||
}}
|
||||
>
|
||||
<Input
|
||||
fwdRef={titleRef}
|
||||
defaultValue={reminder?.title || referencedItem?.title}
|
||||
placeholder={strings.remindeMeOf()}
|
||||
onChangeText={(text) => (title.current = text)}
|
||||
wrapperStyle={{
|
||||
marginTop: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
/>
|
||||
|
||||
<Input
|
||||
defaultValue={
|
||||
reminder ? reminder?.description : referencedItem?.headline
|
||||
}
|
||||
placeholder={strings.addShortNote()}
|
||||
onChangeText={(text) => (details.current = text)}
|
||||
containerStyle={{
|
||||
maxHeight: 80
|
||||
}}
|
||||
multiline
|
||||
textAlignVertical="top"
|
||||
inputStyle={{
|
||||
minHeight: 80,
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
height={80}
|
||||
wrapperStyle={{
|
||||
marginBottom: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
/>
|
||||
|
||||
<ScrollView
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
marginBottom: DefaultAppStyles.GAP_VERTICAL,
|
||||
height: 50
|
||||
}}
|
||||
horizontal
|
||||
>
|
||||
{Object.keys(ReminderModes).map((mode) => (
|
||||
<Button
|
||||
key={mode}
|
||||
title={strings.reminderModes(
|
||||
ReminderModes[mode as keyof typeof ReminderModes] as string
|
||||
)}
|
||||
style={{
|
||||
marginRight: 12,
|
||||
borderRadius: 100,
|
||||
minWidth: 70,
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
|
||||
}}
|
||||
proTag={mode === "Repeat"}
|
||||
height={35}
|
||||
type={
|
||||
reminderMode ===
|
||||
ReminderModes[mode as keyof typeof ReminderModes]
|
||||
? "selected"
|
||||
: "plain"
|
||||
}
|
||||
onPress={() => {
|
||||
if (mode === "Repeat" && !PremiumService.get()) return;
|
||||
setReminderMode(
|
||||
ReminderModes[
|
||||
mode as keyof typeof ReminderModes
|
||||
] as Reminder["mode"]
|
||||
);
|
||||
if (mode === "Repeat") {
|
||||
setSelectedDays((days) => {
|
||||
if (days.length > 0) return days;
|
||||
if (days.indexOf(date.getDay()) > -1) {
|
||||
return days;
|
||||
}
|
||||
days.push(date.getDay());
|
||||
return [...days];
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</ScrollView>
|
||||
|
||||
{reminderMode === ReminderModes.Repeat ? (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: colors.secondary.background,
|
||||
padding: DefaultAppStyles.GAP,
|
||||
borderRadius: defaultBorderRadius,
|
||||
marginBottom: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
marginBottom:
|
||||
recurringMode === "day" || recurringMode === "year" ? 0 : 12,
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{Object.keys(RecurringModes).map((mode) => (
|
||||
<Button
|
||||
key={mode}
|
||||
title={strings.recurringModes(
|
||||
RecurringModes[mode as keyof typeof RecurringModes]
|
||||
)}
|
||||
style={{
|
||||
marginRight: 6,
|
||||
borderRadius: 100,
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
|
||||
}}
|
||||
type={
|
||||
recurringMode ===
|
||||
RecurringModes[mode as keyof typeof RecurringModes]
|
||||
? "selected"
|
||||
: "plain"
|
||||
}
|
||||
onPress={() => {
|
||||
setRecurringMode(
|
||||
RecurringModes[
|
||||
mode as keyof typeof RecurringModes
|
||||
] as Reminder["recurringMode"]
|
||||
);
|
||||
setSelectedDays([]);
|
||||
setRepeatFrequency(1);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<RNScrollView showsHorizontalScrollIndicator={false} horizontal>
|
||||
{recurringMode === RecurringModes.Daily ||
|
||||
recurringMode === RecurringModes.Year
|
||||
? null
|
||||
: recurringMode === RecurringModes.Week
|
||||
? WeekDays.map((item, index) => (
|
||||
<Button
|
||||
key={strings.weekDayNamesShort[
|
||||
index as keyof typeof strings.weekDayNamesShort
|
||||
]()}
|
||||
title={strings.weekDayNamesShort[
|
||||
index as keyof typeof strings.weekDayNamesShort
|
||||
]()}
|
||||
type={
|
||||
selectedDays.indexOf(index) > -1 ? "selected" : "plain"
|
||||
}
|
||||
fontSize={AppFontSize.xs}
|
||||
style={{
|
||||
height: 40,
|
||||
borderRadius: 100,
|
||||
marginRight: 10
|
||||
}}
|
||||
onPress={() => {
|
||||
setSelectedDays((days) => {
|
||||
if (days.indexOf(index) > -1) {
|
||||
days.splice(days.indexOf(index), 1);
|
||||
return [...days];
|
||||
}
|
||||
days.push(index);
|
||||
return [...days];
|
||||
});
|
||||
}}
|
||||
/>
|
||||
))
|
||||
: MonthDays.map((item, index) => (
|
||||
<Button
|
||||
key={index + "monthday"}
|
||||
title={index + 1 + ""}
|
||||
type={
|
||||
selectedDays.indexOf(index + 1) > -1
|
||||
? "selected"
|
||||
: "plain"
|
||||
}
|
||||
fontSize={AppFontSize.xs}
|
||||
style={{
|
||||
height: 40,
|
||||
borderRadius: 100,
|
||||
marginRight: 10
|
||||
}}
|
||||
onPress={() => {
|
||||
setSelectedDays((days) => {
|
||||
if (days.indexOf(index + 1) > -1) {
|
||||
days.splice(days.indexOf(index + 1), 1);
|
||||
return [...days];
|
||||
}
|
||||
days.push(index + 1);
|
||||
return [...days];
|
||||
});
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</RNScrollView>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{reminderMode === ReminderModes.Permanent ? null : (
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
marginBottom: DefaultAppStyles.GAP_VERTICAL,
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<DateTimePickerModal
|
||||
isVisible={isDatePickerVisible}
|
||||
mode="date"
|
||||
onConfirm={handleConfirm}
|
||||
onCancel={hideDatePicker}
|
||||
is24Hour={db.settings.getTimeFormat() === "24-hour"}
|
||||
date={date || new Date(Date.now())}
|
||||
/>
|
||||
|
||||
<DatePicker
|
||||
date={date}
|
||||
maximumDate={dayjs(date).add(3, "months").toDate()}
|
||||
onDateChange={handleConfirm}
|
||||
textColor={isDark ? colors.static.white : colors.static.black}
|
||||
fadeToColor={colors.primary.background}
|
||||
theme={isDark ? "dark" : "light"}
|
||||
androidVariant="nativeAndroid"
|
||||
is24hourSource="locale"
|
||||
locale={
|
||||
db.settings?.getTimeFormat() === "24-hour" ? "en_GB" : "en_US"
|
||||
}
|
||||
mode={
|
||||
reminderMode === ReminderModes.Repeat &&
|
||||
recurringMode !== "year"
|
||||
? "time"
|
||||
: "datetime"
|
||||
}
|
||||
/>
|
||||
|
||||
{reminderMode === ReminderModes.Repeat ? null : (
|
||||
<Button
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
title={date ? date.toLocaleDateString() : strings.selectDate()}
|
||||
type={date ? "secondaryAccented" : "secondary"}
|
||||
icon="calendar"
|
||||
fontSize={AppFontSize.sm}
|
||||
onPress={() => {
|
||||
showDatePicker();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{reminderMode === ReminderModes.Once ||
|
||||
reminderMode === ReminderModes.Permanent ? null : (
|
||||
<View
|
||||
style={{
|
||||
borderRadius: defaultBorderRadius,
|
||||
flexDirection: "row",
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
marginBottom: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<Paragraph
|
||||
size={AppFontSize.xxs}
|
||||
color={colors.secondary.paragraph}
|
||||
>
|
||||
{recurringMode === RecurringModes.Daily
|
||||
? strings.reminderRepeatStrings.day(
|
||||
dayjs(date).format("hh:mm A")
|
||||
)
|
||||
: recurringMode === RecurringModes.Year
|
||||
? strings.reminderRepeatStrings.year(
|
||||
dayjs(date).format("dddd, MMMM D, h:mm A")
|
||||
)
|
||||
: selectedDays.length === 7 &&
|
||||
recurringMode === RecurringModes.Week
|
||||
? strings.reminderRepeatStrings.week.daily(
|
||||
dayjs(date).format("hh:mm A")
|
||||
)
|
||||
: selectedDays.length === 0
|
||||
? strings.reminderRepeatStrings[
|
||||
recurringMode as "week" | "month"
|
||||
].selectDays()
|
||||
: strings.reminderRepeatStrings.repeats(
|
||||
repeatFrequency,
|
||||
recurringMode as string,
|
||||
getSelectedDaysText(selectedDays),
|
||||
dayjs(date).format("hh:mm A")
|
||||
)}
|
||||
</Paragraph>
|
||||
</>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<ReminderTime
|
||||
reminder={reminder}
|
||||
style={{
|
||||
width: "100%",
|
||||
justifyContent: "flex-start",
|
||||
borderWidth: 0,
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
|
||||
alignSelf: "flex-start"
|
||||
}}
|
||||
/>
|
||||
|
||||
{reminderMode === ReminderModes.Permanent ? null : (
|
||||
<RNScrollView
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
marginTop: DefaultAppStyles.GAP_VERTICAL,
|
||||
height: 50
|
||||
}}
|
||||
horizontal
|
||||
>
|
||||
{Object.keys(ReminderNotificationModes).map((mode) => (
|
||||
<Button
|
||||
key={mode}
|
||||
title={strings.reminderNotificationModes(
|
||||
mode as keyof typeof ReminderNotificationModes
|
||||
)}
|
||||
style={{
|
||||
marginRight: 12,
|
||||
borderRadius: 100,
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
|
||||
}}
|
||||
icon={
|
||||
mode === "Silent"
|
||||
? "minus-circle"
|
||||
: mode === "Vibrate"
|
||||
? "vibrate"
|
||||
: "volume-high"
|
||||
}
|
||||
height={35}
|
||||
type={
|
||||
reminderNotificationMode ===
|
||||
ReminderNotificationModes[
|
||||
mode as keyof typeof ReminderNotificationModes
|
||||
]
|
||||
? "selected"
|
||||
: "plain"
|
||||
}
|
||||
onPress={() => {
|
||||
const _mode = ReminderNotificationModes[
|
||||
mode as keyof typeof ReminderNotificationModes
|
||||
] as Reminder["priority"];
|
||||
SettingsService.set({
|
||||
reminderNotificationMode: _mode
|
||||
});
|
||||
setReminderNotificatioMode(_mode);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</RNScrollView>
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
<Button
|
||||
title={strings.save()}
|
||||
type="accent"
|
||||
style={{
|
||||
paddingHorizontal: DefaultAppStyles.GAP * 2,
|
||||
marginTop: DefaultAppStyles.GAP_VERTICAL,
|
||||
width: "100%"
|
||||
}}
|
||||
onPress={saveReminder}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
ReminderSheet.present = (
|
||||
reminder?: Reminder,
|
||||
reference?: Note,
|
||||
isSheet?: boolean
|
||||
) => {
|
||||
presentSheet({
|
||||
context: isSheet ? "local" : undefined,
|
||||
component: (ref, close, update) => (
|
||||
<ReminderSheet
|
||||
actionSheetRef={ref}
|
||||
close={close}
|
||||
update={update}
|
||||
reminder={reminder}
|
||||
reference={reference}
|
||||
/>
|
||||
)
|
||||
});
|
||||
};
|
||||
@@ -40,6 +40,7 @@ import Sync from "../../../services/sync";
|
||||
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import { logoutUser } from "../../../screens/settings/logout";
|
||||
import { sleep } from "../../../utils/time";
|
||||
export const UserSheet = () => {
|
||||
const ref = useSheetRef();
|
||||
const { colors } = useThemeColors();
|
||||
@@ -310,10 +311,10 @@ export const UserSheet = () => {
|
||||
{
|
||||
icon: "logout",
|
||||
title: strings.logout(),
|
||||
onPress: () => {
|
||||
console.log("logout");
|
||||
logoutUser();
|
||||
onPress: async () => {
|
||||
ref.current?.hide();
|
||||
await sleep(300);
|
||||
logoutUser();
|
||||
},
|
||||
hidden: !user
|
||||
}
|
||||
|
||||
@@ -211,6 +211,14 @@ const TabBar = (
|
||||
const ids = useSideMenuNotebookSelectionStore
|
||||
.getState()
|
||||
.getSelectedItemIds();
|
||||
if (!ids.length) {
|
||||
ToastManager.show({
|
||||
context: "local",
|
||||
type: "error",
|
||||
message: strings.noNotebooksSelectedToMove()
|
||||
});
|
||||
return;
|
||||
}
|
||||
const notebooks = await db.notebooks.all.items(ids);
|
||||
Navigation.navigate("MoveNotebook", {
|
||||
selectedNotebooks: notebooks
|
||||
|
||||
@@ -49,7 +49,8 @@ export const NotebookItem = ({
|
||||
onPress,
|
||||
onLongPress,
|
||||
onAddNotebook,
|
||||
canDisableSelectionMode
|
||||
canDisableSelectionMode,
|
||||
disableExpand
|
||||
}: {
|
||||
index: number;
|
||||
item: TreeItem;
|
||||
@@ -64,6 +65,7 @@ export const NotebookItem = ({
|
||||
onLongPress?: () => void;
|
||||
onAddNotebook?: () => void;
|
||||
canDisableSelectionMode?: boolean;
|
||||
disableExpand?: boolean;
|
||||
}) => {
|
||||
const notebook = item.notebook;
|
||||
const isFocused = focused;
|
||||
@@ -166,7 +168,7 @@ export const NotebookItem = ({
|
||||
}
|
||||
testID={item.hasChildren ? `expand-notebook-${index}` : ""}
|
||||
onPress={() => {
|
||||
if (item.hasChildren) {
|
||||
if (item.hasChildren && !disableExpand) {
|
||||
onToggleExpanded?.();
|
||||
} else {
|
||||
onPress?.();
|
||||
@@ -182,7 +184,7 @@ export const NotebookItem = ({
|
||||
borderRadius: defaultBorderRadius
|
||||
}}
|
||||
name={
|
||||
!item.hasChildren
|
||||
!item.hasChildren || disableExpand
|
||||
? "book-outline"
|
||||
: expanded
|
||||
? "chevron-down"
|
||||
|
||||
@@ -20,8 +20,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { Notebook } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { FlashList } from "@shopify/flash-list";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { FlatList, TextInput, View } from "react-native";
|
||||
import { TextInput, View } from "react-native";
|
||||
import { db } from "../../common/database";
|
||||
import NotebookScreen from "../../screens/notebook";
|
||||
import Navigation from "../../services/navigation";
|
||||
@@ -42,6 +43,7 @@ import {
|
||||
useSideMenuNotebookSelectionStore.setState({
|
||||
multiSelect: true
|
||||
});
|
||||
|
||||
export const SideMenuNotebooks = () => {
|
||||
const tree = useSideMenuNotebookTreeStore((state) => state.tree);
|
||||
const [notebooks, loading] = useNotebooks();
|
||||
@@ -64,13 +66,19 @@ export const SideMenuNotebooks = () => {
|
||||
|
||||
const updateNotebooks = React.useCallback(() => {
|
||||
if (lastQuery.current) {
|
||||
// useSideMenuNotebookTreeStore.setState({
|
||||
// isSearching: true
|
||||
// });
|
||||
db.lookup
|
||||
.notebooks(lastQuery.current)
|
||||
.sorted()
|
||||
.sorted(db.settings.getGroupOptions("notebooks"))
|
||||
.then((filtered) => {
|
||||
setFilteredNotebooks(filtered);
|
||||
});
|
||||
} else {
|
||||
// useSideMenuNotebookTreeStore.setState({
|
||||
// isSearching: false
|
||||
// });
|
||||
setFilteredNotebooks(notebooks);
|
||||
}
|
||||
}, [notebooks]);
|
||||
@@ -137,13 +145,13 @@ export const SideMenuNotebooks = () => {
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<FlatList
|
||||
<FlashList
|
||||
data={tree}
|
||||
bounces={false}
|
||||
bouncesZoom={false}
|
||||
overScrollMode="never"
|
||||
keyExtractor={(item) => item.notebook.id}
|
||||
windowSize={3}
|
||||
// keyExtractor={(item,) => item.notebook.id}
|
||||
estimatedItemSize={30}
|
||||
ListHeaderComponent={
|
||||
<View
|
||||
style={{
|
||||
@@ -196,7 +204,9 @@ const NotebookItemWrapper = React.memo(
|
||||
const expanded = useSideMenuNotebookExpandedStore(
|
||||
(state) => state.expanded[item.notebook.id]
|
||||
);
|
||||
|
||||
const disableExpand = useSideMenuNotebookTreeStore(
|
||||
(state) => state.isSearching
|
||||
);
|
||||
const selectionEnabled = useSideMenuNotebookSelectionStore(
|
||||
(state) => state.enabled
|
||||
);
|
||||
@@ -256,6 +266,7 @@ const NotebookItemWrapper = React.memo(
|
||||
.removeChildren(item.notebook.id);
|
||||
}
|
||||
}}
|
||||
disableExpand={disableExpand}
|
||||
selected={selected}
|
||||
selectionEnabled={selectionEnabled}
|
||||
selectionStore={useSideMenuNotebookSelectionStore}
|
||||
|
||||
@@ -220,7 +220,7 @@ export const SideMenuTags = () => {
|
||||
if (lastQuery.current) {
|
||||
db.lookup
|
||||
.tags(lastQuery.current.trim())
|
||||
.sorted()
|
||||
.sorted(db.settings.getGroupOptions("tags"))
|
||||
.then(async (filtered) => {
|
||||
setFilteredTags(filtered);
|
||||
});
|
||||
|
||||
@@ -154,7 +154,7 @@ const Input = ({
|
||||
isError = customValidator && value === customValidator();
|
||||
break;
|
||||
case "url":
|
||||
isError = isURL(value);
|
||||
isError = isURL(value, { allow_underscores: true });
|
||||
break;
|
||||
case "phonenumber": {
|
||||
const result = phone(value, {
|
||||
|
||||
@@ -54,6 +54,7 @@ type ButtonTypes =
|
||||
| "accent"
|
||||
| "shade"
|
||||
| "secondary"
|
||||
| "selectedAccent"
|
||||
| "secondaryAccented"
|
||||
| "inverted"
|
||||
| "white"
|
||||
@@ -126,6 +127,18 @@ const buttonTypes = (
|
||||
isDark
|
||||
)
|
||||
},
|
||||
selectedAccent: {
|
||||
primary: colors.selected.accent,
|
||||
text: colors.selected.accentForeground,
|
||||
selected: colors.selected.accent,
|
||||
borderWidth: 0.8,
|
||||
borderColor: getColorLinearShade(colors.selected.accent, 0.05, isDark),
|
||||
borderSelectedColor: getColorLinearShade(
|
||||
colors.selected.accent,
|
||||
0.05,
|
||||
isDark
|
||||
)
|
||||
},
|
||||
secondaryAccented: {
|
||||
primary: colors.secondary.background,
|
||||
text: colors.primary.accent,
|
||||
|
||||
@@ -23,13 +23,11 @@ import { Platform, View } from "react-native";
|
||||
import ActionSheet from "react-native-actions-sheet";
|
||||
import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
import { PremiumToast } from "../../premium/premium-toast";
|
||||
import { Toast } from "../../toast";
|
||||
import { useAppState } from "../../../hooks/use-app-state";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import { useUserStore } from "../../../stores/use-user-store";
|
||||
import { getContainerBorder } from "../../../utils/colors";
|
||||
|
||||
import { PremiumToast } from "../../premium/premium-toast";
|
||||
import { Toast } from "../../toast";
|
||||
import { NotesnookModule } from "../../../utils/notesnook-module";
|
||||
/**
|
||||
*
|
||||
* @param {any} param0
|
||||
@@ -63,6 +61,8 @@ const SheetWrapper = ({
|
||||
const locked = useUserStore((state) => state.appLocked);
|
||||
|
||||
let width = dimensions.width > 600 ? 600 : 500;
|
||||
const isGestureNavigationEnabled =
|
||||
NotesnookModule.isGestureNavigationEnabled();
|
||||
|
||||
const style = React.useMemo(() => {
|
||||
return {
|
||||
@@ -77,14 +77,18 @@ const SheetWrapper = ({
|
||||
borderBottomRightRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
...getContainerBorder(colors.primary.border, 0.5),
|
||||
borderBottomWidth: 0
|
||||
borderBottomWidth: 0,
|
||||
paddingBottom: isGestureNavigationEnabled
|
||||
? insets.bottom
|
||||
: insets.bottom || 48
|
||||
};
|
||||
}, [
|
||||
colors.primary.background,
|
||||
colors.primary.border,
|
||||
largeTablet,
|
||||
smallTablet,
|
||||
width
|
||||
width,
|
||||
insets.bottom
|
||||
]);
|
||||
|
||||
const _onOpen = () => {
|
||||
|
||||
@@ -336,7 +336,7 @@ const emailconfirmed: { id: string; steps: TStep[] } = {
|
||||
steps: [
|
||||
{
|
||||
title: strings.emailConfirmed(),
|
||||
text: strings.emailNotConfirmedDesc(),
|
||||
text: strings.emailConfirmedDesc(),
|
||||
walkthroughItem: (colors) => (
|
||||
<SvgView src={WELCOME_SVG(colors.primary.paragraph)} />
|
||||
),
|
||||
|
||||
@@ -19,9 +19,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { FeatureType } from "./components/sheets/new-feature";
|
||||
|
||||
export const features: FeatureType[] = [
|
||||
{
|
||||
title: "Improved Search",
|
||||
body: "Improved search results with better relevance and highlighting."
|
||||
}
|
||||
];
|
||||
export const features: FeatureType[] = [];
|
||||
|
||||
@@ -41,7 +41,6 @@ import ExportNotesSheet from "../components/sheets/export-notes";
|
||||
import PublishNoteSheet from "../components/sheets/publish-note";
|
||||
import { ReferencesList } from "../components/sheets/references";
|
||||
import { RelationsList } from "../components/sheets/relations-list/index";
|
||||
import ReminderSheet from "../components/sheets/reminder";
|
||||
import { useSideBarDraggingStore } from "../components/side-menu/dragging-store";
|
||||
import { ButtonProps } from "../components/ui/button";
|
||||
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
|
||||
@@ -54,19 +53,20 @@ import {
|
||||
} from "../services/event-manager";
|
||||
import Navigation from "../services/navigation";
|
||||
import Notifications from "../services/notifications";
|
||||
import SettingsService from "../services/settings";
|
||||
import { useArchivedStore } from "../stores/use-archived-store";
|
||||
import { useMenuStore } from "../stores/use-menu-store";
|
||||
import useNavigationStore from "../stores/use-navigation-store";
|
||||
import { useRelationStore } from "../stores/use-relation-store";
|
||||
import { useSelectionStore } from "../stores/use-selection-store";
|
||||
import { useSettingStore } from "../stores/use-setting-store";
|
||||
import { useTagStore } from "../stores/use-tag-store";
|
||||
import { useUserStore } from "../stores/use-user-store";
|
||||
import { eUpdateNoteInEditor } from "../utils/events";
|
||||
import { deleteItems } from "../utils/functions";
|
||||
import { convertNoteToText } from "../utils/note-to-text";
|
||||
import { sleep } from "../utils/time";
|
||||
import SettingsService from "../services/settings";
|
||||
import { useSettingStore } from "../stores/use-setting-store";
|
||||
import { useArchivedStore } from "../stores/use-archived-store";
|
||||
import AddReminder from "../screens/add-reminder";
|
||||
|
||||
export type ActionId =
|
||||
| "select"
|
||||
@@ -448,7 +448,8 @@ export const useActions = ({
|
||||
title: strings.editReminder(),
|
||||
icon: "pencil",
|
||||
onPress: async () => {
|
||||
ReminderSheet.present(item);
|
||||
AddReminder.present(item);
|
||||
close();
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -514,6 +515,8 @@ export const useActions = ({
|
||||
title: strings.addNotebook(),
|
||||
icon: "plus",
|
||||
onPress: async () => {
|
||||
close();
|
||||
await sleep(300);
|
||||
AddNotebookSheet.present(undefined, item);
|
||||
}
|
||||
},
|
||||
@@ -522,6 +525,8 @@ export const useActions = ({
|
||||
title: strings.editNotebook(),
|
||||
icon: "square-edit-outline",
|
||||
onPress: async () => {
|
||||
close();
|
||||
await sleep(300);
|
||||
AddNotebookSheet.present(item);
|
||||
}
|
||||
},
|
||||
@@ -908,12 +913,18 @@ export const useActions = ({
|
||||
referenceType: "reminder",
|
||||
relationType: "from",
|
||||
title: strings.dataTypesPluralCamelCase.reminder(),
|
||||
onAdd: () => ReminderSheet.present(undefined, item, true),
|
||||
onAdd: () => {
|
||||
AddReminder.present(undefined, item);
|
||||
close();
|
||||
},
|
||||
button: {
|
||||
title: strings.add(),
|
||||
type: "accent",
|
||||
onPress: () => ReminderSheet.present(undefined, item, true),
|
||||
icon: "plus"
|
||||
type: "plain",
|
||||
onPress: () => {
|
||||
AddReminder.present(undefined, item);
|
||||
close();
|
||||
},
|
||||
icon: "plus",
|
||||
iconSize: 20
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -957,7 +968,8 @@ export const useActions = ({
|
||||
title: strings.remindMe(),
|
||||
icon: "clock-plus-outline",
|
||||
onPress: () => {
|
||||
ReminderSheet.present(undefined, item);
|
||||
close();
|
||||
AddReminder.present(undefined, item);
|
||||
}
|
||||
},
|
||||
{
|
||||
|
||||
@@ -49,7 +49,6 @@ import { MMKV } from "../common/database/mmkv";
|
||||
import { endProgress, startProgress } from "../components/dialogs/progress";
|
||||
import Migrate from "../components/sheets/migrate";
|
||||
import NewFeature from "../components/sheets/new-feature";
|
||||
import ReminderSheet from "../components/sheets/reminder";
|
||||
import { Walkthrough } from "../components/walkthroughs";
|
||||
import {
|
||||
resetTabStore,
|
||||
@@ -103,6 +102,7 @@ import { getGithubVersion } from "../utils/github-version";
|
||||
import { fluidTabsRef } from "../utils/global-refs";
|
||||
import { NotesnookModule } from "../utils/notesnook-module";
|
||||
import { sleep } from "../utils/time";
|
||||
import AddReminder from "../screens/add-reminder";
|
||||
|
||||
const onCheckSyncStatus = async (type: SyncStatusEvent) => {
|
||||
const { disableSync, disableAutoSync } = SettingsService.get();
|
||||
@@ -182,10 +182,10 @@ const onAppOpenedFromURL = async (event: { url: string }) => {
|
||||
const id = new URL(url).searchParams.get("id");
|
||||
if (id) {
|
||||
const reminder = await db.reminders.reminder(id);
|
||||
if (reminder) ReminderSheet.present(reminder);
|
||||
if (reminder) AddReminder.present(reminder);
|
||||
}
|
||||
} else if (url.startsWith("https://notesnook.com/new_reminder")) {
|
||||
ReminderSheet.present();
|
||||
AddReminder.present();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
@@ -26,12 +26,14 @@ import { useCallback } from "react";
|
||||
* @returns Is keyboard floating or not
|
||||
*/
|
||||
const useIsFloatingKeyboard = () => {
|
||||
const { width } = useWindowDimensions();
|
||||
const { width, height } = useWindowDimensions();
|
||||
|
||||
const [floating, setFloating] = useState<boolean>(false);
|
||||
const onKeyboardWillChangeFrame = useCallback(
|
||||
(event: KeyboardEvent) => {
|
||||
setFloating(event.endCoordinates.width < width);
|
||||
setFloating(
|
||||
event.endCoordinates.width === 0 || event.endCoordinates.width < width
|
||||
);
|
||||
},
|
||||
[width]
|
||||
);
|
||||
@@ -41,8 +43,13 @@ const useIsFloatingKeyboard = () => {
|
||||
"keyboardWillChangeFrame",
|
||||
onKeyboardWillChangeFrame
|
||||
);
|
||||
const sub2 = Keyboard.addListener(
|
||||
"keyboardWillShow",
|
||||
onKeyboardWillChangeFrame
|
||||
);
|
||||
return () => {
|
||||
sub1?.remove();
|
||||
sub2?.remove();
|
||||
};
|
||||
}, [onKeyboardWillChangeFrame, width]);
|
||||
|
||||
|
||||
@@ -52,7 +52,14 @@ const useTimer = (initialId?: string) => {
|
||||
};
|
||||
}, [seconds, id]);
|
||||
|
||||
return { seconds, setId, start };
|
||||
const reset = () => {
|
||||
if (id) {
|
||||
timers[id] = 0;
|
||||
setSeconds(0);
|
||||
}
|
||||
};
|
||||
|
||||
return { seconds, setId, start, reset };
|
||||
};
|
||||
|
||||
export default useTimer;
|
||||
|
||||
@@ -30,6 +30,10 @@ import React, {
|
||||
useState
|
||||
} from "react";
|
||||
import { Dimensions, LayoutChangeEvent, Platform, View } from "react-native";
|
||||
import Orientation, {
|
||||
OrientationType,
|
||||
useDeviceOrientationChange
|
||||
} from "react-native-orientation-locker";
|
||||
import Animated, {
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
@@ -67,10 +71,6 @@ import {
|
||||
} from "../utils/events";
|
||||
import { editorRef, fluidTabsRef } from "../utils/global-refs";
|
||||
import { AppNavigationStack } from "./navigation-stack";
|
||||
import Orientation, {
|
||||
OrientationType,
|
||||
useDeviceOrientationChange
|
||||
} from "react-native-orientation-locker";
|
||||
|
||||
const MOBILE_SIDEBAR_SIZE = 0.85;
|
||||
|
||||
@@ -421,7 +421,6 @@ export const FluidPanelsView = React.memo(
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor: colors.primary.background,
|
||||
paddingBottom: Platform.OS === "android" ? insets?.bottom : 0,
|
||||
marginRight:
|
||||
orientation === "LANDSCAPE-RIGHT" && Platform.OS === "ios"
|
||||
? insets.right
|
||||
@@ -530,17 +529,20 @@ const onChangeTab = async (event: { i: number; from: number }) => {
|
||||
activateKeepAwake();
|
||||
eSendEvent(eOnEnterEditor);
|
||||
|
||||
if (!useTabStore.getState().getCurrentNoteId()) {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
newNote: true
|
||||
});
|
||||
} else {
|
||||
if (
|
||||
useTabStore.getState().getTab(useTabStore.getState().currentTab)
|
||||
?.session?.locked
|
||||
) {
|
||||
eSendEvent(eUnlockNote);
|
||||
}
|
||||
if (
|
||||
useTabStore.getState().getTab(useTabStore.getState().currentTab)?.session
|
||||
?.locked
|
||||
) {
|
||||
eSendEvent(eUnlockNote);
|
||||
}
|
||||
|
||||
if (
|
||||
fluidTabsRef.current?.tabChangedFromSwipeAction.value &&
|
||||
!useTabStore.getState().getNoteIdForTab(useTabStore.getState().currentTab)
|
||||
) {
|
||||
editorController?.current?.commands?.focus(
|
||||
useTabStore.getState().currentTab
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (event.from === 2) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import { useThemeColors } from "@notesnook/theme";
|
||||
import { NavigationContainer } from "@react-navigation/native";
|
||||
import { createNativeStackNavigator } from "@react-navigation/native-stack";
|
||||
import * as React from "react";
|
||||
import { db } from "../common/database";
|
||||
import { hideAllTooltips } from "../hooks/use-tooltip";
|
||||
import SettingsService from "../services/settings";
|
||||
import useNavigationStore, {
|
||||
@@ -29,7 +30,7 @@ import useNavigationStore, {
|
||||
import { useSelectionStore } from "../stores/use-selection-store";
|
||||
import { useSettingStore } from "../stores/use-setting-store";
|
||||
import { rootNavigatorRef } from "../utils/global-refs";
|
||||
import { db } from "../common/database";
|
||||
import Navigation from "../services/navigation";
|
||||
|
||||
const RootStack = createNativeStackNavigator();
|
||||
const AppStack = createNativeStackNavigator();
|
||||
@@ -248,18 +249,25 @@ let MoveNotebook: any = null;
|
||||
let MoveNotes: any = null;
|
||||
let Settings: any = null;
|
||||
let ManageTags: any = null;
|
||||
let AddReminder: any = null;
|
||||
|
||||
export const RootNavigation = () => {
|
||||
const introCompleted = useSettingStore(
|
||||
(state) => state.settings.introCompleted
|
||||
);
|
||||
const clearSelection = useSelectionStore((state) => state.clearSelection);
|
||||
const onStateChange = React.useCallback(() => {
|
||||
if (useSelectionStore.getState().selectionMode) {
|
||||
clearSelection();
|
||||
}
|
||||
hideAllTooltips();
|
||||
}, [clearSelection]);
|
||||
const onStateChange = React.useCallback(
|
||||
(state: any) => {
|
||||
if (useSelectionStore.getState().selectionMode) {
|
||||
clearSelection();
|
||||
}
|
||||
setTimeout(() => {
|
||||
Navigation.resetRootState(state);
|
||||
}, 1000);
|
||||
hideAllTooltips();
|
||||
},
|
||||
[clearSelection]
|
||||
);
|
||||
|
||||
return (
|
||||
<NavigationContainer onStateChange={onStateChange} ref={rootNavigatorRef}>
|
||||
@@ -336,6 +344,15 @@ export const RootNavigation = () => {
|
||||
return ManageTags;
|
||||
}}
|
||||
/>
|
||||
|
||||
<RootStack.Screen
|
||||
name="AddReminder"
|
||||
getComponent={() => {
|
||||
AddReminder =
|
||||
AddReminder || require("../screens/add-reminder").default;
|
||||
return AddReminder;
|
||||
}}
|
||||
/>
|
||||
</RootStack.Navigator>
|
||||
</NavigationContainer>
|
||||
);
|
||||
|
||||
621
apps/mobile/app/screens/add-reminder/index.tsx
Normal file
621
apps/mobile/app/screens/add-reminder/index.tsx
Normal file
@@ -0,0 +1,621 @@
|
||||
/*
|
||||
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 { Note, Reminder } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import dayjs from "dayjs";
|
||||
import React, { useRef, useState } from "react";
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
ScrollView,
|
||||
TextInput,
|
||||
View
|
||||
} from "react-native";
|
||||
import DatePicker from "react-native-date-picker";
|
||||
import DateTimePickerModal from "react-native-modal-datetime-picker";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import { db } from "../../common/database";
|
||||
import { Dialog } from "../../components/dialog";
|
||||
import { Header } from "../../components/header";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import Input from "../../components/ui/input";
|
||||
import { ReminderTime } from "../../components/ui/reminder-time";
|
||||
import Paragraph from "../../components/ui/typography/paragraph";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||
import Notifications from "../../services/notifications";
|
||||
import PremiumService from "../../services/premium";
|
||||
import SettingsService from "../../services/settings";
|
||||
import { useRelationStore } from "../../stores/use-relation-store";
|
||||
import { AppFontSize, defaultBorderRadius } from "../../utils/size";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { getFormattedDate } from "@notesnook/common";
|
||||
|
||||
const ReminderModes =
|
||||
Platform.OS === "ios"
|
||||
? {
|
||||
Once: "once",
|
||||
Repeat: "repeat"
|
||||
}
|
||||
: {
|
||||
Once: "once",
|
||||
Repeat: "repeat",
|
||||
Permanent: "permanent"
|
||||
};
|
||||
|
||||
const RecurringModes = {
|
||||
Daily: "day",
|
||||
Week: "week",
|
||||
Month: "month",
|
||||
Year: "year"
|
||||
};
|
||||
|
||||
const WeekDays = new Array(7).fill(true);
|
||||
const MonthDays = new Array(31).fill(true);
|
||||
const WeekDayNames = {
|
||||
0: "Sunday",
|
||||
1: "Monday",
|
||||
2: "Tuesday",
|
||||
3: "Wednesday",
|
||||
4: "Thursday",
|
||||
5: "Friday",
|
||||
6: "Saturday"
|
||||
};
|
||||
|
||||
const ReminderNotificationModes = {
|
||||
Silent: "silent",
|
||||
Vibrate: "vibrate",
|
||||
Urgent: "urgent"
|
||||
};
|
||||
|
||||
export default function AddReminder(props: NavigationProps<"AddReminder">) {
|
||||
const { reminder, reference } = props.route.params;
|
||||
const { colors, isDark } = useThemeColors();
|
||||
const [reminderMode, setReminderMode] = useState<Reminder["mode"]>(
|
||||
reminder?.mode || "once"
|
||||
);
|
||||
const [recurringMode, setRecurringMode] = useState<Reminder["recurringMode"]>(
|
||||
reminder?.recurringMode || "week"
|
||||
);
|
||||
const [selectedDays, setSelectedDays] = useState<number[]>(
|
||||
reminder?.selectedDays || []
|
||||
);
|
||||
const [date, setDate] = useState<Date>(
|
||||
new Date(reminder?.date || Date.now())
|
||||
);
|
||||
const [reminderNotificationMode, setReminderNotificatioMode] = useState<
|
||||
Reminder["priority"]
|
||||
>(reminder?.priority || SettingsService.get().reminderNotificationMode);
|
||||
const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
|
||||
const [repeatFrequency, setRepeatFrequency] = useState(1);
|
||||
const referencedItem = reference ? (reference as Note) : null;
|
||||
|
||||
const title = useRef<string | undefined>(
|
||||
!reminder ? referencedItem?.title : reminder?.title
|
||||
);
|
||||
const details = useRef<string | undefined>(
|
||||
!reminder ? referencedItem?.headline : reminder?.description
|
||||
);
|
||||
const titleRef = useRef<TextInput>(null);
|
||||
const descriptionRef = useRef<TextInput>(null);
|
||||
const timer = useRef<NodeJS.Timeout>();
|
||||
|
||||
const showDatePicker = () => {
|
||||
setDatePickerVisibility(true);
|
||||
};
|
||||
|
||||
const hideDatePicker = () => {
|
||||
setDatePickerVisibility(false);
|
||||
};
|
||||
|
||||
const handleConfirm = (date: Date) => {
|
||||
timer.current = setTimeout(() => {
|
||||
hideDatePicker();
|
||||
setDate(date);
|
||||
}, 10);
|
||||
};
|
||||
function nth(n: number) {
|
||||
return (
|
||||
["st", "nd", "rd"][(((((n < 0 ? -n : n) + 90) % 100) - 10) % 10) - 1] ||
|
||||
"th"
|
||||
);
|
||||
}
|
||||
|
||||
function getSelectedDaysText(selectedDays: number[]) {
|
||||
const text = selectedDays
|
||||
.sort((a, b) => a - b)
|
||||
.map((day, index) => {
|
||||
const isLast = index === selectedDays.length - 1;
|
||||
const isSecondLast = index === selectedDays.length - 2;
|
||||
const joinWith = isSecondLast ? " & " : isLast ? "" : ", ";
|
||||
return recurringMode === RecurringModes.Week
|
||||
? WeekDayNames[day as keyof typeof WeekDayNames] + joinWith
|
||||
: `${day}${nth(day)} ${joinWith}`;
|
||||
})
|
||||
.join("");
|
||||
return text;
|
||||
}
|
||||
|
||||
async function saveReminder() {
|
||||
try {
|
||||
if (!(await Notifications.checkAndRequestPermissions(true)))
|
||||
throw new Error(strings.noNotificationPermission());
|
||||
if (!date && reminderMode !== ReminderModes.Permanent) return;
|
||||
if (
|
||||
reminderMode === ReminderModes.Repeat &&
|
||||
recurringMode !== "day" &&
|
||||
recurringMode !== "year" &&
|
||||
selectedDays.length === 0
|
||||
)
|
||||
throw new Error(strings.selectDayError());
|
||||
|
||||
if (!title.current) throw new Error(strings.setTitleError());
|
||||
if (
|
||||
date.getTime() < Date.now() &&
|
||||
reminderMode === "once" &&
|
||||
!props.route.params.reminder
|
||||
) {
|
||||
throw new Error(strings.dateError());
|
||||
}
|
||||
|
||||
date.setSeconds(0, 0);
|
||||
|
||||
const reminderId = await db.reminders?.add({
|
||||
id: reminder?.id,
|
||||
date: date?.getTime(),
|
||||
priority: reminderNotificationMode,
|
||||
title: title.current,
|
||||
description: details.current,
|
||||
recurringMode: recurringMode,
|
||||
selectedDays: selectedDays,
|
||||
mode: reminderMode,
|
||||
localOnly: reminderMode === "permanent",
|
||||
snoozeUntil:
|
||||
date?.getTime() > Date.now() ? undefined : reminder?.snoozeUntil,
|
||||
disabled: false
|
||||
});
|
||||
if (!reminderId) return;
|
||||
const _reminder = await db.reminders?.reminder(reminderId);
|
||||
|
||||
if (reference && _reminder) {
|
||||
await db.relations?.add(reference, {
|
||||
id: _reminder?.id as string,
|
||||
type: _reminder?.type
|
||||
});
|
||||
}
|
||||
Notifications.scheduleNotification(_reminder as Reminder);
|
||||
Navigation.queueRoutesForUpdate();
|
||||
useRelationStore.getState().update();
|
||||
Navigation.goBack();
|
||||
} catch (e) {
|
||||
ToastManager.error(e as Error, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
const KeyboardViewIOS = Platform.OS === "ios" ? KeyboardAvoidingView : View;
|
||||
|
||||
return (
|
||||
<SafeAreaView
|
||||
style={{
|
||||
backgroundColor: colors.primary.background,
|
||||
flex: 1
|
||||
}}
|
||||
>
|
||||
<KeyboardViewIOS
|
||||
behavior="padding"
|
||||
style={{
|
||||
flex: 1
|
||||
}}
|
||||
>
|
||||
<Header
|
||||
title={reminder ? strings.editReminder() : strings.newReminder()}
|
||||
canGoBack
|
||||
rightButton={{
|
||||
name: "check",
|
||||
onPress: saveReminder
|
||||
}}
|
||||
/>
|
||||
<Dialog context="local" />
|
||||
<ScrollView
|
||||
style={{
|
||||
marginBottom: DDS.isTab ? 25 : undefined,
|
||||
paddingHorizontal: DefaultAppStyles.GAP
|
||||
}}
|
||||
keyboardDismissMode="interactive"
|
||||
keyboardShouldPersistTaps="handled"
|
||||
>
|
||||
<Input
|
||||
fwdRef={titleRef}
|
||||
defaultValue={reminder?.title || referencedItem?.title}
|
||||
placeholder={strings.remindeMeOf()}
|
||||
onChangeText={(text) => (title.current = text)}
|
||||
autoFocus
|
||||
wrapperStyle={{
|
||||
marginTop: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
onSubmit={() => {
|
||||
descriptionRef.current?.focus();
|
||||
}}
|
||||
/>
|
||||
|
||||
<Input
|
||||
defaultValue={
|
||||
reminder ? reminder?.description : referencedItem?.headline
|
||||
}
|
||||
fwdRef={descriptionRef}
|
||||
placeholder={strings.addShortNote()}
|
||||
onChangeText={(text) => (details.current = text)}
|
||||
containerStyle={{
|
||||
maxHeight: 80
|
||||
}}
|
||||
multiline
|
||||
textAlignVertical="top"
|
||||
inputStyle={{
|
||||
minHeight: 80,
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
height={80}
|
||||
wrapperStyle={{
|
||||
marginBottom: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
/>
|
||||
|
||||
<ScrollView
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
marginBottom: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
horizontal
|
||||
>
|
||||
{Object.keys(ReminderModes).map((mode) => (
|
||||
<Button
|
||||
key={mode}
|
||||
title={strings.reminderModes(
|
||||
ReminderModes[mode as keyof typeof ReminderModes] as string
|
||||
)}
|
||||
style={{
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
|
||||
marginRight: DefaultAppStyles.GAP_SMALL
|
||||
}}
|
||||
proTag={mode === "Repeat"}
|
||||
height={35}
|
||||
type={
|
||||
reminderMode ===
|
||||
ReminderModes[mode as keyof typeof ReminderModes]
|
||||
? "selectedAccent"
|
||||
: "plain"
|
||||
}
|
||||
onPress={() => {
|
||||
if (mode === "Repeat" && !PremiumService.get()) return;
|
||||
setReminderMode(
|
||||
ReminderModes[
|
||||
mode as keyof typeof ReminderModes
|
||||
] as Reminder["mode"]
|
||||
);
|
||||
if (mode === "Repeat") {
|
||||
setSelectedDays((days) => {
|
||||
if (days.length > 0) return days;
|
||||
if (days.indexOf(date.getDay()) > -1) {
|
||||
return days;
|
||||
}
|
||||
days.push(date.getDay());
|
||||
return [...days];
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</ScrollView>
|
||||
|
||||
{reminderMode === ReminderModes.Repeat ? (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: colors.secondary.background,
|
||||
padding: DefaultAppStyles.GAP,
|
||||
borderRadius: defaultBorderRadius,
|
||||
marginBottom: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
marginBottom:
|
||||
recurringMode === "day" || recurringMode === "year"
|
||||
? 0
|
||||
: 12,
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{Object.keys(RecurringModes).map((mode) => (
|
||||
<Button
|
||||
key={mode}
|
||||
title={strings.recurringModes(
|
||||
RecurringModes[mode as keyof typeof RecurringModes]
|
||||
)}
|
||||
style={{
|
||||
marginRight: 6,
|
||||
borderRadius: 100,
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
|
||||
}}
|
||||
type={
|
||||
recurringMode ===
|
||||
RecurringModes[mode as keyof typeof RecurringModes]
|
||||
? "selected"
|
||||
: "plain"
|
||||
}
|
||||
onPress={() => {
|
||||
setRecurringMode(
|
||||
RecurringModes[
|
||||
mode as keyof typeof RecurringModes
|
||||
] as Reminder["recurringMode"]
|
||||
);
|
||||
setSelectedDays([]);
|
||||
setRepeatFrequency(1);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
|
||||
<ScrollView showsHorizontalScrollIndicator={false} horizontal>
|
||||
{recurringMode === RecurringModes.Daily ||
|
||||
recurringMode === RecurringModes.Year
|
||||
? null
|
||||
: recurringMode === RecurringModes.Week
|
||||
? WeekDays.map((item, index) => (
|
||||
<Button
|
||||
key={strings.weekDayNamesShort[
|
||||
index as keyof typeof strings.weekDayNamesShort
|
||||
]()}
|
||||
title={strings.weekDayNamesShort[
|
||||
index as keyof typeof strings.weekDayNamesShort
|
||||
]()}
|
||||
type={
|
||||
selectedDays.indexOf(index) > -1
|
||||
? "selected"
|
||||
: "plain"
|
||||
}
|
||||
fontSize={AppFontSize.xs}
|
||||
style={{
|
||||
height: 40,
|
||||
borderRadius: 100,
|
||||
marginRight: 10
|
||||
}}
|
||||
onPress={() => {
|
||||
setSelectedDays((days) => {
|
||||
if (days.indexOf(index) > -1) {
|
||||
days.splice(days.indexOf(index), 1);
|
||||
return [...days];
|
||||
}
|
||||
days.push(index);
|
||||
return [...days];
|
||||
});
|
||||
}}
|
||||
/>
|
||||
))
|
||||
: MonthDays.map((item, index) => (
|
||||
<Button
|
||||
key={index + "monthday"}
|
||||
title={index + 1 + ""}
|
||||
type={
|
||||
selectedDays.indexOf(index + 1) > -1
|
||||
? "selected"
|
||||
: "plain"
|
||||
}
|
||||
fontSize={AppFontSize.xs}
|
||||
style={{
|
||||
height: 40,
|
||||
borderRadius: 100,
|
||||
marginRight: 10
|
||||
}}
|
||||
onPress={() => {
|
||||
setSelectedDays((days) => {
|
||||
if (days.indexOf(index + 1) > -1) {
|
||||
days.splice(days.indexOf(index + 1), 1);
|
||||
return [...days];
|
||||
}
|
||||
days.push(index + 1);
|
||||
return [...days];
|
||||
});
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{reminderMode === ReminderModes.Permanent ? null : (
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
flexDirection: "column",
|
||||
justifyContent: "center",
|
||||
marginBottom: DefaultAppStyles.GAP_VERTICAL,
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<DateTimePickerModal
|
||||
isVisible={isDatePickerVisible}
|
||||
mode="datetime"
|
||||
onConfirm={handleConfirm}
|
||||
onCancel={hideDatePicker}
|
||||
isDarkModeEnabled={isDark}
|
||||
is24Hour={db.settings.getTimeFormat() === "24-hour"}
|
||||
date={date || new Date(Date.now())}
|
||||
/>
|
||||
|
||||
<DatePicker
|
||||
date={date}
|
||||
maximumDate={dayjs(date).add(3, "months").toDate()}
|
||||
onDateChange={handleConfirm}
|
||||
textColor={isDark ? colors.static.white : colors.static.black}
|
||||
fadeToColor={colors.primary.background}
|
||||
theme={isDark ? "dark" : "light"}
|
||||
androidVariant="nativeAndroid"
|
||||
is24hourSource="locale"
|
||||
locale={
|
||||
db.settings?.getTimeFormat() === "24-hour" ? "en_GB" : "en_US"
|
||||
}
|
||||
mode={
|
||||
reminderMode === ReminderModes.Repeat &&
|
||||
recurringMode !== "year"
|
||||
? "time"
|
||||
: "datetime"
|
||||
}
|
||||
/>
|
||||
|
||||
{reminderMode === ReminderModes.Repeat ? null : (
|
||||
<Button
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
title={
|
||||
date
|
||||
? getFormattedDate(date, "date-time")
|
||||
: strings.selectDate()
|
||||
}
|
||||
type={date ? "secondaryAccented" : "secondary"}
|
||||
icon="calendar"
|
||||
fontSize={AppFontSize.sm}
|
||||
onPress={() => {
|
||||
showDatePicker();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
{reminderMode === ReminderModes.Once ||
|
||||
reminderMode === ReminderModes.Permanent ? null : (
|
||||
<View
|
||||
style={{
|
||||
borderRadius: defaultBorderRadius,
|
||||
flexDirection: "row",
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
marginBottom: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
>
|
||||
<>
|
||||
<Paragraph
|
||||
size={AppFontSize.xxs}
|
||||
color={colors.secondary.paragraph}
|
||||
>
|
||||
{recurringMode === RecurringModes.Daily
|
||||
? strings.reminderRepeatStrings.day(
|
||||
dayjs(date).format("hh:mm A")
|
||||
)
|
||||
: recurringMode === RecurringModes.Year
|
||||
? strings.reminderRepeatStrings.year(
|
||||
dayjs(date).format("dddd, MMMM D, h:mm A")
|
||||
)
|
||||
: selectedDays.length === 7 &&
|
||||
recurringMode === RecurringModes.Week
|
||||
? strings.reminderRepeatStrings.week.daily(
|
||||
dayjs(date).format("hh:mm A")
|
||||
)
|
||||
: selectedDays.length === 0
|
||||
? strings.reminderRepeatStrings[
|
||||
recurringMode as "week" | "month"
|
||||
].selectDays()
|
||||
: strings.reminderRepeatStrings.repeats(
|
||||
repeatFrequency,
|
||||
recurringMode as string,
|
||||
getSelectedDaysText(selectedDays),
|
||||
dayjs(date).format("hh:mm A")
|
||||
)}
|
||||
</Paragraph>
|
||||
</>
|
||||
</View>
|
||||
)}
|
||||
|
||||
<ReminderTime
|
||||
reminder={reminder}
|
||||
style={{
|
||||
width: "100%",
|
||||
justifyContent: "flex-start",
|
||||
borderWidth: 0,
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL,
|
||||
alignSelf: "flex-start"
|
||||
}}
|
||||
/>
|
||||
|
||||
{reminderMode === ReminderModes.Permanent ? null : (
|
||||
<ScrollView
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
marginTop: DefaultAppStyles.GAP_VERTICAL,
|
||||
height: 50
|
||||
}}
|
||||
horizontal
|
||||
>
|
||||
{Object.keys(ReminderNotificationModes).map((mode) => (
|
||||
<Button
|
||||
key={mode}
|
||||
title={strings.reminderNotificationModes(
|
||||
mode as keyof typeof ReminderNotificationModes
|
||||
)}
|
||||
style={{
|
||||
marginRight: 12,
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL
|
||||
}}
|
||||
icon={
|
||||
mode === "Silent"
|
||||
? "minus-circle"
|
||||
: mode === "Vibrate"
|
||||
? "vibrate"
|
||||
: "volume-high"
|
||||
}
|
||||
fontSize={AppFontSize.xs}
|
||||
height={35}
|
||||
type={
|
||||
reminderNotificationMode ===
|
||||
ReminderNotificationModes[
|
||||
mode as keyof typeof ReminderNotificationModes
|
||||
]
|
||||
? "selectedAccent"
|
||||
: "plain"
|
||||
}
|
||||
onPress={() => {
|
||||
const _mode = ReminderNotificationModes[
|
||||
mode as keyof typeof ReminderNotificationModes
|
||||
] as Reminder["priority"];
|
||||
SettingsService.set({
|
||||
reminderNotificationMode: _mode
|
||||
});
|
||||
setReminderNotificatioMode(_mode);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</ScrollView>
|
||||
)}
|
||||
</ScrollView>
|
||||
</KeyboardViewIOS>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
AddReminder.present = (reminder?: Reminder, reference?: Note) => {
|
||||
Navigation.navigate("AddReminder", {
|
||||
reminder,
|
||||
reference
|
||||
});
|
||||
};
|
||||
@@ -38,11 +38,11 @@ import { WebViewMessageEvent } from "react-native-webview";
|
||||
import { DatabaseLogger, db } from "../../../common/database";
|
||||
import downloadAttachment from "../../../common/filesystem/download-attachment";
|
||||
import { AuthMode } from "../../../components/auth/common";
|
||||
import { Properties } from "../../../components/properties";
|
||||
import EditorTabs from "../../../components/sheets/editor-tabs";
|
||||
import { Issue } from "../../../components/sheets/github/issue";
|
||||
import LinkNote from "../../../components/sheets/link-note";
|
||||
import { RelationsList } from "../../../components/sheets/relations-list";
|
||||
import ReminderSheet from "../../../components/sheets/reminder";
|
||||
import TableOfContents from "../../../components/sheets/toc";
|
||||
import { DDS } from "../../../services/device-detection";
|
||||
import {
|
||||
@@ -73,13 +73,13 @@ import {
|
||||
} from "../../../utils/events";
|
||||
import { openLinkInBrowser } from "../../../utils/functions";
|
||||
import { fluidTabsRef } from "../../../utils/global-refs";
|
||||
import { sleep } from "../../../utils/time";
|
||||
import ManageTags from "../../manage-tags";
|
||||
import { useDragState } from "../../settings/editor/state";
|
||||
import { EditorMessage, EditorProps, useEditorType } from "./types";
|
||||
import { useTabStore } from "./use-tab-store";
|
||||
import { editorState, openInternalLink } from "./utils";
|
||||
import { Properties } from "../../../components/properties";
|
||||
import { sleep } from "../../../utils/time";
|
||||
import ManageTags from "../../manage-tags";
|
||||
import AddReminder from "../../add-reminder";
|
||||
|
||||
const publishNote = async () => {
|
||||
const user = useUserStore.getState().user;
|
||||
@@ -131,7 +131,6 @@ const showActionsheet = async () => {
|
||||
.getState()
|
||||
.getNoteIdForTab(useTabStore.getState().currentTab!);
|
||||
if (noteId) {
|
||||
console.log("OPEN NOTE");
|
||||
const note = await db.notes?.note(noteId);
|
||||
Properties.present(note, false);
|
||||
} else {
|
||||
@@ -438,7 +437,7 @@ export const useEditorEvents = (
|
||||
referenceType: "reminder",
|
||||
relationType: "from",
|
||||
title: strings.dataTypesPluralCamelCase.reminder(),
|
||||
onAdd: () => ReminderSheet.present(undefined, note, true)
|
||||
onAdd: () => AddReminder.present(undefined, note)
|
||||
});
|
||||
break;
|
||||
case EditorEvents.newtag:
|
||||
|
||||
@@ -755,6 +755,8 @@ export const useEditor = (
|
||||
? (data as ContentItem).noteId
|
||||
: data.id;
|
||||
|
||||
if (!useTabStore.getState().hasTabForNote(noteId)) return;
|
||||
|
||||
const note = data.type === "note" ? data : await db.notes?.note(noteId);
|
||||
lock.current = true;
|
||||
// Handle this case where note was locked on another device and synced.
|
||||
@@ -873,7 +875,6 @@ export const useEditor = (
|
||||
}
|
||||
|
||||
lastContentChangeTime.current[note.id] = note.dateEdited;
|
||||
console.log(tab.session?.selection);
|
||||
await postMessage(
|
||||
NativeEvents.updatehtml,
|
||||
{
|
||||
|
||||
@@ -98,7 +98,7 @@ class TabSessionStorage {
|
||||
|
||||
static get(id: string): TabSessionItem | null {
|
||||
if (!id) return null;
|
||||
return TabSessionStorage.storage.getMap(id);
|
||||
return TabSessionStorage.storage.getMap(id) || null;
|
||||
}
|
||||
|
||||
static set(id: string, session: TabSessionItem): void {
|
||||
|
||||
@@ -96,14 +96,16 @@ export const EditorWrapper = ({ widths }: { widths: any }) => {
|
||||
minHeight: "100%",
|
||||
backgroundColor: toolBarColors.primary.background,
|
||||
borderLeftWidth: DDS.isTab ? 1 : 0,
|
||||
borderLeftColor: DDS.isTab ? colors.secondary.background : "transparent"
|
||||
borderLeftColor: DDS.isTab
|
||||
? colors.secondary.background
|
||||
: "transparent",
|
||||
paddingBottom: insets.bottom
|
||||
}}
|
||||
>
|
||||
{loading || !introCompleted ? null : (
|
||||
<KeyboardAvoidingViewIOS
|
||||
behavior="padding"
|
||||
style={{
|
||||
marginBottom: getMarginBottom(),
|
||||
backgroundColor: colors.primary.background,
|
||||
flex: 1
|
||||
}}
|
||||
|
||||
@@ -300,6 +300,8 @@ const NotebookItemWrapper = React.memo(
|
||||
const expanded = useNotebookExpandedStore(
|
||||
(state) => state.expanded[item.notebook.id]
|
||||
);
|
||||
const disableExpand = useNotebookTreeStore((state) => state.isSearching);
|
||||
|
||||
const selectionEnabled = useNotebookSelectionStore(
|
||||
(state) => state.enabled
|
||||
);
|
||||
@@ -371,6 +373,7 @@ const NotebookItemWrapper = React.memo(
|
||||
selectionEnabled={selectionEnabled}
|
||||
selectionStore={useNotebookSelectionStore}
|
||||
onItemUpdate={onItemUpdate}
|
||||
disableExpand={disableExpand}
|
||||
focused={false}
|
||||
onPress={onPress}
|
||||
onAddNotebook={() => {
|
||||
|
||||
@@ -112,7 +112,7 @@ const ManageTags = (props: NavigationProps<"ManageTags">) => {
|
||||
if (query && query.trim() !== "") {
|
||||
db.lookup
|
||||
.tags(query)
|
||||
.sorted()
|
||||
.sorted(db.settings.getGroupOptions("tags"))
|
||||
.then((items) => {
|
||||
setTags(items);
|
||||
});
|
||||
@@ -249,6 +249,7 @@ const ManageTags = (props: NavigationProps<"ManageTags">) => {
|
||||
}}
|
||||
>
|
||||
<Header title={strings.manageTags()} canGoBack />
|
||||
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: DefaultAppStyles.GAP
|
||||
|
||||
@@ -328,6 +328,8 @@ const NotebookItemWrapper = React.memo(
|
||||
const expanded = useNotebookExpandedStore(
|
||||
(state) => state.expanded[item.notebook.id]
|
||||
);
|
||||
const disableExpand = useNotebookTreeStore((state) => state.isSearching);
|
||||
|
||||
const selectionEnabled = useNotebookSelectionStore(
|
||||
(state) => state.enabled
|
||||
);
|
||||
@@ -378,6 +380,7 @@ const NotebookItemWrapper = React.memo(
|
||||
useNotebookTreeStore.getState().removeChildren(item.notebook.id);
|
||||
}
|
||||
}}
|
||||
disableExpand={disableExpand}
|
||||
selected={selected}
|
||||
selectionEnabled={selectionEnabled}
|
||||
selectionStore={useNotebookSelectionStore}
|
||||
|
||||
@@ -17,19 +17,19 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { strings } from "@notesnook/intl";
|
||||
import React from "react";
|
||||
import { FloatingButton } from "../../components/container/floating-button";
|
||||
import DelayLayout from "../../components/delay-layout";
|
||||
import { Header } from "../../components/header";
|
||||
import List from "../../components/list";
|
||||
import SelectionHeader from "../../components/selection-header";
|
||||
import ReminderSheet from "../../components/sheets/reminder";
|
||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||
import SettingsService from "../../services/settings";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useReminders } from "../../stores/use-reminder-store";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import AddReminder from "../add-reminder";
|
||||
|
||||
export const Reminders = ({
|
||||
navigation,
|
||||
@@ -68,7 +68,7 @@ export const Reminders = ({
|
||||
}}
|
||||
id={route.name}
|
||||
onPressDefaultRightButton={() => {
|
||||
ReminderSheet.present();
|
||||
AddReminder.present();
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -84,7 +84,7 @@ export const Reminders = ({
|
||||
paragraph: strings.remindersEmpty(),
|
||||
button: strings.setReminder(),
|
||||
action: () => {
|
||||
ReminderSheet.present();
|
||||
AddReminder.present();
|
||||
},
|
||||
loading: strings.loadingReminders()
|
||||
}}
|
||||
@@ -92,7 +92,7 @@ export const Reminders = ({
|
||||
|
||||
<FloatingButton
|
||||
onPress={() => {
|
||||
ReminderSheet.present();
|
||||
AddReminder.present();
|
||||
}}
|
||||
alwaysVisible
|
||||
/>
|
||||
|
||||
@@ -16,35 +16,22 @@ GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import React, { RefObject, useRef, useState } from "react";
|
||||
import React, { useRef, useState } from "react";
|
||||
import { TextInput, View } from "react-native";
|
||||
import { ActionSheetRef } from "react-native-actions-sheet";
|
||||
import { db } from "../../../common/database";
|
||||
import {
|
||||
eSendEvent,
|
||||
presentSheet,
|
||||
PresentSheetOptions,
|
||||
ToastManager
|
||||
} from "../../../services/event-manager";
|
||||
import DialogHeader from "../../dialog/dialog-header";
|
||||
import { Button } from "../../ui/button";
|
||||
import Input from "../../ui/input";
|
||||
import { eSendEvent, ToastManager } from "../../../services/event-manager";
|
||||
import { eUserLoggedIn } from "../../../utils/events";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
|
||||
type ChangeEmailProps = {
|
||||
actionSheetRef: RefObject<ActionSheetRef>;
|
||||
close?: () => void;
|
||||
update?: (options: PresentSheetOptions) => void;
|
||||
};
|
||||
import Input from "../../../components/ui/input";
|
||||
import { Button } from "../../../components/ui/button";
|
||||
|
||||
enum EmailChangeSteps {
|
||||
verify,
|
||||
changeEmail
|
||||
}
|
||||
|
||||
export const ChangeEmail = ({ close }: ChangeEmailProps) => {
|
||||
export const ChangeEmail = () => {
|
||||
const [step, setStep] = useState(EmailChangeSteps.verify);
|
||||
const emailChangeData = useRef<{
|
||||
email?: string;
|
||||
@@ -103,10 +90,6 @@ export const ChangeEmail = ({ close }: ChangeEmailProps) => {
|
||||
|
||||
return (
|
||||
<View style={{ paddingHorizontal: DefaultAppStyles.GAP }}>
|
||||
<DialogHeader
|
||||
title={strings.changeEmail()}
|
||||
paragraph={strings.changeEmailDesc()}
|
||||
/>
|
||||
<View
|
||||
style={{
|
||||
marginTop: DefaultAppStyles.GAP_VERTICAL
|
||||
@@ -156,23 +139,12 @@ export const ChangeEmail = ({ close }: ChangeEmailProps) => {
|
||||
: strings.changeEmail()
|
||||
}
|
||||
type="accent"
|
||||
width={250}
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
loading={loading}
|
||||
onPress={onSubmit}
|
||||
style={{
|
||||
borderRadius: 100,
|
||||
height: 45,
|
||||
marginTop: 2
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
ChangeEmail.present = () => {
|
||||
presentSheet({
|
||||
component: (ref, close, update) => (
|
||||
<ChangeEmail actionSheetRef={ref} close={close} update={update} />
|
||||
)
|
||||
});
|
||||
};
|
||||
@@ -43,6 +43,8 @@ import ThemeSelector from "./theme-selector";
|
||||
import { TitleFormat } from "./title-format";
|
||||
import { View } from "react-native";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { ChangePassword } from "../../components/auth/change-password";
|
||||
import { ChangeEmail } from "./change-email";
|
||||
|
||||
export const components: { [name: string]: ReactElement } = {
|
||||
colorpicker: <AccentColorPicker />,
|
||||
@@ -69,5 +71,7 @@ export const components: { [name: string]: ReactElement } = {
|
||||
<AttachmentGroupProgress groupId="offline-mode" />
|
||||
</View>
|
||||
),
|
||||
"sidebar-tab-selector": <SidebarTabPicker />
|
||||
"sidebar-tab-selector": <SidebarTabPicker />,
|
||||
"change-password": <ChangePassword />,
|
||||
"change-email": <ChangeEmail />
|
||||
};
|
||||
|
||||
@@ -32,11 +32,9 @@ import { enabled } from "react-native-privacy-snapshot";
|
||||
import ScreenGuardModule from "react-native-screenguard";
|
||||
import { db } from "../../common/database";
|
||||
import filesystem from "../../common/filesystem";
|
||||
import { ChangePassword } from "../../components/auth/change-password";
|
||||
import { presentDialog } from "../../components/dialog/functions";
|
||||
import { AppLockPassword } from "../../components/dialogs/applock-password";
|
||||
import { endProgress, startProgress } from "../../components/dialogs/progress";
|
||||
import { ChangeEmail } from "../../components/sheets/change-email";
|
||||
import ExportNotesSheet from "../../components/sheets/export-notes";
|
||||
import { Issue } from "../../components/sheets/github/issue";
|
||||
import { Progress } from "../../components/sheets/progress";
|
||||
@@ -243,17 +241,15 @@ export const settingsGroups: SettingSection[] = [
|
||||
{
|
||||
id: "change-password",
|
||||
name: strings.changePassword(),
|
||||
modifer: async () => {
|
||||
ChangePassword.present();
|
||||
},
|
||||
description: strings.changePasswordDesc()
|
||||
type: "screen",
|
||||
description: strings.changePasswordDesc(),
|
||||
component: "change-password"
|
||||
},
|
||||
{
|
||||
id: "change-email",
|
||||
name: strings.changeEmail(),
|
||||
modifer: async () => {
|
||||
ChangeEmail.present();
|
||||
},
|
||||
type: "screen",
|
||||
component: "change-email",
|
||||
description: strings.changeEmailDesc()
|
||||
},
|
||||
{
|
||||
|
||||
@@ -113,7 +113,10 @@ async function onBackgroundSyncStarted() {
|
||||
useUserStore.getState().setSyncing(false);
|
||||
}
|
||||
await Notifications.setupReminders();
|
||||
|
||||
if (SettingsService.get().notifNotes) {
|
||||
Notifications.pinQuickNote(false);
|
||||
}
|
||||
Notifications.restorePinnedNotes();
|
||||
NotePreviewWidget.updateNotes();
|
||||
deleteDCacheFiles();
|
||||
DatabaseLogger.info("BACKGROUND SYNC COMPLETE");
|
||||
@@ -142,6 +145,7 @@ const onBoot = async () => {
|
||||
Notifications.pinQuickNote(false);
|
||||
}
|
||||
Notifications.restorePinnedNotes();
|
||||
NotePreviewWidget.updateNotes();
|
||||
DatabaseLogger.info("BOOT TASK COMPLETE");
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e as Error);
|
||||
|
||||
@@ -18,12 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { Platform } from "react-native";
|
||||
import { db } from "../common/database";
|
||||
import ReminderSheet from "../components/sheets/reminder";
|
||||
import { setAppState } from "../screens/editor/tiptap/utils";
|
||||
import { eOnLoadNote } from "../utils/events";
|
||||
import { NotesnookModule } from "../utils/notesnook-module";
|
||||
import { eSendEvent } from "./event-manager";
|
||||
import { fluidTabsRef } from "../utils/global-refs";
|
||||
import AddReminder from "../screens/add-reminder";
|
||||
|
||||
const launchIntent = Platform.OS === "ios" ? {} : NotesnookModule.getIntent();
|
||||
let used = false;
|
||||
@@ -65,9 +65,9 @@ export const IntentService = {
|
||||
const reminder = await db.reminders.reminder(
|
||||
intent["com.streetwriters.notesnook.OpenReminderId"]
|
||||
);
|
||||
if (reminder) ReminderSheet.present(reminder);
|
||||
if (reminder) AddReminder.present(reminder);
|
||||
} else if (intent["com.streetwriters.notesnook.NewReminder"]) {
|
||||
ReminderSheet.present();
|
||||
AddReminder.present();
|
||||
}
|
||||
} catch (e) {
|
||||
/* empty */
|
||||
|
||||
@@ -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 { StackActions } from "@react-navigation/native";
|
||||
import { NavigationHelpers, StackActions } from "@react-navigation/native";
|
||||
import { NativeStackScreenProps } from "@react-navigation/native-stack";
|
||||
import { useFavoriteStore } from "../stores/use-favorite-store";
|
||||
import useNavigationStore, {
|
||||
@@ -69,7 +69,8 @@ const routeNames = {
|
||||
LinkNotebooks: "LinkNotebooks",
|
||||
MoveNotes: "MoveNotes",
|
||||
Archive: "Archive",
|
||||
ManageTags: "ManageTags"
|
||||
ManageTags: "ManageTags",
|
||||
AddReminder: "AddReminder"
|
||||
};
|
||||
|
||||
export type NavigationProps<T extends RouteName> = NativeStackScreenProps<
|
||||
@@ -165,6 +166,32 @@ function closeDrawer() {
|
||||
fluidTabsRef.current?.closeDrawer();
|
||||
}
|
||||
|
||||
function resetRootState(
|
||||
_state?: ReturnType<NavigationHelpers<any, any>["getState"]>
|
||||
) {
|
||||
const state = _state || rootNavigatorRef.getState();
|
||||
const focusedRoute = state.routes[state.index];
|
||||
|
||||
if (state.routes.length < 2) return;
|
||||
|
||||
const routes = state.routes.filter(
|
||||
(route) =>
|
||||
(route.name !== "Auth" && route.name !== "Welcome") ||
|
||||
route.key === focusedRoute.key
|
||||
);
|
||||
|
||||
if (routes.length === state.routes.length) return;
|
||||
if (routes.length === 0) {
|
||||
routes.push(focusedRoute);
|
||||
}
|
||||
const newIndex = routes.findIndex((route) => route.key === focusedRoute.key);
|
||||
rootNavigatorRef.reset({
|
||||
...state,
|
||||
routes: routes,
|
||||
index: newIndex
|
||||
});
|
||||
}
|
||||
|
||||
const Navigation = {
|
||||
navigate,
|
||||
goBack,
|
||||
@@ -176,7 +203,8 @@ const Navigation = {
|
||||
queueRoutesForUpdate,
|
||||
routeNeedsUpdate,
|
||||
routeNames,
|
||||
routeUpdateFunctions
|
||||
routeUpdateFunctions,
|
||||
resetRootState
|
||||
};
|
||||
|
||||
export default Navigation;
|
||||
|
||||
@@ -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 { getFormattedReminderTime } from "@notesnook/common";
|
||||
import { isReminderActive, Reminder } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import notifee, {
|
||||
@@ -36,6 +37,7 @@ import dayjs, { Dayjs } from "dayjs";
|
||||
import { encodeNonAsciiHTML } from "entities";
|
||||
import { Platform } from "react-native";
|
||||
import { db, setupDatabase } from "../common/database";
|
||||
import { MMKV } from "../common/database/mmkv";
|
||||
import { presentDialog } from "../components/dialog/functions";
|
||||
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
|
||||
import { editorState } from "../screens/editor/tiptap/utils";
|
||||
@@ -52,8 +54,6 @@ import { DDS } from "./device-detection";
|
||||
import { eSendEvent } from "./event-manager";
|
||||
import Navigation from "./navigation";
|
||||
import SettingsService from "./settings";
|
||||
import { getFormattedReminderTime } from "@notesnook/common";
|
||||
import { MMKV } from "../common/database/mmkv";
|
||||
|
||||
let pinned: DisplayedNotification[] = [];
|
||||
|
||||
@@ -348,7 +348,7 @@ async function scheduleNotification(
|
||||
title: title,
|
||||
message: description || "",
|
||||
ongoing: true,
|
||||
subtitle: description || "",
|
||||
// subtitle: description || "",
|
||||
actions: [strings.unpin()]
|
||||
});
|
||||
}
|
||||
@@ -396,7 +396,7 @@ async function scheduleNotification(
|
||||
payload: payload || "",
|
||||
dateModified: reminder.dateModified + ""
|
||||
},
|
||||
subtitle: !description ? undefined : description,
|
||||
// subtitle: !description ? undefined : description,
|
||||
android: {
|
||||
channelId: await getChannelId(priority),
|
||||
smallIcon: "ic_stat_name",
|
||||
@@ -454,7 +454,7 @@ async function loadNote(id: string, jump: boolean) {
|
||||
const tab = useTabStore.getState().getTabForNote(id);
|
||||
if (useTabStore.getState().currentTab !== tab) {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
note: note
|
||||
item: note
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -916,8 +916,21 @@ async function pinQuickNote(launch: boolean) {
|
||||
* A function that checks if reminders need to be reconfigured &
|
||||
* reschedules them if anything has changed.
|
||||
*/
|
||||
|
||||
async function setupReminders(checkNeedsScheduling = false) {
|
||||
const reminders = ((await db.reminders?.all.items()) as Reminder[]) || [];
|
||||
let notificationsCancelled = false;
|
||||
if (Platform.OS === "android") {
|
||||
// If the API level has changed, cancel all notifications.
|
||||
// This is to ensure that the app does not crash on Android 14+.
|
||||
const API_LEVEL = MMKV.getInt("android_apiLevel");
|
||||
if (API_LEVEL !== (Platform.Version as number)) {
|
||||
await notifee.cancelAllNotifications();
|
||||
notificationsCancelled = true;
|
||||
MMKV.setInt("android_apiLevel", Platform.Version as number);
|
||||
}
|
||||
}
|
||||
|
||||
const triggers = await notifee.getTriggerNotifications();
|
||||
for (const reminder of reminders) {
|
||||
if (reminder.mode === "permanent") {
|
||||
@@ -927,20 +940,22 @@ async function setupReminders(checkNeedsScheduling = false) {
|
||||
// Skip reminders that are not repeating and their trigger date is in past.
|
||||
if (reminder.mode === "once" && dayjs().isAfter(reminder.date)) continue;
|
||||
|
||||
const pending = triggers.filter((t) =>
|
||||
t.notification.id?.startsWith(reminder.id)
|
||||
);
|
||||
if (!notificationsCancelled) {
|
||||
const pending = triggers.filter((t) =>
|
||||
t.notification.id?.startsWith(reminder.id)
|
||||
);
|
||||
|
||||
let needsReschedule = pending.length === 0 ? true : false;
|
||||
if (!needsReschedule) {
|
||||
needsReschedule = pending[0].notification.data?.dateModified
|
||||
? parseInt(pending[0].notification.data?.dateModified as string) <
|
||||
reminder.dateModified
|
||||
: true;
|
||||
let needsReschedule = pending.length === 0 ? true : false;
|
||||
if (!needsReschedule) {
|
||||
needsReschedule = pending[0].notification.data?.dateModified
|
||||
? parseInt(pending[0].notification.data?.dateModified as string) <
|
||||
reminder.dateModified
|
||||
: true;
|
||||
}
|
||||
|
||||
if (!needsReschedule && checkNeedsScheduling) continue;
|
||||
}
|
||||
|
||||
if (!needsReschedule && checkNeedsScheduling) continue;
|
||||
|
||||
await scheduleNotification(reminder);
|
||||
}
|
||||
// Check for any triggers whose notifications
|
||||
|
||||
@@ -49,6 +49,7 @@ export function createNotebookTreeStores(
|
||||
) {
|
||||
const useNotebookTreeStore = create<{
|
||||
tree: TreeItem[];
|
||||
isSearching?: boolean;
|
||||
setTree: (tree: TreeItem[]) => void;
|
||||
removeItem: (id: string) => void;
|
||||
addNotebooks: (
|
||||
@@ -113,16 +114,32 @@ export function createNotebookTreeStores(
|
||||
}
|
||||
}
|
||||
|
||||
const newTreeItems = notebooks.map((notebook) => {
|
||||
return {
|
||||
parentId,
|
||||
notebook,
|
||||
depth: depth,
|
||||
hasChildren: items.some((item) => {
|
||||
return item.fromId === notebook.id;
|
||||
})
|
||||
};
|
||||
});
|
||||
const rootTreeItems = newTree.filter((item) => item.parentId === "root");
|
||||
const newTreeItems = notebooks.reduce((acc, notebook) => {
|
||||
if (!rootTreeItems.find((item) => item.notebook.id === notebook.id)) {
|
||||
acc.push({
|
||||
parentId,
|
||||
notebook,
|
||||
depth: depth,
|
||||
hasChildren: false
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, [] as TreeItem[]);
|
||||
|
||||
if (parentId === "root") {
|
||||
rootTreeItems.splice(0, 0, ...newTreeItems);
|
||||
}
|
||||
|
||||
for (const treeItem of newTreeItems) {
|
||||
treeItem.hasChildren = items.some((item) => {
|
||||
return (
|
||||
rootTreeItems.findIndex(
|
||||
(treeItem) => treeItem.notebook.id === item.toId
|
||||
) === -1 && item.fromId === treeItem.notebook.id
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
newTree.splice(parentIndex + 1, 0, ...newTreeItems);
|
||||
|
||||
@@ -130,7 +147,7 @@ export function createNotebookTreeStores(
|
||||
const expanded =
|
||||
useNotebookExpandedStore.getState().expanded[item.notebook.id] &&
|
||||
item.hasChildren;
|
||||
if (expanded) {
|
||||
if (expanded && !get().isSearching) {
|
||||
newTree = await get().fetchAndAdd(
|
||||
item.notebook.id,
|
||||
depth + 1,
|
||||
|
||||
@@ -45,10 +45,10 @@ export function initAfterSync(type: "full" | "send" = "send") {
|
||||
useUserStore.setState({
|
||||
profile: db.settings.getProfile()
|
||||
});
|
||||
eSendEvent(eAfterSync);
|
||||
}
|
||||
Notifications.setupReminders(true);
|
||||
NotePreviewWidget.updateNotes();
|
||||
eSendEvent(eAfterSync);
|
||||
}
|
||||
|
||||
export async function initialize() {}
|
||||
|
||||
@@ -94,6 +94,10 @@ export interface RouteParams extends ParamListBase {
|
||||
ManageTags: {
|
||||
ids?: string[];
|
||||
};
|
||||
AddReminder: {
|
||||
reminder?: Reminder;
|
||||
reference?: Note;
|
||||
};
|
||||
}
|
||||
|
||||
export type RouteName = keyof RouteParams;
|
||||
|
||||
@@ -43,7 +43,7 @@ export function changeSystemBarColors() {
|
||||
const isDark = useThemeStore.getState().colorScheme === "dark";
|
||||
changeNavigationBarColor(
|
||||
currTheme.scopes.base.primary.background,
|
||||
isDark,
|
||||
!isDark,
|
||||
false
|
||||
);
|
||||
StatusBar.setBackgroundColor("transparent" as any);
|
||||
|
||||
@@ -40,6 +40,7 @@ interface NotesnookModuleInterface {
|
||||
hasWidgetNote: (noteId: string) => Promise<boolean>;
|
||||
updateWidgetNote: (noteId: string, data: string) => void;
|
||||
updateReminderWidget: () => void;
|
||||
isGestureNavigationEnabled: () => boolean;
|
||||
}
|
||||
|
||||
export const NotesnookModule: NotesnookModuleInterface = Platform.select({
|
||||
@@ -59,7 +60,8 @@ export const NotesnookModule: NotesnookModuleInterface = Platform.select({
|
||||
getWidgetNotes: () => {},
|
||||
hasWidgetNote: () => {},
|
||||
updateWidgetNote: () => {},
|
||||
updateReminderWidget: () => {}
|
||||
updateReminderWidget: () => {},
|
||||
isGestureNavigationEnabled: () => true
|
||||
},
|
||||
android: NativeModules.NNativeModule
|
||||
});
|
||||
|
||||
@@ -89,10 +89,10 @@ function getSize() {
|
||||
xs: normalize(13.5) * scale.fontScale,
|
||||
sm: normalize(14.5) * scale.fontScale,
|
||||
md: normalize(16.5) * scale.fontScale,
|
||||
lg: normalize(22) * scale.fontScale,
|
||||
xl: normalize(24) * scale.fontScale,
|
||||
xxl: normalize(28) * scale.fontScale,
|
||||
xxxl: normalize(32) * scale.fontScale
|
||||
lg: normalize(20) * scale.fontScale,
|
||||
xl: normalize(22) * scale.fontScale,
|
||||
xxl: normalize(25) * scale.fontScale,
|
||||
xxxl: normalize(30) * scale.fontScale
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
multiDexEnabled true
|
||||
versionCode 3058
|
||||
versionCode 3068
|
||||
versionName getNpmVersion()
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
@@ -11,57 +11,69 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:node="remove"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" tools:node="remove"/>
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
tools:node="remove" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_EXTERNAL_STORAGE"
|
||||
tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||
<uses-permission
|
||||
android:name="android.permission.USE_FULL_SCREEN_INTENT"
|
||||
tools:node="remove" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<!-- If you don't know the MIME type in advance, set "mimeType" to "*/*". -->
|
||||
<data android:mimeType="application/pdf" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<!-- If you don't know the MIME type in advance, set "mimeType" to "*/*". -->
|
||||
<data android:mimeType="text/*" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<!-- If you don't know the MIME type in advance, set "mimeType" to "*/*". -->
|
||||
<data android:mimeType="image/*" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<!-- If you don't know the MIME type in advance, set "mimeType" to "*/*". -->
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<!-- If you don't know the MIME type in advance, set "mimeType" to "*/*". -->
|
||||
<data android:mimeType="application/pdf" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<!-- If you don't know the MIME type in advance, set "mimeType" to "*/*". -->
|
||||
<data android:mimeType="text/*" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<!-- If you don't know the MIME type in advance, set "mimeType" to "*/*". -->
|
||||
<data android:mimeType="image/*" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<!-- If you don't know the MIME type in advance, set "mimeType" to "*/*". -->
|
||||
|
||||
<data android:mimeType="video/*" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<!-- If you don't know the MIME type in advance, set "mimeType" to "*/*". -->
|
||||
<data android:mimeType="audio/*" />
|
||||
</intent>
|
||||
</queries>
|
||||
<data android:mimeType="video/*" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<!-- If you don't know the MIME type in advance, set "mimeType" to "*/*". -->
|
||||
<data android:mimeType="audio/*" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:theme="@style/BootTheme"
|
||||
android:largeHeap="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:supportsRtl="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config">
|
||||
|
||||
<receiver android:exported="false" android:label="@string/quick_note" android:name=".NoteWidget">
|
||||
android:theme="@style/BootTheme">
|
||||
|
||||
<receiver
|
||||
android:name=".NoteWidget"
|
||||
android:exported="false"
|
||||
android:label="@string/quick_note">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
@@ -71,7 +83,10 @@
|
||||
android:resource="@xml/new_note_widget_info" />
|
||||
</receiver>
|
||||
|
||||
<receiver android:exported="false" android:label="@string/note" android:name=".NotePreviewWidget">
|
||||
<receiver
|
||||
android:name=".NotePreviewWidget"
|
||||
android:exported="false"
|
||||
android:label="@string/note">
|
||||
<intent-filter>"
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
@@ -81,7 +96,10 @@
|
||||
android:resource="@xml/note_widget_info" />
|
||||
</receiver>
|
||||
|
||||
<receiver android:exported="false" android:label="@string/reminders_title" android:name=".ReminderWidgetProvider">
|
||||
<receiver
|
||||
android:name=".ReminderWidgetProvider"
|
||||
android:exported="false"
|
||||
android:label="@string/reminders_title">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
@@ -90,33 +108,37 @@
|
||||
android:resource="@xml/widget_reminders_info" />
|
||||
</receiver>
|
||||
|
||||
<activity android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||
<activity
|
||||
android:name=".NotePreviewConfigureActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||
android:exported="true"
|
||||
android:label="NotePreviewConfigure"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:windowSoftInputMode="adjustResize" android:name=".NotePreviewConfigureActivity">
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:resizeableActivity="true"
|
||||
android:exported="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>
|
||||
<action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter android:label="Notesnook">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
@@ -127,6 +149,7 @@
|
||||
|
||||
<intent-filter android:label="Notesnook">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
@@ -134,22 +157,25 @@
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
<activity android:exported="false" android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||
<activity
|
||||
android:name="com.facebook.react.devsupport.DevSettingsActivity"
|
||||
android:exported="false" />
|
||||
<activity
|
||||
android:name=".ShareActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:label="@string/title_activity_share"
|
||||
android:noHistory="true"
|
||||
android:screenOrientation="unspecified"
|
||||
android:exported="true"
|
||||
android:taskAffinity=""
|
||||
android:excludeFromRecents="true"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:theme="@style/AppThemeB">
|
||||
android:theme="@style/AppThemeB"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
|
||||
<intent-filter android:label="Make Note">
|
||||
<action android:name="android.intent.action.SEND" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="text/*" />
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="video/*" />
|
||||
@@ -160,6 +186,7 @@
|
||||
<intent-filter android:label="Make Note">
|
||||
<action android:name="android.intent.action.SEND_MULTIPLE" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
|
||||
<data android:mimeType="text/*" />
|
||||
<data android:mimeType="image/*" />
|
||||
<data android:mimeType="video/*" />
|
||||
@@ -179,35 +206,41 @@
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:stopWithTask="false" />
|
||||
<service android:name="com.asterinet.react.bgactions.RNBackgroundActionsTask" />
|
||||
<service android:name="com.asterinet.react.bgactions.RNBackgroundActionsTask" />
|
||||
|
||||
<service android:name="com.streetwriters.notesnook.BootTaskService" android:foregroundServiceType="dataSync" />
|
||||
<service
|
||||
android:name="com.streetwriters.notesnook.BootTaskService"
|
||||
android:foregroundServiceType="specialUse">
|
||||
<property
|
||||
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||
android:value="The service is required by the app to restore notifications of pinned notes, restore notification with reply input for creating notes and restore data in note preview widgets on device reboot." />
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".NotesnookTileService"
|
||||
android:exported="true"
|
||||
android:icon="@drawable/add_note"
|
||||
android:label="New note"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE"/>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".ReminderViewsService"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
||||
<service
|
||||
android:name=".ReminderViewsService"
|
||||
android:exported="true"
|
||||
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:grantUriPermissions="true"
|
||||
android:exported="false">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_viewer_provider_paths" />
|
||||
</provider>
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_viewer_provider_paths" />
|
||||
</provider>
|
||||
|
||||
<provider
|
||||
android:name="com.vinzscam.reactnativefileviewer.FileProvider"
|
||||
@@ -219,12 +252,14 @@
|
||||
android:resource="@xml/file_viewer_provider_paths" />
|
||||
</provider>
|
||||
|
||||
<receiver android:exported="true" android:name=".BootRecieverService">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
<receiver
|
||||
android:name=".BootRecieverService"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
</application>
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ public class BootTaskService extends HeadlessJsTaskService {
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||
NotificationChannel channel = new NotificationChannel("com.streetwriters.notesnook",
|
||||
"Default",
|
||||
@@ -55,7 +54,7 @@ public class BootTaskService extends HeadlessJsTaskService {
|
||||
this.startForeground(
|
||||
1,
|
||||
notification,
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC);
|
||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE);
|
||||
} else {
|
||||
this.startForeground(
|
||||
1,
|
||||
|
||||
@@ -3,8 +3,16 @@ package com.streetwriters.notesnook;
|
||||
import com.facebook.react.ReactActivity;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import androidx.core.graphics.Insets;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import androidx.core.view.OnApplyWindowInsetsListener;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
|
||||
import com.facebook.react.ReactActivityDelegate;
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint;
|
||||
import com.facebook.react.defaults.DefaultReactActivityDelegate;
|
||||
@@ -22,6 +30,20 @@ public class MainActivity extends ReactActivity {
|
||||
try {
|
||||
startService(new Intent(getBaseContext(), OnClearFromRecentService.class));
|
||||
} catch (Exception ignored) {}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= 35) {
|
||||
final View rootView = findViewById(android.R.id.content);
|
||||
ViewCompat.setOnApplyWindowInsetsListener(rootView, (v, insets) -> {
|
||||
Insets innerPadding = insets.getInsets(WindowInsetsCompat.Type.ime());
|
||||
rootView.setPadding(
|
||||
innerPadding.left,
|
||||
innerPadding.top,
|
||||
innerPadding.right,
|
||||
innerPadding.bottom
|
||||
);
|
||||
return insets;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7,7 +7,9 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Color;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.RemoteViews;
|
||||
@@ -217,4 +219,24 @@ public class RCTNNativeModule extends ReactContextBaseJavaModule {
|
||||
wm.notifyAppWidgetViewDataChanged(id, R.id.widget_list_view);
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod(isBlockingSynchronousMethod = true)
|
||||
public boolean isGestureNavigationEnabled() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
try {
|
||||
String navBarMode = Settings.Secure.getString(
|
||||
mContext.getContentResolver(),
|
||||
"navigation_mode"
|
||||
);
|
||||
return "2".equals(navBarMode);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
- New and improved search with highlighting
|
||||
- Fixed app crashing on android 16 devices
|
||||
- Many bug fixes and improvements
|
||||
- Improved editor UI for better usability
|
||||
- Improved UX for creating and editing reminders
|
||||
- Minor bug fixes and improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
@@ -5,5 +5,5 @@ rootProject.name = 'Notesnook'
|
||||
include ':app'
|
||||
includeBuild('../../node_modules/@react-native/gradle-plugin')
|
||||
|
||||
include ":lazysodium"
|
||||
project(':lazysodium').projectDir = new File(rootProject.projectDir, '../../node_modules/@ammarahmed/react-native-sodium/android/lazysodium')
|
||||
include ":lazysodium-android"
|
||||
project(':lazysodium-android').projectDir = new File(rootProject.projectDir, '../../node_modules/@ammarahmed/react-native-sodium/android/lazysodium-android/app')
|
||||
|
||||
@@ -124,7 +124,6 @@
|
||||
6529D2B0257B4A2900B49BC3 /* NotesnookDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = NotesnookDebug.entitlements; path = Notesnook/NotesnookDebug.entitlements; sourceTree = "<group>"; };
|
||||
656835802BB29A8300144BAB /* OpenSans-Italic.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "OpenSans-Italic.ttf"; path = "../../../../packages/editor-mobile/public/fonts/OpenSans-Italic.ttf"; sourceTree = "<group>"; };
|
||||
6593E4A2281C345400492C50 /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = Notesnook/AppDelegate.mm; sourceTree = "<group>"; };
|
||||
6594DDF22CD8BFE8007F5EC2 /* BetterTrigram.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = BetterTrigram.xcframework; sourceTree = "<group>"; };
|
||||
659BE46625E11A5100E05671 /* notesnook-text.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = "notesnook-text.png"; path = "Notesnook/Images.xcassets/notesnook-text.png"; sourceTree = "<group>"; };
|
||||
65A7F34F255689AD00699170 /* Notesnook.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = Notesnook.entitlements; path = Notesnook/Notesnook.entitlements; sourceTree = "<group>"; };
|
||||
65AA857725E6DDEC00772A01 /* NotesWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotesWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -256,7 +255,6 @@
|
||||
2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
6594DDF22CD8BFE8007F5EC2 /* BetterTrigram.xcframework */,
|
||||
6515C42E2580AA2F00E83E39 /* StoreKit.framework */,
|
||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
|
||||
ED2971642150620600B7C4FE /* JavaScriptCore.framework */,
|
||||
@@ -1091,7 +1089,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2136;
|
||||
CURRENT_PROJECT_VERSION = 2147;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
ENABLE_BITCODE = NO;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1165,7 +1163,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.2.2;
|
||||
MARKETING_VERSION = 3.2.12;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -1196,7 +1194,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 2136;
|
||||
CURRENT_PROJECT_VERSION = 2147;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1270,7 +1268,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.2.2;
|
||||
MARKETING_VERSION = 3.2.12;
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
@@ -1429,7 +1427,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2136;
|
||||
CURRENT_PROJECT_VERSION = 2147;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1441,7 +1439,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.2.2;
|
||||
MARKETING_VERSION = 3.2.12;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
@@ -1472,7 +1470,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2136;
|
||||
CURRENT_PROJECT_VERSION = 2147;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1485,7 +1483,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.2.2;
|
||||
MARKETING_VERSION = 3.2.12;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1515,10 +1513,16 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2136;
|
||||
CURRENT_PROJECT_VERSION = 2147;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"${PODS_ROOT}/hermes-engine/destroot/Library/Frameworks/universal\"",
|
||||
"\"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built\"",
|
||||
"\"${PROJECT_DIR}\"",
|
||||
);
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -1589,7 +1593,8 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.2.2;
|
||||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
|
||||
MARKETING_VERSION = 3.2.12;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
@@ -1620,11 +1625,17 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2136;
|
||||
CURRENT_PROJECT_VERSION = 2147;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"\"${PODS_ROOT}/hermes-engine/destroot/Library/Frameworks/universal\"",
|
||||
"\"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built\"",
|
||||
"\"${PROJECT_DIR}\"",
|
||||
);
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
@@ -1695,7 +1706,8 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.2.2;
|
||||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
|
||||
MARKETING_VERSION = 3.2.12;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
||||
@@ -1325,6 +1325,28 @@ PODS:
|
||||
- react-native-pdf (6.7.7):
|
||||
- React-Core
|
||||
- react-native-quick-sqlite (8.2.7):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
- RCT-Folly (= 2024.11.18.00)
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- React-Core
|
||||
- React-debug
|
||||
- React-Fabric
|
||||
- React-featureflags
|
||||
- React-graphics
|
||||
- React-ImageManager
|
||||
- react-native-quick-sqlite/lexbor (= 8.2.7)
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-rendererdebug
|
||||
- React-utils
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- react-native-quick-sqlite/lexbor (8.2.7):
|
||||
- DoubleConversion
|
||||
- glog
|
||||
- hermes-engine
|
||||
@@ -1352,7 +1374,7 @@ PODS:
|
||||
- SDWebImage (~> 5.11.1)
|
||||
- react-native-share-extension (2.9.0):
|
||||
- React
|
||||
- react-native-sodium (1.6.3):
|
||||
- react-native-sodium (1.6.5):
|
||||
- React
|
||||
- react-native-theme-switch-animation (0.6.0):
|
||||
- DoubleConversion
|
||||
@@ -2409,11 +2431,11 @@ SPEC CHECKSUMS:
|
||||
react-native-orientation-locker: cc6f357b289a2e0dd2210fea0c52cb8e0727fdaa
|
||||
react-native-pager-view: 5aaf51a9338f7997b8acee0e0febfddd1ade5c0a
|
||||
react-native-pdf: 6a51a22ccefb23eb93298771e4bf090913e86d70
|
||||
react-native-quick-sqlite: 6aebe0e1c264c48450059e271e5d2404013cea93
|
||||
react-native-quick-sqlite: 1bfc7f1e9acbe9a5aa5c4cc81712e9bde3ab7672
|
||||
react-native-safe-area-context: 9d72abf6d8473da73033b597090a80b709c0b2f1
|
||||
react-native-screenguard: 82437eeb0086a90b5e5d7e54130bb04fb406373e
|
||||
react-native-share-extension: bcb7e466390a9e50c742f4b1019d6f181aedd7ad
|
||||
react-native-sodium: aa26d9f46dcfdebd92d3af793b74fbb234a4d75d
|
||||
react-native-sodium: 285eec063e4232cb67347ef6a434b85e588d38cb
|
||||
react-native-theme-switch-animation: d90fe2de0d9e87a63cd6235d98cba6e7054e9a10
|
||||
react-native-webview: 079eca50edf657503318b66687dadfb903731aa8
|
||||
React-nativeconfig: ecf4dc92c40b97e2b3f0c619938f78bfd6507b08
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
"react-native-screenguard": "^1.0.0",
|
||||
"@formatjs/intl-locale": "4.0.0",
|
||||
"@formatjs/intl-pluralrules": "5.2.14",
|
||||
"@ammarahmed/react-native-sodium": "^1.6.4",
|
||||
"@ammarahmed/react-native-sodium": "^1.6.5",
|
||||
"@react-native-community/datetimepicker": "^8.2.0",
|
||||
"react-native-exit-app": "github:ammarahm-ed/react-native-exit-app",
|
||||
"react-native-begin-background-task": "github:blockfirm/react-native-begin-background-task",
|
||||
|
||||
14
apps/mobile/package-lock.json
generated
14
apps/mobile/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.2.2",
|
||||
"version": "3.2.10",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.2.2",
|
||||
"version": "3.2.10",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
@@ -1098,7 +1098,7 @@
|
||||
"refractor": "^4.8.1",
|
||||
"sqlite-better-trigram": "^0.0.3",
|
||||
"sqlite-regex": "^0.2.4-alpha.1",
|
||||
"sqlite3-fts5-html": "^0.0.3",
|
||||
"sqlite3-fts5-html": "^0.0.4",
|
||||
"vitest": "2.1.8",
|
||||
"vitest-fetch-mock": "^0.2.2",
|
||||
"ws": "^8.13.0"
|
||||
@@ -28324,7 +28324,7 @@
|
||||
"@ammarahmed/react-native-eventsource": "1.1.0",
|
||||
"@ammarahmed/react-native-fingerprint-scanner": "^5.0.0",
|
||||
"@ammarahmed/react-native-share-extension": "^2.9.0",
|
||||
"@ammarahmed/react-native-sodium": "^1.6.4",
|
||||
"@ammarahmed/react-native-sodium": "^1.6.5",
|
||||
"@bam.tech/react-native-image-resizer": "3.0.11",
|
||||
"@callstack/repack": "~5.1.2",
|
||||
"@formatjs/intl-locale": "4.0.0",
|
||||
@@ -28478,9 +28478,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ammarahmed/react-native-sodium": {
|
||||
"version": "1.6.4",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-sodium/-/react-native-sodium-1.6.4.tgz",
|
||||
"integrity": "sha512-+OIBABV/8IPXVQPRxiub0WW86X/bs14GHnQ1VqtxWGv7phwfyOVzi9c1/LDeRqjCDKBAYHOv2a5PLHZb2RzeGw==",
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-sodium/-/react-native-sodium-1.6.5.tgz",
|
||||
"integrity": "sha512-x8YkHWHaHiB4QomjTAXyw/aLjrsFMWumJxSRrEeUOVeArVszbrwZ88B+2zAq+sPZ+HSMi/YrnrvgzbwvOCLnNA==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.2.2",
|
||||
"version": "3.2.12",
|
||||
"private": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
|
||||
39
apps/mobile/patches/@react-navigation+core+6.4.8.patch
Normal file
39
apps/mobile/patches/@react-navigation+core+6.4.8.patch
Normal file
@@ -0,0 +1,39 @@
|
||||
diff --git a/node_modules/@react-navigation/core/lib/commonjs/useNavigationCache.js b/node_modules/@react-navigation/core/lib/commonjs/useNavigationCache.js
|
||||
index c7f1df6..0141f28 100644
|
||||
--- a/node_modules/@react-navigation/core/lib/commonjs/useNavigationCache.js
|
||||
+++ b/node_modules/@react-navigation/core/lib/commonjs/useNavigationCache.js
|
||||
@@ -109,7 +109,7 @@ function useNavigationCache(_ref) {
|
||||
})),
|
||||
isFocused: () => {
|
||||
const state = getState();
|
||||
- if (state.routes[state.index].key !== route.key) {
|
||||
+ if (state.routes[state.index]?.key !== route.key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
diff --git a/node_modules/@react-navigation/core/lib/module/useNavigationCache.js b/node_modules/@react-navigation/core/lib/module/useNavigationCache.js
|
||||
index c4d65c6..89abe63 100644
|
||||
--- a/node_modules/@react-navigation/core/lib/module/useNavigationCache.js
|
||||
+++ b/node_modules/@react-navigation/core/lib/module/useNavigationCache.js
|
||||
@@ -100,7 +100,7 @@ export default function useNavigationCache(_ref) {
|
||||
})),
|
||||
isFocused: () => {
|
||||
const state = getState();
|
||||
- if (state.routes[state.index].key !== route.key) {
|
||||
+ if (state.routes[state.index]?.key !== route.key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
diff --git a/node_modules/@react-navigation/core/src/useNavigationCache.tsx b/node_modules/@react-navigation/core/src/useNavigationCache.tsx
|
||||
index 390120a..1b0f2d1 100644
|
||||
--- a/node_modules/@react-navigation/core/src/useNavigationCache.tsx
|
||||
+++ b/node_modules/@react-navigation/core/src/useNavigationCache.tsx
|
||||
@@ -157,7 +157,7 @@ export default function useNavigationCache<
|
||||
isFocused: () => {
|
||||
const state = getState();
|
||||
|
||||
- if (state.routes[state.index].key !== route.key) {
|
||||
+ if (state.routes[state.index]?.key !== route.key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,24 @@
|
||||
diff --git a/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js b/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
|
||||
index 77c81f0..be34558 100644
|
||||
--- a/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
|
||||
+++ b/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
|
||||
@@ -156,7 +156,8 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
|
||||
|
||||
if (
|
||||
Platform.OS === 'ios' &&
|
||||
- this._windowWidth !== this._keyboardEvent.endCoordinates.width
|
||||
+ this._windowWidth !== this._keyboardEvent.endCoordinates.width &&
|
||||
+ this._windowWidth > this._keyboardEvent.endCoordinates.width
|
||||
) {
|
||||
// The keyboard is not the standard bottom-of-the-screen keyboard. For example, floating keyboard on iPadOS.
|
||||
this._setBottom(0);
|
||||
diff --git a/node_modules/react-native/scripts/.packager.env b/node_modules/react-native/scripts/.packager.env
|
||||
new file mode 100644
|
||||
index 0000000..361f5fb
|
||||
--- /dev/null
|
||||
+++ b/node_modules/react-native/scripts/.packager.env
|
||||
@@ -0,0 +1 @@
|
||||
+export RCT_METRO_PORT=8081
|
||||
diff --git a/node_modules/react-native/scripts/packager.sh b/node_modules/react-native/scripts/packager.sh
|
||||
index 00d6ebc..6846832 100755
|
||||
--- a/node_modules/react-native/scripts/packager.sh
|
||||
|
||||
12
apps/mobile/patches/react-native-actions-sheet+0.9.7.patch
Normal file
12
apps/mobile/patches/react-native-actions-sheet+0.9.7.patch
Normal file
@@ -0,0 +1,12 @@
|
||||
diff --git a/node_modules/react-native-actions-sheet/dist/src/index.js b/node_modules/react-native-actions-sheet/dist/src/index.js
|
||||
index 0e124da..3703153 100644
|
||||
--- a/node_modules/react-native-actions-sheet/dist/src/index.js
|
||||
+++ b/node_modules/react-native-actions-sheet/dist/src/index.js
|
||||
@@ -1088,6 +1088,7 @@ export default forwardRef(function ActionSheet(_a, ref) {
|
||||
* Always true, it causes issue with keyboard handling.
|
||||
*/
|
||||
statusBarTranslucent: true,
|
||||
+ navigationBarTranslucent: true
|
||||
}
|
||||
: {
|
||||
testID: ((_b = props.testIDs) === null || _b === void 0 ? void 0 : _b.root) || props.testID,
|
||||
@@ -36,3 +36,37 @@ index 3dfe1dc..70ef9da 100644
|
||||
+ implementation 'io.legere:pdfiumandroid:1.0.32'
|
||||
implementation 'com.google.code.gson:gson:2.8.5'
|
||||
}
|
||||
diff --git a/node_modules/react-native-pdf/android/src/main/java/org/wonday/pdf/PdfView.java b/node_modules/react-native-pdf/android/src/main/java/org/wonday/pdf/PdfView.java
|
||||
index 7b7a125..361924b 100644
|
||||
--- a/node_modules/react-native-pdf/android/src/main/java/org/wonday/pdf/PdfView.java
|
||||
+++ b/node_modules/react-native-pdf/android/src/main/java/org/wonday/pdf/PdfView.java
|
||||
@@ -12,6 +12,8 @@ import java.io.File;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
+import android.os.Handler;
|
||||
+import android.os.Looper;
|
||||
import android.util.SizeF;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -105,7 +107,7 @@ public class PdfView extends PDFView implements OnPageChangeListener,OnLoadCompl
|
||||
TopChangeEvent tce = new TopChangeEvent(surfaceId, getId(), event);
|
||||
|
||||
if (dispatcher != null) {
|
||||
- dispatcher.dispatchEvent(tce);
|
||||
+ new Handler(Looper.getMainLooper()).postDelayed(() -> dispatcher.dispatchEvent(tce), 10);
|
||||
}
|
||||
|
||||
// ReactContext reactContext = (ReactContext)this.getContext();
|
||||
diff --git a/node_modules/react-native-pdf/index.js b/node_modules/react-native-pdf/index.js
|
||||
index 56df005..dd14412 100644
|
||||
--- a/node_modules/react-native-pdf/index.js
|
||||
+++ b/node_modules/react-native-pdf/index.js
|
||||
@@ -364,7 +364,6 @@ export default class Pdf extends Component {
|
||||
}
|
||||
|
||||
_onChange = (event) => {
|
||||
-
|
||||
let message = event.nativeEvent.message.split('|');
|
||||
//__DEV__ && console.log("onChange: " + message);
|
||||
if (message.length > 0) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
|
||||
FROM oven/bun:1.2.2-alpine
|
||||
FROM oven/bun:1.2.21-slim
|
||||
|
||||
RUN mkdir -p /home/bun/app && chown -R bun:bun /home/bun/app
|
||||
|
||||
|
||||
@@ -107,10 +107,12 @@ function generateTableOfContents() {
|
||||
|
||||
export const MonographPage = ({
|
||||
monograph,
|
||||
encodedKey
|
||||
encodedKey,
|
||||
apiHost
|
||||
}: {
|
||||
monograph: Monograph;
|
||||
encodedKey?: string;
|
||||
apiHost: string;
|
||||
}) => {
|
||||
const [reportDialogVisible, setReportDialogVisible] = useState(false);
|
||||
const [tableOfContents, setTableOfContents] = useState<TableOfContent[]>([]);
|
||||
@@ -227,7 +229,7 @@ export const MonographPage = ({
|
||||
)}
|
||||
<Image
|
||||
sx={{ display: "none" }}
|
||||
src={`https://api.notesnook.com/monographs/${monograph.id}/view`}
|
||||
src={`${apiHost}/monographs/${monograph.id}/view`}
|
||||
/>
|
||||
</Flex>
|
||||
</Box>
|
||||
|
||||
@@ -90,7 +90,7 @@ function getContentSecurityPolicy(nonce?: string) {
|
||||
const connect_src =
|
||||
process.env.NODE_ENV === "development"
|
||||
? "'self' ws://localhost:*"
|
||||
: "'self'";
|
||||
: "'self' https://notesnook.com/api/v1/reports/submit";
|
||||
|
||||
return (
|
||||
`script-src ${script_src} 'strict-dynamic'; ` +
|
||||
|
||||
@@ -85,7 +85,8 @@ export async function loader({ params }: LoaderFunctionArgs) {
|
||||
const metadata = getMonographMetadata(monograph);
|
||||
return {
|
||||
monograph,
|
||||
metadata
|
||||
metadata,
|
||||
apiHost: API_HOST
|
||||
};
|
||||
} catch (e) {
|
||||
// console.error(e);
|
||||
@@ -96,19 +97,24 @@ export async function loader({ params }: LoaderFunctionArgs) {
|
||||
fullDescription: "This monograph does not exist.",
|
||||
shortDescription: "This monograph does not exist.",
|
||||
datePublished: ""
|
||||
}
|
||||
},
|
||||
apiHost: API_HOST
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export default function MonographPost() {
|
||||
const { monograph } = useLoaderData<typeof loader>();
|
||||
const { monograph, apiHost } = useLoaderData<typeof loader>();
|
||||
const [_, hashParams] = useHashLocation();
|
||||
|
||||
return (
|
||||
<>
|
||||
{monograph ? (
|
||||
<MonographPage monograph={monograph} encodedKey={hashParams.key} />
|
||||
<MonographPage
|
||||
monograph={monograph}
|
||||
encodedKey={hashParams.key}
|
||||
apiHost={apiHost}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<Header />
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user