Compare commits

...

123 Commits

Author SHA1 Message Date
ammarahm-ed
aac43274a0 mobile: release 2.4.10 2023-03-30 17:22:00 +05:00
Muhammad Ali
2fb2c4247a web: do not add "none" tool in Configure Toolbar dialog (#2193)
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-03-30 17:10:43 +05:00
Abdulrehman-Jafer
cb1d32d177 core: fix txt, pdf & html export line spacing (#2215)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
Co-authored-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
Co-authored-by: Abdullah Atta <thecodrr@protonmail.com>
2023-03-30 17:09:00 +05:00
ammarahm-ed
75e1985a6f mobile: save value on change 2023-03-30 16:48:55 +05:00
ammarahm-ed
0093b39f64 mobile: fix nav bar does not follow app theme 2023-03-30 16:07:58 +05:00
ammarahm-ed
d19c2ef735 mobile: add mastodon social link 2023-03-30 15:30:55 +05:00
ammarahm-ed
db527cfbf3 mobile: fix splashscreen logo quality 2023-03-30 15:04:08 +05:00
ammarahm-ed
7da8ddb24f mobile: topic title should to single line 2023-03-30 15:02:01 +05:00
ammarahm-ed
5d401ac9ac mobile: use diff sheet size on each platform 2023-03-30 13:54:21 +05:00
ammarahm-ed
9605eafa00 mobile: add support for sorting topics in sheet 2023-03-30 13:54:21 +05:00
Ammar Ahmed
783460c685 Merge pull request #2218 from streetwriters/fix-reminder-date-set
mobile: fix reminder date picker not going in past
2023-03-29 16:54:24 +05:00
ammarahm-ed
af906d8ace mobile: add some null checks 2023-03-29 16:54:04 +05:00
ammarahm-ed
fca26a8793 mobile: fix reminder date picker not going in past 2023-03-29 16:29:40 +05:00
Muhammad Ali
27863def9c web: fix RTL support for outline lists (#2175)
Co-authored-by: Muhammad Ali <alihamuh@gmail.com>
Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
2023-03-24 12:51:00 +05:00
Abdullah Atta
f1d4fd1d5d editor: fix crash when theme.colors is undefined 2023-03-24 12:49:26 +05:00
Abdullah Atta
61b7e43d08 web: do not interrupt importer if notebook is undefined 2023-03-24 11:43:37 +05:00
Abdullah Atta
87ebe1918c web: use debounce instead of inlineDebounce for selection updates in editor 2023-03-24 11:41:03 +05:00
ammarahm-ed
4c5253c267 mobile: release v2.4.9 2023-03-24 02:54:36 +05:00
Abdulrehman-Jafer
f5be8a0c86 web: migrate keyboard.js to typescript (#2160)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-24 00:57:55 +05:00
Abdullah Atta
5392582819 web: fix crash on opening an empty notebook 2023-03-24 00:35:29 +05:00
Abdullah Atta
651ae4cdaa web: bump version to 2.4.6 2023-03-24 00:28:19 +05:00
Abdullah Atta
d87b5c5386 desktop: don't show tray menu icons on flatpak builds 2023-03-24 00:28:19 +05:00
ammarahm-ed
d5331b1997 mobile: release v2.4.8 2023-03-24 00:18:12 +05:00
ammarahm-ed
0a6f8fa18e mobile: improve publish note sheet ui 2023-03-24 00:12:18 +05:00
Abdullah Atta
c1ba16eb80 web: group top level notebook notes 2023-03-23 23:58:17 +05:00
Ammar Ahmed
0cd01b25bd Merge pull request #2187 from streetwriters/fix/publish-note-mobile
Fix publish note with password on mobile
2023-03-23 23:52:50 +05:00
Abdullah Atta
f0c1300ec8 web: hyper optimize move note dialog 2023-03-23 23:48:49 +05:00
ammarahm-ed
70eee2a224 mobile: cleanup logs 2023-03-23 23:48:49 +05:00
ammarahm-ed
42470d1d67 mobile: fix add button color in dark mode 2023-03-23 23:48:49 +05:00
ammarahm-ed
7fc58a3fa5 mobile: make bottom padding optional in sheet 2023-03-23 23:48:49 +05:00
ammarahm-ed
e186037c03 mobile: fix bottom padding on sheet 2023-03-23 23:48:49 +05:00
ammarahm-ed
732a0ffea4 mobile: fix duplicate key warning 2023-03-23 23:48:49 +05:00
ammarahm-ed
89f2e9fde1 mobile: show selected indicator on notebook 2023-03-23 23:48:49 +05:00
Abdullah Atta
c369541a7f web: indicate which notebooks have selected topics 2023-03-23 23:48:49 +05:00
Abdullah Atta
0b7a157ab1 web: enable multiselect mode if note is in > 1 nb/topic 2023-03-23 23:48:49 +05:00
ammarahm-ed
9b86c6ebb7 mobile: fix multiselection bugs 2023-03-23 23:48:49 +05:00
ammarahm-ed
22cd07e414 mobile: improve selection performance 2023-03-23 23:48:49 +05:00
ammarahm-ed
47f919cb61 mobile: fix reset select does not reset selected topics 2023-03-23 23:48:49 +05:00
Abdulrehman-Jafer
5b3c889b7c web: migrate css.js to typescript (#2165)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-23 17:03:23 +05:00
Abdullah Atta
77ac53215c web: remove document as any in page-visibility 2023-03-23 13:55:52 +05:00
Abdulrehman-Jafer
167c7125d2 web: migrate page-visibility.js to typescript (#2166)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-23 13:48:57 +05:00
Abdullah Atta
ba56fd0279 web: don't reset reminders before getting the user 2023-03-23 13:46:54 +05:00
Abdullah Atta
8e795b729a web: read trash cleanup interval from db on startup 2023-03-23 13:46:54 +05:00
Abdullah Atta
a4eb597f35 web: improve notebook vertical pane resizing 2023-03-23 13:46:44 +05:00
Abdullah Atta
0aeade69da web: fix topic search 2023-03-23 13:43:45 +05:00
Abdullah Atta
90a3bd3307 web: fix crash on creating a topic 2023-03-23 13:43:45 +05:00
ammarahm-ed
7a61e3558e mobile: ui cleanup 2023-03-23 13:39:14 +05:00
Abdulrehman-Jafer
dd9c555bff web: migrate parse.js to typescript (#2162)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-23 13:36:31 +05:00
ammarahm-ed
50f84780bb mobile: fix publish note with password not working 2023-03-23 13:23:04 +05:00
Abdulrehman-Jafer
e12372364d web: add support for realtime search (#2171)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-23 12:45:33 +05:00
ammarahm-ed
969611c9e6 mobile: release v2.4.7 2023-03-22 15:05:24 +05:00
ammarahm-ed
06399fe3bd mobile: fix title hidden on navigate sometimes 2023-03-22 15:02:55 +05:00
Abdullah Atta
35c5a90b4e web: update version highlights 2023-03-22 13:47:41 +05:00
Abdullah Atta
3c70cccd14 web: topic is rendered incomplete when opened directly 2023-03-22 13:42:36 +05:00
Abdullah Atta
a3c24bc666 editor: fix image resizing 2023-03-22 13:28:25 +05:00
ammarahm-ed
16b00d21a6 mobile: cleanup 2023-03-22 13:28:25 +05:00
Abdullah Atta
88f90c6240 editor: add padding to placeholder 2023-03-22 13:28:25 +05:00
Abdullah Atta
a5c44dcb3b editor: add src to image on copy 2023-03-22 13:28:25 +05:00
Abdullah Atta
c0a2dd139a web: only load new media attachments on realtime sync 2023-03-22 13:28:25 +05:00
Abdullah Atta
b354a26f0a core: allow selectively downloading media attachments 2023-03-22 13:28:25 +05:00
ammarahm-ed
b3de1205d9 mobile: cleanup 2023-03-22 13:28:25 +05:00
ammarahm-ed
f18e8ae421 mobile: properly handle loading images in editor 2023-03-22 13:28:25 +05:00
ammarahm-ed
a46b863802 core: return only attachment meta if
attachment already exists
2023-03-22 13:28:25 +05:00
ammarahm-ed
2befa03cd2 mobile: conform to new image loading api 2023-03-22 13:28:25 +05:00
Abdullah Atta
763a108abb editor: always use relative height for resizer 2023-03-22 13:28:25 +05:00
Abdullah Atta
30a069c845 editor: always use black icons on image overlay 2023-03-22 13:28:25 +05:00
Abdullah Atta
ffbf5cdc88 editor: fade-in on image load 2023-03-22 13:28:25 +05:00
Abdullah Atta
4318a65a99 editor: use dataurl attr for inserting an image 2023-03-22 13:28:25 +05:00
Abdullah Atta
bc433c56ae core: don't return invalid dataurl if image doesn't exist 2023-03-22 13:28:25 +05:00
Abdullah Atta
e43740c6bc editor: add comments for why we use UTITypes 2023-03-22 13:28:25 +05:00
Abdullah Atta
6123bccf4b editor: only save image height on load 2023-03-22 13:28:25 +05:00
Abdullah Atta
08106c20e8 editor: use dataurl instead of src attr for loading images 2023-03-22 13:28:25 +05:00
Abdullah Atta
eaaecbd5e5 core: hash base64 before saving it as attachment to prevent dups 2023-03-22 13:28:25 +05:00
Abdullah Atta
29608099db web: writeEncrypted -> writeEncryptedBase64 2023-03-22 13:28:25 +05:00
Abdullah Atta
7a773937c2 core: writeEncrypted now accepts only base64 input 2023-03-22 13:28:25 +05:00
Abdullah Atta
fa4a298bfa web: use NodeJS.Platform instead of process.platform 2023-03-22 10:45:03 +05:00
Abdulrehman-Jafer
966bedbb49 web: migrate platform.js to typescript (#2163)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-22 10:42:49 +05:00
Abdulrehman-Jafer
159b6f3ca6 web: migrate dimensions.js to typescript (#2153)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-22 09:21:48 +05:00
Abdulrehman-Jafer
de1b0fc466 web: migrate filename.js to typescript (#2156)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-22 09:07:52 +05:00
Abdulrehman-Jafer
b6a3394dee web: migrate object.js to typescript (#2161)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-22 09:00:29 +05:00
Abdulrehman-Jafer
f5ac51db13 web: migrate random.js to typescript (#2164)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-22 08:59:07 +05:00
Abdullah Atta
1ec0b65fe8 web: extract attachment extension from mime-type 2023-03-21 09:59:45 +05:00
Abdullah Atta
d9a77ff658 core: migrate filename.ts to javascript 2023-03-21 09:59:45 +05:00
ammarahm-ed
c45118515c editor: add react-scripts patch 2023-03-21 09:59:45 +05:00
ammarahm-ed
f9b8865351 editor: export blob as b64 with correct content type 2023-03-21 09:59:45 +05:00
ammarahm-ed
1e92b3fc92 mobile: resolve attachment hash from src if hash not avail 2023-03-21 09:59:45 +05:00
ammarahm-ed
f34fb858ec mobile: add filename extension before download 2023-03-21 09:59:45 +05:00
ammarahm-ed
1fdede5518 core: add getFileNameWithExtension utility 2023-03-21 09:59:45 +05:00
Abdulrehman-Jafer
a454ec4944 web: migrate color.js to typescript (#2151)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-21 09:37:56 +05:00
Abdullah Atta
5afbf103ac ci: disable arm64 builds for snap 2023-03-21 09:25:15 +05:00
Abdullah Atta
2ef1b76bfb ci: disable deb/rpm builds 2023-03-21 09:03:15 +05:00
Abdullah Atta
f7d06d3b82 desktop: enable arm64 builds 2023-03-21 08:40:03 +05:00
Abdullah Atta
e405468471 web: bump version to 2.4.5 2023-03-21 08:07:11 +05:00
Abdullah Atta
9d2bacdc5f web: replace fixed 7 days with trash cleanup interval 2023-03-21 08:06:34 +05:00
Abdullah Atta
b44d4702be web: update @notesnook-importer/core to v1.7.1 2023-03-20 18:52:05 +05:00
Abdullah Atta
03803dbc23 editor: fix task-list tests 2023-03-20 18:05:27 +05:00
Abdullah Atta
9ea675ded1 editor: fix sub checklists not sorting 2023-03-20 18:05:27 +05:00
Abdullah Atta
c85d623e1c web: update tab/window title for locked & conflicted notes 2023-03-20 18:05:27 +05:00
ammarahm-ed
e6f1320b1d mobile: optimize sheets 2023-03-20 17:08:06 +05:00
Abdullah Atta
80f49e22e5 web: reset tab/window title on new note 2023-03-20 17:05:18 +05:00
Abdullah Atta
4884c27a9a editor: update patches 2023-03-20 16:40:58 +05:00
Ammar Ahmed
4976a4dab4 Merge pull request #2143 from streetwriters/fix-ipad-orientation
mobile: fix ipad orientation window sizing
2023-03-20 16:36:02 +05:00
Ammar Ahmed
5eb0b5faa7 Merge pull request #2145 from streetwriters/fix-gallery-not-shown-reupload
Fix gallery not shown when reupload image
2023-03-20 16:35:49 +05:00
Ammar Ahmed
5e33cb93c6 editor: fix focus last node on tap bottom area on mobile (#2148) 2023-03-20 16:35:23 +05:00
ammarahm-ed
b4220d349f editor: fix loading copied image on ios 2023-03-20 16:34:46 +05:00
Abdullah Atta
d4752685d0 editor: keep marks when creating new list items
we shouldn't reset the user's selected styles like bold/italic when they
create a new list item.
2023-03-20 16:33:14 +05:00
ammarahm-ed
0b06d2a4bd mobile: fix attachment dialog ui 2023-03-20 15:09:52 +05:00
ammarahm-ed
a37d9a193f mobile: fix gallery not shown on reupload image 2023-03-20 15:09:23 +05:00
Abdulrehman-Jafer
b1d465e8bf web: fix monographs and reminders search (#2142)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-20 14:40:51 +05:00
Abdullah Atta
31e17c305f web: show correct sync status when sync is disabled 2023-03-20 14:01:20 +05:00
Abdullah Atta
72db8724ec editor: improve gradient overlay on image 2023-03-20 14:00:50 +05:00
ammarahm-ed
b64cb22186 mobile: fix ipad orientation window sizing 2023-03-20 13:51:16 +05:00
Abdullah Atta
f5ca1220de web: add confirm & progress for subscription cancelling 2023-03-20 12:53:20 +05:00
Abdullah Atta
fb54125ee6 web: simplify subscription cancellation 2023-03-20 12:53:20 +05:00
Abdullah Atta
be957d058f core: add basic subscription handling 2023-03-20 12:53:20 +05:00
Abdullah Atta
54a3e3c927 mobile: remove react-native-actions-sheet patch 2023-03-20 12:47:23 +05:00
ammarahm-ed
aabbf7c1c1 mobile: fix tags not properly shown in sheet 2023-03-20 11:32:49 +05:00
ammarahm-ed
7773ba0804 mobile: finalize topic sheet ui 2023-03-20 11:28:53 +05:00
ammarahm-ed
9f812772fe mobile: fix tag text position 2023-03-20 11:28:53 +05:00
ammarahm-ed
75191afc3e mobile: fix image upload broken on ios 2023-03-20 11:28:53 +05:00
Abdulrehman-Jafer
1c25baccea web: set tab/window title to note title (#2127)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
2023-03-19 12:24:55 +05:00
Abdullah Atta
b1d4ccc27e editor: fix dropdown needing 2 clicks to open 2023-03-18 12:28:08 +05:00
Abdulrehman-Jafer
0d714847f3 web: copy version on clicking the version item in settings (#2131)
Signed-off-by: Abdulrehman-Jafer <abdulrehmanjaferwork01233@gmail.com>
Co-authored-by: Abdullah Atta <thecodrr@protonmail.com>
2023-03-18 12:25:20 +05:00
141 changed files with 11289 additions and 1580 deletions

View File

@@ -215,17 +215,17 @@ jobs:
- name: Build snap
if: inputs.publish-snap
run: |
npx electron-builder --linux snap -p never
npx electron-builder --linux snap:x64 -p never
working-directory: ./apps/web/desktop
- name: Build AppImage deb and rpm
- name: Build AppImage
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ ${{ inputs.publish-github }} == true ]; then
npx electron-builder --linux AppImage deb rpm -p always
npx electron-builder --linux AppImage:x64 AppImage:arm64 -p always
else
npx electron-builder --linux AppImage deb rpm -p never
npx electron-builder --linux AppImage:x64 AppImage:arm64 -p never
fi
working-directory: ./apps/web/desktop

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -28,6 +28,7 @@ import { useAttachmentStore } from "../../stores/use-attachment-store";
import { db } from "../database";
import Storage from "../database/storage";
import { cacheDir } from "./utils";
import { getFileNameWithExtension } from "@notesnook/core/utils/filename";
export default async function downloadAttachment(
hash,
@@ -38,7 +39,6 @@ export default async function downloadAttachment(
}
) {
let attachment = db.attachments.attachment(hash);
console.log(attachment);
if (!attachment) {
console.log("attachment not found");
return;
@@ -64,6 +64,11 @@ export default async function downloadAttachment(
)
return;
let filename = getFileNameWithExtension(
attachment.metadata.filename,
attachment.metadata.type
);
let key = await db.attachments.decryptKey(attachment.key);
let info = {
iv: attachment.iv,
@@ -73,7 +78,7 @@ export default async function downloadAttachment(
hash: attachment.metadata.hash,
hashType: attachment.metadata.hashType,
mime: attachment.metadata.type,
fileName: options.cache ? undefined : attachment.metadata.filename,
fileName: options.cache ? undefined : filename,
uri: options.cache ? undefined : folder.uri,
chunkSize: attachment.chunkSize
};
@@ -87,7 +92,7 @@ export default async function downloadAttachment(
if (!options.silent) {
ToastEvent.show({
heading: "Download successful",
message: attachment.metadata.filename + " downloaded",
message: filename + " downloaded",
type: "success"
});
}
@@ -100,28 +105,20 @@ export default async function downloadAttachment(
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
.catch(console.log);
}
if (Platform.OS === "ios") {
fileUri = folder.uri + `/${attachment.metadata.filename}`;
if (Platform.OS === "ios" && !options.cache) {
fileUri = folder.uri + `/${filename}`;
}
console.log("saved file uri: ", fileUri);
if (!options.silent) {
presentSheet({
title: "File downloaded",
paragraph: `${attachment.metadata.filename} saved to ${
paragraph: `${filename} saved to ${
Platform.OS === "android"
? "selected path"
: "File Manager/Notesnook/downloads"
}`,
icon: "download",
context: global ? null : attachment.metadata.hash,
component: (
<ShareComponent
uri={fileUri}
name={attachment.metadata.filename}
padding={12}
/>
)
component: <ShareComponent uri={fileUri} name={filename} padding={12} />
});
}

View File

@@ -23,14 +23,16 @@ import {
deleteFile,
exists,
readEncrypted,
writeEncrypted
writeEncryptedBase64,
hashBase64
} from "./io";
import { uploadFile } from "./upload";
import { cancelable } from "./utils";
export default {
readEncrypted,
writeEncrypted,
writeEncryptedBase64,
hashBase64,
uploadFile: cancelable(uploadFile),
downloadFile: cancelable(downloadFile),
deleteFile,

View File

@@ -63,17 +63,26 @@ export async function readEncrypted(filename, key, cipherData) {
}
}
export async function writeEncrypted(filename, { data, type, key }) {
console.log("file input: ", { type, key });
export async function hashBase64(data) {
const hash = await Sodium.hashFile({
type: "base64",
data,
uri: ""
});
return {
hash: hash,
type: "xxh64"
};
}
export async function writeEncryptedBase64({ data, key }) {
let filepath = cacheDir + `/${getRandomId("imagecache_")}`;
console.log(filepath);
await RNFetchBlob.fs.writeFile(filepath, data, "base64");
let output = await Sodium.encryptFile(key, {
uri: Platform.OS === "ios" ? filepath : "file://" + filepath,
type: "url"
});
RNFetchBlob.fs.unlink(filepath).catch(console.log);
console.log("encrypted file output: ", output);
return {
...output,

View File

@@ -46,7 +46,6 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
const encryptionProgress = useAttachmentStore(
(state) => state.encryptionProgress
);
const onPress = () => {
Actions.present(attachment, setAttachments, attachment.metadata.hash);
};
@@ -122,7 +121,9 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
</View>
</View>
{currentProgress || encryptionProgress || encryption ? (
{currentProgress ||
(encryptionProgress && encryptionProgress !== "0.00") ||
encryption ? (
<TouchableOpacity
activeOpacity={0.9}
onPress={() => {

View File

@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useRef, useState } from "react";
import { View } from "react-native";
import { FlatList } from "react-native-gesture-handler";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import filesystem from "../../common/filesystem";
@@ -31,11 +30,11 @@ import Input from "../ui/input";
import Seperator from "../ui/seperator";
import Paragraph from "../ui/typography/paragraph";
import { AttachmentItem } from "./attachment-item";
import { FlatList } from "react-native-actions-sheet";
export const AttachmentDialog = ({ data }) => {
const colors = useThemeStore((state) => state.colors);
const [note, setNote] = useState(data);
const actionSheetRef = useRef();
const [attachments, setAttachments] = useState(
data
? db.attachments.ofNote(data.id, "all")
@@ -115,14 +114,11 @@ export const AttachmentDialog = ({ data }) => {
) : null}
<FlatList
nestedScrollEnabled
overScrollMode="never"
scrollToOverflowEnabled={false}
keyboardDismissMode="none"
keyboardShouldPersistTaps="always"
onMomentumScrollEnd={() => {
actionSheetRef.current?.handleChildScrollEnd();
}}
maxToRenderPerBatch={10}
initialNumToRender={10}
windowSize={5}
ListEmptyComponent={
<View
style={{

View File

@@ -135,7 +135,6 @@ const ResultDialog = () => {
paddingHorizontal: 12
}}
onPress={close}
height={50}
fontSize={SIZE.md + 2}
/>
</View>

View File

@@ -63,13 +63,15 @@ export const Title = () => {
}
if (data.y > 150) {
if (!hide) return;
titleState[currentScreen.id] = false;
setHide(false);
} else {
if (hide) return;
titleState[currentScreen.id] = true;
setHide(true);
}
},
[currentScreen.name, hide]
[currentScreen.id, currentScreen.name, hide]
);
useEffect(() => {
@@ -84,10 +86,6 @@ export const Title = () => {
}
}, [currentScreen.id, currentScreen.name]);
useEffect(() => {
titleState[currentScreen.id] = hide;
}, [currentScreen.id, hide]);
useEffect(() => {
eSubscribeEvent(eScrollEvent, onScroll);
return () => {
@@ -111,7 +109,7 @@ export const Title = () => {
{!hide && !isHidden ? (
<Heading
onPress={navigateToNotebook}
numberOfLines={isTopic ? 2 : 1}
numberOfLines={1}
size={SIZE.xl}
style={{
flexWrap: "wrap",

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import { Platform, View } from "react-native";
import ImageViewer from "react-native-image-zoom-viewer";
import downloadAttachment from "../../common/filesystem/download-attachment";
import { cacheDir } from "../../common/filesystem/utils";
@@ -30,6 +30,8 @@ import { useThemeStore } from "../../stores/use-theme-store";
import BaseDialog from "../dialog/base-dialog";
import { IconButton } from "../ui/icon-button";
import { ProgressBarComponent } from "../ui/svg/lazy";
import Sodium from "@ammarahmed/react-native-sodium";
import dataurl from "@notesnook/core/utils/dataurl";
const ImagePreview = () => {
const colors = useThemeStore((state) => state.colors);
@@ -49,13 +51,22 @@ const ImagePreview = () => {
setVisible(true);
setLoading(true);
setTimeout(async () => {
const hash = image.hash;
let hash = image.hash;
if (!hash && dataurl.toObject(image.src)) {
const data = dataurl.toObject(image.src);
if (!data) return;
hash = await Sodium.hashFile({
data: data.data,
type: "base64",
uri: ""
});
}
if (!hash) return;
const uri = await downloadAttachment(hash, false, {
silent: true,
cache: true
});
const path = `${cacheDir}/${uri}`;
console.log(path);
setImage("file://" + path);
setLoading(false);
}, 100);
@@ -111,7 +122,7 @@ const ImagePreview = () => {
position: "absolute",
zIndex: 999,
backgroundColor: "rgba(0,0,0,0.3)",
paddingTop: 30
paddingTop: Platform.OS === "android" ? 30 : 0
}}
>
<IconButton

View File

@@ -118,7 +118,7 @@ const NoteItem = ({
{notebooks?.map((item) => (
<Button
title={item.title}
key={item}
key={item.id}
height={25}
icon={item.type === "topic" ? "bookmark" : "book-outline"}
type="grayBg"

View File

@@ -212,7 +212,6 @@ Properties.present = (item, buttons = [], isSheet) => {
if (!props[0]) return;
presentSheet({
context: isSheet ? "local" : undefined,
enableGesturesInScrollView: true,
component: (ref, close) => (
<Properties
close={() => {

View File

@@ -94,7 +94,7 @@ export const Synced = ({ item, close }) => {
fontSize={SIZE.xs + 1}
title="Learn more"
height={30}
type="transparent"
type="grayAccent"
/>
</View>
) : null;

View File

@@ -79,7 +79,7 @@ export const TagStrip = ({ item, close }) => {
}}
>
{item.tags.map((tag) =>
tag ? <TagItem key={tag} tag={item} close={close} /> : null
tag ? <TagItem key={tag} tag={tag} close={close} /> : null
)}
</View>
) : null;
@@ -101,7 +101,6 @@ const TagItem = ({ tag, close }) => {
marginTop: 0,
backgroundColor: "transparent"
};
return (
<Button
onPress={onPress}

View File

@@ -101,6 +101,7 @@ const SheetProvider = ({ context = "global" }) => {
setVisible(false);
setData(null);
}}
bottomPadding={!data.noBottomPadding}
enableGesturesInScrollView={data.enableGesturesInScrollView}
>
<View
@@ -212,7 +213,6 @@ const SheetProvider = ({ context = "global" }) => {
title={item.actionText}
icon={item.icon && item.icon}
type={item.type || "accent"}
height={50}
style={{
marginBottom: 10
}}

View File

@@ -375,7 +375,6 @@ export class AddNotebookSheet extends React.Component {
<Seperator />
<Button
width="100%"
height={50}
fontSize={SIZE.md}
title={
notebook && notebook.dateCreated

View File

@@ -20,13 +20,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { createContext, useContext } from "react";
export const SelectionContext = createContext({
enabled: false,
selected: [],
toggleSelection: (item) => null,
deselect: (item) => null,
select: (item) => null,
isSelected: (item) => null,
setMultiSelect: () => null,
deselectAll: () => null
});
export const SelectionProvider = SelectionContext.Provider;

View File

@@ -71,7 +71,6 @@ export const FilteredList = ({
}
keyboardShouldPersistTaps="always"
keyboardDismissMode="none"
nestedScrollEnabled
/>
);
};

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 React, { useCallback, useEffect, useMemo, useState } from "react";
import React, { useCallback, useEffect, useMemo } from "react";
import { Keyboard, TouchableOpacity, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database";
@@ -26,6 +26,7 @@ import Navigation from "../../../services/navigation";
import SearchService from "../../../services/search";
import { useNotebookStore } from "../../../stores/use-notebook-store";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { useSettingStore } from "../../../stores/use-setting-store";
import { useThemeStore } from "../../../stores/use-theme-store";
import { Dialog } from "../../dialog";
import DialogHeader from "../../dialog/dialog-header";
@@ -35,19 +36,21 @@ import Paragraph from "../../ui/typography/paragraph";
import { SelectionProvider } from "./context";
import { FilteredList } from "./filtered-list";
import { ListItem } from "./list-item";
import { useItemSelectionStore } from "./store";
const MoveNoteSheet = ({ note, actionSheetRef }) => {
const colors = useThemeStore((state) => state.colors);
const [multiSelect, setMultiSelect] = useState(false);
const notebooks = useNotebookStore((state) =>
state.notebooks.filter((n) => n?.type === "notebook")
);
const dimensions = useSettingStore((state) => state.dimensions);
const selectedItemsList = useSelectionStore(
(state) => state.selectedItemsList
);
const setNotebooks = useNotebookStore((state) => state.setNotebooks);
const [itemState, setItemState] = useState({});
const multiSelect = useItemSelectionStore((state) => state.multiSelect);
const onAddNotebook = async (title) => {
if (!title || title.trim().length === 0) {
ToastEvent.show({
@@ -130,41 +133,55 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
useEffect(() => {
resetItemState();
return () => {
useItemSelectionStore.getState().setMultiSelect(false);
useItemSelectionStore.getState().setItemState({});
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const resetItemState = useCallback(
(state) => {
setItemState(() => {
const itemState = {};
const notebooks = db.notebooks.all;
for (let notebook of notebooks) {
itemState[notebook.id] = state
const itemState = {};
const notebooks = db.notebooks.all;
let count = 0;
for (let notebook of notebooks) {
itemState[notebook.id] = state
? state
: areAllSelectedItemsInNotebook(notebook, selectedItemsList)
? "selected"
: getSelectedNotesCountInItem(notebook, selectedItemsList) > 0
? "intermediate"
: "deselected";
if (itemState[notebook.id] === "selected") {
count++;
contextValue.select(notebook);
} else {
contextValue.deselect(notebook);
}
for (let topic of notebook.topics) {
itemState[topic.id] = state
? state
: areAllSelectedItemsInNotebook(notebook, selectedItemsList)
: areAllSelectedItemsInTopic(topic, selectedItemsList) &&
getSelectedNotesCountInItem(topic, selectedItemsList)
? "selected"
: getSelectedNotesCountInItem(notebook, selectedItemsList) > 0
: getSelectedNotesCountInItem(topic, selectedItemsList) > 0
? "intermediate"
: "deselected";
if (itemState[notebook.id] === "selected") {
contextValue.select(notebook);
}
for (let topic of notebook.topics) {
itemState[topic.id] = state
? state
: areAllSelectedItemsInTopic(topic, selectedItemsList) &&
getSelectedNotesCountInItem(topic, selectedItemsList)
? "selected"
: getSelectedNotesCountInItem(topic, selectedItemsList) > 0
? "intermediate"
: "deselected";
if (itemState[topic.id] === "selected") {
contextValue.select(topic);
}
if (itemState[topic.id] === "selected") {
count++;
contextValue.select(topic);
} else {
contextValue.deselect(topic);
}
}
return itemState;
});
}
if (count > 1) {
useItemSelectionStore.getState().setMultiSelect(true);
} else {
useItemSelectionStore.getState().setMultiSelect(false);
}
useItemSelectionStore.getState().setItemState(itemState);
},
[contextValue, getSelectedNotesCountInItem, selectedItemsList]
);
@@ -191,32 +208,26 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
}
const updateItemState = useCallback(function (item, state) {
setItemState((itemState) => {
const mergeState = {
[item.id]: state
};
return {
...itemState,
...mergeState
};
const itemState = useItemSelectionStore.getState().itemState;
const mergeState = {
[item.id]: state
};
useItemSelectionStore.getState().setItemState({
...itemState,
...mergeState
});
}, []);
const contextValue = useMemo(
() => ({
enabled: multiSelect,
toggleSelection: (item) => {
setItemState((itemState) => {
if (itemState[item.id] === "selected") {
updateItemState(item, "deselected");
} else {
updateItemState(item, "selected");
}
return itemState;
});
const itemState = useItemSelectionStore.getState().itemState;
if (itemState[item.id] === "selected") {
updateItemState(item, "deselected");
} else {
updateItemState(item, "selected");
}
},
setMultiSelect: setMultiSelect,
deselect: (item) => {
updateItemState(item, "deselected");
},
@@ -227,7 +238,7 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
resetItemState(state);
}
}),
[multiSelect, resetItemState, updateItemState]
[resetItemState, updateItemState]
);
const getItemFromId = (id) => {
@@ -241,6 +252,7 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
const onSave = async () => {
const noteIds = note ? [note.id] : selectedItemsList.map((n) => n.id);
const itemState = useItemSelectionStore.getState().itemState;
for (const id in itemState) {
const item = getItemFromId(id);
if (itemState[id] === "selected") {
@@ -335,31 +347,32 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
/>
</View>
{multiSelect ? (
<View
<View
style={{
paddingHorizontal: 12
}}
>
<Button
title="Reset selection"
height={30}
style={{
paddingHorizontal: 12
alignSelf: "flex-start",
paddingHorizontal: 0,
width: "100%",
marginTop: 6
}}
>
<Button
title="Reset selection"
height={30}
style={{
alignSelf: "flex-start",
paddingHorizontal: 0
}}
onPress={() => {
resetItemState();
setMultiSelect(false);
}}
/>
</View>
) : null}
type="grayAccent"
onPress={() => {
resetItemState();
}}
/>
</View>
<SelectionProvider value={contextValue}>
<FilteredList
style={{
paddingHorizontal: 12
paddingHorizontal: 12,
maxHeight: dimensions.height * 0.85
}}
ListEmptyComponent={
notebooks.length > 0 ? null : (
@@ -385,13 +398,8 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
item={item}
key={item.id}
index={index}
intermediate={itemState[item.id] === "intermediate"}
removed={
itemState[item.id] === "deselected" &&
getSelectedNotesCountInItem(item) > 0
}
hasNotes={getSelectedNotesCountInItem(item) > 0}
sheetRef={actionSheetRef}
isSelected={itemState[item.id] === "selected"}
infoText={
<>
{item.topics.length === 1
@@ -401,17 +409,14 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
}
getListItems={getItemsForItem}
getSublistItemProps={(topic) => ({
selected: itemState[topic.id] === "selected",
intermediate: itemState[topic.id] === "intermediate",
isSelected: itemState[topic.id] === "selected",
removed:
itemState[topic.id] === "deselected" &&
getSelectedNotesCountInItem(topic) > 0,
hasNotes: getSelectedNotesCountInItem(topic) > 0,
style: {
marginBottom: 0,
height: 40
},
onPress: (item) => {
const itemState =
useItemSelectionStore.getState().itemState;
const currentState = itemState[item.id];
if (currentState !== "selected") {
resetItemState("deselected");
@@ -440,19 +445,23 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
onAddSublistItem={(item) => {
openAddTopicDialog(item);
}}
onPress={(item) => {
const itemState = useItemSelectionStore.getState().itemState;
const currentState = itemState[item.id];
if (currentState !== "selected") {
resetItemState("deselected");
contextValue.select(item);
} else {
contextValue.deselect(item);
}
}}
/>
)}
itemType="notebook"
onAddItem={async (title) => {
return await onAddNotebook(title);
}}
// ListFooterComponent={
// <View
// style={{
// height: 200
// }}
// />
// }
ListFooterComponent={<View style={{ height: 20 }} />}
/>
</SelectionProvider>
</View>
@@ -463,7 +472,8 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
MoveNoteSheet.present = (note) => {
presentSheet({
component: (ref) => <MoveNoteSheet actionSheetRef={ref} note={note} />,
enableGesturesInScrollView: false
enableGesturesInScrollView: false,
noBottomPadding: true
});
};
export default MoveNoteSheet;

View File

@@ -17,8 +17,10 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import { db } from "../../../common/database";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { useThemeStore } from "../../../stores/use-theme-store";
import { SIZE } from "../../../utils/size";
import { IconButton } from "../../ui/icon-button";
@@ -27,13 +29,78 @@ import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
import { useSelectionContext } from "./context";
import { FilteredList } from "./filtered-list";
import { useItemSelectionStore } from "./store";
const _ListItem = ({
const SelectionIndicator = ({
item,
hasNotes,
selectItem,
onPress,
onChange
}) => {
const itemState = useItemSelectionStore((state) => state.itemState[item.id]);
const multiSelect = useItemSelectionStore((state) => state.multiSelect);
const isSelected = itemState === "selected";
const isIntermediate = itemState === "intermediate";
const isRemoved = !isSelected && hasNotes;
const colors = useThemeStore((state) => state.colors);
useEffect(() => {
onChange?.();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [itemState]);
return (
<IconButton
size={22}
customStyle={{
marginRight: 5,
width: 23,
height: 23
}}
color={
isRemoved
? colors.red
: isIntermediate || isSelected
? colors.accent
: colors.icon
}
onPress={() => {
if (multiSelect) return selectItem();
onPress?.(item);
}}
onLongPress={() => {
useItemSelectionStore.getState().setMultiSelect(true);
selectItem();
}}
testID={
isRemoved
? "close-circle-outline"
: isSelected
? "check-circle-outline"
: isIntermediate
? "minus-circle-outline"
: "checkbox-blank-circle-outline"
}
name={
isRemoved
? "close-circle-outline"
: isSelected
? "check-circle-outline"
: isIntermediate
? "minus-circle-outline"
: "checkbox-blank-circle-outline"
}
/>
);
};
export const ListItem = ({
item,
index,
icon,
infoText,
intermediate,
hasSubList,
onPress,
onScrollEnd,
@@ -43,19 +110,49 @@ const _ListItem = ({
sublistItemType,
onAddItem,
getSublistItemProps,
removed,
isSelected,
hasHeaderSearch,
onAddSublistItem,
hasNotes,
onChange,
sheetRef
}) => {
const { enabled, toggleSelection, setMultiSelect } = useSelectionContext();
const { toggleSelection } = useSelectionContext();
const multiSelect = useItemSelectionStore((state) => state.multiSelect);
const [showSelectedIndicator, setShowSelectedIndicator] = useState(false);
const colors = useThemeStore((state) => state.colors);
const [expanded, setExpanded] = useState(false);
function selectItem() {
toggleSelection(item);
}
const getSelectedNotesCountInNotebookTopics = (item) => {
if (item.type === "topic") return;
let count = 0;
const noteIds = [];
for (let topic of item.topics) {
noteIds.push(...(db.notes?.topicReferences.get(topic.id) || []));
if (useItemSelectionStore.getState().itemState[topic.id] === "selected") {
count++;
}
}
useSelectionStore.getState().selectedItemsList.forEach((item) => {
if (noteIds.indexOf(item.id) > -1) {
count++;
}
});
return count;
};
useEffect(() => {
setShowSelectedIndicator(getSelectedNotesCountInNotebookTopics(item) > 0);
}, [item]);
const onChangeSubItem = () => {
setShowSelectedIndicator(getSelectedNotesCountInNotebookTopics(item) > 0);
};
return (
<View
style={{
@@ -67,12 +164,12 @@ const _ListItem = ({
<PressableButton
onPress={() => {
if (hasSubList) return setExpanded(!expanded);
if (enabled) return selectItem();
if (multiSelect) return selectItem();
onPress?.(item);
}}
type={type}
onLongPress={() => {
setMultiSelect(true);
useItemSelectionStore.getState().setMultiSelect(true);
selectItem();
}}
customStyle={{
@@ -97,43 +194,12 @@ const _ListItem = ({
alignItems: "center"
}}
>
<IconButton
size={22}
customStyle={{
marginRight: 5,
width: 23,
height: 23
}}
color={
removed
? colors.red
: intermediate || isSelected
? colors.accent
: colors.icon
}
onPress={() => {
selectItem();
if (enabled) return;
onPress?.(item);
}}
testID={
removed
? "close-circle-outline"
: isSelected
? "check-circle-outline"
: intermediate
? "minus-circle-outline"
: "checkbox-blank-circle-outline"
}
name={
removed
? "close-circle-outline"
: isSelected
? "check-circle-outline"
: intermediate
? "minus-circle-outline"
: "checkbox-blank-circle-outline"
}
<SelectionIndicator
hasNotes={hasNotes}
onPress={onPress}
item={item}
onChange={onChange}
selectItem={selectItem}
/>
<View>
{hasSubList && expanded ? (
@@ -152,14 +218,27 @@ const _ListItem = ({
<View
style={{
flexDirection: "row"
flexDirection: "row",
alignItems: "center"
}}
>
{showSelectedIndicator ? (
<View
style={{
backgroundColor: colors.accent,
width: 7,
height: 7,
borderRadius: 100,
marginRight: 12
}}
/>
) : null}
{onAddSublistItem ? (
<IconButton
name={"plus"}
testID="add-item-icon"
color={colors}
color={colors.pri}
size={SIZE.xl}
onPress={() => {
onAddSublistItem(item);
@@ -201,6 +280,7 @@ const _ListItem = ({
item={item}
{...getSublistItemProps(item)}
index={index}
onChange={onChangeSubItem}
onScrollEnd={onScrollEnd}
/>
)}
@@ -210,11 +290,3 @@ const _ListItem = ({
</View>
);
};
export const ListItem = React.memo(_ListItem, (prev, next) => {
if (prev.selected === undefined) return false;
if (prev.isSelected !== next.isSelected) return false;
if (prev.selected !== next.selected) return false;
if (prev.intermediate !== next.intermediate) return false;
return true;
});

View File

@@ -0,0 +1,42 @@
/*
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 create, { State } from "zustand";
type SelectionItemState = Record<
string,
"intermediate" | "selected" | "deselected"
>;
export interface SelectionStore extends State {
itemState: SelectionItemState;
setItemState: (state: SelectionItemState) => void;
multiSelect: boolean;
setMultiSelect: (multiSelect: boolean) => void;
}
export const useItemSelectionStore = create<SelectionStore>((set) => ({
itemState: {},
setItemState: (itemState) => {
set({
itemState
});
},
multiSelect: false,
setMultiSelect: (multiSelect) => set({ multiSelect })
}));

View File

@@ -277,7 +277,6 @@ const ExportNotesSheet = ({ notes, update }) => {
});
});
}}
height={50}
/>
<Button
title="Share"
@@ -301,7 +300,6 @@ const ExportNotesSheet = ({ notes, update }) => {
}).catch(console.log);
}
}}
height={50}
/>
<Button
title="Export in another format"
@@ -317,7 +315,6 @@ const ExportNotesSheet = ({ notes, update }) => {
setResult(null);
setExporting(false);
}}
height={50}
/>
</>
)}

View File

@@ -48,7 +48,6 @@ export const ShareComponent = ({ uri, name, padding }) => {
});
});
}}
height={50}
/>
<Button
title="Share"
@@ -65,7 +64,6 @@ export const ShareComponent = ({ uri, name, padding }) => {
shareFile: true
}).catch(console.log);
}}
height={50}
/>
</View>
);

View File

@@ -196,7 +196,6 @@ For example:
onPress={onPress}
title={loading ? null : "Submit"}
loading={loading}
height={50}
width="100%"
type="accent"
/>

View File

@@ -142,7 +142,6 @@ const ManageTagsSheet = (props) => {
setFocus(false);
}}
onSubmit={onSubmit}
height={50}
placeholder="Search or add a tag"
/>

View File

@@ -60,7 +60,7 @@ const PublishNoteSheet = ({ note: item, update }) => {
if (isLocked && !passwordValue) return;
await db.monographs.publish(note.id, {
selfDestruct: selfDestruct,
password: isLocked && passwordValue
password: isLocked && passwordValue.current
});
setNote(db.notes.note(note.id)?.data);
Navigation.queueRoutesForUpdate(
@@ -86,9 +86,6 @@ const PublishNoteSheet = ({ note: item, update }) => {
const setPublishLoading = (value) => {
setPublishing(value);
update({
progress: value
});
};
const deletePublishedNote = async () => {
@@ -164,7 +161,7 @@ const PublishNoteSheet = ({ note: item, update }) => {
style={{
flexDirection: "row",
alignItems: "center",
marginTop: 15,
marginTop: 10,
backgroundColor: colors.nav,
padding: 12,
borderRadius: 5
@@ -176,8 +173,8 @@ const PublishNoteSheet = ({ note: item, update }) => {
flexShrink: 1
}}
>
<Heading size={SIZE.sm}>Published at:</Heading>
<Paragraph size={SIZE.xs} numberOfLines={1}>
<Heading size={SIZE.md}>Published at:</Heading>
<Paragraph size={SIZE.sm} numberOfLines={1}>
{publishUrl}
</Paragraph>
<Paragraph
@@ -214,7 +211,6 @@ const PublishNoteSheet = ({ note: item, update }) => {
/>
</View>
)}
<Seperator />
<TouchableOpacity
onPress={() => {
@@ -225,7 +221,11 @@ const PublishNoteSheet = ({ note: item, update }) => {
style={{
flexDirection: "row",
alignItems: "center",
marginBottom: 10
marginBottom: 10,
backgroundColor: colors.nav,
paddingVertical: 12,
borderRadius: 5,
marginTop: 10
}}
>
<IconButton
@@ -234,7 +234,7 @@ const PublishNoteSheet = ({ note: item, update }) => {
setIsLocked(!isLocked);
}}
color={isLocked ? colors.accent : colors.icon}
size={SIZE.lg}
size={SIZE.xl}
name={
isLocked
? "check-circle-outline"
@@ -262,7 +262,10 @@ const PublishNoteSheet = ({ note: item, update }) => {
activeOpacity={0.9}
style={{
flexDirection: "row",
alignItems: "center"
alignItems: "center",
backgroundColor: colors.nav,
paddingVertical: 12,
borderRadius: 5
}}
>
<IconButton
@@ -270,7 +273,7 @@ const PublishNoteSheet = ({ note: item, update }) => {
setSelfDestruct(!selfDestruct);
}}
color={selfDestruct ? colors.accent : colors.icon}
size={SIZE.lg}
size={SIZE.xl}
name={
selfDestruct
? "check-circle-outline"
@@ -313,31 +316,38 @@ const PublishNoteSheet = ({ note: item, update }) => {
</>
) : null}
<Button
onPress={publishNote}
fontSize={SIZE.md}
width="100%"
<View
style={{
marginTop: 10
flexDirection: "row",
width: "100%",
justifyContent: "center"
}}
height={50}
type="accent"
title={isPublished ? "Update published note" : "Publish note"}
/>
{isPublished && (
<>
<Seperator half />
<Button
onPress={deletePublishedNote}
fontSize={SIZE.md}
width="100%"
height={50}
type="error"
title="Unpublish note"
/>
</>
)}
>
{isPublished && (
<>
<Button
onPress={deletePublishedNote}
fontSize={SIZE.md}
type="error"
title="Unpublish"
style={{
width: "49%"
}}
/>
</>
)}
<Seperator half />
<Button
onPress={publishNote}
fontSize={SIZE.md}
style={{
width: isPublished ? "49%" : 250,
borderRadius: isPublished ? 5 : 100
}}
type="accent"
title={isPublished ? "Update" : "Publish"}
/>
</View>
</View>
</>
)}
@@ -347,7 +357,7 @@ const PublishNoteSheet = ({ note: item, update }) => {
size={SIZE.xs}
style={{
textAlign: "center",
marginTop: 5,
marginTop: 10,
textDecorationLine: "underline"
}}
onPress={async () => {

View File

@@ -96,7 +96,6 @@ const RateAppSheet = () => {
onPress={rateApp}
fontSize={SIZE.md}
width="100%"
height={50}
type="accent"
title="Rate now (It takes only a second)"
/>
@@ -121,14 +120,12 @@ const RateAppSheet = () => {
fontSize={SIZE.md}
type="error"
width="48%"
height={50}
title="Never"
/>
<Button
onPress={onClose}
fontSize={SIZE.md}
width="48%"
height={50}
type="grayBg"
title="Later"
/>

View File

@@ -298,7 +298,6 @@ class RecoveryKeySheet extends React.Component {
width="100%"
type="grayAccent"
fontSize={SIZE.md}
height={50}
/>
<Seperator />
<Button
@@ -308,7 +307,6 @@ class RecoveryKeySheet extends React.Component {
type="grayAccent"
fontSize={SIZE.md}
icon="qrcode"
height={50}
/>
<Seperator />
<Button
@@ -318,7 +316,6 @@ class RecoveryKeySheet extends React.Component {
type="grayAccent"
icon="text"
fontSize={SIZE.md}
height={50}
/>
<Seperator />
@@ -329,7 +326,6 @@ class RecoveryKeySheet extends React.Component {
type="grayAccent"
icon="cloud"
fontSize={SIZE.md}
height={50}
/>
<Seperator />
@@ -349,7 +345,6 @@ class RecoveryKeySheet extends React.Component {
<Button
title="I have saved the key."
width="100%"
height={50}
type="error"
fontSize={SIZE.md}
onPress={this.close}

View File

@@ -434,11 +434,6 @@ export default function ReminderSheet({
<DatePicker
date={date}
minimumDate={
dayjs(date).subtract(3, "months").isBefore(dayjs())
? dayjs().toDate()
: dayjs(date).subtract(3, "months").toDate()
}
maximumDate={dayjs(date).add(3, "months").toDate()}
onDateChange={handleConfirm}
textColor={colors.night ? "#ffffff" : "#000000"}

View File

@@ -32,6 +32,7 @@ import Seperator from "../../ui/seperator";
import Heading from "../../ui/typography/heading";
const Sort = ({ type, screen }) => {
const colors = useThemeStore((state) => state.colors);
const isTopicSheet = screen === "TopicSheet";
const [groupOptions, setGroupOptions] = useState(
db.settings.getGroupOptions(type)
);
@@ -41,7 +42,7 @@ const Sort = ({ type, screen }) => {
layoutmanager.withSpringAnimation(600);
setGroupOptions(_groupOptions);
setTimeout(() => {
Navigation.queueRoutesForUpdate(screen);
if (screen !== "TopicSheet") Navigation.queueRoutesForUpdate(screen);
eSendEvent("groupOptionsUpdate");
eSendEvent(refreshNotesPage);
}, 1);
@@ -52,6 +53,9 @@ const Sort = ({ type, screen }) => {
...groupOptions,
sortDirection: groupOptions.sortDirection === "asc" ? "desc" : "asc"
};
if (type === "topics") {
_groupOptions.groupBy = "none";
}
await updateGroupOptions(_groupOptions);
};
@@ -119,7 +123,7 @@ const Sort = ({ type, screen }) => {
flexDirection: "row",
justifyContent: "flex-start",
flexWrap: "wrap",
borderBottomWidth: 1,
borderBottomWidth: isTopicSheet ? 0 : 1,
borderBottomColor: colors.nav,
marginBottom: 12,
paddingHorizontal: 12,
@@ -166,6 +170,9 @@ const Sort = ({ type, screen }) => {
...groupOptions,
sortBy: type === "trash" ? "dateDeleted" : item
};
if (type === "topics") {
_groupOptions.groupBy = "none";
}
await updateGroupOptions(_groupOptions);
}}
iconSize={SIZE.md}
@@ -175,64 +182,70 @@ const Sort = ({ type, screen }) => {
)}
</View>
<Heading
style={{
marginLeft: 12
}}
size={SIZE.lg}
>
Group by
</Heading>
<Seperator />
<View
style={{
borderRadius: 0,
flexDirection: "row",
flexWrap: "wrap",
paddingHorizontal: 12
}}
>
{Object.keys(GROUP).map((item) => (
<Button
key={item}
testID={"btn-" + item}
type={groupOptions.groupBy === GROUP[item] ? "grayBg" : "gray"}
buttonType={{
text:
groupOptions.groupBy === GROUP[item]
? colors.accent
: colors.icon
}}
onPress={async () => {
let _groupOptions = {
...groupOptions,
groupBy: GROUP[item]
};
if (item === "abc") {
_groupOptions.sortBy = "title";
_groupOptions.sortDirection = "asc";
} else {
if (groupOptions.sortBy === "title") {
_groupOptions.sortBy = "dateEdited";
_groupOptions.sortDirection = "desc";
}
}
updateGroupOptions(_groupOptions);
}}
height={40}
icon={groupOptions.groupBy === GROUP[item] ? "check" : null}
title={item.slice(0, 1).toUpperCase() + item.slice(1, item.length)}
{isTopicSheet ? null : (
<>
<Heading
style={{
paddingHorizontal: 8,
marginBottom: 10,
marginRight: 10
marginLeft: 12
}}
/>
))}
</View>
size={SIZE.lg}
>
Group by
</Heading>
<Seperator />
<View
style={{
borderRadius: 0,
flexDirection: "row",
flexWrap: "wrap",
paddingHorizontal: 12
}}
>
{Object.keys(GROUP).map((item) => (
<Button
key={item}
testID={"btn-" + item}
type={groupOptions.groupBy === GROUP[item] ? "grayBg" : "gray"}
buttonType={{
text:
groupOptions.groupBy === GROUP[item]
? colors.accent
: colors.icon
}}
onPress={async () => {
let _groupOptions = {
...groupOptions,
groupBy: GROUP[item]
};
if (item === "abc") {
_groupOptions.sortBy = "title";
_groupOptions.sortDirection = "asc";
} else {
if (groupOptions.sortBy === "title") {
_groupOptions.sortBy = "dateEdited";
_groupOptions.sortDirection = "desc";
}
}
updateGroupOptions(_groupOptions);
}}
height={40}
icon={groupOptions.groupBy === GROUP[item] ? "check" : null}
title={
item.slice(0, 1).toUpperCase() + item.slice(1, item.length)
}
style={{
paddingHorizontal: 8,
marginBottom: 10,
marginRight: 10
}}
/>
))}
</View>
</>
)}
</View>
);
};

View File

@@ -19,12 +19,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import qclone from "qclone";
import React, {
createContext,
useCallback,
useContext,
useEffect,
useRef,
useState
} from "react";
import { Animated, Dimensions, View, RefreshControl } from "react-native";
import {
Animated,
Dimensions,
View,
RefreshControl,
Platform
} from "react-native";
import ActionSheet, {
ActionSheetRef,
FlatList
@@ -37,15 +44,20 @@ import { TopicNotes } from "../../../screens/notes/topic-notes";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
eUnSubscribeEvent,
presentSheet
} from "../../../services/event-manager";
import useNavigationStore, {
NotebookScreenParams
} from "../../../stores/use-navigation-store";
import { useThemeStore } from "../../../stores/use-theme-store";
import { eOnNewTopicAdded, eOpenAddTopicDialog } from "../../../utils/events";
import {
eOnNewTopicAdded,
eOnTopicSheetUpdate,
eOpenAddTopicDialog
} from "../../../utils/events";
import { normalize, SIZE } from "../../../utils/size";
import { NotebookType, TopicType } from "../../../utils/types";
import { GroupHeader, NotebookType, TopicType } from "../../../utils/types";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { openEditor } from "../../../screens/notes/common";
@@ -55,6 +67,8 @@ import { deleteItems } from "../../../utils/functions";
import { presentDialog } from "../../dialog/functions";
import Config from "react-native-config";
import { notesnook } from "../../../../e2e/test.ids";
import Sort from "../sort";
import { groupArray } from "@notesnook/core/utils/grouping";
export const TopicsSheet = () => {
const currentScreen = useNavigationStore((state) => state.currentScreen);
@@ -71,12 +85,33 @@ export const TopicsSheet = () => {
const [enabled, setEnabled] = useState(false);
const colors = useThemeStore((state) => state.colors);
const ref = useRef<ActionSheetRef>(null);
const [topics, setTopics] = useState(notebook ? qclone(notebook.topics) : []);
const [topics, setTopics] = useState(
notebook
? qclone(
groupArray(notebook.topics, db.settings?.getGroupOptions("topics"))
)
: []
);
const [animations] = useState({
translate: new Animated.Value(0),
display: new Animated.Value(-5000),
opacity: new Animated.Value(0)
});
const [groupOptions, setGroupOptions] = useState(
db.settings?.getGroupOptions("topics")
);
const onUpdate = useCallback(() => {
setGroupOptions({ ...(db.settings?.getGroupOptions("topics") as any) });
}, []);
useEffect(() => {
eSubscribeEvent("groupOptionsUpdate", onUpdate);
return () => {
eUnSubscribeEvent("groupOptionsUpdate", onUpdate);
};
}, [onUpdate]);
const onRequestUpdate = React.useCallback(
(data?: NotebookScreenParams) => {
if (!canShow) return;
@@ -85,16 +120,22 @@ export const TopicsSheet = () => {
?.data as NotebookType;
if (_notebook) {
setNotebook(_notebook);
setTopics(qclone(_notebook.topics));
setTopics(qclone(groupArray(_notebook.topics, groupOptions)));
}
},
[notebook, canShow]
[canShow, notebook, groupOptions]
);
useEffect(() => {
const onTopicUpdate = () => {
onRequestUpdate();
};
eSubscribeEvent(eOnTopicSheetUpdate, onTopicUpdate);
eSubscribeEvent(eOnNewTopicAdded, onRequestUpdate);
return () => {
eUnSubscribeEvent(eOnNewTopicAdded, onRequestUpdate);
eUnSubscribeEvent(eOnTopicSheetUpdate, onRequestUpdate);
eUnSubscribeEvent(eOnNewTopicAdded, onTopicUpdate);
};
}, [onRequestUpdate]);
@@ -108,9 +149,16 @@ export const TopicsSheet = () => {
loading: "Loading notebook topics"
};
const renderTopic = ({ item, index }: { item: TopicType; index: number }) => (
<TopicItem item={item} index={index} />
);
const renderTopic = ({
item,
index
}: {
item: TopicType | GroupHeader;
index: number;
}) =>
(item as GroupHeader).type === "header" ? null : (
<TopicItem item={item as TopicType} index={index} />
);
const selectionContext = {
selection: selection,
@@ -145,7 +193,7 @@ export const TopicsSheet = () => {
}
if (id) {
onRequestUpdate({
item: db.notebooks?.notebook(id).data
item: db.notebooks?.notebook(id)?.data
} as any);
}
ref.current?.show();
@@ -167,7 +215,7 @@ export const TopicsSheet = () => {
ref={ref}
isModal={false}
containerStyle={{
maxHeight: 600,
maxHeight: 400,
borderTopRightRadius: 15,
borderTopLeftRadius: 15,
backgroundColor: colors.bg,
@@ -182,11 +230,15 @@ export const TopicsSheet = () => {
backgroundColor: colors.nav
}}
keyboardHandlerEnabled={false}
snapPoints={Config.isTesting === "true" ? [60, 100] : [15, 60, 100]}
snapPoints={
Config.isTesting === "true"
? [100]
: [Platform.OS === "ios" ? 25 : 20, 100]
}
initialSnapIndex={0}
backgroundInteractionEnabled
onChange={(position, height) => {
animations.translate.setValue(position);
animations.translate.setValue(position - 60);
const h = Dimensions.get("window").height;
const minPos = h - height;
if (position - 100 < minPos || !canShow) {
@@ -221,8 +273,7 @@ export const TopicsSheet = () => {
accentText="light"
onPress={openEditor}
customStyle={{
borderRadius: 100,
bottom: 50
borderRadius: 100
}}
>
<View
@@ -241,8 +292,8 @@ export const TopicsSheet = () => {
>
<View
style={{
maxHeight: 600,
height: 600,
maxHeight: 400,
height: 400,
width: "100%"
}}
>
@@ -296,17 +347,38 @@ export const TopicsSheet = () => {
size={22}
/>
) : (
<IconButton
name="plus"
onPress={PLACEHOLDER_DATA.action}
testID="add-topic-button"
color={colors.pri}
size={22}
customStyle={{
width: 40,
height: 40
}}
/>
<>
<IconButton
name={
groupOptions?.sortDirection === "asc"
? "sort-ascending"
: "sort-descending"
}
onPress={() => {
presentSheet({
component: <Sort screen="TopicSheet" type="topics" />
});
}}
testID="group-topic-button"
color={colors.pri}
size={22}
customStyle={{
width: 40,
height: 40
}}
/>
<IconButton
name="plus"
onPress={PLACEHOLDER_DATA.action}
testID="add-topic-button"
color={colors.pri}
size={22}
customStyle={{
width: 40,
height: 40
}}
/>
</>
)}
</View>
</View>
@@ -326,7 +398,7 @@ export const TopicsSheet = () => {
progressBackgroundColor={colors.bg}
/>
}
keyExtractor={(item) => item.id}
keyExtractor={(item) => (item as TopicType).id}
renderItem={renderTopic}
ListEmptyComponent={
<View

View File

@@ -164,7 +164,6 @@ export const PinItem = React.memo(
}}
fontSize={SIZE.md}
width="95%"
height={50}
customStyle={{
marginBottom: 30
}}

View File

@@ -92,7 +92,7 @@ const Input = ({
button,
onBlurInput,
onPress,
height = 50,
height = 45,
fontSize = SIZE.md,
onFocusInput,
buttons,

View File

@@ -38,7 +38,8 @@ const SheetWrapper = ({
keyboardMode,
overlay,
overlayOpacity = 0.3,
enableGesturesInScrollView = false
enableGesturesInScrollView = false,
bottomPadding = true
}) => {
const colors = useThemeStore((state) => state.colors);
const deviceMode = useSettingStore((state) => state.deviceMode);
@@ -119,14 +120,16 @@ const SheetWrapper = ({
>
<BouncingView>
{children}
<View
style={{
height:
Platform.OS === "ios" && insets.bottom !== 0
? insets.bottom + 5
: 20
}}
/>
{bottomPadding ? (
<View
style={{
height:
Platform.OS === "ios" && insets.bottom !== 0
? insets.bottom + 5
: 20
}}
/>
) : null}
</BouncingView>
</ActionSheet>
);

View File

@@ -46,6 +46,7 @@ export default function Tag({
marginLeft: 2,
marginTop: -10,
height: 20,
justifyContent: "center",
...style
}}
>

View File

@@ -19,17 +19,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { FeatureType } from "./components/sheets/new-feature";
export const features: FeatureType[] = [
{
title: "Improved editor performance",
body: "The editor perfomance is now much better especially with images"
},
{
title: "Sortable task lists",
body: "Now you can sort tasks by checked/unchecked in editor"
},
{
title: "Improved outline lists",
body: "Rewritten outline lists to be more performant and less buggy"
}
];
export const features: FeatureType[] = [];

View File

@@ -402,11 +402,9 @@ export const useAppEvents = () => {
const checkAutoBackup = useCallback(async () => {
if (verify || syncing) {
console.log("backup is waiting");
refValues.current.backupDidWait = true;
return;
}
console.log("backup running immediate");
const user = await db.user.getUser();
if (PremiumService.get() && user) {
if (SettingsService.get().reminder === "off") {
@@ -427,7 +425,6 @@ export const useAppEvents = () => {
useEffect(() => {
if (!verify && !syncing && refValues.current.backupDidWait) {
console.log("backup run after wait");
refValues.current.backupDidWait = false;
checkAutoBackup();
}

View File

@@ -69,6 +69,7 @@ import {
import { editorRef, tabBarRef } from "../utils/global-refs";
import { sleep } from "../utils/time";
import { NavigationStack } from "./navigation-stack";
import changeNavigationBarColor from "react-native-navigation-bar-color";
const _TabsHolder = () => {
const colors = useThemeStore((state) => state.colors);
@@ -235,14 +236,17 @@ const _TabsHolder = () => {
let needsUpdate = current !== deviceMode;
if (fullscreen && current !== "mobile") {
editorRef.current?.setNativeProps({
style: {
width: size.width,
zIndex: 999,
paddingHorizontal:
current === "smallTablet" ? size.width * 0 : size.width * 0.15
}
});
// Runs after size is set via state.
setTimeout(() => {
editorRef.current?.setNativeProps({
style: {
width: size.width,
zIndex: 999,
paddingHorizontal:
current === "smallTablet" ? size.width * 0 : size.width * 0.15
}
});
}, 1);
} else {
if (fullscreen) eSendEvent(eCloseFullscreenEditor, current);
editorRef.current?.setNativeProps({
@@ -281,10 +285,8 @@ const _TabsHolder = () => {
!editorState().movedAway &&
useEditorStore.getState().currentEditingNote
) {
console.log("editor");
tabBarRef.current?.goToIndex(2, false);
} else {
console.log("home");
tabBarRef.current?.goToIndex(1, false);
}
break;
@@ -385,6 +387,10 @@ const _TabsHolder = () => {
};
}, []);
useEffect(() => {
changeNavigationBarColor(colors.bg, !colors.night, true);
}, [colors.night, colors.bg]);
return (
<View
onLayout={_onLayout}

View File

@@ -15,7 +15,7 @@
"html-to-text": "8.1.0",
"phone": "^3.1.14",
"qclone": "^1.2.0",
"react-native-actions-sheet": "^0.9.0-alpha.9",
"react-native-actions-sheet": "0.9.0-alpha.16",
"react-native-check-version": "https://github.com/flexible-agency/react-native-check-version",
"react-native-drax": "^0.10.2",
"react-native-image-zoom-viewer": "^3.0.1",

View File

@@ -28,5 +28,5 @@ const EditorMobileSourceUrl =
* The url should be something like this: http://192.168.100.126:3000/index.html
*/
export const EDITOR_URI = __DEV__
? "http://192.168.8.103:3000/index.html"
? EditorMobileSourceUrl
: EditorMobileSourceUrl;

View File

@@ -129,7 +129,6 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
};
setInsets = async (insets: EdgeInsets) => {
logger.info("setInsets", insets);
await this.doAsync(`
if (typeof safeAreaController !== "undefined") {
safeAreaController.update(${JSON.stringify(insets)})
@@ -229,7 +228,7 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
await this.doAsync(
`editor && editor.commands.updateImage(${JSON.stringify({
hash
})},${JSON.stringify({ src, hash, preventUpdate: true })})`
})},${JSON.stringify({ dataurl: src, hash, preventUpdate: true })})`
);
};

View File

@@ -17,13 +17,14 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Sodium from "@ammarahmed/react-native-sodium";
import React from "react";
import { Platform, View } from "react-native";
import DocumentPicker from "react-native-document-picker";
import { launchCamera, launchImageLibrary } from "react-native-image-picker";
import Sodium from "@ammarahmed/react-native-sodium";
import RNFetchBlob from "rn-fetch-blob";
import { db } from "../../../common/database";
import { compressToBase64 } from "../../../common/filesystem/compress";
import { AttachmentItem } from "../../../components/attachments/attachment-item";
import {
eSendEvent,
@@ -32,9 +33,7 @@ import {
} from "../../../services/event-manager";
import PremiumService from "../../../services/premium";
import { eCloseSheet } from "../../../utils/events";
import { sleep } from "../../../utils/time";
import { editorController, editorState } from "./utils";
import { compressToBase64 } from "../../../common/filesystem/compress";
const FILE_SIZE_LIMIT = 500 * 1024 * 1024;
const IMAGE_SIZE_LIMIT = 50 * 1024 * 1024;
@@ -149,8 +148,6 @@ const file = async (fileOptions) => {
const camera = async (options) => {
try {
await db.attachments.generateKey();
eSendEvent(eCloseSheet);
await sleep(400);
launchCamera(
{
includeBase64: true,
@@ -172,8 +169,6 @@ const camera = async (options) => {
const gallery = async (options) => {
try {
await db.attachments.generateKey();
eSendEvent(eCloseSheet);
await sleep(400);
launchImageLibrary(
{
includeBase64: true,
@@ -207,7 +202,7 @@ const pick = async (options) => {
return;
}
if (options?.type.startsWith("image") || options?.type === "camera") {
if (options.type === "image") {
if (options.type.startsWith("image")) {
gallery(options);
} else {
camera(options);
@@ -250,7 +245,10 @@ const handleImageResponse = async (response, options) => {
if (isPng || isJpeg) {
b64 =
`data:${image.type};base64, ` +
(await compressToBase64(image.uri, isPng ? "PNG" : "JPEG"));
(await compressToBase64(
Platform.OS === "ios" ? "file://" + image.uri : image.uri,
isPng ? "PNG" : "JPEG"
));
}
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
@@ -259,7 +257,7 @@ const handleImageResponse = async (response, options) => {
hash: hash,
type: image.type,
title: fileName,
src: b64,
dataurl: b64,
size: image.fileSize,
filename: fileName
});

View File

@@ -76,8 +76,7 @@ export const useEditor = (
const saveCount = useRef(0);
const lastContentChangeTime = useRef<number>(0);
const lock = useRef(false);
const attachedImages = useRef<{ [name: string]: any }>({});
const loadedImages = useRef<{ [name: string]: any }>({});
const loadedImages = useRef<{ [name: string]: boolean }>({});
const postMessage = useCallback(
async <T>(type: string, data: T) =>
@@ -156,8 +155,7 @@ export const useEditor = (
async (resetState = true) => {
currentNote.current?.id && db.fs.cancel(currentNote.current.id);
currentNote.current = null;
attachedImages.current = [];
loadedImages.current = [];
loadedImages.current = {};
currentContent.current = null;
clearTimeout(timers.current["loading-images"]);
sessionHistoryId.current = undefined;
@@ -230,7 +228,6 @@ export const useEditor = (
id = await db.notes?.add(noteData);
if (!note && id) {
currentNote.current = db.notes?.note(id).data as NoteType;
console.log("on Note Created", state.current?.onNoteCreated);
state.current?.onNoteCreated && state.current.onNoteCreated(id);
if (!noteData.title) {
postMessage(
@@ -296,32 +293,38 @@ export const useEditor = (
}
}, []);
const getImagesToLoad = () => {
const getMediaToLoad = (previousContent?: string) => {
if (!currentNote.current?.id) return [];
const currentImages = [
...(db.attachments?.ofNote(currentNote.current?.id, "images") || []),
...(db.attachments?.ofNote(currentNote.current?.id, "webclips") || [])
];
if (!currentImages || currentImages?.length === 0) return [];
const imagesToLoad: any[] = [];
for (const image of currentImages) {
if (!loadedImages.current[image.metadata.hash]) {
loadedImages.current[image.metadata.hash] = false;
imagesToLoad.push(image);
const previousAttachments =
previousContent?.matchAll(/data-hash="(.+?)"/gm) || [];
const attachments =
currentContent.current?.data?.matchAll(/data-hash="(.+?)"/gm) || [];
const media: string[] = [];
const oldMatches = Array.from(previousAttachments).map((match) => match[1]);
const matches = Array.from(attachments).map((match) => match[1]);
for (let i = 0; i < matches.length; i++) {
const currentHash = matches[i];
const oldHash = oldMatches[i];
if (currentHash !== oldHash) {
media.push(currentHash);
loadedImages.current[currentHash] = false;
}
attachedImages.current[image.metadata.hash] = image;
}
return imagesToLoad;
return media;
};
const markImageLoaded = (hash: string) => {
const attachment = attachedImages.current[hash];
if (attachment) {
const attachment = loadedImages.current[hash];
if (typeof attachment === "boolean") {
loadedImages.current[hash] = true;
}
};
const loadImages = useCallback(() => {
const loadImages = useCallback((previousContent?: string) => {
if (!currentNote.current?.id) return;
const timerId = "loading-images";
clearTimeout(timers.current[timerId]);
@@ -334,12 +337,12 @@ export const useEditor = (
true
);
} else {
const images = getImagesToLoad();
if (images.length > 0) {
db.attachments?.downloadMedia(currentNote.current?.id, images);
const media = getMediaToLoad(previousContent);
if (media.length > 0) {
db.attachments?.downloadMedia(currentNote.current?.id, media);
}
}
}, 100);
}, 1000);
}, []);
const loadNote = useCallback(
@@ -423,6 +426,8 @@ export const useEditor = (
lock.current = true;
const previousContent = currentContent.current?.data;
if (data.type === "tiptap") {
if (!currentNote.current.locked && isContentEncrypted) {
lockNoteWithVault(note);
@@ -453,13 +458,14 @@ export const useEditor = (
}
await commands.setStatus(timeConverter(note.dateEdited), "Saved");
}
lock.current = false;
if (data.type === "tiptap") {
loadImages();
loadImages(previousContent);
db.eventManager.subscribe(
EVENTS.syncCompleted,
() => {
loadImages();
loadImages(previousContent);
},
true
);

View File

@@ -24,7 +24,7 @@ import Navigation from "../../services/navigation";
import { useMenuStore } from "../../stores/use-menu-store";
import { NotesScreenParams } from "../../stores/use-navigation-store";
import { useTagStore } from "../../stores/use-tag-store";
import { eOnLoadNote } from "../../utils/events";
import { eOnLoadNote, eOnTopicSheetUpdate } from "../../utils/events";
import { openLinkInBrowser } from "../../utils/functions";
import { tabBarRef } from "../../utils/global-refs";
import { TopicType } from "../../utils/types";
@@ -125,6 +125,7 @@ async function onNoteCreated(id: string, params: FirstSaveData) {
"Notebook",
"Notebooks"
);
eSendEvent(eOnTopicSheetUpdate);
break;
}
case "tag": {

View File

@@ -106,12 +106,6 @@ export const useDragState = create<DragState>(
return;
}
const preset = toolbarConfig?.preset as DragState["preset"];
logger.info(
"DragState",
"Init user toolbar config",
preset,
toolbarConfig?.config
);
set({
preset: preset,
data:

View File

@@ -169,6 +169,14 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
}
item.inputProperties?.onSubmitEditing?.(e);
}}
onChangeText={(text) => {
if (text) {
SettingsService.set({
[item.property as string]: text
});
}
item.inputProperties?.onSubmitEditing?.(text as any);
}}
containerStyle={{ marginTop: 12 }}
fwdRef={inputRef}
onLayout={() => {

View File

@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import notifee from "@notifee/react-native";
import dayjs from "dayjs";
import React from "react";
import { Linking, Platform } from "react-native";
@@ -25,6 +26,7 @@ import * as RNIap from "react-native-iap";
import { enabled } from "react-native-privacy-snapshot";
import { db } from "../../common/database";
import { MMKV } from "../../common/database/mmkv";
import { AttachmentDialog } from "../../components/attachments";
import { ChangePassword } from "../../components/auth/change-password";
import { presentDialog } from "../../components/dialog/functions";
import { ChangeEmail } from "../../components/sheets/change-email";
@@ -56,7 +58,6 @@ import { SUBSCRIPTION_STATUS } from "../../utils/constants";
import {
eCloseSheet,
eCloseSimpleDialog,
eOpenAttachmentsDialog,
eOpenLoginDialog,
eOpenRecoveryKeyDialog,
eOpenRestoreDialog
@@ -68,7 +69,6 @@ import { useDragState } from "./editor/state";
import { verifyUser } from "./functions";
import { SettingSection } from "./types";
import { getTimeLeft } from "./user-section";
import notifee from "@notifee/react-native";
type User = any;
@@ -149,7 +149,7 @@ export const settingsGroups: SettingSection[] = [
name: "Manage attachments",
icon: "attachment",
modifer: () => {
eSendEvent(eOpenAttachmentsDialog);
AttachmentDialog.present();
},
description: "Manage all attachments in one place."
},
@@ -1027,11 +1027,21 @@ export const settingsGroups: SettingSection[] = [
id: "join-telegram",
name: "Join our Telegram group",
description: "We are on telegram, let's talk",
// icon: 'telegram',
modifer: () => {
Linking.openURL("https://t.me/notesnook").catch(console.log);
}
},
{
id: "join-mastodom",
name: "Follow us on Mastodon",
description: "We are on mastodom",
icon: "mastodon",
modifer: () => {
Linking.openURL("https://fosstodon.org/@notesnook").catch(
console.log
);
}
},
{
id: "join-twitter",
name: "Follow us on twitter",

View File

@@ -114,7 +114,8 @@ export type PresentSheetOptions = {
actionsArray: SheetAction[];
learnMore: string;
learnMorePress: () => void;
enableGesturesInScrollView: boolean;
enableGesturesInScrollView?: boolean;
noBottomPadding?: boolean;
};
export function presentSheet(data: Partial<PresentSheetOptions>) {

View File

@@ -367,7 +367,8 @@ async function scheduleNotification(
function loadNote(id: string, jump: boolean) {
if (!id || id === "notesnook_note_input") return;
const note = db.notes?.note(id).data;
const note = db.notes?.note(id)?.data;
if (!note) return;
if (!DDS.isTab && jump) {
tabBarRef.current?.goToPage(1);
}

View File

@@ -104,7 +104,7 @@ export const COLOR_SCHEME_PITCH_BLACK = {
light: "#ffffff",
transGray: "#ffffff10",
border: "#383838",
placeholder: "#404040"
placeholder: "#606060"
};
export const COLOR_SCHEME_DARK = {

View File

@@ -158,3 +158,5 @@ export const eCloseAnnouncementDialog = "604";
export const eOpenLoading = "605";
export const eCloseLoading = "606";
export const eOnTopicSheetUpdate = "607";

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

@@ -1,3 +1,7 @@
- Fixed reminders notifications on android
- Added support for sorting topics in topics sheet
- Navigation Bar in app now follows app theme
- Splashscreen logo quality in dark mode has been improved
- Fix topic title goes into multiple lines if long
- Fix reminder date picker not allowing to pick a date from past
Thank you for using Notesnook!

View File

@@ -1083,7 +1083,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2029;
CURRENT_PROJECT_VERSION = 2033;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1157,7 +1157,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.4.6;
MARKETING_VERSION = 2.4.10;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1187,7 +1187,7 @@
CODE_SIGN_ENTITLEMENTS = Notesnook/Notesnook.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2029;
CURRENT_PROJECT_VERSION = 2033;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
@@ -1260,7 +1260,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.4.6;
MARKETING_VERSION = 2.4.10;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1418,7 +1418,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2029;
CURRENT_PROJECT_VERSION = 2033;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1430,7 +1430,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.4.6;
MARKETING_VERSION = 2.4.10;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
@@ -1460,7 +1460,7 @@
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2029;
CURRENT_PROJECT_VERSION = 2033;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1472,7 +1472,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.4.6;
MARKETING_VERSION = 2.4.10;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1501,7 +1501,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2029;
CURRENT_PROJECT_VERSION = 2033;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1575,7 +1575,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.4.6;
MARKETING_VERSION = 2.4.10;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
@@ -1605,7 +1605,7 @@
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2029;
CURRENT_PROJECT_VERSION = 2033;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1679,7 +1679,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.4.6;
MARKETING_VERSION = 2.4.10;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -371,10 +371,10 @@ PODS:
- React-Core
- RNKeychain (4.0.5):
- React
- RNNotifee (7.4.3):
- RNNotifee (7.4.4):
- React-Core
- RNNotifee/NotifeeCore (= 7.4.3)
- RNNotifee/NotifeeCore (7.4.3):
- RNNotifee/NotifeeCore (= 7.4.4)
- RNNotifee/NotifeeCore (7.4.4):
- React-Core
- RNPrivacySnapshot (1.0.0):
- React-Core
@@ -754,7 +754,7 @@ SPEC CHECKSUMS:
RNGestureHandler: b7a872907ee289ada902127f2554fa1d2c076122
RNIap: d248609d1b8937e63bd904e865c318e9b1457eff
RNKeychain: 840f8e6f13be0576202aefcdffd26a4f54bfe7b5
RNNotifee: 5dfb0c5783ddb3da47b39e75d06cc7e748d6ca39
RNNotifee: 2ae3c18196e6f307fa62ae5c8e5305dea03ff147
RNPrivacySnapshot: 8eaf571478a353f2e5184f5c803164f22428b023
RNReanimated: f1b109fb8341505ace9d7d2eedd150da1686716b
RNScreens: 34cc502acf1b916c582c60003dc3089fa01dc66d

View File

@@ -57,7 +57,8 @@
"@react-native-community/datetimepicker":"6.6.0",
"react-native-date-picker": "4.2.6",
"react-native-notification-sounds": "0.5.5",
"@bam.tech/react-native-image-resizer": "3.0.5"
"@bam.tech/react-native-image-resizer": "3.0.5",
"react-native-navigation-bar-color": "2.0.2"
},
"devDependencies": {
"@babel/core": "^7.12.9",

View File

@@ -1,3 +1,6 @@
- Fixed reminders notifications on android
- Added support for sorting topics in topics sheet
- Fixed topics sheet is hard to swipe up from bottom
- Fix topic title goes into multiple lines if long
- Fix reminder date picker not allowing to pick a date from past
Thank you for using Notesnook!

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/mobile",
"version": "2.4.6",
"version": "2.4.10",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@notesnook/mobile",
"version": "2.4.6",
"version": "2.4.10",
"license": "GPL-3.0-or-later",
"workspaces": [
"native/",
@@ -42,7 +42,7 @@
"qclone": "^1.2.0",
"react": "18.0.0",
"react-native": "0.69.7",
"react-native-actions-sheet": "^0.9.0-alpha.9",
"react-native-actions-sheet": "0.9.0-alpha.16",
"react-native-check-version": "https://github.com/flexible-agency/react-native-check-version",
"react-native-drax": "^0.10.2",
"react-native-image-zoom-viewer": "^3.0.1",
@@ -100,6 +100,7 @@
"react-native-keychain": "4.0.5",
"react-native-mmkv-storage": "^0.8.0",
"react-native-modal-datetime-picker": "14.0.0",
"react-native-navigation-bar-color": "2.0.2",
"react-native-notification-sounds": "0.5.5",
"react-native-orientation": "https://github.com/yamill/react-native-orientation.git",
"react-native-privacy-snapshot": "https://github.com/standardnotes/react-native-privacy-snapshot.git",
@@ -17957,9 +17958,9 @@
}
},
"node_modules/react-native-actions-sheet": {
"version": "0.9.0-alpha.10",
"resolved": "https://registry.npmjs.org/react-native-actions-sheet/-/react-native-actions-sheet-0.9.0-alpha.10.tgz",
"integrity": "sha512-R1WcRPwtgaVwMOvDhVnYD+dxTWSSF84Qgco3UT71U8o3F59nx3J4IJBamt5e8RDBinLXPxgPvXEYYw9UO9VtpA==",
"version": "0.9.0-alpha.16",
"resolved": "https://registry.npmjs.org/react-native-actions-sheet/-/react-native-actions-sheet-0.9.0-alpha.16.tgz",
"integrity": "sha512-/aR+MsJR1IgeZIg7elD2Zu/6In7Ztsvt4fPeOt2sM++FmsAofprsJ4JHjwPFEO06E5djS8ky8VvpaeCrC8HoFg==",
"peerDependencies": {
"react-native": "*",
"react-native-gesture-handler": "*"
@@ -18418,6 +18419,11 @@
"react-native": ">=0.65.0"
}
},
"node_modules/react-native-navigation-bar-color": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/react-native-navigation-bar-color/-/react-native-navigation-bar-color-2.0.2.tgz",
"integrity": "sha512-ZmpLWRocyme1au11e5ZuecMS/UCi57nlzgnioi03Q6ERMbeUOqqbWgNBaNB7SsCeqBV6fZPjo3+A64zEIpzw4w=="
},
"node_modules/react-native-notification-sounds": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/react-native-notification-sounds/-/react-native-notification-sounds-0.5.5.tgz",
@@ -24312,7 +24318,7 @@
"qclone": "^1.2.0",
"react": "18.0.0",
"react-native": "0.69.7",
"react-native-actions-sheet": "^0.9.0-alpha.9",
"react-native-actions-sheet": "0.9.0-alpha.16",
"react-native-check-version": "https://github.com/flexible-agency/react-native-check-version",
"react-native-drax": "^0.10.2",
"react-native-image-zoom-viewer": "^3.0.1",
@@ -24401,6 +24407,7 @@
"react-native-keychain": "4.0.5",
"react-native-mmkv-storage": "^0.8.0",
"react-native-modal-datetime-picker": "14.0.0",
"react-native-navigation-bar-color": "2.0.2",
"react-native-notification-sounds": "0.5.5",
"react-native-orientation": "https://github.com/yamill/react-native-orientation.git",
"react-native-privacy-snapshot": "https://github.com/standardnotes/react-native-privacy-snapshot.git",
@@ -34856,9 +34863,9 @@
}
},
"react-native-actions-sheet": {
"version": "0.9.0-alpha.10",
"resolved": "https://registry.npmjs.org/react-native-actions-sheet/-/react-native-actions-sheet-0.9.0-alpha.10.tgz",
"integrity": "sha512-R1WcRPwtgaVwMOvDhVnYD+dxTWSSF84Qgco3UT71U8o3F59nx3J4IJBamt5e8RDBinLXPxgPvXEYYw9UO9VtpA=="
"version": "0.9.0-alpha.16",
"resolved": "https://registry.npmjs.org/react-native-actions-sheet/-/react-native-actions-sheet-0.9.0-alpha.16.tgz",
"integrity": "sha512-/aR+MsJR1IgeZIg7elD2Zu/6In7Ztsvt4fPeOt2sM++FmsAofprsJ4JHjwPFEO06E5djS8ky8VvpaeCrC8HoFg=="
},
"react-native-actions-shortcuts": {
"version": "1.0.1",
@@ -35172,6 +35179,11 @@
"prop-types": "^15.7.2"
}
},
"react-native-navigation-bar-color": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/react-native-navigation-bar-color/-/react-native-navigation-bar-color-2.0.2.tgz",
"integrity": "sha512-ZmpLWRocyme1au11e5ZuecMS/UCi57nlzgnioi03Q6ERMbeUOqqbWgNBaNB7SsCeqBV6fZPjo3+A64zEIpzw4w=="
},
"react-native-notification-sounds": {
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/react-native-notification-sounds/-/react-native-notification-sounds-0.5.5.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "2.4.6",
"version": "2.4.10",
"private": true,
"license": "GPL-3.0-or-later",
"workspaces": [
@@ -36,4 +36,4 @@
"react-native-fingerprint-scanner": "https://github.com/ammarahm-ed/react-native-fingerprint-scanner.git",
"react-native-iap": "7.5.6"
}
}
}

View File

@@ -1,16 +1,28 @@
diff --git a/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js b/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
index 4536402..5ceaf65 100644
index 4536402..2a100d9 100644
--- a/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
+++ b/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
@@ -82,6 +82,7 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
@@ -64,7 +64,7 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
_subscriptions: Array<EventSubscription> = [];
viewRef: {current: React.ElementRef<typeof View> | null, ...};
_initialFrameHeight: number = 0;
-
+ keyboardShown = false;
constructor(props: Props) {
super(props);
this.state = {bottom: 0};
@@ -80,7 +80,9 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
const keyboardY =
keyboardFrame.screenY - (this.props.keyboardVerticalOffset ?? 0);
// Calculate the displacement needed for the view such that it
- // Calculate the displacement needed for the view such that it
+
+ if (this._initialFrameHeight && frame.height < this._initialFrameHeight && this.keyboardShown) frame.height = this._initialFrameHeight;
+ // Calculate the displacement needed for the view such that it
// no longer overlaps with the keyboard
+ if (this._initialFrameHeight && frame.height < this._initialFrameHeight) frame.height = this._initialFrameHeight;
return Math.max(frame.y + frame.height - keyboardY, 0);
}
@@ -92,7 +93,9 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
@@ -92,7 +94,9 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
_onLayout = (event: ViewLayoutEvent) => {
const wasFrameNull = this._frame == null;
@@ -20,6 +32,19 @@ index 4536402..5ceaf65 100644
if (!this._initialFrameHeight) {
// save the initial frame height, before the keyboard is visible
this._initialFrameHeight = this._frame.height;
@@ -142,6 +146,8 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
this._subscriptions = [
Keyboard.addListener('keyboardDidHide', this._onKeyboardChange),
Keyboard.addListener('keyboardDidShow', this._onKeyboardChange),
+ Keyboard.addListener('keyboardDidHide', () => this.keyboardShown = true),
+ Keyboard.addListener('keyboardDidShow', () => this.keyboardShown = false),
];
}
}
diff --git a/node_modules/react-native/React/.DS_Store b/node_modules/react-native/React/.DS_Store
new file mode 100644
index 0000000..5155fbe
Binary files /dev/null and b/node_modules/react-native/React/.DS_Store differ
diff --git a/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m b/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m
index f0f6402..d645d81 100644
--- a/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m
@@ -44,6 +69,13 @@ index f0f6402..d645d81 100644
CGFloat smallerOffset = 0.0;
CGFloat largerOffset = maximumOffset;
diff --git a/node_modules/react-native/scripts/.packager.env b/node_modules/react-native/scripts/.packager.env
new file mode 100644
index 0000000..361f5fb
--- /dev/null
+++ b/node_modules/react-native/scripts/.packager.env
@@ -0,0 +1 @@
+export RCT_METRO_PORT=8081
diff --git a/node_modules/react-native/scripts/packager.sh b/node_modules/react-native/scripts/packager.sh
index b9f9016..8859130 100755
--- a/node_modules/react-native/scripts/packager.sh

View File

@@ -1,21 +0,0 @@
diff --git a/node_modules/react-native-actions-sheet/dist/src/index.js b/node_modules/react-native-actions-sheet/dist/src/index.js
index 7eb4357..18bf26d 100644
--- a/node_modules/react-native-actions-sheet/dist/src/index.js
+++ b/node_modules/react-native-actions-sheet/dist/src/index.js
@@ -747,7 +747,7 @@ var ActionSheet = /** @class */ (function (_super) {
width: "100%"
},
]}>
- {this.props.ExtraOverlayComponent}
+
<FlatList testID={(_a = this.props.testIDs) === null || _a === void 0 ? void 0 : _a.scrollview} bounces={false} keyboardShouldPersistTaps={keyboardShouldPersistTaps} keyboardDismissMode={keyboardDismissMode} ref={this.scrollViewRef} scrollEventThrottle={16} overScrollMode="never" showsVerticalScrollIndicator={false} onMomentumScrollBegin={this._onScrollBegin} onScrollEndDrag={this._onScrollEnd} onMomentumScrollEnd={this._onScrollEnd} scrollEnabled={scrollable} onScrollBeginDrag={this._onScrollBeginDrag} onTouchEnd={this._onTouchEnd} onScroll={this._onScroll} scrollsToTop={false} style={[
styles.scrollView,
@@ -821,6 +821,7 @@ var ActionSheet = /** @class */ (function (_super) {
</Animated.View>
</View>);
}}/>
+ {this.props.ExtraOverlayComponent}
</Animated.View>
</Root>
</>);

File diff suppressed because it is too large Load Diff

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/desktop",
"version": "2.4.4",
"version": "2.4.6",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@notesnook/desktop",
"version": "2.4.4",
"version": "2.4.6",
"dependencies": {
"diary": "^0.3.1",
"electron-updater": "^5.3.0",

View File

@@ -2,7 +2,7 @@
"name": "@notesnook/desktop",
"productName": "Notesnook",
"description": "Your private note taking space",
"version": "2.4.4",
"version": "2.4.6",
"appAppleId": "1544027013",
"private": true,
"main": "./build/electron.js",
@@ -27,7 +27,7 @@
"scripts": {
"build": "esbuild ./electron.js ./preload.js --external:electron --external:fsevents --minify --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=false --define:RELEASE=true",
"build:mas": "esbuild ./electron.js ./preload.js --minify --external:electron --external:fsevents --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=true --define:RELEASE=true",
"pack": "rm -rf ./build && cp -r ../build ./ && npm run build && yarn electron-builder --linux deb",
"pack": "rm -rf ./build && cp -r ../build ./ && npm run build && yarn electron-builder --linux AppImage:x64 AppImage:arm64",
"postinstall": "patch-package"
},
"author": {
@@ -101,8 +101,20 @@
},
"win": {
"target": [
"nsis",
"portable"
{
"target": "nsis",
"arch": [
"x64",
"arm64"
]
},
{
"target": "portable",
"arch": [
"x64",
"arm64"
]
}
],
"icon": "assets/icons/app.ico"
},
@@ -115,10 +127,20 @@
},
"linux": {
"target": [
"AppImage",
"deb",
"rpm",
"snap"
{
"target": "AppImage",
"arch": [
"x64",
"arm64"
]
},
{
"target": "snap",
"arch": [
"x64",
"arm64"
]
}
],
"category": "Office",
"icon": "assets/icons/app.icns",

View File

@@ -22,12 +22,14 @@ import { AssetManager } from "./asset-manager";
import { EVENTS } from "./events";
import bringToFront from "./ipc/actions/bringToFront";
import { sendMessageToRenderer } from "./ipc/utils";
import { isFlatpak } from "./utils";
let tray: Tray | undefined = undefined;
export function setupTray() {
if (tray) tray.destroy();
const trayIconSize = process.platform === "win32" || process.platform === "darwin" ? 16 : 32;
const trayIconSize =
process.platform === "win32" || process.platform === "darwin" ? 16 : 32;
tray = new Tray(
AssetManager.icon("tray-icon", {
@@ -39,14 +41,18 @@ export function setupTray() {
{
label: "Show app",
type: "normal",
icon: AssetManager.icon("tray-icon", { size: trayIconSize }),
icon: isFlatpak()
? undefined
: AssetManager.icon("tray-icon", { size: trayIconSize }),
click: bringToFront
},
{ type: "separator" },
{
label: "New note",
type: "normal",
icon: AssetManager.icon("note-add", { size: trayIconSize }),
icon: isFlatpak()
? undefined
: AssetManager.icon("note-add", { size: trayIconSize }),
click: () => {
bringToFront();
sendMessageToRenderer(EVENTS.createItem, { itemType: "note" });
@@ -55,7 +61,9 @@ export function setupTray() {
{
label: "New notebook",
type: "normal",
icon: AssetManager.icon("notebook-add", { size: trayIconSize }),
icon: isFlatpak()
? undefined
: AssetManager.icon("notebook-add", { size: trayIconSize }),
click: () => {
bringToFront();
sendMessageToRenderer(EVENTS.createItem, { itemType: "notebook" });
@@ -64,7 +72,9 @@ export function setupTray() {
{ type: "separator" },
{
label: "Quit",
icon: AssetManager.icon("quit", { size: trayIconSize }),
icon: isFlatpak()
? undefined
: AssetManager.icon("quit", { size: trayIconSize }),
type: "normal",
click: () => {
app.exit(0);

View File

@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { app } from "electron";
import { join } from "path";
import { statSync } from "fs";
import { statSync, existsSync } from "fs";
const APP_ICON_PATH = join(
__dirname,
@@ -52,4 +52,8 @@ function getPath(filePath) {
}
}
export { getPath, isDevelopment, APP_ICON_PATH };
function isFlatpak() {
return existsSync("/.flatpak-info");
}
export { getPath, isDevelopment, isFlatpak, APP_ICON_PATH };

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/web",
"version": "2.4.4",
"version": "2.4.6",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@notesnook/web",
"version": "2.4.4",
"version": "2.4.6",
"license": "GPL-3.0-or-later",
"dependencies": {
"@aws-sdk/util-base64-browser": "^3.208.0",
@@ -17,7 +17,7 @@
"@emotion/react": "^11.10.0",
"@mdi/js": "^6.9.96",
"@mdi/react": "^1.6.0",
"@notesnook-importer/core": "^1.6.0",
"@notesnook-importer/core": "^1.7.1",
"@notesnook/localforage-getitems": "^1.4.4",
"@tanstack/react-virtual": "^3.0.0-beta.18",
"@theme-ui/components": "^0.14.7",
@@ -3352,14 +3352,14 @@
}
},
"node_modules/@notesnook-importer/core": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@notesnook-importer/core/-/core-1.6.0.tgz",
"integrity": "sha512-lixZ+BFqCdgXgJeW6qQuwlpunX0CjaOmWKKFxCu7twxS/zIcKvh72tyiXKMQCDlpl0kIC8hCeuGR3FcdaUIjPg==",
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@notesnook-importer/core/-/core-1.7.1.tgz",
"integrity": "sha512-KWwstLbmkCTpRZsjrNU8ZBzlSfta3sAznpaq0s9QM10wCuu4aHrSYARQYfXQWRvyznUeflzL81EvZHe/XGLRvQ==",
"dependencies": {
"@notesnook-importer/enex": "^1.6.0",
"@notesnook-importer/onenote": "^1.6.0",
"@notesnook-importer/storage": "^1.6.0",
"@notesnook-importer/znel": "^1.6.0",
"@notesnook-importer/enex": "*",
"@notesnook-importer/onenote": "*",
"@notesnook-importer/storage": "*",
"@notesnook-importer/znel": "*",
"@standardnotes/features": "1.58.0",
"@standardnotes/models": "1.42.0",
"@streamparser/json": "^0.0.10",
@@ -20264,14 +20264,14 @@
}
},
"@notesnook-importer/core": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@notesnook-importer/core/-/core-1.6.0.tgz",
"integrity": "sha512-lixZ+BFqCdgXgJeW6qQuwlpunX0CjaOmWKKFxCu7twxS/zIcKvh72tyiXKMQCDlpl0kIC8hCeuGR3FcdaUIjPg==",
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@notesnook-importer/core/-/core-1.7.1.tgz",
"integrity": "sha512-KWwstLbmkCTpRZsjrNU8ZBzlSfta3sAznpaq0s9QM10wCuu4aHrSYARQYfXQWRvyznUeflzL81EvZHe/XGLRvQ==",
"requires": {
"@notesnook-importer/enex": "^1.6.0",
"@notesnook-importer/onenote": "^1.6.0",
"@notesnook-importer/storage": "^1.6.0",
"@notesnook-importer/znel": "^1.6.0",
"@notesnook-importer/enex": "*",
"@notesnook-importer/onenote": "*",
"@notesnook-importer/storage": "*",
"@notesnook-importer/znel": "*",
"@standardnotes/features": "1.58.0",
"@standardnotes/models": "1.42.0",
"@streamparser/json": "^0.0.10",

View File

@@ -1,7 +1,7 @@
{
"name": "@notesnook/web",
"description": "Your private note taking space",
"version": "2.4.4",
"version": "2.4.6",
"private": true,
"main": "./src/app.js",
"homepage": "https://notesnook.com/",
@@ -16,7 +16,7 @@
"@emotion/react": "^11.10.0",
"@mdi/js": "^6.9.96",
"@mdi/react": "^1.6.0",
"@notesnook-importer/core": "^1.6.0",
"@notesnook-importer/core": "^1.7.1",
"@notesnook/core": "*",
"@notesnook/crypto": "*",
"@notesnook/crypto-worker": "*",

File diff suppressed because it is too large Load Diff

View File

@@ -93,8 +93,6 @@ export default function AppEffects({ setShow }) {
initEditorStore();
(async function () {
await resetReminders();
await updateLastSynced();
if (await initUser()) {
showUpgradeReminderDialogs();

View File

@@ -209,8 +209,9 @@ export function showError(title: string, message: string) {
export function showMultiDeleteConfirmation(length: number) {
return confirm({
title: `Delete ${length} items?`,
message:
"These items will be **kept in your Trash for 7 days** after which they will be permanently deleted.",
message: `These items will be **kept in your Trash for ${
db.settings?.getTrashCleanupInterval() || 7
} days** after which they will be permanently deleted.`,
positiveButtonText: "Yes",
negativeButtonText: "No"
});
@@ -470,9 +471,9 @@ export function showCreateTopicDialog() {
}}
onAction={async (topic: Record<string, unknown>) => {
if (!topic) return;
const notebookId = notebookStore.get().selectedNotebookId;
await db.notebooks?.notebook(notebookId).topics.add(topic);
notebookStore.setSelectedNotebook(notebookId);
const notebook = notebookStore.get().selectedNotebook;
await db.notebooks?.notebook(notebook.id).topics.add(topic);
notebookStore.setSelectedNotebook(notebook.id);
showToast("success", "Topic created!");
perform(true);
}}

View File

@@ -92,22 +92,17 @@ const features: Record<FeatureKeys, Feature> = {
: isDesktop()
? [
{
title: "Smoother app loading animation",
title: "Topics are now optional",
subtitle:
"Starting the app will now feel much, much smoother and less janky."
"You can now move notes directly into a notebook without ever needing a topic (but topics are still there...just optional)."
},
{
title: "Faster & less buggy outline list"
title: "Adjustable trash cleanup interval",
subtitle:
"You can now customize after how long you want your trash to be automatically cleared. Just go into Settings -> Trash settings."
},
{
title: "Sortable task lists",
subtitle:
"Task lists can now be sorted by their status. This'll move all completed tasks to the bottom."
},
{
title: "Faster editor",
subtitle:
"Editor performance has been vastly improved in this version especially when you have a lot of images, task list etc."
title: "Images are now much more stable"
}
]
: [],

View File

@@ -30,7 +30,6 @@ import { showToast } from "../../utils/toast";
import { pluralize } from "../../utils/string";
import { isMac } from "../../utils/platform";
import create from "zustand";
import { usePersistentState } from "../../hooks/use-persistent-state";
type MoveDialogProps = { onClose: Perform; noteIds: string[] };
type NotebookReference = {
@@ -45,7 +44,11 @@ type Item = {
title: string;
};
type Topic = Item & { notebookId: string };
type Notebook = Item & { topics: Topic[]; dateCreated: number };
type Notebook = Item & {
topics: Topic[];
dateCreated: number;
dateModified: number;
};
interface ISelectionStore {
selected: NotebookReference[];
@@ -61,13 +64,9 @@ export const useSelectionStore = create<ISelectionStore>((set) => ({
}));
function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
const { selected, setIsMultiselect, isMultiselect, setSelected } =
useSelectionStore();
const [suggestion, setSuggestion] = usePersistentState<NotebookReference[]>(
"link-notebooks:suggestion",
[]
);
const setSelected = useSelectionStore((store) => store.setSelected);
const setIsMultiselect = useSelectionStore((store) => store.setIsMultiselect);
const isMultiselect = useSelectionStore((store) => store.isMultiselect);
const refreshNotebooks = useStore((store) => store.refresh);
const notebooks = useStore((store) => store.notebooks);
@@ -109,7 +108,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
}
setSelected(selected);
setIsMultiselect(false);
setIsMultiselect(selected.length > 1);
}, [noteIds, notebooks, setSelected, setIsMultiselect]);
return (
@@ -123,9 +122,8 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
width={450}
positiveButton={{
text: "Done",
disabled: !selected.length,
onClick: async () => {
setSuggestion(selected);
const { selected } = useSelectionStore.getState();
for (const item of selected) {
try {
if (item.op === "remove") {
@@ -145,9 +143,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
if (stringified) {
showToast(
"success",
`${pluralize(noteIds.length, "note", "notes")} ${stringified
.replace("Add", "added")
.replace("remove", "removed")}`
`${pluralize(noteIds.length, "note", "notes")} ${stringified}`
);
}
@@ -159,31 +155,27 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
onClick: () => onClose(false)
}}
>
{isMultiselect && (
<Button
variant="anchor"
onClick={() => {
const originalSelection: NotebookReference[] = useSelectionStore
.getState()
.selected.filter((a) => !a.new)
.map((s) => ({ ...s, op: "add" }));
setSelected(originalSelection);
setIsMultiselect(originalSelection.length > 1);
}}
sx={{ textDecoration: "none", mt: 1 }}
>
Reset selection
</Button>
)}
<Flex
mt={1}
sx={{ overflowY: "hidden", flexDirection: "column" }}
data-test-id="notebook-list"
>
{suggestion.length > 0 && selected.length <= 0 && (
<Flex
sx={{
bg: "shade",
borderRadius: "default",
p: 1,
alignItems: "center",
cursor: "pointer",
":hover": {
filter: "brightness(70%)"
}
}}
onClick={() => setSelected(suggestion.slice())}
>
<Icon.Suggestion color="primary" size={16} sx={{ mr: 1 }} />
<Text variant="body" sx={{ color: "primary" }}>
{stringifySelected(suggestion)}
</Text>
</Flex>
)}
<FilteredTree
placeholders={{
empty: "Add a new notebook",
@@ -200,6 +192,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
}
renderItem={(notebook, _index, refresh, isSearching) => (
<NotebookItem
key={notebook.id}
notebook={notebook}
isSearching={isSearching}
onCreateItem={async (title) => {
@@ -210,20 +203,6 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
)}
/>
</Flex>
{isMultiselect && (
<Button
variant="anchor"
onClick={() => {
setIsMultiselect(false);
setSelected(
selected.filter((a) => !a.new).map((s) => ({ ...s, op: "add" }))
);
}}
sx={{ textDecoration: "none", mt: 1 }}
>
Reset selection
</Button>
)}
</Dialog>
);
}
@@ -235,12 +214,11 @@ function NotebookItem(props: {
}) {
const { notebook, isSearching, onCreateItem } = props;
const { selected, setIsMultiselect, setSelected, isMultiselect } =
useSelectionStore();
const [isCreatingNew, setIsCreatingNew] = useState(false);
const index = findSelectionIndex(notebook, selected);
const isSelected = index > -1;
const setIsMultiselect = useSelectionStore((store) => store.setIsMultiselect);
const setSelected = useSelectionStore((store) => store.setSelected);
const isMultiselect = useSelectionStore((store) => store.isMultiselect);
return (
<Box as="li" data-test-id="notebook">
@@ -273,6 +251,7 @@ function NotebookItem(props: {
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
const { selected } = useSelectionStore.getState();
const isCtrlPressed = e.ctrlKey || e.metaKey;
if (isCtrlPressed) setIsMultiselect(true);
@@ -284,7 +263,7 @@ function NotebookItem(props: {
}
}}
>
<SelectedCheck size={20} selected={isSelected} />
<SelectedCheck size={20} item={notebook} />
<Text
className="title"
data-test-id="notebook-title"
@@ -299,11 +278,12 @@ function NotebookItem(props: {
</Text>
</Flex>
<Flex data-test-id="notebook-tools" sx={{ alignItems: "center" }}>
<TopicSelectionIndicator notebook={notebook} />
<Button
variant="tool"
className="create-topic"
data-test-id="create-topic"
sx={{ display: "none", p: 1, mr: 1 }}
sx={{ display: "none", p: 1 }}
>
<Icon.Plus
size={18}
@@ -342,7 +322,7 @@ function NotebookItem(props: {
p: "small"
}}
>
<SelectedCheck selected={false} />
<SelectedCheck />
<Input
variant="clean"
data-test-id={`new-topic-input`}
@@ -373,14 +353,23 @@ function NotebookItem(props: {
);
}
function TopicSelectionIndicator({ notebook }: { notebook: Notebook }) {
const hasSelectedTopics = useSelectionStore(
(store) =>
store.selected.filter((nb) => nb.id === notebook.id && !!nb.topic)
.length > 0
);
if (!hasSelectedTopics) return null;
return <Icon.Circle size={8} color="primary" sx={{ mr: 1 }} />;
}
function TopicItem(props: { topic: Topic }) {
const { topic } = props;
const { selected, setIsMultiselect, setSelected, isMultiselect } =
useSelectionStore();
const index = findSelectionIndex(topic, selected);
const selectedTopic = selected[index];
const setSelected = useSelectionStore((store) => store.setSelected);
const setIsMultiselect = useSelectionStore((store) => store.setIsMultiselect);
const isMultiselect = useSelectionStore((store) => store.isMultiselect);
return (
<Flex
@@ -395,6 +384,8 @@ function TopicItem(props: { topic: Topic }) {
":hover": { bg: "hover" }
}}
onClick={(e) => {
const { selected } = useSelectionStore.getState();
const isCtrlPressed = e.ctrlKey || e.metaKey;
if (isCtrlPressed) setIsMultiselect(true);
@@ -405,13 +396,7 @@ function TopicItem(props: { topic: Topic }) {
}
}}
>
<SelectedCheck
selected={
selectedTopic?.op === "remove"
? "remove"
: selectedTopic?.op === "add"
}
/>
<SelectedCheck item={topic} />
<Text variant="body" sx={{ fontSize: "subtitle" }}>
{topic.title}
</Text>
@@ -543,12 +528,18 @@ function FilteredTree<T extends Item>(props: FilteredTreeProps<T>) {
}
function SelectedCheck({
selected,
item,
size = 20
}: {
selected: boolean | null | "remove";
item?: Topic | Notebook;
size?: number;
}) {
const selectedItem = useSelectionStore(
(store) => item && store.selected[findSelectionIndex(item, store.selected)]
);
const selected =
selectedItem?.op === "remove" ? "remove" : selectedItem?.op === "add";
return selected === true ? (
<Icon.CheckCircleOutline size={size} sx={{ mr: 1 }} color="primary" />
) : selected === null ? (
@@ -623,21 +614,23 @@ function selectSingle(topic: Topic | Notebook, array: NotebookReference[]) {
function stringifySelected(suggestion: NotebookReference[]) {
const added = suggestion
.filter((a) => a.op === "add")
.map(resolve)
.filter((a) => a.new && a.op === "add")
.map(resolveReference)
.filter(Boolean);
const removed = suggestion
.filter((a) => a.op === "remove")
.map(resolve)
.map(resolveReference)
.filter(Boolean);
if (!added.length && !removed.length) return;
const parts = ["Add to"];
const parts = [];
if (added.length > 0) parts.push("added to");
if (added.length >= 1) parts.push(added[0]);
if (added.length > 1) parts.push(`and ${added.length - 1} others`);
if (removed.length >= 1) {
parts.push("& remove from");
if (parts.length > 0) parts.push("&");
parts.push("removed from");
parts.push(removed[0]);
}
if (removed.length > 1) parts.push(`and ${removed.length - 1} others`);
@@ -645,7 +638,7 @@ function stringifySelected(suggestion: NotebookReference[]) {
return parts.join(" ") + ".";
}
function resolve(ref: NotebookReference) {
function resolveReference(ref: NotebookReference): string | undefined {
const notebook = db.notebooks?.notebook(ref.id);
if (!notebook) return undefined;

View File

@@ -714,8 +714,9 @@ export function getDisabledTools(tools: TreeNode[]) {
for (const key in allTools) {
const tool = allTools[key as ToolId];
if (tool.conditional) continue;
if (items.findIndex((t) => t.toolId === key) <= -1)
disabled.push(key as ToolId);
const isDisabled = items.findIndex((t) => t.toolId === key);
if (isDisabled === -1 && key !== "none") disabled.push(key as ToolId);
}
return disabled;
}

View File

@@ -35,7 +35,7 @@ function DiffViewer(props) {
const setIsEditorOpen = useAppStore((store) => store.setIsEditorOpen);
const sync = useAppStore((store) => store.sync);
const clearSession = useEditorStore((store) => store.clearSession);
const openSession = useEditorStore((store) => store.openSession);
const [conflictedNote, setConflictedNote] = useState();
const [remoteContent, setRemoteContent] = useState();
const [localContent, setLocalContent] = useState();
@@ -98,7 +98,10 @@ function DiffViewer(props) {
hashNavigate(`/notes/create`, { replace: true });
return;
}
notesStore.setSelectedNote(noteId);
await openSession(noteId);
setIsEditorOpen(true);
note = note.data;
const content = await db.content.raw(note.contentId);
@@ -114,12 +117,7 @@ function DiffViewer(props) {
setHtmlDiff({ before: content.data, after: content.conflicted.data });
})();
}, [noteId, resolveConflict]);
useEffect(() => {
clearSession(false);
setIsEditorOpen(true);
}, [setIsEditorOpen, clearSession]);
}, [noteId, openSession, setIsEditorOpen, resolveConflict]);
if (!conflictedNote || !localContent || !remoteContent) return null;
return (

View File

@@ -110,25 +110,31 @@ export default function EditorManager({
const isContent = item.type === "tiptap" && item.id === contentId;
const isNote = item.type === "note" && item.id === id;
if (isContent) {
if (isContent && editorInstance.current) {
if (locked) {
const result = await db.vault?.decryptContent(item).catch(() => {});
if (result) item.data = result.data;
else EV.publish(EVENTS.vaultLocked);
}
const oldHashes = editorInstance.current.getMediaHashes();
editorInstance.current?.updateContent(item.data as string);
editorInstance.current.updateContent(item.data as string);
const newHashes = editorInstance.current.getMediaHashes();
const hashesToLoad = newHashes.filter(
(hash, index) => hash !== oldHashes[index]
);
if (appstore.get().isSyncing()) {
db.eventManager.subscribe(
EVENTS.syncCompleted,
async () => {
await db.attachments?.downloadMedia(id);
await db.attachments?.downloadMedia(id, hashesToLoad);
},
true
);
} else {
await db.attachments?.downloadMedia(id);
await db.attachments?.downloadMedia(id, hashesToLoad);
}
} else if (isNote) {
if (!locked && item.locked) return EV.publish(EVENTS.vaultLocked);

View File

@@ -52,7 +52,7 @@ import { getCurrentPreset } from "../../common/toolbar-config";
import { useIsUserPremium } from "../../hooks/use-is-user-premium";
import { showBuyDialog } from "../../common/dialog-controller";
import { useStore as useSettingsStore } from "../../stores/setting-store";
import { debounceWithId, inlineDebounce } from "../../utils/debounce";
import { debounce, debounceWithId } from "../../utils/debounce";
import { store as editorstore } from "../../stores/editor-store";
type TipTapProps = {
@@ -214,38 +214,32 @@ function TipTap(props: TipTapProps) {
canUndo: editor.can().undo()
});
},
onSelectionUpdate: ({ editor, transaction }) => {
inlineDebounce(
"tiptap:onSelectionUpdate",
() => {
const isEmptySelection = transaction.selection.empty;
configure((old) => {
const oldSelected = old.statistics?.words?.selected;
const oldWords = old.statistics?.words.total || 0;
if (isEmptySelection)
return oldSelected
? {
statistics: { words: { total: oldWords, selected: 0 } }
}
: old;
const selectedWords = getSelectedWords(
editor as Editor,
transaction.selection
);
return {
statistics: {
words: {
total: oldWords,
selected: selectedWords
}
onSelectionUpdate: debounce(({ editor, transaction }) => {
const isEmptySelection = transaction.selection.empty;
configure((old) => {
const oldSelected = old.statistics?.words?.selected;
const oldWords = old.statistics?.words.total || 0;
if (isEmptySelection)
return oldSelected
? {
statistics: { words: { total: oldWords, selected: 0 } }
}
};
});
},
500
);
},
: old;
const selectedWords = getSelectedWords(
editor as Editor,
transaction.selection
);
return {
statistics: {
words: {
total: oldWords,
selected: selectedWords
}
}
};
});
}, 500),
theme,
onOpenAttachmentPicker: (_editor, type) => {
onInsertAttachment?.(type);
@@ -373,6 +367,14 @@ function toIEditor(editor: Editor): IEditor {
focus: () => editor.current?.commands.focus("start"),
undo: () => editor.current?.commands.undo(),
redo: () => editor.current?.commands.redo(),
getMediaHashes: () => {
if (!editor.current) return [];
const hashes: string[] = [];
editor.current.state.doc.descendants((n) => {
if (typeof n.attrs.hash === "string") hashes.push(n.attrs.hash);
});
return hashes;
},
updateContent: (content) => {
const { from, to } = editor.state.selection;
editor.current
@@ -390,15 +392,18 @@ function toIEditor(editor: Editor): IEditor {
},
attachFile: (file: Attachment) => {
if (file.dataurl) {
editor.current?.commands.insertImage({ ...file, src: file.dataurl });
editor.current?.commands.insertImage({
...file,
dataurl: file.dataurl
});
} else editor.current?.commands.insertAttachment(file);
},
loadWebClip: (hash, src) =>
editor.current?.commands.updateWebClip({ hash }, { src }),
loadImage: (hash, src) =>
loadImage: (hash, dataurl) =>
editor.current?.commands.updateImage(
{ hash },
{ hash, src, preventUpdate: true }
{ hash, dataurl, preventUpdate: true }
),
sendAttachmentProgress: (hash, type, progress) =>
editor.current?.commands.setAttachmentProgress({

View File

@@ -30,6 +30,7 @@ export interface IEditor {
focus: () => void;
undo: () => void;
redo: () => void;
getMediaHashes: () => string[];
updateContent: (content: string) => void;
attachFile: (file: Attachment) => void;
loadWebClip: (hash: string, html: string) => void;

View File

@@ -20,8 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import * as Icon from "../icons";
import "./search.css";
import Field from "../field";
function SearchBox(props) {
import { debounce } from "../../utils/debounce";
function SearchBox({ onSearch }) {
return (
<Field
data-test-id="search-input"
@@ -29,18 +29,16 @@ function SearchBox(props) {
id="search"
name="search"
type="text"
sx={{m: 0,mx: 1,mt: 1}}
sx={{ m: 0, mx: 1, mt: 1 }}
placeholder="Type your query here"
onKeyDown={(e) => {
if (e.key === "Enter") props.onSearch(e.target.value);
}}
onChange={debounce((e) => onSearch(e.target.value), 250)}
action={{
icon: Icon.Search,
testId: "search-button",
onClick: () => {
const searchField = document.getElementById("search");
if (searchField && searchField.value && searchField.value.length) {
props.onSearch(searchField.value);
onSearch(searchField.value);
}
}
}}

View File

@@ -23,20 +23,25 @@ import * as Icon from "../icons";
import { db } from "../../common/db";
import { useStore as useEditorStore } from "../../stores/editor-store";
import { useStore as useAppStore } from "../../stores/app-store";
import { useStore as useNoteStore } from "../../stores/note-store";
import Field from "../field";
import { showToast } from "../../utils/toast";
function Unlock(props) {
const { noteId } = props;
const note = useMemo(() => db.notes.note(noteId)?.data, [noteId]);
const passwordRef = useRef();
const [isWrong, setIsWrong] = useState(false);
const [isUnlocking, setIsUnlocking] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const passwordRef = useRef();
const note = useMemo(
() => !isLoading && db.notes.note(noteId)?.data,
[noteId, isLoading]
);
const openLockedSession = useEditorStore((store) => store.openLockedSession);
const clearSession = useEditorStore((store) => store.clearSession);
const openSession = useEditorStore((store) => store.openSession);
const setIsEditorOpen = useAppStore((store) => store.setIsEditorOpen);
const setSelectedNote = useNoteStore((store) => store.setSelectedNote);
const submit = useCallback(async () => {
setIsUnlocking(true);
@@ -58,10 +63,15 @@ function Unlock(props) {
}, [setIsWrong, noteId, openLockedSession]);
useEffect(() => {
clearSession(false);
setIsEditorOpen(true);
setTimeout(() => setSelectedNote(noteId));
}, [clearSession, setIsEditorOpen, setSelectedNote, noteId]);
(async () => {
setIsLoading(true);
await openSession(noteId);
setIsEditorOpen(true);
setIsLoading(false);
})();
}, [openSession, setIsEditorOpen, noteId]);
return (
<Flex

View File

@@ -32,6 +32,7 @@ type DesktopIntegrationSettings = {
};
declare interface Window {
os: NodeJS.Platform | "mas";
config: {
static spellChecker(): Promise<SpellCheckerOptions>;
static desktopIntegration(): Promise<DesktopIntegrationSettings>;

View File

@@ -29,6 +29,7 @@ import { sendAttachmentsProgressEvent } from "@notesnook/core/common";
import { saveAs } from "file-saver";
import { showToast } from "../utils/toast";
import { db } from "../common/db";
import { getFileNameWithExtension } from "@notesnook/core/utils/filename";
const ABYTES = 17;
const CHUNK_SIZE = 512 * 1024;
@@ -97,18 +98,19 @@ async function writeEncryptedFile(file, key, hash) {
/**
* We perform 4 steps here:
* 1. We convert base64 to Uint8Array (if we get base64, that is)
* 1. We convert base64 to Uint8Array
* 2. We hash the Uint8Array.
* 3. We encrypt the Uint8Array
* 4. We save the encrypted Uint8Array
*/
async function writeEncrypted(filename, { data, type, key, mimeType }) {
if (type === "base64") data = new Uint8Array(Buffer.from(data, "base64"));
async function writeEncryptedBase64(metadata) {
const { data, key, mimeType } = metadata;
const { hash, type: hashType } = await hashBuffer(data);
if (!filename) filename = hash;
const bytes = new Uint8Array(Buffer.from(data, "base64"));
const file = new File([data.buffer], filename, {
const { hash, type: hashType } = await hashBuffer(bytes);
const file = new File([bytes.buffer], hash, {
type: mimeType || "application/octet-stream"
});
@@ -120,6 +122,15 @@ async function writeEncrypted(filename, { data, type, key, mimeType }) {
};
}
/**
*
* @param {string} data the base64 data
* @returns
*/
function hashBase64(data) {
return hashBuffer(Buffer.from(data, "base64"));
}
/**
*
* @param {import("hash-wasm/dist/lib/util").IDataType} data
@@ -413,7 +424,8 @@ async function saveFile(filename, fileMetadata) {
},
filename
);
saveAs(new Blob(blobParts, { type }), name);
saveAs(new Blob(blobParts, { type }), getFileNameWithExtension(name, type));
if (isUploaded) await streamablefs.deleteFile(filename);
}
@@ -459,18 +471,20 @@ function clearFileStorage() {
}
const FS = {
writeEncrypted,
writeEncryptedBase64,
readEncrypted,
uploadFile: cancellable(uploadFile),
downloadFile: cancellable(downloadFile),
deleteFile,
saveFile,
exists,
hashBuffer,
hashStream,
writeEncryptedFile,
clearFileStorage,
getUploadedFileSize
getUploadedFileSize,
hashBase64,
hashBuffer,
hashStream
};
export default FS;

View File

@@ -82,6 +82,7 @@ const routes = {
const notebook = db.notebooks.notebook(notebookId);
const topic = notebook?.topics?.topic(topicId)?._topic;
if (!topic) return false;
nbstore.setSelectedNotebook(notebookId);
notestore.setContext({
type: "topic",
value: { id: notebookId, topic: topicId }

View File

@@ -48,7 +48,11 @@ class AppStore extends BaseStore {
isSyncEnabled = Config.get("syncEnabled", true);
isRealtimeSyncEnabled = Config.get("isRealtimeSyncEnabled", true);
syncStatus = {
key: navigator.onLine ? "synced" : "offline",
key: navigator.onLine
? Config.get("syncEnabled", true)
? "synced"
: "disabled"
: "offline",
progress: null,
type: null
};

View File

@@ -28,6 +28,7 @@ import { EV, EVENTS } from "@notesnook/core/common";
import { hashNavigate } from "../navigation";
import { logger } from "../utils/logger";
import Config from "../utils/config";
import { setDocumentTitle } from "../utils/dom";
const SESSION_STATES = {
stale: "stale",
@@ -130,6 +131,7 @@ class EditorStore extends BaseStore {
if (!note) return;
noteStore.setSelectedNote(note.id);
setDocumentTitle(note.title);
if (note.locked)
return hashNavigate(`/notes/${noteId}/unlock`, { replace: true });
@@ -222,6 +224,7 @@ class EditorStore extends BaseStore {
state.session.dateEdited = note.dateEdited;
state.session.attachmentsLength = attachments.length;
});
setDocumentTitle(note.title);
this.setSaveState(1);
} catch (err) {
@@ -248,6 +251,7 @@ class EditorStore extends BaseStore {
});
noteStore.setSelectedNote(0);
appStore.setIsEditorOpen(true);
setDocumentTitle();
};
clearSession = async (shouldNavigate = true) => {
@@ -265,6 +269,7 @@ class EditorStore extends BaseStore {
if (shouldNavigate)
hashNavigate(`/notes/create`, { replace: true, addNonce: true });
appStore.setIsEditorOpen(false);
setDocumentTitle();
};
setTitle = (noteId, title) => {

View File

@@ -17,21 +17,22 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
function hexToRGB(hex, alpha = 1) {
let parseString = hex;
if (hex.startsWith("#")) {
parseString = hex.slice(1, 7);
function hexToRGB(hex:string, alpha = 1) {
let parseString = hex;
if (hex.startsWith("#")) {
parseString = hex.slice(1, 7);
}
if (parseString.length !== 6) {
return null;
}
const r = parseInt(parseString.slice(0, 2), 16);
const g = parseInt(parseString.slice(2, 4), 16);
const b = parseInt(parseString.slice(4, 6), 16);
if (isNaN(r) || isNaN(g) || isNaN(b)) {
return null;
}
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
if (parseString.length !== 6) {
return null;
}
const r = parseInt(parseString.slice(0, 2), 16);
const g = parseInt(parseString.slice(2, 4), 16);
const b = parseInt(parseString.slice(4, 6), 16);
if (isNaN(r) || isNaN(g) || isNaN(b)) {
return null;
}
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
export { hexToRGB };
export { hexToRGB };

View File

@@ -26,8 +26,7 @@ function set<T>(key: string, value: T) {
function get<T>(key: string, def?: T): T {
const value = window.localStorage.getItem(key);
if (!value && def !== undefined) return def;
return tryParse(value);
return value ? tryParse(value) : def;
}
function clear() {

View File

@@ -17,14 +17,16 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export function removeCss(id) {
var link = document.getElementById(id);
link.remove();
export function removeCss(id: string) {
const link = document.getElementById(id);
if(link){
link.remove();
}
}
export function injectCssSrc(id, src) {
var head = document.head;
var link = document.createElement("link");
export function injectCssSrc(id: string, src: string) {
const head = document.head;
const link = document.createElement("link");
link.id = id;
link.type = "text/css";
@@ -34,32 +36,30 @@ export function injectCssSrc(id, src) {
head.appendChild(link);
}
export function injectCss(rule) {
let variableCss = document.getElementById("variables");
let head = document.getElementsByTagName("head")[0];
export function injectCss(rule: string) {
const variableCss = document.getElementById("variables");
const head = document.getElementsByTagName("head")[0];
if (variableCss) {
head.removeChild(variableCss);
}
let css = document.createElement("style");
const css = document.createElement("style");
css.type = "text/css";
css.id = "variables";
// Support for IE
if (css.styleSheet) css.styleSheet.cssText = rule;
// Support for the rest
else css.appendChild(document.createTextNode(rule));
css.appendChild(document.createTextNode(rule));
head.insertBefore(css, getRootStylesheet());
}
function getRootStylesheet() {
for (let sty of document.getElementsByTagName("style")) {
for (const sty of document.getElementsByTagName("style")) {
if (sty.innerHTML.includes("#root")) {
return sty;
}
}
return null;
}
export function changeSvgTheme(newAccent) {
var nodes = document.querySelectorAll('*[fill="#0560ff"]');
for (var n = 0; n < nodes.length; ++n)
export function changeSvgTheme(newAccent: string) {
const nodes = document.querySelectorAll('*[fill="#0560ff"]');
for (let n = 0; n < nodes.length; ++n)
nodes[n].setAttribute("fill", newAccent);
}

View File

@@ -16,37 +16,46 @@ 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/>.
*/
interface DebouncedFunction<
Args extends any[],
F extends (...args: Args) => any
> {
(this: ThisParameterType<F>, ...args: Args & Parameters<F>): void;
}
export function debounce<F extends (...args: never[]) => void>(
interface DebouncedFunctionWithId<
Args extends any[],
F extends (...args: Args) => any
> {
(this: ThisParameterType<F>, id: string, ...args: Args & Parameters<F>): void;
}
export function debounce<Args extends any[], F extends (...args: Args) => void>(
func: F,
waitFor: number
) {
): DebouncedFunction<Args, F> {
let timeout: number | null;
const debounced = (...args: Parameters<F>) => {
return (...args: Args) => {
if (timeout) clearTimeout(timeout);
timeout = setTimeout(() => func(...args), waitFor) as unknown as number;
};
return debounced;
}
export function debounceWithId<F extends (...args: never[]) => void>(
func: F,
waitFor: number
) {
export function debounceWithId<
Args extends any[],
F extends (...args: Args) => void
>(func: F, waitFor: number): DebouncedFunctionWithId<Args, F> {
let timeout: number | null;
let debounceId: string | null = null;
const debounced = (id: string, ...args: Parameters<F>) => {
return (id: string, ...args: Parameters<F>) => {
if (timeout && id === debounceId) clearTimeout(timeout);
debounceId = id;
timeout = setTimeout(() => {
func(...args);
}, waitFor) as unknown as number;
};
return debounced;
}
const DEBOUNCE_TIMEOUTS: Record<string, NodeJS.Timeout> = {};

View File

@@ -17,22 +17,13 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
function isMobile() {
var match = window.matchMedia || window.msMatchMedia;
if (match) {
var mq = match("(max-width: 480px)");
return mq.matches;
}
return false;
function isMobile(): boolean {
return window.matchMedia("(max-width: 480px)").matches;
}
function isTablet() {
var match = window.matchMedia || window.msMatchMedia;
if (match) {
var mq = match("(min-width: 480px) and (max-width: 1000px)");
return mq.matches;
}
return false;
function isTablet(): boolean {
return window.matchMedia("(min-width: 480px) and (max-width: 1000px)")
.matches;
}
export { isMobile, isTablet };

23
apps/web/src/utils/dom.ts Normal file
View File

@@ -0,0 +1,23 @@
/*
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/>.
*/
export const setDocumentTitle = (title?: string) => {
if (!title) document.title = "Notesnook";
else document.title = `${title} - Notesnook`;
};

View File

@@ -44,18 +44,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
* @param {Object} options {replacement: String | Function }
* @return {String} Sanitized filename
*/
var illegalRe = /[/?<>\\:*|"]/g;
const illegalRe = /[/?<>\\:*|"]/g;
// var controlRe = /[x00-x1f\x80-\x9f]/g;
var reservedRe = /^\.+$/;
var windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
var windowsTrailingRe = /[. ]+$/;
var whitespace = /\s+/g;
const reservedRe = /^\.+$/;
const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
const windowsTrailingRe = /[. ]+$/;
const whitespace = /\s+/g;
function sanitize(input, replacement) {
function sanitize(input: string, replacement: string) {
if (typeof input !== "string") {
throw new Error("Input must be string");
}
var sanitized = input
const sanitized = input
.replace(whitespace, replacement)
//.replace(controlRe, replacement)
.replace(illegalRe, replacement)
@@ -66,12 +66,12 @@ function sanitize(input, replacement) {
return sanitized.slice(0, 254).toLowerCase();
}
export function sanitizeFilename(input) {
var replacement = "-";
export function sanitizeFilename(input: string) {
const replacement = "-";
return sanitize(input, replacement);
}
export function formatBytes(bytes, decimals = 2) {
export function formatBytes(bytes: number, decimals = 2) {
if (bytes === 0) return "0 B";
const k = 1024;
@@ -83,7 +83,7 @@ export function formatBytes(bytes, decimals = 2) {
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
}
export function truncateFilename(filename) {
export function truncateFilename(filename: string) {
const MAX_LENGTH = 38;
if (filename.length > MAX_LENGTH) {
return (

View File

@@ -101,8 +101,13 @@ async function importNote(note: Note) {
note.notebooks = [];
const noteId = await db.notes?.add(note);
for (const notebook of notebooks) {
await db.notes?.addToNotebook(await importNotebook(notebook), noteId);
for (const nb of notebooks) {
const notebook = await importNotebook(nb);
if (!notebook.id) continue;
await db.notes?.addToNotebook(
{ id: notebook.id, topic: notebook.topic },
noteId
);
}
}
@@ -111,7 +116,9 @@ async function fileToJson<T>(file: Entry) {
return JSON.parse(text) as T;
}
async function importNotebook(notebook: Notebook) {
async function importNotebook(
notebook: Notebook
): Promise<{ id?: string; topic?: string }> {
let nb = db.notebooks?.all.find((nb) => nb.title === notebook.notebook);
if (!nb) {
const nbId = await db.notebooks?.add({
@@ -122,5 +129,5 @@ async function importNotebook(notebook: Notebook) {
}
const topic = nb?.topics.find((t: any) => t.title === notebook.topic);
return { id: nb.id, topic: topic.id };
return { id: nb?.id, topic: topic?.id };
}

View File

@@ -19,17 +19,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import EventManager from "@notesnook/core/utils/event-manager";
const GlobalKeyboard = {};
const KeyboardEventManager = new EventManager();
GlobalKeyboard.addEventListener = (name, handler) => {
KeyboardEventManager.subscribe(name, handler);
const GlobalKeyboard = {
addEventListener: (name: string, handler: (...args: any[]) => void) => {
KeyboardEventManager.subscribe(name, handler);
},
removeEventListener: (name: string, handler: (...args: any[]) => void) =>
KeyboardEventManager.unsubscribe(name, handler)
};
GlobalKeyboard.removeEventListener = (name, handler) =>
KeyboardEventManager.unsubscribe(name, handler);
// window.addEventListener("keydown", (e) => {
// // KeyboardEventManager.publish("keydown", e);
// });

View File

@@ -17,6 +17,9 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export function objectMap(obj, fn) {
export function objectMap<T, U>(
obj: Record<string, T>,
fn: (k: string, v: T, i: number) => U
) {
return Object.entries(obj).map(([k, v], i) => fn(k, v, i));
}

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