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');
945 $.each( result.errors, function() {
946 var error = '<b>' + _("Line ") + (this.line + 1) + ':</b> ';
948 switch ( this.error ) {
949 case 'failed': error += _("failed to run"); break;
950 case 'unrecognized': error += _("unrecognized command"); break;
956 humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
961 $('#delete-macro').click( function() {
962 if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
964 storeMacro( macroEditor.activeMacro, undefined );
966 loadMacro( undefined );
971 $( '#switch-editor' ).click( function() {
972 if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
974 $.cookie( 'catalogue_editor_[% USER_INFO.borrowernumber %]', 'basic', { expires: 365, path: '/' } );
976 if ( state.backend == 'catalog' ) {
977 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
978 } else if ( state.backend == 'new' ) {
979 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
981 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
985 $( '#show-advanced-search' ).click( function() {
986 showAdvancedSearch();
991 $('#advanced-search').submit( function() {
992 startAdvancedSearch();
997 $( document ).on( 'click', 'a.search-nav', function() {
998 if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
999 $("#search-overlay").show();
1005 $( document ).on( 'click', 'th[data-sort-label]', function() {
1008 if ( $( this ).hasClass( 'sorting_asc' ) ) {
1014 if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1015 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1017 $("#search-overlay").show();
1023 $( document ).on( 'change', 'input.search-toggle-server', function() {
1024 var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1025 server.checked = this.checked;
1027 if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1028 $("#search-overlay").show();
1036 $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1037 $(this).modal({ show: false });
1040 var $quicksearch = $('#quicksearch fieldset');
1041 $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1042 position: 'absolute',
1043 top: $quicksearch.offset().top,
1044 left: $quicksearch.offset().left,
1045 height: $quicksearch.outerHeight(),
1046 width: $quicksearch.outerWidth(),
1047 }).appendTo(document.body).hide();
1049 var prevAlerts = [];
1050 humanMsg.logMsg = function(msg, options) {
1051 $('#show-alerts').popover('hide');
1052 prevAlerts.unshift('<li>' + msg + '</li>');
1053 prevAlerts.splice(5, 999); // Truncate old messages
1056 $('#show-alerts').popover({
1058 placement: 'bottom',
1059 content: function() {
1060 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1064 $('#show-shortcuts').popover({
1066 placement: 'bottom',
1067 content: function() {
1068 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1072 $('#new-record' ).click( function() {
1073 if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1075 openRecord( 'new/', editor );
1080 Preferences.Load( [% USER_INFO.borrowernumber || 0 %] );
1081 displayPreferences(editor);
1082 makeAuthorisedValueWidgets( '' );
1085 onresults: function(data) { showSearchResults( editor, data ) },
1086 onerror: handleSearchError,
1089 function finishCb( data ) {
1090 if ( data.error ) openRecord( 'new/', editor, finishCb );
1092 Resources.GetAll().done( function() {
1093 $("#loading").hide();
1094 $( window ).resize( onResize ).resize();
1099 if ( "[% auth_forwarded_hash %]" ) {
1100 document.location.hash = "[% auth_forwarded_hash %]";
1103 if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1104 openRecord( 'new/', editor, finishCb );