-import {Component, Input, Output, EventEmitter, OnInit, ViewChild,
+import {Component, Input, Output, EventEmitter, OnInit, ViewChild,
AfterViewInit, TemplateRef, ViewEncapsulation} from '@angular/core';
import {ContextMenuService, ContextMenu, ContextMenuEntry} from './context-menu.service';
selector: 'eg-context-menu-container',
templateUrl: './context-menu-container.component.html',
styleUrls: ['context-menu-container.component.css'],
- /* Our CSS affects the style of the popover, which may
+ /* Our CSS affects the style of the popover, which may
* be beyond our reach for standard view encapsulation */
encapsulation: ViewEncapsulation.None
})
constructor(private menuService: ContextMenuService) {}
ngOnInit() {
-
this.menuService.showMenuRequest.subscribe(
(menu: ContextMenu) => {
-
- this.menuEntries = menu.entries
+ this.menuEntries = menu.entries;
});
}
import {ContextMenuService, ContextMenu, ContextMenuEntry} from './context-menu.service';
-/* Import all of this stuff so we can pass it to our parent
+/* Import all of this stuff so we can pass it to our parent
* class via its constructor */
import {
Inject, Injector, Renderer2, ElementRef, TemplateRef, ViewContainerRef,
})
export class ContextMenuDirective extends NgbPopover {
+ // Only one active menu is allowed at a time.
+ static activeDirective: ContextMenuDirective;
+ static menuId = 0;
+
triggers = 'contextmenu';
popoverClass = 'eg-context-menu';
@Output() menuItemSelected: EventEmitter<ContextMenuEntry>;
- // Only one active menu is allowed at a time.
- static activeDirective: ContextMenuDirective;
- static menuId = 0;
-
constructor(
p1: ElementRef<HTMLElement>, p2: Renderer2, p3: Injector,
p4: ComponentFactoryResolver, p5: ViewContainerRef, p6: NgbPopoverConfig,
this.menuService.menuItemSelected.subscribe(
(entry: ContextMenuEntry) => {
- // Only broadcast entry selection to my listeners if I'm
+ // Only broadcast entry selection to my listeners if I'm
// hosting the menu where the selection occurred.
if (this.menu && this.menu.id === this.menuService.activeMenu.id) {
if (ContextMenuDirective.activeDirective) {
ContextMenuDirective.activeDirective.close();
ContextMenuDirective.activeDirective = null;
- this.menuService.activeMenu == null;
+ this.menuService.activeMenu = null;
}
if (!this.menuEntries ||
- this.menuEntries.length === 0) {
+ this.menuEntries.length === 0) {
return;
}
import {Injectable, EventEmitter, TemplateRef} from '@angular/core';
import {tap} from 'rxjs/operators';
-/* Relay requests to/from the context menu directive and its
+/* Relay requests to/from the context menu directive and its
* template container component */
export interface ContextMenuEntry {
@Injectable({providedIn: 'root'})
export class ContextMenuService {
-
+
showMenuRequest: EventEmitter<ContextMenu>;
menuItemSelected: EventEmitter<ContextMenuEntry>;
menuTemplate: TemplateRef<any>;
activeMenu: ContextMenu;
-
+
constructor() {
this.showMenuRequest = new EventEmitter<ContextMenu>();
this.menuItemSelected = new EventEmitter<ContextMenuEntry>();
-import {ElementRef, Component, Input, Output, OnInit, OnDestroy,
+import {ElementRef, Component, Input, Output, OnInit, OnDestroy,
EventEmitter, AfterViewInit, Renderer2} from '@angular/core';
import {Subscription} from 'rxjs';
import {filter} from 'rxjs/operators';
import {MarcRecord, MarcField, MarcSubfield} from './marcrecord';
-import {MarcEditContext, FieldFocusRequest, MARC_EDITABLE_FIELD_TYPE,
+import {MarcEditContext, FieldFocusRequest, MARC_EDITABLE_FIELD_TYPE,
TextUndoRedoAction} from './editor-context';
import {ContextMenuEntry} from '@eg/share/context-menu/context-menu.service';
import {TagTableService} from './tagtable.service';
styleUrls: ['./editable-content.component.css']
})
-export class EditableContentComponent
+export class EditableContentComponent
implements OnInit, AfterViewInit, OnDestroy {
@Input() context: MarcEditContext;
editInput: any; // <input/> or <div contenteditable/>
maxLength: number = null;
- // Track the load-time content so we know what text value to
+ // Track the load-time content so we know what text value to
// track on our undo stack.
undoBackToText: string;
if (req.fieldId !== this.field.fieldId) { return false; }
} else if (req.target === 'ldr') {
return this.isLeader;
- } else if (req.target === 'ffld' &&
+ } else if (req.target === 'ffld' &&
req.ffCode !== this.fixedFieldCode) {
return false;
}
- if (req.sfOffset !== undefined &&
+ if (req.sfOffset !== undefined &&
req.sfOffset !== this.subfield[2]) {
// this is not the subfield you are looking for.
return false;
}
if (!req) {
- // Focus request may have come from keyboard navigation,
+ // Focus request may have come from keyboard navigation,
// clicking, etc. Model the event as a focus request
// so it can be tracked the same.
req = {
contextMenuEntries(): ContextMenuEntry[] {
if (this.isLeader) { return; }
- switch(this.fieldType) {
- case 'tag':
+ switch (this.fieldType) {
+ case 'tag':
return this.tagTable.getFieldTags();
- case 'sfc':
+ case 'sfc':
return this.tagTable.getSubfieldCodes(this.field.tag);
- case 'sfv':
+ case 'sfv':
return this.tagTable.getSubfieldValues(
this.field.tag, this.subfield[0]);
return this.tagTable.getIndicatorValues(
this.field.tag, this.fieldType);
- case 'ffld':
+ case 'ffld':
return this.tagTable.getFfValues(
this.fixedFieldCode, this.record.recordType());
}
if (this.fieldText) { return this.fieldText; } // read-only
switch (this.fieldType) {
- case 'ldr': return this.record.leader;
+ case 'ldr': return this.record.leader;
case 'cfld': return this.field.data;
case 'tag': return this.field.tag;
case 'sfc': return this.subfield[0];
case 'ind1': return this.field.ind1;
case 'ind2': return this.field.ind2;
- case 'ffld':
+ case 'ffld':
// When actively editing a fixed field, track its value
// in a local variable instead of pulling the value
// from record.extractFixedField(), which applies
!this.context.lastFocused ||
!this.focusRequestIsMe(this.context.lastFocused)) {
- this.ffValue =
+ this.ffValue =
this.record.extractFixedField(this.fixedFieldCode);
}
return this.ffValue;
case 'sfv': this.subfield[1] = value; break;
case 'ind1': this.field.ind1 = value; break;
case 'ind2': this.field.ind2 = value; break;
- case 'ffld':
+ case 'ffld':
// Track locally and propagate to the record.
this.ffValue = value;
this.record.setFixedField(this.fixedFieldCode, value);
this.setContent(action.textContent, true, true);
action.textContent = recoverContent;
- const moveTo = action.isRedo ?
+ const moveTo = action.isRedo ?
this.context.undoStack : this.context.redoStack;
moveTo.unshift(action);
inputBlurred() {
// If the text content changed during this focus session,
- // track the new value as the value the next session of
+ // track the new value as the value the next session of
// text edits should return to upon undo.
this.undoBackToText = this.getContent();
}
// Route keydown events to the appropriate handler
inputKeyDown(evt: KeyboardEvent) {
- switch(evt.key) {
- case 'y':
+ switch (evt.key) {
+ case 'y':
if (evt.ctrlKey) { // redo
this.context.requestRedo();
evt.preventDefault();
}
return;
- case 'z':
+ case 'z':
if (evt.ctrlKey) { // undo
this.context.requestUndo();
evt.preventDefault();
}
return;
- case 'F6':
+ case 'F6':
if (evt.shiftKey) {
// shift+F6 => add 006
this.context.add00X('006');
}
return;
- case 'F7':
+ case 'F7':
if (evt.shiftKey) {
// shift+F7 => add 007
this.context.add00X('007');
}
return;
- case 'F8':
+ case 'F8':
if (evt.shiftKey) {
// shift+F8 => add/replace 008
this.context.insertReplace008();
switch (evt.key) {
- case 'Enter':
+ case 'Enter':
if (evt.ctrlKey) {
// ctrl+enter == insert stub field after focused field
// ctrl+shift+enter == insert stub field before focused field
case 'Delete':
- if (evt.ctrlKey) {
+ if (evt.ctrlKey) {
// ctrl+delete == delete whole field
this.context.deleteField(this.field);
evt.preventDefault();
break;
- case 'ArrowDown':
+ case 'ArrowDown':
if (evt.ctrlKey) {
// ctrl+down == copy current field down one
}
deleteField() {
- this.context.focusNextTag(this.field) ||
+ if (!this.context.focusNextTag(this.field)) {
this.context.focusPreviousTag(this.field);
+ }
this.record.deleteFields(this.field);
}
this.field.deleteExactSubfields(this.subfield);
- const focus: FieldFocusRequest =
- {fieldId: this.field.fieldId, target: 'tag'};
+ const focus: FieldFocusRequest = {
+ fieldId: this.field.fieldId, target: 'tag'};
- if (sfpos >= 0) {
+ if (sfpos >= 0) {
focus.target = 'sfv';
- focus.sfOffset = sfpos;
+ focus.sfOffset = sfpos;
}
this.context.requestFieldFocus(focus);
const STUB_DATA_00X = ' ';
-export type MARC_EDITABLE_FIELD_TYPE =
+export type MARC_EDITABLE_FIELD_TYPE =
'ldr' | 'tag' | 'cfld' | 'ind1' | 'ind2' | 'sfc' | 'sfv' | 'ffld';
export interface FieldFocusRequest {
} else {
// Re-insert the removed field and focus it.
-
- if (action.subfield) {
+
+ if (action.subfield) {
this.insertSubfield(action.field, action.subfield, true);
this.focusSubfield(action.field, action.subfield[2]);
} else {
-
+
const fieldId = action.position.fieldId;
- const prevField =
+ const prevField =
this.record.getField(action.prevPosition.fieldId);
this.record.insertFieldsAfter(prevField, action.field);
-
+
// Recover the original fieldId, which gets re-stamped
// in this.record.insertFields* calls.
action.field.fieldId = fieldId;
-
+
// Focus the newly recovered field.
this.requestFieldFocus(action.position);
}
this.undoStack.unshift(action);
}
- deleteField(field: MarcField) {
+ deleteField(field: MarcField) {
this.trackStructuralUndo(field, false);
- this.focusNextTag(field) || this.focusPreviousTag(field);
+ if (!this.focusNextTag(field)) {
+ this.focusPreviousTag(field);
+ }
this.record.deleteFields(field);
}
add00X(tag: string) {
- const field: MarcField =
+ const field: MarcField =
this.record.newField({tag : tag, data : STUB_DATA_00X});
this.record.insertOrderedFields(field);
// Adds a new empty subfield to the provided field at the
// requested subfield position
- insertSubfield(field: MarcField,
+ insertSubfield(field: MarcField,
subfield: MarcSubfield, skipTracking?: boolean) {
const position = subfield[2];
const newSf: MarcSubfield = [' ', '', position];
this.insertSubfield(field, newSf);
}
-
- // Focus the requested subfield by its position. If its
+
+ // Focus the requested subfield by its position. If its
// position is less than zero, focus the field's tag instead.
focusSubfield(field: MarcField, position: number) {
const focus: FieldFocusRequest = {fieldId: field.fieldId, target: 'tag'};
- if (position >= 0) {
+ if (position >= 0) {
// Focus the code instead of the value, because attempting to
// focus an empty (editable) div results in nothing getting focus.
focus.target = 'sfc';
- focus.sfOffset = position;
+ focus.sfOffset = position;
}
this.requestFieldFocus(focus);
// Returns true if the field has a next tag to focus
focusNextTag(field: MarcField) {
const nextField = this.record.getNextField(field.fieldId);
- if (nextField) {
- this.focusTag(nextField);
+ if (nextField) {
+ this.focusTag(nextField);
return true;
}
return false;
// MARC breaker delimiter
const DELIMITER = '$';
-export interface MarcSubfield // code, value, position
- extends Array<string|number>{0: string; 1: string; 2: number}
+export interface MarcSubfield // code, value, position
+ extends Array<string|number> { 0: string; 1: string; 2: number; }
// Only contains the attributes/methods we need so far.
export interface MarcField {
tag?: string;
ind1?: string;
ind2?: string;
- subfields?: MarcSubfield[];
+ subfields?: MarcSubfield[];
isControlfield(): boolean;
const defaultTagTableSelector: TagTableSelector = {
marcFormat : 'marc21',
marcRecordType : 'biblio'
-}
+};
@Injectable()
export class TagTableService {
ffPosMap: {[rtype: string]: any[]} = {};
ffValueMap: {[rtype: string]: any} = {};
- extractedValuesCache:
+ extractedValuesCache:
{[valueType: string]: {[which: string]: any}} = {};
constructor(
}
}
- toCache(dataType: string, which: string,
+ toCache(dataType: string, which: string,
which2: string, values: ContextMenuEntry[]): ContextMenuEntry[] {
const base = this.extractedValuesCache[dataType];
const part1 = base[which];
selector.marcFormat = defaultTagTableSelector.marcFormat;
}
if (!selector.marcRecordType) {
- selector.marcRecordType =
+ selector.marcRecordType =
defaultTagTableSelector.marcRecordType;
}
} else {
})).toPromise();
}
- getSubfieldCodes(tag: string): ContextMenuEntry[] {
+ getSubfieldCodes(tag: string): ContextMenuEntry[] {
if (!tag || !this.tagMap[tag]) { return null; }
const cached = this.fromCache('sfcodes', tag);
const list = this.tagMap[tag].subfields.map(sf => ({
- value: sf.code,
+ value: sf.code,
label: `${sf.code}: ${sf.description}`
- }))
+ }))
.sort((a, b) => a.label < b.label ? -1 : 1);
return this.toCache('sfcodes', tag, null, list);
const cached = this.fromCache('fieldtags');
if (cached) { return cached; }
-
+
return Object.keys(this.tagMap)
.filter(tag => Boolean(this.tagMap[tag]))
.map(tag => ({
getSubfieldValues(tag: string, sfCode: string): ContextMenuEntry[] {
if (!tag || !this.tagMap[tag]) { return []; }
- const cached = this.fromCache('sfvalues', tag, sfCode)
+ const cached = this.fromCache('sfvalues', tag, sfCode);
if (cached) { return cached; }
const list: ContextMenuEntry[] = [];
sf.value_list.forEach(value => {
let label = value.description || value.code;
- let code = value.code || label;
+ const code = value.code || label;
if (code !== label) { label = `${code}: ${label}`; }
list.push({value: code, label: label});
- })
+ });
});
return this.toCache('sfvalues', tag, sfCode, list);
}
getIndicatorValues(tag: string, which: 'ind1' | 'ind2'): ContextMenuEntry[] {
- if (!tag || !this.tagMap[tag]) { return }
+ if (!tag || !this.tagMap[tag]) { return; }
const cached = this.fromCache('indicators', tag, which);
if (cached) { return cached; }