From e1e8a76fdaabb8bed90617b4892fc9c51648f40d Mon Sep 17 00:00:00 2001 From: Erik Michelson Date: Wed, 1 Jul 2020 23:28:49 +0200 Subject: [PATCH] Add note deletion and removal from history modals (#299) * Fix history element's entry menu deletion button The note deletion button inside the EntryMenu of a history element has one button to remove the note from the user's history and one to delete the note from the system. Both buttons pointed to the history-removal. * Added modals for note deletion and note from history removal * Removed redundant code * Added CHANGELOG entry * Added note title in deletion/removal prompts * Refactored DeleteNoteItem and RemoveNoteEntryItem into one common component * Refactored DeleteRemoveNoteItem-component and added two composition components * Redesigned modal dialog to make the note title more clearly readable * Renamed the generic dropdown-with-deletion-modal-component --- CHANGELOG.md | 1 + public/locales/en.json | 9 +++- .../common/entry-menu/delete-note-item.tsx | 21 ++++++++ .../dropdown-item-with-deletion-modal.tsx | 49 +++++++++++++++++++ .../common/{ => entry-menu}/entry-menu.scss | 0 .../common/{ => entry-menu}/entry-menu.tsx | 25 +++++----- .../entry-menu/remove-note-entry-item.tsx | 21 ++++++++ .../history/history-card/history-card.tsx | 7 +-- .../history-table/history-table-row.tsx | 7 +-- .../history-toolbar/import-history-button.tsx | 9 ++-- .../landing/pages/history/history.tsx | 30 +++++------- 11 files changed, 136 insertions(+), 43 deletions(-) create mode 100644 src/components/landing/pages/history/common/entry-menu/delete-note-item.tsx create mode 100644 src/components/landing/pages/history/common/entry-menu/dropdown-item-with-deletion-modal.tsx rename src/components/landing/pages/history/common/{ => entry-menu}/entry-menu.scss (100%) rename src/components/landing/pages/history/common/{ => entry-menu}/entry-menu.tsx (64%) create mode 100644 src/components/landing/pages/history/common/entry-menu/remove-note-entry-item.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 870574c0f..1e3f3c157 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ - Users may now change their display name and password (only email accounts) on the new profile page - Highlighted code blocks can now use line wrapping and line numbers at once - Images, videos, and other non-text content is now wider in View Mode +- Notes may now be deleted directly from the history page - CodiMD instances can now be branded either with a '@ ' or '@ ' after the CodiMD logo and text ### Changed diff --git a/public/locales/en.json b/public/locales/en.json index f7734ff70..c66d23811 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -70,6 +70,12 @@ "textWithFile": "While trying to import history from '{{fileName}}' an error occurred.", "textWithoutFile": "You did not provide any files to upload the history from.", "tooNewVersion": "The file '{{fileName}}' comes from a newer client and can't be imported." + }, + "removeNote": { + "title": "Remove note from history", + "question": "Do you really want to remove this note from your history?", + "warning": "This just removes the history entry and won't delete the note itself.", + "button": "Remove note from history" } }, "tableHeader": { @@ -252,7 +258,8 @@ "deleteNote": { "title": "Delete note", "question": "Do you really want to delete this note?", - "warning": "All users will lose their connection." + "warning": "All users will lose their connection. This process is irreversible.", + "button": "Delete note" } }, "embeddings": { diff --git a/src/components/landing/pages/history/common/entry-menu/delete-note-item.tsx b/src/components/landing/pages/history/common/entry-menu/delete-note-item.tsx new file mode 100644 index 000000000..12ae19e3a --- /dev/null +++ b/src/components/landing/pages/history/common/entry-menu/delete-note-item.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import { DropdownItemWithDeletionModal } from './dropdown-item-with-deletion-modal' + +export interface DeleteNoteItemProps { + onConfirm: () => void + noteTitle: string +} + +export const DeleteNoteItem: React.FC = ({ noteTitle, onConfirm }) => { + return ( + + ) +} diff --git a/src/components/landing/pages/history/common/entry-menu/dropdown-item-with-deletion-modal.tsx b/src/components/landing/pages/history/common/entry-menu/dropdown-item-with-deletion-modal.tsx new file mode 100644 index 000000000..8514fe87e --- /dev/null +++ b/src/components/landing/pages/history/common/entry-menu/dropdown-item-with-deletion-modal.tsx @@ -0,0 +1,49 @@ +import React, { Fragment, useState } from 'react' +import { Dropdown } from 'react-bootstrap' +import { Trans, useTranslation } from 'react-i18next' +import { ForkAwesomeIcon, IconName } from '../../../../../common/fork-awesome/fork-awesome-icon' +import { DeletionModal } from '../../../../../common/modals/deletion-modal' + +export interface DropdownItemWithDeletionModalProps { + onConfirm: () => void + itemI18nKey: string + modalButtonI18nKey: string + modalIcon: IconName + modalTitleI18nKey: string + modalQuestionI18nKey: string + modalWarningI18nKey: string + noteTitle: string +} + +export const DropdownItemWithDeletionModal: React.FC = ({ + onConfirm, noteTitle, + modalTitleI18nKey, modalButtonI18nKey, itemI18nKey, modalIcon, + modalQuestionI18nKey, modalWarningI18nKey +}) => { + useTranslation() + const [showDialog, setShowDialog] = useState(false) + + return ( + + setShowDialog(true)}> + + + + { + setShowDialog(false) + onConfirm() + }} + deletionButtonI18nKey={modalButtonI18nKey} + show={showDialog} + onHide={() => setShowDialog(false)} + titleI18nKey={modalTitleI18nKey}> +
+
    +
  • { noteTitle }
  • +
+
+
+
+ ) +} diff --git a/src/components/landing/pages/history/common/entry-menu.scss b/src/components/landing/pages/history/common/entry-menu/entry-menu.scss similarity index 100% rename from src/components/landing/pages/history/common/entry-menu.scss rename to src/components/landing/pages/history/common/entry-menu/entry-menu.scss diff --git a/src/components/landing/pages/history/common/entry-menu.tsx b/src/components/landing/pages/history/common/entry-menu/entry-menu.tsx similarity index 64% rename from src/components/landing/pages/history/common/entry-menu.tsx rename to src/components/landing/pages/history/common/entry-menu/entry-menu.tsx index 46fe28f46..d0cfa6ef7 100644 --- a/src/components/landing/pages/history/common/entry-menu.tsx +++ b/src/components/landing/pages/history/common/entry-menu/entry-menu.tsx @@ -1,13 +1,16 @@ import React from 'react' import { Dropdown } from 'react-bootstrap' -import { Trans } from 'react-i18next' -import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon' -import { ShowIf } from '../../../../common/show-if/show-if' -import { HistoryEntryOrigin } from '../history' +import { Trans, useTranslation } from 'react-i18next' +import { ForkAwesomeIcon } from '../../../../../common/fork-awesome/fork-awesome-icon' +import { ShowIf } from '../../../../../common/show-if/show-if' +import { HistoryEntryOrigin } from '../../history' import './entry-menu.scss' +import { DeleteNoteItem } from './delete-note-item' +import { RemoveNoteEntryItem } from './remove-note-entry-item' export interface EntryMenuProps { id: string; + title: string location: HistoryEntryOrigin isDark: boolean; onRemove: () => void @@ -15,7 +18,9 @@ export interface EntryMenuProps { className?: string } -const EntryMenu: React.FC = ({ id, location, isDark, onRemove, onDelete, className }) => { +const EntryMenu: React.FC = ({ id, title, location, isDark, onRemove, onDelete, className }) => { + useTranslation() + return ( @@ -40,17 +45,11 @@ const EntryMenu: React.FC = ({ id, location, isDark, onRemove, o - - - - + - - - - + ) diff --git a/src/components/landing/pages/history/common/entry-menu/remove-note-entry-item.tsx b/src/components/landing/pages/history/common/entry-menu/remove-note-entry-item.tsx new file mode 100644 index 000000000..d27b2f8e8 --- /dev/null +++ b/src/components/landing/pages/history/common/entry-menu/remove-note-entry-item.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import { DropdownItemWithDeletionModal } from './dropdown-item-with-deletion-modal' + +export interface RemoveNoteEntryItemProps { + onConfirm: () => void + noteTitle: string +} + +export const RemoveNoteEntryItem: React.FC = ({ noteTitle, onConfirm }) => { + return ( + + ) +} diff --git a/src/components/landing/pages/history/history-card/history-card.tsx b/src/components/landing/pages/history/history-card/history-card.tsx index 3f5632ecd..2bf61484d 100644 --- a/src/components/landing/pages/history/history-card/history-card.tsx +++ b/src/components/landing/pages/history/history-card/history-card.tsx @@ -4,12 +4,12 @@ import { Badge, Card } from 'react-bootstrap' import { Link } from 'react-router-dom' import { formatHistoryDate } from '../../../../../utils/historyUtils' import { ForkAwesomeIcon } from '../../../../common/fork-awesome/fork-awesome-icon' -import { EntryMenu } from '../common/entry-menu' +import { EntryMenu } from '../common/entry-menu/entry-menu' import { PinButton } from '../common/pin-button' import { HistoryEntryProps } from '../history-content/history-content' import './history-card.scss' -export const HistoryCard: React.FC = ({ entry, onPinClick, onRemoveClick }) => { +export const HistoryCard: React.FC = ({ entry, onPinClick, onRemoveClick, onDeleteClick }) => { return (
@@ -36,10 +36,11 @@ export const HistoryCard: React.FC = ({ entry, onPinClick, on
onRemoveClick(entry.id, entry.location)} - onDelete={() => onRemoveClick(entry.id, entry.location)} + onDelete={() => onDeleteClick(entry.id, entry.location)} />
diff --git a/src/components/landing/pages/history/history-table/history-table-row.tsx b/src/components/landing/pages/history/history-table/history-table-row.tsx index 77bb813da..f25bbe742 100644 --- a/src/components/landing/pages/history/history-table/history-table-row.tsx +++ b/src/components/landing/pages/history/history-table/history-table-row.tsx @@ -2,11 +2,11 @@ import React from 'react' import { Badge } from 'react-bootstrap' import { Link } from 'react-router-dom' import { formatHistoryDate } from '../../../../../utils/historyUtils' -import { EntryMenu } from '../common/entry-menu' +import { EntryMenu } from '../common/entry-menu/entry-menu' import { PinButton } from '../common/pin-button' import { HistoryEntryProps } from '../history-content/history-content' -export const HistoryTableRow: React.FC = ({ entry, onPinClick, onRemoveClick }) => { +export const HistoryTableRow: React.FC = ({ entry, onPinClick, onRemoveClick, onDeleteClick }) => { return ( @@ -25,10 +25,11 @@ export const HistoryTableRow: React.FC = ({ entry, onPinClick onPinClick(entry.id, entry.location)} className={'mb-1 mr-1'}/> onRemoveClick(entry.id, entry.location)} - onDelete={() => onRemoveClick(entry.id, entry.location)} + onDelete={() => onDeleteClick(entry.id, entry.location)} /> diff --git a/src/components/landing/pages/history/history-toolbar/import-history-button.tsx b/src/components/landing/pages/history/history-toolbar/import-history-button.tsx index 2d549d95e..e71686e21 100644 --- a/src/components/landing/pages/history/history-toolbar/import-history-button.tsx +++ b/src/components/landing/pages/history/history-toolbar/import-history-button.tsx @@ -81,12 +81,9 @@ export const ImportHistoryButton: React.FC = ({ onImpo titleI18nKey='landing.history.modal.importHistoryError.title' icon='exclamation-circle' > - {fileName !== '' - ?
- -
- :
- } +
+ +
) diff --git a/src/components/landing/pages/history/history.tsx b/src/components/landing/pages/history/history.tsx index f6137feae..e0270fd46 100644 --- a/src/components/landing/pages/history/history.tsx +++ b/src/components/landing/pages/history/history.tsx @@ -115,21 +115,7 @@ export const History: React.FC = () => { } }, [historyWrite, localHistoryEntries, remoteHistoryEntries, user]) - const deleteClick = useCallback((entryId: string, location: HistoryEntryOrigin): void => { - if (user) { - deleteNote(entryId) - .then(() => { - if (location === HistoryEntryOrigin.LOCAL) { - setLocalHistoryEntries(entries => entries.filter(entry => entry.id !== entryId)) - } else if (location === HistoryEntryOrigin.REMOTE) { - setRemoteHistoryEntries(entries => entries.filter(entry => entry.id !== entryId)) - } - }) - .catch(() => setError('deleteNote')) - } - }, [user]) - - const removeClick = useCallback((entryId: string, location: HistoryEntryOrigin): void => { + const removeFromHistoryClick = useCallback((entryId: string, location: HistoryEntryOrigin): void => { if (location === HistoryEntryOrigin.LOCAL) { setLocalHistoryEntries((entries) => entries.filter(entry => entry.id !== entryId)) } else if (location === HistoryEntryOrigin.REMOTE) { @@ -139,6 +125,16 @@ export const History: React.FC = () => { } }, []) + const deleteNoteClick = useCallback((entryId: string, location: HistoryEntryOrigin): void => { + if (user) { + deleteNote(entryId) + .then(() => { + removeFromHistoryClick(entryId, location) + }) + .catch(() => setError('deleteNote')) + } + }, [user, removeFromHistoryClick]) + const pinClick = useCallback((entryId: string, location: HistoryEntryOrigin): void => { if (location === HistoryEntryOrigin.LOCAL) { setLocalHistoryEntries((entries) => { @@ -216,8 +212,8 @@ export const History: React.FC = () => { viewState={toolbarState.viewState} entries={entriesToShow} onPinClick={pinClick} - onRemoveClick={removeClick} - onDeleteClick={deleteClick} + onRemoveClick={removeFromHistoryClick} + onDeleteClick={deleteNoteClick} /> )