Compare commits

..

3 Commits

Author SHA1 Message Date
Muhammad Ali
aaa28b5d24 editor: added all tools in / command pop-up. 2023-07-19 13:23:36 +05:00
Muhammad Ali
6a86a0e088 editor: added filter in console for testing query 2023-06-01 16:57:02 +05:00
Muhammad Ali
40c0b527c9 editor: added slah popup for block-nodes 2023-05-30 09:22:13 +05:00
101 changed files with 41268 additions and 11588 deletions

View File

@@ -20,7 +20,6 @@ const SCOPES = [
"logger",
"theme",
"core",
"fs",
"clipper",
"config",
"ci",

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -17,11 +17,11 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 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);
}
}

View File

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

View File

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

View File

@@ -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 />
</>
);
};

View File

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

View File

@@ -47,7 +47,7 @@ export const Tags = ({ item, close }) => {
>
<Button
onPress={async () => {
ManageTagsSheet.present([item]);
ManageTagsSheet.present(item);
}}
buttonType={{
text: colors.accent

View File

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

View File

@@ -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>
);
};

View File

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

View File

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

View File

@@ -19,17 +19,4 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { FeatureType } from "./components/sheets/new-feature";
export const features: FeatureType[] = [
{
title: "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[] = [];

View File

@@ -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];
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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)";

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -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": "*"
}
}

View 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')
-}

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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);
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -72,7 +72,7 @@ function Header({ readonly }) {
}
export default Header;
export function Autosuggest({
function Autosuggest({
sessionId,
filter,
onRemove,

View File

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

View File

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

View File

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

View File

@@ -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>
</>
);
}

View File

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

View File

@@ -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>
);
}
}

View File

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

View File

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

View File

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

View File

@@ -77,8 +77,7 @@ function Notebook(props) {
sx={{
fontSize: "subBody",
color: "fontTertiary",
alignItems: "center",
fontFamily: "body"
alignItems: "center"
}}
>
{notebook.pinned && (

View File

@@ -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 });
}

View File

@@ -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");
});
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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 = [];

View File

@@ -151,7 +151,6 @@ class AppStore extends BaseStore {
tagStore.refresh();
attachmentStore.refresh();
monographStore.refresh();
await editorstore.refresh();
this.refreshNavItems();
logger.measure("refreshing app");

View File

@@ -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));
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -31,7 +31,7 @@ import { EventTypes, isReactNative, post } from "../utils";
type Attachment = {
hash: string;
filename: string;
mime: string;
type: string;
size: number;
};

File diff suppressed because it is too large Load Diff

View File

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

View 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,

View File

@@ -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');

View File

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

View File

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

View File

@@ -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);
}
};

View File

@@ -87,11 +87,7 @@ export function AttachmentComponent(
{selected && (
<ToolbarGroup
editor={editor}
tools={[
"removeAttachment",
"downloadAttachment",
"previewAttachment"
]}
tools={["removeAttachment", "downloadAttachment"]}
sx={{
boxShadow: "menu",
borderRadius: "default",

View File

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

View File

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

View File

@@ -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;
}
};
},
}
});
/**

View File

@@ -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) }
}
});
}

View 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");
}
});
};

View File

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

View File

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

View File

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

View File

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

View File

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