Compare commits

...

69 Commits

Author SHA1 Message Date
Ammar Ahmed
272788b947 mobile: fix note not opening from notifications 2024-05-04 12:28:27 +05:00
Abdullah Atta
9ef32e8081 web: do not hide attachment manager for logged out user 2024-05-01 20:14:20 +05:00
Abdullah Atta
5f53593a26 web: do not throw if backup doesn't contain .nnbackup file 2024-05-01 20:12:23 +05:00
Abdullah Atta
1f404944d9 web: re-enable service worker 2024-05-01 16:38:09 +05:00
Ammar Ahmed
23dca36475 mobile: release v3.0.1 2024-05-01 16:34:01 +05:00
Ammar Ahmed
3dd8e20e9a mobile: add logging when loading attachments 2024-05-01 16:29:37 +05:00
Abdullah Atta
8f412f9487 editor: fix padding of react node views on mobile 2024-05-01 16:29:12 +05:00
Abdullah Atta
bf790219db web: bump version to 3.0.1 2024-05-01 16:16:08 +05:00
Ammar Ahmed
c6af310b2d mobile: add notebook search 2024-05-01 16:10:26 +05:00
Abdullah Atta
8d89b5dca9 web: bring back notebook search in move note dialog 2024-05-01 16:10:26 +05:00
Abdullah Atta
fa35244607 editor: update test snapshosts 2024-05-01 16:09:06 +05:00
Abdullah Atta
d96815fa36 ci: remove beta publish workflows 2024-05-01 15:23:43 +05:00
Abdullah Atta
4a9f5b1d8b core: remove .only from trash tests 2024-05-01 15:19:57 +05:00
Ammar Ahmed
43fac79011 mobile: fix moving notebook on ios 2024-05-01 15:18:55 +05:00
Ammar Ahmed
f2c62c8d8b mobile: fix moving notebook on ios 2024-05-01 15:18:55 +05:00
Ammar Ahmed
70c9dd0083 mobile: reduce font size in notebook header 2024-05-01 15:15:37 +05:00
Ammar Ahmed
ae6d8c5f2b mobile: hide breadcrumbs in top level notebooks 2024-05-01 15:15:37 +05:00
Abdullah Atta
38e2250fd1 web: log attachment on failure 2024-05-01 14:55:49 +05:00
Ammar Ahmed
17e2c5a8a2 desktop: fix app icon size for macos 2024-05-01 14:53:32 +05:00
Ammar Ahmed
2e226f0f72 mobile: fix cannot read property 'sortDirection' of undefined 2024-05-01 13:34:48 +05:00
Ammar Ahmed
167c54578b mobile: fix scrolling broken in reminder -> repeat -> month 2024-05-01 13:34:37 +05:00
Abdullah Atta
fb656af5ca web: sort tags by title in editor header 2024-05-01 13:34:20 +05:00
Ammar Ahmed
ddfab272d2 common: sort tags by title in resolved items 2024-05-01 13:34:20 +05:00
Ammar Ahmed
28958311c1 mobile: fix share extention crash on sharing from some apps 2024-05-01 13:34:06 +05:00
Ammar Ahmed
f1b8275b4b mobile: add support for setting note title form quick note widget 2024-05-01 13:34:06 +05:00
Ammar Ahmed
c8c75d40dd mobile: fix theme search returns empty results 2024-05-01 13:33:50 +05:00
Ammar Ahmed
bace90808d mobile: fix footer size for notebook screen 2024-05-01 13:33:31 +05:00
Ammar Ahmed
fe7c7e8db4 mobile: fix notebook-sheet position 2024-05-01 13:32:02 +05:00
Ammar Ahmed
519a569366 mobile: fix notebook sheet flicker 2024-05-01 13:32:02 +05:00
Ammar Ahmed
89d602a40f mobile: do not lock app when requesting permission 2024-05-01 13:31:38 +05:00
Abdullah Atta
a8db65b5af editor: fix performance issues due to using portals for react node views 2024-05-01 13:29:55 +05:00
Ammar Ahmed
716239abfd mobile: fix selection header 2024-05-01 13:21:31 +05:00
Ammar Ahmed
62dd5a5416 mobile: fix vault unlock with biometrics 2024-05-01 13:19:00 +05:00
Ammar Ahmed
96558c5df5 mobile: fix search bar position on ios 2024-05-01 13:18:21 +05:00
Ammar Ahmed
45997dd2e4 editor: fix scroll into view should bring heading to top 2024-05-01 13:16:54 +05:00
Ammar Ahmed
bde190f01c mobile: render settings icon if no profile pic is set 2024-05-01 13:15:24 +05:00
Ammar Ahmed
042c97688f mobile: add quick theme switch button 2024-05-01 13:15:24 +05:00
Ammar Ahmed
95b5114070 mobile: fix confusing settings/sync button 2024-05-01 13:15:24 +05:00
Ammar Ahmed
20742e2a1c mobile: fix back navigation from editor (#4867)
Signed-off-by: Ammar Ahmed <40239442+ammarahm-ed@users.noreply.github.com>
2024-05-01 13:13:44 +05:00
Ammar Ahmed
1e3149284e mobile: report error when migration fails due to backup error 2024-05-01 13:01:41 +05:00
Ammar Ahmed
d77c09f1fd mobile: fix opening internal link in readonly mode 2024-05-01 13:00:58 +05:00
Ammar Ahmed
aa602ea8e9 mobile: fix position of context menu 2024-05-01 12:59:50 +05:00
Ammar Ahmed
8ab6b6dc31 mobile: skip checking encrypted backup if user is logged out 2024-05-01 12:57:42 +05:00
Ammar Ahmed
404d136a73 mobile: fix sidebar order not loading on app launch 2024-05-01 12:50:13 +05:00
Abdullah Atta
cf9aeecd7e editor: fix placeholder showing always in first line of editor 2024-05-01 12:37:48 +05:00
Abdullah Atta
1adda96ce2 editor: fix many papercut ux issues in search replace 2024-05-01 12:36:00 +05:00
Abdullah Atta
6dd6d818c9 editor: add support for multi line search/replace 2024-05-01 12:36:00 +05:00
Abdullah Atta
c7e83961d4 web: show all notes if query is empty in note linking dialog 2024-05-01 12:35:38 +05:00
Abdullah Atta
03428e5848 core: allow full FTS5 search syntax 2024-05-01 12:35:38 +05:00
Abdullah Atta
d0fceed56d web: improve app performance by reducing react rerenders 2024-05-01 12:34:43 +05:00
Abdullah Atta
b8c68b5163 web: fix middle pane size increasing on exiting focus mode 2024-04-30 21:52:25 +05:00
Abdullah Atta
2496004896 web: fix position of image preview toolbar 2024-04-30 21:51:57 +05:00
Abdullah Atta
16ec35de25 web: do not change properties top on web app 2024-04-30 21:51:57 +05:00
Abdullah Atta
d248b8e2b8 web: properties & toc side bars must be below the titlebar 2024-04-30 21:51:57 +05:00
Abdullah Atta
641e7484e3 desktop: add custom dns toggle in settings 2024-04-30 21:51:35 +05:00
Abdullah Atta
a46409ff44 core: add more fts trigger tests 2024-04-30 21:51:17 +05:00
Abdullah Atta
5dbabc2706 core: fix database disk image is malformed error on updating deleted content 2024-04-30 21:51:17 +05:00
Abdullah Atta
2d4d656dd9 web: canceling backup on password change should abort 2024-04-30 21:50:48 +05:00
Abdullah Atta
ab51b22e4a web: do not ask for password when downloading backup during recovery 2024-04-30 21:50:32 +05:00
Abdullah Atta
781d09daca web: fix navigation menu resize issues 2024-04-30 21:50:21 +05:00
Abdullah Atta
f21533e5e4 core: fix sql collection only getting first batch on iteration 2024-04-30 21:50:00 +05:00
Abdullah Atta
2ea9b04863 web: fix toggles in properties not working 2024-04-30 21:49:37 +05:00
Abdullah Atta
675d517cbc web: reduce zoom factor step to 0.1 2024-04-30 21:49:13 +05:00
Abdullah Atta
c336c57bda web: disable pwa 2024-04-30 21:48:28 +05:00
Abdullah Atta
7b5fa09c71 web: fix all users are premium 2024-04-30 13:15:54 +05:00
Ammar Ahmed
d85974ed7a mobile: fix editor crash in v3 2024-04-30 12:09:15 +05:00
Ammar Ahmed
9598ea7542 mobile: fix wrong version in update sheet 2024-04-30 10:10:17 +05:00
Ammar Ahmed
42048ad53a mobile: fix multi-selection ux 2024-04-30 00:42:27 +05:00
Ammar Ahmed
1fbd2efb92 mobile: fix crash during backup 2024-04-30 00:17:25 +05:00
114 changed files with 1354 additions and 1224 deletions

View File

@@ -1,69 +0,0 @@
name: Publish @notesnook/web Beta
on:
workflow_dispatch:
push:
branches:
- master
- "!v3-beta"
paths-ignore:
- "apps/mobile/**"
- "packages/editor-mobile/**"
- "apps/desktop/**"
- "apps/vericrypt/**"
- "servers/**"
- "packages/clipper/**"
- "extensions/web-clipper/**"
pull_request:
types:
- closed
- ready_for_review
- reopened
- opened
- synchronize
branches:
- master
- "!v3-beta"
paths-ignore:
- "apps/mobile/**"
- "packages/editor-mobile/**"
- "apps/desktop/**"
- "apps/vericrypt/**"
- "servers/**"
- "packages/clipper/**"
- "extensions/web-clipper/**"
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=web
- name: Setup environment
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
- name: Generate beta build
run: npm run build:beta:web
- name: Publish to Cloudflare Pages
uses: unlike-ltd/github-actions-cloudflare-pages@v0.1.1
id: pages
with:
cloudflare-api-token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
cloudflare-account-id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
cloudflare-project-name: notesnook-beta-app
directory: ./apps/web/build
github-token: ${{ secrets.GITHUB_TOKEN }}
github-environment: ${{ (github.ref == 'refs/heads/master' && 'beta') || 'preview' }}

View File

@@ -1,32 +0,0 @@
name: Publish @notesnook/web v3 Beta
on:
workflow_dispatch:
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Node
uses: ./.github/actions/setup-node-with-cache
- name: Install packages
run: |
npm ci --ignore-scripts --prefer-offline --no-audit
npm run bootstrap -- --scope=web
- name: Setup environment
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
echo "CLOUDFLARE_ACCOUNT_ID=${{ secrets.CLOUDFLARE_ACCOUNT_ID }}" >> $GITHUB_ENV
echo "CLOUDFLARE_API_TOKEN=${{ secrets.CLOUDFLARE_API_TOKEN }}" >> $GITHUB_ENV
- name: Build
run: npm run build:web
- name: Publish to Cloudflare Pages
run: npx --yes wrangler pages deploy --project-name=notesnook-v3-beta ./apps/web/build/

Binary file not shown.

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/desktop",
"version": "3.0.0",
"version": "3.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "3.0.0",
"version": "3.0.1",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "3.0.0",
"version": "3.0.1",
"appAppleId": "1544027013",
"private": true,
"main": "./dist/index.js",

View File

@@ -32,6 +32,7 @@ import { AssetManager } from "../utils/asset-manager";
import { isFlatpak } from "../utils";
import { setupDesktopIntegration } from "../utils/desktop-integration";
import { rm } from "fs/promises";
import { disableCustomDns, enableCustomDns } from "../utils/custom-dns";
const t = initTRPC.create();
@@ -55,6 +56,15 @@ export const osIntegrationRouter = t.router({
config.zoomFactor = factor;
}),
customDns: t.procedure.query(() => config.customDns),
setCustomDns: t.procedure
.input(z.boolean())
.mutation(({ input: customDns }) => {
if (customDns) enableCustomDns();
else disableCustomDns();
config.customDns = customDns;
}),
proxyRules: t.procedure.query(() => config.proxyRules),
setProxyRules: t.procedure
.input(z.string().optional())

View File

@@ -35,6 +35,7 @@ import path from "path";
import { bringToFront } from "./utils/bring-to-front";
import { bridge } from "./api/bridge";
import { setupDesktopIntegration } from "./utils/desktop-integration";
import { disableCustomDns, enableCustomDns } from "./utils/custom-dns";
// only run a single instance
if (!MAC_APP_STORE && !app.requestSingleInstanceLock()) {
@@ -151,14 +152,8 @@ async function createWindow() {
app.once("ready", async () => {
console.info("App ready. Opening window.");
app.configureHostResolver({
secureDnsServers: [
"https://mozilla.cloudflare-dns.com/dns-query",
"https://dns.quad9.net/dns-query"
],
enableBuiltInResolver: true,
secureDnsMode: "automatic"
});
if (config.customDns) enableCustomDns();
else disableCustomDns();
if (!isDevelopment()) registerProtocol();
await createWindow();

View File

@@ -43,6 +43,7 @@ export const config = {
theme: nativeTheme.themeSource,
automaticUpdates: true,
proxyRules: "",
customDns: true,
backgroundColor: nativeTheme.themeSource === "dark" ? "#0f0f0f" : "#ffffff",
windowControlsIconColor:

View File

@@ -0,0 +1,37 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { app } from "electron";
export function enableCustomDns() {
app.configureHostResolver({
secureDnsServers: [
"https://mozilla.cloudflare-dns.com/dns-query",
"https://dns.quad9.net/dns-query"
],
enableBuiltInResolver: true
});
}
export function disableCustomDns() {
app.configureHostResolver({
secureDnsServers: [],
enableBuiltInResolver: true
});
}

View File

@@ -218,12 +218,7 @@ export default async function downloadAttachment(
}
if (options.base64 || options.text) {
console.log(
"starting decrypt base64 file...",
options.base64,
options.text,
attachment.hash
);
DatabaseLogger.log(`Starting to decrypt... hash: ${attachment.hash}`);
return await db.attachments.read(
attachment.hash,
options.base64 ? "base64" : "text"
@@ -292,7 +287,7 @@ export default async function downloadAttachment(
return fileUri;
} catch (e) {
console.log("download attachment error: ", e);
DatabaseLogger.error(e);
if (attachment.dateUploaded) {
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}`)

View File

@@ -24,7 +24,7 @@ import {
setAppLockVerificationCipher,
validateAppLockPassword
} from "../../../common/database/encryption";
import BiometicService from "../../../services/biometrics";
import BiometricService from "../../../services/biometrics";
import { DDS } from "../../../services/device-detection";
import {
ToastManager,
@@ -323,7 +323,7 @@ export const AppLockPassword = () => {
SettingsService.setProperty("appLockHasPasswordSecurity", false);
if (
!(await BiometicService.isBiometryAvailable()) ||
!(await BiometricService.isBiometryAvailable()) ||
SettingsService.getProperty("biometricsAuthEnabled") === false
) {
SettingsService.setProperty("appLockEnabled", false);

View File

@@ -128,7 +128,7 @@ export class VaultDialog extends Component {
: this.state.copyNote
? "Unlock note to copy it. If biometrics are not working, you can enter device pin to unlock vault."
: this.state.goToEditor
? "Unlock note to open it in editor. If biometrics are not working, you can enter device pin to unlock vault."
? "Unlock note to open it in editor."
: "Enter vault password to unlock note. If biometrics are not working, you can enter device pin to unlock vault."
: "Enter vault password to lock note. If biometrics are not working, you can enter device pin to lock note.");
}

View File

@@ -33,6 +33,7 @@ import { eScrollEvent } from "../../utils/events";
import { LeftMenus } from "./left-menus";
import { RightMenus } from "./right-menus";
import { Title } from "./title";
import { useNavigation } from "@react-navigation/native";
type HeaderRightButton = {
title: string;
@@ -66,6 +67,8 @@ export const Header = ({
hasSearch?: boolean;
onSearch?: () => void;
}) => {
const navigation = useNavigation();
const { colors } = useThemeColors();
const insets = useGlobalSafeAreaInsets();
const [borderHidden, setBorderHidden] = useState(true);

View File

@@ -19,6 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { View } from "react-native";
import React from "react";
export const Footer = () => {
return <View style={{ height: 150 }} />;
export const Footer = ({ height = 150 }) => {
return <View style={{ height: height }} />;
};

View File

@@ -95,7 +95,7 @@ export const NotebookHeader = ({
style={{
flexShrink: 1
}}
size={SIZE.xxl}
size={SIZE.lg}
>
{notebook.title}
</Heading>
@@ -114,7 +114,7 @@ export const NotebookHeader = ({
width: 40,
height: 40
}}
type={isPinnedToMenu ? "secondary" : "secondary"}
type="transparent"
color={isPinnedToMenu ? colors.primary.accent : colors.primary.icon}
size={SIZE.lg}
/>
@@ -123,7 +123,7 @@ export const NotebookHeader = ({
onPress={onEditNotebook}
tooltipText="Edit this notebook"
name="pencil"
type="secondary"
type="transparent"
color={colors.primary.icon}
style={{
width: 40,
@@ -134,7 +134,7 @@ export const NotebookHeader = ({
</View>
{notebook.description ? (
<Paragraph size={SIZE.md} color={colors.primary.paragraph}>
<Paragraph size={SIZE.sm} color={colors.primary.paragraph}>
{notebook.description}
</Paragraph>
) : null}

View File

@@ -28,13 +28,12 @@ import {
hideSheet,
presentSheet
} from "../../../services/event-manager";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { eOnLoadNote, eShowMergeDialog } from "../../../utils/events";
import { tabBarRef } from "../../../utils/global-refs";
import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common";
import NotePreview from "../../note-history/preview";
import SelectionWrapper from "../selection-wrapper";
import SelectionWrapper, { selectItem } from "../selection-wrapper";
export const openNote = async (
item: Note,
@@ -47,21 +46,8 @@ export const openNote = async (
if (!isTrash) {
note = (await db.notes.note(item.id)) as Note;
}
if (useSelectionStore.getState().selectionMode === item.type) {
const {
selectedItemsList,
selectionMode,
clearSelection,
setSelectedItem
} = useSelectionStore.getState();
if (selectedItemsList.length > 0 && selectionMode === item.type) {
setSelectedItem(note.id);
} else {
clearSelection();
}
return;
}
if (selectItem(item)) return;
if (note.conflicted) {
eSendEvent(eShowMergeDialog, note);

View File

@@ -27,18 +27,12 @@ import Navigation from "../../../services/navigation";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { useTrashStore } from "../../../stores/use-trash-store";
import { presentDialog } from "../../dialog/functions";
import SelectionWrapper from "../selection-wrapper";
import SelectionWrapper, { selectItem } from "../selection-wrapper";
export const openNotebook = (item: Notebook | BaseTrashItem<Notebook>) => {
const isTrash = item.type === "trash";
const { selectedItemsList, setSelectedItem, selectionMode, clearSelection } =
useSelectionStore.getState();
if (selectedItemsList.length > 0 && selectionMode === item.type) {
setSelectedItem(item.id);
return;
} else {
clearSelection();
}
if (selectItem(item)) return;
if (isTrash) {
presentDialog({

View File

@@ -22,7 +22,6 @@ import React from "react";
import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../../e2e/test.ids";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { SIZE } from "../../../utils/size";
import { Properties } from "../../properties";
import ReminderSheet from "../../sheets/reminder";
@@ -30,7 +29,7 @@ import { IconButton } from "../../ui/icon-button";
import { ReminderTime } from "../../ui/reminder-time";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import SelectionWrapper from "../selection-wrapper";
import SelectionWrapper, { selectItem } from "../selection-wrapper";
const ReminderItem = React.memo(
({
@@ -44,18 +43,7 @@ const ReminderItem = React.memo(
}) => {
const { colors } = useThemeColors();
const openReminder = () => {
const {
selectedItemsList,
setSelectedItem,
selectionMode,
clearSelection
} = useSelectionStore.getState();
if (selectedItemsList.length > 0 && selectionMode === item.type) {
setSelectedItem(item.id);
return;
} else {
clearSelection();
}
if (selectItem(item)) return;
ReminderSheet.present(item, undefined, isSheet);
};

View File

@@ -26,6 +26,23 @@ import { Filler } from "./back-fill";
import { SelectionIcon } from "./selection";
import { Item, TrashItem } from "@notesnook/core";
export function selectItem(item: Item) {
if (useSelectionStore.getState().selectionMode === item.type) {
const { selectionMode, clearSelection, setSelectedItem } =
useSelectionStore.getState();
if (selectionMode === item.type) {
setSelectedItem(item.id);
}
if (useSelectionStore.getState().selectedItemsList.length === 0) {
clearSelection();
}
return true;
}
return false;
}
type SelectionWrapperProps = PropsWithChildren<{
item: Item;
onPress: () => void;

View File

@@ -23,13 +23,12 @@ import React from "react";
import { View } from "react-native";
import { notesnook } from "../../../../e2e/test.ids";
import { TaggedNotes } from "../../../screens/notes/tagged";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { SIZE } from "../../../utils/size";
import { Properties } from "../../properties";
import { IconButton } from "../../ui/icon-button";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import SelectionWrapper from "../selection-wrapper";
import SelectionWrapper, { selectItem } from "../selection-wrapper";
const TagItem = React.memo(
({
@@ -43,18 +42,7 @@ const TagItem = React.memo(
}) => {
const { colors } = useThemeColors();
const onPress = () => {
const {
selectedItemsList,
setSelectedItem,
selectionMode,
clearSelection
} = useSelectionStore.getState();
if (selectedItemsList.length > 0 && selectionMode === item.type) {
setSelectedItem(item.id);
return;
} else {
clearSelection();
}
if (selectItem(item)) return;
TaggedNotes.navigate(item, true);
};

View File

@@ -192,7 +192,9 @@ export default function List(props: ListProps) {
/>
) : null
}
ListFooterComponent={<Footer />}
ListFooterComponent={
<Footer height={props.renderedInRoute === "Notebook" ? 300 : 150} />
}
ListHeaderComponent={
<>
{props.CustomLisHeader ? (

View File

@@ -19,14 +19,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Item, ItemType, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect } from "react";
import React, { useEffect, useRef } from "react";
import {
BackHandler,
NativeEventSubscription,
Platform,
View
} from "react-native";
import Animated, { FadeInUp } from "react-native-reanimated";
import Menu from "react-native-reanimated-material-menu/src/Menu";
import { db } from "../../common/database";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { ToastManager } from "../../services/event-manager";
@@ -42,9 +42,10 @@ import { presentDialog } from "../dialog/functions";
import MoveNoteSheet from "../sheets/add-to";
import ExportNotesSheet from "../sheets/export-notes";
import ManageTagsSheet from "../sheets/manage-tags";
import { MoveNotebookSheet } from "../sheets/move-notebook";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import Heading from "../ui/typography/heading";
import { MoveNotebookSheet } from "../sheets/move-notebook";
export const SelectionHeader = React.memo(
({
@@ -58,6 +59,8 @@ export const SelectionHeader = React.memo(
type?: ItemType;
renderedInRoute?: string;
}) => {
const menuRef = useRef<Menu>(null);
const { colors: contextMenuColors } = useThemeColors("contextMenu");
const { colors } = useThemeColors();
const selectionMode = useSelectionStore((state) => state.selectionMode);
const selectedItemsList = useSelectionStore(
@@ -132,8 +135,7 @@ export const SelectionHeader = React.memo(
}, [clearSelection, selectionMode]);
return selectionMode !== type || focusedRouteId !== id ? null : (
<Animated.View
entering={FadeInUp}
<View
style={{
width: "100%",
height: Platform.OS === "android" ? 50 + insets.top : 50,
@@ -143,8 +145,7 @@ export const SelectionHeader = React.memo(
alignItems: "center",
flexDirection: "row",
zIndex: 999,
paddingHorizontal: 12,
marginVertical: 10
paddingHorizontal: 12
}}
>
<View
@@ -164,18 +165,15 @@ export const SelectionHeader = React.memo(
borderRadius: 100,
marginRight: 10
}}
type="secondary"
onPress={() => {
clearSelection();
}}
size={SIZE.xl}
color={colors.primary.icon}
name="close"
/>
<View
style={{
backgroundColor: colors.secondary.background,
height: 40,
borderRadius: 100,
paddingHorizontal: 16,
@@ -184,11 +182,12 @@ export const SelectionHeader = React.memo(
alignItems: "center"
}}
>
<Heading size={SIZE.md} color={colors.primary.accent}>
<Heading size={SIZE.lg} color={colors.primary.paragraph}>
{selectedItemsList.length}
</Heading>
</View>
</View>
<View
style={{
flexDirection: "row",
@@ -211,161 +210,142 @@ export const SelectionHeader = React.memo(
allSelected ? colors.primary.accent : colors.primary.paragraph
}
name="select-all"
size={SIZE.xl}
/>
{renderedInRoute === "Notebooks" ? (
<IconButton
{selectedItemsList.length ? (
<Menu
ref={menuRef}
animationDuration={200}
style={{
marginLeft: 10
borderRadius: 5,
backgroundColor: contextMenuColors.primary.background,
marginTop: -20
}}
onPress={async () => {
const ids = selectedItemsList;
const notebooks = await db.notebooks.all.items(ids);
MoveNotebookSheet.present(notebooks);
onRequestClose={() => {
menuRef.current?.hide();
}}
tooltipText="Move notebooks"
tooltipPosition={1}
name="arrow-right-bold-box-outline"
size={SIZE.xl}
/>
) : null}
{type !== "note" ? null : (
<>
<IconButton
onPress={async () => {
await sleep(100);
ManageTagsSheet.present(selectedItemsList);
}}
style={{
marginLeft: 10
}}
color={colors.primary.icon}
tooltipText="Manage tags"
tooltipPosition={4}
name="pound"
size={SIZE.xl}
/>
<IconButton
onPress={async () => {
ExportNotesSheet.present(selectedItemsList);
}}
tooltipText="Export"
tooltipPosition={4}
style={{
marginLeft: 10
}}
color={colors.primary.paragraph}
name="export"
size={SIZE.xl}
/>
<IconButton
onPress={async () => {
MoveNoteSheet.present();
}}
style={{
marginLeft: 10
}}
tooltipText="Add to notebooks"
tooltipPosition={4}
color={colors.primary.paragraph}
name="plus"
size={SIZE.xl}
/>
</>
)}
{renderedInRoute === "Notebook" ? (
<IconButton
onPress={async () => {
if (selectedItemsList.length > 0) {
if (!id) return;
await db.notes.removeFromNotebook(id, ...selectedItemsList);
updateNotebook(id);
Navigation.queueRoutesForUpdate();
clearSelection();
anchor={
<IconButton
onPress={() => {
menuRef.current?.show();
}}
name="dots-vertical"
color={colors.primary.paragraph}
/>
}
>
{[
{
title:
selectedItemsList.length > 1
? "Move notebooks"
: "Move notebook",
onPress: async () => {
const ids = selectedItemsList;
const notebooks = await db.notebooks.all.items(ids);
MoveNotebookSheet.present(notebooks);
},
visible: renderedInRoute === "Notebooks",
icon: "arrow-right-bold-box-outline"
},
{
title: "Manage tags",
onPress: async () => {
await sleep(100);
ManageTagsSheet.present(selectedItemsList);
},
visible: type === "note",
icon: "pound"
},
{
title: "Export",
onPress: async () => {
await sleep(100);
ExportNotesSheet.present(selectedItemsList);
},
visible: type === "note",
icon: "export"
},
{
title: "Link notebook",
onPress: async () => {
await sleep(100);
MoveNoteSheet.present();
},
visible: type === "note",
icon: "plus"
},
{
title: "Unlink notebook",
onPress: async () => {
if (!id) return;
await db.notes.removeFromNotebook(id, ...selectedItemsList);
updateNotebook(id);
Navigation.queueRoutesForUpdate();
clearSelection();
},
visible: renderedInRoute === "Notebook",
icon: "minus"
},
{
title: "Unfavorite",
onPress: addToFavorite,
visible: focusedRouteId === "Favorites",
icon: "star-off"
},
{
title: `Move to trash`,
onPress: async () => {
deleteItems(
undefined,
useSelectionStore.getState().selectionMode
).then(() => {
useSelectionStore.getState().clearSelection();
useSelectionStore.getState().setSelectionMode(undefined);
});
},
visible: type !== "trash",
icon: "delete"
},
{
title: `Restore`,
onPress: restoreItem,
visible: type === "trash",
icon: "delete-restore"
},
{
title: `Delete`,
onPress: deleteItem,
visible: type === "trash",
icon: "delete"
}
}}
style={{
marginLeft: 10
}}
tooltipText={`Remove from Notebook`}
tooltipPosition={4}
testID="select-minus"
color={colors.primary.paragraph}
name="minus"
size={SIZE.xl}
/>
) : null}
{focusedRouteId === "Favorites" ? (
<IconButton
onPress={addToFavorite}
style={{
marginLeft: 10
}}
tooltipText="Remove from favorites"
tooltipPosition={4}
color={colors.primary.paragraph}
name="star-off"
size={SIZE.xl}
/>
) : null}
{type === "trash" ? null : (
<IconButton
style={{
marginLeft: 10
}}
onPress={() => {
deleteItems(
undefined,
useSelectionStore.getState().selectionMode
).then(() => {
useSelectionStore.getState().clearSelection();
useSelectionStore.getState().setSelectionMode(undefined);
});
}}
tooltipText="Move to trash"
tooltipPosition={1}
color={colors.primary.paragraph}
name="delete"
size={SIZE.xl}
/>
)}
{type === "trash" ? (
<>
<IconButton
style={{
marginLeft: 10
}}
color={colors.primary.paragraph}
onPress={restoreItem}
name="delete-restore"
tooltipText="Restore"
tooltipPosition={4}
size={SIZE.xl - 3}
/>
<IconButton
style={{
marginLeft: 10
}}
color={colors.primary.paragraph}
onPress={deleteItem}
tooltipText="Delete"
tooltipPosition={4}
name="delete"
size={SIZE.xl - 3}
/>
</>
].map((item) =>
!item.visible ? null : (
<Button
style={{
width: 150,
justifyContent: "flex-start",
borderRadius: 0
}}
type="plain"
buttonType={{
text: contextMenuColors.primary.paragraph
}}
icon={item.icon}
key={item.title}
title={item.title}
onPress={async () => {
menuRef.current?.hide();
if (Platform.OS === "ios") await sleep(300);
item.onPress();
}}
/>
)
)}
</Menu>
) : null}
</View>
</Animated.View>
</View>
);
}
);

View File

@@ -45,14 +45,16 @@ export const AddNotebookSheet = ({
notebook,
parentNotebook,
close,
showMoveNotesOnComplete
showMoveNotesOnComplete,
defaultTitle
}: {
notebook?: Notebook;
parentNotebook?: Notebook;
close?: (didAddNotebook: boolean) => void;
showMoveNotesOnComplete: boolean;
defaultTitle?: string;
}) => {
const title = useRef(notebook?.title);
const title = useRef(notebook?.title || defaultTitle);
const description = useRef(notebook?.description);
const titleInput = useRef<TextInput>(null);
const descriptionInput = useRef<TextInput>(null);
@@ -139,7 +141,7 @@ export const AddNotebookSheet = ({
}}
returnKeyLabel="Next"
returnKeyType="next"
defaultValue={notebook ? notebook.title : ""}
defaultValue={notebook ? notebook.title : title.current}
/>
<Input
@@ -174,7 +176,8 @@ AddNotebookSheet.present = (
parentNotebook?: Notebook,
context?: string,
onClose?: (didAddNotebook: boolean) => void,
showMoveNotesOnComplete = true
showMoveNotesOnComplete = true,
defaultTitle?: string
) => {
presentSheet({
context: context,
@@ -187,6 +190,7 @@ AddNotebookSheet.present = (
onClose?.(didAddNotebook);
}}
showMoveNotesOnComplete={showMoveNotesOnComplete || false}
defaultTitle={defaultTitle}
/>
)
});

View File

@@ -19,7 +19,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Note } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { RefObject, useCallback, useEffect } from "react";
import React, {
RefObject,
useCallback,
useEffect,
useRef,
useState
} from "react";
import {
ActivityIndicator,
Keyboard,
@@ -47,6 +53,8 @@ import Paragraph from "../../ui/typography/paragraph";
import { NotebookItem } from "./notebook-item";
import { useNotebookItemSelectionStore } from "./store";
import { AddNotebookSheet } from "../add-notebook";
import Input from "../../ui/input";
import { presentDialog } from "../../dialog/functions";
async function updateInitialSelectionState(items: string[]) {
const relations = await db.relations
@@ -93,7 +101,11 @@ const MoveNoteSheet = ({
actionSheetRef: RefObject<ActionSheetRef>;
}) => {
const { colors } = useThemeColors();
const [notebooks, loading] = useNotebooks();
const [rootNotebooks, loading] = useNotebooks();
const searchQuery = useRef("");
const searchTimer = useRef<NodeJS.Timeout>();
const [notebooks, setNotebooks] = useState(rootNotebooks);
const dimensions = useSettingStore((state) => state.dimensions);
const selectedItemsList = useSelectionStore(
(state) => state.selectedItemsList
@@ -103,6 +115,12 @@ const MoveNoteSheet = ({
(state) => state.multiSelect
);
useEffect(() => {
if (!loading) {
setNotebooks(rootNotebooks);
}
}, [loading, rootNotebooks]);
useEffect(() => {
const items = note ? [note.id] : selectedItemsList;
updateInitialSelectionState(items);
@@ -199,7 +217,6 @@ const MoveNoteSheet = ({
{hasSelected() ? (
<IconButton
name="restore"
type="secondaryAccented"
color={colors.primary.icon}
onPress={() => {
const items = note ? [note.id] : selectedItemsList;
@@ -239,33 +256,48 @@ const MoveNoteSheet = ({
style={{
width: "100%"
}}
keyboardShouldPersistTaps="handled"
ListHeaderComponent={
<View
style={{
paddingHorizontal: 12,
width: "100%"
width: "100%",
paddingTop: 12
}}
>
<Button
title="Add new notebook"
style={{
alignSelf: "flex-start",
paddingHorizontal: 12,
justifyContent: "space-between"
<Input
placeholder="Search notebooks...."
button={{
icon: "plus",
onPress: () => {
AddNotebookSheet.present(
undefined,
undefined,
"link-notebooks",
undefined,
false,
searchQuery.current
);
},
color: colors.primary.icon
}}
onPress={() => {
AddNotebookSheet.present(
undefined,
undefined,
"link-notebooks",
undefined,
false
);
onChangeText={(value) => {
searchQuery.current = value;
if (!searchQuery.current) {
setNotebooks(rootNotebooks);
return;
}
searchTimer.current = setTimeout(() => {
db.lookup
.notebooks(searchQuery.current)
.sorted()
.then((result) => {
if (searchQuery.current === value) {
setNotebooks(result);
}
});
}, 300);
}}
icon="plus"
iconPosition="right"
type="secondaryAccented"
width="100%"
/>
</View>
}

View File

@@ -219,7 +219,7 @@ export default function LinkNote(props: {
<View
style={{
paddingHorizontal: 12,
minHeight: 400,
minHeight: "100%",
maxHeight: "100%"
}}
>
@@ -311,6 +311,7 @@ export default function LinkNote(props: {
style={{
marginTop: 10
}}
keyboardShouldPersistTaps="handled"
windowSize={3}
keyExtractor={(item) => item.id}
data={nodes}
@@ -324,6 +325,7 @@ export default function LinkNote(props: {
onSelectNote={onSelectNote}
/>
)}
keyboardShouldPersistTaps="handled"
style={{
marginTop: 10
}}

View File

@@ -99,6 +99,7 @@ export default function Migrate() {
const { error } = await BackupService.run(false, "local");
if (error) {
ToastManager.error(error, "Backup failed");
reportError(error);
setLoading(false);
return;
}

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Notebook, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback } from "react";
import React, { useCallback, useEffect, useState } from "react";
import { Text, View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import create from "zustand";
@@ -72,6 +72,20 @@ export const MoveNotebookSheet = ({
}) => {
const [notebooks] = useNotebooks();
const { colors } = useThemeColors();
const [moveToTop, setMoveToTop] = useState(false);
useEffect(() => {
(async () => {
for (const notebook of selectedNotebooks) {
const root = await findRootNotebookId(notebook.id);
if (root !== notebook.id) {
setMoveToTop(true);
return;
}
}
})();
}, [selectedNotebooks]);
const renderItem = useCallback(
({ index }: { index: number }) => {
return (
@@ -93,6 +107,7 @@ export const MoveNotebookSheet = ({
: selectedNotebooks[0].title
} to ${selectedNotebook.title}?`,
positiveText: "Move",
context: "move-notebook",
positivePress: async () => {
for (const notebook of selectedNotebooks) {
const parent = await getParentNotebookId(notebook.id);
@@ -157,36 +172,38 @@ export const MoveNotebookSheet = ({
paddingHorizontal: 12
}}
>
<Button
title="Move to top"
style={{
alignSelf: "flex-start",
width: "100%",
justifyContent: "space-between"
}}
icon="arrow-up-bold"
iconPosition="right"
type="secondaryAccented"
onPress={async () => {
for (const notebook of selectedNotebooks) {
const parent = await getParentNotebookId(notebook.id);
const root = await findRootNotebookId(notebook.id);
if (root !== notebook.id) {
await db.relations.unlink(
{
type: "notebook",
id: parent
},
notebook
);
eSendEvent(eOnNotebookUpdated, parent);
eSendEvent(eOnNotebookUpdated, notebook.id);
{moveToTop ? (
<Button
title="Move to top"
style={{
alignSelf: "flex-start",
width: "100%",
justifyContent: "space-between"
}}
icon="arrow-up-bold"
iconPosition="right"
type="secondaryAccented"
onPress={async () => {
for (const notebook of selectedNotebooks) {
const parent = await getParentNotebookId(notebook.id);
const root = await findRootNotebookId(notebook.id);
if (root !== notebook.id) {
await db.relations.unlink(
{
type: "notebook",
id: parent
},
notebook
);
eSendEvent(eOnNotebookUpdated, parent);
eSendEvent(eOnNotebookUpdated, notebook.id);
}
}
}
useNotebookStore.getState().refresh();
close?.();
}}
/>
useNotebookStore.getState().refresh();
close?.();
}}
/>
) : null}
</View>
}
ListEmptyComponent={

View File

@@ -118,7 +118,12 @@ NewFeature.present = () => {
);
if (_features.length === 0) return;
presentSheet({
component: <NewFeature features={features} version={version} />,
component: (
<NewFeature
features={features}
version={SettingsService.getProperty("version")}
/>
),
disableClosing: true
});
return true;

View File

@@ -19,13 +19,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Notebook, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import { RefreshControl, View, useWindowDimensions } from "react-native";
import {
Platform,
RefreshControl,
View,
useWindowDimensions
} from "react-native";
import ActionSheet, { ActionSheetRef } from "react-native-actions-sheet";
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
import Config from "react-native-config";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import create from "zustand";
import { notesnook } from "../../../../e2e/test.ids";
import { db } from "../../../common/database";
import { MMKV } from "../../../common/database/mmkv";
import { useNotebook } from "../../../hooks/use-notebook";
import NotebookScreen from "../../../screens/notebook";
@@ -43,9 +49,8 @@ import { IconButton } from "../../ui/icon-button";
import { Pressable } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
import { AddNotebookSheet } from "../add-notebook";
import Sort from "../sort";
import { MoveNotebookSheet } from "../move-notebook";
import { db } from "../../../common/database";
import Sort from "../sort";
const useItemSelectionStore = createItemSelectionStore(true, false);
@@ -63,7 +68,8 @@ class NotebookSheetConfig {
}
static get(item: ConfigItem) {
return MMKV.getInt(NotebookSheetConfig.makeId(item)) || 0;
const value = MMKV.getInt(NotebookSheetConfig.makeId(item));
return typeof value === "number" ? value : 0;
}
static set(item: ConfigItem, index = 0) {
@@ -126,11 +132,6 @@ export const NotebookSheet = () => {
if (!focusedRouteId) return;
const nextRoot = await findRootNotebookId(focusedRouteId);
if (nextRoot !== currentItem.current) {
console.log(
"NotebookSheet.useEffect.canShow",
"Root changed to",
nextRoot
);
useItemSelectionStore.setState({
enabled: false,
selection: {}
@@ -145,11 +146,8 @@ export const NotebookSheet = () => {
if (ref.current?.isOpen()) {
ref.current?.snapToIndex(snapPoint);
} else {
setTimeout(() => {
ref.current?.show(snapPoint);
}, 150);
ref.current?.show(snapPoint);
}
console.log("NotebookSheet.useEffect.didShow", focusedRouteId);
setRoot(nextRoot);
onRequestUpdate();
});
@@ -162,7 +160,7 @@ export const NotebookSheet = () => {
ref.current?.hide();
}
}
}, [canShow, onRequestUpdate, focusedRouteId]);
}, [canShow, focusedRouteId]);
return (
<ActionSheet
@@ -198,7 +196,11 @@ export const NotebookSheet = () => {
backgroundColor: colors.secondary.background
}}
keyboardHandlerEnabled={false}
snapPoints={Config.isTesting === "true" ? [100] : [20, 100]}
snapPoints={
Config.isTesting === "true"
? [100]
: [Platform.OS === "android" ? 15 : 10, 100]
}
initialSnapIndex={1}
backgroundInteractionEnabled
gestureEnabled
@@ -237,7 +239,7 @@ export const NotebookSheet = () => {
>
<Icon
name="notebook-plus"
color={colors.primary.accent}
color={colors.primary.icon}
size={SIZE.xxl}
/>
</View>

View File

@@ -18,7 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useThemeColors } from "@notesnook/theme";
import React, { RefObject, useRef, useState } from "react";
import { Platform, TextInput, View } from "react-native";
import {
Platform,
TextInput,
View,
ScrollView as RNScrollView
} from "react-native";
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
import DateTimePickerModal from "react-native-modal-datetime-picker";
import {
@@ -364,7 +369,7 @@ export default function ReminderSheet({
))}
</View>
<ScrollView showsHorizontalScrollIndicator={false} horizontal>
<RNScrollView showsHorizontalScrollIndicator={false} horizontal>
{recurringMode === RecurringModes.Daily ||
recurringMode === RecurringModes.Year
? null
@@ -425,7 +430,7 @@ export default function ReminderSheet({
}}
/>
))}
</ScrollView>
</RNScrollView>
</View>
) : null}

View File

@@ -50,7 +50,7 @@ const Sort = ({ type, screen }) => {
const setOrderBy = async () => {
let _groupOptions = {
...groupOptions,
sortDirection: groupOptions.sortDirection === "asc" ? "desc" : "asc"
sortDirection: groupOptions?.sortDirection === "asc" ? "desc" : "asc"
};
if (type === "topics") {
_groupOptions.groupBy = "none";
@@ -85,7 +85,7 @@ const Sort = ({ type, screen }) => {
<Button
title={
groupOptions.sortDirection === "asc"
groupOptions?.sortDirection === "asc"
? groupOptions.groupBy === "abc" ||
groupOptions.sortBy === "title"
? "A - Z"
@@ -100,7 +100,7 @@ const Sort = ({ type, screen }) => {
: "New - Old"
}
icon={
groupOptions.sortDirection === "asc"
groupOptions?.sortDirection === "asc"
? "sort-ascending"
: "sort-descending"
}
@@ -229,7 +229,7 @@ const Sort = ({ type, screen }) => {
if (item === "abc") {
_groupOptions.sortBy = "title";
_groupOptions.sortDirection = "asc";
_groupOptions.sortDirection = "desc";
}
updateGroupOptions(_groupOptions);

View File

@@ -21,15 +21,11 @@ import { useThemeColors } from "@notesnook/theme";
import React, { useCallback } from "react";
import { View } from "react-native";
import { DraxProvider, DraxScrollView } from "react-native-drax";
import { notesnook } from "../../../e2e/test.ids";
import { db } from "../../common/database";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { DDS } from "../../services/device-detection";
import { eSendEvent } from "../../services/event-manager";
import Navigation from "../../services/navigation";
import { useMenuStore } from "../../stores/use-menu-store";
import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { useUserStore } from "../../stores/use-user-store";
import { SUBSCRIPTION_STATUS } from "../../utils/constants";
import { eOpenPremiumDialog } from "../../utils/events";
@@ -59,26 +55,6 @@ export const SideMenu = React.memo(
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const BottomItemsList = [
{
name: isDark ? "Day" : "Night",
icon: "theme-light-dark",
func: () => {
useThemeStore.getState().setColorScheme();
},
switch: true,
on: !!isDark,
close: false
},
{
name: "Settings",
icon: "cog-outline",
close: true,
func: () => {
Navigation.navigate("Settings");
}
}
];
const pro = {
name: "Notesnook Pro",
@@ -121,7 +97,7 @@ export const SideMenu = React.memo(
<PinnedSection />
</>
),
[]
[order, hiddensItems]
);
return !isAppLoading && introCompleted ? (

View File

@@ -20,29 +20,25 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { useThemeColors } from "@notesnook/theme";
import { useNetInfo } from "@react-native-community/netinfo";
import React from "react";
import {
ActivityIndicator,
Image,
Platform,
TouchableOpacity,
View
} from "react-native";
import { ActivityIndicator, Image, Platform, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import useSyncProgress from "../../hooks/use-sync-progress";
import { eSendEvent } from "../../services/event-manager";
import Navigation from "../../services/navigation";
import Sync from "../../services/sync";
import { useThemeStore } from "../../stores/use-theme-store";
import { SyncStatus, useUserStore } from "../../stores/use-user-store";
import { eOpenLoginDialog } from "../../utils/events";
import { tabBarRef } from "../../utils/global-refs";
import { SIZE } from "../../utils/size";
import { IconButton } from "../ui/icon-button";
import { Pressable } from "../ui/pressable";
import { TimeSince } from "../ui/time-since";
import Paragraph from "../ui/typography/paragraph";
import Navigation from "../../services/navigation";
export const UserStatus = () => {
const { colors } = useThemeColors();
const { colors, isDark } = useThemeColors();
const user = useUserStore((state) => state.user);
const syncing = useUserStore((state) => state.syncing);
const lastSyncStatus = useUserStore((state) => state.lastSyncStatus);
@@ -97,7 +93,16 @@ export const UserStatus = () => {
borderRadius: 100
}}
/>
) : null}
) : (
<Icon
name="cog-outline"
size={SIZE.lg - 2}
color={colors.secondary.icon}
style={{
paddingLeft: 8
}}
/>
)}
<View
style={{
@@ -110,14 +115,8 @@ export const UserStatus = () => {
size={SIZE.sm}
color={colors.primary.heading}
>
{!user
? "Login to sync your notes."
: lastSyncStatus === SyncStatus.Failed
? "Sync failed, tap to retry"
: syncing
? `Syncing ${progress ? `(${progress.current})` : ""}`
: !userProfile?.fullName
? "Tap to sync"
{!user || !userProfile?.fullName
? "Settings"
: userProfile.fullName}
</Paragraph>
@@ -132,16 +131,20 @@ export const UserStatus = () => {
"Not logged in"
) : lastSynced && lastSynced !== "Never" ? (
<>
{lastSyncStatus === SyncStatus.Failed
{syncing
? `Syncing ${progress ? `(${progress.current})` : ""}`
: lastSyncStatus === SyncStatus.Failed
? "Sync failed"
: "Synced"}{" "}
<TimeSince
style={{
fontSize: SIZE.xs,
color: colors.secondary.paragraph
}}
time={lastSynced}
/>
{!syncing ? (
<TimeSince
style={{
fontSize: SIZE.xs,
color: colors.secondary.paragraph
}}
time={lastSynced}
/>
) : null}
{isOffline ? " (offline)" : ""}
</>
) : (
@@ -149,7 +152,7 @@ export const UserStatus = () => {
)}{" "}
<Icon
name="checkbox-blank-circle"
size={11}
size={9}
allowFontScaling
color={
!user || lastSyncStatus === SyncStatus.Failed
@@ -162,56 +165,80 @@ export const UserStatus = () => {
</Paragraph>
</View>
<Pressable
<View
style={{
borderRadius: 100,
width: 40,
height: 40
}}
hitSlop={{
top: 10,
bottom: 10,
left: 20
}}
onPress={() => {
if (user) {
Sync.run();
} else {
tabBarRef.current?.closeDrawer();
eSendEvent(eOpenLoginDialog);
}
flexDirection: "row",
gap: 0
}}
>
{user ? (
syncing ? (
<ActivityIndicator
color={colors.primary.accent}
size={SIZE.xl}
/>
) : lastSyncStatus === SyncStatus.Failed ? (
<Icon
color={colors.error.icon}
name="sync-alert"
size={SIZE.lg}
allowFontScaling
/>
<IconButton
hitSlop={{
top: 10,
bottom: 10
}}
onPress={() => {
useThemeStore.getState().setColorScheme();
}}
name="theme-light-dark"
color={isDark ? colors.primary.accent : colors.primary.icon}
size={SIZE.lg}
style={{
borderRadius: 100,
width: 40,
height: 40
}}
/>
<Pressable
style={{
borderRadius: 100,
width: 40,
height: 40
}}
hitSlop={{
top: 10,
bottom: 10
}}
onPress={() => {
if (user) {
Sync.run();
} else {
tabBarRef.current?.closeDrawer();
eSendEvent(eOpenLoginDialog);
}
}}
>
{user ? (
syncing ? (
<ActivityIndicator
color={colors.primary.accent}
size={SIZE.xl}
/>
) : lastSyncStatus === SyncStatus.Failed ? (
<Icon
color={colors.error.icon}
name="sync-alert"
size={SIZE.lg}
allowFontScaling
/>
) : (
<Icon
allowFontScaling
color={colors.primary.icon}
name="sync"
size={SIZE.lg}
/>
)
) : (
<Icon
allowFontScaling
color={colors.primary.accent}
name="sync"
size={SIZE.lg}
name="login"
/>
)
) : (
<Icon
allowFontScaling
color={colors.primary.accent}
size={SIZE.lg}
name="login"
/>
)}
</Pressable>
)}
</Pressable>
</View>
</Pressable>
</View>
</View>

View File

@@ -59,7 +59,7 @@ export interface TabsRef {
lock: () => boolean;
openDrawer: (animated?: boolean) => void;
closeDrawer: (animated?: boolean) => void;
page: number;
page: () => number;
setScrollEnabled: () => true;
isDrawerOpen: () => boolean;
node: RefObject<Animated.View>;
@@ -239,7 +239,7 @@ export const FluidTabs = forwardRef<TabsRef, TabProps>(function FluidTabs(
onDrawerStateChange(false);
isDrawerOpen.value = false;
},
page: currentTab.value,
page: () => currentTab.value,
setScrollEnabled: () => true,
node: node
}),

View File

@@ -19,35 +19,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { FeatureType } from "./components/sheets/new-feature";
export const features: FeatureType[] = [
{
title: "Bi-directional note linking",
body: "Now you can link notes to each other in both directions."
},
{
title: "Tabs",
body: "Tabs allow you to have multiple notes open at the same time. You can switch between them easily."
},
{
title: "Nested notebooks",
body: "You can now create notebooks inside other notebooks."
},
{
title: "At-rest encryption",
body: "Your notes are now encrypted when stored on the device."
},
{
title: "Material You themed icon",
body: "The app now has a Material You themed icon that changes color based on the wallpaper.",
platform: "android"
},
{
title: "New note quick settings tile",
body: "You can now add a new note tile to quickly create notes from quick settings panel.",
platform: "android"
},
{
title: "And so much more...",
body: "V3 packs so much that it's hard to put everything here, check out our blog at Notesnook V3 is here, and it's packed with new features and improvements such as bi-directional note linking, better syncing, at-rest encryption, nested notebooks, editor tabs, better app lock and so much more. https://blog.notesnook.com/introducing-notesnook-v3."
}
];
export const features: FeatureType[] = [];

View File

@@ -85,7 +85,6 @@ import { SyncStatus, useUserStore } from "../stores/use-user-store";
import { updateStatusBarColor } from "../utils/colors";
import { BETA } from "../utils/constants";
import {
eClearEditor,
eCloseSheet,
eLoginSessionExpired,
eOnLoadNote,
@@ -532,12 +531,6 @@ export const useAppEvents = () => {
//@ts-ignore
globalThis["IS_SHARE_EXTENSION"] = false;
} else {
const id = useTabStore.getState().getCurrentNoteId();
const note = id ? await db.notes.note(id) : undefined;
const locked = note && (await db.vaults.itemExists(note));
if (locked && SettingsService.canLockAppInBackground()) {
eSendEvent(eClearEditor);
}
await saveEditorState();
if (
SettingsService.canLockAppInBackground() &&

View File

@@ -71,12 +71,10 @@ export const useDBItem = <T extends keyof ItemTypeKey>(
if (typeof itemId === "string" && itemId !== itemIdRef.current) return;
if (!isValidIdOrIndex(idOrIndex)) return;
console.log("useDBItem.onUpdateItem", idOrIndex, type);
if (items && typeof idOrIndex === "number") {
items.item(idOrIndex).then((item) => {
setItem(item.item);
itemIdRef.current = item.item.id;
itemIdRef.current = item.item?.id;
});
} else {
if (!(db as any)[type + "s"][type]) {

View File

@@ -36,7 +36,6 @@ export const useNotebook = (
const onRequestUpdate = React.useCallback(() => {
if (!item?.id) return;
console.log("useNotebook.onRequestUpdate", item?.id, Date.now());
const selector = db.relations.from(
{
@@ -59,7 +58,6 @@ export const useNotebook = (
useEffect(() => {
if (nestedNotebooks) {
console.log("useNotebook.useEffect.onRequestUpdate");
onRequestUpdate();
}
}, [item?.id, onRequestUpdate, nestedNotebooks]);

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useCallback, useEffect } from "react";
import BiometicService from "../services/biometrics";
import BiometricService from "../services/biometrics";
import { eSubscribeEvent, eUnSubscribeEvent } from "../services/event-manager";
import { db } from "../common/database";
@@ -39,8 +39,8 @@ export const useVaultStatus = () => {
const checkVaultStatus = useCallback(() => {
db.vault?.exists().then(async (exists) => {
const available = await BiometicService.isBiometryAvailable();
const fingerprint = await BiometicService.hasInternetCredentials();
const available = await BiometricService.isBiometryAvailable();
const fingerprint = await BiometricService.hasInternetCredentials();
if (
VaultStatusCache.exists === exists &&
VaultStatusCache.biometryEnrolled === fingerprint &&

View File

@@ -65,6 +65,7 @@ import {
eClearEditor,
eCloseFullscreenEditor,
eOnEnterEditor,
eOnExitEditor,
eOnLoadNote,
eOpenFullscreenEditor,
eUnlockNote
@@ -511,6 +512,7 @@ const onChangeTab = async (event) => {
editorState().movedAway = false;
editorState().isFocused = true;
activateKeepAwake();
eSendEvent(eOnEnterEditor);
if (!useTabStore.getState().getCurrentNoteId()) {
eSendEvent(eOnLoadNote, {
@@ -522,14 +524,13 @@ const onChangeTab = async (event) => {
) {
eSendEvent(eUnlockNote);
}
eSendEvent(eOnEnterEditor);
}
} else {
if (event.from === 2) {
deactivateKeepAwake();
editorState().movedAway = true;
editorState().isFocused = false;
eSendEvent(eClearEditor, "removeHandler");
eSendEvent(eOnExitEditor);
// Lock all tabs with locked notes...
for (const tab of useTabStore.getState().tabs) {

View File

@@ -32,7 +32,7 @@ import WebView from "react-native-webview";
import { ShouldStartLoadRequest } from "react-native-webview/lib/WebViewTypes";
import { notesnook } from "../../../e2e/test.ids";
import { db } from "../../common/database";
import BiometicService from "../../services/biometrics";
import BiometricService from "../../services/biometrics";
import {
ToastManager,
eSendEvent,
@@ -51,7 +51,11 @@ import { EditorProps, useEditorType } from "./tiptap/types";
import { useEditor } from "./tiptap/use-editor";
import { useEditorEvents } from "./tiptap/use-editor-events";
import { syncTabs, useTabStore } from "./tiptap/use-tab-store";
import { editorController, editorState } from "./tiptap/utils";
import {
editorController,
editorState,
openInternalLink
} from "./tiptap/utils";
const style: ViewStyle = {
height: "100%",
@@ -61,7 +65,10 @@ const style: ViewStyle = {
backgroundColor: "transparent"
};
const onShouldStartLoadWithRequest = (request: ShouldStartLoadRequest) => {
if (request.url.includes("https")) {
if (request.url.includes("nn://")) {
openInternalLink(request.url);
return false;
} else if (request.url.includes("https")) {
if (Platform.OS === "ios" && !request.isTopFrame) return true;
openLinkInBrowser(request.url);
return false;
@@ -201,8 +208,8 @@ const useLockedNoteHandler = () => {
useEffect(() => {
(async () => {
const biometry = await BiometicService.isBiometryAvailable();
const fingerprint = await BiometicService.hasInternetCredentials();
const biometry = await BiometricService.isBiometryAvailable();
const fingerprint = await BiometricService.hasInternetCredentials();
useTabStore.setState({
biometryAvailable: !!biometry,
biometryEnrolled: !!fingerprint
@@ -216,9 +223,9 @@ const useLockedNoteHandler = () => {
try {
if (!tabRef.current?.noteLocked || !tabRef.current) return;
console.log("Trying to unlock with biometrics...");
const credentials = await BiometicService.getCredentials(
const credentials = await BiometricService.getCredentials(
"Unlock note",
"Unlock note to open it in editor. If biometrics are not working, you can enter device pin to unlock vault."
"Unlock note to open it in editor."
);
if (credentials && credentials?.password && tabRef.current.noteId) {
@@ -226,6 +233,7 @@ const useLockedNoteHandler = () => {
tabRef.current.noteId,
credentials?.password
);
eSendEvent(eOnLoadNote, {
item: note
});
@@ -262,7 +270,7 @@ const useLockedNoteHandler = () => {
try {
const unlocked = await db.vault.unlock(password);
if (!unlocked) throw new Error("Incorrect vault password");
await BiometicService.storeCredentials(password);
await BiometricService.storeCredentials(password);
eSendEvent("vaultUpdated");
ToastManager.show({
heading: "Biometric unlocking enabled!",
@@ -271,8 +279,8 @@ const useLockedNoteHandler = () => {
context: "global"
});
const biometry = await BiometicService.isBiometryAvailable();
const fingerprint = await BiometicService.hasInternetCredentials();
const biometry = await BiometricService.isBiometryAvailable();
const fingerprint = await BiometricService.hasInternetCredentials();
useTabStore.setState({
biometryAvailable: !!biometry,
biometryEnrolled: !!fingerprint

View File

@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
/* eslint-disable no-case-declarations */
/* eslint-disable @typescript-eslint/no-var-requires */
import { parseInternalLink } from "@notesnook/core";
import { ItemReference } from "@notesnook/core/dist/types";
import type { Attachment } from "@notesnook/editor/dist/extensions/attachment/index";
import { getDefaultPresets } from "@notesnook/editor/dist/toolbar/tool-definitions";
@@ -27,7 +26,6 @@ import Clipboard from "@react-native-clipboard/clipboard";
import React, { useCallback, useEffect, useRef } from "react";
import {
BackHandler,
InteractionManager,
Keyboard,
KeyboardEventListener,
NativeEventSubscription,
@@ -37,6 +35,7 @@ import { WebViewMessageEvent } from "react-native-webview";
import { DatabaseLogger, db } from "../../../common/database";
import downloadAttachment from "../../../common/filesystem/download-attachment";
import EditorTabs from "../../../components/sheets/editor-tabs";
import { Issue } from "../../../components/sheets/github/issue";
import LinkNote from "../../../components/sheets/link-note";
import ManageTagsSheet from "../../../components/sheets/manage-tags";
import { RelationsList } from "../../../components/sheets/relations-list";
@@ -61,6 +60,7 @@ import {
eCloseFullscreenEditor,
eEditorTabFocused,
eOnEnterEditor,
eOnExitEditor,
eOnLoadNote,
eOpenFullscreenEditor,
eOpenLoginDialog,
@@ -75,8 +75,7 @@ import { useDragState } from "../../settings/editor/state";
import { EventTypes } from "./editor-events";
import { EditorMessage, EditorProps, useEditorType } from "./types";
import { useTabStore } from "./use-tab-store";
import { EditorEvents, editorState } from "./utils";
import { Issue } from "../../../components/sheets/github/issue";
import { EditorEvents, editorState, openInternalLink } from "./utils";
const publishNote = async () => {
const user = useUserStore.getState().user;
@@ -270,21 +269,20 @@ export const useEditorEvents = (
}, [editor, deviceMode, fullscreen]);
const onHardwareBackPress = useCallback(() => {
if (tabBarRef.current?.page === 2) {
console.log(tabBarRef.current?.page());
if (tabBarRef.current?.page() === 2) {
onBackPress();
return true;
}
}, [onBackPress]);
const onEnterEditor = useCallback(async () => {
InteractionManager.runAfterInteractions(() => {
if (!DDS.isTab) {
handleBack.current = BackHandler.addEventListener(
"hardwareBackPress",
onHardwareBackPress
);
}
});
if (!DDS.isTab) {
handleBack.current = BackHandler.addEventListener(
"hardwareBackPress",
onHardwareBackPress
);
}
}, [onHardwareBackPress]);
const onClearEditorSessionRequest = useCallback(
@@ -331,7 +329,14 @@ export const useEditorEvents = (
}, [fullscreen, onHardwareBackPress]);
useEffect(() => {
const onExitEditor = () => {
if (handleBack.current) {
handleBack.current.remove();
}
};
eSubscribeEvent(eOnEnterEditor, onEnterEditor);
eSubscribeEvent(eOnExitEditor, onExitEditor);
eSubscribeEvent(
eClearEditor + editor.editorId,
onClearEditorSessionRequest
@@ -339,6 +344,7 @@ export const useEditorEvents = (
return () => {
eUnSubscribeEvent(eClearEditor, onClearEditorSessionRequest);
eUnSubscribeEvent(eOnEnterEditor, onEnterEditor);
eUnSubscribeEvent(eOnExitEditor, onExitEditor);
};
}, [editor.editorId, onClearEditorSessionRequest, onEnterEditor]);
@@ -462,12 +468,10 @@ export const useEditorEvents = (
case EventTypes.getAttachmentData: {
const attachment = (editorMessage.value as any)
.attachment as Attachment;
?.attachment as Attachment;
console.log(
"Getting attachment data:",
attachment.hash,
attachment.type
DatabaseLogger.log(
`Getting attachment data: ${attachment?.hash} ${attachment?.type}`
);
downloadAttachment(attachment.hash, true, {
base64: attachment.type === "image",
@@ -487,8 +491,8 @@ export const useEditorEvents = (
data
});
})
.catch(() => {
console.log("Error downloading attachment data");
.catch((e) => {
DatabaseLogger.error(e);
editor.postMessage(EditorEvents.attachmentData, {
resolverId: (editorMessage.value as any).resolverId,
data: undefined
@@ -519,27 +523,7 @@ export const useEditorEvents = (
break;
case EventTypes.link:
if (editorMessage.value.startsWith("nn://")) {
const data = parseInternalLink(editorMessage.value);
if (!data?.id) break;
if (
data.id ===
useTabStore
.getState()
.getNoteIdForTab(useTabStore.getState().currentTab)
) {
if (data.params?.blockId) {
setTimeout(() => {
if (!data.params?.blockId) return;
editor.commands.scrollIntoViewById(data.params.blockId);
}, 150);
}
return;
}
eSendEvent(eOnLoadNote, {
item: await db.notes.note(data?.id),
blockId: data.params?.blockId
});
openInternalLink(editorMessage.value);
console.log(
"Opening note from internal link:",
editorMessage.value
@@ -662,10 +646,8 @@ export const useEditorEvents = (
.updateTab(useTabStore.getState().currentTab, {
readonly: false
});
Navigation.queueRoutesForUpdate();
ToastManager.show({
heading: "Readonly mode disabled.",
type: "success"
setTimeout(() => {
Navigation.queueRoutesForUpdate();
});
}
break;

View File

@@ -837,7 +837,7 @@ export const useEditor = (
if (!noteId) {
overlay(false);
loadNote({ newNote: true });
if (tabBarRef.current?.page === 1) {
if (tabBarRef.current?.page() === 1) {
state.current.currentlyEditing = false;
}
}

View File

@@ -22,10 +22,15 @@ import { TextInput } from "react-native";
import WebView from "react-native-webview";
import { MMKV } from "../../../common/database/mmkv";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
} from "../../../services/event-manager";
import { AppState, EditorState, useEditorType } from "./types";
import { useTabStore } from "./use-tab-store";
import { parseInternalLink } from "@notesnook/core";
import { eOnLoadNote } from "../../../utils/events";
import { db } from "../../../common/database";
export const textInput = createRef<TextInput>();
export const editorController =
createRef<useEditorType>() as MutableRefObject<useEditorType>;
@@ -176,3 +181,27 @@ export function clearAppState() {
appState = undefined;
MMKV.removeItem("appState");
}
export async function openInternalLink(url: string) {
const data = parseInternalLink(url);
if (!data?.id) return false;
if (
data.id ===
useTabStore.getState().getNoteIdForTab(useTabStore.getState().currentTab)
) {
if (data.params?.blockId) {
setTimeout(() => {
if (!data.params?.blockId) return;
editorController.current.commands.scrollIntoViewById(
data.params.blockId
);
}, 150);
}
return;
}
eSendEvent(eOnLoadNote, {
item: await db.notes.note(data?.id),
blockId: data.params?.blockId
});
}

View File

@@ -32,6 +32,7 @@ import { openEditor } from "../notes/common";
export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
const [notes, loading] = useNotes();
const isFocused = useNavigationFocus(navigation, {
onFocus: (prev) => {
Navigation.routeNeedsUpdate(

View File

@@ -16,40 +16,37 @@ 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 { resolveItems } from "@notesnook/common";
import { VirtualizedGrouping } from "@notesnook/core";
import { Note, Notebook } from "@notesnook/core/dist/types";
import React, { useEffect, useRef, useState } from "react";
import { View } from "react-native";
import { db } from "../../common/database";
import DelayLayout from "../../components/delay-layout";
import { Header } from "../../components/header";
import List from "../../components/list";
import { NotebookHeader } from "../../components/list-items/headers/notebook-header";
import SelectionHeader from "../../components/selection-header";
import { AddNotebookSheet } from "../../components/sheets/add-notebook";
import { IconButton } from "../../components/ui/icon-button";
import { Pressable } from "../../components/ui/pressable";
import Paragraph from "../../components/ui/typography/paragraph";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import { eSendEvent, eSubscribeEvent } from "../../services/event-manager";
import Navigation, { NavigationProps } from "../../services/navigation";
import useNavigationStore, {
NotebookScreenParams
} from "../../stores/use-navigation-store";
import { eUpdateNotebookRoute } from "../../utils/events";
import { findRootNotebookId } from "../../utils/notebooks";
import { openEditor, setOnFirstSave } from "../notes/common";
import SelectionHeader from "../../components/selection-header";
import Paragraph from "../../components/ui/typography/paragraph";
import { View } from "react-native";
import { SIZE } from "../../utils/size";
import { IconButton } from "../../components/ui/icon-button";
import { Pressable } from "../../components/ui/pressable";
import { resolveItems } from "@notesnook/common";
import { openEditor, setOnFirstSave } from "../notes/common";
const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
const [notes, setNotes] = useState<VirtualizedGrouping<Note>>();
const params = useRef<NotebookScreenParams>(route?.params);
const [loading, setLoading] = useState(true);
const updateOnFocus = useRef(false);
const [breadcrumbs, setBreadcrumbs] = useState<
{
id: string;
@@ -59,11 +56,17 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
useNavigationFocus(navigation, {
onFocus: () => {
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
if (updateOnFocus.current) {
onRequestUpdate();
updateOnFocus.current = false;
} else {
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
}
syncWithNavigation();
return false;
},
onBlur: () => {
updateOnFocus.current = false;
setOnFirstSave(null);
return false;
}
@@ -79,6 +82,15 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
const onRequestUpdate = React.useCallback(
async (data?: NotebookScreenParams) => {
if (
useNavigationStore.getState().focusedRouteId !==
params.current.item.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(
@@ -104,7 +116,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
if (notebook) {
const breadcrumbs = await db.notebooks.breadcrumbs(notebook.id);
setBreadcrumbs(breadcrumbs);
setBreadcrumbs(breadcrumbs.slice(0, breadcrumbs.length - 1));
params.current.item = notebook;
const notes = await db.relations
.from(notebook, "note")
@@ -122,10 +134,10 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
);
useEffect(() => {
onRequestUpdate();
eSubscribeEvent(eUpdateNotebookRoute, onRequestUpdate);
onRequestUpdate(params.current);
const sub = eSubscribeEvent(eUpdateNotebookRoute, onRequestUpdate);
return () => {
eUnSubscribeEvent(eUpdateNotebookRoute, onRequestUpdate);
sub?.unsubscribe();
};
}, [onRequestUpdate]);
@@ -167,7 +179,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
onPressDefaultRightButton={openEditor}
/>
{breadcrumbs ? (
{breadcrumbs && breadcrumbs.length > 0 ? (
<View
style={{
width: "100%",

View File

@@ -81,16 +81,21 @@ const NotesPage = ({
route.name === "ColoredNotes"
? (params.current?.item as Color)?.colorCode
: undefined;
const updateOnFocus = useRef(false);
const isFocused = useNavigationFocus(navigation, {
onFocus: (prev) => {
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
if (updateOnFocus.current) {
onRequestUpdate();
updateOnFocus.current = false;
} else {
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
}
syncWithNavigation();
if (focusControl) return !prev.current;
return false;
},
onBlur: () => {
updateOnFocus.current = false;
setOnFirstSave(null);
return false;
},
@@ -99,7 +104,6 @@ const NotesPage = ({
const syncWithNavigation = React.useCallback(() => {
const { item } = params.current;
useNavigationStore
.getState()
.setFocusedRouteId(params?.current?.item?.id || route.name);
@@ -113,6 +117,14 @@ const NotesPage = ({
const onRequestUpdate = React.useCallback(
async (data?: NotesScreenParams) => {
if (
useNavigationStore.getState().focusedRouteId !==
params.current.item.id &&
!data
) {
updateOnFocus.current = false;
return;
}
const isNew = data && data?.item?.id !== params.current?.item?.id;
if (data) params.current = data;

View File

@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { useThemeColors } from "@notesnook/theme";
import React, { useRef } from "react";
import { View } from "react-native";
import { Platform, View } from "react-native";
import { TextInput } from "react-native-gesture-handler";
import { IconButton } from "../../components/ui/icon-button";
import Navigation from "../../services/navigation";
@@ -49,8 +49,8 @@ export const SearchBar = ({
return selectionMode && isFocused ? null : (
<View
style={{
height: 50 + insets.top,
paddingTop: insets.top,
height: Platform.OS === "android" ? 50 + insets.top : 50,
paddingTop: Platform.OS === "ios" ? 0 : insets.top,
flexDirection: "row",
alignItems: "center",
flexShrink: 1,

View File

@@ -26,7 +26,7 @@ import { Pressable } from "../../components/ui/pressable";
import Seperator from "../../components/ui/seperator";
import Heading from "../../components/ui/typography/heading";
import Paragraph from "../../components/ui/typography/paragraph";
import BiometicService from "../../services/biometrics";
import BiometricService from "../../services/biometrics";
import { ToastManager, presentSheet } from "../../services/event-manager";
import SettingsService from "../../services/settings";
import { useSettingStore } from "../../stores/use-setting-store";
@@ -119,7 +119,7 @@ const AppLock = () => {
type={appLockMode === item.value ? "secondary" : "transparent"}
onPress={async () => {
if (
!(await BiometicService.isBiometryAvailable()) &&
!(await BiometricService.isBiometryAvailable()) &&
!useUserStore.getState().user &&
item.value !== modes[0].value &&
!SettingsService.getProperty("appLockHasPasswordSecurity")
@@ -139,7 +139,7 @@ const AppLock = () => {
) &&
item.value !== modes[0].value
) {
const verified = await BiometicService.validateUser(
const verified = await BiometricService.validateUser(
"Verify it's you"
);
if (verified) {

View File

@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { db } from "../../common/database";
import { validateAppLockPassword } from "../../common/database/encryption";
import { presentDialog } from "../../components/dialog/functions";
import BiometicService from "../../services/biometrics";
import BiometricService from "../../services/biometrics";
import { ToastManager } from "../../services/event-manager";
import SettingsService from "../../services/settings";
import { useUserStore } from "../../stores/use-user-store";
@@ -100,9 +100,9 @@ export async function verifyUserWithApplock() {
}
});
} else {
BiometicService.isBiometryAvailable().then((available) => {
BiometricService.isBiometryAvailable().then((available) => {
if (available) {
BiometicService.validateUser("Verify it's you").then((verified) => {
BiometricService.validateUser("Verify it's you").then((verified) => {
resolve(verified);
});
} else if (useUserStore.getState().user) {

View File

@@ -38,7 +38,7 @@ import { Update } from "../../components/sheets/update";
import { VaultStatusType, useVaultStatus } from "../../hooks/use-vault-status";
import { BackgroundSync } from "../../services/background-sync";
import BackupService from "../../services/backup";
import BiometicService from "../../services/biometrics";
import BiometricService from "../../services/biometrics";
import {
ToastManager,
eSendEvent,
@@ -349,7 +349,7 @@ export const settingsGroups: SettingSection[] = [
await db.user?.logout();
setLoginMessage();
await PremiumService.setPremiumStatus();
await BiometicService.resetCredentials();
await BiometricService.resetCredentials();
MMKV.clearStore();
clearAllStores();
refreshAllStores();
@@ -394,7 +394,7 @@ export const settingsGroups: SettingSection[] = [
if (verified) {
eSendEvent("settings-loading", true);
await db.user?.deleteUser(value);
await BiometicService.resetCredentials();
await BiometricService.resetCredentials();
SettingsService.set({
introCompleted: true
});
@@ -789,7 +789,7 @@ export const settingsGroups: SettingSection[] = [
}
},
{
id: "biometic-unlock",
id: "biometric-unlock",
type: "switch",
name: "Biometric unlocking",
icon: "fingerprint",
@@ -859,13 +859,13 @@ export const settingsGroups: SettingSection[] = [
if (!SettingsService.getProperty("appLockEnabled")) {
if (
!SettingsService.getProperty("appLockHasPasswordSecurity") &&
(await BiometicService.isBiometryAvailable())
(await BiometricService.isBiometryAvailable())
) {
SettingsService.setProperty("biometricsAuthEnabled", true);
}
if (
!(await BiometicService.isBiometryAvailable()) &&
!(await BiometricService.isBiometryAvailable()) &&
!SettingsService.getProperty("appLockHasPasswordSecurity")
) {
ToastManager.show({

View File

@@ -41,7 +41,7 @@ import {
TouchableOpacity,
View
} from "react-native";
import { db } from "../../common/database";
import { DatabaseLogger, db } from "../../common/database";
import SheetProvider from "../../components/sheet-provider";
import { Button } from "../../components/ui/button";
import Input from "../../components/ui/input";
@@ -326,11 +326,10 @@ function ThemeSelector() {
return page.themes;
})
.flat()
.filter(
(theme) =>
(!searchQuery || searchQuery === "") &&
darkTheme.id !== theme.id &&
lightTheme.id !== theme.id
.filter((theme) =>
searchQuery && searchQuery !== ""
? true
: darkTheme.id !== theme.id && lightTheme.id !== theme.id
) || []
);
}
@@ -546,7 +545,7 @@ const ThemeSetter = ({
context: "global"
});
} catch (e) {
console.log("Error", e);
DatabaseLogger.error(e);
}
setTimeout(() => {

View File

@@ -31,6 +31,7 @@ import { eCloseSheet } from "../utils/events";
import { sleep } from "../utils/time";
import { ToastManager, eSendEvent, presentSheet } from "./event-manager";
import SettingsService from "./settings";
import { useUserStore } from "../stores/use-user-store";
const MS_DAY = 86400000;
const MS_WEEK = MS_DAY * 7;
@@ -191,9 +192,10 @@ async function run(progress = false, context) {
await RNFetchBlob.fs.mkdir(zipSourceFolder);
try {
const user = await db.user.getUser();
for await (const file of db.backup.export(
"mobile",
SettingsService.get().encryptedBackup
SettingsService.get().encryptedBackup && user
)) {
console.log("Writing backup chunk of size...", file?.data?.length);
await RNFetchBlob.fs.writeFile(
@@ -252,7 +254,7 @@ async function run(progress = false, context) {
} catch (e) {
ToastManager.error(e, "Backup failed", context || "global");
if (e.message.includes("android.net.Uri") && androidBackupDirectory) {
if (e?.message?.includes("android.net.Uri") && androidBackupDirectory) {
SettingsService.setProperty("backupDirectoryAndroid", null);
return run(progress, context);
}

View File

@@ -179,7 +179,7 @@ async function validateUser(title: string, description?: string) {
}
}
const BiometicService = {
const BiometricService = {
isBiometryAvailable,
enableFingerprintAuth,
isFingerprintAuthEnabled,
@@ -190,4 +190,4 @@ const BiometicService = {
validateUser
};
export default BiometicService;
export default BiometricService;

View File

@@ -40,7 +40,7 @@ import { basename, dirname, join } from "pathe";
import downloadAttachment from "../common/filesystem/download-attachment";
import { presentDialog } from "../components/dialog/functions";
import { useSettingStore } from "../stores/use-setting-store";
import BiometicService from "./biometrics";
import BiometricService from "./biometrics";
import { ToastManager } from "./event-manager";
import { cacheDir } from "../common/filesystem/utils";
@@ -186,10 +186,10 @@ async function exportAs(
}
async function unlockVault() {
const biometry = await BiometicService.isBiometryAvailable();
const fingerprint = await BiometicService.hasInternetCredentials();
const biometry = await BiometricService.isBiometryAvailable();
const fingerprint = await BiometricService.hasInternetCredentials();
if (biometry && fingerprint) {
const credentials = await BiometicService.getCredentials(
const credentials = await BiometricService.getCredentials(
"Unlock vault",
"Unlock vault to export locked notes"
);

View File

@@ -49,6 +49,8 @@ import { DDS } from "./device-detection";
import { eSendEvent } from "./event-manager";
import Navigation from "./navigation";
import SettingsService from "./settings";
import { useUserStore } from "../stores/use-user-store";
import { useTabStore } from "../screens/editor/tiptap/use-tab-store";
let pinned: DisplayedNotification[] = [];
@@ -139,6 +141,7 @@ const onEvent = async ({ type, detail }: Event) => {
}
editorState().movedAway = false;
const noteId = notification?.id;
console.log("NOTE ID", noteId);
loadNote(noteId as string, true);
}
@@ -407,18 +410,31 @@ async function loadNote(id: string, jump: boolean) {
if (!DDS.isTab && jump) {
tabBarRef.current?.goToPage(1);
}
eSendEvent("loadingNote", note);
setTimeout(
() => {
eSendEvent(eOnLoadNote, {
item: note
});
if (!jump && !DDS.isTab) {
tabBarRef.current?.goToPage(1);
}
},
tabBarRef?.current ? 0 : 2000
MMKV.setString(
"appState",
JSON.stringify({
editing: true,
movedAway: false,
timestamp: Date.now()
})
);
const isLocked = await db.vaults.itemExists({
type: "note",
id: id
});
const tab = useTabStore.getState().getTabForNote(id);
if (tab !== undefined) {
useTabStore.getState().focusTab(tab);
} else {
useTabStore.getState().focusPreviewTab(id, {
noteId: id,
readonly: note.readonly,
noteLocked: isLocked
});
}
}
async function getChannelId(id: "silent" | "vibrate" | "urgent" | "default") {
@@ -476,7 +492,15 @@ async function displayNotification({
reply_button_text?: string;
id?: string;
}) {
if (!(await checkAndRequestPermissions())) return;
useUserStore.setState({
disableAppLockRequests: true
});
const permission = await checkAndRequestPermissions();
useUserStore.setState({
disableAppLockRequests: false
});
if (!permission) return;
try {
await notifee.displayNotification({
@@ -846,7 +870,16 @@ async function remove(id: string) {
}
async function pinQuickNote(launch: boolean) {
if (!(await checkAndRequestPermissions())) return;
useUserStore.setState({
disableAppLockRequests: true
});
const permission = await checkAndRequestPermissions();
useUserStore.setState({
disableAppLockRequests: false
});
if (!permission) {
return;
}
get().then((items) => {
const notification = items.filter((n) => n.id === "notesnook_note_input");
if (notification && launch) {

View File

@@ -72,7 +72,7 @@ export const useMenuStore = create<MenuStore>((set, get) => ({
section as SideBarSection
);
hiddenItems[section as SideBarHideableSection] =
db.settings.getSideBarHiddenItems("colors");
db.settings.getSideBarHiddenItems(section as SideBarHideableSection);
}
if (
@@ -80,6 +80,7 @@ export const useMenuStore = create<MenuStore>((set, get) => ({
JSON.stringify(get().hiddenItems || {}) !==
JSON.stringify(hiddenItems || {})
) {
console.log(order, hiddenItems);
set({
order: order,
hiddenItems: hiddenItems

View File

@@ -173,3 +173,4 @@ export const eUnlockWithBiometrics = "618";
export const eUnlockWithPassword = "619";
export const eUpdateNoteInEditor = "620";
export const eOnEnterEditor = "621";
export const eOnExitEditor = "622";

View File

@@ -111,7 +111,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled true
versionCode 3008
versionCode 3009
versionName getNpmVersion()
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'

View File

@@ -1,3 +1,7 @@
Notesnook V3 is here, and it's packed with new features and improvements such as bi-directional note linking, better syncing, at-rest encryption, nested notebooks, editor tabs, better app lock and so much more.
- Now you can set title of note when sharing to Notesnook
- Fix back navigation from editor closes the app
- Fix errors during migration
- Fix sharing to Notesnook not working for some apps
- Numerous other bug fixes and performance improvements
Check out our blogpost to learn about what's new at https://blog.notesnook.com/introducing-notesnook-v3.
Thank you for using Notesnook!

View File

@@ -21,9 +21,9 @@
6517B7C32B6838EB0079FF37 /* OpenSans-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6517B7C02B6838EB0079FF37 /* OpenSans-Bold.ttf */; };
6529A13E279BC4C70048D4A8 /* BootSplash.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 6529A13D279BC4C70048D4A8 /* BootSplash.storyboard */; };
656835812BB29A9800144BAB /* OpenSans-Italic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 656835802BB29A8300144BAB /* OpenSans-Italic.ttf */; };
656DD2AB2B1891DF00A362EA /* (null) in Resources */ = {isa = PBXBuildFile; };
656DD2AC2B1891DF00A362EA /* (null) in Resources */ = {isa = PBXBuildFile; };
656DD2AD2B1891DF00A362EA /* (null) in Resources */ = {isa = PBXBuildFile; };
656DD2AB2B1891DF00A362EA /* BuildFile in Resources */ = {isa = PBXBuildFile; };
656DD2AC2B1891DF00A362EA /* BuildFile in Resources */ = {isa = PBXBuildFile; };
656DD2AD2B1891DF00A362EA /* BuildFile in Resources */ = {isa = PBXBuildFile; };
6593E4A3281C345400492C50 /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 6593E4A2281C345400492C50 /* AppDelegate.mm */; };
659BE46725E11A5100E05671 /* notesnook-text.png in Resources */ = {isa = PBXBuildFile; fileRef = 659BE46625E11A5100E05671 /* notesnook-text.png */; };
65AA857925E6DDEC00772A01 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 65AA857825E6DDEC00772A01 /* WidgetKit.framework */; };
@@ -586,9 +586,9 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
656DD2AB2B1891DF00A362EA /* (null) in Resources */,
656DD2AC2B1891DF00A362EA /* (null) in Resources */,
656DD2AD2B1891DF00A362EA /* (null) in Resources */,
656DD2AB2B1891DF00A362EA /* BuildFile in Resources */,
656DD2AC2B1891DF00A362EA /* BuildFile in Resources */,
656DD2AD2B1891DF00A362EA /* BuildFile in Resources */,
65C400DF2A80B6B600AA3DF5 /* MaterialCommunityIcons.ttf in Resources */,
65C149872A61151B005C40F1 /* extension.bundle in Resources */,
65B5014725A672B200E2D264 /* MainInterface.storyboard in Resources */,
@@ -1015,7 +1015,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2097;
CURRENT_PROJECT_VERSION = 2098;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1089,7 +1089,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.0.0;
MARKETING_VERSION = 3.0.1;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1120,7 +1120,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2097;
CURRENT_PROJECT_VERSION = 2098;
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1194,7 +1194,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 3.0.0;
MARKETING_VERSION = 3.0.1;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1353,7 +1353,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2097;
CURRENT_PROJECT_VERSION = 2098;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1365,7 +1365,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.0.0;
MARKETING_VERSION = 3.0.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
@@ -1396,7 +1396,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2097;
CURRENT_PROJECT_VERSION = 2098;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1409,7 +1409,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.0.0;
MARKETING_VERSION = 3.0.1;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1439,7 +1439,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2097;
CURRENT_PROJECT_VERSION = 2098;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1513,7 +1513,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.0.0;
MARKETING_VERSION = 3.0.1;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
@@ -1544,7 +1544,7 @@
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2097;
CURRENT_PROJECT_VERSION = 2098;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
"DEVELOPMENT_TEAM[sdk=iphoneos*]" = 53CWBG3QUC;
@@ -1619,7 +1619,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 3.0.0;
MARKETING_VERSION = 3.0.1;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -345,7 +345,7 @@ PODS:
- React-Core
- react-native-safe-area-context (4.9.0):
- React-Core
- react-native-share-extension (2.5.6):
- react-native-share-extension (3.0.0):
- React
- react-native-sodium (1.5.4):
- React
@@ -910,7 +910,7 @@ SPEC CHECKSUMS:
react-native-pdf: 33c622cbdf776a649929e8b9d1ce2d313347c4fa
react-native-quick-sqlite: e0e23b749382a85e4b57146f753de737a6c3a9e1
react-native-safe-area-context: b97eb6f9e3b7f437806c2ce5983f479f8eb5de4b
react-native-share-extension: df66a2ee48a62277d79898375e2142bde0782063
react-native-share-extension: faed334b1ddf165f1e576fcabd3dc1c9e748bfa9
react-native-sodium: 955bb0dc3ea05f8ea06d5e96cb89d1be7b5d7681
react-native-theme-switch-animation: 220f883f7be290e79f2ab022093ed1a7a5929e6d
react-native-webview: 9f111dfbcfc826084d6c507f569e5e03342ee1c1

File diff suppressed because one or more lines are too long

View File

@@ -6,7 +6,7 @@
"license": "GPL-3.0-or-later",
"dependencies": {
"@ammarahmed/notifee-react-native": "7.4.7",
"@ammarahmed/react-native-share-extension": "^2.5.9",
"@ammarahmed/react-native-share-extension": "^3.0.0",
"@ammarahmed/react-native-sodium": "1.5.4",
"@bam.tech/react-native-image-resizer": "3.0.5",
"@callstack/repack": "^3.2.0",

View File

@@ -7162,7 +7162,7 @@
},
"../../packages/editor-mobile/node_modules/@types/prop-types": {
"version": "15.7.11",
"dev": true,
"devOptional": true,
"license": "MIT"
},
"../../packages/editor-mobile/node_modules/@types/q": {
@@ -7182,7 +7182,7 @@
},
"../../packages/editor-mobile/node_modules/@types/react": {
"version": "18.2.39",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
@@ -7213,7 +7213,7 @@
},
"../../packages/editor-mobile/node_modules/@types/scheduler": {
"version": "0.16.8",
"dev": true,
"devOptional": true,
"license": "MIT"
},
"../../packages/editor-mobile/node_modules/@types/semver": {
@@ -12071,7 +12071,7 @@
},
"../../packages/editor-mobile/node_modules/immer": {
"version": "9.0.21",
"dev": true,
"devOptional": true,
"license": "MIT",
"funding": {
"type": "opencollective",
@@ -22460,7 +22460,6 @@
},
"../../packages/editor/node_modules/js-tokens": {
"version": "4.0.0",
"dev": true,
"license": "MIT"
},
"../../packages/editor/node_modules/json-parse-even-better-errors": {
@@ -22516,7 +22515,6 @@
},
"../../packages/editor/node_modules/loose-envify": {
"version": "1.4.0",
"dev": true,
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
@@ -23038,7 +23036,6 @@
},
"../../packages/editor/node_modules/react": {
"version": "18.2.0",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
@@ -23057,7 +23054,6 @@
},
"../../packages/editor/node_modules/react-dom": {
"version": "18.2.0",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0",
@@ -23177,7 +23173,6 @@
},
"../../packages/editor/node_modules/scheduler": {
"version": "0.23.0",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
@@ -28443,7 +28438,7 @@
"@ammarahmed/notifee-react-native": "7.4.7",
"@ammarahmed/react-native-background-fetch": "^4.2.2",
"@ammarahmed/react-native-eventsource": "1.1.0",
"@ammarahmed/react-native-share-extension": "^2.5.9",
"@ammarahmed/react-native-share-extension": "^3.0.0",
"@ammarahmed/react-native-sodium": "1.5.4",
"@bam.tech/react-native-image-resizer": "3.0.5",
"@callstack/repack": "^3.2.0",
@@ -28596,9 +28591,9 @@
}
},
"node_modules/@ammarahmed/react-native-share-extension": {
"version": "2.5.9",
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-share-extension/-/react-native-share-extension-2.5.9.tgz",
"integrity": "sha512-IEtlh/YCWggcse88yqSX5M+rTyEhu5xHTcje30QbVKtJwtWofbqjzL7UO4EIXKpyFh5u4/CDaY5Yy9kQS1yTjA==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-share-extension/-/react-native-share-extension-3.0.0.tgz",
"integrity": "sha512-pXf/8Zt46yPfJZ4FMlhI+Bgh/yHllNPiOeb9rqTwGJLuWYA7E6QFNuZ4n4smK7rJZrmhOYV12gFUzFtodgC/8w==",
"dependencies": {
"react-native": "^0.63.1"
}
@@ -30896,7 +30891,6 @@
},
"node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -30910,7 +30904,6 @@
},
"node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -31057,7 +31050,6 @@
},
"node_modules/@babel/plugin-proposal-private-property-in-object": {
"version": "7.21.0-placeholder-for-preset-env.2",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -31068,7 +31060,6 @@
},
"node_modules/@babel/plugin-proposal-unicode-property-regex": {
"version": "7.18.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.18.6",
@@ -31114,7 +31105,6 @@
},
"node_modules/@babel/plugin-syntax-class-static-block": {
"version": "7.14.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.14.5"
@@ -31151,7 +31141,6 @@
},
"node_modules/@babel/plugin-syntax-export-namespace-from": {
"version": "7.8.3",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.3"
@@ -31175,7 +31164,6 @@
},
"node_modules/@babel/plugin-syntax-import-assertions": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -31189,7 +31177,6 @@
},
"node_modules/@babel/plugin-syntax-import-attributes": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -31203,7 +31190,6 @@
},
"node_modules/@babel/plugin-syntax-import-meta": {
"version": "7.10.4",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.10.4"
@@ -31214,7 +31200,6 @@
},
"node_modules/@babel/plugin-syntax-json-strings": {
"version": "7.8.3",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.0"
@@ -31238,7 +31223,6 @@
},
"node_modules/@babel/plugin-syntax-logical-assignment-operators": {
"version": "7.10.4",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.10.4"
@@ -31299,7 +31283,6 @@
},
"node_modules/@babel/plugin-syntax-private-property-in-object": {
"version": "7.14.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.14.5"
@@ -31313,7 +31296,6 @@
},
"node_modules/@babel/plugin-syntax-top-level-await": {
"version": "7.14.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.14.5"
@@ -31340,7 +31322,6 @@
},
"node_modules/@babel/plugin-syntax-unicode-sets-regex": {
"version": "7.18.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.18.6",
@@ -31368,7 +31349,6 @@
},
"node_modules/@babel/plugin-transform-async-generator-functions": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-environment-visitor": "^7.22.5",
@@ -31426,7 +31406,6 @@
},
"node_modules/@babel/plugin-transform-class-properties": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.22.5",
@@ -31441,7 +31420,6 @@
},
"node_modules/@babel/plugin-transform-class-static-block": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.22.5",
@@ -31505,7 +31483,6 @@
},
"node_modules/@babel/plugin-transform-dotall-regex": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.22.5",
@@ -31520,7 +31497,6 @@
},
"node_modules/@babel/plugin-transform-duplicate-keys": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -31534,7 +31510,6 @@
},
"node_modules/@babel/plugin-transform-dynamic-import": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -31563,7 +31538,6 @@
},
"node_modules/@babel/plugin-transform-export-namespace-from": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -31620,7 +31594,6 @@
},
"node_modules/@babel/plugin-transform-json-strings": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -31648,7 +31621,6 @@
},
"node_modules/@babel/plugin-transform-logical-assignment-operators": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -31676,7 +31648,6 @@
},
"node_modules/@babel/plugin-transform-modules-amd": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-module-transforms": "^7.22.5",
@@ -31706,7 +31677,6 @@
},
"node_modules/@babel/plugin-transform-modules-systemjs": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-hoist-variables": "^7.22.5",
@@ -31723,7 +31693,6 @@
},
"node_modules/@babel/plugin-transform-modules-umd": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-module-transforms": "^7.22.5",
@@ -31752,7 +31721,6 @@
},
"node_modules/@babel/plugin-transform-new-target": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -31766,7 +31734,6 @@
},
"node_modules/@babel/plugin-transform-nullish-coalescing-operator": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -31781,7 +31748,6 @@
},
"node_modules/@babel/plugin-transform-numeric-separator": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -31809,7 +31775,6 @@
},
"node_modules/@babel/plugin-transform-object-rest-spread": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.22.5",
@@ -31841,7 +31806,6 @@
},
"node_modules/@babel/plugin-transform-optional-catch-binding": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -31856,7 +31820,6 @@
},
"node_modules/@babel/plugin-transform-optional-chaining": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5",
@@ -31885,7 +31848,6 @@
},
"node_modules/@babel/plugin-transform-private-methods": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.22.5",
@@ -31900,7 +31862,6 @@
},
"node_modules/@babel/plugin-transform-private-property-in-object": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.22.5",
@@ -32000,7 +31961,6 @@
},
"node_modules/@babel/plugin-transform-reserved-words": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -32092,7 +32052,6 @@
},
"node_modules/@babel/plugin-transform-typeof-symbol": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -32122,7 +32081,6 @@
},
"node_modules/@babel/plugin-transform-unicode-escapes": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.22.5"
@@ -32136,7 +32094,6 @@
},
"node_modules/@babel/plugin-transform-unicode-property-regex": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.22.5",
@@ -32165,7 +32122,6 @@
},
"node_modules/@babel/plugin-transform-unicode-sets-regex": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.22.5",
@@ -32180,7 +32136,6 @@
},
"node_modules/@babel/preset-env": {
"version": "7.22.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.22.5",
@@ -32273,7 +32228,6 @@
},
"node_modules/@babel/preset-env/node_modules/semver": {
"version": "6.3.0",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -32296,7 +32250,6 @@
},
"node_modules/@babel/preset-modules": {
"version": "0.1.5",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.0.0",
@@ -34946,7 +34899,6 @@
},
"node_modules/@types/eslint": {
"version": "8.40.2",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/estree": "*",
@@ -34955,7 +34907,6 @@
},
"node_modules/@types/eslint-scope": {
"version": "3.7.4",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/eslint": "*",
@@ -34964,7 +34915,6 @@
},
"node_modules/@types/estree": {
"version": "1.0.1",
"dev": true,
"license": "MIT"
},
"node_modules/@types/graceful-fs": {
@@ -35413,7 +35363,6 @@
},
"node_modules/@webassemblyjs/ast": {
"version": "1.11.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/helper-numbers": "1.11.6",
@@ -35422,22 +35371,18 @@
},
"node_modules/@webassemblyjs/floating-point-hex-parser": {
"version": "1.11.6",
"dev": true,
"license": "MIT"
},
"node_modules/@webassemblyjs/helper-api-error": {
"version": "1.11.6",
"dev": true,
"license": "MIT"
},
"node_modules/@webassemblyjs/helper-buffer": {
"version": "1.11.6",
"dev": true,
"license": "MIT"
},
"node_modules/@webassemblyjs/helper-numbers": {
"version": "1.11.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/floating-point-hex-parser": "1.11.6",
@@ -35447,12 +35392,10 @@
},
"node_modules/@webassemblyjs/helper-wasm-bytecode": {
"version": "1.11.6",
"dev": true,
"license": "MIT"
},
"node_modules/@webassemblyjs/helper-wasm-section": {
"version": "1.11.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
@@ -35463,7 +35406,6 @@
},
"node_modules/@webassemblyjs/ieee754": {
"version": "1.11.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@xtuc/ieee754": "^1.2.0"
@@ -35471,7 +35413,6 @@
},
"node_modules/@webassemblyjs/leb128": {
"version": "1.11.6",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@xtuc/long": "4.2.2"
@@ -35479,12 +35420,10 @@
},
"node_modules/@webassemblyjs/utf8": {
"version": "1.11.6",
"dev": true,
"license": "MIT"
},
"node_modules/@webassemblyjs/wasm-edit": {
"version": "1.11.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
@@ -35499,7 +35438,6 @@
},
"node_modules/@webassemblyjs/wasm-gen": {
"version": "1.11.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
@@ -35511,7 +35449,6 @@
},
"node_modules/@webassemblyjs/wasm-opt": {
"version": "1.11.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
@@ -35522,7 +35459,6 @@
},
"node_modules/@webassemblyjs/wasm-parser": {
"version": "1.11.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
@@ -35535,7 +35471,6 @@
},
"node_modules/@webassemblyjs/wast-printer": {
"version": "1.11.6",
"dev": true,
"license": "MIT",
"dependencies": {
"@webassemblyjs/ast": "1.11.6",
@@ -35593,12 +35528,10 @@
},
"node_modules/@xtuc/ieee754": {
"version": "1.2.0",
"dev": true,
"license": "BSD-3-Clause"
},
"node_modules/@xtuc/long": {
"version": "4.2.2",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/@yarnpkg/lockfile": {
@@ -35654,7 +35587,6 @@
},
"node_modules/acorn-import-assertions": {
"version": "1.9.0",
"dev": true,
"license": "MIT",
"peerDependencies": {
"acorn": "^8"
@@ -36759,7 +36691,6 @@
},
"node_modules/chrome-trace-event": {
"version": "1.0.3",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0"
@@ -37860,7 +37791,6 @@
},
"node_modules/enhanced-resolve": {
"version": "5.15.0",
"dev": true,
"license": "MIT",
"dependencies": {
"graceful-fs": "^4.2.4",
@@ -37973,7 +37903,6 @@
},
"node_modules/es-module-lexer": {
"version": "1.3.0",
"dev": true,
"license": "MIT"
},
"node_modules/es-set-tostringtag": {
@@ -38304,7 +38233,6 @@
},
"node_modules/eslint-scope": {
"version": "5.1.1",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"esrecurse": "^4.3.0",
@@ -38316,7 +38244,6 @@
},
"node_modules/eslint-scope/node_modules/estraverse": {
"version": "4.3.0",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=4.0"
@@ -38502,7 +38429,6 @@
},
"node_modules/esrecurse": {
"version": "4.3.0",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"estraverse": "^5.2.0"
@@ -38513,7 +38439,6 @@
},
"node_modules/estraverse": {
"version": "5.3.0",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=4.0"
@@ -38521,7 +38446,6 @@
},
"node_modules/esutils": {
"version": "2.0.3",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.10.0"
@@ -39578,7 +39502,6 @@
},
"node_modules/glob-to-regexp": {
"version": "0.4.1",
"dev": true,
"license": "BSD-2-Clause"
},
"node_modules/global": {
@@ -41822,7 +41745,6 @@
},
"node_modules/json-parse-even-better-errors": {
"version": "2.3.1",
"dev": true,
"license": "MIT"
},
"node_modules/json-schema-traverse": {
@@ -42090,7 +42012,6 @@
},
"node_modules/loader-runner": {
"version": "4.3.0",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.11.5"
@@ -44742,7 +44663,6 @@
},
"node_modules/randombytes": {
"version": "2.1.0",
"dev": true,
"license": "MIT",
"dependencies": {
"safe-buffer": "^5.1.0"
@@ -45521,7 +45441,6 @@
},
"node_modules/react-test-renderer": {
"version": "18.2.0",
"dev": true,
"license": "MIT",
"dependencies": {
"react-is": "^18.2.0",
@@ -45534,12 +45453,10 @@
},
"node_modules/react-test-renderer/node_modules/react-is": {
"version": "18.2.0",
"dev": true,
"license": "MIT"
},
"node_modules/react-test-renderer/node_modules/scheduler": {
"version": "0.23.0",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.1.0"
@@ -46375,7 +46292,6 @@
},
"node_modules/serialize-javascript": {
"version": "6.0.1",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"randombytes": "^2.1.0"
@@ -47431,7 +47347,6 @@
},
"node_modules/terser-webpack-plugin": {
"version": "5.3.9",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.17",
@@ -47464,7 +47379,6 @@
},
"node_modules/terser-webpack-plugin/node_modules/jest-worker": {
"version": "27.5.1",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*",
@@ -47477,7 +47391,6 @@
},
"node_modules/terser-webpack-plugin/node_modules/supports-color": {
"version": "8.1.1",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
@@ -48222,7 +48135,6 @@
},
"node_modules/watchpack": {
"version": "2.4.0",
"dev": true,
"license": "MIT",
"dependencies": {
"glob-to-regexp": "^0.4.1",
@@ -48245,7 +48157,6 @@
},
"node_modules/webpack": {
"version": "5.88.2",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/eslint-scope": "^3.7.3",
@@ -48360,7 +48271,6 @@
},
"node_modules/webpack-sources": {
"version": "3.2.3",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10.13.0"

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "3.0.0",
"version": "3.0.1",
"private": true,
"license": "GPL-3.0-or-later",
"workspaces": [

View File

@@ -33,6 +33,7 @@ import {
SafeAreaView,
ScrollView,
Text,
TextInput,
TouchableOpacity,
View,
useWindowDimensions
@@ -115,12 +116,14 @@ const ShareView = () => {
const appendNoteId = useShareStore((state) => state.appendNote);
const [note, setNote] = useState({ ...defaultNote });
const noteContent = useRef("");
const noteTitle = useRef("");
const [loading, setLoading] = useState(false);
const [loadingExtension, setLoadingExtension] = useState(true);
const [rawData, setRawData] = useState({
type: null,
value: null
});
const inputRef = useRef(null);
const [mode, setMode] = useState(1);
const keyboardHeight = useRef(0);
const { width, height } = useWindowDimensions();
@@ -192,6 +195,7 @@ const ShareView = () => {
}
return;
}
let note = { ...defaultNote };
for (let item of data) {
if (item.type === "text") {
@@ -207,7 +211,14 @@ const ShareView = () => {
if (!key) continue;
if (key.includes("TITLE") || key.includes("SUBJECT")) {
note.title = item[key];
console.log("Note title will be", note.title);
noteTitle.current = note.title;
inputRef.current?.setNativeProps?.({
text: noteTitle.current
});
}
if (key.includes("TEXT") && !note.content.data) {
note.content.data = item[key];
noteContent.current = item[key];
}
}
} else {
@@ -216,8 +227,9 @@ const ShareView = () => {
if (
(isImage(item.type) && item.size > IMAGE_SIZE_LIMIT) ||
(!isImage(item.type) && item.size > FILE_SIZE_LIMIT)
)
) {
continue;
}
setRawFiles((files) => {
const index = files.findIndex(
@@ -244,6 +256,7 @@ const ShareView = () => {
);
const onLoad = useCallback(() => {
console.log(noteContent.current, "current...");
eSendEvent(eOnLoadNote + "shareEditor", {
id: null,
content: {
@@ -275,7 +288,7 @@ const ShareView = () => {
const onPress = async () => {
setLoading(true);
if (!noteContent.current && rawFiles.length === 0) {
if (!noteContent.current && rawFiles.length === 0 && !noteTitle.current) {
setLoading(false);
return;
}
@@ -298,12 +311,14 @@ const ShareView = () => {
type: "tiptap"
},
id: note.id,
sessionId: Date.now()
sessionId: Date.now(),
title: noteTitle.current
};
} else {
noteData = { ...note };
noteData.content.data = noteContent.current;
noteData.sessionId = Date.now();
noteData.title = noteTitle.current;
}
try {
@@ -468,10 +483,29 @@ const ShareView = () => {
borderBottomWidth: 1,
paddingBottom: 12,
borderBottomColor: colors.secondary.background,
paddingHorizontal: 12
paddingHorizontal: 12,
gap: 10
}}
>
<Heading size={SIZE.lg}>Save to Notesnook</Heading>
<TextInput
placeholder="Enter note title"
ref={inputRef}
style={{
flexShrink: 1,
flexGrow: 1,
fontFamily: "OpenSans-SemiBold",
fontSize: SIZE.lg,
paddingBottom: 0,
paddingTop: 0
}}
onChangeText={(value) => {
noteTitle.current = value;
}}
blurOnSubmit={false}
onSubmitEditing={() => {
editorRef.current.focus();
}}
/>
<Button
title="Done"
type="accent"
@@ -626,13 +660,15 @@ const ShareView = () => {
}}
>
{!loadingExtension && !loadingPage ? (
<Editor
editorRef={editorRef}
onLoad={onLoadEditor}
onChange={(html) => {
noteContent.current = html;
}}
/>
<>
<Editor
editorRef={editorRef}
onLoad={onLoadEditor}
onChange={(html) => {
noteContent.current = html;
}}
/>
</>
) : (
<>
{loadingPage ? (
@@ -646,7 +682,19 @@ const ShareView = () => {
</SafeAreaProvider>
</View>
{appendNoteId ? <AppendNote id={appendNoteId} /> : null}
{appendNoteId ? (
<AppendNote
id={appendNoteId}
onLoad={(title) => {
if (!noteTitle.current) {
noteTitle.current = title;
inputRef.current?.setNativeProps?.({
text: noteTitle.current
});
}
}}
/>
) : null}
<View
style={{
@@ -766,10 +814,16 @@ const ShareView = () => {
);
};
const AppendNote = ({ id }) => {
const AppendNote = ({ id, onLoad }) => {
const { colors } = useThemeColors();
const [item] = useDBItem(id, "note");
useEffect(() => {
if (item?.title) {
onLoad?.(item.title);
}
}, [item?.title, onLoad]);
return !item ? null : (
<Paragraph
size={SIZE.xs}

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/web",
"version": "3.0.0",
"version": "3.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@notesnook/web",
"version": "3.0.0",
"version": "3.0.1",
"hasInstallScript": true,
"license": "GPL-3.0-or-later",
"dependencies": {

View File

@@ -1,7 +1,7 @@
{
"name": "@notesnook/web",
"description": "Your private note taking space",
"version": "3.0.0",
"version": "3.0.1",
"private": true,
"main": "./src/app.js",
"homepage": "https://notesnook.com/",

View File

@@ -142,8 +142,13 @@ function DesktopAppContents({
}, [show]);
useEffect(() => {
if (isFocusMode) navPane.current?.collapse();
else navPane.current?.expand();
if (isFocusMode) {
const middlePaneSize = middlePane.current?.getSize() || 20;
navPane.current?.collapse();
// the middle pane has to be resized because collapsing the nav
// pane increases the middle pane's size every time.
middlePane.current?.resize(middlePaneSize);
} else navPane.current?.expand();
}, [isFocusMode]);
return (
@@ -159,9 +164,10 @@ function DesktopAppContents({
ref={navPane}
className="nav-pane"
defaultSize={10}
minSize={3}
onResize={(size) => setIsNarrow(size <= 3)}
minSize={3.5}
onResize={(size) => setIsNarrow(size <= 5)}
collapsible
collapsedSize={3.5}
>
<NavigationMenu
toggleNavigationContainer={(state) => {
@@ -192,7 +198,7 @@ function DesktopAppContents({
</ScopedThemeProvider>
</Panel>
<PanelResizeHandle className="panel-resize-handle" />
<Panel className="editor-pane">
<Panel className="editor-pane" defaultSize={70}>
<Flex
sx={{
display: "flex",

View File

@@ -77,18 +77,18 @@ export async function introduceFeatures() {
export const DEFAULT_CONTEXT = { colors: [], tags: [], notebook: {} };
export async function createBackup() {
export async function createBackup(rescueMode = false) {
const { isLoggedIn } = useUserStore.getState();
const { encryptBackups, toggleEncryptBackups } = useSettingStore.getState();
if (!isLoggedIn && encryptBackups) toggleEncryptBackups();
const verified = encryptBackups || (await verifyAccount());
const verified = rescueMode || encryptBackups || (await verifyAccount());
if (!verified) {
showToast("error", "Could not create a backup: user verification failed.");
return;
return false;
}
const encryptedBackups = isLoggedIn && encryptBackups;
const encryptedBackups = !rescueMode && isLoggedIn && encryptBackups;
const filename = sanitizeFilename(
`${formatDate(Date.now(), {
@@ -139,7 +139,9 @@ export async function createBackup() {
console.error(error);
} else {
showToast("success", `Backup saved at ${filePath}.`);
return true;
}
return false;
}
export async function selectBackupFile() {
@@ -193,7 +195,10 @@ export async function restoreBackupFile(backupFile: File) {
}
entries.push(entry);
}
if (!isValid) throw new Error("Invalid backup.");
if (!isValid)
console.warn(
"The backup file does not contain the verification .nnbackup file."
);
await db.transaction(async () => {
for (const entry of entries) {

View File

@@ -121,7 +121,7 @@ function Header(props: HeaderProps) {
}}
defaultItems={() =>
db.tags.all.limit(10).items(undefined, {
sortBy: "dateCreated",
sortBy: "title",
sortDirection: "desc"
})
}

View File

@@ -46,6 +46,7 @@ import { Section } from "../properties";
import { scrollIntoViewById } from "@notesnook/editor";
import { Button, Flex, Text } from "@theme-ui/components";
import { useEditorManager } from "./manager";
import { TITLE_BAR_HEIGHT } from "../title-bar";
type TableOfContentsProps = {
sessionId: string;
@@ -100,7 +101,7 @@ function TableOfContents(props: TableOfContentsProps) {
display: "flex",
position: "absolute",
right: 0,
top: 0,
top: TITLE_BAR_HEIGHT,
zIndex: 999,
height: "100%",
width: "300px",

View File

@@ -24,7 +24,6 @@ import "@notesnook/editor/styles/fonts.css";
import {
Toolbar,
useTiptap,
PortalProvider,
Editor,
AttachmentType,
usePermissionHandler,
@@ -41,7 +40,6 @@ import {
import { Box, Flex } from "@theme-ui/components";
import {
PropsWithChildren,
useCallback,
useEffect,
useLayoutEffect,
useMemo,
@@ -61,7 +59,6 @@ import { writeToClipboard } from "../../utils/clipboard";
import { useEditorStore } from "../../stores/editor-store";
import { parseInternalLink } from "@notesnook/core";
import Skeleton from "react-loading-skeleton";
import { showToast } from "../../utils/toast";
export type OnChangeHandler = (
content: () => string,
@@ -402,58 +399,56 @@ function TiptapWrapper(
}, [theme]);
return (
<PortalProvider>
<Flex
ref={containerRef}
sx={{
flex: 1,
flexDirection: "column",
".tiptap.ProseMirror": { pb: 150 }
<Flex
ref={containerRef}
sx={{
flex: 1,
flexDirection: "column",
".tiptap.ProseMirror": { pb: 150 }
}}
>
<TipTap
{...props}
onLoad={(editor) => {
props.onLoad?.(editor);
containerRef.current
?.querySelector(".editor-loading-container")
?.remove();
}}
>
<TipTap
{...props}
onLoad={(editor) => {
props.onLoad?.(editor);
containerRef.current
?.querySelector(".editor-loading-container")
?.remove();
}}
editorContainer={() => {
if (editorContainerRef.current) return editorContainerRef.current;
const editorContainer = document.createElement("div");
editorContainer.classList.add("selectable");
editorContainer.style.flex = "1";
editorContainer.style.cursor = "text";
editorContainer.style.color =
theme.scopes.editor?.primary?.paragraph ||
theme.scopes.base.primary.paragraph;
editorContainer.style.fontSize = `${editorConfig.fontSize}px`;
editorContainer.style.fontFamily =
getFontById(editorConfig.fontFamily)?.font || "sans-serif";
editorContainerRef.current = editorContainer;
return editorContainer;
}}
fontFamily={editorConfig.fontFamily}
fontSize={editorConfig.fontSize}
editorContainer={() => {
if (editorContainerRef.current) return editorContainerRef.current;
const editorContainer = document.createElement("div");
editorContainer.classList.add("selectable");
editorContainer.style.flex = "1";
editorContainer.style.cursor = "text";
editorContainer.style.color =
theme.scopes.editor?.primary?.paragraph ||
theme.scopes.base.primary.paragraph;
editorContainer.style.fontSize = `${editorConfig.fontSize}px`;
editorContainer.style.fontFamily =
getFontById(editorConfig.fontFamily)?.font || "sans-serif";
editorContainerRef.current = editorContainer;
return editorContainer;
}}
fontFamily={editorConfig.fontFamily}
fontSize={editorConfig.fontSize}
/>
{props.children}
<Box className="editor-loading-container">
<Skeleton
enableAnimation={false}
height={22}
style={{ marginTop: 16 }}
count={2}
/>
{props.children}
<Box className="editor-loading-container">
<Skeleton
enableAnimation={false}
height={22}
style={{ marginTop: 16 }}
count={2}
/>
<Skeleton
enableAnimation={false}
height={22}
width={25}
style={{ marginTop: 16 }}
/>
</Box>
</Flex>
</PortalProvider>
<Skeleton
enableAnimation={false}
height={22}
width={25}
style={{ marginTop: 16 }}
/>
</Box>
</Flex>
);
}
export default TiptapWrapper;

View File

@@ -29,6 +29,8 @@ import {
ZoomIn,
ZoomOut
} from "../icons";
import { getPlatform } from "../../utils/platform";
import { TITLE_BAR_HEIGHT } from "../title-bar";
const DEFAULT_ZOOM_STEP = 0.3;
const DEFAULT_LARGE_ZOOM = 4;
@@ -337,7 +339,12 @@ export class Lightbox extends React.Component<LightboxProps> {
borderRadius: "0px 0px 0px 5px",
overflow: "hidden",
alignItems: "center",
justifyContent: "flex-end"
justifyContent: "flex-end",
height: IS_DESKTOP_APP ? TITLE_BAR_HEIGHT : "auto",
pr:
IS_DESKTOP_APP && getPlatform() !== "darwin"
? "calc(100vw - env(titlebar-area-width))"
: 0
}}
>
{tools.map((tool) => (
@@ -349,6 +356,7 @@ export class Lightbox extends React.Component<LightboxProps> {
bg="transparent"
title={tool.title}
sx={{
height: "100%",
borderRadius: 0,
display: [
tool.hideOnMobile ? "none" : "flex",

View File

@@ -106,7 +106,7 @@ function NavigationItem(
<Button
data-test-id={`navigation-item`}
sx={{
px: 2,
px: isTablet ? 1 : 2,
flex: 1,
alignItems: "center",
justifyContent: isTablet ? "center" : "flex-start",
@@ -124,7 +124,10 @@ function NavigationItem(
}}
>
{image ? (
<Image src={image} sx={{ borderRadius: 50, size: 20 }} />
<Image
src={image}
sx={{ borderRadius: 50, size: 20, minWidth: 20, flexShrink: 0 }}
/>
) : Icon ? (
<Icon
size={isTablet ? 16 : 15}

View File

@@ -282,7 +282,8 @@ export default React.memo(Note, function (prevProps, nextProps) {
prevProps.notebooks?.dateEdited === nextProps.notebooks?.dateEdited &&
prevProps.tags?.dateEdited === nextProps.tags?.dateEdited &&
prevProps.reminder?.dateModified === nextProps.reminder?.dateModified &&
prevProps.attachments === nextProps.attachments &&
prevProps.attachments?.failed === nextProps.attachments?.failed &&
prevProps.attachments?.total === nextProps.attachments?.total &&
prevProps.locked === nextProps.locked
);
});

View File

@@ -61,6 +61,7 @@ import {
} from "@notesnook/core";
import { VirtualizedTable } from "../virtualized-table";
import { TextSlice } from "@notesnook/core/dist/utils/content-block";
import { TITLE_BAR_HEIGHT } from "../title-bar";
const tools = [
{ key: "pin", property: "pinned", icon: Pin, label: "Pin" },
@@ -129,7 +130,7 @@ function EditorProperties(props: EditorPropertiesProps) {
sx={{
display: "flex",
position: "absolute",
top: 0,
top: TITLE_BAR_HEIGHT,
right: 0,
zIndex: 999,
height: "100%",

View File

@@ -56,6 +56,7 @@ function Toggle(props: ToggleProps) {
<Switch
sx={{ m: 0, bg: isOn ? "accent" : "icon-secondary" }}
checked={isOn}
onClick={(e) => e.stopPropagation()}
/>
</Flex>
);

View File

@@ -29,6 +29,7 @@ import {
} from "../icons";
import { BaseThemeProvider } from "../theme-provider";
export const TITLE_BAR_HEIGHT = IS_DESKTOP_APP ? 37.8 : 0;
export function TitleBar() {
const { isMaximized, isFullscreen, hasNativeWindowControls } =
useWindowControls();
@@ -65,7 +66,7 @@ export function TitleBar() {
scope="titleBar"
sx={{
background: "background",
height: 37.8,
height: TITLE_BAR_HEIGHT,
display: "flex",
borderBottom: "1px solid var(--border)",
...(!isFullscreen && hasNativeWindowControls

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { useCallback, useEffect, useRef } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { Button, Flex, Text } from "@theme-ui/components";
import {
Plus,
@@ -46,7 +46,8 @@ import {
TreeEnvironmentRef
} from "react-complex-tree";
import { FlexScrollContainer } from "../components/scroll-container";
import { pluralize, usePromise } from "@notesnook/common";
import { pluralize } from "@notesnook/common";
import Field from "../components/field";
type MoveDialogProps = { onClose: Perform; noteIds: string[] };
type NotebookReference = {
@@ -73,32 +74,17 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
const setIsMultiselect = useSelectionStore((store) => store.setIsMultiselect);
const isMultiselect = useSelectionStore((store) => store.isMultiselect);
const refreshNotebooks = useStore((store) => store.refresh);
// const notebooks = useStore((store) => store.notebooks);
const reloadItem = useRef<(changedItemIds: TreeItemIndex[]) => void>();
const treeRef = useRef<TreeEnvironmentRef>(null);
const rootNotebooks = usePromise(() =>
db.notebooks.roots.ids(db.settings.getGroupOptions("notebooks"))
);
const [notebooks, setNotebooks] = useState<string[]>([]);
useEffect(() => {
// for (const notebook of notebooks.ids) {
// if (isGroupHeader(notebook)) continue;
// // for (const topic of notebook.topics) {
// // const isSelected =
// // selected.findIndex(
// // (item) => item.id === notebook.id && item.topic === topic.id
// // ) > -1;
// // if (!isSelected && topicHasNotes(topic, noteIds)) {
// // selected.push({
// // id: notebook.id,
// // topic: topic.id,
// // op: "add",
// // new: false
// // });
// // }
// // }
// }
db.notebooks.roots
.ids(db.settings.getGroupOptions("notebooks"))
.then((ids) => setNotebooks(ids));
}, []);
useEffect(() => {
(async function () {
const selected: NotebookReference[] = useSelectionStore
.getState()
@@ -123,20 +109,6 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
setSelected(selected);
setIsMultiselect(selected.length > 1);
})();
// for (const notebook of noteIds
// .map((id) => db.relations.to({ id, type: "note" }, "notebook"))
// .flat()) {
// const isSelected =
// notebook && selected.findIndex((item) => item.id === notebook.id) > -1;
// if (!notebook || isSelected) continue;
// selected.push({
// id: notebook.id,
// op: "add",
// new: false
// });
// }
}, [noteIds, refreshNotebooks, setSelected, setIsMultiselect]);
const _onClose = useCallback(
@@ -155,7 +127,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
title={"Select notebooks"}
description={`Use ${
isMac() ? "cmd" : "ctrl"
}+click to select multiple topics`}
}+click to select multiple notebooks`}
onClose={() => _onClose(false)}
width={450}
positiveButton={{
@@ -194,6 +166,22 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
onClick: () => _onClose(false)
}}
>
<Field
autoFocus
sx={{ m: 0, mb: 2 }}
styles={{
input: { p: "7.5px" }
}}
placeholder={"Search notebooks"}
onChange={async (e) => {
const query = e.target.value.trim();
const ids = await (query
? db.lookup.notebooks(query).ids()
: db.notebooks.roots.ids(db.settings.getGroupOptions("notebooks")));
setNotebooks(ids);
reloadItem.current?.(["root"]);
}}
/>
{isMultiselect && (
<Button
variant="anchor"
@@ -210,8 +198,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
Reset selection
</Button>
)}
{rootNotebooks.status === "fulfilled" &&
rootNotebooks.value.length > 0 ? (
{notebooks.length > 0 ? (
<FlexScrollContainer>
<UncontrolledTreeEnvironment
ref={treeRef}
@@ -232,7 +219,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
isFolder: true,
canMove: false,
canRename: false,
children: rootNotebooks.value
children: notebooks
};
}
@@ -268,7 +255,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
isFolder: true,
canMove: false,
canRename: false,
children: rootNotebooks.value
children: notebooks
};
}
@@ -332,9 +319,9 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
sx={{ mt: 2 }}
onClick={() =>
showAddNotebookDialog().then(() =>
rootNotebooks.status === "fulfilled"
? rootNotebooks.refresh()
: null
db.notebooks.roots
.ids(db.settings.getGroupOptions("notebooks"))
.then((ids) => setNotebooks(ids))
)
}
>

View File

@@ -185,9 +185,16 @@ export default function NoteLinkingDialog(props: NoteLinkingDialogProps) {
autoFocus
placeholder="Search for a note to link to..."
sx={{ mx: 0 }}
onChange={async (e) =>
setNotes(await db.lookup.notes(e.target.value).sorted())
}
onChange={async (e) => {
const query = e.target.value.trim();
setNotes(
query
? await db.lookup.notes(e.target.value).sorted()
: await db.notes.all.sorted(
db.settings.getGroupOptions("home")
)
);
}}
/>
{notes && (
<ScrollContainer>

View File

@@ -44,7 +44,8 @@ export const AppearanceSettings: SettingsGroup[] = [
type: "input",
inputType: "number",
min: 0.5,
max: 2.0,
max: 3.0,
step: 0.1,
defaultValue: () => useSettingStore.getState().zoomFactor,
onChange: (value) => useSettingStore.getState().setZoomFactor(value)
}

View File

@@ -46,7 +46,7 @@ export const AuthenticationSettings: SettingsGroup[] = [
title: "Change password",
variant: "secondary",
action: async () => {
await createBackup();
if (!(await createBackup())) return;
const result = await showPasswordDialog({
title: "Change account password",
message: `All your data will be re-encrypted and synced with the new password.

View File

@@ -508,6 +508,7 @@ function SettingItem(props: { item: Setting }) {
type={"number"}
min={component.min}
max={component.max}
step={component.step}
defaultValue={component.defaultValue()}
sx={{ width: 80, mr: 1 }}
onChange={debounce((e) => {

View File

@@ -111,6 +111,26 @@ What data is collected & when?`,
section: "privacy",
header: "Advanced",
settings: [
{
key: "custom-dns",
title: "Use custom DNS",
description: `Notesnook uses the following DNS providers:
1. Cloudflare DNS
2. Quad9
This can sometimes bypass local ISP blockages on Notesnook traffic. Disable this if you want the app to use system's DNS settings.`,
onStateChange: (listener) =>
useSettingStore.subscribe((s) => s.customDns, listener),
isHidden: () => !IS_DESKTOP_APP,
components: [
{
type: "toggle",
isToggled: () => useSettingStore.getState().customDns,
toggle: () => useSettingStore.getState().toggleCustomDns()
}
]
},
{
key: "custom-cors",
title: "Custom CORS proxy",

View File

@@ -64,7 +64,6 @@ export const ProfileSettings: SettingsGroup[] = [
key: "manage-attachments",
title: "Attachments",
description: "Manage all your attachments in one place.",
isHidden: () => !useUserStore.getState().isLoggedIn,
components: [
{
type: "button",

View File

@@ -134,6 +134,7 @@ export type NumberInputSettingComponent = BaseSettingComponent<"input"> & {
inputType: "number";
min: number;
max: number;
step?: number;
defaultValue: () => number;
onChange: (value: number) => void;
};

View File

@@ -30,7 +30,6 @@ export function useIsUserPremium() {
}
export function isUserPremium(user?: User) {
return true;
if (IS_TESTING) return true;
if (!user) user = userstore.get().user;
if (!user) return false;

View File

@@ -438,7 +438,11 @@ class EditorStore extends BaseStore<EditorStore> {
if (index === -1) return;
const session = state.sessions[index] as SessionTypeMap[T[number]];
if (typeof partial === "function") partial(session);
else state.sessions[index] = { ...session, ...partial };
else {
for (const key in partial) {
session[key] = partial[key] as any;
}
}
});
};

View File

@@ -42,6 +42,7 @@ class SettingStore extends BaseStore<SettingStore> {
zoomFactor = 1.0;
privacyMode = false;
customDns = true;
hideNoteTitle = Config.get("hideNoteTitle", false);
telemetry = isTelemetryEnabled();
dateFormat = "DD-MM-YYYY";
@@ -67,6 +68,7 @@ class SettingStore extends BaseStore<SettingStore> {
desktopIntegrationSettings:
await desktop?.integration.desktopIntegration.query(),
privacyMode: await desktop?.integration.privacyMode.query(),
customDns: await desktop?.integration.customDns.query(),
zoomFactor: await desktop?.integration.zoomFactor.query(),
autoUpdates: await desktop?.updater.autoUpdates.query(),
proxyRules: await desktop?.integration.proxyRules.query()
@@ -177,6 +179,12 @@ class SettingStore extends BaseStore<SettingStore> {
await desktop?.integration.setPrivacyMode.mutate({ enabled: !privacyMode });
};
toggleCustomDns = async () => {
const customDns = this.get().customDns;
this.set({ customDns: !customDns });
await desktop?.integration.setCustomDns.mutate(!customDns);
};
toggleHideTitle = async () => {
const { hideNoteTitle } = this.get();
this.set({ hideNoteTitle: !hideNoteTitle });

View File

@@ -43,16 +43,15 @@ export class AttachmentStream extends ReadableStream<ZipFile> {
super({
start() {},
async pull(controller) {
if (signal?.aborted) {
controller.close();
return;
}
onProgress && onProgress(index);
const attachment = await resolve(ids[index++]);
if (!attachment) return;
try {
if (signal?.aborted) {
controller.close();
return;
}
onProgress && onProgress(index);
const attachment = await resolve(ids[index++]);
if (!attachment) return;
if (
!(await db
.fs()
@@ -87,7 +86,7 @@ export class AttachmentStream extends ReadableStream<ZipFile> {
);
}
} catch (e) {
console.error(e);
console.error(e, attachment);
showToast("error", (e as Error).message);
} finally {
if (index === ids.length) {

View File

@@ -460,7 +460,7 @@ function BackupData(props: BaseRecoveryComponentProps<"backup">) {
"Please wait while we create a backup file for you to download."
}}
onSubmit={async () => {
await createBackup();
await createBackup(true);
navigate("new");
}}
>

View File

@@ -0,0 +1,7 @@
- Now you can set title of note when sharing to Notesnook
- Fix back navigation from editor closes the app
- Fix errors during migration
- Fix sharing to Notesnook not working for some apps
- Numerous other bug fixes and performance improvements
Thank you for using Notesnook!

View File

@@ -151,7 +151,10 @@ async function resolveNotes(ids: string[]) {
notebooks: await db.notebooks.all.records(
Array.from(relationIds.notebooks)
),
tags: await db.tags.all.records(Array.from(relationIds.tags)),
tags: await db.tags.all.records(Array.from(relationIds.tags), {
sortBy: "title",
sortDirection: "desc"
}),
colors: await db.colors.all.records(Array.from(relationIds.colors)),
reminders: await db.reminders.all.records(
Array.from(relationIds.reminders)

View File

@@ -0,0 +1,176 @@
/*
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 { expect, test } from "vitest";
import { TEST_NOTE, databaseTest, noteTest } from "./utils";
test("updating deleted content should not throw", () =>
databaseTest().then(async (db) => {
const id = await db.notes.add({
title: "New note"
});
const contentId = await db.content.add({
data: "helloworld",
noteId: id
});
await db.content.remove(contentId!);
await expect(
db.content.collection.update(
[contentId!],
{ synced: true },
{ sendEvent: false }
)
).resolves.toBeFalsy();
}));
test("updating content should not break full text search", () =>
databaseTest().then(async (db) => {
const id = await db.notes.add({
title: "New note"
});
const contentId = await db.content.add({
data: "hello world",
noteId: id
});
await db.content.add({
id: contentId,
data: "i am amazing",
noteId: id
});
expect(await db.lookup.notes("amazing").ids()).toContain(id);
expect(await db.lookup.notes("hello world").ids()).not.toContain(id);
}));
test("updating note title should not break full text search", () =>
databaseTest().then(async (db) => {
const id = await db.notes.add({
title: "New note"
});
await db.notes.add({
id,
title: "What an amazing note!"
});
expect(await db.lookup.notes("amazing").ids()).toContain(id);
expect(await db.lookup.notes("new note").ids()).not.toContain(id);
}));
test("updating deleted note should not throw", () =>
databaseTest().then(async (db) => {
const id = await db.notes.add({
title: "New note"
});
await db.notes.remove(id);
await expect(
db.notes.collection.update([id], { synced: true }, { sendEvent: false })
).resolves.toBeFalsy();
}));
test("overwriting unlocked content with locked content should update search index", () =>
noteTest({
content: {
data: "hello world",
type: "tiptap"
}
}).then(async ({ db, id }) => {
await db.content.collection.upsert({
id: "something",
locked: false,
data: "What is this?",
noteId: id,
dateCreated: Date.now(),
dateEdited: Date.now(),
synced: false,
dateModified: Date.now()
});
await db.content.collection.put([
{
id: "something",
locked: true,
data: {
alg: "as",
cipher: "s",
format: "base64",
iv: "",
length: 20,
salt: ""
},
noteId: id,
dateCreated: Date.now(),
dateEdited: Date.now(),
synced: false,
dateModified: Date.now()
}
]);
expect(await db.lookup.notes("what is this").ids()).not.toContain(id);
}));
test("overwriting content with deleted content should update search index", () =>
noteTest({
content: {
data: "hello world",
type: "tiptap"
}
}).then(async ({ db, id }) => {
await db.content.collection.upsert({
id: "something",
locked: false,
data: "What is this?",
noteId: id,
dateCreated: Date.now(),
dateEdited: Date.now(),
synced: false,
dateModified: Date.now()
});
await db.content.collection.put([
{
id: "something",
deleted: true,
synced: true,
dateModified: Date.now()
}
]);
expect(await db.lookup.notes("what is this").ids()).not.toContain(id);
}));
test("overwriting note with deleted note should update search index", () =>
noteTest({
title: "I am title"
}).then(async ({ db, id }) => {
await db.notes.collection.put([
{
id,
deleted: true,
synced: true,
dateModified: Date.now()
}
]);
expect(await db.lookup.notes("title").ids()).not.toContain(id);
}));

View File

@@ -363,7 +363,7 @@ test("permanently deleting a notebook should not delete independently deleted su
expect(trash.some((a) => a.id === child)).toBe(false);
}));
test.only("permanently deleted note should not have note fields", () =>
test("permanently deleted note should not have note fields", () =>
databaseTest().then(async (db) => {
const noteId = await db.notes.add(TEST_NOTE);
await db.notes.moveToTrash(noteId);

View File

@@ -24,6 +24,7 @@ import { DatabaseSchema, RawDatabaseSchema } from "../database";
import { AnyColumnWithTable, Kysely, sql } from "kysely";
import { FilteredSelector } from "../database/sql-collection";
import { VirtualizedGrouping } from "../utils/virtualized-grouping";
import { logger } from "../logger";
type SearchResults<T> = {
sorted: (limit?: number) => Promise<VirtualizedGrouping<T>>;
@@ -44,8 +45,6 @@ export default class Lookup {
if (query.length < 3) return [];
const db = this.db.sql() as unknown as Kysely<RawDatabaseSchema>;
query = query.replace(/"/, '""');
const excludedIds = this.db.trash.cache.notes;
const results = await db
.selectFrom((eb) =>
@@ -57,7 +56,7 @@ export default class Lookup {
.$if(excludedIds.length > 0, (eb) =>
eb.where("id", "not in", excludedIds)
)
.where("title", "match", `"${query}"`)
.where("title", "match", query)
.select(["id", sql<number>`rank * 10`.as("rank")])
.unionAll((eb) =>
eb
@@ -68,7 +67,7 @@ export default class Lookup {
.$if(excludedIds.length > 0, (eb) =>
eb.where("id", "not in", excludedIds)
)
.where("data", "match", `"${query}"`)
.where("data", "match", query)
.select(["noteId as id", "rank"])
.$castTo<{ id: string; rank: number }>()
)
@@ -85,7 +84,11 @@ export default class Lookup {
"in",
(notes || this.db.notes.all).filter.select("id")
)
.execute();
.execute()
.catch((e) => {
logger.error(e, `Error while searching`, { query });
return [];
});
return results.map((r) => r.id);
}, notes || this.db.notes.all);
}

View File

@@ -44,7 +44,6 @@ import {
Kysely,
SelectQueryBuilder,
SqlBool,
Transaction,
sql
} from "kysely";
import { VirtualizedGrouping } from "../utils/virtualized-grouping";
@@ -338,8 +337,10 @@ export class SQLCollection<
}
export class FilteredSelector<T extends Item> {
private _fields: AnyColumnWithTable<DatabaseSchema, keyof DatabaseSchema>[] =
[];
private _fields: (
| AnyColumn<DatabaseSchema, keyof DatabaseSchema>
| AnyColumnWithTable<DatabaseSchema, keyof DatabaseSchema>
)[] = [];
filter: SelectQueryBuilder<DatabaseSchema, keyof DatabaseSchema, unknown>;
private _limit = 0;
constructor(
@@ -524,6 +525,11 @@ export class FilteredSelector<T extends Item> {
async *[Symbol.asyncIterator]() {
let lastRow: any | null = null;
const fields = this._fields.slice();
if (!fields.find((f) => f.includes(".dateCreated")))
fields.push("dateCreated");
if (!fields.find((f) => f.includes(".id"))) fields.push("id");
while (true) {
const rows = await this.filter
.orderBy("dateCreated asc")
@@ -536,8 +542,8 @@ export class FilteredSelector<T extends Item> {
)
)
.limit(this.batchSize)
.$if(this._fields.length === 0, (eb) => eb.selectAll())
.$if(this._fields.length > 0, (eb) => eb.select(this._fields))
.$if(fields.length === 0, (eb) => eb.selectAll())
.$if(fields.length > 0, (eb) => eb.select(fields))
.execute();
if (rows.length === 0) break;
for (const row of rows) {

View File

@@ -31,8 +31,8 @@ export async function createTriggers(db: Kysely<RawDatabaseSchema>) {
.addEvent("insert")
.when((eb) =>
eb.and([
eb.or([eb("new.deleted", "is", null), eb("new.deleted", "==", false)]),
eb.or([eb("new.locked", "is", null), eb("new.locked", "==", false)]),
eb("new.deleted", "is not", true),
eb("new.locked", "is not", true),
eb("new.data", "is not", null)
])
)
@@ -71,6 +71,13 @@ export async function createTriggers(db: Kysely<RawDatabaseSchema>) {
.onTable("content", "main")
.after()
.addEvent("update")
.when((eb) =>
eb.and([
eb("old.deleted", "is not", true),
eb("old.noteId", "is not", null),
eb("old.data", "is not", null)
])
)
.addQuery((c) =>
c.insertInto("content_fts").values({
content_fts: sql.lit("delete"),
@@ -135,7 +142,13 @@ export async function createTriggers(db: Kysely<RawDatabaseSchema>) {
.ifNotExists()
.onTable("notes", "main")
.after()
.addEvent("update", ["title"])
.addEvent("update")
.when((eb) =>
eb.and([
eb("old.deleted", "is not", true),
eb("old.title", "is not", null)
])
)
.addQuery((c) =>
c.insertInto("notes_fts").values({
notes_fts: sql.lit("delete"),

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