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';
@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;
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();
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.
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;
/* 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<MarcRecord>;
fieldFocusRequest: EventEmitter<FieldFocusRequest>;
recordType: 'biblio' | 'authority' = 'biblio';
+ lastFocused: FieldFocusRequest = null;
+
+ undoStack: UndoRedoAction[] = [];
+ redoStack: UndoRedoAction[] = [];
+
private _record: MarcRecord;
set record(r: MarcRecord) {
if (r !== this._record) {
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',
add007() {
+ this.recordChanging();
this.record.insertOrderedFields(
this.record.newField({
tag : '007',
}
insertReplace008() {
+ this.recordChanging();
// delete all of the 008s
[].concat(this.record.field('008', true)).forEach(
// 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
});
}
+ // 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);