Compare commits
123 Commits
feature/im
...
2.4.10-and
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aac43274a0 | ||
|
|
2fb2c4247a | ||
|
|
cb1d32d177 | ||
|
|
75e1985a6f | ||
|
|
0093b39f64 | ||
|
|
d19c2ef735 | ||
|
|
db527cfbf3 | ||
|
|
7da8ddb24f | ||
|
|
5d401ac9ac | ||
|
|
9605eafa00 | ||
|
|
783460c685 | ||
|
|
af906d8ace | ||
|
|
fca26a8793 | ||
|
|
27863def9c | ||
|
|
f1d4fd1d5d | ||
|
|
61b7e43d08 | ||
|
|
87ebe1918c | ||
|
|
4c5253c267 | ||
|
|
f5be8a0c86 | ||
|
|
5392582819 | ||
|
|
651ae4cdaa | ||
|
|
d87b5c5386 | ||
|
|
d5331b1997 | ||
|
|
0a6f8fa18e | ||
|
|
c1ba16eb80 | ||
|
|
0cd01b25bd | ||
|
|
f0c1300ec8 | ||
|
|
70eee2a224 | ||
|
|
42470d1d67 | ||
|
|
7fc58a3fa5 | ||
|
|
e186037c03 | ||
|
|
732a0ffea4 | ||
|
|
89f2e9fde1 | ||
|
|
c369541a7f | ||
|
|
0b7a157ab1 | ||
|
|
9b86c6ebb7 | ||
|
|
22cd07e414 | ||
|
|
47f919cb61 | ||
|
|
5b3c889b7c | ||
|
|
77ac53215c | ||
|
|
167c7125d2 | ||
|
|
ba56fd0279 | ||
|
|
8e795b729a | ||
|
|
a4eb597f35 | ||
|
|
0aeade69da | ||
|
|
90a3bd3307 | ||
|
|
7a61e3558e | ||
|
|
dd9c555bff | ||
|
|
50f84780bb | ||
|
|
e12372364d | ||
|
|
969611c9e6 | ||
|
|
06399fe3bd | ||
|
|
35c5a90b4e | ||
|
|
3c70cccd14 | ||
|
|
a3c24bc666 | ||
|
|
16b00d21a6 | ||
|
|
88f90c6240 | ||
|
|
a5c44dcb3b | ||
|
|
c0a2dd139a | ||
|
|
b354a26f0a | ||
|
|
b3de1205d9 | ||
|
|
f18e8ae421 | ||
|
|
a46b863802 | ||
|
|
2befa03cd2 | ||
|
|
763a108abb | ||
|
|
30a069c845 | ||
|
|
ffbf5cdc88 | ||
|
|
4318a65a99 | ||
|
|
bc433c56ae | ||
|
|
e43740c6bc | ||
|
|
6123bccf4b | ||
|
|
08106c20e8 | ||
|
|
eaaecbd5e5 | ||
|
|
29608099db | ||
|
|
7a773937c2 | ||
|
|
fa4a298bfa | ||
|
|
966bedbb49 | ||
|
|
159b6f3ca6 | ||
|
|
de1b0fc466 | ||
|
|
b6a3394dee | ||
|
|
f5ac51db13 | ||
|
|
1ec0b65fe8 | ||
|
|
d9a77ff658 | ||
|
|
c45118515c | ||
|
|
f9b8865351 | ||
|
|
1e92b3fc92 | ||
|
|
f34fb858ec | ||
|
|
1fdede5518 | ||
|
|
a454ec4944 | ||
|
|
5afbf103ac | ||
|
|
2ef1b76bfb | ||
|
|
f7d06d3b82 | ||
|
|
e405468471 | ||
|
|
9d2bacdc5f | ||
|
|
b44d4702be | ||
|
|
03803dbc23 | ||
|
|
9ea675ded1 | ||
|
|
c85d623e1c | ||
|
|
e6f1320b1d | ||
|
|
80f49e22e5 | ||
|
|
4884c27a9a | ||
|
|
4976a4dab4 | ||
|
|
5eb0b5faa7 | ||
|
|
5e33cb93c6 | ||
|
|
b4220d349f | ||
|
|
d4752685d0 | ||
|
|
0b06d2a4bd | ||
|
|
a37d9a193f | ||
|
|
b1d465e8bf | ||
|
|
31e17c305f | ||
|
|
72db8724ec | ||
|
|
b64cb22186 | ||
|
|
f5ca1220de | ||
|
|
fb54125ee6 | ||
|
|
be957d058f | ||
|
|
54a3e3c927 | ||
|
|
aabbf7c1c1 | ||
|
|
7773ba0804 | ||
|
|
9f812772fe | ||
|
|
75191afc3e | ||
|
|
1c25baccea | ||
|
|
b1d4ccc27e | ||
|
|
0d714847f3 |
8
.github/workflows/desktop.publish.yml
vendored
@@ -215,17 +215,17 @@ jobs:
|
||||
- name: Build snap
|
||||
if: inputs.publish-snap
|
||||
run: |
|
||||
npx electron-builder --linux snap -p never
|
||||
npx electron-builder --linux snap:x64 -p never
|
||||
working-directory: ./apps/web/desktop
|
||||
|
||||
- name: Build AppImage deb and rpm
|
||||
- name: Build AppImage
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
if [ ${{ inputs.publish-github }} == true ]; then
|
||||
npx electron-builder --linux AppImage deb rpm -p always
|
||||
npx electron-builder --linux AppImage:x64 AppImage:arm64 -p always
|
||||
else
|
||||
npx electron-builder --linux AppImage deb rpm -p never
|
||||
npx electron-builder --linux AppImage:x64 AppImage:arm64 -p never
|
||||
fi
|
||||
working-directory: ./apps/web/desktop
|
||||
|
||||
|
||||
BIN
apps/mobile/app/assets/images/notesnook-dark.png
Normal file
|
After Width: | Height: | Size: 38 KiB |
@@ -28,6 +28,7 @@ import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { db } from "../database";
|
||||
import Storage from "../database/storage";
|
||||
import { cacheDir } from "./utils";
|
||||
import { getFileNameWithExtension } from "@notesnook/core/utils/filename";
|
||||
|
||||
export default async function downloadAttachment(
|
||||
hash,
|
||||
@@ -38,7 +39,6 @@ export default async function downloadAttachment(
|
||||
}
|
||||
) {
|
||||
let attachment = db.attachments.attachment(hash);
|
||||
console.log(attachment);
|
||||
if (!attachment) {
|
||||
console.log("attachment not found");
|
||||
return;
|
||||
@@ -64,6 +64,11 @@ export default async function downloadAttachment(
|
||||
)
|
||||
return;
|
||||
|
||||
let filename = getFileNameWithExtension(
|
||||
attachment.metadata.filename,
|
||||
attachment.metadata.type
|
||||
);
|
||||
|
||||
let key = await db.attachments.decryptKey(attachment.key);
|
||||
let info = {
|
||||
iv: attachment.iv,
|
||||
@@ -73,7 +78,7 @@ export default async function downloadAttachment(
|
||||
hash: attachment.metadata.hash,
|
||||
hashType: attachment.metadata.hashType,
|
||||
mime: attachment.metadata.type,
|
||||
fileName: options.cache ? undefined : attachment.metadata.filename,
|
||||
fileName: options.cache ? undefined : filename,
|
||||
uri: options.cache ? undefined : folder.uri,
|
||||
chunkSize: attachment.chunkSize
|
||||
};
|
||||
@@ -87,7 +92,7 @@ export default async function downloadAttachment(
|
||||
if (!options.silent) {
|
||||
ToastEvent.show({
|
||||
heading: "Download successful",
|
||||
message: attachment.metadata.filename + " downloaded",
|
||||
message: filename + " downloaded",
|
||||
type: "success"
|
||||
});
|
||||
}
|
||||
@@ -100,28 +105,20 @@ export default async function downloadAttachment(
|
||||
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
|
||||
.catch(console.log);
|
||||
}
|
||||
|
||||
if (Platform.OS === "ios") {
|
||||
fileUri = folder.uri + `/${attachment.metadata.filename}`;
|
||||
if (Platform.OS === "ios" && !options.cache) {
|
||||
fileUri = folder.uri + `/${filename}`;
|
||||
}
|
||||
console.log("saved file uri: ", fileUri);
|
||||
if (!options.silent) {
|
||||
presentSheet({
|
||||
title: "File downloaded",
|
||||
paragraph: `${attachment.metadata.filename} saved to ${
|
||||
paragraph: `${filename} saved to ${
|
||||
Platform.OS === "android"
|
||||
? "selected path"
|
||||
: "File Manager/Notesnook/downloads"
|
||||
}`,
|
||||
icon: "download",
|
||||
context: global ? null : attachment.metadata.hash,
|
||||
component: (
|
||||
<ShareComponent
|
||||
uri={fileUri}
|
||||
name={attachment.metadata.filename}
|
||||
padding={12}
|
||||
/>
|
||||
)
|
||||
component: <ShareComponent uri={fileUri} name={filename} padding={12} />
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -23,14 +23,16 @@ import {
|
||||
deleteFile,
|
||||
exists,
|
||||
readEncrypted,
|
||||
writeEncrypted
|
||||
writeEncryptedBase64,
|
||||
hashBase64
|
||||
} from "./io";
|
||||
import { uploadFile } from "./upload";
|
||||
import { cancelable } from "./utils";
|
||||
|
||||
export default {
|
||||
readEncrypted,
|
||||
writeEncrypted,
|
||||
writeEncryptedBase64,
|
||||
hashBase64,
|
||||
uploadFile: cancelable(uploadFile),
|
||||
downloadFile: cancelable(downloadFile),
|
||||
deleteFile,
|
||||
|
||||
@@ -63,17 +63,26 @@ export async function readEncrypted(filename, key, cipherData) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function writeEncrypted(filename, { data, type, key }) {
|
||||
console.log("file input: ", { type, key });
|
||||
export async function hashBase64(data) {
|
||||
const hash = await Sodium.hashFile({
|
||||
type: "base64",
|
||||
data,
|
||||
uri: ""
|
||||
});
|
||||
return {
|
||||
hash: hash,
|
||||
type: "xxh64"
|
||||
};
|
||||
}
|
||||
|
||||
export async function writeEncryptedBase64({ data, key }) {
|
||||
let filepath = cacheDir + `/${getRandomId("imagecache_")}`;
|
||||
console.log(filepath);
|
||||
await RNFetchBlob.fs.writeFile(filepath, data, "base64");
|
||||
let output = await Sodium.encryptFile(key, {
|
||||
uri: Platform.OS === "ios" ? filepath : "file://" + filepath,
|
||||
type: "url"
|
||||
});
|
||||
RNFetchBlob.fs.unlink(filepath).catch(console.log);
|
||||
|
||||
console.log("encrypted file output: ", output);
|
||||
return {
|
||||
...output,
|
||||
|
||||
@@ -46,7 +46,6 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
||||
const encryptionProgress = useAttachmentStore(
|
||||
(state) => state.encryptionProgress
|
||||
);
|
||||
|
||||
const onPress = () => {
|
||||
Actions.present(attachment, setAttachments, attachment.metadata.hash);
|
||||
};
|
||||
@@ -122,7 +121,9 @@ export const AttachmentItem = ({ attachment, encryption, setAttachments }) => {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{currentProgress || encryptionProgress || encryption ? (
|
||||
{currentProgress ||
|
||||
(encryptionProgress && encryptionProgress !== "0.00") ||
|
||||
encryption ? (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.9}
|
||||
onPress={() => {
|
||||
|
||||
@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { FlatList } from "react-native-gesture-handler";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../common/database";
|
||||
import filesystem from "../../common/filesystem";
|
||||
@@ -31,11 +30,11 @@ import Input from "../ui/input";
|
||||
import Seperator from "../ui/seperator";
|
||||
import Paragraph from "../ui/typography/paragraph";
|
||||
import { AttachmentItem } from "./attachment-item";
|
||||
import { FlatList } from "react-native-actions-sheet";
|
||||
|
||||
export const AttachmentDialog = ({ data }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [note, setNote] = useState(data);
|
||||
const actionSheetRef = useRef();
|
||||
const [attachments, setAttachments] = useState(
|
||||
data
|
||||
? db.attachments.ofNote(data.id, "all")
|
||||
@@ -115,14 +114,11 @@ export const AttachmentDialog = ({ data }) => {
|
||||
) : null}
|
||||
|
||||
<FlatList
|
||||
nestedScrollEnabled
|
||||
overScrollMode="never"
|
||||
scrollToOverflowEnabled={false}
|
||||
keyboardDismissMode="none"
|
||||
keyboardShouldPersistTaps="always"
|
||||
onMomentumScrollEnd={() => {
|
||||
actionSheetRef.current?.handleChildScrollEnd();
|
||||
}}
|
||||
maxToRenderPerBatch={10}
|
||||
initialNumToRender={10}
|
||||
windowSize={5}
|
||||
ListEmptyComponent={
|
||||
<View
|
||||
style={{
|
||||
|
||||
@@ -135,7 +135,6 @@ const ResultDialog = () => {
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
onPress={close}
|
||||
height={50}
|
||||
fontSize={SIZE.md + 2}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -63,13 +63,15 @@ export const Title = () => {
|
||||
}
|
||||
if (data.y > 150) {
|
||||
if (!hide) return;
|
||||
titleState[currentScreen.id] = false;
|
||||
setHide(false);
|
||||
} else {
|
||||
if (hide) return;
|
||||
titleState[currentScreen.id] = true;
|
||||
setHide(true);
|
||||
}
|
||||
},
|
||||
[currentScreen.name, hide]
|
||||
[currentScreen.id, currentScreen.name, hide]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -84,10 +86,6 @@ export const Title = () => {
|
||||
}
|
||||
}, [currentScreen.id, currentScreen.name]);
|
||||
|
||||
useEffect(() => {
|
||||
titleState[currentScreen.id] = hide;
|
||||
}, [currentScreen.id, hide]);
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent(eScrollEvent, onScroll);
|
||||
return () => {
|
||||
@@ -111,7 +109,7 @@ export const Title = () => {
|
||||
{!hide && !isHidden ? (
|
||||
<Heading
|
||||
onPress={navigateToNotebook}
|
||||
numberOfLines={isTopic ? 2 : 1}
|
||||
numberOfLines={1}
|
||||
size={SIZE.xl}
|
||||
style={{
|
||||
flexWrap: "wrap",
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { Platform, View } from "react-native";
|
||||
import ImageViewer from "react-native-image-zoom-viewer";
|
||||
import downloadAttachment from "../../common/filesystem/download-attachment";
|
||||
import { cacheDir } from "../../common/filesystem/utils";
|
||||
@@ -30,6 +30,8 @@ import { useThemeStore } from "../../stores/use-theme-store";
|
||||
import BaseDialog from "../dialog/base-dialog";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import { ProgressBarComponent } from "../ui/svg/lazy";
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import dataurl from "@notesnook/core/utils/dataurl";
|
||||
|
||||
const ImagePreview = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
@@ -49,13 +51,22 @@ const ImagePreview = () => {
|
||||
setVisible(true);
|
||||
setLoading(true);
|
||||
setTimeout(async () => {
|
||||
const hash = image.hash;
|
||||
let hash = image.hash;
|
||||
if (!hash && dataurl.toObject(image.src)) {
|
||||
const data = dataurl.toObject(image.src);
|
||||
if (!data) return;
|
||||
hash = await Sodium.hashFile({
|
||||
data: data.data,
|
||||
type: "base64",
|
||||
uri: ""
|
||||
});
|
||||
}
|
||||
if (!hash) return;
|
||||
const uri = await downloadAttachment(hash, false, {
|
||||
silent: true,
|
||||
cache: true
|
||||
});
|
||||
const path = `${cacheDir}/${uri}`;
|
||||
console.log(path);
|
||||
setImage("file://" + path);
|
||||
setLoading(false);
|
||||
}, 100);
|
||||
@@ -111,7 +122,7 @@ const ImagePreview = () => {
|
||||
position: "absolute",
|
||||
zIndex: 999,
|
||||
backgroundColor: "rgba(0,0,0,0.3)",
|
||||
paddingTop: 30
|
||||
paddingTop: Platform.OS === "android" ? 30 : 0
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
|
||||
@@ -118,7 +118,7 @@ const NoteItem = ({
|
||||
{notebooks?.map((item) => (
|
||||
<Button
|
||||
title={item.title}
|
||||
key={item}
|
||||
key={item.id}
|
||||
height={25}
|
||||
icon={item.type === "topic" ? "bookmark" : "book-outline"}
|
||||
type="grayBg"
|
||||
|
||||
@@ -212,7 +212,6 @@ Properties.present = (item, buttons = [], isSheet) => {
|
||||
if (!props[0]) return;
|
||||
presentSheet({
|
||||
context: isSheet ? "local" : undefined,
|
||||
enableGesturesInScrollView: true,
|
||||
component: (ref, close) => (
|
||||
<Properties
|
||||
close={() => {
|
||||
|
||||
@@ -94,7 +94,7 @@ export const Synced = ({ item, close }) => {
|
||||
fontSize={SIZE.xs + 1}
|
||||
title="Learn more"
|
||||
height={30}
|
||||
type="transparent"
|
||||
type="grayAccent"
|
||||
/>
|
||||
</View>
|
||||
) : null;
|
||||
|
||||
@@ -79,7 +79,7 @@ export const TagStrip = ({ item, close }) => {
|
||||
}}
|
||||
>
|
||||
{item.tags.map((tag) =>
|
||||
tag ? <TagItem key={tag} tag={item} close={close} /> : null
|
||||
tag ? <TagItem key={tag} tag={tag} close={close} /> : null
|
||||
)}
|
||||
</View>
|
||||
) : null;
|
||||
@@ -101,7 +101,6 @@ const TagItem = ({ tag, close }) => {
|
||||
marginTop: 0,
|
||||
backgroundColor: "transparent"
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
onPress={onPress}
|
||||
|
||||
@@ -101,6 +101,7 @@ const SheetProvider = ({ context = "global" }) => {
|
||||
setVisible(false);
|
||||
setData(null);
|
||||
}}
|
||||
bottomPadding={!data.noBottomPadding}
|
||||
enableGesturesInScrollView={data.enableGesturesInScrollView}
|
||||
>
|
||||
<View
|
||||
@@ -212,7 +213,6 @@ const SheetProvider = ({ context = "global" }) => {
|
||||
title={item.actionText}
|
||||
icon={item.icon && item.icon}
|
||||
type={item.type || "accent"}
|
||||
height={50}
|
||||
style={{
|
||||
marginBottom: 10
|
||||
}}
|
||||
|
||||
@@ -375,7 +375,6 @@ export class AddNotebookSheet extends React.Component {
|
||||
<Seperator />
|
||||
<Button
|
||||
width="100%"
|
||||
height={50}
|
||||
fontSize={SIZE.md}
|
||||
title={
|
||||
notebook && notebook.dateCreated
|
||||
|
||||
@@ -20,13 +20,9 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import { createContext, useContext } from "react";
|
||||
|
||||
export const SelectionContext = createContext({
|
||||
enabled: false,
|
||||
selected: [],
|
||||
toggleSelection: (item) => null,
|
||||
deselect: (item) => null,
|
||||
select: (item) => null,
|
||||
isSelected: (item) => null,
|
||||
setMultiSelect: () => null,
|
||||
deselectAll: () => null
|
||||
});
|
||||
export const SelectionProvider = SelectionContext.Provider;
|
||||
|
||||
@@ -71,7 +71,6 @@ export const FilteredList = ({
|
||||
}
|
||||
keyboardShouldPersistTaps="always"
|
||||
keyboardDismissMode="none"
|
||||
nestedScrollEnabled
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import React, { useCallback, useEffect, useMemo } from "react";
|
||||
import { Keyboard, TouchableOpacity, View } from "react-native";
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { db } from "../../../common/database";
|
||||
@@ -26,6 +26,7 @@ import Navigation from "../../../services/navigation";
|
||||
import SearchService from "../../../services/search";
|
||||
import { useNotebookStore } from "../../../stores/use-notebook-store";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import { useSettingStore } from "../../../stores/use-setting-store";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { Dialog } from "../../dialog";
|
||||
import DialogHeader from "../../dialog/dialog-header";
|
||||
@@ -35,19 +36,21 @@ import Paragraph from "../../ui/typography/paragraph";
|
||||
import { SelectionProvider } from "./context";
|
||||
import { FilteredList } from "./filtered-list";
|
||||
import { ListItem } from "./list-item";
|
||||
import { useItemSelectionStore } from "./store";
|
||||
|
||||
const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [multiSelect, setMultiSelect] = useState(false);
|
||||
const notebooks = useNotebookStore((state) =>
|
||||
state.notebooks.filter((n) => n?.type === "notebook")
|
||||
);
|
||||
|
||||
const dimensions = useSettingStore((state) => state.dimensions);
|
||||
const selectedItemsList = useSelectionStore(
|
||||
(state) => state.selectedItemsList
|
||||
);
|
||||
const setNotebooks = useNotebookStore((state) => state.setNotebooks);
|
||||
const [itemState, setItemState] = useState({});
|
||||
|
||||
const multiSelect = useItemSelectionStore((state) => state.multiSelect);
|
||||
|
||||
const onAddNotebook = async (title) => {
|
||||
if (!title || title.trim().length === 0) {
|
||||
ToastEvent.show({
|
||||
@@ -130,41 +133,55 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
||||
|
||||
useEffect(() => {
|
||||
resetItemState();
|
||||
return () => {
|
||||
useItemSelectionStore.getState().setMultiSelect(false);
|
||||
useItemSelectionStore.getState().setItemState({});
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const resetItemState = useCallback(
|
||||
(state) => {
|
||||
setItemState(() => {
|
||||
const itemState = {};
|
||||
const notebooks = db.notebooks.all;
|
||||
for (let notebook of notebooks) {
|
||||
itemState[notebook.id] = state
|
||||
const itemState = {};
|
||||
const notebooks = db.notebooks.all;
|
||||
let count = 0;
|
||||
for (let notebook of notebooks) {
|
||||
itemState[notebook.id] = state
|
||||
? state
|
||||
: areAllSelectedItemsInNotebook(notebook, selectedItemsList)
|
||||
? "selected"
|
||||
: getSelectedNotesCountInItem(notebook, selectedItemsList) > 0
|
||||
? "intermediate"
|
||||
: "deselected";
|
||||
if (itemState[notebook.id] === "selected") {
|
||||
count++;
|
||||
contextValue.select(notebook);
|
||||
} else {
|
||||
contextValue.deselect(notebook);
|
||||
}
|
||||
for (let topic of notebook.topics) {
|
||||
itemState[topic.id] = state
|
||||
? state
|
||||
: areAllSelectedItemsInNotebook(notebook, selectedItemsList)
|
||||
: areAllSelectedItemsInTopic(topic, selectedItemsList) &&
|
||||
getSelectedNotesCountInItem(topic, selectedItemsList)
|
||||
? "selected"
|
||||
: getSelectedNotesCountInItem(notebook, selectedItemsList) > 0
|
||||
: getSelectedNotesCountInItem(topic, selectedItemsList) > 0
|
||||
? "intermediate"
|
||||
: "deselected";
|
||||
if (itemState[notebook.id] === "selected") {
|
||||
contextValue.select(notebook);
|
||||
}
|
||||
for (let topic of notebook.topics) {
|
||||
itemState[topic.id] = state
|
||||
? state
|
||||
: areAllSelectedItemsInTopic(topic, selectedItemsList) &&
|
||||
getSelectedNotesCountInItem(topic, selectedItemsList)
|
||||
? "selected"
|
||||
: getSelectedNotesCountInItem(topic, selectedItemsList) > 0
|
||||
? "intermediate"
|
||||
: "deselected";
|
||||
if (itemState[topic.id] === "selected") {
|
||||
contextValue.select(topic);
|
||||
}
|
||||
if (itemState[topic.id] === "selected") {
|
||||
count++;
|
||||
contextValue.select(topic);
|
||||
} else {
|
||||
contextValue.deselect(topic);
|
||||
}
|
||||
}
|
||||
return itemState;
|
||||
});
|
||||
}
|
||||
if (count > 1) {
|
||||
useItemSelectionStore.getState().setMultiSelect(true);
|
||||
} else {
|
||||
useItemSelectionStore.getState().setMultiSelect(false);
|
||||
}
|
||||
useItemSelectionStore.getState().setItemState(itemState);
|
||||
},
|
||||
[contextValue, getSelectedNotesCountInItem, selectedItemsList]
|
||||
);
|
||||
@@ -191,32 +208,26 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
||||
}
|
||||
|
||||
const updateItemState = useCallback(function (item, state) {
|
||||
setItemState((itemState) => {
|
||||
const mergeState = {
|
||||
[item.id]: state
|
||||
};
|
||||
return {
|
||||
...itemState,
|
||||
...mergeState
|
||||
};
|
||||
const itemState = useItemSelectionStore.getState().itemState;
|
||||
const mergeState = {
|
||||
[item.id]: state
|
||||
};
|
||||
useItemSelectionStore.getState().setItemState({
|
||||
...itemState,
|
||||
...mergeState
|
||||
});
|
||||
}, []);
|
||||
|
||||
const contextValue = useMemo(
|
||||
() => ({
|
||||
enabled: multiSelect,
|
||||
toggleSelection: (item) => {
|
||||
setItemState((itemState) => {
|
||||
if (itemState[item.id] === "selected") {
|
||||
updateItemState(item, "deselected");
|
||||
} else {
|
||||
updateItemState(item, "selected");
|
||||
}
|
||||
|
||||
return itemState;
|
||||
});
|
||||
const itemState = useItemSelectionStore.getState().itemState;
|
||||
if (itemState[item.id] === "selected") {
|
||||
updateItemState(item, "deselected");
|
||||
} else {
|
||||
updateItemState(item, "selected");
|
||||
}
|
||||
},
|
||||
setMultiSelect: setMultiSelect,
|
||||
deselect: (item) => {
|
||||
updateItemState(item, "deselected");
|
||||
},
|
||||
@@ -227,7 +238,7 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
||||
resetItemState(state);
|
||||
}
|
||||
}),
|
||||
[multiSelect, resetItemState, updateItemState]
|
||||
[resetItemState, updateItemState]
|
||||
);
|
||||
|
||||
const getItemFromId = (id) => {
|
||||
@@ -241,6 +252,7 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
||||
|
||||
const onSave = async () => {
|
||||
const noteIds = note ? [note.id] : selectedItemsList.map((n) => n.id);
|
||||
const itemState = useItemSelectionStore.getState().itemState;
|
||||
for (const id in itemState) {
|
||||
const item = getItemFromId(id);
|
||||
if (itemState[id] === "selected") {
|
||||
@@ -335,31 +347,32 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
||||
/>
|
||||
</View>
|
||||
|
||||
{multiSelect ? (
|
||||
<View
|
||||
<View
|
||||
style={{
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
title="Reset selection"
|
||||
height={30}
|
||||
style={{
|
||||
paddingHorizontal: 12
|
||||
alignSelf: "flex-start",
|
||||
paddingHorizontal: 0,
|
||||
width: "100%",
|
||||
marginTop: 6
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
title="Reset selection"
|
||||
height={30}
|
||||
style={{
|
||||
alignSelf: "flex-start",
|
||||
paddingHorizontal: 0
|
||||
}}
|
||||
onPress={() => {
|
||||
resetItemState();
|
||||
setMultiSelect(false);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
type="grayAccent"
|
||||
onPress={() => {
|
||||
resetItemState();
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<SelectionProvider value={contextValue}>
|
||||
<FilteredList
|
||||
style={{
|
||||
paddingHorizontal: 12
|
||||
paddingHorizontal: 12,
|
||||
maxHeight: dimensions.height * 0.85
|
||||
}}
|
||||
ListEmptyComponent={
|
||||
notebooks.length > 0 ? null : (
|
||||
@@ -385,13 +398,8 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
||||
item={item}
|
||||
key={item.id}
|
||||
index={index}
|
||||
intermediate={itemState[item.id] === "intermediate"}
|
||||
removed={
|
||||
itemState[item.id] === "deselected" &&
|
||||
getSelectedNotesCountInItem(item) > 0
|
||||
}
|
||||
hasNotes={getSelectedNotesCountInItem(item) > 0}
|
||||
sheetRef={actionSheetRef}
|
||||
isSelected={itemState[item.id] === "selected"}
|
||||
infoText={
|
||||
<>
|
||||
{item.topics.length === 1
|
||||
@@ -401,17 +409,14 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
||||
}
|
||||
getListItems={getItemsForItem}
|
||||
getSublistItemProps={(topic) => ({
|
||||
selected: itemState[topic.id] === "selected",
|
||||
intermediate: itemState[topic.id] === "intermediate",
|
||||
isSelected: itemState[topic.id] === "selected",
|
||||
removed:
|
||||
itemState[topic.id] === "deselected" &&
|
||||
getSelectedNotesCountInItem(topic) > 0,
|
||||
hasNotes: getSelectedNotesCountInItem(topic) > 0,
|
||||
style: {
|
||||
marginBottom: 0,
|
||||
height: 40
|
||||
},
|
||||
onPress: (item) => {
|
||||
const itemState =
|
||||
useItemSelectionStore.getState().itemState;
|
||||
const currentState = itemState[item.id];
|
||||
if (currentState !== "selected") {
|
||||
resetItemState("deselected");
|
||||
@@ -440,19 +445,23 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
||||
onAddSublistItem={(item) => {
|
||||
openAddTopicDialog(item);
|
||||
}}
|
||||
onPress={(item) => {
|
||||
const itemState = useItemSelectionStore.getState().itemState;
|
||||
const currentState = itemState[item.id];
|
||||
if (currentState !== "selected") {
|
||||
resetItemState("deselected");
|
||||
contextValue.select(item);
|
||||
} else {
|
||||
contextValue.deselect(item);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
itemType="notebook"
|
||||
onAddItem={async (title) => {
|
||||
return await onAddNotebook(title);
|
||||
}}
|
||||
// ListFooterComponent={
|
||||
// <View
|
||||
// style={{
|
||||
// height: 200
|
||||
// }}
|
||||
// />
|
||||
// }
|
||||
ListFooterComponent={<View style={{ height: 20 }} />}
|
||||
/>
|
||||
</SelectionProvider>
|
||||
</View>
|
||||
@@ -463,7 +472,8 @@ const MoveNoteSheet = ({ note, actionSheetRef }) => {
|
||||
MoveNoteSheet.present = (note) => {
|
||||
presentSheet({
|
||||
component: (ref) => <MoveNoteSheet actionSheetRef={ref} note={note} />,
|
||||
enableGesturesInScrollView: false
|
||||
enableGesturesInScrollView: false,
|
||||
noBottomPadding: true
|
||||
});
|
||||
};
|
||||
export default MoveNoteSheet;
|
||||
|
||||
@@ -17,8 +17,10 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { db } from "../../../common/database";
|
||||
import { useSelectionStore } from "../../../stores/use-selection-store";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
@@ -27,13 +29,78 @@ import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { useSelectionContext } from "./context";
|
||||
import { FilteredList } from "./filtered-list";
|
||||
import { useItemSelectionStore } from "./store";
|
||||
|
||||
const _ListItem = ({
|
||||
const SelectionIndicator = ({
|
||||
item,
|
||||
hasNotes,
|
||||
selectItem,
|
||||
onPress,
|
||||
onChange
|
||||
}) => {
|
||||
const itemState = useItemSelectionStore((state) => state.itemState[item.id]);
|
||||
const multiSelect = useItemSelectionStore((state) => state.multiSelect);
|
||||
|
||||
const isSelected = itemState === "selected";
|
||||
const isIntermediate = itemState === "intermediate";
|
||||
const isRemoved = !isSelected && hasNotes;
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
|
||||
useEffect(() => {
|
||||
onChange?.();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [itemState]);
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
size={22}
|
||||
customStyle={{
|
||||
marginRight: 5,
|
||||
width: 23,
|
||||
height: 23
|
||||
}}
|
||||
color={
|
||||
isRemoved
|
||||
? colors.red
|
||||
: isIntermediate || isSelected
|
||||
? colors.accent
|
||||
: colors.icon
|
||||
}
|
||||
onPress={() => {
|
||||
if (multiSelect) return selectItem();
|
||||
onPress?.(item);
|
||||
}}
|
||||
onLongPress={() => {
|
||||
useItemSelectionStore.getState().setMultiSelect(true);
|
||||
selectItem();
|
||||
}}
|
||||
testID={
|
||||
isRemoved
|
||||
? "close-circle-outline"
|
||||
: isSelected
|
||||
? "check-circle-outline"
|
||||
: isIntermediate
|
||||
? "minus-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
name={
|
||||
isRemoved
|
||||
? "close-circle-outline"
|
||||
: isSelected
|
||||
? "check-circle-outline"
|
||||
: isIntermediate
|
||||
? "minus-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const ListItem = ({
|
||||
item,
|
||||
index,
|
||||
icon,
|
||||
infoText,
|
||||
intermediate,
|
||||
hasSubList,
|
||||
onPress,
|
||||
onScrollEnd,
|
||||
@@ -43,19 +110,49 @@ const _ListItem = ({
|
||||
sublistItemType,
|
||||
onAddItem,
|
||||
getSublistItemProps,
|
||||
removed,
|
||||
isSelected,
|
||||
hasHeaderSearch,
|
||||
onAddSublistItem,
|
||||
hasNotes,
|
||||
onChange,
|
||||
sheetRef
|
||||
}) => {
|
||||
const { enabled, toggleSelection, setMultiSelect } = useSelectionContext();
|
||||
const { toggleSelection } = useSelectionContext();
|
||||
const multiSelect = useItemSelectionStore((state) => state.multiSelect);
|
||||
const [showSelectedIndicator, setShowSelectedIndicator] = useState(false);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
function selectItem() {
|
||||
toggleSelection(item);
|
||||
}
|
||||
|
||||
const getSelectedNotesCountInNotebookTopics = (item) => {
|
||||
if (item.type === "topic") return;
|
||||
|
||||
let count = 0;
|
||||
const noteIds = [];
|
||||
for (let topic of item.topics) {
|
||||
noteIds.push(...(db.notes?.topicReferences.get(topic.id) || []));
|
||||
if (useItemSelectionStore.getState().itemState[topic.id] === "selected") {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
useSelectionStore.getState().selectedItemsList.forEach((item) => {
|
||||
if (noteIds.indexOf(item.id) > -1) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
return count;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setShowSelectedIndicator(getSelectedNotesCountInNotebookTopics(item) > 0);
|
||||
}, [item]);
|
||||
|
||||
const onChangeSubItem = () => {
|
||||
setShowSelectedIndicator(getSelectedNotesCountInNotebookTopics(item) > 0);
|
||||
};
|
||||
|
||||
return (
|
||||
<View
|
||||
style={{
|
||||
@@ -67,12 +164,12 @@ const _ListItem = ({
|
||||
<PressableButton
|
||||
onPress={() => {
|
||||
if (hasSubList) return setExpanded(!expanded);
|
||||
if (enabled) return selectItem();
|
||||
if (multiSelect) return selectItem();
|
||||
onPress?.(item);
|
||||
}}
|
||||
type={type}
|
||||
onLongPress={() => {
|
||||
setMultiSelect(true);
|
||||
useItemSelectionStore.getState().setMultiSelect(true);
|
||||
selectItem();
|
||||
}}
|
||||
customStyle={{
|
||||
@@ -97,43 +194,12 @@ const _ListItem = ({
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
size={22}
|
||||
customStyle={{
|
||||
marginRight: 5,
|
||||
width: 23,
|
||||
height: 23
|
||||
}}
|
||||
color={
|
||||
removed
|
||||
? colors.red
|
||||
: intermediate || isSelected
|
||||
? colors.accent
|
||||
: colors.icon
|
||||
}
|
||||
onPress={() => {
|
||||
selectItem();
|
||||
if (enabled) return;
|
||||
onPress?.(item);
|
||||
}}
|
||||
testID={
|
||||
removed
|
||||
? "close-circle-outline"
|
||||
: isSelected
|
||||
? "check-circle-outline"
|
||||
: intermediate
|
||||
? "minus-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
name={
|
||||
removed
|
||||
? "close-circle-outline"
|
||||
: isSelected
|
||||
? "check-circle-outline"
|
||||
: intermediate
|
||||
? "minus-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
}
|
||||
<SelectionIndicator
|
||||
hasNotes={hasNotes}
|
||||
onPress={onPress}
|
||||
item={item}
|
||||
onChange={onChange}
|
||||
selectItem={selectItem}
|
||||
/>
|
||||
<View>
|
||||
{hasSubList && expanded ? (
|
||||
@@ -152,14 +218,27 @@ const _ListItem = ({
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row"
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{showSelectedIndicator ? (
|
||||
<View
|
||||
style={{
|
||||
backgroundColor: colors.accent,
|
||||
width: 7,
|
||||
height: 7,
|
||||
borderRadius: 100,
|
||||
marginRight: 12
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{onAddSublistItem ? (
|
||||
<IconButton
|
||||
name={"plus"}
|
||||
testID="add-item-icon"
|
||||
color={colors}
|
||||
color={colors.pri}
|
||||
size={SIZE.xl}
|
||||
onPress={() => {
|
||||
onAddSublistItem(item);
|
||||
@@ -201,6 +280,7 @@ const _ListItem = ({
|
||||
item={item}
|
||||
{...getSublistItemProps(item)}
|
||||
index={index}
|
||||
onChange={onChangeSubItem}
|
||||
onScrollEnd={onScrollEnd}
|
||||
/>
|
||||
)}
|
||||
@@ -210,11 +290,3 @@ const _ListItem = ({
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export const ListItem = React.memo(_ListItem, (prev, next) => {
|
||||
if (prev.selected === undefined) return false;
|
||||
if (prev.isSelected !== next.isSelected) return false;
|
||||
if (prev.selected !== next.selected) return false;
|
||||
if (prev.intermediate !== next.intermediate) return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
42
apps/mobile/app/components/sheets/add-to/store.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import create, { State } from "zustand";
|
||||
|
||||
type SelectionItemState = Record<
|
||||
string,
|
||||
"intermediate" | "selected" | "deselected"
|
||||
>;
|
||||
|
||||
export interface SelectionStore extends State {
|
||||
itemState: SelectionItemState;
|
||||
setItemState: (state: SelectionItemState) => void;
|
||||
multiSelect: boolean;
|
||||
setMultiSelect: (multiSelect: boolean) => void;
|
||||
}
|
||||
|
||||
export const useItemSelectionStore = create<SelectionStore>((set) => ({
|
||||
itemState: {},
|
||||
setItemState: (itemState) => {
|
||||
set({
|
||||
itemState
|
||||
});
|
||||
},
|
||||
multiSelect: false,
|
||||
setMultiSelect: (multiSelect) => set({ multiSelect })
|
||||
}));
|
||||
@@ -277,7 +277,6 @@ const ExportNotesSheet = ({ notes, update }) => {
|
||||
});
|
||||
});
|
||||
}}
|
||||
height={50}
|
||||
/>
|
||||
<Button
|
||||
title="Share"
|
||||
@@ -301,7 +300,6 @@ const ExportNotesSheet = ({ notes, update }) => {
|
||||
}).catch(console.log);
|
||||
}
|
||||
}}
|
||||
height={50}
|
||||
/>
|
||||
<Button
|
||||
title="Export in another format"
|
||||
@@ -317,7 +315,6 @@ const ExportNotesSheet = ({ notes, update }) => {
|
||||
setResult(null);
|
||||
setExporting(false);
|
||||
}}
|
||||
height={50}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -48,7 +48,6 @@ export const ShareComponent = ({ uri, name, padding }) => {
|
||||
});
|
||||
});
|
||||
}}
|
||||
height={50}
|
||||
/>
|
||||
<Button
|
||||
title="Share"
|
||||
@@ -65,7 +64,6 @@ export const ShareComponent = ({ uri, name, padding }) => {
|
||||
shareFile: true
|
||||
}).catch(console.log);
|
||||
}}
|
||||
height={50}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -196,7 +196,6 @@ For example:
|
||||
onPress={onPress}
|
||||
title={loading ? null : "Submit"}
|
||||
loading={loading}
|
||||
height={50}
|
||||
width="100%"
|
||||
type="accent"
|
||||
/>
|
||||
|
||||
@@ -142,7 +142,6 @@ const ManageTagsSheet = (props) => {
|
||||
setFocus(false);
|
||||
}}
|
||||
onSubmit={onSubmit}
|
||||
height={50}
|
||||
placeholder="Search or add a tag"
|
||||
/>
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ const PublishNoteSheet = ({ note: item, update }) => {
|
||||
if (isLocked && !passwordValue) return;
|
||||
await db.monographs.publish(note.id, {
|
||||
selfDestruct: selfDestruct,
|
||||
password: isLocked && passwordValue
|
||||
password: isLocked && passwordValue.current
|
||||
});
|
||||
setNote(db.notes.note(note.id)?.data);
|
||||
Navigation.queueRoutesForUpdate(
|
||||
@@ -86,9 +86,6 @@ const PublishNoteSheet = ({ note: item, update }) => {
|
||||
|
||||
const setPublishLoading = (value) => {
|
||||
setPublishing(value);
|
||||
update({
|
||||
progress: value
|
||||
});
|
||||
};
|
||||
|
||||
const deletePublishedNote = async () => {
|
||||
@@ -164,7 +161,7 @@ const PublishNoteSheet = ({ note: item, update }) => {
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginTop: 15,
|
||||
marginTop: 10,
|
||||
backgroundColor: colors.nav,
|
||||
padding: 12,
|
||||
borderRadius: 5
|
||||
@@ -176,8 +173,8 @@ const PublishNoteSheet = ({ note: item, update }) => {
|
||||
flexShrink: 1
|
||||
}}
|
||||
>
|
||||
<Heading size={SIZE.sm}>Published at:</Heading>
|
||||
<Paragraph size={SIZE.xs} numberOfLines={1}>
|
||||
<Heading size={SIZE.md}>Published at:</Heading>
|
||||
<Paragraph size={SIZE.sm} numberOfLines={1}>
|
||||
{publishUrl}
|
||||
</Paragraph>
|
||||
<Paragraph
|
||||
@@ -214,7 +211,6 @@ const PublishNoteSheet = ({ note: item, update }) => {
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<Seperator />
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
@@ -225,7 +221,11 @@ const PublishNoteSheet = ({ note: item, update }) => {
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginBottom: 10
|
||||
marginBottom: 10,
|
||||
backgroundColor: colors.nav,
|
||||
paddingVertical: 12,
|
||||
borderRadius: 5,
|
||||
marginTop: 10
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
@@ -234,7 +234,7 @@ const PublishNoteSheet = ({ note: item, update }) => {
|
||||
setIsLocked(!isLocked);
|
||||
}}
|
||||
color={isLocked ? colors.accent : colors.icon}
|
||||
size={SIZE.lg}
|
||||
size={SIZE.xl}
|
||||
name={
|
||||
isLocked
|
||||
? "check-circle-outline"
|
||||
@@ -262,7 +262,10 @@ const PublishNoteSheet = ({ note: item, update }) => {
|
||||
activeOpacity={0.9}
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center"
|
||||
alignItems: "center",
|
||||
backgroundColor: colors.nav,
|
||||
paddingVertical: 12,
|
||||
borderRadius: 5
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
@@ -270,7 +273,7 @@ const PublishNoteSheet = ({ note: item, update }) => {
|
||||
setSelfDestruct(!selfDestruct);
|
||||
}}
|
||||
color={selfDestruct ? colors.accent : colors.icon}
|
||||
size={SIZE.lg}
|
||||
size={SIZE.xl}
|
||||
name={
|
||||
selfDestruct
|
||||
? "check-circle-outline"
|
||||
@@ -313,31 +316,38 @@ const PublishNoteSheet = ({ note: item, update }) => {
|
||||
</>
|
||||
) : null}
|
||||
|
||||
<Button
|
||||
onPress={publishNote}
|
||||
fontSize={SIZE.md}
|
||||
width="100%"
|
||||
<View
|
||||
style={{
|
||||
marginTop: 10
|
||||
flexDirection: "row",
|
||||
width: "100%",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
height={50}
|
||||
type="accent"
|
||||
title={isPublished ? "Update published note" : "Publish note"}
|
||||
/>
|
||||
|
||||
{isPublished && (
|
||||
<>
|
||||
<Seperator half />
|
||||
<Button
|
||||
onPress={deletePublishedNote}
|
||||
fontSize={SIZE.md}
|
||||
width="100%"
|
||||
height={50}
|
||||
type="error"
|
||||
title="Unpublish note"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
>
|
||||
{isPublished && (
|
||||
<>
|
||||
<Button
|
||||
onPress={deletePublishedNote}
|
||||
fontSize={SIZE.md}
|
||||
type="error"
|
||||
title="Unpublish"
|
||||
style={{
|
||||
width: "49%"
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Seperator half />
|
||||
<Button
|
||||
onPress={publishNote}
|
||||
fontSize={SIZE.md}
|
||||
style={{
|
||||
width: isPublished ? "49%" : 250,
|
||||
borderRadius: isPublished ? 5 : 100
|
||||
}}
|
||||
type="accent"
|
||||
title={isPublished ? "Update" : "Publish"}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
@@ -347,7 +357,7 @@ const PublishNoteSheet = ({ note: item, update }) => {
|
||||
size={SIZE.xs}
|
||||
style={{
|
||||
textAlign: "center",
|
||||
marginTop: 5,
|
||||
marginTop: 10,
|
||||
textDecorationLine: "underline"
|
||||
}}
|
||||
onPress={async () => {
|
||||
|
||||
@@ -96,7 +96,6 @@ const RateAppSheet = () => {
|
||||
onPress={rateApp}
|
||||
fontSize={SIZE.md}
|
||||
width="100%"
|
||||
height={50}
|
||||
type="accent"
|
||||
title="Rate now (It takes only a second)"
|
||||
/>
|
||||
@@ -121,14 +120,12 @@ const RateAppSheet = () => {
|
||||
fontSize={SIZE.md}
|
||||
type="error"
|
||||
width="48%"
|
||||
height={50}
|
||||
title="Never"
|
||||
/>
|
||||
<Button
|
||||
onPress={onClose}
|
||||
fontSize={SIZE.md}
|
||||
width="48%"
|
||||
height={50}
|
||||
type="grayBg"
|
||||
title="Later"
|
||||
/>
|
||||
|
||||
@@ -298,7 +298,6 @@ class RecoveryKeySheet extends React.Component {
|
||||
width="100%"
|
||||
type="grayAccent"
|
||||
fontSize={SIZE.md}
|
||||
height={50}
|
||||
/>
|
||||
<Seperator />
|
||||
<Button
|
||||
@@ -308,7 +307,6 @@ class RecoveryKeySheet extends React.Component {
|
||||
type="grayAccent"
|
||||
fontSize={SIZE.md}
|
||||
icon="qrcode"
|
||||
height={50}
|
||||
/>
|
||||
<Seperator />
|
||||
<Button
|
||||
@@ -318,7 +316,6 @@ class RecoveryKeySheet extends React.Component {
|
||||
type="grayAccent"
|
||||
icon="text"
|
||||
fontSize={SIZE.md}
|
||||
height={50}
|
||||
/>
|
||||
<Seperator />
|
||||
|
||||
@@ -329,7 +326,6 @@ class RecoveryKeySheet extends React.Component {
|
||||
type="grayAccent"
|
||||
icon="cloud"
|
||||
fontSize={SIZE.md}
|
||||
height={50}
|
||||
/>
|
||||
<Seperator />
|
||||
|
||||
@@ -349,7 +345,6 @@ class RecoveryKeySheet extends React.Component {
|
||||
<Button
|
||||
title="I have saved the key."
|
||||
width="100%"
|
||||
height={50}
|
||||
type="error"
|
||||
fontSize={SIZE.md}
|
||||
onPress={this.close}
|
||||
|
||||
@@ -434,11 +434,6 @@ export default function ReminderSheet({
|
||||
|
||||
<DatePicker
|
||||
date={date}
|
||||
minimumDate={
|
||||
dayjs(date).subtract(3, "months").isBefore(dayjs())
|
||||
? dayjs().toDate()
|
||||
: dayjs(date).subtract(3, "months").toDate()
|
||||
}
|
||||
maximumDate={dayjs(date).add(3, "months").toDate()}
|
||||
onDateChange={handleConfirm}
|
||||
textColor={colors.night ? "#ffffff" : "#000000"}
|
||||
|
||||
@@ -32,6 +32,7 @@ import Seperator from "../../ui/seperator";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
const Sort = ({ type, screen }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const isTopicSheet = screen === "TopicSheet";
|
||||
const [groupOptions, setGroupOptions] = useState(
|
||||
db.settings.getGroupOptions(type)
|
||||
);
|
||||
@@ -41,7 +42,7 @@ const Sort = ({ type, screen }) => {
|
||||
layoutmanager.withSpringAnimation(600);
|
||||
setGroupOptions(_groupOptions);
|
||||
setTimeout(() => {
|
||||
Navigation.queueRoutesForUpdate(screen);
|
||||
if (screen !== "TopicSheet") Navigation.queueRoutesForUpdate(screen);
|
||||
eSendEvent("groupOptionsUpdate");
|
||||
eSendEvent(refreshNotesPage);
|
||||
}, 1);
|
||||
@@ -52,6 +53,9 @@ const Sort = ({ type, screen }) => {
|
||||
...groupOptions,
|
||||
sortDirection: groupOptions.sortDirection === "asc" ? "desc" : "asc"
|
||||
};
|
||||
if (type === "topics") {
|
||||
_groupOptions.groupBy = "none";
|
||||
}
|
||||
await updateGroupOptions(_groupOptions);
|
||||
};
|
||||
|
||||
@@ -119,7 +123,7 @@ const Sort = ({ type, screen }) => {
|
||||
flexDirection: "row",
|
||||
justifyContent: "flex-start",
|
||||
flexWrap: "wrap",
|
||||
borderBottomWidth: 1,
|
||||
borderBottomWidth: isTopicSheet ? 0 : 1,
|
||||
borderBottomColor: colors.nav,
|
||||
marginBottom: 12,
|
||||
paddingHorizontal: 12,
|
||||
@@ -166,6 +170,9 @@ const Sort = ({ type, screen }) => {
|
||||
...groupOptions,
|
||||
sortBy: type === "trash" ? "dateDeleted" : item
|
||||
};
|
||||
if (type === "topics") {
|
||||
_groupOptions.groupBy = "none";
|
||||
}
|
||||
await updateGroupOptions(_groupOptions);
|
||||
}}
|
||||
iconSize={SIZE.md}
|
||||
@@ -175,64 +182,70 @@ const Sort = ({ type, screen }) => {
|
||||
)}
|
||||
</View>
|
||||
|
||||
<Heading
|
||||
style={{
|
||||
marginLeft: 12
|
||||
}}
|
||||
size={SIZE.lg}
|
||||
>
|
||||
Group by
|
||||
</Heading>
|
||||
|
||||
<Seperator />
|
||||
|
||||
<View
|
||||
style={{
|
||||
borderRadius: 0,
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
{Object.keys(GROUP).map((item) => (
|
||||
<Button
|
||||
key={item}
|
||||
testID={"btn-" + item}
|
||||
type={groupOptions.groupBy === GROUP[item] ? "grayBg" : "gray"}
|
||||
buttonType={{
|
||||
text:
|
||||
groupOptions.groupBy === GROUP[item]
|
||||
? colors.accent
|
||||
: colors.icon
|
||||
}}
|
||||
onPress={async () => {
|
||||
let _groupOptions = {
|
||||
...groupOptions,
|
||||
groupBy: GROUP[item]
|
||||
};
|
||||
|
||||
if (item === "abc") {
|
||||
_groupOptions.sortBy = "title";
|
||||
_groupOptions.sortDirection = "asc";
|
||||
} else {
|
||||
if (groupOptions.sortBy === "title") {
|
||||
_groupOptions.sortBy = "dateEdited";
|
||||
_groupOptions.sortDirection = "desc";
|
||||
}
|
||||
}
|
||||
updateGroupOptions(_groupOptions);
|
||||
}}
|
||||
height={40}
|
||||
icon={groupOptions.groupBy === GROUP[item] ? "check" : null}
|
||||
title={item.slice(0, 1).toUpperCase() + item.slice(1, item.length)}
|
||||
{isTopicSheet ? null : (
|
||||
<>
|
||||
<Heading
|
||||
style={{
|
||||
paddingHorizontal: 8,
|
||||
marginBottom: 10,
|
||||
marginRight: 10
|
||||
marginLeft: 12
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
size={SIZE.lg}
|
||||
>
|
||||
Group by
|
||||
</Heading>
|
||||
|
||||
<Seperator />
|
||||
|
||||
<View
|
||||
style={{
|
||||
borderRadius: 0,
|
||||
flexDirection: "row",
|
||||
flexWrap: "wrap",
|
||||
paddingHorizontal: 12
|
||||
}}
|
||||
>
|
||||
{Object.keys(GROUP).map((item) => (
|
||||
<Button
|
||||
key={item}
|
||||
testID={"btn-" + item}
|
||||
type={groupOptions.groupBy === GROUP[item] ? "grayBg" : "gray"}
|
||||
buttonType={{
|
||||
text:
|
||||
groupOptions.groupBy === GROUP[item]
|
||||
? colors.accent
|
||||
: colors.icon
|
||||
}}
|
||||
onPress={async () => {
|
||||
let _groupOptions = {
|
||||
...groupOptions,
|
||||
groupBy: GROUP[item]
|
||||
};
|
||||
|
||||
if (item === "abc") {
|
||||
_groupOptions.sortBy = "title";
|
||||
_groupOptions.sortDirection = "asc";
|
||||
} else {
|
||||
if (groupOptions.sortBy === "title") {
|
||||
_groupOptions.sortBy = "dateEdited";
|
||||
_groupOptions.sortDirection = "desc";
|
||||
}
|
||||
}
|
||||
updateGroupOptions(_groupOptions);
|
||||
}}
|
||||
height={40}
|
||||
icon={groupOptions.groupBy === GROUP[item] ? "check" : null}
|
||||
title={
|
||||
item.slice(0, 1).toUpperCase() + item.slice(1, item.length)
|
||||
}
|
||||
style={{
|
||||
paddingHorizontal: 8,
|
||||
marginBottom: 10,
|
||||
marginRight: 10
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -19,12 +19,19 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import qclone from "qclone";
|
||||
import React, {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState
|
||||
} from "react";
|
||||
import { Animated, Dimensions, View, RefreshControl } from "react-native";
|
||||
import {
|
||||
Animated,
|
||||
Dimensions,
|
||||
View,
|
||||
RefreshControl,
|
||||
Platform
|
||||
} from "react-native";
|
||||
import ActionSheet, {
|
||||
ActionSheetRef,
|
||||
FlatList
|
||||
@@ -37,15 +44,20 @@ import { TopicNotes } from "../../../screens/notes/topic-notes";
|
||||
import {
|
||||
eSendEvent,
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
eUnSubscribeEvent,
|
||||
presentSheet
|
||||
} from "../../../services/event-manager";
|
||||
import useNavigationStore, {
|
||||
NotebookScreenParams
|
||||
} from "../../../stores/use-navigation-store";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { eOnNewTopicAdded, eOpenAddTopicDialog } from "../../../utils/events";
|
||||
import {
|
||||
eOnNewTopicAdded,
|
||||
eOnTopicSheetUpdate,
|
||||
eOpenAddTopicDialog
|
||||
} from "../../../utils/events";
|
||||
import { normalize, SIZE } from "../../../utils/size";
|
||||
import { NotebookType, TopicType } from "../../../utils/types";
|
||||
import { GroupHeader, NotebookType, TopicType } from "../../../utils/types";
|
||||
|
||||
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
||||
import { openEditor } from "../../../screens/notes/common";
|
||||
@@ -55,6 +67,8 @@ import { deleteItems } from "../../../utils/functions";
|
||||
import { presentDialog } from "../../dialog/functions";
|
||||
import Config from "react-native-config";
|
||||
import { notesnook } from "../../../../e2e/test.ids";
|
||||
import Sort from "../sort";
|
||||
import { groupArray } from "@notesnook/core/utils/grouping";
|
||||
|
||||
export const TopicsSheet = () => {
|
||||
const currentScreen = useNavigationStore((state) => state.currentScreen);
|
||||
@@ -71,12 +85,33 @@ export const TopicsSheet = () => {
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const ref = useRef<ActionSheetRef>(null);
|
||||
const [topics, setTopics] = useState(notebook ? qclone(notebook.topics) : []);
|
||||
const [topics, setTopics] = useState(
|
||||
notebook
|
||||
? qclone(
|
||||
groupArray(notebook.topics, db.settings?.getGroupOptions("topics"))
|
||||
)
|
||||
: []
|
||||
);
|
||||
const [animations] = useState({
|
||||
translate: new Animated.Value(0),
|
||||
display: new Animated.Value(-5000),
|
||||
opacity: new Animated.Value(0)
|
||||
});
|
||||
const [groupOptions, setGroupOptions] = useState(
|
||||
db.settings?.getGroupOptions("topics")
|
||||
);
|
||||
|
||||
const onUpdate = useCallback(() => {
|
||||
setGroupOptions({ ...(db.settings?.getGroupOptions("topics") as any) });
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent("groupOptionsUpdate", onUpdate);
|
||||
return () => {
|
||||
eUnSubscribeEvent("groupOptionsUpdate", onUpdate);
|
||||
};
|
||||
}, [onUpdate]);
|
||||
|
||||
const onRequestUpdate = React.useCallback(
|
||||
(data?: NotebookScreenParams) => {
|
||||
if (!canShow) return;
|
||||
@@ -85,16 +120,22 @@ export const TopicsSheet = () => {
|
||||
?.data as NotebookType;
|
||||
if (_notebook) {
|
||||
setNotebook(_notebook);
|
||||
setTopics(qclone(_notebook.topics));
|
||||
|
||||
setTopics(qclone(groupArray(_notebook.topics, groupOptions)));
|
||||
}
|
||||
},
|
||||
[notebook, canShow]
|
||||
[canShow, notebook, groupOptions]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const onTopicUpdate = () => {
|
||||
onRequestUpdate();
|
||||
};
|
||||
eSubscribeEvent(eOnTopicSheetUpdate, onTopicUpdate);
|
||||
eSubscribeEvent(eOnNewTopicAdded, onRequestUpdate);
|
||||
return () => {
|
||||
eUnSubscribeEvent(eOnNewTopicAdded, onRequestUpdate);
|
||||
eUnSubscribeEvent(eOnTopicSheetUpdate, onRequestUpdate);
|
||||
eUnSubscribeEvent(eOnNewTopicAdded, onTopicUpdate);
|
||||
};
|
||||
}, [onRequestUpdate]);
|
||||
|
||||
@@ -108,9 +149,16 @@ export const TopicsSheet = () => {
|
||||
loading: "Loading notebook topics"
|
||||
};
|
||||
|
||||
const renderTopic = ({ item, index }: { item: TopicType; index: number }) => (
|
||||
<TopicItem item={item} index={index} />
|
||||
);
|
||||
const renderTopic = ({
|
||||
item,
|
||||
index
|
||||
}: {
|
||||
item: TopicType | GroupHeader;
|
||||
index: number;
|
||||
}) =>
|
||||
(item as GroupHeader).type === "header" ? null : (
|
||||
<TopicItem item={item as TopicType} index={index} />
|
||||
);
|
||||
|
||||
const selectionContext = {
|
||||
selection: selection,
|
||||
@@ -145,7 +193,7 @@ export const TopicsSheet = () => {
|
||||
}
|
||||
if (id) {
|
||||
onRequestUpdate({
|
||||
item: db.notebooks?.notebook(id).data
|
||||
item: db.notebooks?.notebook(id)?.data
|
||||
} as any);
|
||||
}
|
||||
ref.current?.show();
|
||||
@@ -167,7 +215,7 @@ export const TopicsSheet = () => {
|
||||
ref={ref}
|
||||
isModal={false}
|
||||
containerStyle={{
|
||||
maxHeight: 600,
|
||||
maxHeight: 400,
|
||||
borderTopRightRadius: 15,
|
||||
borderTopLeftRadius: 15,
|
||||
backgroundColor: colors.bg,
|
||||
@@ -182,11 +230,15 @@ export const TopicsSheet = () => {
|
||||
backgroundColor: colors.nav
|
||||
}}
|
||||
keyboardHandlerEnabled={false}
|
||||
snapPoints={Config.isTesting === "true" ? [60, 100] : [15, 60, 100]}
|
||||
snapPoints={
|
||||
Config.isTesting === "true"
|
||||
? [100]
|
||||
: [Platform.OS === "ios" ? 25 : 20, 100]
|
||||
}
|
||||
initialSnapIndex={0}
|
||||
backgroundInteractionEnabled
|
||||
onChange={(position, height) => {
|
||||
animations.translate.setValue(position);
|
||||
animations.translate.setValue(position - 60);
|
||||
const h = Dimensions.get("window").height;
|
||||
const minPos = h - height;
|
||||
if (position - 100 < minPos || !canShow) {
|
||||
@@ -221,8 +273,7 @@ export const TopicsSheet = () => {
|
||||
accentText="light"
|
||||
onPress={openEditor}
|
||||
customStyle={{
|
||||
borderRadius: 100,
|
||||
bottom: 50
|
||||
borderRadius: 100
|
||||
}}
|
||||
>
|
||||
<View
|
||||
@@ -241,8 +292,8 @@ export const TopicsSheet = () => {
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
maxHeight: 600,
|
||||
height: 600,
|
||||
maxHeight: 400,
|
||||
height: 400,
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
@@ -296,17 +347,38 @@ export const TopicsSheet = () => {
|
||||
size={22}
|
||||
/>
|
||||
) : (
|
||||
<IconButton
|
||||
name="plus"
|
||||
onPress={PLACEHOLDER_DATA.action}
|
||||
testID="add-topic-button"
|
||||
color={colors.pri}
|
||||
size={22}
|
||||
customStyle={{
|
||||
width: 40,
|
||||
height: 40
|
||||
}}
|
||||
/>
|
||||
<>
|
||||
<IconButton
|
||||
name={
|
||||
groupOptions?.sortDirection === "asc"
|
||||
? "sort-ascending"
|
||||
: "sort-descending"
|
||||
}
|
||||
onPress={() => {
|
||||
presentSheet({
|
||||
component: <Sort screen="TopicSheet" type="topics" />
|
||||
});
|
||||
}}
|
||||
testID="group-topic-button"
|
||||
color={colors.pri}
|
||||
size={22}
|
||||
customStyle={{
|
||||
width: 40,
|
||||
height: 40
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
name="plus"
|
||||
onPress={PLACEHOLDER_DATA.action}
|
||||
testID="add-topic-button"
|
||||
color={colors.pri}
|
||||
size={22}
|
||||
customStyle={{
|
||||
width: 40,
|
||||
height: 40
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
@@ -326,7 +398,7 @@ export const TopicsSheet = () => {
|
||||
progressBackgroundColor={colors.bg}
|
||||
/>
|
||||
}
|
||||
keyExtractor={(item) => item.id}
|
||||
keyExtractor={(item) => (item as TopicType).id}
|
||||
renderItem={renderTopic}
|
||||
ListEmptyComponent={
|
||||
<View
|
||||
|
||||
@@ -164,7 +164,6 @@ export const PinItem = React.memo(
|
||||
}}
|
||||
fontSize={SIZE.md}
|
||||
width="95%"
|
||||
height={50}
|
||||
customStyle={{
|
||||
marginBottom: 30
|
||||
}}
|
||||
|
||||
@@ -92,7 +92,7 @@ const Input = ({
|
||||
button,
|
||||
onBlurInput,
|
||||
onPress,
|
||||
height = 50,
|
||||
height = 45,
|
||||
fontSize = SIZE.md,
|
||||
onFocusInput,
|
||||
buttons,
|
||||
|
||||
@@ -38,7 +38,8 @@ const SheetWrapper = ({
|
||||
keyboardMode,
|
||||
overlay,
|
||||
overlayOpacity = 0.3,
|
||||
enableGesturesInScrollView = false
|
||||
enableGesturesInScrollView = false,
|
||||
bottomPadding = true
|
||||
}) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const deviceMode = useSettingStore((state) => state.deviceMode);
|
||||
@@ -119,14 +120,16 @@ const SheetWrapper = ({
|
||||
>
|
||||
<BouncingView>
|
||||
{children}
|
||||
<View
|
||||
style={{
|
||||
height:
|
||||
Platform.OS === "ios" && insets.bottom !== 0
|
||||
? insets.bottom + 5
|
||||
: 20
|
||||
}}
|
||||
/>
|
||||
{bottomPadding ? (
|
||||
<View
|
||||
style={{
|
||||
height:
|
||||
Platform.OS === "ios" && insets.bottom !== 0
|
||||
? insets.bottom + 5
|
||||
: 20
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</BouncingView>
|
||||
</ActionSheet>
|
||||
);
|
||||
|
||||
@@ -46,6 +46,7 @@ export default function Tag({
|
||||
marginLeft: 2,
|
||||
marginTop: -10,
|
||||
height: 20,
|
||||
justifyContent: "center",
|
||||
...style
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -19,17 +19,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { FeatureType } from "./components/sheets/new-feature";
|
||||
|
||||
export const features: FeatureType[] = [
|
||||
{
|
||||
title: "Improved editor performance",
|
||||
body: "The editor perfomance is now much better especially with images"
|
||||
},
|
||||
{
|
||||
title: "Sortable task lists",
|
||||
body: "Now you can sort tasks by checked/unchecked in editor"
|
||||
},
|
||||
{
|
||||
title: "Improved outline lists",
|
||||
body: "Rewritten outline lists to be more performant and less buggy"
|
||||
}
|
||||
];
|
||||
export const features: FeatureType[] = [];
|
||||
|
||||
@@ -402,11 +402,9 @@ export const useAppEvents = () => {
|
||||
|
||||
const checkAutoBackup = useCallback(async () => {
|
||||
if (verify || syncing) {
|
||||
console.log("backup is waiting");
|
||||
refValues.current.backupDidWait = true;
|
||||
return;
|
||||
}
|
||||
console.log("backup running immediate");
|
||||
const user = await db.user.getUser();
|
||||
if (PremiumService.get() && user) {
|
||||
if (SettingsService.get().reminder === "off") {
|
||||
@@ -427,7 +425,6 @@ export const useAppEvents = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (!verify && !syncing && refValues.current.backupDidWait) {
|
||||
console.log("backup run after wait");
|
||||
refValues.current.backupDidWait = false;
|
||||
checkAutoBackup();
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ import {
|
||||
import { editorRef, tabBarRef } from "../utils/global-refs";
|
||||
import { sleep } from "../utils/time";
|
||||
import { NavigationStack } from "./navigation-stack";
|
||||
import changeNavigationBarColor from "react-native-navigation-bar-color";
|
||||
|
||||
const _TabsHolder = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
@@ -235,14 +236,17 @@ const _TabsHolder = () => {
|
||||
let needsUpdate = current !== deviceMode;
|
||||
|
||||
if (fullscreen && current !== "mobile") {
|
||||
editorRef.current?.setNativeProps({
|
||||
style: {
|
||||
width: size.width,
|
||||
zIndex: 999,
|
||||
paddingHorizontal:
|
||||
current === "smallTablet" ? size.width * 0 : size.width * 0.15
|
||||
}
|
||||
});
|
||||
// Runs after size is set via state.
|
||||
setTimeout(() => {
|
||||
editorRef.current?.setNativeProps({
|
||||
style: {
|
||||
width: size.width,
|
||||
zIndex: 999,
|
||||
paddingHorizontal:
|
||||
current === "smallTablet" ? size.width * 0 : size.width * 0.15
|
||||
}
|
||||
});
|
||||
}, 1);
|
||||
} else {
|
||||
if (fullscreen) eSendEvent(eCloseFullscreenEditor, current);
|
||||
editorRef.current?.setNativeProps({
|
||||
@@ -281,10 +285,8 @@ const _TabsHolder = () => {
|
||||
!editorState().movedAway &&
|
||||
useEditorStore.getState().currentEditingNote
|
||||
) {
|
||||
console.log("editor");
|
||||
tabBarRef.current?.goToIndex(2, false);
|
||||
} else {
|
||||
console.log("home");
|
||||
tabBarRef.current?.goToIndex(1, false);
|
||||
}
|
||||
break;
|
||||
@@ -385,6 +387,10 @@ const _TabsHolder = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
changeNavigationBarColor(colors.bg, !colors.night, true);
|
||||
}, [colors.night, colors.bg]);
|
||||
|
||||
return (
|
||||
<View
|
||||
onLayout={_onLayout}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"html-to-text": "8.1.0",
|
||||
"phone": "^3.1.14",
|
||||
"qclone": "^1.2.0",
|
||||
"react-native-actions-sheet": "^0.9.0-alpha.9",
|
||||
"react-native-actions-sheet": "0.9.0-alpha.16",
|
||||
"react-native-check-version": "https://github.com/flexible-agency/react-native-check-version",
|
||||
"react-native-drax": "^0.10.2",
|
||||
"react-native-image-zoom-viewer": "^3.0.1",
|
||||
|
||||
@@ -28,5 +28,5 @@ const EditorMobileSourceUrl =
|
||||
* The url should be something like this: http://192.168.100.126:3000/index.html
|
||||
*/
|
||||
export const EDITOR_URI = __DEV__
|
||||
? "http://192.168.8.103:3000/index.html"
|
||||
? EditorMobileSourceUrl
|
||||
: EditorMobileSourceUrl;
|
||||
|
||||
@@ -129,7 +129,6 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
|
||||
};
|
||||
|
||||
setInsets = async (insets: EdgeInsets) => {
|
||||
logger.info("setInsets", insets);
|
||||
await this.doAsync(`
|
||||
if (typeof safeAreaController !== "undefined") {
|
||||
safeAreaController.update(${JSON.stringify(insets)})
|
||||
@@ -229,7 +228,7 @@ typeof globalThis.statusBar !== "undefined" && statusBar.current.set({date:"",sa
|
||||
await this.doAsync(
|
||||
`editor && editor.commands.updateImage(${JSON.stringify({
|
||||
hash
|
||||
})},${JSON.stringify({ src, hash, preventUpdate: true })})`
|
||||
})},${JSON.stringify({ dataurl: src, hash, preventUpdate: true })})`
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -17,13 +17,14 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import React from "react";
|
||||
import { Platform, View } from "react-native";
|
||||
import DocumentPicker from "react-native-document-picker";
|
||||
import { launchCamera, launchImageLibrary } from "react-native-image-picker";
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import { db } from "../../../common/database";
|
||||
import { compressToBase64 } from "../../../common/filesystem/compress";
|
||||
import { AttachmentItem } from "../../../components/attachments/attachment-item";
|
||||
import {
|
||||
eSendEvent,
|
||||
@@ -32,9 +33,7 @@ import {
|
||||
} from "../../../services/event-manager";
|
||||
import PremiumService from "../../../services/premium";
|
||||
import { eCloseSheet } from "../../../utils/events";
|
||||
import { sleep } from "../../../utils/time";
|
||||
import { editorController, editorState } from "./utils";
|
||||
import { compressToBase64 } from "../../../common/filesystem/compress";
|
||||
const FILE_SIZE_LIMIT = 500 * 1024 * 1024;
|
||||
const IMAGE_SIZE_LIMIT = 50 * 1024 * 1024;
|
||||
|
||||
@@ -149,8 +148,6 @@ const file = async (fileOptions) => {
|
||||
const camera = async (options) => {
|
||||
try {
|
||||
await db.attachments.generateKey();
|
||||
eSendEvent(eCloseSheet);
|
||||
await sleep(400);
|
||||
launchCamera(
|
||||
{
|
||||
includeBase64: true,
|
||||
@@ -172,8 +169,6 @@ const camera = async (options) => {
|
||||
const gallery = async (options) => {
|
||||
try {
|
||||
await db.attachments.generateKey();
|
||||
eSendEvent(eCloseSheet);
|
||||
await sleep(400);
|
||||
launchImageLibrary(
|
||||
{
|
||||
includeBase64: true,
|
||||
@@ -207,7 +202,7 @@ const pick = async (options) => {
|
||||
return;
|
||||
}
|
||||
if (options?.type.startsWith("image") || options?.type === "camera") {
|
||||
if (options.type === "image") {
|
||||
if (options.type.startsWith("image")) {
|
||||
gallery(options);
|
||||
} else {
|
||||
camera(options);
|
||||
@@ -250,7 +245,10 @@ const handleImageResponse = async (response, options) => {
|
||||
if (isPng || isJpeg) {
|
||||
b64 =
|
||||
`data:${image.type};base64, ` +
|
||||
(await compressToBase64(image.uri, isPng ? "PNG" : "JPEG"));
|
||||
(await compressToBase64(
|
||||
Platform.OS === "ios" ? "file://" + image.uri : image.uri,
|
||||
isPng ? "PNG" : "JPEG"
|
||||
));
|
||||
}
|
||||
|
||||
if (Platform.OS === "ios") await RNFetchBlob.fs.unlink(uri);
|
||||
@@ -259,7 +257,7 @@ const handleImageResponse = async (response, options) => {
|
||||
hash: hash,
|
||||
type: image.type,
|
||||
title: fileName,
|
||||
src: b64,
|
||||
dataurl: b64,
|
||||
size: image.fileSize,
|
||||
filename: fileName
|
||||
});
|
||||
|
||||
@@ -76,8 +76,7 @@ export const useEditor = (
|
||||
const saveCount = useRef(0);
|
||||
const lastContentChangeTime = useRef<number>(0);
|
||||
const lock = useRef(false);
|
||||
const attachedImages = useRef<{ [name: string]: any }>({});
|
||||
const loadedImages = useRef<{ [name: string]: any }>({});
|
||||
const loadedImages = useRef<{ [name: string]: boolean }>({});
|
||||
|
||||
const postMessage = useCallback(
|
||||
async <T>(type: string, data: T) =>
|
||||
@@ -156,8 +155,7 @@ export const useEditor = (
|
||||
async (resetState = true) => {
|
||||
currentNote.current?.id && db.fs.cancel(currentNote.current.id);
|
||||
currentNote.current = null;
|
||||
attachedImages.current = [];
|
||||
loadedImages.current = [];
|
||||
loadedImages.current = {};
|
||||
currentContent.current = null;
|
||||
clearTimeout(timers.current["loading-images"]);
|
||||
sessionHistoryId.current = undefined;
|
||||
@@ -230,7 +228,6 @@ export const useEditor = (
|
||||
id = await db.notes?.add(noteData);
|
||||
if (!note && id) {
|
||||
currentNote.current = db.notes?.note(id).data as NoteType;
|
||||
console.log("on Note Created", state.current?.onNoteCreated);
|
||||
state.current?.onNoteCreated && state.current.onNoteCreated(id);
|
||||
if (!noteData.title) {
|
||||
postMessage(
|
||||
@@ -296,32 +293,38 @@ export const useEditor = (
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getImagesToLoad = () => {
|
||||
const getMediaToLoad = (previousContent?: string) => {
|
||||
if (!currentNote.current?.id) return [];
|
||||
const currentImages = [
|
||||
...(db.attachments?.ofNote(currentNote.current?.id, "images") || []),
|
||||
...(db.attachments?.ofNote(currentNote.current?.id, "webclips") || [])
|
||||
];
|
||||
if (!currentImages || currentImages?.length === 0) return [];
|
||||
const imagesToLoad: any[] = [];
|
||||
for (const image of currentImages) {
|
||||
if (!loadedImages.current[image.metadata.hash]) {
|
||||
loadedImages.current[image.metadata.hash] = false;
|
||||
imagesToLoad.push(image);
|
||||
|
||||
const previousAttachments =
|
||||
previousContent?.matchAll(/data-hash="(.+?)"/gm) || [];
|
||||
const attachments =
|
||||
currentContent.current?.data?.matchAll(/data-hash="(.+?)"/gm) || [];
|
||||
|
||||
const media: string[] = [];
|
||||
|
||||
const oldMatches = Array.from(previousAttachments).map((match) => match[1]);
|
||||
const matches = Array.from(attachments).map((match) => match[1]);
|
||||
|
||||
for (let i = 0; i < matches.length; i++) {
|
||||
const currentHash = matches[i];
|
||||
const oldHash = oldMatches[i];
|
||||
if (currentHash !== oldHash) {
|
||||
media.push(currentHash);
|
||||
loadedImages.current[currentHash] = false;
|
||||
}
|
||||
attachedImages.current[image.metadata.hash] = image;
|
||||
}
|
||||
return imagesToLoad;
|
||||
return media;
|
||||
};
|
||||
|
||||
const markImageLoaded = (hash: string) => {
|
||||
const attachment = attachedImages.current[hash];
|
||||
if (attachment) {
|
||||
const attachment = loadedImages.current[hash];
|
||||
if (typeof attachment === "boolean") {
|
||||
loadedImages.current[hash] = true;
|
||||
}
|
||||
};
|
||||
|
||||
const loadImages = useCallback(() => {
|
||||
const loadImages = useCallback((previousContent?: string) => {
|
||||
if (!currentNote.current?.id) return;
|
||||
const timerId = "loading-images";
|
||||
clearTimeout(timers.current[timerId]);
|
||||
@@ -334,12 +337,12 @@ export const useEditor = (
|
||||
true
|
||||
);
|
||||
} else {
|
||||
const images = getImagesToLoad();
|
||||
if (images.length > 0) {
|
||||
db.attachments?.downloadMedia(currentNote.current?.id, images);
|
||||
const media = getMediaToLoad(previousContent);
|
||||
if (media.length > 0) {
|
||||
db.attachments?.downloadMedia(currentNote.current?.id, media);
|
||||
}
|
||||
}
|
||||
}, 100);
|
||||
}, 1000);
|
||||
}, []);
|
||||
|
||||
const loadNote = useCallback(
|
||||
@@ -423,6 +426,8 @@ export const useEditor = (
|
||||
|
||||
lock.current = true;
|
||||
|
||||
const previousContent = currentContent.current?.data;
|
||||
|
||||
if (data.type === "tiptap") {
|
||||
if (!currentNote.current.locked && isContentEncrypted) {
|
||||
lockNoteWithVault(note);
|
||||
@@ -453,13 +458,14 @@ export const useEditor = (
|
||||
}
|
||||
await commands.setStatus(timeConverter(note.dateEdited), "Saved");
|
||||
}
|
||||
|
||||
lock.current = false;
|
||||
if (data.type === "tiptap") {
|
||||
loadImages();
|
||||
loadImages(previousContent);
|
||||
db.eventManager.subscribe(
|
||||
EVENTS.syncCompleted,
|
||||
() => {
|
||||
loadImages();
|
||||
loadImages(previousContent);
|
||||
},
|
||||
true
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@ import Navigation from "../../services/navigation";
|
||||
import { useMenuStore } from "../../stores/use-menu-store";
|
||||
import { NotesScreenParams } from "../../stores/use-navigation-store";
|
||||
import { useTagStore } from "../../stores/use-tag-store";
|
||||
import { eOnLoadNote } from "../../utils/events";
|
||||
import { eOnLoadNote, eOnTopicSheetUpdate } from "../../utils/events";
|
||||
import { openLinkInBrowser } from "../../utils/functions";
|
||||
import { tabBarRef } from "../../utils/global-refs";
|
||||
import { TopicType } from "../../utils/types";
|
||||
@@ -125,6 +125,7 @@ async function onNoteCreated(id: string, params: FirstSaveData) {
|
||||
"Notebook",
|
||||
"Notebooks"
|
||||
);
|
||||
eSendEvent(eOnTopicSheetUpdate);
|
||||
break;
|
||||
}
|
||||
case "tag": {
|
||||
|
||||
@@ -106,12 +106,6 @@ export const useDragState = create<DragState>(
|
||||
return;
|
||||
}
|
||||
const preset = toolbarConfig?.preset as DragState["preset"];
|
||||
logger.info(
|
||||
"DragState",
|
||||
"Init user toolbar config",
|
||||
preset,
|
||||
toolbarConfig?.config
|
||||
);
|
||||
set({
|
||||
preset: preset,
|
||||
data:
|
||||
|
||||
@@ -169,6 +169,14 @@ const _SectionItem = ({ item }: { item: SettingSection }) => {
|
||||
}
|
||||
item.inputProperties?.onSubmitEditing?.(e);
|
||||
}}
|
||||
onChangeText={(text) => {
|
||||
if (text) {
|
||||
SettingsService.set({
|
||||
[item.property as string]: text
|
||||
});
|
||||
}
|
||||
item.inputProperties?.onSubmitEditing?.(text as any);
|
||||
}}
|
||||
containerStyle={{ marginTop: 12 }}
|
||||
fwdRef={inputRef}
|
||||
onLayout={() => {
|
||||
|
||||
@@ -17,6 +17,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import notifee from "@notifee/react-native";
|
||||
import dayjs from "dayjs";
|
||||
import React from "react";
|
||||
import { Linking, Platform } from "react-native";
|
||||
@@ -25,6 +26,7 @@ import * as RNIap from "react-native-iap";
|
||||
import { enabled } from "react-native-privacy-snapshot";
|
||||
import { db } from "../../common/database";
|
||||
import { MMKV } from "../../common/database/mmkv";
|
||||
import { AttachmentDialog } from "../../components/attachments";
|
||||
import { ChangePassword } from "../../components/auth/change-password";
|
||||
import { presentDialog } from "../../components/dialog/functions";
|
||||
import { ChangeEmail } from "../../components/sheets/change-email";
|
||||
@@ -56,7 +58,6 @@ import { SUBSCRIPTION_STATUS } from "../../utils/constants";
|
||||
import {
|
||||
eCloseSheet,
|
||||
eCloseSimpleDialog,
|
||||
eOpenAttachmentsDialog,
|
||||
eOpenLoginDialog,
|
||||
eOpenRecoveryKeyDialog,
|
||||
eOpenRestoreDialog
|
||||
@@ -68,7 +69,6 @@ import { useDragState } from "./editor/state";
|
||||
import { verifyUser } from "./functions";
|
||||
import { SettingSection } from "./types";
|
||||
import { getTimeLeft } from "./user-section";
|
||||
import notifee from "@notifee/react-native";
|
||||
|
||||
type User = any;
|
||||
|
||||
@@ -149,7 +149,7 @@ export const settingsGroups: SettingSection[] = [
|
||||
name: "Manage attachments",
|
||||
icon: "attachment",
|
||||
modifer: () => {
|
||||
eSendEvent(eOpenAttachmentsDialog);
|
||||
AttachmentDialog.present();
|
||||
},
|
||||
description: "Manage all attachments in one place."
|
||||
},
|
||||
@@ -1027,11 +1027,21 @@ export const settingsGroups: SettingSection[] = [
|
||||
id: "join-telegram",
|
||||
name: "Join our Telegram group",
|
||||
description: "We are on telegram, let's talk",
|
||||
// icon: 'telegram',
|
||||
modifer: () => {
|
||||
Linking.openURL("https://t.me/notesnook").catch(console.log);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "join-mastodom",
|
||||
name: "Follow us on Mastodon",
|
||||
description: "We are on mastodom",
|
||||
icon: "mastodon",
|
||||
modifer: () => {
|
||||
Linking.openURL("https://fosstodon.org/@notesnook").catch(
|
||||
console.log
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "join-twitter",
|
||||
name: "Follow us on twitter",
|
||||
|
||||
@@ -114,7 +114,8 @@ export type PresentSheetOptions = {
|
||||
actionsArray: SheetAction[];
|
||||
learnMore: string;
|
||||
learnMorePress: () => void;
|
||||
enableGesturesInScrollView: boolean;
|
||||
enableGesturesInScrollView?: boolean;
|
||||
noBottomPadding?: boolean;
|
||||
};
|
||||
|
||||
export function presentSheet(data: Partial<PresentSheetOptions>) {
|
||||
|
||||
@@ -367,7 +367,8 @@ async function scheduleNotification(
|
||||
|
||||
function loadNote(id: string, jump: boolean) {
|
||||
if (!id || id === "notesnook_note_input") return;
|
||||
const note = db.notes?.note(id).data;
|
||||
const note = db.notes?.note(id)?.data;
|
||||
if (!note) return;
|
||||
if (!DDS.isTab && jump) {
|
||||
tabBarRef.current?.goToPage(1);
|
||||
}
|
||||
|
||||
@@ -104,7 +104,7 @@ export const COLOR_SCHEME_PITCH_BLACK = {
|
||||
light: "#ffffff",
|
||||
transGray: "#ffffff10",
|
||||
border: "#383838",
|
||||
placeholder: "#404040"
|
||||
placeholder: "#606060"
|
||||
};
|
||||
|
||||
export const COLOR_SCHEME_DARK = {
|
||||
|
||||
@@ -158,3 +158,5 @@ export const eCloseAnnouncementDialog = "604";
|
||||
|
||||
export const eOpenLoading = "605";
|
||||
export const eCloseLoading = "606";
|
||||
|
||||
export const eOnTopicSheetUpdate = "607";
|
||||
|
||||
@@ -164,7 +164,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
multiDexEnabled true
|
||||
versionCode 2037
|
||||
versionCode 2041
|
||||
versionName getNpmVersion()
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 9.6 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 32 KiB |
@@ -1,3 +1,7 @@
|
||||
- Fixed reminders notifications on android
|
||||
- Added support for sorting topics in topics sheet
|
||||
- Navigation Bar in app now follows app theme
|
||||
- Splashscreen logo quality in dark mode has been improved
|
||||
- Fix topic title goes into multiple lines if long
|
||||
- Fix reminder date picker not allowing to pick a date from past
|
||||
|
||||
Thank you for using Notesnook!
|
||||
@@ -1083,7 +1083,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2029;
|
||||
CURRENT_PROJECT_VERSION = 2033;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
ENABLE_BITCODE = NO;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1157,7 +1157,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.6;
|
||||
MARKETING_VERSION = 2.4.10;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -1187,7 +1187,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Notesnook/Notesnook.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 2029;
|
||||
CURRENT_PROJECT_VERSION = 2033;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
|
||||
@@ -1260,7 +1260,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.6;
|
||||
MARKETING_VERSION = 2.4.10;
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
@@ -1418,7 +1418,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2029;
|
||||
CURRENT_PROJECT_VERSION = 2033;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1430,7 +1430,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.6;
|
||||
MARKETING_VERSION = 2.4.10;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
@@ -1460,7 +1460,7 @@
|
||||
CODE_SIGN_IDENTITY = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2029;
|
||||
CURRENT_PROJECT_VERSION = 2033;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1472,7 +1472,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.6;
|
||||
MARKETING_VERSION = 2.4.10;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1501,7 +1501,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2029;
|
||||
CURRENT_PROJECT_VERSION = 2033;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1575,7 +1575,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.6;
|
||||
MARKETING_VERSION = 2.4.10;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
@@ -1605,7 +1605,7 @@
|
||||
CODE_SIGN_IDENTITY = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2029;
|
||||
CURRENT_PROJECT_VERSION = 2033;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1679,7 +1679,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.4.6;
|
||||
MARKETING_VERSION = 2.4.10;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
||||
@@ -371,10 +371,10 @@ PODS:
|
||||
- React-Core
|
||||
- RNKeychain (4.0.5):
|
||||
- React
|
||||
- RNNotifee (7.4.3):
|
||||
- RNNotifee (7.4.4):
|
||||
- React-Core
|
||||
- RNNotifee/NotifeeCore (= 7.4.3)
|
||||
- RNNotifee/NotifeeCore (7.4.3):
|
||||
- RNNotifee/NotifeeCore (= 7.4.4)
|
||||
- RNNotifee/NotifeeCore (7.4.4):
|
||||
- React-Core
|
||||
- RNPrivacySnapshot (1.0.0):
|
||||
- React-Core
|
||||
@@ -754,7 +754,7 @@ SPEC CHECKSUMS:
|
||||
RNGestureHandler: b7a872907ee289ada902127f2554fa1d2c076122
|
||||
RNIap: d248609d1b8937e63bd904e865c318e9b1457eff
|
||||
RNKeychain: 840f8e6f13be0576202aefcdffd26a4f54bfe7b5
|
||||
RNNotifee: 5dfb0c5783ddb3da47b39e75d06cc7e748d6ca39
|
||||
RNNotifee: 2ae3c18196e6f307fa62ae5c8e5305dea03ff147
|
||||
RNPrivacySnapshot: 8eaf571478a353f2e5184f5c803164f22428b023
|
||||
RNReanimated: f1b109fb8341505ace9d7d2eedd150da1686716b
|
||||
RNScreens: 34cc502acf1b916c582c60003dc3089fa01dc66d
|
||||
|
||||
@@ -57,7 +57,8 @@
|
||||
"@react-native-community/datetimepicker":"6.6.0",
|
||||
"react-native-date-picker": "4.2.6",
|
||||
"react-native-notification-sounds": "0.5.5",
|
||||
"@bam.tech/react-native-image-resizer": "3.0.5"
|
||||
"@bam.tech/react-native-image-resizer": "3.0.5",
|
||||
"react-native-navigation-bar-color": "2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.9",
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
- Fixed reminders notifications on android
|
||||
- Added support for sorting topics in topics sheet
|
||||
- Fixed topics sheet is hard to swipe up from bottom
|
||||
- Fix topic title goes into multiple lines if long
|
||||
- Fix reminder date picker not allowing to pick a date from past
|
||||
|
||||
Thank you for using Notesnook!
|
||||
32
apps/mobile/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "2.4.6",
|
||||
"version": "2.4.10",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "2.4.6",
|
||||
"version": "2.4.10",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
"native/",
|
||||
@@ -42,7 +42,7 @@
|
||||
"qclone": "^1.2.0",
|
||||
"react": "18.0.0",
|
||||
"react-native": "0.69.7",
|
||||
"react-native-actions-sheet": "^0.9.0-alpha.9",
|
||||
"react-native-actions-sheet": "0.9.0-alpha.16",
|
||||
"react-native-check-version": "https://github.com/flexible-agency/react-native-check-version",
|
||||
"react-native-drax": "^0.10.2",
|
||||
"react-native-image-zoom-viewer": "^3.0.1",
|
||||
@@ -100,6 +100,7 @@
|
||||
"react-native-keychain": "4.0.5",
|
||||
"react-native-mmkv-storage": "^0.8.0",
|
||||
"react-native-modal-datetime-picker": "14.0.0",
|
||||
"react-native-navigation-bar-color": "2.0.2",
|
||||
"react-native-notification-sounds": "0.5.5",
|
||||
"react-native-orientation": "https://github.com/yamill/react-native-orientation.git",
|
||||
"react-native-privacy-snapshot": "https://github.com/standardnotes/react-native-privacy-snapshot.git",
|
||||
@@ -17957,9 +17958,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-actions-sheet": {
|
||||
"version": "0.9.0-alpha.10",
|
||||
"resolved": "https://registry.npmjs.org/react-native-actions-sheet/-/react-native-actions-sheet-0.9.0-alpha.10.tgz",
|
||||
"integrity": "sha512-R1WcRPwtgaVwMOvDhVnYD+dxTWSSF84Qgco3UT71U8o3F59nx3J4IJBamt5e8RDBinLXPxgPvXEYYw9UO9VtpA==",
|
||||
"version": "0.9.0-alpha.16",
|
||||
"resolved": "https://registry.npmjs.org/react-native-actions-sheet/-/react-native-actions-sheet-0.9.0-alpha.16.tgz",
|
||||
"integrity": "sha512-/aR+MsJR1IgeZIg7elD2Zu/6In7Ztsvt4fPeOt2sM++FmsAofprsJ4JHjwPFEO06E5djS8ky8VvpaeCrC8HoFg==",
|
||||
"peerDependencies": {
|
||||
"react-native": "*",
|
||||
"react-native-gesture-handler": "*"
|
||||
@@ -18418,6 +18419,11 @@
|
||||
"react-native": ">=0.65.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-navigation-bar-color": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-navigation-bar-color/-/react-native-navigation-bar-color-2.0.2.tgz",
|
||||
"integrity": "sha512-ZmpLWRocyme1au11e5ZuecMS/UCi57nlzgnioi03Q6ERMbeUOqqbWgNBaNB7SsCeqBV6fZPjo3+A64zEIpzw4w=="
|
||||
},
|
||||
"node_modules/react-native-notification-sounds": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native-notification-sounds/-/react-native-notification-sounds-0.5.5.tgz",
|
||||
@@ -24312,7 +24318,7 @@
|
||||
"qclone": "^1.2.0",
|
||||
"react": "18.0.0",
|
||||
"react-native": "0.69.7",
|
||||
"react-native-actions-sheet": "^0.9.0-alpha.9",
|
||||
"react-native-actions-sheet": "0.9.0-alpha.16",
|
||||
"react-native-check-version": "https://github.com/flexible-agency/react-native-check-version",
|
||||
"react-native-drax": "^0.10.2",
|
||||
"react-native-image-zoom-viewer": "^3.0.1",
|
||||
@@ -24401,6 +24407,7 @@
|
||||
"react-native-keychain": "4.0.5",
|
||||
"react-native-mmkv-storage": "^0.8.0",
|
||||
"react-native-modal-datetime-picker": "14.0.0",
|
||||
"react-native-navigation-bar-color": "2.0.2",
|
||||
"react-native-notification-sounds": "0.5.5",
|
||||
"react-native-orientation": "https://github.com/yamill/react-native-orientation.git",
|
||||
"react-native-privacy-snapshot": "https://github.com/standardnotes/react-native-privacy-snapshot.git",
|
||||
@@ -34856,9 +34863,9 @@
|
||||
}
|
||||
},
|
||||
"react-native-actions-sheet": {
|
||||
"version": "0.9.0-alpha.10",
|
||||
"resolved": "https://registry.npmjs.org/react-native-actions-sheet/-/react-native-actions-sheet-0.9.0-alpha.10.tgz",
|
||||
"integrity": "sha512-R1WcRPwtgaVwMOvDhVnYD+dxTWSSF84Qgco3UT71U8o3F59nx3J4IJBamt5e8RDBinLXPxgPvXEYYw9UO9VtpA=="
|
||||
"version": "0.9.0-alpha.16",
|
||||
"resolved": "https://registry.npmjs.org/react-native-actions-sheet/-/react-native-actions-sheet-0.9.0-alpha.16.tgz",
|
||||
"integrity": "sha512-/aR+MsJR1IgeZIg7elD2Zu/6In7Ztsvt4fPeOt2sM++FmsAofprsJ4JHjwPFEO06E5djS8ky8VvpaeCrC8HoFg=="
|
||||
},
|
||||
"react-native-actions-shortcuts": {
|
||||
"version": "1.0.1",
|
||||
@@ -35172,6 +35179,11 @@
|
||||
"prop-types": "^15.7.2"
|
||||
}
|
||||
},
|
||||
"react-native-navigation-bar-color": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-navigation-bar-color/-/react-native-navigation-bar-color-2.0.2.tgz",
|
||||
"integrity": "sha512-ZmpLWRocyme1au11e5ZuecMS/UCi57nlzgnioi03Q6ERMbeUOqqbWgNBaNB7SsCeqBV6fZPjo3+A64zEIpzw4w=="
|
||||
},
|
||||
"react-native-notification-sounds": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/react-native-notification-sounds/-/react-native-notification-sounds-0.5.5.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "2.4.6",
|
||||
"version": "2.4.10",
|
||||
"private": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
@@ -36,4 +36,4 @@
|
||||
"react-native-fingerprint-scanner": "https://github.com/ammarahm-ed/react-native-fingerprint-scanner.git",
|
||||
"react-native-iap": "7.5.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,28 @@
|
||||
diff --git a/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js b/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
|
||||
index 4536402..5ceaf65 100644
|
||||
index 4536402..2a100d9 100644
|
||||
--- a/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
|
||||
+++ b/node_modules/react-native/Libraries/Components/Keyboard/KeyboardAvoidingView.js
|
||||
@@ -82,6 +82,7 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
|
||||
@@ -64,7 +64,7 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
|
||||
_subscriptions: Array<EventSubscription> = [];
|
||||
viewRef: {current: React.ElementRef<typeof View> | null, ...};
|
||||
_initialFrameHeight: number = 0;
|
||||
-
|
||||
+ keyboardShown = false;
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {bottom: 0};
|
||||
@@ -80,7 +80,9 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
|
||||
const keyboardY =
|
||||
keyboardFrame.screenY - (this.props.keyboardVerticalOffset ?? 0);
|
||||
|
||||
// Calculate the displacement needed for the view such that it
|
||||
- // Calculate the displacement needed for the view such that it
|
||||
+
|
||||
+ if (this._initialFrameHeight && frame.height < this._initialFrameHeight && this.keyboardShown) frame.height = this._initialFrameHeight;
|
||||
+ // Calculate the displacement needed for the view such that it
|
||||
// no longer overlaps with the keyboard
|
||||
+ if (this._initialFrameHeight && frame.height < this._initialFrameHeight) frame.height = this._initialFrameHeight;
|
||||
return Math.max(frame.y + frame.height - keyboardY, 0);
|
||||
}
|
||||
|
||||
@@ -92,7 +93,9 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
|
||||
@@ -92,7 +94,9 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
|
||||
|
||||
_onLayout = (event: ViewLayoutEvent) => {
|
||||
const wasFrameNull = this._frame == null;
|
||||
@@ -20,6 +32,19 @@ index 4536402..5ceaf65 100644
|
||||
if (!this._initialFrameHeight) {
|
||||
// save the initial frame height, before the keyboard is visible
|
||||
this._initialFrameHeight = this._frame.height;
|
||||
@@ -142,6 +146,8 @@ class KeyboardAvoidingView extends React.Component<Props, State> {
|
||||
this._subscriptions = [
|
||||
Keyboard.addListener('keyboardDidHide', this._onKeyboardChange),
|
||||
Keyboard.addListener('keyboardDidShow', this._onKeyboardChange),
|
||||
+ Keyboard.addListener('keyboardDidHide', () => this.keyboardShown = true),
|
||||
+ Keyboard.addListener('keyboardDidShow', () => this.keyboardShown = false),
|
||||
];
|
||||
}
|
||||
}
|
||||
diff --git a/node_modules/react-native/React/.DS_Store b/node_modules/react-native/React/.DS_Store
|
||||
new file mode 100644
|
||||
index 0000000..5155fbe
|
||||
Binary files /dev/null and b/node_modules/react-native/React/.DS_Store differ
|
||||
diff --git a/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m b/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m
|
||||
index f0f6402..d645d81 100644
|
||||
--- a/node_modules/react-native/React/Views/ScrollView/RCTScrollView.m
|
||||
@@ -44,6 +69,13 @@ index f0f6402..d645d81 100644
|
||||
CGFloat smallerOffset = 0.0;
|
||||
CGFloat largerOffset = maximumOffset;
|
||||
|
||||
diff --git a/node_modules/react-native/scripts/.packager.env b/node_modules/react-native/scripts/.packager.env
|
||||
new file mode 100644
|
||||
index 0000000..361f5fb
|
||||
--- /dev/null
|
||||
+++ b/node_modules/react-native/scripts/.packager.env
|
||||
@@ -0,0 +1 @@
|
||||
+export RCT_METRO_PORT=8081
|
||||
diff --git a/node_modules/react-native/scripts/packager.sh b/node_modules/react-native/scripts/packager.sh
|
||||
index b9f9016..8859130 100755
|
||||
--- a/node_modules/react-native/scripts/packager.sh
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
diff --git a/node_modules/react-native-actions-sheet/dist/src/index.js b/node_modules/react-native-actions-sheet/dist/src/index.js
|
||||
index 7eb4357..18bf26d 100644
|
||||
--- a/node_modules/react-native-actions-sheet/dist/src/index.js
|
||||
+++ b/node_modules/react-native-actions-sheet/dist/src/index.js
|
||||
@@ -747,7 +747,7 @@ var ActionSheet = /** @class */ (function (_super) {
|
||||
width: "100%"
|
||||
},
|
||||
]}>
|
||||
- {this.props.ExtraOverlayComponent}
|
||||
+
|
||||
|
||||
<FlatList testID={(_a = this.props.testIDs) === null || _a === void 0 ? void 0 : _a.scrollview} bounces={false} keyboardShouldPersistTaps={keyboardShouldPersistTaps} keyboardDismissMode={keyboardDismissMode} ref={this.scrollViewRef} scrollEventThrottle={16} overScrollMode="never" showsVerticalScrollIndicator={false} onMomentumScrollBegin={this._onScrollBegin} onScrollEndDrag={this._onScrollEnd} onMomentumScrollEnd={this._onScrollEnd} scrollEnabled={scrollable} onScrollBeginDrag={this._onScrollBeginDrag} onTouchEnd={this._onTouchEnd} onScroll={this._onScroll} scrollsToTop={false} style={[
|
||||
styles.scrollView,
|
||||
@@ -821,6 +821,7 @@ var ActionSheet = /** @class */ (function (_super) {
|
||||
</Animated.View>
|
||||
</View>);
|
||||
}}/>
|
||||
+ {this.props.ExtraOverlayComponent}
|
||||
</Animated.View>
|
||||
</Root>
|
||||
</>);
|
||||
3748
apps/mobile/patches/react-native-navigation-bar-color+2.0.2.patch
Normal file
4
apps/web/desktop/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "2.4.4",
|
||||
"version": "2.4.6",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "2.4.4",
|
||||
"version": "2.4.6",
|
||||
"dependencies": {
|
||||
"diary": "^0.3.1",
|
||||
"electron-updater": "^5.3.0",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@notesnook/desktop",
|
||||
"productName": "Notesnook",
|
||||
"description": "Your private note taking space",
|
||||
"version": "2.4.4",
|
||||
"version": "2.4.6",
|
||||
"appAppleId": "1544027013",
|
||||
"private": true,
|
||||
"main": "./build/electron.js",
|
||||
@@ -27,7 +27,7 @@
|
||||
"scripts": {
|
||||
"build": "esbuild ./electron.js ./preload.js --external:electron --external:fsevents --minify --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=false --define:RELEASE=true",
|
||||
"build:mas": "esbuild ./electron.js ./preload.js --minify --external:electron --external:fsevents --bundle --outdir=./build --platform=node --tsconfig=tsconfig.json --define:MAC_APP_STORE=true --define:RELEASE=true",
|
||||
"pack": "rm -rf ./build && cp -r ../build ./ && npm run build && yarn electron-builder --linux deb",
|
||||
"pack": "rm -rf ./build && cp -r ../build ./ && npm run build && yarn electron-builder --linux AppImage:x64 AppImage:arm64",
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"author": {
|
||||
@@ -101,8 +101,20 @@
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
"nsis",
|
||||
"portable"
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "portable",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"icon": "assets/icons/app.ico"
|
||||
},
|
||||
@@ -115,10 +127,20 @@
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
"AppImage",
|
||||
"deb",
|
||||
"rpm",
|
||||
"snap"
|
||||
{
|
||||
"target": "AppImage",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "snap",
|
||||
"arch": [
|
||||
"x64",
|
||||
"arm64"
|
||||
]
|
||||
}
|
||||
],
|
||||
"category": "Office",
|
||||
"icon": "assets/icons/app.icns",
|
||||
|
||||
@@ -22,12 +22,14 @@ import { AssetManager } from "./asset-manager";
|
||||
import { EVENTS } from "./events";
|
||||
import bringToFront from "./ipc/actions/bringToFront";
|
||||
import { sendMessageToRenderer } from "./ipc/utils";
|
||||
import { isFlatpak } from "./utils";
|
||||
|
||||
let tray: Tray | undefined = undefined;
|
||||
export function setupTray() {
|
||||
if (tray) tray.destroy();
|
||||
|
||||
const trayIconSize = process.platform === "win32" || process.platform === "darwin" ? 16 : 32;
|
||||
const trayIconSize =
|
||||
process.platform === "win32" || process.platform === "darwin" ? 16 : 32;
|
||||
|
||||
tray = new Tray(
|
||||
AssetManager.icon("tray-icon", {
|
||||
@@ -39,14 +41,18 @@ export function setupTray() {
|
||||
{
|
||||
label: "Show app",
|
||||
type: "normal",
|
||||
icon: AssetManager.icon("tray-icon", { size: trayIconSize }),
|
||||
icon: isFlatpak()
|
||||
? undefined
|
||||
: AssetManager.icon("tray-icon", { size: trayIconSize }),
|
||||
click: bringToFront
|
||||
},
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: "New note",
|
||||
type: "normal",
|
||||
icon: AssetManager.icon("note-add", { size: trayIconSize }),
|
||||
icon: isFlatpak()
|
||||
? undefined
|
||||
: AssetManager.icon("note-add", { size: trayIconSize }),
|
||||
click: () => {
|
||||
bringToFront();
|
||||
sendMessageToRenderer(EVENTS.createItem, { itemType: "note" });
|
||||
@@ -55,7 +61,9 @@ export function setupTray() {
|
||||
{
|
||||
label: "New notebook",
|
||||
type: "normal",
|
||||
icon: AssetManager.icon("notebook-add", { size: trayIconSize }),
|
||||
icon: isFlatpak()
|
||||
? undefined
|
||||
: AssetManager.icon("notebook-add", { size: trayIconSize }),
|
||||
click: () => {
|
||||
bringToFront();
|
||||
sendMessageToRenderer(EVENTS.createItem, { itemType: "notebook" });
|
||||
@@ -64,7 +72,9 @@ export function setupTray() {
|
||||
{ type: "separator" },
|
||||
{
|
||||
label: "Quit",
|
||||
icon: AssetManager.icon("quit", { size: trayIconSize }),
|
||||
icon: isFlatpak()
|
||||
? undefined
|
||||
: AssetManager.icon("quit", { size: trayIconSize }),
|
||||
type: "normal",
|
||||
click: () => {
|
||||
app.exit(0);
|
||||
|
||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { app } from "electron";
|
||||
import { join } from "path";
|
||||
import { statSync } from "fs";
|
||||
import { statSync, existsSync } from "fs";
|
||||
|
||||
const APP_ICON_PATH = join(
|
||||
__dirname,
|
||||
@@ -52,4 +52,8 @@ function getPath(filePath) {
|
||||
}
|
||||
}
|
||||
|
||||
export { getPath, isDevelopment, APP_ICON_PATH };
|
||||
function isFlatpak() {
|
||||
return existsSync("/.flatpak-info");
|
||||
}
|
||||
|
||||
export { getPath, isDevelopment, isFlatpak, APP_ICON_PATH };
|
||||
|
||||
34
apps/web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/web",
|
||||
"version": "2.4.4",
|
||||
"version": "2.4.6",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/web",
|
||||
"version": "2.4.4",
|
||||
"version": "2.4.6",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@aws-sdk/util-base64-browser": "^3.208.0",
|
||||
@@ -17,7 +17,7 @@
|
||||
"@emotion/react": "^11.10.0",
|
||||
"@mdi/js": "^6.9.96",
|
||||
"@mdi/react": "^1.6.0",
|
||||
"@notesnook-importer/core": "^1.6.0",
|
||||
"@notesnook-importer/core": "^1.7.1",
|
||||
"@notesnook/localforage-getitems": "^1.4.4",
|
||||
"@tanstack/react-virtual": "^3.0.0-beta.18",
|
||||
"@theme-ui/components": "^0.14.7",
|
||||
@@ -3352,14 +3352,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@notesnook-importer/core": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@notesnook-importer/core/-/core-1.6.0.tgz",
|
||||
"integrity": "sha512-lixZ+BFqCdgXgJeW6qQuwlpunX0CjaOmWKKFxCu7twxS/zIcKvh72tyiXKMQCDlpl0kIC8hCeuGR3FcdaUIjPg==",
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@notesnook-importer/core/-/core-1.7.1.tgz",
|
||||
"integrity": "sha512-KWwstLbmkCTpRZsjrNU8ZBzlSfta3sAznpaq0s9QM10wCuu4aHrSYARQYfXQWRvyznUeflzL81EvZHe/XGLRvQ==",
|
||||
"dependencies": {
|
||||
"@notesnook-importer/enex": "^1.6.0",
|
||||
"@notesnook-importer/onenote": "^1.6.0",
|
||||
"@notesnook-importer/storage": "^1.6.0",
|
||||
"@notesnook-importer/znel": "^1.6.0",
|
||||
"@notesnook-importer/enex": "*",
|
||||
"@notesnook-importer/onenote": "*",
|
||||
"@notesnook-importer/storage": "*",
|
||||
"@notesnook-importer/znel": "*",
|
||||
"@standardnotes/features": "1.58.0",
|
||||
"@standardnotes/models": "1.42.0",
|
||||
"@streamparser/json": "^0.0.10",
|
||||
@@ -20264,14 +20264,14 @@
|
||||
}
|
||||
},
|
||||
"@notesnook-importer/core": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@notesnook-importer/core/-/core-1.6.0.tgz",
|
||||
"integrity": "sha512-lixZ+BFqCdgXgJeW6qQuwlpunX0CjaOmWKKFxCu7twxS/zIcKvh72tyiXKMQCDlpl0kIC8hCeuGR3FcdaUIjPg==",
|
||||
"version": "1.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@notesnook-importer/core/-/core-1.7.1.tgz",
|
||||
"integrity": "sha512-KWwstLbmkCTpRZsjrNU8ZBzlSfta3sAznpaq0s9QM10wCuu4aHrSYARQYfXQWRvyznUeflzL81EvZHe/XGLRvQ==",
|
||||
"requires": {
|
||||
"@notesnook-importer/enex": "^1.6.0",
|
||||
"@notesnook-importer/onenote": "^1.6.0",
|
||||
"@notesnook-importer/storage": "^1.6.0",
|
||||
"@notesnook-importer/znel": "^1.6.0",
|
||||
"@notesnook-importer/enex": "*",
|
||||
"@notesnook-importer/onenote": "*",
|
||||
"@notesnook-importer/storage": "*",
|
||||
"@notesnook-importer/znel": "*",
|
||||
"@standardnotes/features": "1.58.0",
|
||||
"@standardnotes/models": "1.42.0",
|
||||
"@streamparser/json": "^0.0.10",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@notesnook/web",
|
||||
"description": "Your private note taking space",
|
||||
"version": "2.4.4",
|
||||
"version": "2.4.6",
|
||||
"private": true,
|
||||
"main": "./src/app.js",
|
||||
"homepage": "https://notesnook.com/",
|
||||
@@ -16,7 +16,7 @@
|
||||
"@emotion/react": "^11.10.0",
|
||||
"@mdi/js": "^6.9.96",
|
||||
"@mdi/react": "^1.6.0",
|
||||
"@notesnook-importer/core": "^1.6.0",
|
||||
"@notesnook-importer/core": "^1.7.1",
|
||||
"@notesnook/core": "*",
|
||||
"@notesnook/crypto": "*",
|
||||
"@notesnook/crypto-worker": "*",
|
||||
|
||||
4823
apps/web/patches/allotment+1.18.1.patch
Normal file
@@ -93,8 +93,6 @@ export default function AppEffects({ setShow }) {
|
||||
initEditorStore();
|
||||
|
||||
(async function () {
|
||||
await resetReminders();
|
||||
|
||||
await updateLastSynced();
|
||||
if (await initUser()) {
|
||||
showUpgradeReminderDialogs();
|
||||
|
||||
@@ -209,8 +209,9 @@ export function showError(title: string, message: string) {
|
||||
export function showMultiDeleteConfirmation(length: number) {
|
||||
return confirm({
|
||||
title: `Delete ${length} items?`,
|
||||
message:
|
||||
"These items will be **kept in your Trash for 7 days** after which they will be permanently deleted.",
|
||||
message: `These items will be **kept in your Trash for ${
|
||||
db.settings?.getTrashCleanupInterval() || 7
|
||||
} days** after which they will be permanently deleted.`,
|
||||
positiveButtonText: "Yes",
|
||||
negativeButtonText: "No"
|
||||
});
|
||||
@@ -470,9 +471,9 @@ export function showCreateTopicDialog() {
|
||||
}}
|
||||
onAction={async (topic: Record<string, unknown>) => {
|
||||
if (!topic) return;
|
||||
const notebookId = notebookStore.get().selectedNotebookId;
|
||||
await db.notebooks?.notebook(notebookId).topics.add(topic);
|
||||
notebookStore.setSelectedNotebook(notebookId);
|
||||
const notebook = notebookStore.get().selectedNotebook;
|
||||
await db.notebooks?.notebook(notebook.id).topics.add(topic);
|
||||
notebookStore.setSelectedNotebook(notebook.id);
|
||||
showToast("success", "Topic created!");
|
||||
perform(true);
|
||||
}}
|
||||
|
||||
@@ -92,22 +92,17 @@ const features: Record<FeatureKeys, Feature> = {
|
||||
: isDesktop()
|
||||
? [
|
||||
{
|
||||
title: "Smoother app loading animation",
|
||||
title: "Topics are now optional",
|
||||
subtitle:
|
||||
"Starting the app will now feel much, much smoother and less janky."
|
||||
"You can now move notes directly into a notebook without ever needing a topic (but topics are still there...just optional)."
|
||||
},
|
||||
{
|
||||
title: "Faster & less buggy outline list"
|
||||
title: "Adjustable trash cleanup interval",
|
||||
subtitle:
|
||||
"You can now customize after how long you want your trash to be automatically cleared. Just go into Settings -> Trash settings."
|
||||
},
|
||||
{
|
||||
title: "Sortable task lists",
|
||||
subtitle:
|
||||
"Task lists can now be sorted by their status. This'll move all completed tasks to the bottom."
|
||||
},
|
||||
{
|
||||
title: "Faster editor",
|
||||
subtitle:
|
||||
"Editor performance has been vastly improved in this version especially when you have a lot of images, task list etc."
|
||||
title: "Images are now much more stable"
|
||||
}
|
||||
]
|
||||
: [],
|
||||
|
||||
@@ -30,7 +30,6 @@ import { showToast } from "../../utils/toast";
|
||||
import { pluralize } from "../../utils/string";
|
||||
import { isMac } from "../../utils/platform";
|
||||
import create from "zustand";
|
||||
import { usePersistentState } from "../../hooks/use-persistent-state";
|
||||
|
||||
type MoveDialogProps = { onClose: Perform; noteIds: string[] };
|
||||
type NotebookReference = {
|
||||
@@ -45,7 +44,11 @@ type Item = {
|
||||
title: string;
|
||||
};
|
||||
type Topic = Item & { notebookId: string };
|
||||
type Notebook = Item & { topics: Topic[]; dateCreated: number };
|
||||
type Notebook = Item & {
|
||||
topics: Topic[];
|
||||
dateCreated: number;
|
||||
dateModified: number;
|
||||
};
|
||||
|
||||
interface ISelectionStore {
|
||||
selected: NotebookReference[];
|
||||
@@ -61,13 +64,9 @@ export const useSelectionStore = create<ISelectionStore>((set) => ({
|
||||
}));
|
||||
|
||||
function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
|
||||
const { selected, setIsMultiselect, isMultiselect, setSelected } =
|
||||
useSelectionStore();
|
||||
|
||||
const [suggestion, setSuggestion] = usePersistentState<NotebookReference[]>(
|
||||
"link-notebooks:suggestion",
|
||||
[]
|
||||
);
|
||||
const setSelected = useSelectionStore((store) => store.setSelected);
|
||||
const setIsMultiselect = useSelectionStore((store) => store.setIsMultiselect);
|
||||
const isMultiselect = useSelectionStore((store) => store.isMultiselect);
|
||||
|
||||
const refreshNotebooks = useStore((store) => store.refresh);
|
||||
const notebooks = useStore((store) => store.notebooks);
|
||||
@@ -109,7 +108,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
|
||||
}
|
||||
|
||||
setSelected(selected);
|
||||
setIsMultiselect(false);
|
||||
setIsMultiselect(selected.length > 1);
|
||||
}, [noteIds, notebooks, setSelected, setIsMultiselect]);
|
||||
|
||||
return (
|
||||
@@ -123,9 +122,8 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
|
||||
width={450}
|
||||
positiveButton={{
|
||||
text: "Done",
|
||||
disabled: !selected.length,
|
||||
onClick: async () => {
|
||||
setSuggestion(selected);
|
||||
const { selected } = useSelectionStore.getState();
|
||||
for (const item of selected) {
|
||||
try {
|
||||
if (item.op === "remove") {
|
||||
@@ -145,9 +143,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
|
||||
if (stringified) {
|
||||
showToast(
|
||||
"success",
|
||||
`${pluralize(noteIds.length, "note", "notes")} ${stringified
|
||||
.replace("Add", "added")
|
||||
.replace("remove", "removed")}`
|
||||
`${pluralize(noteIds.length, "note", "notes")} ${stringified}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -159,31 +155,27 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
|
||||
onClick: () => onClose(false)
|
||||
}}
|
||||
>
|
||||
{isMultiselect && (
|
||||
<Button
|
||||
variant="anchor"
|
||||
onClick={() => {
|
||||
const originalSelection: NotebookReference[] = useSelectionStore
|
||||
.getState()
|
||||
.selected.filter((a) => !a.new)
|
||||
.map((s) => ({ ...s, op: "add" }));
|
||||
setSelected(originalSelection);
|
||||
setIsMultiselect(originalSelection.length > 1);
|
||||
}}
|
||||
sx={{ textDecoration: "none", mt: 1 }}
|
||||
>
|
||||
Reset selection
|
||||
</Button>
|
||||
)}
|
||||
<Flex
|
||||
mt={1}
|
||||
sx={{ overflowY: "hidden", flexDirection: "column" }}
|
||||
data-test-id="notebook-list"
|
||||
>
|
||||
{suggestion.length > 0 && selected.length <= 0 && (
|
||||
<Flex
|
||||
sx={{
|
||||
bg: "shade",
|
||||
borderRadius: "default",
|
||||
p: 1,
|
||||
alignItems: "center",
|
||||
cursor: "pointer",
|
||||
":hover": {
|
||||
filter: "brightness(70%)"
|
||||
}
|
||||
}}
|
||||
onClick={() => setSelected(suggestion.slice())}
|
||||
>
|
||||
<Icon.Suggestion color="primary" size={16} sx={{ mr: 1 }} />
|
||||
<Text variant="body" sx={{ color: "primary" }}>
|
||||
{stringifySelected(suggestion)}
|
||||
</Text>
|
||||
</Flex>
|
||||
)}
|
||||
<FilteredTree
|
||||
placeholders={{
|
||||
empty: "Add a new notebook",
|
||||
@@ -200,6 +192,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
|
||||
}
|
||||
renderItem={(notebook, _index, refresh, isSearching) => (
|
||||
<NotebookItem
|
||||
key={notebook.id}
|
||||
notebook={notebook}
|
||||
isSearching={isSearching}
|
||||
onCreateItem={async (title) => {
|
||||
@@ -210,20 +203,6 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
|
||||
)}
|
||||
/>
|
||||
</Flex>
|
||||
{isMultiselect && (
|
||||
<Button
|
||||
variant="anchor"
|
||||
onClick={() => {
|
||||
setIsMultiselect(false);
|
||||
setSelected(
|
||||
selected.filter((a) => !a.new).map((s) => ({ ...s, op: "add" }))
|
||||
);
|
||||
}}
|
||||
sx={{ textDecoration: "none", mt: 1 }}
|
||||
>
|
||||
Reset selection
|
||||
</Button>
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -235,12 +214,11 @@ function NotebookItem(props: {
|
||||
}) {
|
||||
const { notebook, isSearching, onCreateItem } = props;
|
||||
|
||||
const { selected, setIsMultiselect, setSelected, isMultiselect } =
|
||||
useSelectionStore();
|
||||
|
||||
const [isCreatingNew, setIsCreatingNew] = useState(false);
|
||||
const index = findSelectionIndex(notebook, selected);
|
||||
const isSelected = index > -1;
|
||||
|
||||
const setIsMultiselect = useSelectionStore((store) => store.setIsMultiselect);
|
||||
const setSelected = useSelectionStore((store) => store.setSelected);
|
||||
const isMultiselect = useSelectionStore((store) => store.isMultiselect);
|
||||
|
||||
return (
|
||||
<Box as="li" data-test-id="notebook">
|
||||
@@ -273,6 +251,7 @@ function NotebookItem(props: {
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const { selected } = useSelectionStore.getState();
|
||||
|
||||
const isCtrlPressed = e.ctrlKey || e.metaKey;
|
||||
if (isCtrlPressed) setIsMultiselect(true);
|
||||
@@ -284,7 +263,7 @@ function NotebookItem(props: {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectedCheck size={20} selected={isSelected} />
|
||||
<SelectedCheck size={20} item={notebook} />
|
||||
<Text
|
||||
className="title"
|
||||
data-test-id="notebook-title"
|
||||
@@ -299,11 +278,12 @@ function NotebookItem(props: {
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex data-test-id="notebook-tools" sx={{ alignItems: "center" }}>
|
||||
<TopicSelectionIndicator notebook={notebook} />
|
||||
<Button
|
||||
variant="tool"
|
||||
className="create-topic"
|
||||
data-test-id="create-topic"
|
||||
sx={{ display: "none", p: 1, mr: 1 }}
|
||||
sx={{ display: "none", p: 1 }}
|
||||
>
|
||||
<Icon.Plus
|
||||
size={18}
|
||||
@@ -342,7 +322,7 @@ function NotebookItem(props: {
|
||||
p: "small"
|
||||
}}
|
||||
>
|
||||
<SelectedCheck selected={false} />
|
||||
<SelectedCheck />
|
||||
<Input
|
||||
variant="clean"
|
||||
data-test-id={`new-topic-input`}
|
||||
@@ -373,14 +353,23 @@ function NotebookItem(props: {
|
||||
);
|
||||
}
|
||||
|
||||
function TopicSelectionIndicator({ notebook }: { notebook: Notebook }) {
|
||||
const hasSelectedTopics = useSelectionStore(
|
||||
(store) =>
|
||||
store.selected.filter((nb) => nb.id === notebook.id && !!nb.topic)
|
||||
.length > 0
|
||||
);
|
||||
|
||||
if (!hasSelectedTopics) return null;
|
||||
return <Icon.Circle size={8} color="primary" sx={{ mr: 1 }} />;
|
||||
}
|
||||
|
||||
function TopicItem(props: { topic: Topic }) {
|
||||
const { topic } = props;
|
||||
|
||||
const { selected, setIsMultiselect, setSelected, isMultiselect } =
|
||||
useSelectionStore();
|
||||
|
||||
const index = findSelectionIndex(topic, selected);
|
||||
const selectedTopic = selected[index];
|
||||
const setSelected = useSelectionStore((store) => store.setSelected);
|
||||
const setIsMultiselect = useSelectionStore((store) => store.setIsMultiselect);
|
||||
const isMultiselect = useSelectionStore((store) => store.isMultiselect);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@@ -395,6 +384,8 @@ function TopicItem(props: { topic: Topic }) {
|
||||
":hover": { bg: "hover" }
|
||||
}}
|
||||
onClick={(e) => {
|
||||
const { selected } = useSelectionStore.getState();
|
||||
|
||||
const isCtrlPressed = e.ctrlKey || e.metaKey;
|
||||
if (isCtrlPressed) setIsMultiselect(true);
|
||||
|
||||
@@ -405,13 +396,7 @@ function TopicItem(props: { topic: Topic }) {
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectedCheck
|
||||
selected={
|
||||
selectedTopic?.op === "remove"
|
||||
? "remove"
|
||||
: selectedTopic?.op === "add"
|
||||
}
|
||||
/>
|
||||
<SelectedCheck item={topic} />
|
||||
<Text variant="body" sx={{ fontSize: "subtitle" }}>
|
||||
{topic.title}
|
||||
</Text>
|
||||
@@ -543,12 +528,18 @@ function FilteredTree<T extends Item>(props: FilteredTreeProps<T>) {
|
||||
}
|
||||
|
||||
function SelectedCheck({
|
||||
selected,
|
||||
item,
|
||||
size = 20
|
||||
}: {
|
||||
selected: boolean | null | "remove";
|
||||
item?: Topic | Notebook;
|
||||
size?: number;
|
||||
}) {
|
||||
const selectedItem = useSelectionStore(
|
||||
(store) => item && store.selected[findSelectionIndex(item, store.selected)]
|
||||
);
|
||||
const selected =
|
||||
selectedItem?.op === "remove" ? "remove" : selectedItem?.op === "add";
|
||||
|
||||
return selected === true ? (
|
||||
<Icon.CheckCircleOutline size={size} sx={{ mr: 1 }} color="primary" />
|
||||
) : selected === null ? (
|
||||
@@ -623,21 +614,23 @@ function selectSingle(topic: Topic | Notebook, array: NotebookReference[]) {
|
||||
|
||||
function stringifySelected(suggestion: NotebookReference[]) {
|
||||
const added = suggestion
|
||||
.filter((a) => a.op === "add")
|
||||
.map(resolve)
|
||||
.filter((a) => a.new && a.op === "add")
|
||||
.map(resolveReference)
|
||||
.filter(Boolean);
|
||||
const removed = suggestion
|
||||
.filter((a) => a.op === "remove")
|
||||
.map(resolve)
|
||||
.map(resolveReference)
|
||||
.filter(Boolean);
|
||||
if (!added.length && !removed.length) return;
|
||||
|
||||
const parts = ["Add to"];
|
||||
const parts = [];
|
||||
if (added.length > 0) parts.push("added to");
|
||||
if (added.length >= 1) parts.push(added[0]);
|
||||
if (added.length > 1) parts.push(`and ${added.length - 1} others`);
|
||||
|
||||
if (removed.length >= 1) {
|
||||
parts.push("& remove from");
|
||||
if (parts.length > 0) parts.push("&");
|
||||
parts.push("removed from");
|
||||
parts.push(removed[0]);
|
||||
}
|
||||
if (removed.length > 1) parts.push(`and ${removed.length - 1} others`);
|
||||
@@ -645,7 +638,7 @@ function stringifySelected(suggestion: NotebookReference[]) {
|
||||
return parts.join(" ") + ".";
|
||||
}
|
||||
|
||||
function resolve(ref: NotebookReference) {
|
||||
function resolveReference(ref: NotebookReference): string | undefined {
|
||||
const notebook = db.notebooks?.notebook(ref.id);
|
||||
if (!notebook) return undefined;
|
||||
|
||||
|
||||
@@ -714,8 +714,9 @@ export function getDisabledTools(tools: TreeNode[]) {
|
||||
for (const key in allTools) {
|
||||
const tool = allTools[key as ToolId];
|
||||
if (tool.conditional) continue;
|
||||
if (items.findIndex((t) => t.toolId === key) <= -1)
|
||||
disabled.push(key as ToolId);
|
||||
|
||||
const isDisabled = items.findIndex((t) => t.toolId === key);
|
||||
if (isDisabled === -1 && key !== "none") disabled.push(key as ToolId);
|
||||
}
|
||||
return disabled;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ function DiffViewer(props) {
|
||||
|
||||
const setIsEditorOpen = useAppStore((store) => store.setIsEditorOpen);
|
||||
const sync = useAppStore((store) => store.sync);
|
||||
const clearSession = useEditorStore((store) => store.clearSession);
|
||||
const openSession = useEditorStore((store) => store.openSession);
|
||||
const [conflictedNote, setConflictedNote] = useState();
|
||||
const [remoteContent, setRemoteContent] = useState();
|
||||
const [localContent, setLocalContent] = useState();
|
||||
@@ -98,7 +98,10 @@ function DiffViewer(props) {
|
||||
hashNavigate(`/notes/create`, { replace: true });
|
||||
return;
|
||||
}
|
||||
notesStore.setSelectedNote(noteId);
|
||||
|
||||
await openSession(noteId);
|
||||
setIsEditorOpen(true);
|
||||
|
||||
note = note.data;
|
||||
|
||||
const content = await db.content.raw(note.contentId);
|
||||
@@ -114,12 +117,7 @@ function DiffViewer(props) {
|
||||
|
||||
setHtmlDiff({ before: content.data, after: content.conflicted.data });
|
||||
})();
|
||||
}, [noteId, resolveConflict]);
|
||||
|
||||
useEffect(() => {
|
||||
clearSession(false);
|
||||
setIsEditorOpen(true);
|
||||
}, [setIsEditorOpen, clearSession]);
|
||||
}, [noteId, openSession, setIsEditorOpen, resolveConflict]);
|
||||
|
||||
if (!conflictedNote || !localContent || !remoteContent) return null;
|
||||
return (
|
||||
|
||||
@@ -110,25 +110,31 @@ export default function EditorManager({
|
||||
const isContent = item.type === "tiptap" && item.id === contentId;
|
||||
const isNote = item.type === "note" && item.id === id;
|
||||
|
||||
if (isContent) {
|
||||
if (isContent && editorInstance.current) {
|
||||
if (locked) {
|
||||
const result = await db.vault?.decryptContent(item).catch(() => {});
|
||||
if (result) item.data = result.data;
|
||||
else EV.publish(EVENTS.vaultLocked);
|
||||
}
|
||||
const oldHashes = editorInstance.current.getMediaHashes();
|
||||
|
||||
editorInstance.current?.updateContent(item.data as string);
|
||||
editorInstance.current.updateContent(item.data as string);
|
||||
|
||||
const newHashes = editorInstance.current.getMediaHashes();
|
||||
const hashesToLoad = newHashes.filter(
|
||||
(hash, index) => hash !== oldHashes[index]
|
||||
);
|
||||
|
||||
if (appstore.get().isSyncing()) {
|
||||
db.eventManager.subscribe(
|
||||
EVENTS.syncCompleted,
|
||||
async () => {
|
||||
await db.attachments?.downloadMedia(id);
|
||||
await db.attachments?.downloadMedia(id, hashesToLoad);
|
||||
},
|
||||
true
|
||||
);
|
||||
} else {
|
||||
await db.attachments?.downloadMedia(id);
|
||||
await db.attachments?.downloadMedia(id, hashesToLoad);
|
||||
}
|
||||
} else if (isNote) {
|
||||
if (!locked && item.locked) return EV.publish(EVENTS.vaultLocked);
|
||||
|
||||
@@ -52,7 +52,7 @@ import { getCurrentPreset } from "../../common/toolbar-config";
|
||||
import { useIsUserPremium } from "../../hooks/use-is-user-premium";
|
||||
import { showBuyDialog } from "../../common/dialog-controller";
|
||||
import { useStore as useSettingsStore } from "../../stores/setting-store";
|
||||
import { debounceWithId, inlineDebounce } from "../../utils/debounce";
|
||||
import { debounce, debounceWithId } from "../../utils/debounce";
|
||||
import { store as editorstore } from "../../stores/editor-store";
|
||||
|
||||
type TipTapProps = {
|
||||
@@ -214,38 +214,32 @@ function TipTap(props: TipTapProps) {
|
||||
canUndo: editor.can().undo()
|
||||
});
|
||||
},
|
||||
onSelectionUpdate: ({ editor, transaction }) => {
|
||||
inlineDebounce(
|
||||
"tiptap:onSelectionUpdate",
|
||||
() => {
|
||||
const isEmptySelection = transaction.selection.empty;
|
||||
configure((old) => {
|
||||
const oldSelected = old.statistics?.words?.selected;
|
||||
const oldWords = old.statistics?.words.total || 0;
|
||||
if (isEmptySelection)
|
||||
return oldSelected
|
||||
? {
|
||||
statistics: { words: { total: oldWords, selected: 0 } }
|
||||
}
|
||||
: old;
|
||||
|
||||
const selectedWords = getSelectedWords(
|
||||
editor as Editor,
|
||||
transaction.selection
|
||||
);
|
||||
return {
|
||||
statistics: {
|
||||
words: {
|
||||
total: oldWords,
|
||||
selected: selectedWords
|
||||
}
|
||||
onSelectionUpdate: debounce(({ editor, transaction }) => {
|
||||
const isEmptySelection = transaction.selection.empty;
|
||||
configure((old) => {
|
||||
const oldSelected = old.statistics?.words?.selected;
|
||||
const oldWords = old.statistics?.words.total || 0;
|
||||
if (isEmptySelection)
|
||||
return oldSelected
|
||||
? {
|
||||
statistics: { words: { total: oldWords, selected: 0 } }
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
500
|
||||
);
|
||||
},
|
||||
: old;
|
||||
|
||||
const selectedWords = getSelectedWords(
|
||||
editor as Editor,
|
||||
transaction.selection
|
||||
);
|
||||
return {
|
||||
statistics: {
|
||||
words: {
|
||||
total: oldWords,
|
||||
selected: selectedWords
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
}, 500),
|
||||
theme,
|
||||
onOpenAttachmentPicker: (_editor, type) => {
|
||||
onInsertAttachment?.(type);
|
||||
@@ -373,6 +367,14 @@ function toIEditor(editor: Editor): IEditor {
|
||||
focus: () => editor.current?.commands.focus("start"),
|
||||
undo: () => editor.current?.commands.undo(),
|
||||
redo: () => editor.current?.commands.redo(),
|
||||
getMediaHashes: () => {
|
||||
if (!editor.current) return [];
|
||||
const hashes: string[] = [];
|
||||
editor.current.state.doc.descendants((n) => {
|
||||
if (typeof n.attrs.hash === "string") hashes.push(n.attrs.hash);
|
||||
});
|
||||
return hashes;
|
||||
},
|
||||
updateContent: (content) => {
|
||||
const { from, to } = editor.state.selection;
|
||||
editor.current
|
||||
@@ -390,15 +392,18 @@ function toIEditor(editor: Editor): IEditor {
|
||||
},
|
||||
attachFile: (file: Attachment) => {
|
||||
if (file.dataurl) {
|
||||
editor.current?.commands.insertImage({ ...file, src: file.dataurl });
|
||||
editor.current?.commands.insertImage({
|
||||
...file,
|
||||
dataurl: file.dataurl
|
||||
});
|
||||
} else editor.current?.commands.insertAttachment(file);
|
||||
},
|
||||
loadWebClip: (hash, src) =>
|
||||
editor.current?.commands.updateWebClip({ hash }, { src }),
|
||||
loadImage: (hash, src) =>
|
||||
loadImage: (hash, dataurl) =>
|
||||
editor.current?.commands.updateImage(
|
||||
{ hash },
|
||||
{ hash, src, preventUpdate: true }
|
||||
{ hash, dataurl, preventUpdate: true }
|
||||
),
|
||||
sendAttachmentProgress: (hash, type, progress) =>
|
||||
editor.current?.commands.setAttachmentProgress({
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface IEditor {
|
||||
focus: () => void;
|
||||
undo: () => void;
|
||||
redo: () => void;
|
||||
getMediaHashes: () => string[];
|
||||
updateContent: (content: string) => void;
|
||||
attachFile: (file: Attachment) => void;
|
||||
loadWebClip: (hash: string, html: string) => void;
|
||||
|
||||
@@ -20,8 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import * as Icon from "../icons";
|
||||
import "./search.css";
|
||||
import Field from "../field";
|
||||
|
||||
function SearchBox(props) {
|
||||
import { debounce } from "../../utils/debounce";
|
||||
function SearchBox({ onSearch }) {
|
||||
return (
|
||||
<Field
|
||||
data-test-id="search-input"
|
||||
@@ -29,18 +29,16 @@ function SearchBox(props) {
|
||||
id="search"
|
||||
name="search"
|
||||
type="text"
|
||||
sx={{m: 0,mx: 1,mt: 1}}
|
||||
sx={{ m: 0, mx: 1, mt: 1 }}
|
||||
placeholder="Type your query here"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") props.onSearch(e.target.value);
|
||||
}}
|
||||
onChange={debounce((e) => onSearch(e.target.value), 250)}
|
||||
action={{
|
||||
icon: Icon.Search,
|
||||
testId: "search-button",
|
||||
onClick: () => {
|
||||
const searchField = document.getElementById("search");
|
||||
if (searchField && searchField.value && searchField.value.length) {
|
||||
props.onSearch(searchField.value);
|
||||
onSearch(searchField.value);
|
||||
}
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -23,20 +23,25 @@ import * as Icon from "../icons";
|
||||
import { db } from "../../common/db";
|
||||
import { useStore as useEditorStore } from "../../stores/editor-store";
|
||||
import { useStore as useAppStore } from "../../stores/app-store";
|
||||
import { useStore as useNoteStore } from "../../stores/note-store";
|
||||
import Field from "../field";
|
||||
import { showToast } from "../../utils/toast";
|
||||
|
||||
function Unlock(props) {
|
||||
const { noteId } = props;
|
||||
const note = useMemo(() => db.notes.note(noteId)?.data, [noteId]);
|
||||
const passwordRef = useRef();
|
||||
|
||||
const [isWrong, setIsWrong] = useState(false);
|
||||
const [isUnlocking, setIsUnlocking] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const passwordRef = useRef();
|
||||
|
||||
const note = useMemo(
|
||||
() => !isLoading && db.notes.note(noteId)?.data,
|
||||
[noteId, isLoading]
|
||||
);
|
||||
const openLockedSession = useEditorStore((store) => store.openLockedSession);
|
||||
const clearSession = useEditorStore((store) => store.clearSession);
|
||||
const openSession = useEditorStore((store) => store.openSession);
|
||||
const setIsEditorOpen = useAppStore((store) => store.setIsEditorOpen);
|
||||
const setSelectedNote = useNoteStore((store) => store.setSelectedNote);
|
||||
|
||||
const submit = useCallback(async () => {
|
||||
setIsUnlocking(true);
|
||||
@@ -58,10 +63,15 @@ function Unlock(props) {
|
||||
}, [setIsWrong, noteId, openLockedSession]);
|
||||
|
||||
useEffect(() => {
|
||||
clearSession(false);
|
||||
setIsEditorOpen(true);
|
||||
setTimeout(() => setSelectedNote(noteId));
|
||||
}, [clearSession, setIsEditorOpen, setSelectedNote, noteId]);
|
||||
(async () => {
|
||||
setIsLoading(true);
|
||||
|
||||
await openSession(noteId);
|
||||
setIsEditorOpen(true);
|
||||
|
||||
setIsLoading(false);
|
||||
})();
|
||||
}, [openSession, setIsEditorOpen, noteId]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
|
||||
1
apps/web/src/global.d.ts
vendored
@@ -32,6 +32,7 @@ type DesktopIntegrationSettings = {
|
||||
};
|
||||
|
||||
declare interface Window {
|
||||
os: NodeJS.Platform | "mas";
|
||||
config: {
|
||||
static spellChecker(): Promise<SpellCheckerOptions>;
|
||||
static desktopIntegration(): Promise<DesktopIntegrationSettings>;
|
||||
|
||||
@@ -29,6 +29,7 @@ import { sendAttachmentsProgressEvent } from "@notesnook/core/common";
|
||||
import { saveAs } from "file-saver";
|
||||
import { showToast } from "../utils/toast";
|
||||
import { db } from "../common/db";
|
||||
import { getFileNameWithExtension } from "@notesnook/core/utils/filename";
|
||||
|
||||
const ABYTES = 17;
|
||||
const CHUNK_SIZE = 512 * 1024;
|
||||
@@ -97,18 +98,19 @@ async function writeEncryptedFile(file, key, hash) {
|
||||
|
||||
/**
|
||||
* We perform 4 steps here:
|
||||
* 1. We convert base64 to Uint8Array (if we get base64, that is)
|
||||
* 1. We convert base64 to Uint8Array
|
||||
* 2. We hash the Uint8Array.
|
||||
* 3. We encrypt the Uint8Array
|
||||
* 4. We save the encrypted Uint8Array
|
||||
*/
|
||||
async function writeEncrypted(filename, { data, type, key, mimeType }) {
|
||||
if (type === "base64") data = new Uint8Array(Buffer.from(data, "base64"));
|
||||
async function writeEncryptedBase64(metadata) {
|
||||
const { data, key, mimeType } = metadata;
|
||||
|
||||
const { hash, type: hashType } = await hashBuffer(data);
|
||||
if (!filename) filename = hash;
|
||||
const bytes = new Uint8Array(Buffer.from(data, "base64"));
|
||||
|
||||
const file = new File([data.buffer], filename, {
|
||||
const { hash, type: hashType } = await hashBuffer(bytes);
|
||||
|
||||
const file = new File([bytes.buffer], hash, {
|
||||
type: mimeType || "application/octet-stream"
|
||||
});
|
||||
|
||||
@@ -120,6 +122,15 @@ async function writeEncrypted(filename, { data, type, key, mimeType }) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} data the base64 data
|
||||
* @returns
|
||||
*/
|
||||
function hashBase64(data) {
|
||||
return hashBuffer(Buffer.from(data, "base64"));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {import("hash-wasm/dist/lib/util").IDataType} data
|
||||
@@ -413,7 +424,8 @@ async function saveFile(filename, fileMetadata) {
|
||||
},
|
||||
filename
|
||||
);
|
||||
saveAs(new Blob(blobParts, { type }), name);
|
||||
|
||||
saveAs(new Blob(blobParts, { type }), getFileNameWithExtension(name, type));
|
||||
|
||||
if (isUploaded) await streamablefs.deleteFile(filename);
|
||||
}
|
||||
@@ -459,18 +471,20 @@ function clearFileStorage() {
|
||||
}
|
||||
|
||||
const FS = {
|
||||
writeEncrypted,
|
||||
writeEncryptedBase64,
|
||||
readEncrypted,
|
||||
uploadFile: cancellable(uploadFile),
|
||||
downloadFile: cancellable(downloadFile),
|
||||
deleteFile,
|
||||
saveFile,
|
||||
exists,
|
||||
hashBuffer,
|
||||
hashStream,
|
||||
writeEncryptedFile,
|
||||
clearFileStorage,
|
||||
getUploadedFileSize
|
||||
getUploadedFileSize,
|
||||
|
||||
hashBase64,
|
||||
hashBuffer,
|
||||
hashStream
|
||||
};
|
||||
export default FS;
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ const routes = {
|
||||
const notebook = db.notebooks.notebook(notebookId);
|
||||
const topic = notebook?.topics?.topic(topicId)?._topic;
|
||||
if (!topic) return false;
|
||||
nbstore.setSelectedNotebook(notebookId);
|
||||
notestore.setContext({
|
||||
type: "topic",
|
||||
value: { id: notebookId, topic: topicId }
|
||||
|
||||
@@ -48,7 +48,11 @@ class AppStore extends BaseStore {
|
||||
isSyncEnabled = Config.get("syncEnabled", true);
|
||||
isRealtimeSyncEnabled = Config.get("isRealtimeSyncEnabled", true);
|
||||
syncStatus = {
|
||||
key: navigator.onLine ? "synced" : "offline",
|
||||
key: navigator.onLine
|
||||
? Config.get("syncEnabled", true)
|
||||
? "synced"
|
||||
: "disabled"
|
||||
: "offline",
|
||||
progress: null,
|
||||
type: null
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ import { EV, EVENTS } from "@notesnook/core/common";
|
||||
import { hashNavigate } from "../navigation";
|
||||
import { logger } from "../utils/logger";
|
||||
import Config from "../utils/config";
|
||||
import { setDocumentTitle } from "../utils/dom";
|
||||
|
||||
const SESSION_STATES = {
|
||||
stale: "stale",
|
||||
@@ -130,6 +131,7 @@ class EditorStore extends BaseStore {
|
||||
if (!note) return;
|
||||
|
||||
noteStore.setSelectedNote(note.id);
|
||||
setDocumentTitle(note.title);
|
||||
|
||||
if (note.locked)
|
||||
return hashNavigate(`/notes/${noteId}/unlock`, { replace: true });
|
||||
@@ -222,6 +224,7 @@ class EditorStore extends BaseStore {
|
||||
state.session.dateEdited = note.dateEdited;
|
||||
state.session.attachmentsLength = attachments.length;
|
||||
});
|
||||
setDocumentTitle(note.title);
|
||||
|
||||
this.setSaveState(1);
|
||||
} catch (err) {
|
||||
@@ -248,6 +251,7 @@ class EditorStore extends BaseStore {
|
||||
});
|
||||
noteStore.setSelectedNote(0);
|
||||
appStore.setIsEditorOpen(true);
|
||||
setDocumentTitle();
|
||||
};
|
||||
|
||||
clearSession = async (shouldNavigate = true) => {
|
||||
@@ -265,6 +269,7 @@ class EditorStore extends BaseStore {
|
||||
if (shouldNavigate)
|
||||
hashNavigate(`/notes/create`, { replace: true, addNonce: true });
|
||||
appStore.setIsEditorOpen(false);
|
||||
setDocumentTitle();
|
||||
};
|
||||
|
||||
setTitle = (noteId, title) => {
|
||||
|
||||
@@ -17,21 +17,22 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
function hexToRGB(hex, alpha = 1) {
|
||||
let parseString = hex;
|
||||
if (hex.startsWith("#")) {
|
||||
parseString = hex.slice(1, 7);
|
||||
function hexToRGB(hex:string, alpha = 1) {
|
||||
let parseString = hex;
|
||||
if (hex.startsWith("#")) {
|
||||
parseString = hex.slice(1, 7);
|
||||
}
|
||||
if (parseString.length !== 6) {
|
||||
return null;
|
||||
}
|
||||
const r = parseInt(parseString.slice(0, 2), 16);
|
||||
const g = parseInt(parseString.slice(2, 4), 16);
|
||||
const b = parseInt(parseString.slice(4, 6), 16);
|
||||
if (isNaN(r) || isNaN(g) || isNaN(b)) {
|
||||
return null;
|
||||
}
|
||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||||
}
|
||||
if (parseString.length !== 6) {
|
||||
return null;
|
||||
}
|
||||
const r = parseInt(parseString.slice(0, 2), 16);
|
||||
const g = parseInt(parseString.slice(2, 4), 16);
|
||||
const b = parseInt(parseString.slice(4, 6), 16);
|
||||
if (isNaN(r) || isNaN(g) || isNaN(b)) {
|
||||
return null;
|
||||
}
|
||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||||
}
|
||||
|
||||
export { hexToRGB };
|
||||
|
||||
export { hexToRGB };
|
||||
|
||||
@@ -26,8 +26,7 @@ function set<T>(key: string, value: T) {
|
||||
function get<T>(key: string, def?: T): T {
|
||||
const value = window.localStorage.getItem(key);
|
||||
if (!value && def !== undefined) return def;
|
||||
|
||||
return tryParse(value);
|
||||
return value ? tryParse(value) : def;
|
||||
}
|
||||
|
||||
function clear() {
|
||||
|
||||
@@ -17,14 +17,16 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export function removeCss(id) {
|
||||
var link = document.getElementById(id);
|
||||
link.remove();
|
||||
export function removeCss(id: string) {
|
||||
const link = document.getElementById(id);
|
||||
if(link){
|
||||
link.remove();
|
||||
}
|
||||
}
|
||||
|
||||
export function injectCssSrc(id, src) {
|
||||
var head = document.head;
|
||||
var link = document.createElement("link");
|
||||
export function injectCssSrc(id: string, src: string) {
|
||||
const head = document.head;
|
||||
const link = document.createElement("link");
|
||||
|
||||
link.id = id;
|
||||
link.type = "text/css";
|
||||
@@ -34,32 +36,30 @@ export function injectCssSrc(id, src) {
|
||||
head.appendChild(link);
|
||||
}
|
||||
|
||||
export function injectCss(rule) {
|
||||
let variableCss = document.getElementById("variables");
|
||||
let head = document.getElementsByTagName("head")[0];
|
||||
export function injectCss(rule: string) {
|
||||
const variableCss = document.getElementById("variables");
|
||||
const head = document.getElementsByTagName("head")[0];
|
||||
if (variableCss) {
|
||||
head.removeChild(variableCss);
|
||||
}
|
||||
let css = document.createElement("style");
|
||||
const css = document.createElement("style");
|
||||
css.type = "text/css";
|
||||
css.id = "variables";
|
||||
// Support for IE
|
||||
if (css.styleSheet) css.styleSheet.cssText = rule;
|
||||
// Support for the rest
|
||||
else css.appendChild(document.createTextNode(rule));
|
||||
css.appendChild(document.createTextNode(rule));
|
||||
head.insertBefore(css, getRootStylesheet());
|
||||
}
|
||||
|
||||
function getRootStylesheet() {
|
||||
for (let sty of document.getElementsByTagName("style")) {
|
||||
for (const sty of document.getElementsByTagName("style")) {
|
||||
if (sty.innerHTML.includes("#root")) {
|
||||
return sty;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function changeSvgTheme(newAccent) {
|
||||
var nodes = document.querySelectorAll('*[fill="#0560ff"]');
|
||||
for (var n = 0; n < nodes.length; ++n)
|
||||
export function changeSvgTheme(newAccent: string) {
|
||||
const nodes = document.querySelectorAll('*[fill="#0560ff"]');
|
||||
for (let n = 0; n < nodes.length; ++n)
|
||||
nodes[n].setAttribute("fill", newAccent);
|
||||
}
|
||||
@@ -16,37 +16,46 @@ GNU General Public License for more details.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
interface DebouncedFunction<
|
||||
Args extends any[],
|
||||
F extends (...args: Args) => any
|
||||
> {
|
||||
(this: ThisParameterType<F>, ...args: Args & Parameters<F>): void;
|
||||
}
|
||||
|
||||
export function debounce<F extends (...args: never[]) => void>(
|
||||
interface DebouncedFunctionWithId<
|
||||
Args extends any[],
|
||||
F extends (...args: Args) => any
|
||||
> {
|
||||
(this: ThisParameterType<F>, id: string, ...args: Args & Parameters<F>): void;
|
||||
}
|
||||
|
||||
export function debounce<Args extends any[], F extends (...args: Args) => void>(
|
||||
func: F,
|
||||
waitFor: number
|
||||
) {
|
||||
): DebouncedFunction<Args, F> {
|
||||
let timeout: number | null;
|
||||
|
||||
const debounced = (...args: Parameters<F>) => {
|
||||
return (...args: Args) => {
|
||||
if (timeout) clearTimeout(timeout);
|
||||
timeout = setTimeout(() => func(...args), waitFor) as unknown as number;
|
||||
};
|
||||
|
||||
return debounced;
|
||||
}
|
||||
|
||||
export function debounceWithId<F extends (...args: never[]) => void>(
|
||||
func: F,
|
||||
waitFor: number
|
||||
) {
|
||||
export function debounceWithId<
|
||||
Args extends any[],
|
||||
F extends (...args: Args) => void
|
||||
>(func: F, waitFor: number): DebouncedFunctionWithId<Args, F> {
|
||||
let timeout: number | null;
|
||||
let debounceId: string | null = null;
|
||||
|
||||
const debounced = (id: string, ...args: Parameters<F>) => {
|
||||
return (id: string, ...args: Parameters<F>) => {
|
||||
if (timeout && id === debounceId) clearTimeout(timeout);
|
||||
debounceId = id;
|
||||
timeout = setTimeout(() => {
|
||||
func(...args);
|
||||
}, waitFor) as unknown as number;
|
||||
};
|
||||
|
||||
return debounced;
|
||||
}
|
||||
|
||||
const DEBOUNCE_TIMEOUTS: Record<string, NodeJS.Timeout> = {};
|
||||
|
||||
@@ -17,22 +17,13 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
function isMobile() {
|
||||
var match = window.matchMedia || window.msMatchMedia;
|
||||
if (match) {
|
||||
var mq = match("(max-width: 480px)");
|
||||
return mq.matches;
|
||||
}
|
||||
return false;
|
||||
function isMobile(): boolean {
|
||||
return window.matchMedia("(max-width: 480px)").matches;
|
||||
}
|
||||
|
||||
function isTablet() {
|
||||
var match = window.matchMedia || window.msMatchMedia;
|
||||
if (match) {
|
||||
var mq = match("(min-width: 480px) and (max-width: 1000px)");
|
||||
return mq.matches;
|
||||
}
|
||||
return false;
|
||||
function isTablet(): boolean {
|
||||
return window.matchMedia("(min-width: 480px) and (max-width: 1000px)")
|
||||
.matches;
|
||||
}
|
||||
|
||||
export { isMobile, isTablet };
|
||||
23
apps/web/src/utils/dom.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export const setDocumentTitle = (title?: string) => {
|
||||
if (!title) document.title = "Notesnook";
|
||||
else document.title = `${title} - Notesnook`;
|
||||
};
|
||||
@@ -44,18 +44,18 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* @param {Object} options {replacement: String | Function }
|
||||
* @return {String} Sanitized filename
|
||||
*/
|
||||
var illegalRe = /[/?<>\\:*|"]/g;
|
||||
const illegalRe = /[/?<>\\:*|"]/g;
|
||||
// var controlRe = /[x00-x1f\x80-\x9f]/g;
|
||||
var reservedRe = /^\.+$/;
|
||||
var windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
|
||||
var windowsTrailingRe = /[. ]+$/;
|
||||
var whitespace = /\s+/g;
|
||||
const reservedRe = /^\.+$/;
|
||||
const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
|
||||
const windowsTrailingRe = /[. ]+$/;
|
||||
const whitespace = /\s+/g;
|
||||
|
||||
function sanitize(input, replacement) {
|
||||
function sanitize(input: string, replacement: string) {
|
||||
if (typeof input !== "string") {
|
||||
throw new Error("Input must be string");
|
||||
}
|
||||
var sanitized = input
|
||||
const sanitized = input
|
||||
.replace(whitespace, replacement)
|
||||
//.replace(controlRe, replacement)
|
||||
.replace(illegalRe, replacement)
|
||||
@@ -66,12 +66,12 @@ function sanitize(input, replacement) {
|
||||
return sanitized.slice(0, 254).toLowerCase();
|
||||
}
|
||||
|
||||
export function sanitizeFilename(input) {
|
||||
var replacement = "-";
|
||||
export function sanitizeFilename(input: string) {
|
||||
const replacement = "-";
|
||||
return sanitize(input, replacement);
|
||||
}
|
||||
|
||||
export function formatBytes(bytes, decimals = 2) {
|
||||
export function formatBytes(bytes: number, decimals = 2) {
|
||||
if (bytes === 0) return "0 B";
|
||||
|
||||
const k = 1024;
|
||||
@@ -83,7 +83,7 @@ export function formatBytes(bytes, decimals = 2) {
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
|
||||
}
|
||||
|
||||
export function truncateFilename(filename) {
|
||||
export function truncateFilename(filename: string) {
|
||||
const MAX_LENGTH = 38;
|
||||
if (filename.length > MAX_LENGTH) {
|
||||
return (
|
||||
@@ -101,8 +101,13 @@ async function importNote(note: Note) {
|
||||
note.notebooks = [];
|
||||
const noteId = await db.notes?.add(note);
|
||||
|
||||
for (const notebook of notebooks) {
|
||||
await db.notes?.addToNotebook(await importNotebook(notebook), noteId);
|
||||
for (const nb of notebooks) {
|
||||
const notebook = await importNotebook(nb);
|
||||
if (!notebook.id) continue;
|
||||
await db.notes?.addToNotebook(
|
||||
{ id: notebook.id, topic: notebook.topic },
|
||||
noteId
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +116,9 @@ async function fileToJson<T>(file: Entry) {
|
||||
return JSON.parse(text) as T;
|
||||
}
|
||||
|
||||
async function importNotebook(notebook: Notebook) {
|
||||
async function importNotebook(
|
||||
notebook: Notebook
|
||||
): Promise<{ id?: string; topic?: string }> {
|
||||
let nb = db.notebooks?.all.find((nb) => nb.title === notebook.notebook);
|
||||
if (!nb) {
|
||||
const nbId = await db.notebooks?.add({
|
||||
@@ -122,5 +129,5 @@ async function importNotebook(notebook: Notebook) {
|
||||
}
|
||||
const topic = nb?.topics.find((t: any) => t.title === notebook.topic);
|
||||
|
||||
return { id: nb.id, topic: topic.id };
|
||||
return { id: nb?.id, topic: topic?.id };
|
||||
}
|
||||
|
||||
@@ -19,17 +19,16 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import EventManager from "@notesnook/core/utils/event-manager";
|
||||
|
||||
const GlobalKeyboard = {};
|
||||
|
||||
const KeyboardEventManager = new EventManager();
|
||||
|
||||
GlobalKeyboard.addEventListener = (name, handler) => {
|
||||
KeyboardEventManager.subscribe(name, handler);
|
||||
const GlobalKeyboard = {
|
||||
addEventListener: (name: string, handler: (...args: any[]) => void) => {
|
||||
KeyboardEventManager.subscribe(name, handler);
|
||||
},
|
||||
removeEventListener: (name: string, handler: (...args: any[]) => void) =>
|
||||
KeyboardEventManager.unsubscribe(name, handler)
|
||||
};
|
||||
|
||||
GlobalKeyboard.removeEventListener = (name, handler) =>
|
||||
KeyboardEventManager.unsubscribe(name, handler);
|
||||
|
||||
// window.addEventListener("keydown", (e) => {
|
||||
// // KeyboardEventManager.publish("keydown", e);
|
||||
// });
|
||||
@@ -17,6 +17,9 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export function objectMap(obj, fn) {
|
||||
export function objectMap<T, U>(
|
||||
obj: Record<string, T>,
|
||||
fn: (k: string, v: T, i: number) => U
|
||||
) {
|
||||
return Object.entries(obj).map(([k, v], i) => fn(k, v, i));
|
||||
}
|
||||