mobile: top level notes

This commit is contained in:
ammarahm-ed
2023-03-16 21:22:21 +05:00
committed by Abdullah Atta
parent 765fcafc38
commit a6021943e1
42 changed files with 1579 additions and 1343 deletions

View File

@@ -17,70 +17,34 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React, { useEffect, useRef, useState } from "react"; import React, { useRef, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
import { FlatList } from "react-native-gesture-handler"; import { FlatList } from "react-native-gesture-handler";
import Icon from "react-native-vector-icons/MaterialCommunityIcons"; import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../common/database"; import { db } from "../../common/database";
import filesystem from "../../common/filesystem"; import filesystem from "../../common/filesystem";
import { import { presentSheet } from "../../services/event-manager";
eSubscribeEvent,
eUnSubscribeEvent
} from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store"; import { useThemeStore } from "../../stores/use-theme-store";
import {
eCloseAttachmentDialog,
eOpenAttachmentsDialog
} from "../../utils/events";
import { SIZE } from "../../utils/size"; import { SIZE } from "../../utils/size";
import DialogHeader from "../dialog/dialog-header"; import DialogHeader from "../dialog/dialog-header";
import { Toast } from "../toast";
import Input from "../ui/input"; import Input from "../ui/input";
import Seperator from "../ui/seperator"; import Seperator from "../ui/seperator";
import SheetWrapper from "../ui/sheet";
import Paragraph from "../ui/typography/paragraph"; import Paragraph from "../ui/typography/paragraph";
import { AttachmentItem } from "./attachment-item"; import { AttachmentItem } from "./attachment-item";
export const AttachmentDialog = () => {
export const AttachmentDialog = ({ data }) => {
const colors = useThemeStore((state) => state.colors); const colors = useThemeStore((state) => state.colors);
const [visible, setVisible] = useState(false); const [note, setNote] = useState(data);
const [note, setNote] = useState(null);
const actionSheetRef = useRef(); const actionSheetRef = useRef();
const [attachments, setAttachments] = useState([]); const [attachments, setAttachments] = useState(
data
? db.attachments.ofNote(data.id, "all")
: [...(db.attachments.all || [])]
);
const attachmentSearchValue = useRef(); const attachmentSearchValue = useRef();
const searchTimer = useRef(); const searchTimer = useRef();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
useEffect(() => {
eSubscribeEvent(eOpenAttachmentsDialog, open);
eSubscribeEvent(eCloseAttachmentDialog, close);
return () => {
eUnSubscribeEvent(eOpenAttachmentsDialog, open);
eUnSubscribeEvent(eCloseAttachmentDialog, close);
};
}, [visible]);
const open = (data) => {
if (data?.id) {
setNote(data);
let _attachments = db.attachments.ofNote(data.id, "all");
setAttachments(_attachments);
} else {
setAttachments([...db.attachments.all]);
}
setVisible(true);
};
useEffect(() => {
if (visible) {
actionSheetRef.current?.show();
}
}, [visible]);
const close = () => {
actionSheetRef.current?.hide();
setVisible(false);
};
const onChangeText = (text) => { const onChangeText = (text) => {
attachmentSearchValue.current = text; attachmentSearchValue.current = text;
if ( if (
@@ -104,107 +68,104 @@ export const AttachmentDialog = () => {
<AttachmentItem setAttachments={setAttachments} attachment={item} /> <AttachmentItem setAttachments={setAttachments} attachment={item} />
); );
return !visible ? null : ( return (
<SheetWrapper <View
centered={false} style={{
fwdRef={actionSheetRef} width: "100%",
onClose={async () => { alignSelf: "center",
setVisible(false); paddingHorizontal: 12
}} }}
> >
<Toast context="local" /> <DialogHeader
<View title={note ? "Attachments" : "Manage attachments"}
style={{ paragraph="Tap on an attachment to view properties"
width: "100%", button={{
alignSelf: "center", title: "Check all",
paddingHorizontal: 12 type: "grayAccent",
}} loading: loading,
> onPress: async () => {
<DialogHeader setLoading(true);
title={note ? "Attachments" : "Manage attachments"} for (let attachment of attachments) {
paragraph="Tap on an attachment to view properties" let result = await filesystem.checkAttachment(
button={{ attachment.metadata.hash
title: "Check all", );
type: "grayAccent", if (result.failed) {
loading: loading, db.attachments.markAsFailed(
onPress: async () => { attachment.metadata.hash,
setLoading(true); result.failed
for (let attachment of attachments) {
let result = await filesystem.checkAttachment(
attachment.metadata.hash
); );
if (result.failed) { } else {
db.attachments.markAsFailed( db.attachments.markAsFailed(attachment.id, null);
attachment.metadata.hash,
result.failed
);
} else {
db.attachments.markAsFailed(attachment.id, null);
}
setAttachments([...db.attachments.all]);
} }
setLoading(false); setAttachments([...db.attachments.all]);
} }
setLoading(false);
}
}}
/>
<Seperator />
{!note ? (
<Input
placeholder="Filter attachments by filename, type or hash"
onChangeText={onChangeText}
onSubmit={() => {
onChangeText(attachmentSearchValue.current);
}} }}
/> />
<Seperator /> ) : null}
{!note ? (
<Input <FlatList
placeholder="Filter attachments by filename, type or hash" nestedScrollEnabled
onChangeText={onChangeText} overScrollMode="never"
onSubmit={() => { scrollToOverflowEnabled={false}
onChangeText(attachmentSearchValue.current); keyboardDismissMode="none"
keyboardShouldPersistTaps="always"
onMomentumScrollEnd={() => {
actionSheetRef.current?.handleChildScrollEnd();
}}
ListEmptyComponent={
<View
style={{
height: 150,
justifyContent: "center",
alignItems: "center"
}}
>
<Icon name="attachment" size={60} color={colors.icon} />
<Paragraph>
{note ? "No attachments on this note" : "No attachments"}
</Paragraph>
</View>
}
ListFooterComponent={
<View
style={{
height: 350
}} }}
/> />
) : null} }
data={attachments}
keyExtractor={(item) => item.id}
renderItem={renderItem}
/>
<FlatList <Paragraph
nestedScrollEnabled color={colors.icon}
overScrollMode="never" size={SIZE.xs}
scrollToOverflowEnabled={false} style={{
keyboardDismissMode="none" textAlign: "center",
keyboardShouldPersistTaps="always" marginTop: 10
onMomentumScrollEnd={() => { }}
actionSheetRef.current?.handleChildScrollEnd(); >
}} <Icon name="shield-key-outline" size={SIZE.xs} color={colors.icon} />
ListEmptyComponent={ {" "}All attachments are end-to-end encrypted.
<View </Paragraph>
style={{ </View>
height: 150,
justifyContent: "center",
alignItems: "center"
}}
>
<Icon name="attachment" size={60} color={colors.icon} />
<Paragraph>
{note ? "No attachments on this note" : "No attachments"}
</Paragraph>
</View>
}
ListFooterComponent={
<View
style={{
height: 350
}}
/>
}
data={attachments}
keyExtractor={(item) => item.id}
renderItem={renderItem}
/>
<Paragraph
color={colors.icon}
size={SIZE.xs}
style={{
textAlign: "center",
marginTop: 10
}}
>
<Icon name="shield-key-outline" size={SIZE.xs} color={colors.icon} />
{" "}All attachments are end-to-end encrypted.
</Paragraph>
</View>
</SheetWrapper>
); );
}; };
AttachmentDialog.present = (note) => {
presentSheet({
component: () => <AttachmentDialog data={note} />
});
};

View File

@@ -21,7 +21,6 @@ import React from "react";
import { useNoteStore } from "../../stores/use-notes-store"; import { useNoteStore } from "../../stores/use-notes-store";
import { useThemeStore } from "../../stores/use-theme-store"; import { useThemeStore } from "../../stores/use-theme-store";
import { AnnouncementDialog } from "../announcements"; import { AnnouncementDialog } from "../announcements";
import { AttachmentDialog } from "../attachments";
import AuthModal from "../auth/auth-modal"; import AuthModal from "../auth/auth-modal";
import { SessionExpired } from "../auth/session-expired"; import { SessionExpired } from "../auth/session-expired";
import { Dialog } from "../dialog"; import { Dialog } from "../dialog";
@@ -34,10 +33,6 @@ import MergeConflicts from "../merge-conflicts";
import PremiumDialog from "../premium"; import PremiumDialog from "../premium";
import { Expiring } from "../premium/expiring"; import { Expiring } from "../premium/expiring";
import SheetProvider from "../sheet-provider"; import SheetProvider from "../sheet-provider";
import { AddNotebookSheet } from "../sheets/add-notebook";
import AddToNotebookSheet from "../sheets/add-to";
import ManageTagsSheet from "../sheets/manage-tags";
import PublishNoteSheet from "../sheets/publish-note";
import RateAppSheet from "../sheets/rate-app"; import RateAppSheet from "../sheets/rate-app";
import RecoveryKeySheet from "../sheets/recovery-key"; import RecoveryKeySheet from "../sheets/recovery-key";
import RestoreDataSheet from "../sheets/restore-data"; import RestoreDataSheet from "../sheets/restore-data";
@@ -51,7 +46,6 @@ const DialogProvider = () => {
<LoadingDialog /> <LoadingDialog />
<Dialog context="global" /> <Dialog context="global" />
<AddTopicDialog colors={colors} /> <AddTopicDialog colors={colors} />
<AddNotebookSheet colors={colors} />
<PremiumDialog colors={colors} /> <PremiumDialog colors={colors} />
<AuthModal colors={colors} /> <AuthModal colors={colors} />
<MergeConflicts /> <MergeConflicts />
@@ -61,12 +55,8 @@ const DialogProvider = () => {
<RestoreDataSheet /> <RestoreDataSheet />
<ResultDialog /> <ResultDialog />
<VaultDialog colors={colors} /> <VaultDialog colors={colors} />
<AddToNotebookSheet colors={colors} />
<RateAppSheet /> <RateAppSheet />
<ImagePreview /> <ImagePreview />
<PublishNoteSheet />
<ManageTagsSheet />
<AttachmentDialog />
{loading ? null : <Expiring />} {loading ? null : <Expiring />}
<AnnouncementDialog /> <AnnouncementDialog />
<SessionExpired /> <SessionExpired />

View File

@@ -59,14 +59,12 @@ export class AddTopicDialog extends React.Component {
addNewTopic = async () => { addNewTopic = async () => {
try { try {
this.setState({ loading: true });
if (!this.title || this.title?.trim() === "") { if (!this.title || this.title?.trim() === "") {
ToastEvent.show({ ToastEvent.show({
heading: "Topic title is required", heading: "Topic title is required",
type: "error", type: "error",
context: "local" context: "local"
}); });
this.setState({ loading: false });
return; return;
} }
@@ -78,10 +76,11 @@ export class AddTopicDialog extends React.Component {
await db.notebooks.notebook(topic.notebookId).topics.add(topic); await db.notebooks.notebook(topic.notebookId).topics.add(topic);
} }
this.setState({ loading: false });
this.close(); this.close();
Navigation.queueRoutesForUpdate("Notebooks", "Notebook", "TopicNotes"); setTimeout(() => {
useMenuStore.getState().setMenuPins(); Navigation.queueRoutesForUpdate("Notebooks", "Notebook", "TopicNotes");
useMenuStore.getState().setMenuPins();
});
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
@@ -177,7 +176,6 @@ export class AddTopicDialog extends React.Component {
positiveTitle={this.toEdit ? "Save" : "Add"} positiveTitle={this.toEdit ? "Save" : "Add"}
onPressNegative={() => this.close()} onPressNegative={() => this.close()}
onPressPositive={() => this.addNewTopic()} onPressPositive={() => this.addNewTopic()}
loading={this.state.loading}
/> />
</DialogContainer> </DialogContainer>
<Toast context="local" /> <Toast context="local" />

View File

@@ -30,7 +30,6 @@ import { useThemeStore } from "../../stores/use-theme-store";
import { eScrollEvent } from "../../utils/events"; import { eScrollEvent } from "../../utils/events";
import { SIZE } from "../../utils/size"; import { SIZE } from "../../utils/size";
import Heading from "../ui/typography/heading"; import Heading from "../ui/typography/heading";
import Paragraph from "../ui/typography/paragraph";
import { useCallback } from "react"; import { useCallback } from "react";
import Tag from "../ui/tag"; import Tag from "../ui/tag";
@@ -120,12 +119,6 @@ export const Title = () => {
}} }}
color={currentScreen.color || colors.heading} color={currentScreen.color || colors.heading}
> >
{isTopic ? (
<Paragraph numberOfLines={1} size={SIZE.xs + 1}>
{notebook?.title}
{"\n"}
</Paragraph>
) : null}
{isTag ? ( {isTag ? (
<Heading <Heading
size={isTopic ? SIZE.md + 2 : SIZE.xl} size={isTopic ? SIZE.md + 2 : SIZE.xl}

View File

@@ -24,6 +24,7 @@ import { View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons"; import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { notesnook } from "../../../../e2e/test.ids"; import { notesnook } from "../../../../e2e/test.ids";
import { db } from "../../../common/database"; import { db } from "../../../common/database";
import Notebook from "../../../screens/notebook";
import { TaggedNotes } from "../../../screens/notes/tagged"; import { TaggedNotes } from "../../../screens/notes/tagged";
import { TopicNotes } from "../../../screens/notes/topic-notes"; import { TopicNotes } from "../../../screens/notes/topic-notes";
import { useRelationStore } from "../../../stores/use-relation-store"; import { useRelationStore } from "../../../stores/use-relation-store";
@@ -39,10 +40,6 @@ import { TimeSince } from "../../ui/time-since";
import Heading from "../../ui/typography/heading"; import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph"; import Paragraph from "../../ui/typography/paragraph";
const navigateToTopic = (topic) => {
TopicNotes.navigate(topic, true);
};
function navigateToTag(item) { function navigateToTag(item) {
const tag = db.tags.tag(item.id); const tag = db.tags.tag(item.id);
if (!tag) return; if (!tag) return;
@@ -55,24 +52,29 @@ const showActionSheet = (item) => {
function getNotebook(item) { function getNotebook(item) {
const isTrash = item.type === "trash"; const isTrash = item.type === "trash";
if (isTrash || !item.notebooks || item.notebooks.length < 1) return []; if (isTrash) return [];
const items = [];
const notebooks = db.relations.to(item, "notebook") || [];
return item.notebooks.reduce(function (prev, curr) { for (let notebook of notebooks) {
if (prev && prev.length > 0) return prev; if (items.length > 1) break;
const topicId = curr.topics[0]; items.push(notebook);
const notebook = db.notebooks?.notebook(curr.id)?.data; }
if (!notebook) return [];
const topic = notebook.topics.find((t) => t.id === topicId);
if (!topic) return [];
return [ if (item.notebooks) {
{ for (let nb of item.notebooks) {
title: `${notebook?.title} ${topic?.title}`, if (items.length > 1) break;
notebook: notebook, const notebook = db.notebooks?.notebook(nb.id)?.data;
topic: topic if (!notebook) continue;
for (let topicId of nb.topics) {
if (items.length > 1) break;
const topic = notebook.topics.find((t) => t.id === topicId);
if (!topic) continue;
items.push(topic);
} }
]; }
}, []); }
return items;
} }
const NoteItem = ({ const NoteItem = ({
@@ -88,10 +90,11 @@ const NoteItem = ({
); );
const compactMode = notesListMode === "compact"; const compactMode = notesListMode === "compact";
const attachmentCount = db.attachments?.ofNote(item.id, "all")?.length || 0; const attachmentCount = db.attachments?.ofNote(item.id, "all")?.length || 0;
const notebooks = React.useMemo(() => getNotebook(item), [item]); const _update = useRelationStore((state) => state.updater);
// eslint-disable-next-line react-hooks/exhaustive-deps
const notebooks = React.useMemo(() => getNotebook(item), [item, _update]);
const reminders = db.relations.from(item, "reminder"); const reminders = db.relations.from(item, "reminder");
const reminder = getUpcomingReminder(reminders); const reminder = getUpcomingReminder(reminders);
const _update = useRelationStore((state) => state.updater);
const noteColor = COLORS_NOTE[item.color?.toLowerCase()]; const noteColor = COLORS_NOTE[item.color?.toLowerCase()];
return ( return (
<> <>
@@ -112,12 +115,12 @@ const NoteItem = ({
flexWrap: "wrap" flexWrap: "wrap"
}} }}
> >
{notebooks?.map((_item) => ( {notebooks?.map((item) => (
<Button <Button
title={_item.title} title={item.title}
key={_item} key={item}
height={25} height={25}
icon="book-outline" icon={item.type === "topic" ? "bookmark" : "book-outline"}
type="grayBg" type="grayBg"
fontSize={SIZE.xs} fontSize={SIZE.xs}
iconSize={SIZE.sm} iconSize={SIZE.sm}
@@ -132,7 +135,13 @@ const NoteItem = ({
paddingHorizontal: 6, paddingHorizontal: 6,
marginBottom: 5 marginBottom: 5
}} }}
onPress={() => navigateToTopic(_item.topic)} onPress={() => {
if (item.type === "topic") {
TopicNotes.navigate(item, true);
} else {
Notebook.navigate(item);
}
}}
/> />
))} ))}

View File

@@ -71,6 +71,7 @@ const RenderItem = ({ item, index, type, ...restArgs }) => {
}; };
}) })
.filter((t) => t !== null) || []; .filter((t) => t !== null) || [];
return ( return (
<Item <Item
item={item} item={item}
@@ -103,7 +104,9 @@ const List = ({
ListHeader, ListHeader,
warning, warning,
isSheet = false, isSheet = false,
onMomentumScrollEnd onMomentumScrollEnd,
handlers,
ScrollComponent
}) => { }) => {
const colors = useThemeStore((state) => state.colors); const colors = useThemeStore((state) => state.colors);
const scrollRef = useRef(); const scrollRef = useRef();
@@ -158,7 +161,7 @@ const List = ({
}; };
const _keyExtractor = (item) => item.id || item.title; const _keyExtractor = (item) => item.id || item.title;
const ListView = ScrollComponent ? ScrollComponent : FlashList;
return ( return (
<> <>
<Animated.View <Animated.View
@@ -167,7 +170,8 @@ const List = ({
}} }}
entering={type === "search" ? undefined : FadeInDown} entering={type === "search" ? undefined : FadeInDown}
> >
<FlashList <ListView
{...handlers}
style={styles} style={styles}
ref={scrollRef} ref={scrollRef}
testID={notesnook.list.id} testID={notesnook.list.id}

View File

@@ -16,10 +16,9 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React from "react"; import React from "react";
import { Platform, View } from "react-native"; import { Platform, View } from "react-native";
import { ScrollView } from "react-native-gesture-handler"; import { FlatList } from "react-native-actions-sheet";
import { db } from "../../common/database"; import { db } from "../../common/database";
import { DDS } from "../../services/device-detection"; import { DDS } from "../../services/device-detection";
import { presentSheet } from "../../services/event-manager"; import { presentSheet } from "../../services/event-manager";
@@ -37,7 +36,6 @@ import { Items } from "./items";
import Notebooks from "./notebooks"; import Notebooks from "./notebooks";
import { Synced } from "./synced"; import { Synced } from "./synced";
import { Tags, TagStrip } from "./tags"; import { Tags, TagStrip } from "./tags";
const Line = ({ top = 6, bottom = 6 }) => { const Line = ({ top = 6, bottom = 6 }) => {
const colors = useThemeStore((state) => state.colors); const colors = useThemeStore((state) => state.colors);
return ( return (
@@ -53,20 +51,11 @@ const Line = ({ top = 6, bottom = 6 }) => {
); );
}; };
export const Properties = ({ export const Properties = ({ close = () => {}, item, buttons = [] }) => {
close = () => {},
item,
buttons = [],
getRef
}) => {
const colors = useThemeStore((state) => state.colors); const colors = useThemeStore((state) => state.colors);
const alias = item.alias || item.title; const alias = item.alias || item.title;
const isColor = !!COLORS_NOTE[item.title]; const isColor = !!COLORS_NOTE[item.title];
const onScrollEnd = () => {
getRef().current?.handleChildScrollEnd();
};
if (!item || !item.id) { if (!item || !item.id) {
return ( return (
<Paragraph style={{ marginVertical: 10, alignSelf: "center" }}> <Paragraph style={{ marginVertical: 10, alignSelf: "center" }}>
@@ -75,89 +64,92 @@ export const Properties = ({
); );
} }
return ( return (
<ScrollView <FlatList
nestedScrollEnabled
onMomentumScrollEnd={onScrollEnd}
keyboardShouldPersistTaps="always" keyboardShouldPersistTaps="always"
keyboardDismissMode="none" keyboardDismissMode="none"
style={{ style={{
backgroundColor: colors.bg, backgroundColor: colors.bg,
paddingHorizontal: 0,
borderBottomRightRadius: DDS.isLargeTablet() ? 10 : 1, borderBottomRightRadius: DDS.isLargeTablet() ? 10 : 1,
borderBottomLeftRadius: DDS.isLargeTablet() ? 10 : 1, borderBottomLeftRadius: DDS.isLargeTablet() ? 10 : 1,
maxHeight: "100%" maxHeight: "100%"
}} }}
> data={[0]}
<View keyExtractor={() => "properties-scroll-item"}
style={{ renderItem={() => (
paddingHorizontal: 12, <View>
marginTop: 5, <View
zIndex: 10 style={{
}} paddingHorizontal: 12,
> marginTop: 5,
<Heading size={SIZE.lg}> zIndex: 10
{item.type === "tag" && !isColor ? ( }}
<Heading size={SIZE.xl} color={colors.accent}> >
# <Heading size={SIZE.lg}>
{item.type === "tag" && !isColor ? (
<Heading size={SIZE.xl} color={colors.accent}>
#
</Heading>
) : null}
{alias}
</Heading> </Heading>
) : null} {item.type === "note" ? (
{alias} <TagStrip close={close} item={item} />
</Heading> ) : null}
{item.type === "note" ? <TagStrip close={close} item={item} /> : null} {item.type === "reminder" ? (
<ReminderTime
reminder={item}
style={{
justifyContent: "flex-start",
borderWidth: 0,
height: 30,
alignSelf: "flex-start",
backgroundColor: "transparent",
paddingHorizontal: 0
}}
fontSize={SIZE.xs + 1}
/>
) : null}
</View>
<Line top={12} />
{item.type === "reminder" ? ( <DateMeta item={item} />
<ReminderTime <Line bottom={0} />
reminder={item} {item.type === "note" ? <Tags close={close} item={item} /> : null}
<View
style={{ style={{
justifyContent: "flex-start", paddingHorizontal: 12
borderWidth: 0, }}
height: 30, >
alignSelf: "flex-start", {item.notebooks ? <Notebooks note={item} close={close} /> : null}
backgroundColor: "transparent", </View>
paddingHorizontal: 0
<Items
item={item}
buttons={buttons}
close={() => {
close();
setTimeout(() => {
SearchService.updateAndSearch();
}, 1000);
}} }}
fontSize={SIZE.xs + 1}
/> />
) : null} <Synced item={item} close={close} />
</View> <DevMode item={item} />
<Line top={12} />
<DateMeta item={item} /> {DDS.isTab ? (
<Line bottom={0} /> <View
{item.type === "note" ? <Tags close={close} item={item} /> : null} style={{
height: 20
<View }}
style={{ />
paddingHorizontal: 12 ) : null}
}} <SheetProvider context="properties" />
> </View>
{item.notebooks ? <Notebooks note={item} close={close} /> : null} )}
</View> />
<Items
item={item}
buttons={buttons}
close={() => {
close();
setTimeout(() => {
SearchService.updateAndSearch();
}, 1000);
}}
/>
<Synced item={item} close={close} />
<DevMode item={item} />
{DDS.isTab ? (
<View
style={{
height: 20
}}
/>
) : null}
<SheetProvider context="properties" />
</ScrollView>
); );
}; };
@@ -219,12 +211,13 @@ Properties.present = (item, buttons = [], isSheet) => {
if (!props[0]) return; if (!props[0]) return;
presentSheet({ presentSheet({
context: isSheet ? "local" : undefined, context: isSheet ? "local" : undefined,
enableGesturesInScrollView: true,
component: (ref, close) => ( component: (ref, close) => (
<Properties <Properties
close={() => { close={() => {
close(); close();
}} }}
getRef={() => ref} actionSheetRef={ref}
item={props[0]} item={props[0]}
buttons={props[1]} buttons={props[1]}
/> />

View File

@@ -18,8 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React from "react"; import React from "react";
import { View } from "react-native"; import { FlatList, ScrollView, View } from "react-native";
import { FlatList } from "react-native-gesture-handler";
import Icon from "react-native-vector-icons/MaterialCommunityIcons"; import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { useActions } from "../../hooks/use-actions"; import { useActions } from "../../hooks/use-actions";
import { DDS } from "../../services/device-detection"; import { DDS } from "../../services/device-detection";
@@ -85,8 +84,9 @@ export const Items = ({ item, buttons, close }) => {
</View> </View>
); );
const renderColumnItem = ({ item }) => ( const renderColumnItem = (item) => (
<Button <Button
key={item.name + item.title}
buttonType={{ buttonType={{
text: item.on text: item.on
? colors.accent ? colors.accent
@@ -108,7 +108,7 @@ export const Items = ({ item, buttons, close }) => {
/> />
); );
const renderTopBarItem = ({ item }) => { const renderTopBarItem = (item) => {
return ( return (
<PressableButton <PressableButton
onPress={item.func} onPress={item.func}
@@ -169,7 +169,7 @@ export const Items = ({ item, buttons, close }) => {
"lock-unlock", "lock-unlock",
"publish" "publish"
]; ];
const bottomBarItems = data.filter( const topBarItems = data.filter(
(item) => topBarItemsList.indexOf(item.id) > -1 (item) => topBarItemsList.indexOf(item.id) > -1
); );
@@ -178,21 +178,19 @@ export const Items = ({ item, buttons, close }) => {
); );
const topBarItemWidth = const topBarItemWidth =
(width - (bottomBarItems.length * 10 + 14)) / bottomBarItems.length; (width - (topBarItems.length * 10 + 14)) / topBarItems.length;
return item.type === "note" ? ( return item.type === "note" ? (
<> <>
<FlatList <ScrollView
data={bottomBarItems}
keyExtractor={(item) => item.title}
horizontal horizontal
disableVirtualization={true}
style={{ style={{
paddingHorizontal: 12, paddingHorizontal: 12,
paddingTop: 12 paddingTop: 12
}} }}
renderItem={renderTopBarItem} >
/> {topBarItems.map(renderTopBarItem)}
</ScrollView>
<FlatList <FlatList
data={bottomGridItems} data={bottomGridItems}
@@ -216,11 +214,6 @@ export const Items = ({ item, buttons, close }) => {
/> />
</> </>
) : ( ) : (
<FlatList <View data={data}>{data.map(renderColumnItem)}</View>
data={data}
keyExtractor={(item) => item.title}
renderItem={renderColumnItem}
disableVirtualization={true}
/>
); );
}; };

View File

@@ -21,11 +21,10 @@ import React from "react";
import { View } from "react-native"; import { View } from "react-native";
import { db } from "../../common/database"; import { db } from "../../common/database";
import { TaggedNotes } from "../../screens/notes/tagged"; import { TaggedNotes } from "../../screens/notes/tagged";
import { eSendEvent } from "../../services/event-manager";
import { useThemeStore } from "../../stores/use-theme-store"; import { useThemeStore } from "../../stores/use-theme-store";
import { eOpenTagsDialog } from "../../utils/events";
import { SIZE } from "../../utils/size"; import { SIZE } from "../../utils/size";
import { sleep } from "../../utils/time"; import { sleep } from "../../utils/time";
import ManageTagsSheet from "../sheets/manage-tags";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { ColorTags } from "./color-tags"; import { ColorTags } from "./color-tags";
export const Tags = ({ item, close }) => { export const Tags = ({ item, close }) => {
@@ -46,9 +45,7 @@ export const Tags = ({ item, close }) => {
> >
<Button <Button
onPress={async () => { onPress={async () => {
close(); ManageTagsSheet.present(item);
await sleep(300);
eSendEvent(eOpenTagsDialog, item);
}} }}
buttonType={{ buttonType={{
text: colors.accent text: colors.accent

View File

@@ -201,7 +201,7 @@ export const SelectionHeader = React.memo(() => {
}} }}
> >
<Heading size={SIZE.md} color={colors.accent}> <Heading size={SIZE.md} color={colors.accent}>
{selectedItemsList.length + " Selected"} {selectedItemsList.length}
</Heading> </Heading>
</View> </View>
</View> </View>

View File

@@ -36,7 +36,7 @@ import Paragraph from "../ui/typography/paragraph";
const SheetProvider = ({ context = "global" }) => { const SheetProvider = ({ context = "global" }) => {
const colors = useThemeStore((state) => state.colors); const colors = useThemeStore((state) => state.colors);
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [dialogData, setDialogData] = useState(null); const [data, setData] = useState(null);
const actionSheetRef = useRef(); const actionSheetRef = useRef();
const editor = useRef({ const editor = useRef({
refocus: false refocus: false
@@ -52,42 +52,35 @@ const SheetProvider = ({ context = "global" }) => {
}, [close, open, visible]); }, [close, open, visible]);
const open = useCallback( const open = useCallback(
async (data) => { async (payload) => {
if (!data.context) data.context = "global"; if (!payload.context) payload.context = "global";
if (data.context !== context) return; if (payload.context !== context) return;
if (visible || dialogData) { setData(payload);
setDialogData(null);
setVisible(false);
await sleep(0);
}
setDialogData(data);
setVisible(true); setVisible(true);
if (data.editor) { if (payload.editor) {
editor.current.refocus = false; editor.current.refocus = false;
if (editorState().keyboardState) { if (editorState().keyboardState) {
// tiny.call(EditorWebView, tiny.cacheRange + tiny.blur);
editor.current.refocus = true; editor.current.refocus = true;
} }
} }
}, },
[context, dialogData, visible] [context]
); );
useEffect(() => { useEffect(() => {
(async () => { (async () => {
if (visible && dialogData) { if (visible && data) {
if (dialogData.editor) await sleep(100); if (data.editor) await sleep(100);
actionSheetRef.current?.setModalVisible(true); actionSheetRef.current?.setModalVisible(true);
return; return;
} else { } else {
if (editor.current?.refocus) { if (editor.current?.refocus) {
editorState().isFocused = true; editorState().isFocused = true;
// tiny.call(EditorWebView, tiny.restoreRange + tiny.clearRange);
editor.current.refocus = false; editor.current.refocus = false;
} }
} }
})(); })();
}, [visible, dialogData]); }, [visible, data]);
const close = useCallback( const close = useCallback(
(ctx) => { (ctx) => {
@@ -98,34 +91,30 @@ const SheetProvider = ({ context = "global" }) => {
[context] [context]
); );
return !visible || !dialogData ? null : ( return !visible || !data ? null : (
<SheetWrapper <SheetWrapper
fwdRef={actionSheetRef} fwdRef={actionSheetRef}
gestureEnabled={!dialogData?.progress && !dialogData?.disableClosing} gestureEnabled={!data?.progress && !data?.disableClosing}
closeOnTouchBackdrop={ closeOnTouchBackdrop={!data?.progress && !data?.disableClosing}
!dialogData?.progress && !dialogData?.disableClosing
}
onClose={() => { onClose={() => {
dialogData.onClose && dialogData.onClose(); data.onClose && data.onClose();
setVisible(false); setVisible(false);
setDialogData(null); setData(null);
}} }}
enableGesturesInScrollView={data.enableGesturesInScrollView}
> >
<View <View
style={{ style={{
justifyContent: "center", justifyContent: "center",
alignItems: "center", alignItems: "center",
marginBottom: marginBottom:
!dialogData.progress && !data.progress && !data.icon && !data.title && !data.paragraph
!dialogData.icon &&
!dialogData.title &&
!dialogData.paragraph
? 0 ? 0
: 10, : 10,
paddingHorizontal: 12 paddingHorizontal: 12
}} }}
> >
{dialogData?.progress ? ( {data?.progress ? (
<ActivityIndicator <ActivityIndicator
style={{ style={{
marginTop: 15 marginTop: 15
@@ -135,47 +124,48 @@ const SheetProvider = ({ context = "global" }) => {
/> />
) : null} ) : null}
{dialogData?.icon ? ( {data?.icon ? (
<Icon <Icon
color={colors[dialogData.iconColor] || colors.accent} color={colors[data.iconColor] || colors.accent}
name={dialogData.icon} name={data.icon}
size={50} size={50}
/> />
) : null} ) : null}
{dialogData?.title ? <Heading> {dialogData?.title}</Heading> : null} {data?.title ? <Heading> {data?.title}</Heading> : null}
{dialogData?.paragraph ? ( {data?.paragraph ? (
<Paragraph style={{ textAlign: "center" }}> <Paragraph style={{ textAlign: "center" }}>
{dialogData?.paragraph} {data?.paragraph}
</Paragraph> </Paragraph>
) : null} ) : null}
</View> </View>
{typeof dialogData.component === "function" {typeof data.component === "function"
? dialogData.component( ? data.component(
actionSheetRef, actionSheetRef,
() => close(context), () => close(context),
(data) => { (data) => {
if (!data) return; if (!data) return;
setDialogData((prevData) => { setData((prevData) => {
return { return {
...prevData, ...prevData,
...data ...data
}; };
}); });
} },
colors
) )
: dialogData.component} : data.component}
<View <View
style={{ style={{
paddingHorizontal: 12, paddingHorizontal: 12,
marginBottom: dialogData.valueArray ? 12 : 0 marginBottom: data.valueArray ? 12 : 0
}} }}
> >
{dialogData.valueArray && {data.valueArray &&
dialogData.valueArray.map((v) => ( data.valueArray.map((v) => (
<Button <Button
title={v} title={v}
type="gray" type="gray"
@@ -197,12 +187,12 @@ const SheetProvider = ({ context = "global" }) => {
paddingHorizontal: 12 paddingHorizontal: 12
}} }}
> >
{dialogData?.action ? ( {data?.action ? (
<Button <Button
onPress={dialogData.action} onPress={data.action}
key={dialogData.actionText} key={data.actionText}
title={dialogData.actionText} title={data.actionText}
accentColor={dialogData.iconColor || "accent"} accentColor={data.iconColor || "accent"}
accentText="light" accentText="light"
type="accent" type="accent"
height={45} height={45}
@@ -214,8 +204,8 @@ const SheetProvider = ({ context = "global" }) => {
/> />
) : null} ) : null}
{dialogData?.actionsArray && {data?.actionsArray &&
dialogData?.actionsArray.map((item) => ( data?.actionsArray.map((item) => (
<Button <Button
onPress={item.action} onPress={item.action}
key={item.accentText} key={item.accentText}
@@ -231,7 +221,7 @@ const SheetProvider = ({ context = "global" }) => {
/> />
))} ))}
{dialogData?.learnMore ? ( {data?.learnMore ? (
<Paragraph <Paragraph
style={{ style={{
alignSelf: "center", alignSelf: "center",
@@ -239,7 +229,7 @@ const SheetProvider = ({ context = "global" }) => {
textDecorationLine: "underline" textDecorationLine: "underline"
}} }}
size={SIZE.xs} size={SIZE.xs}
onPress={dialogData.learnMorePress} onPress={data.learnMorePress}
color={colors.icon} color={colors.icon}
> >
<Icon <Icon
@@ -247,7 +237,7 @@ const SheetProvider = ({ context = "global" }) => {
name="information-outline" name="information-outline"
size={SIZE.xs} size={SIZE.xs}
/>{" "} />{" "}
{dialogData.learnMore} {data.learnMore}
</Paragraph> </Paragraph>
) : null} ) : null}
</View> </View>

View File

@@ -25,41 +25,34 @@ import {
TouchableOpacity, TouchableOpacity,
View View
} from "react-native"; } from "react-native";
import { FlatList } from "react-native-gesture-handler";
import { notesnook } from "../../../../e2e/test.ids"; import { notesnook } from "../../../../e2e/test.ids";
import { useMenuStore } from "../../../stores/use-menu-store";
import { DDS } from "../../../services/device-detection";
import {
eSubscribeEvent,
eUnSubscribeEvent,
ToastEvent
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { db } from "../../../common/database"; import { db } from "../../../common/database";
import { import { DDS } from "../../../services/device-detection";
eCloseAddNotebookDialog, import { presentSheet, ToastEvent } from "../../../services/event-manager";
eOpenAddNotebookDialog import Navigation from "../../../services/navigation";
} from "../../../utils/events"; import { useMenuStore } from "../../../stores/use-menu-store";
import { useRelationStore } from "../../../stores/use-relation-store";
import { ph, pv, SIZE } from "../../../utils/size"; import { ph, pv, SIZE } from "../../../utils/size";
import { sleep } from "../../../utils/time"; import { sleep } from "../../../utils/time";
import { IconButton } from "../../ui/icon-button";
import { Button } from "../../ui/button";
import DialogHeader from "../../dialog/dialog-header"; import DialogHeader from "../../dialog/dialog-header";
import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button";
import Input from "../../ui/input"; import Input from "../../ui/input";
import { MoveNotes } from "../move-notes/movenote";
import Seperator from "../../ui/seperator"; import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet"; import { MoveNotes } from "../move-notes/movenote";
import { Toast } from "../../toast"; import { FlatList } from "react-native-actions-sheet";
let refs = []; let refs = [];
export class AddNotebookSheet extends React.Component { export class AddNotebookSheet extends React.Component {
constructor(props) { constructor(props) {
super(props); super(props);
refs = [];
this.state = { this.state = {
notebook: null, notebook: props.notebook,
visible: false, topics:
topics: [], props.notebook?.topics?.map((item) => {
return item.title;
}) || [],
description: null, description: null,
titleFocused: false, titleFocused: false,
descFocused: false, descFocused: false,
@@ -68,13 +61,14 @@ export class AddNotebookSheet extends React.Component {
editTopic: false, editTopic: false,
loading: false loading: false
}; };
this.title = null;
this.description = null; this.title = props.notebook?.title;
this.description = props.notebook?.description;
this.listRef; this.listRef;
this.prevItem = null; this.prevItem = null;
this.prevIndex = null; this.prevIndex = null;
this.currentSelectedInput = null; this.currentSelectedInput = null;
this.id = null; this.id = props.notebook?.id;
this.backPressCount = 0; this.backPressCount = 0;
this.currentInputValue = null; this.currentInputValue = null;
this.titleRef; this.titleRef;
@@ -83,57 +77,22 @@ export class AddNotebookSheet extends React.Component {
this.hiddenInput = createRef(); this.hiddenInput = createRef();
this.topicInputRef = createRef(); this.topicInputRef = createRef();
this.addingTopic = false; this.addingTopic = false;
this.actionSheetRef = createRef(); this.actionSheetRef = props.actionSheetRef;
}
componentDidMount() {
eSubscribeEvent(eOpenAddNotebookDialog, this.open);
eSubscribeEvent(eCloseAddNotebookDialog, this.close);
} }
componentWillUnmount() { componentWillUnmount() {
eUnSubscribeEvent(eOpenAddNotebookDialog, this.open); refs = [];
eUnSubscribeEvent(eCloseAddNotebookDialog, this.close);
} }
open = (notebook) => { componentDidMount() {
refs = []; sleep(300).then(() => {
!this.state.notebook && this.titleRef?.focus();
if (notebook) {
let topicsList = [];
notebook.topics.forEach((item) => {
topicsList.push(item.title);
});
this.id = notebook.id;
this.title = notebook.title;
this.description = notebook.description;
this.setState({
topics: [...topicsList],
visible: true,
notebook: notebook
});
} else {
this.setState({
visible: true,
notebook: null
});
}
sleep(100).then(() => {
this.actionSheetRef.current?.show();
}); });
}; }
close = () => { close = () => {
this.actionSheetRef.current?.hide();
refs = []; refs = [];
this.prevIndex = null; this.props.close();
this.prevItem = null;
this.currentSelectedInput = null;
this.title = null;
this.description = null;
this.currentInputValue = null;
this.id = null;
}; };
onDelete = (index) => { onDelete = (index) => {
@@ -240,15 +199,8 @@ export class AddNotebookSheet extends React.Component {
"Notebooks", "Notebooks",
"Notebook" "Notebook"
); );
useRelationStore.getState().update();
this.setState({ MoveNotes.present(db.notebooks.notebook(newNotebookId).data);
loading: false
});
this.close();
await sleep(300);
if (!notebook) {
MoveNotes.present(db.notebooks.notebook(newNotebookId).data);
}
}; };
onSubmit = (forward = true) => { onSubmit = (forward = true) => {
@@ -301,166 +253,139 @@ export class AddNotebookSheet extends React.Component {
render() { render() {
const { colors } = this.props; const { colors } = this.props;
const { topics, visible, topicInputFocused, notebook } = this.state; const { topics, topicInputFocused, notebook } = this.state;
if (!visible) return null;
return ( return (
<SheetWrapper <View
onOpen={async () => { style={{
this.topicsToDelete = []; maxHeight: DDS.isTab ? "90%" : "96%",
await sleep(300); borderRadius: DDS.isTab ? 5 : 0,
!this.state.notebook && this.titleRef?.focus(); paddingHorizontal: 12
}} }}
fwdRef={this.actionSheetRef}
onClose={() => {
this.close();
this.setState({
visible: false,
topics: [],
descFocused: false,
titleFocused: false,
editTopic: false,
notebook: null
});
}}
statusBarTranslucent={false}
onRequestClose={this.close}
> >
<View <TextInput
ref={this.hiddenInput}
style={{ style={{
maxHeight: DDS.isTab ? "90%" : "96%", width: 1,
borderRadius: DDS.isTab ? 5 : 0, height: 1,
paddingHorizontal: 12 opacity: 0,
position: "absolute"
}} }}
> blurOnSubmit={false}
<TextInput />
ref={this.hiddenInput} <DialogHeader
style={{ title={
width: 1, notebook && notebook.dateCreated ? "Edit Notebook" : "New Notebook"
height: 1, }
opacity: 0, paragraph={
position: "absolute" notebook && notebook.dateCreated
}} ? "You are editing " + this.title + " notebook."
blurOnSubmit={false} : "Notebooks are the best way to organize your notes."
/> }
<DialogHeader />
title={ <Seperator half />
notebook && notebook.dateCreated
? "Edit Notebook" <Input
: "New Notebook" fwdRef={(ref) => (this.titleRef = ref)}
testID={notesnook.ids.dialogs.notebook.inputs.title}
onChangeText={(value) => {
this.title = value;
}}
placeholder="Enter a title"
onSubmit={() => {
this.descriptionRef.focus();
}}
returnKeyLabel="Next"
returnKeyType="next"
defaultValue={notebook ? notebook.title : null}
/>
<Input
fwdRef={(ref) => (this.descriptionRef = ref)}
testID={notesnook.ids.dialogs.notebook.inputs.description}
onChangeText={(value) => {
this.description = value;
}}
placeholder="Describe your notebook."
onSubmit={() => {
this.topicInputRef.current?.focus();
}}
returnKeyLabel="Next"
returnKeyType="next"
defaultValue={notebook ? notebook.description : null}
/>
<Input
fwdRef={this.topicInputRef}
testID={notesnook.ids.dialogs.notebook.inputs.topic}
onChangeText={(value) => {
this.currentInputValue = value;
if (this.prevItem !== null) {
refs[this.prevIndex].setNativeProps({
text: value,
style: {
borderBottomColor: colors.accent
}
});
} }
paragraph={ }}
notebook && notebook.dateCreated returnKeyLabel="Done"
? "You are editing " + this.title + " notebook." returnKeyType="done"
: "Notebooks are the best way to organize your notes." onSubmit={() => {
} this.onSubmit();
/> }}
<Seperator half /> blurOnSubmit={false}
button={{
testID: "topic-add-button",
icon: this.state.editTopic ? "check" : "plus",
onPress: this.onSubmit,
color: topicInputFocused ? colors.accent : colors.icon
}}
placeholder="Add a topic"
/>
<Input <FlatList
fwdRef={(ref) => (this.titleRef = ref)} data={topics}
testID={notesnook.ids.dialogs.notebook.inputs.title} ref={(ref) => (this.listRef = ref)}
onChangeText={(value) => { nestedScrollEnabled
this.title = value; keyExtractor={(item, index) => item + index.toString()}
}} keyboardShouldPersistTaps="always"
placeholder="Enter a title" keyboardDismissMode="interactive"
onSubmit={() => { ListFooterComponent={<View style={{ height: 50 }} />}
this.descriptionRef.focus(); renderItem={({ item, index }) => (
}} <TopicItem
returnKeyLabel="Next" item={item}
returnKeyType="next" onPress={(item, index) => {
defaultValue={notebook ? notebook.title : null} this.prevIndex = index;
/> this.prevItem = item;
this.topicInputRef.current?.setNativeProps({
<Input text: item
fwdRef={(ref) => (this.descriptionRef = ref)}
testID={notesnook.ids.dialogs.notebook.inputs.description}
onChangeText={(value) => {
this.description = value;
}}
placeholder="Describe your notebook."
onSubmit={() => {
this.topicInputRef.current?.focus();
}}
returnKeyLabel="Next"
returnKeyType="next"
defaultValue={notebook ? notebook.description : null}
/>
<Input
fwdRef={this.topicInputRef}
testID={notesnook.ids.dialogs.notebook.inputs.topic}
onChangeText={(value) => {
this.currentInputValue = value;
if (this.prevItem !== null) {
refs[this.prevIndex].setNativeProps({
text: value,
style: {
borderBottomColor: colors.accent
}
}); });
} this.topicInputRef.current?.focus();
}} this.currentInputValue = item;
returnKeyLabel="Done" this.setState({
returnKeyType="done" editTopic: true
onSubmit={() => { });
this.onSubmit(); }}
}} onDelete={this.onDelete}
blurOnSubmit={false} index={index}
button={{ colors={colors}
testID: "topic-add-button", />
icon: this.state.editTopic ? "check" : "plus", )}
onPress: this.onSubmit, />
color: topicInputFocused ? colors.accent : colors.icon <Seperator />
}} <Button
placeholder="Add a topic" width="100%"
/> height={50}
fontSize={SIZE.md}
<FlatList title={
data={topics} notebook && notebook.dateCreated
ref={(ref) => (this.listRef = ref)} ? "Save changes"
nestedScrollEnabled : "Create notebook"
keyExtractor={(item, index) => item + index.toString()} }
onMomentumScrollEnd={() => { type="accent"
this.actionSheetRef.current?.handleChildScrollEnd(); onPress={this.addNewNotebook}
}} />
keyboardShouldPersistTaps="always" {/*
keyboardDismissMode="interactive"
ListFooterComponent={<View style={{ height: 50 }} />}
renderItem={({ item, index }) => (
<TopicItem
item={item}
onPress={(item, index) => {
this.prevIndex = index;
this.prevItem = item;
this.topicInputRef.current?.setNativeProps({
text: item
});
this.topicInputRef.current?.focus();
this.currentInputValue = item;
this.setState({
editTopic: true
});
}}
onDelete={this.onDelete}
index={index}
colors={colors}
/>
)}
/>
<Seperator />
<Button
width="100%"
height={50}
fontSize={SIZE.md}
title={
notebook && notebook.dateCreated
? "Save changes"
: "Create notebook"
}
type="accent"
onPress={this.addNewNotebook}
/>
{/*
{Platform.OS === 'ios' && ( {Platform.OS === 'ios' && (
<View <View
style={{ style={{
@@ -468,14 +393,24 @@ export class AddNotebookSheet extends React.Component {
}} }}
/> />
)} */} )} */}
</View> </View>
<Toast context="local" />
</SheetWrapper>
); );
} }
} }
AddNotebookSheet.present = (notebook) => {
presentSheet({
component: (ref, close, _update, colors) => (
<AddNotebookSheet
actionSheetRef={ref}
notebook={notebook}
close={close}
colors={colors}
/>
)
});
};
const TopicItem = ({ item, index, colors, onPress, onDelete }) => { const TopicItem = ({ item, index, colors, onPress, onDelete }) => {
const topicRef = (ref) => (refs[index] = ref); const topicRef = (ref) => (refs[index] = ref);

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React, { useCallback, useEffect, useRef, useState } from "react"; import React, { useCallback, useEffect, useRef, useState } from "react";
import { FlatList } from "react-native-gesture-handler"; import { FlatList } from "react-native-actions-sheet";
import { db } from "../../../common/database"; import { db } from "../../../common/database";
import { ListHeaderInputItem } from "./list-header-item.js"; import { ListHeaderInputItem } from "./list-header-item.js";
@@ -27,6 +27,7 @@ export const FilteredList = ({
itemType, itemType,
onAddItem, onAddItem,
hasHeaderSearch, hasHeaderSearch,
listRef,
...restProps ...restProps
}) => { }) => {
const [filtered, setFiltered] = useState(data); const [filtered, setFiltered] = useState(data);
@@ -56,6 +57,7 @@ export const FilteredList = ({
<FlatList <FlatList
{...restProps} {...restProps}
data={filtered} data={filtered}
ref={listRef}
ListHeaderComponent={ ListHeaderComponent={
hasHeaderSearch ? ( hasHeaderSearch ? (
<ListHeaderInputItem <ListHeaderInputItem

View File

@@ -17,91 +17,37 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React, { import React, { useCallback, useEffect, useMemo, useState } from "react";
createRef,
useCallback,
useEffect,
useMemo,
useState
} from "react";
import { Keyboard, TouchableOpacity, View } from "react-native"; import { Keyboard, TouchableOpacity, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons"; import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database"; import { db } from "../../../common/database";
import { import { presentSheet, ToastEvent } from "../../../services/event-manager";
eSubscribeEvent,
eUnSubscribeEvent,
ToastEvent
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation"; import Navigation from "../../../services/navigation";
import SearchService from "../../../services/search"; import SearchService from "../../../services/search";
import { useNotebookStore } from "../../../stores/use-notebook-store"; import { useNotebookStore } from "../../../stores/use-notebook-store";
import { useSelectionStore } from "../../../stores/use-selection-store"; import { useSelectionStore } from "../../../stores/use-selection-store";
import { useThemeStore } from "../../../stores/use-theme-store"; import { useThemeStore } from "../../../stores/use-theme-store";
import { eOpenMoveNoteDialog } from "../../../utils/events";
import { Dialog } from "../../dialog"; import { Dialog } from "../../dialog";
import DialogHeader from "../../dialog/dialog-header"; import DialogHeader from "../../dialog/dialog-header";
import { presentDialog } from "../../dialog/functions"; import { presentDialog } from "../../dialog/functions";
import { Button } from "../../ui/button"; import { Button } from "../../ui/button";
import SheetWrapper from "../../ui/sheet";
import Paragraph from "../../ui/typography/paragraph"; import Paragraph from "../../ui/typography/paragraph";
import { SelectionProvider } from "./context"; import { SelectionProvider } from "./context";
import { FilteredList } from "./filtered-list"; import { FilteredList } from "./filtered-list";
import { ListItem } from "./list-item"; import { ListItem } from "./list-item";
const actionSheetRef = createRef(); const MoveNoteSheet = ({ note, actionSheetRef }) => {
const AddToNotebookSheet = () => {
const [visible, setVisible] = useState(false);
const [note, setNote] = useState(null);
function open(note) {
setNote(note);
setVisible(true);
actionSheetRef.current?.setModalVisible(true);
}
useEffect(() => {
eSubscribeEvent(eOpenMoveNoteDialog, open);
return () => {
eUnSubscribeEvent(eOpenMoveNoteDialog, open);
};
}, []);
const _onClose = () => {
setVisible(false);
setNote(null);
Navigation.queueRoutesForUpdate(
"Notes",
"Favorites",
"ColoredNotes",
"TaggedNotes",
"TopicNotes",
"Notebooks",
"Notebook"
);
};
return !visible ? null : (
<SheetWrapper fwdRef={actionSheetRef} onClose={_onClose}>
<MoveNoteComponent note={note} />
</SheetWrapper>
);
};
export default AddToNotebookSheet;
const MoveNoteComponent = ({ note }) => {
const colors = useThemeStore((state) => state.colors); const colors = useThemeStore((state) => state.colors);
const [multiSelect, setMultiSelect] = useState(false); const [multiSelect, setMultiSelect] = useState(false);
const notebooks = useNotebookStore((state) => const notebooks = useNotebookStore((state) =>
state.notebooks.filter((n) => n?.type === "notebook") state.notebooks.filter((n) => n?.type === "notebook")
); );
const [edited, setEdited] = useState(false);
const selectedItemsList = useSelectionStore( const selectedItemsList = useSelectionStore(
(state) => state.selectedItemsList (state) => state.selectedItemsList
); );
const setNotebooks = useNotebookStore((state) => state.setNotebooks); const setNotebooks = useNotebookStore((state) => state.setNotebooks);
const [itemState, setItemState] = useState({}); const [itemState, setItemState] = useState({});
const onAddNotebook = async (title) => { const onAddNotebook = async (title) => {
if (!title || title.trim().length === 0) { if (!title || title.trim().length === 0) {
ToastEvent.show({ ToastEvent.show({
@@ -159,13 +105,13 @@ const MoveNoteComponent = ({ note }) => {
(item) => { (item) => {
switch (item.type) { switch (item.type) {
case "notebook": { case "notebook": {
const noteIds = []; const notes = db.relations.from(item, "note");
for (let topic of item.topics) { if (notes.length === 0) return 0;
noteIds.push(...(db.notes?.topicReferences.get(topic.id) || []));
}
let count = 0; let count = 0;
selectedItemsList.forEach((item) => selectedItemsList.forEach((item) =>
noteIds.indexOf(item.id) > -1 ? count++ : undefined notes.findIndex((note) => note.id === item.id) > -1
? count++
: undefined
); );
return count; return count;
} }
@@ -195,8 +141,7 @@ const MoveNoteComponent = ({ note }) => {
for (let notebook of notebooks) { for (let notebook of notebooks) {
itemState[notebook.id] = state itemState[notebook.id] = state
? state ? state
: areAllSelectedItemsInAllTopics(notebook, selectedItemsList) && : areAllSelectedItemsInNotebook(notebook, selectedItemsList)
getSelectedNotesCountInItem(notebook, selectedItemsList) > 0
? "selected" ? "selected"
: getSelectedNotesCountInItem(notebook, selectedItemsList) > 0 : getSelectedNotesCountInItem(notebook, selectedItemsList) > 0
? "intermediate" ? "intermediate"
@@ -231,11 +176,11 @@ const MoveNoteComponent = ({ note }) => {
} }
}; };
function areAllSelectedItemsInAllTopics(notebook, items) { function areAllSelectedItemsInNotebook(notebook, items) {
const notes = db.relations.from(notebook, "note");
if (notes.length === 0) return false;
return items.every((item) => { return items.every((item) => {
return notebook.topics.every((topic) => { return notes.find((note) => note.id === item.id);
return db.notes.topicReferences.get(topic.id).indexOf(item.id) > -1;
});
}); });
} }
@@ -250,25 +195,6 @@ const MoveNoteComponent = ({ note }) => {
const mergeState = { const mergeState = {
[item.id]: state [item.id]: state
}; };
const notebooks = db.notebooks.all;
const notebook =
item.type === "notebook"
? item
: notebooks.find((n) => n.id === item.notebookId);
const intermediate = notebook.topics.some((topic) => {
return topic.id === item.id
? state === "selected"
: itemState[topic.id] === "selected";
});
if (intermediate) mergeState[notebook.id] = "intermediate";
const selected = notebook.topics.every((topic) => {
return topic.id === item.id
? state === "selected"
: itemState[topic.id] === "selected";
});
if (selected) mergeState[notebook.id] = "selected";
if (!selected && !intermediate) mergeState[notebook.id] = "deselected";
return { return {
...itemState, ...itemState,
...mergeState ...mergeState
@@ -314,28 +240,39 @@ const MoveNoteComponent = ({ note }) => {
}; };
const onSave = async () => { const onSave = async () => {
const noteIds = note ? [note.id] : selectedItemsList.map((n) => n.id);
for (const id in itemState) { for (const id in itemState) {
const item = getItemFromId(id); const item = getItemFromId(id);
if (item.type === "notebook") continue;
const noteIds = selectedItemsList.map((n) => n.id);
if (itemState[id] === "selected") { if (itemState[id] === "selected") {
await db.notes.addToNotebook( if (item.type === "notebook") {
{ for (let noteId of noteIds) {
topic: item.id, db.relations.add(item, { id: noteId, type: "note" });
id: item.notebookId, }
rebuildCache: true } else {
}, await db.notes.addToNotebook(
...noteIds {
); topic: item.id,
id: item.notebookId,
rebuildCache: true
},
...noteIds
);
}
} else if (itemState[id] === "deselected") { } else if (itemState[id] === "deselected") {
await db.notes.removeFromNotebook( if (item.type === "notebook") {
{ for (let noteId of noteIds) {
id: item.notebookId, db.relations.unlink(item, { id: noteId, type: "note" });
topic: item.id, }
rebuildCache: true } else {
}, await db.notes.removeFromNotebook(
...noteIds {
); id: item.notebookId,
topic: item.id,
rebuildCache: true
},
...noteIds
);
}
} }
} }
Navigation.queueRoutesForUpdate( Navigation.queueRoutesForUpdate(
@@ -343,7 +280,8 @@ const MoveNoteComponent = ({ note }) => {
"Favorites", "Favorites",
"ColoredNotes", "ColoredNotes",
"TaggedNotes", "TaggedNotes",
"TopicNotes" "TopicNotes",
"Notebook"
); );
setNotebooks(); setNotebooks();
SearchService.updateAndSearch(); SearchService.updateAndSearch();
@@ -420,9 +358,6 @@ const MoveNoteComponent = ({ note }) => {
<SelectionProvider value={contextValue}> <SelectionProvider value={contextValue}>
<FilteredList <FilteredList
onMomentumScrollEnd={() => {
actionSheetRef.current?.handleChildScrollEnd();
}}
style={{ style={{
paddingHorizontal: 12 paddingHorizontal: 12
}} }}
@@ -455,6 +390,7 @@ const MoveNoteComponent = ({ note }) => {
itemState[item.id] === "deselected" && itemState[item.id] === "deselected" &&
getSelectedNotesCountInItem(item) > 0 getSelectedNotesCountInItem(item) > 0
} }
sheetRef={actionSheetRef}
isSelected={itemState[item.id] === "selected"} isSelected={itemState[item.id] === "selected"}
infoText={ infoText={
<> <>
@@ -480,10 +416,6 @@ const MoveNoteComponent = ({ note }) => {
if (currentState !== "selected") { if (currentState !== "selected") {
resetItemState("deselected"); resetItemState("deselected");
contextValue.select(item); contextValue.select(item);
updateItemState(
notebooks.find((n) => n.id === item.notebookId),
"intermediate"
);
} else { } else {
contextValue.deselect(item); contextValue.deselect(item);
} }
@@ -514,16 +446,24 @@ const MoveNoteComponent = ({ note }) => {
onAddItem={async (title) => { onAddItem={async (title) => {
return await onAddNotebook(title); return await onAddNotebook(title);
}} }}
ListFooterComponent={ // ListFooterComponent={
<View // <View
style={{ // style={{
height: 200 // height: 200
}} // }}
/> // />
} // }
/> />
</SelectionProvider> </SelectionProvider>
</View> </View>
</> </>
); );
}; };
MoveNoteSheet.present = (note) => {
presentSheet({
component: (ref) => <MoveNoteSheet actionSheetRef={ref} note={note} />,
enableGesturesInScrollView: false
});
};
export default MoveNoteSheet;

View File

@@ -46,25 +46,15 @@ const _ListItem = ({
removed, removed,
isSelected, isSelected,
hasHeaderSearch, hasHeaderSearch,
onAddSublistItem onAddSublistItem,
sheetRef
}) => { }) => {
const { enabled, toggleSelection, setMultiSelect, select, deselect } = const { enabled, toggleSelection, setMultiSelect } = useSelectionContext();
useSelectionContext();
const colors = useThemeStore((state) => state.colors); const colors = useThemeStore((state) => state.colors);
const [expanded, setExpanded] = useState(false); const [expanded, setExpanded] = useState(false);
function selectItem() { function selectItem() {
const currentState = isSelected;
toggleSelection(item); toggleSelection(item);
if (item.type === "notebook") {
item.topics.forEach((item) => {
if (currentState) {
deselect(item);
} else {
select(item);
}
});
}
} }
return ( return (
<View <View
@@ -122,9 +112,6 @@ const _ListItem = ({
: colors.icon : colors.icon
} }
onPress={() => { onPress={() => {
if (item.type === "notebook") {
setMultiSelect(true);
}
selectItem(); selectItem();
if (enabled) return; if (enabled) return;
onPress?.(item); onPress?.(item);
@@ -205,7 +192,7 @@ const _ListItem = ({
style={{ style={{
width: "95%", width: "95%",
alignSelf: "flex-end", alignSelf: "flex-end",
maxHeight: 500 maxHeight: 250
}} }}
itemType={sublistItemType} itemType={sublistItemType}
hasHeaderSearch={hasHeaderSearch} hasHeaderSearch={hasHeaderSearch}

View File

@@ -18,7 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React, { RefObject, useRef, useState } from "react"; import React, { RefObject, useRef, useState } from "react";
import { TextInput, View } from "react-native"; import { TextInput, View } from "react-native";
import ActionSheet from "react-native-actions-sheet"; import { ActionSheetRef } from "react-native-actions-sheet";
import { db } from "../../../common/database"; import { db } from "../../../common/database";
import { import {
eSendEvent, eSendEvent,
@@ -31,7 +31,7 @@ import { Button } from "../../ui/button";
import Input from "../../ui/input"; import Input from "../../ui/input";
type ChangeEmailProps = { type ChangeEmailProps = {
actionSheetRef: RefObject<ActionSheet>; actionSheetRef: RefObject<ActionSheetRef>;
close?: () => void; close?: () => void;
update?: (options: PresentSheetOptions) => void; update?: (options: PresentSheetOptions) => void;
}; };

View File

@@ -17,51 +17,32 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React, { useEffect, useRef, useState } from "react"; import React, { useCallback, useEffect, useRef, useState } from "react";
import { ScrollView, View } from "react-native"; import { View } from "react-native";
import { ScrollView } from "react-native-actions-sheet";
import Icon from "react-native-vector-icons/MaterialCommunityIcons"; import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database"; import { db } from "../../../common/database";
import { import { presentSheet, ToastEvent } from "../../../services/event-manager";
eSubscribeEvent,
eUnSubscribeEvent,
ToastEvent
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation"; import Navigation from "../../../services/navigation";
import { useTagStore } from "../../../stores/use-tag-store"; import { useTagStore } from "../../../stores/use-tag-store";
import { useThemeStore } from "../../../stores/use-theme-store"; import { useThemeStore } from "../../../stores/use-theme-store";
import { eCloseTagsDialog, eOpenTagsDialog } from "../../../utils/events";
import { SIZE } from "../../../utils/size"; import { SIZE } from "../../../utils/size";
import { sleep } from "../../../utils/time";
import Input from "../../ui/input"; import Input from "../../ui/input";
import { PressableButton } from "../../ui/pressable"; import { PressableButton } from "../../ui/pressable";
import SheetWrapper from "../../ui/sheet";
import Heading from "../../ui/typography/heading"; import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph"; import Paragraph from "../../ui/typography/paragraph";
import { useCallback } from "react"; const ManageTagsSheet = (props) => {
const ManageTagsSheet = () => {
const colors = useThemeStore((state) => state.colors); const colors = useThemeStore((state) => state.colors);
const [visible, setVisible] = useState(false); const [note, setNote] = useState(props.note);
const [note, setNote] = useState(null);
const allTags = useTagStore((state) => state.tags); const allTags = useTagStore((state) => state.tags);
const [tags, setTags] = useState([]); const [tags, setTags] = useState([]);
const [query, setQuery] = useState(null); const [query, setQuery] = useState(null);
const inputRef = useRef(); const inputRef = useRef();
const actionSheetRef = useRef(); const [focus, setFocus] = useState(false);
useEffect(() => { useEffect(() => {
eSubscribeEvent(eOpenTagsDialog, open); sortTags();
eSubscribeEvent(eCloseTagsDialog, close); }, [allTags, note, query, sortTags]);
return () => {
eUnSubscribeEvent(eOpenTagsDialog, open);
eUnSubscribeEvent(eCloseTagsDialog, close);
};
}, [open]);
useEffect(() => {
if (visible) {
sortTags();
}
}, [allTags, note, query, sortTags, visible]);
const sortTags = useCallback(() => { const sortTags = useCallback(() => {
let _tags = [...allTags]; let _tags = [...allTags];
@@ -88,26 +69,9 @@ const ManageTagsSheet = () => {
setTags(combinedTags); setTags(combinedTags);
}, [allTags, note, query]); }, [allTags, note, query]);
const open = useCallback(
(item) => {
setNote(item);
useTagStore.getState().setTags();
sortTags();
setVisible(true);
},
[sortTags]
);
useEffect(() => { useEffect(() => {
if (visible) { useTagStore.getState().setTags();
actionSheetRef.current?.show(); }, []);
}
}, [visible]);
const close = () => {
setQuery(null);
actionSheetRef.current?.hide();
};
const onSubmit = async () => { const onSubmit = async () => {
let _query = query; let _query = query;
@@ -150,104 +114,96 @@ const ManageTagsSheet = () => {
); );
}; };
return !visible ? null : ( return (
<SheetWrapper <View
centered={false} style={{
fwdRef={actionSheetRef} width: "100%",
onOpen={async () => { alignSelf: "center",
await sleep(300); paddingHorizontal: 12,
inputRef.current?.focus(); minHeight: focus ? "100%" : "60%"
}}
onClose={async () => {
setQuery(null);
setVisible(false);
}} }}
> >
<View <Input
style={{ button={{
width: "100%", icon: "magnify",
alignSelf: "center", color: colors.accent,
paddingHorizontal: 12, size: SIZE.lg
minHeight: "60%"
}} }}
testID="tag-input"
fwdRef={inputRef}
autoCapitalize="none"
onChangeText={(v) => {
setQuery(db.tags.sanitize(v));
}}
onFocusInput={() => {
setFocus(true);
}}
onBlurInput={() => {
setFocus(false);
}}
onSubmit={onSubmit}
height={50}
placeholder="Search or add a tag"
/>
<ScrollView
overScrollMode="never"
scrollToOverflowEnabled={false}
keyboardDismissMode="none"
keyboardShouldPersistTaps="always"
> >
<Input {query && query !== tags[0]?.title ? (
button={{ <PressableButton
icon: "magnify", key={"query_item"}
color: colors.accent, customStyle={{
size: SIZE.lg flexDirection: "row",
}} marginVertical: 5,
testID="tag-input" justifyContent: "space-between",
fwdRef={inputRef} padding: 12
autoCapitalize="none" }}
onChangeText={(v) => { onPress={onSubmit}
setQuery(db.tags.sanitize(v)); type="accent"
}} >
onSubmit={onSubmit} <Heading size={SIZE.sm} color={colors.light}>
height={50} Add {'"' + "#" + query + '"'}
placeholder="Search or add a tag" </Heading>
/> <Icon name="plus" color={colors.light} size={SIZE.lg} />
</PressableButton>
) : null}
{!allTags || allTags.length === 0 ? (
<View
style={{
width: "100%",
height: 200,
justifyContent: "center",
alignItems: "center"
}}
>
<Heading size={50} color={colors.icon}>
#
</Heading>
<Paragraph textBreakStrategy="balanced" color={colors.icon}>
You do not have any tags.
</Paragraph>
</View>
) : null}
<ScrollView {tags.map((item) => (
nestedScrollEnabled <TagItem key={item.title} tag={item} note={note} setNote={setNote} />
overScrollMode="never" ))}
scrollToOverflowEnabled={false} </ScrollView>
keyboardDismissMode="none" </View>
keyboardShouldPersistTaps="always"
onMomentumScrollEnd={() => {
actionSheetRef.current?.handleChildScrollEnd();
}}
>
{query && query !== tags[0]?.title ? (
<PressableButton
key={"query_item"}
customStyle={{
flexDirection: "row",
marginVertical: 5,
justifyContent: "space-between",
padding: 12
}}
onPress={onSubmit}
type="accent"
>
<Heading size={SIZE.sm} color={colors.light}>
Add {'"' + "#" + query + '"'}
</Heading>
<Icon name="plus" color={colors.light} size={SIZE.lg} />
</PressableButton>
) : null}
{!allTags || allTags.length === 0 ? (
<View
style={{
width: "100%",
height: 200,
justifyContent: "center",
alignItems: "center"
}}
>
<Heading size={50} color={colors.icon}>
#
</Heading>
<Paragraph textBreakStrategy="balanced" color={colors.icon}>
You do not have any tags.
</Paragraph>
</View>
) : null}
{tags.map((item) => (
<TagItem
key={item.title}
tag={item}
note={note}
setNote={setNote}
/>
))}
</ScrollView>
</View>
</SheetWrapper>
); );
}; };
ManageTagsSheet.present = (note) => {
presentSheet({
component: (ref) => {
return <ManageTagsSheet actionSheetRef={ref} note={note} />;
}
});
};
export default ManageTagsSheet; export default ManageTagsSheet;
const TagItem = ({ tag, note, setNote }) => { const TagItem = ({ tag, note, setNote }) => {

View File

@@ -17,11 +17,11 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { FlashList } from "@shopify/flash-list";
import { NotebookType, NoteType, TopicType } from "app/utils/types"; import { NotebookType, NoteType, TopicType } from "app/utils/types";
import React, { RefObject, useState } from "react"; import React, { RefObject, useState } from "react";
import { Platform, useWindowDimensions, View } from "react-native"; import { Platform, useWindowDimensions, View } from "react-native";
import ActionSheet from "react-native-actions-sheet"; import { ActionSheetRef } from "react-native-actions-sheet";
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
import { db } from "../../../common/database"; import { db } from "../../../common/database";
import { import {
eSendEvent, eSendEvent,
@@ -58,7 +58,7 @@ export const MoveNotes = ({
}: { }: {
notebook: NotebookType; notebook: NotebookType;
selectedTopic?: TopicType; selectedTopic?: TopicType;
fwdRef: RefObject<ActionSheet>; fwdRef: RefObject<ActionSheetRef>;
}) => { }) => {
const colors = useThemeStore((state) => state.colors); const colors = useThemeStore((state) => state.colors);
const [currentNotebook, setCurrentNotebook] = useState(notebook); const [currentNotebook, setCurrentNotebook] = useState(notebook);
@@ -277,10 +277,6 @@ export const MoveNotes = ({
)} )}
<FlashList <FlashList
nestedScrollEnabled
onMomentumScrollEnd={() => {
fwdRef.current?.handleChildScrollEnd();
}}
ListEmptyComponent={ ListEmptyComponent={
<View <View
style={{ style={{
@@ -345,7 +341,7 @@ export const MoveNotes = ({
MoveNotes.present = (notebook: NotebookType, topic: TopicType) => { MoveNotes.present = (notebook: NotebookType, topic: TopicType) => {
presentSheet({ presentSheet({
component: (ref: RefObject<ActionSheet>) => ( component: (ref: RefObject<ActionSheetRef>) => (
<MoveNotes fwdRef={ref} notebook={notebook} selectedTopic={topic} /> <MoveNotes fwdRef={ref} notebook={notebook} selectedTopic={topic} />
) )
}); });

View File

@@ -18,22 +18,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import Clipboard from "@react-native-clipboard/clipboard"; import Clipboard from "@react-native-clipboard/clipboard";
import React, { useEffect, useRef, useState } from "react"; import React, { useRef, useState } from "react";
import { ActivityIndicator, TouchableOpacity, View } from "react-native"; import { ActivityIndicator, TouchableOpacity, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialCommunityIcons"; import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database"; import { db } from "../../../common/database";
import { import { presentSheet, ToastEvent } from "../../../services/event-manager";
eSubscribeEvent,
eUnSubscribeEvent,
ToastEvent
} from "../../../services/event-manager";
import Navigation from "../../../services/navigation"; import Navigation from "../../../services/navigation";
import { useAttachmentStore } from "../../../stores/use-attachment-store"; import { useAttachmentStore } from "../../../stores/use-attachment-store";
import { useThemeStore } from "../../../stores/use-theme-store"; import { useThemeStore } from "../../../stores/use-theme-store";
import {
eClosePublishNoteDialog,
eOpenPublishNoteDialog
} from "../../../utils/events";
import { openLinkInBrowser } from "../../../utils/functions"; import { openLinkInBrowser } from "../../../utils/functions";
import { SIZE } from "../../../utils/size"; import { SIZE } from "../../../utils/size";
import DialogHeader from "../../dialog/dialog-header"; import DialogHeader from "../../dialog/dialog-header";
@@ -41,59 +33,27 @@ import { Button } from "../../ui/button";
import { IconButton } from "../../ui/icon-button"; import { IconButton } from "../../ui/icon-button";
import Input from "../../ui/input"; import Input from "../../ui/input";
import Seperator from "../../ui/seperator"; import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet";
import Heading from "../../ui/typography/heading"; import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph"; import Paragraph from "../../ui/typography/paragraph";
let passwordValue = null; const PublishNoteSheet = ({ note: item, update }) => {
const PublishNoteSheet = () => {
const colors = useThemeStore((state) => state.colors); const colors = useThemeStore((state) => state.colors);
const [visible, setVisible] = useState(false);
const actionSheetRef = useRef(); const actionSheetRef = useRef();
const loading = useAttachmentStore((state) => state.loading); const loading = useAttachmentStore((state) => state.loading);
const [selfDestruct, setSelfDestruct] = useState(false); const [selfDestruct, setSelfDestruct] = useState(false);
const [isLocked, setIsLocked] = useState(false); const [isLocked, setIsLocked] = useState(false);
const [note, setNote] = useState(null); const [note, setNote] = useState(item);
const [publishing, setPublishing] = useState(false); const [publishing, setPublishing] = useState(false);
const publishUrl = const publishUrl =
note && note &&
`https://monograph.notesnook.com/${db?.monographs.monograph(note?.id)}`; `https://monograph.notesnook.com/${db?.monographs.monograph(note?.id)}`;
const isPublished = note && db?.monographs.isPublished(note?.id); const isPublished = note && db?.monographs.isPublished(note?.id);
const pwdInput = useRef(); const pwdInput = useRef();
const passwordValue = useRef();
useEffect(() => {
eSubscribeEvent(eOpenPublishNoteDialog, open);
eSubscribeEvent(eClosePublishNoteDialog, close);
return () => {
eUnSubscribeEvent(eOpenPublishNoteDialog, open);
eUnSubscribeEvent(eClosePublishNoteDialog, close);
};
}, []);
const open = (item) => {
if (!item) return;
setNote(item);
setPublishing(false);
setSelfDestruct(false);
setIsLocked(false);
setVisible(true);
passwordValue = null;
};
useEffect(() => {
if (visible) {
actionSheetRef.current?.show();
}
}, [visible]);
const close = () => {
passwordValue = null;
actionSheetRef.current?.hide();
};
const publishNote = async () => { const publishNote = async () => {
if (publishing) return; if (publishing) return;
setPublishing(true); setPublishLoading(true);
try { try {
if (note?.id) { if (note?.id) {
@@ -110,6 +70,7 @@ const PublishNoteSheet = () => {
"TaggedNotes", "TaggedNotes",
"TopicNotes" "TopicNotes"
); );
setPublishLoading(false);
} }
} catch (e) { } catch (e) {
ToastEvent.show({ ToastEvent.show({
@@ -120,12 +81,19 @@ const PublishNoteSheet = () => {
}); });
} }
setPublishing(false); setPublishLoading(false);
};
const setPublishLoading = (value) => {
setPublishing(value);
update({
progress: value
});
}; };
const deletePublishedNote = async () => { const deletePublishedNote = async () => {
if (publishing) return; if (publishing) return;
setPublishing(true); setPublishLoading(true);
try { try {
if (note?.id) { if (note?.id) {
await db.monographs.unpublish(note.id); await db.monographs.unpublish(note.id);
@@ -137,6 +105,7 @@ const PublishNoteSheet = () => {
"TaggedNotes", "TaggedNotes",
"TopicNotes" "TopicNotes"
); );
setPublishLoading(false);
} }
} catch (e) { } catch (e) {
ToastEvent.show({ ToastEvent.show({
@@ -147,267 +116,268 @@ const PublishNoteSheet = () => {
}); });
} }
actionSheetRef.current?.hide(); actionSheetRef.current?.hide();
setPublishing(false); setPublishLoading(false);
}; };
return !visible ? null : ( return (
<SheetWrapper <View
centered={false} style={{
fwdRef={actionSheetRef} width: "100%",
closeOnTouchBackdrop={!publishing} alignSelf: "center",
gestureEnabled={!publishing} paddingHorizontal: 12
onClose={async () => {
passwordValue = null;
setVisible(false);
}} }}
> >
<View <DialogHeader
style={{ title={note?.title}
width: "100%", paragraph={`Anyone with the link${
alignSelf: "center", isLocked ? " and password" : ""
paddingHorizontal: 12 } of the published note can view it.`}
}} />
>
<DialogHeader
title={note?.title}
paragraph={`Anyone with the link${
isLocked ? " and password" : ""
} of the published note can view it.`}
/>
{publishing ? ( {publishing ? (
<View <View
style={{
justifyContent: "center",
alignContent: "center",
height: 150,
width: "100%"
}}
>
<ActivityIndicator size={25} color={colors.accent} />
<Paragraph
style={{ style={{
justifyContent: "center", textAlign: "center"
alignContent: "center",
height: 150,
width: "100%"
}} }}
> >
<ActivityIndicator size={25} color={colors.accent} /> Please wait...
<Paragraph {loading && loading.current && loading.total
? `\nDownloading attachments (${
loading?.current / loading?.total
})`
: ""}
</Paragraph>
</View>
) : (
<>
{isPublished && (
<View
style={{ style={{
textAlign: "center" flexDirection: "row",
alignItems: "center",
marginTop: 15,
backgroundColor: colors.nav,
padding: 12,
borderRadius: 5
}} }}
> >
Please wait...
{loading && loading.current && loading.total
? `\nDownloading attachments (${
loading?.current / loading?.total
})`
: ""}
</Paragraph>
</View>
) : (
<>
{isPublished && (
<View <View
style={{ style={{
flexDirection: "row", width: "100%",
alignItems: "center", flexShrink: 1
marginTop: 15,
backgroundColor: colors.nav,
padding: 12,
borderRadius: 5
}} }}
> >
<View <Heading size={SIZE.sm}>Published at:</Heading>
<Paragraph size={SIZE.xs} numberOfLines={1}>
{publishUrl}
</Paragraph>
<Paragraph
onPress={async () => {
try {
await openLinkInBrowser(publishUrl, colors.accent);
} catch (e) {
console.error(e);
}
}}
size={SIZE.xs}
style={{ style={{
width: "100%", marginTop: 5,
flexShrink: 1 color: colors.pri
}} }}
> >
<Heading size={SIZE.sm}>Published at:</Heading> <Icon color={colors.accent} name="open-in-new" /> Open in
<Paragraph size={SIZE.xs} numberOfLines={1}> browser
{publishUrl} </Paragraph>
</Paragraph>
<Paragraph
onPress={async () => {
try {
await openLinkInBrowser(publishUrl, colors.accent);
} catch (e) {
console.error(e);
}
}}
size={SIZE.xs}
style={{
marginTop: 5,
color: colors.pri
}}
>
<Icon color={colors.accent} name="open-in-new" /> Open in
browser
</Paragraph>
</View>
<IconButton
onPress={() => {
Clipboard.setString(publishUrl);
ToastEvent.show({
heading: "Note publish url copied",
type: "success",
context: "local"
});
}}
color={colors.accent}
size={SIZE.lg}
name="content-copy"
/>
</View> </View>
)}
<Seperator />
<TouchableOpacity <IconButton
onPress={() => {
Clipboard.setString(publishUrl);
ToastEvent.show({
heading: "Note publish url copied",
type: "success",
context: "local"
});
}}
color={colors.accent}
size={SIZE.lg}
name="content-copy"
/>
</View>
)}
<Seperator />
<TouchableOpacity
onPress={() => {
if (publishing) return;
setIsLocked(!isLocked);
}}
activeOpacity={0.9}
style={{
flexDirection: "row",
alignItems: "center",
marginBottom: 10
}}
>
<IconButton
onPress={() => { onPress={() => {
if (publishing) return; if (publishing) return;
setIsLocked(!isLocked); setIsLocked(!isLocked);
}} }}
activeOpacity={0.9} color={isLocked ? colors.accent : colors.icon}
style={{ size={SIZE.lg}
flexDirection: "row", name={
alignItems: "center", isLocked
marginBottom: 10 ? "check-circle-outline"
}} : "checkbox-blank-circle-outline"
> }
<IconButton />
onPress={() => {
if (publishing) return;
setIsLocked(!isLocked);
}}
color={isLocked ? colors.accent : colors.icon}
size={SIZE.lg}
name={
isLocked
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
/>
<View
style={{
width: "100%",
flexShrink: 1
}}
>
<Heading size={SIZE.md}>Password protection</Heading>
<Paragraph>
Published note can only be viewed by someone with the
password.
</Paragraph>
</View>
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
setSelfDestruct(!selfDestruct);
}}
activeOpacity={0.9}
style={{
flexDirection: "row",
alignItems: "center"
}}
>
<IconButton
onPress={() => {
setSelfDestruct(!selfDestruct);
}}
color={selfDestruct ? colors.accent : colors.icon}
size={SIZE.lg}
name={
selfDestruct
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
/>
<View
style={{
width: "100%",
flexShrink: 1
}}
>
<Heading size={SIZE.md}>Self destruct</Heading>
<Paragraph>
Published note link will be automatically deleted once it is
viewed by someone.
</Paragraph>
</View>
</TouchableOpacity>
<View <View
style={{ style={{
width: "100%", width: "100%",
alignSelf: "center", flexShrink: 1
marginTop: 10
}} }}
> >
{isLocked ? ( <Heading size={SIZE.md}>Password protection</Heading>
<> <Paragraph>
<Input Published note can only be viewed by someone with the password.
fwdRef={pwdInput} </Paragraph>
onChangeText={(value) => (passwordValue = value)}
blurOnSubmit
secureTextEntry
defaultValue={passwordValue}
placeholder="Enter Password"
/>
<Seperator half />
</>
) : null}
<Button
onPress={publishNote}
fontSize={SIZE.md}
width="100%"
style={{
marginTop: 10
}}
height={50}
type="accent"
title={isPublished ? "Update published note" : "Publish note"}
/>
{isPublished && (
<>
<Seperator half />
<Button
onPress={deletePublishedNote}
fontSize={SIZE.md}
width="100%"
height={50}
type="error"
title="Unpublish note"
/>
</>
)}
</View> </View>
</> </TouchableOpacity>
)}
<Paragraph <TouchableOpacity
color={colors.icon} onPress={() => {
size={SIZE.xs} setSelfDestruct(!selfDestruct);
style={{ }}
textAlign: "center", activeOpacity={0.9}
marginTop: 5, style={{
textDecorationLine: "underline" flexDirection: "row",
}} alignItems: "center"
onPress={async () => { }}
try { >
await openLinkInBrowser( <IconButton
"https://docs.notesnook.com/monographs/", onPress={() => {
colors.accent setSelfDestruct(!selfDestruct);
); }}
} catch (e) { color={selfDestruct ? colors.accent : colors.icon}
console.error(e); size={SIZE.lg}
} name={
}} selfDestruct
> ? "check-circle-outline"
Learn more about Notesnook Monograph : "checkbox-blank-circle-outline"
</Paragraph> }
</View> />
</SheetWrapper>
<View
style={{
width: "100%",
flexShrink: 1
}}
>
<Heading size={SIZE.md}>Self destruct</Heading>
<Paragraph>
Published note link will be automatically deleted once it is
viewed by someone.
</Paragraph>
</View>
</TouchableOpacity>
<View
style={{
width: "100%",
alignSelf: "center",
marginTop: 10
}}
>
{isLocked ? (
<>
<Input
fwdRef={pwdInput}
onChangeText={(value) => (passwordValue.current = value)}
blurOnSubmit
secureTextEntry
defaultValue={passwordValue.current}
placeholder="Enter Password"
/>
<Seperator half />
</>
) : null}
<Button
onPress={publishNote}
fontSize={SIZE.md}
width="100%"
style={{
marginTop: 10
}}
height={50}
type="accent"
title={isPublished ? "Update published note" : "Publish note"}
/>
{isPublished && (
<>
<Seperator half />
<Button
onPress={deletePublishedNote}
fontSize={SIZE.md}
width="100%"
height={50}
type="error"
title="Unpublish note"
/>
</>
)}
</View>
</>
)}
<Paragraph
color={colors.icon}
size={SIZE.xs}
style={{
textAlign: "center",
marginTop: 5,
textDecorationLine: "underline"
}}
onPress={async () => {
try {
await openLinkInBrowser(
"https://docs.notesnook.com/monographs/",
colors.accent
);
} catch (e) {
console.error(e);
}
}}
>
Learn more about Notesnook Monograph
</Paragraph>
</View>
); );
}; };
PublishNoteSheet.present = (note) => {
presentSheet({
component: (ref, close, update) => (
<PublishNoteSheet
actionSheetRef={ref}
close={close}
update={update}
note={note}
/>
)
});
};
export default PublishNoteSheet; export default PublishNoteSheet;

View File

@@ -18,12 +18,13 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React, { RefObject, useEffect, useState } from "react"; import React, { RefObject, useEffect, useState } from "react";
import { View } from "react-native"; import { View } from "react-native";
import ActionSheet from "react-native-actions-sheet"; import { ActionSheetRef } from "react-native-actions-sheet";
import { FlashList } from "react-native-actions-sheet/dist/src/views/FlashList";
import Icon from "react-native-vector-icons/MaterialCommunityIcons"; import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database"; import { db } from "../../../common/database";
import { import {
PresentSheetOptions, presentSheet,
presentSheet PresentSheetOptions
} from "../../../services/event-manager"; } from "../../../services/event-manager";
import { Reminder } from "../../../services/notifications"; import { Reminder } from "../../../services/notifications";
import { useRelationStore } from "../../../stores/use-relation-store"; import { useRelationStore } from "../../../stores/use-relation-store";
@@ -37,7 +38,7 @@ import { PressableButtonProps } from "../../ui/pressable";
import Paragraph from "../../ui/typography/paragraph"; import Paragraph from "../../ui/typography/paragraph";
type RelationsListProps = { type RelationsListProps = {
actionSheetRef: RefObject<ActionSheet>; actionSheetRef: RefObject<ActionSheetRef>;
close?: () => void; close?: () => void;
update?: (options: PresentSheetOptions) => void; update?: (options: PresentSheetOptions) => void;
item: { id: string; type: string }; item: { id: string; type: string };
@@ -62,8 +63,6 @@ const IconsByType = {
export const RelationsList = ({ export const RelationsList = ({
actionSheetRef, actionSheetRef,
close,
update,
item, item,
referenceType, referenceType,
relationType, relationType,
@@ -75,7 +74,6 @@ export const RelationsList = ({
const [items, setItems] = useState<Reminder[]>([]); const [items, setItems] = useState<Reminder[]>([]);
const colors = useThemeStore((state) => state.colors); const colors = useThemeStore((state) => state.colors);
const hasNoRelations = !items || items.length === 0; const hasNoRelations = !items || items.length === 0;
useEffect(() => { useEffect(() => {
setItems( setItems(
db.relations?.[relationType]?.( db.relations?.[relationType]?.(
@@ -123,6 +121,7 @@ export const RelationsList = ({
) : ( ) : (
<List <List
listData={items} listData={items}
ScrollComponent={FlashList}
loading={false} loading={false}
type={referenceType} type={referenceType}
headerProps={null} headerProps={null}

View File

@@ -18,8 +18,8 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import dayjs from "dayjs"; import dayjs from "dayjs";
import React, { RefObject } from "react"; import React, { RefObject } from "react";
import { ScrollView, View } from "react-native"; import { View } from "react-native";
import ActionSheet from "react-native-actions-sheet"; import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
import Icon from "react-native-vector-icons/MaterialCommunityIcons"; import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { db } from "../../../common/database"; import { db } from "../../../common/database";
import { import {
@@ -36,7 +36,7 @@ import Heading from "../../ui/typography/heading";
import Paragraph from "../../ui/typography/paragraph"; import Paragraph from "../../ui/typography/paragraph";
type ReminderSheetProps = { type ReminderSheetProps = {
actionSheetRef: RefObject<ActionSheet>; actionSheetRef: RefObject<ActionSheetRef>;
close?: () => void; close?: () => void;
update?: (options: PresentSheetOptions) => void; update?: (options: PresentSheetOptions) => void;
reminder?: Reminder; reminder?: Reminder;

View File

@@ -17,14 +17,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React, { RefObject, useRef, useState } from "react"; import React, { RefObject, useRef, useState } from "react";
import { import { Platform, TextInput, View } from "react-native";
Platform, import { ActionSheetRef, ScrollView } from "react-native-actions-sheet";
ScrollView,
TextInput,
useWindowDimensions,
View
} from "react-native";
import ActionSheet from "react-native-actions-sheet";
import DateTimePickerModal from "react-native-modal-datetime-picker"; import DateTimePickerModal from "react-native-modal-datetime-picker";
import { import {
presentSheet, presentSheet,
@@ -44,13 +38,13 @@ import Notifications, { Reminder } from "../../../services/notifications";
import PremiumService from "../../../services/premium"; import PremiumService from "../../../services/premium";
import SettingsService from "../../../services/settings"; import SettingsService from "../../../services/settings";
import { useRelationStore } from "../../../stores/use-relation-store"; import { useRelationStore } from "../../../stores/use-relation-store";
import { ReminderTime } from "../../ui/reminder-time";
import Paragraph from "../../ui/typography/paragraph";
import { NoteType } from "../../../utils/types"; import { NoteType } from "../../../utils/types";
import { Dialog } from "../../dialog"; import { Dialog } from "../../dialog";
import { ReminderTime } from "../../ui/reminder-time";
import Paragraph from "../../ui/typography/paragraph";
type ReminderSheetProps = { type ReminderSheetProps = {
actionSheetRef: RefObject<ActionSheet>; actionSheetRef: RefObject<ActionSheetRef>;
close?: (ctx?: string) => void; close?: (ctx?: string) => void;
update?: (options: PresentSheetOptions) => void; update?: (options: PresentSheetOptions) => void;
reminder?: Reminder; reminder?: Reminder;
@@ -117,13 +111,15 @@ export default function ReminderSheet({
>(reminder?.priority || SettingsService.get().reminderNotificationMode); >(reminder?.priority || SettingsService.get().reminderNotificationMode);
const [isDatePickerVisible, setDatePickerVisibility] = useState(false); const [isDatePickerVisible, setDatePickerVisibility] = useState(false);
const [repeatFrequency, setRepeatFrequency] = useState(1); const [repeatFrequency, setRepeatFrequency] = useState(1);
const title = useRef<string | undefined>(reminder?.title);
const details = useRef<string | undefined>(reminder?.description);
const titleRef = useRef<TextInput>(null);
const { height } = useWindowDimensions();
const referencedItem = reference const referencedItem = reference
? (db.notes?.note(reference.id)?.data as NoteType) ? (db.notes?.note(reference.id)?.data as NoteType)
: null; : null;
const title = useRef<string | undefined>(
reminder?.title || referencedItem?.title
);
const details = useRef<string | undefined>(reminder?.description);
const titleRef = useRef<TextInput>(null);
const timer = useRef<NodeJS.Timeout>();
const showDatePicker = () => { const showDatePicker = () => {
setDatePickerVisibility(true); setDatePickerVisibility(true);
@@ -134,9 +130,10 @@ export default function ReminderSheet({
}; };
const handleConfirm = (date: Date) => { const handleConfirm = (date: Date) => {
hideDatePicker(); timer.current = setTimeout(() => {
setDate(date); hideDatePicker();
console.log(date); setDate(date);
}, 50);
}; };
function nth(n: number) { function nth(n: number) {
return ( return (
@@ -233,13 +230,8 @@ export default function ReminderSheet({
paddingHorizontal: 12 paddingHorizontal: 12
}} }}
> >
<Dialog context="local"/> <Dialog context="local" />
<ScrollView <ScrollView keyboardShouldPersistTaps="always">
onScrollEndDrag={() => actionSheetRef.current?.handleChildScrollEnd()}
style={{
maxHeight: height * 0.85
}}
>
<Input <Input
fwdRef={titleRef} fwdRef={titleRef}
defaultValue={reminder?.title || referencedItem?.title} defaultValue={reminder?.title || referencedItem?.title}
@@ -249,7 +241,9 @@ export default function ReminderSheet({
/> />
<Input <Input
defaultValue={reminder ? reminder?.description : referencedItem?.headline} defaultValue={
reminder ? reminder?.description : referencedItem?.headline
}
placeholder="Add a quick note" placeholder="Add a quick note"
onChangeText={(text) => (details.current = text)} onChangeText={(text) => (details.current = text)}
containerStyle={{ containerStyle={{
@@ -440,6 +434,12 @@ export default function ReminderSheet({
<DatePicker <DatePicker
date={date} date={date}
minimumDate={
dayjs(date).subtract(3, "months").isBefore(dayjs())
? dayjs().toDate()
: dayjs(date).subtract(3, "months").toDate()
}
maximumDate={dayjs(date).add(3, "months").toDate()}
onDateChange={handleConfirm} onDateChange={handleConfirm}
textColor={colors.night ? "#ffffff" : "#000000"} textColor={colors.night ? "#ffffff" : "#000000"}
fadeToColor={colors.bg} fadeToColor={colors.bg}
@@ -561,16 +561,16 @@ export default function ReminderSheet({
alignSelf: "flex-start" alignSelf: "flex-start"
}} }}
/> />
<Button
style={{
width: "100%"
}}
title="Save"
type="accent"
fontSize={SIZE.md}
onPress={saveReminder}
/>
</ScrollView> </ScrollView>
<Button
style={{
width: "100%"
}}
title="Save"
type="accent"
fontSize={SIZE.md}
onPress={saveReminder}
/>
</View> </View>
); );
} }
@@ -582,6 +582,7 @@ ReminderSheet.present = (
) => { ) => {
presentSheet({ presentSheet({
context: isSheet ? "local" : undefined, context: isSheet ? "local" : undefined,
enableGesturesInScrollView: true,
component: (ref, close, update) => ( component: (ref, close, update) => (
<ReminderSheet <ReminderSheet
actionSheetRef={ref} actionSheetRef={ref}

View File

@@ -18,10 +18,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { EVENTS } from "@notesnook/core/common"; import { EVENTS } from "@notesnook/core/common";
import React, { createRef, useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useRef, useState } from "react";
import { ActivityIndicator, Platform, View } from "react-native"; import { ActivityIndicator, Platform, View } from "react-native";
import { FlatList } from "react-native-actions-sheet";
import DocumentPicker from "react-native-document-picker"; import DocumentPicker from "react-native-document-picker";
import { FlatList } from "react-native-gesture-handler";
import * as ScopedStorage from "react-native-scoped-storage"; import * as ScopedStorage from "react-native-scoped-storage";
import { db } from "../../../common/database"; import { db } from "../../../common/database";
import storage from "../../../common/database/storage"; import storage from "../../../common/database/storage";
@@ -35,7 +35,7 @@ import { initialize } from "../../../stores";
import { useThemeStore } from "../../../stores/use-theme-store"; import { useThemeStore } from "../../../stores/use-theme-store";
import { eCloseRestoreDialog, eOpenRestoreDialog } from "../../../utils/events"; import { eCloseRestoreDialog, eOpenRestoreDialog } from "../../../utils/events";
import { SIZE } from "../../../utils/size"; import { SIZE } from "../../../utils/size";
import { sleep, timeConverter } from "../../../utils/time"; import { timeConverter } from "../../../utils/time";
import { Dialog } from "../../dialog"; import { Dialog } from "../../dialog";
import DialogHeader from "../../dialog/dialog-header"; import DialogHeader from "../../dialog/dialog-header";
import { presentDialog } from "../../dialog/functions"; import { presentDialog } from "../../dialog/functions";
@@ -44,12 +44,18 @@ import { Button } from "../../ui/button";
import Seperator from "../../ui/seperator"; import Seperator from "../../ui/seperator";
import SheetWrapper from "../../ui/sheet"; import SheetWrapper from "../../ui/sheet";
import Paragraph from "../../ui/typography/paragraph"; import Paragraph from "../../ui/typography/paragraph";
const actionSheetRef = createRef();
let RNFetchBlob; let RNFetchBlob;
const RestoreDataSheet = () => { const RestoreDataSheet = () => {
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [restoring, setRestoring] = useState(false); const [restoring, setRestoring] = useState(false);
const sheet = useRef();
useEffect(() => { useEffect(() => {
const open = async () => {
setVisible(true);
setTimeout(() => {
sheet.current?.show();
}, 1);
};
eSubscribeEvent(eOpenRestoreDialog, open); eSubscribeEvent(eOpenRestoreDialog, open);
eSubscribeEvent(eCloseRestoreDialog, close); eSubscribeEvent(eCloseRestoreDialog, close);
return () => { return () => {
@@ -58,21 +64,15 @@ const RestoreDataSheet = () => {
}; };
}, [close]); }, [close]);
const open = async () => {
setVisible(true);
await sleep(30);
actionSheetRef.current?.setModalVisible(true);
};
const close = useCallback(() => { const close = useCallback(() => {
if (restoring) { if (restoring) {
showIsWorking(); showIsWorking();
return; return;
} }
actionSheetRef.current?.setModalVisible(false); sheet.current?.hide();
setTimeout(() => { setTimeout(() => {
setVisible(false); setVisible(false);
}, 300); }, 150);
}, [restoring]); }, [restoring]);
const showIsWorking = () => { const showIsWorking = () => {
@@ -86,15 +86,19 @@ const RestoreDataSheet = () => {
return !visible ? null : ( return !visible ? null : (
<SheetWrapper <SheetWrapper
fwdRef={actionSheetRef} fwdRef={sheet}
gestureEnabled={!restoring} gestureEnabled={!restoring}
closeOnTouchBackdrop={!restoring} closeOnTouchBackdrop={!restoring}
onClose={close} onClose={() => {
setVisible(false);
close();
}}
> >
<RestoreDataComponent <RestoreDataComponent
close={close} close={close}
restoring={restoring} restoring={restoring}
setRestoring={setRestoring} setRestoring={setRestoring}
actionSheetRef={sheet}
/> />
<Toast context="local" /> <Toast context="local" />
</SheetWrapper> </SheetWrapper>
@@ -109,7 +113,6 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [backupDirectoryAndroid, setBackupDirectoryAndroid] = useState(false); const [backupDirectoryAndroid, setBackupDirectoryAndroid] = useState(false);
const [progress, setProgress] = useState(); const [progress, setProgress] = useState();
useEffect(() => { useEffect(() => {
const subscription = db.eventManager.subscribe( const subscription = db.eventManager.subscribe(
EVENTS.migrationProgress, EVENTS.migrationProgress,
@@ -123,7 +126,9 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
}, []); }, []);
useEffect(() => { useEffect(() => {
checkBackups(); setTimeout(() => {
checkBackups();
}, 300);
}, []); }, []);
const restore = async (item) => { const restore = async (item) => {
@@ -362,10 +367,6 @@ const RestoreDataComponent = ({ close, setRestoring, restoring }) => {
</View> </View>
<Seperator half /> <Seperator half />
<FlatList <FlatList
nestedScrollEnabled
onMomentumScrollEnd={() => {
actionSheetRef.current?.handleChildScrollEnd();
}}
ListEmptyComponent={ ListEmptyComponent={
!restoring ? ( !restoring ? (
loading ? ( loading ? (

View File

@@ -0,0 +1,423 @@
/*
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 qclone from "qclone";
import React, {
createContext,
useContext,
useEffect,
useRef,
useState
} from "react";
import { Animated, Dimensions, View, RefreshControl } from "react-native";
import ActionSheet, {
ActionSheetRef,
FlatList
} from "react-native-actions-sheet";
import { db } from "../../../common/database";
import { IconButton } from "../../../components/ui/icon-button";
import { PressableButton } from "../../../components/ui/pressable";
import Paragraph from "../../../components/ui/typography/paragraph";
import { TopicNotes } from "../../../screens/notes/topic-notes";
import {
eSendEvent,
eSubscribeEvent,
eUnSubscribeEvent
} from "../../../services/event-manager";
import useNavigationStore, {
NotebookScreenParams
} from "../../../stores/use-navigation-store";
import { useThemeStore } from "../../../stores/use-theme-store";
import { eOnNewTopicAdded, eOpenAddTopicDialog } from "../../../utils/events";
import { normalize, SIZE } from "../../../utils/size";
import { NotebookType, TopicType } from "../../../utils/types";
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
import { openEditor } from "../../../screens/notes/common";
import { getTotalNotes, history } from "../../../utils";
import { Properties } from "../../properties";
import { deleteItems } from "../../../utils/functions";
import { presentDialog } from "../../dialog/functions";
export const TopicsSheet = () => {
const currentScreen = useNavigationStore((state) => state.currentScreen);
const canShow =
currentScreen.name === "Notebook" || currentScreen.name === "TopicNotes";
const [notebook, setNotebook] = useState(
canShow
? db.notebooks?.notebook(
currentScreen?.notebookId || currentScreen?.id || ""
)?.data
: null
);
const [selection, setSelection] = useState<TopicType[]>([]);
const [enabled, setEnabled] = useState(false);
const colors = useThemeStore((state) => state.colors);
const ref = useRef<ActionSheetRef>(null);
const [topics, setTopics] = useState(notebook ? qclone(notebook.topics) : []);
const [animations] = useState({
translate: new Animated.Value(0),
display: new Animated.Value(-5000),
opacity: new Animated.Value(0)
});
const onRequestUpdate = React.useCallback(
(data?: NotebookScreenParams) => {
if (!canShow) return;
if (!data) data = { item: notebook } as NotebookScreenParams;
const _notebook = db.notebooks?.notebook(data.item?.id)
?.data as NotebookType;
if (_notebook) {
setNotebook(_notebook);
setTopics(qclone(_notebook.topics));
}
},
[notebook, canShow]
);
useEffect(() => {
eSubscribeEvent(eOnNewTopicAdded, onRequestUpdate);
return () => {
eUnSubscribeEvent(eOnNewTopicAdded, onRequestUpdate);
};
}, [onRequestUpdate]);
const PLACEHOLDER_DATA = {
heading: "Topics",
paragraph: "You have not added any topics yet.",
button: "Add first topic",
action: () => {
eSendEvent(eOpenAddTopicDialog, { notebookId: notebook.id });
},
loading: "Loading notebook topics"
};
const renderTopic = ({ item, index }: { item: TopicType; index: number }) => (
<TopicItem item={item} index={index} />
);
const selectionContext = {
selection: selection,
enabled,
setEnabled,
toggleSelection: (item: TopicType) => {
setSelection((state) => {
const selection = [...state];
const index = selection.findIndex(
(selected) => selected.id === item.id
);
if (index > -1) {
selection.splice(index, 1);
if (selection.length === 0) {
setEnabled(false);
}
return selection;
}
selection.push(item);
return selection;
});
}
};
useEffect(() => {
if (canShow) {
const isTopic = currentScreen.name === "TopicNotes";
const id = isTopic ? currentScreen?.notebookId : currentScreen?.id;
if (!ref.current?.isOpen()) {
animations.display.setValue(5000);
animations.opacity.setValue(0);
}
if (id) {
onRequestUpdate({
item: db.notebooks?.notebook(id).data
} as any);
}
ref.current?.show();
} else {
ref.current?.hide();
}
}, [
animations.display,
animations.opacity,
canShow,
currentScreen?.id,
currentScreen.name,
currentScreen?.notebookId,
onRequestUpdate
]);
return (
<ActionSheet
ref={ref}
isModal={false}
containerStyle={{
height: 530,
maxHeight: 800,
borderTopRightRadius: 15,
borderTopLeftRadius: 15,
backgroundColor: colors.bg,
borderWidth: 1,
borderColor: colors.border,
borderBottomWidth: 0
}}
closable={!canShow}
elevation={10}
indicatorStyle={{
width: 100,
backgroundColor: colors.nav
}}
keyboardHandlerEnabled={false}
snapPoints={[15, 60, 100]}
initialSnapIndex={0}
backgroundInteractionEnabled
enableGesturesInScrollView
onChange={(position, height) => {
animations.translate.setValue(position);
const h = Dimensions.get("window").height;
const minPos = h - height;
if (position - 100 < minPos || !canShow) {
animations.display.setValue(5000);
animations.opacity.setValue(0);
} else {
animations.display.setValue(0);
setTimeout(() => {
animations.opacity.setValue(1);
}, 300);
}
}}
gestureEnabled
ExtraOverlayComponent={
<Animated.View
style={{
top: animations.translate,
position: "absolute",
right: 12,
opacity: animations.opacity,
transform: [
{
translateY: animations.display
}
]
}}
>
<PressableButton
type="accent"
accentColor={"accent"}
accentText="light"
onPress={openEditor}
customStyle={{
borderRadius: 100,
bottom: 50
}}
>
<View
style={{
alignItems: "center",
justifyContent: "center",
height: normalize(60),
width: normalize(60)
}}
>
<Icon name="plus" color="white" size={SIZE.xxl} />
</View>
</PressableButton>
</Animated.View>
}
>
<View
style={{
height: 530,
width: "100%"
}}
>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
paddingHorizontal: 12,
alignItems: "center"
}}
>
<Paragraph size={SIZE.xs} color={colors.icon}>
TOPICS
</Paragraph>
<View
style={{
flexDirection: "row"
}}
>
{enabled ? (
<IconButton
customStyle={{
marginLeft: 10
}}
onPress={async () => {
//@ts-ignore
history.selectedItemsList = selection;
presentDialog({
title: `Delete ${
selection.length > 1 ? "topics" : "topics"
}`,
paragraph: `Are you sure you want to delete ${
selection.length > 1 ? "these topicss?" : "this topics?"
}`,
positiveText: "Delete",
negativeText: "Cancel",
positivePress: async () => {
await deleteItems();
history.selectedItemsList = [];
setEnabled(false);
setSelection([]);
},
positiveType: "errorShade"
});
return;
}}
color={colors.pri}
tooltipText="Move to trash"
tooltipPosition={1}
name="delete"
size={22}
/>
) : (
<IconButton
name="plus"
onPress={PLACEHOLDER_DATA.action}
color={colors.pri}
size={22}
customStyle={{
width: 40,
height: 40
}}
/>
)}
</View>
</View>
<SelectionContext.Provider value={selectionContext}>
<FlatList
data={topics}
style={{
width: "100%"
}}
refreshControl={
<RefreshControl
refreshing={false}
onRefresh={() => {
onRequestUpdate();
}}
/>
}
keyExtractor={(item) => item.id}
renderItem={renderTopic}
ListFooterComponent={<View style={{ height: 50 }} />}
/>
</SelectionContext.Provider>
</View>
</ActionSheet>
);
};
const SelectionContext = createContext<{
selection: TopicType[];
enabled: boolean;
setEnabled: (value: boolean) => void;
toggleSelection: (item: TopicType) => void;
}>({
selection: [],
enabled: false,
setEnabled: (value: boolean) => {},
toggleSelection: (item: TopicType) => {}
});
const useSelection = () => useContext(SelectionContext);
const TopicItem = ({ item }: { item: TopicType; index: number }) => {
const screen = useNavigationStore((state) => state.currentScreen);
const colors = useThemeStore((state) => state.colors);
const selection = useSelection();
const isSelected =
selection.selection.findIndex((selected) => selected.id === item.id) > -1;
const isFocused = screen.id === item.id;
const notesCount = getTotalNotes(item);
return (
<PressableButton
type={isSelected || isFocused ? "grayBg" : "transparent"}
onLongPress={() => {
if (selection.enabled) return;
selection.setEnabled(true);
selection.toggleSelection(item);
}}
onPress={() => {
if (selection.enabled) {
selection.toggleSelection(item);
return;
}
TopicNotes.navigate(item, true);
}}
customStyle={{
justifyContent: "space-between",
width: "100%",
alignItems: "center",
flexDirection: "row",
paddingHorizontal: 12,
borderRadius: 0
}}
>
<View
style={{
flexDirection: "row",
alignItems: "center"
}}
>
{selection.enabled ? (
<IconButton
size={SIZE.lg}
color={isSelected ? colors.accent : colors.icon}
name={
isSelected
? "check-circle-outline"
: "checkbox-blank-circle-outline"
}
/>
) : null}
<Paragraph size={SIZE.sm}>
{item.title}{" "}
{notesCount ? (
<Paragraph size={SIZE.xs} color={colors.icon}>
{notesCount}
</Paragraph>
) : null}
</Paragraph>
</View>
<IconButton
name="dots-horizontal"
customStyle={{
width: 40,
height: 40
}}
onPress={() => {
Properties.present(item);
}}
left={0}
right={0}
bottom={0}
top={0}
color={colors.pri}
size={SIZE.xl}
/>
</PressableButton>
);
};

View File

@@ -19,10 +19,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Linking, View } from "react-native"; import { Linking, View } from "react-native";
import { ScrollView } from "react-native-actions-sheet";
import { checkVersion } from "react-native-check-version"; import { checkVersion } from "react-native-check-version";
import Config from "react-native-config"; import Config from "react-native-config";
import deviceInfoModule from "react-native-device-info"; import deviceInfoModule from "react-native-device-info";
import { ScrollView } from "react-native-gesture-handler";
import { useThemeStore } from "../../../stores/use-theme-store"; import { useThemeStore } from "../../../stores/use-theme-store";
import { STORE_LINK } from "../../../utils/constants"; import { STORE_LINK } from "../../../utils/constants";
import { SIZE } from "../../../utils/size"; import { SIZE } from "../../../utils/size";
@@ -148,9 +148,6 @@ export const Update = ({ version: appVersion, fwdRef }) => {
<Seperator /> <Seperator />
<ScrollView <ScrollView
nestedScrollEnabled={true} nestedScrollEnabled={true}
onMomentumScrollEnd={() => {
fwdRef?.current?.handleChildScrollEnd();
}}
style={{ style={{
width: "100%" width: "100%"
}} }}

View File

@@ -37,7 +37,8 @@ const SheetWrapper = ({
onHasReachedTop, onHasReachedTop,
keyboardMode, keyboardMode,
overlay, overlay,
overlayOpacity = 0.3 overlayOpacity = 0.3,
enableGesturesInScrollView = false
}) => { }) => {
const colors = useThemeStore((state) => state.colors); const colors = useThemeStore((state) => state.colors);
const deviceMode = useSettingStore((state) => state.deviceMode); const deviceMode = useSettingStore((state) => state.deviceMode);
@@ -59,8 +60,8 @@ const SheetWrapper = ({
zIndex: 10, zIndex: 10,
paddingTop: 5, paddingTop: 5,
paddingBottom: 0, paddingBottom: 0,
borderTopRightRadius: 20, borderTopRightRadius: 15,
borderTopLeftRadius: 20, borderTopLeftRadius: 15,
alignSelf: "center", alignSelf: "center",
borderBottomRightRadius: 0, borderBottomRightRadius: 0,
borderBottomLeftRadius: 0 borderBottomLeftRadius: 0
@@ -84,7 +85,8 @@ const SheetWrapper = ({
backdrop: "sheet-backdrop" backdrop: "sheet-backdrop"
}} }}
indicatorStyle={{ indicatorStyle={{
width: 100 width: 100,
backgroundColor: colors.nav
}} }}
drawUnderStatusBar={false} drawUnderStatusBar={false}
containerStyle={style} containerStyle={style}
@@ -98,8 +100,9 @@ const SheetWrapper = ({
indicatorColor={colors.nav} indicatorColor={colors.nav}
onOpen={_onOpen} onOpen={_onOpen}
keyboardDismissMode="none" keyboardDismissMode="none"
enableGesturesInScrollView={enableGesturesInScrollView}
defaultOverlayOpacity={overlayOpacity} defaultOverlayOpacity={overlayOpacity}
overlayColor={pitchBlack ? "#585858" : "#000000"} overlayColor={pitchBlack ? "#585858" : "#2b2b2b"}
keyboardShouldPersistTaps="always" keyboardShouldPersistTaps="always"
ExtraOverlayComponent={ ExtraOverlayComponent={
<> <>

View File

@@ -22,10 +22,15 @@ import React, { useCallback, useEffect, useState } from "react";
import { Platform } from "react-native"; import { Platform } from "react-native";
import Share from "react-native-share"; import Share from "react-native-share";
import { db } from "../common/database"; import { db } from "../common/database";
import { AttachmentDialog } from "../components/attachments";
import { presentDialog } from "../components/dialog/functions"; import { presentDialog } from "../components/dialog/functions";
import NoteHistory from "../components/note-history"; import NoteHistory from "../components/note-history";
import { AddNotebookSheet } from "../components/sheets/add-notebook";
import MoveNoteSheet from "../components/sheets/add-to";
import ExportNotesSheet from "../components/sheets/export-notes"; import ExportNotesSheet from "../components/sheets/export-notes";
import { MoveNotes } from "../components/sheets/move-notes/movenote"; import { MoveNotes } from "../components/sheets/move-notes/movenote";
import PublishNoteSheet from "../components/sheets/publish-note";
import { RelationsList } from "../components/sheets/relations-list/index";
import ReminderSheet from "../components/sheets/reminder"; import ReminderSheet from "../components/sheets/reminder";
import { import {
eSendEvent, eSendEvent,
@@ -40,24 +45,16 @@ import Notifications from "../services/notifications";
import { useEditorStore } from "../stores/use-editor-store"; import { useEditorStore } from "../stores/use-editor-store";
import { useMenuStore } from "../stores/use-menu-store"; import { useMenuStore } from "../stores/use-menu-store";
import useNavigationStore from "../stores/use-navigation-store"; import useNavigationStore from "../stores/use-navigation-store";
import { useRelationStore } from "../stores/use-relation-store";
import { useSelectionStore } from "../stores/use-selection-store"; import { useSelectionStore } from "../stores/use-selection-store";
import { useTagStore } from "../stores/use-tag-store"; import { useTagStore } from "../stores/use-tag-store";
import { useThemeStore } from "../stores/use-theme-store"; import { useThemeStore } from "../stores/use-theme-store";
import { useUserStore } from "../stores/use-user-store"; import { useUserStore } from "../stores/use-user-store";
import { toTXT } from "../utils"; import { toTXT } from "../utils";
import { toggleDarkMode } from "../utils/color-scheme/utils"; import { toggleDarkMode } from "../utils/color-scheme/utils";
import { import { eOpenAddTopicDialog, eOpenLoginDialog } from "../utils/events";
eOpenAddNotebookDialog,
eOpenAddTopicDialog,
eOpenAttachmentsDialog,
eOpenLoginDialog,
eOpenMoveNoteDialog,
eOpenPublishNoteDialog
} from "../utils/events";
import { deleteItems } from "../utils/functions"; import { deleteItems } from "../utils/functions";
import { sleep } from "../utils/time"; import { sleep } from "../utils/time";
import { RelationsList } from "../components/sheets/relations-list/index";
import { useRelationStore } from "../stores/use-relation-store";
export const useActions = ({ close = () => null, item }) => { export const useActions = ({ close = () => null, item }) => {
const colors = useThemeStore((state) => state.colors); const colors = useThemeStore((state) => state.colors);
@@ -129,12 +126,9 @@ export const useActions = ({ close = () => null, item }) => {
} }
function addTo() { function addTo() {
close();
clearSelection(true); clearSelection(true);
setSelectedItem(item); setSelectedItem(item);
setTimeout(() => { MoveNoteSheet.present(item);
eSendEvent(eOpenMoveNoteDialog, item);
}, 300);
} }
async function addToFavorites() { async function addToFavorites() {
@@ -270,9 +264,7 @@ export const useActions = ({ close = () => null, item }) => {
}); });
return; return;
} }
close(); PublishNoteSheet.present(item);
await sleep(300);
eSendEvent(eOpenPublishNoteDialog, item);
} }
const checkNoteSynced = () => { const checkNoteSynced = () => {
@@ -538,22 +530,16 @@ export const useActions = ({ close = () => null, item }) => {
} }
async function openHistory() { async function openHistory() {
close();
await sleep(300);
presentSheet({ presentSheet({
component: (ref) => <NoteHistory fwdRef={ref} note={item} /> component: (ref) => <NoteHistory fwdRef={ref} note={item} />
}); });
} }
async function showAttachments() { async function showAttachments() {
close(); AttachmentDialog.present();
await sleep(300);
eSendEvent(eOpenAttachmentsDialog, item);
} }
async function exportNote() { async function exportNote() {
close();
await sleep(300);
ExportNotesSheet.present([item]); ExportNotesSheet.present([item]);
} }
@@ -647,8 +633,6 @@ export const useActions = ({ close = () => null, item }) => {
title: "Add notes", title: "Add notes",
icon: "plus", icon: "plus",
func: async () => { func: async () => {
close();
await sleep(500);
MoveNotes.present(db.notebooks.notebook(item.notebookId).data, item); MoveNotes.present(db.notebooks.notebook(item.notebookId).data, item);
} }
}, },
@@ -689,9 +673,7 @@ export const useActions = ({ close = () => null, item }) => {
title: "Edit notebook", title: "Edit notebook",
icon: "square-edit-outline", icon: "square-edit-outline",
func: async () => { func: async () => {
close(); AddNotebookSheet.present(item);
await sleep(300);
eSendEvent(eOpenAddNotebookDialog, item);
} }
}, },
{ {
@@ -777,8 +759,6 @@ export const useActions = ({ close = () => null, item }) => {
title: "Edit reminder", title: "Edit reminder",
icon: "pencil", icon: "pencil",
func: async () => { func: async () => {
close();
await sleep(300);
ReminderSheet.present(item); ReminderSheet.present(item);
}, },
close: false close: false
@@ -789,7 +769,6 @@ export const useActions = ({ close = () => null, item }) => {
title: "Reminders", title: "Reminders",
icon: "clock-outline", icon: "clock-outline",
func: async () => { func: async () => {
close();
RelationsList.present({ RelationsList.present({
reference: item, reference: item,
referenceType: "reminder", referenceType: "reminder",

View File

@@ -24,6 +24,7 @@ import { SafeAreaView } from "react-native";
import Container from "../components/container"; import Container from "../components/container";
import DelayLayout from "../components/delay-layout"; import DelayLayout from "../components/delay-layout";
import Intro from "../components/intro"; import Intro from "../components/intro";
import { TopicsSheet } from "../components/sheets/topic-sheet";
import useGlobalSafeAreaInsets from "../hooks/use-global-safe-area-insets"; import useGlobalSafeAreaInsets from "../hooks/use-global-safe-area-insets";
import { hideAllTooltips } from "../hooks/use-tooltip"; import { hideAllTooltips } from "../hooks/use-tooltip";
import Favorites from "../screens/favorites"; import Favorites from "../screens/favorites";
@@ -198,6 +199,7 @@ const _NavigationStack = () => {
<NavigationContainer onStateChange={onStateChange} ref={rootNavigatorRef}> <NavigationContainer onStateChange={onStateChange} ref={rootNavigatorRef}>
<Tabs /> <Tabs />
</NavigationContainer> </NavigationContainer>
<TopicsSheet />
</Container> </Container>
); );
}; };

View File

@@ -4,6 +4,8 @@
"main": "./App.js", "main": "./App.js",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"dependencies": { "dependencies": {
"react": "18.0.0",
"react-native": "0.69.7",
"@flyerhq/react-native-link-preview": "^1.6.0", "@flyerhq/react-native-link-preview": "^1.6.0",
"@mdi/js": "^6.7.96", "@mdi/js": "^6.7.96",
"absolutify": "^0.1.0", "absolutify": "^0.1.0",
@@ -13,7 +15,7 @@
"html-to-text": "8.1.0", "html-to-text": "8.1.0",
"phone": "^3.1.14", "phone": "^3.1.14",
"qclone": "^1.2.0", "qclone": "^1.2.0",
"react-native-actions-sheet": "^0.7.2", "react-native-actions-sheet": "^0.9.0-alpha.6",
"react-native-check-version": "https://github.com/flexible-agency/react-native-check-version", "react-native-check-version": "https://github.com/flexible-agency/react-native-check-version",
"react-native-drax": "^0.10.2", "react-native-drax": "^0.10.2",
"react-native-image-zoom-viewer": "^3.0.1", "react-native-image-zoom-viewer": "^3.0.1",
@@ -28,7 +30,6 @@
"zustand": "^3.6.0", "zustand": "^3.6.0",
"fflate": "^0.7.3", "fflate": "^0.7.3",
"timeago.js": "4.0.2" "timeago.js": "4.0.2"
}, },
"sideEffects": false "sideEffects": false
} }

View File

@@ -78,7 +78,7 @@ const EditorOverlay = ({ editorId = "", editor }) => {
setTimeout(() => { setTimeout(() => {
translateValue.value = 6000; translateValue.value = 6000;
}, 500); }, 500);
}, 100); }, 0);
} }
}, },
[opacity, translateValue] [opacity, translateValue]

View File

@@ -29,7 +29,7 @@ import {
} from "react-native"; } from "react-native";
import { WebViewMessageEvent } from "react-native-webview"; import { WebViewMessageEvent } from "react-native-webview";
import { db } from "../../../common/database"; import { db } from "../../../common/database";
import ImagePreview from "../../../components/image-preview"; import ManageTagsSheet from "../../../components/sheets/manage-tags";
import { RelationsList } from "../../../components/sheets/relations-list"; import { RelationsList } from "../../../components/sheets/relations-list";
import ReminderSheet from "../../../components/sheets/reminder"; import ReminderSheet from "../../../components/sheets/reminder";
import useKeyboard from "../../../hooks/use-keyboard"; import useKeyboard from "../../../hooks/use-keyboard";
@@ -52,8 +52,7 @@ import {
eOpenFullscreenEditor, eOpenFullscreenEditor,
eOpenLoginDialog, eOpenLoginDialog,
eOpenPremiumDialog, eOpenPremiumDialog,
eOpenPublishNoteDialog, eOpenPublishNoteDialog
eOpenTagsDialog
} from "../../../utils/events"; } from "../../../utils/events";
import { openLinkInBrowser } from "../../../utils/functions"; import { openLinkInBrowser } from "../../../utils/functions";
import { tabBarRef } from "../../../utils/global-refs"; import { tabBarRef } from "../../../utils/global-refs";
@@ -327,7 +326,7 @@ export const useEditorEvents = (
}); });
return; return;
} }
eSendEvent(eOpenTagsDialog, editor.note.current); ManageTagsSheet.present(editor.note.current);
break; break;
case EventTypes.tag: case EventTypes.tag:
if (editorMessage.value) { if (editorMessage.value) {

View File

@@ -230,6 +230,7 @@ export const useEditor = (
id = await db.notes?.add(noteData); id = await db.notes?.add(noteData);
if (!note && id) { if (!note && id) {
currentNote.current = db.notes?.note(id).data as NoteType; currentNote.current = db.notes?.note(id).data as NoteType;
console.log("on Note Created", state.current?.onNoteCreated);
state.current?.onNoteCreated && state.current.onNoteCreated(id); state.current?.onNoteCreated && state.current.onNoteCreated(id);
if (!noteData.title) { if (!noteData.title) {
postMessage( postMessage(

View File

@@ -65,7 +65,6 @@ export const Home = ({ navigation, route }: NavigationProps<"Notes">) => {
onBlur: () => false, onBlur: () => false,
delay: SettingsService.get().homepage === route.name ? 1 : -1 delay: SettingsService.get().homepage === route.name ? 1 : -1
}); });
return ( return (
<DelayLayout wait={loading} delay={500}> <DelayLayout wait={loading} delay={500}>
<List <List

View File

@@ -16,12 +16,9 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import { groupArray } from "@notesnook/core/utils/grouping"; import { groupArray } from "@notesnook/core/utils/grouping";
import qclone from "qclone";
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { db } from "../../common/database"; import { db } from "../../common/database";
import { FloatingButton } from "../../components/container/floating-button";
import DelayLayout from "../../components/delay-layout"; import DelayLayout from "../../components/delay-layout";
import List from "../../components/list"; import List from "../../components/list";
import { NotebookHeader } from "../../components/list-items/headers/notebook-header"; import { NotebookHeader } from "../../components/list-items/headers/notebook-header";
@@ -36,28 +33,29 @@ import SearchService from "../../services/search";
import useNavigationStore, { import useNavigationStore, {
NotebookScreenParams NotebookScreenParams
} from "../../stores/use-navigation-store"; } from "../../stores/use-navigation-store";
import { import { eOnNewTopicAdded, eOpenAddNotebookDialog } from "../../utils/events";
eOnNewTopicAdded,
eOpenAddNotebookDialog,
eOpenAddTopicDialog
} from "../../utils/events";
import { NotebookType } from "../../utils/types"; import { NotebookType } from "../../utils/types";
import { openEditor, setOnFirstSave } from "../notes/common";
const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => { const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
const [topics, setTopics] = useState( const [notes, setNotes] = useState(
groupArray( groupArray(
qclone(route?.params.item?.topics) || [], db.relations?.from(route.params.item, "note") || [],
db.settings?.getGroupOptions("topics") db.settings?.getGroupOptions("notes")
) )
); );
const params = useRef<NotebookScreenParams>(route?.params); const params = useRef<NotebookScreenParams>(route?.params);
useNavigationFocus(navigation, { useNavigationFocus(navigation, {
onFocus: () => { onFocus: () => {
Navigation.routeNeedsUpdate(route.name, onRequestUpdate); Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
syncWithNavigation(); syncWithNavigation();
useNavigationStore.getState().setButtonAction(onPressFloatingButton); useNavigationStore.getState().setButtonAction(openEditor);
return false; return false;
}, },
onBlur: () => false onBlur: () => {
setOnFirstSave(null);
return false;
}
}); });
const syncWithNavigation = React.useCallback(() => { const syncWithNavigation = React.useCallback(() => {
@@ -70,6 +68,10 @@ const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
}, },
params.current?.canGoBack params.current?.canGoBack
); );
setOnFirstSave({
type: "notebook",
id: params.current.item.id
});
SearchService.prepareSearch = prepareSearch; SearchService.prepareSearch = prepareSearch;
}, [route.name]); }, [route.name]);
@@ -82,11 +84,9 @@ const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
?.data as NotebookType; ?.data as NotebookType;
if (notebook) { if (notebook) {
params.current.item = notebook; params.current.item = notebook;
setTopics( const notes = db.relations?.from(notebook, "note");
groupArray( setNotes(
qclone(notebook.topics), groupArray(notes || [], db.settings?.getGroupOptions("notes"))
db.settings?.getGroupOptions("topics")
)
); );
syncWithNavigation(); syncWithNavigation();
} }
@@ -102,60 +102,61 @@ const Notebook = ({ route, navigation }: NavigationProps<"Notebook">) => {
return () => { return () => {
eUnSubscribeEvent(eOnNewTopicAdded, onRequestUpdate); eUnSubscribeEvent(eOnNewTopicAdded, onRequestUpdate);
}; };
}, [onRequestUpdate, topics]); }, [onRequestUpdate]);
useEffect(() => {
return () => {
setOnFirstSave(null);
};
}, []);
const prepareSearch = () => { const prepareSearch = () => {
SearchService.update({ SearchService.update({
placeholder: `Search in "${params.current.title}"`, placeholder: `Search in "${params.current.title}"`,
type: "topics", type: "notes",
title: params.current.title, title: params.current.title,
get: () => { get: () => {
const notebook = db.notebooks?.notebook(params?.current?.item?.id) const notebook = db.notebooks?.notebook(params?.current?.item?.id)
?.data as NotebookType; ?.data as NotebookType;
return notebook?.topics; return db.relations?.from(notebook, "note");
} }
}); });
}; };
const onPressFloatingButton = () => {
const n = params.current.item;
eSendEvent(eOpenAddTopicDialog, { notebookId: n.id });
};
const PLACEHOLDER_DATA = { const PLACEHOLDER_DATA = {
heading: params.current.item?.title, heading: params.current.item?.title,
paragraph: "You have not added any topics yet.", paragraph: "You have not added any notes yet.",
button: "Add first topic", button: "Add first note",
action: onPressFloatingButton, action: openEditor,
loading: "Loading notebook topics" loading: "Loading notebook notes"
}; };
return ( return (
<DelayLayout> <>
<List <DelayLayout>
listData={topics} <List
type="topics" listData={notes}
refreshCallback={() => { type="notes"
onRequestUpdate(); refreshCallback={() => {
}} onRequestUpdate();
screen="Notebook" }}
headerProps={{ screen="Notebook"
heading: params.current.title headerProps={{
}} heading: params.current.title
loading={false} }}
ListHeader={ loading={false}
<NotebookHeader ListHeader={
onEditNotebook={() => { <NotebookHeader
eSendEvent(eOpenAddNotebookDialog, params.current.item); onEditNotebook={() => {
}} eSendEvent(eOpenAddNotebookDialog, params.current.item);
notebook={params.current.item} }}
/> notebook={params.current.item}
} />
placeholderData={PLACEHOLDER_DATA} }
/> placeholderData={PLACEHOLDER_DATA}
/>
<FloatingButton title="Add new topic" onPress={onPressFloatingButton} /> </DelayLayout>
</DelayLayout> </>
); );
}; };

View File

@@ -18,11 +18,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import React from "react"; import React from "react";
import { Config } from "react-native-config";
import { db } from "../../common/database"; import { db } from "../../common/database";
import { FloatingButton } from "../../components/container/floating-button"; import { FloatingButton } from "../../components/container/floating-button";
import DelayLayout from "../../components/delay-layout"; import DelayLayout from "../../components/delay-layout";
import { AddNotebookEvent } from "../../components/dialog-provider/recievers";
import List from "../../components/list"; import List from "../../components/list";
import { AddNotebookSheet } from "../../components/sheets/add-notebook";
import { Walkthrough } from "../../components/walkthroughs"; import { Walkthrough } from "../../components/walkthroughs";
import { useNavigationFocus } from "../../hooks/use-navigation-focus"; import { useNavigationFocus } from "../../hooks/use-navigation-focus";
import Navigation, { NavigationProps } from "../../services/navigation"; import Navigation, { NavigationProps } from "../../services/navigation";
@@ -30,10 +31,9 @@ import SearchService from "../../services/search";
import SettingsService from "../../services/settings"; import SettingsService from "../../services/settings";
import useNavigationStore from "../../stores/use-navigation-store"; import useNavigationStore from "../../stores/use-navigation-store";
import { useNotebookStore } from "../../stores/use-notebook-store"; import { useNotebookStore } from "../../stores/use-notebook-store";
import { Config } from "react-native-config";
const onPressFloatingButton = () => { const onPressFloatingButton = () => {
AddNotebookEvent(); AddNotebookSheet.present();
}; };
const prepareSearch = () => { const prepareSearch = () => {
@@ -83,7 +83,7 @@ export const Notebooks = ({
}); });
return ( return (
<DelayLayout> <DelayLayout delay={1}>
<List <List
listData={notebooks} listData={notebooks}
type="notebooks" type="notebooks"

View File

@@ -81,12 +81,31 @@ export const setOnFirstSave = (
editorState().onNoteCreated = null; editorState().onNoteCreated = null;
return; return;
} }
editorState().onNoteCreated = (id) => onNoteCreated(id, data); setTimeout(() => {
editorState().onNoteCreated = (id) => onNoteCreated(id, data);
}, 0);
}; };
async function onNoteCreated(id: string, params: FirstSaveData) { async function onNoteCreated(id: string, params: FirstSaveData) {
if (!params) return; if (!params) return;
switch (params.type) { switch (params.type) {
case "notebook": {
await db.relations?.add(
{ type: "notebook", id: params.id },
{ type: "note", id: id }
);
Navigation.queueRoutesForUpdate(
"TaggedNotes",
"ColoredNotes",
"TopicNotes",
"Favorites",
"Notes",
"Notebook",
"Notebooks"
);
editorState().onNoteCreated = null;
break;
}
case "topic": { case "topic": {
if (!params.notebook) break; if (!params.notebook) break;
await db.notes?.addToNotebook( await db.notes?.addToNotebook(

View File

@@ -42,6 +42,13 @@ import {
setOnFirstSave, setOnFirstSave,
toCamelCase toCamelCase
} from "./common"; } from "./common";
import { View } from "react-native";
import { db } from "../../common/database";
import Paragraph from "../../components/ui/typography/paragraph";
import { IconButton } from "../../components/ui/icon-button";
import { useThemeStore } from "../../stores/use-theme-store";
import { SIZE } from "../../utils/size";
import Notebook from "../notebook/index";
export const WARNING_DATA = { export const WARNING_DATA = {
title: "Some notes in this topic are not synced" title: "Some notes in this topic are not synced"
}; };
@@ -92,13 +99,18 @@ const NotesPage = ({
}: RouteProps< }: RouteProps<
"NotesPage" | "TaggedNotes" | "Monographs" | "ColoredNotes" | "TopicNotes" "NotesPage" | "TaggedNotes" | "Monographs" | "ColoredNotes" | "TopicNotes"
>) => { >) => {
const colors = useThemeStore((state) => state.colors);
const params = useRef<NotesScreenParams>(route?.params); const params = useRef<NotesScreenParams>(route?.params);
const [notes, setNotes] = useState<NoteType[]>(get(route.params, true)); const [notes, setNotes] = useState<NoteType[]>(get(route.params, true));
const loading = useNoteStore((state) => state.loading); const loading = useNoteStore((state) => state.loading);
const [loadingNotes, setLoadingNotes] = useState(false); const [loadingNotes, setLoadingNotes] = useState(false);
const alias = getAlias(params.current); const alias = getAlias(params.current);
const isMonograph = route.name === "Monographs"; const isMonograph = route.name === "Monographs";
const notebook =
route.name === "TopicNotes" && (params.current.item as TopicType).notebookId
? db.notebooks?.notebook((params.current.item as TopicType).notebookId)
?.data
: null;
const isFocused = useNavigationFocus(navigation, { const isFocused = useNavigationFocus(navigation, {
onFocus: (prev) => { onFocus: (prev) => {
Navigation.routeNeedsUpdate(route.name, onRequestUpdate); Navigation.routeNeedsUpdate(route.name, onRequestUpdate);
@@ -176,6 +188,7 @@ const NotesPage = ({
) { ) {
return Navigation.goBack(); return Navigation.goBack();
} }
if (notes.length === 0) setLoadingNotes(false);
setNotes(notes); setNotes(notes);
syncWithNavigation(); syncWithNavigation();
} catch (e) { } catch (e) {
@@ -187,7 +200,7 @@ const NotesPage = ({
useEffect(() => { useEffect(() => {
if (loadingNotes) { if (loadingNotes) {
setTimeout(() => setLoadingNotes(false), 300); setTimeout(() => setLoadingNotes(false), 50);
} }
}, [loadingNotes, notes]); }, [loadingNotes, notes]);
@@ -208,6 +221,45 @@ const NotesPage = ({
} }
wait={loading || loadingNotes} wait={loading || loadingNotes}
> >
{route.name === "TopicNotes" ? (
<View
style={{
width: "100%",
paddingHorizontal: 12,
flexDirection: "row",
alignItems: "center"
// borderBottomWidth: 1,
// borderBottomColor: colors.nav
}}
>
<Paragraph
onPress={() => {
Navigation.navigate(
{
name: "Notebooks"
},
{}
);
}}
size={SIZE.xs}
>
Notebooks
</Paragraph>
<IconButton
name="chevron-right"
size={14}
customStyle={{ width: 25, height: 25 }}
/>
<Paragraph
onPress={() => {
Notebook.navigate(notebook, true);
}}
size={SIZE.xs}
>
{notebook.title}
</Paragraph>
</View>
) : null}
<List <List
listData={notes} listData={notes}
type="notes" type="notes"

View File

@@ -58,16 +58,18 @@ export const TopicNotes = ({
route route
}: NavigationProps<"TopicNotes">) => { }: NavigationProps<"TopicNotes">) => {
return ( return (
<NotesPage <>
navigation={navigation} <NotesPage
route={route} navigation={navigation}
get={TopicNotes.get} route={route}
placeholderData={PLACEHOLDER_DATA} get={TopicNotes.get}
onPressFloatingButton={openEditor} placeholderData={PLACEHOLDER_DATA}
rightButtons={headerRightButtons} onPressFloatingButton={openEditor}
canGoBack={route.params.canGoBack} rightButtons={headerRightButtons}
focusControl={true} canGoBack={route.params.canGoBack}
/> focusControl={true}
/>
</>
); );
}; };

View File

@@ -17,10 +17,10 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
import Clipboard from "@react-native-clipboard/clipboard";
import EventManager from "@notesnook/core/utils/event-manager"; import EventManager from "@notesnook/core/utils/event-manager";
import Clipboard from "@react-native-clipboard/clipboard";
import { RefObject } from "react"; import { RefObject } from "react";
import ActionSheet from "react-native-actions-sheet"; import { ActionSheetRef } from "react-native-actions-sheet";
import Config from "react-native-config"; import Config from "react-native-config";
import { import {
eCloseSheet, eCloseSheet,
@@ -97,7 +97,7 @@ export type PresentSheetOptions = {
component: component:
| JSX.Element | JSX.Element
| (( | ((
ref: RefObject<ActionSheet>, ref: RefObject<ActionSheetRef>,
close?: (ctx?: string) => void, close?: (ctx?: string) => void,
update?: (props: PresentSheetOptions) => void update?: (props: PresentSheetOptions) => void
) => JSX.Element); ) => JSX.Element);
@@ -114,6 +114,7 @@ export type PresentSheetOptions = {
actionsArray: SheetAction[]; actionsArray: SheetAction[];
learnMore: string; learnMore: string;
learnMorePress: () => void; learnMorePress: () => void;
enableGesturesInScrollView: boolean;
}; };
export function presentSheet(data: Partial<PresentSheetOptions>) { export function presentSheet(data: Partial<PresentSheetOptions>) {

View File

@@ -1,12 +1,12 @@
{ {
"name": "@notesnook/mobile", "name": "@notesnook/mobile",
"version": "2.4.5", "version": "2.4.6",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@notesnook/mobile", "name": "@notesnook/mobile",
"version": "2.4.5", "version": "2.4.6",
"license": "GPL-3.0-or-later", "license": "GPL-3.0-or-later",
"workspaces": [ "workspaces": [
"native/", "native/",
@@ -40,7 +40,9 @@
"html-to-text": "8.1.0", "html-to-text": "8.1.0",
"phone": "^3.1.14", "phone": "^3.1.14",
"qclone": "^1.2.0", "qclone": "^1.2.0",
"react-native-actions-sheet": "^0.7.2", "react": "18.0.0",
"react-native": "0.69.7",
"react-native-actions-sheet": "^0.9.0-alpha.6",
"react-native-check-version": "https://github.com/flexible-agency/react-native-check-version", "react-native-check-version": "https://github.com/flexible-agency/react-native-check-version",
"react-native-drax": "^0.10.2", "react-native-drax": "^0.10.2",
"react-native-image-zoom-viewer": "^3.0.1", "react-native-image-zoom-viewer": "^3.0.1",
@@ -17955,10 +17957,28 @@
} }
}, },
"node_modules/react-native-actions-sheet": { "node_modules/react-native-actions-sheet": {
"version": "0.7.2", "version": "0.9.0-alpha.6",
"resolved": "https://registry.npmjs.org/react-native-actions-sheet/-/react-native-actions-sheet-0.7.2.tgz", "resolved": "https://registry.npmjs.org/react-native-actions-sheet/-/react-native-actions-sheet-0.9.0-alpha.6.tgz",
"integrity": "sha512-au9QkDnSC+lhiTMHYA2cNdOhrKW/6v/vdeOTNigRFuvYoVVS2+vJOJpt2Z3mumRjmD02UocV0WmHu4anSsqqpA==", "integrity": "sha512-CtSttiXk+pTmuzrtfDlX5cwYEBsS2DuvZSE17iEYzbTDaYQ2ahRn7+34h4kuDmqLvp9Sdksb7C8s5pFJj+S9sA==",
"dependencies": {
"@shopify/flash-list": "^1.4.1"
},
"peerDependencies": { "peerDependencies": {
"react-native": "*",
"react-native-gesture-handler": "*"
}
},
"node_modules/react-native-actions-sheet/node_modules/@shopify/flash-list": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@shopify/flash-list/-/flash-list-1.4.1.tgz",
"integrity": "sha512-yM5dlhqolO/R8FKomqCrSYz0Cc82vJDikxhbu1CXXGp3rPvo/ceP9jJyKueW96SXHsn/87fcSq2BjztWjlp74Q==",
"dependencies": {
"recyclerlistview": "4.2.0",
"tslib": "2.4.0"
},
"peerDependencies": {
"@babel/runtime": "*",
"react": "*",
"react-native": "*" "react-native": "*"
} }
}, },
@@ -24307,7 +24327,9 @@
"html-to-text": "8.1.0", "html-to-text": "8.1.0",
"phone": "^3.1.14", "phone": "^3.1.14",
"qclone": "^1.2.0", "qclone": "^1.2.0",
"react-native-actions-sheet": "^0.7.2", "react": "18.0.0",
"react-native": "0.69.7",
"react-native-actions-sheet": "^0.9.0-alpha.6",
"react-native-check-version": "https://github.com/flexible-agency/react-native-check-version", "react-native-check-version": "https://github.com/flexible-agency/react-native-check-version",
"react-native-drax": "^0.10.2", "react-native-drax": "^0.10.2",
"react-native-image-zoom-viewer": "^3.0.1", "react-native-image-zoom-viewer": "^3.0.1",
@@ -34851,9 +34873,23 @@
} }
}, },
"react-native-actions-sheet": { "react-native-actions-sheet": {
"version": "0.7.2", "version": "0.9.0-alpha.6",
"resolved": "https://registry.npmjs.org/react-native-actions-sheet/-/react-native-actions-sheet-0.7.2.tgz", "resolved": "https://registry.npmjs.org/react-native-actions-sheet/-/react-native-actions-sheet-0.9.0-alpha.6.tgz",
"integrity": "sha512-au9QkDnSC+lhiTMHYA2cNdOhrKW/6v/vdeOTNigRFuvYoVVS2+vJOJpt2Z3mumRjmD02UocV0WmHu4anSsqqpA==" "integrity": "sha512-CtSttiXk+pTmuzrtfDlX5cwYEBsS2DuvZSE17iEYzbTDaYQ2ahRn7+34h4kuDmqLvp9Sdksb7C8s5pFJj+S9sA==",
"requires": {
"@shopify/flash-list": "^1.4.1"
},
"dependencies": {
"@shopify/flash-list": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@shopify/flash-list/-/flash-list-1.4.1.tgz",
"integrity": "sha512-yM5dlhqolO/R8FKomqCrSYz0Cc82vJDikxhbu1CXXGp3rPvo/ceP9jJyKueW96SXHsn/87fcSq2BjztWjlp74Q==",
"requires": {
"recyclerlistview": "4.2.0",
"tslib": "2.4.0"
}
}
}
}, },
"react-native-actions-shortcuts": { "react-native-actions-shortcuts": {
"version": "1.0.1", "version": "1.0.1",

View File

@@ -54,7 +54,7 @@ async function inlineImage(element: HTMLImageElement, options?: FetchOptions) {
return element; return element;
} }
return new Promise<HTMLImageElement | null>(function (resolve, reject) { return new Promise<HTMLImageElement | null>(function(resolve, reject) {
if (element.parentElement?.tagName === "PICTURE") { if (element.parentElement?.tagName === "PICTURE") {
element.parentElement?.replaceWith(element); element.parentElement?.replaceWith(element);
} }

View File

@@ -43,8 +43,14 @@ export function ImageSettings(props: ToolProps) {
popupId="imageSettings" popupId="imageSettings"
tools={ tools={
findSelectedNode(editor, "image")?.attrs?.float findSelectedNode(editor, "image")?.attrs?.float
? ["imageAlignLeft", "imageAlignRight", "imageProperties"] ? [
"downloadAttachment",
"imageAlignLeft",
"imageAlignRight",
"imageProperties"
]
: [ : [
"downloadAttachment",
"imageAlignLeft", "imageAlignLeft",
"imageAlignCenter", "imageAlignCenter",
"imageAlignRight", "imageAlignRight",