Compare commits

..

1 Commits

Author SHA1 Message Date
ammarahm-ed
55564ba900 mobile: fix color-scheme not change on toggle
system theme
2023-02-28 11:41:58 +05:00
280 changed files with 17312 additions and 14180 deletions

View File

@@ -50,18 +50,7 @@ jobs:
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.ALIAS }}
keyStorePassword: ${{ secrets.KEY_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
- name: Publish to Playstore
id: deploy
uses: r0adkll/upload-google-play@v1.1.1
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
packageName: com.streetwriters.notesnook
releaseFiles: ${{steps.sign_app.outputs.signedReleaseFile}}
track: production
status: completed
whatsNewDirectory: apps/mobile/native/android/releasenotes/
keyPassword: ${{ secrets.KEY_PASSWORD }}
- name: Build apks for Github release
run: yarn release:android
@@ -102,4 +91,15 @@ jobs:
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-arm64-v8a.apk
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-armeabi-v7a.apk
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86.apk
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86_64.apk
apps/mobile/native/android/app/build/outputs/apk/release/notesnook-x86_64.apk
- name: Publish to Playstore
id: deploy
uses: r0adkll/upload-google-play@v1.1.1
with:
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
packageName: com.streetwriters.notesnook
releaseFile: ${{steps.sign_app.outputs.signedReleaseFile}}
track: production
status: completed
whatsNewDirectory: apps/mobile/native/android/releasenotes/

View File

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

View File

@@ -50,8 +50,5 @@ jobs:
run: |
echo "NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_ACCESS_TOKEN }}" >> $GITHUB_ENV
- name: Build editor
run: npx nx build @notesnook/editor
- name: Run all @notesnook/editor tests
run: npx nx test @notesnook/editor

View File

@@ -1,50 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { Dimensions, Platform } from "react-native";
import ImageResizer from "@bam.tech/react-native-image-resizer";
import RNFetchBlob from "rn-fetch-blob";
/**
* Scale down & compress images to screen width
* for loading in editor.
* @returns
*/
export async function compressToBase64(path, type) {
const { width, scale } = Dimensions.get("window");
const response = await ImageResizer.createResizedImage(
path,
width * scale,
9999,
type,
80,
0,
undefined,
true,
{
mode: "contain",
onlyScaleDown: true
}
);
const base64 = await RNFetchBlob.fs.readFile(
Platform.OS === "ios" ? response.uri?.replace("file://", "") : response.uri,
"base64"
);
RNFetchBlob.fs.unlink(path.replace("file://", "")).catch(console.log);
RNFetchBlob.fs.unlink(response.uri.replace("file://", "")).catch(console.log);
return base64;
}

View File

@@ -28,16 +28,8 @@ 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,
global = true,
options = {
silent: false,
cache: false
}
) {
export default async function downloadAttachment(hash, global = true) {
let attachment = db.attachments.attachment(hash);
if (!attachment) {
console.log("attachment not found");
@@ -45,13 +37,11 @@ export default async function downloadAttachment(
}
let folder = {};
if (!options.cache) {
if (Platform.OS === "android") {
folder = await ScopedStorage.openDocumentTree();
if (!folder) return;
} else {
folder.uri = await Storage.checkAndCreateDir("/downloads/");
}
if (Platform.OS === "android") {
folder = await ScopedStorage.openDocumentTree();
if (!folder) return;
} else {
folder.uri = await Storage.checkAndCreateDir("/downloads/");
}
try {
@@ -64,11 +54,6 @@ 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,
@@ -78,50 +63,46 @@ export default async function downloadAttachment(
hash: attachment.metadata.hash,
hashType: attachment.metadata.hashType,
mime: attachment.metadata.type,
fileName: options.cache ? undefined : filename,
uri: options.cache ? undefined : folder.uri,
fileName: attachment.metadata.filename,
uri: folder.uri,
chunkSize: attachment.chunkSize
};
let fileUri = await Sodium.decryptFile(
key,
info,
options.cache ? "cache" : "file"
);
let fileUri = await Sodium.decryptFile(key, info, "file");
ToastEvent.show({
heading: "Download successful",
message: attachment.metadata.filename + " downloaded",
type: "success"
});
if (!options.silent) {
ToastEvent.show({
heading: "Download successful",
message: filename + " downloaded",
type: "success"
});
}
if (
attachment.dateUploaded &&
!attachment.metadata?.type?.startsWith("image")
) {
if (attachment.dateUploaded) {
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
.catch(console.log);
}
if (Platform.OS === "ios" && !options.cache) {
fileUri = folder.uri + `/${filename}`;
}
if (!options.silent) {
presentSheet({
title: "File downloaded",
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={filename} padding={12} />
});
}
if (Platform.OS === "ios") {
fileUri = folder.uri + `/${attachment.metadata.filename}`;
}
console.log("saved file uri: ", fileUri);
presentSheet({
title: "File downloaded",
paragraph: `${attachment.metadata.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}
/>
)
});
return fileUri;
} catch (e) {
console.log("download attachment error: ", e);

View File

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

View File

@@ -21,8 +21,6 @@ import { Platform } from "react-native";
import Sodium from "@ammarahmed/react-native-sodium";
import RNFetchBlob from "rn-fetch-blob";
import { cacheDir, getRandomId } from "./utils";
import { db } from "../database";
import { compressToBase64 } from "./compress";
export async function readEncrypted(filename, key, cipherData) {
let path = `${cacheDir}/${filename}`;
@@ -31,9 +29,6 @@ export async function readEncrypted(filename, key, cipherData) {
if (!exists) {
return false;
}
const attachment = db.attachments.attachment(filename);
const isPng = /(png)/g.test(attachment?.metadata.type);
const isJpeg = /(jpeg|jpg)/g.test(attachment?.metadata.type);
let output = await Sodium.decryptFile(
key,
@@ -41,20 +36,8 @@ export async function readEncrypted(filename, key, cipherData) {
...cipherData,
hash: filename
},
cipherData.outputType === "base64"
? isPng || isJpeg
? "cache"
: "base64"
: "text"
cipherData.outputType === "base64" ? "base64" : "text"
);
if (cipherData.outputType === "base64" && (isPng || isJpeg)) {
const dCachePath = `${cacheDir}/${output}`;
output = await compressToBase64(
`file://${dCachePath}`,
isPng ? "PNG" : "JPEG"
);
}
return output;
} catch (e) {
RNFetchBlob.fs.unlink(path).catch(console.log);
@@ -63,26 +46,17 @@ export async function readEncrypted(filename, key, cipherData) {
}
}
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 }) {
export async function writeEncrypted(filename, { data, type, key }) {
console.log("file input: ", { type, 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,6 +46,7 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
const encryptionProgress = useAttachmentStore(
(state) => state.encryptionProgress
);
const onPress = () => {
Actions.present(attachment, setAttachments, attachment.metadata.hash);
};
@@ -121,9 +122,7 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
</View>
</View>
{currentProgress ||
(encryptionProgress && encryptionProgress !== "0.00") ||
encryption ? (
{currentProgress || encryptionProgress || encryption ? (
<TouchableOpacity
activeOpacity={0.9}
onPress={() => {

View File

@@ -17,33 +17,70 @@ 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, { useRef, useState } from "react";
import React, { useEffect, 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";
import { presentSheet } from "../../services/event-manager";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
import {
eCloseAttachmentDialog,
eOpenAttachmentsDialog
} from "../../utils/events";
import { SIZE } from "../../utils/size";
import DialogHeader from "../dialog/dialog-header";
import { Toast } from "../toast";
import Input from "../ui/input";
import Seperator from "../ui/seperator";
import SheetWrapper from "../ui/sheet";
import Paragraph from "../ui/typography/paragraph";
import { AttachmentItem } from "./attachment-item";
import { FlatList } from "react-native-actions-sheet";
export const AttachmentDialog = ({ data }) => {
export const AttachmentDialog = () => {
const colors = useThemeStore((state) => state.colors);
const [note, setNote] = useState(data);
const [attachments, setAttachments] = useState(
data
? db.attachments.ofNote(data.id, "all")
: [...(db.attachments.all || [])]
);
const [visible, setVisible] = useState(false);
const [note, setNote] = useState(null);
const actionSheetRef = useRef();
const [attachments, setAttachments] = useState([]);
const attachmentSearchValue = useRef();
const searchTimer = useRef();
const [loading, setLoading] = useState(false);
useEffect(() => {
eSubscribeEvent(eOpenAttachmentsDialog, open);
eSubscribeEvent(eCloseAttachmentDialog, close);
return () => {
eUnSubscribeEvent(eOpenAttachmentsDialog, open);
eUnSubscribeEvent(eCloseAttachmentDialog, close);
};
}, [visible]);
const open = (data) => {
if (data?.id) {
setNote(data);
let _attachments = db.attachments.ofNote(data.id, "all");
setAttachments(_attachments);
} else {
setAttachments([...db.attachments.all]);
}
setVisible(true);
};
useEffect(() => {
if (visible) {
actionSheetRef.current?.show();
}
}, [visible]);
const close = () => {
actionSheetRef.current?.hide();
setVisible(false);
};
const onChangeText = (text) => {
attachmentSearchValue.current = text;
if (
@@ -67,101 +104,107 @@ export const AttachmentDialog = ({ data }) => {
<AttachmentItem setAttachments={setAttachments} attachment={item} />
);
return (
<View
style={{
width: "100%",
alignSelf: "center",
paddingHorizontal: 12
return !visible ? null : (
<SheetWrapper
centered={false}
fwdRef={actionSheetRef}
onClose={async () => {
setVisible(false);
}}
>
<DialogHeader
title={note ? "Attachments" : "Manage attachments"}
paragraph="Tap on an attachment to view properties"
button={{
title: "Check all",
type: "grayAccent",
loading: loading,
onPress: async () => {
setLoading(true);
for (let attachment of attachments) {
let result = await filesystem.checkAttachment(
attachment.metadata.hash
);
if (result.failed) {
db.attachments.markAsFailed(
attachment.metadata.hash,
result.failed
);
} else {
db.attachments.markAsFailed(attachment.id, null);
}
setAttachments([...db.attachments.all]);
}
setLoading(false);
}
}}
/>
<Seperator />
{!note ? (
<Input
placeholder="Filter attachments by filename, type or hash"
onChangeText={onChangeText}
onSubmit={() => {
onChangeText(attachmentSearchValue.current);
}}
/>
) : null}
<FlatList
keyboardDismissMode="none"
keyboardShouldPersistTaps="always"
maxToRenderPerBatch={10}
initialNumToRender={10}
windowSize={5}
ListEmptyComponent={
<View
style={{
height: 150,
justifyContent: "center",
alignItems: "center"
}}
>
<Icon name="attachment" size={60} color={colors.icon} />
<Paragraph>
{note ? "No attachments on this note" : "No attachments"}
</Paragraph>
</View>
}
ListFooterComponent={
<View
style={{
height: 350
}}
/>
}
data={attachments}
keyExtractor={(item) => item.id}
renderItem={renderItem}
/>
<Paragraph
color={colors.icon}
size={SIZE.xs}
<Toast context="local" />
<View
style={{
textAlign: "center",
marginTop: 10
width: "100%",
alignSelf: "center",
paddingHorizontal: 12
}}
>
<Icon name="shield-key-outline" size={SIZE.xs} color={colors.icon} />
{" "}All attachments are end-to-end encrypted.
</Paragraph>
</View>
<DialogHeader
title={note ? "Attachments" : "Manage attachments"}
paragraph="Tap on an attachment to view properties"
button={{
title: "Check all",
type: "grayAccent",
loading: loading,
onPress: async () => {
setLoading(true);
for (let attachment of attachments) {
let result = await filesystem.checkAttachment(
attachment.metadata.hash
);
if (result.failed) {
db.attachments.markAsFailed(
attachment.metadata.hash,
result.failed
);
} else {
db.attachments.markAsFailed(attachment.id, null);
}
setAttachments([...db.attachments.all]);
}
setLoading(false);
}
}}
/>
<Seperator />
{!note ? (
<Input
placeholder="Filter attachments by filename, type or hash"
onChangeText={onChangeText}
onSubmit={() => {
onChangeText(attachmentSearchValue.current);
}}
/>
) : null}
<FlatList
nestedScrollEnabled
overScrollMode="never"
scrollToOverflowEnabled={false}
keyboardDismissMode="none"
keyboardShouldPersistTaps="always"
onMomentumScrollEnd={() => {
actionSheetRef.current?.handleChildScrollEnd();
}}
ListEmptyComponent={
<View
style={{
height: 150,
justifyContent: "center",
alignItems: "center"
}}
>
<Icon name="attachment" size={60} color={colors.icon} />
<Paragraph>
{note ? "No attachments on this note" : "No attachments"}
</Paragraph>
</View>
}
ListFooterComponent={
<View
style={{
height: 350
}}
/>
}
data={attachments}
keyExtractor={(item) => item.id}
renderItem={renderItem}
/>
<Paragraph
color={colors.icon}
size={SIZE.xs}
style={{
textAlign: "center",
marginTop: 10
}}
>
<Icon name="shield-key-outline" size={SIZE.xs} color={colors.icon} />
{" "}All attachments are end-to-end encrypted.
</Paragraph>
</View>
</SheetWrapper>
);
};
AttachmentDialog.present = (note) => {
presentSheet({
component: () => <AttachmentDialog data={note} />
});
};

View File

@@ -21,6 +21,7 @@ import React from "react";
import { useNoteStore } from "../../stores/use-notes-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { AnnouncementDialog } from "../announcements";
import { AttachmentDialog } from "../attachments";
import AuthModal from "../auth/auth-modal";
import { SessionExpired } from "../auth/session-expired";
import { Dialog } from "../dialog";
@@ -33,6 +34,10 @@ import MergeConflicts from "../merge-conflicts";
import PremiumDialog from "../premium";
import { Expiring } from "../premium/expiring";
import SheetProvider from "../sheet-provider";
import { AddNotebookSheet } from "../sheets/add-notebook";
import AddToNotebookSheet from "../sheets/add-to";
import ManageTagsSheet from "../sheets/manage-tags";
import PublishNoteSheet from "../sheets/publish-note";
import RateAppSheet from "../sheets/rate-app";
import RecoveryKeySheet from "../sheets/recovery-key";
import RestoreDataSheet from "../sheets/restore-data";
@@ -46,6 +51,7 @@ const DialogProvider = () => {
<LoadingDialog />
<Dialog context="global" />
<AddTopicDialog colors={colors} />
<AddNotebookSheet colors={colors} />
<PremiumDialog colors={colors} />
<AuthModal colors={colors} />
<MergeConflicts />
@@ -55,8 +61,12 @@ const DialogProvider = () => {
<RestoreDataSheet />
<ResultDialog />
<VaultDialog colors={colors} />
<AddToNotebookSheet colors={colors} />
<RateAppSheet />
<ImagePreview />
<PublishNoteSheet />
<ManageTagsSheet />
<AttachmentDialog />
{loading ? null : <Expiring />}
<AnnouncementDialog />
<SessionExpired />

View File

@@ -35,7 +35,6 @@ import BaseDialog from "./base-dialog";
import DialogButtons from "./dialog-buttons";
import DialogHeader from "./dialog-header";
import { useCallback } from "react";
import { Button } from "../ui/button";
export const Dialog = ({ context = "global" }) => {
const colors = useThemeStore((state) => state.colors);
@@ -55,11 +54,7 @@ export const Dialog = ({ context = "global" }) => {
input: false,
inputPlaceholder: "Enter some text",
defaultValue: "",
disableBackdropClosing: false,
check: {
info: "Check",
type: "transparent"
}
disableBackdropClosing: false
});
useEffect(() => {
@@ -92,7 +87,6 @@ export const Dialog = ({ context = "global" }) => {
if (data.context !== context) return;
setDialogInfo(data);
setVisible(true);
setInputValue(data.defaultValue);
},
[context]
);
@@ -143,9 +137,6 @@ export const Dialog = ({ context = "global" }) => {
paragraph={dialogInfo.paragraph}
paragraphColor={dialogInfo.paragraphColor}
padding={12}
style={{
minHeight: 0
}}
/>
<Seperator half />
@@ -172,28 +163,6 @@ export const Dialog = ({ context = "global" }) => {
</View>
) : null}
{dialogInfo.check ? (
<>
<Button
onPress={() => {
setInputValue(!inputValue);
}}
icon={
inputValue
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
style={{
justifyContent: "flex-start"
}}
height={35}
width="100%"
title={dialogInfo.check.info}
type={inputValue ? dialogInfo.check.type : "gray"}
/>
</>
) : null}
<DialogButtons
onPressNegative={onNegativePress}
onPressPositive={dialogInfo.positivePress && onPressPositive}

View File

@@ -59,12 +59,14 @@ export class AddTopicDialog extends React.Component {
addNewTopic = async () => {
try {
this.setState({ loading: true });
if (!this.title || this.title?.trim() === "") {
ToastEvent.show({
heading: "Topic title is required",
type: "error",
context: "local"
});
this.setState({ loading: false });
return;
}
@@ -76,11 +78,10 @@ export class AddTopicDialog extends React.Component {
await db.notebooks.notebook(topic.notebookId).topics.add(topic);
}
this.setState({ loading: false });
this.close();
setTimeout(() => {
Navigation.queueRoutesForUpdate("Notebooks", "Notebook", "TopicNotes");
useMenuStore.getState().setMenuPins();
});
Navigation.queueRoutesForUpdate("Notebooks", "Notebook", "TopicNotes");
useMenuStore.getState().setMenuPins();
} catch (e) {
console.error(e);
}
@@ -176,6 +177,7 @@ export class AddTopicDialog extends React.Component {
positiveTitle={this.toEdit ? "Save" : "Add"}
onPressNegative={() => this.close()}
onPressPositive={() => this.addNewTopic()}
loading={this.state.loading}
/>
</DialogContainer>
<Toast context="local" />

View File

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

View File

@@ -30,6 +30,7 @@ import { useThemeStore } from "../../stores/use-theme-store";
import { eScrollEvent } from "../../utils/events";
import { SIZE } from "../../utils/size";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { useCallback } from "react";
import Tag from "../ui/tag";
@@ -63,15 +64,13 @@ 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.id, currentScreen.name, hide]
[currentScreen.name, hide]
);
useEffect(() => {
@@ -86,6 +85,10 @@ export const Title = () => {
}
}, [currentScreen.id, currentScreen.name]);
useEffect(() => {
titleState[currentScreen.id] = hide;
}, [currentScreen.id, hide]);
useEffect(() => {
eSubscribeEvent(eScrollEvent, onScroll);
return () => {
@@ -110,15 +113,24 @@ export const Title = () => {
<Heading
onPress={navigateToNotebook}
numberOfLines={isTopic ? 2 : 1}
size={SIZE.xl}
size={isTopic ? SIZE.md + 2 : SIZE.xl}
style={{
flexWrap: "wrap",
marginTop: Platform.OS === "ios" ? -1 : 0
}}
color={currentScreen.color || colors.heading}
>
{isTopic ? (
<Paragraph numberOfLines={1} size={SIZE.xs + 1}>
{notebook?.title}
{"\n"}
</Paragraph>
) : null}
{isTag ? (
<Heading size={SIZE.xl} color={colors.accent}>
<Heading
size={isTopic ? SIZE.md + 2 : SIZE.xl}
color={colors.accent}
>
#
</Heading>
) : null}

View File

@@ -18,26 +18,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useState } from "react";
import { Platform, View } from "react-native";
import { 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";
import {
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
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);
const [visible, setVisible] = useState(false);
const [image, setImage] = useState("");
const [loading, setLoading] = useState(false);
useEffect(() => {
eSubscribeEvent("ImagePreview", open);
@@ -47,29 +39,9 @@ const ImagePreview = () => {
};
}, []);
const open = async (image) => {
const open = (image) => {
setImage(image);
setVisible(true);
setLoading(true);
setTimeout(async () => {
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}`;
setImage("file://" + path);
setLoading(false);
}, 100);
};
const close = () => {
@@ -87,60 +59,44 @@ const ImagePreview = () => {
backgroundColor: "black"
}}
>
{loading ? (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center"
}}
>
<ProgressBarComponent
indeterminate
color={colors.accent}
borderColor="transparent"
/>
</View>
) : (
<ImageViewer
enableImageZoom={true}
renderIndicator={() => <></>}
enableSwipeDown
useNativeDriver
onSwipeDown={close}
saveToLocalByLongPress={false}
renderHeader={() => (
<View
style={{
flexDirection: "row",
width: "100%",
justifyContent: "flex-end",
alignItems: "center",
height: 80,
marginTop: 0,
paddingHorizontal: 12,
position: "absolute",
zIndex: 999,
backgroundColor: "rgba(0,0,0,0.3)",
paddingTop: Platform.OS === "android" ? 30 : 0
<ImageViewer
enableImageZoom={true}
renderIndicator={() => <></>}
enableSwipeDown
useNativeDriver
onSwipeDown={close}
saveToLocalByLongPress={false}
renderHeader={() => (
<View
style={{
flexDirection: "row",
width: "100%",
justifyContent: "flex-end",
alignItems: "center",
height: 80,
marginTop: 0,
paddingHorizontal: 12,
position: "absolute",
zIndex: 999,
backgroundColor: "rgba(0,0,0,0.3)",
paddingTop: 30
}}
>
<IconButton
name="close"
color="white"
onPress={() => {
close();
}}
>
<IconButton
name="close"
color="white"
onPress={() => {
close();
}}
/>
</View>
)}
imageUrls={[
{
url: image
}
]}
/>
)}
/>
</View>
)}
imageUrls={[
{
url: image
}
]}
/>
</View>
</BaseDialog>
)

View File

@@ -54,6 +54,7 @@ import Config from "react-native-config";
import { getGithubVersion } from "../../utils/github-version";
import notifee from "@notifee/react-native";
const Launcher = React.memo(
function Launcher() {
const colors = useThemeStore((state) => state.colors);
@@ -150,7 +151,7 @@ const Launcher = React.memo(
}, [introCompleted]);
const checkAppUpdateAvailable = async () => {
if (__DEV__ || Config.isTesting === "true") return;
if (__DEV__) return;
try {
const version =
Config.GITHUB_RELEASE === "true"

View File

@@ -24,7 +24,6 @@ import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../../e2e/test.ids";
import { db } from "../../../common/database";
import Notebook from "../../../screens/notebook";
import { TaggedNotes } from "../../../screens/notes/tagged";
import { TopicNotes } from "../../../screens/notes/topic-notes";
import { useRelationStore } from "../../../stores/use-relation-store";
@@ -40,6 +39,10 @@ import { TimeSince } from "../../ui/time-since";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
const navigateToTopic = (topic) => {
TopicNotes.navigate(topic, true);
};
function navigateToTag(item) {
const tag = db.tags.tag(item.id);
if (!tag) return;
@@ -52,29 +55,24 @@ const showActionSheet = (item) => {
function getNotebook(item) {
const isTrash = item.type === "trash";
if (isTrash) return [];
const items = [];
const notebooks = db.relations.to(item, "notebook") || [];
if (isTrash || !item.notebooks || item.notebooks.length < 1) return [];
for (let notebook of notebooks) {
if (items.length > 1) break;
items.push(notebook);
}
return item.notebooks.reduce(function (prev, curr) {
if (prev && prev.length > 0) return prev;
const topicId = curr.topics[0];
const notebook = db.notebooks?.notebook(curr.id)?.data;
if (!notebook) return [];
const topic = notebook.topics.find((t) => t.id === topicId);
if (!topic) return [];
if (item.notebooks) {
for (let nb of item.notebooks) {
if (items.length > 1) break;
const notebook = db.notebooks?.notebook(nb.id)?.data;
if (!notebook) continue;
for (let topicId of nb.topics) {
if (items.length > 1) break;
const topic = notebook.topics.find((t) => t.id === topicId);
if (!topic) continue;
items.push(topic);
return [
{
title: `${notebook?.title} ${topic?.title}`,
notebook: notebook,
topic: topic
}
}
}
return items;
];
}, []);
}
const NoteItem = ({
@@ -90,11 +88,10 @@ const NoteItem = ({
);
const compactMode = notesListMode === "compact";
const attachmentCount = db.attachments?.ofNote(item.id, "all")?.length || 0;
const _update = useRelationStore((state) => state.updater);
// eslint-disable-next-line react-hooks/exhaustive-deps
const notebooks = React.useMemo(() => getNotebook(item), [item, _update]);
const notebooks = React.useMemo(() => getNotebook(item), [item]);
const reminders = db.relations.from(item, "reminder");
const reminder = getUpcomingReminder(reminders);
const _update = useRelationStore((state) => state.updater);
const noteColor = COLORS_NOTE[item.color?.toLowerCase()];
return (
<>
@@ -115,12 +112,12 @@ const NoteItem = ({
flexWrap: "wrap"
}}
>
{notebooks?.map((item) => (
{notebooks?.map((_item) => (
<Button
title={item.title}
key={item.id}
title={_item.title}
key={_item}
height={25}
icon={item.type === "topic" ? "bookmark" : "book-outline"}
icon="book-outline"
type="grayBg"
fontSize={SIZE.xs}
iconSize={SIZE.sm}
@@ -135,13 +132,7 @@ const NoteItem = ({
paddingHorizontal: 6,
marginBottom: 5
}}
onPress={() => {
if (item.type === "topic") {
TopicNotes.navigate(item, true);
} else {
Notebook.navigate(item);
}
}}
onPress={() => navigateToTopic(_item.topic)}
/>
))}

View File

@@ -71,7 +71,6 @@ const RenderItem = ({ item, index, type, ...restArgs }) => {
};
})
.filter((t) => t !== null) || [];
return (
<Item
item={item}
@@ -104,9 +103,7 @@ const List = ({
ListHeader,
warning,
isSheet = false,
onMomentumScrollEnd,
handlers,
ScrollComponent
onMomentumScrollEnd
}) => {
const colors = useThemeStore((state) => state.colors);
const scrollRef = useRef();
@@ -161,7 +158,7 @@ const List = ({
};
const _keyExtractor = (item) => item.id || item.title;
const ListView = ScrollComponent ? ScrollComponent : FlashList;
return (
<>
<Animated.View
@@ -170,8 +167,7 @@ const List = ({
}}
entering={type === "search" ? undefined : FadeInDown}
>
<ListView
{...handlers}
<FlashList
style={styles}
ref={scrollRef}
testID={notesnook.list.id}

View File

@@ -89,7 +89,6 @@ export const ColorTags = ({ item }) => {
flexWrap: "wrap",
flexGrow: isTablet ? undefined : 1,
paddingHorizontal: 12,
paddingRight: 0,
alignItems: "center",
justifyContent: isTablet ? "center" : "space-between"
}}

View File

@@ -16,9 +16,10 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { Platform, View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import { ScrollView } from "react-native-gesture-handler";
import { db } from "../../common/database";
import { DDS } from "../../services/device-detection";
import { presentSheet } from "../../services/event-manager";
@@ -36,6 +37,7 @@ import { Items } from "./items";
import Notebooks from "./notebooks";
import { Synced } from "./synced";
import { Tags, TagStrip } from "./tags";
const Line = ({ top = 6, bottom = 6 }) => {
const colors = useThemeStore((state) => state.colors);
return (
@@ -51,11 +53,20 @@ const Line = ({ top = 6, bottom = 6 }) => {
);
};
export const Properties = ({ close = () => {}, item, buttons = [] }) => {
export const Properties = ({
close = () => {},
item,
buttons = [],
getRef
}) => {
const colors = useThemeStore((state) => state.colors);
const alias = item.alias || item.title;
const isColor = !!COLORS_NOTE[item.title];
const onScrollEnd = () => {
getRef().current?.handleChildScrollEnd();
};
if (!item || !item.id) {
return (
<Paragraph style={{ marginVertical: 10, alignSelf: "center" }}>
@@ -64,92 +75,89 @@ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
);
}
return (
<FlatList
<ScrollView
nestedScrollEnabled
onMomentumScrollEnd={onScrollEnd}
keyboardShouldPersistTaps="always"
keyboardDismissMode="none"
style={{
backgroundColor: colors.bg,
paddingHorizontal: 0,
borderBottomRightRadius: DDS.isLargeTablet() ? 10 : 1,
borderBottomLeftRadius: DDS.isLargeTablet() ? 10 : 1,
maxHeight: "100%"
}}
data={[0]}
keyExtractor={() => "properties-scroll-item"}
renderItem={() => (
<View>
<View
style={{
paddingHorizontal: 12,
marginTop: 5,
zIndex: 10
}}
>
<Heading size={SIZE.lg}>
{item.type === "tag" && !isColor ? (
<Heading size={SIZE.xl} color={colors.accent}>
#
</Heading>
) : null}
{alias}
>
<View
style={{
paddingHorizontal: 12,
marginTop: 5,
zIndex: 10
}}
>
<Heading size={SIZE.lg}>
{item.type === "tag" && !isColor ? (
<Heading size={SIZE.xl} color={colors.accent}>
#
</Heading>
{item.type === "note" ? (
<TagStrip close={close} item={item} />
) : null}
{item.type === "reminder" ? (
<ReminderTime
reminder={item}
style={{
justifyContent: "flex-start",
borderWidth: 0,
height: 30,
alignSelf: "flex-start",
backgroundColor: "transparent",
paddingHorizontal: 0
}}
fontSize={SIZE.xs + 1}
/>
) : null}
</View>
<Line top={12} />
<DateMeta item={item} />
<Line bottom={0} />
{item.type === "note" ? <Tags close={close} item={item} /> : null}
<View
style={{
paddingHorizontal: 12
}}
>
<Notebooks note={item} close={close} />
</View>
<Items
item={item}
buttons={buttons}
close={() => {
close();
setTimeout(() => {
SearchService.updateAndSearch();
}, 1000);
}}
/>
<Synced item={item} close={close} />
<DevMode item={item} />
{DDS.isTab ? (
<View
style={{
height: 20
}}
/>
) : null}
<SheetProvider context="properties" />
</View>
)}
/>
{alias}
</Heading>
{item.type === "note" ? <TagStrip close={close} item={item} /> : null}
{item.type === "reminder" ? (
<ReminderTime
reminder={item}
style={{
justifyContent: "flex-start",
borderWidth: 0,
height: 30,
alignSelf: "flex-start",
backgroundColor: "transparent",
paddingHorizontal: 0
}}
fontSize={SIZE.xs + 1}
/>
) : null}
</View>
<Line top={12} />
<DateMeta item={item} />
<Line bottom={0} />
{item.type === "note" ? <Tags close={close} item={item} /> : null}
<View
style={{
paddingHorizontal: 12
}}
>
{item.notebooks ? <Notebooks note={item} close={close} /> : null}
</View>
<Items
item={item}
buttons={buttons}
close={() => {
close();
setTimeout(() => {
SearchService.updateAndSearch();
}, 1000);
}}
/>
<Synced item={item} close={close} />
<DevMode item={item} />
{DDS.isTab ? (
<View
style={{
height: 20
}}
/>
) : null}
<SheetProvider context="properties" />
</ScrollView>
);
};
@@ -179,7 +187,6 @@ Properties.present = (item, buttons = [], isSheet) => {
"lock-unlock",
"trash",
"remove-from-topic",
"remove-from-notebook",
"history",
"read-only",
"reminders",
@@ -217,7 +224,7 @@ Properties.present = (item, buttons = [], isSheet) => {
close={() => {
close();
}}
actionSheetRef={ref}
getRef={() => ref}
item={props[0]}
buttons={props[1]}
/>

View File

@@ -18,7 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { FlatList, ScrollView, View } from "react-native";
import { View } from "react-native";
import { FlatList } from "react-native-gesture-handler";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useActions } from "../../hooks/use-actions";
import { DDS } from "../../services/device-detection";
@@ -84,9 +85,8 @@ export const Items = ({ item, buttons, close }) => {
</View>
);
const renderColumnItem = (item) => (
const renderColumnItem = ({ item }) => (
<Button
key={item.name + item.title}
buttonType={{
text: item.on
? colors.accent
@@ -108,8 +108,7 @@ export const Items = ({ item, buttons, close }) => {
/>
);
const renderTopBarItem = (item, index) => {
const isLast = index === topBarItems.length;
const renderTopBarItem = ({ item }) => {
return (
<PressableButton
onPress={item.func}
@@ -119,7 +118,7 @@ export const Items = ({ item, buttons, close }) => {
alignItems: "center",
width: topBarItemWidth,
marginBottom: 10,
marginRight: isLast ? 0 : 10,
marginRight: 10,
backgroundColor: "transparent"
}}
>
@@ -170,7 +169,7 @@ export const Items = ({ item, buttons, close }) => {
"lock-unlock",
"publish"
];
const topBarItems = data.filter(
const bottomBarItems = data.filter(
(item) => topBarItemsList.indexOf(item.id) > -1
);
@@ -179,19 +178,21 @@ export const Items = ({ item, buttons, close }) => {
);
const topBarItemWidth =
(width - (topBarItems.length * 10 + 14)) / topBarItems.length;
(width - (bottomBarItems.length * 10 + 14)) / bottomBarItems.length;
return item.type === "note" ? (
<>
<ScrollView
<FlatList
data={bottomBarItems}
keyExtractor={(item) => item.title}
horizontal
disableVirtualization={true}
style={{
paddingHorizontal: 12,
paddingTop: 12
}}
>
{topBarItems.map(renderTopBarItem)}
</ScrollView>
renderItem={renderTopBarItem}
/>
<FlatList
data={bottomGridItems}
@@ -201,8 +202,10 @@ export const Items = ({ item, buttons, close }) => {
disableVirtualization={true}
style={{
marginTop: item.type !== "note" ? 10 : 0,
paddingTop: 10,
marginLeft: 6
paddingTop: 10
}}
columnWrapperStyle={{
justifyContent: "flex-start"
}}
contentContainerStyle={{
alignSelf: "center",
@@ -213,6 +216,11 @@ export const Items = ({ item, buttons, close }) => {
/>
</>
) : (
<View data={data}>{data.map(renderColumnItem)}</View>
<FlatList
data={data}
keyExtractor={(item) => item.title}
renderItem={renderColumnItem}
disableVirtualization={true}
/>
);
};

View File

@@ -35,16 +35,8 @@ export default function Notebooks({ note, close, full }) {
const colors = useThemeStore((state) => state.colors);
const notebooks = useNotebookStore((state) => state.notebooks);
function getNotebooks(item) {
if (!item.notebooks || item.notebooks.length < 1) return [];
let filteredNotebooks = [];
const relations = db.relations.to(note, "notebook");
filteredNotebooks.push(
...relations.map((notebook) => ({
...notebook,
topics: []
}))
);
if (!item.notebooks || item.notebooks.length < 1) return filteredNotebooks;
for (let notebookReference of item.notebooks) {
let notebook = {
...(notebooks.find((item) => item.id === notebookReference.id) || {})
@@ -53,14 +45,7 @@ export default function Notebooks({ note, close, full }) {
notebook.topics = notebook.topics.filter((topic) => {
return notebookReference.topics.findIndex((t) => t === topic.id) > -1;
});
const index = filteredNotebooks.findIndex(
(item) => item.id === notebook.id
);
if (index > -1) {
filteredNotebooks[index].topics = notebook.topics;
} else {
filteredNotebooks.push(notebook);
}
filteredNotebooks.push(notebook);
}
}
return filteredNotebooks;
@@ -78,6 +63,7 @@ export default function Notebooks({ note, close, full }) {
if (!item) return;
TopicNotes.navigate(item, true);
};
const renderItem = (item) => (
<View
key={item.id}
@@ -92,8 +78,7 @@ export default function Notebooks({ note, close, full }) {
borderWidth: full ? 0 : 1,
borderColor: colors.nav,
borderRadius: 10,
backgroundColor: full ? "transparent" : colors.nav,
minHeight: 42
backgroundColor: full ? "transparent" : colors.nav
}}
>
<Icon
@@ -175,7 +160,7 @@ export default function Notebooks({ note, close, full }) {
</View>
);
return noteNotebooks.length === 0 ? null : (
return !note.notebooks || note.notebooks.length === 0 ? null : (
<View
style={{
width: "100%",

View File

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

View File

@@ -21,10 +21,11 @@ import React from "react";
import { View } from "react-native";
import { db } from "../../common/database";
import { TaggedNotes } from "../../screens/notes/tagged";
import { eSendEvent } from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store";
import { eOpenTagsDialog } from "../../utils/events";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import ManageTagsSheet from "../sheets/manage-tags";
import { Button } from "../ui/button";
import { ColorTags } from "./color-tags";
export const Tags = ({ item, close }) => {
@@ -40,14 +41,14 @@ export const Tags = ({ item, close }) => {
flexWrap: "wrap",
alignItems: "center",
paddingHorizontal: 12,
alignSelf: "center",
justifyContent: "space-between",
width: "100%"
alignSelf: "center"
}}
>
<Button
onPress={async () => {
ManageTagsSheet.present(item);
close();
await sleep(300);
eSendEvent(eOpenTagsDialog, item);
}}
buttonType={{
text: colors.accent
@@ -78,8 +79,8 @@ export const TagStrip = ({ item, close }) => {
alignItems: "center"
}}
>
{item.tags.map((tag) =>
tag ? <TagItem key={tag} tag={tag} close={close} /> : null
{item.tags.map((item) =>
item ? <TagItem key={item} tag={item} close={close} /> : null
)}
</View>
) : null;
@@ -101,6 +102,7 @@ const TagItem = ({ tag, close }) => {
marginTop: 0,
backgroundColor: "transparent"
};
return (
<Button
onPress={onPress}

View File

@@ -201,7 +201,7 @@ export const SelectionHeader = React.memo(() => {
}}
>
<Heading size={SIZE.md} color={colors.accent}>
{selectedItemsList.length}
{selectedItemsList.length + " Selected"}
</Heading>
</View>
</View>
@@ -264,29 +264,19 @@ export const SelectionHeader = React.memo(() => {
</>
)}
{screen === "TopicNotes" || screen === "Notebook" ? (
{screen === "TopicNotes" ? (
<IconButton
onPress={async () => {
if (selectedItemsList.length > 0) {
const currentScreen =
const currentTopic =
useNavigationStore.getState().currentScreen;
if (screen === "Notebook") {
for (const item of selectedItemsList) {
await db.relations.unlink(
{ type: "notebook", id: currentScreen.id },
item
);
}
} else {
await db.notes.removeFromNotebook(
{
id: currentScreen.notebookId,
topic: currentScreen.id
},
...selectedItemsList.map((item) => item.id)
);
}
await db.notes.removeFromNotebook(
{
id: currentTopic.notebookId,
topic: currentTopic.id
},
...selectedItemsList.map((item) => item.id)
);
Navigation.queueRoutesForUpdate(
"Notes",
@@ -303,9 +293,7 @@ export const SelectionHeader = React.memo(() => {
customStyle={{
marginLeft: 10
}}
tooltipText={`Remove from ${
screen === "Notebook" ? "notebook" : "topic"
}`}
tooltipText="Remove from topic"
tooltipPosition={4}
testID="select-minus"
color={colors.pri}

View File

@@ -36,7 +36,7 @@ import Paragraph from "../ui/typography/paragraph";
const SheetProvider = ({ context = "global" }) => {
const colors = useThemeStore((state) => state.colors);
const [visible, setVisible] = useState(false);
const [data, setData] = useState(null);
const [dialogData, setDialogData] = useState(null);
const actionSheetRef = useRef();
const editor = useRef({
refocus: false
@@ -52,35 +52,42 @@ const SheetProvider = ({ context = "global" }) => {
}, [close, open, visible]);
const open = useCallback(
async (payload) => {
if (!payload.context) payload.context = "global";
if (payload.context !== context) return;
setData(payload);
async (data) => {
if (!data.context) data.context = "global";
if (data.context !== context) return;
if (visible || dialogData) {
setDialogData(null);
setVisible(false);
await sleep(0);
}
setDialogData(data);
setVisible(true);
if (payload.editor) {
if (data.editor) {
editor.current.refocus = false;
if (editorState().keyboardState) {
// tiny.call(EditorWebView, tiny.cacheRange + tiny.blur);
editor.current.refocus = true;
}
}
},
[context]
[context, dialogData, visible]
);
useEffect(() => {
(async () => {
if (visible && data) {
if (data.editor) await sleep(100);
if (visible && dialogData) {
if (dialogData.editor) await sleep(100);
actionSheetRef.current?.setModalVisible(true);
return;
} else {
if (editor.current?.refocus) {
editorState().isFocused = true;
// tiny.call(EditorWebView, tiny.restoreRange + tiny.clearRange);
editor.current.refocus = false;
}
}
})();
}, [visible, data]);
}, [visible, dialogData]);
const close = useCallback(
(ctx) => {
@@ -91,31 +98,34 @@ const SheetProvider = ({ context = "global" }) => {
[context]
);
return !visible || !data ? null : (
return !visible || !dialogData ? null : (
<SheetWrapper
fwdRef={actionSheetRef}
gestureEnabled={!data?.progress && !data?.disableClosing}
closeOnTouchBackdrop={!data?.progress && !data?.disableClosing}
gestureEnabled={!dialogData?.progress && !dialogData?.disableClosing}
closeOnTouchBackdrop={
!dialogData?.progress && !dialogData?.disableClosing
}
onClose={() => {
data.onClose && data.onClose();
dialogData.onClose && dialogData.onClose();
setVisible(false);
setData(null);
setDialogData(null);
}}
bottomPadding={!data.noBottomPadding}
enableGesturesInScrollView={data.enableGesturesInScrollView}
>
<View
style={{
justifyContent: "center",
alignItems: "center",
marginBottom:
!data.progress && !data.icon && !data.title && !data.paragraph
!dialogData.progress &&
!dialogData.icon &&
!dialogData.title &&
!dialogData.paragraph
? 0
: 10,
paddingHorizontal: 12
}}
>
{data?.progress ? (
{dialogData?.progress ? (
<ActivityIndicator
style={{
marginTop: 15
@@ -125,48 +135,47 @@ const SheetProvider = ({ context = "global" }) => {
/>
) : null}
{data?.icon ? (
{dialogData?.icon ? (
<Icon
color={colors[data.iconColor] || colors.accent}
name={data.icon}
color={colors[dialogData.iconColor] || colors.accent}
name={dialogData.icon}
size={50}
/>
) : null}
{data?.title ? <Heading> {data?.title}</Heading> : null}
{dialogData?.title ? <Heading> {dialogData?.title}</Heading> : null}
{data?.paragraph ? (
{dialogData?.paragraph ? (
<Paragraph style={{ textAlign: "center" }}>
{data?.paragraph}
{dialogData?.paragraph}
</Paragraph>
) : null}
</View>
{typeof data.component === "function"
? data.component(
{typeof dialogData.component === "function"
? dialogData.component(
actionSheetRef,
() => close(context),
(data) => {
if (!data) return;
setData((prevData) => {
setDialogData((prevData) => {
return {
...prevData,
...data
};
});
},
colors
}
)
: data.component}
: dialogData.component}
<View
style={{
paddingHorizontal: 12,
marginBottom: data.valueArray ? 12 : 0
marginBottom: dialogData.valueArray ? 12 : 0
}}
>
{data.valueArray &&
data.valueArray.map((v) => (
{dialogData.valueArray &&
dialogData.valueArray.map((v) => (
<Button
title={v}
type="gray"
@@ -188,12 +197,12 @@ const SheetProvider = ({ context = "global" }) => {
paddingHorizontal: 12
}}
>
{data?.action ? (
{dialogData?.action ? (
<Button
onPress={data.action}
key={data.actionText}
title={data.actionText}
accentColor={data.iconColor || "accent"}
onPress={dialogData.action}
key={dialogData.actionText}
title={dialogData.actionText}
accentColor={dialogData.iconColor || "accent"}
accentText="light"
type="accent"
height={45}
@@ -205,14 +214,15 @@ const SheetProvider = ({ context = "global" }) => {
/>
) : null}
{data?.actionsArray &&
data?.actionsArray.map((item) => (
{dialogData?.actionsArray &&
dialogData?.actionsArray.map((item) => (
<Button
onPress={item.action}
key={item.accentText}
title={item.actionText}
icon={item.icon && item.icon}
type={item.type || "accent"}
height={50}
style={{
marginBottom: 10
}}
@@ -221,7 +231,7 @@ const SheetProvider = ({ context = "global" }) => {
/>
))}
{data?.learnMore ? (
{dialogData?.learnMore ? (
<Paragraph
style={{
alignSelf: "center",
@@ -229,7 +239,7 @@ const SheetProvider = ({ context = "global" }) => {
textDecorationLine: "underline"
}}
size={SIZE.xs}
onPress={data.learnMorePress}
onPress={dialogData.learnMorePress}
color={colors.icon}
>
<Icon
@@ -237,7 +247,7 @@ const SheetProvider = ({ context = "global" }) => {
name="information-outline"
size={SIZE.xs}
/>{" "}
{data.learnMore}
{dialogData.learnMore}
</Paragraph>
) : null}
</View>

View File

@@ -25,34 +25,41 @@ import {
TouchableOpacity,
View
} from "react-native";
import { FlatList } from "react-native-gesture-handler";
import { notesnook } from "../../../../e2e/test.ids";
import { db } from "../../../common/database";
import { DDS } from "../../../services/device-detection";
import { presentSheet, ToastEvent } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { useMenuStore } from "../../../stores/use-menu-store";
import { useRelationStore } from "../../../stores/use-relation-store";
import { DDS } from "../../../services/device-detection";
import {
eSubscribeEvent,
eUnSubscribeEvent,
ToastEvent
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { db } from "../../../common/database";
import {
eCloseAddNotebookDialog,
eOpenAddNotebookDialog
} from "../../../utils/events";
import { ph, pv, SIZE } from "../../../utils/size";
import { sleep } from "../../../utils/time";
import DialogHeader from "../../dialog/dialog-header";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import { Button } from "../../ui/button";
import DialogHeader from "../../dialog/dialog-header";
import Input from "../../ui/input";
import Seperator from "../../ui/seperator";
import { MoveNotes } from "../move-notes/movenote";
import { FlatList } from "react-native-actions-sheet";
import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet";
import { Toast } from "../../toast";
let refs = [];
export class AddNotebookSheet extends React.Component {
constructor(props) {
super(props);
refs = [];
this.state = {
notebook: props.notebook,
topics:
props.notebook?.topics?.map((item) => {
return item.title;
}) || [],
notebook: null,
visible: false,
topics: [],
description: null,
titleFocused: false,
descFocused: false,
@@ -61,14 +68,13 @@ export class AddNotebookSheet extends React.Component {
editTopic: false,
loading: false
};
this.title = props.notebook?.title;
this.description = props.notebook?.description;
this.title = null;
this.description = null;
this.listRef;
this.prevItem = null;
this.prevIndex = null;
this.currentSelectedInput = null;
this.id = props.notebook?.id;
this.id = null;
this.backPressCount = 0;
this.currentInputValue = null;
this.titleRef;
@@ -77,22 +83,57 @@ export class AddNotebookSheet extends React.Component {
this.hiddenInput = createRef();
this.topicInputRef = createRef();
this.addingTopic = false;
this.actionSheetRef = props.actionSheetRef;
}
componentWillUnmount() {
refs = [];
this.actionSheetRef = createRef();
}
componentDidMount() {
sleep(300).then(() => {
!this.state.notebook && this.titleRef?.focus();
});
eSubscribeEvent(eOpenAddNotebookDialog, this.open);
eSubscribeEvent(eCloseAddNotebookDialog, this.close);
}
close = () => {
componentWillUnmount() {
eUnSubscribeEvent(eOpenAddNotebookDialog, this.open);
eUnSubscribeEvent(eCloseAddNotebookDialog, this.close);
}
open = (notebook) => {
refs = [];
this.props.close();
if (notebook) {
let topicsList = [];
notebook.topics.forEach((item) => {
topicsList.push(item.title);
});
this.id = notebook.id;
this.title = notebook.title;
this.description = notebook.description;
this.setState({
topics: [...topicsList],
visible: true,
notebook: notebook
});
} else {
this.setState({
visible: true,
notebook: null
});
}
sleep(100).then(() => {
this.actionSheetRef.current?.show();
});
};
close = () => {
this.actionSheetRef.current?.hide();
refs = [];
this.prevIndex = null;
this.prevItem = null;
this.currentSelectedInput = null;
this.title = null;
this.description = null;
this.currentInputValue = null;
this.id = null;
};
onDelete = (index) => {
@@ -199,8 +240,15 @@ export class AddNotebookSheet extends React.Component {
"Notebooks",
"Notebook"
);
useRelationStore.getState().update();
MoveNotes.present(db.notebooks.notebook(newNotebookId).data);
this.setState({
loading: false
});
this.close();
await sleep(300);
if (!notebook) {
MoveNotes.present(db.notebooks.notebook(newNotebookId).data);
}
};
onSubmit = (forward = true) => {
@@ -217,7 +265,7 @@ export class AddNotebookSheet extends React.Component {
topics: prevTopics
});
setTimeout(() => {
this.listRef.current?.scrollToEnd?.({ animated: true });
this.listRef.scrollToEnd({ animated: true });
}, 30);
this.currentInputValue = null;
} else {
@@ -241,7 +289,7 @@ export class AddNotebookSheet extends React.Component {
if (forward) {
setTimeout(() => {
this.listRef.current?.scrollToEnd?.({ animated: true });
this.listRef.scrollToEnd({ animated: true });
}, 30);
}
}
@@ -253,138 +301,166 @@ export class AddNotebookSheet extends React.Component {
render() {
const { colors } = this.props;
const { topics, topicInputFocused, notebook } = this.state;
const { topics, visible, topicInputFocused, notebook } = this.state;
if (!visible) return null;
return (
<View
style={{
maxHeight: DDS.isTab ? "90%" : "96%",
borderRadius: DDS.isTab ? 5 : 0,
paddingHorizontal: 12
<SheetWrapper
onOpen={async () => {
this.topicsToDelete = [];
await sleep(300);
!this.state.notebook && this.titleRef?.focus();
}}
fwdRef={this.actionSheetRef}
onClose={() => {
this.close();
this.setState({
visible: false,
topics: [],
descFocused: false,
titleFocused: false,
editTopic: false,
notebook: null
});
}}
statusBarTranslucent={false}
onRequestClose={this.close}
>
<TextInput
ref={this.hiddenInput}
<View
style={{
width: 1,
height: 1,
opacity: 0,
position: "absolute"
maxHeight: DDS.isTab ? "90%" : "96%",
borderRadius: DDS.isTab ? 5 : 0,
paddingHorizontal: 12
}}
blurOnSubmit={false}
/>
<DialogHeader
title={
notebook && notebook.dateCreated ? "Edit Notebook" : "New Notebook"
}
paragraph={
notebook && notebook.dateCreated
? "You are editing " + this.title + " notebook."
: "Notebooks are the best way to organize your notes."
}
/>
<Seperator half />
<Input
fwdRef={(ref) => (this.titleRef = ref)}
testID={notesnook.ids.dialogs.notebook.inputs.title}
onChangeText={(value) => {
this.title = value;
}}
placeholder="Enter a title"
onSubmit={() => {
this.descriptionRef.focus();
}}
returnKeyLabel="Next"
returnKeyType="next"
defaultValue={notebook ? notebook.title : null}
/>
<Input
fwdRef={(ref) => (this.descriptionRef = ref)}
testID={notesnook.ids.dialogs.notebook.inputs.description}
onChangeText={(value) => {
this.description = value;
}}
placeholder="Describe your notebook."
onSubmit={() => {
this.topicInputRef.current?.focus();
}}
returnKeyLabel="Next"
returnKeyType="next"
defaultValue={notebook ? notebook.description : null}
/>
<Input
fwdRef={this.topicInputRef}
testID={notesnook.ids.dialogs.notebook.inputs.topic}
onChangeText={(value) => {
this.currentInputValue = value;
if (this.prevItem !== null) {
refs[this.prevIndex].setNativeProps({
text: value,
style: {
borderBottomColor: colors.accent
}
});
>
<TextInput
ref={this.hiddenInput}
style={{
width: 1,
height: 1,
opacity: 0,
position: "absolute"
}}
blurOnSubmit={false}
/>
<DialogHeader
title={
notebook && notebook.dateCreated
? "Edit Notebook"
: "New Notebook"
}
}}
returnKeyLabel="Done"
returnKeyType="done"
onSubmit={() => {
this.onSubmit();
}}
blurOnSubmit={false}
button={{
testID: "topic-add-button",
icon: this.state.editTopic ? "check" : "plus",
onPress: this.onSubmit,
color: topicInputFocused ? colors.accent : colors.icon
}}
placeholder="Add a topic"
/>
paragraph={
notebook && notebook.dateCreated
? "You are editing " + this.title + " notebook."
: "Notebooks are the best way to organize your notes."
}
/>
<Seperator half />
<FlatList
data={topics}
ref={(ref) => (this.listRef = ref)}
nestedScrollEnabled
keyExtractor={(item, index) => item + index.toString()}
keyboardShouldPersistTaps="always"
keyboardDismissMode="interactive"
ListFooterComponent={<View style={{ height: 50 }} />}
renderItem={({ item, index }) => (
<TopicItem
item={item}
onPress={(item, index) => {
this.prevIndex = index;
this.prevItem = item;
this.topicInputRef.current?.setNativeProps({
text: item
<Input
fwdRef={(ref) => (this.titleRef = ref)}
testID={notesnook.ids.dialogs.notebook.inputs.title}
onChangeText={(value) => {
this.title = value;
}}
placeholder="Enter a title"
onSubmit={() => {
this.descriptionRef.focus();
}}
returnKeyLabel="Next"
returnKeyType="next"
defaultValue={notebook ? notebook.title : null}
/>
<Input
fwdRef={(ref) => (this.descriptionRef = ref)}
testID={notesnook.ids.dialogs.notebook.inputs.description}
onChangeText={(value) => {
this.description = value;
}}
placeholder="Describe your notebook."
onSubmit={() => {
this.topicInputRef.current?.focus();
}}
returnKeyLabel="Next"
returnKeyType="next"
defaultValue={notebook ? notebook.description : null}
/>
<Input
fwdRef={this.topicInputRef}
testID={notesnook.ids.dialogs.notebook.inputs.topic}
onChangeText={(value) => {
this.currentInputValue = value;
if (this.prevItem !== null) {
refs[this.prevIndex].setNativeProps({
text: value,
style: {
borderBottomColor: colors.accent
}
});
this.topicInputRef.current?.focus();
this.currentInputValue = item;
this.setState({
editTopic: true
});
}}
onDelete={this.onDelete}
index={index}
colors={colors}
/>
)}
/>
<Seperator />
<Button
width="100%"
fontSize={SIZE.md}
title={
notebook && notebook.dateCreated
? "Save changes"
: "Create notebook"
}
type="accent"
onPress={this.addNewNotebook}
/>
{/*
}
}}
returnKeyLabel="Done"
returnKeyType="done"
onSubmit={() => {
this.onSubmit();
}}
blurOnSubmit={false}
button={{
testID: "topic-add-button",
icon: this.state.editTopic ? "check" : "plus",
onPress: this.onSubmit,
color: topicInputFocused ? colors.accent : colors.icon
}}
placeholder="Add a topic"
/>
<FlatList
data={topics}
ref={(ref) => (this.listRef = ref)}
nestedScrollEnabled
keyExtractor={(item, index) => item + index.toString()}
onMomentumScrollEnd={() => {
this.actionSheetRef.current?.handleChildScrollEnd();
}}
keyboardShouldPersistTaps="always"
keyboardDismissMode="interactive"
ListFooterComponent={<View style={{ height: 50 }} />}
renderItem={({ item, index }) => (
<TopicItem
item={item}
onPress={(item, index) => {
this.prevIndex = index;
this.prevItem = item;
this.topicInputRef.current?.setNativeProps({
text: item
});
this.topicInputRef.current?.focus();
this.currentInputValue = item;
this.setState({
editTopic: true
});
}}
onDelete={this.onDelete}
index={index}
colors={colors}
/>
)}
/>
<Seperator />
<Button
width="100%"
height={50}
fontSize={SIZE.md}
title={
notebook && notebook.dateCreated
? "Save changes"
: "Create notebook"
}
type="accent"
onPress={this.addNewNotebook}
/>
{/*
{Platform.OS === 'ios' && (
<View
style={{
@@ -392,24 +468,14 @@ export class AddNotebookSheet extends React.Component {
}}
/>
)} */}
</View>
</View>
<Toast context="local" />
</SheetWrapper>
);
}
}
AddNotebookSheet.present = (notebook) => {
presentSheet({
component: (ref, close, _update, colors) => (
<AddNotebookSheet
actionSheetRef={ref}
notebook={notebook}
close={close}
colors={colors}
/>
)
});
};
const TopicItem = ({ item, index, colors, onPress, onDelete }) => {
const topicRef = (ref) => (refs[index] = ref);

View File

@@ -20,9 +20,13 @@ 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

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useCallback, useEffect, useRef, useState } from "react";
import { FlatList } from "react-native-actions-sheet";
import { FlatList } from "react-native-gesture-handler";
import { db } from "../../../common/database";
import { ListHeaderInputItem } from "./list-header-item.js";
@@ -27,7 +27,6 @@ export const FilteredList = ({
itemType,
onAddItem,
hasHeaderSearch,
listRef,
...restProps
}) => {
const [filtered, setFiltered] = useState(data);
@@ -57,7 +56,6 @@ export const FilteredList = ({
<FlatList
{...restProps}
data={filtered}
ref={listRef}
ListHeaderComponent={
hasHeaderSearch ? (
<ListHeaderInputItem
@@ -71,6 +69,7 @@ export const FilteredList = ({
}
keyboardShouldPersistTaps="always"
keyboardDismissMode="none"
nestedScrollEnabled
/>
);
};

View File

@@ -17,39 +17,90 @@ 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 } from "react";
import React, {
createRef,
useCallback,
useEffect,
useMemo,
useState
} from "react";
import { Keyboard, TouchableOpacity, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database";
import { presentSheet, ToastEvent } from "../../../services/event-manager";
import {
eSubscribeEvent,
eUnSubscribeEvent,
ToastEvent
} from "../../../services/event-manager";
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 { eOpenMoveNoteDialog } from "../../../utils/events";
import { Dialog } from "../../dialog";
import DialogHeader from "../../dialog/dialog-header";
import { presentDialog } from "../../dialog/functions";
import { Button } from "../../ui/button";
import SheetWrapper from "../../ui/sheet";
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 actionSheetRef = createRef();
const AddToNotebookSheet = () => {
const [visible, setVisible] = useState(false);
const [note, setNote] = useState(null);
function open(note) {
setNote(note);
setVisible(true);
actionSheetRef.current?.setModalVisible(true);
}
useEffect(() => {
eSubscribeEvent(eOpenMoveNoteDialog, open);
return () => {
eUnSubscribeEvent(eOpenMoveNoteDialog, open);
};
}, []);
const _onClose = () => {
setVisible(false);
setNote(null);
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Notebooks",
"Notebook"
);
};
return !visible ? null : (
<SheetWrapper fwdRef={actionSheetRef} onClose={_onClose}>
<MoveNoteComponent note={note} />
</SheetWrapper>
);
};
export default AddToNotebookSheet;
const MoveNoteComponent = ({ note }) => {
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 [edited, setEdited] = useState(false);
const selectedItemsList = useSelectionStore(
(state) => state.selectedItemsList
);
const setNotebooks = useNotebookStore((state) => state.setNotebooks);
const multiSelect = useItemSelectionStore((state) => state.multiSelect);
const [itemState, setItemState] = useState({});
const onAddNotebook = async (title) => {
if (!title || title.trim().length === 0) {
@@ -108,13 +159,13 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
(item) => {
switch (item.type) {
case "notebook": {
const notes = db.relations.from(item, "note");
if (notes.length === 0) return 0;
const noteIds = [];
for (let topic of item.topics) {
noteIds.push(...(db.notes?.topicReferences.get(topic.id) || []));
}
let count = 0;
selectedItemsList.forEach((item) =>
notes.findIndex((note) => note.id === item.id) > -1
? count++
: undefined
noteIds.indexOf(item.id) > -1 ? count++ : undefined
);
return count;
}
@@ -133,55 +184,42 @@ 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) => {
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
setItemState(() => {
const itemState = {};
const notebooks = db.notebooks.all;
for (let notebook of notebooks) {
itemState[notebook.id] = state
? state
: areAllSelectedItemsInTopic(topic, selectedItemsList) &&
getSelectedNotesCountInItem(topic, selectedItemsList)
: areAllSelectedItemsInAllTopics(notebook, selectedItemsList) &&
getSelectedNotesCountInItem(notebook, selectedItemsList) > 0
? "selected"
: getSelectedNotesCountInItem(topic, selectedItemsList) > 0
: getSelectedNotesCountInItem(notebook, selectedItemsList) > 0
? "intermediate"
: "deselected";
if (itemState[topic.id] === "selected") {
count++;
contextValue.select(topic);
} else {
contextValue.deselect(topic);
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 (count > 1) {
useItemSelectionStore.getState().setMultiSelect(true);
} else {
useItemSelectionStore.getState().setMultiSelect(false);
}
useItemSelectionStore.getState().setItemState(itemState);
return itemState;
});
},
[contextValue, getSelectedNotesCountInItem, selectedItemsList]
);
@@ -193,11 +231,11 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
}
};
function areAllSelectedItemsInNotebook(notebook, items) {
const notes = db.relations.from(notebook, "note");
if (notes.length === 0) return false;
function areAllSelectedItemsInAllTopics(notebook, items) {
return items.every((item) => {
return notes.find((note) => note.id === item.id);
return notebook.topics.every((topic) => {
return db.notes.topicReferences.get(topic.id).indexOf(item.id) > -1;
});
});
}
@@ -208,26 +246,51 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
}
const updateItemState = useCallback(function (item, state) {
const itemState = useItemSelectionStore.getState().itemState;
const mergeState = {
[item.id]: state
};
useItemSelectionStore.getState().setItemState({
...itemState,
...mergeState
setItemState((itemState) => {
const mergeState = {
[item.id]: state
};
const notebooks = db.notebooks.all;
const notebook =
item.type === "notebook"
? item
: notebooks.find((n) => n.id === item.notebookId);
const intermediate = notebook.topics.some((topic) => {
return topic.id === item.id
? state === "selected"
: itemState[topic.id] === "selected";
});
if (intermediate) mergeState[notebook.id] = "intermediate";
const selected = notebook.topics.every((topic) => {
return topic.id === item.id
? state === "selected"
: itemState[topic.id] === "selected";
});
if (selected) mergeState[notebook.id] = "selected";
if (!selected && !intermediate) mergeState[notebook.id] = "deselected";
return {
...itemState,
...mergeState
};
});
}, []);
const contextValue = useMemo(
() => ({
enabled: multiSelect,
toggleSelection: (item) => {
const itemState = useItemSelectionStore.getState().itemState;
if (itemState[item.id] === "selected") {
updateItemState(item, "deselected");
} else {
updateItemState(item, "selected");
}
setItemState((itemState) => {
if (itemState[item.id] === "selected") {
updateItemState(item, "deselected");
} else {
updateItemState(item, "selected");
}
return itemState;
});
},
setMultiSelect: setMultiSelect,
deselect: (item) => {
updateItemState(item, "deselected");
},
@@ -238,7 +301,7 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
resetItemState(state);
}
}),
[resetItemState, updateItemState]
[multiSelect, resetItemState, updateItemState]
);
const getItemFromId = (id) => {
@@ -251,40 +314,28 @@ 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 (item.type === "notebook") continue;
const noteIds = selectedItemsList.map((n) => n.id);
if (itemState[id] === "selected") {
if (item.type === "notebook") {
for (let noteId of noteIds) {
db.relations.add(item, { id: noteId, type: "note" });
}
} else {
await db.notes.addToNotebook(
{
topic: item.id,
id: item.notebookId,
rebuildCache: true
},
...noteIds
);
}
await db.notes.addToNotebook(
{
topic: item.id,
id: item.notebookId,
rebuildCache: true
},
...noteIds
);
} else if (itemState[id] === "deselected") {
if (item.type === "notebook") {
for (let noteId of noteIds) {
db.relations.unlink(item, { id: noteId, type: "note" });
}
} else {
await db.notes.removeFromNotebook(
{
id: item.notebookId,
topic: item.id,
rebuildCache: true
},
...noteIds
);
}
await db.notes.removeFromNotebook(
{
id: item.notebookId,
topic: item.id,
rebuildCache: true
},
...noteIds
);
}
}
Navigation.queueRoutesForUpdate(
@@ -292,8 +343,7 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Notebook"
"TopicNotes"
);
setNotebooks();
SearchService.updateAndSearch();
@@ -347,32 +397,34 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
/>
</View>
<View
style={{
paddingHorizontal: 12
}}
>
<Button
title="Reset selection"
height={30}
{multiSelect ? (
<View
style={{
alignSelf: "flex-start",
paddingHorizontal: 0,
width: "100%",
marginTop: 6
paddingHorizontal: 12
}}
type="grayAccent"
onPress={() => {
resetItemState();
}}
/>
</View>
>
<Button
title="Reset selection"
height={30}
style={{
alignSelf: "flex-start",
paddingHorizontal: 0
}}
onPress={() => {
resetItemState();
setMultiSelect(false);
}}
/>
</View>
) : null}
<SelectionProvider value={contextValue}>
<FilteredList
onMomentumScrollEnd={() => {
actionSheetRef.current?.handleChildScrollEnd();
}}
style={{
paddingHorizontal: 12,
maxHeight: dimensions.height * 0.85
paddingHorizontal: 12
}}
ListEmptyComponent={
notebooks.length > 0 ? null : (
@@ -398,8 +450,12 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
item={item}
key={item.id}
index={index}
hasNotes={getSelectedNotesCountInItem(item) > 0}
sheetRef={actionSheetRef}
intermediate={itemState[item.id] === "intermediate"}
removed={
itemState[item.id] === "deselected" &&
getSelectedNotesCountInItem(item) > 0
}
isSelected={itemState[item.id] === "selected"}
infoText={
<>
{item.topics.length === 1
@@ -409,18 +465,25 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
}
getListItems={getItemsForItem}
getSublistItemProps={(topic) => ({
hasNotes: getSelectedNotesCountInItem(topic) > 0,
selected: itemState[topic.id] === "selected",
intermediate: itemState[topic.id] === "intermediate",
isSelected: itemState[topic.id] === "selected",
removed:
itemState[topic.id] === "deselected" &&
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");
contextValue.select(item);
updateItemState(
notebooks.find((n) => n.id === item.notebookId),
"intermediate"
);
} else {
contextValue.deselect(item);
}
@@ -445,35 +508,22 @@ 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: 20 }} />}
ListFooterComponent={
<View
style={{
height: 200
}}
/>
}
/>
</SelectionProvider>
</View>
</>
);
};
MoveNoteSheet.present = (note) => {
presentSheet({
component: (ref) => <MoveNoteSheet actionSheetRef={ref} note={note} />,
enableGesturesInScrollView: false,
noBottomPadding: true
});
};
export default MoveNoteSheet;

View File

@@ -17,10 +17,8 @@ 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, { useEffect, useState } from "react";
import React, { 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";
@@ -29,78 +27,13 @@ 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 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 = ({
const _ListItem = ({
item,
index,
icon,
infoText,
intermediate,
hasSubList,
onPress,
onScrollEnd,
@@ -110,49 +43,29 @@ export const ListItem = ({
sublistItemType,
onAddItem,
getSublistItemProps,
removed,
isSelected,
hasHeaderSearch,
onAddSublistItem,
hasNotes,
onChange,
sheetRef
onAddSublistItem
}) => {
const { toggleSelection } = useSelectionContext();
const multiSelect = useItemSelectionStore((state) => state.multiSelect);
const [showSelectedIndicator, setShowSelectedIndicator] = useState(false);
const { enabled, toggleSelection, setMultiSelect, select, deselect } =
useSelectionContext();
const colors = useThemeStore((state) => state.colors);
const [expanded, setExpanded] = useState(false);
function selectItem() {
const currentState = isSelected;
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++;
}
if (item.type === "notebook") {
item.topics.forEach((item) => {
if (currentState) {
deselect(item);
} else {
select(item);
}
});
}
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={{
@@ -164,12 +77,12 @@ export const ListItem = ({
<PressableButton
onPress={() => {
if (hasSubList) return setExpanded(!expanded);
if (multiSelect) return selectItem();
if (enabled) return selectItem();
onPress?.(item);
}}
type={type}
onLongPress={() => {
useItemSelectionStore.getState().setMultiSelect(true);
setMultiSelect(true);
selectItem();
}}
customStyle={{
@@ -194,12 +107,46 @@ export const ListItem = ({
alignItems: "center"
}}
>
<SelectionIndicator
hasNotes={hasNotes}
onPress={onPress}
item={item}
onChange={onChange}
selectItem={selectItem}
<IconButton
size={22}
customStyle={{
marginRight: 5,
width: 23,
height: 23
}}
color={
removed
? colors.red
: intermediate || isSelected
? colors.accent
: colors.icon
}
onPress={() => {
if (item.type === "notebook") {
setMultiSelect(true);
}
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"
}
/>
<View>
{hasSubList && expanded ? (
@@ -218,27 +165,14 @@ export const ListItem = ({
<View
style={{
flexDirection: "row",
alignItems: "center"
flexDirection: "row"
}}
>
{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.pri}
color={colors}
size={SIZE.xl}
onPress={() => {
onAddSublistItem(item);
@@ -271,7 +205,7 @@ export const ListItem = ({
style={{
width: "95%",
alignSelf: "flex-end",
maxHeight: 250
maxHeight: 500
}}
itemType={sublistItemType}
hasHeaderSearch={hasHeaderSearch}
@@ -280,7 +214,6 @@ export const ListItem = ({
item={item}
{...getSublistItemProps(item)}
index={index}
onChange={onChangeSubItem}
onScrollEnd={onScrollEnd}
/>
)}
@@ -290,3 +223,11 @@ export 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

@@ -1,42 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 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

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { RefObject, useRef, useState } from "react";
import { TextInput, View } from "react-native";
import { ActionSheetRef } from "react-native-actions-sheet";
import ActionSheet from "react-native-actions-sheet";
import { db } from "../../../common/database";
import {
eSendEvent,
@@ -31,7 +31,7 @@ import { Button } from "../../ui/button";
import Input from "../../ui/input";
type ChangeEmailProps = {
actionSheetRef: RefObject<ActionSheetRef>;
actionSheetRef: RefObject<ActionSheet>;
close?: () => void;
update?: (options: PresentSheetOptions) => void;
};

View File

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

View File

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

View File

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

View File

@@ -17,32 +17,51 @@ 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, useRef, useState } from "react";
import { View } from "react-native";
import { ScrollView } from "react-native-actions-sheet";
import React, { useEffect, useRef, useState } from "react";
import { ScrollView, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database";
import { presentSheet, ToastEvent } from "../../../services/event-manager";
import {
eSubscribeEvent,
eUnSubscribeEvent,
ToastEvent
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { useTagStore } from "../../../stores/use-tag-store";
import { useThemeStore } from "../../../stores/use-theme-store";
import { eCloseTagsDialog, eOpenTagsDialog } from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import { sleep } from "../../../utils/time";
import Input from "../../ui/input";
import { PressableButton } from "../../ui/pressable";
import SheetWrapper from "../../ui/sheet";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
const ManageTagsSheet = (props) => {
import { useCallback } from "react";
const ManageTagsSheet = () => {
const colors = useThemeStore((state) => state.colors);
const [note, setNote] = useState(props.note);
const [visible, setVisible] = useState(false);
const [note, setNote] = useState(null);
const allTags = useTagStore((state) => state.tags);
const [tags, setTags] = useState([]);
const [query, setQuery] = useState(null);
const inputRef = useRef();
const [focus, setFocus] = useState(false);
const actionSheetRef = useRef();
useEffect(() => {
sortTags();
}, [allTags, note, query, sortTags]);
eSubscribeEvent(eOpenTagsDialog, open);
eSubscribeEvent(eCloseTagsDialog, close);
return () => {
eUnSubscribeEvent(eOpenTagsDialog, open);
eUnSubscribeEvent(eCloseTagsDialog, close);
};
}, [open]);
useEffect(() => {
if (visible) {
sortTags();
}
}, [allTags, note, query, sortTags, visible]);
const sortTags = useCallback(() => {
let _tags = [...allTags];
@@ -69,9 +88,26 @@ const ManageTagsSheet = (props) => {
setTags(combinedTags);
}, [allTags, note, query]);
const open = useCallback(
(item) => {
setNote(item);
useTagStore.getState().setTags();
sortTags();
setVisible(true);
},
[sortTags]
);
useEffect(() => {
useTagStore.getState().setTags();
}, []);
if (visible) {
actionSheetRef.current?.show();
}
}, [visible]);
const close = () => {
setQuery(null);
actionSheetRef.current?.hide();
};
const onSubmit = async () => {
let _query = query;
@@ -114,95 +150,104 @@ const ManageTagsSheet = (props) => {
);
};
return (
<View
style={{
width: "100%",
alignSelf: "center",
paddingHorizontal: 12,
minHeight: focus ? "100%" : "60%"
return !visible ? null : (
<SheetWrapper
centered={false}
fwdRef={actionSheetRef}
onOpen={async () => {
await sleep(300);
inputRef.current?.focus();
}}
onClose={async () => {
setQuery(null);
setVisible(false);
}}
>
<Input
button={{
icon: "magnify",
color: colors.accent,
size: SIZE.lg
<View
style={{
width: "100%",
alignSelf: "center",
paddingHorizontal: 12,
minHeight: "60%"
}}
testID="tag-input"
fwdRef={inputRef}
autoCapitalize="none"
onChangeText={(v) => {
setQuery(db.tags.sanitize(v));
}}
onFocusInput={() => {
setFocus(true);
}}
onBlurInput={() => {
setFocus(false);
}}
onSubmit={onSubmit}
placeholder="Search or add a tag"
/>
<ScrollView
overScrollMode="never"
scrollToOverflowEnabled={false}
keyboardDismissMode="none"
keyboardShouldPersistTaps="always"
>
{query && query !== tags[0]?.title ? (
<PressableButton
key={"query_item"}
customStyle={{
flexDirection: "row",
marginVertical: 5,
justifyContent: "space-between",
padding: 12
}}
onPress={onSubmit}
type="accent"
>
<Heading size={SIZE.sm} color={colors.light}>
Add {'"' + "#" + query + '"'}
</Heading>
<Icon name="plus" color={colors.light} size={SIZE.lg} />
</PressableButton>
) : null}
{!allTags || allTags.length === 0 ? (
<View
style={{
width: "100%",
height: 200,
justifyContent: "center",
alignItems: "center"
}}
>
<Heading size={50} color={colors.icon}>
#
</Heading>
<Paragraph textBreakStrategy="balanced" color={colors.icon}>
You do not have any tags.
</Paragraph>
</View>
) : null}
<Input
button={{
icon: "magnify",
color: colors.accent,
size: SIZE.lg
}}
testID="tag-input"
fwdRef={inputRef}
autoCapitalize="none"
onChangeText={(v) => {
setQuery(db.tags.sanitize(v));
}}
onSubmit={onSubmit}
height={50}
placeholder="Search or add a tag"
/>
{tags.map((item) => (
<TagItem key={item.title} tag={item} note={note} setNote={setNote} />
))}
</ScrollView>
</View>
<ScrollView
nestedScrollEnabled
overScrollMode="never"
scrollToOverflowEnabled={false}
keyboardDismissMode="none"
keyboardShouldPersistTaps="always"
onMomentumScrollEnd={() => {
actionSheetRef.current?.handleChildScrollEnd();
}}
>
{query && query !== tags[0]?.title ? (
<PressableButton
key={"query_item"}
customStyle={{
flexDirection: "row",
marginVertical: 5,
justifyContent: "space-between",
padding: 12
}}
onPress={onSubmit}
type="accent"
>
<Heading size={SIZE.sm} color={colors.light}>
Add {'"' + "#" + query + '"'}
</Heading>
<Icon name="plus" color={colors.light} size={SIZE.lg} />
</PressableButton>
) : null}
{!allTags || allTags.length === 0 ? (
<View
style={{
width: "100%",
height: 200,
justifyContent: "center",
alignItems: "center"
}}
>
<Heading size={50} color={colors.icon}>
#
</Heading>
<Paragraph textBreakStrategy="balanced" color={colors.icon}>
You do not have any tags.
</Paragraph>
</View>
) : null}
{tags.map((item) => (
<TagItem
key={item.title}
tag={item}
note={note}
setNote={setNote}
/>
))}
</ScrollView>
</View>
</SheetWrapper>
);
};
ManageTagsSheet.present = (note) => {
presentSheet({
component: (ref) => {
return <ManageTagsSheet actionSheetRef={ref} note={note} />;
}
});
};
export default ManageTagsSheet;
const TagItem = ({ tag, note, setNote }) => {

View File

@@ -17,11 +17,11 @@ 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 { FlashList } from "@shopify/flash-list";
import { NotebookType, NoteType, TopicType } from "app/utils/types";
import React, { RefObject, useState } from "react";
import { Platform, useWindowDimensions, View } from "react-native";
import { ActionSheetRef } from "react-native-actions-sheet";
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
import ActionSheet from "react-native-actions-sheet";
import { db } from "../../../common/database";
import {
eSendEvent,
@@ -58,7 +58,7 @@ export const MoveNotes = ({
}: {
notebook: NotebookType;
selectedTopic?: TopicType;
fwdRef: RefObject<ActionSheetRef>;
fwdRef: RefObject<ActionSheet>;
}) => {
const colors = useThemeStore((state) => state.colors);
const [currentNotebook, setCurrentNotebook] = useState(notebook);
@@ -277,6 +277,10 @@ export const MoveNotes = ({
)}
<FlashList
nestedScrollEnabled
onMomentumScrollEnd={() => {
fwdRef.current?.handleChildScrollEnd();
}}
ListEmptyComponent={
<View
style={{
@@ -341,7 +345,7 @@ export const MoveNotes = ({
MoveNotes.present = (notebook: NotebookType, topic: TopicType) => {
presentSheet({
component: (ref: RefObject<ActionSheetRef>) => (
component: (ref: RefObject<ActionSheet>) => (
<MoveNotes fwdRef={ref} notebook={notebook} selectedTopic={topic} />
)
});

View File

@@ -93,13 +93,7 @@ NewFeature.present = () => {
});
return;
}
if (!version || version === getVersion()) {
SettingsService.set({
version: getVersion()
});
return false;
}
if (version && version === getVersion()) return false;
SettingsService.set({
version: getVersion()
});

View File

@@ -18,14 +18,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Clipboard from "@react-native-clipboard/clipboard";
import React, { useRef, useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import { ActivityIndicator, TouchableOpacity, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database";
import { presentSheet, ToastEvent } from "../../../services/event-manager";
import {
eSubscribeEvent,
eUnSubscribeEvent,
ToastEvent
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { useAttachmentStore } from "../../../stores/use-attachment-store";
import { useThemeStore } from "../../../stores/use-theme-store";
import {
eClosePublishNoteDialog,
eOpenPublishNoteDialog
} from "../../../utils/events";
import { openLinkInBrowser } from "../../../utils/functions";
import { SIZE } from "../../../utils/size";
import DialogHeader from "../../dialog/dialog-header";
@@ -33,34 +41,66 @@ import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import Input from "../../ui/input";
import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
const PublishNoteSheet = ({ note: item, update }) => {
let passwordValue = null;
const PublishNoteSheet = () => {
const colors = useThemeStore((state) => state.colors);
const [visible, setVisible] = useState(false);
const actionSheetRef = useRef();
const loading = useAttachmentStore((state) => state.loading);
const [selfDestruct, setSelfDestruct] = useState(false);
const [isLocked, setIsLocked] = useState(false);
const [note, setNote] = useState(item);
const [note, setNote] = useState(null);
const [publishing, setPublishing] = useState(false);
const publishUrl =
note &&
`https://monograph.notesnook.com/${db?.monographs.monograph(note?.id)}`;
const isPublished = note && db?.monographs.isPublished(note?.id);
const pwdInput = useRef();
const passwordValue = useRef();
useEffect(() => {
eSubscribeEvent(eOpenPublishNoteDialog, open);
eSubscribeEvent(eClosePublishNoteDialog, close);
return () => {
eUnSubscribeEvent(eOpenPublishNoteDialog, open);
eUnSubscribeEvent(eClosePublishNoteDialog, close);
};
}, []);
const open = (item) => {
if (!item) return;
setNote(item);
setPublishing(false);
setSelfDestruct(false);
setIsLocked(false);
setVisible(true);
passwordValue = null;
};
useEffect(() => {
if (visible) {
actionSheetRef.current?.show();
}
}, [visible]);
const close = () => {
passwordValue = null;
actionSheetRef.current?.hide();
};
const publishNote = async () => {
if (publishing) return;
setPublishLoading(true);
setPublishing(true);
try {
if (note?.id) {
if (isLocked && !passwordValue) return;
await db.monographs.publish(note.id, {
selfDestruct: selfDestruct,
password: isLocked && passwordValue.current
password: isLocked && passwordValue
});
setNote(db.notes.note(note.id)?.data);
Navigation.queueRoutesForUpdate(
@@ -70,7 +110,6 @@ const PublishNoteSheet = ({ note: item, update }) => {
"TaggedNotes",
"TopicNotes"
);
setPublishLoading(false);
}
} catch (e) {
ToastEvent.show({
@@ -81,16 +120,12 @@ const PublishNoteSheet = ({ note: item, update }) => {
});
}
setPublishLoading(false);
};
const setPublishLoading = (value) => {
setPublishing(value);
setPublishing(false);
};
const deletePublishedNote = async () => {
if (publishing) return;
setPublishLoading(true);
setPublishing(true);
try {
if (note?.id) {
await db.monographs.unpublish(note.id);
@@ -102,7 +137,6 @@ const PublishNoteSheet = ({ note: item, update }) => {
"TaggedNotes",
"TopicNotes"
);
setPublishLoading(false);
}
} catch (e) {
ToastEvent.show({
@@ -113,281 +147,267 @@ const PublishNoteSheet = ({ note: item, update }) => {
});
}
actionSheetRef.current?.hide();
setPublishLoading(false);
setPublishing(false);
};
return (
<View
style={{
width: "100%",
alignSelf: "center",
paddingHorizontal: 12
return !visible ? null : (
<SheetWrapper
centered={false}
fwdRef={actionSheetRef}
closeOnTouchBackdrop={!publishing}
gestureEnabled={!publishing}
onClose={async () => {
passwordValue = null;
setVisible(false);
}}
>
<DialogHeader
title={note?.title}
paragraph={`Anyone with the link${
isLocked ? " and password" : ""
} of the published note can view it.`}
/>
<View
style={{
width: "100%",
alignSelf: "center",
paddingHorizontal: 12
}}
>
<DialogHeader
title={note?.title}
paragraph={`Anyone with the link${
isLocked ? " and password" : ""
} of the published note can view it.`}
/>
{publishing ? (
<View
style={{
justifyContent: "center",
alignContent: "center",
height: 150,
width: "100%"
}}
>
<ActivityIndicator size={25} color={colors.accent} />
<Paragraph
{publishing ? (
<View
style={{
textAlign: "center"
justifyContent: "center",
alignContent: "center",
height: 150,
width: "100%"
}}
>
Please wait...
{loading && loading.current && loading.total
? `\nDownloading attachments (${
loading?.current / loading?.total
})`
: ""}
</Paragraph>
</View>
) : (
<>
{isPublished && (
<View
<ActivityIndicator size={25} color={colors.accent} />
<Paragraph
style={{
textAlign: "center"
}}
>
Please wait...
{loading && loading.current && loading.total
? `\nDownloading attachments (${
loading?.current / loading?.total
})`
: ""}
</Paragraph>
</View>
) : (
<>
{isPublished && (
<View
style={{
flexDirection: "row",
alignItems: "center",
marginTop: 15,
backgroundColor: colors.nav,
padding: 12,
borderRadius: 5
}}
>
<View
style={{
width: "100%",
flexShrink: 1
}}
>
<Heading size={SIZE.sm}>Published at:</Heading>
<Paragraph size={SIZE.xs} numberOfLines={1}>
{publishUrl}
</Paragraph>
<Paragraph
onPress={async () => {
try {
await openLinkInBrowser(publishUrl, colors.accent);
} catch (e) {
console.error(e);
}
}}
size={SIZE.xs}
style={{
marginTop: 5,
color: colors.pri
}}
>
<Icon color={colors.accent} name="open-in-new" /> Open in
browser
</Paragraph>
</View>
<IconButton
onPress={() => {
Clipboard.setString(publishUrl);
ToastEvent.show({
heading: "Note publish url copied",
type: "success",
context: "local"
});
}}
color={colors.accent}
size={SIZE.lg}
name="content-copy"
/>
</View>
)}
<Seperator />
<TouchableOpacity
onPress={() => {
if (publishing) return;
setIsLocked(!isLocked);
}}
activeOpacity={0.9}
style={{
flexDirection: "row",
alignItems: "center",
marginTop: 10,
backgroundColor: colors.nav,
padding: 12,
borderRadius: 5
marginBottom: 10
}}
>
<IconButton
onPress={() => {
if (publishing) return;
setIsLocked(!isLocked);
}}
color={isLocked ? colors.accent : colors.icon}
size={SIZE.lg}
name={
isLocked
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
/>
<View
style={{
width: "100%",
flexShrink: 1
}}
>
<Heading size={SIZE.md}>Published at:</Heading>
<Paragraph size={SIZE.sm} numberOfLines={1}>
{publishUrl}
</Paragraph>
<Paragraph
onPress={async () => {
try {
await openLinkInBrowser(publishUrl, colors.accent);
} catch (e) {
console.error(e);
}
}}
size={SIZE.xs}
style={{
marginTop: 5,
color: colors.pri
}}
>
<Icon color={colors.accent} name="open-in-new" /> Open in
browser
<Heading size={SIZE.md}>Password protection</Heading>
<Paragraph>
Published note can only be viewed by someone with the
password.
</Paragraph>
</View>
</TouchableOpacity>
<IconButton
onPress={() => {
Clipboard.setString(publishUrl);
ToastEvent.show({
heading: "Note publish url copied",
type: "success",
context: "local"
});
}}
color={colors.accent}
size={SIZE.lg}
name="content-copy"
/>
</View>
)}
<TouchableOpacity
onPress={() => {
if (publishing) return;
setIsLocked(!isLocked);
}}
activeOpacity={0.9}
style={{
flexDirection: "row",
alignItems: "center",
marginBottom: 10,
backgroundColor: colors.nav,
paddingVertical: 12,
borderRadius: 5,
marginTop: 10
}}
>
<IconButton
onPress={() => {
if (publishing) return;
setIsLocked(!isLocked);
}}
color={isLocked ? colors.accent : colors.icon}
size={SIZE.xl}
name={
isLocked
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
/>
<View
style={{
width: "100%",
flexShrink: 1
}}
>
<Heading size={SIZE.md}>Password protection</Heading>
<Paragraph>
Published note can only be viewed by someone with the password.
</Paragraph>
</View>
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
setSelfDestruct(!selfDestruct);
}}
activeOpacity={0.9}
style={{
flexDirection: "row",
alignItems: "center",
backgroundColor: colors.nav,
paddingVertical: 12,
borderRadius: 5
}}
>
<IconButton
<TouchableOpacity
onPress={() => {
setSelfDestruct(!selfDestruct);
}}
color={selfDestruct ? colors.accent : colors.icon}
size={SIZE.xl}
name={
selfDestruct
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
/>
<View
style={{
width: "100%",
flexShrink: 1
}}
>
<Heading size={SIZE.md}>Self destruct</Heading>
<Paragraph>
Published note link will be automatically deleted once it is
viewed by someone.
</Paragraph>
</View>
</TouchableOpacity>
<View
style={{
width: "100%",
alignSelf: "center",
marginTop: 10
}}
>
{isLocked ? (
<>
<Input
fwdRef={pwdInput}
onChangeText={(value) => (passwordValue.current = value)}
blurOnSubmit
secureTextEntry
defaultValue={passwordValue.current}
placeholder="Enter Password"
/>
<Seperator half />
</>
) : null}
<View
activeOpacity={0.9}
style={{
flexDirection: "row",
width: "100%",
justifyContent: "center"
alignItems: "center"
}}
>
{isPublished && (
<IconButton
onPress={() => {
setSelfDestruct(!selfDestruct);
}}
color={selfDestruct ? colors.accent : colors.icon}
size={SIZE.lg}
name={
selfDestruct
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
/>
<View
style={{
width: "100%",
flexShrink: 1
}}
>
<Heading size={SIZE.md}>Self destruct</Heading>
<Paragraph>
Published note link will be automatically deleted once it is
viewed by someone.
</Paragraph>
</View>
</TouchableOpacity>
<View
style={{
width: "100%",
alignSelf: "center",
marginTop: 10
}}
>
{isLocked ? (
<>
<Button
onPress={deletePublishedNote}
fontSize={SIZE.md}
type="error"
title="Unpublish"
style={{
width: "49%"
}}
<Input
fwdRef={pwdInput}
onChangeText={(value) => (passwordValue = value)}
blurOnSubmit
secureTextEntry
defaultValue={passwordValue}
placeholder="Enter Password"
/>
<Seperator half />
</>
)}
<Seperator half />
) : null}
<Button
onPress={publishNote}
fontSize={SIZE.md}
width="100%"
style={{
width: isPublished ? "49%" : 250,
borderRadius: isPublished ? 5 : 100
marginTop: 10
}}
height={50}
type="accent"
title={isPublished ? "Update" : "Publish"}
title={isPublished ? "Update published note" : "Publish note"}
/>
</View>
</View>
</>
)}
<Paragraph
color={colors.icon}
size={SIZE.xs}
style={{
textAlign: "center",
marginTop: 10,
textDecorationLine: "underline"
}}
onPress={async () => {
try {
await openLinkInBrowser(
"https://docs.notesnook.com/monographs/",
colors.accent
);
} catch (e) {
console.error(e);
}
}}
>
Learn more about Notesnook Monograph
</Paragraph>
</View>
{isPublished && (
<>
<Seperator half />
<Button
onPress={deletePublishedNote}
fontSize={SIZE.md}
width="100%"
height={50}
type="error"
title="Unpublish note"
/>
</>
)}
</View>
</>
)}
<Paragraph
color={colors.icon}
size={SIZE.xs}
style={{
textAlign: "center",
marginTop: 5,
textDecorationLine: "underline"
}}
onPress={async () => {
try {
await openLinkInBrowser(
"https://docs.notesnook.com/monographs/",
colors.accent
);
} catch (e) {
console.error(e);
}
}}
>
Learn more about Notesnook Monograph
</Paragraph>
</View>
</SheetWrapper>
);
};
PublishNoteSheet.present = (note) => {
presentSheet({
component: (ref, close, update) => (
<PublishNoteSheet
actionSheetRef={ref}
close={close}
update={update}
note={note}
/>
)
});
};
export default PublishNoteSheet;

View File

@@ -96,6 +96,7 @@ const RateAppSheet = () => {
onPress={rateApp}
fontSize={SIZE.md}
width="100%"
height={50}
type="accent"
title="Rate now (It takes only a second)"
/>
@@ -120,12 +121,14 @@ 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,6 +298,7 @@ class RecoveryKeySheet extends React.Component {
width="100%"
type="grayAccent"
fontSize={SIZE.md}
height={50}
/>
<Seperator />
<Button
@@ -307,6 +308,7 @@ class RecoveryKeySheet extends React.Component {
type="grayAccent"
fontSize={SIZE.md}
icon="qrcode"
height={50}
/>
<Seperator />
<Button
@@ -316,6 +318,7 @@ class RecoveryKeySheet extends React.Component {
type="grayAccent"
icon="text"
fontSize={SIZE.md}
height={50}
/>
<Seperator />
@@ -326,6 +329,7 @@ class RecoveryKeySheet extends React.Component {
type="grayAccent"
icon="cloud"
fontSize={SIZE.md}
height={50}
/>
<Seperator />
@@ -345,6 +349,7 @@ 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

@@ -18,13 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { RefObject, useEffect, useState } from "react";
import { View } from "react-native";
import { ActionSheetRef } from "react-native-actions-sheet";
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
import ActionSheet from "react-native-actions-sheet";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database";
import {
presentSheet,
PresentSheetOptions
PresentSheetOptions,
presentSheet
} from "../../../services/event-manager";
import { Reminder } from "../../../services/notifications";
import { useRelationStore } from "../../../stores/use-relation-store";
@@ -38,7 +37,7 @@ import { PressableButtonProps } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
type RelationsListProps = {
actionSheetRef: RefObject<ActionSheetRef>;
actionSheetRef: RefObject<ActionSheet>;
close?: () => void;
update?: (options: PresentSheetOptions) => void;
item: { id: string; type: string };
@@ -63,6 +62,8 @@ const IconsByType = {
export const RelationsList = ({
actionSheetRef,
close,
update,
item,
referenceType,
relationType,
@@ -74,6 +75,7 @@ export const RelationsList = ({
const [items, setItems] = useState<Reminder[]>([]);
const colors = useThemeStore((state) => state.colors);
const hasNoRelations = !items || items.length === 0;
useEffect(() => {
setItems(
db.relations?.[relationType]?.(
@@ -121,7 +123,6 @@ export const RelationsList = ({
) : (
<List
listData={items}
ScrollComponent={FlashList}
loading={false}
type={referenceType}
headerProps={null}

View File

@@ -18,8 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import dayjs from "dayjs";
import React, { RefObject } from "react";
import { View } from "react-native";
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
import { ScrollView, View } from "react-native";
import ActionSheet from "react-native-actions-sheet";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database";
import {
@@ -36,7 +36,7 @@ import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
type ReminderSheetProps = {
actionSheetRef: RefObject<ActionSheetRef>;
actionSheetRef: RefObject<ActionSheet>;
close?: () => void;
update?: (options: PresentSheetOptions) => void;
reminder?: Reminder;

View File

@@ -17,8 +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 React, { RefObject, useRef, useState } from "react";
import { Platform, TextInput, View } from "react-native";
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
import {
Platform,
ScrollView,
TextInput,
useWindowDimensions,
View
} from "react-native";
import ActionSheet from "react-native-actions-sheet";
import DateTimePickerModal from "react-native-modal-datetime-picker";
import {
presentSheet,
@@ -38,13 +44,13 @@ import Notifications, { Reminder } from "../../../services/notifications";
import PremiumService from "../../../services/premium";
import SettingsService from "../../../services/settings";
import { useRelationStore } from "../../../stores/use-relation-store";
import { NoteType } from "../../../utils/types";
import { Dialog } from "../../dialog";
import { ReminderTime } from "../../ui/reminder-time";
import Paragraph from "../../ui/typography/paragraph";
import { NoteType } from "../../../utils/types";
import { Dialog } from "../../dialog";
type ReminderSheetProps = {
actionSheetRef: RefObject<ActionSheetRef>;
actionSheetRef: RefObject<ActionSheet>;
close?: (ctx?: string) => void;
update?: (options: PresentSheetOptions) => void;
reminder?: Reminder;
@@ -111,15 +117,13 @@ export default function ReminderSheet({
>(reminder?.priority || SettingsService.get().reminderNotificationMode);
const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
const [repeatFrequency, setRepeatFrequency] = useState(1);
const title = useRef<string | undefined>(reminder?.title);
const details = useRef<string | undefined>(reminder?.description);
const titleRef = useRef<TextInput>(null);
const { height } = useWindowDimensions();
const referencedItem = reference
? (db.notes?.note(reference.id)?.data as NoteType)
: null;
const title = useRef<string | undefined>(
reminder?.title || referencedItem?.title
);
const details = useRef<string | undefined>(reminder?.description);
const titleRef = useRef<TextInput>(null);
const timer = useRef<NodeJS.Timeout>();
const showDatePicker = () => {
setDatePickerVisibility(true);
@@ -130,10 +134,9 @@ export default function ReminderSheet({
};
const handleConfirm = (date: Date) => {
timer.current = setTimeout(() => {
hideDatePicker();
setDate(date);
}, 50);
hideDatePicker();
setDate(date);
console.log(date);
};
function nth(n: number) {
return (
@@ -230,8 +233,13 @@ export default function ReminderSheet({
paddingHorizontal: 12
}}
>
<Dialog context="local" />
<ScrollView keyboardShouldPersistTaps="always">
<Dialog context="local"/>
<ScrollView
onScrollEndDrag={() => actionSheetRef.current?.handleChildScrollEnd()}
style={{
maxHeight: height * 0.85
}}
>
<Input
fwdRef={titleRef}
defaultValue={reminder?.title || referencedItem?.title}
@@ -241,9 +249,7 @@ export default function ReminderSheet({
/>
<Input
defaultValue={
reminder ? reminder?.description : referencedItem?.headline
}
defaultValue={reminder ? reminder?.description : referencedItem?.headline}
placeholder="Add a quick note"
onChangeText={(text) => (details.current = text)}
containerStyle={{
@@ -434,12 +440,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"}
fadeToColor={colors.bg}
@@ -561,16 +561,16 @@ export default function ReminderSheet({
alignSelf: "flex-start"
}}
/>
<Button
style={{
width: "100%"
}}
title="Save"
type="accent"
fontSize={SIZE.md}
onPress={saveReminder}
/>
</ScrollView>
<Button
style={{
width: "100%"
}}
title="Save"
type="accent"
fontSize={SIZE.md}
onPress={saveReminder}
/>
</View>
);
}
@@ -582,7 +582,6 @@ ReminderSheet.present = (
) => {
presentSheet({
context: isSheet ? "local" : undefined,
enableGesturesInScrollView: true,
component: (ref, close, update) => (
<ReminderSheet
actionSheetRef={ref}

View File

@@ -18,10 +18,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { EVENTS } from "@notesnook/core/common";
import React, { useCallback, useEffect, useRef, useState } from "react";
import React, { createRef, useCallback, useEffect, useState } from "react";
import { ActivityIndicator, Platform, View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import DocumentPicker from "react-native-document-picker";
import { FlatList } from "react-native-gesture-handler";
import * as ScopedStorage from "react-native-scoped-storage";
import { db } from "../../../common/database";
import storage from "../../../common/database/storage";
@@ -35,7 +35,7 @@ import { initialize } from "../../../stores";
import { useThemeStore } from "../../../stores/use-theme-store";
import { eCloseRestoreDialog, eOpenRestoreDialog } from "../../../utils/events";
import { SIZE } from "../../../utils/size";
import { timeConverter } from "../../../utils/time";
import { sleep, timeConverter } from "../../../utils/time";
import { Dialog } from "../../dialog";
import DialogHeader from "../../dialog/dialog-header";
import { presentDialog } from "../../dialog/functions";
@@ -44,18 +44,12 @@ import { Button } from "../../ui/button";
import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet";
import Paragraph from "../../ui/typography/paragraph";
const actionSheetRef = createRef();
let RNFetchBlob;
const RestoreDataSheet = () => {
const [visible, setVisible] = useState(false);
const [restoring, setRestoring] = useState(false);
const sheet = useRef();
useEffect(() => {
const open = async () => {
setVisible(true);
setTimeout(() => {
sheet.current?.show();
}, 1);
};
eSubscribeEvent(eOpenRestoreDialog, open);
eSubscribeEvent(eCloseRestoreDialog, close);
return () => {
@@ -64,15 +58,21 @@ const RestoreDataSheet = () => {
};
}, [close]);
const open = async () => {
setVisible(true);
await sleep(30);
actionSheetRef.current?.setModalVisible(true);
};
const close = useCallback(() => {
if (restoring) {
showIsWorking();
return;
}
sheet.current?.hide();
actionSheetRef.current?.setModalVisible(false);
setTimeout(() => {
setVisible(false);
}, 150);
}, 300);
}, [restoring]);
const showIsWorking = () => {
@@ -86,19 +86,15 @@ const RestoreDataSheet = () => {
return !visible ? null : (
<SheetWrapper
fwdRef={sheet}
fwdRef={actionSheetRef}
gestureEnabled={!restoring}
closeOnTouchBackdrop={!restoring}
onClose={() => {
setVisible(false);
close();
}}
onClose={close}
>
<RestoreDataComponent
close={close}
restoring={restoring}
setRestoring={setRestoring}
actionSheetRef={sheet}
/>
<Toast context="local" />
</SheetWrapper>
@@ -113,6 +109,7 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
const [loading, setLoading] = useState(true);
const [backupDirectoryAndroid, setBackupDirectoryAndroid] = useState(false);
const [progress, setProgress] = useState();
useEffect(() => {
const subscription = db.eventManager.subscribe(
EVENTS.migrationProgress,
@@ -126,9 +123,7 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
}, []);
useEffect(() => {
setTimeout(() => {
checkBackups();
}, 300);
checkBackups();
}, []);
const restore = async (item) => {
@@ -367,6 +362,10 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
</View>
<Seperator half />
<FlatList
nestedScrollEnabled
onMomentumScrollEnd={() => {
actionSheetRef.current?.handleChildScrollEnd();
}}
ListEmptyComponent={
!restoring ? (
loading ? (

View File

@@ -100,7 +100,7 @@ const Sort = ({ type, screen }) => {
height={25}
iconPosition="right"
fontSize={SIZE.sm - 1}
type="transparent"
type="grayBg"
buttonType={{
text: colors.accent
}}

View File

@@ -1,451 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import qclone from "qclone";
import React, {
createContext,
useContext,
useEffect,
useRef,
useState
} from "react";
import { Animated, Dimensions, View, RefreshControl } from "react-native";
import ActionSheet, {
ActionSheetRef,
FlatList
} from "react-native-actions-sheet";
import { db } from "../../../common/database";
import { IconButton } from "../../../components/ui/icon-button";
import { PressableButton } from "../../../components/ui/pressable";
import Paragraph from "../../../components/ui/typography/paragraph";
import { TopicNotes } from "../../../screens/notes/topic-notes";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
} from "../../../services/event-manager";
import useNavigationStore, {
NotebookScreenParams
} from "../../../stores/use-navigation-store";
import { useThemeStore } from "../../../stores/use-theme-store";
import {
eOnNewTopicAdded,
eOnTopicSheetUpdate,
eOpenAddTopicDialog
} from "../../../utils/events";
import { normalize, SIZE } from "../../../utils/size";
import { NotebookType, TopicType } from "../../../utils/types";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { openEditor } from "../../../screens/notes/common";
import { getTotalNotes, history } from "../../../utils";
import { Properties } from "../../properties";
import { deleteItems } from "../../../utils/functions";
import { presentDialog } from "../../dialog/functions";
import Config from "react-native-config";
import { notesnook } from "../../../../e2e/test.ids";
export const TopicsSheet = () => {
const currentScreen = useNavigationStore((state) => state.currentScreen);
const canShow =
currentScreen.name === "Notebook" || currentScreen.name === "TopicNotes";
const [notebook, setNotebook] = useState(
canShow
? db.notebooks?.notebook(
currentScreen?.notebookId || currentScreen?.id || ""
)?.data
: null
);
const [selection, setSelection] = useState<TopicType[]>([]);
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 [animations] = useState({
translate: new Animated.Value(0),
display: new Animated.Value(-5000),
opacity: new Animated.Value(0)
});
const onRequestUpdate = React.useCallback(
(data?: NotebookScreenParams) => {
if (!canShow) return;
if (!data) data = { item: notebook } as NotebookScreenParams;
const _notebook = db.notebooks?.notebook(data.item?.id)
?.data as NotebookType;
if (_notebook) {
setNotebook(_notebook);
setTopics(qclone(_notebook.topics));
}
},
[notebook, canShow]
);
useEffect(() => {
const onTopicUpdate = () => {
onRequestUpdate();
};
eSubscribeEvent(eOnTopicSheetUpdate, onTopicUpdate);
eSubscribeEvent(eOnNewTopicAdded, onRequestUpdate);
return () => {
eUnSubscribeEvent(eOnTopicSheetUpdate, onRequestUpdate);
eUnSubscribeEvent(eOnNewTopicAdded, onTopicUpdate);
};
}, [onRequestUpdate]);
const PLACEHOLDER_DATA = {
heading: "Topics",
paragraph: "You have not added any topics yet.",
button: "Add first topic",
action: () => {
eSendEvent(eOpenAddTopicDialog, { notebookId: notebook.id });
},
loading: "Loading notebook topics"
};
const renderTopic = ({ item, index }: { item: TopicType; index: number }) => (
<TopicItem item={item} index={index} />
);
const selectionContext = {
selection: selection,
enabled,
setEnabled,
toggleSelection: (item: TopicType) => {
setSelection((state) => {
const selection = [...state];
const index = selection.findIndex(
(selected) => selected.id === item.id
);
if (index > -1) {
selection.splice(index, 1);
if (selection.length === 0) {
setEnabled(false);
}
return selection;
}
selection.push(item);
return selection;
});
}
};
useEffect(() => {
if (canShow) {
const isTopic = currentScreen.name === "TopicNotes";
const id = isTopic ? currentScreen?.notebookId : currentScreen?.id;
if (!ref.current?.isOpen()) {
animations.display.setValue(5000);
animations.opacity.setValue(0);
}
if (id) {
onRequestUpdate({
item: db.notebooks?.notebook(id).data
} as any);
}
ref.current?.show();
} else {
ref.current?.hide();
}
}, [
animations.display,
animations.opacity,
canShow,
currentScreen?.id,
currentScreen.name,
currentScreen?.notebookId,
onRequestUpdate
]);
return (
<ActionSheet
ref={ref}
isModal={false}
containerStyle={{
maxHeight: 400,
borderTopRightRadius: 15,
borderTopLeftRadius: 15,
backgroundColor: colors.bg,
borderWidth: 1,
borderColor: colors.border,
borderBottomWidth: 0
}}
closable={!canShow}
elevation={10}
indicatorStyle={{
width: 100,
backgroundColor: colors.nav
}}
keyboardHandlerEnabled={false}
snapPoints={Config.isTesting === "true" ? [100] : [15, 100]}
initialSnapIndex={0}
backgroundInteractionEnabled
onChange={(position, height) => {
animations.translate.setValue(position - 60);
const h = Dimensions.get("window").height;
const minPos = h - height;
if (position - 100 < minPos || !canShow) {
animations.display.setValue(5000);
animations.opacity.setValue(0);
} else {
animations.display.setValue(0);
setTimeout(() => {
animations.opacity.setValue(1);
}, 300);
}
}}
gestureEnabled
ExtraOverlayComponent={
<Animated.View
style={{
top: animations.translate,
position: "absolute",
right: 12,
opacity: animations.opacity,
transform: [
{
translateY: animations.display
}
]
}}
>
<PressableButton
testID={notesnook.buttons.add}
type="accent"
accentColor={"accent"}
accentText="light"
onPress={openEditor}
customStyle={{
borderRadius: 100
}}
>
<View
style={{
alignItems: "center",
justifyContent: "center",
height: normalize(60),
width: normalize(60)
}}
>
<Icon name="plus" color="white" size={SIZE.xxl} />
</View>
</PressableButton>
</Animated.View>
}
>
<View
style={{
maxHeight: 400,
height: 400,
width: "100%"
}}
>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
paddingHorizontal: 12,
alignItems: "center"
}}
>
<Paragraph size={SIZE.xs} color={colors.icon}>
TOPICS
</Paragraph>
<View
style={{
flexDirection: "row"
}}
>
{enabled ? (
<IconButton
customStyle={{
marginLeft: 10
}}
onPress={async () => {
//@ts-ignore
history.selectedItemsList = selection;
presentDialog({
title: `Delete ${
selection.length > 1 ? "topics" : "topics"
}`,
paragraph: `Are you sure you want to delete ${
selection.length > 1 ? "these topicss?" : "this topics?"
}`,
positiveText: "Delete",
negativeText: "Cancel",
positivePress: async () => {
await deleteItems();
history.selectedItemsList = [];
setEnabled(false);
setSelection([]);
},
positiveType: "errorShade"
});
return;
}}
color={colors.pri}
tooltipText="Move to trash"
tooltipPosition={1}
name="delete"
size={22}
/>
) : (
<IconButton
name="plus"
onPress={PLACEHOLDER_DATA.action}
testID="add-topic-button"
color={colors.pri}
size={22}
customStyle={{
width: 40,
height: 40
}}
/>
)}
</View>
</View>
<SelectionContext.Provider value={selectionContext}>
<FlatList
data={topics}
style={{
width: "100%"
}}
refreshControl={
<RefreshControl
refreshing={false}
onRefresh={() => {
onRequestUpdate();
}}
colors={[colors.accent]}
progressBackgroundColor={colors.bg}
/>
}
keyExtractor={(item) => item.id}
renderItem={renderTopic}
ListEmptyComponent={
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center",
height: 300
}}
>
<Paragraph color={colors.icon}>No topics</Paragraph>
</View>
}
ListFooterComponent={<View style={{ height: 50 }} />}
/>
</SelectionContext.Provider>
</View>
</ActionSheet>
);
};
const SelectionContext = createContext<{
selection: TopicType[];
enabled: boolean;
setEnabled: (value: boolean) => void;
toggleSelection: (item: TopicType) => void;
}>({
selection: [],
enabled: false,
setEnabled: (value: boolean) => {},
toggleSelection: (item: TopicType) => {}
});
const useSelection = () => useContext(SelectionContext);
const TopicItem = ({ item, index }: { item: TopicType; index: number }) => {
const screen = useNavigationStore((state) => state.currentScreen);
const colors = useThemeStore((state) => state.colors);
const selection = useSelection();
const isSelected =
selection.selection.findIndex((selected) => selected.id === item.id) > -1;
const isFocused = screen.id === item.id;
const notesCount = getTotalNotes(item);
return (
<PressableButton
type={isSelected || isFocused ? "grayBg" : "transparent"}
onLongPress={() => {
if (selection.enabled) return;
selection.setEnabled(true);
selection.toggleSelection(item);
}}
testID={`topic-sheet-item-${index}`}
onPress={() => {
if (selection.enabled) {
selection.toggleSelection(item);
return;
}
TopicNotes.navigate(item, true);
}}
customStyle={{
justifyContent: "space-between",
width: "100%",
alignItems: "center",
flexDirection: "row",
paddingHorizontal: 12,
borderRadius: 0
}}
>
<View
style={{
flexDirection: "row",
alignItems: "center"
}}
>
{selection.enabled ? (
<IconButton
size={SIZE.lg}
color={isSelected ? colors.accent : colors.icon}
name={
isSelected
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
/>
) : null}
<Paragraph size={SIZE.sm}>
{item.title}{" "}
{notesCount ? (
<Paragraph size={SIZE.xs} color={colors.icon}>
{notesCount}
</Paragraph>
) : null}
</Paragraph>
</View>
<IconButton
name="dots-horizontal"
customStyle={{
width: 40,
height: 40
}}
testID={notesnook.ids.notebook.menu}
onPress={() => {
Properties.present(item);
}}
left={0}
right={0}
bottom={0}
top={0}
color={colors.pri}
size={SIZE.xl}
/>
</PressableButton>
);
};

View File

@@ -19,10 +19,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect, useState } from "react";
import { Linking, View } from "react-native";
import { ScrollView } from "react-native-actions-sheet";
import { checkVersion } from "react-native-check-version";
import Config from "react-native-config";
import deviceInfoModule from "react-native-device-info";
import { ScrollView } from "react-native-gesture-handler";
import { useThemeStore } from "../../../stores/use-theme-store";
import { STORE_LINK } from "../../../utils/constants";
import { SIZE } from "../../../utils/size";
@@ -148,6 +148,9 @@ export const Update = ({ version: appVersion, fwdRef }) => {
<Seperator />
<ScrollView
nestedScrollEnabled={true}
onMomentumScrollEnd={() => {
fwdRef?.current?.handleChildScrollEnd();
}}
style={{
width: "100%"
}}

View File

@@ -164,6 +164,7 @@ 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 = 45,
height = 50,
fontSize = SIZE.md,
onFocusInput,
buttons,

View File

@@ -37,9 +37,7 @@ const SheetWrapper = ({
onHasReachedTop,
keyboardMode,
overlay,
overlayOpacity = 0.3,
enableGesturesInScrollView = false,
bottomPadding = true
overlayOpacity = 0.3
}) => {
const colors = useThemeStore((state) => state.colors);
const deviceMode = useSettingStore((state) => state.deviceMode);
@@ -61,8 +59,8 @@ const SheetWrapper = ({
zIndex: 10,
paddingTop: 5,
paddingBottom: 0,
borderTopRightRadius: 15,
borderTopLeftRadius: 15,
borderTopRightRadius: 20,
borderTopLeftRadius: 20,
alignSelf: "center",
borderBottomRightRadius: 0,
borderBottomLeftRadius: 0
@@ -86,8 +84,7 @@ const SheetWrapper = ({
backdrop: "sheet-backdrop"
}}
indicatorStyle={{
width: 100,
backgroundColor: colors.nav
width: 100
}}
drawUnderStatusBar={false}
containerStyle={style}
@@ -101,9 +98,8 @@ const SheetWrapper = ({
indicatorColor={colors.nav}
onOpen={_onOpen}
keyboardDismissMode="none"
enableGesturesInScrollView={enableGesturesInScrollView}
defaultOverlayOpacity={overlayOpacity}
overlayColor={pitchBlack ? "#585858" : "#2b2b2b"}
overlayColor={pitchBlack ? "#585858" : "#000000"}
keyboardShouldPersistTaps="always"
ExtraOverlayComponent={
<>
@@ -120,16 +116,14 @@ const SheetWrapper = ({
>
<BouncingView>
{children}
{bottomPadding ? (
<View
style={{
height:
Platform.OS === "ios" && insets.bottom !== 0
? insets.bottom + 5
: 20
}}
/>
) : null}
<View
style={{
height:
Platform.OS === "ios" && insets.bottom !== 0
? insets.bottom + 5
: 20
}}
/>
</BouncingView>
</ActionSheet>
);

View File

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

View File

@@ -22,15 +22,10 @@ import React, { useCallback, useEffect, useState } from "react";
import { Platform } from "react-native";
import Share from "react-native-share";
import { db } from "../common/database";
import { AttachmentDialog } from "../components/attachments";
import { presentDialog } from "../components/dialog/functions";
import NoteHistory from "../components/note-history";
import { AddNotebookSheet } from "../components/sheets/add-notebook";
import MoveNoteSheet from "../components/sheets/add-to";
import ExportNotesSheet from "../components/sheets/export-notes";
import { MoveNotes } from "../components/sheets/move-notes/movenote";
import PublishNoteSheet from "../components/sheets/publish-note";
import { RelationsList } from "../components/sheets/relations-list/index";
import ReminderSheet from "../components/sheets/reminder";
import {
eSendEvent,
@@ -45,16 +40,24 @@ import Notifications from "../services/notifications";
import { useEditorStore } from "../stores/use-editor-store";
import { useMenuStore } from "../stores/use-menu-store";
import useNavigationStore from "../stores/use-navigation-store";
import { useRelationStore } from "../stores/use-relation-store";
import { useSelectionStore } from "../stores/use-selection-store";
import { useTagStore } from "../stores/use-tag-store";
import { useThemeStore } from "../stores/use-theme-store";
import { useUserStore } from "../stores/use-user-store";
import { toTXT } from "../utils";
import { toggleDarkMode } from "../utils/color-scheme/utils";
import { eOpenAddTopicDialog, eOpenLoginDialog } from "../utils/events";
import {
eOpenAddNotebookDialog,
eOpenAddTopicDialog,
eOpenAttachmentsDialog,
eOpenLoginDialog,
eOpenMoveNoteDialog,
eOpenPublishNoteDialog
} from "../utils/events";
import { deleteItems } from "../utils/functions";
import { sleep } from "../utils/time";
import { RelationsList } from "../components/sheets/relations-list/index";
import { useRelationStore } from "../stores/use-relation-store";
export const useActions = ({ close = () => null, item }) => {
const colors = useThemeStore((state) => state.colors);
@@ -102,15 +105,6 @@ export const useActions = ({ close = () => null, item }) => {
);
};
const isNoteInNotebook = () => {
const currentScreen = useNavigationStore.getState().currentScreen;
if (item.type !== "note" || currentScreen.name !== "Notebook") return;
return !!db.relations
.to(item, "notebook")
.find((notebook) => notebook.id === currentScreen.id);
};
const onUpdate = useCallback(
async (type) => {
if (type === "unpin") {
@@ -135,9 +129,12 @@ export const useActions = ({ close = () => null, item }) => {
}
function addTo() {
close();
clearSelection(true);
setSelectedItem(item);
MoveNoteSheet.present(item);
setTimeout(() => {
eSendEvent(eOpenMoveNoteDialog, item);
}, 300);
}
async function addToFavorites() {
@@ -273,7 +270,9 @@ export const useActions = ({ close = () => null, item }) => {
});
return;
}
PublishNoteSheet.present(item);
close();
await sleep(300);
eSendEvent(eOpenPublishNoteDialog, item);
}
const checkNoteSynced = () => {
@@ -515,22 +514,6 @@ export const useActions = ({ close = () => null, item }) => {
close();
}
async function removeNoteFromNotebook() {
const currentScreen = useNavigationStore.getState().currentScreen;
if (currentScreen.name !== "Notebook") return;
await db.relations.unlink({ type: "notebook", id: currentScreen.id }, item);
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes",
"Notebook",
"Notebooks"
);
close();
}
async function deleteTrashItem() {
if (!checkNoteSynced()) return;
close();
@@ -555,16 +538,22 @@ export const useActions = ({ close = () => null, item }) => {
}
async function openHistory() {
close();
await sleep(300);
presentSheet({
component: (ref) => <NoteHistory fwdRef={ref} note={item} />
});
}
async function showAttachments() {
AttachmentDialog.present();
close();
await sleep(300);
eSendEvent(eOpenAttachmentsDialog, item);
}
async function exportNote() {
close();
await sleep(300);
ExportNotesSheet.present([item]);
}
@@ -658,6 +647,8 @@ export const useActions = ({ close = () => null, item }) => {
title: "Add notes",
icon: "plus",
func: async () => {
close();
await sleep(500);
MoveNotes.present(db.notebooks.notebook(item.notebookId).data, item);
}
},
@@ -698,7 +689,9 @@ export const useActions = ({ close = () => null, item }) => {
title: "Edit notebook",
icon: "square-edit-outline",
func: async () => {
AddNotebookSheet.present(item);
close();
await sleep(300);
eSendEvent(eOpenAddNotebookDialog, item);
}
},
{
@@ -784,6 +777,8 @@ export const useActions = ({ close = () => null, item }) => {
title: "Edit reminder",
icon: "pencil",
func: async () => {
close();
await sleep(300);
ReminderSheet.present(item);
},
close: false
@@ -794,6 +789,7 @@ export const useActions = ({ close = () => null, item }) => {
title: "Reminders",
icon: "clock-outline",
func: async () => {
close();
RelationsList.present({
reference: item,
referenceType: "reminder",
@@ -853,13 +849,6 @@ export const useActions = ({ close = () => null, item }) => {
icon: "minus-circle-outline",
func: removeNoteFromTopic
},
{
id: "remove-from-notebook",
title: "Remove from notebook",
hidden: !isNoteInNotebook(),
icon: "minus-circle-outline",
func: removeNoteFromNotebook
},
{
id: "trash",
title:

View File

@@ -402,9 +402,11 @@ 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") {
@@ -425,6 +427,7 @@ export const useAppEvents = () => {
useEffect(() => {
if (!verify && !syncing && refValues.current.backupDidWait) {
console.log("backup run after wait");
refValues.current.backupDidWait = false;
checkAutoBackup();
}

View File

@@ -24,7 +24,6 @@ import { SafeAreaView } from "react-native";
import Container from "../components/container";
import DelayLayout from "../components/delay-layout";
import Intro from "../components/intro";
import { TopicsSheet } from "../components/sheets/topic-sheet";
import useGlobalSafeAreaInsets from "../hooks/use-global-safe-area-insets";
import { hideAllTooltips } from "../hooks/use-tooltip";
import Favorites from "../screens/favorites";
@@ -199,7 +198,6 @@ const _NavigationStack = () => {
<NavigationContainer onStateChange={onStateChange} ref={rootNavigatorRef}>
<Tabs />
</NavigationContainer>
<TopicsSheet />
</Container>
);
};

View File

@@ -235,17 +235,14 @@ const _TabsHolder = () => {
let needsUpdate = current !== deviceMode;
if (fullscreen && current !== "mobile") {
// 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);
editorRef.current?.setNativeProps({
style: {
width: size.width,
zIndex: 999,
paddingHorizontal:
current === "smallTablet" ? size.width * 0 : size.width * 0.15
}
});
} else {
if (fullscreen) eSendEvent(eCloseFullscreenEditor, current);
editorRef.current?.setNativeProps({
@@ -284,8 +281,10 @@ 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;

View File

@@ -4,8 +4,6 @@
"main": "./App.js",
"license": "GPL-3.0-or-later",
"dependencies": {
"react": "18.0.0",
"react-native": "0.69.7",
"@flyerhq/react-native-link-preview": "^1.6.0",
"@mdi/js": "^6.7.96",
"absolutify": "^0.1.0",
@@ -15,7 +13,7 @@
"html-to-text": "8.1.0",
"phone": "^3.1.14",
"qclone": "^1.2.0",
"react-native-actions-sheet": "^0.9.0-alpha.14",
"react-native-actions-sheet": "^0.7.2",
"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",
@@ -30,6 +28,7 @@
"zustand": "^3.6.0",
"fflate": "^0.7.3",
"timeago.js": "4.0.2"
},
"sideEffects": false
}

View File

@@ -107,7 +107,6 @@ const Editor = React.memo(
attachmentType: string;
}) => {
if (groupId !== editor.note.current?.id) return;
editorController.current.markImageLoaded(hash);
if (attachmentType === "webclip") {
editor.commands.updateWebclip({
hash: hash,

View File

@@ -78,7 +78,7 @@ const EditorOverlay = ({ editorId = "", editor }) => {
setTimeout(() => {
translateValue.value = 6000;
}, 500);
}, 0);
}, 100);
}
},
[opacity, translateValue]

View File

@@ -48,7 +48,6 @@ const fn = (fn: string) => {
const id = randId("fn_");
return {
job: `(async () => {
if (typeof __PLATFORM__ === "undefined") __PLATFORM__ = "${Platform.OS}";
try {
let response = true;
${fn}
@@ -58,7 +57,7 @@ const fn = (fn: string) => {
if (DEV_MODE && typeof logger !== "undefined") logger('error', "webview: ", e.message, e.stack);
}
return true;
})();true;`,
})();`,
id: id
};
};
@@ -129,6 +128,7 @@ 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)})
@@ -228,7 +228,7 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
await this.doAsync(
`editor && editor.commands.updateImage(${JSON.stringify({
hash
})},${JSON.stringify({ dataurl: src, hash, preventUpdate: true })})`
})},${JSON.stringify({ src, hash, preventUpdate: true })})`
);
};

View File

@@ -34,6 +34,5 @@ export const EventTypes = {
fullscreen: "editor-event:fullscreen",
link: "editor-event:link",
contentchange: "editor-event:content-change",
reminders: "editor-event:reminders",
previewAttachment: "editor-event:preview-attachment"
reminders: "editor-event:reminders"
};

View File

@@ -17,14 +17,13 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 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,
@@ -33,6 +32,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";
const FILE_SIZE_LIMIT = 500 * 1024 * 1024;
const IMAGE_SIZE_LIMIT = 50 * 1024 * 1024;
@@ -124,7 +124,6 @@ const file = async (fileOptions) => {
});
if (!(await attachFile(uri, hash, file.type, file.name, fileOptions)))
return;
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
editorController.current?.commands.insertAttachment({
hash: hash,
filename: file.name,
@@ -148,6 +147,8 @@ const file = async (fileOptions) => {
const camera = async (options) => {
try {
await db.attachments.generateKey();
eSendEvent(eCloseSheet);
await sleep(400);
launchCamera(
{
includeBase64: true,
@@ -169,6 +170,8 @@ const camera = async (options) => {
const gallery = async (options) => {
try {
await db.attachments.generateKey();
eSendEvent(eCloseSheet);
await sleep(400);
launchImageLibrary(
{
includeBase64: true,
@@ -202,7 +205,7 @@ const pick = async (options) => {
return;
}
if (options?.type.startsWith("image") || options?.type === "camera") {
if (options.type.startsWith("image")) {
if (options.type === "image") {
gallery(options);
} else {
camera(options);
@@ -231,7 +234,7 @@ const handleImageResponse = async (response, options) => {
});
return;
}
let b64 = `data:${image.type};base64, ` + image.base64;
const b64 = `data:${image.type};base64, ` + image.base64;
const uri = decodeURI(image.uri);
const hash = await Sodium.hashFile({
uri: uri,
@@ -240,24 +243,12 @@ const handleImageResponse = async (response, options) => {
let fileName = image.originalFileName || image.fileName;
if (!(await attachFile(uri, hash, image.type, fileName, options))) return;
const isPng = /(png)/g.test(image.type);
const isJpeg = /(jpeg|jpg)/g.test(image.type);
if (isPng || isJpeg) {
b64 =
`data:${image.type};base64, ` +
(await compressToBase64(
Platform.OS === "ios" ? "file://" + image.uri : image.uri,
isPng ? "PNG" : "JPEG"
));
}
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
editorController.current?.commands.insertImage({
hash: hash,
type: image.type,
title: fileName,
dataurl: b64,
src: b64,
size: image.fileSize,
filename: fileName
});
@@ -298,6 +289,7 @@ async function attachFile(uri, hash, type, filename, options) {
encryptionInfo,
editorController.current?.note?.id
);
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
return true;
} catch (e) {

View File

@@ -29,7 +29,6 @@ import {
} from "react-native";
import { WebViewMessageEvent } from "react-native-webview";
import { db } from "../../../common/database";
import ManageTagsSheet from "../../../components/sheets/manage-tags";
import { RelationsList } from "../../../components/sheets/relations-list";
import ReminderSheet from "../../../components/sheets/reminder";
import useKeyboard from "../../../hooks/use-keyboard";
@@ -52,7 +51,8 @@ import {
eOpenFullscreenEditor,
eOpenLoginDialog,
eOpenPremiumDialog,
eOpenPublishNoteDialog
eOpenPublishNoteDialog,
eOpenTagsDialog
} from "../../../utils/events";
import { openLinkInBrowser } from "../../../utils/functions";
import { tabBarRef } from "../../../utils/global-refs";
@@ -326,7 +326,7 @@ export const useEditorEvents = (
});
return;
}
ManageTagsSheet.present(editor.note.current);
eSendEvent(eOpenTagsDialog, editor.note.current);
break;
case EventTypes.tag:
if (editorMessage.value) {
@@ -380,10 +380,6 @@ export const useEditorEvents = (
case EventTypes.link:
openLinkInBrowser(editorMessage.value as string);
break;
case EventTypes.previewAttachment:
eSendEvent("ImagePreview", editorMessage.value);
break;
default:
break;
}

View File

@@ -17,7 +17,6 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { EVENTS } from "@notesnook/core/common";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import WebView from "react-native-webview";
import { db } from "../../../common/database";
@@ -30,7 +29,6 @@ import {
openVault
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import SettingsService from "../../../services/settings";
import { TipManager } from "../../../services/tip-manager";
import { useEditorStore } from "../../../stores/use-editor-store";
import { useNoteStore } from "../../../stores/use-notes-store";
@@ -52,6 +50,8 @@ import {
makeSessionId,
post
} from "./utils";
import { EVENTS } from "@notesnook/core/common";
import SettingsService from "../../../services/settings";
export const useEditor = (
editorId = "",
@@ -76,7 +76,6 @@ export const useEditor = (
const saveCount = useRef(0);
const lastContentChangeTime = useRef<number>(0);
const lock = useRef(false);
const loadedImages = useRef<{ [name: string]: boolean }>({});
const postMessage = useCallback(
async <T>(type: string, data: T) =>
@@ -155,9 +154,7 @@ export const useEditor = (
async (resetState = true) => {
currentNote.current?.id && db.fs.cancel(currentNote.current.id);
currentNote.current = null;
loadedImages.current = {};
currentContent.current = null;
clearTimeout(timers.current["loading-images"]);
sessionHistoryId.current = undefined;
saveCount.current = 0;
useEditorStore.getState().setReadonly(false);
@@ -293,58 +290,6 @@ export const useEditor = (
}
}, []);
const getMediaToLoad = (previousContent?: string) => {
if (!currentNote.current?.id) return [];
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;
}
}
return media;
};
const markImageLoaded = (hash: string) => {
const attachment = loadedImages.current[hash];
if (typeof attachment === "boolean") {
loadedImages.current[hash] = true;
}
};
const loadImages = useCallback((previousContent?: string) => {
if (!currentNote.current?.id) return;
const timerId = "loading-images";
clearTimeout(timers.current[timerId]);
timers.current[timerId] = setTimeout(() => {
if (!currentNote.current?.id) return;
if (currentNote.current?.content?.isPreview) {
db.content?.downloadMedia(
currentNote.current?.id,
currentNote.current.content,
true
);
} else {
const media = getMediaToLoad(previousContent);
if (media.length > 0) {
db.attachments?.downloadMedia(currentNote.current?.id, media);
}
}
}, 1000);
}, []);
const loadNote = useCallback(
async (
item: Omit<NoteType, "type"> & {
@@ -388,17 +333,25 @@ export const useEditor = (
loadImages();
}
},
[
commands,
isDefaultEditor,
loadContent,
loadImages,
overlay,
postMessage,
reset
]
[commands, isDefaultEditor, loadContent, overlay, postMessage, reset]
);
const loadImages = () => {
if (!currentNote.current?.id) return;
setTimeout(() => {
if (!currentNote.current?.id) return;
if (currentNote.current?.content?.isPreview) {
db.content?.downloadMedia(
currentNote.current?.id,
currentNote.current.content,
true
);
} else {
db.attachments?.downloadMedia(currentNote.current?.id);
}
}, 300);
};
const lockNoteWithVault = useCallback((note: NoteType) => {
eSendEvent(eClearEditor);
openVault({
@@ -425,9 +378,6 @@ export const useEditor = (
return;
lock.current = true;
const previousContent = currentContent.current?.data;
if (data.type === "tiptap") {
if (!currentNote.current.locked && isContentEncrypted) {
lockNoteWithVault(note);
@@ -458,20 +408,16 @@ export const useEditor = (
}
await commands.setStatus(timeConverter(note.dateEdited), "Saved");
}
db.eventManager.subscribe(
EVENTS.syncCompleted,
async () => {
loadImages();
},
true
);
lock.current = false;
if (data.type === "tiptap") {
loadImages(previousContent);
db.eventManager.subscribe(
EVENTS.syncCompleted,
() => {
loadImages(previousContent);
},
true
);
}
},
[loadImages, lockNoteWithVault, postMessage, commands]
[commands, postMessage, lockNoteWithVault]
);
useEffect(() => {
@@ -614,7 +560,6 @@ export const useEditor = (
onReady,
saveContent,
onContentChanged,
editorId: editorId,
markImageLoaded
editorId: editorId
};
};

View File

@@ -65,6 +65,7 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
onBlur: () => false,
delay: SettingsService.get().homepage === route.name ? 1 : -1
});
return (
<DelayLayout wait={loading} delay={500}>
<List

View File

@@ -16,9 +16,12 @@ 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 { groupArray } from "@notesnook/core/utils/grouping";
import qclone from "qclone";
import React, { useEffect, useRef, useState } from "react";
import { db } from "../../common/database";
import { FloatingButton } from "../../components/container/floating-button";
import DelayLayout from "../../components/delay-layout";
import List from "../../components/list";
import { NotebookHeader } from "../../components/list-items/headers/notebook-header";
@@ -33,29 +36,28 @@ import SearchService from "../../services/search";
import useNavigationStore, {
NotebookScreenParams
} from "../../stores/use-navigation-store";
import { eOnNewTopicAdded, eOpenAddNotebookDialog } from "../../utils/events";
import {
eOnNewTopicAdded,
eOpenAddNotebookDialog,
eOpenAddTopicDialog
} from "../../utils/events";
import { NotebookType } from "../../utils/types";
import { openEditor, setOnFirstSave } from "../notes/common";
const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
const [notes, setNotes] = useState(
const [topics, setTopics] = useState(
groupArray(
db.relations?.from(route.params.item, "note") || [],
db.settings?.getGroupOptions("notes")
qclone(route?.params.item?.topics) || [],
db.settings?.getGroupOptions("topics")
)
);
const params = useRef<NotebookScreenParams>(route?.params);
useNavigationFocus(navigation, {
onFocus: () => {
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
syncWithNavigation();
useNavigationStore.getState().setButtonAction(openEditor);
useNavigationStore.getState().setButtonAction(onPressFloatingButton);
return false;
},
onBlur: () => {
setOnFirstSave(null);
return false;
}
onBlur: () => false
});
const syncWithNavigation = React.useCallback(() => {
@@ -68,10 +70,6 @@ const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
},
params.current?.canGoBack
);
setOnFirstSave({
type: "notebook",
id: params.current.item.id
});
SearchService.prepareSearch = prepareSearch;
}, [route.name]);
@@ -84,9 +82,11 @@ const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
?.data as NotebookType;
if (notebook) {
params.current.item = notebook;
const notes = db.relations?.from(notebook, "note");
setNotes(
groupArray(notes || [], db.settings?.getGroupOptions("notes"))
setTopics(
groupArray(
qclone(notebook.topics),
db.settings?.getGroupOptions("topics")
)
);
syncWithNavigation();
}
@@ -102,61 +102,60 @@ const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
return () => {
eUnSubscribeEvent(eOnNewTopicAdded, onRequestUpdate);
};
}, [onRequestUpdate]);
useEffect(() => {
return () => {
setOnFirstSave(null);
};
}, []);
}, [onRequestUpdate, topics]);
const prepareSearch = () => {
SearchService.update({
placeholder: `Search in "${params.current.title}"`,
type: "notes",
type: "topics",
title: params.current.title,
get: () => {
const notebook = db.notebooks?.notebook(params?.current?.item?.id)
?.data as NotebookType;
return db.relations?.from(notebook, "note");
return notebook?.topics;
}
});
};
const onPressFloatingButton = () => {
const n = params.current.item;
eSendEvent(eOpenAddTopicDialog, { notebookId: n.id });
};
const PLACEHOLDER_DATA = {
heading: params.current.item?.title,
paragraph: "You have not added any notes yet.",
button: "Add your first note",
action: openEditor,
loading: "Loading notebook notes"
paragraph: "You have not added any topics yet.",
button: "Add first topic",
action: onPressFloatingButton,
loading: "Loading notebook topics"
};
return (
<>
<DelayLayout>
<List
listData={notes}
type="notes"
refreshCallback={() => {
onRequestUpdate();
}}
screen="Notebook"
headerProps={{
heading: params.current.title
}}
loading={false}
ListHeader={
<NotebookHeader
onEditNotebook={() => {
eSendEvent(eOpenAddNotebookDialog, params.current.item);
}}
notebook={params.current.item}
/>
}
placeholderData={PLACEHOLDER_DATA}
/>
</DelayLayout>
</>
<DelayLayout>
<List
listData={topics}
type="topics"
refreshCallback={() => {
onRequestUpdate();
}}
screen="Notebook"
headerProps={{
heading: params.current.title
}}
loading={false}
ListHeader={
<NotebookHeader
onEditNotebook={() => {
eSendEvent(eOpenAddNotebookDialog, params.current.item);
}}
notebook={params.current.item}
/>
}
placeholderData={PLACEHOLDER_DATA}
/>
<FloatingButton title="Add new topic" onPress={onPressFloatingButton} />
</DelayLayout>
);
};

View File

@@ -18,12 +18,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { Config } from "react-native-config";
import { db } from "../../common/database";
import { FloatingButton } from "../../components/container/floating-button";
import DelayLayout from "../../components/delay-layout";
import { AddNotebookEvent } from "../../components/dialog-provider/recievers";
import List from "../../components/list";
import { AddNotebookSheet } from "../../components/sheets/add-notebook";
import { Walkthrough } from "../../components/walkthroughs";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import Navigation, { NavigationProps } from "../../services/navigation";
@@ -31,9 +30,10 @@ import SearchService from "../../services/search";
import SettingsService from "../../services/settings";
import useNavigationStore from "../../stores/use-navigation-store";
import { useNotebookStore } from "../../stores/use-notebook-store";
import { Config } from "react-native-config";
const onPressFloatingButton = () => {
AddNotebookSheet.present();
AddNotebookEvent();
};
const prepareSearch = () => {
@@ -83,7 +83,7 @@ export const Notebooks = ({
});
return (
<DelayLayout delay={1}>
<DelayLayout>
<List
listData={notebooks}
type="notebooks"

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, eOnTopicSheetUpdate } from "../../utils/events";
import { eOnLoadNote } from "../../utils/events";
import { openLinkInBrowser } from "../../utils/functions";
import { tabBarRef } from "../../utils/global-refs";
import { TopicType } from "../../utils/types";
@@ -81,31 +81,12 @@ export const setOnFirstSave = (
editorState().onNoteCreated = null;
return;
}
setTimeout(() => {
editorState().onNoteCreated = (id) => onNoteCreated(id, data);
}, 0);
editorState().onNoteCreated = (id) => onNoteCreated(id, data);
};
async function onNoteCreated(id: string, params: FirstSaveData) {
if (!params) return;
switch (params.type) {
case "notebook": {
await db.relations?.add(
{ type: "notebook", id: params.id },
{ type: "note", id: id }
);
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes",
"Notebook",
"Notebooks"
);
editorState().onNoteCreated = null;
break;
}
case "topic": {
if (!params.notebook) break;
await db.notes?.addToNotebook(
@@ -125,7 +106,6 @@ async function onNoteCreated(id: string, params: FirstSaveData) {
"Notebook",
"Notebooks"
);
eSendEvent(eOnTopicSheetUpdate);
break;
}
case "tag": {

View File

@@ -42,13 +42,6 @@ import {
setOnFirstSave,
toCamelCase
} from "./common";
import { View } from "react-native";
import { db } from "../../common/database";
import Paragraph from "../../components/ui/typography/paragraph";
import { IconButton } from "../../components/ui/icon-button";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import Notebook from "../notebook/index";
export const WARNING_DATA = {
title: "Some notes in this topic are not synced"
};
@@ -99,18 +92,13 @@ const NotesPage = ({
}: RouteProps<
"NotesPage" | "TaggedNotes" | "Monographs" | "ColoredNotes" | "TopicNotes"
>) => {
const colors = useThemeStore((state) => state.colors);
const params = useRef<NotesScreenParams>(route?.params);
const [notes, setNotes] = useState<NoteType[]>(get(route.params, true));
const loading = useNoteStore((state) => state.loading);
const [loadingNotes, setLoadingNotes] = useState(false);
const alias = getAlias(params.current);
const isMonograph = route.name === "Monographs";
const notebook =
route.name === "TopicNotes" && (params.current.item as TopicType).notebookId
? db.notebooks?.notebook((params.current.item as TopicType).notebookId)
?.data
: null;
const isFocused = useNavigationFocus(navigation, {
onFocus: (prev) => {
Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
@@ -188,7 +176,6 @@ const NotesPage = ({
) {
return Navigation.goBack();
}
if (notes.length === 0) setLoadingNotes(false);
setNotes(notes);
syncWithNavigation();
} catch (e) {
@@ -200,7 +187,7 @@ const NotesPage = ({
useEffect(() => {
if (loadingNotes) {
setTimeout(() => setLoadingNotes(false), 50);
setTimeout(() => setLoadingNotes(false), 300);
}
}, [loadingNotes, notes]);
@@ -221,45 +208,6 @@ const NotesPage = ({
}
wait={loading || loadingNotes}
>
{route.name === "TopicNotes" ? (
<View
style={{
width: "100%",
paddingHorizontal: 12,
flexDirection: "row",
alignItems: "center"
// borderBottomWidth: 1,
// borderBottomColor: colors.nav
}}
>
<Paragraph
onPress={() => {
Navigation.navigate(
{
name: "Notebooks"
},
{}
);
}}
size={SIZE.xs}
>
Notebooks
</Paragraph>
<IconButton
name="chevron-right"
size={14}
customStyle={{ width: 25, height: 25 }}
/>
<Paragraph
onPress={() => {
Notebook.navigate(notebook, true);
}}
size={SIZE.xs}
>
{notebook.title}
</Paragraph>
</View>
) : null}
<List
listData={notes}
type="notes"

View File

@@ -58,18 +58,16 @@ export const TopicNotes = ({
route
}: NavigationProps<"TopicNotes">) => {
return (
<>
<NotesPage
navigation={navigation}
route={route}
get={TopicNotes.get}
placeholderData={PLACEHOLDER_DATA}
onPressFloatingButton={openEditor}
rightButtons={headerRightButtons}
canGoBack={route.params.canGoBack}
focusControl={true}
/>
</>
<NotesPage
navigation={navigation}
route={route}
get={TopicNotes.get}
placeholderData={PLACEHOLDER_DATA}
onPressFloatingButton={openEditor}
rightButtons={headerRightButtons}
canGoBack={route.params.canGoBack}
focusControl={true}
/>
);
};

View File

@@ -25,7 +25,6 @@ import { ConfigureToolbar } from "./editor/configure-toolbar";
import { Licenses } from "./licenses";
import SoundPicker from "./sound-picker";
import { Subscription } from "./subscription";
import { TrashIntervalSelector } from "./trash-interval-selector";
export const components: { [name: string]: ReactElement } = {
colorpicker: <AccentColorPicker />,
homeselector: <HomagePageSelector />,
@@ -34,6 +33,5 @@ export const components: { [name: string]: ReactElement } = {
configuretoolbar: <ConfigureToolbar />,
"debug-logs": <DebugLogs />,
"sound-picker": <SoundPicker />,
licenses: <Licenses />,
"trash-interval-selector": <TrashIntervalSelector />
licenses: <Licenses />
};

View File

@@ -106,6 +106,12 @@ 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

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React from "react";
import { LICENSES } from "./license-data";
import { FlatList, Linking, Platform } from "react-native";
import { FlatList, Linking } from "react-native";
import { PressableButton } from "../../components/ui/pressable";
import Heading from "../../components/ui/typography/heading";
import { SIZE } from "../../utils/size";
@@ -34,11 +34,6 @@ type LicenseEntry = {
export const Licenses = () => {
const colors = useThemeStore((state) => state.colors);
const items =
Platform.OS === "ios"
? LICENSES.filter((l) => l.name.indexOf("android") === -1)
: LICENSES;
const renderItem = React.useCallback(
({ item }: { item: LicenseEntry }) => (
<PressableButton
@@ -53,7 +48,6 @@ export const Licenses = () => {
borderRadius: 0
}}
onPress={() => {
if (!item.link) return;
Linking.openURL(item.link).catch(console.log);
}}
>
@@ -67,7 +61,7 @@ export const Licenses = () => {
);
return (
<FlatList
data={items}
data={LICENSES}
style={{
width: "100%"
}}

View File

@@ -17,7 +17,6 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import notifee from "@notifee/react-native";
import dayjs from "dayjs";
import React from "react";
import { Linking, Platform } from "react-native";
@@ -26,7 +25,6 @@ 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";
@@ -58,6 +56,7 @@ import { SUBSCRIPTION_STATUS } from "../../utils/constants";
import {
eCloseSheet,
eCloseSimpleDialog,
eOpenAttachmentsDialog,
eOpenLoginDialog,
eOpenRecoveryKeyDialog,
eOpenRestoreDialog
@@ -69,6 +68,7 @@ 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: () => {
AttachmentDialog.present();
eSendEvent(eOpenAttachmentsDialog);
},
description: "Manage all attachments in one place."
},
@@ -523,14 +523,6 @@ export const settingsGroups: SettingSection[] = [
name: "Homepage",
description: "Default screen to open on app startup",
component: "homeselector"
},
{
id: "clear-trash-interval",
type: "component",
name: "Clear trash interval",
description:
"Select the duration after which trash items will be cleared",
component: "trash-interval-selector"
}
]
}

View File

@@ -1,117 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useRef, useState } from "react";
import { View } from "react-native";
import Menu, { MenuItem } from "react-native-reanimated-material-menu";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import { PressableButton } from "../../components/ui/pressable";
import Paragraph from "../../components/ui/typography/paragraph";
import PremiumService from "../../services/premium";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
export const TrashIntervalSelector = () => {
const colors = useThemeStore((state) => state.colors);
const [trashInterval, setTrashInterval] = useState(
db.settings.getTrashCleanupInterval()
);
const menuRef = useRef();
const [width, setWidth] = useState(0);
const onChange = (item) => {
menuRef.current?.hide();
setTrashInterval(item);
db.settings.setTrashCleanupInterval(item);
};
return (
<View
onLayout={(event) => {
setWidth(event.nativeEvent.layout.width);
}}
style={{
width: "100%"
}}
>
<Menu
ref={menuRef}
animationDuration={200}
style={{
borderRadius: 5,
backgroundColor: colors.bg,
width: width,
marginTop: 60
}}
onRequestClose={() => {
menuRef.current?.hide();
}}
anchor={
<PressableButton
onPress={async () => {
menuRef.current?.show();
}}
type="grayBg"
customStyle={{
flexDirection: "row",
alignItems: "center",
marginTop: 10,
width: "100%",
justifyContent: "space-between",
padding: 12
}}
>
<Paragraph>
{trashInterval === -1 ? "Never" : trashInterval + " days"}
</Paragraph>
<Icon color={colors.icon} name="menu-down" size={SIZE.md} />
</PressableButton>
}
>
{[-1, 7, 30, 365].map((item) => (
<MenuItem
key={item.name}
onPress={async () => {
if (item === -1) {
await PremiumService.verify(() => {
onChange(item);
});
return;
}
onChange(item);
}}
style={{
backgroundColor:
trashInterval === item ? colors.nav : "transparent",
width: "100%",
maxWidth: width
}}
textStyle={{
fontSize: SIZE.md,
color: trashInterval === item ? colors.accent : colors.pri
}}
>
{item === -1 ? "Never" : item + " days"}
</MenuItem>
))}
</Menu>
</View>
);
};

View File

@@ -17,10 +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 EventManager from "@notesnook/core/utils/event-manager";
import Clipboard from "@react-native-clipboard/clipboard";
import EventManager from "@notesnook/core/utils/event-manager";
import { RefObject } from "react";
import { ActionSheetRef } from "react-native-actions-sheet";
import ActionSheet from "react-native-actions-sheet";
import Config from "react-native-config";
import {
eCloseSheet,
@@ -97,7 +97,7 @@ export type PresentSheetOptions = {
component:
| JSX.Element
| ((
ref: RefObject<ActionSheetRef>,
ref: RefObject<ActionSheet>,
close?: (ctx?: string) => void,
update?: (props: PresentSheetOptions) => void
) => JSX.Element);
@@ -114,8 +114,6 @@ export type PresentSheetOptions = {
actionsArray: SheetAction[];
learnMore: string;
learnMorePress: () => void;
enableGesturesInScrollView?: boolean;
noBottomPadding?: boolean;
};
export function presentSheet(data: Partial<PresentSheetOptions>) {

View File

@@ -85,7 +85,7 @@ async function save(path, data, fileName, extension) {
uri = await ScopedStorage.writeFile(
path,
data,
`${fileName}.${extension}`,
fileName + `.${extension}`,
MIMETypes[extension],
extension === "pdf" ? "base64" : "utf8",
false
@@ -124,6 +124,7 @@ async function exportAs(type, note, bulk) {
case "pdf":
{
let html = await makeHtml(note);
console.log(html);
let fileName = sanitizeFilename(note.title + Date.now(), {
replacement: "_"
});
@@ -196,19 +197,6 @@ function zipsync(results) {
return Buffer.from(data.buffer).toString("base64");
}
function getUniqueFileName(fileName, results) {
const chunks = fileName.split(".");
const ext = chunks.pop();
const name = chunks.join(".");
let resolvedName = fileName;
let count = 0;
while (results[resolvedName]) {
resolvedName = `${name}${++count}.${ext}`;
}
return resolvedName;
}
/**
*
* @param {"txt" | "pdf" | "md" | "html"} type
@@ -224,12 +212,14 @@ async function bulkExport(notes, type, callback) {
let note = notes[i];
if (note.locked) continue;
let result = await exportAs(type, note);
let fileName = sanitizeFilename(note.title, {
let fileName = sanitizeFilename(note.title + Date.now(), {
replacement: "_"
});
if (result) {
results[getUniqueFileName(fileName + `.${type}`, results)] =
Buffer.from(result, type === "pdf" ? "base64" : "utf-8");
results[fileName + `.${type}`] = Buffer.from(
result,
type === "pdf" ? "base64" : "utf-8"
);
}
callback(`${i + 1}/${notes.length}`);
} catch (e) {

View File

@@ -99,6 +99,7 @@ const onEvent = async ({ type, detail }: Event) => {
if (type === EventType.PRESS) {
notifee.decrementBadgeCount();
if (notification?.data?.type === "quickNote") return;
editorState().movedAway = false;
MMKV.removeItem("appState");
await db.init();
await db.notes?.init();
@@ -108,9 +109,8 @@ const onEvent = async ({ type, detail }: Event) => {
const ReminderNotify =
require("../components/sheets/reminder-notify").default;
ReminderNotify.present(reminder);
return;
}
editorState().movedAway = false;
const noteId = notification?.id;
if (useNoteStore?.getState()?.loading === false) {
loadNote(noteId as string, false);

View File

@@ -145,23 +145,23 @@ export const useTip = (
const tips: TTip[] = [
{
text: "You can swipe left anywhere in the app to start a new note.",
text: "You can swipe left anywhere in the app to start a new note",
contexts: ["notes", "first-note"]
},
{
text: "Long press on any item in list to enter multi-select mode.",
text: "Long press on any item in list to open quick actions menu.",
contexts: ["notes", "notebook", "notebook", "tags", "topics"]
},
{
text: "Monographs enable you to share your notes in a secure and private way.",
text: "Monographs enable you to share your notes in a secure and private way",
contexts: ["monographs"]
},
{
text: "Monographs can be encrypted with a secret key and shared with anyone.",
text: "Monographs can be encrypted with a secret key and shared with anyone",
contexts: ["monographs"]
},
{
text: "You can pin frequently used Notebooks to the Side Menu to quickly access them.",
text: "Frequently accessed notebooks can be pinned to Side Menu so that they are easily accessible",
contexts: ["notebook", "notebooks"]
},
{
@@ -173,15 +173,15 @@ const tips: TTip[] = [
contexts: ["notebook", "topics"]
},
{
text: "Mark important notes by adding them to favorites.",
text: "Items in trash are kept for 7 days after which they are permanently deleted.",
contexts: ["trash"]
},
{
text: "Mark important notes by adding them to favorites",
contexts: ["notes"]
},
{
text: "Are you scrolling a lot to find a specific note? Pin it to the top from Note properties.",
contexts: ["notes"]
},
{
text: "You can view & restore older versions of any note by going to its properties -> History.",
text: "Have to scroll down a lot to open a note you are working on? Pin it to top from properties.",
contexts: ["notes"]
}
];

View File

@@ -47,9 +47,8 @@ export const useUserStore = create<UserStore>((set) => ({
verifyUser: false,
setUser: (user) => set({ user: user }),
setPremium: (premium) => set({ premium: premium }),
setSyncing: (syncing, status = SyncStatus.Passed) => {
set({ syncing: syncing, lastSyncStatus: status });
},
setSyncing: (syncing, status = SyncStatus.Passed) =>
set({ syncing: syncing, lastSyncStatus: status }),
setLastSynced: (lastSynced) => set({ lastSynced: lastSynced }),
setVerifyUser: (verified) => set({ verifyUser: verified }),
lastSyncStatus: SyncStatus.Never

View File

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

View File

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

View File

@@ -29,28 +29,22 @@ import { eClearEditor } from "./events";
import { useRelationStore } from "../stores/use-relation-store";
import { presentDialog } from "../components/dialog/functions";
function deleteConfirmDialog(items, type, context) {
function deleteNotesConfirmDialog(items, type, context) {
return new Promise((resolve) => {
presentDialog({
title: `Delete ${
items.length > 1 ? `${items.length} ${type}s` : `${type}`
title: "Delete Contained Notes?",
paragraph: `Do you want to delete notes within ${
items.length > 1 ? `these ${type}s` : `this ${type}`
}?`,
positiveText: "Delete",
negativeText: "Cancel",
positivePress: (value) => {
console.log(value);
resolve({ delete: true, deleteNotes: value });
positiveText: "Yes",
negativeText: "No",
positivePress: () => {
resolve(true);
},
onClose: () => {
resolve({ delete: false });
resolve(false);
},
context: context,
check: {
info: `Move all notes in ${
items.length > 1 ? `these ${type}s` : `this ${type}`
} to trash`,
type: "transparent"
}
context: context
});
});
}
@@ -113,49 +107,50 @@ export const deleteItems = async (item, context) => {
}
if (topics?.length > 0) {
const result = await deleteConfirmDialog(topics, "topic", context);
if (result.delete) {
for (const topic of topics) {
if (result.deleteNotes) {
const deleteNotes = await deleteNotesConfirmDialog(
topics,
"topic",
context
);
for (const topic of topics) {
if (deleteNotes) {
const notes = db.notebooks
.notebook(topic.notebookId)
.topics.topic(topic.id).all;
await db.notes.delete(...notes.map((note) => note.id));
}
await db.notebooks.notebook(topic.notebookId).topics.delete(topic.id);
}
routesForUpdate.push("Notebook", "Notebooks");
useMenuStore.getState().setMenuPins();
ToastEvent.show({
heading: `${topics.length > 1 ? "Topics" : "Topic"} deleted`,
type: "success"
});
}
if (notebooks?.length > 0) {
const deleteNotes = await deleteNotesConfirmDialog(
notebooks,
"notebook",
context
);
let ids = notebooks.map((i) => i.id);
if (deleteNotes) {
for (let id of ids) {
const topics = db.notebooks.notebook(id).topics.all;
for (let topic of topics) {
const notes = db.notebooks
.notebook(topic.notebookId)
.topics.topic(topic.id).all;
await db.notes.delete(...notes.map((note) => note.id));
}
await db.notebooks.notebook(topic.notebookId).topics.delete(topic.id);
}
routesForUpdate.push("Notebook", "Notebooks");
useMenuStore.getState().setMenuPins();
ToastEvent.show({
heading: `${topics.length > 1 ? "Topics" : "Topic"} deleted`,
type: "success"
});
}
}
if (notebooks?.length > 0) {
const result = await deleteConfirmDialog(notebooks, "notebook", context);
if (result.delete) {
let ids = notebooks.map((i) => i.id);
if (result.deleteNotes) {
for (let id of ids) {
const notebook = db.notebooks.notebook(id);
const topics = notebook.topics.all;
for (let topic of topics) {
const notes = db.notebooks
.notebook(topic.notebookId)
.topics.topic(topic.id).all;
await db.notes.delete(...notes.map((note) => note.id));
}
const notes = db.relations.from(notebook.data, "note");
await db.notes.delete(...notes.map((note) => note.id));
}
}
await db.notebooks.delete(...ids);
routesForUpdate.push("Notebook", "Notebooks");
useMenuStore.getState().setMenuPins();
}
await db.notebooks.delete(...ids);
routesForUpdate.push("Notebook", "Notebooks");
useMenuStore.getState().setMenuPins();
}
Navigation.queueRoutesForUpdate(...routesForUpdate);

View File

@@ -51,7 +51,7 @@ var illegalRe = /[/?<>\\:*|"]/g;
var reservedRe = /^\.+$/;
var windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
var windowsTrailingRe = /[. ]+$/;
var whitespace = /\s+/g;
var whitespace = /\W+/g;
function sanitize(input, replacement) {
if (typeof input !== "string") {

View File

@@ -29,8 +29,7 @@ import {
navigate,
elementByText,
sleep,
notVisibleByText,
visibleById
notVisibleByText
} from "./utils";
async function createNotebook(
@@ -129,11 +128,11 @@ describe("NOTEBOOKS", () => {
await device.pressBack();
await sleep(500);
await tapByText("Notebook 1");
await tapById("add-topic-button");
await tapById(notesnook.buttons.add);
await elementById("input-title").typeText("Topic");
await tapByText("Add");
await sleep(500);
await visibleById("topic-sheet-item-0");
await visibleByText("Topic");
});
it("Edit topic", async () => {
@@ -146,11 +145,12 @@ describe("NOTEBOOKS", () => {
await sleep(500);
await tapByText("Notebook 1");
await sleep(300);
await visibleById("topic-sheet-item-0");
await visibleByText("Topic");
await tapById(notesnook.ids.notebook.menu);
await tapByText("Edit topic");
await elementById("input-title").typeText(" (edited)");
await tapByText("Save");
await visibleByText("Topic (edited)");
});
it("Add new note to topic", async () => {
@@ -175,11 +175,12 @@ describe("NOTEBOOKS", () => {
await tapByText("Topic");
let note = await createNote();
await elementByText(note.body).longPress();
await tapByText("Select");
await tapById("select-minus");
await notVisibleById(note.title);
});
it("Add/Remove note to notebook from home", async () => {
it.only("Add/Remove note to notebook from home", async () => {
await prepare();
await navigate("Notebooks");
await sleep(500);
@@ -191,31 +192,31 @@ describe("NOTEBOOKS", () => {
await createNote();
console.log("ADD TO A SINGLE TOPIC");
await tapById(notesnook.listitem.menu);
await tapById("icon-notebooks");
await tapById("icon-Add to notebook");
await sleep(500);
await tapByText("Notebook 1");
await tapByText("Topic");
await tapByText("Save");
await sleep(300);
await visibleByText("Topic");
await visibleByText("Notebook 1 Topic");
console.log("MOVE FROM ONE TOPIC TO ANOTHER");
await tapById(notesnook.listitem.menu);
await tapById("icon-notebooks");
await tapById("icon-Add to notebook");
await tapByText("Notebook 1");
await tapByText("Topic 2");
await tapByText("Save");
await visibleByText("Topic 2");
await visibleByText("Notebook 1 Topic 2");
console.log("REMOVE FROM TOPIC");
await tapById(notesnook.listitem.menu);
await tapById("icon-notebooks");
await tapById("icon-Add to notebook");
await tapByText("Notebook 1");
await tapByText("Topic 2");
await tapByText("Save");
await sleep(300);
await notVisibleByText("Topic 2");
await notVisibleByText("Notebook 1 Topic 2");
console.log("MOVE TO MULTIPLE TOPICS");
await tapById(notesnook.listitem.menu);
await tapById("icon-notebooks");
await tapById("icon-Add to notebook");
await tapByText("Notebook 1");
await elementByText("Topic").longPress();
await visibleByText("Reset selection");

View File

@@ -140,7 +140,8 @@ def reactNativeArchitectures() {
}
def fdroidBuild() {
return project.hasProperty("fdroidBuild") && project.fdroidBuild == "true"
def value = project.getProperties().get("fdroidBuild")
return value ? value : false
}
def getNpmVersion() {
@@ -164,7 +165,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled true
versionCode 2039
versionCode 2027
versionName getNpmVersion()
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'

View File

@@ -1,4 +1,9 @@
- Fix note publishing not working with password protection
- Some fixes and improvements in notebook/topic linking
- Improved UX for linking Notebooks
- Redesign properties sheet to be simple & handy
- Added support for RTL languages in editor
- Telemetery is now opt-in
- Get default reminder title/description from note
- Fix unable to input recovery code on 2FA sheet
- Bug fixes & small improvements
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 = 2031;
CURRENT_PROJECT_VERSION = 2027;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1157,7 +1157,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.4.8;
MARKETING_VERSION = 2.4.4;
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 = 2031;
CURRENT_PROJECT_VERSION = 2027;
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.8;
MARKETING_VERSION = 2.4.4;
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 = 2031;
CURRENT_PROJECT_VERSION = 2027;
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.8;
MARKETING_VERSION = 2.4.4;
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 = 2031;
CURRENT_PROJECT_VERSION = 2027;
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.8;
MARKETING_VERSION = 2.4.4;
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 = 2031;
CURRENT_PROJECT_VERSION = 2027;
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.8;
MARKETING_VERSION = 2.4.4;
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 = 2031;
CURRENT_PROJECT_VERSION = 2027;
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.8;
MARKETING_VERSION = 2.4.4;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -254,8 +254,6 @@ PODS:
- React
- react-native-image-picker (4.1.2):
- React-Core
- react-native-image-resizer (3.0.5):
- React-Core
- react-native-keep-awake (1.1.0):
- React-Core
- react-native-mmkv-storage (0.8.0):
@@ -273,7 +271,7 @@ PODS:
- RCTTypeSafety
- React-Core
- ReactCommon/turbomodule/core
- react-native-sodium (1.3.0):
- react-native-sodium (1.2.0):
- React
- react-native-webview (11.23.1):
- React-Core
@@ -371,10 +369,10 @@ PODS:
- React-Core
- RNKeychain (4.0.5):
- React
- RNNotifee (7.4.4):
- RNNotifee (7.4.3):
- React-Core
- RNNotifee/NotifeeCore (= 7.4.4)
- RNNotifee/NotifeeCore (7.4.4):
- RNNotifee/NotifeeCore (= 7.4.3)
- RNNotifee/NotifeeCore (7.4.3):
- React-Core
- RNPrivacySnapshot (1.0.0):
- React-Core
@@ -461,7 +459,6 @@ DEPENDENCIES:
- react-native-gzip (from `../../node_modules/react-native-gzip`)
- react-native-html-to-pdf-lite (from `../../node_modules/react-native-html-to-pdf-lite`)
- react-native-image-picker (from `../../node_modules/react-native-image-picker`)
- "react-native-image-resizer (from `../../node_modules/@bam.tech/react-native-image-resizer`)"
- "react-native-keep-awake (from `../../node_modules/@sayem314/react-native-keep-awake`)"
- react-native-mmkv-storage (from `../../node_modules/react-native-mmkv-storage`)
- "react-native-netinfo (from `../../node_modules/@react-native-community/netinfo`)"
@@ -581,8 +578,6 @@ EXTERNAL SOURCES:
:path: "../../node_modules/react-native-html-to-pdf-lite"
react-native-image-picker:
:path: "../../node_modules/react-native-image-picker"
react-native-image-resizer:
:path: "../../node_modules/@bam.tech/react-native-image-resizer"
react-native-keep-awake:
:path: "../../node_modules/@sayem314/react-native-keep-awake"
react-native-mmkv-storage:
@@ -719,14 +714,13 @@ SPEC CHECKSUMS:
react-native-gzip: 02f9968afa759e189f0414d41f8f4a951a86b4f1
react-native-html-to-pdf-lite: 21bfb169bf4cbcd7bec9f736975ee1b3f5292d4a
react-native-image-picker: 9c8a2687b69300ad9e95cec5d38f35ab9d32467d
react-native-image-resizer: 00ceb0e05586c7aadf061eea676957a6c2ec60fa
react-native-keep-awake: acbee258db16483744910f0da3ace39eb9ab47fd
react-native-mmkv-storage: 8ba3c0216a6df283ece11205b442a3e435aec4e5
react-native-netinfo: 2517ad504b3d303e90d7a431b0fcaef76d207983
react-native-notification-sounds: da78c828fe1bcbb92d8b505d5261890ed315ff39
react-native-orientation: f1caf84d65f1a4fd4511a18f2b924e634ad7a628
react-native-safe-area-context: 99b24a0c5acd0d5dcac2b1a7f18c49ea317be99a
react-native-sodium: 1681828855ec18fa952f4557cd595bf048cf5c32
react-native-sodium: 460beee758415bd72cd8d820820fae0bc4076f07
react-native-webview: d33e2db8925d090871ffeb232dfa50cb3a727581
React-perflogger: 8e832d4e21fdfa613033c76d58d7e617341e804b
React-RCTActionSheet: 9ca778182a9523991bff6381045885b6e808bb73
@@ -754,7 +748,7 @@ SPEC CHECKSUMS:
RNGestureHandler: b7a872907ee289ada902127f2554fa1d2c076122
RNIap: d248609d1b8937e63bd904e865c318e9b1457eff
RNKeychain: 840f8e6f13be0576202aefcdffd26a4f54bfe7b5
RNNotifee: 2ae3c18196e6f307fa62ae5c8e5305dea03ff147
RNNotifee: 5dfb0c5783ddb3da47b39e75d06cc7e748d6ca39
RNPrivacySnapshot: 8eaf571478a353f2e5184f5c803164f22428b023
RNReanimated: f1b109fb8341505ace9d7d2eedd150da1686716b
RNScreens: 34cc502acf1b916c582c60003dc3089fa01dc66d

View File

@@ -43,7 +43,7 @@
"react-native-screens": "^3.13.1",
"react-native-securerandom": "^1.0.1",
"react-native-share": "^7.2.0",
"@ammarahmed/react-native-sodium": "1.3.0",
"@ammarahmed/react-native-sodium": "1.2.0",
"react-native-svg": "^12.3.0",
"react-native-tooltips": "^1.0.3",
"react-native-vector-icons": "^9.0.0",
@@ -52,12 +52,11 @@
"rn-fetch-blob": "^0.12.0",
"react-native-gzip":"1.0.0",
"@shopify/flash-list":"1.4.0",
"@ammarahmed/notifee-react-native": "7.4.4",
"@ammarahmed/notifee-react-native": "7.4.3",
"react-native-modal-datetime-picker":"14.0.0",
"@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"
"react-native-notification-sounds": "0.5.5"
},
"devDependencies": {
"@babel/core": "^7.12.9",

View File

@@ -1,4 +1,9 @@
- Fix note publishing not working with password protection
- Some fixes and improvements in notebook/topic linking
- Improved UX for linking Notebooks
- Redesign properties sheet to be simple & handy
- Added support for RTL languages in editor
- Telemetery is now opt-in
- Get default reminder title/description from note
- Fix unable to input recovery code on 2FA sheet
- Bug fixes & small improvements
Thank you for using Notesnook!

View File

@@ -1,12 +1,12 @@
{
"name": "@notesnook/mobile",
"version": "2.4.7",
"version": "2.4.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@notesnook/mobile",
"version": "2.4.7",
"version": "2.4.4",
"license": "GPL-3.0-or-later",
"workspaces": [
"native/",
@@ -40,9 +40,7 @@
"html-to-text": "8.1.0",
"phone": "^3.1.14",
"qclone": "^1.2.0",
"react": "18.0.0",
"react-native": "0.69.7",
"react-native-actions-sheet": "^0.9.0-alpha.14",
"react-native-actions-sheet": "^0.7.2",
"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",
@@ -62,9 +60,8 @@
"version": "1.0.0",
"license": "GPL-3.0-or-later",
"dependencies": {
"@ammarahmed/notifee-react-native": "7.4.4",
"@ammarahmed/react-native-sodium": "1.3.0",
"@bam.tech/react-native-image-resizer": "3.0.5",
"@ammarahmed/notifee-react-native": "7.4.3",
"@ammarahmed/react-native-sodium": "1.2.0",
"@callstack/repack": "^3.0.0",
"@react-native-clipboard/clipboard": "^1.9.0",
"@react-native-community/checkbox": "^0.5.8",
@@ -157,17 +154,17 @@
}
},
"node_modules/@ammarahmed/notifee-react-native": {
"version": "7.4.4",
"resolved": "https://registry.npmjs.org/@ammarahmed/notifee-react-native/-/notifee-react-native-7.4.4.tgz",
"integrity": "sha512-soiJp11voU1MvwmzN66vlbq+NHW7nG3OlKtMPVzJ2O4Q44LP+yrm5u+N8kohMd7jGW14s3E+d27rT7NTzeK+ww==",
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/@ammarahmed/notifee-react-native/-/notifee-react-native-7.4.3.tgz",
"integrity": "sha512-7jz91BZLxsz3v2nOPSnUf0ULa/k+KtkyX8zN2ijeluLHSQ5fjSd5jQ+u+4+Rcja0g0xQze7fpg26q6lA6F2j1g==",
"peerDependencies": {
"react-native": "*"
}
},
"node_modules/@ammarahmed/react-native-sodium": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-sodium/-/react-native-sodium-1.3.0.tgz",
"integrity": "sha512-Z0wjclwl69QttV6kupzY4bHrRX4ijjZMhjhy1P+WodNCrJpSnG4hI1Jne0hjKi0ghajuFZi+yPB8rjnDvsgQjA=="
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-sodium/-/react-native-sodium-1.2.0.tgz",
"integrity": "sha512-KOVTeQMIeo8BXHBABIlYDj9QhhcrVzqxaLRra2gg2pB08V3Nq/fPPVexRf0Ir8q7vChd1+gzEFWjMYlvTIssQg=="
},
"node_modules/@ampproject/remapping": {
"version": "2.2.0",
@@ -2134,15 +2131,6 @@
"node": ">=6.9.0"
}
},
"node_modules/@bam.tech/react-native-image-resizer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@bam.tech/react-native-image-resizer/-/react-native-image-resizer-3.0.5.tgz",
"integrity": "sha512-u5QGUQGGVZiVCJ786k9/kd7pPRZ6eYfJCYO18myVCH8FbVI7J8b5GT2Svjj2x808DlWeqfaZOOzxPqo27XYvrQ==",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/@bcoe/v8-coverage": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
@@ -17957,12 +17945,11 @@
}
},
"node_modules/react-native-actions-sheet": {
"version": "0.9.0-alpha.14",
"resolved": "https://registry.npmjs.org/react-native-actions-sheet/-/react-native-actions-sheet-0.9.0-alpha.14.tgz",
"integrity": "sha512-O1fktAenv2+yv/j72Z/jKsNFMMv6pobcavokbmWY/xcOJqHgo7YNtKZ0xU1s6KTk6VitH7TQXkP22hmM7KpQbg==",
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/react-native-actions-sheet/-/react-native-actions-sheet-0.7.2.tgz",
"integrity": "sha512-au9QkDnSC+lhiTMHYA2cNdOhrKW/6v/vdeOTNigRFuvYoVVS2+vJOJpt2Z3mumRjmD02UocV0WmHu4anSsqqpA==",
"peerDependencies": {
"react-native": "*",
"react-native-gesture-handler": "*"
"react-native": "*"
}
},
"node_modules/react-native-actions-shortcuts": {
@@ -21523,14 +21510,14 @@
},
"dependencies": {
"@ammarahmed/notifee-react-native": {
"version": "7.4.4",
"resolved": "https://registry.npmjs.org/@ammarahmed/notifee-react-native/-/notifee-react-native-7.4.4.tgz",
"integrity": "sha512-soiJp11voU1MvwmzN66vlbq+NHW7nG3OlKtMPVzJ2O4Q44LP+yrm5u+N8kohMd7jGW14s3E+d27rT7NTzeK+ww=="
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/@ammarahmed/notifee-react-native/-/notifee-react-native-7.4.3.tgz",
"integrity": "sha512-7jz91BZLxsz3v2nOPSnUf0ULa/k+KtkyX8zN2ijeluLHSQ5fjSd5jQ+u+4+Rcja0g0xQze7fpg26q6lA6F2j1g=="
},
"@ammarahmed/react-native-sodium": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-sodium/-/react-native-sodium-1.3.0.tgz",
"integrity": "sha512-Z0wjclwl69QttV6kupzY4bHrRX4ijjZMhjhy1P+WodNCrJpSnG4hI1Jne0hjKi0ghajuFZi+yPB8rjnDvsgQjA=="
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ammarahmed/react-native-sodium/-/react-native-sodium-1.2.0.tgz",
"integrity": "sha512-KOVTeQMIeo8BXHBABIlYDj9QhhcrVzqxaLRra2gg2pB08V3Nq/fPPVexRf0Ir8q7vChd1+gzEFWjMYlvTIssQg=="
},
"@ampproject/remapping": {
"version": "2.2.0",
@@ -22869,11 +22856,6 @@
"to-fast-properties": "^2.0.0"
}
},
"@bam.tech/react-native-image-resizer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@bam.tech/react-native-image-resizer/-/react-native-image-resizer-3.0.5.tgz",
"integrity": "sha512-u5QGUQGGVZiVCJ786k9/kd7pPRZ6eYfJCYO18myVCH8FbVI7J8b5GT2Svjj2x808DlWeqfaZOOzxPqo27XYvrQ=="
},
"@bcoe/v8-coverage": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
@@ -24310,9 +24292,7 @@
"html-to-text": "8.1.0",
"phone": "^3.1.14",
"qclone": "^1.2.0",
"react": "18.0.0",
"react-native": "0.69.7",
"react-native-actions-sheet": "^0.9.0-alpha.14",
"react-native-actions-sheet": "^0.7.2",
"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",
@@ -24330,14 +24310,13 @@
"@notesnook/mobile-native": {
"version": "file:native",
"requires": {
"@ammarahmed/notifee-react-native": "7.4.4",
"@ammarahmed/react-native-sodium": "1.3.0",
"@ammarahmed/notifee-react-native": "7.4.3",
"@ammarahmed/react-native-sodium": "1.2.0",
"@babel/core": "^7.12.9",
"@babel/eslint-parser": "^7.16.5",
"@babel/plugin-transform-named-capturing-groups-regex": "^7.16.5",
"@babel/preset-env": "^7.18.6",
"@babel/runtime": "^7.12.5",
"@bam.tech/react-native-image-resizer": "3.0.5",
"@callstack/repack": "^3.0.0",
"@react-native-clipboard/clipboard": "^1.9.0",
"@react-native-community/checkbox": "^0.5.8",
@@ -34856,9 +34835,9 @@
}
},
"react-native-actions-sheet": {
"version": "0.9.0-alpha.14",
"resolved": "https://registry.npmjs.org/react-native-actions-sheet/-/react-native-actions-sheet-0.9.0-alpha.14.tgz",
"integrity": "sha512-O1fktAenv2+yv/j72Z/jKsNFMMv6pobcavokbmWY/xcOJqHgo7YNtKZ0xU1s6KTk6VitH7TQXkP22hmM7KpQbg=="
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/react-native-actions-sheet/-/react-native-actions-sheet-0.7.2.tgz",
"integrity": "sha512-au9QkDnSC+lhiTMHYA2cNdOhrKW/6v/vdeOTNigRFuvYoVVS2+vJOJpt2Z3mumRjmD02UocV0WmHu4anSsqqpA=="
},
"react-native-actions-shortcuts": {
"version": "1.0.1",

View File

@@ -1,6 +1,6 @@
{
"name": "@notesnook/mobile",
"version": "2.4.8",
"version": "2.4.4",
"private": true,
"license": "GPL-3.0-or-later",
"workspaces": [
@@ -15,7 +15,7 @@
"repack": "cd native && react-native webpack-start",
"install-pods": "cd native/ios && pod install",
"build-ios": "cd native && detox build -c ios.sim.release",
"build-android": " cd native && detox build -c android.emu.release",
"build-android": "cd native && detox build -c android.emu.release",
"e2e-android": "cd native && detox test --configuration android.emu.release --detectOpenHandles",
"e2e-ios": "cd native && detox test -c ios.sim.release --detectOpenHandles",
"bump": "cd native && npx react-native bump-version --skip-semver-for android",

View File

@@ -1,28 +1,16 @@
diff --git a/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js b/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
index 4536402..2a100d9 100644
index 4536402..5ceaf65 100644
--- a/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
+++ b/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
@@ -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);
@@ -82,6 +82,7 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
- // 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
// 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 +94,9 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
@@ -92,7 +93,9 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
_onLayout = (event: ViewLayoutEvent) => {
const wasFrameNull = this._frame == null;
@@ -32,19 +20,6 @@ index 4536402..2a100d9 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
@@ -69,13 +44,6 @@ 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

@@ -0,0 +1,21 @@
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>
</>);

View File

@@ -132,7 +132,7 @@ export class AppModel {
.waitFor({ state: "visible" });
}
async search(query: string, type: string) {
async search(query: string) {
const searchinput = this.page.locator(getTestId("search-input"));
const searchButton = this.page.locator(getTestId("search-button"));
const openSearch = this.page.locator(getTestId("open-search"));
@@ -140,6 +140,6 @@ export class AppModel {
await openSearch.click();
await searchinput.fill(query);
await searchButton.click();
return new SearchViewModel(this.page, type);
return new SearchViewModel(this.page);
}
}

View File

@@ -29,17 +29,14 @@ export class BaseViewModel {
private readonly listPlaceholder: Locator;
private readonly sortByButton: Locator;
constructor(page: Page, pageId: string, listType: string) {
constructor(page: Page, pageId: string) {
this.page = page;
this.list = page.locator(`#${pageId} >> ${getTestId(`${listType}-list`)}`);
this.list = page.locator(`#${pageId} >> ${getTestId("note-list")}`);
this.listPlaceholder = page.locator(
`#${pageId} >> ${getTestId("list-placeholder")}`
);
this.sortByButton = this.page.locator(
// TODO:
getTestId(`${pageId === "notebook" ? "notes" : pageId}-sort-button`)
);
this.sortByButton = this.list.locator(getTestId("sort-icon-button"));
}
async findGroup(groupName: string) {
@@ -106,15 +103,13 @@ export class BaseViewModel {
async sort(sort: SortOptions) {
const contextMenu: ContextMenuModel = new ContextMenuModel(this.page);
if (sort.groupBy) {
await contextMenu.open(this.sortByButton, "left");
await contextMenu.clickOnItem("groupBy");
if (!(await contextMenu.hasItem(sort.groupBy))) {
await contextMenu.close();
return false;
}
await contextMenu.clickOnItem(sort.groupBy);
await contextMenu.open(this.sortByButton, "left");
await contextMenu.clickOnItem("groupBy");
if (!(await contextMenu.hasItem(sort.groupBy))) {
await contextMenu.close();
return false;
}
await contextMenu.clickOnItem(sort.groupBy);
await contextMenu.open(this.sortByButton, "left");
await contextMenu.clickOnItem("sortDirection");
@@ -136,10 +131,7 @@ export class BaseViewModel {
}
async isEmpty() {
const items = this.list.locator(
`${getTestId(`virtuoso-item-list`)} >> ${getTestId("list-item")}`
);
const totalItems = await items.count();
const totalItems = await this.list.locator(getTestId("list-item")).count();
return totalItems <= 0;
}
}

View File

@@ -22,21 +22,18 @@ import { BaseItemModel } from "./base-item.model";
import { ContextMenuModel } from "./context-menu.model";
import { NotesViewModel } from "./notes-view.model";
import { Item } from "./types";
import { confirmDialog, fillItemDialog } from "./utils";
import { confirmDialog, denyDialog, fillItemDialog } from "./utils";
export class ItemModel extends BaseItemModel {
private readonly contextMenu: ContextMenuModel;
constructor(locator: Locator, private readonly id: "topic" | "tag") {
constructor(locator: Locator) {
super(locator);
this.contextMenu = new ContextMenuModel(this.page);
}
async open() {
await this.locator.click();
return new NotesViewModel(
this.page,
this.id === "topic" ? "notebook" : "notes"
);
return new NotesViewModel(this.page, "notes");
}
async delete() {
@@ -50,10 +47,9 @@ export class ItemModel extends BaseItemModel {
await this.contextMenu.open(this.locator);
await this.contextMenu.clickOnItem("delete");
if (deleteContainedNotes)
await this.page.locator("#deleteContainingNotes").check({ force: true });
if (deleteContainedNotes) await confirmDialog(this.page);
else await denyDialog(this.page);
await confirmDialog(this.page);
await this.waitFor("detached");
}

View File

@@ -28,7 +28,7 @@ export class ItemsViewModel extends BaseViewModel {
private readonly createButton: Locator;
constructor(page: Page, private readonly id: "topics" | "tags") {
super(page, id, id);
super(page, id);
this.createButton = page.locator(getTestId(`${id}-action-button`));
}
@@ -45,11 +45,7 @@ export class ItemsViewModel extends BaseViewModel {
async findItem(item: Item) {
const titleToCompare = this.id === "tags" ? `#${item.title}` : item.title;
for await (const _item of this.iterateItems()) {
const itemModel = new ItemModel(
_item,
// TODO:
this.id === "topics" ? "topic" : "tag"
);
const itemModel = new ItemModel(_item);
const title = await itemModel.getTitle();
if (title === titleToCompare) return itemModel;
}

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