mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-08-29 18:19:38 +02:00
Compare commits
154 Commits
fix-file-e
...
web/defaul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7d4fe9127c | ||
|
|
e899e69410 | ||
|
|
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 | ||
|
|
9982d7b7b4 | ||
|
|
22497199c5 | ||
|
|
873b180dab | ||
|
|
ca8e505cd0 | ||
|
|
465e1af838 | ||
|
|
41094c80d5 | ||
|
|
583a68cabe | ||
|
|
9c1a02dbd5 | ||
|
|
637a19ee21 | ||
|
|
896420064f | ||
|
|
b59a3fb5de | ||
|
|
0a7f8631b6 | ||
|
|
c6d471cec9 |
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.7",
|
||||
"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 3090
|
||||
versionName getNpmVersion()
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
@@ -6,7 +6,14 @@ import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.pm.ShortcutManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
@@ -29,6 +36,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
public class RCTNNativeModule extends ReactContextBaseJavaModule {
|
||||
@@ -237,6 +245,221 @@ public class RCTNNativeModule extends ReactContextBaseJavaModule {
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void addShortcut(final String id, final String type, final String title, final String description, final String color, Promise promise) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
promise.reject("UNSUPPORTED", "Pinned launcher shortcuts require Android 8.0 or higher");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
|
||||
if (shortcutManager == null) {
|
||||
promise.reject("ERROR", "ShortcutManager not available");
|
||||
return;
|
||||
}
|
||||
|
||||
String uri = "https://app.notesnook.com/open_" + type + "?id=" + id;
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, android.net.Uri.parse(uri));
|
||||
intent.setPackage(mContext.getPackageName());
|
||||
|
||||
Icon icon = createLetterIcon(type, title, color);
|
||||
ShortcutInfo shortcut = new ShortcutInfo.Builder(mContext, id)
|
||||
.setShortLabel(title)
|
||||
.setLongLabel(description != null && !description.isEmpty() ? description : title)
|
||||
.setIcon(icon)
|
||||
.setCategories(Set.of(type))
|
||||
.setIntent(intent)
|
||||
.build();
|
||||
|
||||
shortcutManager.requestPinShortcut(shortcut, null);
|
||||
promise.resolve(true);
|
||||
} catch (Exception e) {
|
||||
promise.reject("ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void removeShortcut(final String id, Promise promise) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
promise.reject("UNSUPPORTED", "Pinned launcher shortcuts require Android 8.0 or higher");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
|
||||
if (shortcutManager == null) {
|
||||
promise.reject("ERROR", "ShortcutManager not available");
|
||||
return;
|
||||
}
|
||||
|
||||
shortcutManager.disableShortcuts(java.util.Collections.singletonList(id));
|
||||
promise.resolve(true);
|
||||
} catch (Exception e) {
|
||||
promise.reject("ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void updateShortcut(final String id, final String type, final String title, final String description,final String color, Promise promise) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
promise.reject("UNSUPPORTED", "Pinned launcher shortcuts require Android 8.0 or higher");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
|
||||
if (shortcutManager == null) {
|
||||
promise.reject("ERROR", "ShortcutManager not available");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get existing shortcut to preserve icon and intent
|
||||
List<ShortcutInfo> shortcuts = shortcutManager.getPinnedShortcuts();
|
||||
ShortcutInfo existingShortcut = null;
|
||||
for (ShortcutInfo s : shortcuts) {
|
||||
if (s.getId().equals(id)) {
|
||||
existingShortcut = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingShortcut == null) {
|
||||
return;
|
||||
}
|
||||
Icon icon = createLetterIcon(type, title, color);
|
||||
ShortcutInfo updatedShortcut = new ShortcutInfo.Builder(mContext, id)
|
||||
.setShortLabel(title)
|
||||
.setIcon(icon)
|
||||
.setLongLabel(description != null && !description.isEmpty() ? description : title)
|
||||
.setIntent(existingShortcut.getIntent())
|
||||
.build();
|
||||
|
||||
shortcutManager.updateShortcuts(java.util.Collections.singletonList(updatedShortcut));
|
||||
promise.resolve(true);
|
||||
} catch (Exception e) {
|
||||
promise.reject("ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void removeAllShortcuts(Promise promise) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
promise.reject("UNSUPPORTED", "Pinned launcher shortcuts require Android 8.0 or higher");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
|
||||
if (shortcutManager == null) {
|
||||
promise.reject("ERROR", "ShortcutManager not available");
|
||||
return;
|
||||
}
|
||||
|
||||
List<ShortcutInfo> pinnedShortcuts = shortcutManager.getPinnedShortcuts();
|
||||
if (!pinnedShortcuts.isEmpty()) {
|
||||
List<String> ids = new ArrayList<>();
|
||||
for (ShortcutInfo shortcut : pinnedShortcuts) {
|
||||
ids.add(shortcut.getId());
|
||||
}
|
||||
shortcutManager.disableShortcuts(ids);
|
||||
}
|
||||
promise.resolve(true);
|
||||
} catch (Exception e) {
|
||||
promise.reject("ERROR", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void getAllShortcuts(Promise promise) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
return;
|
||||
}
|
||||
ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
|
||||
WritableArray shortcuts = Arguments.createArray();
|
||||
List<ShortcutInfo> infos = shortcutManager.getPinnedShortcuts();
|
||||
|
||||
for (ShortcutInfo info: infos) {
|
||||
WritableMap data = Arguments.createMap();
|
||||
data.putString("id", info.getId());
|
||||
if (info.getShortLabel() != null) {
|
||||
data.putString("title", info.getShortLabel().toString());
|
||||
}
|
||||
|
||||
if (info.getLongLabel() != null) {
|
||||
data.putString("description", info.getLongLabel().toString());
|
||||
}
|
||||
|
||||
|
||||
if (!Objects.requireNonNull(info.getCategories()).isEmpty()) {
|
||||
if (info.getCategories().contains("note")) {
|
||||
data.putString("type", "note");
|
||||
} else if (info.getCategories().contains("notebook")) {
|
||||
data.putString("type", "notebook");
|
||||
} else if (info.getCategories().contains("tag")) {
|
||||
data.putString("type", "tag");
|
||||
} else if (info.getCategories().contains("color")) {
|
||||
data.putString("type", "color");
|
||||
}
|
||||
}
|
||||
shortcuts.pushMap(data);
|
||||
}
|
||||
promise.resolve(shortcuts);
|
||||
}
|
||||
|
||||
private Icon createLetterIcon(String type, String title, String colorCode) {
|
||||
|
||||
String letter = type.contains("tag") ? "#" : title != null && !title.isEmpty()
|
||||
? title.substring(0, 1).toUpperCase()
|
||||
: "?";
|
||||
int color = type.equals("color") ? Color.parseColor(colorCode) : getColorForLetter(letter);
|
||||
|
||||
if (type.equals("notebook")) return Icon.createWithResource(mContext, R.drawable.ic_notebook);
|
||||
// Use a larger canvas and fill it completely to avoid white borders from launcher masking.
|
||||
int iconSize = 256;
|
||||
Bitmap bitmap = Bitmap.createBitmap(iconSize, iconSize, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(bitmap);
|
||||
|
||||
if (type.equals("color")) {
|
||||
Paint backgroundPaint = new Paint();
|
||||
backgroundPaint.setColor(color);
|
||||
backgroundPaint.setAntiAlias(true);
|
||||
canvas.drawCircle(iconSize / 2, iconSize / 2, iconSize/2, backgroundPaint);
|
||||
} else {
|
||||
Paint textPaint = new Paint();
|
||||
textPaint.setColor(color);
|
||||
textPaint.setTextSize(130);
|
||||
textPaint.setAntiAlias(true);
|
||||
textPaint.setTextAlign(Paint.Align.CENTER);
|
||||
textPaint.setTypeface(android.graphics.Typeface.create(android.graphics.Typeface.DEFAULT, android.graphics.Typeface.BOLD));
|
||||
|
||||
float x = iconSize / 2f;
|
||||
float y = (iconSize / 2f) - ((textPaint.descent() + textPaint.ascent()) / 2f);
|
||||
|
||||
canvas.drawText(letter, x, y, textPaint);
|
||||
}
|
||||
|
||||
return Icon.createWithBitmap(bitmap);
|
||||
}
|
||||
|
||||
private int getColorForLetter(String letter) {
|
||||
int[] colors = {
|
||||
0xFF1976D2, // Blue
|
||||
0xFFD32F2F, // Red
|
||||
0xFF388E3C, // Green
|
||||
0xFFF57C00, // Orange
|
||||
0xFF7B1FA2, // Purple
|
||||
0xFF0097A7, // Cyan
|
||||
0xFFC2185B, // Pink
|
||||
0xFF455A64, // Blue Grey
|
||||
0xFF6A1B9A, // Deep Purple
|
||||
0xFF00796B, // Teal
|
||||
0xFF512DA8, // Indigo
|
||||
0xFF1565C0 // Dark Blue
|
||||
};
|
||||
|
||||
int hash = Math.abs(letter.hashCode());
|
||||
return colors[hash % colors.length];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="#008837"
|
||||
android:alpha="1">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M240,880Q207,880 183.5,856.5Q160,833 160,800L160,160Q160,127 183.5,103.5Q207,80 240,80L720,80Q753,80 776.5,103.5Q800,127 800,160L800,800Q800,833 776.5,856.5Q753,880 720,880L240,880ZM240,800L720,800Q720,800 720,800Q720,800 720,800L720,160Q720,160 720,160Q720,160 720,160L640,160L640,440L540,380L440,440L440,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800ZM240,800Q240,800 240,800Q240,800 240,800L240,160Q240,160 240,160Q240,160 240,160L240,160Q240,160 240,160Q240,160 240,160L240,800Q240,800 240,800Q240,800 240,800ZM440,440L540,380L640,440L640,440L540,380L440,440Z"/>
|
||||
</vector>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 284 B |
Binary file not shown.
|
After Width: | Height: | Size: 211 B |
Binary file not shown.
|
After Width: | Height: | Size: 440 B |
Binary file not shown.
|
After Width: | Height: | Size: 807 B |
@@ -1,6 +1,11 @@
|
||||
- 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 title and date clipped to web clips
|
||||
- Configure Week to start from Sunday or Monday
|
||||
- Change Note's creation date from properties
|
||||
- Set expiry date for notes for auto-deletion
|
||||
- Fix tag and notebook properties cannot be accessed from shortcuts
|
||||
- Fix editor features remain locked after login
|
||||
- Temporarily disable password changing and forgot password features
|
||||
- Note history now includes note title changes
|
||||
- Other minor bug fixes and improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
|
||||
@@ -24,7 +24,7 @@ import {
|
||||
useThemeEngineStore
|
||||
} from "@notesnook/theme";
|
||||
import React, { useEffect } from "react";
|
||||
import { Appearance, I18nManager, StatusBar } from "react-native";
|
||||
import { Appearance, I18nManager, Linking, StatusBar } from "react-native";
|
||||
import "react-native-gesture-handler";
|
||||
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
||||
import { SafeAreaProvider } from "react-native-safe-area-context";
|
||||
@@ -43,7 +43,7 @@ import { changeSystemBarColors, useThemeStore } from "./stores/use-theme-store";
|
||||
import { useUserStore } from "./stores/use-user-store";
|
||||
import RNBootSplash from "react-native-bootsplash";
|
||||
import AppLocked from "./components/app-lock";
|
||||
|
||||
import { useSettingStore } from "./stores/use-setting-store";
|
||||
I18nManager.allowRTL(false);
|
||||
I18nManager.forceRTL(false);
|
||||
I18nManager.swapLeftAndRightInRTL(false);
|
||||
@@ -54,13 +54,18 @@ if (appLockEnabled || appLockMode !== "none") {
|
||||
RNBootSplash.hide({
|
||||
fade: true
|
||||
});
|
||||
Linking.getInitialURL().then((url) => {
|
||||
useSettingStore.setState({
|
||||
initialUrl: url
|
||||
});
|
||||
});
|
||||
const App = (props: { configureMode: "note-preview" }) => {
|
||||
useAppEvents();
|
||||
//@ts-ignore
|
||||
globalThis["IS_MAIN_APP_RUNNING"] = true;
|
||||
useEffect(() => {
|
||||
changeSystemBarColors();
|
||||
SettingsService.onFirstLaunch();
|
||||
changeSystemBarColors();
|
||||
setTimeout(async () => {
|
||||
await Notifications.get();
|
||||
if (SettingsService.get().notifNotes) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
@@ -50,7 +50,7 @@ export const Header = ({
|
||||
onLeftMenuButtonPress?: () => void;
|
||||
renderedInRoute?: RouteName;
|
||||
id?: string;
|
||||
title: string;
|
||||
title?: string;
|
||||
canGoBack?: boolean;
|
||||
onPressDefaultRightButton?: () => void;
|
||||
hasSearch?: boolean;
|
||||
@@ -114,7 +114,16 @@ export const Header = ({
|
||||
onLeftButtonPress={onLeftMenuButtonPress}
|
||||
/>
|
||||
|
||||
{hasSearch ? (
|
||||
{!title ? (
|
||||
<View
|
||||
style={{
|
||||
width: 100,
|
||||
backgroundColor: colors.primary.hover,
|
||||
height: 10,
|
||||
borderRadius: 100
|
||||
}}
|
||||
/>
|
||||
) : hasSearch ? (
|
||||
<Paragraph>
|
||||
{selectionMode
|
||||
? `${selectedItemsList.length} selected`
|
||||
|
||||
@@ -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"
|
||||
@@ -91,15 +113,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 +137,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 +152,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
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
ToastManager.error(
|
||||
e,
|
||||
e as Error,
|
||||
"Failed to load history preview",
|
||||
"local"
|
||||
);
|
||||
@@ -153,7 +190,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",
|
||||
@@ -57,6 +58,8 @@ const BOTTOM_BAR_ITEMS: ActionId[] = [
|
||||
"export",
|
||||
"copy-link",
|
||||
"duplicate",
|
||||
"launcher-shortcut",
|
||||
"expiry-date",
|
||||
"trash"
|
||||
];
|
||||
|
||||
@@ -76,6 +79,7 @@ const COLUMN_BAR_ITEMS: ActionId[] = [
|
||||
"reorder",
|
||||
"rename-color",
|
||||
"rename-tag",
|
||||
"launcher-shortcut",
|
||||
"restore",
|
||||
"trash",
|
||||
"delete"
|
||||
@@ -170,15 +174,15 @@ export const Items = ({
|
||||
DDS.isTab
|
||||
? AppFontSize.xxl
|
||||
: shouldShrink
|
||||
? AppFontSize.xxl
|
||||
: AppFontSize.lg
|
||||
? AppFontSize.xxl
|
||||
: AppFontSize.lg
|
||||
}
|
||||
color={
|
||||
item.checked
|
||||
? item.activeColor || colors.primary.accent
|
||||
: item.id.match(/(delete|trash)/g)
|
||||
? colors.error.icon
|
||||
: colors.secondary.icon
|
||||
? colors.error.icon
|
||||
: colors.secondary.icon
|
||||
}
|
||||
/>
|
||||
</Pressable>
|
||||
@@ -209,8 +213,8 @@ export const Items = ({
|
||||
text: item.checked
|
||||
? item.activeColor || colors.primary.accent
|
||||
: item.id === "delete" || item.id === "trash"
|
||||
? colors.error.paragraph
|
||||
: colors.primary.paragraph
|
||||
? colors.error.paragraph
|
||||
: colors.primary.paragraph
|
||||
}}
|
||||
testID={"icon-" + item.id}
|
||||
onPress={item.onPress}
|
||||
@@ -274,8 +278,8 @@ export const Items = ({
|
||||
item.checked
|
||||
? item.activeColor || colors.primary.accent
|
||||
: item.id === "delete" || item.id === "trash"
|
||||
? colors.error.icon
|
||||
: colors.secondary.icon
|
||||
? colors.error.icon
|
||||
: colors.secondary.icon
|
||||
}
|
||||
/>
|
||||
|
||||
|
||||
@@ -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,12 +17,14 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/* eslint-disable no-inner-declarations */
|
||||
import { useAreFeaturesAvailable } from "@notesnook/common";
|
||||
import { isFeatureAvailable, useAreFeaturesAvailable } from "@notesnook/common";
|
||||
import {
|
||||
Color,
|
||||
createInternalLink,
|
||||
Item,
|
||||
ItemReference,
|
||||
Note,
|
||||
Notebook,
|
||||
VAULT_ERRORS
|
||||
} from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
@@ -30,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";
|
||||
@@ -65,11 +67,13 @@ 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"
|
||||
@@ -114,7 +118,9 @@ export type ActionId =
|
||||
| "remove-from-notebook"
|
||||
| "trash"
|
||||
| "default-homepage"
|
||||
| "default-tag";
|
||||
| "default-tag"
|
||||
| "launcher-shortcut"
|
||||
| "expiry-date";
|
||||
|
||||
export type Action = {
|
||||
id: ActionId;
|
||||
@@ -130,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) {
|
||||
@@ -158,10 +189,12 @@ export const useActions = ({
|
||||
"shortcuts",
|
||||
"notebooks",
|
||||
"customizableSidebar",
|
||||
"customHomepage"
|
||||
"customHomepage",
|
||||
"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)
|
||||
@@ -297,7 +330,6 @@ export const useActions = ({
|
||||
id: item.id,
|
||||
title: value
|
||||
});
|
||||
|
||||
eSendEvent(Navigation.routeNames.TaggedNotes);
|
||||
InteractionManager.runAfterInteractions(() => {
|
||||
useTagStore.getState().refresh();
|
||||
@@ -435,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") {
|
||||
@@ -706,26 +713,6 @@ export const useActions = ({
|
||||
type: item.type
|
||||
}
|
||||
);
|
||||
|
||||
resetStoredState(
|
||||
"app-home-navigtion-key",
|
||||
isHomepage
|
||||
? undefined
|
||||
: {
|
||||
name:
|
||||
item.type === "notebook"
|
||||
? "Notebook"
|
||||
: item.type === "tag"
|
||||
? "TaggedNotes"
|
||||
: item.type === "color"
|
||||
? "ColorNotes"
|
||||
: undefined,
|
||||
params: {
|
||||
item: item,
|
||||
id: item.id
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -965,7 +952,7 @@ export const useActions = ({
|
||||
copyNote: true,
|
||||
novault: true,
|
||||
locked: true,
|
||||
item: item,
|
||||
item: item as Note,
|
||||
title: strings.copyNote()
|
||||
});
|
||||
} else {
|
||||
@@ -1177,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);
|
||||
}}
|
||||
/>
|
||||
)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1207,5 +1233,44 @@ export const useActions = ({
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
Platform.OS === "android" &&
|
||||
(item.type === "tag" ||
|
||||
item.type === "note" ||
|
||||
item.type === "notebook" ||
|
||||
item.type === "color")
|
||||
) {
|
||||
actions.push({
|
||||
id: "launcher-shortcut",
|
||||
title: strings.addToHome(),
|
||||
icon: "cellphone-arrow-down",
|
||||
locked: !features?.androidLauncherShortcuts.isAllowed,
|
||||
onPress: async () => {
|
||||
if (features && !features?.androidLauncherShortcuts.isAllowed) {
|
||||
ToastManager.show({
|
||||
message: features?.androidLauncherShortcuts.error,
|
||||
type: "info",
|
||||
actionText: strings.upgrade(),
|
||||
context: "local",
|
||||
func: () => {
|
||||
PaywallSheet.present(features?.androidLauncherShortcuts);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await NotesnookModule.addShortcut(
|
||||
item.id,
|
||||
item.type,
|
||||
item.title,
|
||||
(item as Note).headline || (item as Notebook).description || "",
|
||||
(item as Color).colorCode
|
||||
);
|
||||
} catch (e) {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return actions;
|
||||
};
|
||||
|
||||
@@ -85,7 +85,7 @@ import Notifications from "../services/notifications";
|
||||
import PremiumService from "../services/premium";
|
||||
import SettingsService from "../services/settings";
|
||||
import Sync from "../services/sync";
|
||||
import { clearAllStores, initAfterSync, initialize } from "../stores";
|
||||
import { clearAllStores, initAfterSync } from "../stores";
|
||||
import { refreshAllStores } from "../stores/create-db-collection-store";
|
||||
import { useAttachmentStore } from "../stores/use-attachment-store";
|
||||
import { useMessageStore } from "../stores/use-message-store";
|
||||
@@ -108,6 +108,8 @@ 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();
|
||||
@@ -160,7 +162,10 @@ const onUserSessionExpired = async () => {
|
||||
eSendEvent(eLoginSessionExpired);
|
||||
};
|
||||
|
||||
const onAppOpenedFromURL = async (event: { url: string }) => {
|
||||
const onAppOpenedFromURL = async (event: {
|
||||
url: string;
|
||||
isInitialUrl?: boolean;
|
||||
}) => {
|
||||
const url = event.url;
|
||||
|
||||
try {
|
||||
@@ -172,7 +177,7 @@ const onAppOpenedFromURL = async (event: { url: string }) => {
|
||||
eSendEvent(eOnLoadNote, { newNote: true });
|
||||
fluidTabsRef.current?.goToPage("editor", false);
|
||||
return;
|
||||
} else if (url.startsWith("https://app.notesnook.com/open_note")) {
|
||||
} else if (url.startsWith("https://app.notesnook.com/open_note?")) {
|
||||
const id = new URL(url).searchParams.get("id");
|
||||
if (id) {
|
||||
const note = await db.notes.note(id);
|
||||
@@ -184,6 +189,53 @@ const onAppOpenedFromURL = async (event: { url: string }) => {
|
||||
fluidTabsRef.current?.goToPage("editor", false);
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
url.startsWith("https://app.notesnook.com/open_notebook?") &&
|
||||
!event.isInitialUrl
|
||||
) {
|
||||
const id = new URL(url).searchParams.get("id");
|
||||
if (id) {
|
||||
const notebook = await db.notebooks.notebook(id);
|
||||
if (notebook) {
|
||||
Navigation.navigate("Notebook", {
|
||||
id: notebook.id,
|
||||
canGoBack: true,
|
||||
item: notebook
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
url.startsWith("https://app.notesnook.com/open_tag?") &&
|
||||
!event.isInitialUrl
|
||||
) {
|
||||
const id = new URL(url).searchParams.get("id");
|
||||
if (id) {
|
||||
const tag = await db.tags.tag(id);
|
||||
if (tag) {
|
||||
Navigation.navigate("TaggedNotes", {
|
||||
type: "tag",
|
||||
id: tag.id,
|
||||
item: tag,
|
||||
canGoBack: true
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
url.startsWith("https://app.notesnook.com/open_color?") &&
|
||||
!event.isInitialUrl
|
||||
) {
|
||||
const id = new URL(url).searchParams.get("id");
|
||||
if (id) {
|
||||
const color = await db.colors.color(id);
|
||||
if (color) {
|
||||
Navigation.navigate("ColoredNotes", {
|
||||
type: "color",
|
||||
id: color.id,
|
||||
item: color,
|
||||
canGoBack: true
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (url.startsWith("https://app.notesnook.com/open_reminder")) {
|
||||
const id = new URL(url).searchParams.get("id");
|
||||
if (id) {
|
||||
@@ -436,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) {
|
||||
@@ -455,16 +529,21 @@ const initializeDatabase = async (password?: string) => {
|
||||
if (db.isInitialized) {
|
||||
await setAppMessage();
|
||||
useSettingStore.getState().setAppLoading(false);
|
||||
useSettingStore.getState().refresh();
|
||||
Notifications.setupReminders(true);
|
||||
DatabaseLogger.info("Database initialized");
|
||||
Notifications.restorePinnedNotes();
|
||||
expiringNotesTimer();
|
||||
deleteDCacheFiles();
|
||||
}
|
||||
Walkthrough.init();
|
||||
};
|
||||
|
||||
export const useAppEvents = () => {
|
||||
const isAppLoading = useSettingStore((state) => state.isAppLoading);
|
||||
const [isAppLoading, initialUrl] = useSettingStore((state) => [
|
||||
state.isAppLoading,
|
||||
state.initialUrl
|
||||
]);
|
||||
const [setLastSynced, setUser, appLocked, syncing] = useUserStore((state) => [
|
||||
state.setLastSynced,
|
||||
state.setUser,
|
||||
@@ -509,6 +588,15 @@ export const useAppEvents = () => {
|
||||
};
|
||||
}, [isAppLoading, onSyncComplete]);
|
||||
|
||||
useEffect(() => {
|
||||
if (initialUrl) {
|
||||
onAppOpenedFromURL({
|
||||
url: initialUrl!,
|
||||
isInitialUrl: true
|
||||
});
|
||||
}
|
||||
}, [initialUrl]);
|
||||
|
||||
const subscribeToPurchaseListeners = useCallback(async () => {
|
||||
if (Platform.OS === "android") {
|
||||
try {
|
||||
@@ -637,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
|
||||
),
|
||||
@@ -724,6 +812,7 @@ export const useAppEvents = () => {
|
||||
if (state === "active") {
|
||||
notifee.setBadgeCount(0);
|
||||
updateStatusBarColor();
|
||||
expiringNotesTimer();
|
||||
checkAutoBackup();
|
||||
Sync.run("global", false, "full");
|
||||
reconnectSSE();
|
||||
@@ -774,23 +863,10 @@ export const useAppEvents = () => {
|
||||
}
|
||||
};
|
||||
|
||||
if (!refValues.current.initialUrl) {
|
||||
Linking.getInitialURL().then((url) => {
|
||||
if (url) {
|
||||
refValues.current.initialUrl = url;
|
||||
}
|
||||
});
|
||||
}
|
||||
let sub: NativeEventSubscription;
|
||||
if (!isAppLoading && !appLocked) {
|
||||
setTimeout(() => {
|
||||
sub = AppState.addEventListener("change", onAppStateChanged);
|
||||
if (refValues.current.initialUrl) {
|
||||
onAppOpenedFromURL({
|
||||
url: refValues.current.initialUrl!
|
||||
});
|
||||
refValues.current.initialUrl = undefined;
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
refValues.current.removeInternetStateListener = NetInfo.addEventListener(
|
||||
|
||||
@@ -31,8 +31,6 @@ import { useSettingStore } from "../stores/use-setting-store";
|
||||
import { rootNavigatorRef } from "../utils/global-refs";
|
||||
import Navigation from "../services/navigation";
|
||||
import { isFeatureAvailable } from "@notesnook/common";
|
||||
import { useStoredValue } from "../hooks/use-stored-state";
|
||||
import { db } from "../common/database";
|
||||
|
||||
const RootStack = createNativeStackNavigator();
|
||||
const AppStack = createNativeStackNavigator();
|
||||
@@ -58,105 +56,117 @@ const AppNavigation = React.memo(
|
||||
() => {
|
||||
const { colors } = useThemeColors();
|
||||
const homepageV2 = useSettingStore((state) => state.settings.homepageV2);
|
||||
const home = useStoredValue(
|
||||
"app-home-navigtion-key",
|
||||
!homepageV2 || homepageV2.id === DEFAULT_HOME.name
|
||||
? DEFAULT_HOME
|
||||
: undefined
|
||||
);
|
||||
const loading = useSettingStore((state) => state.isAppLoading);
|
||||
const [home, setHome] = React.useState<
|
||||
{ name: string; params: any } | undefined
|
||||
>(undefined);
|
||||
|
||||
if (!home.value && !homepageV2) {
|
||||
home.value = DEFAULT_HOME;
|
||||
}
|
||||
React.useEffect(() => {
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
setHome(DEFAULT_HOME);
|
||||
}
|
||||
}
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!homepageV2 || loading) return;
|
||||
(async () => {
|
||||
isFeatureAvailable("customHomepage").then((value) => {
|
||||
if (!value.isAllowed) {
|
||||
home.value = DEFAULT_HOME;
|
||||
SettingsService.setProperty("homepageV2", undefined);
|
||||
}
|
||||
});
|
||||
|
||||
if (!home.value) {
|
||||
switch (homepageV2.type) {
|
||||
case "notebook":
|
||||
{
|
||||
const notebook = await db.notebooks.notebook(homepageV2.id);
|
||||
if (notebook) {
|
||||
home.value = {
|
||||
name: "Notebook",
|
||||
params: {
|
||||
item: notebook,
|
||||
id: notebook.id,
|
||||
title: notebook.title
|
||||
}
|
||||
};
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "color": {
|
||||
const color = await db.colors.color(homepageV2.id);
|
||||
if (color) {
|
||||
home.value = {
|
||||
name: "ColoredNotes",
|
||||
params: {
|
||||
item: color,
|
||||
id: color.id,
|
||||
title: color.title
|
||||
}
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "tag": {
|
||||
const tag = await db.tags.tag(homepageV2.id);
|
||||
if (tag) {
|
||||
home.value = {
|
||||
name: "TaggedNotes",
|
||||
params: {
|
||||
item: tag,
|
||||
id: tag.id,
|
||||
title: tag.title
|
||||
}
|
||||
};
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "default":
|
||||
{
|
||||
home.value = DEFAULT_HOME;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
home.value = undefined;
|
||||
setTimeout(() => {
|
||||
home.value = DEFAULT_HOME;
|
||||
});
|
||||
isFeatureAvailable("customHomepage").then((value) => {
|
||||
if (!value.isAllowed) {
|
||||
SettingsService.setProperty("homepageV2", undefined);
|
||||
}
|
||||
})();
|
||||
}, [homepageV2, loading, home.value]);
|
||||
});
|
||||
}, [homepageV2, loading]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!home) return;
|
||||
useNavigationStore.getState().update(home?.name as keyof RouteParams);
|
||||
useNavigationStore
|
||||
.getState()
|
||||
.update(home.value?.name as keyof RouteParams);
|
||||
useNavigationStore
|
||||
.getState()
|
||||
.setFocusedRouteId(home.value?.params?.id || home.value?.name);
|
||||
.setFocusedRouteId(home?.params?.id || home?.name);
|
||||
}, [home]);
|
||||
|
||||
return !home.value ? null : (
|
||||
return !home ? null : (
|
||||
<AppStack.Navigator
|
||||
initialRouteName={home.value?.name}
|
||||
initialRouteName={home?.name}
|
||||
screenOptions={{
|
||||
headerShown: false,
|
||||
animation: "none",
|
||||
@@ -197,7 +207,7 @@ const AppNavigation = React.memo(
|
||||
return TaggedNotes;
|
||||
}}
|
||||
initialParams={
|
||||
home.value?.name === "TaggedNotes" ? home.value?.params : undefined
|
||||
home?.name === "TaggedNotes" ? home?.params : undefined
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -209,7 +219,7 @@ const AppNavigation = React.memo(
|
||||
return ColoredNotes;
|
||||
}}
|
||||
initialParams={
|
||||
home.value?.name === "ColoredNotes" ? home.value?.params : undefined
|
||||
home?.name === "ColoredNotes" ? home?.params : undefined
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -244,9 +254,7 @@ const AppNavigation = React.memo(
|
||||
Notebook = Notebook || require("../screens/notebook").default;
|
||||
return Notebook;
|
||||
}}
|
||||
initialParams={
|
||||
home.value?.name === "Notebook" ? home.value?.params : undefined
|
||||
}
|
||||
initialParams={home?.name === "Notebook" ? home?.params : undefined}
|
||||
/>
|
||||
|
||||
<AppStack.Screen
|
||||
|
||||
@@ -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,7 +70,8 @@ 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",
|
||||
@@ -92,6 +93,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 +153,7 @@ 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 WeekDayNames]() + joinWith
|
||||
: `${day}${nth(day)} ${joinWith}`;
|
||||
})
|
||||
.join("");
|
||||
@@ -390,37 +392,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 +472,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())}
|
||||
/>
|
||||
|
||||
@@ -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 () => {
|
||||
@@ -733,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);
|
||||
@@ -1097,7 +1097,10 @@ export const useEditor = (
|
||||
|
||||
if (!state.current?.initialLoadCalled) {
|
||||
const url = await Linking.getInitialURL();
|
||||
let noteId = url && new URL(url).searchParams.get("id");
|
||||
let noteId =
|
||||
url &&
|
||||
url.startsWith("https://app.notesnook.com/open_note?") &&
|
||||
new URL(url).searchParams.get("id");
|
||||
if (noteId) {
|
||||
const note = await db.notes?.note(noteId);
|
||||
fluidTabsRef.current?.goToPage("editor");
|
||||
|
||||
@@ -38,7 +38,6 @@ import { eUpdateNotebookRoute } from "../../utils/events";
|
||||
import { findRootNotebookId } from "../../utils/notebooks";
|
||||
import { openEditor, setOnFirstSave } from "../notes/common";
|
||||
import { View } from "react-native";
|
||||
import { DefaultAppStyles } from "../../utils/styles";
|
||||
import { Notebooks } from "../../components/sheets/notebooks";
|
||||
import { useSettingStore } from "../../stores/use-setting-store";
|
||||
import { rootNavigatorRef } from "../../utils/global-refs";
|
||||
@@ -47,6 +46,9 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
|
||||
const params = useRef<NotebookScreenParams>(route?.params);
|
||||
const isAppLoading = useSettingStore((state) => state.isAppLoading);
|
||||
const [notebook, setNotebook] = useState<Notebook | undefined>(
|
||||
params.current.item
|
||||
);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const updateOnFocus = useRef(false);
|
||||
const [breadcrumbs, setBreadcrumbs] = useState<
|
||||
@@ -75,10 +77,10 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
});
|
||||
|
||||
const syncWithNavigation = React.useCallback(() => {
|
||||
useNavigationStore.getState().setFocusedRouteId(params?.current?.item?.id);
|
||||
useNavigationStore.getState().setFocusedRouteId(params?.current?.id);
|
||||
setOnFirstSave({
|
||||
type: "notebook",
|
||||
id: params.current.item.id
|
||||
id: params.current.id
|
||||
});
|
||||
}, []);
|
||||
|
||||
@@ -86,23 +88,20 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
async (data?: NotebookScreenParams) => {
|
||||
if (useSettingStore.getState().isAppLoading) return;
|
||||
if (
|
||||
useNavigationStore.getState().focusedRouteId !==
|
||||
params.current.item.id &&
|
||||
useNavigationStore.getState().focusedRouteId !== params.current.id &&
|
||||
!data
|
||||
) {
|
||||
updateOnFocus.current = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (data?.item?.id && params.current.item?.id !== data?.item?.id) {
|
||||
const nextRootNotebookId = await findRootNotebookId(data?.item?.id);
|
||||
const currentNotebookRoot = await findRootNotebookId(
|
||||
params.current.item.id
|
||||
);
|
||||
if (data?.id && params.current?.id !== data?.id) {
|
||||
const nextRootNotebookId = await findRootNotebookId(data?.id);
|
||||
const currentNotebookRoot = await findRootNotebookId(params.current.id);
|
||||
|
||||
if (
|
||||
nextRootNotebookId !== currentNotebookRoot ||
|
||||
nextRootNotebookId === params.current?.item?.id
|
||||
nextRootNotebookId === params.current?.id
|
||||
) {
|
||||
// Never update notebook in route if root is different or if the root is current notebook.
|
||||
return;
|
||||
@@ -112,14 +111,13 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
if (data) params.current = data;
|
||||
|
||||
try {
|
||||
const notebook = await db.notebooks?.notebook(
|
||||
params?.current?.item?.id
|
||||
);
|
||||
|
||||
const notebook = await db.notebooks?.notebook(params?.current?.id);
|
||||
setNotebook(notebook);
|
||||
params.current.item = notebook;
|
||||
if (notebook) {
|
||||
const breadcrumbs = await db.notebooks.breadcrumbs(notebook.id);
|
||||
setBreadcrumbs(breadcrumbs.slice(0, breadcrumbs.length - 1));
|
||||
params.current.item = notebook;
|
||||
params.current.id = notebook.id;
|
||||
const notes = await db.relations
|
||||
.from(notebook, "note")
|
||||
.selector.grouped(db.settings.getGroupOptions("notes"));
|
||||
@@ -160,29 +158,27 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
<>
|
||||
<Header
|
||||
renderedInRoute={route.name}
|
||||
title={params.current.item?.title}
|
||||
title={notebook?.title}
|
||||
canGoBack={params?.current?.canGoBack}
|
||||
rightButton={{
|
||||
name: "dots-vertical",
|
||||
onPress: () => {
|
||||
Properties.present(params.current.item);
|
||||
Properties.present(notebook);
|
||||
}
|
||||
}}
|
||||
hasSearch={true}
|
||||
onSearch={() => {
|
||||
const selector = db.relations.from(
|
||||
params.current.item,
|
||||
"note"
|
||||
).selector;
|
||||
if (!notebook) return;
|
||||
const selector = db.relations.from(notebook, "note").selector;
|
||||
Navigation.push("Search", {
|
||||
placeholder: strings.searchInRoute(params.current.item?.title),
|
||||
placeholder: strings.searchInRoute(notebook?.title),
|
||||
type: "note",
|
||||
title: params.current.item?.title,
|
||||
title: notebook?.title,
|
||||
route: route.name,
|
||||
items: selector
|
||||
});
|
||||
}}
|
||||
id={params.current.item?.id}
|
||||
id={notebook?.id}
|
||||
/>
|
||||
|
||||
<DelayLayout wait={loading}>
|
||||
@@ -192,19 +188,19 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
onRefresh={() => {
|
||||
onRequestUpdate();
|
||||
}}
|
||||
id={params.current.item?.id}
|
||||
id={params.current?.id}
|
||||
renderedInRoute="Notebook"
|
||||
headerTitle={params.current.item.title}
|
||||
headerTitle={notebook?.title}
|
||||
loading={loading}
|
||||
CustomLisHeader={
|
||||
<NotebookHeader
|
||||
breadcrumbs={breadcrumbs}
|
||||
notebook={params.current.item}
|
||||
notebook={notebook!}
|
||||
totalNotes={notes?.placeholders.length || 0}
|
||||
/>
|
||||
}
|
||||
placeholder={{
|
||||
title: params.current.item?.title,
|
||||
title: notebook?.title!,
|
||||
paragraph: strings.notesEmpty(),
|
||||
button: strings.addFirstNote(),
|
||||
action: openEditor,
|
||||
@@ -225,7 +221,8 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
testID="notebookTreeSheet"
|
||||
size="small"
|
||||
onPress={() => {
|
||||
Notebooks.present(params.current.item);
|
||||
if (!notebook) return;
|
||||
Notebooks.present(notebook);
|
||||
}}
|
||||
style={{
|
||||
position: "relative",
|
||||
@@ -246,7 +243,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
|
||||
/>
|
||||
</View>
|
||||
<SelectionHeader
|
||||
id={route.params?.item?.id}
|
||||
id={route.params?.id}
|
||||
items={notes}
|
||||
type="note"
|
||||
renderedInRoute="Notebook"
|
||||
@@ -260,6 +257,7 @@ NotebookScreen.navigate = async (item: Notebook, canGoBack?: boolean) => {
|
||||
const { currentRoute, focusedRouteId } = useNavigationStore.getState();
|
||||
if (currentRoute === "Notebooks") {
|
||||
Navigation.push("Notebook", {
|
||||
id: item.id,
|
||||
item: item,
|
||||
canGoBack
|
||||
});
|
||||
@@ -276,22 +274,24 @@ NotebookScreen.navigate = async (item: Notebook, canGoBack?: boolean) => {
|
||||
// Update the route in place instead
|
||||
|
||||
eSendEvent(eUpdateNotebookRoute, {
|
||||
item: item,
|
||||
title: item.title,
|
||||
canGoBack: canGoBack
|
||||
id: item.id,
|
||||
canGoBack: canGoBack,
|
||||
item: item
|
||||
});
|
||||
} else {
|
||||
// Push a new route
|
||||
Navigation.push("Notebook", {
|
||||
item: item,
|
||||
canGoBack
|
||||
id: item.id,
|
||||
canGoBack,
|
||||
item: item
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Push a new route anyways
|
||||
Navigation.push("Notebook", {
|
||||
item: item,
|
||||
canGoBack
|
||||
id: item.id,
|
||||
canGoBack,
|
||||
item: item
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -25,7 +25,7 @@ import Navigation, { NavigationProps } from "../../services/navigation";
|
||||
import useNavigationStore, {
|
||||
NotesScreenParams
|
||||
} from "../../stores/use-navigation-store";
|
||||
import { PLACEHOLDER_DATA, openEditor, toCamelCase } from "./common";
|
||||
import { PLACEHOLDER_DATA, openEditor } from "./common";
|
||||
export const ColoredNotes = ({
|
||||
navigation,
|
||||
route
|
||||
@@ -45,11 +45,13 @@ export const ColoredNotes = ({
|
||||
|
||||
ColoredNotes.get = async (params: NotesScreenParams, grouped = true) => {
|
||||
if (!grouped) {
|
||||
return await db.relations.from(params.item, "note").resolve();
|
||||
return await db.relations
|
||||
.from({ id: params.id, type: "color" }, "note")
|
||||
.resolve();
|
||||
}
|
||||
|
||||
return await db.relations
|
||||
.from(params.item, "note")
|
||||
.from({ id: params.id, type: "color" }, "note")
|
||||
.selector.grouped(db.settings.getGroupOptions("notes"));
|
||||
};
|
||||
|
||||
@@ -63,8 +65,10 @@ ColoredNotes.navigate = (item: Color, canGoBack: boolean) => {
|
||||
}
|
||||
|
||||
Navigation.push<"ColoredNotes">("ColoredNotes", {
|
||||
item: item,
|
||||
canGoBack
|
||||
type: "color",
|
||||
id: item.id,
|
||||
canGoBack,
|
||||
item: item
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { resolveItems } from "@notesnook/common";
|
||||
import { VirtualizedGrouping } from "@notesnook/core";
|
||||
import { Tag, VirtualizedGrouping } from "@notesnook/core";
|
||||
import { Color, Note } from "@notesnook/core";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { db } from "../../common/database";
|
||||
@@ -76,14 +76,12 @@ const NotesPage = ({
|
||||
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
|
||||
const [loadingNotes, setLoadingNotes] = useState(true);
|
||||
const isMonograph = route.name === "Monographs";
|
||||
const title =
|
||||
params.current?.item.type === "tag"
|
||||
? "#" + params.current?.item.title
|
||||
: params.current?.item.title;
|
||||
const [item, setItem] = useState<Tag | Color | undefined>(
|
||||
params.current.item
|
||||
);
|
||||
const title = item?.type === "tag" ? "#" + item?.title : item?.title;
|
||||
const accentColor =
|
||||
route.name === "ColoredNotes"
|
||||
? (params.current?.item as Color)?.colorCode
|
||||
: undefined;
|
||||
route.name === "ColoredNotes" ? (item as Color)?.colorCode : undefined;
|
||||
const updateOnFocus = useRef(false);
|
||||
const isAppLoading = useSettingStore((state) => state.isAppLoading);
|
||||
const isFocused = useNavigationFocus(navigation, {
|
||||
@@ -107,31 +105,30 @@ const NotesPage = ({
|
||||
});
|
||||
|
||||
const syncWithNavigation = React.useCallback(() => {
|
||||
const { item } = params.current;
|
||||
useNavigationStore
|
||||
.getState()
|
||||
.setFocusedRouteId(params?.current?.item?.id || route.name);
|
||||
const { id } = params.current;
|
||||
useNavigationStore.getState().setFocusedRouteId(id || route.name);
|
||||
|
||||
!isMonograph &&
|
||||
setOnFirstSave({
|
||||
type: getItemType(route.name),
|
||||
id: item.id
|
||||
id: id
|
||||
});
|
||||
}, [isMonograph, route.name]);
|
||||
|
||||
const onRequestUpdate = React.useCallback(
|
||||
async (data?: NotesScreenParams) => {
|
||||
if (useSettingStore.getState().isAppLoading) return;
|
||||
|
||||
if (
|
||||
params.current.item.id &&
|
||||
useNavigationStore.getState().focusedRouteId !==
|
||||
params.current.item.id &&
|
||||
params.current.id &&
|
||||
useNavigationStore.getState().focusedRouteId !== params.current.id &&
|
||||
!data
|
||||
) {
|
||||
updateOnFocus.current = false;
|
||||
return;
|
||||
}
|
||||
const isNew = data && data?.item?.id !== params.current?.item?.id;
|
||||
|
||||
const isNew = data && data?.id !== params.current?.id;
|
||||
if (data) params.current = data;
|
||||
|
||||
try {
|
||||
@@ -142,9 +139,9 @@ const NotesPage = ({
|
||||
)) as VirtualizedGrouping<Note>;
|
||||
|
||||
if (route.name === "TaggedNotes" || route.name === "ColoredNotes") {
|
||||
const item = await (db as any)[params.current.item.type + "s"][
|
||||
params.current.item.type
|
||||
](params.current.item.id);
|
||||
const item = await (db as any)[params.current.type + "s"][
|
||||
params.current.type
|
||||
](params.current.id);
|
||||
|
||||
if (!item) {
|
||||
if (rootNavigatorRef.canGoBack()) {
|
||||
@@ -154,7 +151,7 @@ const NotesPage = ({
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setItem(item);
|
||||
params.current.item = item;
|
||||
}
|
||||
|
||||
@@ -173,15 +170,7 @@ const NotesPage = ({
|
||||
useEffect(() => {
|
||||
if (isAppLoading) return;
|
||||
if (loadingNotes) {
|
||||
get(params.current, true)
|
||||
.then(async (items) => {
|
||||
setNotes(items as VirtualizedGrouping<Note>);
|
||||
await (items as VirtualizedGrouping<Note>).item(0, resolveItems);
|
||||
setLoadingNotes(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
setLoadingNotes(false);
|
||||
});
|
||||
onRequestUpdate(params.current);
|
||||
}
|
||||
}, [loadingNotes, get, isAppLoading]);
|
||||
|
||||
@@ -202,19 +191,18 @@ const NotesPage = ({
|
||||
}
|
||||
canGoBack={params?.current?.canGoBack}
|
||||
hasSearch={true}
|
||||
id={
|
||||
route.name === "Monographs" ? "Monographs" : params?.current.item?.id
|
||||
}
|
||||
id={route.name === "Monographs" ? "Monographs" : params?.current?.id}
|
||||
onSearch={() => {
|
||||
if (!item) return;
|
||||
const selector =
|
||||
route.name === "Monographs"
|
||||
? db.monographs.all
|
||||
: db.relations.from(params.current.item, "note").selector;
|
||||
: db.relations.from(item, "note").selector;
|
||||
|
||||
Navigation.push("Search", {
|
||||
placeholder: strings.searchInRoute(title || route.name),
|
||||
type: "note",
|
||||
title: title,
|
||||
title: title!,
|
||||
route: route.name,
|
||||
items: selector
|
||||
});
|
||||
@@ -229,7 +217,7 @@ const NotesPage = ({
|
||||
onRefresh={onRequestUpdate}
|
||||
loading={false}
|
||||
renderedInRoute={route.name}
|
||||
id={params.current.item?.id}
|
||||
id={params.current?.id}
|
||||
headerTitle={title || "Monographs"}
|
||||
customAccentColor={accentColor}
|
||||
placeholder={placeholder}
|
||||
@@ -246,7 +234,7 @@ const NotesPage = ({
|
||||
) : null}
|
||||
</DelayLayout>
|
||||
<SelectionHeader
|
||||
id={route.params?.item?.id || route.name}
|
||||
id={route.params?.id || route.name}
|
||||
items={notes}
|
||||
type="note"
|
||||
renderedInRoute={route.name}
|
||||
|
||||
@@ -62,7 +62,8 @@ Monographs.get = async (params?: NotesScreenParams, grouped = true) => {
|
||||
|
||||
Monographs.navigate = (canGoBack?: boolean) => {
|
||||
Navigation.navigate<"Monographs">("Monographs", {
|
||||
item: { type: "monograph" } as any,
|
||||
type: "monograph",
|
||||
id: "monograph",
|
||||
canGoBack: canGoBack as boolean
|
||||
});
|
||||
};
|
||||
|
||||
@@ -46,11 +46,13 @@ export const TaggedNotes = ({
|
||||
|
||||
TaggedNotes.get = async (params: NotesScreenParams, grouped = true) => {
|
||||
if (!grouped) {
|
||||
return await db.relations.from(params.item, "note").resolve();
|
||||
return await db.relations
|
||||
.from({ id: params.id, type: "tag" }, "note")
|
||||
.resolve();
|
||||
}
|
||||
|
||||
return await db.relations
|
||||
.from(params.item, "note")
|
||||
.from({ id: params.id, type: "tag" }, "note")
|
||||
.selector.grouped(db.settings.getGroupOptions("notes"));
|
||||
};
|
||||
|
||||
@@ -64,8 +66,10 @@ TaggedNotes.navigate = (item: Tag, canGoBack?: boolean) => {
|
||||
}
|
||||
|
||||
Navigation.push<"TaggedNotes">("TaggedNotes", {
|
||||
item: item,
|
||||
canGoBack
|
||||
id: item.id,
|
||||
type: "tag",
|
||||
canGoBack,
|
||||
item: item
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -463,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -17,12 +17,13 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { db } from "../common/database";
|
||||
import { DatabaseLogger, db } from "../common/database";
|
||||
import { eSendEvent } from "../services/event-manager";
|
||||
import Navigation from "../services/navigation";
|
||||
import { NotePreviewWidget } from "../services/note-preview-widget";
|
||||
import Notifications from "../services/notifications";
|
||||
import { eAfterSync } from "../utils/events";
|
||||
import { NotesnookModule, ShortcutInfo } from "../utils/notesnook-module";
|
||||
import { useFavoriteStore } from "./use-favorite-store";
|
||||
import { useMenuStore } from "./use-menu-store";
|
||||
import { useMonographStore } from "./use-monograph-store";
|
||||
@@ -34,6 +35,81 @@ import { useTagStore } from "./use-tag-store";
|
||||
import { useTrashStore } from "./use-trash-store";
|
||||
import { useUserStore } from "./use-user-store";
|
||||
|
||||
async function syncShortcuts(result: ShortcutInfo[]) {
|
||||
try {
|
||||
for (let shortcut of result) {
|
||||
switch (shortcut.type) {
|
||||
case "note":
|
||||
{
|
||||
const note = await db.notes.note(shortcut.id);
|
||||
if (!note) {
|
||||
NotesnookModule.removeShortcut(shortcut.id);
|
||||
} else if (note.title !== shortcut.title) {
|
||||
NotesnookModule.updateShortcut(
|
||||
shortcut.id,
|
||||
"note",
|
||||
note.title,
|
||||
note.headline
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "notebook":
|
||||
{
|
||||
const notebook = await db.notebooks.notebook(shortcut.id);
|
||||
if (!notebook) {
|
||||
NotesnookModule.removeShortcut(shortcut.id);
|
||||
} else if (notebook.title !== shortcut.title) {
|
||||
NotesnookModule.updateShortcut(
|
||||
shortcut.id,
|
||||
"notebook",
|
||||
notebook.title,
|
||||
notebook.description
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "tag":
|
||||
{
|
||||
const tag = await db.tags.tag(shortcut.id);
|
||||
if (!tag) {
|
||||
NotesnookModule.removeShortcut(shortcut.id);
|
||||
} else if (tag.title !== shortcut.title) {
|
||||
NotesnookModule.updateShortcut(
|
||||
shortcut.id,
|
||||
"tag",
|
||||
tag.title,
|
||||
tag.title
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "color":
|
||||
{
|
||||
const color = await db.colors.color(shortcut.id);
|
||||
if (!color) {
|
||||
NotesnookModule.removeShortcut(shortcut.id);
|
||||
} else if (color.title !== shortcut.title) {
|
||||
NotesnookModule.updateShortcut(
|
||||
shortcut.id,
|
||||
"color",
|
||||
color.title,
|
||||
color.title,
|
||||
color.colorCode
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
DatabaseLogger.error(
|
||||
e as Error,
|
||||
"Error while syncing homescreen shortcuts"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function initAfterSync(type: "full" | "send" = "send") {
|
||||
if (type === "full") {
|
||||
Navigation.queueRoutesForUpdate();
|
||||
@@ -46,9 +122,16 @@ export function initAfterSync(type: "full" | "send" = "send") {
|
||||
profile: db.settings.getProfile()
|
||||
});
|
||||
}
|
||||
|
||||
Notifications.setupReminders(true);
|
||||
NotePreviewWidget.updateNotes();
|
||||
eSendEvent(eAfterSync);
|
||||
|
||||
NotesnookModule.getAllShortcuts()
|
||||
.then(syncShortcuts)
|
||||
.catch((e) => {
|
||||
DatabaseLogger.log(e);
|
||||
});
|
||||
}
|
||||
|
||||
export async function initialize() {}
|
||||
|
||||
@@ -25,8 +25,7 @@ import {
|
||||
Note,
|
||||
Notebook,
|
||||
Reminder,
|
||||
Tag,
|
||||
TrashItem
|
||||
Tag
|
||||
} from "@notesnook/core";
|
||||
import { ParamListBase } from "@react-navigation/core";
|
||||
import { create } from "zustand";
|
||||
@@ -36,12 +35,15 @@ export type GenericRouteParam = {
|
||||
};
|
||||
|
||||
export type NotebookScreenParams = {
|
||||
item: Notebook;
|
||||
id: string;
|
||||
item?: Notebook;
|
||||
canGoBack?: boolean;
|
||||
};
|
||||
|
||||
export type NotesScreenParams = {
|
||||
item: Note | Notebook | Tag | Color | TrashItem | Reminder;
|
||||
type: "tag" | "color" | "monograph";
|
||||
id: string;
|
||||
item?: Tag | Color;
|
||||
canGoBack?: boolean;
|
||||
};
|
||||
|
||||
|
||||
@@ -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,8 +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");
|
||||
@@ -198,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) => ({
|
||||
@@ -221,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
|
||||
@@ -236,5 +246,14 @@ export const useSettingStore = create<SettingStore>((set, get) => ({
|
||||
},
|
||||
insets: initialWindowMetrics?.insets
|
||||
? initialWindowMetrics.insets
|
||||
: { top: 0, right: 0, left: 0, bottom: 0 }
|
||||
: { top: 0, right: 0, left: 0, bottom: 0 },
|
||||
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
|
||||
};
|
||||
|
||||
@@ -19,6 +19,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { NativeModules, Platform } from "react-native";
|
||||
|
||||
export type ShortcutInfo = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
type: "note" | "notebook" | "tag" | "color";
|
||||
};
|
||||
|
||||
interface NotesnookModuleInterface {
|
||||
getActivityName: () => Promise<string>;
|
||||
setBackgroundColor: (color: string) => void;
|
||||
@@ -41,6 +48,23 @@ interface NotesnookModuleInterface {
|
||||
updateWidgetNote: (noteId: string, data: string) => void;
|
||||
updateReminderWidget: () => void;
|
||||
isGestureNavigationEnabled: () => boolean;
|
||||
addShortcut: (
|
||||
id: string,
|
||||
type: "note" | "notebook" | "tag" | "color",
|
||||
title: string,
|
||||
description?: string,
|
||||
color?: string
|
||||
) => Promise<boolean>;
|
||||
removeShortcut: (id: string) => Promise<boolean>;
|
||||
updateShortcut: (
|
||||
id: string,
|
||||
type: "note" | "notebook" | "tag" | "color",
|
||||
title: string,
|
||||
description?: string,
|
||||
color?: string
|
||||
) => Promise<boolean>;
|
||||
removeAllShortcuts: () => Promise<boolean>;
|
||||
getAllShortcuts: () => Promise<ShortcutInfo[]>;
|
||||
}
|
||||
|
||||
export const NotesnookModule: NotesnookModuleInterface = Platform.select({
|
||||
@@ -61,7 +85,12 @@ export const NotesnookModule: NotesnookModuleInterface = Platform.select({
|
||||
hasWidgetNote: () => {},
|
||||
updateWidgetNote: () => {},
|
||||
updateReminderWidget: () => {},
|
||||
isGestureNavigationEnabled: () => true
|
||||
isGestureNavigationEnabled: () => true,
|
||||
addShortcut: () => Promise.resolve(false),
|
||||
removeShortcut: () => Promise.resolve(false),
|
||||
updateShortcut: () => Promise.resolve(false),
|
||||
removeAllShortcuts: () => Promise.resolve(false),
|
||||
getAllShortcuts: () => Promise.resolve([])
|
||||
},
|
||||
android: NativeModules.NNativeModule
|
||||
});
|
||||
|
||||
@@ -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 = 2170;
|
||||
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 = 2170;
|
||||
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 = 2170;
|
||||
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 = 2170;
|
||||
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 = 2170;
|
||||
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 = 2170;
|
||||
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
|
||||
@@ -2004,35 +2063,6 @@ PODS:
|
||||
- React
|
||||
- react-native-orientation-locker (1.7.0):
|
||||
- React-Core
|
||||
- react-native-pager-view (8.0.0):
|
||||
- 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
|
||||
- SwiftUIIntrospect (~> 1.0)
|
||||
- Yoga
|
||||
- react-native-pdf (7.0.3):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
@@ -2245,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
|
||||
@@ -3062,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
|
||||
@@ -3161,7 +3191,7 @@ PODS:
|
||||
- RNWorklets
|
||||
- SocketRocket
|
||||
- Yoga
|
||||
- RNScreens (4.18.0):
|
||||
- RNScreens (4.19.0):
|
||||
- boost
|
||||
- DoubleConversion
|
||||
- fast_float
|
||||
@@ -3188,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
|
||||
@@ -3414,7 +3444,6 @@ PODS:
|
||||
- pop (~> 1.0)
|
||||
- SocketRocket (0.7.1)
|
||||
- SSZipArchive (2.4.3)
|
||||
- SwiftUIIntrospect (1.3.0)
|
||||
- SwiftyRSA (1.7.0):
|
||||
- SwiftyRSA/ObjC (= 1.7.0)
|
||||
- SwiftyRSA/ObjC (1.7.0)
|
||||
@@ -3434,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`)
|
||||
@@ -3486,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`)
|
||||
@@ -3570,7 +3600,6 @@ SPEC REPOS:
|
||||
- SDWebImage
|
||||
- SocketRocket
|
||||
- SSZipArchive
|
||||
- SwiftUIIntrospect
|
||||
- SwiftyRSA
|
||||
- TOCropViewController
|
||||
|
||||
@@ -3592,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:
|
||||
@@ -3694,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:
|
||||
@@ -3853,6 +3884,8 @@ SPEC CHECKSUMS:
|
||||
JWTDecode: 2eed97c2fa46ccaf3049a787004eedf0be474a87
|
||||
MMKV: 7b5df6a8bf785c6705cc490c541b9d8a957c4a64
|
||||
MMKVCore: 3f40b896e9ab522452df9df3ce983471aa2449ba
|
||||
NitroCloudUploader: 62489381ed6b472d87c8c6b62629136ebd2342d1
|
||||
NitroModules: f964d2f3f5b392d0c0303737085cc30e375dc43f
|
||||
pop: d582054913807fd11fd50bfe6a539d91c7e1a55a
|
||||
RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669
|
||||
RCTDeprecation: f17e2ebc07876ca9ab8eb6e4b0a4e4647497ae3a
|
||||
@@ -3906,7 +3939,6 @@ SPEC CHECKSUMS:
|
||||
react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187
|
||||
react-native-notification-sounds: ce106d58df0dd384bccbd2e84fb53accab7cc068
|
||||
react-native-orientation-locker: cc6f357b289a2e0dd2210fea0c52cb8e0727fdaa
|
||||
react-native-pager-view: d7d2aa47f54343bf55fdcee3973503dd27c2bd37
|
||||
react-native-pdf: edc236298f13f1609e42d41e45b8b6ea88ed10f9
|
||||
react-native-quick-sqlite: 1ed8d3db1e22a8604d006be69f06053382e93bb0
|
||||
react-native-safe-area-context: c6e2edd1c1da07bdce287fa9d9e60c5f7b514616
|
||||
@@ -3914,7 +3946,7 @@ SPEC CHECKSUMS:
|
||||
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
|
||||
@@ -3962,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
|
||||
@@ -3976,7 +4008,6 @@ SPEC CHECKSUMS:
|
||||
SexyTooltip: 5c9b4dec52bfb317938cb0488efd9da3717bb6fd
|
||||
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
|
||||
SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef
|
||||
SwiftUIIntrospect: fee9aa07293ee280373a591e1824e8ddc869ba5d
|
||||
SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6
|
||||
TOCropViewController: 797deaf39c90e6e9ddd848d88817f6b9a8a09888
|
||||
toolbar-android: c426ed5bd3dcccfed20fd79533efc0d1ae0ef018
|
||||
|
||||
93
apps/mobile/package-lock.json
generated
93
apps/mobile/package-lock.json
generated
@@ -1,22 +1,22 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.3.11",
|
||||
"version": "3.3.13-beta.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "3.3.11",
|
||||
"version": "3.3.13-beta.0",
|
||||
"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",
|
||||
@@ -100,9 +100,10 @@
|
||||
"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": "^8.0.0",
|
||||
"react-native-pdf": "^7.0.3",
|
||||
"react-native-privacy-snapshot": "github:standardnotes/react-native-privacy-snapshot",
|
||||
"react-native-progress": "5.0.0",
|
||||
@@ -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.2.2",
|
||||
"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": "*",
|
||||
@@ -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,16 +17771,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-pager-view": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-pager-view/-/react-native-pager-view-8.0.0.tgz",
|
||||
"integrity": "sha512-oAwlWT1lhTkIs9HhODnjNNl/owxzn9DP1MbP+az6OTUdgbmzA16Up83sBH8NRKwrH8rNm7iuWnX1qMqiiWOLhg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-pdf": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-7.0.3.tgz",
|
||||
@@ -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.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-tab-view/-/react-native-tab-view-4.2.2.tgz",
|
||||
"integrity": "sha512-NXtrG6OchvbGjsvbySJGVocXxo4Y2vA17ph4rAaWtA2jh+AasD8OyikKBRg2SmllEfeQ+GEhcKe8kulHv8BhTg==",
|
||||
"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-beta.1",
|
||||
"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",
|
||||
@@ -116,9 +116,10 @@
|
||||
"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": "^8.0.0",
|
||||
"react-native-pdf": "^7.0.3",
|
||||
"react-native-privacy-snapshot": "github:standardnotes/react-native-privacy-snapshot",
|
||||
"react-native-progress": "5.0.0",
|
||||
@@ -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.2.2",
|
||||
"react-native-theme-switch-animation": "^0.6.0",
|
||||
"react-native-tooltips": "^1.0.3",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
|
||||
@@ -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) {
|
||||
@@ -115,7 +115,13 @@ const EXTRA_ICON_NAMES = [
|
||||
"update",
|
||||
"notebook-minus",
|
||||
"calendar-blank",
|
||||
"email-newsletter"
|
||||
"email-newsletter",
|
||||
"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.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user