Nick Clemens
3b0ab38559
This patchset introduces an internal clipboard to the advanced editor and provides some new functionality to make use of this, default shortcuts are provided but can be edited per bug 21411 The default shortcuts for new/changed functions are: Changed: Ctrl-X: Now cuts a line into the clipboard area Shift-Ctrl-X: Now cuts current subfield into clipboard area Added: Ctrl-C: Copies a line into the clipboard area Shift-Ctrl-C: Copies current subfield into clipboard area Ctrl-P: Pastes the selected item from the clipboard at cursor Ctrl-I: Copies the current line and inserts onto a new line below To test: Verify all functionality above and confirm it behaves as expected Note: Ctrl-v pastes from the system clipboard - codemirror does not have access and this is why we use our "Clipboard" For browser cut/paste please use mouse right click or context menus Ctrl-P can be accessed as print by focusing outside the editor window Signed-off-by: Alex Sassmannshausen <alex@komputilo.eu> Signed-off-by: Katrin Fischer <katrin.fischer.83@web.de> Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
875 lines
31 KiB
JavaScript
875 lines
31 KiB
JavaScript
/**
|
|
* Copyright 2015 ByWater Solutions
|
|
*
|
|
* This file is part of Koha.
|
|
*
|
|
* Koha is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Koha is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Koha; if not, see <http://www.gnu.org/licenses>.
|
|
*/
|
|
|
|
define( [ 'marc-record', 'koha-backend', 'preferences', 'text-marc', 'widget' ], function( MARC, KohaBackend, Preferences, TextMARC, Widget ) {
|
|
|
|
var NOTIFY_TIMEOUT = 250;
|
|
|
|
function editorCursorActivity( cm ) {
|
|
var editor = cm.marceditor;
|
|
var field = editor.getCurrentField();
|
|
if ( !field ) return;
|
|
|
|
// Set overwrite mode for tag numbers/indicators and contents of fixed fields
|
|
if ( field.isControlField || cm.getCursor().ch < 8 ) {
|
|
cm.toggleOverwrite(true);
|
|
} else {
|
|
cm.toggleOverwrite(false);
|
|
}
|
|
|
|
editor.onCursorActivity();
|
|
}
|
|
|
|
// This function exists to prevent inserting or partially deleting text that belongs to a
|
|
// widget. The 'marcAware' change source exists for other parts of the editor code to bypass
|
|
// this check.
|
|
function editorBeforeChange( cm, change ) {
|
|
var editor = cm.marceditor;
|
|
if ( editor.textMode || change.origin == 'marcAware' || change.origin == 'widget.clearToText' ) return;
|
|
|
|
// FIXME: Should only cancel changes if this is a control field/subfield widget
|
|
if ( change.from.line !== change.to.line || Math.abs( change.from.ch - change.to.ch ) > 1 || change.text.length != 1 || change.text[0].length != 0 ) return; // Not single-char change
|
|
|
|
if ( change.from.ch == change.to.ch - 1 && cm.findMarksAt( { line: change.from.line, ch: change.from.ch + 1 } ).length ) {
|
|
change.cancel();
|
|
} else if ( change.from.ch == change.to.ch && cm.findMarksAt(change.from).length && !change.text[0] == '‡' ) {
|
|
change.cancel();
|
|
}
|
|
}
|
|
|
|
function editorChanges( cm, changes ) {
|
|
var editor = cm.marceditor;
|
|
if ( editor.textMode ) return;
|
|
|
|
for (var i = 0; i < changes.length; i++) {
|
|
var change = changes[i];
|
|
|
|
var origin = change.from.line;
|
|
var newTo = CodeMirror.changeEnd(change);
|
|
|
|
for (var delLine = origin; delLine <= change.to.line; delLine++) {
|
|
// Line deleted; currently nothing to do
|
|
}
|
|
|
|
for (var line = origin; line <= newTo.line; line++) {
|
|
if ( Preferences.user.fieldWidgets ) Widget.UpdateLine( cm.marceditor, line );
|
|
if ( change.origin != 'setValue' && change.origin != 'marcWidgetPrefill' && change.origin != 'widget.clearToText' ) {
|
|
cm.addLineClass( line, 'wrapper', 'modified-line' );
|
|
editor.modified = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
Widget.ActivateAt( cm, cm.getCursor() );
|
|
cm.marceditor.startNotify();
|
|
}
|
|
|
|
function editorSetOverwriteMode( cm, newState ) {
|
|
var editor = cm.marceditor;
|
|
|
|
editor.overwriteMode = newState;
|
|
}
|
|
|
|
// Editor helper functions
|
|
function activateTabPosition( cm, pos, idx ) {
|
|
// Allow tabbing to as-yet-nonexistent positions
|
|
var lenDiff = pos.ch - cm.getLine( pos.line ).length;
|
|
if ( lenDiff > 0 ) {
|
|
var extra = '';
|
|
while ( lenDiff-- > 0 ) extra += ' ';
|
|
if ( pos.prefill ) extra += pos.prefill;
|
|
cm.replaceRange( extra, { line: pos.line } );
|
|
}
|
|
|
|
cm.setCursor( pos );
|
|
Widget.ActivateAt( cm, pos, idx );
|
|
}
|
|
|
|
function getTabPositions( editor, cur ) {
|
|
cur = cur || editor.cm.getCursor();
|
|
var field = editor.getFieldAt( cur.line );
|
|
|
|
if ( field ) {
|
|
if ( field.isControlField ) {
|
|
var positions = [ { ch: 0 }, { ch: 4 } ];
|
|
|
|
$.each( positions, function( undef, pos ) {
|
|
pos.line = cur.line;
|
|
} );
|
|
|
|
return positions;
|
|
} else {
|
|
var positions = [ { ch: 0 }, { ch: 4, prefill: '_' }, { ch: 6, prefill: '_' } ];
|
|
|
|
$.each( positions, function( undef, pos ) {
|
|
pos.line = cur.line;
|
|
} );
|
|
$.each( field.getSubfields(), function( undef, subfield ) {
|
|
positions.push( { line: cur.line, ch: subfield.contentsStart } );
|
|
} );
|
|
|
|
// Allow to tab to start of empty field
|
|
if ( field.getSubfields().length == 0 ) {
|
|
positions.push( { line: cur.line, ch: 8 } );
|
|
}
|
|
|
|
return positions;
|
|
}
|
|
} else {
|
|
return [];
|
|
}
|
|
}
|
|
var _editorKeys = {};
|
|
|
|
_editorKeys[insert_copyright] = function( cm ) {
|
|
cm.replaceRange( '©', cm.getCursor() );
|
|
}
|
|
|
|
_editorKeys[insert_copyright_sound] = function( cm ) {
|
|
cm.replaceRange( '℗', cm.getCursor() );
|
|
}
|
|
|
|
_editorKeys[new_line] = function( cm ) {
|
|
var cursor = cm.getCursor();
|
|
cm.replaceRange( '\n', { line: cursor.line }, null, 'marcAware' );
|
|
cm.setCursor( { line: cursor.line + 1, ch: 0 } );
|
|
}
|
|
|
|
_editorKeys[line_break] = function( cm ) {
|
|
var cur = cm.getCursor();
|
|
|
|
cm.replaceRange( "\n", cur, null );
|
|
}
|
|
|
|
_editorKeys[delete_field] = function( cm ) {
|
|
// Delete line (or cut)
|
|
if ( cm.somethingSelected() ) return true;
|
|
var curLine = cm.getLine( cm.getCursor().line );
|
|
|
|
$("#clipboard").prepend('<option>'+curLine+'</option>');
|
|
|
|
cm.execCommand('deleteLine');
|
|
}
|
|
|
|
_editorKeys[link_authorities] = function( cm ) {
|
|
// Launch the auth search popup
|
|
var field = cm.marceditor.getCurrentField();
|
|
|
|
if ( !field ) return;
|
|
if ( authInfo[field.tag] == undefined ) return;
|
|
authtype = authInfo[field.tag].authtypecode;
|
|
index = 'tag_'+field.tag+'_rancor';
|
|
var mainmainstring = '';
|
|
if( field.getSubfields( authInfo[field.tag].subfield ).length != 0 ){
|
|
mainmainstring += field.getSubfields( authInfo[field.tag].subfield )[0].text;
|
|
}
|
|
|
|
var subfields = field.getSubfields();
|
|
var mainstring= '';
|
|
for(i=0;i < subfields.length ;i++){
|
|
if ( authInfo[field.tag].subfield == subfields[i].code ) continue;
|
|
if( subfields[i].code == '9' ) continue;
|
|
mainstring += subfields[i].text+' ';
|
|
}
|
|
newin=window.open("../authorities/auth_finder.pl?source=biblio&authtypecode="+authtype+"&index="+index+"&value_mainstr="+encodeURI(mainmainstring)+"&value_main="+encodeURI(mainstring), "_blank",'width=700,height=550,toolbar=false,scrollbars=yes');
|
|
|
|
}
|
|
|
|
_editorKeys[delete_subfield] = function( cm ) {
|
|
// Delete subfield
|
|
var field = cm.marceditor.getCurrentField();
|
|
if ( !field ) return;
|
|
|
|
var curCursor = cm.getCursor();
|
|
var subfield = field.getSubfieldAt( curCursor().ch );
|
|
var subfieldText= cm.getRange({line:curCursor.line,ch:subfield.start},{line:curCursor.line,ch:subfield.end});
|
|
if ( subfield ) {
|
|
$("#clipboard").prepend('<option>'+subfieldText+'</option>');
|
|
subfield.delete();
|
|
}
|
|
}
|
|
|
|
_editorKeys[copy_line] = function( cm ) {
|
|
// Copy line
|
|
if ( cm.somethingSelected() ) return true;
|
|
var curLine = cm.getLine( cm.getCursor().line );
|
|
$("#clipboard").prepend('<option>'+curLine+'</option>');
|
|
}
|
|
|
|
_editorKeys[copy_subfield] = function( cm ) {
|
|
// Copy subfield
|
|
var field = cm.marceditor.getCurrentField();
|
|
if ( !field ) return;
|
|
|
|
var curCursor = cm.getCursor();
|
|
var subfield = field.getSubfieldAt( curCursor().ch );
|
|
var subfieldText= cm.getRange({line:curCursor.line,ch:subfield.start},{line:curCursor.line,ch:subfield.end});
|
|
if ( subfield ) {
|
|
$("#clipboard").prepend('<option>'+subfieldText+'</option>');
|
|
}
|
|
}
|
|
|
|
_editorKeys[paste_line] = function( cm ) {
|
|
// Paste line from "clipboard"
|
|
if ( cm.somethingSelected() ) return true;
|
|
var cBoard = document.getElementById("clipboard");
|
|
var strUser = cBoard.options[cBoard.selectedIndex].text;
|
|
cm.replaceRange( strUser, cm.getCursor(), null );
|
|
}
|
|
|
|
_editorKeys[insert_line] = function( cm ) {
|
|
// Copy line and insert below
|
|
if ( cm.somethingSelected() ) return true;
|
|
var curLine = cm.getLine( cm.getCursor().line );
|
|
cm.execCommand('newlineAndIndent');
|
|
cm.replaceRange( curLine, cm.getCursor(), null );
|
|
}
|
|
|
|
_editorKeys[next_position] = function( cm ) {
|
|
// Move through parts of tag/fixed fields
|
|
var positions = getTabPositions( cm.marceditor );
|
|
var cur = cm.getCursor();
|
|
|
|
for ( var i = 0; i < positions.length; i++ ) {
|
|
if ( positions[i].ch > cur.ch ) {
|
|
activateTabPosition( cm, positions[i] );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
cm.setCursor( { line: cur.line + 1, ch: 0 } );
|
|
}
|
|
|
|
_editorKeys[prev_position] = function( cm ) {
|
|
// Move backwards through parts of tag/fixed fields
|
|
var positions = getTabPositions( cm.marceditor );
|
|
var cur = cm.getCursor();
|
|
|
|
for ( var i = positions.length - 1; i >= 0; i-- ) {
|
|
if ( positions[i].ch < cur.ch ) {
|
|
activateTabPosition( cm, positions[i], -1 );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if ( cur.line == 0 ) return;
|
|
|
|
var prevPositions = getTabPositions( cm.marceditor, { line: cur.line - 1, ch: cm.getLine( cur.line - 1 ).length } );
|
|
|
|
if ( prevPositions.length ) {
|
|
activateTabPosition( cm, prevPositions[ prevPositions.length - 1 ], -1 );
|
|
} else {
|
|
cm.setCursor( { line: cur.line - 1, ch: 0 } );
|
|
}
|
|
}
|
|
|
|
_editorKeys[insert_delimiter] = function(cm){
|
|
var cur = cm.getCursor();
|
|
|
|
cm.replaceRange( "‡", cur, null );
|
|
}
|
|
|
|
_editorKeys[toggle_keyboard] = function( cm ) {
|
|
let keyboard = $(cm.getInputField()).getkeyboard();
|
|
keyboard.isVisible()?keyboard.close():keyboard.reveal();
|
|
}
|
|
|
|
// The objects below are part of a field/subfield manipulation API, accessed through the base
|
|
// editor object.
|
|
//
|
|
// Each one is tied to a particular line; this means that using a field or subfield object after
|
|
// any other changes to the record will cause entertaining explosions. The objects are meant to
|
|
// be temporary, and should only be reused with great care. The macro code does this only
|
|
// because it is careful to dispose of the object after any other updates.
|
|
//
|
|
// Note, however, tha you can continue to use a field object after changing subfields. It's just
|
|
// the subfield objects that become invalid.
|
|
|
|
// This is an exception raised by the EditorSubfield and EditorField when an invalid change is
|
|
// attempted.
|
|
function FieldError(line, message) {
|
|
this.line = line;
|
|
this.message = message;
|
|
};
|
|
|
|
FieldError.prototype.toString = function() {
|
|
return 'FieldError(' + this.line + ', "' + this.message + '")';
|
|
};
|
|
|
|
// This is the temporary object for a particular subfield in a field. Any change to any other
|
|
// subfields will invalidate this subfield object.
|
|
function EditorSubfield( field, index, start, end ) {
|
|
this.field = field;
|
|
this.index = index;
|
|
this.start = start;
|
|
this.end = end;
|
|
|
|
if ( this.field.isControlField ) {
|
|
this.contentsStart = start;
|
|
this.code = '@';
|
|
} else {
|
|
this.contentsStart = start + 2;
|
|
this.code = this.field.contents.substr( this.start + 1, 1 );
|
|
}
|
|
|
|
this.cm = field.cm;
|
|
|
|
var marks = this.cm.findMarksAt( { line: field.line, ch: this.contentsStart } );
|
|
if ( marks[0] && marks[0].widget ) {
|
|
this.widget = marks[0].widget;
|
|
|
|
this.text = this.widget.text;
|
|
this.setText = this.widget.setText;
|
|
this.getFixed = this.widget.getFixed;
|
|
this.setFixed = this.widget.setFixed;
|
|
} else {
|
|
this.widget = null;
|
|
this.text = this.field.contents.substr( this.contentsStart, end - this.contentsStart );
|
|
}
|
|
};
|
|
|
|
$.extend( EditorSubfield.prototype, {
|
|
_invalid: function() {
|
|
return this.field._subfieldsInvalid();
|
|
},
|
|
|
|
delete: function() {
|
|
this.cm.replaceRange( "", { line: this.field.line, ch: this.start }, { line: this.field.line, ch: this.end }, 'marcAware' );
|
|
},
|
|
focus: function() {
|
|
this.cm.setCursor( { line: this.field.line, ch: this.contentsStart } );
|
|
},
|
|
focusEnd: function() {
|
|
this.cm.setCursor( { line: this.field.line, ch: this.end } );
|
|
},
|
|
getText: function() {
|
|
return this.text;
|
|
},
|
|
setText: function( text ) {
|
|
if ( !this._invalid() ) throw new FieldError( this.field.line, 'subfield invalid' );
|
|
this.cm.replaceRange( text, { line: this.field.line, ch: this.contentsStart }, { line: this.field.line, ch: this.end }, 'marcAware' );
|
|
this.field._invalidateSubfields();
|
|
},
|
|
} );
|
|
|
|
function EditorField( editor, line ) {
|
|
this.editor = editor;
|
|
this.line = line;
|
|
|
|
this.cm = editor.cm;
|
|
|
|
this._updateInfo();
|
|
this.tag = this.contents.substr( 0, 3 );
|
|
this.isControlField = ( this.tag < '010' );
|
|
|
|
if ( this.isControlField ) {
|
|
this._ind1 = this.contents.substr( 4, 1 );
|
|
this._ind2 = this.contents.substr( 6, 1 );
|
|
} else {
|
|
this._ind1 = null;
|
|
this._ind2 = null;
|
|
}
|
|
|
|
this.subfields = null;
|
|
}
|
|
|
|
$.extend( EditorField.prototype, {
|
|
_subfieldsInvalid: function() {
|
|
return !this.subfields;
|
|
},
|
|
_invalidateSubfields: function() {
|
|
this._subfields = null;
|
|
},
|
|
|
|
_updateInfo: function() {
|
|
this.info = this.editor.getLineInfo( { line: this.line, ch: 0 } );
|
|
if ( this.info == null ) throw new FieldError( 'Invalid field' );
|
|
this.contents = this.info.contents;
|
|
},
|
|
_scanSubfields: function() {
|
|
this._updateInfo();
|
|
|
|
if ( this.isControlField ) {
|
|
this._subfields = [ new EditorSubfield( this, 0, 4, this.contents.length ) ];
|
|
} else {
|
|
var field = this;
|
|
var subfields = this.info.subfields;
|
|
this._subfields = [];
|
|
|
|
for (var i = 0; i < this.info.subfields.length; i++) {
|
|
var end = i == subfields.length - 1 ? this.contents.length : subfields[i+1].ch;
|
|
|
|
this._subfields.push( new EditorSubfield( this, i, subfields[i].ch, end ) );
|
|
}
|
|
}
|
|
},
|
|
|
|
delete: function() {
|
|
this.cm.replaceRange( "", { line: this.line, ch: 0 }, { line: this.line + 1, ch: 0 }, 'marcAware' );
|
|
},
|
|
focus: function() {
|
|
this.cm.setCursor( { line: this.line, ch: 0 } );
|
|
|
|
return this;
|
|
},
|
|
|
|
getText: function() {
|
|
var result = '';
|
|
|
|
$.each( this.getSubfields(), function() {
|
|
if ( this.code != '@' ) result += '‡' + this.code;
|
|
|
|
result += this.getText();
|
|
} );
|
|
|
|
return result;
|
|
},
|
|
setText: function( text ) {
|
|
var indicator_match = /^([_ 0-9])([_ 0-9])\‡/.exec( text );
|
|
if ( indicator_match ) {
|
|
text = text.substr(2);
|
|
this.setIndicator1( indicator_match[1] );
|
|
this.setIndicator2( indicator_match[2] );
|
|
}
|
|
|
|
this.cm.replaceRange( text, { line: this.line, ch: this.isControlField ? 4 : 8 }, { line: this.line }, 'marcAware' );
|
|
this._invalidateSubfields();
|
|
|
|
return this;
|
|
},
|
|
|
|
getIndicator1: function() {
|
|
return this._ind1;
|
|
},
|
|
getIndicator2: function() {
|
|
return this._ind2;
|
|
},
|
|
setIndicator1: function(val) {
|
|
if ( this.isControlField ) throw new FieldError('Cannot set indicators on control field');
|
|
|
|
this._ind1 = ( !val || val == ' ' ) ? '_' : val;
|
|
this.cm.replaceRange( this._ind1, { line: this.line, ch: 4 }, { line: this.line, ch: 5 }, 'marcAware' );
|
|
|
|
return this;
|
|
},
|
|
setIndicator2: function(val) {
|
|
if ( this.isControlField ) throw new FieldError('Cannot set indicators on control field');
|
|
|
|
this._ind2 = ( !val || val == ' ' ) ? '_' : val;
|
|
this.cm.replaceRange( this._ind2, { line: this.line, ch: 6 }, { line: this.line, ch: 7 }, 'marcAware' );
|
|
|
|
return this;
|
|
},
|
|
|
|
appendSubfield: function( code ) {
|
|
if ( this.isControlField ) throw new FieldError('Cannot add subfields to control field');
|
|
|
|
this._invalidateSubfields();
|
|
this.cm.replaceRange( '‡' + code, { line: this.line }, null, 'marcAware' );
|
|
var subfields = this.getSubfields();
|
|
|
|
return subfields[ subfields.length - 1 ];
|
|
},
|
|
insertSubfield: function( code, position ) {
|
|
if ( this.isControlField ) throw new FieldError('Cannot add subfields to control field');
|
|
|
|
position = position || 0;
|
|
|
|
var subfields = this.getSubfields();
|
|
this._invalidateSubfields();
|
|
this.cm.replaceRange( '‡' + code, { line: this.line, ch: subfields[position] ? subfields[position].start : null }, null, 'marcAware' );
|
|
subfields = this.getSubfields();
|
|
|
|
return subfields[ position ];
|
|
},
|
|
getSubfields: function( code ) {
|
|
if ( !this._subfields ) this._scanSubfields();
|
|
if ( code == null ) return this._subfields;
|
|
|
|
var result = [];
|
|
|
|
$.each( this._subfields, function() {
|
|
if ( code == null || this.code == code ) result.push(this);
|
|
} );
|
|
|
|
return result;
|
|
},
|
|
getFirstSubfield: function( code ) {
|
|
var result = this.getSubfields( code );
|
|
|
|
return ( result && result.length ) ? result[0] : null;
|
|
},
|
|
getSubfieldAt: function( ch ) {
|
|
var subfields = this.getSubfields();
|
|
|
|
for (var i = 0; i < subfields.length; i++) {
|
|
if ( subfields[i].start < ch && subfields[i].end >= ch ) return subfields[i];
|
|
}
|
|
},
|
|
} );
|
|
|
|
function MARCEditor( options ) {
|
|
this.frameworkcode = '';
|
|
|
|
this.cm = CodeMirror(
|
|
options.position,
|
|
{
|
|
extraKeys: _editorKeys,
|
|
gutters: [
|
|
'modified-line-gutter',
|
|
],
|
|
lineWrapping: true,
|
|
mode: {
|
|
name: 'marc',
|
|
nonRepeatableTags: KohaBackend.GetTagsBy( '', 'repeatable', '0' ),
|
|
nonRepeatableSubfields: KohaBackend.GetSubfieldsBy( '', 'repeatable', '0' )
|
|
}
|
|
}
|
|
);
|
|
var inf = this.cm.getInputField();
|
|
var self = this;
|
|
var kb = $(inf).keyboard({
|
|
//keyBinding: "mousedown touchstart",
|
|
usePreview: false,
|
|
lockInput: false,
|
|
autoAccept: true,
|
|
autoAcceptOnEsc: true,
|
|
userClosed: true,
|
|
//alwaysOpen: true,
|
|
openOn : '',
|
|
position: {
|
|
of: $("#statusbar"), // optional - null (attach to input/textarea) or a jQuery object (attach elsewhere)
|
|
my: 'center top',
|
|
at: 'center bottom',
|
|
at2: 'center bottom' // used when "usePreview" is false (centers keyboard at bottom of the input/textarea)
|
|
},
|
|
beforeInsert: function(evnt, keyboard, elem, txt) {
|
|
var position = self.cm.getCursor();
|
|
if (txt === "\b") {
|
|
self.cm.execCommand("delCharBefore");
|
|
}
|
|
if (txt === "\b" && position.ch === 0 && position.line !== 0) {
|
|
elem.value = self.cm.getLine(position.line) || "";
|
|
txt = "";
|
|
}
|
|
return txt;
|
|
},
|
|
visible: function() {
|
|
$('#set-keyboard-layout').removeClass('hide');
|
|
},
|
|
hidden: function(e, keyboard, el, accepted) {
|
|
inf.focus();
|
|
$('#set-keyboard-layout').addClass('hide');
|
|
}
|
|
}).getkeyboard();
|
|
|
|
|
|
Object.keys($.keyboard.layouts).forEach(function(layout) {
|
|
var div = $('#keyboard-layout .layouts').append('<div class="layout" data-layout="'+layout+'" data-name="'+($.keyboard.layouts[layout].name||layout)+'" >'+($.keyboard.layouts[layout].name||layout)+'</div>')
|
|
if(kb.layout == layout) {
|
|
div.addClass('active');
|
|
}
|
|
});
|
|
$('#keyboard-layout')
|
|
.on('show.bs.modal', function() {
|
|
kb.close();
|
|
$('#keyboard-layout .filter').focus();
|
|
$('#set-keyboard-layout').removeClass('hide');
|
|
})
|
|
.on('hide.bs.modal', function() {
|
|
!kb.isVisible() && kb.reveal();
|
|
});
|
|
$('#keyboard-layout .layout').click(function(event) {
|
|
$('#keyboard-layout .layout').removeClass('active');
|
|
$(this).addClass('active');
|
|
var layout = $(this).data().layout;
|
|
kb.redraw(layout);
|
|
$('#keyboard-layout').modal('hide');
|
|
$('#keyboard-layout .filter').val('');
|
|
$('#keyboard-layout .layout').show();
|
|
});
|
|
$('#keyboard-layout .filter').keyup(function() {
|
|
var val = $(this).val();
|
|
if(!val||!val.length) return $('#keyboard-layout .layout').show();
|
|
var filter = new RegExp(val, 'i');
|
|
$('#keyboard-layout .layout').hide();
|
|
$('#keyboard-layout .layout').each(function() {
|
|
var name = $(this).data().name;
|
|
if(filter.test(name)) $(this).show();
|
|
})
|
|
});
|
|
|
|
this.cm.marceditor = this;
|
|
|
|
this.cm.on( 'beforeChange', editorBeforeChange );
|
|
this.cm.on( 'changes', editorChanges );
|
|
this.cm.on( 'cursorActivity', editorCursorActivity );
|
|
this.cm.on( 'overwriteToggle', editorSetOverwriteMode );
|
|
|
|
this.onCursorActivity = options.onCursorActivity;
|
|
|
|
this.subscribers = [];
|
|
this.subscribe( function( marceditor ) {
|
|
Widget.Notify( marceditor );
|
|
} );
|
|
}
|
|
|
|
MARCEditor.FieldError = FieldError;
|
|
|
|
$.extend( MARCEditor.prototype, {
|
|
setUseWidgets: function( val ) {
|
|
if ( val ) {
|
|
for ( var line = 0; line <= this.cm.lastLine(); line++ ) {
|
|
Widget.UpdateLine( this, line );
|
|
}
|
|
} else {
|
|
$.each( this.cm.getAllMarks(), function( undef, mark ) {
|
|
if ( mark.widget ) mark.widget.clearToText();
|
|
} );
|
|
}
|
|
},
|
|
|
|
focus: function() {
|
|
this.cm.focus();
|
|
},
|
|
|
|
getCursor: function() {
|
|
return this.cm.getCursor();
|
|
},
|
|
|
|
refresh: function() {
|
|
this.cm.refresh();
|
|
},
|
|
|
|
setFrameworkCode: function( code, updateFields, callback ) {
|
|
this.frameworkcode = code;
|
|
$( 'a.change-framework i.selected' ).addClass( 'hidden' );
|
|
$( 'a.change-framework i.unselected' ).removeClass( 'hidden' );
|
|
$( 'a.change-framework[data-frameworkcode="' + code + '"] i.unselected' ).addClass( 'hidden' );
|
|
$( 'a.change-framework[data-frameworkcode="' + code + '"] i.selected' ).removeClass( 'hidden ');
|
|
var cm = this.cm;
|
|
KohaBackend.InitFramework( code, function ( error ) {
|
|
cm.setOption( 'mode', {
|
|
name: 'marc',
|
|
nonRepeatableTags: KohaBackend.GetTagsBy( code, 'repeatable', '0' ),
|
|
nonRepeatableSubfields: KohaBackend.GetSubfieldsBy( code, 'repeatable', '0' )
|
|
});
|
|
if ( updateFields ) {
|
|
var record = TextMARC.TextToRecord( cm.getValue() );
|
|
KohaBackend.FillRecord( code, record );
|
|
cm.setValue( TextMARC.RecordToText(record) );
|
|
}
|
|
callback( error );
|
|
} );
|
|
},
|
|
|
|
displayRecord: function( record ) {
|
|
this.cm.setValue( TextMARC.RecordToText(record) );
|
|
this.modified = false;
|
|
this.setFrameworkCode(
|
|
typeof record.frameworkcode !== 'undefined' ? record.frameworkcode : '',
|
|
false,
|
|
function ( error ) {
|
|
if ( typeof error !== 'undefined' ) {
|
|
humanMsg.displayAlert( _(error), { className: 'humanError' } );
|
|
}
|
|
}
|
|
);
|
|
},
|
|
|
|
getRecord: function() {
|
|
this.textMode = true;
|
|
|
|
$.each( this.cm.getAllMarks(), function( undef, mark ) {
|
|
if ( mark.widget ) mark.widget.clearToText();
|
|
} );
|
|
var record = TextMARC.TextToRecord( this.cm.getValue() );
|
|
for ( var line = 0; line <= this.cm.lastLine(); line++ ) {
|
|
if ( Preferences.user.fieldWidgets ) Widget.UpdateLine( this, line );
|
|
}
|
|
|
|
this.textMode = false;
|
|
|
|
record.frameworkcode = this.frameworkcode;
|
|
return record;
|
|
},
|
|
|
|
getLineInfo: function( pos ) {
|
|
var contents = this.cm.getLine( pos.line );
|
|
if ( contents == null ) return {};
|
|
|
|
var tagNumber = contents.match( /^([A-Za-z0-9]{3})/ );
|
|
|
|
if ( !tagNumber ) return null; // No tag at all on this line
|
|
tagNumber = tagNumber[1];
|
|
|
|
if ( tagNumber < '010' ) return { tagNumber: tagNumber, contents: contents }; // No current subfield
|
|
|
|
var matcher = /‡([a-z0-9%])/g;
|
|
var match;
|
|
|
|
var subfields = [];
|
|
var currentSubfield;
|
|
|
|
while ( ( match = matcher.exec(contents) ) ) {
|
|
subfields.push( { code: match[1], ch: match.index } );
|
|
if ( match.index < pos.ch ) currentSubfield = match[1];
|
|
}
|
|
|
|
return { tagNumber: tagNumber, subfields: subfields, currentSubfield: currentSubfield, contents: contents };
|
|
},
|
|
|
|
addError: function( line, error ) {
|
|
var found = false;
|
|
var options = {};
|
|
|
|
if ( line == null ) {
|
|
line = 0;
|
|
options.above = true;
|
|
}
|
|
|
|
$.each( this.cm.getLineHandle(line).widgets || [], function( undef, widget ) {
|
|
if ( !widget.isErrorMarker ) return;
|
|
|
|
found = true;
|
|
|
|
$( widget.node ).append( '; ' + error );
|
|
widget.changed();
|
|
|
|
return false;
|
|
} );
|
|
|
|
if ( found ) return;
|
|
|
|
var node = $( '<div class="structure-error"><i class="fa fa-remove"></i> ' + error + '</div>' )[0];
|
|
var widget = this.cm.addLineWidget( line, node, options );
|
|
|
|
widget.node = node;
|
|
widget.isErrorMarker = true;
|
|
},
|
|
|
|
removeErrors: function() {
|
|
for ( var line = 0; line < this.cm.lineCount(); line++ ) {
|
|
$.each( this.cm.getLineHandle( line ).widgets || [], function( undef, lineWidget ) {
|
|
if ( lineWidget.isErrorMarker ) lineWidget.clear();
|
|
} );
|
|
}
|
|
},
|
|
|
|
startNotify: function() {
|
|
if ( this.notifyTimeout ) clearTimeout( this.notifyTimeout );
|
|
this.notifyTimeout = setTimeout( $.proxy( function() {
|
|
this.notifyAll();
|
|
|
|
this.notifyTimeout = null;
|
|
}, this ), NOTIFY_TIMEOUT );
|
|
},
|
|
|
|
notifyAll: function() {
|
|
$.each( this.subscribers, $.proxy( function( undef, subscriber ) {
|
|
subscriber(this);
|
|
}, this ) );
|
|
},
|
|
|
|
subscribe: function( subscriber ) {
|
|
this.subscribers.push( subscriber );
|
|
},
|
|
|
|
createField: function( tag, line ) {
|
|
var contents = tag + ( tag < '010' ? ' ' : ' _ _ ' );
|
|
|
|
if ( line > this.cm.lastLine() ) {
|
|
contents = '\n' + contents;
|
|
} else {
|
|
contents = contents + '\n';
|
|
}
|
|
|
|
this.cm.replaceRange( contents, { line: line, ch: 0 }, null, 'marcAware' );
|
|
|
|
return new EditorField( this, line );
|
|
},
|
|
|
|
createFieldOrdered: function( tag ) {
|
|
var line, contents;
|
|
|
|
for ( line = 0; (contents = this.cm.getLine(line)); line++ ) {
|
|
if ( contents && contents.substr(0, 3) > tag ) break;
|
|
}
|
|
|
|
return this.createField( tag, line );
|
|
},
|
|
|
|
createFieldGrouped: function( tag ) {
|
|
// Control fields should be inserted in actual order, whereas other fields should be
|
|
// inserted grouped
|
|
if ( tag < '010' ) return this.createFieldOrdered( tag );
|
|
|
|
var line, contents;
|
|
|
|
for ( line = 0; (contents = this.cm.getLine(line)); line++ ) {
|
|
if ( contents && contents[0] > tag[0] ) break;
|
|
}
|
|
|
|
return this.createField( tag, line );
|
|
},
|
|
|
|
getFieldAt: function( line ) {
|
|
try {
|
|
return new EditorField( this, line );
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
},
|
|
|
|
getCurrentField: function() {
|
|
return this.getFieldAt( this.cm.getCursor().line );
|
|
},
|
|
|
|
getFields: function( tag ) {
|
|
var result = [];
|
|
|
|
if ( tag != null ) tag += ' ';
|
|
|
|
for ( var line = 0; line < this.cm.lineCount(); line++ ) {
|
|
if ( tag && this.cm.getLine(line).substr( 0, 4 ) != tag ) continue;
|
|
|
|
// If this throws a FieldError, pretend it doesn't exist
|
|
try {
|
|
result.push( new EditorField( this, line ) );
|
|
} catch (e) {
|
|
if ( !( e instanceof FieldError ) ) throw e;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
},
|
|
|
|
getFirstField: function( tag ) {
|
|
var result = this.getFields( tag );
|
|
|
|
return ( result && result.length ) ? result[0] : null;
|
|
},
|
|
|
|
getAllFields: function( tag ) {
|
|
return this.getFields( null );
|
|
},
|
|
} );
|
|
|
|
return MARCEditor;
|
|
} );
|