fix: add delete permanently option, restore icon and show alerts for locked notes
This commit is contained in:
@@ -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 = {
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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.";
|
||||
},
|
||||
};
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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'"
|
||||
|
||||
@@ -712,7 +712,7 @@ class EditorViewCtrl extends PureViewCtrl<unknown, EditorState> {
|
||||
);
|
||||
}
|
||||
|
||||
deleteNotePermanantely() {
|
||||
deleteNotePermanently() {
|
||||
this.deleteNote(true);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user