mirror of
https://github.com/streetwriters/notesnook.git
synced 2026-02-23 19:49:56 +01:00
web: replace note creation date edit popover with dialog
Signed-off-by: 01zulfi <85733202+01zulfi@users.noreply.github.com>
This commit is contained in:
@@ -239,15 +239,15 @@ export class NotePropertiesModel extends BaseProperties {
|
||||
const editIcon = this.page.locator(getTestId("edit-date-created"));
|
||||
await editIcon.click();
|
||||
|
||||
const editDateCreatedPopup = this.page.locator(
|
||||
getTestId("edit-date-created-popup")
|
||||
const editDateCreatedDialog = this.page.locator(
|
||||
getTestId("edit-note-creation-date-dialog")
|
||||
);
|
||||
await editDateCreatedPopup.waitFor({ state: "visible" });
|
||||
await editDateCreatedDialog.waitFor({ state: "visible" });
|
||||
|
||||
const dateInput = editDateCreatedPopup.locator(
|
||||
const dateInput = editDateCreatedDialog.locator(
|
||||
getTestId("date-created-input")
|
||||
);
|
||||
const timeInput = editDateCreatedPopup.locator(
|
||||
const timeInput = editDateCreatedDialog.locator(
|
||||
getTestId("time-created-input")
|
||||
);
|
||||
|
||||
@@ -255,7 +255,7 @@ export class NotePropertiesModel extends BaseProperties {
|
||||
await dateInput.fill(dayjs(date).format("DD-MM-YYYY"));
|
||||
await timeInput.fill(dayjs(date).format("hh:mm A"));
|
||||
|
||||
await editDateCreatedPopup.locator(getTestId("save-date-created")).click();
|
||||
await confirmDialog(editDateCreatedDialog);
|
||||
|
||||
await this.close();
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
Unlock,
|
||||
Readonly,
|
||||
SyncOff,
|
||||
ArrowLeft,
|
||||
Circle,
|
||||
Checkmark,
|
||||
ChevronDown,
|
||||
@@ -32,9 +31,10 @@ import {
|
||||
LinkedTo,
|
||||
ReferencedIn as ReferencedInIcon,
|
||||
Note as NoteIcon,
|
||||
Archive
|
||||
Archive,
|
||||
Edit
|
||||
} from "../icons";
|
||||
import { Box, Button, Flex, Text, FlexProps } from "@theme-ui/components";
|
||||
import { Button, Flex, Text, FlexProps } from "@theme-ui/components";
|
||||
import {
|
||||
useEditorStore,
|
||||
ReadonlyEditorSession,
|
||||
@@ -45,13 +45,12 @@ import { useStore as useAppStore } from "../../stores/app-store";
|
||||
import { useStore as useAttachmentStore } from "../../stores/attachment-store";
|
||||
import { store as noteStore } from "../../stores/note-store";
|
||||
import Toggle from "./toggle";
|
||||
import { NoteDateCreatedEditor } from "./note-date-created-editor";
|
||||
import { EditNoteCreationDateDialog } from "../../dialogs/edit-note-creation-date-dialog";
|
||||
import ScrollContainer from "../scroll-container";
|
||||
import {
|
||||
getFormattedDate,
|
||||
usePromise,
|
||||
ResolvedItem,
|
||||
useResolvedItem,
|
||||
useUnresolvedItem
|
||||
} from "@notesnook/common";
|
||||
import { ScopedThemeProvider } from "../theme-provider";
|
||||
@@ -60,7 +59,6 @@ import { VirtualizedList } from "../virtualized-list";
|
||||
import { SessionItem } from "../session-item";
|
||||
import {
|
||||
ContentBlock,
|
||||
InternalLink,
|
||||
Note,
|
||||
VirtualizedGrouping,
|
||||
createInternalLink,
|
||||
@@ -68,7 +66,6 @@ import {
|
||||
} from "@notesnook/core";
|
||||
import { VirtualizedTable } from "../virtualized-table";
|
||||
import { TextSlice } from "@notesnook/core";
|
||||
import { TITLE_BAR_HEIGHT } from "../title-bar";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
const tools = [
|
||||
@@ -213,13 +210,30 @@ function EditorProperties(props: EditorPropertiesProps) {
|
||||
>
|
||||
{item.label}
|
||||
</Text>
|
||||
|
||||
{item.key === "dateCreated" ? (
|
||||
<NoteDateCreatedEditor
|
||||
noteId={session.note.id}
|
||||
dateCreated={session.note.dateCreated}
|
||||
dateEdited={session.note.dateEdited}
|
||||
displayValue={item.value(session.note[item.key])}
|
||||
/>
|
||||
<Flex sx={{ alignItems: "center", gap: 1 }}>
|
||||
<Text
|
||||
data-test-id="date-created"
|
||||
className="selectable"
|
||||
variant="subBody"
|
||||
sx={{ fontSize: "body", flexShrink: 0 }}
|
||||
>
|
||||
{item.value(session.note[item.key])}
|
||||
</Text>
|
||||
<Edit
|
||||
size={14}
|
||||
sx={{ cursor: "pointer", color: "icon" }}
|
||||
onClick={() => {
|
||||
EditNoteCreationDateDialog.show({
|
||||
noteId: session.note.id,
|
||||
dateCreated: session.note.dateCreated,
|
||||
dateEdited: session.note.dateEdited
|
||||
});
|
||||
}}
|
||||
data-test-id="edit-date-created"
|
||||
/>
|
||||
</Flex>
|
||||
) : (
|
||||
<Text
|
||||
className="selectable"
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { useState, useRef } from "react";
|
||||
import { Button, Flex, Text } from "@theme-ui/components";
|
||||
import { Calendar, Edit } from "../icons";
|
||||
import Field from "../field";
|
||||
import { DayPicker } from "../day-picker";
|
||||
import { PopupPresenter } from "@notesnook/ui";
|
||||
import { db } from "../../common/db";
|
||||
import { showToast } from "../../utils/toast";
|
||||
import { useStore as useThemeStore } from "../../stores/theme-store";
|
||||
import { store as noteStore } from "../../stores/note-store";
|
||||
import { getFormattedDate } from "@notesnook/common";
|
||||
import { getTimeFormat } from "@notesnook/core";
|
||||
import { setTimeOnly, setDateOnly } from "../../utils/date-time";
|
||||
import dayjs from "dayjs";
|
||||
import customParseFormat from "dayjs/plugin/customParseFormat";
|
||||
import { strings } from "@notesnook/intl";
|
||||
|
||||
dayjs.extend(customParseFormat);
|
||||
|
||||
type NoteDateCreatedEditorProps = {
|
||||
noteId: string;
|
||||
dateCreated: number;
|
||||
dateEdited: number;
|
||||
displayValue: string;
|
||||
};
|
||||
|
||||
export function NoteDateCreatedEditor({
|
||||
noteId,
|
||||
dateCreated,
|
||||
dateEdited,
|
||||
displayValue
|
||||
}: NoteDateCreatedEditorProps) {
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [showCalendar, setShowCalendar] = useState(false);
|
||||
const [editedDate, setEditedDate] = useState<dayjs.Dayjs>(dayjs());
|
||||
const dateTextRef = useRef<HTMLDivElement>(null);
|
||||
const dateInputRef = useRef<HTMLInputElement>(null);
|
||||
const theme = useThemeStore((store) => store.colorScheme);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Flex sx={{ alignItems: "center", gap: 1 }}>
|
||||
<Text
|
||||
data-test-id="date-created"
|
||||
ref={dateTextRef}
|
||||
className="selectable"
|
||||
variant="subBody"
|
||||
sx={{ fontSize: "body", flexShrink: 0 }}
|
||||
>
|
||||
{displayValue}
|
||||
</Text>
|
||||
<Edit
|
||||
size={14}
|
||||
sx={{ cursor: "pointer", color: "icon" }}
|
||||
onClick={() => {
|
||||
setEditedDate(dayjs(dateCreated));
|
||||
setIsEditing(true);
|
||||
}}
|
||||
data-test-id="edit-date-created"
|
||||
/>
|
||||
</Flex>
|
||||
{isEditing && (
|
||||
<PopupPresenter
|
||||
isOpen={isEditing}
|
||||
onClose={() => {
|
||||
setIsEditing(false);
|
||||
setShowCalendar(false);
|
||||
}}
|
||||
position={{
|
||||
isTargetAbsolute: true,
|
||||
target: dateTextRef.current,
|
||||
location: "below"
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
bg: "background",
|
||||
p: 3,
|
||||
boxShadow: `0px 0px 25px 5px ${
|
||||
theme === "dark" ? "#000000aa" : "#0000004e"
|
||||
}`,
|
||||
borderRadius: "dialog",
|
||||
flexDirection: "column",
|
||||
gap: 2,
|
||||
width: 350
|
||||
}}
|
||||
data-test-id="edit-date-created-popup"
|
||||
>
|
||||
<Text variant="subtitle">{strings.editCreationDate()}</Text>
|
||||
<Flex sx={{ gap: 2, flexDirection: "column" }}>
|
||||
<Field
|
||||
id="date-created"
|
||||
label={strings.date()}
|
||||
required
|
||||
inputRef={dateInputRef}
|
||||
data-test-id="date-created-input"
|
||||
helpText={`${db.settings.getDateFormat()}`}
|
||||
action={{
|
||||
icon: Calendar,
|
||||
onClick() {
|
||||
setShowCalendar(!showCalendar);
|
||||
}
|
||||
}}
|
||||
validate={(t) =>
|
||||
dayjs(t, db.settings.getDateFormat(), true).isValid()
|
||||
}
|
||||
defaultValue={editedDate.format(db.settings.getDateFormat())}
|
||||
onChange={(e) =>
|
||||
setEditedDate((d) => setDateOnly(e.target.value, d))
|
||||
}
|
||||
/>
|
||||
<PopupPresenter
|
||||
isOpen={showCalendar}
|
||||
onClose={() => setShowCalendar(false)}
|
||||
position={{
|
||||
isTargetAbsolute: true,
|
||||
target: dateInputRef.current,
|
||||
location: "below"
|
||||
}}
|
||||
>
|
||||
<DayPicker
|
||||
sx={{
|
||||
bg: "background",
|
||||
p: 2,
|
||||
boxShadow: `0px 0px 25px 5px ${
|
||||
theme === "dark" ? "#000000aa" : "#0000004e"
|
||||
}`,
|
||||
borderRadius: "dialog",
|
||||
width: 300
|
||||
}}
|
||||
selected={dayjs(editedDate).toDate()}
|
||||
maxDate={new Date()}
|
||||
onSelect={(day) => {
|
||||
if (!day) return;
|
||||
const date = getFormattedDate(day, "date");
|
||||
setEditedDate((d) => setDateOnly(date, d));
|
||||
if (dateInputRef.current) dateInputRef.current.value = date;
|
||||
setShowCalendar(false);
|
||||
}}
|
||||
/>
|
||||
</PopupPresenter>
|
||||
<Field
|
||||
id="time-created"
|
||||
label={strings.time()}
|
||||
required
|
||||
data-test-id="time-created-input"
|
||||
helpText={`${
|
||||
db.settings.getTimeFormat() === "12-hour"
|
||||
? "hh:mm AM/PM"
|
||||
: "hh:mm"
|
||||
}`}
|
||||
validate={(t) => {
|
||||
const format =
|
||||
db.settings.getTimeFormat() === "12-hour"
|
||||
? "hh:mm a"
|
||||
: "HH:mm";
|
||||
return dayjs(t.toLowerCase(), format, true).isValid();
|
||||
}}
|
||||
defaultValue={editedDate.format(
|
||||
getTimeFormat(db.settings.getTimeFormat())
|
||||
)}
|
||||
onChange={(e) =>
|
||||
setEditedDate((d) => setTimeOnly(e.target.value, d))
|
||||
}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex sx={{ gap: 2, justifyContent: "flex-end" }}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setIsEditing(false);
|
||||
setShowCalendar(false);
|
||||
}}
|
||||
>
|
||||
{strings.cancel()}
|
||||
</Button>
|
||||
<Button
|
||||
variant="accentSecondary"
|
||||
data-test-id="save-date-created"
|
||||
onClick={async () => {
|
||||
try {
|
||||
if (editedDate.isAfter(dayjs())) {
|
||||
showToast(
|
||||
"error",
|
||||
"Creation date cannot be in the future"
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (dateEdited && editedDate.isAfter(dayjs(dateEdited))) {
|
||||
showToast(
|
||||
"error",
|
||||
"Creation date cannot be after last edited date"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await noteStore
|
||||
.get()
|
||||
.updateDateCreated(noteId, editedDate.valueOf());
|
||||
setIsEditing(false);
|
||||
setShowCalendar(false);
|
||||
} catch (error) {
|
||||
showToast(
|
||||
"error",
|
||||
"Failed to update creation date: " +
|
||||
(error as Error).message
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{strings.save()}
|
||||
</Button>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</PopupPresenter>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
177
apps/web/src/dialogs/edit-note-creation-date-dialog.tsx
Normal file
177
apps/web/src/dialogs/edit-note-creation-date-dialog.tsx
Normal file
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
This file is part of the Notesnook project (https://notesnook.com/)
|
||||
|
||||
Copyright (C) 2023 Streetwriters (Private) Limited
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getFormattedDate } from "@notesnook/common";
|
||||
import { getTimeFormat } from "@notesnook/core";
|
||||
import { strings } from "@notesnook/intl";
|
||||
import { PopupPresenter } from "@notesnook/ui";
|
||||
import { Flex } from "@theme-ui/components";
|
||||
import dayjs from "dayjs";
|
||||
import customParseFormat from "dayjs/plugin/customParseFormat";
|
||||
import { useRef, useState } from "react";
|
||||
import { db } from "../common/db";
|
||||
import { BaseDialogProps, DialogManager } from "../common/dialog-manager";
|
||||
import { DayPicker } from "../components/day-picker";
|
||||
import Dialog from "../components/dialog";
|
||||
import Field from "../components/field";
|
||||
import { Calendar } from "../components/icons";
|
||||
import { store as noteStore } from "../stores/note-store";
|
||||
import { useStore as useThemeStore } from "../stores/theme-store";
|
||||
import { setDateOnly, setTimeOnly } from "../utils/date-time";
|
||||
import { showToast } from "../utils/toast";
|
||||
|
||||
dayjs.extend(customParseFormat);
|
||||
|
||||
type EditNoteCreationDateDialogProps = BaseDialogProps<boolean> & {
|
||||
noteId: string;
|
||||
dateCreated: number;
|
||||
dateEdited: number;
|
||||
};
|
||||
|
||||
export const EditNoteCreationDateDialog = DialogManager.register(
|
||||
function EditNoteCreationDateDialog({
|
||||
noteId,
|
||||
dateCreated,
|
||||
dateEdited,
|
||||
onClose
|
||||
}: EditNoteCreationDateDialogProps) {
|
||||
const [showCalendar, setShowCalendar] = useState(false);
|
||||
const [date, setDate] = useState<dayjs.Dayjs>(dayjs(dateCreated));
|
||||
const dateInputRef = useRef<HTMLInputElement>(null);
|
||||
const theme = useThemeStore((store) => store.colorScheme);
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
isOpen
|
||||
testId="edit-note-creation-date-dialog"
|
||||
onClose={() => {
|
||||
setShowCalendar(false);
|
||||
onClose(false);
|
||||
}}
|
||||
title={strings.editCreationDate()}
|
||||
negativeButton={{
|
||||
text: strings.cancel(),
|
||||
onClick: () => {
|
||||
setShowCalendar(false);
|
||||
onClose(false);
|
||||
}
|
||||
}}
|
||||
positiveButton={{
|
||||
text: strings.save(),
|
||||
onClick: async () => {
|
||||
try {
|
||||
if (date.isAfter(dayjs())) {
|
||||
showToast("error", "Creation date cannot be in the future");
|
||||
return;
|
||||
}
|
||||
if (dateEdited && date.isAfter(dayjs(dateEdited))) {
|
||||
showToast(
|
||||
"error",
|
||||
"Creation date cannot be after last edited date"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
await noteStore.get().updateDateCreated(noteId, date.valueOf());
|
||||
setShowCalendar(false);
|
||||
onClose(false);
|
||||
} catch (error) {
|
||||
showToast(
|
||||
"error",
|
||||
"Failed to update creation date: " + (error as Error).message
|
||||
);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Flex sx={{ gap: 2, flexDirection: "column" }}>
|
||||
<Field
|
||||
id="date-created"
|
||||
label={strings.date()}
|
||||
required
|
||||
inputRef={dateInputRef}
|
||||
data-test-id="date-created-input"
|
||||
helpText={`${db.settings.getDateFormat()}`}
|
||||
action={{
|
||||
icon: Calendar,
|
||||
onClick() {
|
||||
setShowCalendar(!showCalendar);
|
||||
}
|
||||
}}
|
||||
validate={(t) =>
|
||||
dayjs(t, db.settings.getDateFormat(), true).isValid()
|
||||
}
|
||||
defaultValue={date.format(db.settings.getDateFormat())}
|
||||
onChange={(e) => setDate((d) => setDateOnly(e.target.value, d))}
|
||||
/>
|
||||
<PopupPresenter
|
||||
isOpen={showCalendar}
|
||||
onClose={() => setShowCalendar(false)}
|
||||
position={{
|
||||
isTargetAbsolute: true,
|
||||
target: dateInputRef.current,
|
||||
location: "below"
|
||||
}}
|
||||
>
|
||||
<DayPicker
|
||||
sx={{
|
||||
bg: "background",
|
||||
p: 2,
|
||||
boxShadow: `0px 0px 25px 5px ${
|
||||
theme === "dark" ? "#000000aa" : "#0000004e"
|
||||
}`,
|
||||
borderRadius: "dialog",
|
||||
width: 300
|
||||
}}
|
||||
selected={dayjs(date).toDate()}
|
||||
maxDate={new Date()}
|
||||
onSelect={(day) => {
|
||||
if (!day) return;
|
||||
const date = getFormattedDate(day, "date");
|
||||
setDate((d) => setDateOnly(date, d));
|
||||
if (dateInputRef.current) dateInputRef.current.value = date;
|
||||
setShowCalendar(false);
|
||||
}}
|
||||
/>
|
||||
</PopupPresenter>
|
||||
<Field
|
||||
id="time-created"
|
||||
label={strings.time()}
|
||||
required
|
||||
data-test-id="time-created-input"
|
||||
helpText={`${
|
||||
db.settings.getTimeFormat() === "12-hour"
|
||||
? "hh:mm AM/PM"
|
||||
: "hh:mm"
|
||||
}`}
|
||||
validate={(t) => {
|
||||
const format =
|
||||
db.settings.getTimeFormat() === "12-hour" ? "hh:mm a" : "HH:mm";
|
||||
return dayjs(t.toLowerCase(), format, true).isValid();
|
||||
}}
|
||||
defaultValue={date.format(
|
||||
getTimeFormat(db.settings.getTimeFormat())
|
||||
)}
|
||||
onChange={(e) => setDate((d) => setTimeOnly(e.target.value, d))}
|
||||
/>
|
||||
</Flex>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
);
|
||||
Reference in New Issue
Block a user