mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-02-24 12:12:54 +01:00
* core: expiring notes * core: add tests * mobile: support setting not expiry date * web: support note expiry dates Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com> * core: add tests for expiring notes * core: fix tests * mobile: delete expiring notes at startup * core: create index on expiry date * core: minor refactor * core: remove `.only` sync test * web: refactors * mobile: set limit on expiring notes * web: improve expiry date menu option && note item ui Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com> * web: add premium check for setting expiry for notes Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com> * web: move note expiry date dialog into its own file && minor changes Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com> * web: delete expired notes on startup * web: minor refactors --------- Co-authored-by: 01zulfi <85733202+01zulfi@users.noreply.github.com> Co-authored-by: Abdullah Atta <abdullahatta@streetwriters.co>
407 lines
11 KiB
TypeScript
407 lines
11 KiB
TypeScript
/*
|
|
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 { Item } from "@notesnook/core";
|
|
import { useThemeColors } from "@notesnook/theme";
|
|
import React from "react";
|
|
import { Dimensions, View } from "react-native";
|
|
import SwiperFlatList from "react-native-swiper-flatlist";
|
|
import Icon from "react-native-vector-icons/MaterialCommunityIcons";
|
|
import { Action, ActionId, useActions } from "../../hooks/use-actions";
|
|
import { useStoredRef } from "../../hooks/use-stored-ref";
|
|
import { DDS } from "../../services/device-detection";
|
|
import { useSettingStore } from "../../stores/use-setting-store";
|
|
import { AppFontSize, defaultBorderRadius } from "../../utils/size";
|
|
import { DefaultAppStyles } from "../../utils/styles";
|
|
import AppIcon from "../ui/AppIcon";
|
|
import { Button } from "../ui/button";
|
|
import { Pressable } from "../ui/pressable";
|
|
import Paragraph from "../ui/typography/paragraph";
|
|
import { Dialog } from "../dialog";
|
|
|
|
const TOP_BAR_ITEMS: ActionId[] = [
|
|
"pin",
|
|
"favorite",
|
|
"archive",
|
|
"lock-unlock",
|
|
"publish",
|
|
"local-only",
|
|
"read-only",
|
|
"pin-to-notifications"
|
|
];
|
|
|
|
const BOTTOM_BAR_ITEMS: ActionId[] = [
|
|
"notebooks",
|
|
"add-reminder",
|
|
"history",
|
|
"reminders",
|
|
"attachments",
|
|
"references",
|
|
"copy",
|
|
"share",
|
|
"export",
|
|
"copy-link",
|
|
"duplicate",
|
|
"launcher-shortcut",
|
|
"expiry-date",
|
|
"trash"
|
|
];
|
|
|
|
const COLUMN_BAR_ITEMS: ActionId[] = [
|
|
"select",
|
|
"add-notebook",
|
|
"edit-notebook",
|
|
"move-notes",
|
|
"move-notebook",
|
|
"edit-reminder",
|
|
"pin",
|
|
"disable-reminder",
|
|
"default-notebook",
|
|
"default-tag",
|
|
"default-homepage",
|
|
"add-shortcut",
|
|
"reorder",
|
|
"rename-color",
|
|
"rename-tag",
|
|
"launcher-shortcut",
|
|
"restore",
|
|
"trash",
|
|
"delete"
|
|
];
|
|
|
|
export const Items = ({
|
|
item,
|
|
close,
|
|
buttons
|
|
}: {
|
|
item: Item;
|
|
close: () => void;
|
|
buttons: Action[];
|
|
}) => {
|
|
const { colors } = useThemeColors();
|
|
const topBarSorting = useStoredRef<{ [name: string]: number }>(
|
|
"topbar-sorting-ref",
|
|
{}
|
|
);
|
|
const dimensions = useSettingStore((state) => state.dimensions);
|
|
const actions = useActions({ item, close });
|
|
const selectedActions = actions.filter((i) => !i.hidden);
|
|
const deviceMode = useSettingStore((state) => state.deviceMode);
|
|
const width = Math.min(dimensions.width, 600);
|
|
|
|
const shouldShrink =
|
|
Dimensions.get("window").fontScale > 1 &&
|
|
Dimensions.get("window").width < 450;
|
|
|
|
const columnItemsCount = deviceMode === "tablet" ? 7 : shouldShrink ? 4 : 5;
|
|
|
|
const columnItemWidth =
|
|
deviceMode !== "mobile"
|
|
? (width - DefaultAppStyles.GAP) / columnItemsCount
|
|
: (width - DefaultAppStyles.GAP) / columnItemsCount;
|
|
|
|
const topBarItems = selectedActions
|
|
.filter((item) => TOP_BAR_ITEMS.indexOf(item.id) > -1)
|
|
.sort((a, b) =>
|
|
TOP_BAR_ITEMS.indexOf(a.id) > TOP_BAR_ITEMS.indexOf(b.id) ? 1 : -1
|
|
)
|
|
.sort((a, b) => {
|
|
return (
|
|
(topBarSorting.current[b.id] || 0) - (topBarSorting.current[a.id] || 0)
|
|
);
|
|
});
|
|
|
|
const bottomGridItems = selectedActions
|
|
.filter((item) => BOTTOM_BAR_ITEMS.indexOf(item.id) > -1)
|
|
.sort((a, b) =>
|
|
BOTTOM_BAR_ITEMS.indexOf(a.id) > BOTTOM_BAR_ITEMS.indexOf(b.id) ? 1 : -1
|
|
);
|
|
|
|
const columnItems = selectedActions
|
|
.filter((item) => COLUMN_BAR_ITEMS.indexOf(item.id) > -1)
|
|
.sort((a, b) =>
|
|
COLUMN_BAR_ITEMS.indexOf(a.id) > COLUMN_BAR_ITEMS.indexOf(b.id) ? 1 : -1
|
|
);
|
|
|
|
const topBarItemHeight = Math.min(
|
|
(width - (topBarItems.length * 10 + 14)) / topBarItems.length,
|
|
60
|
|
);
|
|
|
|
const renderRowItem = React.useCallback(
|
|
({ item }: { item: Action }) => (
|
|
<View
|
|
key={item.id}
|
|
style={{
|
|
alignItems: "center",
|
|
width: columnItemWidth - 8,
|
|
opacity: item.locked ? 0.5 : 1
|
|
}}
|
|
>
|
|
<Pressable
|
|
onPress={item.onPress}
|
|
type={item.checked ? "shade" : "secondary"}
|
|
testID={"icon-" + item.id}
|
|
style={{
|
|
height: columnItemWidth / 1.5,
|
|
width: columnItemWidth - 8,
|
|
borderRadius: 10,
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
marginBottom: DDS.isTab ? 7 : 3.5
|
|
}}
|
|
>
|
|
<Icon
|
|
allowFontScaling
|
|
name={item.icon}
|
|
size={
|
|
DDS.isTab
|
|
? AppFontSize.xxl
|
|
: shouldShrink
|
|
? AppFontSize.xxl
|
|
: AppFontSize.lg
|
|
}
|
|
color={
|
|
item.checked
|
|
? item.activeColor || colors.primary.accent
|
|
: item.id.match(/(delete|trash)/g)
|
|
? colors.error.icon
|
|
: colors.secondary.icon
|
|
}
|
|
/>
|
|
</Pressable>
|
|
|
|
<Paragraph
|
|
size={AppFontSize.xxs}
|
|
textBreakStrategy="simple"
|
|
style={{ textAlign: "center" }}
|
|
>
|
|
{item.title}
|
|
</Paragraph>
|
|
</View>
|
|
),
|
|
[
|
|
colors.error.icon,
|
|
colors.primary.accent,
|
|
colors.secondary.icon,
|
|
columnItemWidth,
|
|
shouldShrink
|
|
]
|
|
);
|
|
|
|
const renderColumnItem = React.useCallback(
|
|
(item: Action) => (
|
|
<Button
|
|
key={item.id}
|
|
buttonType={{
|
|
text: item.checked
|
|
? item.activeColor || colors.primary.accent
|
|
: item.id === "delete" || item.id === "trash"
|
|
? colors.error.paragraph
|
|
: colors.primary.paragraph
|
|
}}
|
|
testID={"icon-" + item.id}
|
|
onPress={item.onPress}
|
|
title={item.title}
|
|
icon={item.icon}
|
|
type={item.checked ? "inverted" : "plain"}
|
|
fontSize={AppFontSize.sm}
|
|
style={{
|
|
borderRadius: 0,
|
|
justifyContent: "flex-start",
|
|
alignSelf: "flex-start",
|
|
width: "100%",
|
|
opacity: item.locked ? 0.5 : 1
|
|
}}
|
|
/>
|
|
),
|
|
[colors.error.paragraph, colors.primary.accent, colors.primary.paragraph]
|
|
);
|
|
|
|
const renderTopBarItem = React.useCallback(
|
|
(item: Action) => {
|
|
return (
|
|
<Pressable
|
|
onPress={() => {
|
|
item.onPress();
|
|
setImmediate(() => {
|
|
const currentValue = topBarSorting.current[item.id] || 0;
|
|
topBarSorting.current = {
|
|
...topBarSorting.current,
|
|
[item.id]: currentValue + 1
|
|
};
|
|
});
|
|
}}
|
|
key={item.id}
|
|
testID={"icon-" + item.id}
|
|
style={{
|
|
width: columnItemWidth - 8,
|
|
alignSelf: "flex-start",
|
|
gap: DefaultAppStyles.GAP_VERTICAL_SMALL
|
|
}}
|
|
>
|
|
<View
|
|
style={{
|
|
height: columnItemWidth / 2,
|
|
width: columnItemWidth - DefaultAppStyles.GAP_SMALL,
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
borderWidth: 1,
|
|
borderRadius: defaultBorderRadius,
|
|
borderColor: item.checked
|
|
? item.activeColor || colors.primary.accent
|
|
: colors.primary.border,
|
|
overflow: "hidden"
|
|
}}
|
|
>
|
|
<Icon
|
|
name={item.icon}
|
|
allowFontScaling
|
|
size={DDS.isTab ? AppFontSize.xxl : AppFontSize.md + 4}
|
|
color={
|
|
item.checked
|
|
? item.activeColor || colors.primary.accent
|
|
: item.id === "delete" || item.id === "trash"
|
|
? colors.error.icon
|
|
: colors.secondary.icon
|
|
}
|
|
/>
|
|
|
|
{item.locked ? (
|
|
<View
|
|
style={{
|
|
width: 20,
|
|
height: 20,
|
|
borderRadius: 100,
|
|
backgroundColor: colors.primary.accent,
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
position: "absolute",
|
|
bottom: -3,
|
|
right: -3
|
|
}}
|
|
>
|
|
<AppIcon
|
|
color={colors.static.orange}
|
|
size={AppFontSize.xxxs}
|
|
name="crown"
|
|
/>
|
|
</View>
|
|
) : null}
|
|
</View>
|
|
|
|
<Paragraph
|
|
textBreakStrategy="simple"
|
|
size={AppFontSize.xxs}
|
|
style={{ textAlign: "center" }}
|
|
>
|
|
{item.title}
|
|
</Paragraph>
|
|
</Pressable>
|
|
);
|
|
},
|
|
[
|
|
colors.error.icon,
|
|
colors.primary.accent,
|
|
colors.secondary.icon,
|
|
columnItemWidth,
|
|
topBarSorting
|
|
]
|
|
);
|
|
|
|
const getTopBarItemChunksOfFour = () => {
|
|
const chunks = [];
|
|
for (let i = 0; i < topBarItems.length; i += 5) {
|
|
chunks.push(topBarItems.slice(i, i + 5));
|
|
}
|
|
return chunks;
|
|
};
|
|
|
|
return (
|
|
<View
|
|
style={{
|
|
gap: DefaultAppStyles.GAP
|
|
}}
|
|
>
|
|
{item.type === "note" ? (
|
|
<>
|
|
<View>
|
|
<SwiperFlatList
|
|
data={getTopBarItemChunksOfFour()}
|
|
autoplay={false}
|
|
showPagination
|
|
paginationStyleItemActive={{
|
|
borderRadius: 2,
|
|
backgroundColor: colors.selected.background,
|
|
height: 6,
|
|
marginHorizontal: 2
|
|
}}
|
|
paginationStyleItemInactive={{
|
|
borderRadius: 2,
|
|
backgroundColor: colors.secondary.background,
|
|
height: 6,
|
|
marginHorizontal: 2
|
|
}}
|
|
paginationStyle={{
|
|
position: "relative",
|
|
marginHorizontal: 2,
|
|
marginBottom: -10,
|
|
marginTop: 10
|
|
}}
|
|
contentContainerStyle={{
|
|
justifyContent: "flex-start",
|
|
alignItems: "flex-start",
|
|
alignSelf: "flex-start"
|
|
}}
|
|
centerContent={false}
|
|
renderItem={({ item, index }) => (
|
|
<View
|
|
style={{
|
|
flexDirection: "row",
|
|
paddingHorizontal: DefaultAppStyles.GAP,
|
|
gap: 5
|
|
}}
|
|
>
|
|
{item.map(renderTopBarItem)}
|
|
</View>
|
|
)}
|
|
/>
|
|
</View>
|
|
|
|
<View
|
|
style={{
|
|
flexDirection: "row",
|
|
flexWrap: "wrap",
|
|
gap: 5,
|
|
paddingHorizontal: DefaultAppStyles.GAP
|
|
}}
|
|
>
|
|
{bottomGridItems.map((item) => renderRowItem({ item }))}
|
|
</View>
|
|
</>
|
|
) : (
|
|
<View>
|
|
{buttons.map(renderColumnItem)}
|
|
{columnItems.map(renderColumnItem)}
|
|
</View>
|
|
)}
|
|
</View>
|
|
);
|
|
};
|