mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-09-01 03:29:21 +02:00
Compare commits
3 Commits
2.5.0-andr
...
feat-slash
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aaa28b5d24 | ||
|
|
6a86a0e088 | ||
|
|
40c0b527c9 |
@@ -20,7 +20,6 @@ const SCOPES = [
|
||||
"logger",
|
||||
"theme",
|
||||
"core",
|
||||
"fs",
|
||||
"clipper",
|
||||
"config",
|
||||
"ci",
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Platform } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import {
|
||||
decrypt,
|
||||
deriveCryptoKey,
|
||||
|
||||
@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import { Dimensions, Platform } from "react-native";
|
||||
import ImageResizer from "@bam.tech/react-native-image-resizer";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
/**
|
||||
* Scale down & compress images to screen width
|
||||
* for loading in editor.
|
||||
|
||||
@@ -20,14 +20,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import {
|
||||
getFileNameWithExtension,
|
||||
isImage,
|
||||
isDocument
|
||||
isImage
|
||||
} from "@notesnook/core/utils/filename";
|
||||
import React from "react";
|
||||
import { Platform } from "react-native";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import { subscribe, zip } from "react-native-zip-archive";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import { ShareComponent } from "../../components/sheets/export-notes/share";
|
||||
import { ToastEvent, presentSheet } from "../../services/event-manager";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
@@ -246,11 +245,7 @@ export default async function downloadAttachment(
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
attachment.dateUploaded &&
|
||||
!isImage(attachment.metadata?.type) &&
|
||||
!isDocument(attachment.metadata?.type)
|
||||
) {
|
||||
if (attachment.dateUploaded && !isImage(attachment.metadata?.type)) {
|
||||
RNFetchBlob.fs
|
||||
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
|
||||
.catch(console.log);
|
||||
|
||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import hosts from "@notesnook/core/utils/constants";
|
||||
import NetInfo from "@react-native-community/netinfo";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import { ToastEvent } from "../../services/event-manager";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { db } from "../database";
|
||||
|
||||
@@ -19,7 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Platform } from "react-native";
|
||||
import Sodium from "@ammarahmed/react-native-sodium";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import { cacheDir, getRandomId } from "./utils";
|
||||
import { db } from "../database";
|
||||
import { compressToBase64 } from "./compress";
|
||||
|
||||
@@ -17,11 +17,11 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import { useAttachmentStore } from "../../stores/use-attachment-store";
|
||||
import { db } from "../database";
|
||||
import { cacheDir } from "./utils";
|
||||
import { isImage, isDocument } from "@notesnook/core/utils/filename";
|
||||
import { isImage } from "@notesnook/core/utils/filename";
|
||||
|
||||
export async function uploadFile(filename, data, cancelToken) {
|
||||
if (!data) return false;
|
||||
@@ -64,10 +64,7 @@ export async function uploadFile(filename, data, cancelToken) {
|
||||
if (result) {
|
||||
let attachment = db.attachments.attachment(filename);
|
||||
if (!attachment) return result;
|
||||
if (
|
||||
!isImage(attachment.metadata.type) &&
|
||||
!isDocument(attachment.metadata?.type)
|
||||
) {
|
||||
if (!isImage(attachment.metadata.type)) {
|
||||
RNFetchBlob.fs.unlink(`${cacheDir}/${filename}`).catch(console.log);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +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/>.
|
||||
*/
|
||||
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import { Platform } from "react-native";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
|
||||
export const cacheDir = RNFetchBlob.fs.dirs.CacheDir;
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ const DownloadAttachments = ({ close, attachments, isNote, update }) => {
|
||||
width={null}
|
||||
animated={true}
|
||||
useNativeDriver
|
||||
progress={progress.value ? progress.value / attachments.length : 0}
|
||||
progress={progress.value ? progress.value / 100 : 0}
|
||||
unfilledColor={colors.nav}
|
||||
color={colors.accent}
|
||||
borderWidth={0}
|
||||
|
||||
@@ -36,7 +36,6 @@ import SheetProvider from "../sheet-provider";
|
||||
import RateAppSheet from "../sheets/rate-app";
|
||||
import RecoveryKeySheet from "../sheets/recovery-key";
|
||||
import RestoreDataSheet from "../sheets/restore-data";
|
||||
import PDFPreview from "../dialogs/pdf-preview";
|
||||
|
||||
const DialogProvider = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
@@ -61,7 +60,6 @@ const DialogProvider = () => {
|
||||
{loading ? null : <Expiring />}
|
||||
<AnnouncementDialog />
|
||||
<SessionExpired />
|
||||
<PDFPreview />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,324 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { Dimensions, TextInput, View } from "react-native";
|
||||
import {
|
||||
addOrientationListener,
|
||||
removeOrientationListener
|
||||
} from "react-native-orientation";
|
||||
import Pdf from "react-native-pdf";
|
||||
import Animated, { FadeIn, FadeOut } from "react-native-reanimated";
|
||||
import downloadAttachment from "../../../common/filesystem/download-attachment";
|
||||
import { cacheDir } from "../../../common/filesystem/utils";
|
||||
import { useAttachmentProgress } from "../../../hooks/use-attachment-progress";
|
||||
import useGlobalSafeAreaInsets from "../../../hooks/use-global-safe-area-insets";
|
||||
import {
|
||||
eSubscribeEvent,
|
||||
eUnSubscribeEvent
|
||||
} from "../../../services/event-manager";
|
||||
import { useThemeStore } from "../../../stores/use-theme-store";
|
||||
import { SIZE } from "../../../utils/size";
|
||||
import { Dialog } from "../../dialog";
|
||||
import BaseDialog from "../../dialog/base-dialog";
|
||||
import { presentDialog } from "../../dialog/functions";
|
||||
import SheetProvider from "../../sheet-provider";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
import { ProgressBarComponent } from "../../ui/svg/lazy";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { sleep } from "../../../utils/time";
|
||||
import { MMKV } from "../../../common/database/mmkv";
|
||||
|
||||
const WIN_WIDTH = Dimensions.get("window").width;
|
||||
const WIN_HEIGHT = Dimensions.get("window").height;
|
||||
|
||||
const attachmentSnapshotsKey = "___attachmentsnapshots";
|
||||
const usePDFSnapshot = (attachment) => {
|
||||
const snapshots = useRef(MMKV.getMap(attachmentSnapshotsKey) || {});
|
||||
const snapshot = useRef(snapshots[attachment?.id]);
|
||||
|
||||
function saveSnapshot(ss) {
|
||||
if (!attachment) return;
|
||||
snapshots.current[attachment.id] = ss;
|
||||
MMKV.setMap(attachmentSnapshotsKey, snapshots.current);
|
||||
snapshot.current = snapshots.current[attachment.id];
|
||||
}
|
||||
|
||||
return [snapshot, saveSnapshot];
|
||||
};
|
||||
|
||||
const PDFPreview = () => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [pdfSource, setPDFSource] = useState();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [width, setWidth] = useState(WIN_WIDTH);
|
||||
const insets = useGlobalSafeAreaInsets();
|
||||
const [numPages, setNumPages] = useState(0);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const inputRef = useRef();
|
||||
const pdfRef = useRef();
|
||||
const [attachment, setAttachment] = useState(null);
|
||||
const [password, setPassword] = useState("");
|
||||
const [progress] = useAttachmentProgress(attachment);
|
||||
const [snapshot, saveSnapshot] = usePDFSnapshot(attachment);
|
||||
const snapshotValue = useRef(snapshot.current);
|
||||
|
||||
useEffect(() => {
|
||||
eSubscribeEvent("PDFPreview", open);
|
||||
return () => {
|
||||
eUnSubscribeEvent("PDFPreview", open);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onOrientationChange = (o) => {
|
||||
if (o.includes("LANDSCAPE")) {
|
||||
setWidth(WIN_HEIGHT);
|
||||
} else {
|
||||
setWidth(WIN_WIDTH);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
addOrientationListener(onOrientationChange);
|
||||
return () => {
|
||||
removeOrientationListener(onOrientationChange);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const open = async (attachment) => {
|
||||
setVisible(true);
|
||||
setLoading(true);
|
||||
setTimeout(async () => {
|
||||
setAttachment(attachment);
|
||||
let hash = attachment.metadata.hash;
|
||||
if (!hash) return;
|
||||
const uri = await downloadAttachment(hash, false, {
|
||||
silent: true,
|
||||
cache: true
|
||||
});
|
||||
const path = `${cacheDir}/${uri}`;
|
||||
snapshotValue.current = snapshot.current;
|
||||
setPDFSource("file://" + path);
|
||||
setLoading(false);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const close = () => {
|
||||
setPDFSource(null);
|
||||
setVisible(false);
|
||||
setPassword("");
|
||||
};
|
||||
|
||||
const onError = async (error) => {
|
||||
if (error?.message === "Password required or incorrect password.") {
|
||||
await sleep(300);
|
||||
presentDialog({
|
||||
context: attachment?.metadata?.hash,
|
||||
input: true,
|
||||
inputPlaceholder: "Enter password",
|
||||
positiveText: "Unlock",
|
||||
title: "Decrypt",
|
||||
paragraph: "Please input password to view pdf.",
|
||||
positivePress: (value) => {
|
||||
setTimeout(() => {
|
||||
setPassword(value);
|
||||
});
|
||||
},
|
||||
onClose: () => {
|
||||
close();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
visible && (
|
||||
<BaseDialog animation="fade" visible={true} onRequestClose={close}>
|
||||
<SheetProvider context={attachment?.metadata?.hash} />
|
||||
<Dialog context={attachment?.metadata?.hash} />
|
||||
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: "black"
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<Animated.View
|
||||
exiting={FadeOut}
|
||||
style={{
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<ProgressBarComponent
|
||||
indeterminate={!progress}
|
||||
color={colors.accent}
|
||||
borderColor="transparent"
|
||||
progress={parseInt(progress?.value || "100") / 100}
|
||||
/>
|
||||
<Paragraph
|
||||
style={{
|
||||
marginTop: 10
|
||||
}}
|
||||
color={colors.light}
|
||||
>
|
||||
Loading {`${progress?.percent ? `(${progress?.percent})` : ""}`}
|
||||
... Please wait
|
||||
</Paragraph>
|
||||
</Animated.View>
|
||||
) : (
|
||||
<>
|
||||
<View
|
||||
style={{
|
||||
width: "100%",
|
||||
height: 50,
|
||||
marginTop: insets.top,
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
paddingHorizontal: 12,
|
||||
paddingLeft: 6
|
||||
}}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row"
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
color={colors.light}
|
||||
name="arrow-left"
|
||||
onPress={close}
|
||||
customStyle={{
|
||||
marginRight: 12
|
||||
}}
|
||||
size={SIZE.xxl}
|
||||
/>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
marginRight: 12
|
||||
}}
|
||||
>
|
||||
<TextInput
|
||||
ref={inputRef}
|
||||
defaultValue={currentPage + ""}
|
||||
style={{
|
||||
color: colors.pri,
|
||||
padding: 0,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
paddingVertical: 0,
|
||||
height: 25,
|
||||
backgroundColor: colors.nav,
|
||||
width: 40,
|
||||
textAlign: "center",
|
||||
marginRight: 4,
|
||||
borderRadius: 3,
|
||||
fontFamily: "OpenSans-Regular"
|
||||
}}
|
||||
selectTextOnFocus
|
||||
keyboardType="decimal-pad"
|
||||
onSubmitEditing={(event) => {
|
||||
setCurrentPage(event.nativeEvent.text);
|
||||
pdfRef.current?.setPage(parseInt(event.nativeEvent.text));
|
||||
}}
|
||||
blurOnSubmit
|
||||
/>
|
||||
<Paragraph color={colors.light}>/{numPages}</Paragraph>
|
||||
</View>
|
||||
|
||||
<View
|
||||
style={{
|
||||
flexDirection: "row"
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
color={colors.light}
|
||||
name="download"
|
||||
onPress={() => {
|
||||
downloadAttachment(attachment.metadata.hash, false);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
{pdfSource ? (
|
||||
<Animated.View
|
||||
style={{
|
||||
flex: 1
|
||||
}}
|
||||
entering={FadeIn}
|
||||
>
|
||||
<Pdf
|
||||
source={{
|
||||
uri: pdfSource
|
||||
}}
|
||||
ref={pdfRef}
|
||||
onLoadComplete={(numberOfPages) => {
|
||||
setNumPages(numberOfPages);
|
||||
}}
|
||||
onPageChanged={(page) => {
|
||||
setCurrentPage(page);
|
||||
inputRef.current?.setNativeProps({
|
||||
text: page + ""
|
||||
});
|
||||
saveSnapshot({
|
||||
currentPage: page,
|
||||
scale: snapshot?.current?.scale
|
||||
});
|
||||
}}
|
||||
// scale={snapshotValue.current?.scale}
|
||||
// onScaleChanged={(scale) => {
|
||||
// saveSnapshot({
|
||||
// currentPage: snapshot?.current?.currentPage,
|
||||
// scale: scale
|
||||
// });
|
||||
// }}
|
||||
page={snapshotValue?.current?.currentPage}
|
||||
password={password}
|
||||
maxScale={6}
|
||||
onError={onError}
|
||||
onPressLink={(uri) => {
|
||||
console.log(`Link pressed: ${uri}`);
|
||||
}}
|
||||
style={{
|
||||
flex: 1,
|
||||
width: width,
|
||||
height: Dimensions.get("window").height
|
||||
}}
|
||||
/>
|
||||
</Animated.View>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
</BaseDialog>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default PDFPreview;
|
||||
@@ -47,7 +47,7 @@ export const Tags = ({ item, close }) => {
|
||||
>
|
||||
<Button
|
||||
onPress={async () => {
|
||||
ManageTagsSheet.present([item]);
|
||||
ManageTagsSheet.present(item);
|
||||
}}
|
||||
buttonType={{
|
||||
text: colors.accent
|
||||
|
||||
@@ -36,7 +36,6 @@ import MoveNoteSheet from "../sheets/add-to";
|
||||
import ExportNotesSheet from "../sheets/export-notes";
|
||||
import { IconButton } from "../ui/icon-button";
|
||||
import Heading from "../ui/typography/heading";
|
||||
import ManageTagsSheet from "../sheets/manage-tags";
|
||||
|
||||
export const SelectionHeader = React.memo(() => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
@@ -210,20 +209,6 @@ export const SelectionHeader = React.memo(() => {
|
||||
screen === "Notebook" ||
|
||||
screen === "Reminders" ? null : (
|
||||
<>
|
||||
<IconButton
|
||||
onPress={async () => {
|
||||
await sleep(100);
|
||||
ManageTagsSheet.present(selectedItemsList);
|
||||
}}
|
||||
customStyle={{
|
||||
marginLeft: 10
|
||||
}}
|
||||
color={colors.pri}
|
||||
tooltipText="Manage tags"
|
||||
tooltipPosition={4}
|
||||
name="pound"
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
<IconButton
|
||||
onPress={async () => {
|
||||
//setSelectionMode(false);
|
||||
@@ -239,7 +224,6 @@ export const SelectionHeader = React.memo(() => {
|
||||
name="plus"
|
||||
size={SIZE.xl}
|
||||
/>
|
||||
|
||||
<IconButton
|
||||
onPress={async () => {
|
||||
ExportNotesSheet.present(selectedItemsList);
|
||||
|
||||
@@ -31,10 +31,9 @@ import Input from "../../ui/input";
|
||||
import { PressableButton } from "../../ui/pressable";
|
||||
import Heading from "../../ui/typography/heading";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import { IconButton } from "../../ui/icon-button";
|
||||
const ManageTagsSheet = (props) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const [notes, setNotes] = useState(props.notes || []);
|
||||
const [note, setNote] = useState(props.note);
|
||||
const allTags = useTagStore((state) => state.tags);
|
||||
const [tags, setTags] = useState([]);
|
||||
const [query, setQuery] = useState(null);
|
||||
@@ -43,7 +42,7 @@ const ManageTagsSheet = (props) => {
|
||||
|
||||
useEffect(() => {
|
||||
sortTags();
|
||||
}, [allTags, notes, query, sortTags]);
|
||||
}, [allTags, note, query, sortTags]);
|
||||
|
||||
const sortTags = useCallback(() => {
|
||||
let _tags = [...allTags];
|
||||
@@ -52,14 +51,13 @@ const ManageTagsSheet = (props) => {
|
||||
if (query) {
|
||||
_tags = _tags.filter((t) => t.title.startsWith(query));
|
||||
}
|
||||
const tagsMerged = [...notes.map((note) => note.tags || []).flat()];
|
||||
|
||||
if (!tagsMerged || !tagsMerged.length) {
|
||||
if (!note || !note.tags) {
|
||||
setTags(_tags);
|
||||
return;
|
||||
}
|
||||
let noteTags = [];
|
||||
for (let tag of tagsMerged) {
|
||||
for (let tag of note.tags) {
|
||||
let index = _tags.findIndex((t) => t.title === tag);
|
||||
if (index !== -1) {
|
||||
noteTags.push(_tags[index]);
|
||||
@@ -69,7 +67,7 @@ const ManageTagsSheet = (props) => {
|
||||
noteTags = noteTags.sort((a, b) => a.title.localeCompare(b.title));
|
||||
let combinedTags = [...noteTags, ..._tags];
|
||||
setTags(combinedTags);
|
||||
}, [allTags, notes, query]);
|
||||
}, [allTags, note, query]);
|
||||
|
||||
useEffect(() => {
|
||||
useTagStore.getState().setTags();
|
||||
@@ -87,23 +85,15 @@ const ManageTagsSheet = (props) => {
|
||||
}
|
||||
|
||||
let tag = _query;
|
||||
setNotes(
|
||||
notes.map((note) => ({
|
||||
...note,
|
||||
tags: note.tags ? [...note.tags, tag] : [tag]
|
||||
}))
|
||||
);
|
||||
|
||||
setNote({ ...note, tags: note.tags ? [...note.tags, tag] : [tag] });
|
||||
setQuery(null);
|
||||
inputRef.current?.setNativeProps({
|
||||
text: ""
|
||||
});
|
||||
try {
|
||||
for (let note of notes) {
|
||||
await db.notes.note(note.id).tag(tag);
|
||||
}
|
||||
await db.notes.note(note.id).tag(tag);
|
||||
useTagStore.getState().setTags();
|
||||
setNotes(notes.map((note) => db.notes.note(note.id).data));
|
||||
setNote(db.notes.note(note.id).data);
|
||||
} catch (e) {
|
||||
ToastEvent.show({
|
||||
heading: "Cannot add tag",
|
||||
@@ -190,52 +180,41 @@ const ManageTagsSheet = (props) => {
|
||||
) : null}
|
||||
|
||||
{tags.map((item) => (
|
||||
<TagItem
|
||||
key={item.title}
|
||||
tag={item}
|
||||
notes={notes}
|
||||
setNotes={setNotes}
|
||||
/>
|
||||
<TagItem key={item.title} tag={item} note={note} setNote={setNote} />
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
ManageTagsSheet.present = (notes) => {
|
||||
ManageTagsSheet.present = (note) => {
|
||||
presentSheet({
|
||||
component: (ref) => {
|
||||
return <ManageTagsSheet actionSheetRef={ref} notes={notes} />;
|
||||
return <ManageTagsSheet actionSheetRef={ref} note={note} />;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export default ManageTagsSheet;
|
||||
|
||||
const TagItem = ({ tag, notes, setNotes }) => {
|
||||
const TagItem = ({ tag, note, setNote }) => {
|
||||
const colors = useThemeStore((state) => state.colors);
|
||||
const someNotesTagged = notes.some(
|
||||
(note) => note.tags?.indexOf(tag.title) !== -1
|
||||
);
|
||||
const allNotesTagged = notes.every(
|
||||
(note) => note.tags?.indexOf(tag.title) !== -1
|
||||
);
|
||||
|
||||
const onPress = async () => {
|
||||
for (let note of notes) {
|
||||
try {
|
||||
if (someNotesTagged) {
|
||||
await db.notes
|
||||
.note(note.id)
|
||||
.untag(note.tags[note.tags.indexOf(tag.title)]);
|
||||
} else {
|
||||
await db.notes.note(note.id).tag(tag.title);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
let prevNote = { ...note };
|
||||
try {
|
||||
if (prevNote.tags.indexOf(tag.title) !== -1) {
|
||||
await db.notes
|
||||
.note(note.id)
|
||||
.untag(prevNote.tags[prevNote.tags.indexOf(tag.title)]);
|
||||
} else {
|
||||
await db.notes.note(note.id).tag(tag.title);
|
||||
}
|
||||
useTagStore.getState().setTags();
|
||||
setNote(db.notes.note(note.id).data);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
useTagStore.getState().setTags();
|
||||
setNotes(notes.map((note) => db.notes.note(note.id).data));
|
||||
setTimeout(() => {
|
||||
Navigation.queueRoutesForUpdate();
|
||||
}, 1);
|
||||
@@ -246,36 +225,39 @@ const TagItem = ({ tag, notes, setNotes }) => {
|
||||
customStyle={{
|
||||
flexDirection: "row",
|
||||
marginVertical: 5,
|
||||
justifyContent: "flex-start",
|
||||
height: 40
|
||||
justifyContent: "space-between",
|
||||
padding: 12
|
||||
}}
|
||||
onPress={onPress}
|
||||
type="gray"
|
||||
type={
|
||||
note && note.tags.findIndex((t) => t === tag.title) !== -1
|
||||
? "shade"
|
||||
: "grayBg"
|
||||
}
|
||||
>
|
||||
<IconButton
|
||||
size={22}
|
||||
customStyle={{
|
||||
marginRight: 5,
|
||||
width: 23,
|
||||
height: 23
|
||||
}}
|
||||
color={someNotesTagged || allNotesTagged ? colors.accent : colors.icon}
|
||||
testID={
|
||||
allNotesTagged
|
||||
? "check-circle-outline"
|
||||
: someNotesTagged
|
||||
? "minus-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
<Heading
|
||||
size={SIZE.sm}
|
||||
color={
|
||||
note && note?.tags.findIndex((t) => t === tag.title) !== -1
|
||||
? colors.accent
|
||||
: colors.pri
|
||||
}
|
||||
>
|
||||
{"#" + tag.title}
|
||||
</Heading>
|
||||
<Icon
|
||||
name={
|
||||
allNotesTagged
|
||||
? "check-circle-outline"
|
||||
: someNotesTagged
|
||||
? "minus-circle-outline"
|
||||
: "checkbox-blank-circle-outline"
|
||||
note && note?.tags.findIndex((t) => t === tag.title) !== -1
|
||||
? "minus"
|
||||
: "plus"
|
||||
}
|
||||
color={
|
||||
note && note?.tags.findIndex((t) => t === tag.title) !== -1
|
||||
? colors.accent
|
||||
: colors.accent
|
||||
}
|
||||
size={SIZE.lg}
|
||||
/>
|
||||
<Paragraph size={SIZE.sm}>{"#" + tag.title}</Paragraph>
|
||||
</PressableButton>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -43,7 +43,7 @@ import Seperator from "../../ui/seperator";
|
||||
import SheetWrapper from "../../ui/sheet";
|
||||
import { QRCode } from "../../ui/svg/lazy";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
|
||||
class RecoveryKeySheet extends React.Component {
|
||||
constructor(props) {
|
||||
|
||||
@@ -44,7 +44,7 @@ import { Button } from "../../ui/button";
|
||||
import Seperator from "../../ui/seperator";
|
||||
import SheetWrapper from "../../ui/sheet";
|
||||
import Paragraph from "../../ui/typography/paragraph";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
|
||||
const RestoreDataSheet = () => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
@@ -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: "Attachments preview",
|
||||
body: "You can now preview PDFs & Images directly inside Notesnook."
|
||||
},
|
||||
{
|
||||
title: "New attachments manager",
|
||||
body: "The new attachments manager makes is much easier to view & interact with your attachments. It also allows you to download all (or some) of your attachments."
|
||||
},
|
||||
{
|
||||
title: "Assign tags to multiple notes",
|
||||
body: "You can now easily tag multiple notes without first opening them."
|
||||
}
|
||||
];
|
||||
export const features: FeatureType[] = [];
|
||||
|
||||
@@ -42,7 +42,7 @@ export const useAttachmentProgress = (
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const attachmentProgress = progress?.[attachment?.metadata?.hash];
|
||||
const attachmentProgress = progress?.[attachment.metadata.hash];
|
||||
if (attachmentProgress) {
|
||||
const type = attachmentProgress.type;
|
||||
const loaded =
|
||||
@@ -60,7 +60,7 @@ export const useAttachmentProgress = (
|
||||
setCurrentProgress(undefined);
|
||||
}, 300);
|
||||
}
|
||||
}, [attachment, progress]);
|
||||
}, [attachment.metadata.hash, progress]);
|
||||
|
||||
return [currentProgress, setCurrentProgress];
|
||||
};
|
||||
|
||||
@@ -27,8 +27,7 @@
|
||||
"validator": "^13.5.2",
|
||||
"zustand": "^3.6.0",
|
||||
"fflate": "^0.7.3",
|
||||
"timeago.js": "4.0.2",
|
||||
"react-native-blob-util": "0.17.3"
|
||||
"timeago.js": "4.0.2"
|
||||
},
|
||||
"sideEffects": false
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ 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 RNFetchBlob from "react-native-blob-util";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import { db } from "../../../common/database";
|
||||
import { compressToBase64 } from "../../../common/filesystem/compress";
|
||||
import { AttachmentItem } from "../../../components/attachments/attachment-item";
|
||||
@@ -124,7 +124,7 @@ const file = async (fileOptions) => {
|
||||
editorController.current?.commands.insertImage({
|
||||
hash: hash,
|
||||
filename: file.name,
|
||||
mime: file.type,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
dataurl: await db.attachments.read(hash, "base64"),
|
||||
title: file.name
|
||||
@@ -133,7 +133,7 @@ const file = async (fileOptions) => {
|
||||
editorController.current?.commands.insertAttachment({
|
||||
hash: hash,
|
||||
filename: file.name,
|
||||
mime: file.type,
|
||||
type: file.type,
|
||||
size: file.size
|
||||
});
|
||||
}
|
||||
@@ -262,7 +262,7 @@ const handleImageResponse = async (response, options) => {
|
||||
|
||||
editorController.current?.commands.insertImage({
|
||||
hash: hash,
|
||||
mime: image.type,
|
||||
type: image.type,
|
||||
title: fileName,
|
||||
dataurl: b64,
|
||||
size: image.fileSize,
|
||||
|
||||
@@ -349,7 +349,7 @@ export const useEditorEvents = (
|
||||
});
|
||||
return;
|
||||
}
|
||||
ManageTagsSheet.present([editor.note.current]);
|
||||
ManageTagsSheet.present(editor.note.current);
|
||||
break;
|
||||
case EventTypes.tag:
|
||||
if (editorMessage.value) {
|
||||
@@ -398,18 +398,9 @@ export const useEditorEvents = (
|
||||
openLinkInBrowser(editorMessage.value as string);
|
||||
break;
|
||||
|
||||
case EventTypes.previewAttachment: {
|
||||
const hash = (editorMessage.value as Attachment)?.hash;
|
||||
const attachment = db.attachments?.attachment(hash);
|
||||
if (attachment.metadata.type.startsWith("image/")) {
|
||||
eSendEvent("ImagePreview", editorMessage.value);
|
||||
} else {
|
||||
eSendEvent("PDFPreview", attachment);
|
||||
}
|
||||
|
||||
case EventTypes.previewAttachment:
|
||||
eSendEvent("ImagePreview", editorMessage.value);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ import React, {
|
||||
import { ActivityIndicator, Linking, Platform, View } from "react-native";
|
||||
import { FlatList } from "react-native-gesture-handler";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import { db } from "../../common/database";
|
||||
import Storage from "../../common/database/storage";
|
||||
import DialogHeader from "../../components/dialog/dialog-header";
|
||||
|
||||
@@ -18,12 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import Clipboard from "@react-native-clipboard/clipboard";
|
||||
import { LogMessage } from "@notesnook/logger";
|
||||
import { LogMessage } from "@streetwriters/logger";
|
||||
import { format, LogLevel, logManager } from "@notesnook/core/logger";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { FlatList, Platform, TouchableOpacity, View } from "react-native";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import Storage from "../../common/database/storage";
|
||||
import { presentDialog } from "../../components/dialog/functions";
|
||||
import { IconButton } from "../../components/ui/icon-button";
|
||||
|
||||
@@ -268,10 +268,10 @@ export const LICENSES = [
|
||||
link: "https://github.com/RocketChat/rn-extensions-share"
|
||||
},
|
||||
{
|
||||
name: "react-native-blob-util",
|
||||
name: "rn-fetch-blob",
|
||||
licenseType: "MIT",
|
||||
author: "RonRadtke",
|
||||
link: "https://github.com/RonRadtke/react-native-blob-util"
|
||||
author: "Joltup",
|
||||
link: "https://github.com/joltup/rn-fetch-blob"
|
||||
},
|
||||
{
|
||||
name: "react-native-gzip",
|
||||
|
||||
@@ -21,7 +21,7 @@ import { Platform } from "react-native";
|
||||
import FileViewer from "react-native-file-viewer";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import Share from "react-native-share";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import { presentDialog } from "../components/dialog/functions";
|
||||
import { DatabaseLogger, db } from "../common/database";
|
||||
import storage from "../common/database/storage";
|
||||
|
||||
@@ -22,7 +22,7 @@ import { zipSync } from "fflate";
|
||||
import { Platform } from "react-native";
|
||||
import RNHTMLtoPDF from "react-native-html-to-pdf-lite";
|
||||
import * as ScopedStorage from "react-native-scoped-storage";
|
||||
import RNFetchBlob from "react-native-blob-util";
|
||||
import RNFetchBlob from "rn-fetch-blob";
|
||||
import { DatabaseLogger, db } from "../common/database/index";
|
||||
import Storage from "../common/database/storage";
|
||||
import { toTXT } from "../utils";
|
||||
|
||||
@@ -154,11 +154,9 @@ const onUserStatusCheck = async (type) => {
|
||||
};
|
||||
break;
|
||||
case CHECK_IDS.notebookAdd:
|
||||
message = {
|
||||
context: "sheet",
|
||||
title: "Get Notesnook Pro",
|
||||
desc: "With Notesnook Pro you can create unlimited notebooks and do so much more! Get it now."
|
||||
};
|
||||
setTimeout(() => {
|
||||
eSendEvent(eOpenPremiumDialog);
|
||||
}, 500);
|
||||
break;
|
||||
case CHECK_IDS.vaultAdd:
|
||||
message = {
|
||||
|
||||
@@ -164,7 +164,7 @@ android {
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
multiDexEnabled true
|
||||
versionCode 2049
|
||||
versionCode 2048
|
||||
versionName getNpmVersion()
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
@@ -276,13 +276,6 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
pickFirst 'lib/x86/libc++_shared.so'
|
||||
pickFirst 'lib/x86_64/libc++_shared.so'
|
||||
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
|
||||
pickFirst 'lib/arm64-v8a/libc++_shared.so'
|
||||
}
|
||||
|
||||
// applicationVariants are e.g. debug, release
|
||||
applicationVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
- Preview PDFs & Images directly inside Notesnook."
|
||||
- Improved attachments manager with support for downloading all attachments
|
||||
- Assign tags to multiple notes
|
||||
- Bug fixes and performance improvements
|
||||
- Fix pro features locked accidentally for pro users
|
||||
- Added in app review
|
||||
|
||||
Thank you for using Notesnook!
|
||||
@@ -993,7 +993,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2041;
|
||||
CURRENT_PROJECT_VERSION = 2040;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
ENABLE_BITCODE = NO;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1067,7 +1067,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.0;
|
||||
MARKETING_VERSION = 2.4.17;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
"-ObjC",
|
||||
@@ -1097,7 +1097,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Notesnook/Notesnook.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
CURRENT_PROJECT_VERSION = 2041;
|
||||
CURRENT_PROJECT_VERSION = 2040;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
GCC_GENERATE_DEBUGGING_SYMBOLS = YES;
|
||||
@@ -1170,7 +1170,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.0;
|
||||
MARKETING_VERSION = 2.4.17;
|
||||
ONLY_ACTIVE_ARCH = NO;
|
||||
OTHER_LDFLAGS = (
|
||||
"$(inherited)",
|
||||
@@ -1328,7 +1328,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2041;
|
||||
CURRENT_PROJECT_VERSION = 2040;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1340,7 +1340,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.0;
|
||||
MARKETING_VERSION = 2.4.17;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
@@ -1370,7 +1370,7 @@
|
||||
CODE_SIGN_IDENTITY = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2041;
|
||||
CURRENT_PROJECT_VERSION = 2040;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1382,7 +1382,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.0;
|
||||
MARKETING_VERSION = 2.4.17;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.notewidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -1411,7 +1411,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = "Make Note/Make Note.entitlements";
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 2041;
|
||||
CURRENT_PROJECT_VERSION = 2040;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1485,7 +1485,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.0;
|
||||
MARKETING_VERSION = 2.4.17;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
@@ -1515,7 +1515,7 @@
|
||||
CODE_SIGN_IDENTITY = "iPhone Distribution";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 2041;
|
||||
CURRENT_PROJECT_VERSION = 2040;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = 53CWBG3QUC;
|
||||
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = arm64;
|
||||
@@ -1589,7 +1589,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 2.5.0;
|
||||
MARKETING_VERSION = 2.4.17;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = org.streetwriters.notesnook.share;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
PODS:
|
||||
- Base64 (1.1.2)
|
||||
- BEMCheckBox (1.4.1)
|
||||
- boost (1.76.0)
|
||||
- callstack-repack (3.2.0):
|
||||
- JWTDecode (~> 3.0)
|
||||
- callstack-repack (3.0.0):
|
||||
- React-Core
|
||||
- SwiftyRSA
|
||||
- DoubleConversion (1.1.6)
|
||||
- FBLazyVector (0.69.7)
|
||||
- FBReactNativeSpec (0.69.7):
|
||||
@@ -18,7 +15,6 @@ PODS:
|
||||
- fmt (6.2.1)
|
||||
- glog (0.3.5)
|
||||
- GZIP (1.3.0)
|
||||
- JWTDecode (3.0.1)
|
||||
- MMKV (1.2.13):
|
||||
- MMKVCore (~> 1.2.13)
|
||||
- MMKVCore (1.2.13)
|
||||
@@ -238,11 +234,9 @@ PODS:
|
||||
- React-Core
|
||||
- react-native-begin-background-task (0.1.0):
|
||||
- React
|
||||
- react-native-blob-util (0.17.3):
|
||||
- React-Core
|
||||
- react-native-config (1.5.1):
|
||||
- react-native-config/App (= 1.5.1)
|
||||
- react-native-config/App (1.5.1):
|
||||
- react-native-config (1.4.11):
|
||||
- react-native-config/App (= 1.4.11)
|
||||
- react-native-config/App (1.4.11):
|
||||
- React-Core
|
||||
- react-native-date-picker (4.2.6):
|
||||
- React-Core
|
||||
@@ -250,7 +244,7 @@ PODS:
|
||||
- React-Core
|
||||
- react-native-fingerprint-scanner (5.0.0):
|
||||
- React-Core
|
||||
- react-native-get-random-values (1.9.0):
|
||||
- react-native-get-random-values (1.8.0):
|
||||
- React-Core
|
||||
- react-native-gzip (1.0.0):
|
||||
- Base64
|
||||
@@ -269,15 +263,13 @@ PODS:
|
||||
- react-native-mmkv-storage (0.8.0):
|
||||
- MMKV (= 1.2.13)
|
||||
- React-Core
|
||||
- react-native-netinfo (9.3.10):
|
||||
- react-native-netinfo (9.3.7):
|
||||
- React-Core
|
||||
- react-native-notification-sounds (0.5.5):
|
||||
- React
|
||||
- react-native-orientation (3.1.3):
|
||||
- React
|
||||
- react-native-pdf (6.6.2):
|
||||
- React-Core
|
||||
- react-native-safe-area-context (4.5.3):
|
||||
- react-native-safe-area-context (4.4.1):
|
||||
- RCT-Folly
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
@@ -285,7 +277,7 @@ PODS:
|
||||
- ReactCommon/turbomodule/core
|
||||
- react-native-sodium (1.3.0):
|
||||
- React
|
||||
- react-native-webview (11.26.1):
|
||||
- react-native-webview (11.23.1):
|
||||
- React-Core
|
||||
- React-perflogger (0.69.7)
|
||||
- React-RCTActionSheet (0.69.7):
|
||||
@@ -355,14 +347,15 @@ PODS:
|
||||
- React-perflogger (= 0.69.7)
|
||||
- rn-extensions-share (2.4.0):
|
||||
- React-Core
|
||||
- RNBootSplash (4.7.1):
|
||||
- rn-fetch-blob (0.12.0):
|
||||
- React-Core
|
||||
- RNCCheckbox (0.5.15):
|
||||
- BEMCheckBox (~> 1.4)
|
||||
- RNBootSplash (4.3.2):
|
||||
- React-Core
|
||||
- RNCClipboard (1.11.2):
|
||||
- RNCCheckbox (0.5.12):
|
||||
- React-Core
|
||||
- RNCMaskedView (0.2.9):
|
||||
- RNCClipboard (1.11.1):
|
||||
- React-Core
|
||||
- RNCMaskedView (0.2.8):
|
||||
- React-Core
|
||||
- RNDateTimePicker (6.6.0):
|
||||
- React-Core
|
||||
@@ -374,7 +367,7 @@ PODS:
|
||||
- React-Core
|
||||
- RNFlashList (1.4.0):
|
||||
- React-Core
|
||||
- RNGestureHandler (2.10.1):
|
||||
- RNGestureHandler (2.7.1):
|
||||
- React-Core
|
||||
- RNIap (7.5.6):
|
||||
- React-Core
|
||||
@@ -414,19 +407,21 @@ PODS:
|
||||
- React-RCTText
|
||||
- ReactCommon/turbomodule/core
|
||||
- Yoga
|
||||
- RNScreens (3.20.0):
|
||||
- RNScreens (3.18.2):
|
||||
- React-Core
|
||||
- React-RCTImage
|
||||
- RNSecureRandom (1.0.1):
|
||||
- React
|
||||
- RNShare (7.9.1):
|
||||
- React-Core
|
||||
- RNSVG (12.5.1):
|
||||
- RNSVG (12.4.4):
|
||||
- React-Core
|
||||
- RNTooltips (1.0.3):
|
||||
- pop (~> 1.0)
|
||||
- React
|
||||
- SexyTooltip
|
||||
- RNVectorIcons (9.2.0):
|
||||
- React-Core
|
||||
- RNZipArchive (6.0.9):
|
||||
- React-Core
|
||||
- RNZipArchive/Core (= 6.0.9)
|
||||
@@ -437,9 +432,6 @@ PODS:
|
||||
- SexyTooltip (1.2.5):
|
||||
- pop (~> 1.0)
|
||||
- SSZipArchive (2.4.3)
|
||||
- SwiftyRSA (1.7.0):
|
||||
- SwiftyRSA/ObjC (= 1.7.0)
|
||||
- SwiftyRSA/ObjC (1.7.0)
|
||||
- toolbar-android (0.2.1):
|
||||
- React
|
||||
- Yoga (1.14.0)
|
||||
@@ -471,7 +463,6 @@ DEPENDENCIES:
|
||||
- react-native-actions-shortcuts (from `../../node_modules/react-native-actions-shortcuts`)
|
||||
- react-native-background-actions (from `../../node_modules/react-native-background-actions`)
|
||||
- react-native-begin-background-task (from `../../node_modules/react-native-begin-background-task`)
|
||||
- react-native-blob-util (from `../../node_modules/react-native-blob-util`)
|
||||
- react-native-config (from `../../node_modules/react-native-config`)
|
||||
- react-native-date-picker (from `../../node_modules/react-native-date-picker`)
|
||||
- react-native-document-picker (from `../../node_modules/react-native-document-picker`)
|
||||
@@ -487,7 +478,6 @@ DEPENDENCIES:
|
||||
- "react-native-netinfo (from `../../node_modules/@react-native-community/netinfo`)"
|
||||
- react-native-notification-sounds (from `../../node_modules/react-native-notification-sounds`)
|
||||
- react-native-orientation (from `../../node_modules/react-native-orientation`)
|
||||
- react-native-pdf (from `../../node_modules/react-native-pdf`)
|
||||
- react-native-safe-area-context (from `../../node_modules/react-native-safe-area-context`)
|
||||
- "react-native-sodium (from `../../node_modules/@ammarahmed/react-native-sodium`)"
|
||||
- react-native-webview (from `../../node_modules/react-native-webview`)
|
||||
@@ -504,6 +494,7 @@ DEPENDENCIES:
|
||||
- React-runtimeexecutor (from `../../node_modules/react-native/ReactCommon/runtimeexecutor`)
|
||||
- ReactCommon/turbomodule/core (from `../../node_modules/react-native/ReactCommon`)
|
||||
- rn-extensions-share (from `../../node_modules/rn-extensions-share`)
|
||||
- rn-fetch-blob (from `../../node_modules/rn-fetch-blob`)
|
||||
- RNBootSplash (from `../../node_modules/react-native-bootsplash`)
|
||||
- "RNCCheckbox (from `../../node_modules/@react-native-community/checkbox`)"
|
||||
- "RNCClipboard (from `../../node_modules/@react-native-clipboard/clipboard`)"
|
||||
@@ -524,6 +515,7 @@ DEPENDENCIES:
|
||||
- RNShare (from `../../node_modules/react-native-share`)
|
||||
- RNSVG (from `../../node_modules/react-native-svg`)
|
||||
- RNTooltips (from `../../node_modules/react-native-tooltips`)
|
||||
- RNVectorIcons (from `../../node_modules/react-native-vector-icons`)
|
||||
- RNZipArchive (from `../../node_modules/react-native-zip-archive`)
|
||||
- SexyTooltip (from `https://github.com/ammarahm-ed/SexyTooltip.git`)
|
||||
- "toolbar-android (from `../../node_modules/@react-native-community/toolbar-android`)"
|
||||
@@ -532,15 +524,12 @@ DEPENDENCIES:
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- Base64
|
||||
- BEMCheckBox
|
||||
- fmt
|
||||
- GZIP
|
||||
- JWTDecode
|
||||
- MMKV
|
||||
- MMKVCore
|
||||
- pop
|
||||
- SSZipArchive
|
||||
- SwiftyRSA
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
boost:
|
||||
@@ -589,8 +578,6 @@ EXTERNAL SOURCES:
|
||||
:path: "../../node_modules/react-native-background-actions"
|
||||
react-native-begin-background-task:
|
||||
:path: "../../node_modules/react-native-begin-background-task"
|
||||
react-native-blob-util:
|
||||
:path: "../../node_modules/react-native-blob-util"
|
||||
react-native-config:
|
||||
:path: "../../node_modules/react-native-config"
|
||||
react-native-date-picker:
|
||||
@@ -621,8 +608,6 @@ EXTERNAL SOURCES:
|
||||
:path: "../../node_modules/react-native-notification-sounds"
|
||||
react-native-orientation:
|
||||
:path: "../../node_modules/react-native-orientation"
|
||||
react-native-pdf:
|
||||
:path: "../../node_modules/react-native-pdf"
|
||||
react-native-safe-area-context:
|
||||
:path: "../../node_modules/react-native-safe-area-context"
|
||||
react-native-sodium:
|
||||
@@ -655,6 +640,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../../node_modules/react-native/ReactCommon"
|
||||
rn-extensions-share:
|
||||
:path: "../../node_modules/rn-extensions-share"
|
||||
rn-fetch-blob:
|
||||
:path: "../../node_modules/rn-fetch-blob"
|
||||
RNBootSplash:
|
||||
:path: "../../node_modules/react-native-bootsplash"
|
||||
RNCCheckbox:
|
||||
@@ -695,6 +682,8 @@ EXTERNAL SOURCES:
|
||||
:path: "../../node_modules/react-native-svg"
|
||||
RNTooltips:
|
||||
:path: "../../node_modules/react-native-tooltips"
|
||||
RNVectorIcons:
|
||||
:path: "../../node_modules/react-native-vector-icons"
|
||||
RNZipArchive:
|
||||
:path: "../../node_modules/react-native-zip-archive"
|
||||
SexyTooltip:
|
||||
@@ -711,16 +700,14 @@ CHECKOUT OPTIONS:
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
Base64: cecfb41a004124895a7bcee567a89bae5a89d49b
|
||||
BEMCheckBox: 5ba6e37ade3d3657b36caecc35c8b75c6c2b1a4e
|
||||
boost: a7c83b31436843459a1961bfd74b96033dc77234
|
||||
callstack-repack: 3e48a96824e0e0411ae1f48749a2ab103aa62a3a
|
||||
callstack-repack: 9e5425dfffeda7ea87b71729c4097141c7d8ce1c
|
||||
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
|
||||
FBLazyVector: 6b7f5692909b4300d50e7359cdefbcd09dd30faa
|
||||
FBReactNativeSpec: f53cf57758c70c6bfba5230b739cd1071e7a6824
|
||||
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
|
||||
glog: 3d02b25ca00c2d456734d0bcff864cbc62f6ae1a
|
||||
GZIP: 416858efbe66b41b206895ac6dfd5493200d95b3
|
||||
JWTDecode: 2eed97c2fa46ccaf3049a787004eedf0be474a87
|
||||
MMKV: aac95d817a100479445633f2b3ed8961b4ac5043
|
||||
MMKVCore: 3388952ded307e41b3ed8a05892736a236ed1b8e
|
||||
pop: d582054913807fd11fd50bfe6a539d91c7e1a55a
|
||||
@@ -741,12 +728,11 @@ SPEC CHECKSUMS:
|
||||
react-native-actions-shortcuts: 5d9cf0c9c308333dfcc1e05c3f9afa8c428e2533
|
||||
react-native-background-actions: 2c251c986f23347f9c1722f05fd296938f60edb1
|
||||
react-native-begin-background-task: 3b889e07458afc5822a7277cf9cbc7cd077e39ee
|
||||
react-native-blob-util: 99f4d79189252f597fe0d810c57a3733b1b1dea6
|
||||
react-native-config: 86038147314e2e6d10ea9972022aa171e6b1d4d8
|
||||
react-native-config: bcafda5b4c51491ee1b0e1d0c4e3905bc7b56c1b
|
||||
react-native-date-picker: 93e43b3084cea595b4d68b1405d6d99849663bd6
|
||||
react-native-document-picker: ec07866a30707f23660c0f3ae591d669d3e89096
|
||||
react-native-fingerprint-scanner: be63e626b31fb951780a5fac5328b065a61a3d6e
|
||||
react-native-get-random-values: dee677497c6a740b71e5612e8dbd83e7539ed5bb
|
||||
react-native-get-random-values: a6ea6a8a65dc93e96e24a11105b1a9c8cfe1d72a
|
||||
react-native-gzip: 02f9968afa759e189f0414d41f8f4a951a86b4f1
|
||||
react-native-html-to-pdf-lite: 21bfb169bf4cbcd7bec9f736975ee1b3f5292d4a
|
||||
react-native-image-picker: 9c8a2687b69300ad9e95cec5d38f35ab9d32467d
|
||||
@@ -754,13 +740,12 @@ SPEC CHECKSUMS:
|
||||
react-native-in-app-review: db8bb167a5f238e7ceca5c242d6b36ce8c4404a4
|
||||
react-native-keep-awake: acbee258db16483744910f0da3ace39eb9ab47fd
|
||||
react-native-mmkv-storage: 8ba3c0216a6df283ece11205b442a3e435aec4e5
|
||||
react-native-netinfo: ccbe1085dffd16592791d550189772e13bf479e2
|
||||
react-native-netinfo: 2517ad504b3d303e90d7a431b0fcaef76d207983
|
||||
react-native-notification-sounds: da78c828fe1bcbb92d8b505d5261890ed315ff39
|
||||
react-native-orientation: f1caf84d65f1a4fd4511a18f2b924e634ad7a628
|
||||
react-native-pdf: 33c622cbdf776a649929e8b9d1ce2d313347c4fa
|
||||
react-native-safe-area-context: b8979f5eda6ed5903d4dbc885be3846ea3daa753
|
||||
react-native-safe-area-context: 99b24a0c5acd0d5dcac2b1a7f18c49ea317be99a
|
||||
react-native-sodium: 1681828855ec18fa952f4557cd595bf048cf5c32
|
||||
react-native-webview: 9f111dfbcfc826084d6c507f569e5e03342ee1c1
|
||||
react-native-webview: d33e2db8925d090871ffeb232dfa50cb3a727581
|
||||
React-perflogger: 8e832d4e21fdfa613033c76d58d7e617341e804b
|
||||
React-RCTActionSheet: 9ca778182a9523991bff6381045885b6e808bb73
|
||||
React-RCTAnimation: 9ced26ad20b96e532ac791a8ab92a7b1ce2266b8
|
||||
@@ -774,30 +759,31 @@ SPEC CHECKSUMS:
|
||||
React-runtimeexecutor: 65cd2782a57e1d59a68aa5d504edf94278578e41
|
||||
ReactCommon: 1e783348b9aa73ae68236271df972ba898560a95
|
||||
rn-extensions-share: 3f0ecce20dfbca1f0358deb4ebfb9ee121a6d92a
|
||||
RNBootSplash: 3f3f7f82efe2addbfe7ddeda20877ff4d579cd81
|
||||
RNCCheckbox: 43bcc6493611468af0e19f19f029dab3da8561c4
|
||||
RNCClipboard: 3f0451a8100393908bea5c5c5b16f96d45f30bfc
|
||||
RNCMaskedView: 949696f25ec596bfc697fc88e6f95cf0c79669b6
|
||||
rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba
|
||||
RNBootSplash: 5f346163977573d6b2aeba1b25df9d2245c0d73c
|
||||
RNCCheckbox: ed1b4ca295475b41e7251ebae046360a703b6eb5
|
||||
RNCClipboard: 2834e1c4af68697089cdd455ee4a4cdd198fa7dd
|
||||
RNCMaskedView: bc0170f389056201c82a55e242e5d90070e18e5a
|
||||
RNDateTimePicker: 818672afa85519722533d017b832ed09539d9ddb
|
||||
RNDeviceInfo: aad3c663b25752a52bf8fce93f2354001dd185aa
|
||||
RNExitApp: c4e052df2568b43bec8a37c7cd61194d4cfee2c3
|
||||
RNFileViewer: ce7ca3ac370e18554d35d6355cffd7c30437c592
|
||||
RNFlashList: 399bf6a0db68f594ad2c86aaff3ea39564f39f8a
|
||||
RNGestureHandler: 42ec7c28dd02d540ed6c9159c57a98ff016492dc
|
||||
RNGestureHandler: b7a872907ee289ada902127f2554fa1d2c076122
|
||||
RNIap: d248609d1b8937e63bd904e865c318e9b1457eff
|
||||
RNKeychain: 840f8e6f13be0576202aefcdffd26a4f54bfe7b5
|
||||
RNNotifee: 2ae3c18196e6f307fa62ae5c8e5305dea03ff147
|
||||
RNPrivacySnapshot: 8eaf571478a353f2e5184f5c803164f22428b023
|
||||
RNReanimated: f1b109fb8341505ace9d7d2eedd150da1686716b
|
||||
RNScreens: 218801c16a2782546d30bd2026bb625c0302d70f
|
||||
RNScreens: 34cc502acf1b916c582c60003dc3089fa01dc66d
|
||||
RNSecureRandom: 07efbdf2cd99efe13497433668e54acd7df49fef
|
||||
RNShare: a5dc3b9c53ddc73e155b8cd9a94c70c91913c43c
|
||||
RNSVG: d7d7bc8229af3842c9cfc3a723c815a52cdd1105
|
||||
RNSVG: ecd661f380a07ba690c9c5929c475a44f432d674
|
||||
RNTooltips: 5424d4bf0b3d441104127943b1115cc7f0616b1f
|
||||
RNVectorIcons: fcc2f6cb32f5735b586e66d14103a74ce6ad61f8
|
||||
RNZipArchive: 68a0c6db4b1c103f846f1559622050df254a3ade
|
||||
SexyTooltip: 5c9b4dec52bfb317938cb0488efd9da3717bb6fd
|
||||
SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef
|
||||
SwiftyRSA: 8c6dd1ea7db1b8dc4fb517a202f88bb1354bc2c6
|
||||
toolbar-android: 2a73856e98b750d7e71ce4644d3f41cc98211719
|
||||
Yoga: 0b84a956f7393ef1f37f3bb213c516184e4a689d
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"main": "index.js",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@callstack/repack": "^3.2.0",
|
||||
"@callstack/repack": "^3.0.0",
|
||||
"@react-native-clipboard/clipboard": "^1.9.0",
|
||||
"@react-native-community/checkbox": "^0.5.8",
|
||||
"@react-native-community/netinfo": "^9.3.7",
|
||||
@@ -48,6 +48,7 @@
|
||||
"react-native-tooltips": "^1.0.3",
|
||||
"react-native-webview": "^11.14.1",
|
||||
"rn-extensions-share": "^2.4.0",
|
||||
"rn-fetch-blob": "^0.12.0",
|
||||
"react-native-gzip":"1.0.0",
|
||||
"@shopify/flash-list":"1.4.0",
|
||||
"@ammarahmed/notifee-react-native": "7.4.4",
|
||||
@@ -60,9 +61,7 @@
|
||||
"react-native-actions-shortcuts": "^1.0.1",
|
||||
"react-native-in-app-review": "4.3.3",
|
||||
"react-native-zip-archive": "6.0.9",
|
||||
"react-native-vector-icons": "9.2.0",
|
||||
"react-native-pdf": "6.6.2",
|
||||
"react-native-blob-util": "0.17.3"
|
||||
"react-native-vector-icons": "9.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.9",
|
||||
@@ -101,7 +100,6 @@
|
||||
"react-test-renderer": "18.0.0",
|
||||
"terser-webpack-plugin": "^5.3.5",
|
||||
"ts-jest": "^28.0.7",
|
||||
"webpack": "^5.74.0",
|
||||
"react-refresh": "0.14.0"
|
||||
"webpack": "^5.74.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
- Preview PDFs & Images directly inside Notesnook."
|
||||
- Improved attachments manager with support for downloading all attachments
|
||||
- Assign tags to multiple notes
|
||||
- Bug fixes and performance improvements
|
||||
- Fix pro features locked accidentally for pro users
|
||||
- Added in app review
|
||||
|
||||
Thank you for using Notesnook!
|
||||
29231
apps/mobile/package-lock.json
generated
29231
apps/mobile/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@notesnook/mobile",
|
||||
"version": "2.5.0",
|
||||
"version": "2.4.17",
|
||||
"private": true,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"workspaces": [
|
||||
@@ -25,15 +25,13 @@
|
||||
"devDependencies": {
|
||||
"patch-package": "^6.4.7",
|
||||
"typescript": "^4.8.2",
|
||||
"otplib": "12.0.1",
|
||||
"react-refresh": "0.14.0"
|
||||
"otplib": "12.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "18.0.0",
|
||||
"react-native": "0.69.7",
|
||||
"@notesnook/core": "*",
|
||||
"@notesnook/editor": "*",
|
||||
"@notesnook/editor-mobile": "*",
|
||||
"@notesnook/logger": "*"
|
||||
"@notesnook/editor-mobile": "*"
|
||||
}
|
||||
}
|
||||
|
||||
103
apps/mobile/patches/rn-fetch-blob+0.12.0.patch
Normal file
103
apps/mobile/patches/rn-fetch-blob+0.12.0.patch
Normal file
@@ -0,0 +1,103 @@
|
||||
diff --git a/node_modules/rn-fetch-blob/android/build.gradle b/node_modules/rn-fetch-blob/android/build.gradle
|
||||
index a4ca7a4..4fd3cfa 100644
|
||||
--- a/node_modules/rn-fetch-blob/android/build.gradle
|
||||
+++ b/node_modules/rn-fetch-blob/android/build.gradle
|
||||
@@ -41,6 +41,7 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation "com.facebook.react:react-native:${safeExtGet('reactNativeVersion', '+')}"
|
||||
- //compile 'com.squareup.okhttp3:okhttp:+'
|
||||
+ implementation 'com.squareup.okhttp3:okhttp:3.4.1'
|
||||
+
|
||||
//{RNFetchBlob_PRE_0.28_DEPDENDENCY}
|
||||
}
|
||||
diff --git a/node_modules/rn-fetch-blob/react-native.config.js b/node_modules/rn-fetch-blob/react-native.config.js
|
||||
deleted file mode 100644
|
||||
index 03c61b6..0000000
|
||||
--- a/node_modules/rn-fetch-blob/react-native.config.js
|
||||
+++ /dev/null
|
||||
@@ -1,7 +0,0 @@
|
||||
-module.exports = {
|
||||
- dependency: {
|
||||
- hooks: {
|
||||
- prelink: 'node ./node_modules/rn-fetch-blob/scripts/prelink.js',
|
||||
- },
|
||||
- },
|
||||
-};
|
||||
diff --git a/node_modules/rn-fetch-blob/scripts/prelink.js b/node_modules/rn-fetch-blob/scripts/prelink.js
|
||||
deleted file mode 100644
|
||||
index e2c3ac4..0000000
|
||||
--- a/node_modules/rn-fetch-blob/scripts/prelink.js
|
||||
+++ /dev/null
|
||||
@@ -1,71 +0,0 @@
|
||||
-try {
|
||||
- var fs = require('fs');
|
||||
- var glob = require('glob');
|
||||
- var addAndroidPermissions = process.env.RNFB_ANDROID_PERMISSIONS == 'true';
|
||||
- var MANIFEST_PATH = glob.sync(process.cwd() + '/android/app/src/main/**/AndroidManifest.xml')[0];
|
||||
- var PACKAGE_JSON = process.cwd() + '/package.json';
|
||||
- var package = JSON.parse(fs.readFileSync(PACKAGE_JSON));
|
||||
- var APP_NAME = package.name;
|
||||
- var PACKAGE_GRADLE = process.cwd() + '/node_modules/rn-fetch-blob/android/build.gradle'
|
||||
- var VERSION = checkVersion();
|
||||
-
|
||||
- console.log('RNFetchBlob detected app version => ' + VERSION);
|
||||
-
|
||||
- if(VERSION < 0.28) {
|
||||
- console.log('You project version is '+ VERSION + ' which may not compatible to rn-fetch-blob 7.0+, please consider upgrade your application template to react-native 0.27+.')
|
||||
- // add OkHttp3 dependency fo pre 0.28 project
|
||||
- var main = fs.readFileSync(PACKAGE_GRADLE);
|
||||
- console.log('adding OkHttp3 dependency to pre 0.28 project .. ')
|
||||
- main = String(main).replace('//{RNFetchBlob_PRE_0.28_DEPDENDENCY}', "compile 'com.squareup.okhttp3:okhttp:3.4.1'");
|
||||
- fs.writeFileSync(PACKAGE_GRADLE, main);
|
||||
- console.log('adding OkHttp3 dependency to pre 0.28 project .. ok')
|
||||
- }
|
||||
-
|
||||
- console.log('Add Android permissions => ' + (addAndroidPermissions == "true"))
|
||||
-
|
||||
- if(addAndroidPermissions) {
|
||||
-
|
||||
- // set file access permission for Android < 6.0
|
||||
- fs.readFile(MANIFEST_PATH, function(err, data) {
|
||||
-
|
||||
- if(err)
|
||||
- console.log('failed to locate AndroidManifest.xml file, you may have to add file access permission manually.');
|
||||
- else {
|
||||
-
|
||||
- console.log('RNFetchBlob patching AndroidManifest.xml .. ');
|
||||
- // append fs permission
|
||||
- data = String(data).replace(
|
||||
- '<uses-permission android:name="android.permission.INTERNET" />',
|
||||
- '<uses-permission android:name="android.permission.INTERNET" />\n <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> '
|
||||
- )
|
||||
- // append DOWNLOAD_COMPLETE intent permission
|
||||
- data = String(data).replace(
|
||||
- '<category android:name="android.intent.category.LAUNCHER" />',
|
||||
- '<category android:name="android.intent.category.LAUNCHER" />\n <action android:name="android.intent.action.DOWNLOAD_COMPLETE"/>'
|
||||
- )
|
||||
- fs.writeFileSync(MANIFEST_PATH, data);
|
||||
- console.log('RNFetchBlob patching AndroidManifest.xml .. ok');
|
||||
-
|
||||
- }
|
||||
-
|
||||
- })
|
||||
- }
|
||||
- else {
|
||||
- console.log(
|
||||
- '\033[95mrn-fetch-blob \033[97mwill not automatically add Android permissions after \033[92m0.9.4 '+
|
||||
- '\033[97mplease run the following command if you want to add default permissions :\n\n' +
|
||||
- '\033[96m\tRNFB_ANDROID_PERMISSIONS=true react-native link \n')
|
||||
- }
|
||||
-
|
||||
- function checkVersion() {
|
||||
- console.log('RNFetchBlob checking app version ..');
|
||||
- return parseFloat(/\d\.\d+(?=\.)/.exec(package.dependencies['react-native']));
|
||||
- }
|
||||
-
|
||||
-} catch(err) {
|
||||
- console.log(
|
||||
- '\033[95mrn-fetch-blob\033[97m link \033[91mFAILED \033[97m\nCould not automatically link package :'+
|
||||
- err.stack +
|
||||
- 'please follow the instructions to manually link the library : ' +
|
||||
- '\033[4mhttps://github.com/joltup/rn-fetch-blob/wiki/Manually-Link-Package\n')
|
||||
-}
|
||||
@@ -256,23 +256,6 @@ test("select notes using Shift+Click upwards", async ({ page }, info) => {
|
||||
expect(await notesList[0].isFocused()).toBeTruthy();
|
||||
});
|
||||
|
||||
test("using Shift+Click when no notes are selected should not crash the app", async ({
|
||||
page
|
||||
}, info) => {
|
||||
info.setTimeout(60 * 1000);
|
||||
|
||||
const { notes } = await populateList(page, 5);
|
||||
await page.reload();
|
||||
|
||||
const note = await notes.findNote({ title: "Test note 3" });
|
||||
|
||||
await page.keyboard.down("Shift");
|
||||
await note?.click();
|
||||
await page.keyboard.up("Shift");
|
||||
|
||||
expect(await notes.isEmpty()).toBeFalsy();
|
||||
});
|
||||
|
||||
test("Ctrl+Click to select/unselect notes", async ({ page }, info) => {
|
||||
info.setTimeout(60 * 1000);
|
||||
const { notesList, notes } = await populateList(page, 10);
|
||||
|
||||
4
apps/web/desktop/package-lock.json
generated
4
apps/web/desktop/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "2.5.0",
|
||||
"version": "2.4.11",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/desktop",
|
||||
"version": "2.5.0",
|
||||
"version": "2.4.11",
|
||||
"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.5.0",
|
||||
"version": "2.4.11",
|
||||
"appAppleId": "1544027013",
|
||||
"private": true,
|
||||
"main": "./build/electron.js",
|
||||
|
||||
19724
apps/web/package-lock.json
generated
19724
apps/web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@notesnook/web",
|
||||
"description": "Your private note taking space",
|
||||
"version": "2.5.0",
|
||||
"version": "2.4.11",
|
||||
"private": true,
|
||||
"main": "./src/app.js",
|
||||
"homepage": "https://notesnook.com/",
|
||||
@@ -27,8 +27,6 @@
|
||||
"@notesnook/streamable-fs": "*",
|
||||
"@notesnook/theme": "*",
|
||||
"@notesnook/web-clipper": "*",
|
||||
"@react-pdf-viewer/core": "^3.12.0",
|
||||
"@react-pdf-viewer/toolbar": "^3.12.0",
|
||||
"@tanstack/react-virtual": "^3.0.0-beta.18",
|
||||
"@theme-ui/components": "^0.14.7",
|
||||
"@theme-ui/core": "^0.14.7",
|
||||
@@ -50,7 +48,6 @@
|
||||
"localforage-driver-memory": "^1.0.5",
|
||||
"mac-scrollbar": "^0.10.3",
|
||||
"marked": "^4.1.0",
|
||||
"pdfjs-dist": "^3.6.172",
|
||||
"phone": "^3.1.14",
|
||||
"platform": "^1.3.6",
|
||||
"print-js": "^1.6.0",
|
||||
|
||||
22
apps/web/public/pdf.worker.min.js
vendored
22
apps/web/public/pdf.worker.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,10 +1,3 @@
|
||||
:root {
|
||||
--focus-border: var(--primary);
|
||||
--separator-border: var(--border);
|
||||
--sash-size: 10px;
|
||||
--sash-hover-size: 4px;
|
||||
}
|
||||
|
||||
/* open-sans-regular - vietnamese_latin-ext_latin_hebrew_greek-ext_greek_cyrillic-ext_cyrillic */
|
||||
@font-face {
|
||||
font-family: "Open Sans";
|
||||
@@ -102,8 +95,6 @@
|
||||
format("truetype");
|
||||
}
|
||||
|
||||
.rpv-core__text-layer,
|
||||
.rpv-core__text-layer *,
|
||||
.selectable,
|
||||
.selectable *,
|
||||
input,
|
||||
@@ -129,17 +120,6 @@ textarea,
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.rpv-core__text-layer-text::selection {
|
||||
background-color: var(--dimPrimary) !important;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.rpv-core__text-layer-text::-moz-selection {
|
||||
/* Code for Firefox */
|
||||
background-color: var(--dimPrimary) !important;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
*::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
@@ -185,6 +165,13 @@ textarea,
|
||||
width: 1px !important;
|
||||
}
|
||||
|
||||
:root {
|
||||
--focus-border: var(--primary);
|
||||
--separator-border: var(--border);
|
||||
--sash-size: 10px;
|
||||
--sash-hover-size: 4px;
|
||||
}
|
||||
|
||||
.route#settings,
|
||||
#mainRouteContainer {
|
||||
overflow: hidden;
|
||||
|
||||
@@ -25,6 +25,7 @@ import useTablet from "./hooks/use-tablet";
|
||||
import { LazyMotion, domAnimation } from "framer-motion";
|
||||
import useDatabase from "./hooks/use-database";
|
||||
import { Allotment, LayoutPriority } from "allotment";
|
||||
import "allotment/dist/style.css";
|
||||
import Config from "./utils/config";
|
||||
import { useStore } from "./stores/app-store";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
|
||||
@@ -20,8 +20,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import FS from "../interfaces/fs";
|
||||
import { db } from "./db";
|
||||
|
||||
async function download(hash: string) {
|
||||
const attachment = db.attachments?.attachment(hash);
|
||||
export async function downloadAttachment(hash) {
|
||||
const attachment = db.attachments.attachment(hash);
|
||||
if (!attachment) return;
|
||||
const downloadResult = await db.fs.downloadFile(
|
||||
attachment.metadata.hash,
|
||||
@@ -31,17 +31,9 @@ async function download(hash: string) {
|
||||
);
|
||||
if (!downloadResult) throw new Error("Failed to download file.");
|
||||
|
||||
const key = await db.attachments?.decryptKey(attachment.key);
|
||||
const key = await db.attachments.decryptKey(attachment.key);
|
||||
if (!key) throw new Error("Invalid key for attachment.");
|
||||
|
||||
return { key, attachment };
|
||||
}
|
||||
|
||||
export async function saveAttachment(hash: string) {
|
||||
const response = await download(hash);
|
||||
if (!response) return;
|
||||
|
||||
const { attachment, key } = response;
|
||||
await FS.saveFile(attachment.metadata.hash, {
|
||||
key,
|
||||
iv: attachment.iv,
|
||||
@@ -51,51 +43,23 @@ export async function saveAttachment(hash: string) {
|
||||
});
|
||||
}
|
||||
|
||||
type OutputTypeToReturnType = {
|
||||
blob: Blob;
|
||||
base64: string;
|
||||
text: string;
|
||||
};
|
||||
export async function downloadAttachment<
|
||||
TType extends "blob" | "base64" | "text",
|
||||
TOutputType = OutputTypeToReturnType[TType]
|
||||
>(hash: string, type: TType): Promise<TOutputType | undefined> {
|
||||
const response = await download(hash);
|
||||
if (!response) return;
|
||||
const { attachment, key } = response;
|
||||
|
||||
if (type === "base64" || type === "text")
|
||||
return (await db.attachments?.read(hash, type)) as TOutputType;
|
||||
|
||||
const blob = await FS.decryptFile(attachment.metadata.hash, {
|
||||
key,
|
||||
iv: attachment.iv,
|
||||
name: attachment.metadata.filename,
|
||||
type: attachment.metadata.type,
|
||||
isUploaded: !!attachment.dateUploaded
|
||||
});
|
||||
|
||||
if (!blob) return;
|
||||
return blob as TOutputType;
|
||||
}
|
||||
|
||||
export async function checkAttachment(hash: string) {
|
||||
const attachment = db.attachments?.attachment(hash);
|
||||
export async function checkAttachment(hash) {
|
||||
const attachment = db.attachments.attachment(hash);
|
||||
if (!attachment) return { failed: "Attachment not found." };
|
||||
|
||||
try {
|
||||
const size = await FS.getUploadedFileSize(hash);
|
||||
if (size <= 0) return { failed: "File length is 0." };
|
||||
} catch (e) {
|
||||
return { failed: e instanceof Error ? e.message : "Unknown error." };
|
||||
return { failed: e.message };
|
||||
}
|
||||
return { success: true };
|
||||
}
|
||||
|
||||
const ABYTES = 17;
|
||||
export function getTotalSize(attachments: any[]) {
|
||||
export function getTotalSize(attachments) {
|
||||
let size = 0;
|
||||
for (const attachment of attachments) {
|
||||
for (let attachment of attachments) {
|
||||
size += attachment.length + ABYTES;
|
||||
}
|
||||
return size;
|
||||
@@ -88,12 +88,6 @@ export function closeOpenedDialog() {
|
||||
dialogs.forEach((elem) => elem.remove());
|
||||
}
|
||||
|
||||
export function showAddTagsDialog(noteIds: string[]) {
|
||||
return showDialog("AddTagsDialog", (Dialog, perform) => (
|
||||
<Dialog onClose={(res) => perform(res)} noteIds={noteIds} />
|
||||
));
|
||||
}
|
||||
|
||||
export function showAddNotebookDialog() {
|
||||
return showDialog("AddNotebookDialog", (Dialog, perform) => (
|
||||
<Dialog
|
||||
|
||||
@@ -17,8 +17,8 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { db } from "./db";
|
||||
import { showPasswordDialog } from "./dialog-controller";
|
||||
import { db } from "../common/db";
|
||||
import { showPasswordDialog } from "../common/dialog-controller";
|
||||
import { showToast } from "../utils/toast";
|
||||
|
||||
class Vault {
|
||||
|
||||
@@ -47,7 +47,7 @@ import {
|
||||
} from "../../common/dialog-controller";
|
||||
import { store } from "../../stores/attachment-store";
|
||||
import { db } from "../../common/db";
|
||||
import { saveAttachment } from "../../common/attachments";
|
||||
import { downloadAttachment } from "../../common/attachments";
|
||||
import { reuploadAttachment } from "../editor/picker";
|
||||
import { Multiselect } from "../../common/multi-select";
|
||||
import { Menu } from "../../hooks/use-menu";
|
||||
@@ -56,8 +56,6 @@ import {
|
||||
WebClipMimeType,
|
||||
PDFMimeType
|
||||
} from "@notesnook/core/utils/filename";
|
||||
import { useEffect, useState } from "react";
|
||||
import { AppEventManager, AppEvents } from "../../common/app-events";
|
||||
|
||||
const FILE_ICONS: Record<string, Icon> = {
|
||||
"image/": FileImage,
|
||||
@@ -77,12 +75,6 @@ function getFileIcon(type: string) {
|
||||
return FileGeneral;
|
||||
}
|
||||
|
||||
type AttachmentProgressStatus = {
|
||||
type: "download" | "upload";
|
||||
loaded: number;
|
||||
total: number;
|
||||
};
|
||||
|
||||
type AttachmentProps = {
|
||||
attachment: any;
|
||||
isSelected?: boolean;
|
||||
@@ -95,31 +87,6 @@ export function Attachment({
|
||||
onSelected,
|
||||
compact
|
||||
}: AttachmentProps) {
|
||||
const [status, setStatus] = useState<AttachmentProgressStatus>();
|
||||
|
||||
useEffect(() => {
|
||||
const event = AppEventManager.subscribe(
|
||||
AppEvents.UPDATE_ATTACHMENT_PROGRESS,
|
||||
(progress: any) => {
|
||||
if (progress.hash === attachment.metadata.hash) {
|
||||
const percent = Math.round((progress.loaded / progress.total) * 100);
|
||||
setStatus(
|
||||
percent < 100
|
||||
? {
|
||||
type: progress.type,
|
||||
loaded: progress.loaded,
|
||||
total: progress.total
|
||||
}
|
||||
: undefined
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
return () => {
|
||||
event.unsubscribe();
|
||||
};
|
||||
}, [attachment.metadata.hash]);
|
||||
|
||||
const Icon = getFileIcon(attachment.metadata.type);
|
||||
return (
|
||||
<Box
|
||||
@@ -128,11 +95,9 @@ export function Attachment({
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
Menu.openMenu(AttachmentMenuItems, {
|
||||
attachment,
|
||||
status
|
||||
attachment
|
||||
});
|
||||
}}
|
||||
onClick={onSelected}
|
||||
>
|
||||
{!compact && (
|
||||
<td>
|
||||
@@ -146,17 +111,12 @@ export function Attachment({
|
||||
</td>
|
||||
)}
|
||||
<td>
|
||||
<Flex
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
maxWidth: compact ? 180 : "95%"
|
||||
}}
|
||||
>
|
||||
{status ? (
|
||||
status.type === "download" ? (
|
||||
<Download size={16} color="primary" />
|
||||
<Flex sx={{ alignItems: "center" }}>
|
||||
{attachment.status ? (
|
||||
attachment.status.type === "download" ? (
|
||||
<Download size={16} />
|
||||
) : (
|
||||
<Uploading size={16} color="primary" />
|
||||
<Uploading size={16} />
|
||||
)
|
||||
) : attachment.failed ? (
|
||||
<AttachmentError
|
||||
@@ -174,6 +134,7 @@ export function Attachment({
|
||||
sx={{
|
||||
ml: 1,
|
||||
whiteSpace: "nowrap",
|
||||
maxWidth: compact ? 180 : "80%",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis"
|
||||
}}
|
||||
@@ -201,10 +162,11 @@ export function Attachment({
|
||||
/>
|
||||
)}
|
||||
</Text>
|
||||
<Text as="td" variant="body" sx={{ color: status ? "primary" : "text" }}>
|
||||
{status ? (
|
||||
<Text as="td" variant="body">
|
||||
{attachment.status ? (
|
||||
<>
|
||||
{formatBytes(status.loaded, 1)}/{formatBytes(status.total, 1)}
|
||||
{formatBytes(attachment.status.loaded, 1)}/
|
||||
{formatBytes(attachment.status.total, 1)}
|
||||
</>
|
||||
) : (
|
||||
formatBytes(attachment.length, compact ? 1 : 2)
|
||||
@@ -226,7 +188,6 @@ export function Attachment({
|
||||
|
||||
type MenuActionParams = {
|
||||
attachment: any;
|
||||
status: AttachmentProgressStatus;
|
||||
};
|
||||
|
||||
type MenuItemValue<T> = T | ((options: MenuActionParams) => T);
|
||||
@@ -294,25 +255,25 @@ const AttachmentMenuItems: MenuItem[] = [
|
||||
},
|
||||
{
|
||||
key: "download",
|
||||
title: ({ status }) =>
|
||||
status?.type === "download" ? "Cancel download" : "Download",
|
||||
title: ({ attachment }) =>
|
||||
attachment.status?.type === "download" ? "Cancel download" : "Download",
|
||||
icon: Download,
|
||||
disabled: ({ attachment }) =>
|
||||
!attachment.dateUploaded ? "This attachment is not uploaded yet." : false,
|
||||
onClick: async ({ attachment, status }) => {
|
||||
const isDownloading = status?.type === "download";
|
||||
onClick: async ({ attachment }) => {
|
||||
const isDownloading = attachment.status?.type === "download";
|
||||
if (isDownloading) {
|
||||
await db.fs.cancel(attachment.metadata.hash, "download");
|
||||
} else await saveAttachment(attachment.metadata.hash);
|
||||
} else await downloadAttachment(attachment.metadata.hash);
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "reupload",
|
||||
title: ({ status }) =>
|
||||
status?.type === "upload" ? "Cancel upload" : "Reupload",
|
||||
title: ({ attachment }) =>
|
||||
attachment.status?.type === "upload" ? "Cancel upload" : "Reupload",
|
||||
icon: Reupload,
|
||||
onClick: async ({ attachment, status }) => {
|
||||
const isDownloading = status?.type === "upload";
|
||||
onClick: async ({ attachment }) => {
|
||||
const isDownloading = attachment.status?.type === "upload";
|
||||
if (isDownloading) {
|
||||
await db.fs.cancel(attachment.metadata.hash, "upload");
|
||||
} else
|
||||
|
||||
@@ -1,220 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { Flex, Text } from "@theme-ui/components";
|
||||
import * as Icon from "../icons";
|
||||
import { db } from "../../common/db";
|
||||
import Dialog from "./dialog";
|
||||
import { useStore, store } from "../../stores/tag-store";
|
||||
import { store as notestore } from "../../stores/note-store";
|
||||
import { Perform } from "../../common/dialog-controller";
|
||||
import { FilteredList } from "../filtered-list";
|
||||
|
||||
type SelectedReference = {
|
||||
id: string;
|
||||
new: boolean;
|
||||
op: "add" | "remove";
|
||||
};
|
||||
|
||||
type Item = {
|
||||
id: string;
|
||||
type: "tag" | "header";
|
||||
title: string;
|
||||
};
|
||||
type Tag = Item & { noteIds: string[] };
|
||||
|
||||
export type AddTagsDialogProps = {
|
||||
onClose: Perform;
|
||||
noteIds: string[];
|
||||
};
|
||||
|
||||
function AddTagsDialog(props: AddTagsDialogProps) {
|
||||
const { onClose, noteIds } = props;
|
||||
|
||||
const refreshTags = useStore((store) => store.refresh);
|
||||
const tags = useStore((store) => store.tags);
|
||||
|
||||
useEffect(() => {
|
||||
refreshTags();
|
||||
}, [refreshTags]);
|
||||
|
||||
const [selected, setSelected] = useState<SelectedReference[]>([]);
|
||||
|
||||
const getAllTags = useCallback(() => {
|
||||
refreshTags();
|
||||
return (store.get().tags as Item[]).filter((a) => a.type !== "header");
|
||||
}, [refreshTags]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!tags) return;
|
||||
|
||||
setSelected((s) => {
|
||||
const selected = s.slice();
|
||||
for (const tag of tags as Tag[]) {
|
||||
if (tag.type === "header") continue;
|
||||
if (selected.findIndex((a) => a.id === tag.id) > -1) continue;
|
||||
if (tagHasNotes(tag, noteIds)) {
|
||||
selected.push({
|
||||
id: tag.id,
|
||||
op: "add",
|
||||
new: false
|
||||
});
|
||||
}
|
||||
}
|
||||
return selected;
|
||||
});
|
||||
}, [noteIds, tags, setSelected]);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isOpen={true}
|
||||
title={"Add tags"}
|
||||
description={`Add tags to multiple notes at once`}
|
||||
onClose={() => onClose(false)}
|
||||
width={450}
|
||||
positiveButton={{
|
||||
text: "Done",
|
||||
onClick: async () => {
|
||||
for (const id of noteIds) {
|
||||
for (const item of selected) {
|
||||
if (item.op === "add") await db.notes?.note(id).tag(item.id);
|
||||
else await db.notes?.note(id).untag(item.id);
|
||||
}
|
||||
}
|
||||
notestore.refresh();
|
||||
onClose(true);
|
||||
}
|
||||
}}
|
||||
negativeButton={{
|
||||
text: "Cancel",
|
||||
onClick: () => onClose(false)
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
mt={1}
|
||||
sx={{ overflowY: "hidden", flexDirection: "column" }}
|
||||
data-test-id="tag-list"
|
||||
>
|
||||
<FilteredList
|
||||
items={getAllTags}
|
||||
placeholders={{
|
||||
empty: "Add a new tag",
|
||||
filter: "Search or add a new tag"
|
||||
}}
|
||||
filter={(tags, query) => db.lookup?.tags(tags, query) || []}
|
||||
onCreateNewItem={async (title) => {
|
||||
const tag = await db.tags?.add(title);
|
||||
setSelected((selected) => [
|
||||
...selected,
|
||||
{ id: tag.id, new: true, op: "add" }
|
||||
]);
|
||||
}}
|
||||
renderItem={(tag, _index) => {
|
||||
const selectedTag = selected.find((item) => item.id === tag.id);
|
||||
return (
|
||||
<TagItem
|
||||
key={tag.id}
|
||||
tag={tag}
|
||||
selected={selectedTag ? selectedTag.op : false}
|
||||
onSelect={() => {
|
||||
setSelected((selected) => {
|
||||
const copy = selected.slice();
|
||||
const index = copy.findIndex((item) => item.id === tag.id);
|
||||
const isNew = copy[index] && copy[index].new;
|
||||
if (isNew) {
|
||||
copy.splice(index, 1);
|
||||
} else if (index > -1) {
|
||||
copy[index] = {
|
||||
...copy[index],
|
||||
op: copy[index].op === "add" ? "remove" : "add"
|
||||
};
|
||||
} else {
|
||||
copy.push({ id: tag.id, new: true, op: "add" });
|
||||
}
|
||||
return copy;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
function TagItem(props: {
|
||||
tag: Item;
|
||||
selected: boolean | SelectedReference["op"];
|
||||
onSelect: () => void;
|
||||
}) {
|
||||
const { tag, selected, onSelect } = props;
|
||||
|
||||
return (
|
||||
<Flex
|
||||
as="li"
|
||||
data-test-id="tag"
|
||||
sx={{
|
||||
cursor: "pointer",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
bg: "bgSecondary",
|
||||
borderRadius: "default",
|
||||
p: 1,
|
||||
background: "bgSecondary"
|
||||
}}
|
||||
onClick={onSelect}
|
||||
>
|
||||
<Flex sx={{ alignItems: "center" }}>
|
||||
<SelectedCheck size={20} selected={selected} />
|
||||
<Text
|
||||
className="title"
|
||||
data-test-id="notebook-title"
|
||||
variant="subtitle"
|
||||
sx={{ fontWeight: "body", color: "text" }}
|
||||
>
|
||||
#{tag.title}
|
||||
</Text>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default AddTagsDialog;
|
||||
|
||||
function SelectedCheck({
|
||||
selected,
|
||||
size = 20
|
||||
}: {
|
||||
selected: SelectedReference["op"] | boolean;
|
||||
size?: number;
|
||||
}) {
|
||||
return selected === "add" ? (
|
||||
<Icon.CheckCircleOutline size={size} sx={{ mr: 1 }} color="primary" />
|
||||
) : selected === "remove" ? (
|
||||
<Icon.CheckRemove size={size} sx={{ mr: 1 }} color="error" />
|
||||
) : (
|
||||
<Icon.CircleEmpty size={size} sx={{ mr: 1, opacity: 0.4 }} />
|
||||
);
|
||||
}
|
||||
|
||||
function tagHasNotes(tag: Tag, noteIds: string[]) {
|
||||
return tag.noteIds.some((id) => noteIds.indexOf(id) > -1);
|
||||
}
|
||||
@@ -169,7 +169,7 @@ function AttachmentsDialog({ onClose }: AttachmentsDialogProps) {
|
||||
pt: 2,
|
||||
overflowY: "hidden",
|
||||
overflow: "hidden",
|
||||
table: { width: "100%", tableLayout: "fixed" },
|
||||
table: { width: "100%" },
|
||||
"tbody::before": {
|
||||
content: `''`,
|
||||
display: "block",
|
||||
@@ -220,7 +220,6 @@ function AttachmentsDialog({ onClose }: AttachmentsDialogProps) {
|
||||
return (
|
||||
<Attachment
|
||||
{...props}
|
||||
key={attachment.id}
|
||||
attachment={attachment}
|
||||
isSelected={selected.indexOf(attachment.id) > -1}
|
||||
onSelected={() => {
|
||||
@@ -273,10 +272,10 @@ function AttachmentsDialog({ onClose }: AttachmentsDialogProps) {
|
||||
</Label>
|
||||
</Text>
|
||||
{[
|
||||
{ id: "name", title: "Name", width: "65%" },
|
||||
{ id: "status", width: "24px" },
|
||||
{ id: "size", title: "Size", width: "15%" },
|
||||
{ id: "dateUploaded", title: "Date uploaded", width: "20%" }
|
||||
{ id: "name", title: "Name" },
|
||||
{ id: "status" },
|
||||
{ id: "size", title: "Size" },
|
||||
{ id: "dateUploaded", title: "Date uploaded" }
|
||||
].map((column) =>
|
||||
!column.title ? (
|
||||
<th key={column.id} />
|
||||
@@ -285,7 +284,7 @@ function AttachmentsDialog({ onClose }: AttachmentsDialogProps) {
|
||||
as="th"
|
||||
key={column.id}
|
||||
sx={{
|
||||
width: column.width,
|
||||
width: "auto",
|
||||
cursor: "pointer",
|
||||
px: 1,
|
||||
mb: 2,
|
||||
|
||||
@@ -91,19 +91,14 @@ const features: Record<FeatureKeys, Feature> = {
|
||||
]
|
||||
: [
|
||||
{
|
||||
title: "Attachments preview",
|
||||
title: "Billing history",
|
||||
subtitle:
|
||||
"You can now preview PDFs & Images directly inside Notesnook."
|
||||
"You can now get a list of all the transactions you have made, their amount and when they were made."
|
||||
},
|
||||
{
|
||||
title: "New attachments manager",
|
||||
title: "Request refund",
|
||||
subtitle:
|
||||
"The new attachments manager makes is much easier to view & interact with your attachments. It also allows you to download all (or some) of your attachments."
|
||||
},
|
||||
{
|
||||
title: "Assign tags to multiple notes",
|
||||
subtitle:
|
||||
"You can now easily tag multiple notes without first opening them."
|
||||
"You can now request refunds directly from inside the app. No need for emails etc. Go to Settings > Request refund to send your request for a refund."
|
||||
}
|
||||
],
|
||||
cta: {
|
||||
|
||||
@@ -60,7 +60,6 @@ const LanguageSelectorDialog = React.lazy(
|
||||
const BillingHistoryDialog = React.lazy(
|
||||
() => import("./billing-history-dialog")
|
||||
);
|
||||
const AddTagsDialog = React.lazy(() => import("./add-tags-dialog"));
|
||||
|
||||
export const Dialogs = {
|
||||
AddNotebookDialog,
|
||||
@@ -90,6 +89,5 @@ export const Dialogs = {
|
||||
ReminderPreviewDialog,
|
||||
EmailChangeDialog,
|
||||
LanguageSelectorDialog,
|
||||
BillingHistoryDialog,
|
||||
AddTagsDialog
|
||||
BillingHistoryDialog
|
||||
};
|
||||
|
||||
@@ -17,11 +17,12 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { ChangeEvent, useCallback, useEffect, useRef, useState } from "react";
|
||||
import { Box, Button, Flex, Input, Text } from "@theme-ui/components";
|
||||
import * as Icon from "../icons";
|
||||
import { db } from "../../common/db";
|
||||
import Dialog from "./dialog";
|
||||
import Field from "../field";
|
||||
import { useStore, store } from "../../stores/notebook-store";
|
||||
import { store as notestore } from "../../stores/note-store";
|
||||
import { Perform } from "../../common/dialog-controller";
|
||||
@@ -29,7 +30,6 @@ import { showToast } from "../../utils/toast";
|
||||
import { pluralize } from "../../utils/string";
|
||||
import { isMac } from "../../utils/platform";
|
||||
import create from "zustand";
|
||||
import { FilteredList } from "../filtered-list";
|
||||
|
||||
type MoveDialogProps = { onClose: Perform; noteIds: string[] };
|
||||
type NotebookReference = {
|
||||
@@ -59,7 +59,7 @@ interface ISelectionStore {
|
||||
export const useSelectionStore = create<ISelectionStore>((set) => ({
|
||||
selected: [],
|
||||
isMultiselect: false,
|
||||
setSelected: (selected) => set({ selected: selected.slice() }),
|
||||
setSelected: (selected) => set({ selected }),
|
||||
setIsMultiselect: (isMultiselect) => set({ isMultiselect })
|
||||
}));
|
||||
|
||||
@@ -193,7 +193,7 @@ function MoveDialog({ onClose, noteIds }: MoveDialogProps) {
|
||||
sx={{ overflowY: "hidden", flexDirection: "column" }}
|
||||
data-test-id="notebook-list"
|
||||
>
|
||||
<FilteredList
|
||||
<FilteredTree
|
||||
placeholders={{
|
||||
empty: "Add a new notebook",
|
||||
filter: "Search or add a new notebook"
|
||||
@@ -423,6 +423,127 @@ function TopicItem(props: { topic: Topic }) {
|
||||
|
||||
export default MoveDialog;
|
||||
|
||||
type FilteredTreeProps<T extends Item> = {
|
||||
placeholders: { filter: string; empty: string };
|
||||
items: () => T[];
|
||||
filter: (items: T[], query: string) => T[];
|
||||
onCreateNewItem: (title: string) => Promise<void>;
|
||||
renderItem: (
|
||||
item: T,
|
||||
index: number,
|
||||
refresh: () => void,
|
||||
isSearching: boolean
|
||||
) => JSX.Element;
|
||||
};
|
||||
|
||||
function FilteredTree<T extends Item>(props: FilteredTreeProps<T>) {
|
||||
const {
|
||||
items: _items,
|
||||
filter,
|
||||
onCreateNewItem,
|
||||
placeholders,
|
||||
renderItem
|
||||
} = props;
|
||||
|
||||
const [items, setItems] = useState<T[]>([]);
|
||||
const [query, setQuery] = useState<string>();
|
||||
const noItemsFound = items.length <= 0 && query && query.length > 0;
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const refresh = useCallback(() => {
|
||||
setItems(_items());
|
||||
}, [_items]);
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
}, [refresh]);
|
||||
|
||||
const _filter = useCallback(
|
||||
(query) => {
|
||||
setItems(() => {
|
||||
const items = _items();
|
||||
if (!query) {
|
||||
return items;
|
||||
}
|
||||
return filter(items, query);
|
||||
});
|
||||
setQuery(query);
|
||||
},
|
||||
[_items, filter]
|
||||
);
|
||||
|
||||
const _createNewItem = useCallback(
|
||||
async (title) => {
|
||||
await onCreateNewItem(title);
|
||||
refresh();
|
||||
setQuery(undefined);
|
||||
if (inputRef.current) inputRef.current.value = "";
|
||||
},
|
||||
[inputRef, refresh, onCreateNewItem]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Field
|
||||
inputRef={inputRef}
|
||||
data-test-id={"filter-input"}
|
||||
autoFocus
|
||||
placeholder={
|
||||
items.length <= 0 ? placeholders.empty : placeholders.filter
|
||||
}
|
||||
onChange={(e: ChangeEvent) =>
|
||||
_filter((e.target as HTMLInputElement).value)
|
||||
}
|
||||
onKeyUp={async (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter" && noItemsFound) {
|
||||
await _createNewItem(query);
|
||||
}
|
||||
}}
|
||||
action={
|
||||
items.length <= 0
|
||||
? {
|
||||
icon: Icon.Plus,
|
||||
onClick: async () => await _createNewItem(query)
|
||||
}
|
||||
: { icon: Icon.Search, onClick: () => _filter(query) }
|
||||
}
|
||||
/>
|
||||
<Flex
|
||||
as="ul"
|
||||
mt={1}
|
||||
sx={{
|
||||
overflowY: "hidden",
|
||||
listStyle: "none",
|
||||
m: 0,
|
||||
p: 0,
|
||||
gap: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column"
|
||||
}}
|
||||
>
|
||||
{noItemsFound && (
|
||||
<Button
|
||||
variant={"secondary"}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
py: 2
|
||||
}}
|
||||
onClick={async () => {
|
||||
await _createNewItem(query);
|
||||
}}
|
||||
>
|
||||
<Text variant={"body"}>{`Add "${query}"`}</Text>
|
||||
<Icon.Plus size={16} color="primary" />
|
||||
</Button>
|
||||
)}
|
||||
{items.map((item, index) => renderItem(item, index, refresh, !!query))}
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function SelectedCheck({
|
||||
item,
|
||||
size = 20
|
||||
|
||||
@@ -72,7 +72,7 @@ function Header({ readonly }) {
|
||||
}
|
||||
export default Header;
|
||||
|
||||
export function Autosuggest({
|
||||
function Autosuggest({
|
||||
sessionId,
|
||||
filter,
|
||||
onRemove,
|
||||
|
||||
@@ -24,8 +24,7 @@ import {
|
||||
useRef,
|
||||
PropsWithChildren
|
||||
} from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Box, Button, Flex, Progress, Text } from "@theme-ui/components";
|
||||
import { Box, Button, Flex, Text } from "@theme-ui/components";
|
||||
import Properties from "../properties";
|
||||
import { useStore, store as editorstore } from "../../stores/editor-store";
|
||||
import {
|
||||
@@ -41,7 +40,7 @@ import Header from "./header";
|
||||
import { Attachment } from "../icons";
|
||||
import { useEditorInstance } from "./context";
|
||||
import { attachFile, AttachmentProgress, insertAttachment } from "./picker";
|
||||
import { saveAttachment, downloadAttachment } from "../../common/attachments";
|
||||
import { downloadAttachment } from "../../common/attachments";
|
||||
import { EV, EVENTS } from "@notesnook/core/common";
|
||||
import { db } from "../../common/db";
|
||||
import useMobile from "../../hooks/use-mobile";
|
||||
@@ -50,11 +49,6 @@ import useTablet from "../../hooks/use-tablet";
|
||||
import Config from "../../utils/config";
|
||||
import { AnimatedFlex } from "../animated";
|
||||
import { EditorLoader } from "../loaders/editor-loader";
|
||||
import { Lightbox } from "../lightbox";
|
||||
import ThemeProviderWrapper from "../theme-provider";
|
||||
import { Allotment } from "allotment";
|
||||
import { PdfPreview } from "../pdf-preview";
|
||||
import { showToast } from "../../utils/toast";
|
||||
|
||||
type PreviewSession = {
|
||||
content: { data: string; type: string };
|
||||
@@ -62,11 +56,6 @@ type PreviewSession = {
|
||||
dateEdited: number;
|
||||
};
|
||||
|
||||
type DocumentPreview = {
|
||||
url?: string;
|
||||
hash: string;
|
||||
};
|
||||
|
||||
function onEditorChange(noteId: string, sessionId: string, content: string) {
|
||||
if (!content) return;
|
||||
|
||||
@@ -90,9 +79,7 @@ export default function EditorManager({
|
||||
// stored in refs. Update this value to trigger an
|
||||
// update.
|
||||
const [timestamp, setTimestamp] = useState<number>(0);
|
||||
|
||||
const lastSavedTime = useRef<number>(0);
|
||||
const [docPreview, setDocPreview] = useState<DocumentPreview>();
|
||||
|
||||
const previewSession = useRef<PreviewSession>();
|
||||
const [dropRef, overlayRef] = useDragOverlay();
|
||||
@@ -113,7 +100,7 @@ export default function EditorManager({
|
||||
async (item?: Record<string, string | number>) => {
|
||||
if (
|
||||
!item ||
|
||||
lastSavedTime.current >= (item.dateEdited as number) ||
|
||||
lastSavedTime.current >= item.dateEdited ||
|
||||
isPreviewSession ||
|
||||
!appstore.get().isRealtimeSyncEnabled
|
||||
)
|
||||
@@ -198,126 +185,48 @@ export default function EditorManager({
|
||||
openSession(noteId);
|
||||
}, [noteId]);
|
||||
|
||||
return (
|
||||
<Allotment
|
||||
proportionalLayout={true}
|
||||
onDragEnd={(sizes) => {
|
||||
Config.set("editor:panesize", sizes[1]);
|
||||
}}
|
||||
>
|
||||
<Allotment.Pane className="editor-pane">
|
||||
<Flex
|
||||
ref={dropRef}
|
||||
id="editorContainer"
|
||||
sx={{
|
||||
position: "relative",
|
||||
alignSelf: "stretch",
|
||||
overflow: "hidden",
|
||||
flex: 1,
|
||||
flexDirection: "column"
|
||||
}}
|
||||
>
|
||||
{previewSession.current && (
|
||||
<PreviewModeNotice
|
||||
{...previewSession.current}
|
||||
onDiscard={() => openSession(noteId)}
|
||||
/>
|
||||
)}
|
||||
<Editor
|
||||
nonce={timestamp}
|
||||
content={
|
||||
previewSession.current?.content?.data ||
|
||||
editorstore.get().session?.content?.data
|
||||
}
|
||||
onPreviewDocument={(url) => setDocPreview(url)}
|
||||
onContentChange={() => (lastSavedTime.current = Date.now())}
|
||||
options={{
|
||||
readonly: isReadonly || isPreviewSession,
|
||||
onRequestFocus: () => toggleProperties(false),
|
||||
onLoadMedia: loadMedia,
|
||||
focusMode: isFocusMode,
|
||||
isMobile: isMobile || isTablet
|
||||
}}
|
||||
/>
|
||||
|
||||
{arePropertiesVisible && (
|
||||
<Properties
|
||||
onOpenPreviewSession={async (session: PreviewSession) => {
|
||||
previewSession.current = session;
|
||||
setTimestamp(Date.now());
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<DropZone overlayRef={overlayRef} />
|
||||
</Flex>
|
||||
</Allotment.Pane>
|
||||
{docPreview && (
|
||||
<Allotment.Pane
|
||||
minSize={450}
|
||||
preferredSize={Config.get("editor:panesize", 500)}
|
||||
>
|
||||
{docPreview.url ? (
|
||||
<Flex
|
||||
id="editorSidebar"
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
overflow: "hidden",
|
||||
borderLeft: "1px solid var(--border)",
|
||||
height: "100%"
|
||||
}}
|
||||
>
|
||||
<PdfPreview
|
||||
fileUrl={docPreview.url}
|
||||
hash={docPreview.hash}
|
||||
onClose={() => setDocPreview(undefined)}
|
||||
/>
|
||||
</Flex>
|
||||
) : (
|
||||
<DownloadAttachmentProgress hash={docPreview.hash} />
|
||||
)}
|
||||
</Allotment.Pane>
|
||||
)}
|
||||
</Allotment>
|
||||
);
|
||||
}
|
||||
|
||||
type DownloadAttachmentProgressProps = {
|
||||
hash: string;
|
||||
};
|
||||
function DownloadAttachmentProgress(props: DownloadAttachmentProgressProps) {
|
||||
const { hash } = props;
|
||||
|
||||
const [progress, setProgress] = useState(0);
|
||||
useEffect(() => {
|
||||
const event = AppEventManager.subscribe(
|
||||
AppEvents.UPDATE_ATTACHMENT_PROGRESS,
|
||||
(progress: AttachmentProgress) => {
|
||||
if (progress.hash === hash) {
|
||||
setProgress(Math.round((progress.loaded / progress.total) * 100));
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return () => {
|
||||
event.unsubscribe();
|
||||
};
|
||||
}, [hash]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
ref={dropRef}
|
||||
id="editorContainer"
|
||||
sx={{
|
||||
height: "100%",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
position: "relative",
|
||||
alignSelf: "stretch",
|
||||
overflow: "hidden",
|
||||
flex: 1,
|
||||
flexDirection: "column"
|
||||
}}
|
||||
>
|
||||
<Text variant="title">Downloading attachment ({progress}%)</Text>
|
||||
<Progress
|
||||
value={progress}
|
||||
max={100}
|
||||
sx={{ width: ["90%", "35%"], mt: 1 }}
|
||||
{previewSession.current && (
|
||||
<PreviewModeNotice
|
||||
{...previewSession.current}
|
||||
onDiscard={() => openSession(noteId)}
|
||||
/>
|
||||
)}
|
||||
<Editor
|
||||
nonce={timestamp}
|
||||
content={
|
||||
previewSession.current?.content?.data ||
|
||||
editorstore.get().session?.content?.data
|
||||
}
|
||||
onContentChange={() => (lastSavedTime.current = Date.now())}
|
||||
options={{
|
||||
readonly: isReadonly || isPreviewSession,
|
||||
onRequestFocus: () => toggleProperties(false),
|
||||
onLoadMedia: loadMedia,
|
||||
focusMode: isFocusMode,
|
||||
isMobile: isMobile || isTablet
|
||||
}}
|
||||
/>
|
||||
{arePropertiesVisible && (
|
||||
<Properties
|
||||
onOpenPreviewSession={async (session: PreviewSession) => {
|
||||
previewSession.current = session;
|
||||
setTimestamp(Date.now());
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<DropZone overlayRef={overlayRef} />
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
@@ -335,10 +244,9 @@ type EditorProps = {
|
||||
nonce?: number;
|
||||
options?: EditorOptions;
|
||||
onContentChange?: () => void;
|
||||
onPreviewDocument?: (preview: DocumentPreview) => void;
|
||||
};
|
||||
export function Editor(props: EditorProps) {
|
||||
const { content, nonce, options, onContentChange, onPreviewDocument } = props;
|
||||
const { content, nonce, options, onContentChange } = props;
|
||||
const { readonly, headless, onLoadMedia, isMobile } = options || {
|
||||
headless: false,
|
||||
readonly: false,
|
||||
@@ -406,35 +314,9 @@ export function Editor(props: EditorProps) {
|
||||
}}
|
||||
onContentChange={onContentChange}
|
||||
onChange={onEditorChange}
|
||||
onDownloadAttachment={(attachment) => saveAttachment(attachment.hash)}
|
||||
onPreviewAttachment={async ({ hash, dataurl }) => {
|
||||
const attachment = db.attachments?.attachment(hash);
|
||||
if (attachment && attachment.metadata.type.startsWith("image/")) {
|
||||
const container = document.getElementById("dialogContainer");
|
||||
if (!(container instanceof HTMLElement)) return;
|
||||
|
||||
dataurl = dataurl || (await downloadAttachment(hash, "base64"));
|
||||
if (!dataurl)
|
||||
return showToast("error", "This image cannot be previewed.");
|
||||
|
||||
ReactDOM.render(
|
||||
<ThemeProviderWrapper>
|
||||
<Lightbox
|
||||
image={dataurl}
|
||||
onClose={() => {
|
||||
ReactDOM.unmountComponentAtNode(container);
|
||||
}}
|
||||
/>
|
||||
</ThemeProviderWrapper>,
|
||||
container
|
||||
);
|
||||
} else if (attachment && onPreviewDocument) {
|
||||
onPreviewDocument({ hash });
|
||||
const blob = await downloadAttachment(hash, "blob");
|
||||
if (!blob) return;
|
||||
onPreviewDocument({ url: URL.createObjectURL(blob), hash });
|
||||
}
|
||||
}}
|
||||
onDownloadAttachment={(attachment) =>
|
||||
downloadAttachment(attachment.hash)
|
||||
}
|
||||
onInsertAttachment={(type) => {
|
||||
const mime = type === "file" ? "*/*" : "image/*";
|
||||
insertAttachment(mime).then((file) => {
|
||||
@@ -526,6 +408,7 @@ function EditorChrome(
|
||||
</AnimatedFlex>
|
||||
</Flex>
|
||||
</FlexScrollContainer>
|
||||
|
||||
{isMobile && (
|
||||
<Box
|
||||
id="editorToolbar"
|
||||
|
||||
@@ -155,7 +155,7 @@ export type AttachmentProgress = {
|
||||
export type Attachment = {
|
||||
hash: string;
|
||||
filename: string;
|
||||
mime: string;
|
||||
type: string;
|
||||
size: number;
|
||||
dataurl?: string;
|
||||
};
|
||||
@@ -206,7 +206,7 @@ async function addAttachment(
|
||||
return {
|
||||
hash: hash,
|
||||
filename: file.name,
|
||||
mime: file.type,
|
||||
type: file.type,
|
||||
size: file.size,
|
||||
dataurl
|
||||
};
|
||||
|
||||
@@ -64,7 +64,6 @@ type TipTapProps = {
|
||||
onContentChange?: () => void;
|
||||
onInsertAttachment?: (type: AttachmentType) => void;
|
||||
onDownloadAttachment?: (attachment: Attachment) => void;
|
||||
onPreviewAttachment?: (attachment: Attachment) => void;
|
||||
onAttachFile?: (file: File) => void;
|
||||
onFocus?: () => void;
|
||||
content?: string;
|
||||
@@ -110,7 +109,6 @@ function TipTap(props: TipTapProps) {
|
||||
onChange,
|
||||
onInsertAttachment,
|
||||
onDownloadAttachment,
|
||||
onPreviewAttachment,
|
||||
onAttachFile,
|
||||
onContentChange,
|
||||
onFocus = () => {},
|
||||
@@ -255,10 +253,6 @@ function TipTap(props: TipTapProps) {
|
||||
onDownloadAttachment?.(attachment);
|
||||
return true;
|
||||
},
|
||||
onPreviewAttachment(_editor, attachment) {
|
||||
onPreviewAttachment?.(attachment);
|
||||
return true;
|
||||
},
|
||||
onOpenLink: (url) => {
|
||||
window.open(url, "_blank");
|
||||
return true;
|
||||
|
||||
@@ -1,151 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { ChangeEvent, useCallback, useEffect, useRef, useState } from "react";
|
||||
import Field from "../field";
|
||||
import { Plus, Search } from "../icons";
|
||||
import { Button, Flex, Text } from "@theme-ui/components";
|
||||
|
||||
type FilterableItem = {
|
||||
id: string;
|
||||
title: string;
|
||||
};
|
||||
|
||||
type FilteredListProps<T extends FilterableItem> = {
|
||||
placeholders: { filter: string; empty: string };
|
||||
items: () => T[];
|
||||
filter: (items: T[], query: string) => T[];
|
||||
onCreateNewItem: (title: string) => Promise<void>;
|
||||
renderItem: (
|
||||
item: T,
|
||||
index: number,
|
||||
refresh: () => void,
|
||||
isSearching: boolean
|
||||
) => JSX.Element;
|
||||
};
|
||||
|
||||
export function FilteredList<T extends FilterableItem>(
|
||||
props: FilteredListProps<T>
|
||||
) {
|
||||
const {
|
||||
items: _items,
|
||||
filter,
|
||||
onCreateNewItem,
|
||||
placeholders,
|
||||
renderItem
|
||||
} = props;
|
||||
|
||||
const [items, setItems] = useState<T[]>([]);
|
||||
const [query, setQuery] = useState<string>();
|
||||
const noItemsFound = items.length <= 0 && query && query.length > 0;
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const refresh = useCallback(() => {
|
||||
setItems(_items());
|
||||
}, [_items]);
|
||||
|
||||
useEffect(() => {
|
||||
refresh();
|
||||
}, [refresh]);
|
||||
|
||||
const _filter = useCallback(
|
||||
(query) => {
|
||||
setItems(() => {
|
||||
const items = _items();
|
||||
if (!query) {
|
||||
return items;
|
||||
}
|
||||
return filter(items, query);
|
||||
});
|
||||
setQuery(query);
|
||||
},
|
||||
[_items, filter]
|
||||
);
|
||||
|
||||
const _createNewItem = useCallback(
|
||||
async (title) => {
|
||||
await onCreateNewItem(title);
|
||||
refresh();
|
||||
setQuery(undefined);
|
||||
if (inputRef.current) inputRef.current.value = "";
|
||||
},
|
||||
[inputRef, refresh, onCreateNewItem]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Field
|
||||
inputRef={inputRef}
|
||||
data-test-id={"filter-input"}
|
||||
autoFocus
|
||||
placeholder={
|
||||
items.length <= 0 ? placeholders.empty : placeholders.filter
|
||||
}
|
||||
onChange={(e: ChangeEvent) =>
|
||||
_filter((e.target as HTMLInputElement).value)
|
||||
}
|
||||
onKeyUp={async (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter" && noItemsFound) {
|
||||
await _createNewItem(query);
|
||||
}
|
||||
}}
|
||||
action={
|
||||
items.length <= 0
|
||||
? {
|
||||
icon: Plus,
|
||||
onClick: async () => await _createNewItem(query)
|
||||
}
|
||||
: { icon: Search, onClick: () => _filter(query) }
|
||||
}
|
||||
/>
|
||||
<Flex
|
||||
as="ul"
|
||||
mt={1}
|
||||
sx={{
|
||||
overflowY: "hidden",
|
||||
listStyle: "none",
|
||||
m: 0,
|
||||
p: 0,
|
||||
gap: 1,
|
||||
display: "flex",
|
||||
flexDirection: "column"
|
||||
}}
|
||||
>
|
||||
{noItemsFound && (
|
||||
<Button
|
||||
variant={"secondary"}
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
py: 2
|
||||
}}
|
||||
onClick={async () => {
|
||||
await _createNewItem(query);
|
||||
}}
|
||||
>
|
||||
<Text variant={"body"}>{`Add "${query}"`}</Text>
|
||||
<Plus size={16} color="primary" />
|
||||
</Button>
|
||||
)}
|
||||
{items.map((item, index) => renderItem(item, index, refresh, !!query))}
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -197,11 +197,7 @@ import {
|
||||
mdiFileVideoOutline,
|
||||
mdiWeb,
|
||||
mdiUploadOutline,
|
||||
mdiLinkOff,
|
||||
mdiMagnifyPlusOutline,
|
||||
mdiMagnifyMinusOutline,
|
||||
mdiRotateRight,
|
||||
mdiRotateLeft
|
||||
mdiLinkOff
|
||||
} from "@mdi/js";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { Theme } from "@notesnook/theme";
|
||||
@@ -494,8 +490,3 @@ export const FileVideo = createIcon(mdiFileVideoOutline);
|
||||
export const FileGeneral = createIcon(mdiFileOutline);
|
||||
export const FileWebClip = createIcon(mdiWeb);
|
||||
export const Unlink = createIcon(mdiLinkOff);
|
||||
export const ZoomIn = createIcon(mdiMagnifyPlusOutline);
|
||||
export const ZoomOut = createIcon(mdiMagnifyMinusOutline);
|
||||
export const RotateCW = createIcon(mdiRotateRight);
|
||||
export const RotateACW = createIcon(mdiRotateLeft);
|
||||
export const Reset = createIcon(mdiRestore);
|
||||
|
||||
@@ -1,416 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Button, Flex, Image } from "@theme-ui/components";
|
||||
import React from "react";
|
||||
import {
|
||||
Close,
|
||||
Icon,
|
||||
Loading,
|
||||
Reset,
|
||||
RotateACW,
|
||||
RotateCW,
|
||||
ZoomIn,
|
||||
ZoomOut
|
||||
} from "../icons";
|
||||
|
||||
const DEFAULT_ZOOM_STEP = 0.3;
|
||||
const DEFAULT_LARGE_ZOOM = 4;
|
||||
|
||||
function getXY(e: React.MouseEvent | React.TouchEvent) {
|
||||
let x = 0;
|
||||
let y = 0;
|
||||
if ("touches" in e && e.touches.length) {
|
||||
x = e.touches[0].pageX;
|
||||
y = e.touches[0].pageY;
|
||||
} else if ("pageX" in e && "pageY" in e) {
|
||||
x = e.pageX;
|
||||
y = e.pageY;
|
||||
}
|
||||
return { x, y };
|
||||
}
|
||||
|
||||
type Image = { url: string; title?: string };
|
||||
export type LightboxProps = {
|
||||
image?: string;
|
||||
// title?: string;
|
||||
zoomStep?: number;
|
||||
images?: Image[];
|
||||
startIndex?: number;
|
||||
keyboardInteraction?: boolean;
|
||||
doubleClickZoom?: number;
|
||||
// showTitle?: boolean;
|
||||
// buttonAlign?: "flex-end" | "flex-start" | "center";
|
||||
allowZoom?: boolean;
|
||||
allowReset?: boolean;
|
||||
allowRotate?: boolean;
|
||||
onNavigateImage?: (index: number) => void;
|
||||
clickOutsideToExit?: boolean;
|
||||
onClose?: (e: React.MouseEvent | KeyboardEvent) => void;
|
||||
};
|
||||
|
||||
export class Lightbox extends React.Component<LightboxProps> {
|
||||
initX = 0;
|
||||
initY = 0;
|
||||
lastX = 0;
|
||||
lastY = 0;
|
||||
_cont = React.createRef<HTMLDivElement>();
|
||||
state = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
zoom: 1,
|
||||
rotate: 0,
|
||||
loading: true,
|
||||
moving: false,
|
||||
current: this.props?.startIndex ?? 0,
|
||||
multi: this.props?.images?.length ? true : false
|
||||
};
|
||||
|
||||
createTransform = (x: number, y: number, zoom: number, rotate: number) =>
|
||||
`translate3d(${x}px,${y}px,0px) scale(${zoom}) rotate(${rotate}deg)`;
|
||||
|
||||
stopSideEffect = (
|
||||
e: React.KeyboardEvent | React.MouseEvent | KeyboardEvent | MouseEvent
|
||||
) => e.stopPropagation();
|
||||
|
||||
getCurrentImage = () => {
|
||||
if (!this.state.multi) return this.props.image ?? "";
|
||||
return this.props.images?.[this.state.current]?.url ?? "";
|
||||
};
|
||||
|
||||
resetZoom = () => this.setState({ x: 0, y: 0, zoom: 1 });
|
||||
shockZoom = (e: React.MouseEvent) => {
|
||||
const {
|
||||
zoomStep = DEFAULT_ZOOM_STEP,
|
||||
allowZoom = true,
|
||||
doubleClickZoom = DEFAULT_LARGE_ZOOM
|
||||
} = this.props;
|
||||
if (!allowZoom || !doubleClickZoom) return false;
|
||||
this.stopSideEffect(e);
|
||||
if (this.state.zoom > 1) return this.resetZoom();
|
||||
const _z =
|
||||
(zoomStep < 1 ? Math.ceil(doubleClickZoom / zoomStep) : zoomStep) *
|
||||
zoomStep;
|
||||
const _xy = getXY(e);
|
||||
const _cbr = this._cont.current?.getBoundingClientRect?.();
|
||||
if (!_cbr) return false;
|
||||
const _ccx = _cbr.x + _cbr.width / 2;
|
||||
const _ccy = _cbr.y + _cbr.height / 2;
|
||||
const x = (_xy.x - _ccx) * -1 * _z;
|
||||
const y = (_xy.y - _ccy) * -1 * _z;
|
||||
this.setState({ x, y, zoom: _z });
|
||||
};
|
||||
navigateImage = (
|
||||
direction: "next" | "prev",
|
||||
e: React.KeyboardEvent | React.MouseEvent | KeyboardEvent | MouseEvent
|
||||
) => {
|
||||
if (!this.props.images) return;
|
||||
|
||||
this.stopSideEffect(e);
|
||||
let current = 0;
|
||||
switch (direction) {
|
||||
case "next":
|
||||
current = this.state.current + 1;
|
||||
break;
|
||||
case "prev":
|
||||
current = this.state.current - 1;
|
||||
break;
|
||||
}
|
||||
if (current >= this.props.images.length) current = 0;
|
||||
else if (current < 0) current = this.props.images.length - 1;
|
||||
this.setState({ current, x: 0, y: 0, zoom: 1, rotate: 0, loading: true });
|
||||
if (typeof this.props.onNavigateImage === "function") {
|
||||
this.props.onNavigateImage(current);
|
||||
}
|
||||
};
|
||||
startMove = (e: React.MouseEvent | React.TouchEvent) => {
|
||||
if (this.state.zoom <= 1) return false;
|
||||
this.setState({ moving: true });
|
||||
const xy = getXY(e);
|
||||
this.initX = xy.x - this.lastX;
|
||||
this.initY = xy.y - this.lastY;
|
||||
};
|
||||
duringMove = (e: React.MouseEvent | React.TouchEvent) => {
|
||||
if (!this.state.moving) return false;
|
||||
const xy = getXY(e);
|
||||
this.lastX = xy.x - this.initX;
|
||||
this.lastY = xy.y - this.initY;
|
||||
this.setState({
|
||||
x: xy.x - this.initX,
|
||||
y: xy.y - this.initY
|
||||
});
|
||||
};
|
||||
endMove = () => this.setState({ moving: false });
|
||||
applyZoom = (type: "in" | "out" | "reset") => {
|
||||
const { zoomStep = DEFAULT_ZOOM_STEP } = this.props;
|
||||
switch (type) {
|
||||
case "in":
|
||||
this.setState({ zoom: this.state.zoom + zoomStep });
|
||||
break;
|
||||
case "out": {
|
||||
const newZoom = this.state.zoom - zoomStep;
|
||||
if (newZoom < 1) break;
|
||||
else if (newZoom === 1) this.setState({ x: 0, y: 0, zoom: 1 });
|
||||
else this.setState({ zoom: newZoom });
|
||||
break;
|
||||
}
|
||||
case "reset":
|
||||
this.resetZoom();
|
||||
break;
|
||||
}
|
||||
};
|
||||
applyRotate = (type: "cw" | "acw") => {
|
||||
switch (type) {
|
||||
case "cw":
|
||||
this.setState({ rotate: this.state.rotate + 90 });
|
||||
break;
|
||||
case "acw":
|
||||
this.setState({ rotate: this.state.rotate - 90 });
|
||||
break;
|
||||
}
|
||||
};
|
||||
reset = (e: React.MouseEvent | KeyboardEvent) => {
|
||||
this.stopSideEffect(e);
|
||||
this.setState({ x: 0, y: 0, zoom: 1, rotate: 0 });
|
||||
};
|
||||
exit = (e: React.MouseEvent | KeyboardEvent) => {
|
||||
if (typeof this.props.onClose === "function") return this.props.onClose(e);
|
||||
console.error(
|
||||
"No Exit function passed on prop: onClose. Clicking the close button will do nothing"
|
||||
);
|
||||
};
|
||||
shouldShowReset = () =>
|
||||
this.state.x ||
|
||||
this.state.y ||
|
||||
this.state.zoom !== 1 ||
|
||||
this.state.rotate !== 0;
|
||||
canvasClick = (e: React.MouseEvent) => {
|
||||
const { clickOutsideToExit = true } = this.props;
|
||||
if (clickOutsideToExit && this.state.zoom <= 1) return this.exit(e);
|
||||
};
|
||||
keyboardNavigation = (e: KeyboardEvent) => {
|
||||
const { allowZoom = true, allowReset = true } = this.props;
|
||||
const { multi, x, y, zoom } = this.state;
|
||||
switch (e.key) {
|
||||
case "ArrowLeft":
|
||||
if (multi && zoom === 1) this.navigateImage("prev", e);
|
||||
else if (zoom > 1) this.setState({ x: x - 20 });
|
||||
break;
|
||||
case "ArrowRight":
|
||||
if (multi && zoom === 1) this.navigateImage("next", e);
|
||||
else if (zoom > 1) this.setState({ x: x + 20 });
|
||||
break;
|
||||
case "ArrowUp":
|
||||
if (zoom > 1) this.setState({ y: y + 20 });
|
||||
break;
|
||||
case "ArrowDown":
|
||||
if (zoom > 1) this.setState({ y: y - 20 });
|
||||
break;
|
||||
case "+":
|
||||
if (allowZoom) this.applyZoom("in");
|
||||
break;
|
||||
case "-":
|
||||
if (allowZoom) this.applyZoom("out");
|
||||
break;
|
||||
case "Escape":
|
||||
if (allowReset && this.shouldShowReset()) this.reset(e);
|
||||
else this.exit(e);
|
||||
break;
|
||||
}
|
||||
};
|
||||
componentDidMount() {
|
||||
document.body.classList.add("lb-open-lightbox");
|
||||
const { keyboardInteraction = true } = this.props;
|
||||
if (keyboardInteraction)
|
||||
document.addEventListener("keyup", this.keyboardNavigation);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
document.body.classList.remove("lb-open-lightbox");
|
||||
const { keyboardInteraction = true } = this.props;
|
||||
if (keyboardInteraction)
|
||||
document.removeEventListener("keyup", this.keyboardNavigation);
|
||||
}
|
||||
|
||||
render() {
|
||||
const image = this.getCurrentImage();
|
||||
if (!image) {
|
||||
console.warn("Not showing lightbox because no image(s) was supplied");
|
||||
return null;
|
||||
}
|
||||
const {
|
||||
allowZoom = true,
|
||||
allowRotate = true,
|
||||
allowReset = true,
|
||||
onClose
|
||||
} = this.props;
|
||||
const { x, y, zoom, rotate, multi, loading, moving } = this.state;
|
||||
const _reset = allowReset && this.shouldShowReset();
|
||||
|
||||
const tools: {
|
||||
title: string;
|
||||
icon: Icon;
|
||||
enabled: boolean;
|
||||
onClick: (e: React.MouseEvent) => void;
|
||||
hidden?: boolean;
|
||||
hideOnMobile?: boolean;
|
||||
}[] = [
|
||||
{
|
||||
title: "Reset",
|
||||
icon: Reset,
|
||||
enabled: true,
|
||||
hidden: !allowReset,
|
||||
onClick: (e) => this.reset(e)
|
||||
},
|
||||
{
|
||||
title: "Rotate left",
|
||||
icon: RotateACW,
|
||||
enabled: true,
|
||||
hidden: !allowRotate,
|
||||
onClick: () => this.applyRotate("acw")
|
||||
},
|
||||
{
|
||||
title: "Rotate rigth",
|
||||
icon: RotateCW,
|
||||
enabled: true,
|
||||
hidden: !allowRotate,
|
||||
onClick: () => this.applyRotate("cw")
|
||||
},
|
||||
{
|
||||
title: "Zoom out",
|
||||
icon: ZoomOut,
|
||||
enabled: zoom > 1,
|
||||
hidden: !allowZoom,
|
||||
onClick: () => this.applyZoom("out")
|
||||
},
|
||||
{
|
||||
title: "Zoom in",
|
||||
icon: ZoomIn,
|
||||
enabled: true,
|
||||
hidden: !allowZoom,
|
||||
onClick: () => this.applyZoom("in")
|
||||
},
|
||||
{
|
||||
title: "Close",
|
||||
icon: Close,
|
||||
enabled: !!onClose,
|
||||
onClick: (e) => this.exit(e)
|
||||
}
|
||||
];
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
zIndex: 50000,
|
||||
position: "fixed",
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
bg: "#000000a1",
|
||||
flexDirection: "column"
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
justifyContent: "flex-end",
|
||||
zIndex: 10
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
bg="bgSecondary"
|
||||
sx={{
|
||||
borderRadius: "0px 0px 0px 5px",
|
||||
overflow: "hidden",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end"
|
||||
}}
|
||||
>
|
||||
{tools.map((tool) => (
|
||||
<Button
|
||||
data-test-id={tool.title}
|
||||
disabled={!tool.enabled}
|
||||
variant="tool"
|
||||
bg="transparent"
|
||||
title={tool.title}
|
||||
key={tool.title}
|
||||
sx={{
|
||||
borderRadius: 0,
|
||||
display: [
|
||||
tool.hideOnMobile ? "none" : "flex",
|
||||
tool.hidden ? "none" : "flex"
|
||||
],
|
||||
color: tool.enabled ? "text" : "disabled",
|
||||
cursor: tool.enabled ? "pointer" : "not-allowed",
|
||||
flexDirection: "row",
|
||||
flexShrink: 0,
|
||||
alignItems: "center"
|
||||
}}
|
||||
onClick={tool.onClick}
|
||||
>
|
||||
<tool.icon
|
||||
size={18}
|
||||
color={tool.enabled ? "text" : "disabled"}
|
||||
/>
|
||||
</Button>
|
||||
))}
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Flex
|
||||
sx={{
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
overflow: "hidden",
|
||||
maxWidth: "100%",
|
||||
maxHeight: "100%",
|
||||
position: "relative"
|
||||
}}
|
||||
ref={this._cont}
|
||||
onClick={(e) => this.canvasClick(e)}
|
||||
>
|
||||
{loading ? <Loading color="static" size={60} /> : null}
|
||||
<Image
|
||||
draggable="false"
|
||||
sx={{
|
||||
transform: this.createTransform(x, y, zoom, rotate),
|
||||
cursor: zoom > 1 ? "grab" : "unset",
|
||||
transition: moving ? "none" : "all 0.1s",
|
||||
maxWidth: "80vw",
|
||||
maxHeight: "80vh",
|
||||
minWidth: "100px",
|
||||
minHeight: "100px",
|
||||
backgroundSize: "50px",
|
||||
transformOrigin: "center center"
|
||||
}}
|
||||
onMouseDown={(e) => this.startMove(e)}
|
||||
onTouchStart={(e) => this.startMove(e)}
|
||||
onMouseMove={(e) => this.duringMove(e)}
|
||||
onTouchMove={(e) => this.duringMove(e)}
|
||||
onMouseUp={() => this.endMove()}
|
||||
onMouseLeave={() => this.endMove()}
|
||||
onTouchEnd={() => this.endMove()}
|
||||
onClick={(e) => this.stopSideEffect(e)}
|
||||
onDoubleClick={(e) => this.shockZoom(e)}
|
||||
onLoad={() => this.setState({ loading: false })}
|
||||
src={image}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,7 @@ function ListContainer(props: ListContainerProps) {
|
||||
element.focus()
|
||||
);
|
||||
},
|
||||
skip: (index) => !items[index] || items[index].type === "header",
|
||||
skip: (index) => items[index].type === "header",
|
||||
open: (index) => {
|
||||
const item = items[index];
|
||||
if (!item || !listRef.current) return;
|
||||
|
||||
@@ -70,11 +70,11 @@ function ListItem(props) {
|
||||
const isMenuTarget = target && target === listItemRef.current;
|
||||
|
||||
const isSelected = useSelectionStore((store) => {
|
||||
const isInSelection =
|
||||
const inInSelection =
|
||||
store.selectedItems.findIndex((item) => props.item.id === item.id) > -1;
|
||||
return isFocused
|
||||
? store.selectedItems.length > 1 && isInSelection
|
||||
: isInSelection;
|
||||
? store.selectedItems.length > 1 && inInSelection
|
||||
: inInSelection;
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -25,7 +25,6 @@ import ListItem from "../list-item";
|
||||
import {
|
||||
confirm,
|
||||
showAddReminderDialog,
|
||||
showAddTagsDialog,
|
||||
showMoveNoteDialog
|
||||
} from "../../common/dialog-controller";
|
||||
import { store, useStore } from "../../stores/note-store";
|
||||
@@ -373,29 +372,7 @@ const menuItems = [
|
||||
icon: Icon.Colors,
|
||||
items: colorsToMenuItems()
|
||||
},
|
||||
{
|
||||
key: "add-tags",
|
||||
title: "Tags",
|
||||
icon: Icon.Tag2,
|
||||
multiSelect: true,
|
||||
items: tagsMenuItems
|
||||
// onClick: async ({ items }) => {
|
||||
// await showAddTagsDialog(items.map((i) => i.id));
|
||||
// }
|
||||
},
|
||||
{ key: "sep2", type: "separator" },
|
||||
{
|
||||
key: "print",
|
||||
title: "Print",
|
||||
disabled: ({ note }) => {
|
||||
if (!db.notes.note(note.id).synced()) return notFullySyncedText;
|
||||
if (note.locked) return "Locked notes cannot be printed.";
|
||||
},
|
||||
icon: Icon.Print,
|
||||
onClick: async ({ note }) => {
|
||||
await exportNotes("pdf", [note.id]);
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "publish",
|
||||
disabled: ({ note }) => {
|
||||
@@ -609,60 +586,3 @@ function notebooksMenuItems({ items }) {
|
||||
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
function tagsMenuItems({ items }) {
|
||||
const noteIds = items.map((i) => i.id);
|
||||
|
||||
const menuItems = [];
|
||||
menuItems.push({
|
||||
key: "assign-tags",
|
||||
title: "Assign to...",
|
||||
icon: Icon.Plus,
|
||||
onClick: async () => {
|
||||
await showAddTagsDialog(noteIds);
|
||||
}
|
||||
});
|
||||
|
||||
const tags = items.map((note) => note.tags).flat();
|
||||
|
||||
if (tags?.length > 0) {
|
||||
menuItems.push(
|
||||
{
|
||||
key: "remove-from-all-tags",
|
||||
title: "Remove from all",
|
||||
icon: Icon.RemoveShortcutLink,
|
||||
onClick: async () => {
|
||||
for (const note of items) {
|
||||
for (const tag of tags) {
|
||||
if (!note.tags.includes(tag)) continue;
|
||||
await db.notes.note(note).untag(tag);
|
||||
}
|
||||
}
|
||||
store.refresh();
|
||||
}
|
||||
},
|
||||
{ key: "sep", type: "separator" }
|
||||
);
|
||||
|
||||
tags?.forEach((tag) => {
|
||||
if (menuItems.find((item) => item.key === tag)) return;
|
||||
|
||||
menuItems.push({
|
||||
key: tag,
|
||||
title: db.tags.alias(tag),
|
||||
icon: Icon.Tag,
|
||||
checked: true,
|
||||
tooltip: "Click to remove from this tag",
|
||||
onClick: async () => {
|
||||
for (const note of items) {
|
||||
if (!note.tags.includes(tag)) continue;
|
||||
await db.notes.note(note).untag(tag);
|
||||
}
|
||||
store.refresh();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
@@ -77,8 +77,7 @@ function Notebook(props) {
|
||||
sx={{
|
||||
fontSize: "subBody",
|
||||
color: "fontTertiary",
|
||||
alignItems: "center",
|
||||
fontFamily: "body"
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
{notebook.pinned && (
|
||||
|
||||
@@ -1,372 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Worker, Viewer, PasswordStatus } from "@react-pdf-viewer/core";
|
||||
import "@react-pdf-viewer/core/lib/styles/index.css";
|
||||
import { ToolbarSlot, toolbarPlugin } from "@react-pdf-viewer/toolbar";
|
||||
import { Button, Flex, Text } from "@theme-ui/components";
|
||||
import { searchPlugin } from "@react-pdf-viewer/search";
|
||||
import {
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Download,
|
||||
Fullscreen,
|
||||
Icon,
|
||||
ZoomIn,
|
||||
ZoomOut,
|
||||
Close,
|
||||
Search,
|
||||
Alert
|
||||
} from "../icons";
|
||||
import { useStore as useThemeStore } from "../../stores/theme-store";
|
||||
import Field from "../field";
|
||||
import { LinkPlugin } from "./links-plugin";
|
||||
import Config from "../../utils/config";
|
||||
|
||||
export type PdfPreviewProps = {
|
||||
fileUrl: string | Uint8Array;
|
||||
onClose?: () => void;
|
||||
hash?: string;
|
||||
};
|
||||
export function PdfPreview(props: PdfPreviewProps) {
|
||||
const { fileUrl, onClose, hash } = props;
|
||||
|
||||
const toolbarInstance = toolbarPlugin();
|
||||
const { Toolbar } = toolbarInstance;
|
||||
const searchPluginInstance = searchPlugin();
|
||||
const { ShowSearchPopover } = searchPluginInstance;
|
||||
|
||||
const theme = useThemeStore((state) => state.theme);
|
||||
|
||||
return (
|
||||
<Worker workerUrl="/pdf.worker.min.js">
|
||||
<Flex sx={{ p: 1, justifyContent: "space-between" }}>
|
||||
<Toolbar>
|
||||
{(props: ToolbarSlot) => {
|
||||
const {
|
||||
CurrentPageInput,
|
||||
CurrentScale,
|
||||
GoToNextPage,
|
||||
GoToPreviousPage,
|
||||
NumberOfPages,
|
||||
EnterFullScreen
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex
|
||||
sx={{
|
||||
bg: "bgSecondary",
|
||||
borderRadius: "default",
|
||||
overflow: "hidden",
|
||||
alignItems: "center",
|
||||
|
||||
".rpv-search__popover label, .rpv-search__popover span": {
|
||||
fontFamily: "body",
|
||||
fontSize: "body"
|
||||
},
|
||||
|
||||
".rpv-core__popover-body, .rpv-core__arrow": {
|
||||
bg: "background",
|
||||
borderColor: "border"
|
||||
},
|
||||
|
||||
".rpv-search__popover-label-checkbox": {
|
||||
accentColor: "var(--primary)"
|
||||
},
|
||||
|
||||
".rpv-core__textbox": {
|
||||
width: "45px",
|
||||
mr: 1,
|
||||
borderRadius: "default",
|
||||
px: 1,
|
||||
py: "2px",
|
||||
height: "auto",
|
||||
bg: "background",
|
||||
border: "none",
|
||||
outline: "1.5px solid var(--border)",
|
||||
fontFamily: "body",
|
||||
fontWeight: "body",
|
||||
fontSize: "input",
|
||||
color: "text",
|
||||
":focus": {
|
||||
outline: "2px solid var(--primary)"
|
||||
},
|
||||
":hover:not(:focus)": {
|
||||
outline: "1.5px solid var(--dimPrimary)"
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ShowSearchPopover>
|
||||
{(props) => (
|
||||
<ToolbarButton
|
||||
icon={Search}
|
||||
title="Search"
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
)}
|
||||
</ShowSearchPopover>
|
||||
<GoToPreviousPage>
|
||||
{(props) => (
|
||||
<ToolbarButton
|
||||
icon={ChevronUp}
|
||||
disabled={props.isDisabled}
|
||||
title="Go to previous page"
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
)}
|
||||
</GoToPreviousPage>
|
||||
<CurrentPageInput />
|
||||
<NumberOfPages>
|
||||
{(props) => (
|
||||
<Text variant="body" sx={{ mr: 1 }}>
|
||||
/ {props.numberOfPages}
|
||||
</Text>
|
||||
)}
|
||||
</NumberOfPages>
|
||||
<GoToNextPage>
|
||||
{(props) => (
|
||||
<ToolbarButton
|
||||
icon={ChevronDown}
|
||||
disabled={props.isDisabled}
|
||||
title="Go to next page"
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
)}
|
||||
</GoToNextPage>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
sx={{
|
||||
bg: "bgSecondary",
|
||||
borderRadius: "default",
|
||||
overflow: "hidden",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<props.ZoomOut>
|
||||
{(props) => (
|
||||
<ToolbarButton
|
||||
icon={ZoomOut}
|
||||
title="Zoom out"
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
)}
|
||||
</props.ZoomOut>
|
||||
<CurrentScale>
|
||||
{(props) => (
|
||||
<Text variant="body" sx={{ mx: 1 }}>{`${Math.round(
|
||||
props.scale * 100
|
||||
)}%`}</Text>
|
||||
)}
|
||||
</CurrentScale>
|
||||
<props.ZoomIn>
|
||||
{(props) => (
|
||||
<ToolbarButton
|
||||
icon={ZoomIn}
|
||||
title="Zoom in"
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
)}
|
||||
</props.ZoomIn>
|
||||
</Flex>
|
||||
|
||||
<Flex
|
||||
sx={{
|
||||
bg: "bgSecondary",
|
||||
borderRadius: "default",
|
||||
overflow: "hidden",
|
||||
alignItems: "center"
|
||||
}}
|
||||
>
|
||||
<props.Download>
|
||||
{(props) => (
|
||||
<ToolbarButton
|
||||
icon={Download}
|
||||
title="Download"
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
)}
|
||||
</props.Download>
|
||||
<EnterFullScreen>
|
||||
{(props) => (
|
||||
<ToolbarButton
|
||||
icon={Fullscreen}
|
||||
title="Enter fullscreen"
|
||||
onClick={props.onClick}
|
||||
/>
|
||||
)}
|
||||
</EnterFullScreen>
|
||||
|
||||
{onClose && (
|
||||
<ToolbarButton
|
||||
icon={Close}
|
||||
title="Close"
|
||||
onClick={onClose}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</Toolbar>
|
||||
</Flex>
|
||||
<Viewer
|
||||
fileUrl={fileUrl}
|
||||
theme={theme}
|
||||
initialPage={hash ? getPDFConfig(hash).page : 1}
|
||||
defaultScale={hash ? getPDFConfig(hash).scale : 1}
|
||||
onPageChange={(e) => {
|
||||
if (hash) setPDFConfig(hash, { page: e.currentPage });
|
||||
}}
|
||||
onZoom={(e) => {
|
||||
if (hash) setPDFConfig(hash, { scale: e.scale });
|
||||
}}
|
||||
// onDocumentAskPassword={(e) => {
|
||||
// e.verifyPassword("failed");
|
||||
// }}
|
||||
renderProtectedView={(props) => (
|
||||
<Flex
|
||||
mx={2}
|
||||
sx={{
|
||||
flex: "1",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
height: "100%"
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
data-test-id="unlock-note-title"
|
||||
variant="heading"
|
||||
sx={{ fontSize: 28, textAlign: "center" }}
|
||||
>
|
||||
Unlock document
|
||||
</Text>
|
||||
</Flex>
|
||||
<Text
|
||||
variant="subheading"
|
||||
mt={1}
|
||||
mb={4}
|
||||
sx={{ textAlign: "center", color: "fontTertiary" }}
|
||||
>
|
||||
Please enter the password to unlock this document.
|
||||
</Text>
|
||||
<Field
|
||||
id="document-password"
|
||||
autoFocus
|
||||
sx={{ width: "95%", maxWidth: 400 }}
|
||||
placeholder="Enter password"
|
||||
type="password"
|
||||
onKeyUp={async (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (e.key === "Enter") {
|
||||
props.verifyPassword(e.currentTarget.value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{props.passwordStatus === PasswordStatus.WrongPassword && (
|
||||
<Flex
|
||||
mt={2}
|
||||
sx={{
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
color: "error",
|
||||
alignSelf: "flex-center"
|
||||
}}
|
||||
>
|
||||
<Alert color="error" size={12} />
|
||||
<Text ml={1} sx={{ fontSize: "body" }}>
|
||||
Wrong password
|
||||
</Text>
|
||||
</Flex>
|
||||
)}
|
||||
<Button
|
||||
mt={3}
|
||||
variant="primary"
|
||||
data-test-id="unlock-note-submit"
|
||||
sx={{ borderRadius: 100, px: 30 }}
|
||||
onClick={async () => {}}
|
||||
>
|
||||
Unlock
|
||||
</Button>
|
||||
</Flex>
|
||||
)}
|
||||
plugins={[toolbarInstance, searchPluginInstance, LinkPlugin()]}
|
||||
></Viewer>
|
||||
</Worker>
|
||||
);
|
||||
}
|
||||
|
||||
type ToolbarButtonProps = {
|
||||
title: string;
|
||||
disabled?: boolean;
|
||||
hideOnMobile?: boolean;
|
||||
hidden?: boolean;
|
||||
onClick: () => void;
|
||||
icon: Icon;
|
||||
iconSize?: number;
|
||||
};
|
||||
function ToolbarButton(props: ToolbarButtonProps) {
|
||||
const { title, disabled, hideOnMobile, hidden, onClick, iconSize } = props;
|
||||
|
||||
return (
|
||||
<Button
|
||||
data-test-id={title}
|
||||
disabled={disabled}
|
||||
variant="tool"
|
||||
bg="transparent"
|
||||
title={title}
|
||||
sx={{
|
||||
borderRadius: 0,
|
||||
display: [hideOnMobile ? "none" : "flex", hidden ? "none" : "flex"],
|
||||
color: !disabled ? "text" : "disabled",
|
||||
cursor: !disabled ? "pointer" : "not-allowed",
|
||||
flexDirection: "row",
|
||||
flexShrink: 0,
|
||||
alignItems: "center"
|
||||
}}
|
||||
onClick={onClick}
|
||||
>
|
||||
<props.icon
|
||||
size={iconSize || 18}
|
||||
color={!disabled ? "text" : "disabled"}
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
type PDFConfig = {
|
||||
scale: number;
|
||||
page: number;
|
||||
};
|
||||
|
||||
function getPDFConfig(hash: string): PDFConfig {
|
||||
return Config.get(`pdf:config:${hash}`, { scale: 1, page: 0 });
|
||||
}
|
||||
function setPDFConfig(hash: string, config: Partial<PDFConfig>) {
|
||||
Config.set(`pdf:config:${hash}`, { ...getPDFConfig(hash), ...config });
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Plugin, PluginOnAnnotationLayerRender } from "@react-pdf-viewer/core";
|
||||
|
||||
export function LinkPlugin(): Plugin {
|
||||
return {
|
||||
onAnnotationLayerRender: findAndReplaceLinkAnnotations
|
||||
};
|
||||
}
|
||||
|
||||
function findAndReplaceLinkAnnotations(e: PluginOnAnnotationLayerRender) {
|
||||
e.container
|
||||
.querySelectorAll(".rpv-core__annotation--link a")
|
||||
.forEach((link) => {
|
||||
if ((link as HTMLAnchorElement).href)
|
||||
link.setAttribute("target", "_blank");
|
||||
});
|
||||
}
|
||||
@@ -29,7 +29,7 @@ function SearchBox({ onSearch }) {
|
||||
id="search"
|
||||
name="search"
|
||||
type="text"
|
||||
sx={{ m: 0, mx: 2, mt: 1 }}
|
||||
sx={{ m: 0, mx: 1, mt: 1 }}
|
||||
placeholder="Type your query here"
|
||||
onChange={debounce((e) => onSearch(e.target.value), 250)}
|
||||
action={{
|
||||
|
||||
@@ -102,16 +102,12 @@ function Unlock(props) {
|
||||
</Text>
|
||||
</Flex>
|
||||
<Text
|
||||
variant="body"
|
||||
variant="subheading"
|
||||
mt={1}
|
||||
mb={4}
|
||||
sx={{
|
||||
textAlign: "center",
|
||||
fontSize: "title",
|
||||
color: "fontTertiary"
|
||||
}}
|
||||
sx={{ textAlign: "center", color: "fontTertiary" }}
|
||||
>
|
||||
Please enter the password to unlock this note.
|
||||
Please enter the password to unlock this note
|
||||
</Text>
|
||||
<Field
|
||||
id="vaultPassword"
|
||||
|
||||
@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { initializeDatabase } from "../common/db";
|
||||
import "allotment/dist/style.css";
|
||||
import "../utils/analytics";
|
||||
import "../app.css";
|
||||
|
||||
|
||||
@@ -90,7 +90,6 @@ export function useKeyboardListNavigation(
|
||||
itemIndex > cursor.current ? itemIndex : cursor.current;
|
||||
const indices = [];
|
||||
for (let i = startIndex; i <= endIndex; ++i) {
|
||||
if (skip && skip(i)) continue;
|
||||
indices.push(i);
|
||||
}
|
||||
bulkSelect(indices);
|
||||
@@ -100,7 +99,7 @@ export function useKeyboardListNavigation(
|
||||
select(itemIndex);
|
||||
}
|
||||
},
|
||||
[select, resetSelection, bulkSelect, skip, focusItemAt]
|
||||
[select, resetSelection, bulkSelect, focusItemAt]
|
||||
);
|
||||
|
||||
const onKeyDown = useCallback(
|
||||
|
||||
@@ -111,10 +111,8 @@ async function writeEncryptedBase64(metadata: {
|
||||
|
||||
const { hash, type: hashType } = await hashBuffer(bytes);
|
||||
|
||||
const attachment = db.attachments?.attachment(hash);
|
||||
|
||||
const file = new File([bytes.buffer], hash, {
|
||||
type: attachment?.metadata.type || mimeType || "application/octet-stream"
|
||||
type: mimeType || "application/octet-stream"
|
||||
});
|
||||
|
||||
const result = await writeEncryptedFile(file, key, hash);
|
||||
@@ -302,8 +300,9 @@ async function uploadFile(filename: string, requestOptions: RequestOptions) {
|
||||
});
|
||||
|
||||
await fileHandle.addAdditionalData("uploaded", true);
|
||||
|
||||
if (isAttachmentDeletable(fileHandle.file.type)) {
|
||||
// Keep the images cached; delete everything else.
|
||||
if (!fileHandle.file.type?.startsWith("image/")) {
|
||||
console.log("DELETING FILE", fileHandle);
|
||||
await streamablefs.deleteFile(filename);
|
||||
}
|
||||
await checkUpload(filename);
|
||||
@@ -338,16 +337,8 @@ function reportProgress(
|
||||
|
||||
async function downloadFile(filename: string, requestOptions: RequestOptions) {
|
||||
const { url, headers, chunkSize, signal } = requestOptions;
|
||||
const handle = await streamablefs.readFile(filename);
|
||||
if (await streamablefs.exists(filename)) return true;
|
||||
|
||||
if (
|
||||
handle &&
|
||||
handle.file.size === (await handle.size()) - handle.file.chunks * ABYTES
|
||||
)
|
||||
return true;
|
||||
else if (handle) await handle.delete();
|
||||
|
||||
const attachment = db.attachments?.attachment(filename);
|
||||
try {
|
||||
reportProgress(
|
||||
{ total: 100, loaded: 0 },
|
||||
@@ -387,18 +378,10 @@ async function downloadFile(filename: string, requestOptions: RequestOptions) {
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
const totalChunks = Math.ceil(contentLength / chunkSize);
|
||||
const decryptedLength = contentLength - totalChunks * ABYTES;
|
||||
if (attachment && attachment.length !== decryptedLength) {
|
||||
const error = `File length mismatch. Please upload this file again from the attachment manager. (File hash: ${filename})`;
|
||||
await db.attachments?.markAsFailed(filename, error);
|
||||
throw new Error(error);
|
||||
}
|
||||
|
||||
const fileHandle = await streamablefs.createFile(
|
||||
filename,
|
||||
decryptedLength,
|
||||
attachment?.metadata.type || "application/octet-stream"
|
||||
contentLength,
|
||||
"application/octet-stream"
|
||||
);
|
||||
|
||||
await response.body
|
||||
@@ -459,8 +442,7 @@ async function saveFile(filename: string, fileMetadata: FileMetadata) {
|
||||
const decrypted = await decryptFile(filename, fileMetadata);
|
||||
if (decrypted) saveAs(decrypted, getFileNameWithExtension(name, type));
|
||||
|
||||
if (isUploaded && isAttachmentDeletable(type))
|
||||
await streamablefs.deleteFile(filename);
|
||||
if (isUploaded) await streamablefs.deleteFile(filename);
|
||||
}
|
||||
|
||||
async function deleteFile(filename: string, requestOptions: RequestOptions) {
|
||||
@@ -522,10 +504,6 @@ const FS = {
|
||||
};
|
||||
export default FS;
|
||||
|
||||
function isAttachmentDeletable(type: string) {
|
||||
return !type.startsWith("image/") && !type.startsWith("application/pdf");
|
||||
}
|
||||
|
||||
function isSuccessStatusCode(statusCode: number) {
|
||||
return statusCode >= 200 && statusCode <= 299;
|
||||
}
|
||||
|
||||
@@ -25,15 +25,12 @@ import { isUserPremium } from "../hooks/use-is-user-premium";
|
||||
import { SUBSCRIPTION_STATUS } from "../common/constants";
|
||||
import { appVersion } from "../utils/version";
|
||||
import { findItemAndDelete } from "@notesnook/core/utils/array";
|
||||
import { isTesting } from "../utils/platform";
|
||||
|
||||
class AnnouncementStore extends BaseStore {
|
||||
inlineAnnouncements = [];
|
||||
dialogAnnouncements = [];
|
||||
|
||||
refresh = async () => {
|
||||
if (isTesting()) return;
|
||||
|
||||
try {
|
||||
const inlineAnnouncements = [];
|
||||
const dialogAnnouncements = [];
|
||||
|
||||
@@ -151,7 +151,6 @@ class AppStore extends BaseStore {
|
||||
tagStore.refresh();
|
||||
attachmentStore.refresh();
|
||||
monographStore.refresh();
|
||||
await editorstore.refresh();
|
||||
this.refreshNavItems();
|
||||
|
||||
logger.measure("refreshing app");
|
||||
|
||||
@@ -20,6 +20,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import createStore from "../common/store";
|
||||
import { db } from "../common/db";
|
||||
import BaseStore from "./index";
|
||||
import { AppEventManager, AppEvents } from "../common/app-events";
|
||||
import { store as editorStore } from "./editor-store";
|
||||
import { checkAttachment } from "../common/attachments";
|
||||
import { showToast } from "../utils/toast";
|
||||
@@ -41,6 +42,25 @@ class AttachmentStore extends BaseStore {
|
||||
};
|
||||
|
||||
init = () => {
|
||||
AppEventManager.subscribe(
|
||||
AppEvents.UPDATE_ATTACHMENT_PROGRESS,
|
||||
({ hash, type, total, loaded }) => {
|
||||
this.set((state) => {
|
||||
const index = state.attachments.findIndex(
|
||||
(a) => a.metadata.hash === hash
|
||||
);
|
||||
if (index <= -1) return;
|
||||
const percent = Math.round((loaded / total) * 100);
|
||||
const status =
|
||||
percent < 100 ? { type, loaded, total, progress: percent } : null;
|
||||
if (!status) this.refresh();
|
||||
state.attachments[index] = {
|
||||
...state.attachments[index],
|
||||
status
|
||||
};
|
||||
});
|
||||
}
|
||||
);
|
||||
this.refresh();
|
||||
};
|
||||
|
||||
@@ -67,9 +87,7 @@ class AttachmentStore extends BaseStore {
|
||||
);
|
||||
await attachmentStream
|
||||
.pipeThrough(new ZipStream())
|
||||
.pipeTo(
|
||||
createWriteStream("attachments.zip", { signal: abortController.signal })
|
||||
);
|
||||
.pipeTo(createWriteStream("attachments.zip"));
|
||||
|
||||
this.set((state) => (state.status = undefined));
|
||||
};
|
||||
|
||||
@@ -84,11 +84,6 @@ class EditorStore extends BaseStore {
|
||||
});
|
||||
};
|
||||
|
||||
async refresh() {
|
||||
const sessionId = this.get().session.id;
|
||||
if (sessionId && !db.notes.note(sessionId)) await this.clearSession();
|
||||
}
|
||||
|
||||
updateSession = async (item) => {
|
||||
this.set((state) => {
|
||||
state.session.title = item.title;
|
||||
|
||||
@@ -38,7 +38,6 @@ function makeIframe(src: string, doc = true) {
|
||||
if (doc) iframe.srcdoc = src;
|
||||
else iframe.src = src;
|
||||
document.body.appendChild(iframe);
|
||||
return iframe;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -73,14 +72,12 @@ export function createWriteStream(
|
||||
opts: {
|
||||
size?: number;
|
||||
pathname?: string;
|
||||
signal?: AbortSignal;
|
||||
} = {}
|
||||
): WritableStream<Uint8Array> {
|
||||
// let bytesWritten = 0; // by StreamSaver.js (not the service worker)
|
||||
let downloadUrl: string | null = null;
|
||||
let channel: MessageChannel | null = null;
|
||||
let ts: TransformStream | null = null;
|
||||
let frame: HTMLIFrameElement | null = null;
|
||||
let ts = null;
|
||||
if (!useBlobFallback) {
|
||||
channel = new MessageChannel();
|
||||
|
||||
@@ -109,18 +106,14 @@ export function createWriteStream(
|
||||
if (supportsTransferable) {
|
||||
ts = new TransformStream();
|
||||
const readableStream = ts.readable;
|
||||
if (opts.signal) {
|
||||
opts.signal.addEventListener("abort", () => frame?.remove(), {
|
||||
once: true
|
||||
});
|
||||
}
|
||||
|
||||
channel.port1.postMessage({ readableStream }, [readableStream]);
|
||||
}
|
||||
channel.port1.onmessage = async (evt) => {
|
||||
// Service worker sent us a link that we should open.
|
||||
if (evt.data.download) {
|
||||
// We never remove this iframes because it can interrupt saving
|
||||
frame = makeIframe(evt.data.download, false);
|
||||
makeIframe(evt.data.download, false);
|
||||
} else if (evt.data.abort) {
|
||||
chunks = [];
|
||||
if (channel) {
|
||||
|
||||
@@ -85,7 +85,7 @@ export class WebExtensionServer implements Server {
|
||||
|
||||
clipContent += h("iframe", [], {
|
||||
"data-hash": attachment.hash,
|
||||
"data-mime": attachment.mime,
|
||||
"data-mime": attachment.type,
|
||||
src: clip.url,
|
||||
title: clip.pageTitle || clip.title,
|
||||
width: clip.width ? `${clip.width}` : undefined,
|
||||
|
||||
@@ -31,12 +31,6 @@ import Placeholder from "../components/placeholders";
|
||||
|
||||
async function typeToItems(type, context) {
|
||||
switch (type) {
|
||||
case "notebook": {
|
||||
const selectedNotebook = notebookstore.get().selectedNotebook;
|
||||
if (!selectedNotebook) return ["notes", []];
|
||||
const notes = db.relations.to(selectedNotebook, "note");
|
||||
return ["notes", notes];
|
||||
}
|
||||
case "notes": {
|
||||
await db.notes.init();
|
||||
if (!context) return ["notes", db.notes.all];
|
||||
@@ -119,14 +113,11 @@ function Search({ type }) {
|
||||
}
|
||||
case "notebooks":
|
||||
return "all notebooks";
|
||||
case "notebook":
|
||||
case "topics": {
|
||||
const selectedNotebook = notebookstore.get().selectedNotebook;
|
||||
if (!selectedNotebook) return "";
|
||||
const notebook = db.notebooks.notebook(selectedNotebook.id);
|
||||
return `${type === "topics" ? "topics" : "notes"} in ${
|
||||
notebook.title
|
||||
} notebook`;
|
||||
return `topics in ${notebook.title} notebook`;
|
||||
}
|
||||
case "tags":
|
||||
return "all tags";
|
||||
|
||||
4
extensions/web-clipper/package-lock.json
generated
4
extensions/web-clipper/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@notesnook/web-clipper",
|
||||
"version": "0.2.1",
|
||||
"version": "0.1.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@notesnook/web-clipper",
|
||||
"version": "0.2.1",
|
||||
"version": "0.1.0",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.10.0",
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
- Preview PDFs & Images directly inside Notesnook."
|
||||
- Improved attachments manager with support for downloading all attachments
|
||||
- Assign tags to multiple notes
|
||||
- Bug fixes and performance improvements
|
||||
|
||||
Thank you for using Notesnook!
|
||||
@@ -194,8 +194,7 @@ export default class Note {
|
||||
}
|
||||
|
||||
async untag(tag) {
|
||||
const tagItem = this._db.tags.tag(tag);
|
||||
if (tagItem && deleteItem(this._note.tags, tagItem.title)) {
|
||||
if (deleteItem(this._note.tags, tag)) {
|
||||
await this._db.notes.add(this._note);
|
||||
} else console.error("This note is not tagged by the specified tag.", tag);
|
||||
await this._db.tags.untag(tag, this._note.id);
|
||||
|
||||
@@ -31,7 +31,7 @@ import { EventTypes, isReactNative, post } from "../utils";
|
||||
type Attachment = {
|
||||
hash: string;
|
||||
filename: string;
|
||||
mime: string;
|
||||
type: string;
|
||||
size: number;
|
||||
};
|
||||
|
||||
|
||||
662
packages/editor/package-lock.json
generated
662
packages/editor/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@notesnook/editor",
|
||||
"version": "1.5.0",
|
||||
"version": "1.4.0",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"license": "GPL-3.0-or-later",
|
||||
@@ -10,27 +10,27 @@
|
||||
"@social-embed/lib": "^0.0.2-next.1",
|
||||
"@theme-ui/components": "^0.14.7",
|
||||
"@theme-ui/core": "^0.14.7",
|
||||
"@tiptap/core": "2.0.3",
|
||||
"@tiptap/extension-character-count": "2.0.3",
|
||||
"@tiptap/extension-color": "2.0.3",
|
||||
"@tiptap/extension-font-family": "2.0.3",
|
||||
"@tiptap/extension-history": "2.0.3",
|
||||
"@tiptap/extension-horizontal-rule": "2.0.3",
|
||||
"@tiptap/extension-link": "2.0.3",
|
||||
"@tiptap/extension-placeholder": "2.0.3",
|
||||
"@tiptap/extension-subscript": "2.0.3",
|
||||
"@tiptap/extension-superscript": "2.0.3",
|
||||
"@tiptap/extension-table": "2.0.3",
|
||||
"@tiptap/extension-table-cell": "2.0.3",
|
||||
"@tiptap/extension-table-header": "2.0.3",
|
||||
"@tiptap/extension-table-row": "2.0.3",
|
||||
"@tiptap/extension-task-item": "2.0.3",
|
||||
"@tiptap/extension-task-list": "2.0.3",
|
||||
"@tiptap/extension-text-align": "2.0.3",
|
||||
"@tiptap/extension-text-style": "2.0.3",
|
||||
"@tiptap/extension-underline": "2.0.3",
|
||||
"@tiptap/pm": "2.0.3",
|
||||
"@tiptap/starter-kit": "2.0.3",
|
||||
"@tiptap/core": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-character-count": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-color": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-font-family": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-history": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-horizontal-rule": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-link": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-placeholder": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-subscript": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-superscript": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-table": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-table-cell": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-table-header": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-table-row": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-task-item": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-task-list": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-text-align": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-text-style": "^2.0.0-beta.220",
|
||||
"@tiptap/extension-underline": "^2.0.0-beta.220",
|
||||
"@tiptap/pm": "^2.0.0-beta.220",
|
||||
"@tiptap/starter-kit": "^2.0.0-beta.220",
|
||||
"detect-indent": "^7.0.0",
|
||||
"katex": "^0.16.2",
|
||||
"nanoid": "^4.0.1",
|
||||
|
||||
29
packages/editor/patches/@tiptap+core+2.0.0-beta.220.patch
Normal file
29
packages/editor/patches/@tiptap+core+2.0.0-beta.220.patch
Normal file
@@ -0,0 +1,29 @@
|
||||
diff --git a/node_modules/@tiptap/core/dist/index.js b/node_modules/@tiptap/core/dist/index.js
|
||||
index 163e352..8cd7ec6 100644
|
||||
--- a/node_modules/@tiptap/core/dist/index.js
|
||||
+++ b/node_modules/@tiptap/core/dist/index.js
|
||||
@@ -3129,8 +3129,7 @@ const Keymap = Extension.create({
|
||||
new Plugin({
|
||||
key: new PluginKey('clearDocument'),
|
||||
appendTransaction: (transactions, oldState, newState) => {
|
||||
- const docChanges = transactions.some(transaction => transaction.docChanged)
|
||||
- && !oldState.doc.eq(newState.doc);
|
||||
+ const docChanges = transactions.some(transaction => transaction.docChanged);
|
||||
if (!docChanges) {
|
||||
return;
|
||||
}
|
||||
@@ -3138,10 +3137,12 @@ const Keymap = Extension.create({
|
||||
const allFrom = Selection.atStart(oldState.doc).from;
|
||||
const allEnd = Selection.atEnd(oldState.doc).to;
|
||||
const allWasSelected = from === allFrom && to === allEnd;
|
||||
- const isEmpty = newState.doc.textBetween(0, newState.doc.content.size, ' ', ' ').length === 0;
|
||||
- if (empty || !allWasSelected || !isEmpty) {
|
||||
+ if (empty || !allWasSelected) {
|
||||
return;
|
||||
}
|
||||
+ const isEmpty = newState.doc.textBetween(0, newState.doc.content.size, ' ', ' ').length === 0;
|
||||
+ if (!isEmpty) return;
|
||||
+
|
||||
const tr = newState.tr;
|
||||
const state = createChainableState({
|
||||
state: newState,
|
||||
@@ -0,0 +1,39 @@
|
||||
diff --git a/node_modules/@tiptap/extension-link/dist/index.cjs b/node_modules/@tiptap/extension-link/dist/index.cjs
|
||||
index a79f326..a5f8fdd 100644
|
||||
--- a/node_modules/@tiptap/extension-link/dist/index.cjs
|
||||
+++ b/node_modules/@tiptap/extension-link/dist/index.cjs
|
||||
@@ -101,6 +101,8 @@ function clickHandler(options) {
|
||||
key: new state.PluginKey('handleClickLink'),
|
||||
props: {
|
||||
handleClick: (view, pos, event) => {
|
||||
+ if (event.button !== 1) return;
|
||||
+
|
||||
var _a, _b, _c;
|
||||
const attrs = core.getAttributes(view.state, options.type.name);
|
||||
const link = (_a = event.target) === null || _a === void 0 ? void 0 : _a.closest('a');
|
||||
diff --git a/node_modules/@tiptap/extension-link/dist/index.js b/node_modules/@tiptap/extension-link/dist/index.js
|
||||
index d579117..3f6d893 100644
|
||||
--- a/node_modules/@tiptap/extension-link/dist/index.js
|
||||
+++ b/node_modules/@tiptap/extension-link/dist/index.js
|
||||
@@ -97,6 +97,8 @@ function clickHandler(options) {
|
||||
key: new PluginKey('handleClickLink'),
|
||||
props: {
|
||||
handleClick: (view, pos, event) => {
|
||||
+ if (event.button !== 1) return;
|
||||
+
|
||||
var _a, _b, _c;
|
||||
const attrs = getAttributes(view.state, options.type.name);
|
||||
const link = (_a = event.target) === null || _a === void 0 ? void 0 : _a.closest('a');
|
||||
diff --git a/node_modules/@tiptap/extension-link/dist/index.umd.js b/node_modules/@tiptap/extension-link/dist/index.umd.js
|
||||
index 743ae18..643089d 100644
|
||||
--- a/node_modules/@tiptap/extension-link/dist/index.umd.js
|
||||
+++ b/node_modules/@tiptap/extension-link/dist/index.umd.js
|
||||
@@ -99,6 +99,8 @@
|
||||
key: new state.PluginKey('handleClickLink'),
|
||||
props: {
|
||||
handleClick: (view, pos, event) => {
|
||||
+ if (event.button !== 1) return;
|
||||
+
|
||||
var _a, _b, _c;
|
||||
const attrs = core.getAttributes(view.state, options.type.name);
|
||||
const link = (_a = event.target) === null || _a === void 0 ? void 0 : _a.closest('a');
|
||||
@@ -1,42 +0,0 @@
|
||||
diff --git a/node_modules/@tiptap/extension-link/dist/index.cjs b/node_modules/@tiptap/extension-link/dist/index.cjs
|
||||
index 301cdb9..1390415 100644
|
||||
--- a/node_modules/@tiptap/extension-link/dist/index.cjs
|
||||
+++ b/node_modules/@tiptap/extension-link/dist/index.cjs
|
||||
@@ -102,6 +102,7 @@ function clickHandler(options) {
|
||||
props: {
|
||||
handleClick: (view, pos, event) => {
|
||||
var _a, _b, _c;
|
||||
+ event.preventDefault()
|
||||
if (event.button !== 0) {
|
||||
return false;
|
||||
}
|
||||
@@ -110,7 +111,7 @@ function clickHandler(options) {
|
||||
const href = (_b = link === null || link === void 0 ? void 0 : link.href) !== null && _b !== void 0 ? _b : attrs.href;
|
||||
const target = (_c = link === null || link === void 0 ? void 0 : link.target) !== null && _c !== void 0 ? _c : attrs.target;
|
||||
if (link && href) {
|
||||
- window.open(href, target);
|
||||
+ if (view.editable) window.open(href, target);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
diff --git a/node_modules/@tiptap/extension-link/dist/index.js b/node_modules/@tiptap/extension-link/dist/index.js
|
||||
index e3b8602..b75336f 100644
|
||||
--- a/node_modules/@tiptap/extension-link/dist/index.js
|
||||
+++ b/node_modules/@tiptap/extension-link/dist/index.js
|
||||
@@ -97,6 +97,7 @@ function clickHandler(options) {
|
||||
key: new PluginKey('handleClickLink'),
|
||||
props: {
|
||||
handleClick: (view, pos, event) => {
|
||||
+ event.preventDefault()
|
||||
var _a, _b, _c;
|
||||
if (event.button !== 0) {
|
||||
return false;
|
||||
@@ -106,7 +107,7 @@ function clickHandler(options) {
|
||||
const href = (_b = link === null || link === void 0 ? void 0 : link.href) !== null && _b !== void 0 ? _b : attrs.href;
|
||||
const target = (_c = link === null || link === void 0 ? void 0 : link.target) !== null && _c !== void 0 ? _c : attrs.target;
|
||||
if (link && href) {
|
||||
- window.open(href, target);
|
||||
+ if (view.editable) window.open(href, target);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -309,7 +309,7 @@ export function usePopupHandler(options: UsePopupHandlerOptions) {
|
||||
}
|
||||
|
||||
type ShowPopupOptions = {
|
||||
popup: (closePopup: () => void) => React.ReactNode;
|
||||
popup?: (closePopup: () => void) => React.ReactNode;
|
||||
} & Partial<ResponsivePresenterProps>;
|
||||
export function showPopup(options: ShowPopupOptions) {
|
||||
const { popup, ...props } = options;
|
||||
@@ -337,7 +337,7 @@ export function showPopup(options: ShowPopupOptions) {
|
||||
props.onClose?.();
|
||||
}}
|
||||
>
|
||||
{popup(hide)}
|
||||
{popup&&popup(hide)}
|
||||
</ResponsivePresenter>
|
||||
</ThemeProvider>,
|
||||
getPopupContainer()
|
||||
|
||||
@@ -35,7 +35,7 @@ export type AttachmentWithProgress = AttachmentProgress & Attachment;
|
||||
export type Attachment = {
|
||||
hash: string;
|
||||
filename: string;
|
||||
mime: string;
|
||||
type: string;
|
||||
size: number;
|
||||
};
|
||||
|
||||
@@ -88,7 +88,7 @@ export const AttachmentNode = Node.create<AttachmentOptions>({
|
||||
},
|
||||
hash: getDataAttribute("hash"),
|
||||
filename: getDataAttribute("filename"),
|
||||
mime: getDataAttribute("mime"),
|
||||
type: getDataAttribute("mime"),
|
||||
size: getDataAttribute("size")
|
||||
};
|
||||
},
|
||||
@@ -166,7 +166,6 @@ export const AttachmentNode = Node.create<AttachmentOptions>({
|
||||
previewAttachment:
|
||||
(attachment) =>
|
||||
({ editor }) => {
|
||||
if (!this.options.onPreviewAttachment) return false;
|
||||
return this.options.onPreviewAttachment(editor, attachment);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -87,11 +87,7 @@ export function AttachmentComponent(
|
||||
{selected && (
|
||||
<ToolbarGroup
|
||||
editor={editor}
|
||||
tools={[
|
||||
"removeAttachment",
|
||||
"downloadAttachment",
|
||||
"previewAttachment"
|
||||
]}
|
||||
tools={["removeAttachment", "downloadAttachment"]}
|
||||
sx={{
|
||||
boxShadow: "menu",
|
||||
borderRadius: "default",
|
||||
|
||||
@@ -83,7 +83,7 @@ export function ImageComponent(
|
||||
setSource(url);
|
||||
editor.current?.commands.updateImage(
|
||||
{ src },
|
||||
{ src: await toDataURL(blob), size, mime: type }
|
||||
{ src: await toDataURL(blob), size, type }
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -206,7 +206,6 @@ export function ImageComponent(
|
||||
<ToolbarGroup
|
||||
editor={editor}
|
||||
tools={[
|
||||
hash ? "previewAttachment" : "none",
|
||||
hash ? "downloadAttachment" : "none",
|
||||
"imageAlignLeft",
|
||||
float ? "none" : "imageAlignCenter",
|
||||
|
||||
@@ -138,7 +138,7 @@ export const ImageNode = Node.create<ImageOptions>({
|
||||
|
||||
hash: getDataAttribute("hash"),
|
||||
filename: getDataAttribute("filename"),
|
||||
mime: getDataAttribute("mime"),
|
||||
type: getDataAttribute("mime"),
|
||||
size: getDataAttribute("size"),
|
||||
aspectRatio: {
|
||||
default: undefined,
|
||||
@@ -209,7 +209,6 @@ export const ImageNode = Node.create<ImageOptions>({
|
||||
insertImage:
|
||||
(options) =>
|
||||
({ commands }) => {
|
||||
console.log(options);
|
||||
return commands.insertContent({
|
||||
type: this.name,
|
||||
attrs: options
|
||||
|
||||
@@ -17,11 +17,16 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Editor, Extension } from "@tiptap/core";
|
||||
import { Extension } from "@tiptap/core";
|
||||
import { isInTable } from "@tiptap/pm/tables";
|
||||
import { LIST_ITEM_NODE_TYPES, LIST_NODE_TYPES } from "../../utils/node-types";
|
||||
import { isListActive } from "../../utils/prosemirror";
|
||||
import { CodeBlock } from "../code-block";
|
||||
import { ShowBlockNodesComponent } from "../../toolbar/popups/blocknodes-popup";
|
||||
import { Editor } from "../../types";
|
||||
import { getAllTools } from "../../toolbar";
|
||||
import { ToolId } from "../../toolbar/tools";
|
||||
import { MenuItem } from "../../components/menu/types";
|
||||
|
||||
export const KeyMap = Extension.create({
|
||||
name: "key-map",
|
||||
@@ -43,10 +48,77 @@ export const KeyMap = Extension.create({
|
||||
return true;
|
||||
},
|
||||
Backspace: ({ editor }) => {
|
||||
return joinUpWithLastListItem(editor);
|
||||
return joinUpWithLastListItem(editor as Editor);
|
||||
},
|
||||
"/": ({ editor }) => {
|
||||
const { state } = editor as Editor;
|
||||
const { $from } = state.selection;
|
||||
const before = $from.nodeBefore?.textContent;
|
||||
|
||||
if (before) return false;
|
||||
|
||||
const selectedElement = editor.view.domAtPos($from.pos)
|
||||
.node as HTMLElement;
|
||||
|
||||
const menuItems: MenuItem[] = [];
|
||||
const defaultTools = Object.keys(getAllTools()).splice(1, 6);
|
||||
for (const key of defaultTools) {
|
||||
const blocknode = getAllTools()[key as ToolId];
|
||||
menuItems.push({
|
||||
key: blocknode.icon,
|
||||
type: "button",
|
||||
title: blocknode.title,
|
||||
icon: blocknode.icon,
|
||||
onClick() {
|
||||
console.log("clicked");
|
||||
/*
|
||||
How to apply on click methods
|
||||
editor.current?.chain().focus()....
|
||||
//.toggleOutlineList().run();//
|
||||
*/
|
||||
}
|
||||
});
|
||||
}
|
||||
ShowBlockNodesComponent({
|
||||
editor: editor as Editor,
|
||||
selectedElement: selectedElement,
|
||||
items: menuItems
|
||||
});
|
||||
|
||||
(editor as Editor).current?.commands.focus();
|
||||
|
||||
(editor as Editor).current?.on("update", ({ editor }) => {
|
||||
const menuItems: MenuItem[] = [];
|
||||
const { state } = editor as Editor;
|
||||
const { $from } = state.selection;
|
||||
const before = $from.nodeBefore?.textContent;
|
||||
|
||||
let keys = Object.keys(getAllTools()).filter(
|
||||
(string) => string.indexOf(before as string) > -1
|
||||
);
|
||||
console.log("keys", keys);
|
||||
if (!before && keys.length === 0) keys = defaultTools;
|
||||
for (const key of keys.slice(0, 6)) {
|
||||
const blocknode = getAllTools()[key as ToolId];
|
||||
console.log(blocknode);
|
||||
menuItems.push({
|
||||
key: blocknode.icon,
|
||||
icon: blocknode.icon,
|
||||
type: "button",
|
||||
title: blocknode.title
|
||||
});
|
||||
}
|
||||
ShowBlockNodesComponent({
|
||||
editor: editor as Editor,
|
||||
selectedElement: selectedElement,
|
||||
items: menuItems
|
||||
});
|
||||
(editor as Editor).current?.commands.focus();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
};
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,9 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Table as TiptapTable, TableOptions } from "@tiptap/extension-table";
|
||||
import { tableEditing, columnResizing, TableView } from "@tiptap/pm/tables";
|
||||
import { columnResizing, tableEditing } from "@tiptap/pm/tables";
|
||||
import { TableNodeView } from "./component";
|
||||
import { Plugin, PluginKey } from "prosemirror-state";
|
||||
|
||||
export const Table = TiptapTable.extend<TableOptions>({
|
||||
addProseMirrorPlugins() {
|
||||
@@ -36,20 +35,10 @@ export const Table = TiptapTable.extend<TableOptions>({
|
||||
lastColumnResizable: this.options.lastColumnResizable
|
||||
})
|
||||
]
|
||||
: [tiptapTableView(this.options.cellMinWidth)]),
|
||||
: []),
|
||||
tableEditing({
|
||||
allowTableNodeSelection: this.options.allowTableNodeSelection
|
||||
})
|
||||
];
|
||||
}
|
||||
});
|
||||
|
||||
const TiptapTableViewPluginKey = new PluginKey("TiptapTableView");
|
||||
function tiptapTableView(cellMinWidth: number): Plugin {
|
||||
return new Plugin({
|
||||
key: TiptapTableViewPluginKey,
|
||||
props: {
|
||||
nodeViews: { [Table.name]: (node) => new TableView(node, cellMinWidth) }
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
62
packages/editor/src/toolbar/popups/blocknodes-popup.tsx
Normal file
62
packages/editor/src/toolbar/popups/blocknodes-popup.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
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 { useToolbarStore } from "../stores/toolbar-store";
|
||||
import { showPopup } from "../../components/popup-presenter";
|
||||
import { Editor } from "../../types";
|
||||
import { MenuItem } from "../../components/menu/types";
|
||||
import { getToolbarElement } from "../utils/dom";
|
||||
|
||||
export const ShowBlockNodesComponent = (props: {
|
||||
editor: Editor;
|
||||
selectedElement: HTMLElement;
|
||||
items: MenuItem[];
|
||||
}) => {
|
||||
const { editor, selectedElement, items } = props;
|
||||
//const items = toMenuItems(editor);
|
||||
const toolbarLocation = useToolbarStore.getState().toolbarLocation;
|
||||
const isMobile = useToolbarStore.getState().isMobile;
|
||||
const isBottom = toolbarLocation === "bottom";
|
||||
const xOffset = isBottom ? 0 : -selectedElement.offsetWidth / 2 + 75;
|
||||
|
||||
showPopup({
|
||||
items: items,
|
||||
position: {
|
||||
target: isBottom ? getToolbarElement() : selectedElement,
|
||||
isTargetAbsolute: true,
|
||||
location: isBottom ? "top" : "below",
|
||||
align: "center",
|
||||
yOffset: 0,
|
||||
xOffset: xOffset
|
||||
},
|
||||
blocking: !isMobile,
|
||||
focusOnRender: !isMobile,
|
||||
sx: {
|
||||
minWidth: 150,
|
||||
maxWidth: isBottom ? "95vw" : "auto",
|
||||
flexDirection: isBottom ? "row" : "column",
|
||||
overflowX: isBottom ? "auto" : "hidden",
|
||||
marginRight: isBottom ? "10px" : 0,
|
||||
display: "flex",
|
||||
alignItems: isBottom ? "center" : "unset"
|
||||
},
|
||||
onClose: () => {
|
||||
editor.current?.off("update");
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -53,7 +53,7 @@ export function ImageUploadPopup(props: ImageUploadPopupProps) {
|
||||
url,
|
||||
downloadOptions
|
||||
);
|
||||
onInsert({ src: await toDataURL(blob), size, mime: type });
|
||||
onInsert({ src: await toDataURL(blob), size, type });
|
||||
} catch (e) {
|
||||
if (e instanceof Error) setError(e.message);
|
||||
} finally {
|
||||
|
||||
@@ -351,7 +351,6 @@ export function getDefaultPresets() {
|
||||
return defaultPresets;
|
||||
}
|
||||
|
||||
export const MOBILE_ONLY_TOOLS: ToolbarDefinition = [["previewAttachment"]];
|
||||
export const STATIC_TOOLBAR_GROUPS: ToolbarDefinition = [
|
||||
[
|
||||
"insertBlock",
|
||||
@@ -359,6 +358,7 @@ export const STATIC_TOOLBAR_GROUPS: ToolbarDefinition = [
|
||||
"cellProperties",
|
||||
"imageSettings",
|
||||
"embedSettings",
|
||||
"previewAttachment",
|
||||
"attachmentSettings",
|
||||
"linkSettings",
|
||||
"codeRemove",
|
||||
|
||||
@@ -22,11 +22,7 @@ import { Editor } from "../types";
|
||||
import { Flex, FlexProps } from "@theme-ui/components";
|
||||
import { ThemeProvider } from "@emotion/react";
|
||||
import { EditorFloatingMenus } from "./floating-menus";
|
||||
import {
|
||||
getDefaultPresets,
|
||||
STATIC_TOOLBAR_GROUPS,
|
||||
MOBILE_ONLY_TOOLS
|
||||
} from "./tool-definitions";
|
||||
import { getDefaultPresets, STATIC_TOOLBAR_GROUPS } from "./tool-definitions";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import {
|
||||
ToolbarLocation,
|
||||
@@ -60,15 +56,13 @@ export function Toolbar(props: ToolbarProps) {
|
||||
sx,
|
||||
...flexProps
|
||||
} = props;
|
||||
const isMobile = useIsMobile();
|
||||
const toolbarTools = useMemo(
|
||||
() =>
|
||||
isMobile
|
||||
? [...STATIC_TOOLBAR_GROUPS, ...MOBILE_ONLY_TOOLS, ...tools]
|
||||
: [...STATIC_TOOLBAR_GROUPS, ...tools],
|
||||
[tools, isMobile]
|
||||
() => [...STATIC_TOOLBAR_GROUPS, ...tools],
|
||||
[tools]
|
||||
);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const setToolbarLocation = useToolbarStore(
|
||||
(store) => store.setToolbarLocation
|
||||
);
|
||||
|
||||
@@ -64,12 +64,9 @@ export function DownloadAttachment(props: ToolProps) {
|
||||
|
||||
export function PreviewAttachment(props: ToolProps) {
|
||||
const { editor } = props;
|
||||
const attachmentNode =
|
||||
findSelectedNode(editor, "attachment") || findSelectedNode(editor, "image");
|
||||
const attachment = (attachmentNode?.attrs || {}) as Attachment;
|
||||
const isBottom = useToolbarLocation() === "bottom";
|
||||
|
||||
if (!editor.isActive("image") && !canPreviewAttachment(attachment))
|
||||
return null;
|
||||
if (!editor.isActive("image") || !isBottom) return null;
|
||||
|
||||
return (
|
||||
<ToolButton
|
||||
@@ -81,7 +78,7 @@ export function PreviewAttachment(props: ToolProps) {
|
||||
findSelectedNode(editor, "image");
|
||||
|
||||
const attachment = (attachmentNode?.attrs || {}) as Attachment;
|
||||
editor.current?.commands.previewAttachment(attachment);
|
||||
editor.current?.chain().focus().previewAttachment(attachment).run();
|
||||
}}
|
||||
/>
|
||||
);
|
||||
@@ -97,20 +94,3 @@ export function RemoveAttachment(props: ToolProps) {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const previewableFileExtensions = ["pdf"];
|
||||
const previewableMimeTypes = ["application/pdf"];
|
||||
|
||||
function canPreviewAttachment(attachment: Attachment) {
|
||||
if (!attachment) return false;
|
||||
if (
|
||||
attachment.mime &&
|
||||
previewableMimeTypes.some((mime) => attachment.mime.startsWith(mime))
|
||||
)
|
||||
return true;
|
||||
|
||||
const extension = attachment.filename?.split(".").pop();
|
||||
if (!extension) return false;
|
||||
|
||||
return previewableFileExtensions.indexOf(extension) > -1;
|
||||
}
|
||||
|
||||
@@ -92,14 +92,4 @@ export default class FileHandle {
|
||||
}
|
||||
return new Blob(blobParts, { type: this.file.type });
|
||||
}
|
||||
|
||||
async size() {
|
||||
let size = 0;
|
||||
for (let i = 0; i < this.file.chunks; ++i) {
|
||||
const array = await this.readChunk(i);
|
||||
if (!array) continue;
|
||||
size += array.length;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user