mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-09-01 11:39:21 +02:00
Compare commits
185 Commits
feat-home-
...
3.3.13-and
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97f3e02f59 | ||
|
|
5c6564f00b | ||
|
|
605e75efb3 | ||
|
|
496f9ba340 | ||
|
|
f98283f2dd | ||
|
|
2d32e85a64 | ||
|
|
1a56a22a33 | ||
|
|
c85204d0c1 | ||
|
|
1d8a958eaf | ||
|
|
b52f70d57d | ||
|
|
12687dae7b | ||
|
|
c53c3167d8 | ||
|
|
60df472d53 | ||
|
|
67fe25bc8b | ||
|
|
44a7aed308 | ||
|
|
f22937e1eb | ||
|
|
c816f8b15b | ||
|
|
fa4f4e7bea | ||
|
|
076cea6d6d | ||
|
|
d951b97bed | ||
|
|
bd888e57e5 | ||
|
|
3ba1234056 | ||
|
|
4c5a668114 | ||
|
|
06d1891389 | ||
|
|
c97a228ec8 | ||
|
|
d11a200ebb | ||
|
|
bfedc1f43e | ||
|
|
b148083750 | ||
|
|
cdfd7333cd | ||
|
|
2ed32efefb | ||
|
|
e3331e6dee | ||
|
|
b3406be1ad | ||
|
|
751d0ca861 | ||
|
|
a614693dfa | ||
|
|
c4bbceef67 | ||
|
|
d39e7c990d | ||
|
|
e248dbfa5b | ||
|
|
29f789493e | ||
|
|
2b0e5c41c8 | ||
|
|
e241b2fe72 | ||
|
|
a91983340c | ||
|
|
2aa3aaf2f8 | ||
|
|
6b2bf369ff | ||
|
|
fea5818951 | ||
|
|
e6eb71a484 | ||
|
|
1e023500be | ||
|
|
2c078de2c0 | ||
|
|
b373881b8b | ||
|
|
6b37cf0dda | ||
|
|
1025c4ec6e | ||
|
|
c44f9f685d | ||
|
|
5633d4504f | ||
|
|
c7aa49e541 | ||
|
|
ee139463d2 | ||
|
|
2cf7f0cc27 | ||
|
|
210549445e | ||
|
|
5b85158a7c | ||
|
|
09cba1a1df | ||
|
|
d75e2e85c9 | ||
|
|
966673dfd1 | ||
|
|
b8ad9265b9 | ||
|
|
b2f1fda641 | ||
|
|
1544b656e0 | ||
|
|
d97314c484 | ||
|
|
1432553897 | ||
|
|
f585fb0381 | ||
|
|
98557be52d | ||
|
|
e7da0a35d3 | ||
|
|
965a7f91ed | ||
|
|
2b1c973177 | ||
|
|
7e87cf7fdc | ||
|
|
c8cd5a4dc8 | ||
|
|
d00f7f6255 | ||
|
|
e8690e3419 | ||
|
|
72ff45c722 | ||
|
|
22a6271423 | ||
|
|
a6032ffd72 | ||
|
|
69ea9992ce | ||
|
|
36b4d69c2d | ||
|
|
2129533aa9 | ||
|
|
e635ace338 | ||
|
|
e729086015 | ||
|
|
3fbb7ce548 | ||
|
|
e0c31d6fdd | ||
|
|
d19c5b9775 | ||
|
|
f01b88083f | ||
|
|
d3ab1d1d2a | ||
|
|
7855442d33 | ||
|
|
bbaa6c76b4 | ||
|
|
cf71809f6c | ||
|
|
3b7d0ddb36 | ||
|
|
482fea91f3 | ||
|
|
4bace10bab | ||
|
|
603b7da9e6 | ||
|
|
bf4d168c96 | ||
|
|
756bb82fb7 | ||
|
|
81b1005b02 | ||
|
|
7ba52d1358 | ||
|
|
fcfb2c7e01 | ||
|
|
fa0a5afa63 | ||
|
|
b881b236a0 | ||
|
|
ec9c40bd64 | ||
|
|
f0f7857355 | ||
|
|
9c59cbbc40 | ||
|
|
6dce41a070 | ||
|
|
dffeb05f86 | ||
|
|
ecfbfd83b9 | ||
|
|
0aea1deea0 | ||
|
|
f041b92360 | ||
|
|
64074030dd | ||
|
|
3387dda22b | ||
|
|
534b810350 | ||
|
|
50666c1307 | ||
|
|
200c9bd9eb | ||
|
|
2f93c6fdc8 | ||
|
|
5c21c0a498 | ||
|
|
93c07d6842 | ||
|
|
5d9f18ed77 | ||
|
|
313d47d9ff | ||
|
|
304a9f3218 | ||
|
|
a160725acb | ||
|
|
05535b50da | ||
|
|
70fdcf2b44 | ||
|
|
67d47c283e | ||
|
|
dd59aab328 | ||
|
|
509b308419 | ||
|
|
7a12fd701e | ||
|
|
9396a06a89 | ||
|
|
9d9c3b7db5 | ||
|
|
4cfb10db7c | ||
|
|
50024b62cb | ||
|
|
f31084baa7 | ||
|
|
2688becb05 | ||
|
|
8298900f76 | ||
|
|
b0c18a8ece | ||
|
|
9659a12d70 | ||
|
|
6abc25f58a | ||
|
|
a164ac67ce | ||
|
|
255677d98e | ||
|
|
2a92ea4efd | ||
|
|
608b4f3009 | ||
|
|
a69cb1bf9c | ||
|
|
be40b3d160 | ||
|
|
51147b17cd | ||
|
|
5d0c2ae0b9 | ||
|
|
8435e69cb5 | ||
|
|
1d6a33e3be | ||
|
|
3f4d78af7d | ||
|
|
f94012d184 | ||
|
|
ca73c0c2ce | ||
|
|
742bf31038 | ||
|
|
1369b64cb1 | ||
|
|
bc3488512f | ||
|
|
51f87911e1 | ||
|
|
4d4af5c278 | ||
|
|
a6fcff4d86 | ||
|
|
50b7f9eb68 | ||
|
|
a81c534258 | ||
|
|
67cd2e6504 | ||
|
|
ed5c84b069 | ||
|
|
f6c442ba88 | ||
|
|
19e59b778d | ||
|
|
0643a74ade | ||
|
|
1babbb567c | ||
|
|
11899501f2 | ||
|
|
9982d7b7b4 | ||
|
|
2f66ee04cc | ||
|
|
a829594fad | ||
|
|
1427518e4b | ||
|
|
ecf75a07e4 | ||
|
|
41094c80d5 | ||
|
|
09b12c6d7e | ||
|
|
c560685285 | ||
|
|
48d2099308 | ||
|
|
ca3604129d | ||
|
|
583a68cabe | ||
|
|
924eaf8486 | ||
|
|
9c1a02dbd5 | ||
|
|
637a19ee21 | ||
|
|
896420064f | ||
|
|
f6140e8396 | ||
|
|
de37cb0da1 | ||
|
|
167ce97dd9 | ||
|
|
a218ee1c02 | ||
|
|
c6d471cec9 |
@@ -113,7 +113,7 @@ jobs:
|
||||
keyStorePassword: ${{ secrets.KEY_PASSWORD }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
env:
|
||||
BUILD_TOOLS_VERSION: "33.0.0"
|
||||
BUILD_TOOLS_VERSION: "36.0.0"
|
||||
|
||||
- name: Build apks for Github release
|
||||
run: yarn release:android
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
keyStorePassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
|
||||
keyPassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
|
||||
env:
|
||||
BUILD_TOOLS_VERSION: "33.0.0"
|
||||
BUILD_TOOLS_VERSION: "36.0.0"
|
||||
|
||||
- name: Rename apk files
|
||||
run: |
|
||||
|
||||
4
.github/workflows/android.publish.yml
vendored
4
.github/workflows/android.publish.yml
vendored
@@ -113,7 +113,7 @@ jobs:
|
||||
keyStorePassword: ${{ secrets.KEY_PASSWORD }}
|
||||
keyPassword: ${{ secrets.KEY_PASSWORD }}
|
||||
env:
|
||||
BUILD_TOOLS_VERSION: "33.0.0"
|
||||
BUILD_TOOLS_VERSION: "36.0.0"
|
||||
|
||||
- name: Build apks for Github release
|
||||
run: yarn release:android
|
||||
@@ -128,7 +128,7 @@ jobs:
|
||||
keyStorePassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
|
||||
keyPassword: ${{ secrets.PUBLIC_KEY_PASSWORD }}
|
||||
env:
|
||||
BUILD_TOOLS_VERSION: "33.0.0"
|
||||
BUILD_TOOLS_VERSION: "36.0.0"
|
||||
|
||||
- name: Rename apk files
|
||||
run: |
|
||||
|
||||
78
.github/workflows/themes.publish.yml
vendored
Normal file
78
.github/workflows/themes.publish.yml
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
|
||||
# GitHub recommends pinning actions to a commit SHA.
|
||||
# To get a newer version, you will need to update the SHA.
|
||||
# You can also reference a tag or branch, but the action may change without warning.
|
||||
|
||||
name: Publish @notesnook/themes-server
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
name: Push Docker image to Docker Hub
|
||||
runs-on: ubuntu-22.04
|
||||
permissions:
|
||||
packages: write
|
||||
contents: read
|
||||
attestations: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Collect package metadata
|
||||
id: package_metadata
|
||||
working-directory: ./servers/themes
|
||||
run: |
|
||||
echo ::set-output name=app_version::$(cat package.json | jq -r .version)
|
||||
|
||||
# Setup Buildx
|
||||
- name: Docker Setup Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
ecr: auto
|
||||
logout: true
|
||||
|
||||
# Pull previous image from docker hub to use it as cache to improve the image build time.
|
||||
- name: docker pull cache image
|
||||
continue-on-error: true
|
||||
run: docker pull streetwriters/themes-server:latest
|
||||
|
||||
# Setup QEMU
|
||||
# - name: Set up QEMU
|
||||
# uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: streetwriters/themes-server
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: servers/themes/Dockerfile
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: streetwriters/themes-server:${{ steps.package_metadata.outputs.app_version }},streetwriters/themes-server:latest
|
||||
cache-from: streetwriters/themes-server:latest
|
||||
|
||||
- name: Generate artifact attestation
|
||||
uses: actions/attest-build-provenance@v1
|
||||
with:
|
||||
subject-name: index.docker.io/streetwriters/themes-server
|
||||
subject-digest: ${{ steps.push.outputs.digest }}
|
||||
push-to-registry: true
|
||||
577
apps/desktop/package-lock.json
generated
577
apps/desktop/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
"name": "@notesnook/desktop",
|
||||
"productName": "Notesnook",
|
||||
"description": "Your private note taking space",
|
||||
"version": "3.3.6",
|
||||
"version": "3.3.8-beta.1",
|
||||
"appAppleId": "1544027013",
|
||||
"private": true,
|
||||
"main": "./dist/cjs/index.js",
|
||||
|
||||
@@ -39,7 +39,7 @@ const LINUX_AUTOSTART_DIRECTORY_PATH = path.join(
|
||||
"autostart"
|
||||
);
|
||||
|
||||
const STARTUP_ARGS = ["--hidden"];
|
||||
const HIDDEN_ARG = "--hidden";
|
||||
|
||||
export class AutoLaunch {
|
||||
static enable(hidden: boolean) {
|
||||
@@ -54,13 +54,13 @@ export class AutoLaunch {
|
||||
);
|
||||
} else {
|
||||
const loginItemSettings = app.getLoginItemSettings({
|
||||
args: STARTUP_ARGS
|
||||
args: hidden ? [HIDDEN_ARG] : undefined
|
||||
});
|
||||
if (loginItemSettings.openAtLogin) return;
|
||||
app.setLoginItemSettings({
|
||||
openAtLogin: true,
|
||||
openAsHidden: hidden,
|
||||
args: STARTUP_ARGS
|
||||
args: hidden ? [HIDDEN_ARG] : undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
multiDexEnabled true
|
||||
versionCode 3087
|
||||
versionCode 3091
|
||||
versionName getNpmVersion()
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
- Get an overview of your year with Notesnook with Wrapped 2025
|
||||
- Cell selection and column resizing in tables
|
||||
- Added support for uploading large files in background
|
||||
- Bug fixes and minor improvements
|
||||
- Add notebooks, tags and colors to Home Screen Shortcuts
|
||||
- Change day format and use /day in notes
|
||||
- Add Setting to change default editor line height
|
||||
- Set a custom title for monographs
|
||||
- Add webpage title and date clipped to web clips
|
||||
- Configure Week to start from Sunday or Monday
|
||||
- Change Note's creation date
|
||||
- Set expiry date on notes
|
||||
- Temporarily disable password change and recovery options
|
||||
- Note history now includes note title
|
||||
- Minor bug fixes
|
||||
|
||||
Thank you for using Notesnook!
|
||||
|
||||
@@ -69,7 +69,7 @@ const App = (props: { configureMode: "note-preview" }) => {
|
||||
setTimeout(async () => {
|
||||
await Notifications.get();
|
||||
if (SettingsService.get().notifNotes) {
|
||||
Notifications.pinQuickNote(true);
|
||||
Notifications.pinQuickNote();
|
||||
}
|
||||
TipManager.init();
|
||||
}, 100);
|
||||
|
||||
@@ -222,15 +222,21 @@ export async function deleteCacheFileByName(name: string) {
|
||||
}
|
||||
|
||||
export async function deleteDCacheFiles() {
|
||||
await createCacheDir();
|
||||
const files = await RNFetchBlob.fs.ls(cacheDir);
|
||||
for (const file of files) {
|
||||
if (file.includes("_dcache") || file.startsWith("NN_")) {
|
||||
await RNFetchBlob.fs.unlink(file).catch(() => {
|
||||
/* empty */
|
||||
});
|
||||
try {
|
||||
await createCacheDir();
|
||||
const files = await RNFetchBlob.fs.ls(cacheDir);
|
||||
for (const file of files) {
|
||||
if (
|
||||
file.includes("_dcache") ||
|
||||
file.startsWith("NN_") ||
|
||||
file.endsWith(".pdf")
|
||||
) {
|
||||
await RNFetchBlob.fs.unlink(file).catch(() => {
|
||||
/* empty */
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
export async function getCachePathForFile(filename: string) {
|
||||
|
||||
@@ -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 { isImage, RequestOptions } from "@notesnook/core";
|
||||
import { isImage, RequestOptions, hosts } from "@notesnook/core";
|
||||
import { PermissionsAndroid, Platform } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
@@ -32,6 +32,133 @@ import {
|
||||
getUploadedFileSize
|
||||
} from "./utils";
|
||||
import Upload from "@ammarahmed/react-native-upload";
|
||||
import { CloudUploader } from "react-native-nitro-cloud-uploader";
|
||||
|
||||
// Upload constants
|
||||
const CHUNK_SIZE = 10 * 1024 * 1024; // 10 MB
|
||||
const MINIMUM_MULTIPART_FILE_SIZE = 25 * 1024 * 1024; // 25MB
|
||||
|
||||
interface InitiateMultipartResponse {
|
||||
uploadId: string;
|
||||
parts: string[];
|
||||
error?: string;
|
||||
}
|
||||
|
||||
async function initiateMultipartUpload(
|
||||
filename: string,
|
||||
fileSize: number,
|
||||
headers: Record<string, string>
|
||||
): Promise<InitiateMultipartResponse> {
|
||||
const totalParts = Math.ceil(fileSize / CHUNK_SIZE);
|
||||
|
||||
const url = `${hosts.API_HOST}/s3/multipart?name=${filename}&parts=${totalParts}&uploadId=`;
|
||||
const response = await fetch(url, { headers });
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to initiate multipart upload: ${response.statusText}`
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
throw new Error(data.error);
|
||||
}
|
||||
|
||||
if (!data.uploadId || !data.parts) {
|
||||
throw new Error("Failed to initiate multipart upload: invalid response.");
|
||||
}
|
||||
|
||||
DatabaseLogger.info(
|
||||
`Initiated multipart upload for ${filename} with upload ID: ${data.uploadId}`
|
||||
);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
async function multipartUploadFile(
|
||||
filename: string,
|
||||
filePath: string,
|
||||
fileSize: number,
|
||||
requestOptions: RequestOptions,
|
||||
cancelToken: { cancel: (reason?: string) => Promise<void> }
|
||||
): Promise<Response> {
|
||||
const { headers } = requestOptions;
|
||||
|
||||
try {
|
||||
const uploadData = await initiateMultipartUpload(
|
||||
filename,
|
||||
fileSize,
|
||||
headers
|
||||
);
|
||||
const { uploadId, parts } = uploadData;
|
||||
|
||||
DatabaseLogger.info(
|
||||
`Starting upload for ${filename} with ${parts.length} parts`
|
||||
);
|
||||
|
||||
cancelToken.cancel = async () => {
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
await CloudUploader.cancelUpload(uploadId);
|
||||
};
|
||||
|
||||
CloudUploader.addListener("upload-progress", (event) => {
|
||||
useAttachmentStore
|
||||
.getState()
|
||||
.setProgress(
|
||||
event.bytesUploaded || 0,
|
||||
event.totalBytes || fileSize,
|
||||
filename,
|
||||
0,
|
||||
"upload"
|
||||
);
|
||||
DatabaseLogger.info(
|
||||
`File upload progress: ${filename}, ${event.bytesUploaded}/${
|
||||
event.totalBytes || fileSize
|
||||
}, chunk: ${event.chunkIndex}, progress: ${event.progress}`
|
||||
);
|
||||
});
|
||||
// CloudUploader handles chunking and uploading all parts internally
|
||||
const result = await CloudUploader.startUpload(
|
||||
filename,
|
||||
filePath,
|
||||
parts,
|
||||
3, // maxParallel
|
||||
true // showNotification
|
||||
);
|
||||
|
||||
CloudUploader.removeListener("upload-progress");
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error("Failed to upload multipart file");
|
||||
}
|
||||
|
||||
DatabaseLogger.info(
|
||||
`Multipart upload completed for ${filename} with upload ID: ${uploadId}`
|
||||
);
|
||||
|
||||
const response = await fetch(`${hosts.API_HOST}/s3/multipart`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
Key: filename,
|
||||
UploadId: uploadId,
|
||||
PartETags: result.etags.map((etag, index) => ({
|
||||
partNumber: index + 1,
|
||||
etag: etag
|
||||
}))
|
||||
}),
|
||||
headers: { ...headers, "Content-Type": "application/json" }
|
||||
});
|
||||
|
||||
return response;
|
||||
} catch (error) {
|
||||
DatabaseLogger.error(error, "Multipart upload failed", { filename });
|
||||
CloudUploader.removeListener("upload-progress");
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function uploadFile(
|
||||
filename: string,
|
||||
@@ -96,72 +223,111 @@ export async function uploadFile(
|
||||
}
|
||||
}
|
||||
|
||||
const upload = Upload.create({
|
||||
customUploadId: filename,
|
||||
path: Platform.OS === "ios" ? "file://" + fileInfo.path : fileInfo.path,
|
||||
url: url,
|
||||
method: "PUT",
|
||||
headers: {
|
||||
...headers,
|
||||
"content-type": "application/octet-stream"
|
||||
},
|
||||
appGroup: IOS_APPGROUPID,
|
||||
notification: {
|
||||
filename:
|
||||
attachmentInfo && isImage(attachmentInfo?.mimeType)
|
||||
? "image"
|
||||
: attachmentInfo?.filename || "file",
|
||||
enabled: true,
|
||||
enableRingTone: true,
|
||||
autoClear: true
|
||||
}
|
||||
}).onChange((event) => {
|
||||
switch (event.status) {
|
||||
case "running":
|
||||
case "pending":
|
||||
useAttachmentStore
|
||||
.getState()
|
||||
.setProgress(
|
||||
event.uploadedBytes || 0,
|
||||
event.totalBytes || fileInfo.size,
|
||||
filename,
|
||||
0,
|
||||
"upload"
|
||||
);
|
||||
DatabaseLogger.info(
|
||||
`File upload progress: ${filename}, ${event.uploadedBytes}/${
|
||||
event.totalBytes || fileInfo.size
|
||||
}`
|
||||
);
|
||||
break;
|
||||
case "completed":
|
||||
console.log("Upload completed");
|
||||
break;
|
||||
}
|
||||
});
|
||||
const result = await upload.start();
|
||||
cancelToken.cancel = async () => {
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
upload.cancel();
|
||||
};
|
||||
let uploaded = false;
|
||||
|
||||
const status = result.responseCode || 0;
|
||||
const uploaded = status >= 200 && status < 300;
|
||||
// Use multipart upload for files larger than MINIMUM_MULTIPART_FILE_SIZE
|
||||
if (fileInfo.size >= MINIMUM_MULTIPART_FILE_SIZE) {
|
||||
DatabaseLogger.info(
|
||||
`Using multipart upload for large file: ${filename} (${fileInfo.size} bytes)`
|
||||
);
|
||||
const result = await multipartUploadFile(
|
||||
filename,
|
||||
filePath,
|
||||
fileInfo.size,
|
||||
requestOptions,
|
||||
cancelToken
|
||||
);
|
||||
const status = result.status || 0;
|
||||
uploaded = status >= 200 && status < 300;
|
||||
|
||||
if (!uploaded) {
|
||||
const fileInfo = await RNFetchBlob.fs.stat(filePath);
|
||||
throw new Error(
|
||||
`${status}, name: ${fileInfo.filename}, length: ${
|
||||
fileInfo.size
|
||||
}, info: ${JSON.stringify(await result.text())}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Use single-part upload for smaller files
|
||||
DatabaseLogger.info(
|
||||
`Using single-part upload for file: ${filename} (${fileInfo.size} bytes)`
|
||||
);
|
||||
const upload = Upload.create({
|
||||
customUploadId: filename,
|
||||
path: Platform.OS === "ios" ? "file://" + fileInfo.path : fileInfo.path,
|
||||
url: url,
|
||||
method: "PUT",
|
||||
headers: {
|
||||
...headers,
|
||||
"content-type": "application/octet-stream"
|
||||
},
|
||||
appGroup: IOS_APPGROUPID,
|
||||
notification: {
|
||||
filename:
|
||||
attachmentInfo && isImage(attachmentInfo?.mimeType)
|
||||
? "image"
|
||||
: attachmentInfo?.filename || "file",
|
||||
enabled: true,
|
||||
enableRingTone: true,
|
||||
autoClear: true
|
||||
}
|
||||
}).onChange((event) => {
|
||||
switch (event.status) {
|
||||
case "running":
|
||||
case "pending":
|
||||
useAttachmentStore
|
||||
.getState()
|
||||
.setProgress(
|
||||
event.uploadedBytes || 0,
|
||||
event.totalBytes || fileInfo.size,
|
||||
filename,
|
||||
0,
|
||||
"upload"
|
||||
);
|
||||
DatabaseLogger.info(
|
||||
`File upload progress: ${filename}, ${event.uploadedBytes}/${
|
||||
event.totalBytes || fileInfo.size
|
||||
}`
|
||||
);
|
||||
break;
|
||||
case "completed":
|
||||
DatabaseLogger.info("Upload completed");
|
||||
break;
|
||||
}
|
||||
});
|
||||
const result = await upload.start();
|
||||
cancelToken.cancel = async () => {
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
upload.cancel();
|
||||
};
|
||||
|
||||
const status = result.responseCode || 0;
|
||||
uploaded = status >= 200 && status < 300;
|
||||
|
||||
if (!uploaded) {
|
||||
const fileInfo = await RNFetchBlob.fs.stat(filePath);
|
||||
throw new Error(
|
||||
`${status}, name: ${fileInfo.filename}, length: ${
|
||||
fileInfo.size
|
||||
}, info: ${JSON.stringify(result.error)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
|
||||
if (!uploaded) {
|
||||
const fileInfo = await RNFetchBlob.fs.stat(filePath);
|
||||
throw new Error(
|
||||
`${status}, name: ${fileInfo.filename}, length: ${
|
||||
fileInfo.size
|
||||
}, info: ${JSON.stringify(result.error)}`
|
||||
if (uploaded) {
|
||||
attachmentInfo = await db.attachments.attachment(filename);
|
||||
if (!attachmentInfo) return false;
|
||||
await checkUpload(
|
||||
filename,
|
||||
requestOptions.chunkSize,
|
||||
attachmentInfo.size
|
||||
);
|
||||
DatabaseLogger.info(`File upload status: ${filename}, success`);
|
||||
}
|
||||
attachmentInfo = await db.attachments.attachment(filename);
|
||||
if (!attachmentInfo) return false;
|
||||
await checkUpload(filename, requestOptions.chunkSize, attachmentInfo.size);
|
||||
DatabaseLogger.info(`File upload status: ${filename}, ${status}`);
|
||||
|
||||
return uploaded;
|
||||
} catch (e) {
|
||||
useAttachmentStore.getState().remove(filename);
|
||||
|
||||
@@ -161,10 +161,10 @@ export async function checkUpload(
|
||||
size === 0
|
||||
? `File size is 0.`
|
||||
: size === -1
|
||||
? `File verification check failed.`
|
||||
: expectedSize !== decryptedLength
|
||||
? `File size mismatch. Expected ${size} bytes but got ${decryptedLength} bytes.`
|
||||
: undefined;
|
||||
? `File verification check failed.`
|
||||
: expectedSize !== decryptedLength
|
||||
? `File size mismatch. Expected ${size} bytes but got ${decryptedLength} bytes.`
|
||||
: undefined;
|
||||
if (error) throw new Error(error);
|
||||
}
|
||||
|
||||
@@ -189,3 +189,7 @@ export async function checkAndCreateDir(path: string) {
|
||||
}
|
||||
return dir;
|
||||
}
|
||||
|
||||
export const santizeUri = (uri: string) => {
|
||||
return Platform.OS === "ios" ? decodeURI(uri).replace("file:///", "/") : uri;
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ import { TouchableOpacity, View, useWindowDimensions } from "react-native";
|
||||
import { SheetManager } from "react-native-actions-sheet";
|
||||
import { KeyboardAwareScrollView } from "react-native-keyboard-aware-scroll-view";
|
||||
import { DDS } from "../../services/device-detection";
|
||||
import { eSendEvent } from "../../services/event-manager";
|
||||
import { eSendEvent, ToastManager } from "../../services/event-manager";
|
||||
import Navigation from "../../services/navigation";
|
||||
import PremiumService from "../../services/premium";
|
||||
import SettingsService from "../../services/settings";
|
||||
@@ -257,8 +257,13 @@ export const Login = ({
|
||||
paddingHorizontal: 0
|
||||
}}
|
||||
onPress={() => {
|
||||
if (loading || !email.current) return;
|
||||
SheetManager.show("forgotpassword_sheet");
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message:
|
||||
"Password changing has been disabled temporarily to address some issues faced by users. It will be enabled again once the issues have resolved."
|
||||
});
|
||||
// if (loading || !email.current) return;
|
||||
// SheetManager.show("forgotpassword_sheet");
|
||||
}}
|
||||
textStyle={{
|
||||
textDecorationLine: "underline"
|
||||
|
||||
66
apps/mobile/app/components/date-picker/index.tsx
Normal file
66
apps/mobile/app/components/date-picker/index.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { useRef } from "react";
|
||||
import { View } from "react-native";
|
||||
import { defaultBorderRadius } from "../../utils/size";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import DatePicker from "react-native-date-picker";
|
||||
import dayjs from "dayjs";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { Button } from "../ui/button";
|
||||
|
||||
export default function DatePickerComponent(props: {
|
||||
onConfirm: (date: Date) => void;
|
||||
onCancel: () => void;
|
||||
}) {
|
||||
const { colors, isDark } = useThemeColors();
|
||||
const dateRef = useRef<Date>(dayjs().add(1, "day").toDate());
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: colors.primary.background,
|
||||
borderRadius: defaultBorderRadius,
|
||||
padding: DefaultAppStyles.GAP,
|
||||
borderWidth: 0.5,
|
||||
borderColor: colors.primary.border,
|
||||
width: "80%",
|
||||
gap: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
>
|
||||
<DatePicker
|
||||
theme={isDark ? "dark" : "light"}
|
||||
mode="date"
|
||||
minimumDate={dayjs().add(1, "day").toDate()}
|
||||
onCancel={() => {
|
||||
close?.();
|
||||
}}
|
||||
date={dateRef.current}
|
||||
onDateChange={(date) => {
|
||||
dateRef.current = date;
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title={strings.setExpiry()}
|
||||
type="accent"
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
onPress={async () => {
|
||||
if (!dateRef.current) return;
|
||||
props.onConfirm(dateRef.current);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
title={strings.cancel()}
|
||||
type="secondary"
|
||||
style={{
|
||||
width: "100%"
|
||||
}}
|
||||
onPress={async () => {
|
||||
props.onCancel();
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -17,7 +17,6 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { TextInput, View, ViewStyle } from "react-native";
|
||||
|
||||
@@ -23,6 +23,7 @@ import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Dimensions, TextInput, View } from "react-native";
|
||||
import Orientation from "react-native-orientation-locker";
|
||||
import Pdf from "react-native-pdf";
|
||||
import FileViewer from "react-native-file-viewer";
|
||||
import { MMKV } from "../../../common/database/mmkv";
|
||||
import downloadAttachment from "../../../common/filesystem/download-attachment";
|
||||
import { deleteCacheFileByPath, exists } from "../../../common/filesystem/io";
|
||||
@@ -43,6 +44,7 @@ import SheetProvider from "../../sheet-provider";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import { ProgressBarComponent } from "../../ui/svg/lazy";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import ReactNativeBlobUtil from "react-native-blob-util";
|
||||
|
||||
const WIN_WIDTH = Dimensions.get("window").width;
|
||||
const WIN_HEIGHT = Dimensions.get("window").height;
|
||||
@@ -117,8 +119,11 @@ const PDFPreview = () => {
|
||||
setVisible(false);
|
||||
return;
|
||||
}
|
||||
const path = `${cacheDir}/${uri}`;
|
||||
let path = `${cacheDir}/${attachment.filename}`;
|
||||
snapshotValue.current = snapshot.current;
|
||||
await ReactNativeBlobUtil.fs
|
||||
.mv(`${cacheDir}/${uri}`, path)
|
||||
.catch(console.log);
|
||||
setPDFSource("file://" + path);
|
||||
setLoading(false);
|
||||
}, 100);
|
||||
@@ -127,7 +132,7 @@ const PDFPreview = () => {
|
||||
);
|
||||
|
||||
const close = () => {
|
||||
deleteCacheFileByPath(pdfSource);
|
||||
deleteCacheFileByPath(pdfSource.replace("file://", ""));
|
||||
setPDFSource(null);
|
||||
setVisible(false);
|
||||
setPassword("");
|
||||
@@ -157,7 +162,12 @@ const PDFPreview = () => {
|
||||
|
||||
return (
|
||||
visible && (
|
||||
<BaseDialog animation="fade" visible={true} onRequestClose={close}>
|
||||
<BaseDialog
|
||||
animation="fade"
|
||||
visible={true}
|
||||
onRequestClose={close}
|
||||
useSafeArea={false}
|
||||
>
|
||||
<SheetProvider context={attachment?.hash} />
|
||||
<Dialog context={attachment?.hash} />
|
||||
|
||||
@@ -223,45 +233,48 @@ const PDFPreview = () => {
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginRight: 12
|
||||
gap: DefaultAppStyles.GAP_SMALL
|
||||
}}
|
||||
>
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
defaultValue={currentPage + ""}
|
||||
<View
|
||||
style={{
|
||||
color: colors.primary.paragraph,
|
||||
padding: 0,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
paddingVertical: 0,
|
||||
height: 25,
|
||||
backgroundColor: colors.secondary.background,
|
||||
width: 40,
|
||||
textAlign: "center",
|
||||
marginRight: 4,
|
||||
borderRadius: 3,
|
||||
fontFamily: "Inter-Regular"
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
}}
|
||||
selectTextOnFocus
|
||||
keyboardType="decimal-pad"
|
||||
onSubmitEditing={(event) => {
|
||||
setCurrentPage(event.nativeEvent.text);
|
||||
pdfRef.current?.setPage(parseInt(event.nativeEvent.text));
|
||||
}}
|
||||
blurOnSubmit
|
||||
/>
|
||||
<Paragraph color={colors.static.white}>/{numPages}</Paragraph>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row"
|
||||
}}
|
||||
>
|
||||
>
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
defaultValue={currentPage + ""}
|
||||
style={{
|
||||
color: colors.primary.paragraph,
|
||||
padding: 0,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
paddingVertical: 0,
|
||||
height: 25,
|
||||
backgroundColor: colors.secondary.background,
|
||||
width: 40,
|
||||
textAlign: "center",
|
||||
marginRight: 4,
|
||||
borderRadius: 3,
|
||||
fontFamily: "Inter-Regular"
|
||||
}}
|
||||
selectTextOnFocus
|
||||
keyboardType="decimal-pad"
|
||||
onSubmitEditing={(event) => {
|
||||
setCurrentPage(event.nativeEvent.text);
|
||||
pdfRef.current?.setPage(
|
||||
parseInt(event.nativeEvent.text)
|
||||
);
|
||||
}}
|
||||
blurOnSubmit
|
||||
/>
|
||||
<Paragraph color={colors.static.white}>
|
||||
/{numPages}
|
||||
</Paragraph>
|
||||
</View>
|
||||
<IconButton
|
||||
color={colors.static.white}
|
||||
name="download"
|
||||
@@ -269,6 +282,16 @@ const PDFPreview = () => {
|
||||
downloadAttachment(attachment.hash, false);
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
color={colors.static.white}
|
||||
name="open-in-new"
|
||||
onPress={() => {
|
||||
FileViewer.open(pdfSource, {
|
||||
showOpenWithDialog: true,
|
||||
showAppsSuggestions: true
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
{pdfSource ? (
|
||||
|
||||
@@ -1,900 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import React, { Component, createRef } from "react";
|
||||
import { InteractionManager, View } from "react-native";
|
||||
import Share from "react-native-share";
|
||||
import { notesnook } from "../../../../e2e/test.ids";
|
||||
import { db } from "../../../common/database";
|
||||
import BiometricService from "../../../services/biometrics";
|
||||
import { DDS } from "../../../services/device-detection";
|
||||
import {
|
||||
ToastManager,
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../../services/event-manager";
|
||||
import Navigation from "../../../services/navigation";
|
||||
import { getElevationStyle } from "../../../utils/elevation";
|
||||
import {
|
||||
eCloseActionSheet,
|
||||
eCloseVaultDialog,
|
||||
eOnLoadNote,
|
||||
eOpenVaultDialog,
|
||||
eUpdateNoteInEditor
|
||||
} from "../../../utils/events";
|
||||
import { deleteItems } from "../../../utils/functions";
|
||||
import { fluidTabsRef } from "../../../utils/global-refs";
|
||||
import { convertNoteToText } from "../../../utils/note-to-text";
|
||||
import { sleep } from "../../../utils/time";
|
||||
import BaseDialog from "../../dialog/base-dialog";
|
||||
import DialogButtons from "../../dialog/dialog-buttons";
|
||||
import DialogHeader from "../../dialog/dialog-header";
|
||||
import { Toast } from "../../toast";
|
||||
import { Button } from "../../ui/button";
|
||||
import Input from "../../ui/input";
|
||||
import Seperator from "../../ui/seperator";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { DefaultAppStyles } from "../../../utils/styles";
|
||||
|
||||
export class VaultDialog extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
visible: false,
|
||||
wrongPassword: false,
|
||||
loading: false,
|
||||
note: {},
|
||||
vault: false,
|
||||
locked: true,
|
||||
permanant: false,
|
||||
goToEditor: false,
|
||||
share: false,
|
||||
passwordsDontMatch: false,
|
||||
deleteNote: false,
|
||||
focusIndex: null,
|
||||
biometricUnlock: false,
|
||||
isBiometryEnrolled: false,
|
||||
isBiometryAvailable: false,
|
||||
fingerprintAccess: false,
|
||||
changePassword: false,
|
||||
copyNote: false,
|
||||
revokeFingerprintAccess: false,
|
||||
title: strings.goToEditor(),
|
||||
description: null,
|
||||
clearVault: false,
|
||||
deleteVault: false,
|
||||
deleteAll: false,
|
||||
noteLocked: false
|
||||
};
|
||||
|
||||
this.passInputRef = createRef();
|
||||
this.confirmPassRef = createRef();
|
||||
this.changePassInputRef = createRef();
|
||||
|
||||
this.password = null;
|
||||
this.confirmPassword = null;
|
||||
this.newPassword = null;
|
||||
this.title = !this.state.novault
|
||||
? strings.createVault()
|
||||
: this.state.fingerprintAccess
|
||||
? strings.vaultFingerprintUnlock()
|
||||
: this.state.revokeFingerprintAccess
|
||||
? strings.revokeVaultFingerprintUnlock()
|
||||
: this.state.changePassword
|
||||
? strings.changeVaultPassword()
|
||||
: this.state.noteLocked
|
||||
? this.state.deleteNote
|
||||
? strings.deleteNote()
|
||||
: this.state.share
|
||||
? strings.shareNote()
|
||||
: this.state.copyNote
|
||||
? strings.copyNote()
|
||||
: this.state.goToEditor
|
||||
? strings.goToEditor()
|
||||
: strings.goToEditor()
|
||||
: strings.lockNote();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
eSubscribeEvent(eOpenVaultDialog, this.open);
|
||||
eSubscribeEvent(eCloseVaultDialog, this.close);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
eUnSubscribeEvent(eOpenVaultDialog, this.open);
|
||||
eUnSubscribeEvent(eCloseVaultDialog, this.close);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import('../../../services/event-manager').vaultType} data
|
||||
*/
|
||||
open = async (data) => {
|
||||
let biometry = await BiometricService.isBiometryAvailable();
|
||||
let available = false;
|
||||
let fingerprint = await BiometricService.hasInternetCredentials("nn_vault");
|
||||
|
||||
if (biometry) {
|
||||
available = true;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
note: data.item,
|
||||
novault: data.novault,
|
||||
locked: data.locked,
|
||||
permanant: data.permanant,
|
||||
goToEditor: data.goToEditor,
|
||||
share: data.share,
|
||||
deleteNote: data.deleteNote,
|
||||
copyNote: data.copyNote,
|
||||
isBiometryAvailable: available,
|
||||
biometricUnlock: fingerprint,
|
||||
isBiometryEnrolled: fingerprint,
|
||||
fingerprintAccess: data.fingerprintAccess,
|
||||
changePassword: data.changePassword,
|
||||
revokeFingerprintAccess: data.revokeFingerprintAccess,
|
||||
title: data.title,
|
||||
description: data.description,
|
||||
clearVault: data.clearVault,
|
||||
deleteVault: data.deleteVault,
|
||||
noteLocked: data.item && (await db.vaults.itemExists(data.item))
|
||||
});
|
||||
|
||||
if (
|
||||
fingerprint &&
|
||||
data.novault &&
|
||||
!data.fingerprintAccess &&
|
||||
!data.revokeFingerprintAccess &&
|
||||
!data.changePassword &&
|
||||
!data.clearVault &&
|
||||
!data.deleteVault
|
||||
) {
|
||||
await this._onPressFingerprintAuth(data.title, data.description);
|
||||
} else {
|
||||
this.setState({
|
||||
visible: true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
close = () => {
|
||||
if (this.state.loading) {
|
||||
ToastManager.show({
|
||||
heading: this.state.title,
|
||||
message: strings.pleaseWait() + "...",
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
Navigation.queueRoutesForUpdate();
|
||||
|
||||
this.password = null;
|
||||
this.confirmPassword = null;
|
||||
this.setState({
|
||||
visible: false,
|
||||
note: {},
|
||||
locked: false,
|
||||
permanant: false,
|
||||
goToEditor: false,
|
||||
share: false,
|
||||
novault: false,
|
||||
deleteNote: false,
|
||||
passwordsDontMatch: false
|
||||
});
|
||||
};
|
||||
|
||||
onPress = async () => {
|
||||
if (this.state.revokeFingerprintAccess) {
|
||||
await this._revokeFingerprintAccess();
|
||||
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
if (this.state.loading) return;
|
||||
|
||||
if (!this.password) {
|
||||
ToastManager.show({
|
||||
heading: strings.passwordNotEntered(),
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.state.novault) {
|
||||
if (this.password !== this.confirmPassword) {
|
||||
ToastManager.show({
|
||||
heading: strings.passwordNotMatched(),
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
this.setState({
|
||||
passwordsDontMatch: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this._createVault();
|
||||
} else if (this.state.changePassword) {
|
||||
this.setState({
|
||||
loading: true
|
||||
});
|
||||
|
||||
db.vault
|
||||
.changePassword(this.password, this.newPassword)
|
||||
.then(() => {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
if (this.state.biometricUnlock) {
|
||||
this._enrollFingerprint(this.newPassword);
|
||||
}
|
||||
ToastManager.show({
|
||||
heading: strings.passwordUpdated(),
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
this.close();
|
||||
})
|
||||
.catch((e) => {
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
if (e.message === db.vault.ERRORS.wrongPassword) {
|
||||
ToastManager.show({
|
||||
heading: strings.passwordIncorrect(),
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
} else {
|
||||
ToastManager.error(e);
|
||||
}
|
||||
});
|
||||
} else if (this.state.locked) {
|
||||
if (!this.password || this.password.trim() === 0) {
|
||||
ToastManager.show({
|
||||
heading: strings.passwordIncorrect(),
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
this.setState({
|
||||
wrongPassword: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.state.noteLocked) {
|
||||
await this._unlockNote();
|
||||
} else {
|
||||
db.vault
|
||||
.unlock(this.password)
|
||||
.then(async () => {
|
||||
this.setState({
|
||||
wrongPassword: false
|
||||
});
|
||||
await this._lockNote();
|
||||
})
|
||||
.catch((e) => {
|
||||
this._takeErrorAction(e);
|
||||
});
|
||||
}
|
||||
} else if (this.state.fingerprintAccess) {
|
||||
this._enrollFingerprint(this.password);
|
||||
} else if (this.state.clearVault) {
|
||||
await this.clearVault();
|
||||
} else if (this.state.deleteVault) {
|
||||
await this.deleteVault();
|
||||
}
|
||||
};
|
||||
|
||||
deleteVault = async () => {
|
||||
this.setState({
|
||||
loading: true
|
||||
});
|
||||
try {
|
||||
let verified = true;
|
||||
if (await db.user.getUser()) {
|
||||
verified = await db.user.verifyPassword(this.password);
|
||||
}
|
||||
if (verified) {
|
||||
let noteIds = [];
|
||||
if (this.state.deleteAll) {
|
||||
const vault = await db.vaults.default();
|
||||
const relations = await db.relations.from(vault, "note").get();
|
||||
noteIds = relations.map((item) => item.toId);
|
||||
}
|
||||
await db.vault.delete(this.state.deleteAll);
|
||||
|
||||
if (this.state.deleteAll) {
|
||||
noteIds.forEach((id) => {
|
||||
eSendEvent(
|
||||
eUpdateNoteInEditor,
|
||||
{
|
||||
id: id,
|
||||
deleted: true
|
||||
},
|
||||
true
|
||||
);
|
||||
});
|
||||
}
|
||||
eSendEvent("vaultUpdated");
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.close();
|
||||
}, 100);
|
||||
} else {
|
||||
ToastManager.show({
|
||||
heading: strings.passwordIncorrect(),
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
clearVault = async () => {
|
||||
this.setState({
|
||||
loading: true
|
||||
});
|
||||
try {
|
||||
const vault = await db.vaults.default();
|
||||
const relations = await db.relations.from(vault, "note").get();
|
||||
const noteIds = relations.map((item) => item.toId);
|
||||
|
||||
await db.vault.clear(this.password);
|
||||
|
||||
noteIds.forEach((id) => {
|
||||
eSendEvent(
|
||||
eUpdateNoteInEditor,
|
||||
{
|
||||
id: id,
|
||||
deleted: true
|
||||
},
|
||||
true
|
||||
);
|
||||
});
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
this.close();
|
||||
eSendEvent("vaultUpdated");
|
||||
} catch (e) {
|
||||
ToastManager.show({
|
||||
heading: strings.passwordIncorrect(),
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
}
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
};
|
||||
|
||||
async _lockNote() {
|
||||
if (!this.password || this.password.trim() === 0) {
|
||||
ToastManager.show({
|
||||
heading: strings.passwordIncorrect(),
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
await db.vault.add(this.state.note.id);
|
||||
|
||||
eSendEvent(eUpdateNoteInEditor, this.state.note, true);
|
||||
|
||||
this.close();
|
||||
ToastManager.show({
|
||||
message: strings.noteLocked(),
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async _unlockNote() {
|
||||
if (!this.password || this.password.trim() === 0) {
|
||||
ToastManager.show({
|
||||
heading: strings.passwordIncorrect(),
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (this.state.permanant) {
|
||||
this._permanantUnlock();
|
||||
} else {
|
||||
await this._openNote();
|
||||
}
|
||||
}
|
||||
|
||||
_openNote = async () => {
|
||||
try {
|
||||
let note = await db.vault.open(this.state.note.id, this.password);
|
||||
if (this.state.biometricUnlock && !this.state.isBiometryEnrolled) {
|
||||
await this._enrollFingerprint(this.password);
|
||||
}
|
||||
|
||||
if (this.state.goToEditor) {
|
||||
this._openInEditor(note);
|
||||
} else if (this.state.share) {
|
||||
await this._shareNote(note);
|
||||
} else if (this.state.deleteNote) {
|
||||
await this._deleteNote();
|
||||
} else if (this.state.copyNote) {
|
||||
await this._copyNote(note);
|
||||
}
|
||||
} catch (e) {
|
||||
this._takeErrorAction(e);
|
||||
}
|
||||
};
|
||||
async _deleteNote() {
|
||||
try {
|
||||
await db.vault.remove(this.state.note.id, this.password);
|
||||
await deleteItems("note", [this.state.note.id]);
|
||||
this.close();
|
||||
} catch (e) {
|
||||
this._takeErrorAction(e);
|
||||
}
|
||||
}
|
||||
|
||||
async _enrollFingerprint(password) {
|
||||
this.setState(
|
||||
{
|
||||
loading: true
|
||||
},
|
||||
async () => {
|
||||
try {
|
||||
await db.vault.unlock(password);
|
||||
await BiometricService.storeCredentials(password);
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
eSendEvent("vaultUpdated");
|
||||
ToastManager.show({
|
||||
heading: strings.biometricUnlockEnabled(),
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
this.close();
|
||||
} catch (e) {
|
||||
this.close();
|
||||
ToastManager.show({
|
||||
heading: strings.passwordIncorrect(),
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async _createVault() {
|
||||
await db.vault.create(this.password);
|
||||
|
||||
if (this.state.biometricUnlock) {
|
||||
await this._enrollFingerprint(this.password);
|
||||
}
|
||||
if (this.state.note?.id) {
|
||||
await db.vault.add(this.state.note.id);
|
||||
eSendEvent(eUpdateNoteInEditor, this.state.note, true);
|
||||
this.setState({
|
||||
loading: false
|
||||
});
|
||||
ToastManager.show({
|
||||
heading: strings.noteLocked(),
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
this.close();
|
||||
} else {
|
||||
ToastManager.show({
|
||||
heading: strings.vaultCreated(),
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
this.close();
|
||||
}
|
||||
eSendEvent("vaultUpdated");
|
||||
}
|
||||
|
||||
_permanantUnlock() {
|
||||
db.vault
|
||||
.remove(this.state.note.id, this.password)
|
||||
.then(() => {
|
||||
ToastManager.show({
|
||||
heading: strings.noteUnlocked(),
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
eSendEvent(eUpdateNoteInEditor, this.state.note, true);
|
||||
this.close();
|
||||
})
|
||||
.catch((e) => {
|
||||
this._takeErrorAction(e);
|
||||
});
|
||||
}
|
||||
|
||||
_openInEditor(note) {
|
||||
this.close();
|
||||
InteractionManager.runAfterInteractions(async () => {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note
|
||||
});
|
||||
if (!DDS.isTab) {
|
||||
fluidTabsRef.current?.goToPage("editor");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async _copyNote(note) {
|
||||
Clipboard.setString((await convertNoteToText(note, true)) || "");
|
||||
ToastManager.show({
|
||||
heading: strings.noteCopied(),
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
this.close();
|
||||
}
|
||||
|
||||
async _shareNote(note) {
|
||||
this.close();
|
||||
try {
|
||||
await Share.open({
|
||||
heading: note.title,
|
||||
failOnCancel: false,
|
||||
message: (await convertNoteToText(note)) || ""
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
_takeErrorAction(e) {
|
||||
this.setState({
|
||||
wrongPassword: true,
|
||||
visible: true
|
||||
});
|
||||
setTimeout(() => {
|
||||
ToastManager.show({
|
||||
heading: strings.passwordIncorrect(),
|
||||
type: "error",
|
||||
context: "local"
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
|
||||
_revokeFingerprintAccess = async () => {
|
||||
try {
|
||||
await BiometricService.resetCredentials();
|
||||
eSendEvent("vaultUpdated");
|
||||
ToastManager.show({
|
||||
heading: strings.biometricUnlockDisabled(),
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
} catch (e) {
|
||||
ToastManager.show({
|
||||
heading: e.message,
|
||||
type: "success",
|
||||
context: "global"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
_onPressFingerprintAuth = async (title, description) => {
|
||||
try {
|
||||
let credentials = await BiometricService.getCredentials(
|
||||
title || this.state.title,
|
||||
description || this.state.description
|
||||
);
|
||||
|
||||
if (credentials?.password) {
|
||||
this.password = credentials.password;
|
||||
this.onPress();
|
||||
} else {
|
||||
eSendEvent(eCloseActionSheet);
|
||||
await sleep(300);
|
||||
this.setState({
|
||||
visible: true
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { colors } = this.props;
|
||||
const {
|
||||
note,
|
||||
visible,
|
||||
novault,
|
||||
deleteNote,
|
||||
share,
|
||||
goToEditor,
|
||||
fingerprintAccess,
|
||||
changePassword,
|
||||
loading,
|
||||
deleteVault,
|
||||
clearVault
|
||||
} = this.state;
|
||||
|
||||
if (!visible) return null;
|
||||
return (
|
||||
<BaseDialog
|
||||
onShow={async () => {
|
||||
await sleep(100);
|
||||
|
||||
this.passInputRef.current?.focus();
|
||||
}}
|
||||
statusBarTranslucent={false}
|
||||
onRequestClose={this.close}
|
||||
visible={true}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
...getElevationStyle(5),
|
||||
width: DDS.isTab ? 350 : "85%",
|
||||
borderRadius: 10,
|
||||
backgroundColor: colors.primary.background,
|
||||
paddingTop: 12
|
||||
}}
|
||||
>
|
||||
<DialogHeader title={this.state.title} icon="shield" padding={12} />
|
||||
<Seperator half />
|
||||
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: DefaultAppStyles.GAP
|
||||
}}
|
||||
>
|
||||
{(novault ||
|
||||
changePassword ||
|
||||
this.state.clearVault ||
|
||||
this.state.deleteVault) &&
|
||||
!this.state.revokeFingerprintAccess ? (
|
||||
<>
|
||||
<Input
|
||||
fwdRef={this.passInputRef}
|
||||
editable={!loading}
|
||||
autoCapitalize="none"
|
||||
testID={notesnook.ids.dialogs.vault.pwd}
|
||||
onChangeText={(value) => {
|
||||
this.password = value;
|
||||
}}
|
||||
marginBottom={
|
||||
!this.state.biometricUnlock ||
|
||||
!this.state.isBiometryEnrolled ||
|
||||
!novault ||
|
||||
changePassword
|
||||
? 0
|
||||
: 10
|
||||
}
|
||||
onSubmit={() => {
|
||||
changePassword
|
||||
? this.confirmPassRef.current?.focus()
|
||||
: this.onPress;
|
||||
}}
|
||||
autoComplete="password"
|
||||
returnKeyLabel={
|
||||
changePassword ? strings.next() : this.state.title
|
||||
}
|
||||
returnKeyType={changePassword ? "next" : "done"}
|
||||
secureTextEntry
|
||||
placeholder={
|
||||
changePassword
|
||||
? strings.currentPassword()
|
||||
: strings.password()
|
||||
}
|
||||
/>
|
||||
|
||||
{!this.state.biometricUnlock ||
|
||||
!this.state.isBiometryEnrolled ||
|
||||
!novault ||
|
||||
changePassword ? null : (
|
||||
<Button
|
||||
onPress={() =>
|
||||
this._onPressFingerprintAuth(strings.unlockNote(), "")
|
||||
}
|
||||
icon="fingerprint"
|
||||
width="100%"
|
||||
title={strings.unlockWithBiometrics()}
|
||||
type="transparent"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{this.state.deleteVault && (
|
||||
<Button
|
||||
onPress={() =>
|
||||
this.setState({
|
||||
deleteAll: !this.state.deleteAll
|
||||
})
|
||||
}
|
||||
icon={
|
||||
this.state.deleteAll
|
||||
? "check-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
style={{
|
||||
marginTop: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
width="100%"
|
||||
title={strings.deleteAllNotes()}
|
||||
type="errorShade"
|
||||
/>
|
||||
)}
|
||||
|
||||
{changePassword ? (
|
||||
<>
|
||||
<Seperator half />
|
||||
<Input
|
||||
ref={this.confirmPassRef}
|
||||
editable={!loading}
|
||||
testID={notesnook.ids.dialogs.vault.changePwd}
|
||||
autoCapitalize="none"
|
||||
onChangeText={(value) => {
|
||||
this.newPassword = value;
|
||||
}}
|
||||
autoComplete="password"
|
||||
onSubmit={this.onPress}
|
||||
returnKeyLabel="Change"
|
||||
returnKeyType="done"
|
||||
secureTextEntry
|
||||
placeholder={strings.newPassword()}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
|
||||
{!novault ? (
|
||||
<View>
|
||||
<Input
|
||||
fwdRef={this.passInputRef}
|
||||
autoCapitalize="none"
|
||||
testID={notesnook.ids.dialogs.vault.pwd}
|
||||
onChangeText={(value) => {
|
||||
this.password = value;
|
||||
}}
|
||||
autoComplete="password"
|
||||
returnKeyLabel={strings.next()}
|
||||
returnKeyType="next"
|
||||
secureTextEntry
|
||||
onSubmit={() => {
|
||||
this.confirmPassRef.current?.focus();
|
||||
}}
|
||||
placeholder={strings.password()}
|
||||
/>
|
||||
|
||||
<Input
|
||||
fwdRef={this.confirmPassRef}
|
||||
autoCapitalize="none"
|
||||
testID={notesnook.ids.dialogs.vault.pwdAlt}
|
||||
secureTextEntry
|
||||
validationType="confirmPassword"
|
||||
customValidator={() => this.password}
|
||||
errorMessage="Passwords do not match."
|
||||
onErrorCheck={() => null}
|
||||
marginBottom={0}
|
||||
autoComplete="password"
|
||||
returnKeyLabel="Create"
|
||||
returnKeyType="done"
|
||||
onChangeText={(value) => {
|
||||
this.confirmPassword = value;
|
||||
if (value !== this.password) {
|
||||
this.setState({
|
||||
passwordsDontMatch: true
|
||||
});
|
||||
} else {
|
||||
this.setState({
|
||||
passwordsDontMatch: false
|
||||
});
|
||||
}
|
||||
}}
|
||||
onSubmit={this.onPress}
|
||||
placeholder={strings.confirmPassword()}
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
|
||||
{this.state.biometricUnlock &&
|
||||
!this.state.isBiometryEnrolled &&
|
||||
novault ? (
|
||||
<Paragraph>{strings.vaultEnableBiometrics()}</Paragraph>
|
||||
) : null}
|
||||
|
||||
{this.state.isBiometryAvailable &&
|
||||
!this.state.fingerprintAccess &&
|
||||
!this.state.clearVault &&
|
||||
!this.state.deleteVault &&
|
||||
((!this.state.biometricUnlock && !changePassword) || !novault) ? (
|
||||
<Button
|
||||
onPress={() => {
|
||||
this.setState({
|
||||
biometricUnlock: !this.state.biometricUnlock
|
||||
});
|
||||
}}
|
||||
style={{
|
||||
marginTop: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
icon="fingerprint"
|
||||
width="100%"
|
||||
title={strings.unlockWithBiometrics()}
|
||||
iconColor={
|
||||
this.state.biometricUnlock
|
||||
? colors.selected.accent
|
||||
: colors.primary.icon
|
||||
}
|
||||
type={this.state.biometricUnlock ? "transparent" : "plain"}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
<DialogButtons
|
||||
onPressNegative={this.close}
|
||||
onPressPositive={this.onPress}
|
||||
loading={loading}
|
||||
positiveType={
|
||||
deleteVault || clearVault ? "errorShade" : "transparent"
|
||||
}
|
||||
positiveTitle={
|
||||
deleteVault
|
||||
? strings.delete()
|
||||
: clearVault
|
||||
? strings.clear()
|
||||
: fingerprintAccess
|
||||
? strings.enable()
|
||||
: this.state.revokeFingerprintAccess
|
||||
? strings.revoke()
|
||||
: changePassword
|
||||
? strings.change()
|
||||
: this.state.noteLocked
|
||||
? deleteNote
|
||||
? strings.delete()
|
||||
: share
|
||||
? strings.share()
|
||||
: goToEditor
|
||||
? strings.open()
|
||||
: strings.unlock()
|
||||
: !note.id
|
||||
? strings.create()
|
||||
: strings.lock()
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
<Toast context="local" />
|
||||
</BaseDialog>
|
||||
);
|
||||
}
|
||||
}
|
||||
1001
apps/mobile/app/components/dialogs/vault/index.tsx
Normal file
1001
apps/mobile/app/components/dialogs/vault/index.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@@ -131,7 +131,7 @@ function ReorderableList<T extends { id: string }>({
|
||||
]
|
||||
);
|
||||
|
||||
function getOrderedItems() {
|
||||
const getOrderedItems = React.useCallback(() => {
|
||||
if (!customizableSidebarFeature?.isAllowed) return data;
|
||||
const items: T[] = [];
|
||||
itemOrderState.forEach((id) => {
|
||||
@@ -142,7 +142,7 @@ function ReorderableList<T extends { id: string }>({
|
||||
|
||||
items.push(...data.filter((i) => !itemOrderState.includes(i.id)));
|
||||
return items;
|
||||
}
|
||||
}, [data, customizableSidebarFeature?.isAllowed]);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
|
||||
@@ -18,8 +18,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getFormattedDate } from "@notesnook/common";
|
||||
import {
|
||||
EncryptedContentItem,
|
||||
isEncryptedContent,
|
||||
Note,
|
||||
UnencryptedContentItem
|
||||
} from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import KeepAwake from "@sayem314/react-native-keep-awake";
|
||||
import { diff } from "diffblazer";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { SafeAreaView, Text, View } from "react-native";
|
||||
import Animated from "react-native-reanimated";
|
||||
@@ -32,13 +40,16 @@ import { DDS } from "../../services/device-detection";
|
||||
import {
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
eUnSubscribeEvent,
|
||||
openVault
|
||||
} from "../../services/event-manager";
|
||||
import Navigation from "../../services/navigation";
|
||||
import Sync from "../../services/sync";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { eOnLoadNote, eShowMergeDialog } from "../../utils/events";
|
||||
import { AppFontSize } from "../../utils/size";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { Dialog } from "../dialog";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import DialogButtons from "../dialog/dialog-buttons";
|
||||
import DialogContainer from "../dialog/dialog-container";
|
||||
@@ -47,46 +58,55 @@ import { Button } from "../ui/button";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import Seperator from "../ui/seperator";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { diff } from "diffblazer";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { presentDialog } from "../dialog/functions";
|
||||
|
||||
const MergeConflicts = () => {
|
||||
const { colors } = useThemeColors();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [keep, setKeep] = useState(null);
|
||||
const [copy, setCopy] = useState(null);
|
||||
const [selectedContent, setSelectedContent] =
|
||||
useState<UnencryptedContentItem>();
|
||||
const [copyOfDiscardedContent, setCopyOfDiscardedContent] =
|
||||
useState<UnencryptedContentItem>();
|
||||
const [dialogVisible, setDialogVisible] = useState(false);
|
||||
const insets = useGlobalSafeAreaInsets();
|
||||
const content = useRef(null);
|
||||
const isKeepingConflicted = !keep?.conflicted;
|
||||
const isKeeping = !!keep;
|
||||
const content = useRef<UnencryptedContentItem>(null);
|
||||
const { height } = useSettingStore((state) => state.dimensions);
|
||||
|
||||
const applyChanges = async () => {
|
||||
let _content = keep;
|
||||
let note = await db.notes.note(_content.noteId);
|
||||
let contentToSave = selectedContent;
|
||||
if (!contentToSave) return;
|
||||
let note = await db.notes.note(contentToSave.noteId);
|
||||
if (!note) return;
|
||||
await db.notes.add({
|
||||
id: note.id,
|
||||
conflicted: false,
|
||||
dateEdited: _content.dateEdited
|
||||
dateEdited: contentToSave.dateEdited
|
||||
});
|
||||
|
||||
await db.content.add({
|
||||
id: note.contentId,
|
||||
data: _content.data,
|
||||
type: _content.type,
|
||||
dateResolved: content.current?.conflicted?.dateModified || Date.now(),
|
||||
sessionId: Date.now(),
|
||||
conflicted: false
|
||||
});
|
||||
const noteLocked = await db.vaults.itemExists(note);
|
||||
|
||||
if (copy) {
|
||||
if (noteLocked) {
|
||||
await db.vault.save({
|
||||
...contentToSave,
|
||||
sessionId: `${Date.now()}`
|
||||
});
|
||||
} else {
|
||||
await db.content.add({
|
||||
id: note.contentId,
|
||||
data: contentToSave.data,
|
||||
type: contentToSave.type,
|
||||
dateResolved: content.current?.conflicted?.dateModified || Date.now(),
|
||||
sessionId: `${Date.now()}`,
|
||||
conflicted: undefined
|
||||
});
|
||||
}
|
||||
|
||||
if (copyOfDiscardedContent) {
|
||||
await db.notes.add({
|
||||
title: note.title + " (Copy)",
|
||||
content: {
|
||||
data: copy.data,
|
||||
type: copy.type
|
||||
data: copyOfDiscardedContent.data,
|
||||
type: copyOfDiscardedContent.type
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -103,15 +123,86 @@ const MergeConflicts = () => {
|
||||
Sync.run();
|
||||
};
|
||||
|
||||
const show = async (item) => {
|
||||
let noteContent = await db.content.get(item.contentId);
|
||||
content.current = { ...noteContent };
|
||||
if (__DEV__) {
|
||||
if (!noteContent.conflicted) {
|
||||
content.current.conflicted = { ...noteContent };
|
||||
const show = async (item: Note) => {
|
||||
const isLocked = await db.vaults.itemExists(item);
|
||||
let noteContent: UnencryptedContentItem;
|
||||
if (isLocked) {
|
||||
openVault({
|
||||
item: item,
|
||||
novault: true,
|
||||
customActionTitle: "Unlock note",
|
||||
customActionParagraph: "Unlock note to merge conflicts",
|
||||
onUnlock: async (item, password) => {
|
||||
if (!item || !password) return;
|
||||
const currentContent = await db.content.get(item.contentId!);
|
||||
|
||||
try {
|
||||
noteContent = {
|
||||
...(await db.content.get(item.contentId!)),
|
||||
...item.content,
|
||||
conflicted: currentContent?.conflicted
|
||||
? await db.vault.decryptContent(
|
||||
currentContent?.conflicted as EncryptedContentItem,
|
||||
password
|
||||
)
|
||||
: undefined
|
||||
} as UnencryptedContentItem;
|
||||
|
||||
content.current = noteContent;
|
||||
if (__DEV__) {
|
||||
if (!noteContent?.conflicted) {
|
||||
content.current.conflicted = noteContent;
|
||||
}
|
||||
}
|
||||
setVisible(true);
|
||||
} catch (e) {
|
||||
presentDialog({
|
||||
input: true,
|
||||
inputPlaceholder: strings.enterPassword(),
|
||||
title: strings.unlockIncomingNote(),
|
||||
paragraph: strings.unlockIncomingNoteDesc(),
|
||||
positiveText: "Unlock",
|
||||
positivePress: async (password) => {
|
||||
try {
|
||||
noteContent = {
|
||||
...(await db.content.get(item.contentId!)),
|
||||
...item.content,
|
||||
conflicted: currentContent?.conflicted
|
||||
? await db.vault.decryptContent(
|
||||
currentContent?.conflicted as EncryptedContentItem,
|
||||
password
|
||||
)
|
||||
: undefined
|
||||
} as UnencryptedContentItem;
|
||||
|
||||
content.current = noteContent;
|
||||
if (__DEV__) {
|
||||
if (!noteContent?.conflicted) {
|
||||
content.current.conflicted = noteContent;
|
||||
}
|
||||
}
|
||||
setVisible(true);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
noteContent = (await db.content.get(
|
||||
item.contentId!
|
||||
)) as UnencryptedContentItem;
|
||||
content.current = noteContent;
|
||||
if (__DEV__) {
|
||||
if (!noteContent?.conflicted) {
|
||||
content.current.conflicted = noteContent;
|
||||
}
|
||||
}
|
||||
setVisible(true);
|
||||
}
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -123,8 +214,8 @@ const MergeConflicts = () => {
|
||||
|
||||
const close = () => {
|
||||
setVisible(false);
|
||||
setCopy(null);
|
||||
setKeep(null);
|
||||
setCopyOfDiscardedContent(undefined);
|
||||
setSelectedContent(undefined);
|
||||
setDialogVisible(false);
|
||||
};
|
||||
|
||||
@@ -134,6 +225,12 @@ const MergeConflicts = () => {
|
||||
back,
|
||||
isCurrent,
|
||||
contentToKeep
|
||||
}: {
|
||||
isDiscarded: boolean;
|
||||
keeping: boolean;
|
||||
back: boolean;
|
||||
isCurrent: boolean;
|
||||
contentToKeep: UnencryptedContentItem;
|
||||
}) => {
|
||||
return (
|
||||
<View
|
||||
@@ -194,7 +291,7 @@ const MergeConflicts = () => {
|
||||
{isDiscarded ? (
|
||||
<Button
|
||||
onPress={() => {
|
||||
setCopy(contentToKeep);
|
||||
setCopyOfDiscardedContent(contentToKeep);
|
||||
setDialogVisible(true);
|
||||
}}
|
||||
title={strings.saveACopy()}
|
||||
@@ -222,7 +319,6 @@ const MergeConflicts = () => {
|
||||
paddingHorizontal: DefaultAppStyles.GAP
|
||||
}}
|
||||
fontSize={AppFontSize.xs}
|
||||
color={colors.error.paragraph}
|
||||
onPress={() => {
|
||||
setDialogVisible(true);
|
||||
}}
|
||||
@@ -244,7 +340,9 @@ const MergeConflicts = () => {
|
||||
keeping && !isDiscarded ? strings.undo() : strings.keep()
|
||||
}
|
||||
onPress={() => {
|
||||
setKeep(keeping && !isDiscarded ? null : contentToKeep);
|
||||
setSelectedContent(
|
||||
keeping && !isDiscarded ? undefined : contentToKeep
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
@@ -258,7 +356,6 @@ const MergeConflicts = () => {
|
||||
<BaseDialog
|
||||
statusBarTranslucent
|
||||
transparent={false}
|
||||
animationType="slide"
|
||||
animated={false}
|
||||
bounce={false}
|
||||
onRequestClose={() => {
|
||||
@@ -266,15 +363,9 @@ const MergeConflicts = () => {
|
||||
}}
|
||||
centered={false}
|
||||
background={colors?.primary.background}
|
||||
supportedOrientations={[
|
||||
"portrait",
|
||||
"portrait-upside-down",
|
||||
"landscape",
|
||||
"landscape-left",
|
||||
"landscape-right"
|
||||
]}
|
||||
visible={true}
|
||||
>
|
||||
<Dialog context="merge-conflicts" />
|
||||
<SafeAreaView
|
||||
style={{
|
||||
backgroundColor: colors.primary.background,
|
||||
@@ -301,15 +392,15 @@ const MergeConflicts = () => {
|
||||
style={{
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
backgroundColor: DDS.isLargeTablet() ? "rgba(0,0,0,0.3)" : null
|
||||
backgroundColor: DDS.isLargeTablet() ? "rgba(0,0,0,0.3)" : undefined
|
||||
}}
|
||||
>
|
||||
<ConfigBar
|
||||
back={true}
|
||||
isCurrent={true}
|
||||
isDiscarded={isKeeping && isKeepingConflicted}
|
||||
keeping={isKeeping}
|
||||
contentToKeep={content.current}
|
||||
isDiscarded={!!selectedContent && !selectedContent.conflicted}
|
||||
keeping={!!selectedContent}
|
||||
contentToKeep={content.current!}
|
||||
/>
|
||||
|
||||
<Animated.View
|
||||
@@ -323,15 +414,18 @@ const MergeConflicts = () => {
|
||||
<ReadonlyEditor
|
||||
editorId="conflictPrimary"
|
||||
onLoad={async (loadContent) => {
|
||||
const note = await db.notes.note(content.current?.noteId);
|
||||
const note = await db.notes.note(content.current!.noteId);
|
||||
if (!note) return;
|
||||
loadContent({
|
||||
id: note.id,
|
||||
data: diff(
|
||||
content.current.conflicted.data,
|
||||
content.current.data
|
||||
)
|
||||
});
|
||||
if (content.current && content.current.conflicted) {
|
||||
loadContent({
|
||||
id: note.id,
|
||||
data: diff(
|
||||
(content.current.conflicted as UnencryptedContentItem)
|
||||
.data,
|
||||
content.current.data
|
||||
)
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Animated.View>
|
||||
@@ -339,9 +433,11 @@ const MergeConflicts = () => {
|
||||
<ConfigBar
|
||||
back={false}
|
||||
isCurrent={false}
|
||||
isDiscarded={isKeeping && !isKeepingConflicted}
|
||||
keeping={isKeeping}
|
||||
contentToKeep={content.current.conflicted}
|
||||
isDiscarded={!!selectedContent && !!selectedContent.conflicted}
|
||||
keeping={!!selectedContent}
|
||||
contentToKeep={
|
||||
content.current!.conflicted! as UnencryptedContentItem
|
||||
}
|
||||
/>
|
||||
|
||||
<Animated.View
|
||||
@@ -354,11 +450,12 @@ const MergeConflicts = () => {
|
||||
<ReadonlyEditor
|
||||
editorId="conflictSecondary"
|
||||
onLoad={async (loadContent) => {
|
||||
const note = await db.notes.note(content.current?.noteId);
|
||||
const note = await db.notes.note(content.current?.noteId!);
|
||||
if (!note) return;
|
||||
loadContent({
|
||||
id: note.id,
|
||||
data: content.current.conflicted.data
|
||||
data: (content.current!.conflicted as UnencryptedContentItem)
|
||||
.data
|
||||
});
|
||||
}}
|
||||
/>
|
||||
@@ -47,7 +47,7 @@ const HistoryItem = ({
|
||||
}: {
|
||||
index: number;
|
||||
items?: VirtualizedGrouping<HistorySession>;
|
||||
note?: Note;
|
||||
note: Note;
|
||||
}) => {
|
||||
const [item] = useDBItem(index, "noteHistory", items);
|
||||
const { colors } = useThemeColors();
|
||||
@@ -73,6 +73,7 @@ const HistoryItem = ({
|
||||
session: getDate(item.dateCreated, item.dateModified)
|
||||
}}
|
||||
content={content}
|
||||
note={note}
|
||||
/>
|
||||
),
|
||||
context: "note_history"
|
||||
@@ -135,7 +136,7 @@ export default function NoteHistory({
|
||||
|
||||
const renderItem = useCallback(
|
||||
({ index }: { index: number }) => (
|
||||
<HistoryItem index={index} items={history} />
|
||||
<HistoryItem index={index} items={history} note={note} />
|
||||
),
|
||||
[history]
|
||||
);
|
||||
|
||||
@@ -37,21 +37,43 @@ import Paragraph from "../ui/typography/paragraph";
|
||||
import { diff } from "diffblazer";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import {
|
||||
HistorySession,
|
||||
isEncryptedContent,
|
||||
Note,
|
||||
NoteContent,
|
||||
SessionContentItem,
|
||||
TrashOrItem
|
||||
} from "@notesnook/core";
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {any} param0
|
||||
* @returns
|
||||
*/
|
||||
export default function NotePreview({ session, content, note }) {
|
||||
export default function NotePreview({
|
||||
session,
|
||||
content,
|
||||
note
|
||||
}: {
|
||||
session?: HistorySession & { session: string };
|
||||
content:
|
||||
| Partial<
|
||||
NoteContent<boolean> & {
|
||||
title: string;
|
||||
}
|
||||
>
|
||||
| undefined;
|
||||
note: TrashOrItem<Note>;
|
||||
}) {
|
||||
const { colors } = useThemeColors();
|
||||
const [locked, setLocked] = useState(false);
|
||||
|
||||
async function restore() {
|
||||
if (note && note.type === "trash") {
|
||||
if ((await db.trash.restore(note.id)) === false) return;
|
||||
await db.trash.restore(note.id);
|
||||
Navigation.queueRoutesForUpdate();
|
||||
useSelectionStore.getState().setSelectionMode(false);
|
||||
useSelectionStore.getState().setSelectionMode();
|
||||
ToastManager.show({
|
||||
heading: strings.noteRestored(),
|
||||
type: "success"
|
||||
@@ -59,24 +81,26 @@ export default function NotePreview({ session, content, note }) {
|
||||
eSendEvent(eCloseSheet);
|
||||
return;
|
||||
}
|
||||
await db.noteHistory.restore(session.id);
|
||||
if (useTabStore.getState().hasTabForNote(session?.noteId)) {
|
||||
const note = editorController.current.note.current[session?.noteId];
|
||||
if (note) {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note,
|
||||
forced: true
|
||||
});
|
||||
if (session) {
|
||||
await db.noteHistory.restore(session.id);
|
||||
if (useTabStore.getState().hasTabForNote(session?.noteId)) {
|
||||
const note = editorController.current.note.current[session?.noteId];
|
||||
if (note) {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note,
|
||||
forced: true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
eSendEvent(eCloseSheet, "note_history");
|
||||
eSendEvent(eCloseSheet);
|
||||
Navigation.queueRoutesForUpdate();
|
||||
eSendEvent(eCloseSheet, "note_history");
|
||||
eSendEvent(eCloseSheet);
|
||||
Navigation.queueRoutesForUpdate();
|
||||
|
||||
ToastManager.show({
|
||||
heading: strings.noteRestoredFromHistory(),
|
||||
type: "success"
|
||||
});
|
||||
ToastManager.show({
|
||||
heading: strings.noteRestoredFromHistory(),
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -91,15 +115,17 @@ export default function NotePreview({ session, content, note }) {
|
||||
negativeText: strings.cancel(),
|
||||
context: "local",
|
||||
positivePress: async () => {
|
||||
await db.trash.delete(note.id);
|
||||
useTrashStore.getState().refresh();
|
||||
useSelectionStore.getState().setSelectionMode(false);
|
||||
ToastManager.show({
|
||||
heading: strings.noteDeleted(),
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
eSendEvent(eCloseSheet);
|
||||
if (note) {
|
||||
await db.trash.delete(note.id);
|
||||
useTrashStore.getState().refresh();
|
||||
useSelectionStore.getState().setSelectionMode();
|
||||
ToastManager.show({
|
||||
heading: strings.noteDeleted(),
|
||||
type: "success",
|
||||
context: "local"
|
||||
});
|
||||
eSendEvent(eCloseSheet);
|
||||
}
|
||||
},
|
||||
positiveType: "error"
|
||||
});
|
||||
@@ -113,8 +139,11 @@ export default function NotePreview({ session, content, note }) {
|
||||
}}
|
||||
>
|
||||
<Dialog context="local" />
|
||||
<DialogHeader padding={12} title={note?.title || session?.session} />
|
||||
{!session?.locked && !locked ? (
|
||||
<DialogHeader
|
||||
padding={12}
|
||||
title={content?.title || note.title || session?.session}
|
||||
/>
|
||||
{!session?.locked && !locked && content?.data ? (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
@@ -125,17 +154,27 @@ export default function NotePreview({ session, content, note }) {
|
||||
editorId="historyPreview"
|
||||
onLoad={async (loadContent) => {
|
||||
try {
|
||||
if (content.data) {
|
||||
const _note = note || (await db.notes.note(session?.noteId));
|
||||
const currentContent = await db.content.get(_note.contentId);
|
||||
loadContent({
|
||||
data: diff(currentContent.data, content.data),
|
||||
id: _note.id
|
||||
});
|
||||
if (content?.data) {
|
||||
const currentContent = note?.contentId
|
||||
? await db.content.get(note.contentId)
|
||||
: undefined;
|
||||
|
||||
if (
|
||||
currentContent?.data &&
|
||||
!isEncryptedContent(currentContent)
|
||||
) {
|
||||
loadContent({
|
||||
data: diff(
|
||||
currentContent?.data || "<p></p>",
|
||||
content.data as string
|
||||
),
|
||||
id: session?.noteId || note.id
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
ToastManager.error(
|
||||
e,
|
||||
e as Error,
|
||||
"Failed to load history preview",
|
||||
"local"
|
||||
);
|
||||
@@ -153,7 +192,9 @@ export default function NotePreview({ session, content, note }) {
|
||||
}}
|
||||
>
|
||||
<Paragraph color={colors.secondary.paragraph}>
|
||||
{strings.encryptedNoteHistoryNotice()}
|
||||
{!content?.data
|
||||
? strings.noContent()
|
||||
: strings.encryptedNoteHistoryNotice()}
|
||||
</Paragraph>
|
||||
</View>
|
||||
)}
|
||||
@@ -1,72 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { AppFontSize } from "../../utils/size";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { getFormattedDate } from "@notesnook/common";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
export const DateMeta = ({ item }) => {
|
||||
const { colors } = useThemeColors();
|
||||
|
||||
function getDateMeta() {
|
||||
let keys = Object.keys(item);
|
||||
if (keys.includes("dateEdited"))
|
||||
keys.splice(
|
||||
keys.findIndex((k) => k === "dateModified"),
|
||||
1
|
||||
);
|
||||
return keys.filter((key) => key.startsWith("date") && key !== "date");
|
||||
}
|
||||
|
||||
const renderItem = (key) =>
|
||||
!item[key] ? null : (
|
||||
<View
|
||||
key={key}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL / 2
|
||||
}}
|
||||
>
|
||||
<Paragraph size={AppFontSize.xs} color={colors.secondary.paragraph}>
|
||||
{strings.dateDescFromKey(key)}
|
||||
</Paragraph>
|
||||
<Paragraph size={AppFontSize.xs} color={colors.secondary.paragraph}>
|
||||
{getFormattedDate(item[key], "date-time")}
|
||||
</Paragraph>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.primary.border,
|
||||
paddingHorizontal: DefaultAppStyles.GAP,
|
||||
paddingTop: DefaultAppStyles.GAP_VERTICAL_SMALL
|
||||
}}
|
||||
>
|
||||
{getDateMeta().map(renderItem)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
130
apps/mobile/app/components/properties/date-meta.tsx
Normal file
130
apps/mobile/app/components/properties/date-meta.tsx
Normal file
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { AppFontSize } from "../../utils/size";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { getFormattedDate } from "@notesnook/common";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import DateTimePickerModal from "react-native-modal-datetime-picker";
|
||||
import { db } from "../../common/database";
|
||||
import { Item, Note } from "@notesnook/core";
|
||||
import AppIcon from "../ui/AppIcon";
|
||||
export const DateMeta = ({ item }: { item: Item }) => {
|
||||
const { colors, isDark } = useThemeColors();
|
||||
const [isDatePickerVisible, setIsDatePickerVisible] = useState(false);
|
||||
const [dateCreated, setDateCreated] = useState(item.dateCreated);
|
||||
|
||||
function getDateMeta() {
|
||||
let keys = Object.keys(item);
|
||||
if (keys.includes("dateEdited"))
|
||||
keys.splice(
|
||||
keys.findIndex((k) => k === "dateModified"),
|
||||
1
|
||||
);
|
||||
return keys.filter((key) => key.startsWith("date") && key !== "date");
|
||||
}
|
||||
|
||||
const renderItem = (key: string) =>
|
||||
!item[key as keyof Item] ? null : (
|
||||
<View
|
||||
key={key}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
paddingVertical: DefaultAppStyles.GAP_VERTICAL_SMALL / 2
|
||||
}}
|
||||
>
|
||||
<Paragraph size={AppFontSize.xs} color={colors.secondary.paragraph}>
|
||||
{strings.dateDescFromKey(
|
||||
key as
|
||||
| "dateDeleted"
|
||||
| "dateEdited"
|
||||
| "dateModified"
|
||||
| "dateCreated"
|
||||
| "dateUploaded"
|
||||
)}
|
||||
</Paragraph>
|
||||
<Paragraph
|
||||
size={AppFontSize.xs}
|
||||
color={colors.secondary.paragraph}
|
||||
onPress={
|
||||
item.type !== "note"
|
||||
? undefined
|
||||
: () => {
|
||||
setIsDatePickerVisible(true);
|
||||
}
|
||||
}
|
||||
>
|
||||
{getFormattedDate(
|
||||
key === "dateCreated"
|
||||
? dateCreated
|
||||
: (item[key as keyof Item] as string),
|
||||
"date-time"
|
||||
)}
|
||||
{key === "dateCreated" && item.type === "note" ? (
|
||||
<>
|
||||
{" "}
|
||||
<AppIcon name="pencil" size={AppFontSize.md} />
|
||||
</>
|
||||
) : null}
|
||||
</Paragraph>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
{item.type === "note" ? (
|
||||
<DateTimePickerModal
|
||||
isVisible={isDatePickerVisible}
|
||||
mode="datetime"
|
||||
onConfirm={async (date: Date) => {
|
||||
await db.notes.add({
|
||||
id: item.id,
|
||||
dateCreated: date.getTime()
|
||||
});
|
||||
setDateCreated(date.getTime());
|
||||
setIsDatePickerVisible(false);
|
||||
}}
|
||||
onCancel={() => {
|
||||
setIsDatePickerVisible(false);
|
||||
}}
|
||||
maximumDate={new Date((item as Note).dateEdited)}
|
||||
isDarkModeEnabled={isDark}
|
||||
is24Hour={db.settings.getTimeFormat() === "24-hour"}
|
||||
date={new Date(dateCreated)}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<View
|
||||
style={{
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.primary.border,
|
||||
paddingHorizontal: DefaultAppStyles.GAP,
|
||||
paddingTop: DefaultAppStyles.GAP_VERTICAL_SMALL
|
||||
}}
|
||||
>
|
||||
{getDateMeta().map(renderItem)}
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -39,6 +39,7 @@ import { DateMeta } from "./date-meta";
|
||||
import { Items } from "./items";
|
||||
import Notebooks from "./notebooks";
|
||||
import { TagStrip, Tags } from "./tags";
|
||||
import { Dialog } from "../dialog";
|
||||
|
||||
const Line = ({ top = 6, bottom = 6 }) => {
|
||||
const { colors } = useThemeColors();
|
||||
@@ -203,6 +204,7 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
|
||||
/>
|
||||
) : null}
|
||||
<SheetProvider context="properties" />
|
||||
<Dialog context="properties" />
|
||||
</View>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -33,6 +33,7 @@ import AppIcon from "../ui/AppIcon";
|
||||
import { Button } from "../ui/button";
|
||||
import { Pressable } from "../ui/pressable";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { Dialog } from "../dialog";
|
||||
|
||||
const TOP_BAR_ITEMS: ActionId[] = [
|
||||
"pin",
|
||||
@@ -58,6 +59,7 @@ const BOTTOM_BAR_ITEMS: ActionId[] = [
|
||||
"copy-link",
|
||||
"duplicate",
|
||||
"launcher-shortcut",
|
||||
"expiry-date",
|
||||
"trash"
|
||||
];
|
||||
|
||||
|
||||
@@ -78,12 +78,15 @@ const PublishNoteSheet = ({
|
||||
const isFeatureAvailable = useIsFeatureAvailable("monographAnalytics");
|
||||
const [isLocked, setIsLocked] = useState(false);
|
||||
const [publishing, setPublishing] = useState(false);
|
||||
const customTitle = useRef<string>("");
|
||||
const pwdInput = useRef<TextInput>(null);
|
||||
const titleInput = useRef<TextInput>(null);
|
||||
const passwordValue = useRef<string>(undefined);
|
||||
const monographData = useAsync(async () => {
|
||||
return fetchMonographData(note?.id);
|
||||
}, []);
|
||||
const monograph = monographData.result?.monograph;
|
||||
customTitle.current = monograph?.title || note.title || "";
|
||||
const publishUrl = monograph && `${hosts.MONOGRAPH_HOST}/${monograph?.id}`;
|
||||
const isPublished = db.monographs.monograph(note?.id);
|
||||
|
||||
@@ -108,7 +111,7 @@ const PublishNoteSheet = ({
|
||||
try {
|
||||
if (note?.id) {
|
||||
if (isLocked && !passwordValue.current) return;
|
||||
await db.monographs.publish(note.id, {
|
||||
await db.monographs.publish(note.id, customTitle.current, {
|
||||
selfDestruct: selfDestruct,
|
||||
password: isLocked ? passwordValue.current : undefined
|
||||
});
|
||||
@@ -129,7 +132,6 @@ const PublishNoteSheet = ({
|
||||
|
||||
setPublishLoading(false);
|
||||
};
|
||||
|
||||
const setPublishLoading = (value: boolean) => {
|
||||
setPublishing(value);
|
||||
};
|
||||
@@ -245,6 +247,13 @@ const PublishNoteSheet = ({
|
||||
</TouchableOpacity>
|
||||
) : null}
|
||||
|
||||
<Input
|
||||
fwdRef={titleInput}
|
||||
onChangeText={(value) => (customTitle.current = value)}
|
||||
defaultValue={customTitle.current}
|
||||
placeholder={strings.noteTitle()}
|
||||
/>
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
if (publishing) return;
|
||||
|
||||
@@ -30,6 +30,7 @@ import ReorderableList from "../list/reorderable-list";
|
||||
import { Properties } from "../properties";
|
||||
import { useSideBarDraggingStore } from "./dragging-store";
|
||||
import { MenuItem } from "./menu-item";
|
||||
import { Default_Drag_Action } from "../../hooks/use-actions";
|
||||
|
||||
export const ColorSection = React.memo(
|
||||
function ColorSection() {
|
||||
@@ -59,7 +60,7 @@ export const ColorSection = React.memo(
|
||||
|
||||
const onLongPress = React.useCallback((item: SideMenuItem) => {
|
||||
if (useSideBarDraggingStore.getState().dragging) return;
|
||||
Properties.present(item.data as Color);
|
||||
Properties.present(item.data as Color, false, [Default_Drag_Action]);
|
||||
}, []);
|
||||
|
||||
const renderIcon = React.useCallback((item: SideMenuItem, size: number) => {
|
||||
|
||||
@@ -21,15 +21,6 @@ import { strings } from "@notesnook/intl";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import React from "react";
|
||||
import { View } from "react-native";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import {
|
||||
NavigationState,
|
||||
Route,
|
||||
SceneMap,
|
||||
SceneRendererProps,
|
||||
TabDescriptor,
|
||||
TabView
|
||||
} from "react-native-tab-view";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../common/database";
|
||||
import { useGroupOptions } from "../../hooks/use-group-options";
|
||||
@@ -59,7 +50,104 @@ import SettingsService from "../../services/settings";
|
||||
import { isFeatureAvailable } from "@notesnook/common";
|
||||
import PaywallSheet from "../sheets/paywall";
|
||||
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
|
||||
const renderScene = SceneMap({
|
||||
|
||||
/**
|
||||
* Simple Tab View Implementation for the Side bar
|
||||
*/
|
||||
type SimpleRoute = {
|
||||
key: string;
|
||||
title?: string;
|
||||
};
|
||||
|
||||
type SimpleNavigationState = {
|
||||
index: number;
|
||||
routes: SimpleRoute[];
|
||||
};
|
||||
|
||||
type SimpleTabBarProps = {
|
||||
navigationState: SimpleNavigationState;
|
||||
jumpTo: (key: string) => void;
|
||||
};
|
||||
|
||||
type SimpleTabViewProps = {
|
||||
navigationState: SimpleNavigationState;
|
||||
renderScene: ({ route }: { route: SimpleRoute }) => React.ReactNode;
|
||||
renderTabBar?: (props: SimpleTabBarProps) => React.ReactNode;
|
||||
onIndexChange?: (index: number) => void;
|
||||
};
|
||||
|
||||
const createSceneMap = (
|
||||
scenes: Record<string, React.ComponentType<any>>
|
||||
): ((props: { route: SimpleRoute }) => React.ReactNode) => {
|
||||
return ({ route }: { route: SimpleRoute }) => {
|
||||
const SceneComponent = scenes[route.key];
|
||||
if (!SceneComponent) return null;
|
||||
return <SceneComponent />;
|
||||
};
|
||||
};
|
||||
|
||||
const SimpleTabView = ({
|
||||
navigationState,
|
||||
renderScene,
|
||||
renderTabBar,
|
||||
onIndexChange
|
||||
}: SimpleTabViewProps) => {
|
||||
const loadedKeysRef = React.useRef(new Set<string>());
|
||||
const scenesRef = React.useRef(new Map<string, React.ReactNode>());
|
||||
const jumpTo = React.useCallback(
|
||||
(key: string) => {
|
||||
const nextIndex = navigationState.routes.findIndex(
|
||||
(route) => route.key === key
|
||||
);
|
||||
if (nextIndex !== -1 && nextIndex !== navigationState.index) {
|
||||
onIndexChange?.(nextIndex);
|
||||
}
|
||||
},
|
||||
[navigationState.index, navigationState.routes, onIndexChange]
|
||||
);
|
||||
|
||||
const activeKey = navigationState.routes[navigationState.index]?.key;
|
||||
if (activeKey && !loadedKeysRef.current.has(activeKey)) {
|
||||
loadedKeysRef.current.add(activeKey);
|
||||
}
|
||||
|
||||
const getSceneForRoute = React.useCallback(
|
||||
(route: SimpleRoute) => {
|
||||
const cached = scenesRef.current.get(route.key);
|
||||
if (cached) return cached;
|
||||
const created = renderScene({ route });
|
||||
scenesRef.current.set(route.key, created);
|
||||
return created;
|
||||
},
|
||||
[renderScene]
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<View style={{ flex: 1 }}>
|
||||
{navigationState.routes.map((route, routeIndex) => (
|
||||
<View
|
||||
key={route.key}
|
||||
style={{
|
||||
flex: 1,
|
||||
display: navigationState.index === routeIndex ? "flex" : "none"
|
||||
}}
|
||||
>
|
||||
{loadedKeysRef.current.has(route.key)
|
||||
? getSceneForRoute(route)
|
||||
: navigationState.index === routeIndex
|
||||
? getSceneForRoute(route)
|
||||
: null}
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
|
||||
{renderTabBar ? renderTabBar({ navigationState, jumpTo }) : null}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const renderScene = createSceneMap({
|
||||
home: SideMenuHome,
|
||||
notebooks: SideMenuNotebooks,
|
||||
tags: SideMenuTags,
|
||||
@@ -73,7 +161,7 @@ export const SideMenu = React.memo(
|
||||
const [index, setIndex] = React.useState(
|
||||
SettingsService.getProperty("defaultSidebarTab")
|
||||
);
|
||||
const [routes] = React.useState<Route[]>([
|
||||
const [routes] = React.useState<SimpleRoute[]>([
|
||||
{
|
||||
key: "home",
|
||||
title: "Home"
|
||||
@@ -98,15 +186,11 @@ export const SideMenu = React.memo(
|
||||
paddingLeft: insets.left
|
||||
}}
|
||||
>
|
||||
<TabView
|
||||
<SimpleTabView
|
||||
navigationState={{ index, routes }}
|
||||
renderTabBar={(props) => <TabBar {...props} />}
|
||||
tabBarPosition="bottom"
|
||||
renderScene={renderScene}
|
||||
onIndexChange={setIndex}
|
||||
swipeEnabled={false}
|
||||
animationEnabled={false}
|
||||
lazy
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
@@ -114,12 +198,7 @@ export const SideMenu = React.memo(
|
||||
() => true
|
||||
);
|
||||
|
||||
const TabBar = (
|
||||
props: SceneRendererProps & {
|
||||
navigationState: NavigationState<Route>;
|
||||
options: Record<string, TabDescriptor<Route>> | undefined;
|
||||
}
|
||||
) => {
|
||||
const TabBar = (props: SimpleTabBarProps) => {
|
||||
const dragging = useSideBarDraggingStore((state) => state.dragging);
|
||||
const { colors, isDark } = useThemeColors();
|
||||
const groupOptions = useGroupOptions(
|
||||
|
||||
@@ -37,7 +37,7 @@ import { Pressable } from "../ui/pressable";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { useSideBarDraggingStore } from "./dragging-store";
|
||||
|
||||
function _MenuItem({
|
||||
export function MenuItem({
|
||||
item,
|
||||
index,
|
||||
testID,
|
||||
@@ -179,12 +179,3 @@ function _MenuItem({
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
export const MenuItem = React.memo(_MenuItem, (prev, next) => {
|
||||
if (
|
||||
prev.item.id !== next.item.id &&
|
||||
prev.item.data?.dateModified !== next.item.data?.dateModified
|
||||
)
|
||||
return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
@@ -31,6 +31,9 @@ import ReorderableList from "../list/reorderable-list";
|
||||
import { MenuItem } from "./menu-item";
|
||||
import { useThemeColors } from "@notesnook/theme";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { useSideBarDraggingStore } from "./dragging-store";
|
||||
import { Properties } from "../properties";
|
||||
import { Default_Drag_Action } from "../../hooks/use-actions";
|
||||
|
||||
export const PinnedSection = React.memo(
|
||||
function PinnedSection() {
|
||||
@@ -57,6 +60,11 @@ export const PinnedSection = React.memo(
|
||||
});
|
||||
}, []);
|
||||
|
||||
const onLongPress = React.useCallback((item: SideMenuItem) => {
|
||||
if (useSideBarDraggingStore.getState().dragging) return;
|
||||
Properties.present(item.data as Notebook, false, [Default_Drag_Action]);
|
||||
}, []);
|
||||
|
||||
const menuItems = useMemo(
|
||||
() =>
|
||||
menuPins.map((item) => ({
|
||||
@@ -65,7 +73,8 @@ export const PinnedSection = React.memo(
|
||||
icon: item.type === "notebook" ? "notebook-outline" : "pound",
|
||||
dataType: item.type,
|
||||
data: item,
|
||||
onPress: onPress
|
||||
onPress: onPress,
|
||||
onLongPress: onLongPress
|
||||
})) as SideMenuItem[],
|
||||
[menuPins, onPress]
|
||||
);
|
||||
@@ -98,6 +107,7 @@ export const PinnedSection = React.memo(
|
||||
flexGrow: 1,
|
||||
width: "100%"
|
||||
}}
|
||||
disableDefaultDrag
|
||||
contentContainerStyle={{
|
||||
flexGrow: 1
|
||||
}}
|
||||
|
||||
@@ -85,6 +85,7 @@ export function SideMenuHome() {
|
||||
keyboardDismissMode="interactive"
|
||||
keyboardShouldPersistTaps="handled"
|
||||
keyExtractor={() => "scroll-items"}
|
||||
bounces={false}
|
||||
renderItem={() => (
|
||||
<>
|
||||
<ReorderableList
|
||||
|
||||
@@ -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/>.
|
||||
*/
|
||||
/* eslint-disable no-inner-declarations */
|
||||
import { useAreFeaturesAvailable } from "@notesnook/common";
|
||||
import { isFeatureAvailable, useAreFeaturesAvailable } from "@notesnook/common";
|
||||
import {
|
||||
Color,
|
||||
createInternalLink,
|
||||
@@ -32,7 +32,7 @@ import { useThemeColors } from "@notesnook/theme";
|
||||
import { DisplayedNotification } from "@notifee/react-native";
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { InteractionManager, Platform } from "react-native";
|
||||
import { InteractionManager, Platform, View } from "react-native";
|
||||
import Share from "react-native-share";
|
||||
import { DatabaseLogger, db } from "../common/database";
|
||||
import { AttachmentDialog } from "../components/attachments";
|
||||
@@ -67,13 +67,14 @@ import { useSelectionStore } from "../stores/use-selection-store";
|
||||
import { useSettingStore } from "../stores/use-setting-store";
|
||||
import { useTagStore } from "../stores/use-tag-store";
|
||||
import { useUserStore } from "../stores/use-user-store";
|
||||
import { eUpdateNoteInEditor } from "../utils/events";
|
||||
import { eCloseSheet, eUpdateNoteInEditor } from "../utils/events";
|
||||
import { deleteItems } from "../utils/functions";
|
||||
import { convertNoteToText } from "../utils/note-to-text";
|
||||
import { sleep } from "../utils/time";
|
||||
import { resetStoredState } from "./use-stored-state";
|
||||
import { NotesnookModule } from "../utils/notesnook-module";
|
||||
|
||||
import DatePickerComponent from "../components/date-picker";
|
||||
|
||||
export type ActionId =
|
||||
| "select"
|
||||
| "archive"
|
||||
@@ -118,7 +119,8 @@ export type ActionId =
|
||||
| "trash"
|
||||
| "default-homepage"
|
||||
| "default-tag"
|
||||
| "launcher-shortcut";
|
||||
| "launcher-shortcut"
|
||||
| "expiry-date";
|
||||
|
||||
export type Action = {
|
||||
id: ActionId;
|
||||
@@ -134,6 +136,31 @@ export type Action = {
|
||||
locked?: boolean;
|
||||
};
|
||||
|
||||
export const Default_Drag_Action: Action = {
|
||||
id: "reorder",
|
||||
title: strings.reorder(),
|
||||
icon: "sort-ascending",
|
||||
onPress: async () => {
|
||||
const feature = await isFeatureAvailable("customizableSidebar");
|
||||
if (feature && !feature.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: feature.error,
|
||||
type: "info",
|
||||
context: "local",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(feature);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
useSideBarDraggingStore.setState({
|
||||
dragging: true
|
||||
});
|
||||
eSendEvent(eCloseSheet);
|
||||
}
|
||||
};
|
||||
|
||||
function isNotePinnedInNotifications(item: Item) {
|
||||
const pinned = Notifications.getPinnedNotes();
|
||||
if (!pinned || pinned.length === 0) {
|
||||
@@ -163,10 +190,11 @@ export const useActions = ({
|
||||
"notebooks",
|
||||
"customizableSidebar",
|
||||
"customHomepage",
|
||||
"androidLauncherShortcuts"
|
||||
"androidLauncherShortcuts",
|
||||
"expiringNotes"
|
||||
]);
|
||||
const [item, setItem] = useState(propItem);
|
||||
const { colors } = useThemeColors();
|
||||
const { colors, isDark } = useThemeColors();
|
||||
const setMenuPins = useMenuStore((state) => state.setMenuPins);
|
||||
const [isPinnedToMenu, setIsPinnedToMenu] = useState(
|
||||
db.shortcuts.exists(item.id)
|
||||
@@ -439,31 +467,6 @@ export const useActions = ({
|
||||
icon: "square-edit-outline",
|
||||
onPress: renameColor
|
||||
});
|
||||
|
||||
actions.push({
|
||||
id: "reorder",
|
||||
title: strings.reorder(),
|
||||
icon: "sort-ascending",
|
||||
onPress: async () => {
|
||||
if (features && !features.customizableSidebar.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: features.customizableSidebar.error,
|
||||
type: "info",
|
||||
context: "local",
|
||||
actionText: strings.upgrade(),
|
||||
func: () => {
|
||||
PaywallSheet.present(features.customizableSidebar);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
useSideBarDraggingStore.setState({
|
||||
dragging: true
|
||||
});
|
||||
close();
|
||||
},
|
||||
locked: !features?.customizableSidebar.isAllowed
|
||||
});
|
||||
}
|
||||
|
||||
if (item.type === "reminder") {
|
||||
@@ -949,7 +952,7 @@ export const useActions = ({
|
||||
copyNote: true,
|
||||
novault: true,
|
||||
locked: true,
|
||||
item: item,
|
||||
item: item as Note,
|
||||
title: strings.copyNote()
|
||||
});
|
||||
} else {
|
||||
@@ -1161,6 +1164,45 @@ export const useActions = ({
|
||||
},
|
||||
checked: item.archived,
|
||||
isToggle: true
|
||||
},
|
||||
{
|
||||
id: "expiry-date",
|
||||
title: item.expiryDate ? strings.unsetExpiry() : strings.setExpiry(),
|
||||
icon: item.expiryDate ? "bomb-off" : "bomb",
|
||||
locked: !features?.expiringNotes?.isAllowed,
|
||||
onPress: async () => {
|
||||
if (item.expiryDate) {
|
||||
await db.notes.setExpiryDate(null, item.id);
|
||||
setItem((await db.notes.note(item.id)) as Item);
|
||||
} else {
|
||||
if (features && !features?.expiringNotes.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: features?.expiringNotes.error,
|
||||
type: "info",
|
||||
actionText: strings.upgrade(),
|
||||
context: "local",
|
||||
func: () => {
|
||||
PaywallSheet.present(features?.expiringNotes);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
presentDialog({
|
||||
context: "properties",
|
||||
component: (close) => (
|
||||
<DatePickerComponent
|
||||
onCancel={() => close?.()}
|
||||
onConfirm={async (date) => {
|
||||
close?.();
|
||||
await db.notes.setExpiryDate(date.getTime(), item.id);
|
||||
setItem((await db.notes.note(item.id)) as Item);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -107,6 +107,9 @@ import { fluidTabsRef } from "../utils/global-refs";
|
||||
import { NotesnookModule } from "../utils/notesnook-module";
|
||||
import { sleep } from "../utils/time";
|
||||
import useFeatureManager from "./use-feature-manager";
|
||||
import { deleteDCacheFiles } from "../common/filesystem/io";
|
||||
import dayjs from "dayjs";
|
||||
import { useRelationStore } from "../stores/use-relation-store";
|
||||
|
||||
const onCheckSyncStatus = async (type: SyncStatusEvent) => {
|
||||
const { disableSync, disableAutoSync } = SettingsService.get();
|
||||
@@ -179,6 +182,7 @@ const onAppOpenedFromURL = async (event: {
|
||||
if (id) {
|
||||
const note = await db.notes.note(id);
|
||||
if (note) {
|
||||
editorState().initialLoadCalled = true;
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note
|
||||
});
|
||||
@@ -484,6 +488,28 @@ const IsDatabaseMigrationRequired = () => {
|
||||
return true;
|
||||
};
|
||||
|
||||
let timer: NodeJS.Timeout | null = null;
|
||||
let initialDate = -1;
|
||||
async function expiringNotesTimer() {
|
||||
if (timer != null) clearInterval(timer);
|
||||
|
||||
if (initialDate === -1) {
|
||||
DatabaseLogger.info("Deleting expired notes at startup");
|
||||
db.notes.deleteExpiredNotes();
|
||||
initialDate = dayjs().date();
|
||||
}
|
||||
|
||||
timer = setInterval(() => {
|
||||
if (dayjs().date() != initialDate) {
|
||||
DatabaseLogger.info("Deleting expired notes");
|
||||
db.notes.deleteExpiredNotes();
|
||||
Navigation.queueRoutesForUpdate();
|
||||
useRelationStore.getState().update();
|
||||
initialDate = dayjs().date();
|
||||
}
|
||||
}, 1000 * 60);
|
||||
}
|
||||
|
||||
const initializeDatabase = async (password?: string) => {
|
||||
if (useUserStore.getState().appLocked) return;
|
||||
if (!db.isInitialized) {
|
||||
@@ -503,12 +529,12 @@ const initializeDatabase = async (password?: string) => {
|
||||
if (db.isInitialized) {
|
||||
await setAppMessage();
|
||||
useSettingStore.getState().setAppLoading(false);
|
||||
useSettingStore.getState().refresh();
|
||||
Notifications.setupReminders(true);
|
||||
if (SettingsService.get().notifNotes) {
|
||||
Notifications.pinQuickNote(false);
|
||||
}
|
||||
DatabaseLogger.info("Database initialized");
|
||||
Notifications.restorePinnedNotes();
|
||||
expiringNotesTimer();
|
||||
deleteDCacheFiles();
|
||||
}
|
||||
Walkthrough.init();
|
||||
};
|
||||
@@ -699,10 +725,10 @@ export const useAppEvents = () => {
|
||||
EV.subscribe(EVENTS.syncCheckStatus, onCheckSyncStatus),
|
||||
EV.subscribe(EVENTS.syncAborted, onSyncAborted),
|
||||
EV.subscribe(EVENTS.appRefreshRequested, onSyncComplete),
|
||||
EV.subscribe(EVENTS.userLoggedOut, onLogout),
|
||||
EV.subscribe(EVENTS.userEmailConfirmed, onUserEmailVerified),
|
||||
db.eventManager.subscribe(EVENTS.userLoggedOut, onLogout),
|
||||
db.eventManager.subscribe(EVENTS.userEmailConfirmed, onUserEmailVerified),
|
||||
EV.subscribe(EVENTS.userSessionExpired, onUserSessionExpired),
|
||||
EV.subscribe(
|
||||
db.eventManager.subscribe(
|
||||
EVENTS.userSubscriptionUpdated,
|
||||
onUserSubscriptionStatusChanged
|
||||
),
|
||||
@@ -786,6 +812,7 @@ export const useAppEvents = () => {
|
||||
if (state === "active") {
|
||||
notifee.setBadgeCount(0);
|
||||
updateStatusBarColor();
|
||||
expiringNotesTimer();
|
||||
checkAutoBackup();
|
||||
Sync.run("global", false, "full");
|
||||
reconnectSSE();
|
||||
|
||||
@@ -62,95 +62,98 @@ const AppNavigation = React.memo(
|
||||
>(undefined);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (useSettingStore.getState().initialUrl) {
|
||||
const url = useSettingStore.getState().initialUrl;
|
||||
if (url?.startsWith("https://app.notesnook.com/open_notebook?")) {
|
||||
const id = new URL(url).searchParams.get("id");
|
||||
if (id) {
|
||||
setHome({
|
||||
name: "Notebook",
|
||||
params: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else if (url?.startsWith("https://app.notesnook.com/open_tag?")) {
|
||||
const id = new URL(url).searchParams.get("id");
|
||||
if (id) {
|
||||
setHome({
|
||||
name: "TaggedNotes",
|
||||
params: {
|
||||
type: "tag",
|
||||
id: id
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else if (url?.startsWith("https://app.notesnook.com/open_color?")) {
|
||||
const id = new URL(url).searchParams.get("id");
|
||||
if (id) {
|
||||
setHome({
|
||||
name: "ColoredNotes",
|
||||
params: {
|
||||
type: "color",
|
||||
id: id
|
||||
}
|
||||
});
|
||||
return;
|
||||
if (!home) {
|
||||
if (useSettingStore.getState().initialUrl) {
|
||||
const url = useSettingStore.getState().initialUrl;
|
||||
if (url?.startsWith("https://app.notesnook.com/open_notebook?")) {
|
||||
const id = new URL(url).searchParams.get("id");
|
||||
if (id) {
|
||||
setHome({
|
||||
name: "Notebook",
|
||||
params: {
|
||||
id: id
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else if (url?.startsWith("https://app.notesnook.com/open_tag?")) {
|
||||
const id = new URL(url).searchParams.get("id");
|
||||
if (id) {
|
||||
setHome({
|
||||
name: "TaggedNotes",
|
||||
params: {
|
||||
type: "tag",
|
||||
id: id
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else if (url?.startsWith("https://app.notesnook.com/open_color?")) {
|
||||
const id = new URL(url).searchParams.get("id");
|
||||
if (id) {
|
||||
setHome({
|
||||
name: "ColoredNotes",
|
||||
params: {
|
||||
type: "color",
|
||||
id: id
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (homepageV2) {
|
||||
switch (homepageV2.type) {
|
||||
case "notebook": {
|
||||
setHome({
|
||||
name: "Notebook",
|
||||
params: {
|
||||
id: homepageV2.id
|
||||
}
|
||||
});
|
||||
return;
|
||||
if (homepageV2) {
|
||||
switch (homepageV2.type) {
|
||||
case "notebook": {
|
||||
setHome({
|
||||
name: "Notebook",
|
||||
params: {
|
||||
id: homepageV2.id
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
case "color": {
|
||||
setHome({
|
||||
name: "ColoredNotes",
|
||||
params: {
|
||||
type: "color",
|
||||
id: homepageV2.id
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
case "tag": {
|
||||
setHome({
|
||||
name: "TaggedNotes",
|
||||
params: {
|
||||
type: "tag",
|
||||
id: homepageV2.id
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
case "default":
|
||||
setHome({
|
||||
name: homepageV2.id,
|
||||
params: undefined
|
||||
});
|
||||
return;
|
||||
}
|
||||
case "color": {
|
||||
setHome({
|
||||
name: "ColoredNotes",
|
||||
params: {
|
||||
type: "color",
|
||||
id: homepageV2.id
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
case "tag": {
|
||||
setHome({
|
||||
name: "TaggedNotes",
|
||||
params: {
|
||||
type: "tag",
|
||||
id: homepageV2.id
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
case "default":
|
||||
setHome(DEFAULT_HOME);
|
||||
return;
|
||||
} else {
|
||||
setHome(DEFAULT_HOME);
|
||||
}
|
||||
} else {
|
||||
setHome(DEFAULT_HOME);
|
||||
}
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!homepageV2 || loading) return;
|
||||
(async () => {
|
||||
isFeatureAvailable("customHomepage").then((value) => {
|
||||
if (!value.isAllowed) {
|
||||
SettingsService.setProperty("homepageV2", undefined);
|
||||
}
|
||||
});
|
||||
})();
|
||||
isFeatureAvailable("customHomepage").then((value) => {
|
||||
if (!value.isAllowed) {
|
||||
SettingsService.setProperty("homepageV2", undefined);
|
||||
}
|
||||
});
|
||||
}, [homepageV2, loading]);
|
||||
|
||||
React.useEffect(() => {
|
||||
|
||||
@@ -42,9 +42,9 @@ import { DDS } from "../../services/device-detection";
|
||||
import { ToastManager } from "../../services/event-manager";
|
||||
import Navigation, { NavigationProps } from "../../services/navigation";
|
||||
import Notifications from "../../services/notifications";
|
||||
import PremiumService from "../../services/premium";
|
||||
import SettingsService from "../../services/settings";
|
||||
import { useRelationStore } from "../../stores/use-relation-store";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { AppFontSize, defaultBorderRadius } from "../../utils/size";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { getFormattedDate, useIsFeatureAvailable } from "@notesnook/common";
|
||||
@@ -70,17 +70,9 @@ const RecurringModes = {
|
||||
Year: "year"
|
||||
};
|
||||
|
||||
const WeekDays = new Array(7).fill(true);
|
||||
const WeekDays = [0, 1, 2, 3, 4, 5, 6];
|
||||
const WeekDaysMon = [1, 2, 3, 4, 5, 6, 0];
|
||||
const MonthDays = new Array(31).fill(true);
|
||||
const WeekDayNames = {
|
||||
0: "Sunday",
|
||||
1: "Monday",
|
||||
2: "Tuesday",
|
||||
3: "Wednesday",
|
||||
4: "Thursday",
|
||||
5: "Friday",
|
||||
6: "Saturday"
|
||||
};
|
||||
|
||||
const ReminderNotificationModes = {
|
||||
Silent: "silent",
|
||||
@@ -92,6 +84,7 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
|
||||
const { reminder, reference } = props.route.params;
|
||||
useNavigationFocus(props.navigation, { focusOnInit: true });
|
||||
const { colors, isDark } = useThemeColors();
|
||||
const weekFormat = useSettingStore((state) => state.weekFormat);
|
||||
const [reminderMode, setReminderMode] = useState<Reminder["mode"]>(
|
||||
reminder?.mode || "once"
|
||||
);
|
||||
@@ -151,7 +144,8 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
|
||||
const isSecondLast = index === selectedDays.length - 2;
|
||||
const joinWith = isSecondLast ? " & " : isLast ? "" : ", ";
|
||||
return recurringMode === RecurringModes.Week
|
||||
? WeekDayNames[day as keyof typeof WeekDayNames] + joinWith
|
||||
? strings.weekDayNames[day as keyof typeof strings.weekDayNames]() +
|
||||
joinWith
|
||||
: `${day}${nth(day)} ${joinWith}`;
|
||||
})
|
||||
.join("");
|
||||
@@ -390,37 +384,39 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
|
||||
recurringMode === RecurringModes.Year
|
||||
? null
|
||||
: recurringMode === RecurringModes.Week
|
||||
? WeekDays.map((item, index) => (
|
||||
<Button
|
||||
key={strings.weekDayNamesShort[
|
||||
index as keyof typeof strings.weekDayNamesShort
|
||||
]()}
|
||||
title={strings.weekDayNamesShort[
|
||||
index as keyof typeof strings.weekDayNamesShort
|
||||
]()}
|
||||
type={
|
||||
selectedDays.indexOf(index) > -1
|
||||
? "selected"
|
||||
: "plain"
|
||||
}
|
||||
fontSize={AppFontSize.xs}
|
||||
style={{
|
||||
height: 40,
|
||||
borderRadius: 100,
|
||||
marginRight: 10
|
||||
}}
|
||||
onPress={() => {
|
||||
setSelectedDays((days) => {
|
||||
if (days.indexOf(index) > -1) {
|
||||
days.splice(days.indexOf(index), 1);
|
||||
? (weekFormat === "Mon" ? WeekDaysMon : WeekDays).map(
|
||||
(item) => (
|
||||
<Button
|
||||
key={strings.weekDayNamesShort[
|
||||
item as keyof typeof strings.weekDayNamesShort
|
||||
]()}
|
||||
title={strings.weekDayNamesShort[
|
||||
item as keyof typeof strings.weekDayNamesShort
|
||||
]()}
|
||||
type={
|
||||
selectedDays.indexOf(item) > -1
|
||||
? "selected"
|
||||
: "plain"
|
||||
}
|
||||
fontSize={AppFontSize.xs}
|
||||
style={{
|
||||
height: 40,
|
||||
borderRadius: 100,
|
||||
marginRight: 10
|
||||
}}
|
||||
onPress={() => {
|
||||
setSelectedDays((days) => {
|
||||
if (days.indexOf(item) > -1) {
|
||||
days.splice(days.indexOf(item), 1);
|
||||
return [...days];
|
||||
}
|
||||
days.push(item);
|
||||
return [...days];
|
||||
}
|
||||
days.push(index);
|
||||
return [...days];
|
||||
});
|
||||
}}
|
||||
/>
|
||||
))
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)
|
||||
)
|
||||
: MonthDays.map((item, index) => (
|
||||
<Button
|
||||
key={index + "monthday"}
|
||||
@@ -468,6 +464,7 @@ export default function AddReminder(props: NavigationProps<"AddReminder">) {
|
||||
onConfirm={handleConfirm}
|
||||
onCancel={hideDatePicker}
|
||||
isDarkModeEnabled={isDark}
|
||||
firstDayOfWeek={weekFormat === "Mon" ? 1 : 0}
|
||||
is24Hour={db.settings.getTimeFormat() === "24-hour"}
|
||||
date={date || new Date(Date.now())}
|
||||
/>
|
||||
|
||||
@@ -22,10 +22,10 @@ import { isFeatureAvailable } from "@notesnook/common";
|
||||
import { isImage } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import {
|
||||
pick as pickFile,
|
||||
DocumentPickerOptions,
|
||||
DocumentPickerResponse,
|
||||
keepLocalCopy
|
||||
keepLocalCopy,
|
||||
KeepLocalCopyResponse,
|
||||
pick as pickFile
|
||||
} from "@react-native-documents/picker";
|
||||
import { basename } from "pathe";
|
||||
import { Platform } from "react-native";
|
||||
@@ -35,31 +35,13 @@ import { DatabaseLogger, db } from "../../../common/database";
|
||||
import filesystem from "../../../common/filesystem";
|
||||
import { compressToFile } from "../../../common/filesystem/compress";
|
||||
import AttachImage from "../../../components/dialogs/attach-image-dialog";
|
||||
import {
|
||||
ToastManager,
|
||||
eSendEvent,
|
||||
presentSheet
|
||||
} from "../../../services/event-manager";
|
||||
import { ToastManager } from "../../../services/event-manager";
|
||||
import PremiumService from "../../../services/premium";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
import { useUserStore } from "../../../stores/use-user-store";
|
||||
import { eCloseSheet } from "../../../utils/events";
|
||||
import { useTabStore } from "./use-tab-store";
|
||||
import { editorController, editorState } from "./utils";
|
||||
|
||||
const showEncryptionSheet = (file: DocumentPickerResponse) => {
|
||||
presentSheet({
|
||||
title: strings.encryptingAttachment(),
|
||||
paragraph: strings.encryptingAttachmentDesc(file.name || ""),
|
||||
icon: "attachment"
|
||||
});
|
||||
};
|
||||
|
||||
const santizeUri = (uri: string) => {
|
||||
uri = decodeURI(uri);
|
||||
uri = Platform.OS === "ios" ? uri.replace("file:///", "/") : uri;
|
||||
return uri;
|
||||
};
|
||||
import { santizeUri } from "../../../common/filesystem/utils";
|
||||
|
||||
type PickerOptions = {
|
||||
noteId?: string;
|
||||
@@ -80,7 +62,7 @@ const file = async (fileOptions: PickerOptions) => {
|
||||
await db.attachments.generateKey();
|
||||
|
||||
let file;
|
||||
let fileCopyUri;
|
||||
let fileCopyUri: KeepLocalCopyResponse[0];
|
||||
let fileName;
|
||||
try {
|
||||
useSettingStore.getState().setAppDidEnterBackgroundForAction(true);
|
||||
@@ -97,12 +79,10 @@ const file = async (fileOptions: PickerOptions) => {
|
||||
});
|
||||
fileCopyUri = result[0];
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e as Error, "Error picking file");
|
||||
return;
|
||||
}
|
||||
|
||||
let uri =
|
||||
Platform.OS === "ios" ? fileCopyUri.sourceUri || file.uri : file.uri;
|
||||
|
||||
const featureResult = await isFeatureAvailable("fileSize", file.size || 0);
|
||||
if (!featureResult.isAllowed) {
|
||||
ToastManager.show({
|
||||
@@ -122,8 +102,7 @@ const file = async (fileOptions: PickerOptions) => {
|
||||
return;
|
||||
}
|
||||
|
||||
uri = Platform.OS === "ios" ? santizeUri(uri) : uri;
|
||||
showEncryptionSheet(file);
|
||||
let uri = santizeUri(fileCopyUri.localUri);
|
||||
const hash = await Sodium.hashFile({
|
||||
uri: uri,
|
||||
type: "url"
|
||||
@@ -139,7 +118,8 @@ const file = async (fileOptions: PickerOptions) => {
|
||||
) {
|
||||
throw new Error("Failed to attach file");
|
||||
}
|
||||
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
|
||||
|
||||
await RNFetchBlob.fs.unlink(uri);
|
||||
|
||||
if (
|
||||
fileOptions.tabId !== undefined &&
|
||||
@@ -173,10 +153,7 @@ const file = async (fileOptions: PickerOptions) => {
|
||||
} else {
|
||||
throw new Error("Failed to attach file, no tabId is set");
|
||||
}
|
||||
|
||||
eSendEvent(eCloseSheet);
|
||||
} catch (e) {
|
||||
eSendEvent(eCloseSheet);
|
||||
ToastManager.show({
|
||||
heading: (e as Error).message,
|
||||
type: "error",
|
||||
|
||||
@@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import type { ToolbarGroupDefinition } from "@notesnook/editor";
|
||||
import { useEditor } from "./use-editor";
|
||||
import { FeatureId, FeatureResult } from "@notesnook/common";
|
||||
import { DayFormat } from "@notesnook/core";
|
||||
export type useEditorType = ReturnType<typeof useEditor>;
|
||||
|
||||
export type EditorState = {
|
||||
@@ -60,6 +61,8 @@ export type Settings = {
|
||||
markdownShortcuts: boolean;
|
||||
features: Record<any, any>;
|
||||
loggedIn: boolean;
|
||||
defaultLineHeight: number;
|
||||
dayFormat: DayFormat;
|
||||
};
|
||||
|
||||
export type EditorProps = {
|
||||
|
||||
@@ -28,11 +28,13 @@ import { getDefaultPresets } from "@notesnook/editor/dist/cjs/toolbar/tool-defin
|
||||
import { strings } from "@notesnook/intl";
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import React, { useCallback, useEffect, useRef } from "react";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import {
|
||||
BackHandler,
|
||||
Keyboard,
|
||||
KeyboardEventListener,
|
||||
NativeEventSubscription,
|
||||
Platform,
|
||||
useWindowDimensions
|
||||
} from "react-native";
|
||||
import { WebViewMessageEvent } from "react-native-webview";
|
||||
@@ -82,6 +84,9 @@ import { useDragState } from "../../settings/editor/state";
|
||||
import { EditorMessage, EditorProps, useEditorType } from "./types";
|
||||
import { useTabStore } from "./use-tab-store";
|
||||
import { editorState, openInternalLink } from "./utils";
|
||||
import filesystem from "../../../common/filesystem";
|
||||
import ReactNativeBlobUtil from "react-native-blob-util";
|
||||
import { ShareComponent } from "../../../components/sheets/export-notes/share";
|
||||
|
||||
const publishNote = async () => {
|
||||
const user = useUserStore.getState().user;
|
||||
@@ -153,16 +158,22 @@ export const useEditorEvents = (
|
||||
const features = useAreFeaturesAvailable([
|
||||
"callout",
|
||||
"outlineList",
|
||||
"taskList"
|
||||
"taskList",
|
||||
"importCsvToTable",
|
||||
"exportTableAsCsv"
|
||||
]);
|
||||
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
const fullscreen = useSettingStore((state) => state.fullscreen);
|
||||
const corsProxy = useSettingStore((state) => state.settings.corsProxy);
|
||||
const loading = useSettingStore((state) => state.isAppLoading);
|
||||
const [dateFormat, timeFormat] = useSettingStore((state) => [
|
||||
state.dateFormat,
|
||||
state.timeFormat
|
||||
]);
|
||||
const [dateFormat, timeFormat, defaultLineHeight, dayFormat] =
|
||||
useSettingStore((state) => [
|
||||
state.dateFormat,
|
||||
state.timeFormat,
|
||||
state.settings.defaultLineHeight,
|
||||
state.dayFormat
|
||||
]);
|
||||
const handleBack = useRef<NativeEventSubscription>(undefined);
|
||||
const loggedIn = useUserStore((state) => !!state.user);
|
||||
const { fontScale } = useWindowDimensions();
|
||||
@@ -222,10 +233,12 @@ export const useEditorEvents = (
|
||||
fontFamily: SettingsService.get().defaultFontFamily,
|
||||
dateFormat: db.settings?.getDateFormat(),
|
||||
timeFormat: db.settings?.getTimeFormat(),
|
||||
dayFormat: db.settings?.getDayFormat(),
|
||||
fontScale,
|
||||
markdownShortcuts,
|
||||
features,
|
||||
loggedIn
|
||||
loggedIn,
|
||||
defaultLineHeight
|
||||
});
|
||||
}, [
|
||||
fullscreen,
|
||||
@@ -241,10 +254,13 @@ export const useEditorEvents = (
|
||||
defaultFontFamily,
|
||||
dateFormat,
|
||||
timeFormat,
|
||||
dayFormat,
|
||||
loading,
|
||||
fontScale,
|
||||
markdownShortcuts,
|
||||
loggedIn
|
||||
loggedIn,
|
||||
defaultLineHeight,
|
||||
features
|
||||
]);
|
||||
|
||||
const onBackPress = useCallback(async () => {
|
||||
@@ -664,7 +680,8 @@ export const useEditorEvents = (
|
||||
if (note) {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note,
|
||||
tabId: editorMessage.tabId
|
||||
tabId: editorMessage.tabId,
|
||||
loadedFromEditor: true
|
||||
});
|
||||
}
|
||||
} else {
|
||||
@@ -675,7 +692,8 @@ export const useEditorEvents = (
|
||||
if (note) {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note,
|
||||
tabId: editorMessage.tabId
|
||||
tabId: editorMessage.tabId,
|
||||
loadedFromEditor: true
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -731,6 +749,62 @@ export const useEditorEvents = (
|
||||
}
|
||||
break;
|
||||
}
|
||||
case EditorEvents.downloadCsv: {
|
||||
try {
|
||||
const csv = editorMessage.value;
|
||||
let filePath: string;
|
||||
let fileName: string;
|
||||
if (Platform.OS === "android") {
|
||||
let file = await ScopedStorage.createDocument(
|
||||
"table.csv",
|
||||
"text/csv",
|
||||
csv,
|
||||
"utf8"
|
||||
);
|
||||
if (!file) return;
|
||||
filePath = file.path || file.uri;
|
||||
fileName = file.name;
|
||||
} else {
|
||||
const path = await filesystem.checkAndCreateDir("/");
|
||||
let possibleFileName = "table.csv";
|
||||
let counter = 1;
|
||||
|
||||
// Check if file exists and find available filename
|
||||
while (
|
||||
await ReactNativeBlobUtil.fs.exists(path + possibleFileName)
|
||||
) {
|
||||
possibleFileName = `table (${counter}).csv`;
|
||||
counter++;
|
||||
}
|
||||
await ReactNativeBlobUtil.fs.writeFile(
|
||||
path + possibleFileName,
|
||||
csv,
|
||||
"utf8"
|
||||
);
|
||||
filePath = path + possibleFileName;
|
||||
fileName = possibleFileName;
|
||||
}
|
||||
|
||||
await sleep(500);
|
||||
presentSheet({
|
||||
title: "Table saved to csv",
|
||||
paragraph: strings.fileSaved(fileName, Platform.OS),
|
||||
icon: "download",
|
||||
context: "global",
|
||||
component: (
|
||||
<ShareComponent uri={filePath} name={fileName} padding={12} />
|
||||
)
|
||||
});
|
||||
} catch (e) {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message: "Could not save table to csv"
|
||||
});
|
||||
DatabaseLogger.error(e as Error);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -298,7 +298,7 @@ export const useEditor = (
|
||||
return;
|
||||
}
|
||||
|
||||
if (isContentInvalid(data) && id) {
|
||||
if (!title && isContentInvalid(data) && id) {
|
||||
// Create a new history session if recieved empty or invalid content
|
||||
// To ensure that history is preserved for correct content.
|
||||
currentSessionHistoryId = editorSessionHistory.newSession(id);
|
||||
@@ -508,9 +508,17 @@ export const useEditor = (
|
||||
newTab?: boolean;
|
||||
refresh?: boolean;
|
||||
searchResultIndex?: number;
|
||||
loadedFromEditor?: boolean;
|
||||
}) => {
|
||||
loadNoteMutex.runExclusive(async () => {
|
||||
if (!event) return;
|
||||
if (
|
||||
!event ||
|
||||
(event.loadedFromEditor &&
|
||||
event.item &&
|
||||
event.item?.id !== useTabStore.getState().getCurrentNoteId())
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (event.blockId) {
|
||||
blockIdRef.current = event.blockId;
|
||||
}
|
||||
@@ -669,7 +677,7 @@ export const useEditor = (
|
||||
state.current.currentlyEditing = true;
|
||||
if (!tabLocked) {
|
||||
await loadContent(item);
|
||||
} else {
|
||||
} else if (fluidTabsRef.current?.page() === "editor") {
|
||||
commands.focus(tabId!);
|
||||
}
|
||||
|
||||
@@ -719,6 +727,7 @@ export const useEditor = (
|
||||
}, 300);
|
||||
}
|
||||
postMessage(NativeEvents.theme, theme);
|
||||
console.log("load finished", event.item?.id);
|
||||
});
|
||||
},
|
||||
[
|
||||
|
||||
@@ -32,6 +32,8 @@ import {
|
||||
BackupReminderPicker,
|
||||
BackupWithAttachmentsReminderPicker,
|
||||
DateFormatPicker,
|
||||
DayFormatPicker,
|
||||
WeekFormatPicker,
|
||||
FontPicker,
|
||||
HomePicker,
|
||||
SidebarTabPicker,
|
||||
@@ -57,6 +59,8 @@ export const components: { [name: string]: ReactElement } = {
|
||||
"title-format": <TitleFormat />,
|
||||
"date-format-selector": <DateFormatPicker />,
|
||||
"time-format-selector": <TimeFormatPicker />,
|
||||
"day-format-selector": <DayFormatPicker />,
|
||||
"week-format-selector": <WeekFormatPicker />,
|
||||
"theme-selector": <ThemeSelector />,
|
||||
"applock-timer": <ApplockTimerPicker />,
|
||||
autobackupsattachments: <BackupWithAttachmentsReminderPicker />,
|
||||
|
||||
@@ -32,6 +32,18 @@ import { strings } from "@notesnook/intl";
|
||||
import { isFeatureAvailable } from "@notesnook/common";
|
||||
import PaywallSheet from "../../../components/sheets/paywall";
|
||||
|
||||
const DAY_FORMATS = ["short", "long"];
|
||||
const DayFormatFormats = {
|
||||
short: "ddd",
|
||||
long: "dddd"
|
||||
};
|
||||
|
||||
const WEEK_FORMATS = ["Sun", "Mon"];
|
||||
const WeekFormatNames = {
|
||||
Sun: "Sunday",
|
||||
Mon: "Monday"
|
||||
};
|
||||
|
||||
export const FontPicker = createSettingsPicker({
|
||||
getValue: () => useSettingStore.getState().settings.defaultFontFamily,
|
||||
updateValue: (item) => {
|
||||
@@ -111,8 +123,8 @@ export const TrashIntervalPicker = createSettingsPicker({
|
||||
return item === -1
|
||||
? strings.never()
|
||||
: item === 1
|
||||
? strings.reminderRecurringMode.day()
|
||||
: strings.days(item);
|
||||
? strings.reminderRecurringMode.day()
|
||||
: strings.days(item);
|
||||
},
|
||||
getItemKey: (item) => item.toString(),
|
||||
options: [-1, 1, 7, 30, 365],
|
||||
@@ -152,6 +164,42 @@ export const DateFormatPicker = createSettingsPicker({
|
||||
isOptionAvailable: () => true
|
||||
});
|
||||
|
||||
export const DayFormatPicker = createSettingsPicker({
|
||||
getValue: () => db.settings.getDayFormat(),
|
||||
updateValue: (item) => {
|
||||
db.settings.setDayFormat(item);
|
||||
useSettingStore.setState({
|
||||
dayFormat: item
|
||||
});
|
||||
},
|
||||
formatValue: (item) => {
|
||||
return `${strings.dayFormat()} (${dayjs().format(DayFormatFormats[item])})`;
|
||||
},
|
||||
getItemKey: (item) => item,
|
||||
options: DAY_FORMATS,
|
||||
compareValue: (current, item) => current === item,
|
||||
isFeatureAvailable: () => true,
|
||||
isOptionAvailable: () => true
|
||||
});
|
||||
|
||||
export const WeekFormatPicker = createSettingsPicker({
|
||||
getValue: () => db.settings.getWeekFormat(),
|
||||
updateValue: (item) => {
|
||||
db.settings.setWeekFormat(item);
|
||||
useSettingStore.setState({
|
||||
weekFormat: item
|
||||
});
|
||||
},
|
||||
formatValue: (item) => {
|
||||
return `${WeekFormatNames[item]}`;
|
||||
},
|
||||
getItemKey: (item) => item,
|
||||
options: WEEK_FORMATS,
|
||||
compareValue: (current, item) => current === item,
|
||||
isFeatureAvailable: () => true,
|
||||
isOptionAvailable: () => true
|
||||
});
|
||||
|
||||
const TimeFormats = {
|
||||
"12-hour": "hh:mm A",
|
||||
"24-hour": "HH:mm"
|
||||
@@ -228,10 +276,10 @@ export const ApplockTimerPicker = createSettingsPicker({
|
||||
return item === -1
|
||||
? strings.never()
|
||||
: item === 0 || item === undefined
|
||||
? strings.immediately()
|
||||
: item === 1
|
||||
? strings.minutes(1)
|
||||
: strings.minutes(item);
|
||||
? strings.immediately()
|
||||
: item === 1
|
||||
? strings.minutes(1)
|
||||
: strings.minutes(item);
|
||||
},
|
||||
getItemKey: (item) => item.toString(),
|
||||
options: [-1, 0, 1, 5, 15, 30],
|
||||
|
||||
@@ -95,6 +95,15 @@ const restoreBackup = async (options: {
|
||||
deleteFile?: boolean;
|
||||
}) => {
|
||||
try {
|
||||
if (
|
||||
!options.uri.endsWith(".nnbackup") &&
|
||||
!options.uri.endsWith(".nnbackupz")
|
||||
) {
|
||||
throw new Error(
|
||||
`Invalid backup file selected. Only .nnbackup and .nnbackupz files can be restored.`
|
||||
);
|
||||
}
|
||||
|
||||
const isLegacyBackup = options.uri.endsWith(".nnbackup");
|
||||
|
||||
startProgress({
|
||||
@@ -367,7 +376,6 @@ export const RestoreBackup = () => {
|
||||
disableAppLockRequests: true
|
||||
});
|
||||
const file = await pick();
|
||||
|
||||
const fileCopy = await keepLocalCopy({
|
||||
destination: "cachesDirectory",
|
||||
files: [
|
||||
@@ -390,10 +398,7 @@ export const RestoreBackup = () => {
|
||||
}, 1000);
|
||||
|
||||
restoreBackup({
|
||||
uri:
|
||||
Platform.OS === "android"
|
||||
? (("file://" + fileCopy[0].sourceUri) as string)
|
||||
: (fileCopy[0].sourceUri as string),
|
||||
uri: fileCopy[0].localUri,
|
||||
deleteFile: true
|
||||
});
|
||||
},
|
||||
|
||||
@@ -71,6 +71,7 @@ import { verifyUser, verifyUserWithApplock } from "./functions";
|
||||
import { logoutUser } from "./logout";
|
||||
import { SettingSection } from "./types";
|
||||
import { getTimeLeft } from "./user-section";
|
||||
import { EDITOR_LINE_HEIGHT } from "../../utils/constants";
|
||||
|
||||
export const settingsGroups: SettingSection[] = [
|
||||
{
|
||||
@@ -143,21 +144,20 @@ export const settingsGroups: SettingSection[] = [
|
||||
"dddd, MMMM D, YYYY h:mm A"
|
||||
);
|
||||
|
||||
const trialEndDate = dayjs(user?.subscription?.start)
|
||||
.add(
|
||||
user?.subscription?.productId?.includes("monthly") ? 7 : 14,
|
||||
"day"
|
||||
)
|
||||
.format("dddd, MMMM D, YYYY h:mm A");
|
||||
|
||||
if (
|
||||
user.subscription?.plan !== SubscriptionPlan.FREE &&
|
||||
user.subscription?.productId
|
||||
) {
|
||||
const status = user.subscription?.status;
|
||||
return status === SubscriptionStatus.TRIAL
|
||||
? strings.trialEndsOn(
|
||||
dayjs(user?.subscription?.start)
|
||||
.add(
|
||||
user?.subscription?.productId?.includes("monthly")
|
||||
? 7
|
||||
: 14
|
||||
)
|
||||
.format("dddd, MMMM D, YYYY h:mm A")
|
||||
)
|
||||
? strings.trialOnGoing(trialEndDate)
|
||||
: status === SubscriptionStatus.ACTIVE
|
||||
? strings.subRenewOn(expiryDate)
|
||||
: status === SubscriptionStatus.CANCELED ||
|
||||
@@ -286,10 +286,17 @@ export const settingsGroups: SettingSection[] = [
|
||||
{
|
||||
id: "change-password",
|
||||
name: strings.changePassword(),
|
||||
type: "screen",
|
||||
// type: "screen",
|
||||
description: strings.changePasswordDesc(),
|
||||
component: "change-password",
|
||||
icon: "form-textbox-password"
|
||||
// component: "change-password",
|
||||
icon: "form-textbox-password",
|
||||
modifer: () => {
|
||||
ToastManager.show({
|
||||
type: "info",
|
||||
message:
|
||||
"Password changing has been disabled temporarily to address some issues faced by users. It will be enabled again once the issues have resolved."
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "change-email",
|
||||
@@ -716,6 +723,22 @@ export const settingsGroups: SettingSection[] = [
|
||||
component: "date-format-selector",
|
||||
icon: "calendar-blank"
|
||||
},
|
||||
{
|
||||
id: "day-format",
|
||||
name: strings.dayFormat(),
|
||||
description: strings.dayFormatDesc(),
|
||||
type: "component",
|
||||
component: "day-format-selector",
|
||||
icon: "calendar-today"
|
||||
},
|
||||
{
|
||||
id: "week-format",
|
||||
name: strings.weekFormat(),
|
||||
description: strings.weekFormatDesc(),
|
||||
type: "component",
|
||||
component: "week-format-selector",
|
||||
icon: "calendar"
|
||||
},
|
||||
{
|
||||
id: "time-format",
|
||||
name: strings.timeFormat(),
|
||||
@@ -815,6 +838,16 @@ export const settingsGroups: SettingSection[] = [
|
||||
property: "defaultFontFamily",
|
||||
component: "font-selector"
|
||||
},
|
||||
{
|
||||
id: "default-line-height",
|
||||
name: strings.lineHeight(),
|
||||
description: strings.lineHeightDesc(),
|
||||
type: "input-selector",
|
||||
property: "defaultLineHeight",
|
||||
icon: "format-line-spacing",
|
||||
minInputValue: EDITOR_LINE_HEIGHT.MIN,
|
||||
maxInputValue: EDITOR_LINE_HEIGHT.MAX
|
||||
},
|
||||
{
|
||||
id: "title-format",
|
||||
name: strings.titleFormat(),
|
||||
@@ -896,7 +929,6 @@ export const settingsGroups: SettingSection[] = [
|
||||
hidden: (current) => (current as VaultStatusType)?.exists,
|
||||
modifer: () => {
|
||||
openVault({
|
||||
item: {},
|
||||
novault: false,
|
||||
title: strings.createVault()
|
||||
});
|
||||
@@ -910,7 +942,6 @@ export const settingsGroups: SettingSection[] = [
|
||||
hidden: (current) => !(current as VaultStatusType)?.exists,
|
||||
modifer: () =>
|
||||
openVault({
|
||||
item: {},
|
||||
changePassword: true,
|
||||
novault: true,
|
||||
title: strings.changeVaultPassword()
|
||||
@@ -924,7 +955,6 @@ export const settingsGroups: SettingSection[] = [
|
||||
hidden: (current) => !(current as VaultStatusType)?.exists,
|
||||
modifer: () => {
|
||||
openVault({
|
||||
item: {},
|
||||
clearVault: true,
|
||||
novault: true,
|
||||
title: strings.clearVault() + "?"
|
||||
@@ -939,7 +969,6 @@ export const settingsGroups: SettingSection[] = [
|
||||
hidden: (current) => !(current as VaultStatusType)?.exists,
|
||||
modifer: () => {
|
||||
openVault({
|
||||
item: {},
|
||||
deleteVault: true,
|
||||
novault: true,
|
||||
title: strings.deleteVault() + "?"
|
||||
@@ -961,7 +990,6 @@ export const settingsGroups: SettingSection[] = [
|
||||
modifer: (current) => {
|
||||
const _current = current as VaultStatusType;
|
||||
openVault({
|
||||
item: {},
|
||||
fingerprintAccess: !_current.biometryEnrolled,
|
||||
revokeFingerprintAccess: _current.biometryEnrolled,
|
||||
novault: true,
|
||||
@@ -1329,7 +1357,7 @@ export const settingsGroups: SettingSection[] = [
|
||||
if (settings.notifNotes) {
|
||||
Notifications.unpinQuickNote();
|
||||
} else {
|
||||
Notifications.pinQuickNote(false);
|
||||
Notifications.pinQuickNote();
|
||||
}
|
||||
SettingsService.set({
|
||||
notifNotes: !settings.notifNotes
|
||||
|
||||
@@ -16,6 +16,8 @@ GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { LegendList } from "@legendapp/list";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import {
|
||||
THEME_COMPATIBILITY_VERSION,
|
||||
ThemeDefinition,
|
||||
@@ -29,7 +31,6 @@ import type {
|
||||
ThemesRouter
|
||||
} from "@notesnook/themes-server";
|
||||
import { keepLocalCopy, pick } from "@react-native-documents/picker";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
|
||||
import { createTRPCReact } from "@trpc/react-query";
|
||||
@@ -40,24 +41,24 @@ import {
|
||||
TouchableOpacity,
|
||||
View
|
||||
} from "react-native";
|
||||
import ReactNativeBlobUtil from "react-native-blob-util";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { DatabaseLogger, db } from "../../common/database";
|
||||
import { santizeUri } from "../../common/filesystem/utils";
|
||||
import SheetProvider from "../../components/sheet-provider";
|
||||
import { Button } from "../../components/ui/button";
|
||||
import { IconButton } from "../../components/ui/icon-button";
|
||||
import Input from "../../components/ui/input";
|
||||
import { Pressable } from "../../components/ui/pressable";
|
||||
import Heading from "../../components/ui/typography/heading";
|
||||
import Paragraph from "../../components/ui/typography/paragraph";
|
||||
import { ToastManager, presentSheet } from "../../services/event-manager";
|
||||
import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import { defaultBorderRadius, AppFontSize } from "../../utils/size";
|
||||
import { getColorLinearShade } from "../../utils/colors";
|
||||
import { getElevationStyle } from "../../utils/elevation";
|
||||
import { MenuItemsList } from "../../utils/menu-items";
|
||||
import { IconButton } from "../../components/ui/icon-button";
|
||||
import { Pressable } from "../../components/ui/pressable";
|
||||
import { getColorLinearShade } from "../../utils/colors";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { AppFontSize, defaultBorderRadius } from "../../utils/size";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { LegendList } from "@legendapp/list";
|
||||
import ReactNativeBlobUtil from "react-native-blob-util";
|
||||
|
||||
const THEME_SERVER_URL = "https://themes-api.notesnook.com";
|
||||
//@ts-ignore
|
||||
@@ -429,12 +430,16 @@ function ThemeSelector() {
|
||||
});
|
||||
if (copiedFile[0].status !== "success") return;
|
||||
|
||||
const themeJsonCopiedPath = santizeUri(
|
||||
copiedFile[0].localUri
|
||||
);
|
||||
|
||||
const themeJson = await ReactNativeBlobUtil.fs.readFile(
|
||||
copiedFile[0].localUri,
|
||||
themeJsonCopiedPath,
|
||||
"utf8"
|
||||
);
|
||||
ReactNativeBlobUtil.fs
|
||||
.unlink(copiedFile[0].localUri)
|
||||
.unlink(themeJsonCopiedPath)
|
||||
.catch(() => {});
|
||||
const json = JSON.parse(themeJson);
|
||||
const result = validateTheme(json);
|
||||
|
||||
@@ -114,7 +114,7 @@ async function onBackgroundSyncStarted() {
|
||||
}
|
||||
await Notifications.setupReminders();
|
||||
if (SettingsService.get().notifNotes) {
|
||||
Notifications.pinQuickNote(false);
|
||||
Notifications.pinQuickNote();
|
||||
}
|
||||
Notifications.restorePinnedNotes();
|
||||
NotePreviewWidget.updateNotes();
|
||||
@@ -142,7 +142,7 @@ const onBoot = async () => {
|
||||
|
||||
await Notifications.setupReminders();
|
||||
if (SettingsService.get().notifNotes) {
|
||||
Notifications.pinQuickNote(false);
|
||||
Notifications.pinQuickNote();
|
||||
}
|
||||
Notifications.restorePinnedNotes();
|
||||
NotePreviewWidget.updateNotes();
|
||||
|
||||
@@ -76,11 +76,11 @@ async function getCredentials(title?: string, description?: string) {
|
||||
const options = Platform.select({
|
||||
ios: {
|
||||
fallbackEnabled: false,
|
||||
description: description
|
||||
description: description || title || "Unlock"
|
||||
},
|
||||
android: {
|
||||
title: title,
|
||||
description: description,
|
||||
description: description || "Unlock",
|
||||
deviceCredentialAllowed: false
|
||||
}
|
||||
});
|
||||
@@ -132,11 +132,11 @@ async function validateUser(title: string, description?: string) {
|
||||
Platform.select({
|
||||
ios: {
|
||||
fallbackEnabled: false,
|
||||
description: title
|
||||
description: title || "Unlock"
|
||||
},
|
||||
android: {
|
||||
title: title,
|
||||
description: description,
|
||||
description: description || "Unlock",
|
||||
deviceCredentialAllowed: false
|
||||
}
|
||||
}) as AuthenticateIOS
|
||||
|
||||
@@ -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 { EventHandler, EventManager } from "@notesnook/core";
|
||||
import { EventHandler, EventManager, Note, NoteContent } from "@notesnook/core";
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import { RefObject } from "react";
|
||||
import { ActionSheetRef } from "react-native-actions-sheet";
|
||||
@@ -32,8 +32,8 @@ import {
|
||||
} from "../utils/events";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
type Vault = {
|
||||
item: unknown;
|
||||
export type Vault = {
|
||||
item: Note;
|
||||
novault: boolean;
|
||||
title: string;
|
||||
description: string;
|
||||
@@ -48,6 +48,14 @@ type Vault = {
|
||||
clearVault: boolean;
|
||||
deleteVault: boolean;
|
||||
copyNote: boolean;
|
||||
customActionTitle: string;
|
||||
customActionParagraph: string;
|
||||
onUnlock: (
|
||||
item: Note & {
|
||||
content?: NoteContent<false>;
|
||||
},
|
||||
password: string
|
||||
) => void;
|
||||
};
|
||||
|
||||
type NoteEdit = {
|
||||
|
||||
@@ -109,6 +109,15 @@ async function initDatabase() {
|
||||
const onEvent = async ({ type, detail }: Event) => {
|
||||
await initDatabase();
|
||||
const { notification, pressAction, input } = detail;
|
||||
if (
|
||||
type === EventType.DISMISSED &&
|
||||
Platform.OS === "android" &&
|
||||
notification?.id === "notesnook_note_input" &&
|
||||
SettingsService.getProperty("notifNotes")
|
||||
) {
|
||||
pinQuickNote();
|
||||
}
|
||||
|
||||
if (type === EventType.DELIVERED && Platform.OS === "android") {
|
||||
if (notification?.id) {
|
||||
const reminder = await db.reminders?.reminder(
|
||||
@@ -438,6 +447,7 @@ async function scheduleNotification(
|
||||
|
||||
async function loadNote(id: string, jump: boolean) {
|
||||
if (!id || id === "notesnook_note_input") return;
|
||||
editorState().initialLoadCalled = true;
|
||||
const note = await db.notes.note(id);
|
||||
if (!note) return;
|
||||
if (!DDS.isTab && jump) {
|
||||
@@ -453,9 +463,11 @@ async function loadNote(id: string, jump: boolean) {
|
||||
|
||||
const tab = useTabStore.getState().getTabForNote(id);
|
||||
if (useTabStore.getState().currentTab !== tab) {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note
|
||||
});
|
||||
setTimeout(() => {
|
||||
eSendEvent(eOnLoadNote, {
|
||||
item: note
|
||||
});
|
||||
}, 300);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -502,7 +514,8 @@ async function displayNotification({
|
||||
ongoing,
|
||||
reply_placeholder_text,
|
||||
reply_button_text,
|
||||
id
|
||||
id,
|
||||
channelId = "default"
|
||||
}: {
|
||||
title?: string;
|
||||
message: string;
|
||||
@@ -513,6 +526,7 @@ async function displayNotification({
|
||||
reply_placeholder_text?: string;
|
||||
reply_button_text?: string;
|
||||
id?: string;
|
||||
channelId?: "silent" | "vibrate" | "urgent" | "default";
|
||||
}) {
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: true
|
||||
@@ -537,7 +551,7 @@ async function displayNotification({
|
||||
ongoing: ongoing,
|
||||
smallIcon: "ic_stat_name",
|
||||
localOnly: true,
|
||||
channelId: await getChannelId("default"),
|
||||
channelId: await getChannelId(channelId),
|
||||
autoCancel: false,
|
||||
pressAction: {
|
||||
id: "default",
|
||||
@@ -884,7 +898,7 @@ function init() {
|
||||
notifee.onForegroundEvent(onEvent);
|
||||
}
|
||||
|
||||
async function pinQuickNote(launch: boolean) {
|
||||
async function pinQuickNote() {
|
||||
useUserStore.setState({
|
||||
disableAppLockRequests: true
|
||||
});
|
||||
@@ -896,14 +910,13 @@ async function pinQuickNote(launch: boolean) {
|
||||
return;
|
||||
}
|
||||
get().then((items) => {
|
||||
const notification = items.filter((n) => n.id === "notesnook_note_input");
|
||||
if (notification && launch) {
|
||||
return;
|
||||
}
|
||||
const notification = items.find((n) => n.id === "notesnook_note_input");
|
||||
if (notification) return;
|
||||
displayNotification({
|
||||
title: strings.quickNoteTitle(),
|
||||
message: strings.quickNoteContent(),
|
||||
ongoing: true,
|
||||
channelId: "silent",
|
||||
actions: ["ReplyInput", strings.hide()],
|
||||
reply_button_text: strings.takeNote(),
|
||||
reply_placeholder_text: strings.quickNotePlaceholder(),
|
||||
@@ -1030,6 +1043,7 @@ async function pinNote(id: string) {
|
||||
subtitle: "",
|
||||
bigText: html,
|
||||
ongoing: true,
|
||||
channelId: "silent",
|
||||
actions: [strings.unpin()],
|
||||
id: note.id
|
||||
});
|
||||
@@ -1040,9 +1054,13 @@ async function pinNote(id: string) {
|
||||
|
||||
async function restorePinnedNotes() {
|
||||
const pinnedNotes = PinnedNotesStorage.get();
|
||||
for (const id of pinnedNotes) {
|
||||
pinNote(id);
|
||||
}
|
||||
Notifications.get().then(() => {
|
||||
for (const id of pinnedNotes) {
|
||||
if (!isNotePinned(id)) {
|
||||
pinNote(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function clearPinnedNotes() {
|
||||
|
||||
@@ -23,6 +23,7 @@ import ShareExtension, {
|
||||
import { getPreviewData } from "@flyerhq/react-native-link-preview";
|
||||
import {
|
||||
formatBytes,
|
||||
getFormattedDate,
|
||||
isFeatureAvailable,
|
||||
useIsFeatureAvailable
|
||||
} from "@notesnook/common";
|
||||
@@ -47,7 +48,6 @@ import {
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import {
|
||||
SafeAreaProvider,
|
||||
SafeAreaView,
|
||||
useSafeAreaInsets
|
||||
} from "react-native-safe-area-context";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
@@ -70,14 +70,20 @@ import { Search } from "./search";
|
||||
import { initDatabase, useShareStore } from "./store";
|
||||
import { isTablet } from "react-native-device-info";
|
||||
import { NotesnookModule } from "../utils/notesnook-module";
|
||||
import { DefaultAppStyles } from "../utils/styles";
|
||||
|
||||
const getLinkPreview = (url: string) => {
|
||||
return getPreviewData(url, 5000);
|
||||
};
|
||||
async function sanitizeHtml(site: string) {
|
||||
async function sanitizeHtml(site: string, title?: string) {
|
||||
try {
|
||||
let html = await fetchHandle.current?.processUrl(site);
|
||||
return html;
|
||||
return (
|
||||
html +
|
||||
"<hr/>" +
|
||||
`<p>Clipped from <a href="${site}">${title || site}</a></p>
|
||||
<p>Date clipped ${getFormattedDate(Date.now())}</p>`
|
||||
);
|
||||
} catch (e) {
|
||||
return "";
|
||||
}
|
||||
@@ -145,6 +151,7 @@ const ShareView = () => {
|
||||
const noteContent = useRef<string>(undefined);
|
||||
const noteTitle = useRef<string>(undefined);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [loadingData, setLoadingData] = useState(true);
|
||||
const [loadingExtension, setLoadingExtension] = useState(true);
|
||||
const fullQualityImages = useIsFeatureAvailable("fullQualityImages");
|
||||
const [rawData, setRawData] = useState<{
|
||||
@@ -167,6 +174,11 @@ const ShareView = () => {
|
||||
globalThis["IS_SHARE_EXTENSION"] = true;
|
||||
const onKeyboardDidShow = (event: KeyboardEvent) => {
|
||||
let height = Dimensions.get("screen").height - event.endCoordinates.screenY;
|
||||
if (height < 100) {
|
||||
setKeyboardHeight(0);
|
||||
keyboardHeightRef.current = 0;
|
||||
return;
|
||||
}
|
||||
keyboardHeightRef.current = height;
|
||||
setKeyboardHeight(height);
|
||||
};
|
||||
@@ -210,7 +222,6 @@ const ShareView = () => {
|
||||
};
|
||||
|
||||
const onLoad = useCallback(() => {
|
||||
console.log(noteContent.current, "current...");
|
||||
eSendEvent(eOnLoadNote + "shareEditor", {
|
||||
id: null,
|
||||
content: {
|
||||
@@ -226,6 +237,7 @@ const ShareView = () => {
|
||||
try {
|
||||
if (noteContent.current) {
|
||||
onLoad();
|
||||
setLoadingData(false);
|
||||
return;
|
||||
}
|
||||
defaultNote.content.data = undefined;
|
||||
@@ -242,6 +254,7 @@ const ShareView = () => {
|
||||
editorRef.current?.focus();
|
||||
}, 300);
|
||||
}
|
||||
setLoadingData(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -298,7 +311,9 @@ const ShareView = () => {
|
||||
});
|
||||
onLoad();
|
||||
setNote({ ...note });
|
||||
setLoadingData(false);
|
||||
} catch (e) {
|
||||
setLoadingData(false);
|
||||
console.error(e);
|
||||
}
|
||||
},
|
||||
@@ -311,6 +326,7 @@ const ShareView = () => {
|
||||
await initDatabase();
|
||||
setLoadingExtension(false);
|
||||
useShareStore.getState().restore();
|
||||
loadData(false);
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(e);
|
||||
}
|
||||
@@ -389,7 +405,7 @@ const ShareView = () => {
|
||||
if (value === 2) {
|
||||
setLoadingPage(true);
|
||||
setTimeout(async () => {
|
||||
let html = await sanitizeHtml(rawData?.value || "");
|
||||
let html = await sanitizeHtml(rawData?.value || "", note.title);
|
||||
noteContent.current = html || "";
|
||||
setLoadingPage(false);
|
||||
onLoad();
|
||||
@@ -448,11 +464,7 @@ const ShareView = () => {
|
||||
overflow: "hidden",
|
||||
paddingTop: insets.top,
|
||||
paddingLeft: insets.left,
|
||||
paddingRight: insets.right,
|
||||
paddingBottom:
|
||||
Platform.OS === "android" && keyboardHeight
|
||||
? keyboardHeight - (insets.bottom || (gesturesEnabled ? 25 : 50))
|
||||
: 0
|
||||
paddingRight: insets.right
|
||||
}}
|
||||
>
|
||||
{loadingPage ? <HtmlLoadingWebViewAgent /> : null}
|
||||
@@ -500,394 +512,414 @@ const ShareView = () => {
|
||||
display: searchMode ? "none" : "flex",
|
||||
borderTopRightRadius: Platform.OS === "ios" ? 10 : 15,
|
||||
borderTopLeftRadius: Platform.OS === "ios" ? 10 : 15,
|
||||
maxHeight: Platform.OS === "android" ? undefined : "100%"
|
||||
maxHeight: Platform.OS === "android" ? undefined : "100%",
|
||||
paddingBottom:
|
||||
Platform.OS === "android" && keyboardHeight
|
||||
? keyboardHeight
|
||||
: insets.bottom
|
||||
}}
|
||||
>
|
||||
<ScrollView>
|
||||
{loadingData ? (
|
||||
<View
|
||||
style={{
|
||||
maxHeight: "100%"
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: 300,
|
||||
gap: DefaultAppStyles.GAP_VERTICAL
|
||||
}}
|
||||
>
|
||||
<ActivityIndicator color={colors.primary.accent} />
|
||||
<Paragraph>Loading clip...</Paragraph>
|
||||
</View>
|
||||
) : (
|
||||
<ScrollView>
|
||||
<View
|
||||
style={{
|
||||
width: "100%"
|
||||
maxHeight: "100%"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
minHeight: 100,
|
||||
backgroundColor: colors.primary.background,
|
||||
overflow: "hidden"
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
justifyContent: "space-between",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
borderBottomWidth: 1,
|
||||
paddingBottom: 12,
|
||||
borderBottomColor: colors.secondary.background,
|
||||
paddingHorizontal: 12,
|
||||
gap: 10
|
||||
minHeight: 100,
|
||||
backgroundColor: colors.primary.background,
|
||||
overflow: "hidden"
|
||||
}}
|
||||
>
|
||||
{appendNoteId ? (
|
||||
<Heading
|
||||
style={{
|
||||
flexShrink: 1,
|
||||
flexGrow: 1,
|
||||
fontFamily: "Inter-SemiBold",
|
||||
fontSize: AppFontSize.lg,
|
||||
paddingBottom: 0,
|
||||
paddingTop: 0
|
||||
}}
|
||||
allowFontScaling={false}
|
||||
>
|
||||
Save note
|
||||
</Heading>
|
||||
) : (
|
||||
<TextInput
|
||||
placeholder="Enter note title"
|
||||
ref={inputRef}
|
||||
allowFontScaling={false}
|
||||
style={{
|
||||
flexShrink: 1,
|
||||
flexGrow: 1,
|
||||
fontFamily: "Inter-SemiBold",
|
||||
fontSize: AppFontSize.lg,
|
||||
paddingBottom: 0,
|
||||
paddingTop: 0,
|
||||
color: colors.primary.heading
|
||||
}}
|
||||
onChangeText={(value) => {
|
||||
noteTitle.current = value;
|
||||
}}
|
||||
defaultValue={noteTitle.current}
|
||||
blurOnSubmit={false}
|
||||
onSubmitEditing={() => {
|
||||
editorRef.current?.focus();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
title="Done"
|
||||
type="accent"
|
||||
loading={loading}
|
||||
onPress={onPress}
|
||||
allowFontScaling={false}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{rawFiles?.length > 0 ? (
|
||||
<View
|
||||
style={{
|
||||
justifyContent: "space-between",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
borderBottomWidth: 1,
|
||||
paddingBottom: 12,
|
||||
borderBottomColor: colors.secondary.background,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 12,
|
||||
backgroundColor: colors.secondary.background
|
||||
gap: 10
|
||||
}}
|
||||
>
|
||||
<Paragraph
|
||||
style={{ marginBottom: 6 }}
|
||||
color={colors.primary.paragraph}
|
||||
{appendNoteId ? (
|
||||
<Heading
|
||||
style={{
|
||||
flexShrink: 1,
|
||||
flexGrow: 1,
|
||||
fontFamily: "Inter-SemiBold",
|
||||
fontSize: AppFontSize.lg,
|
||||
paddingBottom: 0,
|
||||
paddingTop: 0
|
||||
}}
|
||||
allowFontScaling={false}
|
||||
>
|
||||
Save note
|
||||
</Heading>
|
||||
) : (
|
||||
<TextInput
|
||||
placeholder="Enter note title"
|
||||
ref={inputRef}
|
||||
allowFontScaling={false}
|
||||
style={{
|
||||
flexShrink: 1,
|
||||
flexGrow: 1,
|
||||
fontFamily: "Inter-SemiBold",
|
||||
fontSize: AppFontSize.lg,
|
||||
paddingBottom: 0,
|
||||
paddingTop: 0,
|
||||
color: colors.primary.heading
|
||||
}}
|
||||
onChangeText={(value) => {
|
||||
noteTitle.current = value;
|
||||
}}
|
||||
defaultValue={noteTitle.current}
|
||||
blurOnSubmit={false}
|
||||
onSubmitEditing={() => {
|
||||
editorRef.current?.focus();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
title="Done"
|
||||
type="accent"
|
||||
loading={loading}
|
||||
onPress={onPress}
|
||||
allowFontScaling={false}
|
||||
>
|
||||
Attaching {rawFiles.length} file(s):
|
||||
</Paragraph>
|
||||
<ScrollView horizontal>
|
||||
{rawFiles.map((item) =>
|
||||
isImage(item.type) || item.value?.endsWith(".png") ? (
|
||||
<TouchableOpacity
|
||||
onPress={() => onRemoveFile(item)}
|
||||
key={item.name}
|
||||
activeOpacity={0.9}
|
||||
>
|
||||
<Image
|
||||
source={{
|
||||
uri:
|
||||
Platform.OS === "android"
|
||||
? `file://${item.value}`
|
||||
: item.value
|
||||
}}
|
||||
style={{
|
||||
width: 100,
|
||||
height: 100,
|
||||
borderRadius: defaultBorderRadius,
|
||||
backgroundColor: "black",
|
||||
marginRight: 6
|
||||
}}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.9}
|
||||
key={item.name}
|
||||
onPress={() => onRemoveFile(item)}
|
||||
style={{
|
||||
borderRadius: defaultBorderRadius,
|
||||
backgroundColor: colors.secondary.background,
|
||||
flexDirection: "row",
|
||||
borderWidth: 1,
|
||||
borderColor: colors.primary.border,
|
||||
alignItems: "center",
|
||||
paddingVertical: 5,
|
||||
paddingHorizontal: 8,
|
||||
marginRight: 6
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
color={colors.primary.icon}
|
||||
size={15}
|
||||
name="file"
|
||||
/>
|
||||
/>
|
||||
</View>
|
||||
|
||||
<Paragraph
|
||||
size={AppFontSize.xs}
|
||||
color={colors.primary.paragraph}
|
||||
allowFontScaling={false}
|
||||
style={{
|
||||
marginLeft: 4,
|
||||
paddingRight: 8
|
||||
}}
|
||||
>
|
||||
{item.name || item.value.split("/").pop()}
|
||||
{item.size ? `(${formatBytes(item.size)})` : ""}
|
||||
</Paragraph>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
<Paragraph
|
||||
color={colors.secondary.paragraph}
|
||||
size={AppFontSize.xs}
|
||||
allowFontScaling={false}
|
||||
{rawFiles?.length > 0 ? (
|
||||
<View
|
||||
style={{
|
||||
marginTop: 6
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 12,
|
||||
backgroundColor: colors.secondary.background
|
||||
}}
|
||||
>
|
||||
Tap to remove an attachment.
|
||||
</Paragraph>
|
||||
{rawFiles.some((item) => isImage(item.type)) ? (
|
||||
<TouchableOpacity
|
||||
activeOpacity={1}
|
||||
<Paragraph
|
||||
style={{ marginBottom: 6 }}
|
||||
color={colors.primary.paragraph}
|
||||
allowFontScaling={false}
|
||||
>
|
||||
Attaching {rawFiles.length} file(s):
|
||||
</Paragraph>
|
||||
<ScrollView horizontal>
|
||||
{rawFiles.map((item) =>
|
||||
isImage(item.type) || item.value?.endsWith(".png") ? (
|
||||
<TouchableOpacity
|
||||
onPress={() => onRemoveFile(item)}
|
||||
key={item.name}
|
||||
activeOpacity={0.9}
|
||||
>
|
||||
<Image
|
||||
source={{
|
||||
uri:
|
||||
Platform.OS === "android"
|
||||
? `file://${item.value}`
|
||||
: item.value
|
||||
}}
|
||||
style={{
|
||||
width: 100,
|
||||
height: 100,
|
||||
borderRadius: defaultBorderRadius,
|
||||
backgroundColor: "black",
|
||||
marginRight: 6
|
||||
}}
|
||||
resizeMode="cover"
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.9}
|
||||
key={item.name}
|
||||
onPress={() => onRemoveFile(item)}
|
||||
style={{
|
||||
borderRadius: defaultBorderRadius,
|
||||
backgroundColor: colors.secondary.background,
|
||||
flexDirection: "row",
|
||||
borderWidth: 1,
|
||||
borderColor: colors.primary.border,
|
||||
alignItems: "center",
|
||||
paddingVertical: 5,
|
||||
paddingHorizontal: 8,
|
||||
marginRight: 6
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
color={colors.primary.icon}
|
||||
size={15}
|
||||
name="file"
|
||||
/>
|
||||
|
||||
<Paragraph
|
||||
size={AppFontSize.xs}
|
||||
color={colors.primary.paragraph}
|
||||
allowFontScaling={false}
|
||||
style={{
|
||||
marginLeft: 4,
|
||||
paddingRight: 8
|
||||
}}
|
||||
>
|
||||
{item.name || item.value.split("/").pop()}
|
||||
{item.size ? `(${formatBytes(item.size)})` : ""}
|
||||
</Paragraph>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
)}
|
||||
</ScrollView>
|
||||
|
||||
<Paragraph
|
||||
color={colors.secondary.paragraph}
|
||||
size={AppFontSize.xs}
|
||||
allowFontScaling={false}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignSelf: "center",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
marginTop: 6
|
||||
}}
|
||||
onPress={() => {
|
||||
if (fullQualityImages?.isAllowed) {
|
||||
setCompress(!compress);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
size={20}
|
||||
name={
|
||||
compress
|
||||
? "checkbox-marked"
|
||||
: "checkbox-blank-outline"
|
||||
}
|
||||
allowFontScaling={false}
|
||||
color={
|
||||
compress && fullQualityImages?.isAllowed
|
||||
? colors.primary.accent
|
||||
: colors.primary.icon
|
||||
}
|
||||
/>
|
||||
|
||||
<Text
|
||||
Tap to remove an attachment.
|
||||
</Paragraph>
|
||||
{rawFiles.some((item) => isImage(item.type)) ? (
|
||||
<TouchableOpacity
|
||||
activeOpacity={1}
|
||||
style={{
|
||||
flexShrink: 1,
|
||||
marginLeft: 3,
|
||||
fontSize: 12
|
||||
flexDirection: "row",
|
||||
alignSelf: "center",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
marginTop: 6
|
||||
}}
|
||||
onPress={() => {
|
||||
if (fullQualityImages?.isAllowed) {
|
||||
setCompress(!compress);
|
||||
}
|
||||
}}
|
||||
allowFontScaling={false}
|
||||
>
|
||||
Compress image(s) (recommended)
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
) : null}
|
||||
<Icon
|
||||
size={20}
|
||||
name={
|
||||
compress
|
||||
? "checkbox-marked"
|
||||
: "checkbox-blank-outline"
|
||||
}
|
||||
allowFontScaling={false}
|
||||
color={
|
||||
compress && fullQualityImages?.isAllowed
|
||||
? colors.primary.accent
|
||||
: colors.primary.icon
|
||||
}
|
||||
/>
|
||||
|
||||
<Text
|
||||
style={{
|
||||
flexShrink: 1,
|
||||
marginLeft: 3,
|
||||
fontSize: 12
|
||||
}}
|
||||
allowFontScaling={false}
|
||||
>
|
||||
Compress image(s) (recommended)
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
) : null}
|
||||
</View>
|
||||
) : null}
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
height: rawFiles.length > 0 ? 100 : 200,
|
||||
paddingBottom: 15,
|
||||
marginBottom: 10,
|
||||
borderBottomColor: colors.secondary.background,
|
||||
borderBottomWidth: 1
|
||||
}}
|
||||
>
|
||||
<SafeAreaProvider
|
||||
style={{
|
||||
flex: 1,
|
||||
paddingTop: 10,
|
||||
justifyContent: loadingPage ? "center" : undefined,
|
||||
alignItems: loadingPage ? "center" : undefined
|
||||
}}
|
||||
>
|
||||
{!loadingExtension && !loadingPage ? (
|
||||
<>
|
||||
<Editor
|
||||
editorRef={editorRef}
|
||||
onLoad={onLoadEditor}
|
||||
onChange={(html) => {
|
||||
noteContent.current = html;
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{loadingPage ? (
|
||||
<>
|
||||
<ActivityIndicator
|
||||
color={colors.primary.accent}
|
||||
/>
|
||||
<Paragraph>Preparing web clip...</Paragraph>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</SafeAreaProvider>
|
||||
</View>
|
||||
) : null}
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
height: rawFiles.length > 0 ? 100 : 200,
|
||||
paddingBottom: 15,
|
||||
marginBottom: 10,
|
||||
borderBottomColor: colors.secondary.background,
|
||||
borderBottomWidth: 1
|
||||
}}
|
||||
>
|
||||
<SafeAreaProvider
|
||||
|
||||
{appendNoteId ? (
|
||||
<AppendNote
|
||||
id={appendNoteId}
|
||||
onLoad={(title) => {
|
||||
if (!noteTitle.current) {
|
||||
noteTitle.current = title;
|
||||
inputRef.current?.setNativeProps?.({
|
||||
text: noteTitle.current
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
paddingTop: 10,
|
||||
justifyContent: loadingPage ? "center" : undefined,
|
||||
alignItems: loadingPage ? "center" : undefined
|
||||
flexDirection: "row",
|
||||
paddingHorizontal: 12,
|
||||
width: "100%",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{!loadingExtension && !loadingPage ? (
|
||||
<>
|
||||
<Editor
|
||||
editorRef={editorRef}
|
||||
onLoad={onLoadEditor}
|
||||
onChange={(html) => {
|
||||
noteContent.current = html;
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{loadingPage ? (
|
||||
<>
|
||||
<ActivityIndicator color={colors.primary.accent} />
|
||||
<Paragraph>Preparing web clip...</Paragraph>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</SafeAreaProvider>
|
||||
</View>
|
||||
|
||||
{appendNoteId ? (
|
||||
<AppendNote
|
||||
id={appendNoteId}
|
||||
onLoad={(title) => {
|
||||
if (!noteTitle.current) {
|
||||
noteTitle.current = title;
|
||||
inputRef.current?.setNativeProps?.({
|
||||
text: noteTitle.current
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
paddingHorizontal: 12,
|
||||
width: "100%",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<Paragraph
|
||||
style={{
|
||||
marginRight: 10
|
||||
}}
|
||||
allowFontScaling={false}
|
||||
>
|
||||
Clip Mode:
|
||||
</Paragraph>
|
||||
{rawData.value && isURL(rawData.value) ? (
|
||||
<Button
|
||||
type={mode === 2 ? "inverted" : "plain"}
|
||||
icon={mode === 2 ? "radiobox-marked" : "radiobox-blank"}
|
||||
onPress={() => changeMode(2)}
|
||||
title={modes[2].title}
|
||||
height={30}
|
||||
<Paragraph
|
||||
style={{
|
||||
marginRight: 10
|
||||
}}
|
||||
allowFontScaling={false}
|
||||
>
|
||||
Clip Mode:
|
||||
</Paragraph>
|
||||
{rawData.value && isURL(rawData.value) ? (
|
||||
<Button
|
||||
type={mode === 2 ? "inverted" : "plain"}
|
||||
icon={mode === 2 ? "radiobox-marked" : "radiobox-blank"}
|
||||
onPress={() => changeMode(2)}
|
||||
title={modes[2].title}
|
||||
height={30}
|
||||
allowFontScaling={false}
|
||||
style={{
|
||||
paddingHorizontal: 6
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<Button
|
||||
type={mode === 1 ? "inverted" : "plain"}
|
||||
icon={mode === 1 ? "radiobox-marked" : "radiobox-blank"}
|
||||
onPress={() => changeMode(1)}
|
||||
allowFontScaling={false}
|
||||
title={modes[1].title}
|
||||
height={30}
|
||||
style={{
|
||||
paddingHorizontal: 6
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<Button
|
||||
type={mode === 1 ? "inverted" : "plain"}
|
||||
icon={mode === 1 ? "radiobox-marked" : "radiobox-blank"}
|
||||
onPress={() => changeMode(1)}
|
||||
allowFontScaling={false}
|
||||
title={modes[1].title}
|
||||
height={30}
|
||||
style={{
|
||||
paddingHorizontal: 6
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
borderRadius: 10,
|
||||
flexDirection: "column",
|
||||
marginTop: 10,
|
||||
alignSelf: "center",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 12,
|
||||
gap: 10
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
icon="plus"
|
||||
onPress={() => {
|
||||
useShareStore.getState().setAppendNote(null);
|
||||
}}
|
||||
type={!appendNoteId ? "transparent" : "plain"}
|
||||
title="New note"
|
||||
allowFontScaling={false}
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
height: 45,
|
||||
width: "100%",
|
||||
marginRight: 0,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.secondary.background,
|
||||
justifyContent: "flex-start"
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
icon="text-short"
|
||||
onPress={() => {
|
||||
setSearchMode("appendNote");
|
||||
}}
|
||||
allowFontScaling={false}
|
||||
type={appendNoteId ? "transparent" : "plain"}
|
||||
title={`Append to a note`}
|
||||
style={{
|
||||
borderRadius: 10,
|
||||
flexDirection: "column",
|
||||
marginTop: 10,
|
||||
alignSelf: "center",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: 12,
|
||||
height: 45,
|
||||
width: "100%",
|
||||
marginRight: 0,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.secondary.background,
|
||||
justifyContent: "flex-start"
|
||||
gap: 10
|
||||
}}
|
||||
/>
|
||||
|
||||
{!appendNoteId ? (
|
||||
<AddTags
|
||||
>
|
||||
<Button
|
||||
icon="plus"
|
||||
onPress={() => {
|
||||
setSearchMode("selectTags");
|
||||
useShareStore.getState().setAppendNote(null);
|
||||
}}
|
||||
type={!appendNoteId ? "transparent" : "plain"}
|
||||
title="New note"
|
||||
allowFontScaling={false}
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
height: 45,
|
||||
width: "100%",
|
||||
marginRight: 0,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.secondary.background,
|
||||
justifyContent: "flex-start"
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{!appendNoteId ? (
|
||||
<AddNotebooks
|
||||
<Button
|
||||
icon="text-short"
|
||||
onPress={() => {
|
||||
setSearchMode("selectNotebooks");
|
||||
setSearchMode("appendNote");
|
||||
}}
|
||||
allowFontScaling={false}
|
||||
type={appendNoteId ? "transparent" : "plain"}
|
||||
title={`Append to a note`}
|
||||
style={{
|
||||
paddingHorizontal: 12,
|
||||
height: 45,
|
||||
width: "100%",
|
||||
marginRight: 0,
|
||||
borderWidth: 1,
|
||||
borderColor: colors.secondary.background,
|
||||
justifyContent: "flex-start"
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
height: isTablet() ? 150 : Platform.OS === "ios" ? 110 : 0
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
{!appendNoteId ? (
|
||||
<AddTags
|
||||
onPress={() => {
|
||||
setSearchMode("selectTags");
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{!appendNoteId ? (
|
||||
<AddNotebooks
|
||||
onPress={() => {
|
||||
setSearchMode("selectNotebooks");
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
height: isTablet() ? 150 : Platform.OS === "ios" ? 110 : 0
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</ScrollView>
|
||||
)}
|
||||
</WrapperView>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -24,7 +24,9 @@ import { initialWindowMetrics } from "react-native-safe-area-context";
|
||||
import { FileType } from "react-native-scoped-storage";
|
||||
import { create } from "zustand";
|
||||
import { ThemeDark, ThemeLight, ThemeDefinition } from "@notesnook/theme";
|
||||
import { Reminder } from "@notesnook/core";
|
||||
import { DayFormat, WeekFormat, Reminder } from "@notesnook/core";
|
||||
import { db } from "../common/database";
|
||||
import { EDITOR_LINE_HEIGHT } from "../utils/constants";
|
||||
export const HostIds = [
|
||||
"API_HOST",
|
||||
"AUTH_HOST",
|
||||
@@ -99,6 +101,7 @@ export type Settings = {
|
||||
serverUrls?: Record<HostId, string>;
|
||||
defaultSidebarTab: number;
|
||||
checkForUpdates?: boolean;
|
||||
defaultLineHeight: number;
|
||||
};
|
||||
|
||||
type DimensionsType = {
|
||||
@@ -134,9 +137,12 @@ export interface SettingStore {
|
||||
setInsets: (insets: Insets) => void;
|
||||
timeFormat: string;
|
||||
dateFormat: string;
|
||||
dayFormat: DayFormat;
|
||||
weekFormat: WeekFormat;
|
||||
dbPassword?: string;
|
||||
isOldAppLock: () => boolean;
|
||||
initialUrl: string | null;
|
||||
refresh: () => void;
|
||||
}
|
||||
|
||||
const { width, height } = Dimensions.get("window");
|
||||
@@ -199,7 +205,8 @@ export const defaultSettings: SettingStore["settings"] = {
|
||||
backupType: "partial",
|
||||
fullBackupReminder: "never",
|
||||
lastFullBackupDate: 0,
|
||||
checkForUpdates: true
|
||||
checkForUpdates: true,
|
||||
defaultLineHeight: EDITOR_LINE_HEIGHT.DEFAULT
|
||||
};
|
||||
|
||||
export const useSettingStore = create<SettingStore>((set, get) => ({
|
||||
@@ -222,6 +229,8 @@ export const useSettingStore = create<SettingStore>((set, get) => ({
|
||||
setInsets: (insets) => set({ insets }),
|
||||
timeFormat: "12-hour",
|
||||
dateFormat: "DD-MM-YYYY",
|
||||
dayFormat: "short",
|
||||
weekFormat: "Sun",
|
||||
setAppDidEnterBackgroundForAction: (value: boolean) => {
|
||||
set({
|
||||
appDidEnterBackgroundForAction: value
|
||||
@@ -238,5 +247,13 @@ export const useSettingStore = create<SettingStore>((set, get) => ({
|
||||
insets: initialWindowMetrics?.insets
|
||||
? initialWindowMetrics.insets
|
||||
: { top: 0, right: 0, left: 0, bottom: 0 },
|
||||
initialUrl: null
|
||||
initialUrl: null,
|
||||
refresh: () => {
|
||||
set({
|
||||
dayFormat: db.settings.getDayFormat(),
|
||||
timeFormat: db.settings.getTimeFormat(),
|
||||
dateFormat: db.settings?.getTimeFormat(),
|
||||
weekFormat: db.settings.getWeekFormat()
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -122,3 +122,9 @@ export const SUBSCRIPTION_PROVIDER = {
|
||||
icon: "web"
|
||||
}
|
||||
};
|
||||
|
||||
export const EDITOR_LINE_HEIGHT = {
|
||||
DEFAULT: 1.2,
|
||||
MAX: 10,
|
||||
MIN: 1
|
||||
};
|
||||
|
||||
@@ -134,7 +134,7 @@ describe("NOTE TESTS", () => {
|
||||
.run();
|
||||
});
|
||||
|
||||
it.only("Assign colors to a note", async () => {
|
||||
it("Assign colors to a note", async () => {
|
||||
await TestBuilder.create()
|
||||
.prepare()
|
||||
.createNote()
|
||||
|
||||
@@ -21,7 +21,7 @@ import { notesnook } from "../test.ids";
|
||||
import { TestBuilder } from "./utils";
|
||||
|
||||
describe("Sort & filter", () => {
|
||||
it("Sort by date-edited/date-created", async () => {
|
||||
it.only("Sort by date-edited/date-created", async () => {
|
||||
await TestBuilder.create()
|
||||
.prepare()
|
||||
.createNote("Note 1", "Note 1")
|
||||
@@ -34,7 +34,7 @@ describe("Sort & filter", () => {
|
||||
.element(by.web.className("ProseMirror"))
|
||||
.typeText("Edited ", true);
|
||||
})
|
||||
.pressBack(2)
|
||||
.pressBack(1)
|
||||
.waitAndTapById("icon-sort")
|
||||
.wait(500)
|
||||
.waitAndTapByText("Date created")
|
||||
|
||||
@@ -94,7 +94,7 @@ describe("VAULT", () => {
|
||||
.typeTextById(notesnook.ids.dialogs.vault.pwd, "1234")
|
||||
.typeTextById(notesnook.ids.dialogs.vault.changePwd, "2362")
|
||||
.waitAndTapByText("Change")
|
||||
.pressBack(4)
|
||||
.pressBack(3)
|
||||
.addStep(async () => await openLockedNote("2362"))
|
||||
.run();
|
||||
});
|
||||
|
||||
Binary file not shown.
@@ -5,11 +5,11 @@
|
||||
<key>provisioningProfiles</key>
|
||||
<dict>
|
||||
<key>org.streetwriters.notesnook</key>
|
||||
<string>Notesnook App Distribution 2025</string>
|
||||
<string>Notesnook 2026</string>
|
||||
<key>org.streetwriters.notesnook.notewidget</key>
|
||||
<string>Notesnook Widget Distribution 2025</string>
|
||||
<string>Notesnook Note Widget 2026</string>
|
||||
<key>org.streetwriters.notesnook.share</key>
|
||||
<string>Notesnook Share Distribution 2025</string>
|
||||
<string>Notesnook Share 2026</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1029,7 +1029,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2167;
|
||||
CURRENT_PROJECT_VERSION = 2171;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
ENABLE_BITCODE = NO;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1104,7 +1104,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.3.11;
|
||||
MARKETING_VERSION = 3.3.13;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -1135,7 +1135,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 2167;
|
||||
CURRENT_PROJECT_VERSION = 2171;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1210,7 +1210,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.3.11;
|
||||
MARKETING_VERSION = 3.3.13;
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
@@ -1220,7 +1220,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook;
|
||||
PRODUCT_NAME = Notesnook;
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook App Distribution 2025";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook 2026";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Notesnook-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
@@ -1367,7 +1367,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2167;
|
||||
CURRENT_PROJECT_VERSION = 2171;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1379,7 +1379,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.3.11;
|
||||
MARKETING_VERSION = 3.3.13;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
@@ -1410,7 +1410,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2167;
|
||||
CURRENT_PROJECT_VERSION = 2171;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1423,12 +1423,12 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.3.11;
|
||||
MARKETING_VERSION = 3.3.13;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Widget Distribution 2025";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Note Widget 2026";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_COMPILATION_MODE = wholemodule;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
@@ -1453,7 +1453,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2167;
|
||||
CURRENT_PROJECT_VERSION = 2171;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1534,7 +1534,7 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
|
||||
MARKETING_VERSION = 3.3.11;
|
||||
MARKETING_VERSION = 3.3.13;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
@@ -1565,7 +1565,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2167;
|
||||
CURRENT_PROJECT_VERSION = 2171;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
|
||||
@@ -1647,12 +1647,12 @@
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift$(inherited)";
|
||||
MARKETING_VERSION = 3.3.11;
|
||||
MARKETING_VERSION = 3.3.13;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Share Distribution 2025";
|
||||
"PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Notesnook Share 2026";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Make Note/Make Note-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
||||
@@ -45,6 +45,65 @@ PODS:
|
||||
- MMKV (1.3.14):
|
||||
- MMKVCore (~> 1.3.14)
|
||||
- MMKVCore (1.3.14)
|
||||
- NitroCloudUploader (1.0.9):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
- fmt
|
||||
- glog
|
||||
- hermes-engine
|
||||
- NitroModules
|
||||
- RCT-Folly
|
||||
- RCT-Folly/Fabric
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- React-callinvoker
|
||||
- React-Core
|
||||
- React-debug
|
||||
- React-Fabric
|
||||
- React-featureflags
|
||||
- React-graphics
|
||||
- React-ImageManager
|
||||
- React-jsi
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-renderercss
|
||||
- React-rendererdebug
|
||||
- React-utils
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- NitroModules (0.32.0):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
- fmt
|
||||
- glog
|
||||
- hermes-engine
|
||||
- RCT-Folly
|
||||
- RCT-Folly/Fabric
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- React-callinvoker
|
||||
- React-Core
|
||||
- React-debug
|
||||
- React-Fabric
|
||||
- React-featureflags
|
||||
- React-graphics
|
||||
- React-ImageManager
|
||||
- React-jsi
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-renderercss
|
||||
- React-rendererdebug
|
||||
- React-utils
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- pop (1.0.12)
|
||||
- RCT-Folly (2024.11.18.00):
|
||||
- boost
|
||||
@@ -1887,7 +1946,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- react-native-document-picker (11.0.1):
|
||||
- react-native-document-picker (11.0.3):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
@@ -2004,35 +2063,7 @@ PODS:
|
||||
- React
|
||||
- react-native-orientation-locker (1.7.0):
|
||||
- React-Core
|
||||
- react-native-pager-view (6.9.1):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
- fmt
|
||||
- glog
|
||||
- hermes-engine
|
||||
- RCT-Folly
|
||||
- RCT-Folly/Fabric
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- React-Core
|
||||
- React-debug
|
||||
- React-Fabric
|
||||
- React-featureflags
|
||||
- React-graphics
|
||||
- React-ImageManager
|
||||
- React-jsi
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-renderercss
|
||||
- React-rendererdebug
|
||||
- React-utils
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- react-native-pdf (6.7.7):
|
||||
- react-native-pdf (7.0.3):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
@@ -2244,7 +2275,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- react-native-upload (6.29.0):
|
||||
- react-native-upload (6.31.0):
|
||||
- React
|
||||
- react-native-view-shot (4.0.3):
|
||||
- boost
|
||||
@@ -3061,10 +3092,10 @@ PODS:
|
||||
- Yoga
|
||||
- RNKeychain (4.0.5):
|
||||
- React
|
||||
- RNNotifee (7.4.10):
|
||||
- RNNotifee (7.4.11):
|
||||
- React-Core
|
||||
- RNNotifee/NotifeeCore (= 7.4.10)
|
||||
- RNNotifee/NotifeeCore (7.4.10):
|
||||
- RNNotifee/NotifeeCore (= 7.4.11)
|
||||
- RNNotifee/NotifeeCore (7.4.11):
|
||||
- React-Core
|
||||
- RNPrivacySnapshot (1.0.0):
|
||||
- React-Core
|
||||
@@ -3160,7 +3191,7 @@ PODS:
|
||||
- RNWorklets
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- RNScreens (4.18.0):
|
||||
- RNScreens (4.19.0):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
@@ -3187,10 +3218,10 @@ PODS:
|
||||
- ReactCodegen
|
||||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- RNScreens/common (= 4.18.0)
|
||||
- RNScreens/common (= 4.19.0)
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- RNScreens/common (4.18.0):
|
||||
- RNScreens/common (4.19.0):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
@@ -3432,6 +3463,8 @@ DEPENDENCIES:
|
||||
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
||||
- hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
|
||||
- MMKV
|
||||
- NitroCloudUploader (from `../node_modules/react-native-nitro-cloud-uploader`)
|
||||
- NitroModules (from `../node_modules/react-native-nitro-modules`)
|
||||
- RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`)
|
||||
- RCTDeprecation (from `../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)
|
||||
- RCTRequired (from `../node_modules/react-native/Libraries/Required`)
|
||||
@@ -3484,7 +3517,6 @@ DEPENDENCIES:
|
||||
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
|
||||
- react-native-notification-sounds (from `../node_modules/react-native-notification-sounds`)
|
||||
- react-native-orientation-locker (from `../node_modules/react-native-orientation-locker`)
|
||||
- react-native-pager-view (from `../node_modules/react-native-pager-view`)
|
||||
- react-native-pdf (from `../node_modules/react-native-pdf`)
|
||||
- react-native-quick-sqlite (from `../node_modules/react-native-quick-sqlite`)
|
||||
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
|
||||
@@ -3589,6 +3621,10 @@ EXTERNAL SOURCES:
|
||||
hermes-engine:
|
||||
:podspec: "../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec"
|
||||
:tag: hermes-2025-09-01-RNv0.82.0-265ef62ff3eb7289d17e366664ac0da82303e101
|
||||
NitroCloudUploader:
|
||||
:path: "../node_modules/react-native-nitro-cloud-uploader"
|
||||
NitroModules:
|
||||
:path: "../node_modules/react-native-nitro-modules"
|
||||
RCT-Folly:
|
||||
:podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec"
|
||||
RCTDeprecation:
|
||||
@@ -3691,8 +3727,6 @@ EXTERNAL SOURCES:
|
||||
:path: "../node_modules/react-native-notification-sounds"
|
||||
react-native-orientation-locker:
|
||||
:path: "../node_modules/react-native-orientation-locker"
|
||||
react-native-pager-view:
|
||||
:path: "../node_modules/react-native-pager-view"
|
||||
react-native-pdf:
|
||||
:path: "../node_modules/react-native-pdf"
|
||||
react-native-quick-sqlite:
|
||||
@@ -3850,6 +3884,8 @@ SPEC CHECKSUMS:
|
||||
JWTDecode: 2eed97c2fa46ccaf3049a787004eedf0be474a87
|
||||
MMKV: 7b5df6a8bf785c6705cc490c541b9d8a957c4a64
|
||||
MMKVCore: 3f40b896e9ab522452df9df3ce983471aa2449ba
|
||||
NitroCloudUploader: 62489381ed6b472d87c8c6b62629136ebd2342d1
|
||||
NitroModules: f964d2f3f5b392d0c0303737085cc30e375dc43f
|
||||
pop: d582054913807fd11fd50bfe6a539d91c7e1a55a
|
||||
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
|
||||
RCTDeprecation: f17e2ebc07876ca9ab8eb6e4b0a4e4647497ae3a
|
||||
@@ -3891,7 +3927,7 @@ SPEC CHECKSUMS:
|
||||
react-native-blob-util: 7946b7e13acf0da5e849dc2f73fcfebe1d981699
|
||||
react-native-config: 963b5efabc864cf69412e54b5de49b6a23e4af03
|
||||
react-native-date-picker: 4f4f40f6e65798038bb4b1bff47890c2be69c2e6
|
||||
react-native-document-picker: 254467fec90f263dfc4828210daf3e8baa4fcb81
|
||||
react-native-document-picker: d624d3d9bd9311da87f6f7b64aa44f69927d8543
|
||||
react-native-fingerprint-scanner: d5e143a361f3f01858e9c45141ddcabc4fd57055
|
||||
react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba
|
||||
react-native-gzip: 794e0e964a0d9e1dfd1773fee938adb4d4310e26
|
||||
@@ -3903,15 +3939,14 @@ SPEC CHECKSUMS:
|
||||
react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187
|
||||
react-native-notification-sounds: ce106d58df0dd384bccbd2e84fb53accab7cc068
|
||||
react-native-orientation-locker: cc6f357b289a2e0dd2210fea0c52cb8e0727fdaa
|
||||
react-native-pager-view: a0516effb17ca5120ac2113bfd21b91130ad5748
|
||||
react-native-pdf: c586da0d19c14e6d859e62bf957851687fba0f25
|
||||
react-native-pdf: edc236298f13f1609e42d41e45b8b6ea88ed10f9
|
||||
react-native-quick-sqlite: 1ed8d3db1e22a8604d006be69f06053382e93bb0
|
||||
react-native-safe-area-context: c6e2edd1c1da07bdce287fa9d9e60c5f7b514616
|
||||
react-native-screenguard: 9fc3b4ad5b97783fc0832638fae0dce51272c661
|
||||
react-native-share-extension: fdc6aaab51591a2d445df239c446aaa3a99658ec
|
||||
react-native-sodium: 066f76e46c9be13e9260521e3fa994937c4cdab4
|
||||
react-native-theme-switch-animation: 449d6db7a760f55740505e7403ae8061debc9a7e
|
||||
react-native-upload: 1459a4be625dda39e4364d9e7d859477a3000f2a
|
||||
react-native-upload: 6285965d20879bcfcf7953f05eb785ba03a60e40
|
||||
react-native-view-shot: 6c008e58f4720de58370848201c5d4a082c6d4ca
|
||||
react-native-webview: 654f794a7686b47491cf43aa67f7f428bea00eed
|
||||
React-NativeModulesApple: 46690a0fe94ec28fc6fc686ec797b911d251ded0
|
||||
@@ -3959,10 +3994,10 @@ SPEC CHECKSUMS:
|
||||
RNIap: 61f183ac917792fae42b0326b1bef33598c1adf6
|
||||
RNImageCropPicker: 5fd4ceaead64d8c53c787e4e559004f97bc76df7
|
||||
RNKeychain: ffd0513e676445c637410b47249460cbf56bc9cb
|
||||
RNNotifee: 3840cc81add9954a5dec040f96442498f3f95d7b
|
||||
RNNotifee: c4827fa2cec60ab634d62bafdb5cf57cd2999d54
|
||||
RNPrivacySnapshot: ccad3a548338c2f526bb7b1789af3fb0618b7d1d
|
||||
RNReanimated: f1868b36f4b2b52a0ed00062cfda69506f75eaee
|
||||
RNScreens: d821082c6dd1cb397cc0c98b026eeafaa68be479
|
||||
RNScreens: ffbb0296608eb3560de641a711bbdb663ed1f6b4
|
||||
RNSecureRandom: b64d263529492a6897e236a22a2c4249aa1b53dc
|
||||
RNShare: 5d39a36f2d3d9a6c9344bfb3232c8ed607924a35
|
||||
RNSVG: 8c0bbfa480a24b24468f1c76bd852a4aac3178e6
|
||||
|
||||
109
apps/mobile/package-lock.json
generated
109
apps/mobile/package-lock.json
generated
@@ -1,22 +1,22 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.3.10-beta.6",
|
||||
"version": "3.3.13-beta.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.3.10-beta.6",
|
||||
"version": "3.3.13-beta.1",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@ammarahmed/notifee-react-native": "^7.4.10",
|
||||
"@ammarahmed/notifee-react-native": "^7.4.11",
|
||||
"@ammarahmed/react-native-background-fetch": "^4.2.2",
|
||||
"@ammarahmed/react-native-eventsource": "1.1.0",
|
||||
"@ammarahmed/react-native-fingerprint-scanner": "^5.0.1",
|
||||
"@ammarahmed/react-native-share-extension": "^2.9.5",
|
||||
"@ammarahmed/react-native-sodium": "^1.6.8",
|
||||
"@ammarahmed/react-native-upload": "^6.29.0",
|
||||
"@ammarahmed/react-native-upload": "^6.31.0",
|
||||
"@azure/core-asynciterator-polyfill": "^1.0.2",
|
||||
"@bam.tech/react-native-image-resizer": "3.0.11",
|
||||
"@callstack/repack": "~5.2.1",
|
||||
@@ -42,7 +42,7 @@
|
||||
"@react-native-community/datetimepicker": "^8.4.5",
|
||||
"@react-native-community/netinfo": "^11.4.1",
|
||||
"@react-native-community/toolbar-android": "^0.2.1",
|
||||
"@react-native-documents/picker": "^11.0.1",
|
||||
"@react-native-documents/picker": "^11.0.3",
|
||||
"@react-native-masked-view/masked-view": "^0.3.2",
|
||||
"@react-navigation/elements": "^1.3.3",
|
||||
"@react-navigation/native": "^6.0.10",
|
||||
@@ -100,10 +100,11 @@
|
||||
"react-native-mmkv-storage": "^12.0.1",
|
||||
"react-native-modal-datetime-picker": "14.0.0",
|
||||
"react-native-navigation-bar-color": "2.0.2",
|
||||
"react-native-nitro-cloud-uploader": "^1.0.9",
|
||||
"react-native-nitro-modules": "^0.32.0",
|
||||
"react-native-notification-sounds": "0.5.5",
|
||||
"react-native-orientation-locker": "^1.7.0",
|
||||
"react-native-pager-view": "^6.5.1",
|
||||
"react-native-pdf": "6.7.7",
|
||||
"react-native-pdf": "^7.0.3",
|
||||
"react-native-privacy-snapshot": "github:standardnotes/react-native-privacy-snapshot",
|
||||
"react-native-progress": "5.0.0",
|
||||
"react-native-qrcode-svg": "^6.0.6",
|
||||
@@ -113,12 +114,11 @@
|
||||
"react-native-safe-area-context": "^5.6.1",
|
||||
"react-native-scoped-storage": "^1.9.5",
|
||||
"react-native-screenguard": "1.0.0",
|
||||
"react-native-screens": "^4.16.0",
|
||||
"react-native-screens": "^4.19.0",
|
||||
"react-native-securerandom": "^1.0.1",
|
||||
"react-native-share": "^12.0.3",
|
||||
"react-native-svg": "^15.12.0",
|
||||
"react-native-swiper-flatlist": "3.2.2",
|
||||
"react-native-tab-view": "^4.1.3",
|
||||
"react-native-theme-switch-animation": "^0.6.0",
|
||||
"react-native-tooltips": "^1.0.3",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
@@ -331,9 +331,11 @@
|
||||
"colord": "^2.9.3",
|
||||
"detect-indent": "^7.0.1",
|
||||
"entities": "5.0.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"katex": "0.16.11",
|
||||
"linkifyjs": "^4.1.3",
|
||||
"nanoid": "5.0.7",
|
||||
"papaparse": "^5.5.3",
|
||||
"prism-themes": "^1.9.0",
|
||||
"prosemirror-codemark": "^0.4.2",
|
||||
"prosemirror-view": "1.34.2",
|
||||
@@ -341,7 +343,6 @@
|
||||
"react-colorful": "^5.6.1",
|
||||
"redent": "^4.0.0",
|
||||
"refractor": "^4.8.1",
|
||||
"simplebar-react": "^3.3.2",
|
||||
"strip-indent": "^4.0.0",
|
||||
"unfurl.js": "^6.4.0"
|
||||
},
|
||||
@@ -350,7 +351,9 @@
|
||||
"@mdi/js": "7.4.47",
|
||||
"@theme-ui/components": "0.16.1",
|
||||
"@theme-ui/core": "0.16.1",
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/katex": "0.16.7",
|
||||
"@types/papaparse": "^5.5.2",
|
||||
"@types/prismjs": "1.26.4",
|
||||
"@types/react": "18.3.5",
|
||||
"@types/react-color": "^3.0.12",
|
||||
@@ -467,27 +470,29 @@
|
||||
},
|
||||
"../../servers/themes": {
|
||||
"name": "@notesnook/themes-server",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.4",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@notesnook/theme": "file:../../packages/theme",
|
||||
"@orama/orama": "^1.0.8",
|
||||
"@trpc/server": "10.45.2",
|
||||
"async-mutex": "0.5.0",
|
||||
"cloudflare": "^5.2.0",
|
||||
"cors": "^2.8.5",
|
||||
"react": "18.3.1",
|
||||
"util": "^0.12.5",
|
||||
"zod": "3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@notesnook/theme": "file:../../packages/theme",
|
||||
"@types/cors": "^2.8.13",
|
||||
"react": "18.3.1",
|
||||
"esbuild": "0.21.5",
|
||||
"vite-node": "2.1.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@ammarahmed/notifee-react-native": {
|
||||
"version": "7.4.10",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/notifee-react-native/-/notifee-react-native-7.4.10.tgz",
|
||||
"integrity": "sha512-19t1vhIddFneOI7JVmXghunEt5mEmDBY/G1YVMuRNNT/HFQ1EcArJWuAaFAJrhrl/2CBS7xoPGLusKbkbT/JjA==",
|
||||
"version": "7.4.11",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/notifee-react-native/-/notifee-react-native-7.4.11.tgz",
|
||||
"integrity": "sha512-9zKhONgir2FN/sMj/rsKAhDwfxadDuxB6LJILKxpGwzqvcIuG70oCKWmBBG9LJJpSfcVpNTdxBoH9RtNZTZSsQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"react-native": "*"
|
||||
@@ -533,9 +538,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@ammarahmed/react-native-upload": {
|
||||
"version": "6.29.0",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-upload/-/react-native-upload-6.29.0.tgz",
|
||||
"integrity": "sha512-k2FJAVR4Nohx4VhfgHuXzLXQpgEnco/PAv9R080X7oY3O12DTgY0ruDvOFwvQGFW0PDIHlXfLfUiMq2kZc/k1A==",
|
||||
"version": "6.31.0",
|
||||
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-upload/-/react-native-upload-6.31.0.tgz",
|
||||
"integrity": "sha512-GwMDd2IND3nuS4l13pQPP8zA/GJoxQKz0pxCGUoWj65fbPuZPnuUA2Rpf4jZzLlgY62vdjI/TqmX+rPiwwCceQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
@@ -4853,9 +4858,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native-documents/picker": {
|
||||
"version": "11.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-documents/picker/-/picker-11.0.1.tgz",
|
||||
"integrity": "sha512-aUq4MHGO/f8BslCFFx9OXz9NLLmcLkYAXp5PAEVau31v7obItPpb71Fe84bxpGV6gALIvGlGgSm6W9kEyU4toA==",
|
||||
"version": "11.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-documents/picker/-/picker-11.0.3.tgz",
|
||||
"integrity": "sha512-q+vQNT1NtZcFGwsDpTwMKNNcgOEq6foFDGlPQVt8H7KaEjCjC+wTGdXuVSe8JJr2uO9xZR6kJ7aRHReZ4XDRBw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
@@ -17716,6 +17721,30 @@
|
||||
"integrity": "sha512-ZmpLWRocyme1au11e5ZuecMS/UCi57nlzgnioi03Q6ERMbeUOqqbWgNBaNB7SsCeqBV6fZPjo3+A64zEIpzw4w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-native-nitro-cloud-uploader": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/react-native-nitro-cloud-uploader/-/react-native-nitro-cloud-uploader-1.0.9.tgz",
|
||||
"integrity": "sha512-J96JC/yp0DWeyS512zQFYbw2KGiengWs0IAGGoV6NCAystluBJlVO4WqgiCiy/nd3bFidy/+uteC+P5h28j0vw==",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"example"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*",
|
||||
"react-native-nitro-modules": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-nitro-modules": {
|
||||
"version": "0.32.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-nitro-modules/-/react-native-nitro-modules-0.32.0.tgz",
|
||||
"integrity": "sha512-nLDsmi/H9/cLcYxArYxcTDL641JiWnAH3u2LmsFETgyqSEHvukswDTdmX6AHBRIAwXu/iUDppZyjsaRpTG6WMg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-notification-sounds": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native-notification-sounds/-/react-native-notification-sounds-0.5.5.tgz",
|
||||
@@ -17742,20 +17771,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-pager-view": {
|
||||
"version": "6.9.1",
|
||||
"resolved": "https://registry.npmjs.org/react-native-pager-view/-/react-native-pager-view-6.9.1.tgz",
|
||||
"integrity": "sha512-uUT0MMMbNtoSbxe9pRvdJJKEi9snjuJ3fXlZhG8F2vVMOBJVt/AFtqMPUHu9yMflmqOr08PewKzj9EPl/Yj+Gw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-pdf": {
|
||||
"version": "6.7.7",
|
||||
"resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.7.tgz",
|
||||
"integrity": "sha512-D0ga/eyPsVWSPEBm622sGVZLl3gibxPmfm2cxsLcUrZ4WDSGR5HyGmvvWaR/m9wXEyIbD4J6q9qzuG6yObcSXw==",
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-7.0.3.tgz",
|
||||
"integrity": "sha512-zDtF6CGXPAfGptQZqX7LQK3CVQrIGsD+rYuBnMK0sVmd8mrq7ciwmWXINT+d92emMtZ7+PLnx1IQZIdsh0fphA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"crypto-js": "4.2.0",
|
||||
@@ -17900,9 +17919,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-screens": {
|
||||
"version": "4.18.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.18.0.tgz",
|
||||
"integrity": "sha512-mRTLWL7Uc1p/RFNveEIIrhP22oxHduC2ZnLr/2iHwBeYpGXR0rJZ7Bgc0ktxQSHRjWTPT70qc/7yd4r9960PBQ==",
|
||||
"version": "4.19.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.19.0.tgz",
|
||||
"integrity": "sha512-qSDAO3AL5bti0Ri7KZRSVmWlhDr8MV86N5GruiKVQfEL7Zx2nUi3Dl62lqHUAD/LnDvOPuDDsMHCfIpYSv3hPQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-freeze": "^1.0.0",
|
||||
@@ -17958,20 +17977,6 @@
|
||||
"react-native": ">=0.59.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-tab-view": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-tab-view/-/react-native-tab-view-4.2.0.tgz",
|
||||
"integrity": "sha512-TUbh7Yr0tE/99t1pJQLbQ+4/Px67xkT7/r3AhfV+93Q3WoUira0Lx7yuKUP2C118doqxub8NCLERwcqsHr29nQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"use-latest-callback": "^0.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 18.2.0",
|
||||
"react-native": "*",
|
||||
"react-native-pager-view": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-theme-switch-animation": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-theme-switch-animation/-/react-native-theme-switch-animation-0.6.0.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.3.11",
|
||||
"version": "3.3.13",
|
||||
"private": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"scripts": {
|
||||
@@ -26,13 +26,13 @@
|
||||
"react": "19.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ammarahmed/notifee-react-native": "^7.4.10",
|
||||
"@ammarahmed/notifee-react-native": "^7.4.11",
|
||||
"@ammarahmed/react-native-background-fetch": "^4.2.2",
|
||||
"@ammarahmed/react-native-eventsource": "1.1.0",
|
||||
"@ammarahmed/react-native-fingerprint-scanner": "^5.0.1",
|
||||
"@ammarahmed/react-native-share-extension": "^2.9.5",
|
||||
"@ammarahmed/react-native-sodium": "^1.6.8",
|
||||
"@ammarahmed/react-native-upload": "^6.29.0",
|
||||
"@ammarahmed/react-native-upload": "^6.31.0",
|
||||
"@azure/core-asynciterator-polyfill": "^1.0.2",
|
||||
"@bam.tech/react-native-image-resizer": "3.0.11",
|
||||
"@callstack/repack": "~5.2.1",
|
||||
@@ -58,7 +58,7 @@
|
||||
"@react-native-community/datetimepicker": "^8.4.5",
|
||||
"@react-native-community/netinfo": "^11.4.1",
|
||||
"@react-native-community/toolbar-android": "^0.2.1",
|
||||
"@react-native-documents/picker": "^11.0.1",
|
||||
"@react-native-documents/picker": "^11.0.3",
|
||||
"@react-native-masked-view/masked-view": "^0.3.2",
|
||||
"@react-navigation/elements": "^1.3.3",
|
||||
"@react-navigation/native": "^6.0.10",
|
||||
@@ -116,10 +116,11 @@
|
||||
"react-native-mmkv-storage": "^12.0.1",
|
||||
"react-native-modal-datetime-picker": "14.0.0",
|
||||
"react-native-navigation-bar-color": "2.0.2",
|
||||
"react-native-nitro-cloud-uploader": "^1.0.9",
|
||||
"react-native-nitro-modules": "^0.32.0",
|
||||
"react-native-notification-sounds": "0.5.5",
|
||||
"react-native-orientation-locker": "^1.7.0",
|
||||
"react-native-pager-view": "^6.5.1",
|
||||
"react-native-pdf": "6.7.7",
|
||||
"react-native-pdf": "^7.0.3",
|
||||
"react-native-privacy-snapshot": "github:standardnotes/react-native-privacy-snapshot",
|
||||
"react-native-progress": "5.0.0",
|
||||
"react-native-qrcode-svg": "^6.0.6",
|
||||
@@ -129,12 +130,11 @@
|
||||
"react-native-safe-area-context": "^5.6.1",
|
||||
"react-native-scoped-storage": "^1.9.5",
|
||||
"react-native-screenguard": "1.0.0",
|
||||
"react-native-screens": "^4.16.0",
|
||||
"react-native-screens": "^4.19.0",
|
||||
"react-native-securerandom": "^1.0.1",
|
||||
"react-native-share": "^12.0.3",
|
||||
"react-native-svg": "^15.12.0",
|
||||
"react-native-swiper-flatlist": "3.2.2",
|
||||
"react-native-tab-view": "^4.1.3",
|
||||
"react-native-theme-switch-animation": "^0.6.0",
|
||||
"react-native-tooltips": "^1.0.3",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
@@ -208,4 +208,4 @@
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
diff --git a/node_modules/react-native-nitro-cloud-uploader/android/src/main/AndroidManifest.xml b/node_modules/react-native-nitro-cloud-uploader/android/src/main/AndroidManifest.xml
|
||||
index 9ea903a..e023649 100644
|
||||
--- a/node_modules/react-native-nitro-cloud-uploader/android/src/main/AndroidManifest.xml
|
||||
+++ b/node_modules/react-native-nitro-cloud-uploader/android/src/main/AndroidManifest.xml
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<!-- Required for background uploads -->
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
- <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
||||
+ <uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
|
||||
<!-- Keep CPU awake during uploads -->
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
diff --git a/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/NitroCloudUploader.kt b/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/NitroCloudUploader.kt
|
||||
index f93d4dc..f95a537 100644
|
||||
--- a/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/NitroCloudUploader.kt
|
||||
+++ b/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/NitroCloudUploader.kt
|
||||
@@ -388,7 +388,7 @@ class NitroCloudUploader(
|
||||
)
|
||||
|
||||
if (shouldNotify) {
|
||||
- showNotification(uploadId, 100, "Upload complete!", isComplete = true)
|
||||
+// showNotification(uploadId, 100, "Upload complete!", isComplete = true)
|
||||
}
|
||||
|
||||
// ✅ Stop foreground service on success
|
||||
@@ -414,7 +414,7 @@ class NitroCloudUploader(
|
||||
println("❌ Upload failed: ${e.message}")
|
||||
|
||||
if (shouldNotify) {
|
||||
- showNotification(uploadId, -1, "Upload failed", isComplete = true)
|
||||
+// showNotification(uploadId, -1, "Upload failed", isComplete = true)
|
||||
}
|
||||
|
||||
// ✅ Stop foreground service on failure
|
||||
@@ -458,7 +458,7 @@ class NitroCloudUploader(
|
||||
)
|
||||
|
||||
if (shouldNotify) {
|
||||
- showNotification(uploadId, 0, "Starting upload...")
|
||||
+// showNotification(uploadId, 0, "Starting upload...")
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
@@ -672,7 +672,7 @@ class NitroCloudUploader(
|
||||
|
||||
if (showNotification) {
|
||||
val progressPercent = (progress * 100).toInt()
|
||||
- showNotification(uploadId, progressPercent, "Uploading... ${progressPercent}%")
|
||||
+// showNotification(uploadId, progressPercent, "Uploading... ${progressPercent}%")
|
||||
|
||||
// ✅ Update foreground service notification
|
||||
val ctx = appContext // Cache to avoid smart cast issues
|
||||
diff --git a/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/UploadForegroundService.kt b/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/UploadForegroundService.kt
|
||||
index 466c212..e261329 100644
|
||||
--- a/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/UploadForegroundService.kt
|
||||
+++ b/node_modules/react-native-nitro-cloud-uploader/android/src/main/java/com/margelo/nitro/nitroclouduploader/UploadForegroundService.kt
|
||||
@@ -134,7 +134,7 @@ class UploadForegroundService : Service() {
|
||||
startForeground(
|
||||
NOTIFICATION_ID,
|
||||
notification,
|
||||
- ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC
|
||||
+ ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
|
||||
)
|
||||
} else {
|
||||
startForeground(NOTIFICATION_ID, notification)
|
||||
@@ -165,6 +165,7 @@ class UploadForegroundService : Service() {
|
||||
|
||||
isServiceStarted = false
|
||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||
+ notificationManager.cancel(NOTIFICATION_ID);
|
||||
stopSelf()
|
||||
println("✅ Foreground service stopped")
|
||||
}
|
||||
@@ -201,7 +202,7 @@ class UploadForegroundService : Service() {
|
||||
}
|
||||
|
||||
val builder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
|
||||
- .setContentTitle("Cloud Uploader")
|
||||
+ .setContentTitle("Uploading file")
|
||||
.setContentText(message)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_upload)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
@@ -1,72 +0,0 @@
|
||||
diff --git a/node_modules/react-native-pdf/android/.gradle/5.6.1/fileChanges/last-build.bin b/node_modules/react-native-pdf/android/.gradle/5.6.1/fileChanges/last-build.bin
|
||||
deleted file mode 100644
|
||||
index f76dd23..0000000
|
||||
Binary files a/node_modules/react-native-pdf/android/.gradle/5.6.1/fileChanges/last-build.bin and /dev/null differ
|
||||
diff --git a/node_modules/react-native-pdf/android/.gradle/5.6.1/fileHashes/fileHashes.lock b/node_modules/react-native-pdf/android/.gradle/5.6.1/fileHashes/fileHashes.lock
|
||||
deleted file mode 100644
|
||||
index 52d8866..0000000
|
||||
Binary files a/node_modules/react-native-pdf/android/.gradle/5.6.1/fileHashes/fileHashes.lock and /dev/null differ
|
||||
diff --git a/node_modules/react-native-pdf/android/.gradle/5.6.1/gc.properties b/node_modules/react-native-pdf/android/.gradle/5.6.1/gc.properties
|
||||
deleted file mode 100644
|
||||
index e69de29..0000000
|
||||
diff --git a/node_modules/react-native-pdf/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/node_modules/react-native-pdf/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock
|
||||
deleted file mode 100644
|
||||
index 1508dc9..0000000
|
||||
Binary files a/node_modules/react-native-pdf/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock and /dev/null differ
|
||||
diff --git a/node_modules/react-native-pdf/android/.gradle/buildOutputCleanup/cache.properties b/node_modules/react-native-pdf/android/.gradle/buildOutputCleanup/cache.properties
|
||||
deleted file mode 100644
|
||||
index f4a32a7..0000000
|
||||
--- a/node_modules/react-native-pdf/android/.gradle/buildOutputCleanup/cache.properties
|
||||
+++ /dev/null
|
||||
@@ -1,2 +0,0 @@
|
||||
-#Sat Oct 12 21:44:37 CST 2019
|
||||
-gradle.version=5.6.1
|
||||
diff --git a/node_modules/react-native-pdf/android/.gradle/vcs-1/gc.properties b/node_modules/react-native-pdf/android/.gradle/vcs-1/gc.properties
|
||||
deleted file mode 100644
|
||||
index e69de29..0000000
|
||||
diff --git a/node_modules/react-native-pdf/android/build.gradle b/node_modules/react-native-pdf/android/build.gradle
|
||||
index 3dfe1dc..70ef9da 100644
|
||||
--- a/node_modules/react-native-pdf/android/build.gradle
|
||||
+++ b/node_modules/react-native-pdf/android/build.gradle
|
||||
@@ -126,6 +126,6 @@ dependencies {
|
||||
// The repo from zacharee is based on PdfiumAndroidKt, a much newer fork of PdfiumAndroid, with better maintenance and updated native libraries.
|
||||
implementation 'com.github.zacharee:AndroidPdfViewer:4.0.1'
|
||||
// Depend on PdfiumAndroidKt directly so this can be updated independently of AndroidPdfViewer as updates are provided.
|
||||
- implementation 'io.legere:pdfiumandroid:1.0.24'
|
||||
+ implementation 'io.legere:pdfiumandroid:1.0.32'
|
||||
implementation 'com.google.code.gson:gson:2.8.5'
|
||||
}
|
||||
diff --git a/node_modules/react-native-pdf/android/src/main/java/org/wonday/pdf/PdfView.java b/node_modules/react-native-pdf/android/src/main/java/org/wonday/pdf/PdfView.java
|
||||
index 7b7a125..361924b 100644
|
||||
--- a/node_modules/react-native-pdf/android/src/main/java/org/wonday/pdf/PdfView.java
|
||||
+++ b/node_modules/react-native-pdf/android/src/main/java/org/wonday/pdf/PdfView.java
|
||||
@@ -12,6 +12,8 @@ import java.io.File;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
+import android.os.Handler;
|
||||
+import android.os.Looper;
|
||||
import android.util.SizeF;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -105,7 +107,7 @@ public class PdfView extends PDFView implements OnPageChangeListener,OnLoadCompl
|
||||
TopChangeEvent tce = new TopChangeEvent(surfaceId, getId(), event);
|
||||
|
||||
if (dispatcher != null) {
|
||||
- dispatcher.dispatchEvent(tce);
|
||||
+ new Handler(Looper.getMainLooper()).postDelayed(() -> dispatcher.dispatchEvent(tce), 10);
|
||||
}
|
||||
|
||||
// ReactContext reactContext = (ReactContext)this.getContext();
|
||||
diff --git a/node_modules/react-native-pdf/index.js b/node_modules/react-native-pdf/index.js
|
||||
index 56df005..dd14412 100644
|
||||
--- a/node_modules/react-native-pdf/index.js
|
||||
+++ b/node_modules/react-native-pdf/index.js
|
||||
@@ -364,7 +364,6 @@ export default class Pdf extends Component {
|
||||
}
|
||||
|
||||
_onChange = (event) => {
|
||||
-
|
||||
let message = event.nativeEvent.message.split('|');
|
||||
//__DEV__ && console.log("onChange: " + message);
|
||||
if (message.length > 0) {
|
||||
@@ -155,39 +155,8 @@ module.exports = (env) => {
|
||||
type: "javascript/auto"
|
||||
},
|
||||
...Repack.getAssetTransformRules({
|
||||
inline: true
|
||||
}),
|
||||
|
||||
/**
|
||||
* This loader handles all static assets (images, video, audio and others), so that you can
|
||||
* use (reference) them inside your application.
|
||||
*
|
||||
* If you wan to handle specific asset type manually, filter out the extension
|
||||
* from `ASSET_EXTENSIONS`, for example:
|
||||
* ```
|
||||
* Repack.ASSET_EXTENSIONS.filter((ext) => ext !== 'svg')
|
||||
* ```
|
||||
*/
|
||||
{
|
||||
test: Repack.getAssetExtensionsRegExp(
|
||||
Repack.ASSET_EXTENSIONS.filter((ext) => ext !== "svg")
|
||||
),
|
||||
use: {
|
||||
loader: "@callstack/repack/assets-loader"
|
||||
}
|
||||
},
|
||||
{
|
||||
test: /\.svg$/,
|
||||
use: [
|
||||
{
|
||||
loader: "@svgr/webpack",
|
||||
options: {
|
||||
native: true,
|
||||
dimensions: false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
inline: false
|
||||
})
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
|
||||
@@ -116,7 +116,12 @@ const EXTRA_ICON_NAMES = [
|
||||
"notebook-minus",
|
||||
"calendar-blank",
|
||||
"email-newsletter",
|
||||
"cellphone-arrow-down"
|
||||
"cellphone-arrow-down",
|
||||
"format-line-spacing",
|
||||
"calendar-today",
|
||||
"bomb",
|
||||
"bomb-off",
|
||||
"cancel"
|
||||
];
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
|
||||
File diff suppressed because one or more lines are too long
BIN
apps/monograph/app/assets/fonts/Inter-Bold.ttf
Normal file
BIN
apps/monograph/app/assets/fonts/Inter-Bold.ttf
Normal file
Binary file not shown.
BIN
apps/monograph/app/assets/fonts/Inter-Bold.woff2
Normal file
BIN
apps/monograph/app/assets/fonts/Inter-Bold.woff2
Normal file
Binary file not shown.
BIN
apps/monograph/app/assets/fonts/Inter-BoldItalic.ttf
Normal file
BIN
apps/monograph/app/assets/fonts/Inter-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
apps/monograph/app/assets/fonts/Inter-BoldItalic.woff2
Normal file
BIN
apps/monograph/app/assets/fonts/Inter-BoldItalic.woff2
Normal file
Binary file not shown.
BIN
apps/monograph/app/assets/fonts/Inter-Italic.ttf
Normal file
BIN
apps/monograph/app/assets/fonts/Inter-Italic.ttf
Normal file
Binary file not shown.
BIN
apps/monograph/app/assets/fonts/Inter-Italic.woff2
Normal file
BIN
apps/monograph/app/assets/fonts/Inter-Italic.woff2
Normal file
Binary file not shown.
BIN
apps/monograph/app/assets/fonts/Inter-Medium.ttf
Normal file
BIN
apps/monograph/app/assets/fonts/Inter-Medium.ttf
Normal file
Binary file not shown.
BIN
apps/monograph/app/assets/fonts/Inter-Medium.woff2
Normal file
BIN
apps/monograph/app/assets/fonts/Inter-Medium.woff2
Normal file
Binary file not shown.
BIN
apps/monograph/app/assets/fonts/Inter-MediumItalic.ttf
Normal file
BIN
apps/monograph/app/assets/fonts/Inter-MediumItalic.ttf
Normal file
Binary file not shown.
BIN
apps/monograph/app/assets/fonts/Inter-MediumItalic.woff2
Normal file
BIN
apps/monograph/app/assets/fonts/Inter-MediumItalic.woff2
Normal file
Binary file not shown.
BIN
apps/monograph/app/assets/fonts/Inter-Regular.ttf
Normal file
BIN
apps/monograph/app/assets/fonts/Inter-Regular.ttf
Normal file
Binary file not shown.
BIN
apps/monograph/app/assets/fonts/Inter-Regular.woff2
Normal file
BIN
apps/monograph/app/assets/fonts/Inter-Regular.woff2
Normal file
Binary file not shown.
BIN
apps/monograph/app/assets/fonts/Inter-SemiBold.ttf
Normal file
BIN
apps/monograph/app/assets/fonts/Inter-SemiBold.ttf
Normal file
Binary file not shown.
BIN
apps/monograph/app/assets/fonts/Inter-SemiBold.woff2
Normal file
BIN
apps/monograph/app/assets/fonts/Inter-SemiBold.woff2
Normal file
Binary file not shown.
BIN
apps/monograph/app/assets/fonts/Inter-SemiBoldItalic.ttf
Normal file
BIN
apps/monograph/app/assets/fonts/Inter-SemiBoldItalic.ttf
Normal file
Binary file not shown.
BIN
apps/monograph/app/assets/fonts/Inter-SemiBoldItalic.woff2
Normal file
BIN
apps/monograph/app/assets/fonts/Inter-SemiBoldItalic.woff2
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user