mobile: update ui
@@ -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={{
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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: {}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
247
apps/mobile/app/screens/note-history/index.tsx
Normal 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;
|
||||
298
apps/mobile/app/screens/note-preview/index.tsx
Normal 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;
|
||||
426
apps/mobile/app/screens/references/index.tsx
Normal 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;
|
||||
@@ -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
|
||||
});
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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<
|
||||
|
||||
@@ -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;
|
||||
|
||||
73
packages/editor/package-lock.json
generated
@@ -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"
|
||||
}
|
||||
|
||||
10
packages/icons/svgs/check-circle.svg
Executable 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 |
10
packages/icons/svgs/cloud-upload.svg
Normal 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
@@ -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 |
3
packages/icons/svgs/link-simple.svg
Normal 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 |
3
packages/icons/svgs/music-notes.svg
Executable 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
@@ -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 |
3
packages/icons/svgs/video-camera.svg
Executable 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 |
@@ -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)`
|
||||
};
|
||||
|
||||