diff --git a/app/assets/icons/ic-restore.svg b/app/assets/icons/ic-restore.svg
new file mode 100644
index 000000000..7b59194bc
--- /dev/null
+++ b/app/assets/icons/ic-restore.svg
@@ -0,0 +1,3 @@
+
diff --git a/app/assets/javascripts/components/Icon.tsx b/app/assets/javascripts/components/Icon.tsx
index 8702fa60d..6fb7dbeda 100644
--- a/app/assets/javascripts/components/Icon.tsx
+++ b/app/assets/javascripts/components/Icon.tsx
@@ -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 = {
diff --git a/app/assets/javascripts/components/NotesOptions.tsx b/app/assets/javascripts/components/NotesOptions.tsx
index 41b5bfb0e..2e804d94e 100644
--- a/app/assets/javascripts/components/NotesOptions.tsx
+++ b/app/assets/javascripts/components/NotesOptions.tsx
@@ -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);
}}
>
-
+
{trashed ? 'Restore' : 'Move to Trash'}
+ {appState.selectedTag?.isTrashTag && (
+
+ )}
>
);
}
diff --git a/app/assets/javascripts/directives/views/accountMenu.ts b/app/assets/javascripts/directives/views/accountMenu.ts
index 1772e7975..abea399ab 100644
--- a/app/assets/javascripts/directives/views/accountMenu.ts
+++ b/app/assets/javascripts/directives/views/accountMenu.ts
@@ -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 {
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(),
diff --git a/app/assets/javascripts/strings.ts b/app/assets/javascripts/strings.ts
index b3de2183b..1c112f341 100644
--- a/app/assets/javascripts/strings.ts
+++ b/app/assets/javascripts/strings.ts
@@ -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.";
+ },
+};
\ No newline at end of file
diff --git a/app/assets/javascripts/ui_models/app_state/notes_state.ts b/app/assets/javascripts/ui_models/app_state/notes_state.ts
index 585a0aca3..e3324b86c 100644
--- a/app/assets/javascripts/ui_models/app_state/notes_state.ts
+++ b/app/assets/javascripts/ui_models/app_state/notes_state.ts
@@ -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
): Promise {
- 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(
Object.keys(this.selectedNotes),
(mutator) => {
@@ -177,11 +181,62 @@ export class NotesState {
this.selectedNotes = {};
this.contextMenuOpen = false;
});
- } else {
- trashButtonRef.current?.focus();
}
}
+ async deleteNotesPermanently(): Promise {
+ await this.deleteNotes(true);
+ }
+
+ async deleteNotes(permanently: boolean): Promise {
+ 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(
+ Object.keys(this.selectedNotes),
+ (mutator) => {
+ mutator.trashed = true;
+ },
+ false
+ );
+ }
+ return true;
+ }
+
+ return false;
+ }
+
setPinSelectedNotes(pinned: boolean): void {
this.application.changeItems(
Object.keys(this.selectedNotes),
@@ -192,7 +247,13 @@ export class NotesState {
);
}
- setArchiveSelectedNotes(archived: boolean): void {
+ async setArchiveSelectedNotes(archived: boolean): Promise {
+ if (Object.values(this.selectedNotes).some((note) => note.locked)) {
+ this.application.alertService.alert(
+ StringUtils.archiveLockedNotesAttempt(archived, this.selectedNotesCount)
+ );
+ return;
+ }
this.application.changeItems(
Object.keys(this.selectedNotes),
(mutator) => {
@@ -201,6 +262,7 @@ export class NotesState {
);
runInAction(() => {
this.selectedNotes = {};
+ this.contextMenuOpen = false;
});
}
diff --git a/app/assets/javascripts/views/editor/editor-view.pug b/app/assets/javascripts/views/editor/editor-view.pug
index cf0d5e751..273a96e10 100644
--- a/app/assets/javascripts/views/editor/editor-view.pug
+++ b/app/assets/javascripts/views/editor/editor-view.pug
@@ -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'"
diff --git a/app/assets/javascripts/views/editor/editor_view.ts b/app/assets/javascripts/views/editor/editor_view.ts
index c01f4a101..2092e990e 100644
--- a/app/assets/javascripts/views/editor/editor_view.ts
+++ b/app/assets/javascripts/views/editor/editor_view.ts
@@ -712,7 +712,7 @@ class EditorViewCtrl extends PureViewCtrl {
);
}
- deleteNotePermanantely() {
+ deleteNotePermanently() {
this.deleteNote(true);
}
diff --git a/app/assets/stylesheets/_sn.scss b/app/assets/stylesheets/_sn.scss
index df6d0af1b..3c55ae8f8 100644
--- a/app/assets/stylesheets/_sn.scss
+++ b/app/assets/stylesheets/_sn.scss
@@ -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);
}