diff --git a/app/assets/icons/ic-textbox-password.svg b/app/assets/icons/ic-textbox-password.svg
new file mode 100644
index 000000000..a5edc4e38
--- /dev/null
+++ b/app/assets/icons/ic-textbox-password.svg
@@ -0,0 +1,3 @@
+
diff --git a/app/assets/javascripts/components/Icon.tsx b/app/assets/javascripts/components/Icon.tsx
index 45a6431ff..39e893d2b 100644
--- a/app/assets/javascripts/components/Icon.tsx
+++ b/app/assets/javascripts/components/Icon.tsx
@@ -9,6 +9,7 @@ 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 PasswordIcon from '../../icons/ic-textbox-password.svg';
import { toDirective } from './utils';
const ICONS = {
@@ -23,6 +24,7 @@ const ICONS = {
'chevron-right': ChevronRightIcon,
'restore': RestoreIcon,
'close': CloseIcon,
+ 'password': PasswordIcon,
};
type Props = {
diff --git a/app/assets/javascripts/components/NotesOptions.tsx b/app/assets/javascripts/components/NotesOptions.tsx
index 11a928da6..4fd6d5f89 100644
--- a/app/assets/javascripts/components/NotesOptions.tsx
+++ b/app/assets/javascripts/components/NotesOptions.tsx
@@ -35,6 +35,7 @@ export const NotesOptions = observer(
const notes = Object.values(appState.notes.selectedNotes);
const hidePreviews = toggleOn(note => note.hidePreview);
const locked = toggleOn(note => note.locked);
+ const protect = toggleOn(note => note.protected);
const archived = notes.some((note) => note.archived);
const unarchived = notes.some((note) => !note.archived);
const trashed = notes.some((note) => note.trashed);
@@ -84,6 +85,19 @@ export const NotesOptions = observer(
Show preview
+ {
+ appState.notes.setProtectSelectedNotes(!protect);
+ }}
+ >
+
+
+ Protect
+
+
{appState.tags.tagsCount > 0 && (
void, notes: SNNote[]): Promise {
+ let protectedNotesAccessRequest: Promise;
+ await Promise.all(
+ notes.map(async (note) => {
+ if (note.protected) {
+ if (!protectedNotesAccessRequest) {
+ protectedNotesAccessRequest =
+ this.application.authorizeNoteAccess(note);
+ }
+ }
+ if (!note.protected || await protectedNotesAccessRequest) {
+ action(note);
+ }
+ })
+ );
+ }
+
async selectNotesRange(selectedNote: SNNote): Promise {
const notes = this.application.getDisplayableItems(
ContentType.Note
@@ -85,32 +105,18 @@ export class NotesState {
const selectedNoteIndex = notes.findIndex(
(note) => note.uuid == selectedNote.uuid
);
- let protectedNotesAccessRequest: Promise;
- let notesToSelect = [];
+ let notesToSelect = [];
if (selectedNoteIndex > lastSelectedNoteIndex) {
notesToSelect = notes.slice(lastSelectedNoteIndex, selectedNoteIndex + 1);
} else {
notesToSelect = notes.slice(selectedNoteIndex, lastSelectedNoteIndex + 1);
}
- await Promise.all(
- notesToSelect.map(async (note) => {
- const requestAccess =
- note.protected && this.application.hasProtectionSources();
- if (requestAccess) {
- if (!protectedNotesAccessRequest) {
- protectedNotesAccessRequest =
- this.application.authorizeNoteAccess(note);
- }
- }
- if (!requestAccess || (await protectedNotesAccessRequest)) {
- this.selectedNotes[note.uuid] = note;
- }
- })
- );
-
- this.lastSelectedNote = selectedNote;
+ this.runProtectedAction((note) => {
+ this.selectedNotes[note.uuid] = note;
+ this.lastSelectedNote = selectedNote;
+ }, notesToSelect);
}
async selectNote(note: SNNote): Promise {
@@ -211,7 +217,7 @@ export class NotesState {
});
}
} else {
- this.changeSelectedNotes((mutator) => {
+ await this.changeSelectedNotes((mutator) => {
mutator.trashed = trashed;
});
this.unselectNotes();
@@ -258,7 +264,7 @@ export class NotesState {
await this.application.deleteItem(note);
}
} else {
- this.changeSelectedNotes((mutator) => {
+ await this.changeSelectedNotes((mutator) => {
mutator.trashed = true;
});
}
@@ -282,7 +288,7 @@ export class NotesState {
return;
}
- this.changeSelectedNotes((mutator) => {
+ await this.changeSelectedNotes((mutator) => {
mutator.archived = archived;
});
@@ -292,6 +298,25 @@ export class NotesState {
});
}
+ async setProtectSelectedNotes(protect: boolean): Promise {
+ if (protect) {
+ await this.changeSelectedNotes((mutator) => {
+ mutator.protected = protect;
+ });
+ if (!this.application.hasProtectionSources()) {
+ this.setShowProtectedWarning(true);
+ }
+ } else {
+ const selectedNotes = Object.values(this.selectedNotes);
+ this.runProtectedAction(async (note) => {
+ await this.application.changeItem(note.uuid, (mutator) => {
+ mutator.protected = protect;
+ });
+ }, selectedNotes);
+ this.setShowProtectedWarning(false);
+ }
+ }
+
unselectNotes(): void {
this.selectedNotes = {};
}
@@ -326,6 +351,10 @@ export class NotesState {
);
}
+ setShowProtectedWarning(show: boolean): void {
+ this.showProtectedWarning = show;
+ }
+
private get io() {
return this.application.io;
}
diff --git a/app/assets/javascripts/views/editor/editor-view.pug b/app/assets/javascripts/views/editor/editor-view.pug
index 273a96e10..75c75f6ab 100644
--- a/app/assets/javascripts/views/editor/editor-view.pug
+++ b/app/assets/javascripts/views/editor/editor-view.pug
@@ -1,11 +1,11 @@
#editor-column.section.editor.sn-component(aria-label='Note')
protected-note-panel.h-full.flex.justify-center.items-center(
- ng-if='self.state.showProtectedWarning'
+ ng-if='self.appState.notes.showProtectedWarning'
app-state='self.appState'
on-view-note='self.dismissProtectedWarning()'
)
.flex-grow.flex.flex-col(
- ng-if='!self.state.showProtectedWarning'
+ ng-if='!self.appState.notes.showProtectedWarning'
)
.sn-component
.sk-app-bar.no-edges(
diff --git a/app/assets/javascripts/views/editor/editor_view.ts b/app/assets/javascripts/views/editor/editor_view.ts
index 2092e990e..8f66ccb96 100644
--- a/app/assets/javascripts/views/editor/editor_view.ts
+++ b/app/assets/javascripts/views/editor/editor_view.ts
@@ -85,7 +85,6 @@ type EditorState = {
textareaUnloading: boolean;
/** Fields that can be directly mutated by the template */
mutable: any;
- showProtectedWarning: boolean;
};
type EditorValues = {
@@ -241,7 +240,6 @@ class EditorViewCtrl extends PureViewCtrl {
mutable: {
tagsString: '',
},
- showProtectedWarning: false,
} as EditorState;
}
@@ -291,8 +289,8 @@ class EditorViewCtrl extends PureViewCtrl {
async handleEditorNoteChange() {
this.cancelPendingSetStatus();
const note = this.editor.note;
- const showProtectedWarning =
- note.protected && !this.application.hasProtectionSources();
+ const showProtectedWarning = note.protected && !this.application.hasProtectionSources();
+ this.setShowProtectedWarning(showProtectedWarning);
await this.setState({
showActionsMenu: false,
showOptionsMenu: false,
@@ -300,7 +298,6 @@ class EditorViewCtrl extends PureViewCtrl {
showHistoryMenu: false,
altKeyDown: false,
noteStatus: undefined,
- showProtectedWarning,
});
this.editorValues.title = note.title;
this.editorValues.text = note.text;
@@ -318,9 +315,7 @@ class EditorViewCtrl extends PureViewCtrl {
}
async dismissProtectedWarning() {
- await this.setState({
- showProtectedWarning: false,
- });
+ this.setShowProtectedWarning(false);
this.focusTitle();
}
@@ -658,6 +653,10 @@ class EditorViewCtrl extends PureViewCtrl {
}
}
+ setShowProtectedWarning(show: boolean) {
+ this.application.getAppState().notes.setShowProtectedWarning(show);
+ }
+
async deleteNote(permanently: boolean) {
if (this.editor.isTemplateNote) {
this.application.alertService!.alert(STRING_DELETE_PLACEHOLDER_ATTEMPT);
@@ -767,9 +766,7 @@ class EditorViewCtrl extends PureViewCtrl {
} else {
const note = await this.application.protectNote(this.note);
if (note?.protected && !this.application.hasProtectionSources()) {
- this.setState({
- showProtectedWarning: true,
- });
+ this.setShowProtectedWarning(true);
}
}
}