fix: add delete permanently option, restore icon and show alerts for locked notes
This commit is contained in:
3
app/assets/icons/ic-restore.svg
Normal file
3
app/assets/icons/ic-restore.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10 2.5C8.01088 2.5 6.10322 3.29018 4.6967 4.6967C3.29018 6.10322 2.5 8.01088 2.5 10H0L3.33333 13.3333L6.66667 10H4.16667C4.16667 8.4529 4.78125 6.96917 5.87521 5.87521C6.96917 4.78125 8.4529 4.16667 10 4.16667C11.5471 4.16667 13.0308 4.78125 14.1248 5.87521C15.2188 6.96917 15.8333 8.4529 15.8333 10C15.8333 11.5471 15.2188 13.0308 14.1248 14.1248C13.0308 15.2188 11.5471 15.8333 10 15.8333C8.75 15.8333 7.575 15.4167 6.61667 14.75L5.41667 15.95C6.7 16.9167 8.28333 17.5 10 17.5C11.9891 17.5 13.8968 16.7098 15.3033 15.3033C16.7098 13.8968 17.5 11.9891 17.5 10C17.5 8.01088 16.7098 6.10322 15.3033 4.6967C13.8968 3.29018 11.9891 2.5 10 2.5ZM11.6667 10C11.6667 9.55797 11.4911 9.13405 11.1785 8.82149C10.8659 8.50893 10.442 8.33333 10 8.33333C9.55797 8.33333 9.13405 8.50893 8.82149 8.82149C8.50893 9.13405 8.33333 9.55797 8.33333 10C8.33333 10.442 8.50893 10.866 8.82149 11.1785C9.13405 11.4911 9.55797 11.6667 10 11.6667C10.442 11.6667 10.8659 11.4911 11.1785 11.1785C11.4911 10.866 11.6667 10.442 11.6667 10Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,10 @@
|
||||
color: var(--sn-stylekit-foreground-color);
|
||||
}
|
||||
|
||||
.color-danger {
|
||||
color: var(--sn-stylekit-danger-color);
|
||||
}
|
||||
|
||||
.ring-info {
|
||||
box-shadow: 0 0 0 2px var(--sn-stylekit-info-color);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user