fix: add delete permanently option, restore icon and show alerts for locked notes

This commit is contained in:
Antonella Sgarlatta
2021-05-07 15:56:35 -03:00
parent 9906cd2a20
commit 02249ebaca
9 changed files with 138 additions and 25 deletions

View File

@@ -7,6 +7,8 @@ import ArchiveIcon from '../../icons/ic-archive.svg';
import UnarchiveIcon from '../../icons/ic-unarchive.svg';
import HashtagIcon from '../../icons/ic-hashtag.svg';
import ChevronRightIcon from '../../icons/ic-chevron-right.svg';
import RestoreIcon from '../../icons/ic-restore.svg';
import CloseIcon from '../../icons/ic-close.svg';
import { toDirective } from './utils';
export enum IconType {
@@ -19,6 +21,8 @@ export enum IconType {
Unarchive = 'unarchive',
Hashtag = 'hashtag',
ChevronRight = 'chevron-right',
Restore = 'restore',
Close = 'close',
}
const ICONS = {
@@ -31,6 +35,8 @@ const ICONS = {
[IconType.Unarchive]: UnarchiveIcon,
[IconType.Hashtag]: HashtagIcon,
[IconType.ChevronRight]: ChevronRightIcon,
[IconType.Restore]: RestoreIcon,
[IconType.Close]: CloseIcon,
};
type Props = {

View File

@@ -44,8 +44,8 @@ export const NotesOptions = observer(
useEffect(() => {
const openTrashAlert = async () => {
if (shouldTrashNotes && blurLocked) {
await appState.notes.setTrashSelectedNotes(!trashed, trashButtonRef);
setShouldTrashNotes(false);
await appState.notes.setTrashSelectedNotes(!trashed, trashButtonRef);
setLockCloseOnBlur(false);
}
};
@@ -190,9 +190,23 @@ export const NotesOptions = observer(
setLockCloseOnBlur(true);
}}
>
<Icon type={IconType.Trash} className={iconClass} />
<Icon type={trashed ? IconType.Restore : IconType.Trash} className={iconClass} />
{trashed ? 'Restore' : 'Move to Trash'}
</button>
{appState.selectedTag?.isTrashTag && (
<button
onBlur={closeOnBlur}
className={`${buttonClass} py-1.5`}
onClick={async () => {
setLockCloseOnBlur(true);
await appState.notes.deleteNotesPermanently();
setLockCloseOnBlur(false);
}}
>
<Icon type={IconType.Close} className="fill-current color-danger mr-2" />
<span className="color-danger">Delete Permanently</span>
</button>
)}
</>
);
}

View File

@@ -4,7 +4,6 @@ import template from '%/directives/account-menu.pug';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import {
STRING_ACCOUNT_MENU_UNCHECK_MERGE,
STRING_SIGN_OUT_CONFIRMATION,
STRING_E2E_ENABLED,
STRING_LOCAL_ENC_ENABLED,
STRING_ENC_NOT_ENABLED,
@@ -18,7 +17,7 @@ import {
STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_CHANGE,
STRING_CONFIRM_APP_QUIT_DURING_PASSCODE_REMOVAL,
STRING_UNSUPPORTED_BACKUP_FILE_VERSION,
Strings,
StringUtils,
} from '@/strings';
import { PasswordWizardType } from '@/types';
import {
@@ -110,7 +109,7 @@ class AccountMenuCtrl extends PureViewCtrl<unknown, AccountMenuState> {
storage.get(StorageKey.DisableErrorReporting) === false,
showSessions: false,
errorReportingId: errorReportingId(),
keyStorageInfo: Strings.keyStorageInfo(this.application),
keyStorageInfo: StringUtils.keyStorageInfo(this.application),
importData: null,
syncInProgress: false,
protectionsDisabledUntil: this.getProtectionsDisabledUntil(),

View File

@@ -109,6 +109,13 @@ export const STRING_UPGRADE_ACCOUNT_CONFIRM_TEXT =
export const STRING_UPGRADE_ACCOUNT_CONFIRM_BUTTON = 'Upgrade';
export const Strings = {
protectingNoteWithoutProtectionSources: 'Access to this note will not be restricted until you set up a passcode or account.',
openAccountMenu: 'Open Account Menu',
trashNotesTitle: 'Move to Trash',
trashNotesText: 'Are you sure you want to move these notes to the trash?',
};
export const StringUtils = {
keyStorageInfo(application: SNApplication): string | null {
if (!isDesktopApplication()) {
return null;
@@ -125,8 +132,26 @@ export const Strings = {
: 'password manager';
return `Your keys are currently stored in your operating system's ${keychainName}. Adding a passcode prevents even your operating system from reading them.`;
},
protectingNoteWithoutProtectionSources: 'Access to this note will not be restricted until you set up a passcode or account.',
openAccountMenu: 'Open Account Menu',
trashNotesTitle: 'Move To Trash',
trashNotesText: 'Are you sure you want to move these notes to the trash?'
};
deleteNotes(permanently: boolean, notesCount = 1, title?: string): string {
if (notesCount === 1) {
return permanently
? `Are you sure you want to permanently delete ${title}?`
: `Are you sure you want to move ${title} to the trash?`;
} else {
return permanently
? `Are you sure you want to permanently delete these notes?`
: `Are you sure you want to move these notes to the trash?`;
}
},
archiveLockedNotesAttempt(archive: boolean, notesCount = 1): string {
const archiveString = archive ? 'archive' : 'unarchive';
return notesCount === 1
? `This note has editing disabled. If you'd like to ${archiveString} it, enable editing, and try again.`
: `One or more of these notes have editing disabled. If you'd like to ${archiveString} them, make sure editing is enabled on all of them, and try again.`;
},
deleteLockedNotesAttempt(notesCount = 1): string {
return notesCount === 1
? "This note has editing disabled. If you'd like to delete it, enable editing, and try again."
: "One or more of these notes have editing disabled. If you'd like to delete them, make sure editing is enabled on all of them, and try again.";
},
};

View File

@@ -1,6 +1,6 @@
import { confirmDialog } from '@/services/alertService';
import { KeyboardModifier } from '@/services/ioService';
import { Strings } from '@/strings';
import { Strings, StringUtils } from '@/strings';
import {
UuidString,
SNNote,
@@ -36,6 +36,7 @@ export class NotesState {
selectedNotesCount: computed,
deleteNotesPermanently: action,
selectNote: action,
setArchiveSelectedNotes: action,
setContextMenuOpen: action,
@@ -158,14 +159,17 @@ export class NotesState {
trashed: boolean,
trashButtonRef: RefObject<HTMLButtonElement>
): Promise<void> {
if (
!trashed ||
(await confirmDialog({
title: Strings.trashNotesTitle,
text: Strings.trashNotesText,
confirmButtonStyle: 'danger',
}))
) {
if (trashed) {
const notesDeleted = await this.deleteNotes(false);
if (notesDeleted) {
runInAction(() => {
this.selectedNotes = {};
this.contextMenuOpen = false;
});
} else {
trashButtonRef.current?.focus();
}
} else {
this.application.changeItems<NoteMutator>(
Object.keys(this.selectedNotes),
(mutator) => {
@@ -177,11 +181,62 @@ export class NotesState {
this.selectedNotes = {};
this.contextMenuOpen = false;
});
} else {
trashButtonRef.current?.focus();
}
}
async deleteNotesPermanently(): Promise<void> {
await this.deleteNotes(true);
}
async deleteNotes(permanently: boolean): Promise<boolean> {
if (Object.values(this.selectedNotes).some((note) => note.locked)) {
const text = StringUtils.deleteLockedNotesAttempt(
this.selectedNotesCount
);
this.application.alertService.alert(text);
return false;
}
const title = Strings.trashNotesTitle;
let noteTitle = undefined;
if (this.selectedNotesCount === 1) {
const selectedNote = Object.values(this.selectedNotes)[0];
noteTitle = selectedNote.safeTitle().length
? `'${selectedNote.title}'`
: 'this note';
}
const text = StringUtils.deleteNotes(
permanently,
this.selectedNotesCount,
noteTitle
);
if (
await confirmDialog({
title,
text,
confirmButtonStyle: 'danger',
})
) {
if (permanently) {
for (const note of Object.values(this.selectedNotes)) {
await this.application.deleteItem(note);
}
} else {
this.application.changeItems<NoteMutator>(
Object.keys(this.selectedNotes),
(mutator) => {
mutator.trashed = true;
},
false
);
}
return true;
}
return false;
}
setPinSelectedNotes(pinned: boolean): void {
this.application.changeItems<NoteMutator>(
Object.keys(this.selectedNotes),
@@ -192,7 +247,13 @@ export class NotesState {
);
}
setArchiveSelectedNotes(archived: boolean): void {
async setArchiveSelectedNotes(archived: boolean): Promise<void> {
if (Object.values(this.selectedNotes).some((note) => note.locked)) {
this.application.alertService.alert(
StringUtils.archiveLockedNotesAttempt(archived, this.selectedNotesCount)
);
return;
}
this.application.changeItems<NoteMutator>(
Object.keys(this.selectedNotes),
(mutator) => {
@@ -201,6 +262,7 @@ export class NotesState {
);
runInAction(() => {
this.selectedNotes = {};
this.contextMenuOpen = false;
});
}

View File

@@ -117,7 +117,7 @@
stylekit-class="'warning'"
)
menu-row(
action='self.selectedMenuItem(); self.deleteNotePermanantely()'
action='self.selectedMenuItem(); self.deleteNotePermanently()'
desc="'Delete this note permanently from all your devices'",
label="'Delete Permanently'",
ng-show='!self.note.trashed && self.note.errorDecrypting',
@@ -132,7 +132,7 @@
stylekit-class="'info'"
)
menu-row(
action='self.selectedMenuItem(true); self.deleteNotePermanantely()'
action='self.selectedMenuItem(true); self.deleteNotePermanently()'
desc="'Delete this note permanently from all your devices'",
label="'Delete Permanently'",
stylekit-class="'danger'"

View File

@@ -712,7 +712,7 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
);
}
deleteNotePermanantely() {
deleteNotePermanently() {
this.deleteNote(true);
}