From ef19c0db020cbee54af046b720ccf4f5dd716796 Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Mon, 25 Nov 2019 18:18:30 -0500 Subject: [PATCH] LPXXX editor continued Signed-off-by: Bill Erickson --- .../marc-edit/editable-content.component.html | 4 +- .../share/marc-edit/editable-content.component.ts | 54 +++++++++---- .../app/staff/share/marc-edit/editor-context.ts | 93 +++++++++++++++++++++- 3 files changed, 131 insertions(+), 20 deletions(-) diff --git a/Open-ILS/src/eg2/src/app/staff/share/marc-edit/editable-content.component.html b/Open-ILS/src/eg2/src/app/staff/share/marc-edit/editable-content.component.html index e5da09ba0b..25289e102b 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/marc-edit/editable-content.component.html +++ b/Open-ILS/src/eg2/src/app/staff/share/marc-edit/editable-content.component.html @@ -8,7 +8,7 @@ [egContextMenu]="contextMenuEntries()" (menuItemSelected)="contextMenuChange($event.value)" (keydown)="inputKeyDown($event)" - (focus)="focusBigText()" + (focus)="selectText()" (input)="bigTextValueChange()"> @@ -25,7 +25,7 @@ [egContextMenu]="contextMenuEntries()" (menuItemSelected)="contextMenuChange($event.value)" (keydown)="inputKeyDown($event)" - (focus)="$event.target.select()" + (focus)="selectText()" [ngModel]="getContent()" (ngModelChange)="setContent($event)" /> diff --git a/Open-ILS/src/eg2/src/app/staff/share/marc-edit/editable-content.component.ts b/Open-ILS/src/eg2/src/app/staff/share/marc-edit/editable-content.component.ts index 62ed01eee1..ac7f5ebd7b 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/marc-edit/editable-content.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/marc-edit/editable-content.component.ts @@ -2,7 +2,7 @@ import {ElementRef, Component, Input, Output, OnInit, EventEmitter, AfterViewInit, Renderer2} from '@angular/core'; import {filter} from 'rxjs/operators'; import {MarcRecord, MarcField, MarcSubfield} from './marcrecord'; -import {MarcEditContext, FieldFocusRequest} from './editor-context'; +import {MarcEditContext, FieldFocusRequest, MARC_EDITABLE_FIELD_TYPE} from './editor-context'; import {ContextMenuEntry} from '@eg/share/context-menu/context-menu.service'; import {TagTableService} from './tagtable.service'; @@ -20,7 +20,7 @@ export class EditableContentComponent implements OnInit, AfterViewInit { @Input() context: MarcEditContext; @Input() field: MarcField; - @Input() fieldType: 'ldr' | 'tag' | 'cfld' | 'ind1' | 'ind2' | 'sfc' | 'sfv' = null; + @Input() fieldType: MARC_EDITABLE_FIELD_TYPE = null; // read-only field text. E.g. 'LDR' @Input() fieldText: string = null; @@ -64,14 +64,31 @@ export class EditableContentComponent implements OnInit, AfterViewInit { return; } - if (this.bigText) { - this.focusBigText(); - } else { - this.editInput.select(); - } + this.selectText(req); }); } + selectText(req?: FieldFocusRequest) { + if (this.bigText) { + this.focusBigText(); + } else { + this.editInput.select(); + } + + if (!req) { + // Focus request may have come from keyboard navigation, + // clicking, etc. Model the event as a focus request + // so our context can track it just like an auto-focus. + req = { + fieldId: this.field.fieldId, + target: this.fieldType, + sfOffset: this.subfield ? this.subfield[2] : null + }; + } + + this.context.lastFocused = req; + } + setupFieldType() { const content = this.getContent(); @@ -205,7 +222,7 @@ export class EditableContentComponent implements OnInit, AfterViewInit { if (evt.ctrlKey) { // ctrl+enter == insert stub field after focused field // ctrl+shift+enter == insert stub field before focused field - this.insertField(evt.shiftKey); + this.context.insertStubField(this.field, evt.shiftKey); } evt.preventDefault(); // Bare newlines not allowed. @@ -215,39 +232,44 @@ export class EditableContentComponent implements OnInit, AfterViewInit { if (evt.ctrlKey) { // ctrl+delete == delete whole field - this.deleteField(); + this.context.deleteField(this.field); evt.preventDefault(); } else if (evt.shiftKey && this.subfield) { // shift+delete == delete subfield - this.deleteSubfield(); + this.context.deleteSubfield(this.field, this.subfield); evt.preventDefault(); } break; case 'ArrowDown': + if (evt.ctrlKey) { // ctrl+down == copy current field down one - this.record.insertFieldsAfter( + this.context.insertField( this.field, this.record.cloneField(this.field)); + } else { + // avoid dupe focus requests + this.context.focusNextTag(this.field); } - // down == move focus to tag of previous field - this.context.focusNextTag(this.field); evt.preventDefault(); break; case 'ArrowUp': + if (evt.ctrlKey) { // ctrl+up == copy current field up one - this.record.insertFieldsBefore( - this.field, this.record.cloneField(this.field)); + this.context.insertField( + this.field, this.record.cloneField(this.field), true); + } else { + // avoid dupe focus requests + this.context.focusPreviousTag(this.field); } // up == move focus to tag of previous field - this.context.focusPreviousTag(this.field); evt.preventDefault(); break; diff --git a/Open-ILS/src/eg2/src/app/staff/share/marc-edit/editor-context.ts b/Open-ILS/src/eg2/src/app/staff/share/marc-edit/editor-context.ts index 56655f0ffd..11ce5fb2ec 100644 --- a/Open-ILS/src/eg2/src/app/staff/share/marc-edit/editor-context.ts +++ b/Open-ILS/src/eg2/src/app/staff/share/marc-edit/editor-context.ts @@ -4,20 +4,38 @@ import {NgbPopover} from '@ng-bootstrap/ng-bootstrap'; /* Per-instance MARC editor context. */ +export type MARC_EDITABLE_FIELD_TYPE = + 'ldr' | 'tag' | 'cfld' | 'ind1' | 'ind2' | 'sfc' | 'sfv'; + export interface FieldFocusRequest { fieldId: number; - target: 'tag' | 'sfc' | 'sfv'; + target: MARC_EDITABLE_FIELD_TYPE; + + // in some cases, field IDs change out from under our feet (e.g. + // undo / redo applications) so track the tag offset as well + // so we can find our way back if needed. + tagOffset?: number; // focus a specific subfield by its offset sfOffset?: number; } +export interface UndoRedoAction { + breakerText: string; + position: FieldFocusRequest; +} + export class MarcEditContext { recordChange: EventEmitter; fieldFocusRequest: EventEmitter; recordType: 'biblio' | 'authority' = 'biblio'; + lastFocused: FieldFocusRequest = null; + + undoStack: UndoRedoAction[] = []; + redoStack: UndoRedoAction[] = []; + private _record: MarcRecord; set record(r: MarcRecord) { if (r !== this._record) { @@ -39,10 +57,31 @@ export class MarcEditContext { requestFieldFocus(req: FieldFocusRequest) { // timeout allows for new components to be built before the // focus request is emitted. - setTimeout(() => this.fieldFocusRequest.emit(req)); + setTimeout(() => { + this.fieldFocusRequest.emit(req); + }); + } + + getFieldOffset(fieldId: number): number { + for (let idx = 0; idx < this.record.fields.length; idx++) { + if (this.record.fields[idx].fieldId === fieldId) { + return idx; + } + } + } + + recordChanging() { + this.lastFocused.tagOffset = + this.getFieldOffset(this.lastFocused.fieldId); + + this.undoStack.unshift({ + breakerText: this.record.toBreaker(), + position: this.lastFocused + }); } add006() { + this.recordChanging(); this.record.insertOrderedFields( this.record.newField({ tag : '006', @@ -53,6 +92,7 @@ export class MarcEditContext { add007() { + this.recordChanging(); this.record.insertOrderedFields( this.record.newField({ tag : '007', @@ -62,6 +102,7 @@ export class MarcEditContext { } insertReplace008() { + this.recordChanging(); // delete all of the 008s [].concat(this.record.field('008', true)).forEach( @@ -79,6 +120,7 @@ export class MarcEditContext { // Adds a new empty subfield to the provided field at the // requested subfield position insertSubfield(field: MarcField, position: number) { + this.recordChanging(); // array index 3 contains that position of the subfield // in the MARC field. When splicing a new subfield into @@ -97,6 +139,53 @@ export class MarcEditContext { }); } + // Add stub field before or after the context field + insertStubField(field: MarcField, before?: boolean) { + + const newField = this.record.newField( + {tag: '999', subfields: [[' ', '', 0]]}); + + this.insertField(field, newField, before); + } + + insertField(contextField: MarcField, newField: MarcField, before?: boolean) { + this.recordChanging(); + + if (before) { + this.record.insertFieldsBefore(contextField, newField); + this.focusPreviousTag(contextField); + + } else { + this.record.insertFieldsAfter(contextField, newField); + this.focusNextTag(contextField); + } + } + + + deleteField(field: MarcField) { + this.recordChanging(); + this.record.deleteFields(field); + this.focusNextTag(field) || this.focusPreviousTag(field); + } + + deleteSubfield(field: MarcField, subfield: MarcSubfield) { + this.recordChanging(); + // If subfields remain, focus the previous subfield. + // otherwise focus our tag. + const sfpos = subfield[2] - 1; + + field.deleteExactSubfields(subfield); + + const focus: FieldFocusRequest = {fieldId: field.fieldId, target: 'tag'}; + + if (sfpos >= 0) { + focus.target = 'sfv'; + focus.sfOffset = sfpos; + } + + this.requestFieldFocus(focus); + } + // Returns true if the field has a next tag to focus focusNextTag(field: MarcField) { const nextField = this.record.getNextField(field.fieldId); -- 2.11.0