3 [% Asset.js("lib/codemirror/codemirror-compressed.js") | $raw %]
4 [% Asset.js("lib/filesaver.js") | $raw %]
5 [% Asset.css("lib/keyboard/css/keyboard.min.css") | $raw %]
6 [% Asset.js("lib/keyboard/js/jquery.keyboard.js") | $raw %]
7 [% Asset.js("lib/keyboard/languages/all.min.js") | $raw %]
8 [% Asset.js("lib/keyboard/layouts/all.min.js") | $raw %]
9 [% Asset.js("lib/koha/cateditor/marc-mode.js") | $raw %]
10 [% Asset.js("lib/require.js") | $raw %]
12 [% FOREACH shortcut IN shortcuts -%]
13 var [% shortcut.shortcut_name | html %] = "[% shortcut.shortcut_keys | html %]";
16 [%- FOREACH authtag = authtags -%]
17 [% authtag.tagfield | html %]: {
18 subfield: '[% authtag.tagsubfield | html %]',
19 authtypecode: '[% authtag.authtypecode | html %]',
24 baseUrl: '[% interface | html %]/lib/koha/cateditor/',
27 marcflavour: '[% marcflavour | html %]',
28 themelang: '[% themelang | html %]',
35 [% IF marcflavour == 'MARC21' %]
36 [% PROCESS 'cateditor-widgets-marc21.inc' %]
38 <script>var editorWidgets = {};</script>
42 require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'preferences', 'resources', 'text-marc', 'widget' ], function( KohaBackend, Search, Macros, MARCEditor, MARC, Preferences, Resources, TextMARC, Widget ) {
44 'koha:biblioserver': {
45 name: _("Local catalog"),
49 [%- FOREACH server = z3950_servers -%]
50 [% server.id | html %]: {
51 name: '[% server.servername | html %]',
52 recordtype: '[% server.recordtype | html %]',
53 checked: [% server.checked ? 'true' : 'false' | html %],
58 // The columns that should show up in a search, in order, and keyed by the corresponding <metadata> tag in the XSL and Pazpar2 config
60 [ "local_number", _("Local number") ],
61 [ "title", _("Title") ],
62 [ "series", _("Series title") ],
63 [ "author", _("Author") ],
64 [ "lccn", _("LCCN") ],
65 [ "isbn", _("ISBN") ],
66 [ "issn", _("ISSN") ],
67 [ "medium", _("Medium") ],
68 [ "edition", _("Edition") ],
69 [ "notes", _("Notes") ],
74 saveBackend: 'catalog',
81 function makeAuthorisedValueWidgets( frameworkCode ) {
82 $.each( KohaBackend.GetAllTagsInfo( frameworkCode ), function( tag, tagInfo ) {
83 $.each( tagInfo.subfields, function( subfield, subfieldInfo ) {
84 if ( !subfieldInfo.authorised_value ) return;
85 var authvals = KohaBackend.GetAuthorisedValues( subfieldInfo.authorised_value );
86 if ( !authvals ) return;
88 var defaultvalue = subfield.defaultvalue || authvals[0].value;
90 Widget.Register( tag + subfield, {
92 var $result = $( '<span class="subfield-widget"></span>' );
96 postCreate: function() {
97 var value = defaultvalue;
100 $.each( authvals, function() {
101 if ( this.value == widget.text ) {
106 this.setText( value );
108 $( '<select></select>' ).appendTo( this.node );
109 var $node = $( this.node ).find( 'select' );
110 $.each( authvals, function( undef, authval ) {
111 $node.append( '<option value="' + authval.value + '"' + (authval.value == value ? ' selected="selected"' : '') + '>' + authval.lib + '</option>' );
113 $node.val( this.text );
115 $node.change( $.proxy( function() {
116 this.setText( $node.val() );
119 makeTemplate: function() {
127 function bindGlobalKeys() {
128 shortcut.add( 'ctrl+s', function(event) {
129 $( '#save-record' ).click();
131 event.preventDefault();
134 shortcut.add( 'alt+ctrl+k', function(event) {
135 $( '#search-by-keywords' ).focus();
140 shortcut.add( 'alt+ctrl+a', function(event) {
141 $( '#search-by-author' ).focus();
146 shortcut.add( 'alt+ctrl+i', function(event) {
147 $( '#search-by-isbn' ).focus();
152 shortcut.add( 'alt+ctrl+t', function(event) {
153 $( '#search-by-title' ).focus();
158 shortcut.add( 'ctrl+h', function() {
159 var field = editor.getCurrentField();
161 if ( !field ) return;
163 window.open( getFieldHelpURL( field.tag ) );
166 $('#quicksearch .search-box').each( function() {
167 shortcut.add( 'enter', $.proxy( function() {
170 $('#quicksearch .search-box').each( function() {
171 if ( !this.value ) return;
173 terms.push( [ $(this).data('qualifier'), this.value ] );
176 if ( !terms.length ) return;
178 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
179 $("#search-overlay").show();
184 }, this), { target: this, type: 'keypress' } );
188 function getFieldHelpURL( tag ) {
189 [% IF Koha.Preference('marcfielddocurl') %]
190 var docurl = "[% Koha.Preference('marcfielddocurl').replace('"','"') | html %]";
191 docurl = docurl.replace("{MARC}", "[% marcflavour | html %]");
192 docurl = docurl.replace("{FIELD}", ""+tag);
193 docurl = docurl.replace("{LANG}", "[% lang | html %]");
195 [% ELSIF ( marcflavour == 'MARC21' ) %]
196 if ( tag == '000' ) {
197 return "http://www.loc.gov/marc/bibliographic/bdleader.html";
198 } else if ( tag >= '090' && tag < '100' ) {
199 return "http://www.loc.gov/marc/bibliographic/bd09x.html";
200 } else if ( tag < '900' ) {
201 return "http://www.loc.gov/marc/bibliographic/bd" + tag + ".html";
203 return "http://www.loc.gov/marc/bibliographic/bd9xx.html";
205 [% ELSIF ( marcflavour == 'UNIMARC' ) %]
206 /* http://archive.ifla.org/VI/3/p1996-1/ is an outdated version of UNIMARC, but
207 seems to be the only version available that can be linked to per tag. More recent
208 versions of the UNIMARC standard are available on the IFLA website only as
211 if ( tag == '000' ) {
212 return "http://archive.ifla.org/VI/3/p1996-1/uni.htm";
215 var url = "http://archive.ifla.org/VI/3/p1996-1/uni" + first + ".htm#";
216 if ( first == '0' ) url += "b";
217 if ( first != '9' ) url += tag;
227 titleForRecord: _("Editing new record"),
228 get: function( id, callback ) {
229 record = new MARC.Record();
230 KohaBackend.FillRecord( '', record );
236 titleForRecord: _("Editing new full record"),
237 get: function( id, callback ) {
238 record = new MARC.Record();
239 KohaBackend.FillRecord( '', record, true );
245 titleForRecord: _("Editing duplicate record of #{ID}"),
246 get: function( id, callback ) {
247 if ( !id ) return false;
249 KohaBackend.GetRecord( id, callback );
251 save: function( id, record, done ) {
252 function finishCb( data ) {
253 done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
256 KohaBackend.CreateRecord( record, finishCb );
260 titleForRecord: _("Editing catalog record #{ID}"),
262 { title: _("view"), href: "/cgi-bin/koha/catalogue/detail.pl?biblionumber={ID}" },
263 { title: _("edit items"), href: "/cgi-bin/koha/cataloguing/additem.pl?biblionumber={ID}" },
265 saveLabel: _("Save to catalog"),
266 get: function( id, callback ) {
267 if ( !id ) return false;
269 KohaBackend.GetRecord( id, callback );
271 save: function( id, record, done ) {
272 function finishCb( data ) {
273 done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
277 KohaBackend.SaveRecord( id, record, finishCb );
279 KohaBackend.CreateRecord( record, finishCb );
284 saveLabel: _("Save as MARC (.mrc) file"),
285 save: function( id, record, done ) {
286 saveAs( new Blob( [record.toISO2709()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.mrc' );
292 saveLabel: _("Save as MARCXML (.xml) file"),
293 save: function( id, record, done ) {
294 saveAs( new Blob( [record.toXML()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.xml' );
300 titleForRecord: _("Editing search result"),
301 get: function( id, callback ) {
302 if ( !id ) return false;
303 if ( !backends.search.records[ id ] ) {
304 callback( { error: _( "Invalid record" ) } );
308 callback( backends.search.records[ id ] );
314 function setSource(parts) {
315 state.backend = parts[0];
316 state.recordID = parts[1];
317 state.canSave = backends[ state.backend ].save != null;
318 state.saveBackend = state.canSave ? state.backend : 'catalog';
320 var backend = backends[state.backend];
322 document.location.hash = '#' + parts[0] + '/' + parts[1];
324 $('#title').text( backend.titleForRecord.replace( '{ID}', parts[1] ) );
326 $.each( backend.links || [], function( i, link ) {
327 $('#title').append(' <a target="_blank" href="' + link.href.replace( '{ID}', parts[1] ) + '">(' + link.title + ')</a>' );
329 $( 'title', document.head ).html( _("Koha › Cataloging › ") + backend.titleForRecord.replace( '{ID}', parts[1] ) );
330 $('#save-record span').text( backends[ state.saveBackend ].saveLabel );
333 function saveRecord( recid, editor, callback ) {
334 var parts = recid.split('/');
335 if ( parts.length != 2 ) return false;
337 if ( !backends[ parts[0] ] || !backends[ parts[0] ].save ) return false;
339 editor.removeErrors();
340 var record = editor.getRecord();
342 if ( record.errors ) {
343 state.saving = false;
344 callback( { error: 'syntax', errors: record.errors } );
348 var errors = KohaBackend.ValidateRecord( '', record );
349 if ( errors.length ) {
350 state.saving = false;
351 callback( { error: 'invalid', errors: errors } );
355 backends[ parts[0] ].save( parts[1], record, function(data) {
356 state.saving = false;
358 if (data.newRecord) {
359 var record = new MARC.Record();
360 record.loadMARCXML(data.newRecord);
361 record.frameworkcode = data.newRecord.frameworkcode;
362 editor.displayRecord( record );
366 setSource(data.newId);
368 setSource( [ state.backend, state.recordID ] );
371 if (callback) callback( data );
375 function loadRecord( recid, editor, callback ) {
376 var parts = recid.split('/');
377 if ( parts.length != 2 ) return false;
379 if ( !backends[ parts[0] ] || !backends[ parts[0] ].get ) return false;
381 backends[ parts[0] ].get( parts[1], function( record ) {
382 if ( !record.error ) {
383 editor.displayRecord( record );
387 if (callback) callback(record);
393 function openRecord( recid, editor, callback ) {
394 return loadRecord( recid, editor, function ( record ) {
395 setSource( recid.split('/') );
397 if (callback) callback( record );
402 function showAdvancedSearch() {
403 $('#advanced-search-servers').empty();
404 $.each( z3950Servers, function( server_id, server ) {
405 $('#advanced-search-servers').append( '<li data-server-id="' + server_id + '"><label><input class="search-toggle-server" type="checkbox"' + ( server.checked ? ' checked="checked">' : '>' ) + server.name + '</label></li>' );
407 $('#advanced-search-ui').modal('show');
410 function startAdvancedSearch() {
413 $('#advanced-search-ui .search-box').each( function() {
414 if ( !this.value ) return;
416 terms.push( [ $(this).data('qualifier'), this.value ] );
419 if ( !terms.length ) return;
421 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
422 $('#advanced-search-ui').modal('hide');
423 $("#search-overlay").show();
428 function showResultsBox(data) {
429 $('#search-top-pages, #search-bottom-pages').find('nav').empty();
430 $('#searchresults thead tr').empty();
431 $('#searchresults tbody').empty();
432 $('#search-serversinfo').empty().append('<li>' + _("Loading...") + '</li>');
433 $('#search-results-ui').modal('show');
436 function showSearchSorting( sort_key, sort_direction ) {
437 var $th = $('#searchresults thead tr th[data-sort-label="' + sort_key + '"]');
438 $th.parent().find( 'th[data-sort-label]' ).attr( 'class', 'sorting' );
440 if ( sort_direction == 'asc' ) {
442 $th.attr( 'class', 'sorting_asc' );
445 $th.attr( 'class', 'sorting_desc' );
449 function showSearchResults( editor, data ) {
450 backends.search.records = {};
452 $('#searchresults thead tr').empty();
453 $('#searchresults tbody').empty();
454 $('#search-serversinfo').empty();
456 $.each( z3950Servers, function( server_id, server ) {
457 var num_fetched = data.num_fetched[server_id];
459 if ( data.errors[server_id] ) {
460 num_fetched = data.errors[server_id];
461 } else if ( num_fetched == null ) {
463 } else if ( num_fetched < data.num_hits[server_id] ) {
467 $('#search-serversinfo').append( '<li data-server-id="' + server_id + '"><label><input class="search-toggle-server" type="checkbox"' + ( server.checked ? ' checked="checked">' : '>' ) + server.name + ' (' + num_fetched + ')' + '</label></li>' );
470 var seenColumns = {};
472 $.each( data.hits, function( undef, hit ) {
473 $.each( hit.metadata, function(key) {
474 seenColumns[key] = true;
478 $('#searchresults thead tr').append('<th>' + _("Source") + '</th>');
480 $.each( z3950Labels, function( undef, label ) {
481 if ( seenColumns[ label[0] ] ) {
482 $('#searchresults thead tr').append( '<th class="sorting" data-sort-label="' + label[0] + '">' + label[1] + '</th>' );
486 showSearchSorting( data.sort_key, data.sort_direction );
488 $('#searchresults thead tr').append('<th>' + _("Tools") + '</th>');
490 var bibnumMap = KohaBackend.GetSubfieldForKohaField('biblio.biblionumber');
491 $.each( data.hits, function( undef, hit ) {
492 backends.search.records[ hit.server + ':' + hit.index ] = hit.record;
494 switch ( hit.server ) {
495 case 'koha:biblioserver':
496 var bibnumField = hit.record.field( bibnumMap[0] );
498 if ( bibnumField && bibnumField.hasSubfield( bibnumMap[1] ) ) {
499 hit.id = 'catalog/' + bibnumField.subfield( bibnumMap[1] );
503 // Otherwise, fallthrough
506 hit.id = 'search/' + hit.server + ':' + hit.index;
510 result += '<td class="sourcecol">' + z3950Servers[ hit.server ].name + '</td>';
512 $.each( z3950Labels, function( undef, label ) {
513 if ( !seenColumns[ label[0] ] ) return;
515 if ( hit.metadata[ label[0] ] ) {
516 result += '<td class="infocol">' + hit.metadata[ label[0] ] + '</td>';
518 result += '<td class="infocol"> </td>';
522 result += '<td class="toolscol"><ul><li><a href="#" class="marc-link">' + _("View MARC") + '</a></li>';
523 result += '<li><a href="#" class="open-link">' + ( hit.server == 'koha:biblioserver' ? _("Edit") : _("Import") ) + '</a></li>';
524 if ( state.canSave ) result += '<li><a href="#" class="substitute-link" title="' + _("Replace the current record's contents") + '">' + _("Substitute") + '</a></li>';
525 result += '</ul></td></tr>';
527 var $tr = $( result );
528 $tr.find( '.marc-link' ).click( function() {
529 var $info_columns = $tr.find( '.infocol' );
530 var $marc_column = $tr.find( '.marccol' );
532 if ( !$marc_column.length ) {
533 $marc_column = $( '<td class="marccol" colspan="' + $info_columns.length + '"></td>' ).insertAfter( $info_columns.eq(-1) ).hide();
534 CodeMirror.runMode( TextMARC.RecordToText( hit.record ), 'marc', $marc_column[0] );
537 if ( $marc_column.is(':visible') ) {
538 $tr.find('.marc-link').text( _("View MARC") );
539 $info_columns.show();
542 $tr.find('.marc-link').text( _("Hide MARC") );
544 $info_columns.hide();
549 $tr.find( '.open-link' ).click( function() {
550 $( '#search-results-ui' ).modal('hide');
551 openRecord( hit.id, editor );
555 $tr.find( '.substitute-link' ).click( function() {
556 $( '#search-results-ui' ).modal('hide');
557 loadRecord( hit.id, editor );
561 $('#searchresults tbody').append( $tr );
565 var cur_page = data.offset / data.page_size;
566 var max_page = Math.ceil( data.total_fetched / data.page_size ) - 1;
568 if ( cur_page != 0 ) {
569 pages.push( '<li><a class="search-nav" href="#" data-offset="' + (data.offset - data.page_size) + '"><span aria-hidden="true">«</span> ' + _("Previous") + '</a></li>' );
572 for ( var page = Math.max( 0, cur_page - 9 ); page <= Math.min( max_page, cur_page + 9 ); page++ ) {
573 if ( page == cur_page ) {
574 pages.push( ' <li class="active"><a href="#">' + ( page + 1 ) + '</a></li>' );
576 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + ( page * data.page_size ) + '">' + ( page + 1 ) + '</a></li>' );
580 if ( cur_page < max_page ) {
581 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + (data.offset + data.page_size) + '">' + _("Next") + ' <span aria-hidden="true">»</span></a></li>' );
584 $( '#search-top-pages, #search-bottom-pages' ).find( 'nav' ).html( pages.length > 1 ? ( '<ul class="pagination pagination-sm">' + pages.join( '' ) + '</ul>' ) : '' );
586 var $overlay = $('#search-overlay');
587 $overlay.find('span').text(_("Loading"));
588 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
590 if ( data.activeclients ) {
591 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
594 $overlay.find('.bar').css( { display: 'block', width: '100%' } );
596 $('#searchresults')[0].focus();
600 function invalidateSearchResults() {
601 var $overlay = $('#search-overlay');
602 $overlay.find('span').text(_("Search expired, please try again"));
603 $overlay.find('.bar').css( { display: 'none' } );
607 function handleSearchError(error) {
608 if (error.code == 1) {
609 invalidateSearchResults();
612 humanMsg.displayMsg( '<h3>' + _("Internal search error") + '</h3><p>' + error.responseText + '</p><p>' + _("Please refresh the page and try again.") + '</p>', { className: 'humanError' } );
616 function handleSearchInitError(error) {
617 $('#quicksearch-overlay').fadeIn().find('p').text(error);
620 // Preference functions
621 function showPreference( pref ) {
622 var value = Preferences.user[pref];
626 $( '#set-field-widgets' ).text( value ? _("Show fields verbatim") : _("Show helpers for fixed and coded fields") );
629 $( '#editor .CodeMirror' ).css( { fontFamily: value } );
633 $( '#editor .CodeMirror' ).css( { fontSize: value } );
637 // Macros loaded on first show of modal
639 case 'selected_search_targets':
640 $.each( z3950Servers, function( server_id, server ) {
641 var saved_val = Preferences.user.selected_search_targets[server_id];
643 if ( saved_val != null ) server.checked = saved_val;
649 function bindPreference( editor, pref ) {
650 function _addHandler( sel, event, handler ) {
651 $( sel ).on( event, function (e) {
653 handler( e, Preferences.user[pref] );
654 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
655 showPreference(pref);
661 _addHandler( '#set-field-widgets', 'click', function( e, oldValue ) {
662 editor.setUseWidgets( Preferences.user.fieldWidgets = !Preferences.user.fieldWidgets );
666 _addHandler( '#prefs-menu .set-font', 'click', function( e, oldValue ) {
667 Preferences.user.font = $( e.target ).css( 'font-family' );
671 _addHandler( '#prefs-menu .set-fontSize', 'click', function( e, oldValue ) {
672 Preferences.user.fontSize = $( e.target ).css( 'font-size' );
675 case 'selected_search_targets':
676 $( document ).on( 'change', 'input.search-toggle-server', function() {
677 var server_id = $( this ).closest('li').data('server-id');
678 Preferences.user.selected_search_targets[server_id] = this.checked;
679 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
685 function displayPreferences( editor ) {
686 $.each( Preferences.user, function( pref, value ) {
687 showPreference( pref );
688 bindPreference( editor, pref );
693 function loadMacro( name ) {
694 $( '#macro-list li' ).removeClass( 'active' );
695 macroEditor.activeMacro = name;
698 macroEditor.setValue( '' );
702 $( '#macro-list li[data-name="' + name + '"]' ).addClass( 'active' );
703 var macro = Preferences.user.macros[name];
704 macroEditor.setValue( macro.contents );
705 macroEditor.setOption( 'readOnly', false );
706 $( '#macro-format' ).val( macro.format || 'its' );
707 if ( macro.history ) macroEditor.setHistory( macro.history );
710 function storeMacro( name, macro ) {
712 Preferences.user.macros[name] = macro;
714 delete Preferences.user.macros[name];
717 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
720 function showSavedMacros( macros ) {
721 var scrollTop = $('#macro-list').scrollTop();
722 $( '#macro-list' ).empty();
723 var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
724 return $.extend( { name: name }, macro );
726 macro_list.sort( function( a, b ) {
727 return a.name.localeCompare(b.name);
729 $.each( macro_list, function( undef, macro ) {
730 var $li = $( '<li data-name="' + macro.name + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
731 $li.click( function() {
732 loadMacro(macro.name);
735 if ( macro.name == macroEditor.activeMacro ) $li.addClass( 'active' );
736 var modified = macro.modified && new Date(macro.modified);
737 $li.find( '.macro-info' ).append(
738 '<li><span class="label">' + _("Last changed:") + '</span>' +
739 ( modified ? ( modified.toLocaleDateString() + ', ' + modified.toLocaleTimeString() ) : _("never") ) + '</li>'
741 $('#macro-list').append($li);
743 var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
744 $new_li.click( function() {
745 // TODO: make this a bit less retro
746 var name = prompt(_("Please enter the name for the new macro:"));
749 if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
753 $('#macro-list').append($new_li);
754 $('#macro-list').scrollTop(scrollTop);
757 function saveMacro() {
758 var name = macroEditor.activeMacro;
760 if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() ) return;
762 macroEditor.savedGeneration = macroEditor.changeGeneration();
763 storeMacro( name, { contents: macroEditor.getValue(), modified: (new Date()).valueOf(), history: macroEditor.getHistory(), format: $('#macro-format').val() } );
764 $('#macro-save-message').text(_("Saved"));
768 $(document).ready( function() {
770 editor = new MARCEditor( {
771 onCursorActivity: function() {
772 $('#status-tag-info').empty();
773 $('#status-subfield-info').empty();
775 var field = editor.getCurrentField();
776 var cur = editor.getCursor();
778 if ( !field ) return;
780 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
781 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
784 $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> ' + taginfo.lib );
786 var subfield = field.getSubfieldAt( cur.ch );
787 if ( !subfield ) return;
789 var subfieldinfo = taginfo.subfields[ subfield.code ];
790 $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
792 if ( subfieldinfo ) {
793 $('#status-subfield-info').append( subfieldinfo.lib );
795 $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
798 $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
801 position: function (elt) { $(elt).insertAfter('#toolbar') },
804 // Automatically detect resizes and change the height of the editor and position of modals.
805 var resizeTimer = null;
806 function onResize() {
807 if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
810 var pos = $('#editor .CodeMirror').offset();
811 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
813 $('.modal-body').each( function() {
814 $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
819 $( '#macro-ui' ).on( 'shown.bs.modal', function() {
820 if ( macroEditor ) return;
822 macroEditor = CodeMirror(
823 $('#macro-editor')[0],
826 'Ctrl-D': function( cm ) {
827 var cur = cm.getCursor();
829 cm.replaceRange( "‡", cur, null );
838 macroEditor.on( 'change', function( cm, change ) {
839 $('#macro-save-message').empty();
840 if ( change.origin == 'setValue' ) return;
842 if ( saveTimeout ) clearTimeout( saveTimeout );
843 saveTimeout = setTimeout( function() {
853 var saveableBackends = [];
854 $.each( backends, function( id, backend ) {
855 if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
857 saveableBackends.sort();
858 $.each( saveableBackends, function( undef, backend ) {
859 $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
862 var macro_format_list = $.map( Macros.formats, function( format, name ) {
863 return $.extend( { name: name }, format );
865 macro_format_list.sort( function( a, b ) {
866 return a.description.localeCompare(b.description);
868 $.each( macro_format_list, function() {
869 $('#macro-format').append( '<option value="' + this.name + '">' + this.description + '</option>' );
873 $( '#save-record, #save-dropdown a' ).click( function() {
874 $( '#save-record' ).find('i').attr( 'class', 'fa fa-spinner fa-spin' ).siblings( 'span' ).text( _("Saving...") );
876 function finishCb(result) {
877 if ( result.error == 'syntax' ) {
878 humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
879 } else if ( result.error == 'invalid' ) {
880 humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
881 } else if ( !result.error ) {
882 humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
885 $.each( result.errors || [], function( undef, error ) {
886 switch ( error.type ) {
888 editor.addError( error.line, _("Invalid tag number") );
891 editor.addError( error.line, _("Invalid indicators") );
894 editor.addError( error.line, _("Tag has no subfields") );
897 editor.addError( null, _("Missing mandatory tag: ") + error.tag );
899 case 'missingSubfield':
900 if ( error.subfield == '@' ) {
901 editor.addError( error.line, _("Missing control field contents") );
903 editor.addError( error.line, _("Missing mandatory subfield: ‡") + error.subfield );
906 case 'unrepeatableTag':
907 editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
909 case 'unrepeatableSubfield':
910 editor.addError( error.line, _("Subfield ‡") + error.subfield + _(" cannot be repeated") );
912 case 'itemTagUnsupported':
913 editor.addError( error.line, _("Item tags cannot currently be saved") );
918 $( '#save-record' ).find('i').attr( 'class', 'fa fa-hdd-o' );
920 if ( result.error ) {
921 // Reset backend info
922 setSource( [ state.backend, state.recordID ] );
926 var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
927 if ( state.backend == backend ) {
928 saveRecord( backend + '/' + state.recordID, editor, finishCb );
930 saveRecord( backend + '/', editor, finishCb );
936 $('#import-records').click( function() {
937 $('#import-records-input')
939 .change( function() {
940 if ( !this.files || !this.files.length ) return;
942 var file = this.files[0];
943 var reader = new FileReader();
945 reader.onload = function() {
946 var record = new MARC.Record();
948 if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
949 record.loadISO2709( reader.result );
950 } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
951 record.loadMARCXML( reader.result );
953 humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
957 if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
959 editor.displayRecord( record );
962 reader.readAsText( file );
969 $('#open-macros').click( function() {
970 $('#macro-ui').modal('show');
975 $('#run-macro').click( function() {
976 var result = Macros.Run( editor, $('#macro-format').val(), macroEditor.getValue() );
978 if ( !result.errors.length ) {
979 $('#macro-ui').modal('hide');
980 editor.focus(); //Return cursor to editor after macro run
985 $.each( result.errors, function() {
986 var error = '<b>' + _("Line ") + (this.line + 1) + ':</b> ';
988 switch ( this.error ) {
989 case 'failed': error += _("failed to run"); break;
990 case 'unrecognized': error += _("unrecognized command"); break;
996 humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
1001 $('#delete-macro').click( function() {
1002 if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
1004 storeMacro( macroEditor.activeMacro, undefined );
1006 loadMacro( undefined );
1011 $( '#switch-editor' ).click( function() {
1012 if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
1014 $.cookie( 'catalogue_editor_[% logged_in_user.borrowernumber | html %]', 'basic', { expires: 365, path: '/' } );
1016 if ( state.backend == 'catalog' ) {
1017 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
1018 } else if ( state.backend == 'new' ) {
1019 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
1021 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
1025 $( '#show-advanced-search' ).click( function() {
1026 showAdvancedSearch();
1031 $('#advanced-search').submit( function() {
1032 startAdvancedSearch();
1037 $( document ).on( 'click', 'a.search-nav', function() {
1038 if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1039 $("#search-overlay").show();
1045 $( document ).on( 'click', 'th[data-sort-label]', function() {
1048 if ( $( this ).hasClass( 'sorting_asc' ) ) {
1054 if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1055 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1057 $("#search-overlay").show();
1063 $( document ).on( 'change', 'input.search-toggle-server', function() {
1064 var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1065 server.checked = this.checked;
1067 if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1068 $("#search-overlay").show();
1076 $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1077 $(this).modal({ show: false });
1080 var $quicksearch = $('#quicksearch fieldset');
1081 $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1082 position: 'absolute',
1083 top: $quicksearch.offset().top,
1084 left: $quicksearch.offset().left,
1085 height: $quicksearch.outerHeight(),
1086 width: $quicksearch.outerWidth(),
1087 }).appendTo(document.body).hide();
1089 var prevAlerts = [];
1090 humanMsg.logMsg = function(msg, options) {
1091 $('#show-alerts').popover('hide');
1092 prevAlerts.unshift('<li>' + msg + '</li>');
1093 prevAlerts.splice(5, 999); // Truncate old messages
1096 $('#show-alerts').popover({
1098 placement: 'bottom',
1099 content: function() {
1100 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1104 $('#show-shortcuts').popover({
1106 placement: 'bottom',
1107 content: function() {
1108 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1112 $('#new-record' ).click( function() {
1113 if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1115 openRecord( 'new/', editor );
1119 window.onbeforeunload = function() {
1120 if(editor.modified )
1123 { return undefined; }
1126 $('a.change-framework').click( function() {
1127 $("#loading").show();
1128 editor.setFrameworkCode(
1129 $(this).data( 'frameworkcode' ),
1131 function ( error ) {
1132 if ( typeof error !== 'undefined' ) {
1133 humanMsg.displayAlert( _("Failed to change framework"), { className: 'humanError' } );
1135 $('#loading').hide();
1141 Preferences.Load( [% logged_in_user.borrowernumber || 0 | html %] );
1142 displayPreferences(editor);
1143 makeAuthorisedValueWidgets( '' );
1146 onresults: function(data) { showSearchResults( editor, data ) },
1147 onerror: handleSearchError,
1150 function finishCb( data ) {
1152 humanMsg.displayAlert( data.error );
1153 openRecord( 'new/', editor, finishCb );
1156 Resources.GetAll().done( function() {
1157 $("#loading").hide();
1158 $( window ).resize( onResize ).resize();
1163 if ( "[% auth_forwarded_hash | html %]" ) {
1164 document.location.hash = "[% auth_forwarded_hash | html %]";
1167 if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1168 openRecord( 'new/', editor, finishCb );