mobile: update ui

This commit is contained in:
Ammar Ahmed
2026-07-08 09:57:25 +05:00
parent 368e9b6fe1
commit e9d968aa8a
35 changed files with 6294 additions and 5898 deletions

View File

@@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { formatBytes } from "@notesnook/common";
import { Attachment, Note, VirtualizedGrouping } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import Clipboard from "@react-native-clipboard/clipboard";
import React, { RefObject, useEffect, useState } from "react";
@@ -26,6 +27,7 @@ import { TextInput, View } from "react-native";
import { ActionSheetRef } from "react-native-actions-sheet";
import { ScrollView } from "react-native-gesture-handler";
import { db } from "../../common/database";
import { Radius, Spacing } from "../../common/design/spacing";
import filesystem from "../../common/filesystem";
import downloadAttachment from "../../common/filesystem/download-attachment";
import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
@@ -37,6 +39,7 @@ import {
eSendEvent,
presentSheet
} from "../../services/event-manager";
import Navigation from "../../services/navigation";
import PremiumService from "../../services/premium";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import {
@@ -46,20 +49,19 @@ import {
eOnLoadNote
} from "../../utils/events";
import { AppFontSize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { Dialog } from "../dialog";
import { presentDialog } from "../dialog/functions";
import { openNote } from "../list-items/note/wrapper";
import { DateMeta } from "../properties/date-meta";
import SheetProvider from "../sheet-provider";
import { Button } from "../ui/button";
import AppIcon from "../ui/AppIcon";
import { createFormRef, validators } from "../ui/input/form-input";
import { Notice } from "../ui/notice";
import { Pressable } from "../ui/pressable";
import LineSeparator from "../ui/seperator/line-separator";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../utils/styles";
import Navigation from "../../services/navigation";
import { createFormRef, validators } from "../ui/input/form-input";
const Actions = ({
attachment,
@@ -94,7 +96,7 @@ const Actions = ({
downloadAttachment(attachment.hash, context === "global");
fwdRef.current?.hide();
},
icon: "download"
icon: "download-simple"
},
{
name: strings.network.reupload(),
@@ -114,7 +116,7 @@ const Actions = ({
type: attachment.mimeType.startsWith("image") ? "image" : "file"
});
},
icon: "upload"
icon: "upload-simple"
},
{
name: strings.fileCheck(),
@@ -149,7 +151,7 @@ const Actions = ({
name: undefined
});
},
icon: "file-check"
icon: "file"
},
{
name: strings.rename(),
@@ -197,7 +199,7 @@ const Actions = ({
});
}, 500);
},
icon: "form-textbox"
icon: "pencil-simple"
},
{
name: strings.delete(),
@@ -249,7 +251,7 @@ const Actions = ({
});
}, 500);
},
icon: "delete-outline"
icon: "trash"
}
];
@@ -267,6 +269,7 @@ const Actions = ({
onMomentumScrollEnd={() => {
fwdRef?.current?.handleChildScrollEnd();
}}
bounces={false}
nestedScrollEnabled={true}
style={{
maxHeight: "100%"
@@ -277,42 +280,34 @@ const Actions = ({
<SheetProvider context={contextId} />
<View
style={{
borderBottomWidth: 1,
borderBottomColor: colors.primary.border,
marginBottom:
notes && notes.length > 0 ? 0 : DefaultAppStyles.GAP_VERTICAL
paddingHorizontal: Spacing.LEVEL_3,
paddingTop: Spacing.LEVEL_4,
gap: Spacing.LEVEL_1
}}
>
<Heading
style={{
paddingHorizontal: DefaultAppStyles.GAP
}}
size={AppFontSize.lg}
>
{filename}
</Heading>
<Heading fontSize="LG">{filename}</Heading>
<View
style={{
flexDirection: "row",
marginBottom: DefaultAppStyles.GAP_VERTICAL,
paddingHorizontal: DefaultAppStyles.GAP,
marginTop: DefaultAppStyles.GAP_VERTICAL_SMALL,
gap: 10
gap: Spacing.LEVEL_1,
alignItems: "center"
}}
>
<Paragraph size={AppFontSize.xs} color={colors.secondary.paragraph}>
<Paragraph fontSize="XS" color={colors.secondary.paragraph}>
{attachment.mimeType}
</Paragraph>
<Paragraph fontSize="XS"></Paragraph>
<Paragraph size={AppFontSize.xs} color={colors.secondary.paragraph}>
{formatBytes(attachment.size)}
</Paragraph>
<Paragraph fontSize="XS"></Paragraph>
{notes.length ? (
<Paragraph size={AppFontSize.xs} color={colors.secondary.paragraph}>
<Paragraph fontSize="XS" color={colors.secondary.paragraph}>
{strings.notes(notes.length)}
</Paragraph>
) : null}
<Paragraph fontSize="XS"></Paragraph>
<Paragraph
onPress={() => {
Clipboard.setString(attachment.hash);
@@ -322,82 +317,123 @@ const Actions = ({
context: "local"
});
}}
size={AppFontSize.xs}
fontSize="XS"
color={colors.secondary.paragraph}
>
<AppIcon
name="copy"
size={10}
color={colors.secondary.paragraph}
iconFamily="notesnook"
/>{" "}
{attachment.hash}
</Paragraph>
</View>
<LineSeparator paddingVertical={Spacing.LEVEL_2} />
<DateMeta item={attachment} />
</View>
{notes && notes.length > 0 ? (
<View
style={{
borderBottomWidth: 1,
borderBottomColor: colors.primary.border,
marginBottom: DefaultAppStyles.GAP_VERTICAL,
paddingVertical: DefaultAppStyles.GAP_VERTICAL
}}
>
<>
<Heading
style={{
paddingHorizontal: DefaultAppStyles.GAP
}}
size={AppFontSize.sm}
>
{strings.listOf()} {strings.dataTypesPlural.note()}:
</Heading>
<>
<LineSeparator
paddingHorizontal={Spacing.LEVEL_3}
paddingVertical={Spacing.LEVEL_3}
/>
<View
style={{
paddingHorizontal: Spacing.LEVEL_3,
gap: Spacing.LEVEL_2
}}
>
<>
<Heading size={AppFontSize.sm}>
{strings.notes(notes.length)}
</Heading>
{notes.map((item) => (
<Pressable
onPress={async () => {
eSendEvent(eCloseSheet, contextId);
close?.();
eSendEvent(eCloseAttachmentDialog);
Navigation.navigate("FluidPanelsView");
openNote(item, (item as any).type === "trash");
}}
style={{
paddingVertical: DefaultAppStyles.GAP_VERTICAL,
alignItems: "flex-start",
paddingHorizontal: DefaultAppStyles.GAP
}}
key={item.id}
>
<Paragraph size={AppFontSize.xs}>{item.title}</Paragraph>
</Pressable>
))}
</>
</View>
{notes.map((item) => (
<Pressable
onPress={async () => {
eSendEvent(eCloseSheet, contextId);
close?.();
eSendEvent(eCloseAttachmentDialog);
Navigation.navigate("FluidPanelsView");
openNote(item, (item as any).type === "trash");
}}
style={{
alignItems: "flex-start"
}}
key={item.id}
>
<Paragraph fontSize="XS">{item.title}</Paragraph>
</Pressable>
))}
</>
</View>
</>
) : null}
{actions.map((item) => (
<Button
key={item.name}
buttonType={{
text:
item.name === "Delete"
? colors.error.paragraph
: colors.primary.paragraph
}}
onPress={item.onPress}
title={item.name}
icon={item.icon}
loading={loading?.name === item.name}
type="plain"
fontSize={AppFontSize.sm}
style={{
borderRadius: 0,
justifyContent: "flex-start",
alignSelf: "flex-start",
width: "100%"
}}
/>
))}
<LineSeparator
paddingHorizontal={Spacing.LEVEL_3}
paddingVertical={Spacing.LEVEL_3}
/>
<View
style={{
gap: Spacing.LEVEL_2
}}
>
{actions.map((item) => (
<Pressable
key={item.name}
onPress={item.onPress}
type="plain"
style={{
borderRadius: 0,
flexDirection: "row",
width: "100%",
gap: Spacing.LEVEL_1,
justifyContent: "flex-start",
paddingHorizontal: Spacing.LEVEL_3
}}
>
<View
style={{
width: 32,
height: 32,
backgroundColor: colors.secondary.background,
borderRadius: Radius.XS,
justifyContent: "center",
alignItems: "center"
}}
>
<AppIcon
color={
item.icon === "trash"
? colors.error.accent
: colors.primary.icon
}
name={item.icon}
iconFamily="notesnook"
size={16}
/>
</View>
<Paragraph
color={
item.icon === "trash"
? colors.error.accent
: colors.primary.heading
}
fontSize="SM"
fontFamily="MEDIUM"
>
{item.name}
</Paragraph>
</Pressable>
))}
</View>
<View
style={{

View File

@@ -17,26 +17,29 @@ 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 { formatBytes } from "@notesnook/common";
import { formatBytes, getFormattedDate } from "@notesnook/common";
import { Attachment, VirtualizedGrouping } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React from "react";
import { TouchableOpacity, View } from "react-native";
import { db } from "../../common/database";
import { View } from "react-native";
import { Radius, Spacing } from "../../common/design/spacing";
import { useAttachmentProgress } from "../../hooks/use-attachment-progress";
import { useDBItem } from "../../hooks/use-db-item";
import { defaultBorderRadius, AppFontSize } from "../../utils/size";
import { IconButton } from "../ui/icon-button";
import { ProgressCircleComponent } from "../ui/svg/lazy";
import AppIcon from "../ui/AppIcon";
import { Pressable } from "../ui/pressable";
import { ProgressBarComponent } from "../ui/svg/lazy";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import Actions from "./actions";
import { strings } from "@notesnook/intl";
import { Pressable } from "../ui/pressable";
import { DefaultAppStyles } from "../../utils/styles";
import { IconButton } from "../ui/icon-button";
function getFileExtension(filename: string) {
const ext = /^.+\.([^.]+)$/.exec(filename);
return ext == null ? "" : ext[1];
function getAttachmentIcon(attachment: Attachment) {
const mimeType = attachment.mimeType || "";
if (mimeType.startsWith("image/")) return "image";
if (mimeType.startsWith("video/")) return "video-camera";
if (mimeType.startsWith("audio/")) return "music-notes";
return "file-text";
}
export const AttachmentItem = ({
@@ -75,11 +78,15 @@ export const AttachmentItem = ({
onPress={onPress}
style={{
flexDirection: "row",
marginVertical: 5,
alignItems: "center",
justifyContent: "space-between",
padding: DefaultAppStyles.GAP,
paddingVertical: DefaultAppStyles.GAP_VERTICAL,
minHeight: 45
gap: Spacing.LEVEL_2,
borderWidth: 1,
borderColor: colors.primary.border,
borderRadius: Radius.S,
paddingHorizontal: Spacing.LEVEL_2,
paddingVertical: Spacing.LEVEL_3,
marginBottom: Spacing.LEVEL_2
}}
>
{!attachment ? null : (
@@ -89,107 +96,112 @@ export const AttachmentItem = ({
flexShrink: 1,
flexDirection: "row",
alignItems: "center",
gap: 10
gap: Spacing.LEVEL_2
}}
>
<View
style={{
width: 40,
height: 40,
borderRadius: Radius.XS,
backgroundColor: colors.secondary.background,
justifyContent: "center",
alignItems: "center",
marginLeft: -5,
borderWidth: 1,
borderColor: colors.secondary.border,
paddingHorizontal: 2,
width: 30,
height: 30,
borderRadius: defaultBorderRadius
alignItems: "center"
}}
>
<Paragraph
adjustsFontSizeToFit
size={8}
color={colors.secondary.paragraph}
style={{
maxWidth: 30
}}
numberOfLines={1}
>
{getFileExtension(attachment.filename).toUpperCase() ||
attachment.mimeType.split("/")?.[1]?.toUpperCase()}
</Paragraph>
<AppIcon
name={getAttachmentIcon(attachment)}
iconFamily="notesnook"
size={20}
color={colors.primary.icon}
/>
</View>
<View
style={{
flexShrink: 1
flexShrink: 1,
width: "100%",
gap: Spacing.LEVEL_1
}}
>
<Paragraph
size={AppFontSize.sm}
style={{
flexWrap: "wrap"
}}
numberOfLines={1}
<Heading
fontSize="MD"
lineBreakMode="middle"
color={colors.primary.paragraph}
color={colors.primary.heading}
>
{attachment.filename}
</Paragraph>
book.pdf
</Heading>
{!hideWhenNotDownloading ? (
<Paragraph
color={colors.secondary.paragraph}
size={AppFontSize.xxs}
<View
style={{
flexDirection: "row",
gap: Spacing.LEVEL_1
}}
>
{attachment.failed ? (
<AppIcon
size={13}
name="alert-circle-outline"
color={colors.error.paragraph}
/>
) : null}
{!hideWhenNotDownloading ? (
<Paragraph
fontSize="XS"
numberOfLines={1}
color={colors.secondary.paragraph}
>
{getFormattedDate(attachment.dateModified)} |{" "}
{formatBytes(attachment.size)}
</Paragraph>
) : null}
</View>
{currentProgress ? (
<View
style={{
width: "100%",
gap: Spacing.LEVEL_0,
flexGrow: 1
}}
>
{strings.fileSize()}: {formatBytes(attachment.size)}
</Paragraph>
<ProgressBarComponent
height={6}
width={null}
animated={true}
useNativeDriver
progress={currentProgress.value}
unfilledColor={colors.secondary.background}
color={colors.primary.accent}
borderWidth={0}
/>
<Paragraph color={colors.secondary.paragraph} fontSize="XS">
{currentProgress.percent} |{" "}
{currentProgress.type === "upload"
? strings.uploading()
: strings.downloading()}
</Paragraph>
</View>
) : null}
</View>
</View>
{currentProgress ? (
<TouchableOpacity
activeOpacity={0.9}
onPress={() => {
if (encryption || !pressable) return;
db.fs().cancel(attachment.hash);
setCurrentProgress(undefined);
}}
style={{
justifyContent: "center",
marginLeft: 5,
marginTop: 5,
marginRight: -5
}}
>
<ProgressCircleComponent
size={AppFontSize.xxl}
progress={
currentProgress?.value ? currentProgress?.value / 100 : 0
}
showsText
textStyle={{
fontSize: 10
}}
color={colors.primary.accent}
formatText={(progress) => (progress * 100).toFixed(0)}
borderWidth={0}
thickness={2}
/>
</TouchableOpacity>
) : (
<>
{attachment.failed ? (
<IconButton
onPress={onPress}
name="alert-circle-outline"
color={colors.error.paragraph}
/>
) : null}
</>
)}
</>
)}
<IconButton
color={colors.secondary.icon}
name="dots-three"
iconFamily="notesnook"
size={20}
onPress={onPress}
style={{
justifyContent: "center",
height: undefined,
width: undefined,
borderRadius: 100,
alignItems: "center"
}}
/>
</Pressable>
);
};

View File

@@ -23,19 +23,15 @@ import NoteItem from ".";
import { notesnook } from "../../../../e2e/test.ids";
import { db } from "../../../common/database";
import { DDS } from "../../../services/device-detection";
import {
eSendEvent,
hideSheet,
presentSheet
} from "../../../services/event-manager";
import { eSendEvent, hideSheet } from "../../../services/event-manager";
import { eOnLoadNote, eShowMergeDialog } from "../../../utils/events";
import { fluidTabsRef } from "../../../utils/global-refs";
import { NotebooksWithDateEdited, TagsWithDateEdited } from "@notesnook/common";
import { useTabStore } from "../../../screens/editor/tiptap/use-tab-store";
import { editorController } from "../../../screens/editor/tiptap/utils";
import Navigation from "../../../services/navigation";
import { RouteParams } from "../../../stores/use-navigation-store";
import NotePreview from "../../note-history/preview";
import SelectionWrapper from "../selection-wrapper";
import { selectItem } from "../../../stores/use-selection-store";
@@ -60,11 +56,7 @@ export const openNote = async (
if (isTrash) {
if (!note.contentId) return;
const content = await db.content.get(note.contentId as string);
presentSheet({
component: <NotePreview note={item} content={content} />
});
Navigation.navigate("NotePreview", { note: item });
} else {
if (!useTabStore.getState().hasTabForNote(note.id!)) {
editorController.current.commands.setLoading(

View File

@@ -1,265 +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, { RefObject, useCallback, useEffect, useState } from "react";
import { ActivityIndicator, Text, View } from "react-native";
import { LegendList } from "@legendapp/list";
import { getFormattedDate, getTimeAgo } from "@notesnook/common";
import { HistorySession, Note, VirtualizedGrouping } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import { useDBItem } from "../../hooks/use-db-item";
import { presentSheet, ToastManager } from "../../services/event-manager";
import { openLinkInBrowser } from "../../utils/functions";
import { AppFontSize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import DialogHeader from "../dialog/dialog-header";
import SheetProvider from "../sheet-provider";
import { Pressable } from "../ui/pressable";
import Seperator from "../ui/seperator";
import Paragraph from "../ui/typography/paragraph";
import NotePreview from "./preview";
import { presentDialog } from "../dialog/functions";
import { Dialog } from "../dialog";
const HistoryItem = ({
index,
items,
note,
refresh
}: {
index: number;
items?: VirtualizedGrouping<HistorySession>;
note: Note;
refresh: () => void;
}) => {
const [item] = useDBItem(index, "noteHistory", items);
const { colors } = useThemeColors();
const getDate = (start: number, end: number) => {
const _start_date = getFormattedDate(start, "date");
const _end_date = getFormattedDate(end + 60000, "date");
const _start_time = getFormattedDate(start, "time");
const _end_time = getFormattedDate(end + 60000, "time");
return `${_start_date} ${_start_time} - ${
_end_date === _start_date ? " " : _end_date + " "
}${_end_time}`;
};
const preview = useCallback(
async (item: HistorySession) => {
const content = await db.noteHistory.content(item.id);
presentSheet({
component: (
<NotePreview
session={{
...item,
session: getDate(item.dateCreated, item.dateModified)
}}
content={content}
note={note}
update={refresh}
/>
),
context: "note_history"
});
},
[note, refresh]
);
return (
<Pressable
type="secondary"
onPress={() => {
if (!item) return;
preview(item);
}}
style={{
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: DefaultAppStyles.GAP,
height: 45,
marginBottom: DefaultAppStyles.GAP_VERTICAL,
flexDirection: "row"
}}
>
{!item ? null : (
<>
<Paragraph>{getDate(item.dateCreated, item.dateModified)}</Paragraph>
<Paragraph color={colors.secondary.paragraph} size={AppFontSize.xs}>
{getTimeAgo(item.dateModified)}
</Paragraph>
</>
)}
</Pressable>
);
};
export default function NoteHistory({
note,
fwdRef
}: {
note: Note;
fwdRef: RefObject<ActionSheetRef>;
}) {
const [history, setHistory] = useState<VirtualizedGrouping<HistorySession>>();
const [_loading, setLoading] = useState(true);
const { colors } = useThemeColors();
const refresh = useCallback(() => {
db.noteHistory
.get(note.id)
.sorted({
sortBy: "dateModified",
sortDirection: "desc"
})
.then((result) => {
setHistory(result);
setLoading(false);
})
.catch((e) => {
setLoading(false);
});
}, [note.id]);
useEffect(() => {
refresh();
}, [refresh]);
const renderItem = useCallback(
({ index }: { index: number }) => (
<HistoryItem
index={index}
items={history}
note={note}
refresh={refresh}
/>
),
[history, note, refresh]
);
return (
<View>
<SheetProvider context="note_history" />
<Dialog context="local" />
<DialogHeader
title={strings.noteHistory()}
padding={12}
button={
history?.placeholders.length
? {
title: strings.clearHistory(),
type: "secondary",
onPress: () => {
presentDialog({
title: strings.clearHistory(),
paragraph: strings.clearHistoryConfirmation(),
positiveText: strings.clear(),
negativeText: strings.cancel(),
positiveType: "errorShade",
context: "local",
positivePress: async () => {
await db.noteHistory.clearSessions(note.id);
fwdRef.current?.hide();
ToastManager.show({
heading: strings.historyCleared(),
type: "success"
});
}
});
}
}
: undefined
}
/>
<Seperator />
<View
style={{
paddingHorizontal: DefaultAppStyles.GAP,
height: !history?.placeholders.length
? 300
: (history.placeholders.length + 1) * 55,
maxHeight: "100%"
}}
>
<LegendList
renderScrollComponent={(props) => <ScrollView {...props} />}
data={history?.placeholders || []}
estimatedItemSize={55}
extraData={history}
ListEmptyComponent={
<View
style={{
width: "100%",
justifyContent: "center",
alignItems: "center",
height: 300,
gap: 10
}}
>
{_loading ? (
<ActivityIndicator
size={AppFontSize.xl}
color={colors.primary.accent}
/>
) : (
<>
<Icon name="history" size={50} color={colors.primary.icon} />
<Paragraph color={colors.secondary.paragraph}>
{strings.noteHistoryPlaceholder()}
</Paragraph>
</>
)}
</View>
}
renderItem={renderItem}
/>
</View>
<Paragraph
size={AppFontSize.xs}
color={colors.secondary.paragraph}
style={{
alignSelf: "center"
}}
>
{strings.noteHistoryNotice[0]()}{" "}
<Text
onPress={() => {
openLinkInBrowser(
"https://help.notesnook.com/note-version-history"
);
}}
style={{
color: colors.primary.accent,
textDecorationLine: "underline"
}}
>
{strings.noteHistoryNotice[1]()}
</Text>
</Paragraph>
</View>
);
}

View File

@@ -1,237 +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 { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import { db } from "../../common/database";
import { ReadonlyEditor } from "../../screens/editor/readonly-editor";
import { useTabStore } from "../../screens/editor/tiptap/use-tab-store";
import { editorController } from "../../screens/editor/tiptap/utils";
import { ToastManager, eSendEvent } from "../../services/event-manager";
import Navigation from "../../services/navigation";
import { useSelectionStore } from "../../stores/use-selection-store";
import { useTrashStore } from "../../stores/use-trash-store";
import { eCloseSheet, eOnLoadNote } from "../../utils/events";
import { Dialog } from "../dialog";
import DialogHeader from "../dialog/dialog-header";
import { presentDialog } from "../dialog/functions";
import { Button } from "../ui/button";
import Paragraph from "../ui/typography/paragraph";
import { diff } from "diffblazer";
import { strings } from "@notesnook/intl";
import { DefaultAppStyles } from "../../utils/styles";
import {
HistorySession,
isEncryptedContent,
Note,
NoteContent,
TrashOrItem
} from "@notesnook/core";
/**
*
* @param {any} param0
* @returns
*/
export default function NotePreview({
session,
content,
note,
update
}: {
session?: HistorySession & { session: string };
content:
| Partial<
NoteContent<boolean> & {
title: string;
}
>
| undefined;
note: TrashOrItem<Note>;
update?: () => void;
}) {
const { colors } = useThemeColors();
const [locked, setLocked] = useState(false);
async function restore() {
if (note && note.type === "trash") {
await db.trash.restore(note.id);
Navigation.queueRoutesForUpdate();
useSelectionStore.getState().setSelectionMode();
ToastManager.show({
heading: strings.noteRestored(),
type: "success"
});
eSendEvent(eCloseSheet);
return;
}
if (session) {
await db.noteHistory.restore(session.id);
if (useTabStore.getState().hasTabForNote(session?.noteId)) {
const note = editorController.current.note.current[session?.noteId];
if (note) {
eSendEvent(eOnLoadNote, {
item: note,
forced: true
});
}
}
eSendEvent(eCloseSheet, "note_history");
eSendEvent(eCloseSheet);
Navigation.queueRoutesForUpdate();
ToastManager.show({
heading: strings.noteRestoredFromHistory(),
type: "success"
});
}
}
useEffect(() => {
db.vaults.itemExists(note).then((locked) => setLocked(locked));
}, [note]);
const deleteNote = async () => {
const isSession = !!session;
presentDialog({
title: isSession ? strings.deleteVersion() : strings.deleteNote(),
paragraph: isSession
? strings.deleteVersionConfirmation()
: strings.deleteNoteConfirmation(),
positiveText: strings.delete(),
negativeText: strings.cancel(),
context: "preview",
positivePress: async () => {
if (isSession) {
await db.noteHistory.remove(session.id);
update?.();
eSendEvent(eCloseSheet, "note_history");
Navigation.queueRoutesForUpdate();
} else if (note.type === "trash") {
await db.trash.delete(note.id);
useTrashStore.getState().refresh();
useSelectionStore.getState().setSelectionMode();
eSendEvent(eCloseSheet);
}
ToastManager.show({
heading: isSession ? strings.versionDeleted() : strings.noteDeleted(),
type: "success",
context: isSession ? "local" : "global"
});
return true;
},
positiveType: "error"
});
};
return (
<View
style={{
height: locked || session?.locked ? null : 600,
width: "100%"
}}
>
<Dialog context="preview" />
<DialogHeader
padding={12}
title={content?.title || note.title || session?.session}
/>
{!session?.locked && !locked && content?.data ? (
<View
style={{
flex: 1,
backgroundColor: colors.primary.background
}}
>
<ReadonlyEditor
editorId="historyPreview"
onLoad={async (loadContent) => {
try {
if (content?.data) {
const currentContent = note?.contentId
? await db.content.get(note.contentId)
: undefined;
if (
currentContent?.data &&
!isEncryptedContent(currentContent)
) {
loadContent({
data: diff(
currentContent?.data || "<p></p>",
content.data as string
),
id: session?.noteId || note.id
});
}
}
} catch (e) {
ToastManager.error(
e as Error,
"Failed to load history preview",
"local"
);
}
}}
/>
</View>
) : (
<View
style={{
width: "100%",
height: 100,
justifyContent: "center",
alignItems: "center"
}}
>
<Paragraph color={colors.secondary.paragraph}>
{!content?.data
? strings.noContent()
: strings.encryptedNoteHistoryNotice()}
</Paragraph>
</View>
)}
<View
style={{
paddingHorizontal: DefaultAppStyles.GAP
}}
>
<Button
onPress={restore}
title={strings.restore()}
type="accent"
width="100%"
/>
<Button
onPress={deleteNote}
title={strings.deletePermanently()}
type="error"
width="100%"
style={{
marginTop: DefaultAppStyles.GAP_VERTICAL
}}
/>
</View>
</View>
);
}

View File

@@ -438,8 +438,8 @@ export const Items = ({
paginationStyle={{
position: "relative",
marginHorizontal: 2,
marginBottom: -10,
marginTop: 10
marginBottom: Spacing.LEVEL_0,
marginTop: Spacing.LEVEL_2
}}
contentContainerStyle={{
justifyContent: "flex-start",

View File

@@ -22,14 +22,8 @@ import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import Clipboard from "@react-native-clipboard/clipboard";
import React, { useEffect, useRef, useState } from "react";
import {
ActivityIndicator,
TextInput,
TouchableOpacity,
View
} from "react-native";
//@ts-ignore
import ToggleSwitch from "toggle-switch-react-native";
import { ActivityIndicator, TextInput, View } from "react-native";
import { Radius, Spacing } from "../../../common/design/spacing";
import { db } from "../../../common/database";
import { requestInAppReview } from "../../../services/app-review";
import {
@@ -40,12 +34,11 @@ import {
import Navigation from "../../../services/navigation";
import { useAttachmentStore } from "../../../stores/use-attachment-store";
import { openLinkInBrowser } from "../../../utils/functions";
import { AppFontSize, defaultBorderRadius } from "../../../utils/size";
import { DefaultAppStyles } from "../../../utils/styles";
import DialogHeader from "../../dialog/dialog-header";
import { presentDialog } from "../../dialog/functions";
import AppIcon, { IconProps } from "../../ui/AppIcon";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import Heading from "../../ui/typography/heading";
import { Pressable } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
import { useAsync } from "react-async-hook";
import { eMenuItemUpdate } from "../../../utils/events";
@@ -55,6 +48,70 @@ import FormInput, {
validators
} from "../../ui/input/form-input";
import { useAppState } from "../../../../app/hooks/use-app-state";
import Heading from "../../ui/typography/heading";
import { FontSizes } from "../../../common/design/font";
import { Dialog } from "../../dialog";
import LineSeparator from "../../ui/seperator/line-separator";
const OptionRow = ({
icon,
iconFamily = "notesnook",
title,
description,
right,
onPress,
disabled
}: {
icon: string;
iconFamily?: IconProps["iconFamily"];
title: string;
description: string;
right: React.ReactNode;
onPress?: () => void;
disabled?: boolean;
}) => {
const { colors } = useThemeColors();
return (
<Pressable
type="transparent"
disabled={disabled || !onPress}
onPress={onPress}
style={{
flexDirection: "row",
alignItems: "center",
gap: Spacing.LEVEL_2,
width: "100%"
}}
>
<View
style={{
width: 32,
height: 32,
borderRadius: Radius.XS,
backgroundColor: colors.secondary.background,
alignItems: "center",
justifyContent: "center"
}}
>
<AppIcon
name={icon}
iconFamily={iconFamily}
size={16}
color={colors.primary.icon}
/>
</View>
<View style={{ flex: 1, gap: Spacing.LEVEL_0 }}>
<Paragraph fontFamily="MEDIUM" fontSize="SM">
{title}
</Paragraph>
<Paragraph fontSize="XS" color={colors.secondary.paragraph}>
{description}
</Paragraph>
</View>
{right}
</Pressable>
);
};
async function fetchMonographData(noteId: string) {
const monographId = db.monographs.monograph(noteId);
@@ -73,7 +130,8 @@ async function fetchMonographData(noteId: string) {
}
const PublishNoteSheet = ({
note
note,
close
}: {
note: Note;
close?: (ctx?: string) => void;
@@ -153,7 +211,7 @@ const PublishNoteSheet = ({
if (note?.id) {
await db.monographs.publish(note.id, values.title, {
selfDestruct,
password: isLocked ? values.password : undefined
password: values.password || undefined
});
await monographData.execute();
Navigation.queueRoutesForUpdate();
@@ -175,16 +233,22 @@ const PublishNoteSheet = ({
setPublishing(value);
};
const deletePublishedNote = async () => {
const unpublishNote = async () => {
if (publishing) return;
setPublishLoading(true);
try {
if (note?.id) {
await db.monographs.unpublish(note.id);
monographData.execute();
close?.();
// monographData.execute();
Navigation.queueRoutesForUpdate();
eSendEvent(eMenuItemUpdate);
setPublishLoading(false);
ToastManager.show({
message: strings.noteUnpublished(),
type: "success",
context: "global"
});
}
} catch (e) {
ToastManager.show({
@@ -197,6 +261,20 @@ const PublishNoteSheet = ({
setPublishLoading(false);
};
const confirmUnpublishNote = () => {
presentDialog({
title: strings.unpublish() + "?",
paragraph: strings.unpublishNoteConfirm(),
icon: "warning-circle",
iconFamily: "notesnook",
iconType: "error",
centered: true,
positiveText: strings.yes(),
positivePress: unpublishNote,
context: "local"
});
};
const monographMetadata =
monographData.result?.metadata ?? lastMonographDataResult.current;
@@ -225,17 +303,18 @@ const PublishNoteSheet = ({
style={{
width: "100%",
alignSelf: "center",
paddingHorizontal: DefaultAppStyles.GAP,
gap: DefaultAppStyles.GAP_VERTICAL
paddingHorizontal: Spacing.LEVEL_3,
gap: Spacing.LEVEL_3,
paddingTop: Spacing.LEVEL_3
}}
>
{isPublished &&
(monographData?.result?.monograph || monographData?.loading) ? null : (
<DialogHeader
title={strings.publishNote()}
paragraph={strings.publishNoteDesc()}
/>
)}
<Dialog context="local" />
<DialogHeader
title={
isPublished ? strings.editPublishedMonograph() : strings.publishNote()
}
paragraph={isPublished ? undefined : strings.publishNoteDesc()}
/>
{publishing || monographData.loading ? (
<View
@@ -263,38 +342,50 @@ const PublishNoteSheet = ({
) : (
<>
{isPublished && publishUrl ? (
<TouchableOpacity
onPress={async () => {
try {
await openLinkInBrowser(publishUrl);
} catch (e) {
console.error(e);
}
}}
<View
style={{
flexDirection: "row",
alignItems: "center",
marginTop: DefaultAppStyles.GAP_VERTICAL,
backgroundColor: colors.secondary.background,
padding: DefaultAppStyles.GAP,
borderRadius: defaultBorderRadius
gap: Spacing.LEVEL_0,
backgroundColor: colors.selected.background,
padding: Spacing.LEVEL_2,
borderRadius: Radius.S,
flexDirection: "row"
}}
>
<View
style={{
width: "100%",
flexShrink: 1
gap: Spacing.LEVEL_0,
flexGrow: 1
}}
>
<Heading size={AppFontSize.md}>
{strings.publishedAt()}:
<Heading fontFamily="MEDIUM" fontSize="SM">
{strings.publishedNoteLink()}
</Heading>
<Paragraph size={AppFontSize.sm} numberOfLines={1}>
<Paragraph
fontSize="SM"
color={colors.secondary.paragraph}
onPress={async () => {
try {
await openLinkInBrowser(publishUrl);
} catch (e) {
console.error(e);
}
}}
>
{monographMetadata?.publishUrl}
</Paragraph>
</View>
<IconButton
<Button
type="accent"
icon="copy"
iconFamily="notesnook"
fontFamily="SEMI_BOLD"
style={{
paddingHorizontal: Spacing.LEVEL_1,
paddingVertical: Spacing.LEVEL_0
}}
fontSize={FontSizes.XS}
title={strings.copy()}
onPress={() => {
Clipboard.setString(publishUrl as string);
ToastManager.show({
@@ -303,208 +394,114 @@ const PublishNoteSheet = ({
context: "local"
});
}}
color={colors.primary.accent}
size={AppFontSize.lg}
name="content-copy"
/>
</TouchableOpacity>
) : null}
<FormInput
name="title"
formRef={formRef}
fwdRef={titleInput}
multiline
scrollEnabled
containerStyle={{
maxHeight: 100
}}
placeholder={strings.noteTitle()}
validators={[validators.required(strings.titleIsRequired())]}
/>
<TouchableOpacity
onPress={() => {
if (publishing) return;
setIsLocked(!isLocked);
}}
activeOpacity={0.9}
style={{
flexDirection: "row",
alignItems: "center",
backgroundColor: colors.secondary.background,
borderRadius: defaultBorderRadius,
paddingHorizontal: DefaultAppStyles.GAP,
paddingVertical: DefaultAppStyles.GAP_VERTICAL
}}
>
<View
style={{
width: "100%",
flexShrink: 1
}}
>
<View
style={{
flexDirection: "row",
justifyContent: "space-between"
}}
>
<Paragraph size={AppFontSize.sm}>
{strings.monographPassHeading()}
</Paragraph>
<ToggleSwitch
isOn={isLocked}
onColor={colors.primary.accent}
offColor={colors.primary.icon}
size="small"
animationSpeed={150}
onToggle={() => setIsLocked(!isLocked)}
/>
</View>
{/* <Paragraph>{strings.monographPassDesc()}</Paragraph> */}
{isLocked ? (
<>
<FormInput
name="password"
formRef={formRef}
fwdRef={pwdInput}
blurOnSubmit
secureTextEntry
placeholder={strings.enterPassword()}
validators={[
validators.required(strings.passwordRequired())
]}
containerStyle={{
marginTop: DefaultAppStyles.GAP_VERTICAL
}}
/>
</>
) : null}
</View>
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
setSelfDestruct(!selfDestruct);
}}
activeOpacity={0.9}
style={{
flexDirection: "row",
alignItems: "center",
backgroundColor: colors.secondary.background,
paddingVertical: DefaultAppStyles.GAP_VERTICAL,
borderRadius: defaultBorderRadius,
paddingHorizontal: DefaultAppStyles.GAP
}}
>
<View
style={{
width: "100%",
flexShrink: 1
}}
>
<View
style={{
flexDirection: "row",
justifyContent: "space-between"
}}
>
<Paragraph size={AppFontSize.sm}>
{strings.monographSelfDestructHeading()}
</Paragraph>
<ToggleSwitch
isOn={selfDestruct}
onColor={colors.primary.accent}
offColor={colors.primary.icon}
size="small"
animationSpeed={150}
onToggle={() => setSelfDestruct(!selfDestruct)}
/>
</View>
{/* <Paragraph>{strings.monographSelfDestructDesc()}</Paragraph> */}
</View>
</TouchableOpacity>
{isFeatureAvailable?.isAllowed &&
!selfDestruct &&
monographMetadata &&
monographMetadata?.analytics.totalViews > 0 ? (
<View
style={{
flexDirection: "row",
alignItems: "center",
backgroundColor: colors.secondary.background,
paddingVertical: DefaultAppStyles.GAP_VERTICAL,
borderRadius: defaultBorderRadius,
paddingHorizontal: DefaultAppStyles.GAP
}}
>
<View
style={{
width: "100%",
flexShrink: 1
}}
>
<View
style={{
flexDirection: "row",
justifyContent: "space-between"
}}
>
<Paragraph size={AppFontSize.sm}>{strings.views()}</Paragraph>
<Paragraph>
{monographMetadata?.analytics.totalViews || 0}
</Paragraph>
</View>
</View>
</View>
) : null}
<View
style={{
width: "100%",
justifyContent: "center",
gap: DefaultAppStyles.GAP_VERTICAL
gap: Spacing.LEVEL_2
}}
>
<FormInput
name="title"
formRef={formRef}
label={strings.title()}
fwdRef={titleInput}
multiline
scrollEnabled
containerStyle={{
maxHeight: 100
}}
placeholder={strings.noteTitle()}
validators={[validators.required(strings.titleIsRequired())]}
/>
<FormInput
name="password"
formRef={formRef}
label={strings.passwordProtection()}
fwdRef={pwdInput}
blurOnSubmit
secureTextEntry
placeholder={
isPublished
? strings.enterNewPassword()
: strings.enterPassword()
}
/>
</View>
<LineSeparator />
<View style={{ width: "100%", gap: Spacing.LEVEL_2 }}>
<OptionRow
icon="clock-counter-clockwise"
title={strings.monographSelfDestructHeading()}
description={strings.deleteAfterViewing()}
onPress={() => setSelfDestruct(!selfDestruct)}
right={
<AppIcon
name={selfDestruct ? "toggle-on" : "toggle-off"}
iconFamily="notesnook"
size={16}
color={
selfDestruct
? [colors.primary.accent, colors.primary.background]
: [colors.disabled.icon, colors.primary.background]
}
/>
}
/>
{isFeatureAvailable?.isAllowed &&
!selfDestruct &&
monographMetadata &&
isPublished ? (
<OptionRow
icon="eye-open"
title={strings.totalViews()}
description={strings.viewsOnPublishedNote()}
right={
<Paragraph fontFamily="MEDIUM" fontSize="SM">
{monographMetadata?.analytics.totalViews || 0}
</Paragraph>
}
/>
) : null}
</View>
<View
style={{
width: "100%",
flexDirection: "row",
gap: Spacing.LEVEL_2
}}
>
{isPublished ? (
<Button
onPress={confirmUnpublishNote}
style={{ flex: 1 }}
type="error-outline"
title={strings.unpublish()}
/>
) : null}
<Button
onPress={publishNote}
style={{
width: "100%",
borderRadius: defaultBorderRadius
}}
style={{ flex: 1 }}
type="accent"
title={isPublished ? strings.update() : strings.publish()}
/>
{isPublished && (
<>
<Button
onPress={deletePublishedNote}
type="error"
title={strings.unpublish()}
style={{
width: "100%",
borderRadius: defaultBorderRadius
}}
/>
</>
)}
</View>
</>
)}
<Paragraph
color={colors.secondary.paragraph}
size={AppFontSize.xs}
fontSize="XS"
style={{
textAlign: "center",
marginTop: DefaultAppStyles.GAP_VERTICAL,
textDecorationLine: "underline"
textAlign: "center"
}}
onPress={async () => {
try {
@@ -516,7 +513,14 @@ const PublishNoteSheet = ({
}
}}
>
{strings.monographLearnMore()}
{strings.monographLearnMore[0]()}{" "}
<Paragraph
fontSize="XS"
fontFamily="MEDIUM"
color={colors.primary.accent}
>
{strings.monographLearnMore[1]()}
</Paragraph>
</Paragraph>
</View>
);

View File

@@ -1,541 +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 { LegendList } from "@legendapp/list";
import {
ContentBlock,
InternalLink,
ItemReference,
Note,
TextSlice,
VirtualizedGrouping,
createInternalLink,
highlightInternalLinks
} from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { MutableRefObject, useEffect, useRef, useState } from "react";
import { ActivityIndicator, View } from "react-native";
import { ScrollView } from "react-native-actions-sheet";
import { create } from "zustand";
import { db } from "../../../common/database";
import { useDBItem, useNoteLocked } from "../../../hooks/use-db-item";
import { eSendEvent, presentSheet } from "../../../services/event-manager";
import { useRelationStore } from "../../../stores/use-relation-store";
import { eOnLoadNote } from "../../../utils/events";
import { fluidTabsRef } from "../../../utils/global-refs";
import { AppFontSize, defaultBorderRadius } from "../../../utils/size";
import { DefaultAppStyles } from "../../../utils/styles";
import SheetProvider from "../../sheet-provider";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import { Pressable } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph";
export const useExpandedStore = create<{
expanded: {
[id: string]: boolean;
};
setExpanded: (id: string) => void;
}>((set, get) => ({
expanded: {},
setExpanded(id: string) {
set({
expanded: {
...get().expanded,
[id]: !get().expanded[id]
}
});
}
}));
const ListBlockItem = ({
item,
onSelectBlock
}: {
item: ContentBlock;
onSelectBlock: () => void;
}) => {
const { colors } = useThemeColors();
return (
<Pressable
onPress={() => {
onSelectBlock();
}}
type={"transparent"}
style={{
flexDirection: "row",
width: "100%",
paddingLeft: 35,
justifyContent: "flex-start",
minHeight: 45,
paddingHorizontal: DefaultAppStyles.GAP
}}
>
<View
style={{
flexDirection: "row",
width: "100%",
columnGap: 10,
justifyContent: "space-between"
}}
>
<Paragraph
style={{
flexShrink: 1,
marginTop: 2
}}
>
{item?.content.length > 200
? item?.content.slice(0, 200) + "..."
: !item.content || item.content.trim() === ""
? strings.linkNoteEmptyBlock()
: item.content}
</Paragraph>
<View
style={{
borderRadius: defaultBorderRadius,
backgroundColor: colors.secondary.background,
width: 25,
height: 25,
alignItems: "center",
justifyContent: "center"
}}
>
<Paragraph color={colors.secondary.paragraph} size={AppFontSize.xs}>
{item.type.toUpperCase()}
</Paragraph>
</View>
</View>
</Pressable>
);
};
const ListNoteInternalLink = ({
link,
onSelect
}: {
link: {
blockId: string;
highlightedText: [TextSlice, TextSlice, TextSlice][];
};
onSelect: () => void;
}) => {
const { colors } = useThemeColors();
return (
<Pressable
onPress={() => {
onSelect();
}}
type={"transparent"}
style={{
flexDirection: "row",
width: "100%",
paddingLeft: 35,
justifyContent: "flex-start",
minHeight: 45
}}
>
<View
style={{
width: "100%",
rowGap: 10
}}
>
{link.highlightedText.map((text) => (
<Paragraph
key={`root_${text[0].text}`}
style={{
flexShrink: 1,
marginTop: 2,
flexWrap: "wrap"
}}
>
{text.map((slice) =>
!slice.highlighted ? (
slice.text
) : (
<Paragraph
key={slice.text}
color={colors.selected.accent}
style={{
textDecorationLine: "underline",
textDecorationColor: colors.selected.accent
}}
>
{slice.text}
</Paragraph>
)
)}
</Paragraph>
))}
</View>
</Pressable>
);
};
const ListNoteItem = ({
id,
items,
onSelect,
reference,
internalLinks,
listType
}: {
id: number;
items: VirtualizedGrouping<Note> | undefined;
onSelect: (item: Note, blockId?: string) => void;
reference: Note;
internalLinks: MutableRefObject<InternalLink[] | undefined>;
listType: "linkedNotes" | "referencedIn";
}) => {
const { colors } = useThemeColors();
const [item] = useDBItem(id, "note", items);
const expanded = useExpandedStore((state) =>
!item ? false : state.expanded[item.id]
);
const locked = useNoteLocked(item?.id);
const [linkedBlocks, setLinkedBlocks] = useState<ContentBlock[]>([]);
const [noteInternalLinks, setNoteInternalLinks] = useState<
{
blockId: string;
highlightedText: [TextSlice, TextSlice, TextSlice][];
}[]
>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (item?.id && expanded) {
(async () => {
if (listType === "linkedNotes") {
if (linkedBlocks.length) return;
setLoading(true);
if (!internalLinks.current) {
internalLinks.current = await db.notes.internalLinks(reference.id);
}
const noteLinks = internalLinks.current.filter(
(link) =>
link.id === item.id &&
link.type === "note" &&
link.params?.blockId
);
if (noteLinks.length) {
const blocks = await db.notes.contentBlocks(item.id);
setLinkedBlocks(
blocks.filter((block) =>
noteLinks.find(
(link) =>
link.type === "note" && block.id === link.params?.blockId
)
)
);
}
} else {
if (noteInternalLinks.length) return;
setLoading(true);
const blocks = await db.notes.contentBlocksWithLinks(item.id);
setNoteInternalLinks(
blocks
.filter((block) =>
block.content.includes(createInternalLink("note", reference.id))
)
.map((block) => {
return {
blockId: block?.id as string,
highlightedText: highlightInternalLinks(
block as ContentBlock,
reference.id
)
};
})
);
}
setLoading(false);
})();
}
}, [
item?.id,
expanded,
linkedBlocks.length,
internalLinks,
reference.id,
listType,
noteInternalLinks.length
]);
const renderBlock = React.useCallback(
(block: ContentBlock) => (
<ListBlockItem
item={block}
onSelectBlock={() => {
if (!item) return;
onSelect(item, block.id);
}}
/>
),
[item, onSelect]
);
const renderInternalLink = React.useCallback(
(link: {
blockId: string;
highlightedText: [TextSlice, TextSlice, TextSlice][];
}) => (
<ListNoteInternalLink
link={link}
onSelect={() => {
if (!item) return;
onSelect(item, link.blockId);
}}
/>
),
[item, onSelect]
);
return (
<View
style={{
flexDirection: "column",
width: "100%",
justifyContent: "flex-start",
alignItems: "center"
}}
>
<Pressable
type={"plain"}
onPress={() => {
if (!item) return;
onSelect(item as Note);
}}
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "flex-start",
width: "100%",
height: 45
}}
>
<IconButton
size={AppFontSize.xl}
onPress={() => {
if (!item?.id) return;
useExpandedStore.getState().setExpanded(item?.id);
}}
top={0}
left={0}
bottom={0}
right={0}
style={{
width: 35,
height: 35
}}
name={expanded ? "chevron-down" : "chevron-right"}
/>
<Paragraph numberOfLines={1}>{item?.title}</Paragraph>
</Pressable>
{expanded && !locked ? (
<View
style={{
justifyContent: "center",
alignItems: "center",
width: "100%"
}}
>
{loading ? (
<ActivityIndicator
color={colors.primary.accent}
size={AppFontSize.lg}
/>
) : (
<>
{listType === "linkedNotes" ? (
<>
{linkedBlocks.length === 0 ? (
<Paragraph color={colors.secondary.paragraph}>
{strings.noBlocksLinked()}
</Paragraph>
) : (
linkedBlocks.map(renderBlock)
)}
</>
) : (
<>
{noteInternalLinks.length === 0 ? (
<Paragraph color={colors.secondary.paragraph}>
{strings.noReferencesFound()}
</Paragraph>
) : (
noteInternalLinks.map(renderInternalLink)
)}
</>
)}
</>
)}
</View>
) : null}
</View>
);
};
type ReferencesListProps = {
item: { id: string; type: string };
close?: (ctx?: any) => void;
};
export const ReferencesList = ({ item, close }: ReferencesListProps) => {
const [tab, setTab] = useState(0);
const updater = useRelationStore((state) => state.updater);
const { colors } = useThemeColors();
const [items, setItems] = useState<VirtualizedGrouping<Note>>();
const hasNoRelations = !items || items?.placeholders?.length === 0;
const internalLinks = useRef<InternalLink[]>([]);
useEffect(() => {
db.relations?.[tab === 0 ? "from" : "to"]?.(
{ id: item?.id, type: item?.type } as ItemReference,
"note"
)
.selector.sorted({
sortBy: "dateEdited",
sortDirection: "desc"
})
.then((items) => {
setItems(items);
});
}, [item?.id, item?.type, tab]);
const renderNote = React.useCallback(
({ index }: any) => (
<ListNoteItem
id={index}
items={items}
onSelect={(note, blockId) => {
eSendEvent(eOnLoadNote, {
item: note,
blockId: blockId
});
fluidTabsRef.current?.goToPage("editor");
close?.();
}}
reference={item as Note}
internalLinks={internalLinks}
listType={tab === 0 ? "linkedNotes" : "referencedIn"}
/>
),
[items, item, tab, close]
);
return (
<View style={{ height: "100%" }}>
<SheetProvider context="local" />
<View
style={{
flexDirection: "row",
borderBottomWidth: 1,
borderBottomColor: colors.primary.border
}}
>
<Button
type={"plain"}
title={strings.linkedNotes()}
style={{
borderRadius: 0,
borderWidth: 0,
borderBottomWidth: 3,
borderColor: tab === 0 ? colors.primary.accent : "transparent",
height: 40,
width: "50%"
}}
onPress={() => {
setTab(0);
}}
/>
<Button
type={"plain"}
title={strings.referencedIn()}
style={{
width: "50%",
borderWidth: 0,
borderRadius: 0,
borderBottomWidth: 3,
borderColor: tab === 1 ? colors.primary.accent : "transparent",
height: 40
}}
onPress={() => {
setTab(1);
}}
/>
</View>
{hasNoRelations ? (
<View
style={{
height: "85%",
justifyContent: "center",
alignItems: "center"
}}
>
<Paragraph color={colors.secondary.paragraph}>
{tab === 1 ? strings.notReferenced() : strings.notLinked()}
</Paragraph>
</View>
) : (
<View
style={{
paddingHorizontal: DefaultAppStyles.GAP,
flex: 1,
marginTop: DefaultAppStyles.GAP_VERTICAL
}}
>
<LegendList
renderScrollComponent={(props) => <ScrollView {...props} />}
bounces={false}
data={items.placeholders || []}
extraData={items}
renderItem={renderNote}
/>
</View>
)}
</View>
);
};
ReferencesList.present = ({
reference
}: {
reference: { id: string; type: string };
}) => {
presentSheet({
component: (ref, close, update) => (
<ReferencesList item={reference} close={close} />
),
onClose: () => {
useExpandedStore.setState({
expanded: {}
});
}
});
};

View File

@@ -303,10 +303,6 @@ export function FormInput({
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: Spacing.LEVEL_2,
paddingRight:
buttons || button || secureTextEntry || error
? Spacing.LEVEL_3
: Spacing.LEVEL_3,
...containerStyle
};
@@ -317,6 +313,7 @@ export function FormInput({
onPress && loading ? colors.primary.accent : colors.primary.paragraph,
paddingTop: Spacing.LEVEL_3,
paddingBottom: Spacing.LEVEL_3,
lineHeight: fontSize + fontSize * 0.3,
flexGrow: 1,
flexShrink: 1,
fontFamily: "Inter-Regular",
@@ -388,7 +385,7 @@ export function FormInput({
<IconButton
name={!secureEntry ? "eye-closed" : "eye-open"}
iconFamily="notesnook"
size={16}
size={18}
top={10}
bottom={10}
onPress={() => {
@@ -396,7 +393,6 @@ export function FormInput({
setSecureEntry(!secureEntry);
}}
style={{
width: 25,
marginLeft: 5
}}
color={colors.primary.icon}

View File

@@ -35,6 +35,7 @@ export type ButtonTypes =
| "white"
| "error"
| "errorShade"
| "error-outline"
| "warn"
| "selected"
| "accent-outline"
@@ -233,6 +234,14 @@ const buttonTypes = (
isDark
)
},
"error-outline": {
primary: "transparent",
text: colors.error.paragraph,
selected: "transparent",
borderWidth: 0.8,
borderColor: colors.error.border,
borderSelectedColor: getColorLinearShade(colors.error.border, 0.07, isDark)
},
errorShade: {
primary: "transparent",
text: colors.error.buttonForeground,

View File

@@ -36,15 +36,12 @@ import React, { useEffect, useRef, useState } from "react";
import { InteractionManager, Platform } from "react-native";
import Share from "react-native-share";
import { DatabaseLogger, db } from "../common/database";
import { AttachmentDialog } from "../components/attachments";
import { AuthMode } from "../components/auth/common";
import { presentDialog } from "../components/dialog/functions";
import NoteHistory from "../components/note-history";
import { AddNotebookSheet } from "../components/sheets/add-notebook";
import ExportNotesSheet from "../components/sheets/export-notes";
import PaywallSheet from "../components/sheets/paywall";
import PublishNoteSheet from "../components/sheets/publish-note";
import { ReferencesList } from "../components/sheets/references";
import { useSideBarDraggingStore } from "../components/side-menu/dragging-store";
import { ButtonProps } from "../components/ui/button";
import AddReminder from "../screens/add-reminder";
@@ -54,7 +51,6 @@ import {
eSendEvent,
eSubscribeEvent,
openVault,
presentSheet,
ToastManager,
VaultRequestType
} from "../services/event-manager";
@@ -756,13 +752,11 @@ export const useActions = ({
if (item.type === "note") {
async function openHistory() {
presentSheet({
component: (ref) => <NoteHistory fwdRef={ref} note={item as Note} />
});
Navigation.navigate("NoteHistory", { note: item as Note });
}
async function showAttachments() {
AttachmentDialog.present(item as Note);
Navigation.navigate("Attachments", { note: item as Note });
}
async function exportNote() {
@@ -1080,6 +1074,8 @@ export const useActions = ({
referenceType: "reminder",
relationType: "from",
title: strings.dataTypesPluralCamelCase.reminder(),
listEmptyTitle: strings.noRemindersFound(),
listEmptyParagraph: strings.reminderPlaceholder(),
onAdd: async () => {
if (features && !features.activeReminders.isAllowed) {
ToastManager.show({
@@ -1152,7 +1148,7 @@ export const useActions = ({
{
id: "publish",
title: isPublished ? strings.published() : strings.publish(),
icon: "cloud-upload-outline",
icon: "cloud-upload",
checked: isPublished,
onPress: publishNote
},
@@ -1167,7 +1163,7 @@ export const useActions = ({
{
id: "notebooks",
title: strings.addToNotebook(),
icon: "book-outline",
icon: "bookmark",
onPress: addTo
},
// {
@@ -1181,7 +1177,7 @@ export const useActions = ({
title: strings.references(),
icon: "link-alt",
onPress: () => {
ReferencesList.present({
Navigation.navigate("References", {
reference: item as ItemReference
});
}

View File

@@ -296,6 +296,10 @@ let AddReminder: any = null;
let RelationsList: any = null;
let PayWall: any = null;
let Wrapped: any = null;
let Attachments: any = null;
let References: any = null;
let NoteHistory: any = null;
let NotePreview: any = null;
export const RootNavigation = () => {
const introCompleted = useSettingStore(
(state) => state.settings.introCompleted
@@ -424,6 +428,41 @@ export const RootNavigation = () => {
return Wrapped;
}}
/>
<RootStack.Screen
name="Attachments"
getComponent={() => {
Attachments =
Attachments || require("../screens/attachments").default;
return Attachments;
}}
/>
<RootStack.Screen
name="References"
getComponent={() => {
References = References || require("../screens/references").default;
return References;
}}
/>
<RootStack.Screen
name="NoteHistory"
getComponent={() => {
NoteHistory =
NoteHistory || require("../screens/note-history").default;
return NoteHistory;
}}
/>
<RootStack.Screen
name="NotePreview"
getComponent={() => {
NotePreview =
NotePreview || require("../screens/note-preview").default;
return NotePreview;
}}
/>
</RootStack.Navigator>
</NavigationContainer>
);

View File

@@ -27,32 +27,32 @@ import {
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useRef, useState } from "react";
import { ActivityIndicator, FlatList, View } from "react-native";
import { ScrollView } from "react-native-actions-sheet";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { ActivityIndicator, FlatList, ScrollView, View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import create from "zustand";
import { db } from "../../common/database";
import { FontSizes } from "../../common/design/font";
import { Radius, Spacing } from "../../common/design/spacing";
import filesystem from "../../common/filesystem";
import { downloadAttachments } from "../../common/filesystem/download-attachment";
import { AttachmentGroupProgress } from "../../screens/settings/components/attachment-group-progress";
import { presentSheet, ToastManager } from "../../services/event-manager";
import { AttachmentItem } from "../../components/attachments/attachment-item";
import { presentDialog } from "../../components/dialog/functions";
import { Header } from "../../components/header";
import AppIcon from "../../components/ui/AppIcon";
import { Button } from "../../components/ui/button";
import { IconButton } from "../../components/ui/icon-button";
import Input from "../../components/ui/input";
import { Pressable } from "../../components/ui/pressable";
import LineSeparator from "../../components/ui/seperator/line-separator";
import Heading from "../../components/ui/typography/heading";
import Paragraph from "../../components/ui/typography/paragraph";
import { ToastManager } from "../../services/event-manager";
import { NavigationProps } from "../../services/navigation";
import { useAttachmentStore } from "../../stores/use-attachment-store";
import { AppFontSize } from "../../utils/size";
import { DefaultAppStyles } from "../../utils/styles";
import { Dialog } from "../dialog";
import { presentDialog } from "../dialog/functions";
import { Header } from "../header";
import SheetProvider from "../sheet-provider";
import { Button } from "../ui/button";
import { IconButton } from "../ui/icon-button";
import Input from "../ui/input";
import Seperator from "../ui/seperator";
import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { AttachmentItem } from "./attachment-item";
import { AttachmentGroupProgress } from "../settings/components/attachment-group-progress";
const DEFAULT_SORTING: SortOptions = {
sortBy: "dateEdited",
sortBy: "dateModified",
sortDirection: "desc"
};
@@ -124,17 +124,13 @@ const attachmentTypes = [
}
];
export const AttachmentDialog = ({
note,
isSheet
}: {
note?: Note;
isSheet: boolean;
}) => {
const Attachments = (props: NavigationProps<"Attachments">) => {
const note = props.route.params?.note;
const { colors } = useThemeColors();
const [attachments, setAttachments] =
useState<VirtualizedGrouping<Attachment>>();
const attachmentSearchValue = useRef<string>(undefined);
const [searchVisible, setSearchVisible] = useState(false);
const [loading, setLoading] = useState(true);
const searchTimer = useRef<NodeJS.Timeout>(undefined);
const [currentFilter, setCurrentFilter] = useState("all");
@@ -154,24 +150,16 @@ export const AttachmentDialog = ({
if (note) {
db.attachments
.ofNote(note.id, "all")
.sorted({
...DEFAULT_SORTING,
sortBy: "dateModified"
})
.sorted(DEFAULT_SORTING)
.then((attachments) => {
setLoading(false);
setAttachments(attachments);
});
} else {
db.attachments.all
.sorted({
...DEFAULT_SORTING,
sortBy: "dateModified"
})
.then((attachments) => {
setAttachments(attachments);
setLoading(false);
});
db.attachments.all.sorted(DEFAULT_SORTING).then((attachments) => {
setAttachments(attachments);
setLoading(false);
});
}
}, [note]);
@@ -206,7 +194,7 @@ export const AttachmentDialog = ({
errorOnly={currentFilter === "errors"}
attachments={attachments}
id={index}
context={!isSheet ? "global" : "attachments-list"}
context="global"
/>
);
@@ -237,7 +225,7 @@ export const AttachmentDialog = ({
ToastManager.show({
message: strings.attachmentRecheckCancelled(),
type: "info",
context: isSheet ? "local" : "global"
context: "global"
});
return;
}
@@ -283,7 +271,7 @@ export const AttachmentDialog = ({
: db.attachments.images;
break;
case "video":
items = items = note
items = note
? db.attachments.ofNote(note.id, "videos")
: db.attachments.videos;
break;
@@ -301,198 +289,99 @@ export const AttachmentDialog = ({
items = db.attachments.orphaned;
break;
case "errors":
items = items = note
items = note
? db.attachments.ofNote(note.id, "all")
: db.attachments.all;
}
return await items.sorted({
...DEFAULT_SORTING,
sortBy: "dateModified"
return await items.sorted(DEFAULT_SORTING);
};
const downloadAll = () => {
if (!attachments) return;
presentDialog({
title: strings.doActions.download.attachment(
attachments.placeholders.length
),
positiveText: strings.network.download(),
positivePress: async () => {
downloadAttachments(await attachments.ids());
},
negativeText: strings.cancel()
});
};
db.attachments.orphaned.items().then((r) => {
console.log(r);
});
const currentType = attachmentTypes.find(
(item) => item.filterBy === currentFilter
);
const sectionTitle =
currentFilter === "all"
? strings.allAttachments()
: currentType?.title || strings.allAttachments();
return (
<>
{isSheet ? (
<>
<SheetProvider context="attachments-list" />
<Dialog context="local" />
</>
) : null}
{!isSheet ? (
<Header
title={strings.manageAttachments()}
renderedInRoute="SettingsGroup"
canGoBack
/>
) : (
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
paddingHorizontal: DefaultAppStyles.GAP
}}
>
<Heading>{strings.dataTypesPluralCamelCase.attachment()}</Heading>
<View
style={{
flexDirection: "row"
}}
>
<IconButton
name="check-all"
style={{
marginRight: 10
}}
color={colors.primary.paragraph}
size={AppFontSize.lg}
onPress={onCheck}
/>
<IconButton
name="download"
color={colors.primary.paragraph}
onPress={() => {
if (!attachments) return;
presentDialog({
title: strings.doActions.download.attachment(
attachments.placeholders.length
),
positiveText: strings.network.download(),
positivePress: async () => {
downloadAttachments(await attachments.ids());
},
negativeText: strings.cancel()
});
}}
size={AppFontSize.lg}
/>
</View>
</View>
)}
<SafeAreaView
style={{ flex: 1, backgroundColor: colors.primary.background }}
>
<Header
renderedInRoute="Attachments"
id="Attachments"
title={strings.dataTypesPluralCamelCase.attachment()}
canGoBack
style={{
backgroundColor: colors.primary.background
}}
/>
<View
style={{
width: "100%",
alignSelf: "center",
paddingHorizontal: DefaultAppStyles.GAP,
height: "100%"
flex: 1,
paddingHorizontal: Spacing.LEVEL_3,
gap: Spacing.LEVEL_3
}}
>
<Seperator />
<Input
placeholder={strings.filterAttachments()}
onChangeText={onChangeText}
containerStyle={{
borderWidth: 0,
backgroundColor: colors.secondary.background
}}
onSubmit={() => {
onChangeText(attachmentSearchValue.current as string);
}}
button={{
icon: "search",
iconFamily: "notesnook",
color: colors.primary.icon,
size: 16,
onPress: () => {}
}}
/>
{rechecker.shown ? (
<View
style={{
flexDirection: "row",
alignItems: "center",
width: "100%",
borderRadius: 10,
padding: DefaultAppStyles.GAP_SMALL,
borderWidth: 1,
borderColor: colors.primary.border,
gap: 12,
justifyContent: "space-between"
}}
>
<View
style={{
flexDirection: "row",
gap: 12,
alignItems: "center"
}}
>
{rechecker.isWorking ? (
<ActivityIndicator color={colors.primary.accent} />
) : (
<Icon
name="file-check"
size={30}
color={colors.primary.accent}
/>
)}
<View>
<Paragraph>
{rechecker.isWorking
? note
? strings.checkingNoteAttachments()
: strings.checkingAllAttachments()
: strings.attachmentRecheckComplete()}
</Paragraph>
<Paragraph>
{`${
rechecker.isWorking ? `${strings.pleaseWait()} ` : ""
}${strings.passed()}: ${
rechecker.passed
}, ${strings.failed()}: ${rechecker.failed}`}
</Paragraph>
</View>
</View>
<IconButton
type={rechecker.isWorking ? "errorShade" : "plain"}
name={rechecker.isWorking ? "close" : "check"}
size={AppFontSize.lg}
color={
rechecker.isWorking ? colors.error.icon : colors.primary.accent
}
onPress={() => {
useRechecker.getState().setProgress(note?.id || "all", {
...useRechecker.getState().progress[note?.id || "all"],
shown: false,
isWorking: false
});
}}
/>
</View>
) : null}
<LineSeparator
paddingHorizontal={Spacing.LEVEL_3}
style={{
marginBottom: -Spacing.LEVEL_1
}}
/>
<View>
<ScrollView
style={{
backgroundColor: colors.primary.background,
flexWrap: "wrap",
flexDirection: "row",
paddingVertical: DefaultAppStyles.GAP_VERTICAL
}}
contentContainerStyle={{
alignItems: "center"
}}
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={{
gap: Spacing.LEVEL_1
}}
>
{attachmentTypes.map((item) =>
item.filterBy === "orphaned" && note ? null : (
<Button
<Pressable
key={item.filterBy}
type={
currentFilter === item.filterBy
? "secondaryAccented"
: "plain"
currentFilter === item.filterBy ? "selected" : "transparent"
}
key={item.title}
title={item.title}
fontSize={AppFontSize.sm}
style={{
borderRadius: 100,
paddingHorizontal: DefaultAppStyles.GAP,
height: 40,
minWidth: 80
}}
onPress={async () => {
onPress={() => {
const filterBy = item.filterBy;
setCurrentFilter(filterBy);
setLoading(true);
@@ -506,52 +395,168 @@ export const AttachmentDialog = ({
/* empty */
});
}}
/>
style={{
borderRadius: Radius.XXL,
borderWidth: currentFilter === item.filterBy ? 0 : 1,
borderColor: colors.primary.border,
paddingHorizontal: Spacing.LEVEL_3,
paddingVertical: Spacing.LEVEL_1,
width: "auto"
}}
>
<Paragraph
fontFamily="MEDIUM"
fontSize="SM"
color={
currentFilter === item.filterBy
? colors.primary.heading
: colors.secondary.paragraph
}
>
{item.title}
</Paragraph>
</Pressable>
)
)}
</ScrollView>
</View>
{rechecker.shown ? (
<View
style={{
flexDirection: "row",
alignItems: "center",
width: "100%",
borderRadius: Radius.S,
padding: Spacing.LEVEL_2,
gap: Spacing.LEVEL_2,
justifyContent: "space-between",
backgroundColor: colors.primary.shade
}}
>
<View
style={{
flexDirection: "row",
gap: Spacing.LEVEL_2,
alignItems: "center",
flexShrink: 1
}}
>
<View
style={{
width: 32,
height: 32,
backgroundColor: colors.secondary.background,
borderRadius: Radius.XS,
justifyContent: "center",
alignItems: "center"
}}
>
<AppIcon
name="check-circle"
iconFamily="notesnook"
size={20}
color={colors.primary.icon}
/>
</View>
<View style={{ flexShrink: 1, gap: Spacing.LEVEL_0 }}>
<Heading color={colors.primary.heading} fontSize="SM">
{rechecker.isWorking
? note
? strings.checkingNoteAttachments()
: strings.checkingAllAttachments()
: strings.attachmentRecheckComplete()}
</Heading>
<Paragraph fontSize="XS" color={colors.secondary.paragraph}>
{`${
rechecker.isWorking ? `${strings.pleaseWait()} ` : ""
}${strings.passed()}: ${
rechecker.passed
}, ${strings.failed()}: ${rechecker.failed}`}
</Paragraph>
</View>
</View>
<IconButton
type={rechecker.isWorking ? "errorShade" : "plain"}
name={"close"}
iconFamily="notesnook"
size={12}
color={colors.secondary.icon}
onPress={() => {
useRechecker.getState().setProgress(note?.id || "all", {
...useRechecker.getState().progress[note?.id || "all"],
shown: false,
isWorking: false
});
}}
/>
</View>
) : null}
<View
style={{
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between"
}}
>
<Heading fontSize="LG">{sectionTitle}</Heading>
<Button
title={strings.verifyFiles()}
icon="shield-check"
iconFamily="notesnook"
type="accent"
fontSize={FontSizes.XS}
loading={rechecker.isWorking}
style={{
paddingVertical: Spacing.LEVEL_2,
paddingHorizontal: Spacing.LEVEL_2
}}
onPress={onCheck}
/>
</View>
<FlatList
renderScrollComponent={(props) => <ScrollView {...props} />}
style={{ flex: 1 }}
keyboardDismissMode="none"
keyboardShouldPersistTaps="always"
ListEmptyComponent={
<View
style={{
height: "100%",
height: 250,
justifyContent: "center",
alignItems: "center"
alignItems: "center",
gap: Spacing.LEVEL_2
}}
>
{loading ? (
<ActivityIndicator size={40} color={colors.primary.accent} />
) : (
<>
<Icon
name="attachment"
size={60}
<AppIcon
name="paperclip"
iconFamily="notesnook"
size={50}
color={colors.secondary.icon}
/>
<Paragraph>{strings.noAttachments()}</Paragraph>
<Paragraph color={colors.secondary.paragraph}>
{strings.noAttachments()}
</Paragraph>
</>
)}
</View>
}
ListHeaderComponent={<AllProgress note={note} />}
ListFooterComponent={
<View
style={{
height: 350
}}
/>
}
ListFooterComponent={<View style={{ height: 350 }} />}
data={loading ? [] : attachments?.placeholders || []}
extraData={attachments}
renderItem={renderItem}
/>
</View>
</>
</SafeAreaView>
);
};
@@ -572,7 +577,7 @@ const AllProgress = ({ note }: { note?: Note }) => {
return (
<View
style={{
gap: 10,
gap: Spacing.LEVEL_2,
width: "100%"
}}
>
@@ -587,9 +592,4 @@ const AllProgress = ({ note }: { note?: Note }) => {
);
};
AttachmentDialog.present = (note?: Note) => {
presentSheet({
component: () => <AttachmentDialog note={note} isSheet={true} />,
keyboardHandlerDisabled: true
});
};
export default Attachments;

View File

@@ -0,0 +1,247 @@
/*
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 { LegendList } from "@legendapp/list";
import { getFormattedDate, getTimeAgo } from "@notesnook/common";
import { HistorySession, Note, VirtualizedGrouping } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useState } from "react";
import { ActivityIndicator, Text, View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { Radius, Spacing } from "../../common/design/spacing";
import { db } from "../../common/database";
import { Header } from "../../components/header";
import AppIcon from "../../components/ui/AppIcon";
import { Pressable } from "../../components/ui/pressable";
import Paragraph from "../../components/ui/typography/paragraph";
import { useDBItem } from "../../hooks/use-db-item";
import Navigation, { NavigationProps } from "../../services/navigation";
import { openLinkInBrowser } from "../../utils/functions";
const getDate = (start: number, end: number) => {
const _start_date = getFormattedDate(start, "date");
const _end_date = getFormattedDate(end + 60000, "date");
const _start_time = getFormattedDate(start, "time");
const _end_time = getFormattedDate(end + 60000, "time");
return `${_start_date} ${_start_time} - ${
_end_date === _start_date ? " " : _end_date + " "
}${_end_time}`;
};
const HistoryItem = ({
index,
items,
note
}: {
index: number;
items?: VirtualizedGrouping<HistorySession>;
note: Note;
}) => {
const [item] = useDBItem(index, "noteHistory", items);
const { colors } = useThemeColors();
const selected = index === 0;
const preview = useCallback(
(item: HistorySession) => {
Navigation.navigate("NotePreview", {
note,
session: {
...item,
session: getDate(item.dateCreated, item.dateModified)
}
});
},
[note]
);
return (
<Pressable
type={"transparent"}
onPress={() => {
if (!item) return;
preview(item);
}}
customSelectedColor={colors.primary.shade}
style={{
flexDirection: "row",
alignItems: "center",
gap: Spacing.LEVEL_1,
padding: Spacing.LEVEL_2,
borderRadius: Radius.XS,
borderWidth: 1,
borderColor: colors.secondary.border,
marginBottom: Spacing.LEVEL_2
}}
>
{!item ? null : (
<>
<AppIcon
name="clock"
iconFamily="notesnook"
size={20}
color={colors.primary.icon}
/>
<Paragraph
style={{ flex: 1 }}
fontSize="XS"
color={selected ? colors.primary.heading : colors.primary.paragraph}
>
{getDate(item.dateCreated, item.dateModified)}
</Paragraph>
<Paragraph fontSize="XXS" color={colors.secondary.paragraph}>
{getTimeAgo(item.dateModified)}
</Paragraph>
<AppIcon
name="chevron-right"
iconFamily="notesnook"
size={16}
color={colors.secondary.icon}
/>
</>
)}
</Pressable>
);
};
const NoteHistory = (props: NavigationProps<"NoteHistory">) => {
const note = props.route.params.note;
const { colors } = useThemeColors();
const [history, setHistory] = useState<VirtualizedGrouping<HistorySession>>();
const [loading, setLoading] = useState(true);
useEffect(() => {
db.noteHistory
.get(note.id)
.sorted({
sortBy: "dateModified",
sortDirection: "desc"
})
.then((result) => {
setHistory(result);
setLoading(false);
})
.catch(() => {
setLoading(false);
});
}, [note.id]);
const renderItem = useCallback(
({ index }: { index: number }) => (
<HistoryItem index={index} items={history} note={note} />
),
[history, note]
);
const hasHistory = !!history?.placeholders.length;
return (
<SafeAreaView
style={{ flex: 1, backgroundColor: colors.primary.background }}
>
<Header
renderedInRoute="NoteHistory"
id="NoteHistory"
title={strings.noteHistory()}
style={{ backgroundColor: colors.primary.background }}
canGoBack
/>
<View
style={{
flex: 1,
paddingHorizontal: Spacing.LEVEL_3,
paddingTop: Spacing.LEVEL_3,
gap: Spacing.LEVEL_3
}}
>
{hasHistory ? (
<Paragraph
fontFamily="MEDIUM"
fontSize="SM"
color={colors.secondary.paragraph}
>
{strings.recentHistory()}
</Paragraph>
) : null}
<View style={{ flex: 1 }}>
<LegendList
data={history?.placeholders || []}
estimatedItemSize={50}
extraData={history}
ListEmptyComponent={
<View
style={{
width: "100%",
justifyContent: "center",
alignItems: "center",
paddingTop: Spacing.LEVEL_8,
gap: Spacing.LEVEL_2
}}
>
{loading ? (
<ActivityIndicator color={colors.primary.accent} />
) : (
<>
<AppIcon
name="clock"
iconFamily="notesnook"
size={50}
color={colors.primary.icon}
/>
<Paragraph color={colors.secondary.paragraph}>
{strings.noteHistoryPlaceholder()}
</Paragraph>
</>
)}
</View>
}
renderItem={renderItem}
/>
</View>
<Paragraph
fontSize="XS"
color={colors.secondary.paragraph}
style={{ alignSelf: "center", paddingBottom: Spacing.LEVEL_2 }}
>
{strings.noteHistoryNotice[0]()}{" "}
<Text
onPress={() => {
openLinkInBrowser(
"https://help.notesnook.com/note-version-history"
);
}}
style={{
color: colors.primary.accent,
textDecorationLine: "underline"
}}
>
{strings.noteHistoryNotice[1]()}
</Text>
</Paragraph>
</View>
</SafeAreaView>
);
};
export default NoteHistory;

View File

@@ -0,0 +1,298 @@
/*
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 { getFormattedDate, getTimeAgo } from "@notesnook/common";
import { NoteContent, isEncryptedContent } from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import { diff } from "diffblazer";
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { Radius, Spacing } from "../../common/design/spacing";
import { db } from "../../common/database";
import { Dialog } from "../../components/dialog";
import { presentDialog } from "../../components/dialog/functions";
import { Header } from "../../components/header";
import AppIcon from "../../components/ui/AppIcon";
import { Button } from "../../components/ui/button";
import Paragraph from "../../components/ui/typography/paragraph";
import { ToastManager, eSendEvent } from "../../services/event-manager";
import Navigation, { NavigationProps } from "../../services/navigation";
import { useSelectionStore } from "../../stores/use-selection-store";
import { useTrashStore } from "../../stores/use-trash-store";
import { eCloseSheet, eOnLoadNote } from "../../utils/events";
import { ReadonlyEditor } from "../editor/readonly-editor";
import { useTabStore } from "../editor/tiptap/use-tab-store";
import { editorController } from "../editor/tiptap/utils";
const NotePreview = (props: NavigationProps<"NotePreview">) => {
const { note, session } = props.route.params;
const { colors } = useThemeColors();
const [locked, setLocked] = useState(false);
const [content, setContent] = useState<
Partial<NoteContent<boolean> & { title: string }> | undefined
>();
const [loading, setLoading] = useState(true);
useEffect(() => {
db.vaults.itemExists(note).then((locked) => setLocked(locked));
}, [note]);
useEffect(() => {
(async () => {
try {
if (session) {
setContent(await db.noteHistory.content(session.id));
} else if (note.contentId) {
setContent(await db.content.get(note.contentId));
}
} finally {
setLoading(false);
}
})();
}, [note.contentId, session]);
async function restore() {
if (note && note.type === "trash") {
await db.trash.restore(note.id);
Navigation.queueRoutesForUpdate();
useSelectionStore.getState().setSelectionMode();
ToastManager.show({
heading: strings.noteRestored(),
type: "success"
});
Navigation.goBack();
return;
}
if (session) {
await db.noteHistory.restore(session.id);
if (useTabStore.getState().hasTabForNote(session?.noteId)) {
const note = editorController.current.note.current[session?.noteId];
if (note) {
eSendEvent(eOnLoadNote, {
item: note,
forced: true
});
}
}
eSendEvent(eCloseSheet, "note_history");
Navigation.queueRoutesForUpdate();
ToastManager.show({
heading: strings.noteRestoredFromHistory(),
type: "success"
});
Navigation.goBack();
}
}
const deleteNote = async () => {
presentDialog({
title: strings.deleteNote(),
paragraph: strings.deleteNoteConfirmation(),
positiveText: strings.delete(),
negativeText: strings.cancel(),
context: "local",
positivePress: async () => {
if (note) {
await db.trash.delete(note.id);
useTrashStore.getState().refresh();
useSelectionStore.getState().setSelectionMode();
ToastManager.show({
heading: strings.noteDeleted(),
type: "success",
context: "local"
});
Navigation.goBack();
}
},
positiveType: "error"
});
};
const canPreview = !session?.locked && !locked && !!content?.data;
return (
<SafeAreaView
style={{ flex: 1, backgroundColor: colors.primary.background }}
>
<Dialog context="local" />
<Header
renderedInRoute="NotePreview"
id="NotePreview"
title={session ? strings.historyDetail() : note.title}
style={{ backgroundColor: colors.primary.background }}
canGoBack
/>
<View
style={{
flex: 1,
paddingHorizontal: Spacing.LEVEL_3,
paddingTop: Spacing.LEVEL_3,
gap: Spacing.LEVEL_2
}}
>
{session ? (
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: Spacing.LEVEL_1,
padding: Spacing.LEVEL_2,
borderRadius: Radius.XS,
backgroundColor: colors.selected.background
}}
>
<AppIcon
name="clock"
iconFamily="notesnook"
size={20}
color={colors.primary.icon}
/>
<View style={{ flex: 1, gap: Spacing.LEVEL_1 }}>
<Paragraph fontSize="XS" color={colors.primary.heading}>
{getFormattedDate(session.dateCreated, "date")}
</Paragraph>
<Paragraph fontSize="XS" color={colors.primary.paragraph}>
{`${getFormattedDate(
session.dateCreated,
"time"
)} - ${getFormattedDate(session.dateModified + 60000, "time")}`}
</Paragraph>
</View>
<View
style={{
backgroundColor: colors.primary.background,
paddingHorizontal: Spacing.LEVEL_1,
paddingVertical: Spacing.LEVEL_0,
borderRadius: 50
}}
>
<Paragraph fontSize="XXS" color={colors.primary.heading}>
{getTimeAgo(session.dateModified)}
</Paragraph>
</View>
</View>
) : null}
<Paragraph
fontFamily="MEDIUM"
fontSize="SM"
color={colors.secondary.paragraph}
>
{strings.noteContent()}
</Paragraph>
<View
style={{
flex: 1,
borderWidth: 1,
borderColor: colors.secondary.border,
borderRadius: Radius.XS,
padding: Spacing.LEVEL_2,
overflow: "hidden"
}}
>
{canPreview ? (
<ReadonlyEditor
editorId="historyPreview"
onLoad={async (loadContent) => {
try {
if (content?.data) {
const currentContent = note?.contentId
? await db.content.get(note.contentId)
: undefined;
if (
currentContent?.data &&
!isEncryptedContent(currentContent)
) {
loadContent({
data: diff(
currentContent?.data || "<p></p>",
content.data as string
),
id: session?.noteId || note.id
});
}
}
} catch (e) {
ToastManager.error(
e as Error,
"Failed to load history preview",
"local"
);
}
}}
/>
) : (
<View
style={{
flex: 1,
justifyContent: "center",
alignItems: "center"
}}
>
<Paragraph color={colors.secondary.paragraph}>
{loading
? ""
: !content?.data
? strings.noContent()
: strings.encryptedNoteHistoryNotice()}
</Paragraph>
</View>
)}
</View>
</View>
<View
style={{
paddingHorizontal: Spacing.LEVEL_3,
paddingVertical: Spacing.LEVEL_3,
gap: Spacing.LEVEL_2,
marginTop: Spacing.LEVEL_1
}}
>
<Button
onPress={restore}
title={session ? strings.restoreThisVersion() : strings.restore()}
type="accent"
width="100%"
/>
<Button
onPress={deleteNote}
title={strings.deletePermanently()}
type="error-outline"
width="100%"
/>
<Paragraph
fontSize="SM"
color={colors.secondary.paragraph}
style={{ alignSelf: "center" }}
>
{strings.deletedVersionsCannotBeRecovered()}
</Paragraph>
</View>
</SafeAreaView>
);
};
export default NotePreview;

View File

@@ -0,0 +1,426 @@
/*
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 { LegendList } from "@legendapp/list";
import {
ItemReference,
Note,
TextSlice,
createInternalLink,
highlightInternalLinks
} from "@notesnook/core";
import { strings } from "@notesnook/intl";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { View } from "react-native";
import { Radius, Spacing } from "../../common/design/spacing";
import { db } from "../../common/database";
import { Header } from "../../components/header";
import AppIcon from "../../components/ui/AppIcon";
import { Pressable } from "../../components/ui/pressable";
import { TimeSince } from "../../components/ui/time-since";
import Heading from "../../components/ui/typography/heading";
import Paragraph from "../../components/ui/typography/paragraph";
import { eSendEvent } from "../../services/event-manager";
import Navigation, { NavigationProps } from "../../services/navigation";
import { useRelationStore } from "../../stores/use-relation-store";
import { getElevationStyle } from "../../utils/elevation";
import { eOnLoadNote } from "../../utils/events";
import { fluidTabsRef } from "../../utils/global-refs";
import { AppFontSize } from "../../utils/size";
import { SafeAreaView } from "react-native-safe-area-context";
import CirclesBackground from "../../components/ui/circles-background";
import LineSeparator from "../../components/ui/seperator/line-separator";
import Input from "../../components/ui/input";
type ListType = "linkedNotes" | "referencedIn";
type ReferenceItem = {
note: Note;
blockId?: string;
highlightedText?: [TextSlice, TextSlice, TextSlice][];
searchText: string;
};
const ReferenceNoteCard = ({
item: { note, blockId, highlightedText },
listType,
onSelect
}: {
item: ReferenceItem;
listType: ListType;
onSelect: (item: Note, blockId?: string) => void;
}) => {
const { colors } = useThemeColors();
return (
<Pressable
type="transparent"
onPress={() =>
onSelect(note, listType === "referencedIn" ? blockId : undefined)
}
style={{
alignItems: "flex-start",
borderWidth: 1,
borderColor: colors.primary.border,
borderRadius: Radius.S,
padding: Spacing.LEVEL_3,
gap: Spacing.LEVEL_2,
marginBottom: Spacing.LEVEL_2
}}
>
{listType === "linkedNotes" ? (
<View
style={{
flexDirection: "row",
alignItems: "center",
gap: Spacing.LEVEL_0
}}
>
<AppIcon
name="check-circle"
iconFamily="notesnook"
size={12}
color={colors.primary.accent}
/>
<Paragraph
fontFamily="SEMI_BOLD"
fontSize="XS"
color={colors.primary.accent}
>
{strings.linked()}
</Paragraph>
</View>
) : null}
<Heading fontSize="MD" color={colors.primary.heading}>
{note.title}
</Heading>
{listType === "referencedIn" && highlightedText ? (
<Paragraph
fontSize="SM"
numberOfLines={2}
color={colors.secondary.paragraph}
>
{highlightedText[0]?.map((slice, index) =>
slice.highlighted ? (
<Paragraph
key={`${slice.text}_${index}`}
fontFamily="SEMI_BOLD"
fontSize="SM"
color={colors.primary.accent}
style={{ textDecorationLine: "underline" }}
>
{slice.text}
</Paragraph>
) : (
slice.text
)
)}
</Paragraph>
) : null}
<TimeSince
time={note.dateEdited}
updateFrequency={60000}
style={{ fontSize: AppFontSize.xs, color: colors.secondary.paragraph }}
/>
{listType === "referencedIn" ? (
<Pressable
type="transparent"
onPress={() => onSelect(note, blockId)}
style={{
flexDirection: "row",
alignItems: "center",
alignSelf: "flex-start",
gap: Spacing.LEVEL_1,
borderWidth: 1,
borderColor: colors.primary.border,
borderRadius: Radius.S,
paddingHorizontal: Spacing.LEVEL_2,
paddingVertical: Spacing.LEVEL_1,
flexShrink: 1,
width: "auto"
}}
>
<Heading fontSize="SM" fontFamily="SEMI_BOLD">
{strings.jumpToReference()}
</Heading>
<AppIcon
name="arrow-square-out"
iconFamily="notesnook"
size={16}
color={colors.primary.icon}
/>
</Pressable>
) : null}
</Pressable>
);
};
const References = (props: NavigationProps<"References">) => {
const reference = props.route.params.reference;
const [tab, setTab] = useState(0);
const updater = useRelationStore((state) => state.updater);
const { colors } = useThemeColors();
const [items, setItems] = useState<ReferenceItem[]>();
const [query, setQuery] = useState("");
const [searchQuery, setSearchQuery] = useState("");
const isSearching = searchQuery.length > 0;
const hasNoRelations = !items || items.length === 0;
const cache = useRef<Record<number, ReferenceItem[]>>({});
useEffect(() => {
cache.current = {};
}, [reference.id, reference.type, updater]);
useEffect(() => {
const timeout = setTimeout(() => setSearchQuery(query.trim()), 300);
return () => clearTimeout(timeout);
}, [query]);
useEffect(() => {
let cancelled = false;
const listType: ListType = tab === 0 ? "linkedNotes" : "referencedIn";
const build = async (): Promise<ReferenceItem[]> => {
const notes = await db.relations[tab === 0 ? "from" : "to"](
{ id: reference.id, type: reference.type } as ItemReference,
"note"
).selector.items(undefined, {
sortBy: "dateEdited",
sortDirection: "desc"
});
if (listType === "linkedNotes") {
return notes.map((note) => ({
note,
searchText: note.title.toLowerCase()
}));
}
const link = createInternalLink("note", reference.id);
return Promise.all(
notes.map(async (note) => {
const blocks = await db.notes.contentBlocksWithLinks(note.id);
const linkedBlocks = blocks.filter((block) =>
block.content.includes(link)
);
const excerpt = linkedBlocks[0];
return {
note,
blockId: excerpt?.id,
highlightedText: excerpt
? highlightInternalLinks(excerpt, reference.id)
: undefined,
searchText: `${note.title} ${linkedBlocks
.map((block) => block.content)
.join(" ")}`.toLowerCase()
};
})
);
};
(async () => {
const resolved = cache.current[tab] || (await build());
cache.current[tab] = resolved;
if (cancelled) return;
const q = searchQuery.toLowerCase();
setItems(
q ? resolved.filter((item) => item.searchText.includes(q)) : resolved
);
})();
return () => {
cancelled = true;
};
}, [reference.id, reference.type, tab, updater, searchQuery]);
const onSelect = useCallback((note: Note, blockId?: string) => {
eSendEvent(eOnLoadNote, {
item: note,
blockId: blockId
});
fluidTabsRef.current?.goToPage("editor");
Navigation.goBack();
}, []);
const renderNote = useCallback(
({ item }: { item: ReferenceItem }) => (
<ReferenceNoteCard
item={item}
listType={tab === 0 ? "linkedNotes" : "referencedIn"}
onSelect={onSelect}
/>
),
[tab, onSelect]
);
return (
<SafeAreaView
style={{ flex: 1, backgroundColor: colors.primary.background }}
>
<Header
renderedInRoute="References"
id="References"
title={strings.references()}
style={{
backgroundColor: colors.primary.background
}}
canGoBack
/>
<View
style={{
paddingHorizontal: Spacing.LEVEL_3
}}
>
<Input
placeholder={strings.searchANote()}
onChangeText={setQuery}
returnKeyType="search"
containerStyle={{
borderWidth: 0,
backgroundColor: colors.secondary.background
}}
button={{
icon: "search",
iconFamily: "notesnook",
color: colors.primary.icon,
size: 16,
onPress: () => {}
}}
/>
<LineSeparator paddingVertical={Spacing.LEVEL_3} />
</View>
<View
style={{
flex: 1,
paddingHorizontal: Spacing.LEVEL_3,
gap: Spacing.LEVEL_3
}}
>
<View
style={{
flexDirection: "row",
gap: Spacing.LEVEL_2,
backgroundColor: colors.secondary.background,
padding: Spacing.LEVEL_1,
borderRadius: Radius.S
}}
>
{[strings.linkedNotes(), strings.usedIn()].map((label, index) => {
const active = tab === index;
return (
<Pressable
key={label}
type="transparent"
onPress={() => setTab(index)}
style={{
flex: 1,
paddingVertical: Spacing.LEVEL_3,
paddingHorizontal: Spacing.LEVEL_1,
borderRadius: Radius.XS,
backgroundColor: active
? colors.primary.background
: "transparent",
...(active ? getElevationStyle(2) : {})
}}
>
<Heading
fontSize="MD"
style={{ textAlign: "center" }}
color={
active ? colors.primary.accent : colors.secondary.paragraph
}
>
{label}
</Heading>
</Pressable>
);
})}
</View>
{!hasNoRelations ? (
<View
style={{
flex: 1,
alignItems: "center",
gap: Spacing.LEVEL_2
}}
>
<CirclesBackground>
<AppIcon
name={isSearching ? "search" : "link-simple"}
iconFamily="notesnook"
size={18}
color={colors.primary.accentForeground}
/>
</CirclesBackground>
<View
style={{
gap: Spacing.LEVEL_1,
alignItems: "center"
}}
>
<Heading
style={{
textAlign: "center"
}}
fontSize="XL"
>
{isSearching
? strings.noResultsFound(searchQuery)
: tab === 1
? strings.notReferenced()
: strings.notLinked()}
</Heading>
{isSearching ? null : (
<Paragraph
style={{
textAlign: "center",
maxWidth: 270
}}
fontSize="SM"
>
{tab === 1
? strings.emptyReferences()
: strings.linkedNotes()}
</Paragraph>
)}
</View>
</View>
) : (
<LegendList
bounces={false}
data={items || []}
keyExtractor={(item) => item.note.id}
renderItem={renderNote}
/>
)}
</View>
</SafeAreaView>
);
};
export default References;

View File

@@ -22,7 +22,6 @@ import { useThemeColors } from "@notesnook/theme";
import React, { useEffect, useState } from "react";
import { View } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database";
import { Header } from "../../components/header";
import List from "../../components/list";
@@ -31,12 +30,18 @@ import Paragraph from "../../components/ui/typography/paragraph";
import Navigation, { NavigationProps } from "../../services/navigation";
import { useRelationStore } from "../../stores/use-relation-store";
import { AppFontSize } from "../../utils/size";
import AppIcon from "../../components/ui/AppIcon";
import CirclesBackground from "../../components/ui/circles-background";
import Heading from "../../components/ui/typography/heading";
import { Spacing } from "../../common/design/spacing";
type RelationsListProps = {
item: Item;
referenceType: "notebook" | "tag" | "reminder" | "note";
relationType: "to" | "from";
title: string;
listEmptyTitle?: string;
listEmptyParagraph?: string;
onAdd?: () => void;
};
@@ -45,12 +50,20 @@ const IconsByType = {
};
function RelationsList(props: NavigationProps<"RelationsList">) {
const { item, referenceType, relationType, title, onAdd } = props.route
.params as RelationsListProps;
const {
item,
referenceType,
relationType,
title,
onAdd,
listEmptyTitle,
listEmptyParagraph
} = props.route.params as RelationsListProps;
const updater = useRelationStore((state) => state.updater);
const { colors } = useThemeColors();
const [items, setItems] = useState<VirtualizedGrouping<Item>>();
const hasNoRelations = !items || items?.placeholders?.length === 0;
console.log(listEmptyTitle, listEmptyParagraph);
useEffect(() => {
db.relations?.[relationType]?.(
@@ -76,27 +89,58 @@ function RelationsList(props: NavigationProps<"RelationsList">) {
width: "100%"
}}
>
<Header title={title} canGoBack />
<Header
title={title}
canGoBack
style={{
backgroundColor: colors.primary.background
}}
/>
<View style={{ flex: 1 }}>
{hasNoRelations ? (
<View
style={{
height: "85%",
justifyContent: "center",
alignItems: "center"
justifyContent: "flex-start",
alignItems: "center",
gap: Spacing.LEVEL_3,
paddingHorizontal: Spacing.LEVEL_3 * 2,
marginTop: 46
}}
>
<Icon
name={IconsByType[referenceType as keyof typeof IconsByType]}
size={60}
color={colors.primary.icon}
/>
<Paragraph>{strings.noLinksFound()}</Paragraph>
<CirclesBackground>
<AppIcon
name={IconsByType[referenceType as keyof typeof IconsByType]}
iconFamily="notesnook"
size={18}
color={colors.primary.accentForeground}
/>
</CirclesBackground>
<View
style={{
gap: Spacing.LEVEL_1,
alignItems: "center"
}}
>
<Heading fontSize="XL">{listEmptyTitle}</Heading>
<Paragraph
style={{
textAlign: "center",
maxWidth: 270
}}
fontSize="SM"
>
{listEmptyParagraph}
</Paragraph>
</View>
<Button
onPress={onAdd}
fontSize={AppFontSize.sm}
type="inverted"
icon="plus"
fontSize={AppFontSize.md}
type="accent"
style={{
marginTop: Spacing.LEVEL_0
}}
title={strings.addItem(referenceType)}
/>
</View>
@@ -128,12 +172,16 @@ RelationsList.present = ({
relationType,
referenceType,
title,
listEmptyTitle,
listEmptyParagraph,
onAdd
}: RelationsListProps) => {
Navigation.navigate("RelationsList", {
item,
relationType,
referenceType,
listEmptyTitle,
listEmptyParagraph,
title,
onAdd
});

View File

@@ -19,7 +19,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { ReactElement } from "react";
import { View } from "react-native";
import { AttachmentDialog } from "../../../components/attachments";
import { ChangePassword } from "../../../components/auth/change-password";
import { DefaultAppStyles } from "../../../utils/styles";
import { AttachmentGroupProgress } from "./attachment-group-progress";
@@ -69,7 +68,6 @@ export const components: { [name: string]: ReactElement } = {
autobackupsattachments: <BackupWithAttachmentsReminderPicker />,
backuprestore: <RestoreBackup />,
"server-config": <ServersConfiguration />,
"attachments-manager": <AttachmentDialog note={undefined} isSheet={false} />,
"offline-mode-progress": (
<View style={{ paddingHorizontal: DefaultAppStyles.GAP }}>
<AttachmentGroupProgress groupId="offline-mode" />

View File

@@ -452,10 +452,10 @@ export const accountGroup: SettingSection = {
name: strings.manageAttachments(),
icon: "paperclip",
iconFamily: "notesnook",
type: "screen",
component: "attachments-manager",
description: strings.manageAttachmentsDesc(),
hideHeader: true
modifer: () => {
Navigation.navigate("Attachments");
}
},
{
id: "clear-cache",

View File

@@ -73,7 +73,11 @@ const routeNames = {
AddReminder: "AddReminder",
RelationsList: "RelationsList",
PayWall: "PayWall",
Wrapped: "Wrapped"
Wrapped: "Wrapped",
Attachments: "Attachments",
References: "References",
NoteHistory: "NoteHistory",
NotePreview: "NotePreview"
};
export type NavigationProps<T extends RouteName> = NativeStackScreenProps<

View File

@@ -20,12 +20,15 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import {
Color,
FilteredSelector,
HistorySession,
Item,
ItemReference,
ItemType,
Note,
Notebook,
Reminder,
Tag
Tag,
TrashOrItem
} from "@notesnook/core";
import { ParamListBase } from "@react-navigation/core";
import { create } from "zustand";
@@ -113,6 +116,8 @@ export interface RouteParams extends ParamListBase {
referenceType: "notebook" | "tag" | "reminder" | "note";
relationType: "to" | "from";
title: string;
listEmptyTitle?: string;
listEmptyParagraph?: string;
onAdd?: () => void;
};
Intro: GenericRouteParam;
@@ -122,6 +127,19 @@ export interface RouteParams extends ParamListBase {
state?: BillingState;
};
Wrapped: GenericRouteParam;
Attachments: {
note?: Note;
};
References: {
reference: ItemReference;
};
NoteHistory: {
note: Note;
};
NotePreview: {
note: TrashOrItem<Note>;
session?: HistorySession & { session: string };
};
}
export type RouteName = keyof RouteParams;

File diff suppressed because one or more lines are too long

View File

@@ -1540,6 +1540,39 @@
"@styled-system/css": "^5.1.5"
}
},
"node_modules/@theme-ui/color-modes": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@theme-ui/color-modes/-/color-modes-0.16.2.tgz",
"integrity": "sha512-jWEWx53lxNgWCT38i/kwLV2rsvJz8lVZgi5oImnVwYba9VejXD23q1ckbNFJHosQ8KKXY87ht0KPC6BQFIiHtQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@theme-ui/core": "^0.16.2",
"@theme-ui/css": "^0.16.2",
"deepmerge": "^4.2.2"
},
"peerDependencies": {
"@emotion/react": "^11.11.1",
"react": ">=18"
}
},
"node_modules/@theme-ui/color-modes/node_modules/@theme-ui/core": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@theme-ui/core/-/core-0.16.2.tgz",
"integrity": "sha512-bBd/ltbwO9vIUjF1jtlOX6XN0IIOdf1vzBp2JCKsSOqdfn84m+XL8OogIe/zOhQ+aM94Nrq4+32tFJc8sFav4Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@theme-ui/css": "^0.16.2",
"deepmerge": "^4.2.2"
},
"peerDependencies": {
"@emotion/react": "^11.11.1",
"react": ">=18"
}
},
"node_modules/@theme-ui/components": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/@theme-ui/components/-/components-0.16.1.tgz",
@@ -1585,6 +1618,39 @@
"@emotion/react": "^11.11.1"
}
},
"node_modules/@theme-ui/theme-provider": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@theme-ui/theme-provider/-/theme-provider-0.16.2.tgz",
"integrity": "sha512-LRnVevODcGqO0JyLJ3wht+PV3ZoZcJ7XXLJAJWDoGeII4vZcPQKwVy4Lpz/juHsZppQxKcB3U+sQDGBnP25irQ==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@theme-ui/color-modes": "^0.16.2",
"@theme-ui/core": "^0.16.2",
"@theme-ui/css": "^0.16.2"
},
"peerDependencies": {
"@emotion/react": "^11.11.1",
"react": ">=18"
}
},
"node_modules/@theme-ui/theme-provider/node_modules/@theme-ui/core": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@theme-ui/core/-/core-0.16.2.tgz",
"integrity": "sha512-bBd/ltbwO9vIUjF1jtlOX6XN0IIOdf1vzBp2JCKsSOqdfn84m+XL8OogIe/zOhQ+aM94Nrq4+32tFJc8sFav4Q==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@theme-ui/css": "^0.16.2",
"deepmerge": "^4.2.2"
},
"peerDependencies": {
"@emotion/react": "^11.11.1",
"react": ">=18"
}
},
"node_modules/@tiptap/core": {
"version": "2.6.6",
"resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.6.6.tgz",
@@ -3432,8 +3498,7 @@
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"node_modules/js-yaml": {
"version": "3.14.1",
@@ -3542,7 +3607,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
@@ -5075,7 +5139,6 @@
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"dev": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -5096,7 +5159,6 @@
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"dev": true,
"dependencies": {
"loose-envify": "^1.1.0",
"scheduler": "^0.23.2"
@@ -5411,7 +5473,6 @@
"version": "0.23.2",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
"integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
"dev": true,
"dependencies": {
"loose-envify": "^1.1.0"
}

View File

@@ -0,0 +1,10 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_2961_32908)">
<path d="M6 1.02539C7.319 1.02678 8.58393 1.55073 9.5166 2.4834C10.4493 3.41607 10.9732 4.681 10.9746 6L10.9609 6.36816C10.8976 7.22235 10.6148 8.048 10.1367 8.76367C9.59011 9.58173 8.81325 10.2201 7.9043 10.5967C6.99523 10.9732 5.99435 11.0718 5.0293 10.8799C4.06425 10.6879 3.17818 10.2133 2.48242 9.51758C1.78666 8.82182 1.31208 7.93575 1.12012 6.9707C0.928156 6.00565 1.02677 5.00477 1.40332 4.0957C1.77988 3.18675 2.41827 2.40989 3.23633 1.86328C4.0544 1.31674 5.01615 1.02539 6 1.02539ZM7.54004 2.28125C6.80464 1.9767 5.99552 1.89745 5.21484 2.05273C4.43407 2.20804 3.7172 2.59139 3.1543 3.1543C2.59139 3.7172 2.20804 4.43407 2.05273 5.21484C1.89745 5.99552 1.9767 6.80464 2.28125 7.54004C2.58588 8.27547 3.10181 8.90441 3.76367 9.34668C4.42558 9.78895 5.20393 10.0254 6 10.0254L6.19922 10.0195C7.19376 9.96918 8.13725 9.5522 8.84473 8.84473C9.59929 8.09016 10.0242 7.06711 10.0254 6L10.0137 5.70215C9.96239 5.01098 9.7336 4.34274 9.34668 3.76367C8.90441 3.10181 8.27547 2.58588 7.54004 2.28125ZM7.875 4.39941C7.9373 4.39941 7.99908 4.41174 8.05664 4.43555C8.11429 4.45944 8.16683 4.49492 8.21094 4.53906C8.25508 4.58317 8.29056 4.63571 8.31445 4.69336C8.33826 4.75093 8.35059 4.8127 8.35059 4.875C8.35059 4.9373 8.33826 4.99907 8.31445 5.05664C8.29056 5.11429 8.25508 5.16683 8.21094 5.21094L5.58594 7.83594C5.54183 7.88008 5.48929 7.91556 5.43164 7.93945C5.37408 7.96326 5.3123 7.97559 5.25 7.97559C5.1877 7.97559 5.12593 7.96326 5.06836 7.93945C5.01071 7.91556 4.95817 7.88008 4.91406 7.83594L3.78906 6.71094C3.69994 6.62182 3.64941 6.50103 3.64941 6.375L3.65918 6.28223C3.6774 6.19076 3.72215 6.10598 3.78906 6.03906C3.87818 5.94994 3.99897 5.89941 4.125 5.89941C4.25103 5.89941 4.37182 5.94994 4.46094 6.03906L5.25 6.82812L7.53906 4.53906C7.58317 4.49492 7.63571 4.45944 7.69336 4.43555C7.75092 4.41174 7.8127 4.39941 7.875 4.39941Z" fill="#008836" stroke="#008836" stroke-width="0.2"/>
</g>
<defs>
<clipPath id="clip0_2961_32908">
<rect width="12" height="12" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1422_6377)">
<path d="M12.9 6.69341C12.4467 4.39341 10.4267 2.66675 8 2.66675C6.07333 2.66675 4.4 3.76008 3.56667 5.36008C1.56 5.57341 0 7.27342 0 9.33342C0 11.5401 1.79333 13.3334 4 13.3334H12.6667C14.5067 13.3334 16 11.8401 16 10.0001C16 8.24008 14.6333 6.81342 12.9 6.69341ZM12.6667 12.0001H4C2.52667 12.0001 1.33333 10.8067 1.33333 9.33342C1.33333 7.96675 2.35333 6.82675 3.70667 6.68675L4.42 6.61341L4.75333 5.98008C5.38667 4.76008 6.62667 4.00008 8 4.00008C9.74667 4.00008 11.2533 5.24008 11.5933 6.95341L11.7933 7.95341L12.8133 8.02675C13.8533 8.09341 14.6667 8.96675 14.6667 10.0001C14.6667 11.1001 13.7667 12.0001 12.6667 12.0001ZM5.33333 8.66675H7.03333V10.6667H8.96667V8.66675H10.6667L8 6.00008L5.33333 8.66675Z" fill="#858585"/>
</g>
<defs>
<clipPath id="clip0_1422_6377">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 977 B

3
packages/icons/svgs/image.svg Executable file
View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.875 3.125H3.125C2.79348 3.125 2.47554 3.2567 2.24112 3.49112C2.0067 3.72554 1.875 4.04348 1.875 4.375V15.625C1.875 15.9565 2.0067 16.2745 2.24112 16.5089C2.47554 16.7433 2.79348 16.875 3.125 16.875H16.875C17.2065 16.875 17.5245 16.7433 17.7589 16.5089C17.9933 16.2745 18.125 15.9565 18.125 15.625V4.375C18.125 4.04348 17.9933 3.72554 17.7589 3.49112C17.5245 3.2567 17.2065 3.125 16.875 3.125ZM16.875 4.375V12.4023L14.8383 10.3664C14.7222 10.2503 14.5844 10.1582 14.4327 10.0954C14.281 10.0325 14.1185 10.0002 13.9543 10.0002C13.7901 10.0002 13.6276 10.0325 13.4759 10.0954C13.3242 10.1582 13.1864 10.2503 13.0703 10.3664L11.5078 11.9289L8.07031 8.49141C7.83592 8.25716 7.5181 8.12558 7.18672 8.12558C6.85534 8.12558 6.53752 8.25716 6.30312 8.49141L3.125 11.6695V4.375H16.875ZM3.125 13.4375L7.1875 9.375L13.4375 15.625H3.125V13.4375ZM16.875 15.625H15.2055L12.393 12.8125L13.9555 11.25L16.875 14.1703V15.625ZM11.25 7.8125C11.25 7.62708 11.305 7.44582 11.408 7.29165C11.511 7.13748 11.6574 7.01732 11.8287 6.94636C12 6.87541 12.1885 6.85684 12.3704 6.89301C12.5523 6.92919 12.7193 7.01848 12.8504 7.14959C12.9815 7.2807 13.0708 7.44775 13.107 7.6296C13.1432 7.81146 13.1246 7.99996 13.0536 8.17127C12.9827 8.34257 12.8625 8.48899 12.7083 8.592C12.5542 8.69502 12.3729 8.75 12.1875 8.75C11.9389 8.75 11.7004 8.65123 11.5246 8.47541C11.3488 8.2996 11.25 8.06114 11.25 7.8125Z" fill="#181818"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.94629 8.02515C6.13858 8.02515 6.32297 8.10114 6.45898 8.23706C6.59494 8.37301 6.67179 8.5575 6.67188 8.74976C6.67188 8.94213 6.59501 9.1274 6.45898 9.26343L4.11133 11.6111H4.11035C3.82429 11.8908 3.59666 12.2252 3.44043 12.5935C3.2843 12.9617 3.20247 13.3576 3.2002 13.7576C3.19797 14.1575 3.27579 14.5537 3.42773 14.9236C3.57971 15.2935 3.80323 15.63 4.08594 15.9128C4.36875 16.1957 4.70526 16.4199 5.0752 16.572C5.44525 16.7241 5.8421 16.8018 6.24219 16.7996C6.64227 16.7974 7.0379 16.7155 7.40625 16.5593C7.77454 16.4032 8.10797 16.1754 8.3877 15.8894L8.38867 15.8884L10.7373 13.5408L10.8477 13.45C10.9659 13.371 11.1057 13.3279 11.25 13.3279C11.4422 13.3278 11.6267 13.4039 11.7627 13.5398C11.8987 13.6757 11.9755 13.8602 11.9756 14.0525C11.9757 14.2448 11.8986 14.4291 11.7627 14.5652L9.41504 16.9148C8.57577 17.7541 7.4369 18.2253 6.25 18.2253C5.06328 18.2253 3.92513 17.7539 3.08594 16.9148C2.24667 16.0755 1.77539 14.9367 1.77539 13.7498C1.77547 12.563 2.24675 11.4249 3.08594 10.5857L5.43359 8.23706C5.5696 8.10113 5.75399 8.02515 5.94629 8.02515ZM12.5 6.77417C12.5953 6.77417 12.6903 6.79335 12.7783 6.82983C12.866 6.86626 12.9455 6.9199 13.0127 6.98706C13.0801 7.05439 13.1344 7.1344 13.1709 7.22241C13.2073 7.31029 13.2255 7.40464 13.2256 7.49976C13.2256 7.59503 13.2074 7.69006 13.1709 7.77808C13.1344 7.86603 13.0801 7.94614 13.0127 8.01343L8.0127 13.0134C7.94543 13.0806 7.86515 13.1343 7.77734 13.1707C7.6895 13.207 7.59506 13.2253 7.5 13.2253C7.40488 13.2253 7.31054 13.207 7.22266 13.1707C7.13475 13.1342 7.05461 13.0807 6.9873 13.0134C6.92004 12.9462 6.86653 12.8659 6.83008 12.7781C6.79363 12.6901 6.77441 12.595 6.77441 12.4998C6.77446 12.4046 6.79367 12.3103 6.83008 12.2224C6.86653 12.1344 6.91997 12.0544 6.9873 11.9871L11.9873 6.98706L12.0977 6.89722C12.137 6.87092 12.1787 6.84805 12.2227 6.82983C12.3106 6.79339 12.4048 6.7742 12.5 6.77417ZM13.75 1.77515C14.3377 1.77515 14.92 1.8901 15.4629 2.11499C16.0059 2.33991 16.4995 2.6701 16.915 3.08569C17.3305 3.5012 17.6599 3.99498 17.8848 4.53784C18.1096 5.08062 18.2255 5.66227 18.2256 6.24976C18.2256 6.83746 18.1097 7.41969 17.8848 7.96265C17.6599 8.50559 17.3306 8.99926 16.915 9.41479L14.5654 11.7664V11.7673C14.4294 11.9031 14.2449 11.9793 14.0527 11.9792C13.8604 11.9792 13.676 11.9024 13.54 11.7664C13.4042 11.6303 13.328 11.4459 13.3281 11.2537C13.3282 11.0614 13.4041 10.8769 13.54 10.741L15.8896 8.39233C16.4458 7.82297 16.7555 7.05743 16.751 6.26147C16.7464 5.46509 16.4274 4.7025 15.8643 4.1394C15.3011 3.57648 14.5385 3.25805 13.7422 3.25366C12.9459 3.24937 12.1796 3.55919 11.6104 4.11597L9.2627 6.45874C9.12667 6.59474 8.94235 6.67163 8.75 6.67163C8.55771 6.67157 8.37328 6.59472 8.2373 6.45874C8.10138 6.32273 8.02441 6.13834 8.02441 5.94604C8.02445 5.75374 8.10134 5.56935 8.2373 5.43335L10.5859 3.08569C11.0015 2.67013 11.4951 2.3399 12.0381 2.11499C12.5808 1.89025 13.1626 1.77518 13.75 1.77515Z" fill="white" stroke="white" stroke-width="0.2"/>
</svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M16.6344 1.38199C16.5595 1.32366 16.4723 1.28314 16.3795 1.26351C16.2866 1.24388 16.1905 1.24566 16.0984 1.26871L6.09844 3.76871C5.96323 3.80251 5.84319 3.88053 5.75741 3.99038C5.67163 4.10022 5.62502 4.23559 5.625 4.37496V12.975C5.10362 12.6266 4.48078 12.4625 3.85543 12.5086C3.23008 12.5548 2.63809 12.8086 2.17352 13.2298C1.70895 13.6509 1.39845 14.2152 1.29134 14.8331C1.18423 15.4509 1.28666 16.0868 1.58236 16.6398C1.87805 17.1927 2.35006 17.631 2.92337 17.885C3.49669 18.139 4.13844 18.194 4.74665 18.0415C5.35487 17.889 5.89467 17.5376 6.28029 17.0431C6.66591 16.5487 6.87523 15.9395 6.875 15.3125V8.61324L15.625 6.42574V10.475C15.1036 10.1266 14.4808 9.96246 13.8554 10.0086C13.2301 10.0548 12.6381 10.3086 12.1735 10.7298C11.709 11.1509 11.3984 11.7152 11.2913 12.3331C11.1842 12.9509 11.2867 13.5868 11.5824 14.1398C11.8781 14.6927 12.3501 15.131 12.9234 15.385C13.4967 15.639 14.1384 15.6941 14.7467 15.5415C15.3549 15.389 15.8947 15.0376 16.2803 14.5431C16.6659 14.0487 16.8752 13.4395 16.875 12.8125V1.87496C16.875 1.7799 16.8534 1.68609 16.8117 1.60067C16.77 1.51524 16.7093 1.44045 16.6344 1.38199ZM4.0625 16.875C3.75347 16.875 3.45138 16.7833 3.19443 16.6116C2.93747 16.4399 2.7372 16.1959 2.61894 15.9104C2.50068 15.6249 2.46974 15.3107 2.53003 15.0076C2.59032 14.7045 2.73913 14.4261 2.95765 14.2076C3.17617 13.9891 3.45458 13.8403 3.75768 13.78C4.06077 13.7197 4.37494 13.7506 4.66045 13.8689C4.94596 13.9872 5.18999 14.1874 5.36168 14.4444C5.53337 14.7013 5.625 15.0034 5.625 15.3125C5.625 15.7269 5.46038 16.1243 5.16736 16.4173C4.87433 16.7103 4.4769 16.875 4.0625 16.875ZM6.875 7.32418V4.86324L15.625 2.67574V5.13668L6.875 7.32418ZM14.0625 14.375C13.7535 14.375 13.4514 14.2833 13.1944 14.1116C12.9375 13.9399 12.7372 13.6959 12.6189 13.4104C12.5007 13.1249 12.4697 12.8107 12.53 12.5076C12.5903 12.2045 12.7391 11.9261 12.9576 11.7076C13.1762 11.4891 13.4546 11.3403 13.7577 11.28C14.0608 11.2197 14.3749 11.2506 14.6604 11.3689C14.946 11.4872 15.19 11.6874 15.3617 11.9444C15.5334 12.2013 15.625 12.5034 15.625 12.8125C15.625 13.2269 15.4604 13.6243 15.1674 13.9173C14.8743 14.2103 14.4769 14.375 14.0625 14.375Z" fill="#181818"/>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

3
packages/icons/svgs/upload.svg Executable file
View File

@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 8.99991V12.9999C14 13.1325 13.9473 13.2597 13.8536 13.3535C13.7598 13.4472 13.6326 13.4999 13.5 13.4999H2.5C2.36739 13.4999 2.24021 13.4472 2.14645 13.3535C2.05268 13.2597 2 13.1325 2 12.9999V8.99991C2 8.8673 2.05268 8.74012 2.14645 8.64635C2.24021 8.55258 2.36739 8.49991 2.5 8.49991C2.63261 8.49991 2.75979 8.55258 2.85355 8.64635C2.94732 8.74012 3 8.8673 3 8.99991V12.4999H13V8.99991C13 8.8673 13.0527 8.74012 13.1464 8.64635C13.2402 8.55258 13.3674 8.49991 13.5 8.49991C13.6326 8.49991 13.7598 8.55258 13.8536 8.64635C13.9473 8.74012 14 8.8673 14 8.99991ZM5.85375 4.85366L7.5 3.20678V8.99991C7.5 9.13251 7.55268 9.25969 7.64645 9.35346C7.74021 9.44723 7.86739 9.4999 8 9.4999C8.13261 9.4999 8.25979 9.44723 8.35355 9.35346C8.44732 9.25969 8.5 9.13251 8.5 8.99991V3.20678L10.1462 4.85366C10.2401 4.94748 10.3673 5.00018 10.5 5.00018C10.6327 5.00018 10.7599 4.94748 10.8538 4.85366C10.9476 4.75984 11.0003 4.63259 11.0003 4.49991C11.0003 4.36722 10.9476 4.23998 10.8538 4.14615L8.35375 1.64615C8.30731 1.59967 8.25217 1.56279 8.19147 1.53763C8.13077 1.51246 8.06571 1.49951 8 1.49951C7.93429 1.49951 7.86923 1.51246 7.80853 1.53763C7.74783 1.56279 7.69269 1.59967 7.64625 1.64615L5.14625 4.14615C5.05243 4.23998 4.99972 4.36722 4.99972 4.49991C4.99972 4.63259 5.05243 4.75984 5.14625 4.85366C5.24007 4.94748 5.36732 5.00018 5.5 5.00018C5.63268 5.00018 5.75993 4.94748 5.85375 4.85366Z" fill="#181818"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.6695 5.70312C19.5699 5.6497 19.4576 5.62432 19.3446 5.62969C19.2317 5.63505 19.1223 5.67097 19.0281 5.73359L16.25 7.58203V5.625C16.25 5.29348 16.1183 4.97554 15.8839 4.74112C15.6495 4.5067 15.3315 4.375 15 4.375H2.5C2.16848 4.375 1.85054 4.5067 1.61612 4.74112C1.3817 4.97554 1.25 5.29348 1.25 5.625V14.375C1.25 14.7065 1.3817 15.0245 1.61612 15.2589C1.85054 15.4933 2.16848 15.625 2.5 15.625H15C15.3315 15.625 15.6495 15.4933 15.8839 15.2589C16.1183 15.0245 16.25 14.7065 16.25 14.375V12.4219L19.0281 14.2742C19.1313 14.3412 19.252 14.3763 19.375 14.375C19.5408 14.375 19.6997 14.3092 19.8169 14.1919C19.9342 14.0747 20 13.9158 20 13.75V6.25C19.9992 6.13755 19.9681 6.02739 19.9099 5.93114C19.8518 5.83489 19.7687 5.75612 19.6695 5.70312ZM15 14.375H2.5V5.625H15V14.375ZM18.75 12.582L16.25 10.9156V9.08437L18.75 7.42188V12.582Z" fill="#181818"/>
</svg>

After

Width:  |  Height:  |  Size: 962 B

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -263,12 +263,26 @@ export const strings = {
monographDesc: () => t`This note will be published to a public URL.`,
openInBrowser: () => t`Open in browser`,
monographPassHeading: () => t`Password protection`,
monographPassHeadingOptional: () => t`Password protection (optional)`,
monographPassDesc: () =>
t`Published note can only be viewed by someone with the password.`,
secureYourNote: () => t`Secure your note`,
monographSelfDestructHeading: () => t`Self destruct`,
deleteAfterViewing: () => t`Delete after viewing`,
monographSelfDestructDesc: () =>
t`Published note link will be automatically deleted once it is viewed by someone.`,
monographLearnMore: () => t`Learn more about Notesnook Monograph`,
monographLearnMore: {
0: () => t`Learn more about Notesnook`,
1: () => t`Monographs`
},
editPublishedMonograph: () => t`Edit published monograph`,
publishedNoteLink: () => t`Published note link`,
totalViews: () => t`Total views`,
viewsOnPublishedNote: () => t`Views on your published note`,
enterNewPassword: () => t`Enter new password`,
unpublishNoteConfirm: () =>
t`Are you sure you want to unpublish this note? It will no longer be accessible via the public link.`,
yesUnpublish: () => t`Yes, Unpublish`,
rateAppHeading: () => t`Do you enjoy using Notesnook?`,
rateAppDesc: () =>
t`It took us a year to bring Notesnook to life. Share your experience and suggestions to help us improve it.`,
@@ -276,8 +290,8 @@ export const strings = {
t`Tap twice to confirm you have saved the recovery key.`,
noBlocksLinked: () => t`No blocks linked`,
noReferencesFound: () => t`No references found of this note`,
notReferenced: () => t`This note is not referenced in other notes.`,
notLinked: () => t`This note is not linked to any other note.`,
notReferenced: () => t`No references yet`,
notLinked: () => t`No links found`,
noLinksFound: () => t`No links found`,
noBlocksOnNote: () => t`There are no blocks in this note.`,
dataTypes: {
@@ -369,6 +383,9 @@ export const strings = {
},
remindMeIn: () => t`Remind me in`,
referencedIn: () => t`Referenced in`,
usedIn: () => t`Used in`,
linked: () => t`Linked`,
jumpToReference: () => t`Jump to Reference`,
restoreSelectFolder: () =>
t`Select the folder that includes your backup files to list them here.`,
noBackupsFound: () => t`No backups found`,
@@ -459,6 +476,11 @@ $day$: Current day (eg. Monday)`,
lockNote: () => t`Lock note`,
applyChanges: () => t`Apply changes`,
noteHistory: () => t`Note history`,
recentHistory: () => t`Recent history`,
historyDetail: () => t`History detail`,
noteContent: () => t`Note content`,
deletedVersionsCannotBeRecovered: () =>
t`Note: Deleted versions cannot be recovered.`,
selectNotebooks: () => t`Select notebooks`,
selectNotebooksDesc: () => t`Select notebooks you want to add note(s) to.`,
selectNotebooksDesktopDesc: (keyboardShortcut: string) =>
@@ -1068,6 +1090,8 @@ $day$: Current day (eg. Monday)`,
t`Recovery key helps you recover your data if you forget your password.`,
manageAttachments: () => t`Manage attachments`,
manageAttachmentsDesc: () => t`Manage your attachments in one place`,
allAttachments: () => t`All attachments`,
verifyFiles: () => t`Verify Files`,
changePasswordDesc: () => t`Change your account password`,
change2faMethodDesc: () =>
t`Change your primary two-factor authentication method`,
@@ -2957,5 +2981,11 @@ Continue without attachments?`,
shareZip: () => t`Share ZIP`,
tryDownloadLogsAgain: () => t`Try again`,
createSubnotebook: () => t`Create sub-notebook`,
saveChanges: () => t`Save changes`
saveChanges: () => t`Save changes`,
noteUnpublished: () => t`Note unpublished`,
noRemindersFound: () => t`No reminders found`,
reminderPlaceholder: () => t`Never miss a task. Set your first reminder now.`,
emptyLinkedNotes: () => t`Links of notes that are references in this note.`,
emptyReferences: () => t`Links of notes where this note is referenced.`,
passwordProtection: () => t`Password protection (optional)`
};