1 <script src="[% interface %]/lib/codemirror/codemirror-compressed.js"></script>
2 <script src="[% interface %]/lib/filesaver.js"></script>
3 <script src="[% interface %]/lib/koha/cateditor/marc-mode.js"></script>
4 <script src="[% interface %]/lib/require.js"></script>
7 baseUrl: '[% interface %]/lib/koha/cateditor/',
10 marcflavour: '[% marcflavour %]',
11 themelang: '[% themelang %]',
18 [% IF marcflavour == 'MARC21' %]
19 [% PROCESS 'cateditor-widgets-marc21.inc' %]
21 <script>var editorWidgets = {};</script>
25 require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'preferences', 'resources', 'text-marc', 'widget' ], function( KohaBackend, Search, Macros, MARCEditor, MARC, Preferences, Resources, TextMARC, Widget ) {
27 'koha:biblioserver': {
28 name: _("Local catalog"),
32 [%- FOREACH server = z3950_servers -%]
34 name: '[% server.servername %]',
35 recordtype: '[% server.recordtype %]',
36 checked: [% server.checked ? 'true' : 'false' %],
41 // The columns that should show up in a search, in order, and keyed by the corresponding <metadata> tag in the XSL and Pazpar2 config
43 [ "local_number", _("Local number") ],
44 [ "title", _("Title") ],
45 [ "series", _("Series title") ],
46 [ "author", _("Author") ],
47 [ "lccn", _("LCCN") ],
48 [ "isbn", _("ISBN") ],
49 [ "issn", _("ISSN") ],
50 [ "medium", _("Medium") ],
51 [ "edition", _("Edition") ],
52 [ "notes", _("Notes") ],
57 saveBackend: 'catalog',
64 function makeAuthorisedValueWidgets( frameworkCode ) {
65 $.each( KohaBackend.GetAllTagsInfo( frameworkCode ), function( tag, tagInfo ) {
66 $.each( tagInfo.subfields, function( subfield, subfieldInfo ) {
67 if ( !subfieldInfo.authorised_value ) return;
68 var authvals = KohaBackend.GetAuthorisedValues( subfieldInfo.authorised_value );
69 if ( !authvals ) return;
71 var defaultvalue = subfield.defaultvalue || authvals[0].value;
73 Widget.Register( tag + subfield, {
75 var $result = $( '<span class="subfield-widget"></span>' );
79 postCreate: function() {
80 var value = defaultvalue;
83 $.each( authvals, function() {
84 if ( this.value == widget.text ) {
89 this.setText( value );
91 $( '<select></select>' ).appendTo( this.node );
92 var $node = $( this.node ).find( 'select' );
93 $.each( authvals, function( undef, authval ) {
94 $node.append( '<option value="' + authval.value + '"' + (authval.value == value ? ' selected="selected"' : '') + '>' + authval.lib + '</option>' );
96 $node.val( this.text );
98 $node.change( $.proxy( function() {
99 this.setText( $node.val() );
102 makeTemplate: function() {
110 function bindGlobalKeys() {
111 shortcut.add( 'ctrl+s', function(event) {
112 $( '#save-record' ).click();
114 event.preventDefault();
117 shortcut.add( 'alt+ctrl+k', function(event) {
118 $( '#search-by-keywords' ).focus();
123 shortcut.add( 'alt+ctrl+a', function(event) {
124 $( '#search-by-author' ).focus();
129 shortcut.add( 'alt+ctrl+i', function(event) {
130 $( '#search-by-isbn' ).focus();
135 shortcut.add( 'alt+ctrl+t', function(event) {
136 $( '#search-by-title' ).focus();
141 shortcut.add( 'ctrl+h', function() {
142 var field = editor.getCurrentField();
144 if ( !field ) return;
146 window.open( getFieldHelpURL( field.tag ) );
149 $('#quicksearch .search-box').each( function() {
150 shortcut.add( 'enter', $.proxy( function() {
153 $('#quicksearch .search-box').each( function() {
154 if ( !this.value ) return;
156 terms.push( [ $(this).data('qualifier'), this.value ] );
159 if ( !terms.length ) return;
161 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
162 $("#search-overlay").show();
167 }, this), { target: this, type: 'keypress' } );
171 function getFieldHelpURL( tag ) {
172 [% IF ( marcflavour == 'MARC21' ) %]
173 if ( tag == '000' ) {
174 return "http://www.loc.gov/marc/bibliographic/bdleader.html";
175 } else if ( tag >= '090' && tag < '100' ) {
176 return "http://www.loc.gov/marc/bibliographic/bd09x.html";
177 } else if ( tag < '900' ) {
178 return "http://www.loc.gov/marc/bibliographic/bd" + tag + ".html";
180 return "http://www.loc.gov/marc/bibliographic/bd9xx.html";
182 [% ELSIF ( marcflavour == 'UNIMARC' ) %]
183 /* http://archive.ifla.org/VI/3/p1996-1/ is an outdated version of UNIMARC, but
184 seems to be the only version available that can be linked to per tag. More recent
185 versions of the UNIMARC standard are available on the IFLA website only as
188 if ( tag == '000' ) {
189 return "http://archive.ifla.org/VI/3/p1996-1/uni.htm";
192 var url = "http://archive.ifla.org/VI/3/p1996-1/uni" + first + ".htm#";
193 if ( first == '0' ) url += "b";
194 if ( first != '9' ) url += tag;
204 titleForRecord: _("Editing new record"),
205 get: function( id, callback ) {
206 record = new MARC.Record();
207 KohaBackend.FillRecord( '', record );
213 titleForRecord: _("Editing new full record"),
214 get: function( id, callback ) {
215 record = new MARC.Record();
216 KohaBackend.FillRecord( '', record, true );
222 titleForRecord: _("Editing catalog record #{ID}"),
224 { title: _("view"), href: "/cgi-bin/koha/catalogue/detail.pl?biblionumber={ID}" },
225 { title: _("edit items"), href: "/cgi-bin/koha/cataloguing/additem.pl?biblionumber={ID}" },
227 saveLabel: _("Save to catalog"),
228 get: function( id, callback ) {
229 if ( !id ) return false;
231 KohaBackend.GetRecord( id, callback );
233 save: function( id, record, done ) {
234 function finishCb( data ) {
235 done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
239 KohaBackend.SaveRecord( id, record, finishCb );
241 KohaBackend.CreateRecord( record, finishCb );
246 saveLabel: _("Save as ISO2709 (.mrc) file"),
247 save: function( id, record, done ) {
248 saveAs( new Blob( [record.toISO2709()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.mrc' );
254 saveLabel: _("Save as MARCXML (.xml) file"),
255 save: function( id, record, done ) {
256 saveAs( new Blob( [record.toXML()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.xml' );
262 titleForRecord: _("Editing search result"),
263 get: function( id, callback ) {
264 if ( !id ) return false;
265 if ( !backends.search.records[ id ] ) {
266 callback( { error: _( "Invalid record" ) } );
270 callback( backends.search.records[ id ] );
276 function setSource(parts) {
277 state.backend = parts[0];
278 state.recordID = parts[1];
279 state.canSave = backends[ state.backend ].save != null;
280 state.saveBackend = state.canSave ? state.backend : 'catalog';
282 var backend = backends[state.backend];
284 document.location.hash = '#' + parts[0] + '/' + parts[1];
286 $('#title').text( backend.titleForRecord.replace( '{ID}', parts[1] ) );
288 $.each( backend.links || [], function( i, link ) {
289 $('#title').append(' <a target="_blank" href="' + link.href.replace( '{ID}', parts[1] ) + '">(' + link.title + ')</a>' );
291 $( 'title', document.head ).html( _("Koha › Cataloging › ") + backend.titleForRecord.replace( '{ID}', parts[1] ) );
292 $('#save-record span').text( backends[ state.saveBackend ].saveLabel );
295 function saveRecord( recid, editor, callback ) {
296 var parts = recid.split('/');
297 if ( parts.length != 2 ) return false;
299 if ( !backends[ parts[0] ] || !backends[ parts[0] ].save ) return false;
301 editor.removeErrors();
302 var record = editor.getRecord();
304 if ( record.errors ) {
305 state.saving = false;
306 callback( { error: 'syntax', errors: record.errors } );
310 var errors = KohaBackend.ValidateRecord( '', record );
311 if ( errors.length ) {
312 state.saving = false;
313 callback( { error: 'invalid', errors: errors } );
317 backends[ parts[0] ].save( parts[1], record, function(data) {
318 state.saving = false;
320 if (data.newRecord) {
321 var record = new MARC.Record();
322 record.loadMARCXML(data.newRecord);
323 editor.displayRecord( record );
327 setSource(data.newId);
329 setSource( [ state.backend, state.recordID ] );
332 if (callback) callback( data );
336 function loadRecord( recid, editor, callback ) {
337 var parts = recid.split('/');
338 if ( parts.length != 2 ) return false;
340 if ( !backends[ parts[0] ] || !backends[ parts[0] ].get ) return false;
342 backends[ parts[0] ].get( parts[1], function( record ) {
343 if ( !record.error ) {
344 editor.displayRecord( record );
348 if (callback) callback(record);
354 function openRecord( recid, editor, callback ) {
355 return loadRecord( recid, editor, function ( record ) {
356 setSource( recid.split('/') );
358 if (callback) callback( record );
363 function showAdvancedSearch() {
364 $('#advanced-search-servers').empty();
365 $.each( z3950Servers, function( server_id, server ) {
366 $('#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>' );
368 $('#advanced-search-ui').modal('show');
371 function startAdvancedSearch() {
374 $('#advanced-search-ui .search-box').each( function() {
375 if ( !this.value ) return;
377 terms.push( [ $(this).data('qualifier'), this.value ] );
380 if ( !terms.length ) return;
382 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
383 $('#advanced-search-ui').modal('hide');
384 $("#search-overlay").show();
389 function showResultsBox(data) {
390 $('#search-top-pages, #search-bottom-pages').find('nav').empty();
391 $('#searchresults thead tr').empty();
392 $('#searchresults tbody').empty();
393 $('#search-serversinfo').empty().append('<li>' + _("Loading...") + '</li>');
394 $('#search-results-ui').modal('show');
397 function showSearchSorting( sort_key, sort_direction ) {
398 var $th = $('#searchresults thead tr th[data-sort-label="' + sort_key + '"]');
399 $th.parent().find( 'th[data-sort-label]' ).attr( 'class', 'sorting' );
401 if ( sort_direction == 'asc' ) {
403 $th.attr( 'class', 'sorting_asc' );
406 $th.attr( 'class', 'sorting_desc' );
410 function showSearchResults( editor, data ) {
411 backends.search.records = {};
413 $('#searchresults thead tr').empty();
414 $('#searchresults tbody').empty();
415 $('#search-serversinfo').empty();
417 $.each( z3950Servers, function( server_id, server ) {
418 var num_fetched = data.num_fetched[server_id];
420 if ( data.errors[server_id] ) {
421 num_fetched = data.errors[server_id];
422 } else if ( num_fetched == null ) {
424 } else if ( num_fetched < data.num_hits[server_id] ) {
428 $('#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>' );
431 var seenColumns = {};
433 $.each( data.hits, function( undef, hit ) {
434 $.each( hit.metadata, function(key) {
435 seenColumns[key] = true;
439 $('#searchresults thead tr').append('<th>' + _("Source") + '</th>');
441 $.each( z3950Labels, function( undef, label ) {
442 if ( seenColumns[ label[0] ] ) {
443 $('#searchresults thead tr').append( '<th class="sorting" data-sort-label="' + label[0] + '">' + label[1] + '</th>' );
447 showSearchSorting( data.sort_key, data.sort_direction );
449 $('#searchresults thead tr').append('<th>' + _("Tools") + '</th>');
451 var bibnumMap = KohaBackend.GetSubfieldForKohaField('biblio.biblionumber');
452 $.each( data.hits, function( undef, hit ) {
453 backends.search.records[ hit.server + ':' + hit.index ] = hit.record;
455 switch ( hit.server ) {
456 case 'koha:biblioserver':
457 var bibnumField = hit.record.field( bibnumMap[0] );
459 if ( bibnumField && bibnumField.hasSubfield( bibnumMap[1] ) ) {
460 hit.id = 'catalog/' + bibnumField.subfield( bibnumMap[1] );
464 // Otherwise, fallthrough
467 hit.id = 'search/' + hit.server + ':' + hit.index;
471 result += '<td class="sourcecol">' + z3950Servers[ hit.server ].name + '</td>';
473 $.each( z3950Labels, function( undef, label ) {
474 if ( !seenColumns[ label[0] ] ) return;
476 if ( hit.metadata[ label[0] ] ) {
477 result += '<td class="infocol">' + hit.metadata[ label[0] ] + '</td>';
479 result += '<td class="infocol"> </td>';
483 result += '<td class="toolscol"><ul><li><a href="#" class="marc-link">' + _("View MARC") + '</a></li>';
484 result += '<li><a href="#" class="open-link">' + ( hit.server == 'koha:biblioserver' ? _("Edit") : _("Import") ) + '</a></li>';
485 if ( state.canSave ) result += '<li><a href="#" class="substitute-link" title="' + _("Replace the current record's contents") + '">' + _("Substitute") + '</a></li>';
486 result += '</ul></td></tr>';
488 var $tr = $( result );
489 $tr.find( '.marc-link' ).click( function() {
490 var $info_columns = $tr.find( '.infocol' );
491 var $marc_column = $tr.find( '.marccol' );
493 if ( !$marc_column.length ) {
494 $marc_column = $( '<td class="marccol" colspan="' + $info_columns.length + '"></td>' ).insertAfter( $info_columns.eq(-1) ).hide();
495 CodeMirror.runMode( TextMARC.RecordToText( hit.record ), 'marc', $marc_column[0] );
498 if ( $marc_column.is(':visible') ) {
499 $tr.find('.marc-link').text( _("View MARC") );
500 $info_columns.show();
503 $tr.find('.marc-link').text( _("Hide MARC") );
505 $info_columns.hide();
510 $tr.find( '.open-link' ).click( function() {
511 $( '#search-results-ui' ).modal('hide');
512 openRecord( hit.id, editor );
516 $tr.find( '.substitute-link' ).click( function() {
517 $( '#search-results-ui' ).modal('hide');
518 loadRecord( hit.id, editor );
522 $('#searchresults tbody').append( $tr );
526 var cur_page = data.offset / data.page_size;
527 var max_page = Math.ceil( data.total_fetched / data.page_size ) - 1;
529 if ( cur_page != 0 ) {
530 pages.push( '<li><a class="search-nav" href="#" data-offset="' + (data.offset - data.page_size) + '"><span aria-hidden="true">«</span> ' + _("Previous") + '</a></li>' );
533 for ( var page = Math.max( 0, cur_page - 9 ); page <= Math.min( max_page, cur_page + 9 ); page++ ) {
534 if ( page == cur_page ) {
535 pages.push( ' <li class="active"><a href="#">' + ( page + 1 ) + '</a></li>' );
537 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + ( page * data.page_size ) + '">' + ( page + 1 ) + '</a></li>' );
541 if ( cur_page < max_page ) {
542 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + (data.offset + data.page_size) + '">' + _("Next") + ' <span aria-hidden="true">»</span></a></li>' );
545 $( '#search-top-pages, #search-bottom-pages' ).find( 'nav' ).html( pages.length > 1 ? ( '<ul class="pagination pagination-sm">' + pages.join( '' ) + '</ul>' ) : '' );
547 var $overlay = $('#search-overlay');
548 $overlay.find('span').text(_("Loading"));
549 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
551 if ( data.activeclients ) {
552 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
555 $overlay.find('.bar').css( { display: 'block', width: '100%' } );
557 $('#searchresults')[0].focus();
561 function invalidateSearchResults() {
562 var $overlay = $('#search-overlay');
563 $overlay.find('span').text(_("Search expired, please try again"));
564 $overlay.find('.bar').css( { display: 'none' } );
568 function handleSearchError(error) {
569 if (error.code == 1) {
570 invalidateSearchResults();
573 humanMsg.displayMsg( '<h3>' + _("Internal search error") + '</h3><p>' + error + '</p><p>' + _("Please refresh the page and try again.") + '</p>', { className: 'humanError' } );
577 function handleSearchInitError(error) {
578 $('#quicksearch-overlay').fadeIn().find('p').text(error);
581 // Preference functions
582 function showPreference( pref ) {
583 var value = Preferences.user[pref];
587 $( '#set-field-widgets' ).text( value ? _("Show fields verbatim") : _("Show helpers for fixed and coded fields") );
590 $( '#editor .CodeMirror' ).css( { fontFamily: value } );
594 $( '#editor .CodeMirror' ).css( { fontSize: value } );
598 // Macros loaded on first show of modal
600 case 'selected_search_targets':
601 $.each( z3950Servers, function( server_id, server ) {
602 var saved_val = Preferences.user.selected_search_targets[server_id];
604 if ( saved_val != null ) server.checked = saved_val;
610 function bindPreference( editor, pref ) {
611 function _addHandler( sel, event, handler ) {
612 $( sel ).on( event, function (e) {
614 handler( e, Preferences.user[pref] );
615 Preferences.Save( [% USER_INFO.borrowernumber %] );
616 showPreference(pref);
622 _addHandler( '#set-field-widgets', 'click', function( e, oldValue ) {
623 editor.setUseWidgets( Preferences.user.fieldWidgets = !Preferences.user.fieldWidgets );
627 _addHandler( '#prefs-menu .set-font', 'click', function( e, oldValue ) {
628 Preferences.user.font = $( e.target ).css( 'font-family' );
632 _addHandler( '#prefs-menu .set-fontSize', 'click', function( e, oldValue ) {
633 Preferences.user.fontSize = $( e.target ).css( 'font-size' );
636 case 'selected_search_targets':
637 $( document ).on( 'change', 'input.search-toggle-server', function() {
638 var server_id = $( this ).closest('li').data('server-id');
639 Preferences.user.selected_search_targets[server_id] = this.checked;
640 Preferences.Save( [% USER_INFO.borrowernumber %] );
646 function displayPreferences( editor ) {
647 $.each( Preferences.user, function( pref, value ) {
648 showPreference( pref );
649 bindPreference( editor, pref );
654 function loadMacro( name ) {
655 $( '#macro-list li' ).removeClass( 'active' );
656 macroEditor.activeMacro = name;
659 macroEditor.setValue( '' );
663 $( '#macro-list li[data-name="' + name + '"]' ).addClass( 'active' );
664 var macro = Preferences.user.macros[name];
665 macroEditor.setValue( macro.contents );
666 macroEditor.setOption( 'readOnly', false );
667 $( '#macro-format' ).val( macro.format || 'its' );
668 if ( macro.history ) macroEditor.setHistory( macro.history );
671 function storeMacro( name, macro ) {
673 Preferences.user.macros[name] = macro;
675 delete Preferences.user.macros[name];
678 Preferences.Save( [% USER_INFO.borrowernumber %] );
681 function showSavedMacros( macros ) {
682 var scrollTop = $('#macro-list').scrollTop();
683 $( '#macro-list' ).empty();
684 var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
685 return $.extend( { name: name }, macro );
687 macro_list.sort( function( a, b ) {
688 return a.name.localeCompare(b.name);
690 $.each( macro_list, function( undef, macro ) {
691 var $li = $( '<li data-name="' + macro.name + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
692 $li.click( function() {
693 loadMacro(macro.name);
696 if ( macro.name == macroEditor.activeMacro ) $li.addClass( 'active' );
697 var modified = macro.modified && new Date(macro.modified);
698 $li.find( '.macro-info' ).append(
699 '<li><span class="label">' + _("Last changed:") + '</span>' +
700 ( modified ? ( modified.toLocaleDateString() + ', ' + modified.toLocaleTimeString() ) : _("never") ) + '</li>'
702 $('#macro-list').append($li);
704 var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
705 $new_li.click( function() {
706 // TODO: make this a bit less retro
707 var name = prompt(_("Please enter the name for the new macro:"));
710 if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
714 $('#macro-list').append($new_li);
715 $('#macro-list').scrollTop(scrollTop);
718 function saveMacro() {
719 var name = macroEditor.activeMacro;
721 if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() ) return;
723 macroEditor.savedGeneration = macroEditor.changeGeneration();
724 storeMacro( name, { contents: macroEditor.getValue(), modified: (new Date()).valueOf(), history: macroEditor.getHistory(), format: $('#macro-format').val() } );
725 $('#macro-save-message').text(_("Saved"));
729 $(document).ready( function() {
731 editor = new MARCEditor( {
732 onCursorActivity: function() {
733 $('#status-tag-info').empty();
734 $('#status-subfield-info').empty();
736 var field = editor.getCurrentField();
737 var cur = editor.getCursor();
739 if ( !field ) return;
741 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
742 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
745 $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> ' + taginfo.lib );
747 var subfield = field.getSubfieldAt( cur.ch );
748 if ( !subfield ) return;
750 var subfieldinfo = taginfo.subfields[ subfield.code ];
751 $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
753 if ( subfieldinfo ) {
754 $('#status-subfield-info').append( subfieldinfo.lib );
756 $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
759 $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
762 position: function (elt) { $(elt).insertAfter('#toolbar') },
765 // Automatically detect resizes and change the height of the editor and position of modals.
766 var resizeTimer = null;
767 function onResize() {
768 if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
771 var pos = $('#editor .CodeMirror').position();
772 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
774 $('.modal-body').each( function() {
775 $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
780 $( '#macro-ui' ).on( 'shown.bs.modal', function() {
781 if ( macroEditor ) return;
783 macroEditor = CodeMirror(
784 $('#macro-editor')[0],
787 'Ctrl-D': function( cm ) {
788 var cur = cm.getCursor();
790 cm.replaceRange( "‡", cur, null );
799 macroEditor.on( 'change', function( cm, change ) {
800 $('#macro-save-message').empty();
801 if ( change.origin == 'setValue' ) return;
803 if ( saveTimeout ) clearTimeout( saveTimeout );
804 saveTimeout = setTimeout( function() {
814 var saveableBackends = [];
815 $.each( backends, function( id, backend ) {
816 if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
818 saveableBackends.sort();
819 $.each( saveableBackends, function( undef, backend ) {
820 $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
823 var macro_format_list = $.map( Macros.formats, function( format, name ) {
824 return $.extend( { name: name }, format );
826 macro_format_list.sort( function( a, b ) {
827 return a.description.localeCompare(b.description);
829 $.each( macro_format_list, function() {
830 $('#macro-format').append( '<option value="' + this.name + '">' + this.description + '</option>' );
834 $( '#save-record, #save-dropdown a' ).click( function() {
835 $( '#save-record' ).find('i').attr( 'class', 'fa fa-spinner' ).siblings( 'span' ).text( _("Saving...") );
837 function finishCb(result) {
838 if ( result.error == 'syntax' ) {
839 humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
840 } else if ( result.error == 'invalid' ) {
841 humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
842 } else if ( !result.error ) {
843 humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
846 $.each( result.errors || [], function( undef, error ) {
847 switch ( error.type ) {
849 editor.addError( error.line, _("Invalid tag number") );
852 editor.addError( error.line, _("Invalid indicators") );
855 editor.addError( error.line, _("Tag has no subfields") );
858 editor.addError( null, _("Missing mandatory tag: ") + error.tag );
860 case 'missingSubfield':
861 if ( error.subfield == '@' ) {
862 editor.addError( error.line, _("Missing control field contents") );
864 editor.addError( error.line, _("Missing mandatory subfield: ‡") + error.subfield );
867 case 'unrepeatableTag':
868 editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
870 case 'unrepeatableSubfield':
871 editor.addError( error.line, _("Subfield ‡") + error.subfield + _(" cannot be repeated") );
873 case 'itemTagUnsupported':
874 editor.addError( error.line, _("Item tags cannot currently be saved") );
879 $( '#save-record' ).find('i').attr( 'class', 'fa fa-hdd-o' );
881 if ( result.error ) {
882 // Reset backend info
883 setSource( [ state.backend, state.recordID ] );
887 var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
888 if ( state.backend == backend ) {
889 saveRecord( backend + '/' + state.recordID, editor, finishCb );
891 saveRecord( backend + '/', editor, finishCb );
897 $('#import-records').click( function() {
898 $('#import-records-input')
900 .change( function() {
901 if ( !this.files || !this.files.length ) return;
903 var file = this.files[0];
904 var reader = new FileReader();
906 reader.onload = function() {
907 var record = new MARC.Record();
909 if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
910 record.loadISO2709( reader.result );
911 } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
912 record.loadMARCXML( reader.result );
914 humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
918 if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
920 editor.displayRecord( record );
923 reader.readAsText( file );
930 $('#open-macros').click( function() {
931 $('#macro-ui').modal('show');
936 $('#run-macro').click( function() {
937 var result = Macros.Run( editor, $('#macro-format').val(), macroEditor.getValue() );
939 if ( !result.errors.length ) {
940 $('#macro-ui').modal('hide');
941 editor.focus(); //Return cursor to editor after macro run
946 $.each( result.errors, function() {
947 var error = '<b>' + _("Line ") + (this.line + 1) + ':</b> ';
949 switch ( this.error ) {
950 case 'failed': error += _("failed to run"); break;
951 case 'unrecognized': error += _("unrecognized command"); break;
957 humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
962 $('#delete-macro').click( function() {
963 if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
965 storeMacro( macroEditor.activeMacro, undefined );
967 loadMacro( undefined );
972 $( '#switch-editor' ).click( function() {
973 if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
975 $.cookie( 'catalogue_editor_[% USER_INFO.borrowernumber %]', 'basic', { expires: 365, path: '/' } );
977 if ( state.backend == 'catalog' ) {
978 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
979 } else if ( state.backend == 'new' ) {
980 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
982 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
986 $( '#show-advanced-search' ).click( function() {
987 showAdvancedSearch();
992 $('#advanced-search').submit( function() {
993 startAdvancedSearch();
998 $( document ).on( 'click', 'a.search-nav', function() {
999 if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1000 $("#search-overlay").show();
1006 $( document ).on( 'click', 'th[data-sort-label]', function() {
1009 if ( $( this ).hasClass( 'sorting_asc' ) ) {
1015 if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1016 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1018 $("#search-overlay").show();
1024 $( document ).on( 'change', 'input.search-toggle-server', function() {
1025 var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1026 server.checked = this.checked;
1028 if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1029 $("#search-overlay").show();
1037 $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1038 $(this).modal({ show: false });
1041 var $quicksearch = $('#quicksearch fieldset');
1042 $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1043 position: 'absolute',
1044 top: $quicksearch.offset().top,
1045 left: $quicksearch.offset().left,
1046 height: $quicksearch.outerHeight(),
1047 width: $quicksearch.outerWidth(),
1048 }).appendTo(document.body).hide();
1050 var prevAlerts = [];
1051 humanMsg.logMsg = function(msg, options) {
1052 $('#show-alerts').popover('hide');
1053 prevAlerts.unshift('<li>' + msg + '</li>');
1054 prevAlerts.splice(5, 999); // Truncate old messages
1057 $('#show-alerts').popover({
1059 placement: 'bottom',
1060 content: function() {
1061 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1065 $('#show-shortcuts').popover({
1067 placement: 'bottom',
1068 content: function() {
1069 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1073 $('#new-record' ).click( function() {
1074 if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1076 openRecord( 'new/', editor );
1081 Preferences.Load( [% USER_INFO.borrowernumber || 0 %] );
1082 displayPreferences(editor);
1083 makeAuthorisedValueWidgets( '' );
1086 onresults: function(data) { showSearchResults( editor, data ) },
1087 onerror: handleSearchError,
1090 function finishCb( data ) {
1091 if ( data.error ) openRecord( 'new/', editor, finishCb );
1093 Resources.GetAll().done( function() {
1094 $("#loading").hide();
1095 $( window ).resize( onResize ).resize();
1100 if ( "[% auth_forwarded_hash %]" ) {
1101 document.location.hash = "[% auth_forwarded_hash %]";
1104 if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1105 openRecord( 'new/', editor, finishCb );