This commit is contained in:
Mo Bitar
2020-04-17 16:41:51 -05:00
parent 8050c9cccd
commit 851269200b
15 changed files with 521 additions and 487 deletions

View File

@@ -0,0 +1,36 @@
.sk-modal-background(ng-click="ctrl.cancel()")
#privileges-modal.sk-modal-content
.sn-component
.sk-panel
.sk-panel-header
.sk-panel-header-title Authentication Required
a.close-button.info(
ng-if="ctrl.cancelable"
ng-click="ctrl.cancel()"
) Cancel
.sk-panel-content
.sk-panel-section
div(ng-repeat="type in ctrl.state.types")
.sk-p.sk-bold.sk-panel-row
strong {{ctrl.promptForChallenge(type)}}
.sk-panel-row
input.sk-input.contrast(
ng-model="ctrl.state.values[type].value"
should-focus="$index == 0"
sn-autofocus="true"
sn-enter="ctrl.submit()" ,
ng-change="ctrl.onTextValueChange(type)"
type="password"
)
.sk-panel-row
label.sk-label.danger(
ng-if="ctrl.state.values[type].invalid"
) Invalid authentication. Please try again.
.sk-panel-row
.sk-panel-footer.extra-padding
.sk-button.info.big.block.bold(
ng-click="ctrl.submit()",
ng-class="{'info' : !ctrl.state.processing, 'neutral': ctrl.state.processing}"
ng-disabled="ctrl.state.processing"
)
.sk-label {{ctrl.state.processing ? 'Generating Keys...' : 'Submit'}}

View File

@@ -0,0 +1,169 @@
import { WebApplication } from '@/ui_models/application';
import template from './challenge-modal.pug';
import {
ChallengeType,
ChallengeValue,
removeFromArray,
Challenge,
ChallengeOrchestrator
} from 'snjs';
import { PureViewCtrl } from '@Views/abstract/pure_view_ctrl';
import { WebDirective } from '@/types';
type InputValue = {
value: string
invalid: boolean
}
type ChallengeModalScope = {
application: WebApplication
challenge: Challenge
orchestrator: ChallengeOrchestrator
}
type Values = Record<ChallengeType, InputValue>
type ChallengeModalState = {
types: ChallengeType[]
values: Partial<Values>
processing: boolean
}
class ChallengeModalCtrl extends PureViewCtrl implements ChallengeModalScope {
private $element: JQLite
private processingTypes: ChallengeType[] = []
application!: WebApplication
challenge!: Challenge
orchestrator!: ChallengeOrchestrator
/* @ngInject */
constructor(
$element: JQLite,
$timeout: ng.ITimeoutService
) {
super($timeout);
this.$element = $element;
}
getState() {
return this.state as ChallengeModalState;
}
$onInit() {
super.$onInit();
const values = {} as Values;
const types = this.challenge.types;
for (const type of types) {
values[type] = {
value: '',
invalid: false
};
}
this.setState({
types: types,
values: values,
processing: false
});
this.orchestrator.setCallbacks(
(value) => {
this.getState().values[value.type]!.invalid = false;
removeFromArray(this.processingTypes, value.type);
this.reloadProcessingStatus();
},
(value) => {
this.getState().values[value.type]!.invalid = true;
removeFromArray(this.processingTypes, value.type);
this.reloadProcessingStatus();
},
() => {
this.dismiss();
},
);
}
deinit() {
(this.application as any) = undefined;
(this.orchestrator as any) = undefined;
(this.challenge as any) = undefined;
super.deinit();
}
reloadProcessingStatus() {
this.setState({
processing: this.processingTypes.length > 0
});
}
promptForChallenge(challenge: ChallengeType) {
if (challenge === ChallengeType.LocalPasscode) {
return 'Enter your application passcode';
} else {
return 'Enter your account password';
}
}
cancel() {
// if (!this.cancelable) {
// return;
// }
this.dismiss();
}
onTextValueChange(challenge: ChallengeType) {
const values = this.getState().values;
values[challenge]!.invalid = false;
this.setState({ values });
}
validate() {
const failed = [];
for (const type of this.getState().types) {
const value = this.getState().values[type];
if (!value || value.value.length === 0) {
this.getState().values[type]!.invalid = true;
}
}
return failed.length === 0;
}
async submit() {
if (!this.validate()) {
return;
}
await this.setState({ processing: true });
const values = [];
for (const key of Object.keys(this.getState().values)) {
const type = Number(key) as ChallengeType;
if (this.getState().values[type]!.invalid) {
continue;
}
const rawValue = this.getState().values[type]!.value;
const value = new ChallengeValue(type, rawValue);
values.push(value);
}
this.processingTypes = values.map((v) => v.type);
this.orchestrator.submitValues(values);
}
dismiss() {
const elem = this.$element;
const scope = elem.scope();
scope.$destroy();
elem.remove();
}
}
export class ChallengeModal extends WebDirective {
constructor() {
super();
this.restrict = 'E';
this.template = template;
this.controller = ChallengeModalCtrl;
this.controllerAs = 'ctrl';
this.bindToController = true;
this.scope = {
challenge: '=',
orchestrator: '=',
application: '='
};
}
}

View File

@@ -8,7 +8,8 @@ import {
SNComponent,
SNTheme,
ComponentArea,
ComponentAction
ComponentAction,
topLevelCompare
} from 'snjs';
import template from './footer-view.pug';
import { AppStateEvent, EventSource } from '@/ui_models/app_state';
@@ -231,10 +232,13 @@ class FooterViewCtrl extends PureViewCtrl {
activationHandler: () => { },
actionHandler: (component, action, data) => {
if (action === ComponentAction.SetSize) {
this.application!.changeItem(component.uuid, (m) => {
const mutator = m as ComponentMutator;
mutator.setLastSize(data);
})
/** Do comparison to avoid repetitive calls by arbitrary component */
if(!topLevelCompare(component.getLastSize(), data)) {
this.application!.changeItem(component.uuid, (m) => {
const mutator = m as ComponentMutator;
mutator.setLastSize(data);
})
}
}
},
focusHandler: (component, focused) => {

View File

@@ -1,11 +1,9 @@
export { PureViewCtrl } from './abstract/pure_view_ctrl';
export { ApplicationGroupView } from './application_group/application_group_view';
export { ApplicationView } from './application/application_view';
export { EditorGroupView } from './editor_group/editor_group_view';
export { EditorView } from './editor/editor_view';
export { FooterView } from './footer/footer_view';
export { NotesView } from './notes/notes_view';
export { TagsView } from './tags/tags_view';
export { TagsView } from './tags/tags_view';
export { ChallengeModal } from './challenge_modal/challenge_modal'

View File

@@ -1,3 +1,4 @@
import { Editor } from '@/ui_models/editor';
import { PanelPuppet, WebDirective } from './../../types';
import angular from 'angular';
import template from './notes-view.pug';
@@ -26,6 +27,7 @@ type NotesState = {
panelTitle: string
notes?: SNNote[]
renderedNotes?: SNNote[]
activeEditor: Editor
sortBy?: string
sortReverse?: boolean
showArchived?: boolean
@@ -62,6 +64,7 @@ class NotesViewCtrl extends PureViewCtrl {
private previousNoteKeyObserver: any
private searchKeyObserver: any
private noteFlags: Partial<Record<UuidString, NoteFlag[]>> = {}
private unsubEditorChange: any
/* @ngInject */
constructor($timeout: ng.ITimeoutService, ) {
@@ -77,6 +80,11 @@ class NotesViewCtrl extends PureViewCtrl {
this.panelPuppet = {
onReady: () => this.reloadPreferences()
};
this.unsubEditorChange = this.application.editorGroup.addChangeObserver(() => {
this.setNotesState({
activeEditor: this.application.editorGroup.activeEditor
});
})
this.onWindowResize = this.onWindowResize.bind(this);
this.onPanelResize = this.onPanelResize.bind(this);
window.addEventListener('resize', this.onWindowResize, true);
@@ -90,6 +98,8 @@ class NotesViewCtrl extends PureViewCtrl {
deinit() {
this.panelPuppet!.onReady = undefined;
this.panelPuppet = undefined;
this.unsubEditorChange();
this.unsubEditorChange = undefined;
window.removeEventListener('resize', this.onWindowResize, true);
(this.onWindowResize as any) = undefined;
(this.onPanelResize as any) = undefined;
@@ -142,9 +152,9 @@ class NotesViewCtrl extends PureViewCtrl {
}
}
get activeEditorNote() {
const activeEditor = this.appState.getActiveEditor();
return activeEditor && activeEditor.note;
/** @template */
public get activeEditorNote() {
return this.getState().activeEditor?.note;
}
public get editorNotes() {