3 [% Asset.js("lib/codemirror/codemirror.min.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 %]
11 <!-- cateditor-ui.inc -->
13 [% FOREACH shortcut IN shortcuts -%]
14 var [% shortcut.shortcut_name | html %] = "[% shortcut.shortcut_keys | html %]";
17 [%- FOREACH authtag = authtags -%]
18 [% authtag.tagfield | html %]: {
19 subfield: '[% authtag.tagsubfield | html %]',
20 authtypecode: '[% authtag.authtypecode | html %]',
25 baseUrl: '[% interface | html %]/lib/koha/cateditor/',
28 marcflavour: '[% marcflavour | html %]',
29 themelang: '[% themelang | html %]',
36 [% IF marcflavour == 'MARC21' %]
37 [% PROCESS 'cateditor-widgets-marc21.inc' %]
39 <script>var editorWidgets = {};</script>
43 require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'preferences', 'resources', 'text-marc', 'widget' ], function( KohaBackend, Search, Macros, MARCEditor, MARC, Preferences, Resources, TextMARC, Widget ) {
45 'koha:biblioserver': {
46 name: _("Local catalog"),
50 [%- FOREACH server = z3950_servers -%]
51 [% server.id | html %]: {
52 name: '[% server.servername | html %]',
53 recordtype: '[% server.recordtype | html %]',
54 checked: [% server.checked ? 'true' : 'false' | html %],
59 // The columns that should show up in a search, in order, and keyed by the corresponding <metadata> tag in the XSL and Pazpar2 config
61 [ "local_number", _("Local number") ],
62 [ "title", _("Title") ],
63 [ "subtitle",_("Subtitle") ],
64 [ "series", _("Series title") ],
65 [ "author", _("Author") ],
66 [ "lccn", _("LCCN") ],
67 [ "isbn", _("ISBN") ],
68 [ "issn", _("ISSN") ],
69 [ "medium", _("Medium") ],
70 [ "edition", _("Edition") ],
71 [ "date", _("Published") ],
72 [ "notes", _("Notes") ],
77 saveBackend: 'catalog',
84 function makeAuthorisedValueWidgets( frameworkCode ) {
85 $.each( KohaBackend.GetAllTagsInfo( frameworkCode ), function( tag, tagInfo ) {
86 $.each( tagInfo.subfields, function( subfield, subfieldInfo ) {
87 if ( !subfieldInfo.authorised_value ) return;
88 var authvals = KohaBackend.GetAuthorisedValues( subfieldInfo.authorised_value );
89 if ( !authvals ) return;
91 var defaultvalue = subfield.defaultvalue || authvals[0].value;
93 Widget.Register( tag + subfield, {
95 var $result = $( '<span class="subfield-widget"></span>' );
99 postCreate: function() {
100 var value = defaultvalue;
103 $.each( authvals, function() {
104 if ( this.value == widget.text ) {
109 this.setText( value );
111 $( '<select></select>' ).appendTo( this.node );
112 var $node = $( this.node ).find( 'select' );
113 $.each( authvals, function( undef, authval ) {
114 $node.append( '<option value="' + authval.value + '"' + (authval.value == value ? ' selected="selected"' : '') + '>' + authval.lib + '</option>' );
116 $node.val( this.text );
118 $node.change( $.proxy( function() {
119 this.setText( $node.val() );
122 makeTemplate: function() {
130 function bindGlobalKeys() {
131 shortcut.add( 'ctrl+s', function(event) {
132 $( '#save-record' ).click();
134 event.preventDefault();
137 shortcut.add( 'alt+ctrl+k', function(event) {
138 $( '#search-by-keywords' ).focus();
143 shortcut.add( 'alt+ctrl+a', function(event) {
144 $( '#search-by-author' ).focus();
149 shortcut.add( 'alt+ctrl+i', function(event) {
150 $( '#search-by-isbn' ).focus();
155 shortcut.add( 'alt+ctrl+t', function(event) {
156 $( '#search-by-title' ).focus();
161 shortcut.add( 'ctrl+h', function() {
162 var field = editor.getCurrentField();
164 if ( !field ) return;
166 window.open( getFieldHelpURL( field.tag ) );
169 $('#quicksearch .search-box').each( function() {
170 shortcut.add( 'enter', $.proxy( function() {
173 $('#quicksearch .search-box').each( function() {
174 if ( !this.value ) return;
176 terms.push( [ $(this).data('qualifier'), this.value ] );
179 if ( !terms.length ) return;
181 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
182 $("#search-overlay").show();
187 }, this), { target: this, type: 'keypress' } );
191 function getFieldHelpURL( tag ) {
192 [% IF Koha.Preference('marcfielddocurl') %]
193 var docurl = "[% Koha.Preference('marcfielddocurl').replace('"','"') | html %]";
194 docurl = docurl.replace("{MARC}", "[% marcflavour | html %]");
195 docurl = docurl.replace("{FIELD}", ""+tag);
196 docurl = docurl.replace("{LANG}", "[% lang | html %]");
198 [% ELSIF ( marcflavour == 'MARC21' ) %]
199 if ( tag == '000' ) {
200 return "http://www.loc.gov/marc/bibliographic/bdleader.html";
201 } else if ( tag >= '090' && tag < '100' ) {
202 return "http://www.loc.gov/marc/bibliographic/bd09x.html";
203 } else if ( tag < '900' ) {
204 return "http://www.loc.gov/marc/bibliographic/bd" + tag + ".html";
206 return "http://www.loc.gov/marc/bibliographic/bd9xx.html";
208 [% ELSIF ( marcflavour == 'UNIMARC' ) %]
209 /* http://archive.ifla.org/VI/3/p1996-1/ is an outdated version of UNIMARC, but
210 seems to be the only version available that can be linked to per tag. More recent
211 versions of the UNIMARC standard are available on the IFLA website only as
214 if ( tag == '000' ) {
215 return "http://archive.ifla.org/VI/3/p1996-1/uni.htm";
218 var url = "http://archive.ifla.org/VI/3/p1996-1/uni" + first + ".htm#";
219 if ( first == '0' ) url += "b";
220 if ( first != '9' ) url += tag;
230 titleForRecord: _("Editing new record"),
231 get: function( id, callback ) {
232 record = new MARC.Record();
233 KohaBackend.FillRecord( '', record );
239 titleForRecord: _("Editing new full record"),
240 get: function( id, callback ) {
241 record = new MARC.Record();
242 KohaBackend.FillRecord( '', record, true );
248 titleForRecord: _("Editing duplicate record of #{ID}"),
249 saveLabel: _("Duplicate"),
250 get: function( id, callback ) {
251 if ( !id ) return false;
253 KohaBackend.GetRecord( id, callback );
255 save: function( id, record, done ) {
256 function finishCb( data ) {
257 done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
260 KohaBackend.CreateRecord( record, finishCb );
264 titleForRecord: _("Editing catalog record #{ID}"),
266 { title: _("view"), href: "/cgi-bin/koha/catalogue/detail.pl?biblionumber={ID}" },
267 { title: _("edit items"), href: "/cgi-bin/koha/cataloguing/additem.pl?biblionumber={ID}" },
269 saveLabel: _("Save to catalog"),
270 get: function( id, callback ) {
271 if ( !id ) return false;
273 KohaBackend.GetRecord( id, callback );
275 save: function( id, record, done ) {
276 function finishCb( data ) {
277 done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
281 KohaBackend.SaveRecord( id, record, finishCb );
283 KohaBackend.CreateRecord( record, finishCb );
288 saveLabel: _("Save as MARC (.mrc) file"),
289 save: function( id, record, done ) {
290 saveAs( new Blob( [record.toISO2709()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.mrc' );
296 saveLabel: _("Save as MARCXML (.xml) file"),
297 save: function( id, record, done ) {
298 saveAs( new Blob( [record.toXML()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.xml' );
304 titleForRecord: _("Editing search result"),
305 get: function( id, callback ) {
306 if ( !id ) return false;
307 if ( !backends.search.records[ id ] ) {
308 callback( { error: _( "Invalid record" ) } );
312 callback( backends.search.records[ id ] );
318 function setSource(parts) {
319 state.backend = parts[0];
320 state.recordID = parts[1];
321 state.canSave = backends[ state.backend ].save != null;
322 state.saveBackend = state.canSave ? state.backend : 'catalog';
324 var backend = backends[state.backend];
326 document.location.hash = '#' + parts[0] + '/' + parts[1];
328 $('#title').text( backend.titleForRecord.replace( '{ID}', parts[1] ) );
330 $.each( backend.links || [], function( i, link ) {
331 $('#title').append(' <a target="_blank" href="' + link.href.replace( '{ID}', parts[1] ) + '">(' + link.title + ')</a>' );
333 $( 'title', document.head ).html( _("Koha › Cataloging › ") + backend.titleForRecord.replace( '{ID}', parts[1] ) );
334 $('#save-record span').text( backends[ state.saveBackend ].saveLabel );
337 function saveRecord( recid, editor, callback ) {
338 var parts = recid.split('/');
339 if ( parts.length != 2 ) return false;
341 if ( !backends[ parts[0] ] || !backends[ parts[0] ].save ) return false;
343 editor.removeErrors();
344 var record = editor.getRecord();
346 if ( record.errors ) {
347 state.saving = false;
348 callback( { error: 'syntax', errors: record.errors } );
352 var errors = KohaBackend.ValidateRecord( '', record );
353 if ( errors.length ) {
354 state.saving = false;
355 callback( { error: 'invalid', errors: errors } );
359 backends[ parts[0] ].save( parts[1], record, function(data) {
360 state.saving = false;
362 if (data.newRecord) {
363 var record = new MARC.Record();
364 record.loadMARCXML(data.newRecord);
365 record.frameworkcode = data.newRecord.frameworkcode;
366 editor.displayRecord( record );
370 setSource(data.newId);
372 setSource( [ state.backend, state.recordID ] );
375 if (callback) callback( data );
379 function loadRecord( recid, editor, callback ) {
380 var parts = recid.split('/');
381 if ( parts.length != 2 ) return false;
383 if ( !backends[ parts[0] ] || !backends[ parts[0] ].get ) return false;
385 backends[ parts[0] ].get( parts[1], function( record ) {
386 if ( !record.error ) {
387 editor.displayRecord( record );
391 if (callback) callback(record);
397 function openRecord( recid, editor, callback ) {
398 return loadRecord( recid, editor, function ( record ) {
399 setSource( recid.split('/') );
401 if (callback) callback( record );
406 function showAdvancedSearch() {
407 $('#advanced-search-servers').empty();
408 $.each( z3950Servers, function( server_id, server ) {
409 $('#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>' );
411 $('#advanced-search-ui').modal('show');
414 function startAdvancedSearch() {
417 $('#advanced-search-ui .search-box').each( function() {
418 if ( !this.value ) return;
420 terms.push( [ $(this).data('qualifier'), this.value ] );
423 if ( !terms.length ) return;
425 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
426 $('#advanced-search-ui').modal('hide');
427 $("#search-overlay").show();
432 function showResultsBox(data) {
433 $('#search-top-pages, #search-bottom-pages').find('nav').empty();
434 $('#searchresults thead tr').empty();
435 $('#searchresults tbody').empty();
436 $('#search-serversinfo').empty().append('<li>' + _("Loading...") + '</li>');
437 $('#search-results-ui').modal('show');
440 function showSearchSorting( sort_key, sort_direction ) {
441 var $th = $('#searchresults thead tr th[data-sort-label="' + sort_key + '"]');
442 $th.parent().find( 'th[data-sort-label]' ).attr( 'class', 'sorting' );
444 if ( sort_direction == 'asc' ) {
446 $th.attr( 'class', 'sorting_asc' );
449 $th.attr( 'class', 'sorting_desc' );
453 function showSearchResults( editor, data ) {
454 backends.search.records = {};
456 $('#searchresults thead tr').empty();
457 $('#searchresults tbody').empty();
458 $('#search-serversinfo').empty();
460 $.each( z3950Servers, function( server_id, server ) {
461 var num_fetched = data.num_fetched[server_id];
463 if ( data.errors[server_id] ) {
464 num_fetched = data.errors[server_id];
465 } else if ( num_fetched == null ) {
467 } else if ( num_fetched < data.num_hits[server_id] ) {
471 $('#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>' );
474 var seenColumns = {};
476 $.each( data.hits, function( undef, hit ) {
477 $.each( hit.metadata, function(key) {
478 seenColumns[key] = true;
482 $('#searchresults thead tr').append('<th>' + _("Source") + '</th>');
484 $.each( z3950Labels, function( undef, label ) {
485 if ( seenColumns[ label[0] ] ) {
486 $('#searchresults thead tr').append( '<th class="sorting" data-sort-label="' + label[0] + '">' + label[1] + '</th>' );
490 showSearchSorting( data.sort_key, data.sort_direction );
492 $('#searchresults thead tr').append('<th>' + _("Tools") + '</th>');
494 var bibnumMap = KohaBackend.GetSubfieldForKohaField('biblio.biblionumber');
495 $.each( data.hits, function( undef, hit ) {
496 backends.search.records[ hit.server + ':' + hit.index ] = hit.record;
498 switch ( hit.server ) {
499 case 'koha:biblioserver':
500 var bibnumField = hit.record.field( bibnumMap[0] );
502 if ( bibnumField && bibnumField.hasSubfield( bibnumMap[1] ) ) {
503 hit.id = 'catalog/' + bibnumField.subfield( bibnumMap[1] );
507 // Otherwise, fallthrough
510 hit.id = 'search/' + hit.server + ':' + hit.index;
514 result += '<td class="sourcecol">' + z3950Servers[ hit.server ].name + '</td>';
516 $.each( z3950Labels, function( undef, label ) {
517 if ( !seenColumns[ label[0] ] ) return;
519 if ( hit.metadata[ label[0] ] ) {
520 result += '<td class="infocol">' + hit.metadata[ label[0] ] + '</td>';
522 result += '<td class="infocol"> </td>';
526 result += '<td class="toolscol"><ul><li><a href="#" class="marc-link">' + _("View MARC") + '</a></li>';
527 result += '<li><a href="#" class="open-link">' + ( hit.server == 'koha:biblioserver' ? _("Edit") : _("Import") ) + '</a></li>';
528 if ( state.canSave ) result += '<li><a href="#" class="substitute-link" title="' + _("Replace the current record's contents") + '">' + _("Substitute") + '</a></li>';
529 result += '</ul></td></tr>';
531 var $tr = $( result );
532 $tr.find( '.marc-link' ).click( function() {
533 var $info_columns = $tr.find( '.infocol' );
534 var $marc_column = $tr.find( '.marccol' );
536 if ( !$marc_column.length ) {
537 $marc_column = $( '<td class="marccol" colspan="' + $info_columns.length + '"></td>' ).insertAfter( $info_columns.eq(-1) ).hide();
538 CodeMirror.runMode( TextMARC.RecordToText( hit.record ), 'marc', $marc_column[0] );
541 if ( $marc_column.is(':visible') ) {
542 $tr.find('.marc-link').text( _("View MARC") );
543 $info_columns.show();
546 $tr.find('.marc-link').text( _("Hide MARC") );
548 $info_columns.hide();
553 $tr.find( '.open-link' ).click( function() {
554 $( '#search-results-ui' ).modal('hide');
555 openRecord( hit.id, editor );
559 $tr.find( '.substitute-link' ).click( function() {
560 $( '#search-results-ui' ).modal('hide');
561 loadRecord( hit.id, editor );
565 $('#searchresults tbody').append( $tr );
569 var cur_page = data.offset / data.page_size;
570 var max_page = Math.ceil( data.total_fetched / data.page_size ) - 1;
572 if ( cur_page != 0 ) {
573 pages.push( '<li><a class="search-nav" href="#" data-offset="' + (data.offset - data.page_size) + '"><span aria-hidden="true">«</span> ' + _("Previous") + '</a></li>' );
576 for ( var page = Math.max( 0, cur_page - 9 ); page <= Math.min( max_page, cur_page + 9 ); page++ ) {
577 if ( page == cur_page ) {
578 pages.push( ' <li class="active"><a href="#">' + ( page + 1 ) + '</a></li>' );
580 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + ( page * data.page_size ) + '">' + ( page + 1 ) + '</a></li>' );
584 if ( cur_page < max_page ) {
585 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + (data.offset + data.page_size) + '">' + _("Next") + ' <span aria-hidden="true">»</span></a></li>' );
588 $( '#search-top-pages, #search-bottom-pages' ).find( 'nav' ).html( pages.length > 1 ? ( '<ul class="pagination pagination-sm">' + pages.join( '' ) + '</ul>' ) : '' );
590 var $overlay = $('#search-overlay');
591 $overlay.find('span').text(_("Loading"));
592 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
594 if ( data.activeclients ) {
595 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
598 $overlay.find('.bar').css( { display: 'block', width: '100%' } );
600 $('#searchresults')[0].focus();
604 function invalidateSearchResults() {
605 var $overlay = $('#search-overlay');
606 $overlay.find('span').text(_("Search expired, please try again"));
607 $overlay.find('.bar').css( { display: 'none' } );
611 function handleSearchError(error) {
612 if (error.code == 1) {
613 invalidateSearchResults();
616 humanMsg.displayMsg( '<h3>' + _("Internal search error") + '</h3><p>' + error.responseText + '</p><p>' + _("Please refresh the page and try again.") + '</p>', { className: 'humanError' } );
620 function handleSearchInitError(error) {
621 $('#quicksearch-overlay').fadeIn().find('p').text(error);
624 // Preference functions
625 function showPreference( pref ) {
626 var value = Preferences.user[pref];
630 $( '#set-field-widgets' ).text( value ? _("Show fields verbatim") : _("Show helpers for fixed and coded fields") );
633 $( '#editor .CodeMirror' ).css( { fontFamily: value } );
637 $( '#editor .CodeMirror' ).css( { fontSize: value } );
641 // Macros loaded on first show of modal
643 case 'selected_search_targets':
644 $.each( z3950Servers, function( server_id, server ) {
645 var saved_val = Preferences.user.selected_search_targets[server_id];
647 if ( saved_val != null ) server.checked = saved_val;
653 function bindPreference( editor, pref ) {
654 function _addHandler( sel, event, handler ) {
655 $( sel ).on( event, function (e) {
657 handler( e, Preferences.user[pref] );
658 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
659 showPreference(pref);
665 _addHandler( '#set-field-widgets', 'click', function( e, oldValue ) {
666 editor.setUseWidgets( Preferences.user.fieldWidgets = !Preferences.user.fieldWidgets );
670 _addHandler( '#prefs-menu .set-font', 'click', function( e, oldValue ) {
671 Preferences.user.font = $( e.target ).css( 'font-family' );
675 _addHandler( '#prefs-menu .set-fontSize', 'click', function( e, oldValue ) {
676 Preferences.user.fontSize = $( e.target ).css( 'font-size' );
679 case 'selected_search_targets':
680 $( document ).on( 'change', 'input.search-toggle-server', function() {
681 var server_id = $( this ).closest('li').data('server-id');
682 Preferences.user.selected_search_targets[server_id] = this.checked;
683 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
689 function displayPreferences( editor ) {
690 $.each( Preferences.user, function( pref, value ) {
691 showPreference( pref );
692 bindPreference( editor, pref );
697 function loadMacro( name ) {
698 $( '#macro-list li' ).removeClass( 'active' );
699 macroEditor.activeMacro = name;
702 macroEditor.setValue( '' );
706 $( '#macro-list li[data-name="' + name + '"]' ).addClass( 'active' );
707 var macro = Preferences.user.macros[name];
708 macroEditor.setValue( macro.contents );
709 macroEditor.setOption( 'readOnly', false );
710 if ( macro.history ) macroEditor.setHistory( macro.history );
713 function storeMacro( name, macro ) {
715 Preferences.user.macros[name] = macro;
717 delete Preferences.user.macros[name];
720 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
723 function showSavedMacros( macros ) {
724 var scrollTop = $('#macro-list').scrollTop();
725 $( '#macro-list' ).empty();
726 var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
727 return $.extend( { name: name }, macro );
729 macro_list.sort( function( a, b ) {
730 return a.name.localeCompare(b.name);
732 $.each( macro_list, function( undef, macro ) {
733 var $li = $( '<li data-name="' + macro.name + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
734 $li.click( function() {
735 loadMacro(macro.name);
738 if ( macro.name == macroEditor.activeMacro ) $li.addClass( 'active' );
739 var modified = macro.modified && new Date(macro.modified);
740 $li.find( '.macro-info' ).append(
741 '<li><span class="label">' + _("Last changed:") + '</span>' +
742 ( modified ? ( modified.toLocaleDateString() + ', ' + modified.toLocaleTimeString() ) : _("never") ) + '</li>'
744 $('#macro-list').append($li);
746 var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
747 $new_li.click( function() {
748 // TODO: make this a bit less retro
749 var name = prompt(_("Please enter the name for the new macro:"));
752 if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
756 $('#macro-list').append($new_li);
757 $('#macro-list').scrollTop(scrollTop);
760 function saveMacro() {
761 var name = macroEditor.activeMacro;
763 if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() ) return;
765 macroEditor.savedGeneration = macroEditor.changeGeneration();
766 storeMacro( name, { contents: macroEditor.getValue(), modified: (new Date()).valueOf(), history: macroEditor.getHistory() } );
767 $('#macro-save-message').text(_("Saved"));
771 $(document).ready( function() {
773 editor = new MARCEditor( {
774 onCursorActivity: function() {
775 $('#status-tag-info').empty();
776 $('#status-subfield-info').empty();
778 var field = editor.getCurrentField();
779 var cur = editor.getCursor();
781 if ( !field ) return;
783 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
784 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
787 $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> ' + taginfo.lib );
789 var subfield = field.getSubfieldAt( cur.ch );
790 if ( !subfield ) return;
792 var subfieldinfo = taginfo.subfields[ subfield.code ];
793 $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
795 if ( subfieldinfo ) {
796 $('#status-subfield-info').append( subfieldinfo.lib );
798 $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
801 $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
804 position: function (elt) { $(elt).insertAfter('#toolbar') },
807 // Automatically detect resizes and change the height of the editor and position of modals.
808 var resizeTimer = null;
809 function onResize() {
810 if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
813 var pos = $('#editor .CodeMirror').offset();
814 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
816 $('.modal-body').each( function() {
817 $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
822 $( '#macro-ui' ).on( 'shown.bs.modal', function() {
823 if ( macroEditor ) return;
825 macroEditor = CodeMirror(
826 $('#macro-editor')[0],
829 'Ctrl-D': function( cm ) {
830 var cur = cm.getCursor();
832 cm.replaceRange( "‡", cur, null );
841 macroEditor.on( 'change', function( cm, change ) {
842 $('#macro-save-message').empty();
843 if ( change.origin == 'setValue' ) return;
845 if ( saveTimeout ) clearTimeout( saveTimeout );
846 saveTimeout = setTimeout( function() {
856 var saveableBackends = [];
857 $.each( backends, function( id, backend ) {
858 if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
860 saveableBackends.sort();
861 $.each( saveableBackends, function( undef, backend ) {
862 $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
866 $( '#save-record, #save-dropdown a' ).click( function() {
867 $( '#save-record' ).find('i').attr( 'class', 'fa fa-spinner fa-spin' ).siblings( 'span' ).text( _("Saving...") );
869 function finishCb(result) {
870 if ( result.error == 'syntax' ) {
871 humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
872 } else if ( result.error == 'invalid' ) {
873 humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
874 } else if ( result.error ) {
875 humanMsg.displayAlert( _("Something went wrong, cannot save"), { className: 'humanError' } );
876 } else if ( !result.error ) {
877 humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
880 $.each( result.errors || [], function( undef, error ) {
881 switch ( error.type ) {
883 editor.addError( error.line, _("Invalid tag number") );
886 editor.addError( error.line, _("Invalid indicators") );
889 editor.addError( error.line, _("Tag has no subfields") );
892 editor.addError( null, _("Missing mandatory tag: ") + error.tag );
894 case 'missingSubfield':
895 if ( error.subfield == '@' ) {
896 editor.addError( error.line, _("Missing control field contents") );
898 editor.addError( error.line, _("Missing mandatory subfield: ‡") + error.subfield );
901 case 'unrepeatableTag':
902 editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
904 case 'unrepeatableSubfield':
905 editor.addError( error.line, _("Subfield ‡") + error.subfield + _(" cannot be repeated") );
907 case 'itemTagUnsupported':
908 editor.addError( error.line, _("Item tags cannot currently be saved") );
913 $( '#save-record' ).find('i').attr( 'class', 'fa fa-hdd-o' );
915 if ( result.error ) {
916 // Reset backend info
917 setSource( [ state.backend, state.recordID ] );
921 var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
922 if ( state.backend == backend ) {
923 saveRecord( backend + '/' + state.recordID, editor, finishCb );
925 saveRecord( backend + '/', editor, finishCb );
931 $('#import-records').click( function() {
932 $('#import-records-input')
934 .change( function() {
935 if ( !this.files || !this.files.length ) return;
937 var file = this.files[0];
938 var reader = new FileReader();
940 reader.onload = function() {
941 var record = new MARC.Record();
943 if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
944 record.loadISO2709( reader.result );
945 } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
946 record.loadMARCXML( reader.result );
948 humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
952 if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
954 editor.displayRecord( record );
957 reader.readAsText( file );
964 $('#open-macros').click( function() {
965 $('#macro-ui').modal('show');
970 $('#run-macro').click( function() {
971 var result = Macros.Run( editor, 'rancor', macroEditor.getValue() );
973 if ( !result.errors.length ) {
974 $('#macro-ui').modal('hide');
975 editor.focus(); //Return cursor to editor after macro run
980 $.each( result.errors, function() {
981 var error = '<b>' + _("Line ") + (this.line + 1) + ':</b> ';
983 switch ( this.error ) {
984 case 'failed': error += _("failed to run"); break;
985 case 'unrecognized': error += _("unrecognized command"); break;
991 humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
996 $('#delete-macro').click( function() {
997 if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
999 storeMacro( macroEditor.activeMacro, undefined );
1001 loadMacro( undefined );
1006 $( '#switch-editor' ).click( function() {
1007 if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
1009 $.cookie( 'catalogue_editor_[% logged_in_user.borrowernumber | html %]', 'basic', { expires: 365, path: '/' } );
1011 if ( state.backend == 'catalog' ) {
1012 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
1013 } else if ( state.backend == 'new' ) {
1014 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
1016 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
1020 $( '#show-advanced-search' ).click( function() {
1021 showAdvancedSearch();
1026 $('#advanced-search').submit( function() {
1027 startAdvancedSearch();
1032 $( document ).on( 'click', 'a.search-nav', function() {
1033 if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1034 $("#search-overlay").show();
1040 $( document ).on( 'click', 'th[data-sort-label]', function() {
1043 if ( $( this ).hasClass( 'sorting_asc' ) ) {
1049 if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1050 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1052 $("#search-overlay").show();
1058 $( document ).on( 'change', 'input.search-toggle-server', function() {
1059 var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1060 server.checked = this.checked;
1062 if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1063 $("#search-overlay").show();
1071 $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1072 $(this).modal({ show: false });
1075 var $quicksearch = $('#quicksearch fieldset');
1076 $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1077 position: 'absolute',
1078 top: $quicksearch.offset().top,
1079 left: $quicksearch.offset().left,
1080 height: $quicksearch.outerHeight(),
1081 width: $quicksearch.outerWidth(),
1082 }).appendTo(document.body).hide();
1084 var prevAlerts = [];
1085 humanMsg.logMsg = function(msg, options) {
1086 $('#show-alerts').popover('hide');
1087 prevAlerts.unshift('<li>' + msg + '</li>');
1088 prevAlerts.splice(5, 999); // Truncate old messages
1091 $('#show-alerts').popover({
1093 placement: 'bottom',
1094 content: function() {
1095 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1099 $('#show-shortcuts').popover({
1101 placement: 'bottom',
1102 content: function() {
1103 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1107 $('#new-record' ).click( function() {
1108 if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1110 openRecord( 'new/', editor );
1114 window.onbeforeunload = function() {
1115 if(editor.modified )
1118 { return undefined; }
1121 $('a.change-framework').click( function() {
1122 $("#loading").show();
1123 editor.setFrameworkCode(
1124 $(this).data( 'frameworkcode' ),
1126 function ( error ) {
1127 if ( typeof error !== 'undefined' ) {
1128 humanMsg.displayAlert( _("Failed to change framework"), { className: 'humanError' } );
1130 $('#loading').hide();
1136 Preferences.Load( [% logged_in_user.borrowernumber || 0 | html %] );
1137 displayPreferences(editor);
1138 makeAuthorisedValueWidgets( '' );
1141 onresults: function(data) { showSearchResults( editor, data ) },
1142 onerror: handleSearchError,
1145 function finishCb( data ) {
1147 humanMsg.displayAlert( data.error );
1148 openRecord( 'new/', editor, finishCb );
1151 Resources.GetAll().done( function() {
1152 $("#loading").hide();
1153 $( window ).resize( onResize ).resize();
1158 if ( "[% auth_forwarded_hash | html %]" ) {
1159 document.location.hash = "[% auth_forwarded_hash | html %]";
1162 if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1163 openRecord( 'new/', editor, finishCb );
1169 <!-- / cateditor-ui.inc -->