Compare commits
113 Commits
v3.0.14-be
...
fix-remind
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6f42d1419 | ||
|
|
7b5fa09c71 | ||
|
|
d85974ed7a | ||
|
|
9598ea7542 | ||
|
|
42048ad53a | ||
|
|
1fbd2efb92 | ||
|
|
fe91ef79a0 | ||
|
|
583d585b2f | ||
|
|
94b28f86ca | ||
|
|
f927f05f86 | ||
|
|
bde8dea048 | ||
|
|
511bea9b23 | ||
|
|
eda95530de | ||
|
|
5173decaa5 | ||
|
|
00dcb55f6e | ||
|
|
6dbe1073cd | ||
|
|
9be3e4fcfa | ||
|
|
f9f0a866bd | ||
|
|
daafeb2fa8 | ||
|
|
a92b34b81c | ||
|
|
e29b6838b3 | ||
|
|
16ff7c92e4 | ||
|
|
07e1ea6edf | ||
|
|
b26ede95fb | ||
|
|
971793a3ad | ||
|
|
6a2b5e1499 | ||
|
|
d18b775d06 | ||
|
|
d6cc899d95 | ||
|
|
ea10058276 | ||
|
|
568c88be3e | ||
|
|
f047552f18 | ||
|
|
9a58430b5d | ||
|
|
205885edf5 | ||
|
|
8ffb49b700 | ||
|
|
25b18a2edd | ||
|
|
cfea85c8fb | ||
|
|
82fc6d31c3 | ||
|
|
fb2de1753e | ||
|
|
ceb5ce5cdd | ||
|
|
98d443001c | ||
|
|
5115dee069 | ||
|
|
0dc29c7026 | ||
|
|
a5eb5e9df3 | ||
|
|
b680575328 | ||
|
|
c94ef616a5 | ||
|
|
3ae40c09fc | ||
|
|
5de4e00ce2 | ||
|
|
99e4e61a55 | ||
|
|
1ad7bfb82f | ||
|
|
30ef679432 | ||
|
|
bb97f5e757 | ||
|
|
6cd5a04470 | ||
|
|
0c32ebf163 | ||
|
|
e150e08f29 | ||
|
|
bbd603f10c | ||
|
|
677944cba9 | ||
|
|
dd2cee53d3 | ||
|
|
60c8d5ac89 | ||
|
|
59276de406 | ||
|
|
67981be516 | ||
|
|
77dfcec08c | ||
|
|
c4360ab95f | ||
|
|
17c4b72381 | ||
|
|
e91cc2bdca | ||
|
|
811f2305ec | ||
|
|
98a707b5f5 | ||
|
|
aa9b24fa10 | ||
|
|
0b3060e4c0 | ||
|
|
d523db84cc | ||
|
|
6a6a9ae6b3 | ||
|
|
b1bc28e069 | ||
|
|
f95c068bc1 | ||
|
|
0e4cd087d4 | ||
|
|
f217b8cfec | ||
|
|
d3787efbf0 | ||
|
|
e279878b12 | ||
|
|
154a227be0 | ||
|
|
756259c907 | ||
|
|
453ed4820a | ||
|
|
10ab612366 | ||
|
|
8dc4f7ed3d | ||
|
|
a73538bafc | ||
|
|
014887c7dc | ||
|
|
246e49bd8c | ||
|
|
146ac12935 | ||
|
|
4f5245adcf | ||
|
|
0bc0cc5729 | ||
|
|
1136072eff | ||
|
|
4cc8a538a4 | ||
|
|
271913ba20 | ||
|
|
3efe541869 | ||
|
|
b3bcdd4f9c | ||
|
|
13f739763c | ||
|
|
bde2b26133 | ||
|
|
6eef65952d | ||
|
|
3d85795216 | ||
|
|
f1a1989990 | ||
|
|
76ff5eddd0 | ||
|
|
068ee8dc68 | ||
|
|
bbf0fb641b | ||
|
|
3cae22faf1 | ||
|
|
34a4a27ba0 | ||
|
|
d96a78c741 | ||
|
|
c9e5ee9ddb | ||
|
|
f43c2a40f5 | ||
|
|
b12a0e979c | ||
|
|
d0372b82db | ||
|
|
dac1441add | ||
|
|
a65ebec7ae | ||
|
|
d5cf65e568 | ||
|
|
424073298e | ||
|
|
c54759e766 | ||
|
|
f15446b1d6 |
16
.github/workflows/desktop.publish.yml
vendored
@@ -147,7 +147,7 @@ jobs:
|
||||
CSC_KEY_PASSWORD: ${{ secrets.mac_certs_password }}
|
||||
run: |
|
||||
npx nx run release --project @notesnook/desktop -- --variant=mas
|
||||
npx electron-builder --mac mas --universal -p never
|
||||
yarn electron-builder --mac mas --universal -p never
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Build zip and dmg
|
||||
@@ -161,9 +161,9 @@ jobs:
|
||||
run: |
|
||||
npx nx run release --project @notesnook/desktop
|
||||
if [ ${{ inputs.publish-github }} == true ]; then
|
||||
npx electron-builder --mac zip dmg --arm64 --x64 -p always
|
||||
yarn electron-builder --mac zip dmg --arm64 --x64 -p always
|
||||
else
|
||||
npx electron-builder --mac zip dmg --arm64 --x64 -p never
|
||||
yarn electron-builder --mac zip dmg --arm64 --x64 -p never
|
||||
fi
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
@@ -219,7 +219,7 @@ jobs:
|
||||
- name: Build snap
|
||||
if: inputs.publish-snap
|
||||
run: |
|
||||
npx electron-builder --linux snap:x64 -p never
|
||||
yarn electron-builder --linux snap:x64 -p never
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
- name: Build AppImage
|
||||
@@ -227,9 +227,9 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
if [ ${{ inputs.publish-github }} == true ]; then
|
||||
npx electron-builder --linux AppImage:x64 AppImage:arm64 -p always
|
||||
yarn electron-builder --linux AppImage:x64 AppImage:arm64 -p always
|
||||
else
|
||||
npx electron-builder --linux AppImage:x64 AppImage:arm64 -p never
|
||||
yarn electron-builder --linux AppImage:x64 AppImage:arm64 -p never
|
||||
fi
|
||||
working-directory: ./apps/desktop
|
||||
|
||||
@@ -254,8 +254,8 @@ jobs:
|
||||
with:
|
||||
dotnet-version: 6.0.x
|
||||
|
||||
- name: Install AzureCodeSigning
|
||||
run: Install-Module -Name AzureCodeSigning -RequiredVersion 0.3.0 -Force -Repository PSGallery
|
||||
- name: Install TrustedSigning
|
||||
run: Install-Module -Name TrustedSigning -RequiredVersion 0.3.15 -Force -Repository PSGallery
|
||||
shell: pwsh
|
||||
|
||||
- name: Setup Node
|
||||
|
||||
2
.github/workflows/web.tests.yml
vendored
@@ -67,7 +67,7 @@ jobs:
|
||||
- name: Install packages
|
||||
run: |
|
||||
npm ci --ignore-scripts --prefer-offline --no-audit
|
||||
npm install -D @playwright/test otplib
|
||||
npm install -D @playwright/test otplib dayjs @zip.js/zip.js
|
||||
|
||||
- name: Validate build extraction
|
||||
run: ls -ld ./apps/web/build/index.html
|
||||
|
||||
|
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 520 B After Width: | Height: | Size: 401 B |
|
Before Width: | Height: | Size: 917 B After Width: | Height: | Size: 543 B |
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 763 B |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 353 KiB After Width: | Height: | Size: 353 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 7.3 KiB |
630
apps/desktop/package-lock.json
generated
@@ -2,7 +2,7 @@
|
||||
"name": "@notesnook/desktop",
|
||||
"productName": "Notesnook",
|
||||
"description": "Your private note taking space",
|
||||
"version": "3.0.14-beta",
|
||||
"version": "3.0.0",
|
||||
"appAppleId": "1544027013",
|
||||
"private": true,
|
||||
"main": "./dist/index.js",
|
||||
@@ -14,11 +14,11 @@
|
||||
"@notesnook/crypto": "file:../../packages/crypto",
|
||||
"@trpc/client": "10.38.3",
|
||||
"@trpc/server": "10.38.3",
|
||||
"better-sqlite3-multiple-ciphers": "^9.4.0",
|
||||
"better-sqlite3-multiple-ciphers": "^9.5.0",
|
||||
"electron-trpc": "0.5.2",
|
||||
"electron-updater": "6.1.4",
|
||||
"icojs": "^0.17.1",
|
||||
"sodium-native": "^4.0.6",
|
||||
"sodium-native": "^4.1.1",
|
||||
"typed-emitter": "^2.1.0",
|
||||
"yargs": "^17.6.2",
|
||||
"zod": "^3.21.4"
|
||||
@@ -27,12 +27,13 @@
|
||||
"@types/node": "18.16.1",
|
||||
"@types/yargs": "^17.0.24",
|
||||
"chokidar": "^3.5.3",
|
||||
"electron": "^28.2.1",
|
||||
"electron-builder": "^24.9.1",
|
||||
"electron": "^29.3.1",
|
||||
"electron-builder": "^24.13.3",
|
||||
"esbuild": "^0.20.0",
|
||||
"prebuildify": "^6.0.0",
|
||||
"node-gyp-build": "^4.8.0",
|
||||
"prebuildify": "^6.0.1",
|
||||
"tree-kill": "^1.2.2",
|
||||
"undici": "^6.6.1"
|
||||
"undici": "^6.14.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"dmg-license": "^1.0.11"
|
||||
@@ -56,8 +57,8 @@
|
||||
"productName": "Notesnook",
|
||||
"copyright": "Copyright © 2023 Streetwriters (Private) Limited",
|
||||
"artifactName": "notesnook_${os}_${arch}.${ext}",
|
||||
"asar": false,
|
||||
"generateUpdatesFilesForAllChannels": true,
|
||||
"asar": false,
|
||||
"files": [
|
||||
"!*.chunk.js.map",
|
||||
"!*.chunk.js.LICENSE.txt",
|
||||
@@ -220,8 +221,7 @@
|
||||
{
|
||||
"provider": "github",
|
||||
"repo": "notesnook",
|
||||
"owner": "streetwriters",
|
||||
"channel": "beta"
|
||||
"owner": "streetwriters"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
diff --git a/node_modules/prebuildify/index.js b/node_modules/prebuildify/index.js
|
||||
index ca9829a..7729aaf 100644
|
||||
--- a/node_modules/prebuildify/index.js
|
||||
+++ b/node_modules/prebuildify/index.js
|
||||
@@ -1,5 +1,4 @@
|
||||
var proc = require('child_process')
|
||||
-var execspawn = require('execspawn')
|
||||
var os = require('os')
|
||||
var path = require('path')
|
||||
var fs = require('fs')
|
||||
@@ -179,11 +178,12 @@ function copySharedLibs (builds, folder, opts, cb) {
|
||||
function run (cmd, opts, cb) {
|
||||
if (!cmd) return cb()
|
||||
|
||||
- var child = execspawn(cmd, {
|
||||
+ var child = proc.spawn(cmd, [], {
|
||||
cwd: opts.cwd,
|
||||
env: opts.env,
|
||||
stdio: 'inherit',
|
||||
- shell: opts.shell
|
||||
+ shell: opts.shell || true,
|
||||
+ windowsHide: true
|
||||
})
|
||||
|
||||
child.on('exit', function (code) {
|
||||
@@ -230,6 +230,8 @@ function build (target, runtime, opts, cb) {
|
||||
var child = proc.spawn(opts.nodeGyp, args, {
|
||||
cwd: opts.cwd,
|
||||
env: opts.env,
|
||||
+ shell: opts.shell,
|
||||
+ windowsHide: true,
|
||||
stdio: opts.quiet ? 'ignore' : 'inherit'
|
||||
})
|
||||
|
||||
@@ -266,7 +268,11 @@ function strip (file, opts, cb) {
|
||||
if (!opts.strip || (platform !== 'darwin' && platform !== 'linux')) return cb()
|
||||
|
||||
var args = platform === 'darwin' ? [file, '-Sx'] : [file, '--strip-all']
|
||||
- var child = proc.spawn(opts.stripBin, args, { stdio: 'ignore' })
|
||||
+ var child = proc.spawn(opts.stripBin, args, {
|
||||
+ stdio: 'ignore',
|
||||
+ shell: opts.shell,
|
||||
+ windowsHide: true
|
||||
+ })
|
||||
|
||||
child.on('exit', function (code) {
|
||||
if (code) return cb(spawnError(opts.stripBin, code))
|
||||
@@ -300,7 +306,11 @@ function npmbin (name) {
|
||||
}
|
||||
|
||||
function shell () {
|
||||
- return os.platform() === 'android' ? 'sh' : undefined
|
||||
+ switch (os.platform()) {
|
||||
+ case 'win32': return true
|
||||
+ case 'android': return 'sh'
|
||||
+ default: return undefined
|
||||
+ }
|
||||
}
|
||||
|
||||
function resolveTargets (targets, all, napi, electronCompat) {
|
||||
@@ -19,27 +19,32 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import path from "path";
|
||||
import fs, { readFile, writeFile } from "fs/promises";
|
||||
import { existsSync } from "fs";
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import yargs from "yargs-parser";
|
||||
import os from "os";
|
||||
import * as childProcess from "child_process";
|
||||
import { fileURLToPath } from "url";
|
||||
import packageJson from "../package.json" with {type: "json"};
|
||||
|
||||
const args = yargs(process.argv);
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const packageJson = JSON.parse(
|
||||
readFileSync(path.join(__dirname, "..", "package.json"), "utf-8")
|
||||
);
|
||||
|
||||
const webAppPath = path.resolve(path.join(__dirname, "..", "..", "web"));
|
||||
|
||||
await fs.rm("./build/", { force: true, recursive: true });
|
||||
|
||||
if (args.rebuild || !existsSync(path.join(webAppPath, "build"))) {
|
||||
await exec(`cd ${webAppPath} && npm run build:desktop`);
|
||||
await exec(
|
||||
"npx nx build:desktop @notesnook/web",
|
||||
path.join(__dirname, "..", "..", "..")
|
||||
);
|
||||
}
|
||||
|
||||
// temporary until there's support for prebuilt binaries for windows ARM & linux ARM
|
||||
await patchBetterSQLite3();
|
||||
// temporary until there's support for prebuilt binaries for linux ARM
|
||||
if (os.platform() === "linux") await patchBetterSQLite3();
|
||||
|
||||
if (os.platform() === "win32")
|
||||
await exec(
|
||||
@@ -89,7 +94,7 @@ async function patchBetterSQLite3() {
|
||||
);
|
||||
const json = JSON.parse(await readFile(jsonPath, "utf-8"));
|
||||
|
||||
json.version = "9.4.1";
|
||||
json.version = "9.5.1";
|
||||
json.homepage = "https://github.com/thecodrr/better-sqlite3-multiple-ciphers";
|
||||
json.repository.url =
|
||||
"git://github.com/thecodrr/better-sqlite3-multiple-ciphers.git";
|
||||
|
||||
@@ -33,7 +33,7 @@ module.exports = async function (configuration) {
|
||||
const DescriptionUrl = "https://notesnook.com/";
|
||||
const FilesCatalog = createCatalog(configuration.path);
|
||||
|
||||
const command = `Invoke-AzureCodeSigning -Endpoint "${Endpoint}" -CodeSigningAccountName "${CodeSigningAccountName}" -CertificateProfileName "${CertificateProfileName}" -FileDigest "${FileDigest}" -TimestampRfc3161 "${TimestampRfc3161}" -TimestampDigest "${TimestampDigest}" -Description "${Description}" -DescriptionUrl "${DescriptionUrl}" -FilesCatalog "${FilesCatalog}"`;
|
||||
const command = `Invoke-TrustedSigning -Endpoint "${Endpoint}" -CodeSigningAccountName "${CodeSigningAccountName}" -CertificateProfileName "${CertificateProfileName}" -FileDigest "${FileDigest}" -TimestampRfc3161 "${TimestampRfc3161}" -TimestampDigest "${TimestampDigest}" -Description "${Description}" -DescriptionUrl "${DescriptionUrl}" -FilesCatalog "${FilesCatalog}"`;
|
||||
|
||||
console.debug("Signing", configuration.path, "using command", command);
|
||||
|
||||
|
||||
@@ -151,6 +151,15 @@ async function createWindow() {
|
||||
app.once("ready", async () => {
|
||||
console.info("App ready. Opening window.");
|
||||
|
||||
app.configureHostResolver({
|
||||
secureDnsServers: [
|
||||
"https://mozilla.cloudflare-dns.com/dns-query",
|
||||
"https://dns.quad9.net/dns-query"
|
||||
],
|
||||
enableBuiltInResolver: true,
|
||||
secureDnsMode: "automatic"
|
||||
});
|
||||
|
||||
if (!isDevelopment()) registerProtocol();
|
||||
await createWindow();
|
||||
configureAutoUpdater();
|
||||
|
||||
@@ -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 { protocol } from "electron";
|
||||
import { net, protocol } from "electron";
|
||||
import { isDevelopment } from "./index";
|
||||
import { createReadStream } from "fs";
|
||||
import { extname, normalize } from "path";
|
||||
@@ -62,26 +62,7 @@ function registerProtocol() {
|
||||
headers: { "Content-Type": extensionToMimeType[fileExtension] }
|
||||
});
|
||||
} else {
|
||||
if (request.headers.has("X-Content-Length")) {
|
||||
request.headers.set(
|
||||
"Content-Length",
|
||||
request.headers.get("X-Content-Length") || "0"
|
||||
);
|
||||
request.headers.delete("X-Content-Length");
|
||||
}
|
||||
const headers = Object.fromEntries(request.headers.entries());
|
||||
|
||||
return await fetch(request.url, {
|
||||
signal: request.signal,
|
||||
mode: request.mode,
|
||||
headers,
|
||||
method: request.method,
|
||||
body: request.body,
|
||||
credentials: request.credentials,
|
||||
referrer: (request as any).referrer,
|
||||
duplex: "half",
|
||||
redirect: "manual"
|
||||
});
|
||||
return net.fetch(request, { bypassCustomProtocolHandlers: true });
|
||||
}
|
||||
});
|
||||
console.info(`${SCHEME} protocol inteception "successful"`);
|
||||
|
||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 108 KiB |
@@ -67,7 +67,7 @@ export async function hashBase64(data) {
|
||||
};
|
||||
}
|
||||
|
||||
export async function writeEncryptedBase64({ data, key }) {
|
||||
export async function writeEncryptedBase64(data, key) {
|
||||
await createCacheDir();
|
||||
let filepath = cacheDir + `/${getRandomId("imagecache_")}`;
|
||||
await RNFetchBlob.fs.writeFile(filepath, data, "base64");
|
||||
@@ -77,6 +77,8 @@ export async function writeEncryptedBase64({ data, key }) {
|
||||
});
|
||||
RNFetchBlob.fs.unlink(filepath).catch(console.log);
|
||||
console.log("encrypted file output: ", output);
|
||||
output.size = output.length;
|
||||
delete output.length;
|
||||
return {
|
||||
...output,
|
||||
alg: "xcha-stream"
|
||||
|
||||
@@ -221,7 +221,7 @@ const Actions = ({
|
||||
<View
|
||||
style={{
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.secondary.background,
|
||||
borderBottomColor: colors.primary.border,
|
||||
marginBottom: notes && notes.length > 0 ? 0 : 12
|
||||
}}
|
||||
>
|
||||
@@ -295,7 +295,7 @@ const Actions = ({
|
||||
<View
|
||||
style={{
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.secondary.background,
|
||||
borderBottomColor: colors.primary.border,
|
||||
marginBottom: 12,
|
||||
paddingVertical: 12
|
||||
}}
|
||||
|
||||
@@ -296,11 +296,6 @@ export const Login = ({ changeMode }) => {
|
||||
</Paragraph>
|
||||
</TouchableOpacity>
|
||||
) : null}
|
||||
|
||||
<Notice
|
||||
type="alert"
|
||||
text="After logging in from V3 beta, your account data will be migrated and you won't be able to use V2 clients to sync your notes."
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -21,8 +21,10 @@ import React, { PropsWithChildren, useEffect, useRef, useState } from "react";
|
||||
import { Dimensions, Platform, View, useWindowDimensions } from "react-native";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import useKeyboard from "../../hooks/use-keyboard";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
|
||||
export const Container = ({ children }: PropsWithChildren) => {
|
||||
const { colors } = useThemeColors();
|
||||
const insets = useGlobalSafeAreaInsets();
|
||||
const keyboard = useKeyboard();
|
||||
const [height, setHeight] = useState(0);
|
||||
@@ -43,7 +45,8 @@ export const Container = ({ children }: PropsWithChildren) => {
|
||||
paddingTop: Platform.OS === "android" ? 0 : insets.top,
|
||||
paddingBottom: Platform.OS === "android" ? 0 : insets.bottom,
|
||||
height: height || "100%",
|
||||
width: "100%"
|
||||
width: "100%",
|
||||
backgroundColor: colors.primary.background
|
||||
}}
|
||||
onLayout={(event) => {
|
||||
const height = event.nativeEvent.layout.height;
|
||||
|
||||
@@ -45,8 +45,8 @@ const DialogButtons = ({
|
||||
{
|
||||
backgroundColor: colors.secondary.background,
|
||||
height: 60,
|
||||
borderBottomRightRadius: 10,
|
||||
borderBottomLeftRadius: 10,
|
||||
borderBottomRightRadius: 5,
|
||||
borderBottomLeftRadius: 5,
|
||||
paddingHorizontal: 12,
|
||||
borderTopWidth: 0.7,
|
||||
borderTopColor: getColorLinearShade(
|
||||
|
||||
@@ -36,6 +36,7 @@ import DialogButtons from "./dialog-buttons";
|
||||
import DialogHeader from "./dialog-header";
|
||||
import { useCallback } from "react";
|
||||
import { Button } from "../ui/button";
|
||||
import { getContainerBorder } from "../../utils/colors";
|
||||
|
||||
export const Dialog = ({ context = "global" }) => {
|
||||
const { colors } = useThemeColors();
|
||||
@@ -121,7 +122,8 @@ export const Dialog = ({ context = "global" }) => {
|
||||
maxHeight: 450,
|
||||
borderRadius: 5,
|
||||
backgroundColor: colors.primary.background,
|
||||
paddingTop: 12
|
||||
paddingTop: 12,
|
||||
...getContainerBorder(colors.primary.border, 0.5)
|
||||
};
|
||||
|
||||
return visible ? (
|
||||
|
||||
@@ -146,12 +146,16 @@ const ColorPicker = ({
|
||||
onChangeText={(value) => {
|
||||
title.current = value;
|
||||
}}
|
||||
testID="color-title-input"
|
||||
defaultValue={title.current}
|
||||
placeholder={title.current || "Color title"}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title="Add color"
|
||||
style={{
|
||||
marginBottom: 10
|
||||
}}
|
||||
onPress={async () => {
|
||||
if (!selectedColor)
|
||||
return ToastManager.error(
|
||||
|
||||
@@ -71,7 +71,7 @@ export const RightMenus = ({
|
||||
testID={notesnook.ids.default.addBtn}
|
||||
icon={renderedInRoute === "Trash" ? "delete" : "plus"}
|
||||
iconSize={SIZE.xl}
|
||||
type="shade"
|
||||
type="accent"
|
||||
hitSlop={{
|
||||
top: 10,
|
||||
right: 10,
|
||||
@@ -82,7 +82,7 @@ export const RightMenus = ({
|
||||
marginLeft: 10,
|
||||
width: 32,
|
||||
height: 32,
|
||||
borderRadius: 5,
|
||||
borderRadius: 100,
|
||||
paddingHorizontal: 0,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.primary.accent
|
||||
|
||||
@@ -99,7 +99,6 @@ export const SectionHeader = React.memo<
|
||||
color={color || colors.primary.accent}
|
||||
size={SIZE.sm}
|
||||
style={{
|
||||
minWidth: 60,
|
||||
alignSelf: "center",
|
||||
textAlignVertical: "center"
|
||||
}}
|
||||
|
||||
@@ -28,13 +28,12 @@ import {
|
||||
hideSheet,
|
||||
presentSheet
|
||||
} from "../../../services/event-manager";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import { eOnLoadNote, eShowMergeDialog } from "../../../utils/events";
|
||||
import { tabBarRef } from "../../../utils/global-refs";
|
||||
|
||||
import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common";
|
||||
import NotePreview from "../../note-history/preview";
|
||||
import SelectionWrapper from "../selection-wrapper";
|
||||
import SelectionWrapper, { selectItem } from "../selection-wrapper";
|
||||
|
||||
export const openNote = async (
|
||||
item: Note,
|
||||
@@ -47,21 +46,8 @@ export const openNote = async (
|
||||
if (!isTrash) {
|
||||
note = (await db.notes.note(item.id)) as Note;
|
||||
}
|
||||
if (useSelectionStore.getState().selectionMode === item.type) {
|
||||
const {
|
||||
selectedItemsList,
|
||||
selectionMode,
|
||||
clearSelection,
|
||||
setSelectedItem
|
||||
} = useSelectionStore.getState();
|
||||
|
||||
if (selectedItemsList.length > 0 && selectionMode === item.type) {
|
||||
setSelectedItem(note.id);
|
||||
} else {
|
||||
clearSelection();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (selectItem(item)) return;
|
||||
|
||||
if (note.conflicted) {
|
||||
eSendEvent(eShowMergeDialog, note);
|
||||
|
||||
@@ -27,18 +27,12 @@ import Navigation from "../../../services/navigation";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import { useTrashStore } from "../../../stores/use-trash-store";
|
||||
import { presentDialog } from "../../dialog/functions";
|
||||
import SelectionWrapper from "../selection-wrapper";
|
||||
import SelectionWrapper, { selectItem } from "../selection-wrapper";
|
||||
|
||||
export const openNotebook = (item: Notebook | BaseTrashItem<Notebook>) => {
|
||||
const isTrash = item.type === "trash";
|
||||
const { selectedItemsList, setSelectedItem, selectionMode, clearSelection } =
|
||||
useSelectionStore.getState();
|
||||
if (selectedItemsList.length > 0 && selectionMode === item.type) {
|
||||
setSelectedItem(item.id);
|
||||
return;
|
||||
} else {
|
||||
clearSelection();
|
||||
}
|
||||
|
||||
if (selectItem(item)) return;
|
||||
|
||||
if (isTrash) {
|
||||
presentDialog({
|
||||
|
||||
@@ -22,7 +22,6 @@ import React from "react";
|
||||
import { View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { notesnook } from "../../../../e2e/test.ids";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Properties } from "../../properties";
|
||||
import ReminderSheet from "../../sheets/reminder";
|
||||
@@ -30,7 +29,7 @@ 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 from "../selection-wrapper";
|
||||
import SelectionWrapper, { selectItem } from "../selection-wrapper";
|
||||
|
||||
const ReminderItem = React.memo(
|
||||
({
|
||||
@@ -44,18 +43,7 @@ const ReminderItem = React.memo(
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const openReminder = () => {
|
||||
const {
|
||||
selectedItemsList,
|
||||
setSelectedItem,
|
||||
selectionMode,
|
||||
clearSelection
|
||||
} = useSelectionStore.getState();
|
||||
if (selectedItemsList.length > 0 && selectionMode === item.type) {
|
||||
setSelectedItem(item.id);
|
||||
return;
|
||||
} else {
|
||||
clearSelection();
|
||||
}
|
||||
if (selectItem(item)) return;
|
||||
|
||||
ReminderSheet.present(item, undefined, isSheet);
|
||||
};
|
||||
|
||||
@@ -26,6 +26,23 @@ import { Filler } from "./back-fill";
|
||||
import { SelectionIcon } from "./selection";
|
||||
import { Item, TrashItem } from "@notesnook/core";
|
||||
|
||||
export function selectItem(item: Item) {
|
||||
if (useSelectionStore.getState().selectionMode === item.type) {
|
||||
const { selectionMode, clearSelection, setSelectedItem } =
|
||||
useSelectionStore.getState();
|
||||
|
||||
if (selectionMode === item.type) {
|
||||
setSelectedItem(item.id);
|
||||
}
|
||||
|
||||
if (useSelectionStore.getState().selectedItemsList.length === 0) {
|
||||
clearSelection();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
type SelectionWrapperProps = PropsWithChildren<{
|
||||
item: Item;
|
||||
onPress: () => void;
|
||||
|
||||
@@ -23,13 +23,12 @@ import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { notesnook } from "../../../../e2e/test.ids";
|
||||
import { TaggedNotes } from "../../../screens/notes/tagged";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Properties } from "../../properties";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import SelectionWrapper from "../selection-wrapper";
|
||||
import SelectionWrapper, { selectItem } from "../selection-wrapper";
|
||||
|
||||
const TagItem = React.memo(
|
||||
({
|
||||
@@ -43,18 +42,7 @@ const TagItem = React.memo(
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const onPress = () => {
|
||||
const {
|
||||
selectedItemsList,
|
||||
setSelectedItem,
|
||||
selectionMode,
|
||||
clearSelection
|
||||
} = useSelectionStore.getState();
|
||||
if (selectedItemsList.length > 0 && selectionMode === item.type) {
|
||||
setSelectedItem(item.id);
|
||||
return;
|
||||
} else {
|
||||
clearSelection();
|
||||
}
|
||||
if (selectItem(item)) return;
|
||||
|
||||
TaggedNotes.navigate(item, true);
|
||||
};
|
||||
|
||||
@@ -48,6 +48,7 @@ import { NoteWrapper } from "../list-items/note/wrapper";
|
||||
import { NotebookWrapper } from "../list-items/notebook/wrapper";
|
||||
import ReminderItem from "../list-items/reminder";
|
||||
import TagItem from "../list-items/tag";
|
||||
import { useIsCompactModeEnabled } from "../../hooks/use-is-compact-mode-enabled";
|
||||
|
||||
type ListItemWrapperProps<TItem = Item> = {
|
||||
group?: GroupingKey;
|
||||
@@ -75,6 +76,7 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||
const refreshTimeout = useRef<NodeJS.Timeout>();
|
||||
const currentItemId = useRef<string>();
|
||||
const locked = useRef(false);
|
||||
const compactMode = useIsCompactModeEnabled(props.dataType as ItemType);
|
||||
|
||||
const refreshItem = useCallback((resolvedItem: any) => {
|
||||
if (!resolvedItem || !resolvedItem.data) {
|
||||
@@ -117,14 +119,24 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||
}
|
||||
currentItemId.current = resolvedItem.item.id;
|
||||
setItem(resolvedItem.item);
|
||||
}
|
||||
|
||||
if (resolvedItem?.group) {
|
||||
setGroupHeader(resolvedItem.group);
|
||||
} else {
|
||||
setGroupHeader(undefined);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (previousIndex.current !== index) {
|
||||
previousIndex.current = index;
|
||||
const resolvedItem = items?.cacheItem(index);
|
||||
refreshItem(resolvedItem);
|
||||
if (resolvedItem) {
|
||||
refreshItem(resolvedItem);
|
||||
} else {
|
||||
setItem(undefined);
|
||||
setGroupHeader(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -132,12 +144,15 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||
try {
|
||||
clearTimeout(refreshTimeout.current);
|
||||
const idx = index;
|
||||
refreshTimeout.current = setTimeout(async () => {
|
||||
if (idx !== previousIndex.current) return;
|
||||
const resolvedItem = await items?.item(idx, resolveItems);
|
||||
if (idx !== previousIndex.current) return;
|
||||
refreshItem(resolvedItem);
|
||||
}, 100);
|
||||
refreshTimeout.current = setTimeout(
|
||||
async () => {
|
||||
if (idx !== previousIndex.current) return;
|
||||
const resolvedItem = await items?.item(idx, resolveItems);
|
||||
if (idx !== previousIndex.current) return;
|
||||
refreshItem(resolvedItem);
|
||||
},
|
||||
items?.cacheItem(index) ? 100 : 0
|
||||
);
|
||||
} catch (e) {
|
||||
console.log("Error", e);
|
||||
}
|
||||
@@ -148,7 +163,7 @@ export function ListItemWrapper(props: ListItemWrapperProps) {
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
height: 120,
|
||||
height: compactMode ? 50 : 120,
|
||||
width: "100%"
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -313,7 +313,7 @@ const MergeConflicts = () => {
|
||||
height: height / 2 - (50 + insets.top / 2),
|
||||
backgroundColor: colors.primary.background,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.secondary.background
|
||||
borderBottomColor: colors.primary.border
|
||||
}}
|
||||
>
|
||||
<ReadonlyEditor
|
||||
|
||||
@@ -48,7 +48,7 @@ export const Synced = ({ item, close }) => {
|
||||
paddingTop: 10,
|
||||
marginTop: 10,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.secondary.background
|
||||
borderTopColor: colors.primary.border
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
|
||||
@@ -214,7 +214,7 @@ export const SelectionHeader = React.memo(
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
|
||||
{renderedInRoute === "Notebooks" ? (
|
||||
{renderedInRoute === "Notebooks" && selectedItemsList.length ? (
|
||||
<IconButton
|
||||
style={{
|
||||
marginLeft: 10
|
||||
@@ -231,7 +231,7 @@ export const SelectionHeader = React.memo(
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{type !== "note" ? null : (
|
||||
{type !== "note" || !selectedItemsList.length ? null : (
|
||||
<>
|
||||
<IconButton
|
||||
onPress={async () => {
|
||||
@@ -278,16 +278,14 @@ export const SelectionHeader = React.memo(
|
||||
</>
|
||||
)}
|
||||
|
||||
{renderedInRoute === "Notebook" ? (
|
||||
{renderedInRoute === "Notebook" && selectedItemsList.length ? (
|
||||
<IconButton
|
||||
onPress={async () => {
|
||||
if (selectedItemsList.length > 0) {
|
||||
if (!id) return;
|
||||
await db.notes.removeFromNotebook(id, ...selectedItemsList);
|
||||
updateNotebook(id);
|
||||
Navigation.queueRoutesForUpdate();
|
||||
clearSelection();
|
||||
}
|
||||
if (!id) return;
|
||||
await db.notes.removeFromNotebook(id, ...selectedItemsList);
|
||||
updateNotebook(id);
|
||||
Navigation.queueRoutesForUpdate();
|
||||
clearSelection();
|
||||
}}
|
||||
style={{
|
||||
marginLeft: 10
|
||||
@@ -301,7 +299,7 @@ export const SelectionHeader = React.memo(
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{focusedRouteId === "Favorites" ? (
|
||||
{focusedRouteId === "Favorites" && selectedItemsList.length ? (
|
||||
<IconButton
|
||||
onPress={addToFavorite}
|
||||
style={{
|
||||
@@ -315,7 +313,7 @@ export const SelectionHeader = React.memo(
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{type === "trash" ? null : (
|
||||
{type === "trash" || !selectedItemsList.length ? null : (
|
||||
<IconButton
|
||||
style={{
|
||||
marginLeft: 10
|
||||
@@ -337,7 +335,7 @@ export const SelectionHeader = React.memo(
|
||||
/>
|
||||
)}
|
||||
|
||||
{type === "trash" ? (
|
||||
{type === "trash" && selectedItemsList.length ? (
|
||||
<>
|
||||
<IconButton
|
||||
style={{
|
||||
|
||||
@@ -20,7 +20,7 @@ import { Note } from "@notesnook/core";
|
||||
import { EVENTS } from "@notesnook/core/dist/common";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useEffect } from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
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";
|
||||
@@ -43,6 +43,7 @@ type TabItem = {
|
||||
locked?: boolean;
|
||||
noteLocked?: boolean;
|
||||
readonly?: boolean;
|
||||
pinned?: boolean;
|
||||
};
|
||||
|
||||
const TabItemComponent = (props: {
|
||||
@@ -140,24 +141,50 @@ const TabItemComponent = (props: {
|
||||
</Paragraph>
|
||||
</View>
|
||||
|
||||
<IconButton
|
||||
name="close"
|
||||
size={SIZE.lg}
|
||||
color={colors.primary.icon}
|
||||
onPress={() => {
|
||||
const isLastTab = useTabStore.getState().tabs.length === 1;
|
||||
useTabStore.getState().removeTab(props.tab.id);
|
||||
// The last tab is not actually removed, it is just cleaned up.
|
||||
if (isLastTab) {
|
||||
editorController.current?.reset(props.tab.id, true, true);
|
||||
props.close?.();
|
||||
}
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
gap: 5,
|
||||
paddingRight: 12
|
||||
}}
|
||||
top={0}
|
||||
left={0}
|
||||
right={20}
|
||||
bottom={0}
|
||||
/>
|
||||
>
|
||||
<IconButton
|
||||
name="pin"
|
||||
size={SIZE.lg}
|
||||
color={props.tab.pinned ? colors.primary.accent : colors.primary.icon}
|
||||
onPress={() => {
|
||||
useTabStore.getState().updateTab(props.tab.id, {
|
||||
pinned: !props.tab.pinned,
|
||||
previewTab: false
|
||||
});
|
||||
}}
|
||||
top={0}
|
||||
left={0}
|
||||
right={20}
|
||||
bottom={0}
|
||||
/>
|
||||
|
||||
{!props.tab?.pinned ? (
|
||||
<IconButton
|
||||
name="close"
|
||||
size={SIZE.lg}
|
||||
color={colors.primary.icon}
|
||||
onPress={() => {
|
||||
const isLastTab = useTabStore.getState().tabs.length === 1;
|
||||
useTabStore.getState().removeTab(props.tab.id);
|
||||
// The last tab is not actually removed, it is just cleaned up.
|
||||
if (isLastTab) {
|
||||
editorController.current?.reset(props.tab.id, true, true);
|
||||
props.close?.();
|
||||
}
|
||||
}}
|
||||
top={0}
|
||||
left={0}
|
||||
right={20}
|
||||
bottom={0}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
};
|
||||
@@ -225,7 +252,16 @@ export default function EditorTabs({
|
||||
/>
|
||||
</View>
|
||||
|
||||
<FlatList windowSize={3} data={tabs} renderItem={renderTabItem} />
|
||||
<FlatList
|
||||
windowSize={3}
|
||||
data={tabs.sort((t1, t2) => {
|
||||
if (t1.pinned && t2.pinned) return 0;
|
||||
if (t1.pinned) return -1;
|
||||
if (t2.pinned) return 1;
|
||||
return 0;
|
||||
})}
|
||||
renderItem={renderTabItem}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ Logged in: ${user ? "yes" : "no"}`,
|
||||
defaultValue={title.current}
|
||||
style={{
|
||||
borderWidth: 1,
|
||||
borderColor: colors.secondary.background,
|
||||
borderColor: colors.primary.border,
|
||||
borderRadius: 5,
|
||||
padding: 12,
|
||||
fontFamily: "OpenSans-Regular",
|
||||
@@ -178,7 +178,7 @@ For example:
|
||||
}}
|
||||
style={{
|
||||
borderWidth: 1,
|
||||
borderColor: colors.secondary.background,
|
||||
borderColor: colors.primary.border,
|
||||
borderRadius: 5,
|
||||
padding: 12,
|
||||
fontFamily: "OpenSans-Regular",
|
||||
|
||||
@@ -159,13 +159,14 @@ export default function Migrate() {
|
||||
}}
|
||||
>
|
||||
Migrating database{progress ? `(${progress?.collection})` : null}
|
||||
{progress ? `(${progress.current}/${progress.total}) ` : null}...
|
||||
please wait
|
||||
{progress ? `(${progress.current}/${progress.total}) ` : null}
|
||||
... please wait
|
||||
</Paragraph>
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: 200
|
||||
width: 200,
|
||||
alignSelf: "center"
|
||||
}}
|
||||
>
|
||||
<ProgressBarComponent
|
||||
|
||||
@@ -20,8 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { VirtualizedGrouping } from "@notesnook/core";
|
||||
import { Note, Notebook } from "@notesnook/core/dist/types";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { RefObject, useEffect, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import React, { RefObject, useEffect, useRef, useState } from "react";
|
||||
import { ActivityIndicator, TextInput, View } from "react-native";
|
||||
import { ActionSheetRef } from "react-native-actions-sheet";
|
||||
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
|
||||
import { db } from "../../../common/database";
|
||||
@@ -35,6 +35,7 @@ import { Dialog } from "../../dialog";
|
||||
import DialogHeader from "../../dialog/dialog-header";
|
||||
import { Button } from "../../ui/button";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import Input from "../../ui/input";
|
||||
import { Pressable } from "../../ui/pressable";
|
||||
import Seperator from "../../ui/seperator";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
@@ -50,39 +51,60 @@ export const MoveNotes = ({
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
const currentNotebook = notebook;
|
||||
const inputRef = useRef<TextInput>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const selectionCount = useItemSelectionStore(
|
||||
(state) =>
|
||||
Object.keys(state.selection).filter(
|
||||
(k) =>
|
||||
state.initialState?.[k] !== "selected" &&
|
||||
state.selection[k] !== "deselected"
|
||||
state.selection?.[k] === "selected" ||
|
||||
state.selection[k] === "deselected"
|
||||
)?.length > 0
|
||||
);
|
||||
|
||||
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
|
||||
|
||||
const loadNotes = React.useCallback(
|
||||
async (query?: string) => {
|
||||
setLoading(true);
|
||||
const notes = query
|
||||
? db.lookup.notes(query).sorted()
|
||||
: db.notes?.all.sorted(db.settings.getGroupOptions("notes") as any);
|
||||
|
||||
notes
|
||||
.then(async (notes) => {
|
||||
await notes.item(0);
|
||||
setNotes(notes);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
|
||||
db.relations
|
||||
.from(currentNotebook, "note")
|
||||
.get()
|
||||
.then((existingNotes) => {
|
||||
const selection: { [name: string]: any } = {};
|
||||
existingNotes.forEach((rel) => {
|
||||
selection[rel.toId] = "selected";
|
||||
});
|
||||
useItemSelectionStore.setState({
|
||||
selection: selection,
|
||||
initialState: selection
|
||||
});
|
||||
});
|
||||
},
|
||||
[currentNotebook]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
db.notes?.all.sorted(db.settings.getGroupOptions("notes")).then((notes) => {
|
||||
setNotes(notes);
|
||||
});
|
||||
db.relations
|
||||
.from(currentNotebook, "note")
|
||||
.get()
|
||||
.then((existingNotes) => {
|
||||
const selection: { [name: string]: any } = {};
|
||||
existingNotes.forEach((rel) => {
|
||||
selection[rel.toId] = "selected";
|
||||
});
|
||||
useItemSelectionStore.setState({
|
||||
selection: selection,
|
||||
initialState: selection
|
||||
});
|
||||
});
|
||||
loadNotes();
|
||||
|
||||
return () => {
|
||||
useItemSelectionStore.getState().reset();
|
||||
};
|
||||
}, [currentNotebook]);
|
||||
}, [currentNotebook, loadNotes]);
|
||||
|
||||
const renderItem = React.useCallback(
|
||||
({ index }: { item: boolean; index: number }) => {
|
||||
@@ -96,9 +118,7 @@ export const MoveNotes = ({
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
maxHeight: "100%",
|
||||
height: notes?.placeholders.length
|
||||
? 50 * (notes?.placeholders.length + 2)
|
||||
: 300
|
||||
height: "100%"
|
||||
}}
|
||||
>
|
||||
<Dialog context="local" />
|
||||
@@ -109,6 +129,22 @@ export const MoveNotes = ({
|
||||
/>
|
||||
<Seperator />
|
||||
|
||||
<Input
|
||||
button={{
|
||||
icon: "magnify",
|
||||
color: colors.primary.accent,
|
||||
size: SIZE.lg,
|
||||
onPress: () => {}
|
||||
}}
|
||||
testID="search-input"
|
||||
fwdRef={inputRef}
|
||||
autoCapitalize="none"
|
||||
onChangeText={(v) => {
|
||||
loadNotes(v && v.trim() === "" ? undefined : v.trim());
|
||||
}}
|
||||
placeholder="Search a note"
|
||||
/>
|
||||
|
||||
<FlashList
|
||||
ListEmptyComponent={
|
||||
<View
|
||||
@@ -118,13 +154,17 @@ export const MoveNotes = ({
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Paragraph color={colors.secondary.paragraph}>
|
||||
No notes to show
|
||||
</Paragraph>
|
||||
{loading ? (
|
||||
<ActivityIndicator size="large" color={colors.primary.accent} />
|
||||
) : (
|
||||
<Paragraph color={colors.secondary.paragraph}>
|
||||
No notes to show
|
||||
</Paragraph>
|
||||
)}
|
||||
</View>
|
||||
}
|
||||
estimatedItemSize={50}
|
||||
data={notes?.placeholders}
|
||||
data={loading ? [] : notes?.placeholders}
|
||||
renderItem={renderItem}
|
||||
/>
|
||||
|
||||
@@ -135,6 +175,12 @@ export const MoveNotes = ({
|
||||
currentNotebook.id,
|
||||
...useItemSelectionStore.getState().getSelectedItemIds()
|
||||
);
|
||||
|
||||
await db.notes?.removeFromNotebook(
|
||||
currentNotebook.id,
|
||||
...useItemSelectionStore.getState().getDeselectedItemIds()
|
||||
);
|
||||
|
||||
updateNotebook(currentNotebook.id);
|
||||
Navigation.queueRoutesForUpdate();
|
||||
fwdRef?.current?.hide();
|
||||
@@ -161,11 +207,12 @@ const SelectableNoteItem = React.memo(
|
||||
const selected = useItemSelectionStore((state) =>
|
||||
item?.id ? state.selection[item.id] === "selected" : false
|
||||
);
|
||||
|
||||
const exists = useItemSelectionStore((state) =>
|
||||
item?.id ? state.initialState[item.id] === "selected" : false
|
||||
);
|
||||
|
||||
return exists ? null : (
|
||||
return (
|
||||
<Pressable
|
||||
testID="listitem.select"
|
||||
onPress={() => {
|
||||
@@ -205,8 +252,14 @@ const SelectableNoteItem = React.memo(
|
||||
? "check-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
type="selected"
|
||||
color={selected ? colors.selected.icon : colors.primary.icon}
|
||||
type="plain"
|
||||
color={
|
||||
selected
|
||||
? colors.selected.icon
|
||||
: exists && !selected
|
||||
? colors.static.red
|
||||
: colors.primary.icon
|
||||
}
|
||||
/>
|
||||
|
||||
<View
|
||||
|
||||
@@ -17,13 +17,14 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import { ScrollView } from "react-native-actions-sheet";
|
||||
import { getVersion } from "react-native-device-info";
|
||||
import { features } from "../../../features";
|
||||
import { eSendEvent, presentSheet } from "../../../services/event-manager";
|
||||
import SettingsService from "../../../services/settings";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { eCloseSheet } from "../../../utils/events";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Button } from "../../ui/button";
|
||||
@@ -36,7 +37,13 @@ export type FeatureType = {
|
||||
platform?: "ios" | "android";
|
||||
};
|
||||
|
||||
const NewFeature = ({ features }: { features: FeatureType[] }) => {
|
||||
const NewFeature = ({
|
||||
features,
|
||||
version
|
||||
}: {
|
||||
features: FeatureType[];
|
||||
version?: string | null;
|
||||
}) => {
|
||||
const { colors } = useThemeColors();
|
||||
|
||||
return (
|
||||
@@ -44,30 +51,33 @@ const NewFeature = ({ features }: { features: FeatureType[] }) => {
|
||||
style={{
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 12,
|
||||
paddingTop: 12
|
||||
paddingTop: 12,
|
||||
maxHeight: "100%"
|
||||
}}
|
||||
>
|
||||
<Heading color={colors.secondary.heading} size={SIZE.md}>
|
||||
New Version Highlights 🎉
|
||||
{!version ? "New version " : `v${version} `}Highlights 🎉
|
||||
</Heading>
|
||||
|
||||
<Seperator />
|
||||
|
||||
{features.map((item) => (
|
||||
<View
|
||||
key={item.title}
|
||||
style={{
|
||||
backgroundColor: colors.secondary.background,
|
||||
padding: 12,
|
||||
borderRadius: 10,
|
||||
width: "100%",
|
||||
marginBottom: 10
|
||||
}}
|
||||
>
|
||||
<Heading size={SIZE.lg - 2}>{item.title}</Heading>
|
||||
<Paragraph>{item.body}</Paragraph>
|
||||
</View>
|
||||
))}
|
||||
<ScrollView>
|
||||
{features.map((item) => (
|
||||
<View
|
||||
key={item.title}
|
||||
style={{
|
||||
backgroundColor: colors.secondary.background,
|
||||
padding: 12,
|
||||
borderRadius: 10,
|
||||
width: "100%",
|
||||
marginBottom: 10
|
||||
}}
|
||||
>
|
||||
<Heading size={SIZE.lg - 2}>{item.title}</Heading>
|
||||
<Paragraph selectable>{item.body}</Paragraph>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
<Seperator />
|
||||
|
||||
<Button
|
||||
@@ -108,7 +118,13 @@ NewFeature.present = () => {
|
||||
);
|
||||
if (_features.length === 0) return;
|
||||
presentSheet({
|
||||
component: <NewFeature features={features} />
|
||||
component: (
|
||||
<NewFeature
|
||||
features={features}
|
||||
version={SettingsService.getProperty("version")}
|
||||
/>
|
||||
),
|
||||
disableClosing: true
|
||||
});
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -139,7 +139,7 @@ export const NotebookSheet = () => {
|
||||
currentItem.current = nextRoot;
|
||||
const snapPoint = NotebookSheetConfig.get({
|
||||
type: "notebook",
|
||||
id: nextRoot as string
|
||||
id: focusedRouteId as string
|
||||
});
|
||||
|
||||
if (ref.current?.isOpen()) {
|
||||
|
||||
@@ -159,7 +159,7 @@ export default function ReminderNotify({
|
||||
? 160 * references?.placeholders?.length
|
||||
: 500,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.secondary.background,
|
||||
borderTopColor: colors.primary.border,
|
||||
marginTop: 5,
|
||||
paddingTop: 5
|
||||
}}
|
||||
|
||||
@@ -18,7 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { RefObject, useRef, useState } from "react";
|
||||
import { Platform, TextInput, View } from "react-native";
|
||||
import {
|
||||
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 {
|
||||
@@ -364,7 +369,7 @@ export default function ReminderSheet({
|
||||
))}
|
||||
</View>
|
||||
|
||||
<ScrollView showsHorizontalScrollIndicator={false} horizontal>
|
||||
<RNScrollView showsHorizontalScrollIndicator={false} horizontal>
|
||||
{recurringMode === RecurringModes.Daily ||
|
||||
recurringMode === RecurringModes.Year
|
||||
? null
|
||||
@@ -425,7 +430,7 @@ export default function ReminderSheet({
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</ScrollView>
|
||||
</RNScrollView>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ import { Button } from "../../ui/button";
|
||||
import Seperator from "../../ui/seperator";
|
||||
import SheetWrapper from "../../ui/sheet";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { useUserStore } from "../../../stores/use-user-store";
|
||||
|
||||
const RestoreDataSheet = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
@@ -246,7 +247,6 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
|
||||
await db.backup.import(backup, password, key);
|
||||
});
|
||||
await db.initCollections();
|
||||
initialize();
|
||||
refreshAllStores();
|
||||
ToastManager.show({
|
||||
heading: "Backup restored successfully.",
|
||||
@@ -384,10 +384,20 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: true
|
||||
});
|
||||
console.log("disabled...");
|
||||
const file = await DocumentPicker.pickSingle({
|
||||
copyTo: "cachesDirectory"
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: false
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
if (file.name.endsWith(".nnbackupz")) {
|
||||
setRestoring(true);
|
||||
await restoreFromZip(file.fileCopyUri, true);
|
||||
@@ -399,6 +409,11 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
|
||||
}
|
||||
} catch (e) {
|
||||
console.log("error", e.stack);
|
||||
setTimeout(() => {
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: false
|
||||
});
|
||||
}, 1000);
|
||||
setRestoring(false);
|
||||
backupError(e);
|
||||
}
|
||||
@@ -415,7 +430,7 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
|
||||
borderRadius: 0,
|
||||
flexDirection: "row",
|
||||
borderBottomWidth: 0.5,
|
||||
borderBottomColor: colors.secondary.background
|
||||
borderBottomColor: colors.primary.border
|
||||
}}
|
||||
>
|
||||
<View
|
||||
|
||||
@@ -127,7 +127,7 @@ const Sort = ({ type, screen }) => {
|
||||
justifyContent: "flex-start",
|
||||
flexWrap: "wrap",
|
||||
borderBottomWidth: isTopicSheet ? 0 : 1,
|
||||
borderBottomColor: colors.secondary.background,
|
||||
borderBottomColor: colors.primary.border,
|
||||
marginBottom: 12,
|
||||
paddingHorizontal: 12,
|
||||
paddingBottom: 12,
|
||||
|
||||
@@ -170,6 +170,7 @@ export const Update = ({ version: appVersion, fwdRef }) => {
|
||||
lineHeight: 20,
|
||||
marginTop: 10
|
||||
}}
|
||||
selectable
|
||||
>
|
||||
{version.body}
|
||||
</Paragraph>
|
||||
@@ -181,6 +182,7 @@ export const Update = ({ version: appVersion, fwdRef }) => {
|
||||
style={{
|
||||
marginBottom: 5
|
||||
}}
|
||||
selectable
|
||||
>
|
||||
• {item}
|
||||
</Paragraph>
|
||||
|
||||
@@ -43,6 +43,10 @@ export const ColorSection = React.memo(
|
||||
]);
|
||||
const loading = useSettingStore((state) => state.isAppLoading);
|
||||
const setColorNotes = useMenuStore((state) => state.setColorNotes);
|
||||
const [order, hiddensItems] = useMenuStore((state) => [
|
||||
state.order["colors"],
|
||||
state.hiddenItems["colors"]
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
@@ -59,8 +63,8 @@ export const ColorSection = React.memo(
|
||||
db.settings.setSideBarHiddenItems("colors", data);
|
||||
}}
|
||||
disableDefaultDrag={true}
|
||||
itemOrder={db.settings.getSideBarOrder("colors")}
|
||||
hiddenItems={db.settings.getSideBarHiddenItems("colors")}
|
||||
itemOrder={order}
|
||||
hiddenItems={hiddensItems}
|
||||
alwaysBounceVertical={false}
|
||||
data={colorNotes}
|
||||
style={{
|
||||
|
||||
@@ -27,6 +27,7 @@ import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import { eSendEvent } from "../../services/event-manager";
|
||||
import Navigation from "../../services/navigation";
|
||||
import { useMenuStore } from "../../stores/use-menu-store";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { useUserStore } from "../../stores/use-user-store";
|
||||
@@ -50,6 +51,10 @@ export const SideMenu = React.memo(
|
||||
);
|
||||
const isAppLoading = useSettingStore((state) => state.isAppLoading);
|
||||
const dragging = useSideBarDraggingStore((state) => state.dragging);
|
||||
const [order, hiddensItems] = useMenuStore((state) => [
|
||||
state.order["routes"],
|
||||
state.hiddenItems["routes"]
|
||||
]);
|
||||
|
||||
const introCompleted = useSettingStore(
|
||||
(state) => state.settings.introCompleted
|
||||
@@ -93,8 +98,8 @@ export const SideMenu = React.memo(
|
||||
onHiddenItemsChanged={(data) => {
|
||||
db.settings.setSideBarHiddenItems("routes", data);
|
||||
}}
|
||||
itemOrder={db.settings.getSideBarOrder("routes")}
|
||||
hiddenItems={db.settings.getSideBarHiddenItems("routes")}
|
||||
itemOrder={order}
|
||||
hiddenItems={hiddensItems}
|
||||
alwaysBounceVertical={false}
|
||||
data={MenuItemsList}
|
||||
style={{
|
||||
@@ -150,26 +155,6 @@ export const SideMenu = React.memo(
|
||||
subscriptionType === SUBSCRIPTION_STATUS.BASIC ? (
|
||||
<MenuItem testID={pro.name} key={pro.name} item={pro} index={0} />
|
||||
) : null}
|
||||
|
||||
{BottomItemsList.slice(DDS.isLargeTablet() ? 0 : 1, 3).map(
|
||||
(item, index) => (
|
||||
<MenuItem
|
||||
testID={
|
||||
item.name == "Night mode"
|
||||
? notesnook.ids.menu.nightmode
|
||||
: item.name
|
||||
}
|
||||
key={item.name}
|
||||
item={item}
|
||||
index={index}
|
||||
rightBtn={
|
||||
DDS.isLargeTablet() || item.name === "Notesnook Pro"
|
||||
? undefined
|
||||
: BottomItemsList[0]
|
||||
}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</View>
|
||||
|
||||
{dragging ? (
|
||||
@@ -201,14 +186,7 @@ export const SideMenu = React.memo(
|
||||
/>
|
||||
</View>
|
||||
) : (
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
paddingHorizontal: 0
|
||||
}}
|
||||
>
|
||||
<UserStatus />
|
||||
</View>
|
||||
<UserStatus />
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -44,7 +44,7 @@ export const PinnedSection = React.memo(
|
||||
const menuPins = useMenuStore((state) => state.menuPins);
|
||||
const loading = useSettingStore((state) => state.isAppLoading);
|
||||
const setMenuPins = useMenuStore((state) => state.setMenuPins);
|
||||
|
||||
const [order] = useMenuStore((state) => [state.order["shortcuts"]]);
|
||||
useEffect(() => {
|
||||
if (!loading) {
|
||||
setMenuPins();
|
||||
@@ -61,7 +61,13 @@ export const PinnedSection = React.memo(
|
||||
Navigation.closeDrawer();
|
||||
});
|
||||
};
|
||||
const renderItem = ({ item }: { item: Notebook | Tag; index: number }) => {
|
||||
const renderItem = ({
|
||||
item,
|
||||
index
|
||||
}: {
|
||||
item: Notebook | Tag;
|
||||
index: number;
|
||||
}) => {
|
||||
return <PinItem item={item} onPress={onPress} />;
|
||||
};
|
||||
|
||||
@@ -77,7 +83,7 @@ export const PinnedSection = React.memo(
|
||||
}}
|
||||
onHiddenItemsChanged={(data) => {}}
|
||||
canHideItems={false}
|
||||
itemOrder={db.settings.getSideBarOrder("shortcuts")}
|
||||
itemOrder={order}
|
||||
hiddenItems={[]}
|
||||
alwaysBounceVertical={false}
|
||||
data={menuPins}
|
||||
|
||||
@@ -20,7 +20,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { useNetInfo } from "@react-native-community/netinfo";
|
||||
import React from "react";
|
||||
import { ActivityIndicator, Image, Platform, View } from "react-native";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Image,
|
||||
Platform,
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
import useSyncProgress from "../../hooks/use-sync-progress";
|
||||
@@ -33,6 +39,7 @@ import { SIZE } from "../../utils/size";
|
||||
import { Pressable } from "../ui/pressable";
|
||||
import { TimeSince } from "../ui/time-since";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import Navigation from "../../services/navigation";
|
||||
|
||||
export const UserStatus = () => {
|
||||
const { colors } = useThemeColors();
|
||||
@@ -53,7 +60,8 @@ export const UserStatus = () => {
|
||||
alignSelf: "center",
|
||||
paddingBottom: Platform.OS === "ios" ? insets.bottom / 2 : null,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.secondary.background
|
||||
borderTopColor: colors.primary.border,
|
||||
backgroundColor: colors.primary.background
|
||||
}}
|
||||
>
|
||||
<View
|
||||
@@ -64,13 +72,9 @@ export const UserStatus = () => {
|
||||
}}
|
||||
>
|
||||
<Pressable
|
||||
onPress={async () => {
|
||||
if (user) {
|
||||
Sync.run();
|
||||
} else {
|
||||
tabBarRef.current?.closeDrawer();
|
||||
eSendEvent(eOpenLoginDialog);
|
||||
}
|
||||
onPress={() => {
|
||||
Navigation.navigate("Settings");
|
||||
tabBarRef.current.closeDrawer();
|
||||
}}
|
||||
type="plain"
|
||||
style={{
|
||||
@@ -78,7 +82,8 @@ export const UserStatus = () => {
|
||||
justifyContent: "flex-start",
|
||||
padding: 12,
|
||||
borderRadius: 0,
|
||||
alignItems: "center"
|
||||
alignItems: "center",
|
||||
gap: 10
|
||||
}}
|
||||
>
|
||||
{userProfile?.profilePicture ? (
|
||||
@@ -89,8 +94,7 @@ export const UserStatus = () => {
|
||||
style={{
|
||||
width: 35,
|
||||
height: 35,
|
||||
borderRadius: 100,
|
||||
marginRight: 10
|
||||
borderRadius: 100
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
@@ -102,9 +106,7 @@ export const UserStatus = () => {
|
||||
}}
|
||||
>
|
||||
<Paragraph
|
||||
style={{
|
||||
flexWrap: "wrap"
|
||||
}}
|
||||
numberOfLines={1}
|
||||
size={SIZE.sm}
|
||||
color={colors.primary.heading}
|
||||
>
|
||||
@@ -113,9 +115,7 @@ export const UserStatus = () => {
|
||||
: lastSyncStatus === SyncStatus.Failed
|
||||
? "Sync failed, tap to retry"
|
||||
: syncing
|
||||
? `Syncing your notes${
|
||||
progress ? ` (${progress.current})` : ""
|
||||
}`
|
||||
? `Syncing ${progress ? `(${progress.current})` : ""}`
|
||||
: !userProfile?.fullName
|
||||
? "Tap to sync"
|
||||
: userProfile.fullName}
|
||||
@@ -162,25 +162,56 @@ export const UserStatus = () => {
|
||||
</Paragraph>
|
||||
</View>
|
||||
|
||||
{user ? (
|
||||
syncing ? (
|
||||
<ActivityIndicator color={colors.primary.accent} size={SIZE.xl} />
|
||||
) : lastSyncStatus === SyncStatus.Failed ? (
|
||||
<Icon
|
||||
color={colors.error.icon}
|
||||
name="sync-alert"
|
||||
size={SIZE.lg}
|
||||
allowFontScaling
|
||||
/>
|
||||
<Pressable
|
||||
style={{
|
||||
borderRadius: 100,
|
||||
width: 40,
|
||||
height: 40
|
||||
}}
|
||||
hitSlop={{
|
||||
top: 10,
|
||||
bottom: 10,
|
||||
left: 20
|
||||
}}
|
||||
onPress={() => {
|
||||
if (user) {
|
||||
Sync.run();
|
||||
} else {
|
||||
tabBarRef.current?.closeDrawer();
|
||||
eSendEvent(eOpenLoginDialog);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{user ? (
|
||||
syncing ? (
|
||||
<ActivityIndicator
|
||||
color={colors.primary.accent}
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
) : lastSyncStatus === SyncStatus.Failed ? (
|
||||
<Icon
|
||||
color={colors.error.icon}
|
||||
name="sync-alert"
|
||||
size={SIZE.lg}
|
||||
allowFontScaling
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
allowFontScaling
|
||||
color={colors.primary.accent}
|
||||
name="sync"
|
||||
size={SIZE.lg}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<Icon
|
||||
allowFontScaling
|
||||
color={colors.primary.accent}
|
||||
name="sync"
|
||||
size={SIZE.lg}
|
||||
name="login"
|
||||
/>
|
||||
)
|
||||
) : null}
|
||||
)}
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -121,7 +121,12 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
|
||||
} else {
|
||||
if (prevWidths.current?.sidebar !== widths.sidebar) {
|
||||
translateX.value =
|
||||
!appState || appState?.movedAway ? widths.sidebar : editorPosition;
|
||||
!appState || (appState && appState?.movedAway)
|
||||
? widths.sidebar
|
||||
: editorPosition;
|
||||
if (translateX.value === editorPosition) {
|
||||
onChangeTab?.({ i: 2, from: 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
isLoaded.current = true;
|
||||
|
||||
@@ -28,6 +28,7 @@ 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";
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -73,9 +74,17 @@ const SheetWrapper = ({
|
||||
borderTopLeftRadius: 15,
|
||||
alignSelf: "center",
|
||||
borderBottomRightRadius: 0,
|
||||
borderBottomLeftRadius: 0
|
||||
borderBottomLeftRadius: 0,
|
||||
...getContainerBorder(colors.primary.border, 0.5),
|
||||
borderBottomWidth: 0
|
||||
};
|
||||
}, [colors.primary.background, largeTablet, smallTablet, width]);
|
||||
}, [
|
||||
colors.primary.background,
|
||||
colors.primary.border,
|
||||
largeTablet,
|
||||
smallTablet,
|
||||
width
|
||||
]);
|
||||
|
||||
const _onOpen = () => {
|
||||
if (lockEvents.current) return;
|
||||
|
||||
@@ -19,4 +19,35 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { FeatureType } from "./components/sheets/new-feature";
|
||||
|
||||
export const features: FeatureType[] = [];
|
||||
export const features: FeatureType[] = [
|
||||
{
|
||||
title: "Bi-directional note linking",
|
||||
body: "Now you can link notes to each other in both directions."
|
||||
},
|
||||
{
|
||||
title: "Tabs",
|
||||
body: "Tabs allow you to have multiple notes open at the same time. You can switch between them easily."
|
||||
},
|
||||
{
|
||||
title: "Nested notebooks",
|
||||
body: "You can now create notebooks inside other notebooks."
|
||||
},
|
||||
{
|
||||
title: "At-rest encryption",
|
||||
body: "Your notes are now encrypted when stored on the device."
|
||||
},
|
||||
{
|
||||
title: "Material You themed icon",
|
||||
body: "The app now has a Material You themed icon that changes color based on the wallpaper.",
|
||||
platform: "android"
|
||||
},
|
||||
{
|
||||
title: "New note quick settings tile",
|
||||
body: "You can now add a new note tile to quickly create notes from quick settings panel.",
|
||||
platform: "android"
|
||||
},
|
||||
{
|
||||
title: "And so much more...",
|
||||
body: "V3 packs so much that it's hard to put everything here, check out our blog at Notesnook V3 is here, and it's packed with new features and improvements such as bi-directional note linking, better syncing, at-rest encryption, nested notebooks, editor tabs, better app lock and so much more. https://blog.notesnook.com/introducing-notesnook-v3."
|
||||
}
|
||||
];
|
||||
|
||||
@@ -748,41 +748,45 @@ export const useActions = ({
|
||||
}
|
||||
|
||||
async function copyContent() {
|
||||
if (processingId.current === "copyContent") {
|
||||
ToastManager.show({
|
||||
heading: "Please wait...",
|
||||
message: "We are preparing your note for copy to clipboard",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!checkItemSynced()) return;
|
||||
if (locked) {
|
||||
close();
|
||||
await sleep(300);
|
||||
openVault({
|
||||
copyNote: true,
|
||||
novault: true,
|
||||
locked: true,
|
||||
item: item,
|
||||
title: "Copy note",
|
||||
description: "Unlock note to copy to clipboard."
|
||||
});
|
||||
} else {
|
||||
processingId.current = "copyContent";
|
||||
|
||||
const text = await convertNoteToText(item as Note, true);
|
||||
if (!text) {
|
||||
ToastManager.error(new Error(Errors.export("text")));
|
||||
try {
|
||||
if (processingId.current === "copyContent") {
|
||||
ToastManager.show({
|
||||
heading: "Please wait...",
|
||||
message: "We are preparing your note for copy to clipboard",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
Clipboard.setString(text);
|
||||
processingId.current = undefined;
|
||||
ToastManager.show({
|
||||
heading: "Note copied to clipboard",
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
|
||||
if (locked) {
|
||||
close();
|
||||
await sleep(300);
|
||||
openVault({
|
||||
copyNote: true,
|
||||
novault: true,
|
||||
locked: true,
|
||||
item: item,
|
||||
title: "Copy note",
|
||||
description: "Unlock note to copy to clipboard."
|
||||
});
|
||||
} else {
|
||||
processingId.current = "copyContent";
|
||||
const text = await convertNoteToText(item as Note, true);
|
||||
if (!text) {
|
||||
ToastManager.error(new Error(Errors.export("text")));
|
||||
return;
|
||||
}
|
||||
Clipboard.setString(text);
|
||||
processingId.current = undefined;
|
||||
ToastManager.show({
|
||||
heading: "Note copied to clipboard",
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
ToastManager.error(e as Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -307,9 +307,6 @@ export const useAppEvents = () => {
|
||||
initAfterSync();
|
||||
setLastSynced(await db.lastSynced());
|
||||
eSendEvent(eCloseSheet, "sync_progress");
|
||||
useUserStore.setState({
|
||||
profile: db.settings.getProfile()
|
||||
});
|
||||
}, [setLastSynced]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -682,6 +679,9 @@ export const useAppEvents = () => {
|
||||
|
||||
if (db.isInitialized) {
|
||||
Notifications.setupReminders(true);
|
||||
if (SettingsService.get().notifNotes) {
|
||||
Notifications.pinQuickNote(false);
|
||||
}
|
||||
useSettingStore.getState().setAppLoading(false);
|
||||
DatabaseLogger.info("Database initialized");
|
||||
}
|
||||
|
||||
@@ -64,6 +64,7 @@ import { useSettingStore } from "../stores/use-setting-store";
|
||||
import {
|
||||
eClearEditor,
|
||||
eCloseFullscreenEditor,
|
||||
eOnEnterEditor,
|
||||
eOnLoadNote,
|
||||
eOpenFullscreenEditor,
|
||||
eUnlockNote
|
||||
@@ -164,9 +165,7 @@ const _TabsHolder = () => {
|
||||
_deviceMode === "smallTablet"
|
||||
? dimensions.width -
|
||||
valueLimiter(dimensions.width * 0.4, 300, 450)
|
||||
: dimensions.width > 1100
|
||||
? dimensions.width * 0.55
|
||||
: dimensions.width * 0.5,
|
||||
: dimensions.width * 0.48,
|
||||
zIndex: null,
|
||||
paddingHorizontal: 0
|
||||
}
|
||||
@@ -257,9 +256,7 @@ const _TabsHolder = () => {
|
||||
position: "relative",
|
||||
width:
|
||||
current === "tablet"
|
||||
? size.width > 1100
|
||||
? size.width * 0.55
|
||||
: size.width * 0.5
|
||||
? size.width * 0.48
|
||||
: current === "smallTablet"
|
||||
? size.width - valueLimiter(size.width * 0.4, 300, 450)
|
||||
: size.width,
|
||||
@@ -371,15 +368,9 @@ const _TabsHolder = () => {
|
||||
editor: dimensions.width - valueLimiter(dimensions.width * 0.4, 300, 450)
|
||||
},
|
||||
tablet: {
|
||||
sidebar:
|
||||
dimensions.width > 1100
|
||||
? dimensions.width * 0.15
|
||||
: dimensions.width * 0.2,
|
||||
sidebar: dimensions.width * 0.22,
|
||||
list: dimensions.width * 0.3,
|
||||
editor:
|
||||
dimensions.width > 1100
|
||||
? dimensions.width * 0.55
|
||||
: dimensions.width * 0.5
|
||||
editor: dimensions.width * 0.48
|
||||
}
|
||||
};
|
||||
|
||||
@@ -515,11 +506,12 @@ const _TabsHolder = () => {
|
||||
};
|
||||
export const TabHolder = React.memo(_TabsHolder, () => true);
|
||||
|
||||
const onChangeTab = async (obj) => {
|
||||
if (obj.i === 2) {
|
||||
const onChangeTab = async (event) => {
|
||||
if (event.i === 2) {
|
||||
editorState().movedAway = false;
|
||||
editorState().isFocused = true;
|
||||
activateKeepAwake();
|
||||
|
||||
if (!useTabStore.getState().getCurrentNoteId()) {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
newNote: true
|
||||
@@ -529,13 +521,11 @@ const onChangeTab = async (obj) => {
|
||||
useTabStore.getState().getTab(useTabStore.getState().currentTab).locked
|
||||
) {
|
||||
eSendEvent(eUnlockNote);
|
||||
} else {
|
||||
editorState().currentlyEditing = true;
|
||||
eSendEvent(eOnLoadNote);
|
||||
}
|
||||
eSendEvent(eOnEnterEditor);
|
||||
}
|
||||
} else {
|
||||
if (obj.from === 2) {
|
||||
if (event.from === 2) {
|
||||
deactivateKeepAwake();
|
||||
editorState().movedAway = true;
|
||||
editorState().isFocused = false;
|
||||
|
||||
@@ -60,6 +60,7 @@ import {
|
||||
eClearEditor,
|
||||
eCloseFullscreenEditor,
|
||||
eEditorTabFocused,
|
||||
eOnEnterEditor,
|
||||
eOnLoadNote,
|
||||
eOpenFullscreenEditor,
|
||||
eOpenLoginDialog,
|
||||
@@ -269,14 +270,13 @@ export const useEditorEvents = (
|
||||
}, [editor, deviceMode, fullscreen]);
|
||||
|
||||
const onHardwareBackPress = useCallback(() => {
|
||||
if (editorState().currentlyEditing) {
|
||||
if (tabBarRef.current?.page === 2) {
|
||||
onBackPress();
|
||||
return true;
|
||||
}
|
||||
}, [onBackPress]);
|
||||
|
||||
const onLoadNote = useCallback(async () => {
|
||||
console.log("Loading...");
|
||||
const onEnterEditor = useCallback(async () => {
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
if (!DDS.isTab) {
|
||||
handleBack.current = BackHandler.addEventListener(
|
||||
@@ -331,16 +331,16 @@ export const useEditorEvents = (
|
||||
}, [fullscreen, onHardwareBackPress]);
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent(eOnLoadNote + editor.editorId, onLoadNote);
|
||||
eSubscribeEvent(eOnEnterEditor, onEnterEditor);
|
||||
eSubscribeEvent(
|
||||
eClearEditor + editor.editorId,
|
||||
onClearEditorSessionRequest
|
||||
);
|
||||
return () => {
|
||||
eUnSubscribeEvent(eClearEditor, onClearEditorSessionRequest);
|
||||
eUnSubscribeEvent(eOnLoadNote, onLoadNote);
|
||||
eUnSubscribeEvent(eOnEnterEditor, onEnterEditor);
|
||||
};
|
||||
}, [editor.editorId, onClearEditorSessionRequest, onLoadNote]);
|
||||
}, [editor.editorId, onClearEditorSessionRequest, onEnterEditor]);
|
||||
|
||||
const onMessage = useCallback(
|
||||
async (event: WebViewMessageEvent) => {
|
||||
|
||||
@@ -76,6 +76,7 @@ export type TabItem = {
|
||||
readonly?: boolean;
|
||||
locked?: boolean;
|
||||
noteLocked?: boolean;
|
||||
pinned?: boolean;
|
||||
};
|
||||
|
||||
const history = new History();
|
||||
|
||||
@@ -29,7 +29,6 @@ import SettingsService from "../../services/settings";
|
||||
import useNavigationStore from "../../stores/use-navigation-store";
|
||||
import { useNotes } from "../../stores/use-notes-store";
|
||||
import { openEditor } from "../notes/common";
|
||||
import LinkNote from "../../components/sheets/link-note";
|
||||
|
||||
export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
||||
const [notes, loading] = useNotes();
|
||||
@@ -80,7 +79,9 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
|
||||
loading: "Loading your notes"
|
||||
}}
|
||||
/>
|
||||
<FloatingButton title="Create a new note" onPress={openEditor} />
|
||||
{!notes || !notes.placeholders?.length ? null : (
|
||||
<FloatingButton title="Create a new note" onPress={openEditor} />
|
||||
)}
|
||||
</DelayLayout>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -65,7 +65,6 @@ export const Reminders = ({
|
||||
route: route.name
|
||||
});
|
||||
}}
|
||||
isBeta
|
||||
id={route.name}
|
||||
onPressDefaultRightButton={() => {
|
||||
ReminderSheet.present();
|
||||
|
||||
@@ -19,11 +19,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Item, Note, VirtualizedGrouping } from "@notesnook/core";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { db } from "../../common/database";
|
||||
import { DatabaseLogger, db } from "../../common/database";
|
||||
import List from "../../components/list";
|
||||
import SelectionHeader from "../../components/selection-header";
|
||||
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
|
||||
import {
|
||||
ToastManager,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../services/event-manager";
|
||||
@@ -37,6 +38,7 @@ export const Search = ({ route, navigation }: NavigationProps<"Search">) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [searchStatus, setSearchStatus] = useState<string>();
|
||||
const currentQuery = useRef<string>();
|
||||
const timer = useRef<NodeJS.Timeout>();
|
||||
const isFocused = useNavigationFocus(navigation, {
|
||||
onFocus: (prev) => {
|
||||
useNavigationStore.getState().setFocusedRouteId(route.name);
|
||||
@@ -68,6 +70,8 @@ export const Search = ({ route, navigation }: NavigationProps<"Search">) => {
|
||||
console.log(
|
||||
`Found ${results.placeholders?.length} results for ${query}`
|
||||
);
|
||||
if (currentQuery.current !== query) return;
|
||||
await results.item(0);
|
||||
setResults(results);
|
||||
if (results.placeholders?.length === 0) {
|
||||
setSearchStatus(`No results found for ${query}`);
|
||||
@@ -76,7 +80,8 @@ export const Search = ({ route, navigation }: NavigationProps<"Search">) => {
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
ToastManager.error(e as Error);
|
||||
DatabaseLogger.error(e);
|
||||
}
|
||||
},
|
||||
[route.params?.items, route.params.type]
|
||||
@@ -84,12 +89,11 @@ export const Search = ({ route, navigation }: NavigationProps<"Search">) => {
|
||||
|
||||
useEffect(() => {
|
||||
const onRefreshSearch = (type: string) => {
|
||||
if (type === route.params?.type) {
|
||||
if (type === undefined || type === route.params?.type) {
|
||||
onSearch(currentQuery.current);
|
||||
}
|
||||
};
|
||||
eSubscribeEvent(eOnRefreshSearch, onRefreshSearch);
|
||||
|
||||
return () => {
|
||||
eUnSubscribeEvent(eOnRefreshSearch, onRefreshSearch);
|
||||
};
|
||||
@@ -103,18 +107,26 @@ export const Search = ({ route, navigation }: NavigationProps<"Search">) => {
|
||||
type={route.params?.type}
|
||||
renderedInRoute={route.name}
|
||||
/>
|
||||
<SearchBar onChangeText={onSearch} loading={loading} />
|
||||
<SearchBar
|
||||
onChangeText={(query) => {
|
||||
clearTimeout(timer.current);
|
||||
timer.current = setTimeout(() => {
|
||||
onSearch(query);
|
||||
}, 300);
|
||||
}}
|
||||
loading={loading}
|
||||
/>
|
||||
<List
|
||||
data={results}
|
||||
dataType={route.params?.type}
|
||||
renderedInRoute={route.name}
|
||||
loading={false}
|
||||
loading={loading}
|
||||
placeholder={{
|
||||
title: route.name,
|
||||
paragraph:
|
||||
searchStatus ||
|
||||
`Type a keyword to search in ${route.params?.title}`,
|
||||
loading: "Searching..."
|
||||
loading: `Searching for ${currentQuery.current}...`
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -77,7 +77,7 @@ const AppLock = () => {
|
||||
alignSelf: "center",
|
||||
minHeight: 125,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.secondary.background
|
||||
borderBottomColor: colors.primary.border
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
|
||||
@@ -100,7 +100,7 @@ export default function DebugLogs() {
|
||||
backgroundColor: background,
|
||||
flexShrink: 1,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.secondary.background
|
||||
borderBottomColor: colors.primary.border
|
||||
}}
|
||||
>
|
||||
<Paragraph
|
||||
|
||||
@@ -49,7 +49,7 @@ export const Licenses = () => {
|
||||
alignSelf: "flex-start",
|
||||
padding: 12,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.secondary.background,
|
||||
borderBottomColor: colors.primary.border,
|
||||
borderRadius: 0
|
||||
}}
|
||||
onPress={() => {
|
||||
|
||||
@@ -456,7 +456,7 @@ export const settingsGroups: SettingSection[] = [
|
||||
},
|
||||
{
|
||||
id: "background-sync",
|
||||
name: "Background sync (Experimental)",
|
||||
name: "Background sync (experimental)",
|
||||
description:
|
||||
"Periodically wake up the app in background to sync your notes from other devices.",
|
||||
type: "switch",
|
||||
|
||||
@@ -104,7 +104,7 @@ const onChangePicture = () => {
|
||||
|
||||
const SettingsUserSection = ({ item }) => {
|
||||
const { colors } = useThemeColors();
|
||||
const user = useUserStore((state) => state.user);
|
||||
const [user, premium] = useUserStore((state) => [state.user, state.premium]);
|
||||
const lastSynced = useUserStore((state) => state.lastSynced);
|
||||
const lastSyncStatus = useUserStore((state) => state.lastSyncStatus);
|
||||
const { isInternetReachable } = useNetInfo();
|
||||
@@ -121,7 +121,7 @@ const SettingsUserSection = ({ item }) => {
|
||||
paddingTop: 50,
|
||||
borderBottomWidth: 1,
|
||||
paddingBottom: 20,
|
||||
borderColor: colors.secondary.background
|
||||
borderColor: colors.primary.border
|
||||
}}
|
||||
>
|
||||
<View
|
||||
@@ -172,17 +172,20 @@ const SettingsUserSection = ({ item }) => {
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Heading color={colors.primary.accent} size={SIZE.sm}>
|
||||
{SUBSCRIPTION_STATUS_STRINGS[
|
||||
user.subscription?.type
|
||||
]?.toUpperCase() || "Basic"}
|
||||
</Heading>
|
||||
{premium ? (
|
||||
<Heading color={colors.primary.accent} size={SIZE.sm}>
|
||||
{SUBSCRIPTION_STATUS_STRINGS[
|
||||
user.subscription?.type
|
||||
]?.toUpperCase() || "Basic"}
|
||||
</Heading>
|
||||
) : null}
|
||||
|
||||
<Paragraph
|
||||
onPress={() => {
|
||||
presentDialog({
|
||||
title: "Set name",
|
||||
paragraph: "Set your full name",
|
||||
title: "Set your full name",
|
||||
paragraph:
|
||||
"Your name is end-to-end encrypted and only visible to you.",
|
||||
positiveText: "Save",
|
||||
input: true,
|
||||
inputPlaceholder: "Enter your full name",
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
endBackgroundTask
|
||||
} from "react-native-begin-background-task";
|
||||
import BackgroundFetch from "@ammarahmed/react-native-background-fetch";
|
||||
import { DatabaseLogger, db } from "../common/database";
|
||||
import { DatabaseLogger, db, setupDatabase } from "../common/database";
|
||||
import { AppState, AppRegistry } from "react-native";
|
||||
import Notifications from "./notifications";
|
||||
import SettingsService from "./settings";
|
||||
@@ -110,11 +110,15 @@ BackgroundFetch.registerHeadlessTask(task);
|
||||
async function onBackgroundSyncStarted() {
|
||||
try {
|
||||
if (!db.isInitialized) {
|
||||
await setupDatabase();
|
||||
await db.init();
|
||||
}
|
||||
const user = await db.user?.getUser();
|
||||
if (user) {
|
||||
await db.sync(true, false);
|
||||
await db.sync({
|
||||
type: "full",
|
||||
force: false
|
||||
});
|
||||
}
|
||||
await Notifications.setupReminders();
|
||||
DatabaseLogger.info("BACKGROUND SYNC COMPLETE");
|
||||
|
||||
@@ -252,7 +252,7 @@ async function run(progress = false, context) {
|
||||
} catch (e) {
|
||||
ToastManager.error(e, "Backup failed", context || "global");
|
||||
|
||||
if (e.message.includes("android.net.Uri") && androidBackupDirectory) {
|
||||
if (e?.message?.includes("android.net.Uri") && androidBackupDirectory) {
|
||||
SettingsService.setProperty("backupDirectoryAndroid", null);
|
||||
return run(progress, context);
|
||||
}
|
||||
|
||||
@@ -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 { db } from "../common/database";
|
||||
import Navigation from "../services/navigation";
|
||||
import Notifications from "../services/notifications";
|
||||
import { useFavoriteStore } from "./use-favorite-store";
|
||||
@@ -27,6 +28,7 @@ import { useRelationStore } from "./use-relation-store";
|
||||
import { useReminderStore } from "./use-reminder-store";
|
||||
import { useTagStore } from "./use-tag-store";
|
||||
import { useTrashStore } from "./use-trash-store";
|
||||
import { useUserStore } from "./use-user-store";
|
||||
|
||||
export function initAfterSync() {
|
||||
Navigation.queueRoutesForUpdate();
|
||||
@@ -36,6 +38,9 @@ export function initAfterSync() {
|
||||
useRelationStore.getState().update();
|
||||
useMenuStore.getState().setColorNotes();
|
||||
useMenuStore.getState().setMenuPins();
|
||||
useUserStore.setState({
|
||||
profile: db.settings.getProfile()
|
||||
});
|
||||
}
|
||||
|
||||
export async function initialize() {}
|
||||
|
||||
@@ -32,6 +32,7 @@ export interface SelectionStore extends State {
|
||||
reset: () => void;
|
||||
enabled?: boolean;
|
||||
getSelectedItemIds: () => string[];
|
||||
getDeselectedItemIds: () => string[];
|
||||
}
|
||||
|
||||
export function createItemSelectionStore(
|
||||
@@ -84,6 +85,16 @@ export function createItemSelectionStore(
|
||||
}
|
||||
}
|
||||
return selected;
|
||||
},
|
||||
getDeselectedItemIds: () => {
|
||||
const deselected: string[] = [];
|
||||
if (!get().selection) return deselected;
|
||||
for (const item in get().selection) {
|
||||
if (get().selection[item] === "deselected") {
|
||||
deselected.push(item);
|
||||
}
|
||||
}
|
||||
return deselected;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -17,7 +17,13 @@ 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 { Color, Notebook, Tag } from "@notesnook/core/dist/types";
|
||||
import {
|
||||
Color,
|
||||
Notebook,
|
||||
SideBarHideableSection,
|
||||
SideBarSection,
|
||||
Tag
|
||||
} from "@notesnook/core/dist/types";
|
||||
import create, { State } from "zustand";
|
||||
import { db } from "../common/database";
|
||||
|
||||
@@ -29,17 +35,56 @@ export interface MenuStore extends State {
|
||||
clearAll: () => void;
|
||||
loadingShortcuts: boolean;
|
||||
loadingColors: boolean;
|
||||
order: Record<SideBarSection, string[]>;
|
||||
hiddenItems: Record<SideBarHideableSection, string[]>;
|
||||
}
|
||||
|
||||
export const useMenuStore = create<MenuStore>((set) => ({
|
||||
export const useMenuStore = create<MenuStore>((set, get) => ({
|
||||
menuPins: [],
|
||||
colorNotes: [],
|
||||
loadingShortcuts: true,
|
||||
loadingColors: true,
|
||||
order: {
|
||||
colors: [],
|
||||
shortcuts: [],
|
||||
routes: []
|
||||
},
|
||||
hiddenItems: {
|
||||
colors: [],
|
||||
routes: []
|
||||
},
|
||||
setMenuPins: () => {
|
||||
db.shortcuts.resolved().then((shortcuts) => {
|
||||
set({ menuPins: [...(shortcuts as [])], loadingShortcuts: false });
|
||||
});
|
||||
const sections = ["colors", "shortcuts", "routes"];
|
||||
const order: Record<SideBarSection, string[]> = {
|
||||
colors: [],
|
||||
shortcuts: [],
|
||||
routes: []
|
||||
};
|
||||
const hiddenItems: Record<SideBarHideableSection, string[]> = {
|
||||
colors: [],
|
||||
routes: []
|
||||
};
|
||||
for (const section of sections) {
|
||||
order[section as SideBarSection] = db.settings.getSideBarOrder(
|
||||
section as SideBarSection
|
||||
);
|
||||
hiddenItems[section as SideBarHideableSection] =
|
||||
db.settings.getSideBarHiddenItems("colors");
|
||||
}
|
||||
|
||||
if (
|
||||
JSON.stringify(get().order || {}) !== JSON.stringify(order || {}) ||
|
||||
JSON.stringify(get().hiddenItems || {}) !==
|
||||
JSON.stringify(hiddenItems || {})
|
||||
) {
|
||||
set({
|
||||
order: order,
|
||||
hiddenItems: hiddenItems
|
||||
});
|
||||
}
|
||||
},
|
||||
setColorNotes: () => {
|
||||
db.colors?.all
|
||||
|
||||
@@ -168,7 +168,8 @@ export const defaultSettings: SettingStore["settings"] = {
|
||||
darkTheme: ThemeDark,
|
||||
markdownShortcuts: true,
|
||||
biometricsAuthEnabled: false,
|
||||
appLockHasPasswordSecurity: false
|
||||
appLockHasPasswordSecurity: false,
|
||||
backgroundSync: true
|
||||
};
|
||||
|
||||
export const useSettingStore = create<SettingStore>((set, get) => ({
|
||||
|
||||
@@ -172,3 +172,4 @@ export const eOnChangeFluidTab = "617";
|
||||
export const eUnlockWithBiometrics = "618";
|
||||
export const eUnlockWithPassword = "619";
|
||||
export const eUpdateNoteInEditor = "620";
|
||||
export const eOnEnterEditor = "621";
|
||||
|
||||
@@ -47,8 +47,7 @@ export const MenuItemsList = [
|
||||
id: "reminders",
|
||||
name: "Reminders",
|
||||
icon: "bell",
|
||||
close: true,
|
||||
isBeta: true
|
||||
close: true
|
||||
},
|
||||
{
|
||||
id: "monographs",
|
||||
|
||||
@@ -28,7 +28,9 @@ import {
|
||||
notVisibleById,
|
||||
sleep,
|
||||
exitEditor,
|
||||
tapByText
|
||||
tapByText,
|
||||
elementByText,
|
||||
elementById
|
||||
} from "./utils";
|
||||
|
||||
describe("NOTE TESTS", () => {
|
||||
@@ -40,15 +42,15 @@ describe("NOTE TESTS", () => {
|
||||
it("Open and close a note", async () => {
|
||||
await prepare();
|
||||
await createNote();
|
||||
await tapById(notesnook.ids.note.get(1));
|
||||
await tapById(notesnook.ids.note.get(0));
|
||||
await exitEditor();
|
||||
});
|
||||
|
||||
it("Notes properties should show", async () => {
|
||||
it.only("Notes properties should show", async () => {
|
||||
await prepare();
|
||||
let note = await createNote();
|
||||
await tapById(notesnook.listitem.menu);
|
||||
await visibleByText("Created at:");
|
||||
await waitFor(elementByText("Created at:")).toBeVisible().withTimeout(500);
|
||||
});
|
||||
|
||||
it("Favorite and unfavorite a note", async () => {
|
||||
@@ -94,29 +96,27 @@ describe("NOTE TESTS", () => {
|
||||
await prepare();
|
||||
await createNote();
|
||||
await tapById(notesnook.listitem.menu);
|
||||
await sleep(1000);
|
||||
await waitFor(elementById("icon-copy")).toBeVisible().withTimeout(500);
|
||||
await tapById("icon-copy");
|
||||
await visibleByText("Note copied to clipboard");
|
||||
});
|
||||
|
||||
it("Export note dialog should show", async () => {
|
||||
await prepare();
|
||||
await createNote();
|
||||
await tapById(notesnook.listitem.menu);
|
||||
await tapById("icon-export");
|
||||
await visibleByText("PDF");
|
||||
});
|
||||
|
||||
it("Assign colors to a note", async () => {
|
||||
await prepare();
|
||||
let note = await createNote();
|
||||
await tapById(notesnook.listitem.menu);
|
||||
await tapById(notesnook.ids.dialogs.actionsheet.color("red"));
|
||||
await sleep(1000);
|
||||
await tapByText("Add color");
|
||||
await sleep(500);
|
||||
await elementById("color-title-input").typeText("Test color");
|
||||
await tapByText("Add color");
|
||||
await sleep(3000);
|
||||
await visibleById("icon-check");
|
||||
await tapById(notesnook.ids.dialogs.actionsheet.color("red"));
|
||||
await tapById("icon-color-#efefef");
|
||||
await notVisibleById("icon-check");
|
||||
await tapById(notesnook.ids.dialogs.actionsheet.color("green"));
|
||||
await tapById("icon-color-#efefef");
|
||||
await device.pressBack();
|
||||
await navigate("Green");
|
||||
await navigate("Test color");
|
||||
await visibleByText(note.body);
|
||||
});
|
||||
|
||||
@@ -124,12 +124,15 @@ describe("NOTE TESTS", () => {
|
||||
await prepare();
|
||||
await createNote();
|
||||
await tapById(notesnook.listitem.menu);
|
||||
await sleep(500);
|
||||
await tapById("icon-trash");
|
||||
await navigate("Trash");
|
||||
await sleep(500);
|
||||
await tapById(notesnook.listitem.menu);
|
||||
await sleep(500);
|
||||
await tapByText("Restore note");
|
||||
await device.pressBack();
|
||||
await sleep(500);
|
||||
await visibleByText(
|
||||
"Test note description that is very long and should not fit in text."
|
||||
);
|
||||
|
||||
@@ -85,7 +85,9 @@ async function createNote(_title, _body) {
|
||||
await webview.element(by.web.className("ProseMirror")).tap();
|
||||
await webview.element(by.web.className("ProseMirror")).typeText(body, true);
|
||||
await exitEditor();
|
||||
await expect(element(by.text(body))).toBeVisible();
|
||||
await waitFor(element(by.text(body)))
|
||||
.toBeVisible()
|
||||
.withTimeout(500);
|
||||
|
||||
return { title, body };
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
multiDexEnabled true
|
||||
versionCode 3006
|
||||
versionCode 3008
|
||||
versionName getNpmVersion()
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
@@ -52,7 +52,6 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:theme="@style/BootTheme"
|
||||
android:largeHeap="true"
|
||||
android:supportsRtl="false"
|
||||
@@ -141,6 +140,17 @@
|
||||
|
||||
<service android:name="com.streetwriters.notesnook.BootTaskService" />
|
||||
|
||||
<service
|
||||
android:name=".NotesnookTileService"
|
||||
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"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.provider"
|
||||
@@ -167,6 +177,8 @@
|
||||
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
|
||||
|
||||
|
||||
</application>
|
||||
|
||||
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1,19 @@
|
||||
package com.streetwriters.notesnook;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.service.quicksettings.Tile;
|
||||
import android.service.quicksettings.TileService;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
public class NotesnookTileService extends TileService {
|
||||
@Override
|
||||
public void onClick() {
|
||||
super.onClick();
|
||||
Intent intent = new Intent(this.getApplicationContext(), ShareActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivityAndCollapse(intent);
|
||||
}
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#FFFFFF">
|
||||
<group android:scaleX="0.12527233"
|
||||
android:scaleY="0.12527233"
|
||||
android:translateX="5.3605666"
|
||||
android:translateY="-1.5294118">
|
||||
<group android:translateY="151.2">
|
||||
<path android:pathData="M95.904,0L75.744,0L30.096,-68.976L30.096,0L9.936,0L9.936,-100.656L30.096,-100.656L75.744,-31.536L75.744,-100.656L95.904,-100.656Z"
|
||||
android:fillColor="#000000"/>
|
||||
</group>
|
||||
</group>
|
||||
</vector>
|
||||
|
Before Width: | Height: | Size: 215 B After Width: | Height: | Size: 509 B |
|
Before Width: | Height: | Size: 188 B After Width: | Height: | Size: 358 B |
|
Before Width: | Height: | Size: 270 B After Width: | Height: | Size: 667 B |
|
Before Width: | Height: | Size: 346 B After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 211 B After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M14,2L6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6zM16,16h-3v3h-2v-3L8,16v-2h3v-3h2v3h3v2zM13,9L13,3.5L18.5,9L13,9z"/>
|
||||
</vector>
|
||||
@@ -1,74 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
android:height="108dp"
|
||||
android:width="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#008577"
|
||||
android:pathData="M0,0h108v108h-108z"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
|
||||
</vector>
|
||||
@@ -1,5 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<background android:drawable="@mipmap/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@mipmap/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |