Compare commits

..

4 Commits

Author SHA1 Message Date
Abdullah Atta
eecfabef61 web: make sure editor takes full vertical space 2023-03-20 09:46:18 +05:00
Abdullah Atta
19a5d6353d editor: branch out search ui for desktop 2023-03-18 13:01:36 +05:00
Abdullah Atta
0ad1006076 editor: refactor search 2023-03-18 12:59:38 +05:00
Abdullah Atta
9b4c711285 web: add editor sidebar container 2023-03-18 12:56:50 +05:00
238 changed files with 4194 additions and 14220 deletions

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

@@ -10,11 +10,11 @@
## Overview
Notesnook is a free (as in speech) & open-source note-taking app focused on user privacy & ease of use. To ensure zero knowledge principles, Notesnook encrypts everything on your device using `XChaCha20-Poly1305` & `Argon2`.
Notesnook is a free (as in speech) & open source note taking app focused on user privacy & ease of use. To ensure zero knowledge principles, Notesnook encrypts everything on your device using `XChaCha20-Poly1305` & `Argon2`.
Notesnook is our **proof** that privacy does _not_ (always) have to come at the cost of convenience. We aim to provide users peace of mind & 100% confidence that their notes are safe and secure. The decision to go fully open source is one of the most crucial steps towards that.
Notesnook is our **proof** that privacy does _not_ (always) have to come at the cost of convenience. Our goal is to provide users peace of mind & 100% confidence that their notes are safe and secure. The decision to go fully open source is one of the most crucial steps towards that.
This repository contains all the code required to build & use the Notesnook web, desktop & mobile clients. If you are looking for a full feature list or screenshots, please check the [website](https://notesnook.com/).
This repository contains all the code required to build & use the Notesnook web, desktop & mobile clients. If you are looking for a full features list or screenshots, please check the [website](https://notesnook.com/).
## Developer guide

View File

@@ -19,9 +19,9 @@
## Build instructions
> **Before you start, it is recommended that you read [the contributing guidelines](/CONTRIBUTING.md).**
> **Before you start it is recommended that you read [the contributing guidelines](/CONTRIBUTING.md).**
### Setting up the development environment
### Setting up development environment
Requirements:
@@ -57,7 +57,7 @@ npm install
### Running the app on Android
[Setup an Android emulator from Android Studio](https://developer.android.com/studio/run/managing-avds) if you haven't already, and then run the following command to start the app in the Emulator:
[Setup an Android emulator from Android Studio](https://developer.android.com/studio/run/managing-avds) if you haven't already and then run the following command to start the app in the Emulator:
```bash
npm run start:android
@@ -78,11 +78,11 @@ npm run start:ios
## Developer guide
> This project is in a transition state between Javascript & Typescript. We are gradually porting everything over to Typescript, so if you can help with that, it'd be great!
> This project is in a transition state between Javascript & Typescript. We are gradually porting everything over to Typescript so if you can help with that, it'd be great!
### The tech stack
We try to keep the stack as lean as possible:
We try to keep the stack as lean as possible
1. React Native
2. Typescript/Javascript
@@ -95,17 +95,17 @@ We try to keep the stack as lean as possible:
The app codebase is distributed over two primary directories. `native/` and `app/`.
- `native/`: Includes `android/` and `ios/` folders and everything related to react native core functionality like bundling, development, and packaging. Any react-native dependency with native code, i.e., android & ios folders, is installed here.
- `native/`: Includes `android/` and `ios/` folders and everything related to react native core functionality like bundling, development and packaging. Any react-native dependency that has native code i.e android & ios folders, is installed here.
- `app/`: Includes all the app code other than the native part. All JS-only dependencies are installed here.
- `components/`: Each component serves a specific purpose in the app UI. For example, the `Paragraph` component is used to render paragraphs in the app, and a `Header` component is used to render a `header` on all screens.
- `common/`: Features that are integral to the app's functionality. For example, the notesnook core is initialized here.
- `app/`: Includes all the app code other than the native part. All JS only dependencies are installed here.
- `components/`: Each component serves a specific purpose in the app UI, for example the `Paragraph` component is used to render paragraphs in the app and a `Header` component is used to render a `header` on all screens.
- `common/`: Features that have integral role in app functionality, for example, notesnook core is initialized here.
- `hooks/`: Hooks for different app logic
- `navigation/`: Includes app navigation-specific code. Here the app navigation, editor & side menu are rendered side by side in fluid tabs.
- `navigation/`: Includes app navigation specific code. Here the app navigation, editor & side menu are rendered side by side in fluid tabs.
- `screens`: Navigator screens.
- `services`: Parts of code that do a specific function. For example, the `sync` service runs Sync from anywhere in the app.
- `stores`: We use `zustand` for global state management in the app. Multiple stores provide the state for different parts of the app.
- `utils`: General purpose stuff such as constant values, utility functions, etc.
- `services`: Parts of code that do a specific function, for example, the `sync` service is responsibe for running Sync from anywhere in the app.
- `stores`: We use `zustand` for global state management in the app. There are multiple stores that provide the state for different parts of the app.
- `utils`: General purpose stuff such as constant values, utility functions etc.
There are several other folders at the root:
@@ -115,11 +115,11 @@ There are several other folders at the root:
### Running the tests
When you are done making the required changes, you must run the tests to ensure you didn't break anything. We use Detox as the testing framework & the tests can be started as follows:
When you are done making the required changes, you will need to run the tests to make sure you didn't break anything. We use Detox as the testing framework & the tests can be started as follows:
### Android
To run the tests on Android, you will need to create an emulator device on your system:
To run the tests on android, you will need to create an emulator device on your system:
```
$ANDROID_HOME/tools/bin/avdmanager create avd -n Pixel_5_API_31 -d pixel --package "system-images;android-31;default;x86_64"
@@ -127,13 +127,13 @@ $ANDROID_HOME/tools/bin/avdmanager create avd -n Pixel_5_API_31 -d pixel --packa
If you face problems, follow the detailed guide in [Detox documentation](https://wix.github.io/Detox/docs/introduction/android-dev-env). Keep the emulator name set to `Pixel_5_API_31`.
Once you have created an emulator device, build the Android apks:
Once you have created an emulator device, build the android apks
```
npm run build:android
```
Finally, run the tests:
Finally run the tests
```
npm run test:android
@@ -141,9 +141,9 @@ npm run test:android
### iOS
To run e2e tests on the iOS simulator, you must be on a Mac with XCode installed.
To run e2e tests on iOS simulator, you must be on a Mac with XCode installed.
First, install [AppleSimulatorUtils](https://github.com/wix/AppleSimulatorUtils):
First install [AppleSimulatorUtils](https://github.com/wix/AppleSimulatorUtils)
```
brew tap wix/brew
@@ -156,7 +156,7 @@ Now build the iOS app for testing:
npm run build:ios
```
Finally, run the tests:
Finally run the tests:
```
npm run test:ios

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -68,3 +68,14 @@ db.host(
ISSUES_HOST: "https://issues.streetwriters.co"
}
);
export async function loadDatabase() {
// if (!DB) {
// let module = await import(/* webpackChunkName: "notes-core" */ 'notes-core/api/index');
// DB = module.default;
// }
// db = new DB(Storage, Platform.OS === 'ios' ? EventSource : AndroidEventSource, filesystem);
// if (DOMParser) {
// await DOMParser.prepare();
// }
}

View File

@@ -28,7 +28,6 @@ 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,
@@ -39,6 +38,7 @@ export default async function downloadAttachment(
}
) {
let attachment = db.attachments.attachment(hash);
console.log(attachment);
if (!attachment) {
console.log("attachment not found");
return;
@@ -64,11 +64,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,7 +73,7 @@ export default async function downloadAttachment(
hash: attachment.metadata.hash,
hashType: attachment.metadata.hashType,
mime: attachment.metadata.type,
fileName: options.cache ? undefined : filename,
fileName: options.cache ? undefined : attachment.metadata.filename,
uri: options.cache ? undefined : folder.uri,
chunkSize: attachment.chunkSize
};
@@ -92,7 +87,7 @@ export default async function downloadAttachment(
if (!options.silent) {
ToastEvent.show({
heading: "Download successful",
message: filename + " downloaded",
message: attachment.metadata.filename + " downloaded",
type: "success"
});
}
@@ -105,20 +100,28 @@ export default async function downloadAttachment(
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
.catch(console.log);
}
if (Platform.OS === "ios" && !options.cache) {
fileUri = folder.uri + `/${filename}`;
if (Platform.OS === "ios") {
fileUri = folder.uri + `/${attachment.metadata.filename}`;
}
console.log("saved file uri: ", fileUri);
if (!options.silent) {
presentSheet({
title: "File downloaded",
paragraph: `${filename} saved to ${
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={filename} padding={12} />
component: (
<ShareComponent
uri={fileUri}
name={attachment.metadata.filename}
padding={12}
/>
)
});
}

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

@@ -63,26 +63,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

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

View File

@@ -22,7 +22,6 @@ import { View } from "react-native";
import { useMenuStore } from "../../../stores/use-menu-store";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent,
ToastEvent
@@ -31,7 +30,6 @@ import Navigation from "../../../services/navigation";
import { db } from "../../../common/database";
import {
eCloseAddTopicDialog,
eOnTopicSheetUpdate,
eOpenAddTopicDialog
} from "../../../utils/events";
import { sleep } from "../../../utils/time";
@@ -42,7 +40,6 @@ import DialogHeader from "../../dialog/dialog-header";
import Input from "../../ui/input";
import Seperator from "../../ui/seperator";
import { Toast } from "../../toast";
import { useRelationStore } from "../../../stores/use-relation-store";
export class AddTopicDialog extends React.Component {
constructor(props) {
@@ -81,11 +78,9 @@ export class AddTopicDialog extends React.Component {
}
this.close();
setTimeout(() => {
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate("Notebooks", "Notebook", "TopicNotes");
useMenuStore.getState().setMenuPins();
});
eSendEvent(eOnTopicSheetUpdate);
useRelationStore.getState().update();
} catch (e) {
console.error(e);
}

View File

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

View File

@@ -207,7 +207,13 @@ export class VaultDialog extends Component {
return;
}
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"TopicNotes",
"TaggedNotes",
"ColoredNotes"
);
this.password = null;
this.confirmPassword = null;

View File

@@ -63,15 +63,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 +84,10 @@ export const Title = () => {
}
}, [currentScreen.id, currentScreen.name]);
useEffect(() => {
titleState[currentScreen.id] = hide;
}, [currentScreen.id, hide]);
useEffect(() => {
eSubscribeEvent(eScrollEvent, onScroll);
return () => {
@@ -109,7 +111,7 @@ export const Title = () => {
{!hide && !isHidden ? (
<Heading
onPress={navigateToNotebook}
numberOfLines={1}
numberOfLines={isTopic ? 2 : 1}
size={SIZE.xl}
style={{
flexWrap: "wrap",

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useState } from "react";
import { 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";
@@ -30,8 +30,6 @@ 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);
@@ -51,22 +49,13 @@ const ImagePreview = () => {
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 hash = image.hash;
const uri = await downloadAttachment(hash, false, {
silent: true,
cache: true
});
const path = `${cacheDir}/${uri}`;
console.log(path);
setImage("file://" + path);
setLoading(false);
}, 100);
@@ -122,7 +111,7 @@ const ImagePreview = () => {
position: "absolute",
zIndex: 999,
backgroundColor: "rgba(0,0,0,0.3)",
paddingTop: Platform.OS === "android" ? 30 : 0
paddingTop: 30
}}
>
<IconButton

View File

@@ -17,14 +17,12 @@ 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 React, { useCallback, useEffect, useRef } from "react";
import { Platform, View } from "react-native";
import RNBootSplash from "react-native-bootsplash";
import { checkVersion } from "react-native-check-version";
import Config from "react-native-config";
import { enabled } from "react-native-privacy-snapshot";
import { DatabaseLogger, db } from "../../common/database";
import { DatabaseLogger, db, loadDatabase } from "../../common/database";
import { useAppState } from "../../hooks/use-app-state";
import BiometricService from "../../services/biometrics";
import { eSendEvent, presentSheet } from "../../services/event-manager";
@@ -38,7 +36,6 @@ import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { useUserStore } from "../../stores/use-user-store";
import { eOpenAnnouncementDialog } from "../../utils/events";
import { getGithubVersion } from "../../utils/github-version";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import { SVG } from "../auth/background";
@@ -53,6 +50,9 @@ import { SvgView } from "../ui/svg";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { Walkthrough } from "../walkthroughs";
import Config from "react-native-config";
import { getGithubVersion } from "../../utils/github-version";
import notifee from "@notifee/react-native";
const Launcher = React.memo(
function Launcher() {
@@ -69,7 +69,7 @@ const Launcher = React.memo(
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
);
const dbInitCompleted = useRef(false);
const loadNotes = useCallback(async () => {
if (verifyUser) {
return;
@@ -80,25 +80,28 @@ const Launcher = React.memo(
initialize();
setImmediate(() => {
setLoading(false);
if (!dbInitCompleted.current) {
setImmediate(() => doAppLoadActions());
}
});
});
});
}, [setLoading, verifyUser]);
}, [doAppLoadActions, setLoading, verifyUser]);
const init = useCallback(async () => {
if (!db.isInitialized) {
if (!dbInitCompleted.current) {
await RNBootSplash.hide({ fade: true });
await loadDatabase();
DatabaseLogger.info("Initializing database");
await db.init();
dbInitCompleted.current = true;
}
if (db.migrations.required() && !verifyUser) {
presentSheet({
component: <Migrate />,
onClose: async () => {
if (!db.isInitialized) {
await db.init();
}
await db.init();
loadNotes();
},
disableClosing: true
@@ -117,6 +120,9 @@ const Launcher = React.memo(
if (!loading) {
doAppLoadActions();
}
return () => {
dbInitCompleted.current = false;
};
}, [doAppLoadActions, loading]);
const doAppLoadActions = useCallback(async () => {

View File

@@ -27,7 +27,6 @@ import { db } from "../../../common/database";
import Notebook from "../../../screens/notebook";
import { TaggedNotes } from "../../../screens/notes/tagged";
import { TopicNotes } from "../../../screens/notes/topic-notes";
import useNavigationStore from "../../../stores/use-navigation-store";
import { useRelationStore } from "../../../stores/use-relation-store";
import { useSettingStore } from "../../../stores/use-setting-store";
import { useThemeStore } from "../../../stores/use-theme-store";
@@ -53,14 +52,12 @@ const showActionSheet = (item) => {
function getNotebook(item) {
const isTrash = item.type === "trash";
const currentId = useNavigationStore.getState().currentScreen.id;
if (isTrash) return [];
const items = [];
const notebooks = db.relations.to(item, "notebook") || [];
for (let notebook of notebooks) {
if (items.length > 1) break;
if (notebook.id === currentId) continue;
items.push(notebook);
}
@@ -71,7 +68,6 @@ function getNotebook(item) {
if (!notebook) continue;
for (let topicId of nb.topics) {
if (items.length > 1) break;
if (topicId === currentId) continue;
const topic = notebook.topics.find((t) => t.id === topicId);
if (!topic) continue;
items.push(topic);
@@ -81,20 +77,10 @@ function getNotebook(item) {
return items;
}
function getTags(item) {
const noteTags = item.tags?.slice(0, 3) || [];
const tags = [];
for (const tagName of noteTags) {
const tag = db.tags.tag(tagName);
if (!tag) continue;
tags.push(tag);
}
return tags;
}
const NoteItem = ({
item,
isTrash,
tags,
dateBy = "dateCreated",
noOpen = false
}) => {
@@ -110,7 +96,6 @@ const NoteItem = ({
const reminders = db.relations.from(item, "reminder");
const reminder = getUpcomingReminder(reminders);
const noteColor = COLORS_NOTE[item.color?.toLowerCase()];
const tags = getTags(item);
return (
<>
<View
@@ -132,13 +117,8 @@ const NoteItem = ({
>
{notebooks?.map((item) => (
<Button
title={
item.title.length > 25
? item.title.slice(0, 25) + "..."
: item.title
}
tooltipText={item.title}
key={item.id}
title={item.title}
key={item}
height={25}
icon={item.type === "topic" ? "bookmark" : "book-outline"}
type="grayBg"

View File

@@ -100,7 +100,7 @@ export const openNote = async (item, isTrash, setSelectedItem, isSheet) => {
};
export const NoteWrapper = React.memo(
function NoteWrapper({ item, index, dateBy, isSheet }) {
function NoteWrapper({ item, index, tags, dateBy, isSheet }) {
const isTrash = item.type === "trash";
const setSelectedItem = useSelectionStore((state) => state.setSelectedItem);
@@ -113,7 +113,7 @@ export const NoteWrapper = React.memo(
isSheet={isSheet}
item={item}
>
<NoteItem item={item} dateBy={dateBy} isTrash={isTrash} />
<NoteItem item={item} dateBy={dateBy} tags={tags} isTrash={isTrash} />
</SelectionWrapper>
);
},
@@ -125,6 +125,10 @@ export const NoteWrapper = React.memo(
return false;
}
if (JSON.stringify(prev.tags) !== JSON.stringify(next.tags)) {
return false;
}
if (prev.item !== next.item) {
return false;
}

View File

@@ -64,7 +64,16 @@ export const openNotebookTopic = (item) => {
negativeText: "Delete",
positivePress: async () => {
await db.trash.restore(item.id);
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"Tags",
"Notes",
"Notebooks",
"Favorites",
"Trash",
"TaggedNotes",
"ColoredNotes",
"TopicNotes"
);
useSelectionStore.getState().setSelectionMode(false);
ToastEvent.show({
heading: "Restore successful",

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useRef } from "react";
import React, { useRef } from "react";
import { RefreshControl, View } from "react-native";
import { FlashList } from "@shopify/flash-list";
import Animated, { FadeInDown } from "react-native-reanimated";
@@ -57,10 +57,25 @@ const RenderItem = ({ item, index, type, ...restArgs }) => {
const dateBy =
groupOptions.sortBy !== "title" ? groupOptions.sortBy : "dateEdited";
const totalNotes = getTotalNotes(item);
const tags =
item.tags
?.slice(0, 3)
?.map((item) => {
let tag = db.tags.tag(item);
if (!tag) return null;
return {
title: tag.title,
id: tag.id,
alias: tag.alias
};
})
.filter((t) => t !== null) || [];
return (
<Item
item={item}
tags={tags}
dateBy={dateBy}
index={index}
type={type}
@@ -139,20 +154,13 @@ const List = ({
[screen]
);
useEffect(() => {
eSendEvent(eScrollEvent, {
y: 0,
screen
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
let styles = {
width: "100%",
minHeight: 1,
minWidth: 1
};
const _keyExtractor = (item) => item.id || item.title;
const ListView = ScrollComponent ? ScrollComponent : FlashList;
return (
<>
@@ -218,14 +226,12 @@ const List = ({
}
/>
</Animated.View>
{listData ? (
<JumpToSectionDialog
screen={screen}
data={listData}
type={screen === "Notes" ? "home" : type}
scrollRef={scrollRef}
/>
) : null}
<JumpToSectionDialog
screen={screen}
data={listData}
type={screen === "Notes" ? "home" : type}
scrollRef={scrollRef}
/>
</>
);
};

View File

@@ -85,7 +85,13 @@ const MergeConflicts = () => {
}
});
}
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
if (editorController.current?.note?.id === note.id) {
// reload the note in editor
eSendEvent(eOnLoadNote, {

View File

@@ -44,7 +44,16 @@ export default function NotePreview({ session, content, note }) {
async function restore() {
if (note && note.type === "trash") {
await db.trash.restore(note.id);
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"Tags",
"Notes",
"Notebooks",
"Favorites",
"Trash",
"TaggedNotes",
"ColoredNotes",
"TopicNotes"
);
useSelectionStore.getState().setSelectionMode(false);
ToastEvent.show({
heading: "Restore successful",
@@ -64,7 +73,13 @@ export default function NotePreview({ session, content, note }) {
}
eSendEvent(eCloseSheet, "note_history");
eSendEvent(eCloseSheet);
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
ToastEvent.show({
heading: "Note restored successfully",

View File

@@ -43,7 +43,13 @@ export const ColorTags = ({ item }) => {
let _note = db.notes.note(note.id).data;
setNote({ ..._note });
setColorNotes();
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
eSendEvent(refreshNotesPage);
};

View File

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

View File

@@ -145,7 +145,13 @@ export default function Notebooks({ note, close, full }) {
note
);
useNotebookStore.getState().setNotebooks();
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
ToastEvent.show({
heading: "Note removed from topic",
context: "local",

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

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

View File

@@ -21,18 +21,18 @@ import React, { useCallback, useEffect } from "react";
import { BackHandler, Platform, View } from "react-native";
import { db } from "../../common/database";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { ToastEvent } from "../../services/event-manager";
import { eSendEvent, ToastEvent } from "../../services/event-manager";
import Navigation from "../../services/navigation";
import SearchService from "../../services/search";
import useNavigationStore from "../../stores/use-navigation-store";
import { useSelectionStore } from "../../stores/use-selection-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { eOpenMoveNoteDialog } from "../../utils/events";
import { deleteItems } from "../../utils/functions";
import { tabBarRef } from "../../utils/global-refs";
import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time";
import { presentDialog } from "../dialog/functions";
import MoveNoteSheet from "../sheets/add-to";
import ExportNotesSheet from "../sheets/export-notes";
import { IconButton } from "../ui/icon-button";
import Heading from "../ui/typography/heading";
@@ -64,7 +64,13 @@ export const SelectionHeader = React.memo(() => {
selectedItemsList.forEach((item) => {
db.notes.note(item.id).favorite();
});
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
clearSelection();
}
};
@@ -76,7 +82,16 @@ export const SelectionHeader = React.memo(() => {
noteIds.push(item.id);
});
await db.trash.restore(...noteIds);
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Trash",
"Notebooks",
"Tags"
);
clearSelection();
ToastEvent.show({
@@ -103,7 +118,16 @@ export const SelectionHeader = React.memo(() => {
noteIds.push(item.id);
});
await db.trash.delete(...noteIds);
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Trash",
"Notebooks",
"Tags"
);
clearSelection();
}
},
@@ -213,7 +237,7 @@ export const SelectionHeader = React.memo(() => {
onPress={async () => {
//setSelectionMode(false);
await sleep(100);
MoveNoteSheet.present();
eSendEvent(eOpenMoveNoteDialog);
}}
customStyle={{
marginLeft: 10
@@ -264,7 +288,15 @@ export const SelectionHeader = React.memo(() => {
);
}
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Notebooks",
"Notebook"
);
clearSelection();
}
}}

View File

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

View File

@@ -25,22 +25,22 @@ import {
TouchableOpacity,
View
} from "react-native";
import { FlatList } from "react-native-actions-sheet";
import { notesnook } from "../../../../e2e/test.ids";
import { db } from "../../../common/database";
import { DDS } from "../../../services/device-detection";
import { ToastEvent, presentSheet } from "../../../services/event-manager";
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 { SIZE, ph, pv } from "../../../utils/size";
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 Input from "../../ui/input";
import Seperator from "../../ui/seperator";
import Heading from "../../ui/typography/heading";
import { MoveNotes } from "../move-notes/movenote";
import { FlatList } from "react-native-actions-sheet";
let refs = [];
export class AddNotebookSheet extends React.Component {
@@ -92,7 +92,7 @@ export class AddNotebookSheet extends React.Component {
close = () => {
refs = [];
this.props.close(true);
this.props.close();
};
onDelete = (index) => {
@@ -182,7 +182,6 @@ export class AddNotebookSheet extends React.Component {
});
await db.notebooks.notebook(toEdit.id).topics.add(...nextTopics);
this.close();
} else {
newNotebookId = await db.notebooks.add({
title: this.title,
@@ -192,7 +191,14 @@ export class AddNotebookSheet extends React.Component {
});
}
useMenuStore.getState().setMenuPins();
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"Notes",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Notebooks",
"Notebook"
);
useRelationStore.getState().update();
MoveNotes.present(db.notebooks.notebook(newNotebookId).data);
};
@@ -245,34 +251,13 @@ export class AddNotebookSheet extends React.Component {
willFocus && this.topicInputRef.current?.focus();
};
renderTopicItem = ({ 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={this.props.colors}
/>
);
render() {
const { colors } = this.props;
const { topics, topicInputFocused, notebook } = this.state;
return (
<View
style={{
maxHeight: DDS.isTab ? "90%" : "97%",
maxHeight: DDS.isTab ? "90%" : "96%",
borderRadius: DDS.isTab ? 5 : 0,
paddingHorizontal: 12
}}
@@ -287,34 +272,17 @@ export class AddNotebookSheet extends React.Component {
}}
blurOnSubmit={false}
/>
<View
style={{
flexDirection: "row",
width: "100%",
justifyContent: "space-between",
alignItems: "center"
}}
>
<Heading size={SIZE.lg}>
{notebook && notebook.dateCreated
? "Edit Notebook"
: "New Notebook"}
</Heading>
<Button
title="Save"
type="accent"
height={40}
style={{
borderRadius: 100,
paddingHorizontal: 24
}}
fontSize={SIZE.md}
onPress={this.addNewNotebook}
/>
</View>
<Seperator />
<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)}
@@ -382,11 +350,49 @@ export class AddNotebookSheet extends React.Component {
keyExtractor={(item, index) => item + index.toString()}
keyboardShouldPersistTaps="always"
keyboardDismissMode="interactive"
ListFooterComponent={
topics.length === 0 ? null : <View style={{ height: 50 }} />
}
renderItem={this.renderTopicItem}
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={{
height: 40
}}
/>
)} */}
</View>
);
}

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

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

View File

@@ -17,22 +17,16 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useCallback, useEffect, useMemo } from "react";
import React, { 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 {
eSendEvent,
presentSheet,
ToastEvent
} from "../../../services/event-manager";
import { presentSheet, 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 { eOnTopicSheetUpdate } from "../../../utils/events";
import { Dialog } from "../../dialog";
import DialogHeader from "../../dialog/dialog-header";
import { presentDialog } from "../../dialog/functions";
@@ -41,21 +35,19 @@ import Paragraph from "../../ui/typography/paragraph";
import { SelectionProvider } from "./context";
import { FilteredList } from "./filtered-list";
import { ListItem } from "./list-item";
import { useItemSelectionStore } from "./store";
const MoveNoteSheet = ({ note, actionSheetRef }) => {
const colors = useThemeStore((state) => state.colors);
const [multiSelect, setMultiSelect] = useState(false);
const notebooks = useNotebookStore((state) =>
state.notebooks.filter((n) => n?.type === "notebook")
);
const dimensions = useSettingStore((state) => state.dimensions);
const selectedItemsList = useSelectionStore(
(state) => state.selectedItemsList
);
const setNotebooks = useNotebookStore((state) => state.setNotebooks);
const multiSelect = useItemSelectionStore((state) => state.multiSelect);
const [itemState, setItemState] = useState({});
const onAddNotebook = async (title) => {
if (!title || title.trim().length === 0) {
ToastEvent.show({
@@ -138,55 +130,41 @@ 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)
: areAllSelectedItemsInNotebook(notebook, selectedItemsList)
? "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]
);
@@ -213,26 +191,32 @@ 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
};
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");
},
@@ -243,7 +227,7 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
resetItemState(state);
}
}),
[resetItemState, updateItemState]
[multiSelect, resetItemState, updateItemState]
);
const getItemFromId = (id) => {
@@ -257,7 +241,6 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
const onSave = async () => {
const noteIds = note ? [note.id] : selectedItemsList.map((n) => n.id);
const itemState = useItemSelectionStore.getState().itemState;
for (const id in itemState) {
const item = getItemFromId(id);
if (itemState[id] === "selected") {
@@ -292,9 +275,15 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
}
}
}
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Notebook"
);
setNotebooks();
eSendEvent(eOnTopicSheetUpdate);
SearchService.updateAndSearch();
actionSheetRef.current?.hide();
};
@@ -346,32 +335,31 @@ 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
style={{
paddingHorizontal: 12,
maxHeight: dimensions.height * 0.85
paddingHorizontal: 12
}}
ListEmptyComponent={
notebooks.length > 0 ? null : (
@@ -397,8 +385,13 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
item={item}
key={item.id}
index={index}
hasNotes={getSelectedNotesCountInItem(item) > 0}
intermediate={itemState[item.id] === "intermediate"}
removed={
itemState[item.id] === "deselected" &&
getSelectedNotesCountInItem(item) > 0
}
sheetRef={actionSheetRef}
isSelected={itemState[item.id] === "selected"}
infoText={
<>
{item.topics.length === 1
@@ -408,14 +401,17 @@ 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");
@@ -444,23 +440,19 @@ 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>
@@ -471,8 +463,7 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
MoveNoteSheet.present = (note) => {
presentSheet({
component: (ref) => <MoveNoteSheet actionSheetRef={ref} note={note} />,
enableGesturesInScrollView: false,
noBottomPadding: true
enableGesturesInScrollView: false
});
};
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,19 @@ export const ListItem = ({
sublistItemType,
onAddItem,
getSublistItemProps,
removed,
isSelected,
hasHeaderSearch,
onAddSublistItem,
hasNotes,
onChange,
sheetRef
}) => {
const { toggleSelection } = useSelectionContext();
const multiSelect = useItemSelectionStore((state) => state.multiSelect);
const [showSelectedIndicator, setShowSelectedIndicator] = useState(false);
const { enabled, toggleSelection, setMultiSelect } = useSelectionContext();
const colors = useThemeStore((state) => state.colors);
const [expanded, setExpanded] = useState(false);
function selectItem() {
toggleSelection(item);
}
const getSelectedNotesCountInNotebookTopics = (item) => {
if (item.type === "topic") return;
let count = 0;
const noteIds = [];
for (let topic of item.topics) {
noteIds.push(...(db.notes?.topicReferences.get(topic.id) || []));
if (useItemSelectionStore.getState().itemState[topic.id] === "selected") {
count++;
}
}
useSelectionStore.getState().selectedItemsList.forEach((item) => {
if (noteIds.indexOf(item.id) > -1) {
count++;
}
});
return count;
};
useEffect(() => {
setShowSelectedIndicator(getSelectedNotesCountInNotebookTopics(item) > 0);
}, [item]);
const onChangeSubItem = () => {
setShowSelectedIndicator(getSelectedNotesCountInNotebookTopics(item) > 0);
};
return (
<View
style={{
@@ -164,12 +67,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 +97,43 @@ 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={() => {
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 +152,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);
@@ -280,7 +201,6 @@ export const ListItem = ({
item={item}
{...getSublistItemProps(item)}
index={index}
onChange={onChangeSubItem}
onScrollEnd={onScrollEnd}
/>
)}
@@ -290,3 +210,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

@@ -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

@@ -103,7 +103,15 @@ const ManageTagsSheet = (props) => {
});
}
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Notebooks",
"Notebook"
);
};
return (
@@ -134,6 +142,7 @@ const ManageTagsSheet = (props) => {
setFocus(false);
}}
onSubmit={onSubmit}
height={50}
placeholder="Search or add a tag"
/>
@@ -216,7 +225,13 @@ const TagItem = ({ tag, note, setNote }) => {
console.error(e);
}
setTimeout(() => {
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
}, 1);
};

View File

@@ -122,7 +122,15 @@ export const MoveNotes = ({
db.notebooks?.notebook(currentNotebook.id).data as NotebookType
);
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Notebook",
"Notebooks"
);
return true;
};
@@ -310,7 +318,15 @@ export const MoveNotes = ({
},
...selectedNoteIds
);
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Notebook",
"Notebooks"
);
SearchService.updateAndSearch();
eSendEvent(eCloseSheet);
}}

View File

@@ -60,10 +60,16 @@ const PublishNoteSheet = ({ note: item, update }) => {
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();
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
setPublishLoading(false);
}
} catch (e) {
@@ -80,6 +86,9 @@ const PublishNoteSheet = ({ note: item, update }) => {
const setPublishLoading = (value) => {
setPublishing(value);
update({
progress: value
});
};
const deletePublishedNote = async () => {
@@ -89,7 +98,13 @@ const PublishNoteSheet = ({ note: item, update }) => {
if (note?.id) {
await db.monographs.unpublish(note.id);
setNote(db.notes.note(note.id)?.data);
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes"
);
setPublishLoading(false);
}
} catch (e) {
@@ -149,7 +164,7 @@ const PublishNoteSheet = ({ note: item, update }) => {
style={{
flexDirection: "row",
alignItems: "center",
marginTop: 10,
marginTop: 15,
backgroundColor: colors.nav,
padding: 12,
borderRadius: 5
@@ -161,8 +176,8 @@ const PublishNoteSheet = ({ note: item, update }) => {
flexShrink: 1
}}
>
<Heading size={SIZE.md}>Published at:</Heading>
<Paragraph size={SIZE.sm} numberOfLines={1}>
<Heading size={SIZE.sm}>Published at:</Heading>
<Paragraph size={SIZE.xs} numberOfLines={1}>
{publishUrl}
</Paragraph>
<Paragraph
@@ -199,6 +214,7 @@ const PublishNoteSheet = ({ note: item, update }) => {
/>
</View>
)}
<Seperator />
<TouchableOpacity
onPress={() => {
@@ -209,11 +225,7 @@ const PublishNoteSheet = ({ note: item, update }) => {
style={{
flexDirection: "row",
alignItems: "center",
marginBottom: 10,
backgroundColor: colors.nav,
paddingVertical: 12,
borderRadius: 5,
marginTop: 10
marginBottom: 10
}}
>
<IconButton
@@ -222,7 +234,7 @@ const PublishNoteSheet = ({ note: item, update }) => {
setIsLocked(!isLocked);
}}
color={isLocked ? colors.accent : colors.icon}
size={SIZE.xl}
size={SIZE.lg}
name={
isLocked
? "check-circle-outline"
@@ -250,10 +262,7 @@ const PublishNoteSheet = ({ note: item, update }) => {
activeOpacity={0.9}
style={{
flexDirection: "row",
alignItems: "center",
backgroundColor: colors.nav,
paddingVertical: 12,
borderRadius: 5
alignItems: "center"
}}
>
<IconButton
@@ -261,7 +270,7 @@ const PublishNoteSheet = ({ note: item, update }) => {
setSelfDestruct(!selfDestruct);
}}
color={selfDestruct ? colors.accent : colors.icon}
size={SIZE.xl}
size={SIZE.lg}
name={
selfDestruct
? "check-circle-outline"
@@ -304,38 +313,31 @@ const PublishNoteSheet = ({ note: item, update }) => {
</>
) : null}
<View
<Button
onPress={publishNote}
fontSize={SIZE.md}
width="100%"
style={{
flexDirection: "row",
width: "100%",
justifyContent: "center"
marginTop: 10
}}
>
{isPublished && (
<>
<Button
onPress={deletePublishedNote}
fontSize={SIZE.md}
type="error"
title="Unpublish"
style={{
width: "49%"
}}
/>
</>
)}
<Seperator half />
<Button
onPress={publishNote}
fontSize={SIZE.md}
style={{
width: isPublished ? "49%" : 250,
borderRadius: isPublished ? 5 : 100
}}
type="accent"
title={isPublished ? "Update" : "Publish"}
/>
</View>
height={50}
type="accent"
title={isPublished ? "Update published note" : "Publish note"}
/>
{isPublished && (
<>
<Seperator half />
<Button
onPress={deletePublishedNote}
fontSize={SIZE.md}
width="100%"
height={50}
type="error"
title="Unpublish note"
/>
</>
)}
</View>
</>
)}
@@ -345,7 +347,7 @@ const PublishNoteSheet = ({ note: item, update }) => {
size={SIZE.xs}
style={{
textAlign: "center",
marginTop: 10,
marginTop: 5,
textDecorationLine: "underline"
}}
onPress={async () => {

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

@@ -21,9 +21,9 @@ import { Platform, TextInput, View } from "react-native";
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
import DateTimePickerModal from "react-native-modal-datetime-picker";
import {
presentSheet,
PresentSheetOptions,
ToastEvent,
presentSheet
ToastEvent
} from "../../../services/event-manager";
import { useThemeStore } from "../../../stores/use-theme-store";
import { SIZE } from "../../../utils/size";
@@ -33,7 +33,6 @@ import Input from "../../ui/input";
import dayjs from "dayjs";
import DatePicker from "react-native-date-picker";
import { db } from "../../../common/database";
import { DDS } from "../../../services/device-detection";
import Navigation from "../../../services/navigation";
import Notifications, { Reminder } from "../../../services/notifications";
import PremiumService from "../../../services/premium";
@@ -42,7 +41,6 @@ import { useRelationStore } from "../../../stores/use-relation-store";
import { NoteType } from "../../../utils/types";
import { Dialog } from "../../dialog";
import { ReminderTime } from "../../ui/reminder-time";
import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph";
type ReminderSheetProps = {
@@ -200,8 +198,7 @@ export default function ReminderSheet({
if (!_reminder) {
ToastEvent.show({
heading: "Failed to add a new reminder",
context: "local"
heading: "Failed to add a new reminder"
});
}
if (reference) {
@@ -211,68 +208,54 @@ export default function ReminderSheet({
});
}
Notifications.scheduleNotification(_reminder as Reminder);
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"Notes",
"NotesPage",
"Reminders",
"Favorites",
"TopicNotes"
);
useRelationStore.getState().update();
close?.();
} catch (e) {
ToastEvent.error(e as Error, undefined, "local");
ToastEvent.error(e as Error);
}
}
return (
<View
style={{
paddingHorizontal: 12,
maxHeight: DDS.isTab ? "90%" : "99.99%"
paddingHorizontal: 12
}}
>
<View
style={{
flexDirection: "row",
width: "100%",
justifyContent: "space-between",
alignItems: "center"
}}
>
<Heading size={SIZE.lg}>Set reminder</Heading>
<Button
title="Save"
type="accent"
height={40}
style={{
borderRadius: 100,
paddingHorizontal: 24
}}
fontSize={SIZE.md}
onPress={saveReminder}
/>
</View>
<Dialog context="local" />
<ScrollView>
<ScrollView keyboardShouldPersistTaps="always">
<Input
fwdRef={titleRef}
defaultValue={reminder?.title || referencedItem?.title}
placeholder="Remind me of..."
onChangeText={(text) => (title.current = text)}
wrapperStyle={{
marginTop: 10
}}
containerStyle={{ borderWidth: 0, borderBottomWidth: 1 }}
/>
<Input
defaultValue={
reminder ? reminder?.description : referencedItem?.headline
}
placeholder="Add a short note"
placeholder="Add a quick note"
onChangeText={(text) => (details.current = text)}
containerStyle={{
maxHeight: 80
borderWidth: 0,
borderBottomWidth: 1,
maxHeight: 80,
marginTop: 10
}}
multiline
textAlignVertical="top"
inputStyle={{
minHeight: 80,
paddingVertical: 12
height: 80
}}
height={80}
wrapperStyle={{
@@ -310,16 +293,6 @@ export default function ReminderSheet({
mode as keyof typeof ReminderModes
] as Reminder["mode"]
);
if (mode === "Repeat") {
setSelectedDays((days) => {
if (days.length > 0) return days;
if (days.indexOf(date.getDay()) > -1) {
return days;
}
days.push(date.getDay());
return [...days];
});
}
}}
/>
))}
@@ -461,13 +434,17 @@ 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}
theme={colors.night ? "dark" : "light"}
is24hourSource="locale"
androidVariant="nativeAndroid"
mode={reminderMode === ReminderModes.Repeat ? "time" : "datetime"}
/>
@@ -584,6 +561,15 @@ export default function ReminderSheet({
alignSelf: "flex-start"
}}
/>
<Button
style={{
width: "100%"
}}
title="Save"
type="accent"
fontSize={SIZE.md}
onPress={saveReminder}
/>
</ScrollView>
</View>
);
@@ -597,7 +583,6 @@ ReminderSheet.present = (
presentSheet({
context: isSheet ? "local" : undefined,
enableGesturesInScrollView: true,
noBottomPadding: true,
component: (ref, close, update) => (
<ReminderSheet
actionSheetRef={ref}

View File

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

View File

@@ -19,14 +19,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import qclone from "qclone";
import React, {
createContext,
RefObject,
useCallback,
useContext,
useEffect,
useRef,
useState
} from "react";
import { RefreshControl, View } from "react-native";
import { Animated, Dimensions, View, RefreshControl } from "react-native";
import ActionSheet, {
ActionSheetRef,
FlatList
@@ -39,53 +37,26 @@ import { TopicNotes } from "../../../screens/notes/topic-notes";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent,
presentSheet
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 { eOnNewTopicAdded, eOpenAddTopicDialog } from "../../../utils/events";
import { normalize, SIZE } from "../../../utils/size";
import { GroupHeader, NotebookType, TopicType } from "../../../utils/types";
import { NotebookType, TopicType } from "../../../utils/types";
import { groupArray } from "@notesnook/core/utils/grouping";
import Config from "react-native-config";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../../e2e/test.ids";
import { MMKV } from "../../../common/database/mmkv";
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 { Properties } from "../../properties";
import Sort from "../sort";
import Heading from "../../ui/typography/heading";
type ConfigItem = { id: string; type: string };
class TopicSheetConfig {
static storageKey: "$$sp";
static makeId(item: ConfigItem) {
return `${TopicSheetConfig.storageKey}:${item.type}:${item.id}`;
}
static get(item: ConfigItem) {
return MMKV.getInt(TopicSheetConfig.makeId(item)) || 0;
}
static set(item: ConfigItem, index = 0) {
MMKV.setInt(TopicSheetConfig.makeId(item), index);
}
}
import Config from "react-native-config";
import { notesnook } from "../../../../e2e/test.ids";
export const TopicsSheet = () => {
const [collapsed, setCollapsed] = useState(false);
const currentScreen = useNavigationStore((state) => state.currentScreen);
const canShow =
currentScreen.name === "Notebook" || currentScreen.name === "TopicNotes";
@@ -100,19 +71,12 @@ export const TopicsSheet = () => {
const [enabled, setEnabled] = useState(false);
const colors = useThemeStore((state) => state.colors);
const ref = useRef<ActionSheetRef>(null);
const isTopic = currentScreen.name === "TopicNotes";
const [topics, setTopics] = useState(
notebook
? qclone(
groupArray(notebook.topics, db.settings?.getGroupOptions("topics"))
)
: []
);
const [groupOptions, setGroupOptions] = useState(
db.settings?.getGroupOptions("topics")
);
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;
@@ -121,40 +85,16 @@ export const TopicsSheet = () => {
?.data as NotebookType;
if (_notebook) {
setNotebook(_notebook);
setTopics(
qclone(
groupArray(_notebook.topics, db.settings?.getGroupOptions("topics"))
)
);
setTopics(qclone(_notebook.topics));
}
},
[canShow, notebook]
[notebook, canShow]
);
const onUpdate = useCallback(() => {
setGroupOptions({ ...(db.settings?.getGroupOptions("topics") as any) });
onRequestUpdate();
}, [onRequestUpdate]);
useEffect(() => {
eSubscribeEvent("groupOptionsUpdate", onUpdate);
return () => {
eUnSubscribeEvent("groupOptionsUpdate", onUpdate);
};
}, [onUpdate]);
useEffect(() => {
const onTopicUpdate = () => {
setTimeout(() => {
onRequestUpdate();
}, 1);
};
eSubscribeEvent(eOnTopicSheetUpdate, onTopicUpdate);
eSubscribeEvent(eOnNewTopicAdded, onRequestUpdate);
return () => {
eUnSubscribeEvent(eOnTopicSheetUpdate, onRequestUpdate);
eUnSubscribeEvent(eOnNewTopicAdded, onTopicUpdate);
eUnSubscribeEvent(eOnNewTopicAdded, onRequestUpdate);
};
}, [onRequestUpdate]);
@@ -168,16 +108,9 @@ export const TopicsSheet = () => {
loading: "Loading notebook topics"
};
const renderTopic = ({
item,
index
}: {
item: TopicType | GroupHeader;
index: number;
}) =>
(item as GroupHeader).type === "header" ? null : (
<TopicItem sheetRef={ref} item={item as TopicType} index={index} />
);
const renderTopic = ({ item, index }: { item: TopicType; index: number }) => (
<TopicItem item={item} index={index} />
);
const selectionContext = {
selection: selection,
@@ -204,37 +137,29 @@ export const TopicsSheet = () => {
useEffect(() => {
if (canShow) {
setTimeout(() => {
const id = isTopic ? currentScreen?.notebookId : currentScreen?.id;
const notebook = db.notebooks?.notebook(id as string)?.data;
const snapPoint = isTopic
? 0
: TopicSheetConfig.get({
type: isTopic ? "topic" : "notebook",
id: currentScreen.id as string
});
if (ref.current?.isOpen()) {
ref.current?.snapToIndex(snapPoint);
} else {
ref.current?.show(snapPoint);
}
if (notebook) {
onRequestUpdate({
item: notebook
} as any);
}
}, 300);
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,
isTopic
onRequestUpdate
]);
return (
@@ -242,27 +167,14 @@ export const TopicsSheet = () => {
ref={ref}
isModal={false}
containerStyle={{
maxHeight: 300,
maxHeight: 600,
borderTopRightRadius: 15,
borderTopLeftRadius: 15,
backgroundColor: colors.bg,
borderWidth: 1,
borderColor: colors.nav,
borderColor: colors.border,
borderBottomWidth: 0
}}
openAnimationConfig={{
friction: 10
}}
onSnapIndexChange={(index) => {
setCollapsed(index === 0);
TopicSheetConfig.set(
{
type: isTopic ? "topic" : "notebook",
id: currentScreen.id as string
},
index
);
}}
closable={!canShow}
elevation={10}
indicatorStyle={{
@@ -270,44 +182,67 @@ export const TopicsSheet = () => {
backgroundColor: colors.nav
}}
keyboardHandlerEnabled={false}
snapPoints={Config.isTesting === "true" ? [100] : [25, 100]}
initialSnapIndex={1}
snapPoints={Config.isTesting === "true" ? [60, 100] : [15, 60, 100]}
initialSnapIndex={0}
backgroundInteractionEnabled
onChange={(position, height) => {
animations.translate.setValue(position);
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,
bottom: 50
}}
>
<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={{
position: "absolute",
right: 12,
marginTop: -80
}}
>
<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>
</View>
<View
style={{
maxHeight: 300,
height: 300,
maxHeight: 600,
height: 600,
width: "100%"
}}
>
@@ -340,7 +275,7 @@ export const TopicsSheet = () => {
selection.length > 1 ? "topics" : "topics"
}`,
paragraph: `Are you sure you want to delete ${
selection.length > 1 ? "these topics?" : "this topic?"
selection.length > 1 ? "these topicss?" : "this topics?"
}`,
positiveText: "Delete",
negativeText: "Cancel",
@@ -361,57 +296,17 @@ export const TopicsSheet = () => {
size={22}
/>
) : (
<>
<IconButton
name={
groupOptions?.sortDirection === "asc"
? "sort-ascending"
: "sort-descending"
}
onPress={() => {
presentSheet({
component: <Sort screen="TopicSheet" type="topics" />
});
}}
testID="group-topic-button"
color={colors.pri}
size={22}
customStyle={{
width: 40,
height: 40
}}
/>
<IconButton
name="plus"
onPress={PLACEHOLDER_DATA.action}
testID="add-topic-button"
color={colors.pri}
size={22}
customStyle={{
width: 40,
height: 40
}}
/>
<IconButton
name={collapsed ? "chevron-up" : "chevron-down"}
onPress={() => {
if (ref.current?.currentSnapIndex() !== 0) {
setCollapsed(true);
ref.current?.snapToIndex(0);
} else {
setCollapsed(false);
ref.current?.snapToIndex(1);
}
}}
color={colors.pri}
size={22}
customStyle={{
width: 40,
height: 40
}}
/>
</>
<IconButton
name="plus"
onPress={PLACEHOLDER_DATA.action}
testID="add-topic-button"
color={colors.pri}
size={22}
customStyle={{
width: 40,
height: 40
}}
/>
)}
</View>
</View>
@@ -431,7 +326,7 @@ export const TopicsSheet = () => {
progressBackgroundColor={colors.bg}
/>
}
keyExtractor={(item) => (item as TopicType).id}
keyExtractor={(item) => item.id}
renderItem={renderTopic}
ListEmptyComponent={
<View
@@ -439,7 +334,7 @@ export const TopicsSheet = () => {
flex: 1,
justifyContent: "center",
alignItems: "center",
height: 200
height: 300
}}
>
<Paragraph color={colors.icon}>No topics</Paragraph>
@@ -461,20 +356,12 @@ const SelectionContext = createContext<{
}>({
selection: [],
enabled: false,
setEnabled: (_value: boolean) => {},
toggleSelection: (_item: TopicType) => {}
setEnabled: (value: boolean) => {},
toggleSelection: (item: TopicType) => {}
});
const useSelection = () => useContext(SelectionContext);
const TopicItem = ({
item,
index,
sheetRef
}: {
item: TopicType;
index: number;
sheetRef: RefObject<ActionSheetRef>;
}) => {
const TopicItem = ({ item, index }: { item: TopicType; index: number }) => {
const screen = useNavigationStore((state) => state.currentScreen);
const colors = useThemeStore((state) => state.colors);
const selection = useSelection();

View File

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

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 { TouchableOpacity, View } from "react-native";
import { Keyboard, TouchableOpacity, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../e2e/test.ids";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
@@ -34,25 +34,24 @@ import { SIZE } from "../../utils/size";
import { Button } from "../ui/button";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
let toastMessages = [];
export const Toast = ({ context = "global" }) => {
const colors = useThemeStore((state) => state.colors);
const [keyboard, setKeyboard] = useState(false);
const [data, setData] = useState({});
const insets = useGlobalSafeAreaInsets();
const hideTimeout = useRef();
const [visible, setVisible] = useState(false);
const toastMessages = useRef([]);
const show = useCallback(
const showToastFunc = useCallback(
async (data) => {
if (!data) return;
if (data.context !== context) return;
if (
toastMessages.current.findIndex((m) => m.message === data.message) >= 0
) {
if (toastMessages.findIndex((m) => m.message === data.message) >= 0) {
return;
}
toastMessages.current.push(data);
if (toastMessages.current?.length > 1) return;
toastMessages.push(data);
if (toastMessages?.length > 1) return;
setData(data);
setVisible(true);
@@ -60,16 +59,16 @@ export const Toast = ({ context = "global" }) => {
clearTimeout(hideTimeout.current);
}
hideTimeout.current = setTimeout(() => {
hide();
hideToastFunc();
}, data.duration);
},
[context, hide]
[context, hideToastFunc]
);
const next = useCallback(
const showNext = useCallback(
(data) => {
if (!data) {
hide();
hideToastFunc();
return;
}
setData(data);
@@ -77,28 +76,27 @@ export const Toast = ({ context = "global" }) => {
clearTimeout(hideTimeout.current);
}
hideTimeout.current = setTimeout(() => {
hide();
hideToastFunc();
}, data?.duration);
},
[hide]
[hideToastFunc]
);
const hide = useCallback(() => {
const hideToastFunc = useCallback(() => {
if (hideTimeout.current) {
clearTimeout(hideTimeout.current);
}
let msg =
toastMessages.current.length > 1 ? toastMessages.current.shift() : null;
let msg = toastMessages.length > 1 ? toastMessages.shift() : null;
if (msg) {
setVisible(false);
next(msg);
showNext(msg);
setTimeout(() => {
setVisible(true);
}, 300);
} else {
setVisible(false);
toastMessages.current.shift();
toastMessages.shift();
setTimeout(() => {
setData({});
if (hideTimeout.current) {
@@ -106,22 +104,44 @@ export const Toast = ({ context = "global" }) => {
}
}, 100);
}
}, [next]);
}, [showNext]);
const _onKeyboardShow = () => {
setKeyboard(true);
};
const _onKeyboardHide = () => {
setKeyboard(false);
};
useEffect(() => {
eSubscribeEvent(eShowToast, show);
eSubscribeEvent(eHideToast, hide);
toastMessages = [];
let sub1 = Keyboard.addListener("keyboardDidShow", _onKeyboardShow);
let sub2 = Keyboard.addListener("keyboardDidHide", _onKeyboardHide);
eSubscribeEvent(eShowToast, showToastFunc);
eSubscribeEvent(eHideToast, hideToastFunc);
return () => {
toastMessages.current = [];
eUnSubscribeEvent(eShowToast, show);
eUnSubscribeEvent(eHideToast, hide);
if (hideTimeout.current) {
clearTimeout(hideTimeout.current);
}
toastMessages = [];
sub1?.remove();
sub2?.remove();
eUnSubscribeEvent(eShowToast, showToastFunc);
eUnSubscribeEvent(eHideToast, hideToastFunc);
};
}, [hide, show]);
}, [hideToastFunc, keyboard, showToastFunc]);
return (
visible && (
<TouchableOpacity
onPress={hide}
onPress={() => {
if (hideTimeout.current) {
clearTimeout(hideTimeout.current);
}
hideToastFunc();
}}
activeOpacity={1}
style={{
width: DDS.isTab ? 400 : "100%",
@@ -189,7 +209,7 @@ export const Toast = ({ context = "global" }) => {
color={colors.pri}
size={SIZE.md}
onPress={() => {
hide();
hideToastFunc();
}}
>
{data.heading}
@@ -204,7 +224,7 @@ export const Toast = ({ context = "global" }) => {
paddingRight: 10
}}
onPress={() => {
hide();
hideToastFunc();
}}
>
{data.message}

View File

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

View File

@@ -38,8 +38,7 @@ const SheetWrapper = ({
keyboardMode,
overlay,
overlayOpacity = 0.3,
enableGesturesInScrollView = false,
bottomPadding = true
enableGesturesInScrollView = false
}) => {
const colors = useThemeStore((state) => state.colors);
const deviceMode = useSettingStore((state) => state.deviceMode);
@@ -120,16 +119,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

@@ -21,7 +21,15 @@ import { FeatureType } from "./components/sheets/new-feature";
export const features: FeatureType[] = [
{
title: "Default font size & font family",
body: "Now you can set default font size and font family in editor that will be used across all your new and old notes."
title: "Improved editor performance",
body: "The editor perfomance is now much better especially with images"
},
{
title: "Sortable task lists",
body: "Now you can sort tasks by checked/unchecked in editor"
},
{
title: "Improved outline lists",
body: "Rewritten outline lists to be more performant and less buggy"
}
];

View File

@@ -52,11 +52,7 @@ 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 {
eOnTopicSheetUpdate,
eOpenAddTopicDialog,
eOpenLoginDialog
} from "../utils/events";
import { eOpenAddTopicDialog, eOpenLoginDialog } from "../utils/events";
import { deleteItems } from "../utils/functions";
import { sleep } from "../utils/time";
@@ -148,7 +144,13 @@ export const useActions = ({ close = () => null, item }) => {
if (!item.id) return;
close();
await db.notes.note(item.id).favorite();
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes"
);
}
async function pinItem() {
@@ -156,7 +158,14 @@ export const useActions = ({ close = () => null, item }) => {
close();
let type = item.type;
await db[`${type}s`][type](item.id).pin();
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes",
"Notebooks"
);
}
async function pinToNotifications() {
@@ -170,17 +179,9 @@ export const useActions = ({ close = () => null, item }) => {
checkNotifPinned();
return;
}
if (item.locked) {
ToastEvent.show({
heading: "Note is locked",
type: "error",
message: "Locked notes cannot be pinned to notifications",
context: "local"
});
return;
}
let text = await toTXT(item, false);
let html = text.replace(/\n/g, "<br />");
if (item.locked) return;
let html = await db.notes.note(item.id).content();
let text = await toTXT(item);
Notifications.displayNotification({
title: item.title,
message: item.headline || text,
@@ -199,7 +200,15 @@ export const useActions = ({ close = () => null, item }) => {
if (!checkNoteSynced()) return;
close();
await db.trash.restore(item.id);
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes",
"Notebooks",
"Trash"
);
let type = item.type === "trash" ? item.itemType : item.type;
ToastEvent.show({
heading:
@@ -315,7 +324,13 @@ export const useActions = ({ close = () => null, item }) => {
let note = db.notes.note(item.id).data;
if (note.locked) {
close();
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes"
);
}
} catch (e) {
close();
@@ -381,11 +396,16 @@ export const useActions = ({ close = () => null, item }) => {
positivePress: async (value) => {
if (!value || value === "" || value.trimStart().length == 0) return;
await db.tags.rename(item.id, db.tags.sanitize(value));
setImmediate(() => {
useTagStore.getState().setTags();
useMenuStore.getState().setMenuPins();
Navigation.queueRoutesForUpdate();
});
useTagStore.getState().setTags();
useMenuStore.getState().setMenuPins();
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes",
"Tags"
);
},
input: true,
defaultValue: alias,
@@ -428,16 +448,24 @@ export const useActions = ({ close = () => null, item }) => {
? "This reminder will be removed"
: "This tag will be removed from all notes.",
positivePress: async () => {
const routes = [];
routes.push(
"TaggedNotes",
"ColoredNotes",
"Notes",
"NotesPage",
"Reminders",
"Favorites"
);
if (item.type === "reminder") {
await db.reminders.remove(item.id);
} else {
await db.tags.remove(item.id);
}
setImmediate(() => {
useTagStore.getState().setTags();
Navigation.queueRoutesForUpdate();
useRelationStore.getState().update();
});
routes.push("Tags");
}
Navigation.queueRoutesForUpdate(...routes);
useRelationStore.getState().update();
},
positiveText: "Delete",
positiveType: "errorShade"
@@ -475,8 +503,15 @@ export const useActions = ({ close = () => null, item }) => {
},
item.id
);
Navigation.queueRoutesForUpdate();
eSendEvent(eOnTopicSheetUpdate);
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes",
"Notebook",
"Notebooks"
);
close();
}
@@ -484,7 +519,15 @@ export const useActions = ({ close = () => null, item }) => {
const currentScreen = useNavigationStore.getState().currentScreen;
if (currentScreen.name !== "Notebook") return;
await db.relations.unlink({ type: "notebook", id: currentScreen.id }, item);
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes",
"Notebook",
"Notebooks"
);
close();
}
@@ -499,14 +542,12 @@ export const useActions = ({ close = () => null, item }) => {
negativeText: "Cancel",
positivePress: async () => {
await db.trash.delete(item.id);
setImmediate(() => {
Navigation.queueRoutesForUpdate();
useSelectionStore.getState().setSelectionMode(false);
ToastEvent.show({
heading: "Permanantly deleted items",
type: "success",
context: "local"
});
Navigation.queueRoutesForUpdate("Trash");
useSelectionStore.getState().setSelectionMode(false);
ToastEvent.show({
heading: "Permanantly deleted items",
type: "success",
context: "local"
});
},
positiveType: "errorShade"
@@ -524,22 +565,19 @@ export const useActions = ({ close = () => null, item }) => {
}
async function exportNote() {
if (item.locked) {
ToastEvent.show({
heading: "Note is locked",
type: "error",
message: "Locked notes cannot be exported",
context: "local"
});
return;
}
ExportNotesSheet.present([item]);
}
async function toggleLocalOnly() {
if (!checkNoteSynced() || !user) return;
db.notes.note(item.id).localOnly();
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes"
);
close();
}
@@ -550,14 +588,26 @@ export const useActions = ({ close = () => null, item }) => {
useEditorStore.getState().setReadonly(current);
// tiny.call(EditorWebView, tiny.toogleReadMode(current ? 'readonly' : 'design'));
}
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes"
);
close();
};
const duplicateNote = async () => {
if (!checkNoteSynced()) return;
await db.notes.note(item.id).duplicate();
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes"
);
close();
};
const actions = [
@@ -636,7 +686,7 @@ export const useActions = ({ close = () => null, item }) => {
id: "pin-to-notifications",
title:
notifPinned !== null
? "Unpin from notifications"
? "Unpin from Notifications"
: "Pin to notifications",
icon: "message-badge-outline",
on: notifPinned !== null,
@@ -785,7 +835,15 @@ export const useActions = ({ close = () => null, item }) => {
});
Notifications.scheduleNotification(item);
useRelationStore.getState().update();
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes",
"NotesPage",
"Reminders"
);
}
},
{

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();
}
@@ -558,7 +561,7 @@ export const useAppEvents = () => {
let shareExtensionOpened = MMKV.getString("shareExtensionOpened");
if (notesAddedFromIntent) {
if (Platform.OS === "ios") {
await db.initCollections();
await db.init();
await db.notes.init();
}
useNoteStore.getState().setNotes();

View File

@@ -69,7 +69,6 @@ import {
import { editorRef, tabBarRef } from "../utils/global-refs";
import { sleep } from "../utils/time";
import { NavigationStack } from "./navigation-stack";
import changeNavigationBarColor from "react-native-navigation-bar-color";
const _TabsHolder = () => {
const colors = useThemeStore((state) => state.colors);
@@ -236,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({
@@ -285,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;
@@ -387,10 +385,6 @@ const _TabsHolder = () => {
};
}, []);
useEffect(() => {
changeNavigationBarColor(colors.bg, !colors.night, true);
}, [colors.night, colors.bg]);
return (
<View
onLayout={_onLayout}

View File

@@ -4,16 +4,18 @@
"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",
"buffer": "^6.0.3",
"dayjs": "^1.10.4",
"entities": "^3.0.1",
"html-to-text": "9.0.5",
"html-to-text": "8.1.0",
"phone": "^3.1.14",
"qclone": "^1.2.0",
"react-native-actions-sheet": "0.9.0-alpha.18",
"react-native-actions-sheet": "^0.9.0-alpha.9",
"react-native-check-version": "https://github.com/flexible-agency/react-native-check-version",
"react-native-drax": "^0.10.2",
"react-native-image-zoom-viewer": "^3.0.1",

View File

@@ -24,7 +24,6 @@ import React, {
useCallback,
useEffect,
useImperativeHandle,
useRef,
useState
} from "react";
import { Platform, ViewStyle } from "react-native";
@@ -90,7 +89,7 @@ const Editor = React.memo(
noToolbar,
noHeader
});
const renderKey = useRef(`editor-0`);
useImperativeHandle(ref, () => ({
get: () => editor
}));
@@ -125,8 +124,6 @@ const Editor = React.memo(
);
const onError = useCallback(() => {
renderKey.current =
renderKey.current === `editor-0` ? `editor-1` : `editor-0`;
editor.setLoading(true);
setTimeout(() => editor.setLoading(false), 10);
}, [editor]);
@@ -155,7 +152,6 @@ const Editor = React.memo(
testID={notesnook.editor.id}
ref={editor.ref}
onLoad={editor.onLoad}
key={renderKey.current}
onRenderProcessGone={onError}
nestedScrollEnabled
onError={onError}

View File

@@ -28,12 +28,12 @@ import { Button } from "../../components/ui/button";
import { IconButton } from "../../components/ui/icon-button";
import Paragraph from "../../components/ui/typography/paragraph";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { DDS } from "../../services/device-detection";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { eClearEditor } from "../../utils/events";
import { SIZE } from "../../utils/size";
@@ -42,9 +42,7 @@ const EditorOverlay = ({ editorId = "", editor }) => {
const colors = useThemeStore((state) => state.colors);
const [error, setError] = useState(false);
const opacity = useSharedValue(1);
const translateValue = useSharedValue(0);
const deviceMode = useSettingStore((state) => state.deviceMode);
const isTablet = deviceMode !== "mobile";
const translateValue = useSharedValue(6000);
const insets = useGlobalSafeAreaInsets();
const isDefaultEditor = editorId === "";
const timers = useRef({
@@ -52,9 +50,6 @@ const EditorOverlay = ({ editorId = "", editor }) => {
error: 0,
closing: 0
});
const loadingState = useRef({
startTime: 0
});
const clearTimers = () => {
clearTimeout(timers.current.loading);
@@ -67,7 +62,6 @@ const EditorOverlay = ({ editorId = "", editor }) => {
editorState().overlay = true;
clearTimers();
if (_loading) {
loadingState.current.startTime = Date.now();
opacity.value = 1;
translateValue.value = 0;
timers.current.error = setTimeout(() => {
@@ -75,14 +69,7 @@ const EditorOverlay = ({ editorId = "", editor }) => {
}, 15 * 1000);
} else {
clearTimers();
const timeDiffSinceLoadStarted =
Date.now() - loadingState.current.startTime > 300 ? 300 : 0;
if (!timeDiffSinceLoadStarted) {
setError(false);
editorState().overlay = false;
opacity.value = 0;
translateValue.value = 6000;
} else {
setTimeout(() => {
setError(false);
editorState().overlay = false;
opacity.value = withTiming(0, {
@@ -91,24 +78,19 @@ const EditorOverlay = ({ editorId = "", editor }) => {
setTimeout(() => {
translateValue.value = 6000;
}, 500);
}
}, 0);
}
},
[opacity, translateValue]
);
useEffect(() => {
setTimeout(() => {
if (!loadingState.current.startTime) {
translateValue.value = 6000;
}
}, 1000);
eSubscribeEvent("loadingNote" + editorId, load);
return () => {
clearTimers();
eUnSubscribeEvent("loadingNote" + editorId, load);
};
}, [editorId, load, translateValue]);
}, [editorId, load]);
const animatedStyle = useAnimatedStyle(() => {
return {
@@ -158,7 +140,7 @@ const EditorOverlay = ({ editorId = "", editor }) => {
paddingRight: 12
}}
>
{isTablet ? (
{DDS.isTablet() ? (
<View />
) : (
<IconButton
@@ -202,9 +184,7 @@ const EditorOverlay = ({ editorId = "", editor }) => {
alignItems: "center",
flexDirection: "row",
paddingHorizontal: 10,
marginTop: 5,
borderWidth: 1,
borderColor: colors.border
marginTop: 10
}}
>
<Paragraph color={colors.icon} size={13}>

View File

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

View File

@@ -106,7 +106,6 @@ typeof globalThis.editorTitle !== "undefined" && editorTitle.current && editorTi
if (editorController.content) editorController.content.current = null;
editorController.onUpdate();
editorController.setTitle(null);
editorController.countWords(0);
typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",saved:""});
`
);
@@ -130,6 +129,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)})
@@ -229,7 +229,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 })})`
);
};
@@ -238,10 +238,6 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
'response = window.dispatchEvent(new Event("handleBackPress",{cancelable:true}));'
);
};
keyboardShown = async (keyboardShown: boolean) => {
return this.doAsync(`globalThis['keyboardShown']=${keyboardShown};`);
};
//todo add replace image function
}

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,7 +32,9 @@ import {
} from "../../../services/event-manager";
import PremiumService from "../../../services/premium";
import { eCloseSheet } from "../../../utils/events";
import { sleep } from "../../../utils/time";
import { editorController, editorState } from "./utils";
import { compressToBase64 } from "../../../common/filesystem/compress";
const FILE_SIZE_LIMIT = 500 * 1024 * 1024;
const IMAGE_SIZE_LIMIT = 50 * 1024 * 1024;
@@ -148,6 +149,8 @@ const file = async (fileOptions) => {
const camera = async (options) => {
try {
await db.attachments.generateKey();
eSendEvent(eCloseSheet);
await sleep(400);
launchCamera(
{
includeBase64: true,
@@ -169,6 +172,8 @@ const camera = async (options) => {
const gallery = async (options) => {
try {
await db.attachments.generateKey();
eSendEvent(eCloseSheet);
await sleep(400);
launchImageLibrary(
{
includeBase64: true,
@@ -202,7 +207,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);
@@ -245,10 +250,7 @@ const handleImageResponse = async (response, options) => {
if (isPng || isJpeg) {
b64 =
`data:${image.type};base64, ` +
(await compressToBase64(
Platform.OS === "ios" ? "file://" + image.uri : image.uri,
isPng ? "PNG" : "JPEG"
));
(await compressToBase64(image.uri, isPng ? "PNG" : "JPEG"));
}
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
@@ -257,7 +259,7 @@ const handleImageResponse = async (response, options) => {
hash: hash,
type: image.type,
title: fileName,
dataurl: b64,
src: b64,
size: image.fileSize,
filename: fileName
});

View File

@@ -48,8 +48,6 @@ export type Settings = {
keyboardShown?: boolean;
doubleSpacedLines?: boolean;
corsProxy: string;
fontSize: string;
fontFamily: string;
};
export type EditorProps = {

View File

@@ -25,8 +25,6 @@ import { useCallback, useEffect, useRef } from "react";
import {
BackHandler,
InteractionManager,
Keyboard,
KeyboardEventListener,
NativeEventSubscription
} from "react-native";
import { WebViewMessageEvent } from "react-native-webview";
@@ -34,12 +32,13 @@ 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";
import { DDS } from "../../../services/device-detection";
import {
ToastEvent,
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
eUnSubscribeEvent,
ToastEvent
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { useEditorStore } from "../../../stores/use-editor-store";
@@ -136,30 +135,8 @@ export const useEditorEvents = (
const doubleSpacedLines = useSettingStore(
(state) => state.settings?.doubleSpacedLines
);
const defaultFontSize = useSettingStore(
(state) => state.settings.defaultFontSize
);
const defaultFontFamily = useSettingStore(
(state) => state.settings.defaultFontFamily
);
const tools = useDragState((state) => state.data);
useEffect(() => {
const handleKeyboardDidShow: KeyboardEventListener = () => {
editor.commands.keyboardShown(true);
};
const handleKeyboardDidHide: KeyboardEventListener = () => {
editor.commands.keyboardShown(false);
};
const subscriptions = [
Keyboard.addListener("keyboardDidShow", handleKeyboardDidShow),
Keyboard.addListener("keyboardDidHide", handleKeyboardDidHide)
];
return () => {
subscriptions.forEach((subscription) => subscription.remove());
};
}, [editor.commands]);
const { keyboardShown } = useKeyboard();
useEffect(() => {
editor.commands.setSettings({
@@ -170,10 +147,9 @@ export const useEditorEvents = (
tools: tools || getDefaultPresets().default,
noHeader: noHeader,
noToolbar: readonly || editorPropReadonly || noToolbar,
keyboardShown: keyboardShown || false,
doubleSpacedLines: doubleSpacedLines,
corsProxy: corsProxy,
fontSize: defaultFontSize,
fontFamily: defaultFontFamily
corsProxy: corsProxy
});
}, [
fullscreen,
@@ -184,13 +160,12 @@ export const useEditorEvents = (
deviceMode,
tools,
editor.commands,
keyboardShown,
doubleSpacedLines,
editorPropReadonly,
noHeader,
noToolbar,
corsProxy,
defaultFontSize,
defaultFontFamily
corsProxy
]);
const onBackPress = useCallback(async () => {
@@ -214,7 +189,12 @@ export const useEditorEvents = (
setImmediate(() => {
useEditorStore.getState().setCurrentlyEditingNote(null);
setTimeout(() => {
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"ColoredNotes",
"Notes",
"TaggedNotes",
"TopicNotes"
);
}, 500);
});
editorState().currentlyEditing = false;
@@ -293,28 +273,12 @@ export const useEditorEvents = (
(event: WebViewMessageEvent) => {
const data = event.nativeEvent.data;
const editorMessage = JSON.parse(data) as EditorMessage;
if (editorMessage.type === EventTypes.content) {
editor.saveContent({
type: editorMessage.type,
content: editorMessage.value as string,
forSessionId: editorMessage.sessionId
});
} else if (editorMessage.type === EventTypes.title) {
editor.saveContent({
type: editorMessage.type,
title: editorMessage.value as string,
forSessionId: editorMessage.sessionId
});
}
if (
editorMessage.sessionId !== editor.sessionId &&
editorMessage.type !== EditorEvents.status
) {
return;
}
switch (editorMessage.type) {
case EventTypes.logger:
logger.info("[WEBVIEW LOG]", editorMessage.value);
@@ -322,8 +286,21 @@ export const useEditorEvents = (
case EventTypes.contentchange:
editor.onContentChanged();
break;
case EventTypes.content:
editor.saveContent({
type: editorMessage.type,
content: editorMessage.value as string
});
break;
case EventTypes.selection:
break;
case EventTypes.title:
editor.saveContent({
type: editorMessage.type,
title: editorMessage.value as string
});
break;
case EventTypes.reminders:
if (!editor.note.current) {
ToastEvent.show({
@@ -360,7 +337,13 @@ export const useEditorEvents = (
.then(async () => {
useTagStore.getState().setTags();
await editor.commands.setTags(editor.note.current);
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"ColoredNotes",
"Notes",
"TaggedNotes",
"TopicNotes",
"Tags"
);
});
}
break;

View File

@@ -43,9 +43,9 @@ import { NoteType } from "../../../utils/types";
import Commands from "./commands";
import { Content, EditorState, Note, SavePayload } from "./types";
import {
EditorEvents,
clearAppState,
defaultState,
EditorEvents,
getAppState,
isContentInvalid,
isEditorLoaded,
@@ -76,8 +76,8 @@ export const useEditor = (
const saveCount = useRef(0);
const lastContentChangeTime = useRef<number>(0);
const lock = useRef(false);
const loadedImages = useRef<{ [name: string]: boolean }>({});
const lockedSessionId = useRef<string>();
const attachedImages = useRef<{ [name: string]: any }>({});
const loadedImages = useRef<{ [name: string]: any }>({});
const postMessage = useCallback(
async <T>(type: string, data: T) =>
@@ -120,9 +120,29 @@ export const useEditor = (
[editorId]
);
if (loading) {
setLoading(false);
}
const onReady = useCallback(async () => {
if (!(await isEditorLoaded(editorRef, sessionIdRef.current))) {
overlay(true);
setLoading(true);
}
}, [overlay]);
useEffect(() => {
state.current.saveCount = 0;
async () => {
await commands.setSessionId(sessionIdRef.current);
if (sessionIdRef.current) {
if (!state.current?.ready) return;
await onReady();
}
};
}, [sessionId, loading, commands, onReady]);
useEffect(() => {
if (loading) {
setLoading(false);
}
}, [loading]);
const withTimer = useCallback(
(id: string, fn: () => void, duration: number) => {
@@ -133,19 +153,20 @@ export const useEditor = (
);
const reset = useCallback(
async (resetState = true, resetContent = true) => {
async (resetState = true) => {
currentNote.current?.id && db.fs.cancel(currentNote.current.id);
currentNote.current = null;
loadedImages.current = {};
attachedImages.current = [];
loadedImages.current = [];
currentContent.current = null;
clearTimeout(timers.current["loading-images"]);
sessionHistoryId.current = undefined;
saveCount.current = 0;
useEditorStore.getState().setReadonly(false);
resetContent && postMessage(EditorEvents.title, "");
postMessage(EditorEvents.title, "");
lastContentChangeTime.current = 0;
resetContent && (await commands.clearContent());
resetContent && (await commands.clearTags());
await commands.clearContent();
await commands.clearTags();
if (resetState) {
isDefaultEditor &&
useEditorStore.getState().setCurrentlyEditingNote(null);
@@ -209,6 +230,7 @@ export const useEditor = (
id = await db.notes?.add(noteData);
if (!note && id) {
currentNote.current = db.notes?.note(id).data as NoteType;
console.log("on Note Created", state.current?.onNoteCreated);
state.current?.onNoteCreated && state.current.onNoteCreated(id);
if (!noteData.title) {
postMessage(
@@ -220,15 +242,9 @@ export const useEditor = (
if (
useEditorStore.getState().currentEditingNote !== id &&
isDefaultEditor &&
state.current.currentlyEditing
isDefaultEditor
) {
setTimeout(() => {
if (
(currentNote.current?.id && currentNote.current?.id !== id) ||
!state.current.currentlyEditing
)
return;
id && useEditorStore.getState().setCurrentlyEditingNote(id);
});
}
@@ -249,7 +265,12 @@ export const useEditor = (
currentNote.current?.headline?.slice(0, 200) !==
note.headline?.slice(0, 200)
) {
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
"ColoredNotes",
"Notes",
"TaggedNotes",
"TopicNotes"
);
}
}
@@ -275,38 +296,32 @@ export const useEditor = (
}
}, []);
const getMediaToLoad = (previousContent?: string) => {
const getImagesToLoad = () => {
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;
const currentImages = [
...(db.attachments?.ofNote(currentNote.current?.id, "images") || []),
...(db.attachments?.ofNote(currentNote.current?.id, "webclips") || [])
];
if (!currentImages || currentImages?.length === 0) return [];
const imagesToLoad: any[] = [];
for (const image of currentImages) {
if (!loadedImages.current[image.metadata.hash]) {
loadedImages.current[image.metadata.hash] = false;
imagesToLoad.push(image);
}
attachedImages.current[image.metadata.hash] = image;
}
return media;
return imagesToLoad;
};
const markImageLoaded = (hash: string) => {
const attachment = loadedImages.current[hash];
if (typeof attachment === "boolean") {
const attachment = attachedImages.current[hash];
if (attachment) {
loadedImages.current[hash] = true;
}
};
const loadImages = useCallback((previousContent?: string) => {
const loadImages = useCallback(() => {
if (!currentNote.current?.id) return;
const timerId = "loading-images";
clearTimeout(timers.current[timerId]);
@@ -319,12 +334,12 @@ export const useEditor = (
true
);
} else {
const media = getMediaToLoad(previousContent);
if (media.length > 0) {
db.attachments?.downloadMedia(currentNote.current?.id, media);
const images = getImagesToLoad();
if (images.length > 0) {
db.attachments?.downloadMedia(currentNote.current?.id, images);
}
}
}, 1000);
}, 100);
}, []);
const loadNote = useCallback(
@@ -349,26 +364,16 @@ export const useEditor = (
useEditorStore.getState().setReadonly(false);
} else {
if (!item.forced && currentNote.current?.id === item.id) return;
state.current.movedAway = false;
state.current.currentlyEditing = true;
isDefaultEditor && editorState.setCurrentlyEditingNote(item.id);
currentNote.current && (await reset(false, false));
overlay(true, item);
currentNote.current && (await reset(false));
await loadContent(item as NoteType);
if (
!currentContent.current?.data ||
currentContent.current?.data.length < 50000
) {
overlay(false);
} else {
overlay(true);
}
lastContentChangeTime.current = item.dateEdited;
const nextSessionId = makeSessionId(item as NoteType);
lockedSessionId.current = nextSessionId;
sessionHistoryId.current = Date.now();
setSessionId(nextSessionId);
commands.setSessionId(nextSessionId);
sessionIdRef.current = nextSessionId;
await commands.setSessionId(nextSessionId);
currentNote.current = item as NoteType;
await commands.setStatus(timeConverter(item.dateEdited), "Saved");
await postMessage(EditorEvents.title, item.title);
@@ -376,11 +381,6 @@ export const useEditor = (
useEditorStore.getState().setReadonly(item.readonly);
await commands.setTags(currentNote.current);
commands.setSettings();
setTimeout(() => {
if (lockedSessionId.current === nextSessionId) {
lockedSessionId.current = undefined;
}
}, 300);
overlay(false);
loadImages();
}
@@ -423,8 +423,6 @@ export const useEditor = (
lock.current = true;
const previousContent = currentContent.current?.data;
if (data.type === "tiptap") {
if (!currentNote.current.locked && isContentEncrypted) {
lockNoteWithVault(note);
@@ -455,14 +453,13 @@ export const useEditor = (
}
await commands.setStatus(timeConverter(note.dateEdited), "Saved");
}
lock.current = false;
if (data.type === "tiptap") {
loadImages(previousContent);
loadImages();
db.eventManager.subscribe(
EVENTS.syncCompleted,
() => {
loadImages(previousContent);
loadImages();
},
true
);
@@ -487,16 +484,14 @@ export const useEditor = (
({
title,
content,
type,
forSessionId
type
}: {
title?: string;
content?: string;
type: string;
forSessionId: string;
}) => {
if (lock.current || lockedSessionId.current === forSessionId) return;
lastContentChangeTime.current = Date.now();
if (lock.current) return;
if (type === EditorEvents.content) {
currentContent.current = {
data: content,
@@ -504,27 +499,22 @@ export const useEditor = (
noteId: currentNote.current?.id as string
};
}
const noteIdFromSessionId =
!forSessionId || forSessionId.startsWith("session")
? null
: forSessionId.split("_")[0];
const noteId = noteIdFromSessionId || currentNote.current?.id;
const params = {
title,
data: content,
type: "tiptap",
sessionId: forSessionId,
id: noteId,
sessionId,
id: currentNote.current?.id,
sessionHistoryId: sessionHistoryId.current
};
withTimer(
noteId || "newnote",
currentNote.current?.id || "newnote",
() => {
if (
currentNote.current &&
!params.id &&
params.sessionId === forSessionId
params.sessionId === sessionId
) {
params.id = currentNote.current?.id;
}
@@ -534,10 +524,10 @@ export const useEditor = (
}
saveNote(params);
},
150
500
);
},
[withTimer, onChange, saveNote]
[sessionId, withTimer, onChange, saveNote]
);
const restoreEditorState = useCallback(async () => {
@@ -547,7 +537,6 @@ export const useEditor = (
state.current.isRestoringState = true;
state.current.currentlyEditing = true;
state.current.movedAway = false;
if (!DDS.isTab) {
tabBarRef.current?.goToPage(1, false);
}
@@ -567,6 +556,10 @@ export const useEditor = (
state.current.isRestoringState = false;
}, [loadNote, overlay]);
useEffect(() => {
isDefaultEditor && restoreEditorState();
}, [isDefaultEditor, restoreEditorState]);
useEffect(() => {
eSubscribeEvent(eOnLoadNote + editorId, loadNote);
return () => {
@@ -574,30 +567,6 @@ export const useEditor = (
};
}, [editorId, loadNote, restoreEditorState, isDefaultEditor]);
const onContentChanged = () => {
lastContentChangeTime.current = Date.now();
};
const onReady = useCallback(async () => {
if (!(await isEditorLoaded(editorRef, sessionIdRef.current))) {
overlay(true);
setLoading(true);
} else {
isDefaultEditor && restoreEditorState();
}
}, [overlay, isDefaultEditor, restoreEditorState]);
useEffect(() => {
state.current.saveCount = 0;
async () => {
await commands.setSessionId(sessionIdRef.current);
if (sessionIdRef.current) {
if (!state.current?.ready) return;
await onReady();
}
};
}, [sessionId, loading, commands, onReady]);
const onLoad = useCallback(async () => {
state.current.ready = true;
onReady();
@@ -621,6 +590,10 @@ export const useEditor = (
loadNote
]);
const onContentChanged = () => {
lastContentChangeTime.current = Date.now();
};
return {
ref: editorRef,
onLoad,

View File

@@ -22,9 +22,9 @@ import { db } from "../../common/database";
import DelayLayout from "../../components/delay-layout";
import List from "../../components/list";
import { NotebookHeader } from "../../components/list-items/headers/notebook-header";
import { AddNotebookSheet } from "../../components/sheets/add-notebook";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
@@ -33,7 +33,7 @@ import SearchService from "../../services/search";
import useNavigationStore, {
NotebookScreenParams
} from "../../stores/use-navigation-store";
import { eOnNewTopicAdded } from "../../utils/events";
import { eOnNewTopicAdded, eOpenAddNotebookDialog } from "../../utils/events";
import { NotebookType } from "../../utils/types";
import { openEditor, setOnFirstSave } from "../notes/common";
const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
@@ -148,7 +148,7 @@ const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
ListHeader={
<NotebookHeader
onEditNotebook={() => {
AddNotebookSheet.present(params.current.item);
eSendEvent(eOpenAddNotebookDialog, params.current.item);
}}
notebook={params.current.item}
/>

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";
@@ -94,6 +94,15 @@ async function onNoteCreated(id: string, params: FirstSaveData) {
{ type: "notebook", id: params.id },
{ type: "note", id: id }
);
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes",
"Notebook",
"Notebooks"
);
editorState().onNoteCreated = null;
break;
}
@@ -107,18 +116,40 @@ async function onNoteCreated(id: string, params: FirstSaveData) {
id
);
editorState().onNoteCreated = null;
eSendEvent(eOnTopicSheetUpdate);
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes",
"Notebook",
"Notebooks"
);
break;
}
case "tag": {
await db.notes?.note(id).tag(params.id);
editorState().onNoteCreated = null;
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes"
);
useTagStore.getState().setTags();
break;
}
case "color": {
await db.notes?.note(id).color(params.color);
editorState().onNoteCreated = null;
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes"
);
useMenuStore.getState().setColorNotes();
break;
}
@@ -126,5 +157,4 @@ async function onNoteCreated(id: string, params: FirstSaveData) {
break;
}
}
Navigation.queueRoutesForUpdate();
}

View File

@@ -18,13 +18,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useRef, useState } from "react";
import { View } from "react-native";
import { db } from "../../common/database";
import { FloatingButton } from "../../components/container/floating-button";
import DelayLayout from "../../components/delay-layout";
import List from "../../components/list";
import { IconButton } from "../../components/ui/icon-button";
import Paragraph from "../../components/ui/typography/paragraph";
import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import {
eSubscribeEvent,
@@ -38,9 +34,7 @@ import useNavigationStore, {
RouteName
} from "../../stores/use-navigation-store";
import { useNoteStore } from "../../stores/use-notes-store";
import { SIZE } from "../../utils/size";
import { NoteType, TopicType } from "../../utils/types";
import Notebook from "../notebook/index";
import {
getAlias,
openEditor,
@@ -48,6 +42,13 @@ 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"
};
@@ -98,6 +99,7 @@ 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);
@@ -181,9 +183,8 @@ const NotesPage = ({
if (isNew) setLoadingNotes(true);
const notes = get(params.current, true) as NoteType[];
if (
((item.type === "tag" || item.type === "color") &&
(!notes || notes.length === 0)) ||
(item.type === "topic" && !notes)
(item.type === "tag" || item.type === "color") &&
(!notes || notes.length === 0)
) {
return Navigation.goBack();
}
@@ -244,23 +245,19 @@ const NotesPage = ({
>
Notebooks
</Paragraph>
{notebook ? (
<>
<IconButton
name="chevron-right"
size={14}
customStyle={{ width: 25, height: 25 }}
/>
<Paragraph
onPress={() => {
Notebook.navigate(notebook, true);
}}
size={SIZE.xs}
>
{notebook.title}
</Paragraph>
</>
) : null}
<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
@@ -279,9 +276,7 @@ const NotesPage = ({
placeholderData={placeholderData}
/>
{!isMonograph &&
route.name !== "TopicNotes" &&
(notes?.length > 0 || isFocused) ? (
{notes?.length > 0 || (isFocused && !isMonograph) ? (
<FloatingButton title="Create a note" onPress={onPressFloatingButton} />
) : null}
</DelayLayout>

View File

@@ -75,11 +75,7 @@ export const TopicNotes = ({
TopicNotes.get = (params: NotesScreenParams, grouped = true) => {
const { id, notebookId } = params.item as TopicType;
const topic = db.notebooks?.notebook(notebookId)?.topics.topic(id);
if (!topic) {
return null;
}
const notes = topic?.all || [];
const notes = db.notebooks?.notebook(notebookId)?.topics.topic(id)?.all || [];
return grouped
? groupArray(notes, db.settings?.getGroupOptions("notes"))
: notes;

View File

@@ -26,7 +26,6 @@ import { Licenses } from "./licenses";
import SoundPicker from "./sound-picker";
import { Subscription } from "./subscription";
import { TrashIntervalSelector } from "./trash-interval-selector";
import { FontSelector } from "./font-selector";
export const components: { [name: string]: ReactElement } = {
colorpicker: <AccentColorPicker />,
homeselector: <HomagePageSelector />,
@@ -36,6 +35,5 @@ export const components: { [name: string]: ReactElement } = {
"debug-logs": <DebugLogs />,
"sound-picker": <SoundPicker />,
licenses: <Licenses />,
"trash-interval-selector": <TrashIntervalSelector />,
"font-selector": <FontSelector />
"trash-interval-selector": <TrashIntervalSelector />
};

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

@@ -1,110 +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 { PressableButton } from "../../components/ui/pressable";
import Paragraph from "../../components/ui/typography/paragraph";
import SettingsService from "../../services/settings";
import { useSettingStore } from "../../stores/use-setting-store";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import { getFontById, getFonts } from "@notesnook/editor/dist/utils/font";
export const FontSelector = () => {
const colors = useThemeStore((state) => state.colors);
const defaultFontFamily = useSettingStore(
(state) => state.settings.defaultFontFamily
);
const menuRef = useRef();
const [width, setWidth] = useState(0);
const onChange = (item) => {
menuRef.current?.hide();
SettingsService.set({
defaultFontFamily: 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>{getFontById(defaultFontFamily).title}</Paragraph>
<Icon color={colors.icon} name="menu-down" size={SIZE.md} />
</PressableButton>
}
>
{getFonts().map((item) => (
<MenuItem
key={item.id}
onPress={async () => {
onChange(item.id);
}}
style={{
backgroundColor:
defaultFontFamily === item.id ? colors.nav : "transparent",
width: "100%",
maxWidth: width
}}
textStyle={{
fontSize: SIZE.md,
color: defaultFontFamily === item.id ? colors.accent : colors.pri
}}
>
{item.title}
</MenuItem>
))}
</Menu>
</View>
);
};

View File

@@ -20,7 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { NativeStackScreenProps } from "@react-navigation/native-stack";
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import Animated, { FadeInDown } from "react-native-reanimated";
import Animated, { FadeInDown, FadeOutDown } from "react-native-reanimated";
import DelayLayout from "../../components/delay-layout";
import BaseDialog from "../../components/dialog/base-dialog";
import { ProgressBarComponent } from "../../components/ui/svg/lazy";
@@ -75,55 +75,57 @@ const Home = ({
return (
<DelayLayout delay={300} type="settings">
{loading && (
//@ts-ignore // Migrate to typescript required.
<BaseDialog animated={false} bounce={false} visible={true}>
<View
style={{
width: "100%",
height: "100%",
backgroundColor: colors.bg,
justifyContent: "center",
alignItems: "center"
}}
>
<Heading color={colors.pri} size={SIZE.lg}>
Logging out
</Heading>
<Paragraph color={colors.icon}>
Please wait while we log out and clear app data.
</Paragraph>
<View>
{loading && (
//@ts-ignore // Migrate to typescript required.
<BaseDialog animated={false} bounce={false} visible={true}>
<View
style={{
flexDirection: "row",
width: 100,
marginTop: 15
width: "100%",
height: "100%",
backgroundColor: colors.bg,
justifyContent: "center",
alignItems: "center"
}}
>
<ProgressBarComponent
height={5}
width={100}
animated={true}
useNativeDriver
indeterminate
indeterminateAnimationDuration={2000}
unfilledColor={colors.nav}
color={colors.accent}
borderWidth={0}
/>
<Heading color={colors.pri} size={SIZE.lg}>
Logging out
</Heading>
<Paragraph color={colors.icon}>
Please wait while we log out and clear app data.
</Paragraph>
<View
style={{
flexDirection: "row",
width: 100,
marginTop: 15
}}
>
<ProgressBarComponent
height={5}
width={100}
animated={true}
useNativeDriver
indeterminate
indeterminateAnimationDuration={2000}
unfilledColor={colors.nav}
color={colors.accent}
borderWidth={0}
/>
</View>
</View>
</View>
</BaseDialog>
)}
</BaseDialog>
)}
<Animated.FlatList
entering={FadeInDown}
data={settingsGroups}
windowSize={1}
keyExtractor={keyExtractor}
ListFooterComponent={<View style={{ height: 200 }} />}
renderItem={renderItem}
/>
<Animated.FlatList
entering={FadeInDown}
exiting={FadeOutDown}
data={settingsGroups}
keyExtractor={keyExtractor}
ListFooterComponent={<View style={{ height: 200 }} />}
renderItem={renderItem}
/>
</View>
</DelayLayout>
);
};

View File

@@ -42,12 +42,12 @@ export const SectionGroup = ({ item }: { item: SettingSection }) => {
color={colors.accent}
size={SIZE.xs}
>
{(item.name as string).toUpperCase()}
{item.name.toUpperCase()}
</Heading>
) : null}
{item.sections?.map((item) => (
<SectionItem key={item.name as string} item={item} />
<SectionItem key={item.name} item={item} />
))}
</View>
);

View File

@@ -37,7 +37,6 @@ import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import { components } from "./components";
import { RouteParams, SettingSection } from "./types";
import { IconButton } from "../../components/ui/icon-button";
const _SectionItem = ({ item }: { item: SettingSection }) => {
const colors = useThemeStore((state) => state.colors);
@@ -65,12 +64,6 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
backgroundColor: colors.errorBg
}
: {};
const updateInput = (value: any) => {
inputRef?.current?.setNativeProps({
text: value + ""
});
};
return isHidden ? null : (
<PressableButton
disabled={item.type === "component"}
@@ -176,14 +169,6 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
}
item.inputProperties?.onSubmitEditing?.(e);
}}
onChangeText={(text) => {
if (text) {
SettingsService.set({
[item.property as string]: text
});
}
item.inputProperties?.onSubmitEditing?.(text as any);
}}
containerStyle={{ marginTop: 12 }}
fwdRef={inputRef}
onLayout={() => {
@@ -197,93 +182,6 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
defaultValue={item.inputProperties?.defaultValue}
/>
)}
{item.type === "input-selector" && (
<View
style={{
flexDirection: "row",
alignItems: "center",
marginTop: 12
}}
>
<IconButton
name="minus"
onPress={() => {
const rawValue = SettingsService.get()[
item.property as keyof SettingStore["settings"]
] as string;
if (rawValue) {
const currentValue = parseInt(rawValue);
if (currentValue <= 0) return;
const nextValue = currentValue - 1;
SettingsService.set({
[item.property as string]: nextValue
});
updateInput(nextValue);
}
}}
size={SIZE.xl}
/>
<Input
{...item.inputProperties}
onSubmit={(e) => {
if (e.nativeEvent.text) {
SettingsService.set({
[item.property as string]: e.nativeEvent.text
});
}
item.inputProperties?.onSubmitEditing?.(e);
}}
onChangeText={(text) => {
if (text) {
if (item.minInputValue) {
text =
parseInt(text) < item.minInputValue
? item.minInputValue + ""
: text;
}
SettingsService.set({
[item.property as string]: text
});
}
item.inputProperties?.onSubmitEditing?.(text as any);
}}
keyboardType="decimal-pad"
containerStyle={{
width: 45
}}
wrapperStyle={{
maxWidth: 45,
marginBottom: 0,
marginHorizontal: 6
}}
fwdRef={inputRef}
onLayout={() => {
if (item.property) {
updateInput(SettingsService.get()[item.property]);
}
}}
defaultValue={item.inputProperties?.defaultValue}
/>
<IconButton
name="plus"
onPress={() => {
const rawValue = SettingsService.get()[
item.property as keyof SettingStore["settings"]
] as string;
if (rawValue) {
const currentValue = parseInt(rawValue);
const nextValue = currentValue + 1;
SettingsService.set({
[item.property as string]: nextValue
});
updateInput(nextValue);
}
}}
size={SIZE.xl}
/>
</View>
)}
</View>
</View>

View File

@@ -17,8 +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";
@@ -27,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";
@@ -59,6 +56,7 @@ import { SUBSCRIPTION_STATUS } from "../../utils/constants";
import {
eCloseSheet,
eCloseSimpleDialog,
eOpenAttachmentsDialog,
eOpenLoginDialog,
eOpenRecoveryKeyDialog,
eOpenRestoreDialog
@@ -70,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;
@@ -150,7 +149,7 @@ export const settingsGroups: SettingSection[] = [
name: "Manage attachments",
icon: "attachment",
modifer: () => {
AttachmentDialog.present();
eSendEvent(eOpenAttachmentsDialog);
},
description: "Manage all attachments in one place."
},
@@ -318,6 +317,7 @@ export const settingsGroups: SettingSection[] = [
await PremiumService.setPremiumStatus();
await BiometicService.resetCredentials();
MMKV.clearStore();
await db.init();
await clearAllStores();
SettingsService.init();
setTimeout(() => {
@@ -451,13 +451,13 @@ export const settingsGroups: SettingSection[] = [
},
{
id: "customize",
name: "Customization",
name: "Customize",
sections: [
{
id: "personalization",
type: "screen",
name: "Theme",
description: "Change app look and feel with color themes",
description: "Change app look and feel",
icon: "shape",
sections: [
{
@@ -515,7 +515,7 @@ export const settingsGroups: SettingSection[] = [
id: "behaviour",
type: "screen",
name: "Behaviour",
description: "Change how the app behaves in different situations",
description: "Change app homepage",
sections: [
{
id: "default-home",
@@ -533,63 +533,6 @@ export const settingsGroups: SettingSection[] = [
component: "trash-interval-selector"
}
]
},
{
id: "editor",
name: "Editor",
type: "screen",
icon: "note-edit-outline",
description: "Customize the editor to fit your needs",
sections: [
{
id: "configure-toolbar",
type: "screen",
name: "Configure toolbar",
description: "Make the toolbar adaptable to your needs.",
component: "configuretoolbar"
},
{
id: "reset-toolbar",
name: "Reset toolbar",
description: "Reset toolbar configuration to default",
modifer: () => {
useDragState.getState().setPreset("default");
}
},
{
id: "double-spaced-lines",
name: "Use double spaced lines",
description:
"New lines will be double spaced (old ones won't be affected).",
type: "switch",
property: "doubleSpacedLines",
icon: "format-line-spacing",
onChange: () => {
ToastEvent.show({
heading: "Line spacing changed",
type: "success"
});
}
},
{
id: "default-font-size",
name: "Default font size",
description: "Set the default font size in editor",
type: "input-selector",
minInputValue: 8,
icon: "format-size",
property: "defaultFontSize"
},
{
id: "default-font-family",
name: "Default font family",
description: "Set the default font family in editor",
type: "component",
icon: "format-font",
property: "defaultFontFamily",
component: "font-selector"
}
]
}
]
},
@@ -986,6 +929,44 @@ export const settingsGroups: SettingSection[] = [
}
]
},
{
id: "editor",
name: "Editor",
sections: [
{
id: "configure-toolbar",
type: "screen",
name: "Configure toolbar",
description: "Make the toolbar adaptable to your needs.",
component: "configuretoolbar"
},
{
id: "reset-toolbar",
name: "Reset toolbar",
description: "Reset toolbar configuration to default",
modifer: () => {
useDragState.getState().setPreset("default");
}
},
{
id: "double-spaced-lines",
name: "Use double spaced lines",
description:
"New lines will be double spaced (old ones won't be affected).",
type: "switch",
property: "doubleSpacedLines",
icon: "format-line-spacing",
onChange: () => {
ToastEvent.show({
heading: "Line spacing changed",
type: "success",
message:
"Close and reopen the current opened note or restart the app for changes to take affect."
});
}
}
]
},
{
id: "help-support",
name: "Help and support",
@@ -1046,21 +1027,11 @@ export const settingsGroups: SettingSection[] = [
id: "join-telegram",
name: "Join our Telegram group",
description: "We are on telegram, let's talk",
// icon: 'telegram',
modifer: () => {
Linking.openURL("https://t.me/notesnook").catch(console.log);
}
},
{
id: "join-mastodom",
name: "Follow us on Mastodon",
description: "We are on mastodom",
icon: "mastodon",
modifer: () => {
Linking.openURL("https://fosstodon.org/@notesnook").catch(
console.log
);
}
},
{
id: "join-twitter",
name: "Follow us on twitter",

View File

@@ -87,7 +87,7 @@ export const TrashIntervalSelector = () => {
>
{[-1, 7, 30, 365].map((item) => (
<MenuItem
key={item.toString()}
key={item.name}
onPress={async () => {
if (item === -1) {
await PremiumService.verify(() => {

View File

@@ -22,14 +22,7 @@ import { Settings } from "../../stores/use-setting-store";
export type SettingSection = {
id: string;
type?:
| "screen"
| "switch"
| "component"
| "danger"
| "input"
| "input-selector"
| "dropdown-selector";
type?: "screen" | "switch" | "component" | "danger" | "input";
name?: string | ((current?: unknown) => string);
description?: string | ((current: unknown) => string);
icon?: string;
@@ -42,8 +35,6 @@ export type SettingSection = {
hidden?: (current: unknown) => boolean;
onChange?: (property: boolean) => void;
inputProperties?: TextInput["props"];
options?: any[];
minInputValue?: number;
};
export type SettingsGroup = {

View File

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

View File

@@ -35,7 +35,6 @@ import { eOnNewTopicAdded } from "../utils/events";
import { rootNavigatorRef, tabBarRef } from "../utils/global-refs";
import { eSendEvent } from "./event-manager";
import SettingsService from "./settings";
import SearchService from "./search";
/**
* Routes that should be updated on focus
@@ -87,8 +86,7 @@ const routeUpdateFunctions: {
ColoredNotes: (params) => eSendEvent("ColoredNotes", params),
TopicNotes: (params) => eSendEvent("TopicNotes", params),
Monographs: (params) => eSendEvent("Monographs", params),
Reminders: () => useReminderStore.getState().setReminders(),
Search: () => SearchService.updateAndSearch()
Reminders: () => useReminderStore.getState().setReminders()
};
function clearRouteFromQueue(routeName: RouteName) {
@@ -108,14 +106,10 @@ function routeNeedsUpdate(routeName: RouteName, callback: () => void) {
}
}
function queueRoutesForUpdate(...routesToUpdate: RouteName[]) {
const routes =
routesToUpdate?.length > 0
? routesToUpdate
: (Object.keys(routeNames) as (keyof RouteParams)[]);
function queueRoutesForUpdate(...routes: RouteName[]) {
const currentScreen = useNavigationStore.getState().currentScreen;
if (routes.indexOf(currentScreen.name) > -1) {
routeUpdateFunctions[currentScreen.name]?.();
routeUpdateFunctions[currentScreen.name]();
clearRouteFromQueue(currentScreen.name);
// Remove focused screen from queue
routes.splice(routes.indexOf(currentScreen.name), 1);
@@ -131,7 +125,7 @@ function navigate<T extends RouteName>(
useNavigationStore.getState().update(screen, !!params?.canGoBack);
if (screen.name === "Notebook") routeUpdateFunctions["Notebook"](params);
if (screen.name.endsWith("Notes") && screen.name !== "Notes")
routeUpdateFunctions[screen.name]?.(params);
routeUpdateFunctions[screen.name](params);
//@ts-ignore Not sure how to fix this for now ignore it.
rootNavigatorRef.current?.navigate<RouteName>(screen.name, params);
}

View File

@@ -85,20 +85,13 @@ async function getNextMonthlyReminderDate(
return await getNextMonthlyReminderDate(reminder, dayjs().year() + 1);
}
async function initDatabase(notes = true) {
if (db.isInitialized) return;
await db.initCollections();
if (notes) {
await db.notes?.init();
}
}
const onEvent = async ({ type, detail }: Event) => {
const { notification, pressAction, input } = detail;
if (type === EventType.DELIVERED && Platform.OS === "android") {
const reminder = db.reminders?.reminder(notification?.id?.split("_")[0]);
if (reminder && reminder.recurringMode === "month") {
await initDatabase();
await db.init();
await db.notes?.init();
await scheduleNotification(reminder);
}
return;
@@ -107,10 +100,10 @@ const onEvent = async ({ type, detail }: Event) => {
notifee.decrementBadgeCount();
if (notification?.data?.type === "quickNote") return;
MMKV.removeItem("appState");
await initDatabase();
await db.init();
await db.notes?.init();
if (notification?.data?.type === "reminder") {
const reminder = db.reminders?.reminder(notification.id?.split("_")[0]);
if (!reminder) return;
await sleep(1000);
const ReminderNotify =
require("../components/sheets/reminder-notify").default;
@@ -138,7 +131,8 @@ const onEvent = async ({ type, detail }: Event) => {
notifee.decrementBadgeCount();
switch (pressAction?.id) {
case "REMINDER_SNOOZE": {
await initDatabase();
await db.init();
await db.notes?.init();
const reminder = db.reminders?.reminder(
notification?.id?.split("_")[0]
);
@@ -157,7 +151,8 @@ const onEvent = async ({ type, detail }: Event) => {
break;
}
case "REMINDER_DISABLE": {
await initDatabase();
await db.init();
await db.notes?.init();
const reminder = db.reminders?.reminder(
notification?.id?.split("_")[0]
);
@@ -173,7 +168,8 @@ const onEvent = async ({ type, detail }: Event) => {
break;
}
case "UNPIN": {
await initDatabase();
await db.init();
await db.notes?.init();
remove(notification?.id as string);
const reminder = db.reminders?.reminder(
notification?.id?.split("_")[0]
@@ -202,7 +198,7 @@ const onEvent = async ({ type, detail }: Event) => {
reply_button_text: "Take note",
reply_placeholder_text: "Write something..."
});
await initDatabase(false);
await db.init();
await db.notes?.add({
content: {
type: "tiptap",
@@ -371,8 +367,7 @@ async function scheduleNotification(
function loadNote(id: string, jump: boolean) {
if (!id || id === "notesnook_note_input") return;
const note = db.notes?.note(id)?.data;
if (!note) return;
const note = db.notes?.note(id).data;
if (!DDS.isTab && jump) {
tabBarRef.current?.goToPage(1);
}

View File

@@ -26,7 +26,7 @@ let searchInformation = {
placeholder: "Search in all notes",
data: [],
type: "notes",
get: () => []
get: () => null
};
let keyword = null;

View File

@@ -19,19 +19,22 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { db } from "../common/database";
import Navigation from "../services/navigation";
import Notifications from "../services/notifications";
import { useFavoriteStore } from "./use-favorite-store";
import { useMenuStore } from "./use-menu-store";
import { RouteName } from "./use-navigation-store";
import { useNotebookStore } from "./use-notebook-store";
import { useNoteStore } from "./use-notes-store";
import { useRelationStore } from "./use-relation-store";
import { useReminderStore } from "./use-reminder-store";
import { useTagStore } from "./use-tag-store";
import { useTrashStore } from "./use-trash-store";
import { useReminderStore } from "./use-reminder-store";
import Notifications from "../services/notifications";
import { useRelationStore } from "./use-relation-store";
export function initAfterSync() {
useMenuStore.getState().setColorNotes();
useMenuStore.getState().setMenuPins();
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(
...(Object.keys(Navigation.routeUpdateFunctions) as unknown as RouteName[])
);
// Whenever sync completes, try to reschedule
// any new/updated reminders.
Notifications.setupReminders(true);

View File

@@ -69,7 +69,6 @@ export type RouteParams = {
AppLock: AppLockRouteParams;
Auth: AuthParams;
Reminders: GenericRouteParam;
SettingsGroup: GenericRouteParam;
};
export type RouteName = keyof RouteParams;

View File

@@ -73,8 +73,6 @@ export type Settings = {
corsProxy: string;
disableRealtimeSync?: boolean;
notificationSound?: Sound & { platform: PlatformOSType };
defaultFontSize: string;
defaultFontFamily: string;
};
type DimensionsType = {
@@ -151,9 +149,7 @@ export const useSettingStore = create<SettingStore>((set) => ({
defaultSnoozeTime: "5",
corsProxy: "https://cors.notesnook.com",
reminderNotificationMode: "urgent",
notificationSound: undefined,
defaultFontFamily: "sans-serif",
defaultFontSize: "16"
notificationSound: undefined
},
sheetKeyboardHandler: true,
fullscreen: false,

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

@@ -25,7 +25,7 @@ import SearchService from "../services/search";
import { useSelectionStore } from "../stores/use-selection-store";
import { useMenuStore } from "../stores/use-menu-store";
import { db } from "../common/database";
import { eClearEditor, eOnTopicSheetUpdate } from "./events";
import { eClearEditor } from "./events";
import { useRelationStore } from "../stores/use-relation-store";
import { presentDialog } from "../components/dialog/functions";
@@ -38,14 +38,11 @@ function deleteConfirmDialog(items, type, context) {
positiveText: "Delete",
negativeText: "Cancel",
positivePress: (value) => {
setTimeout(() => {
resolve({ delete: true, deleteNotes: value });
});
console.log(value);
resolve({ delete: true, deleteNotes: value });
},
onClose: () => {
setTimeout(() => {
resolve({ delete: false });
});
resolve({ delete: false });
},
context: context,
check: {
@@ -161,7 +158,7 @@ export const deleteItems = async (item, context) => {
}
}
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(...routesForUpdate);
let msgPart = history.selectedItemsList.length === 1 ? " item" : " items";
let message = history.selectedItemsList.length + msgPart + " moved to trash.";
@@ -185,7 +182,7 @@ export const deleteItems = async (item, context) => {
}
await db.trash.restore(...ids);
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate(routesForUpdate);
useMenuStore.getState().setMenuPins();
useMenuStore.getState().setColorNotes();
ToastEvent.hide();
@@ -194,12 +191,11 @@ export const deleteItems = async (item, context) => {
});
}
history.selectedItemsList = [];
Navigation.queueRoutesForUpdate();
Navigation.queueRoutesForUpdate("Trash");
useSelectionStore.getState().clearSelection(true);
useMenuStore.getState().setMenuPins();
useMenuStore.getState().setColorNotes();
SearchService.updateAndSearch();
eSendEvent(eOnTopicSheetUpdate);
};
export const openLinkInBrowser = async (link) => {

View File

@@ -27,6 +27,7 @@ import { db } from "../common/database";
import { tabBarRef } from "./global-refs";
let prevTarget = null;
let htmlToText;
export const TOOLTIP_POSITIONS = {
LEFT: 1,
@@ -144,12 +145,19 @@ export function getTotalNotes(item) {
return db.notebooks.notebook(item.id)?.totalNotes || 0;
}
export async function toTXT(note, template = true) {
export async function toTXT(note, notitle) {
let text;
if (note.locked) {
text = await db.notes.note(note.id).export("txt", note.content, template);
text = note.content.data;
} else {
text = await db.notes.note(note.id).export("txt", undefined, template);
text = await db.notes.note(note.id).content();
}
htmlToText = htmlToText || require("html-to-text");
text = htmlToText.convert(text, {
selectors: [{ selector: "img", format: "skip" }]
});
if (!notitle) {
text = `${note.title}\n \n ${text}`;
}
return text;
}
@@ -170,8 +178,3 @@ export function showTooltip(event, text, position = 2) {
clickToHide: true
});
}
export function toTitleCase(value) {
if (!value) return;
return value.slice(0, 1).toUpperCase() + value.slice(1);
}

View File

@@ -80,10 +80,10 @@ export const normalize = (size) => {
}
};
export const SIZE = {
xxs: normalize(11) * scale.fontScale,
xs: normalize(12.5) * scale.fontScale,
sm: normalize(15) * scale.fontScale,
md: normalize(16.5) * scale.fontScale,
xxs: normalize(10.5) * scale.fontScale,
xs: normalize(12) * scale.fontScale,
sm: normalize(14.5) * scale.fontScale,
md: normalize(16) * scale.fontScale,
lg: normalize(22) * scale.fontScale,
xl: normalize(24) * scale.fontScale,
xxl: normalize(28) * scale.fontScale,
@@ -91,10 +91,10 @@ export const SIZE = {
};
export function updateSize() {
SIZE.xxs = normalize(11) * scale.fontScale;
SIZE.xs = normalize(12.5) * scale.fontScale;
SIZE.sm = normalize(15) * scale.fontScale;
SIZE.md = normalize(16.5) * scale.fontScale;
SIZE.xxs = normalize(10.5) * scale.fontScale;
SIZE.xs = normalize(12) * scale.fontScale;
SIZE.sm = normalize(14.5) * scale.fontScale;
SIZE.md = normalize(16) * scale.fontScale;
SIZE.lg = normalize(22) * scale.fontScale;
SIZE.xl = normalize(24) * scale.fontScale;
SIZE.xxl = normalize(28) * scale.fontScale;

View File

@@ -55,13 +55,12 @@ describe("NOTE TESTS", () => {
await prepare();
let note = await createNote();
await tapById(notesnook.listitem.menu);
await tapById("icon-favorite");
await tapById("icon-Favorite");
await visibleById("icon-star");
await navigate("Favorites");
await visibleByText(note.body);
await sleep(500);
await tapById(notesnook.listitem.menu);
await tapById("icon-favorite");
await tapById("icon-Favorite");
await expect(element(by.text(note.body))).not.toBeVisible();
await navigate("Notes");
});
@@ -70,11 +69,11 @@ describe("NOTE TESTS", () => {
await prepare();
await createNote();
await tapById(notesnook.listitem.menu);
await tapById("icon-pin");
await tapById("icon-Pin");
await visibleByText("Pinned");
await visibleById("icon-pinned");
await tapById(notesnook.listitem.menu);
await tapById("icon-pin");
await tapById("icon-Pin");
expect(element(by.id("icon-pinned"))).not.toBeVisible();
});
@@ -82,12 +81,11 @@ describe("NOTE TESTS", () => {
await prepare();
await createNote();
await tapById(notesnook.listitem.menu);
await tapById("icon-pin-to-notifications");
await visibleByText("Unpin from notifications");
await tapById("icon-PinToNotif");
await visibleByText("Unpin from Notifications");
await sleep(500);
await tapById("icon-pin-to-notifications");
await sleep(500);
await visibleByText("Pin to notifications");
await tapById("icon-PinToNotif");
await visibleByText("Pin to Notifications");
});
// it("Copy note", async () => {
@@ -102,7 +100,7 @@ describe("NOTE TESTS", () => {
await prepare();
await createNote();
await tapById(notesnook.listitem.menu);
await tapById("icon-export");
await tapById("icon-Export");
await visibleByText("PDF");
});
@@ -124,7 +122,7 @@ describe("NOTE TESTS", () => {
await prepare();
await createNote();
await tapById(notesnook.listitem.menu);
await tapById("icon-delete");
await tapById("icon-Delete");
await navigate("Trash");
await tapById(notesnook.listitem.menu);
await tapByText("Restore note");

View File

@@ -17,15 +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 { notesnook } from "../test.ids";
import {
tapById,
visibleByText,
createNote,
prepare,
elementById,
sleep,
tapByText
sleep
} from "./utils";
describe("Search", () => {
@@ -36,10 +34,6 @@ describe("Search", () => {
await sleep(300);
await elementById("search-input").typeText("n");
await sleep(1000);
await tapByText(note.body);
await sleep(1000);
await device.pressBack();
await device.pressBack();
await visibleByText(note.body);
});
});

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@@ -1,10 +1,3 @@
- Added default font size & font family settings
- Redesign reminder/notebook sheets to improve UX
- Fixed an issue causing sync to fail
- Fix tag suggestions in share extension
- Fix app font sizes too small in some places
- Improved mobile editor performance on low-end devices
- Fix pin to notifications not working
- Bug fixes and performance improvements
- Fixed reminders notifications on android
Thank you for using Notesnook!

View File

@@ -33,8 +33,6 @@
65B5014725A672B200E2D264 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 65B5014525A672B200E2D264 /* MainInterface.storyboard */; };
65B5014B25A672B200E2D264 /* Make Note.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 65B5014025A672B200E2D264 /* Make Note.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
65B5020325A6756700E2D264 /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65B5020225A6756700E2D264 /* File.swift */; };
65D145C529DC30470056FE7D /* MaterialCommunityIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 65D145C429DC30470056FE7D /* MaterialCommunityIcons.ttf */; };
65D145C629DC30470056FE7D /* MaterialCommunityIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 65D145C429DC30470056FE7D /* MaterialCommunityIcons.ttf */; };
65E0340B257B9FF100793428 /* File.swift in Sources */ = {isa = PBXBuildFile; fileRef = 65E0340A257B9FF100793428 /* File.swift */; };
7C72B5EA418785B334F3B5EB /* libPods-Notesnook-Make Note.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 35547D7C07E83F889159A73D /* libPods-Notesnook-Make Note.a */; };
87CE5B71DC72F3D95B7B1AA5 /* libPods-Notesnook-tvOSTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A03E3F5851D27269540E147C /* libPods-Notesnook-tvOSTests.a */; };
@@ -131,7 +129,6 @@
65B501AA25A6733400E2D264 /* Make Note.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Make Note.entitlements"; sourceTree = "<group>"; };
65B5020125A6756700E2D264 /* Make Note-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Make Note-Bridging-Header.h"; sourceTree = "<group>"; };
65B5020225A6756700E2D264 /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = "<group>"; };
65D145C429DC30470056FE7D /* MaterialCommunityIcons.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = MaterialCommunityIcons.ttf; path = "../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf"; sourceTree = "<group>"; };
65E03409257B9FF100793428 /* Notesnook-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Notesnook-Bridging-Header.h"; sourceTree = "<group>"; };
65E0340A257B9FF100793428 /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = "<group>"; };
65EC5B71272A7EE200FB3748 /* NotesWidgetExtensionDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = NotesWidgetExtensionDebug.entitlements; sourceTree = "<group>"; };
@@ -295,7 +292,6 @@
65EC5B71272A7EE200FB3748 /* NotesWidgetExtensionDebug.entitlements */,
6552012F27019F7700A43C51 /* OpenSans-Regular.ttf */,
6552012E27019F6E00A43C51 /* OpenSans-SemiBold.ttf */,
65D145C429DC30470056FE7D /* MaterialCommunityIcons.ttf */,
13B07FAE1A68108700A75B9A /* Notesnook */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
00E356EF1AD99517003FC87E /* NotesnookTests */,
@@ -537,7 +533,6 @@
buildActionMask = 2147483647;
files = (
6510626F27042891009661C3 /* OpenSans-Regular.ttf in Resources */,
65D145C529DC30470056FE7D /* MaterialCommunityIcons.ttf in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
6529A13E279BC4C70048D4A8 /* BootSplash.storyboard in Resources */,
6510E6D72877215700DACAA9 /* build.bundle in Resources */,
@@ -574,7 +569,6 @@
buildActionMask = 2147483647;
files = (
6510E6D82877215700DACAA9 /* build.bundle in Resources */,
65D145C629DC30470056FE7D /* MaterialCommunityIcons.ttf in Resources */,
6510627727042896009661C3 /* OpenSans-SemiBold.ttf in Resources */,
6510627527042893009661C3 /* OpenSans-Regular.ttf in Resources */,
65B5014725A672B200E2D264 /* MainInterface.storyboard in Resources */,
@@ -627,10 +621,42 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Notesnook/Pods-Notesnook-resources.sh",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/Feather.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Fontisto.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
@@ -744,10 +770,42 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Notesnook-NotesnookTests/Pods-Notesnook-NotesnookTests-resources.sh",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/Feather.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Fontisto.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
@@ -784,10 +842,42 @@
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Notesnook-Make Note/Pods-Notesnook-Make Note-resources.sh",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/Feather.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf",
"${PODS_ROOT}/../../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf",
"${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EvilIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Feather.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Brands.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Regular.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FontAwesome5_Solid.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Fontisto.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Foundation.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Ionicons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialCommunityIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Octicons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SimpleLineIcons.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Zocial.ttf",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AccessibilityResources.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
@@ -993,7 +1083,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2036;
CURRENT_PROJECT_VERSION = 2029;
DEVELOPMENT_TEAM = 53CWBG3QUC;
ENABLE_BITCODE = NO;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1067,7 +1157,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.4.13;
MARKETING_VERSION = 2.4.6;
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
@@ -1097,7 +1187,7 @@
CODE_SIGN_ENTITLEMENTS = Notesnook/Notesnook.entitlements;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = 2036;
CURRENT_PROJECT_VERSION = 2029;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
@@ -1170,7 +1260,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.4.13;
MARKETING_VERSION = 2.4.6;
ONLY_ACTIVE_ARCH = NO;
OTHER_LDFLAGS = (
"$(inherited)",
@@ -1328,7 +1418,7 @@
CODE_SIGN_IDENTITY = "Apple Development";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2036;
CURRENT_PROJECT_VERSION = 2029;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1340,7 +1430,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.4.13;
MARKETING_VERSION = 2.4.6;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
@@ -1370,7 +1460,7 @@
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2036;
CURRENT_PROJECT_VERSION = 2029;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1382,7 +1472,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.4.13;
MARKETING_VERSION = 2.4.6;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -1411,7 +1501,7 @@
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2036;
CURRENT_PROJECT_VERSION = 2029;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1485,7 +1575,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.4.13;
MARKETING_VERSION = 2.4.6;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
@@ -1515,7 +1605,7 @@
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2036;
CURRENT_PROJECT_VERSION = 2029;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 53CWBG3QUC;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
@@ -1589,7 +1679,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.4.13;
MARKETING_VERSION = 2.4.6;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
PRODUCT_NAME = "$(TARGET_NAME)";

View File

@@ -371,10 +371,10 @@ PODS:
- React-Core
- RNKeychain (4.0.5):
- React
- RNNotifee (7.4.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
@@ -418,6 +418,8 @@ PODS:
- pop (~> 1.0)
- React
- SexyTooltip
- RNVectorIcons (9.2.0):
- React-Core
- SexyTooltip (1.2.5):
- pop (~> 1.0)
- toolbar-android (0.2.1):
@@ -502,6 +504,7 @@ DEPENDENCIES:
- RNShare (from `../../node_modules/react-native-share`)
- RNSVG (from `../../node_modules/react-native-svg`)
- RNTooltips (from `../../node_modules/react-native-tooltips`)
- RNVectorIcons (from `../../node_modules/react-native-vector-icons`)
- SexyTooltip (from `https://github.com/ammarahm-ed/SexyTooltip.git`)
- "toolbar-android (from `../../node_modules/@react-native-community/toolbar-android`)"
- Yoga (from `../../node_modules/react-native/ReactCommon/yoga`)
@@ -664,6 +667,8 @@ EXTERNAL SOURCES:
:path: "../../node_modules/react-native-svg"
RNTooltips:
:path: "../../node_modules/react-native-tooltips"
RNVectorIcons:
:path: "../../node_modules/react-native-vector-icons"
SexyTooltip:
:git: https://github.com/ammarahm-ed/SexyTooltip.git
toolbar-android:
@@ -749,7 +754,7 @@ SPEC CHECKSUMS:
RNGestureHandler: b7a872907ee289ada902127f2554fa1d2c076122
RNIap: d248609d1b8937e63bd904e865c318e9b1457eff
RNKeychain: 840f8e6f13be0576202aefcdffd26a4f54bfe7b5
RNNotifee: 2ae3c18196e6f307fa62ae5c8e5305dea03ff147
RNNotifee: 5dfb0c5783ddb3da47b39e75d06cc7e748d6ca39
RNPrivacySnapshot: 8eaf571478a353f2e5184f5c803164f22428b023
RNReanimated: f1b109fb8341505ace9d7d2eedd150da1686716b
RNScreens: 34cc502acf1b916c582c60003dc3089fa01dc66d
@@ -757,6 +762,7 @@ SPEC CHECKSUMS:
RNShare: a5dc3b9c53ddc73e155b8cd9a94c70c91913c43c
RNSVG: ecd661f380a07ba690c9c5929c475a44f432d674
RNTooltips: 5424d4bf0b3d441104127943b1115cc7f0616b1f
RNVectorIcons: fcc2f6cb32f5735b586e66d14103a74ce6ad61f8
SexyTooltip: 5c9b4dec52bfb317938cb0488efd9da3717bb6fd
toolbar-android: 2a73856e98b750d7e71ce4644d3f41cc98211719
Yoga: 0b84a956f7393ef1f37f3bb213c516184e4a689d

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