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 ) {
46 server_id: 'koha:biblioserver',
47 name: _("Local catalog"),
51 [%- FOREACH server = z3950_servers -%]
53 server_id: [% server.id | html %],
54 name: '[% server.servername | html_entity %]',
55 recordtype: '[% server.recordtype | html %]',
56 checked: [% server.checked ? 'true' : 'false' | html %],
61 // The columns that should show up in a search, in order, and keyed by the corresponding <metadata> tag in the XSL and Pazpar2 config
63 [ "local_number", _("Local number") ],
64 [ "title", _("Title") ],
65 [ "subtitle",_("Subtitle") ],
66 [ "series", _("Series title") ],
67 [ "author", _("Author") ],
68 [ "lccn", _("LCCN") ],
69 [ "isbn", _("ISBN") ],
70 [ "issn", _("ISSN") ],
71 [ "medium", _("Medium") ],
72 [ "edition", _("Edition") ],
73 [ "date", _("Published") ],
74 [ "notes", _("Notes") ],
79 saveBackend: 'catalog',
86 function makeAuthorisedValueWidgets( frameworkCode ) {
87 $.each( KohaBackend.GetAllTagsInfo( frameworkCode ), function( tag, tagInfo ) {
88 $.each( tagInfo.subfields, function( subfield, subfieldInfo ) {
89 if ( !subfieldInfo.authorised_value ) return;
90 var authvals = KohaBackend.GetAuthorisedValues( subfieldInfo.authorised_value );
91 if ( !authvals ) return;
93 var defaultvalue = subfield.defaultvalue || authvals[0].value;
95 Widget.Register( tag + subfield, {
97 var $result = $( '<span class="subfield-widget"></span>' );
101 postCreate: function() {
102 var value = defaultvalue;
105 $.each( authvals, function() {
106 if ( this.value == widget.text ) {
111 this.setText( value );
113 $( '<select></select>' ).appendTo( this.node );
114 var $node = $( this.node ).find( 'select' );
115 $.each( authvals, function( undef, authval ) {
116 $node.append( '<option value="' + authval.value + '"' + (authval.value == value ? ' selected="selected"' : '') + '>' + authval.lib + '</option>' );
118 $node.val( this.text );
120 $node.change( $.proxy( function() {
121 this.setText( $node.val() );
124 makeTemplate: function() {
132 function bindGlobalKeys() {
133 shortcut.add( 'ctrl+s', function(event) {
134 $( '#save-record' ).click();
136 event.preventDefault();
139 shortcut.add( 'alt+ctrl+k', function(event) {
140 $( '#search-by-keywords' ).focus();
145 shortcut.add( 'alt+ctrl+a', function(event) {
146 $( '#search-by-author' ).focus();
151 shortcut.add( 'alt+ctrl+i', function(event) {
152 $( '#search-by-isbn' ).focus();
157 shortcut.add( 'alt+ctrl+t', function(event) {
158 $( '#search-by-title' ).focus();
163 shortcut.add( 'ctrl+h', function() {
164 var field = editor.getCurrentField();
166 if ( !field ) return;
168 window.open( getFieldHelpURL( field.tag ) );
171 $('#quicksearch .search-box').each( function() {
172 shortcut.add( 'enter', $.proxy( function() {
175 $('#quicksearch .search-box').each( function() {
176 if ( !this.value ) return;
178 terms.push( [ $(this).data('qualifier'), this.value ] );
181 if ( !terms.length ) return;
183 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
184 $("#search-overlay").show();
189 }, this), { target: this, type: 'keypress' } );
193 function getFieldHelpURL( tag ) {
194 [% IF Koha.Preference('marcfielddocurl') %]
195 var docurl = "[% Koha.Preference('marcfielddocurl').replace('"','"') | html %]";
196 docurl = docurl.replace("{MARC}", "[% marcflavour | html %]");
197 docurl = docurl.replace("{FIELD}", ""+tag);
198 docurl = docurl.replace("{LANG}", "[% lang | html %]");
200 [% ELSIF ( marcflavour == 'MARC21' ) %]
201 if ( tag == '000' ) {
202 return "http://www.loc.gov/marc/bibliographic/bdleader.html";
203 } else if ( tag >= '090' && tag < '100' ) {
204 return "http://www.loc.gov/marc/bibliographic/bd09x.html";
205 } else if ( tag < '900' ) {
206 return "http://www.loc.gov/marc/bibliographic/bd" + tag + ".html";
208 return "http://www.loc.gov/marc/bibliographic/bd9xx.html";
210 [% ELSIF ( marcflavour == 'UNIMARC' ) %]
211 /* http://archive.ifla.org/VI/3/p1996-1/ is an outdated version of UNIMARC, but
212 seems to be the only version available that can be linked to per tag. More recent
213 versions of the UNIMARC standard are available on the IFLA website only as
216 if ( tag == '000' ) {
217 return "http://archive.ifla.org/VI/3/p1996-1/uni.htm";
220 var url = "http://archive.ifla.org/VI/3/p1996-1/uni" + first + ".htm#";
221 if ( first == '0' ) url += "b";
222 if ( first != '9' ) url += tag;
232 titleForRecord: _("Editing new record"),
233 get: function( id, callback ) {
234 record = new MARC.Record();
235 KohaBackend.FillRecord( '', record );
241 titleForRecord: _("Editing new full record"),
242 get: function( id, callback ) {
243 record = new MARC.Record();
244 KohaBackend.FillRecord( '', record, true );
250 titleForRecord: _("Editing duplicate record of #{ID}"),
251 saveLabel: _("Duplicate"),
252 get: function( id, callback ) {
253 if ( !id ) return false;
254 var remove_control_num = [% IF Koha.Preference('autoControlNumber') == 'OFF' %] 0 [% ELSE %] 1 [% END %];
256 KohaBackend.GetRecord( id, remove_control_num, callback );
258 save: function( id, record, done ) {
259 function finishCb( data ) {
260 done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
262 [% IF Koha.Preference('autoControlNumber') != 'OFF' %]
263 record.removeField("001");
266 KohaBackend.CreateRecord( record, finishCb );
270 titleForRecord: _("Editing catalog record #{ID}"),
272 { title: _("view"), href: "/cgi-bin/koha/catalogue/detail.pl?biblionumber={ID}" },
273 { title: _("edit items"), href: "/cgi-bin/koha/cataloguing/additem.pl?biblionumber={ID}" },
275 saveLabel: _("Save to catalog"),
276 get: function( id, callback ) {
277 if ( !id ) return false;
279 KohaBackend.GetRecord( id, "", callback );
281 save: function( id, record, done ) {
282 function finishCb( data ) {
283 done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
287 KohaBackend.SaveRecord( id, record, finishCb );
289 KohaBackend.CreateRecord( record, finishCb );
294 saveLabel: _("Save as MARC (.mrc) file"),
295 save: function( id, record, done ) {
296 var recname = 'record.mrc';
298 recname = 'bib-'+state.recordID+'.mrc';
301 [% IF (Koha.Preference('DefaultSaveRecordFileID') == 'controlnumber') %]
302 var controlnumfield = record.field('001');
303 if(controlnumfield) {
304 recname = 'record-'+controlnumfield.subfield('@')+'.mrc';
307 saveAs( new Blob( [record.toISO2709()], { 'type': 'application/octet-stream;charset=utf-8' } ), recname );
313 saveLabel: _("Save as MARCXML (.xml) file"),
314 save: function( id, record, done ) {
315 var recname = 'record.xml';
317 recname = 'bib-'+state.recordID+'.xml';
320 [% IF (Koha.Preference('DefaultSaveRecordFileID') == 'controlnumber') %]
321 var controlnumfield = record.field('001');
322 if(controlnumfield) {
323 recname = 'record-'+controlnumfield.subfield('@')+'.xml';
326 saveAs( new Blob( [record.toXML()], { 'type': 'application/octet-stream;charset=utf-8' } ), recname );
332 titleForRecord: _("Editing search result"),
333 get: function( id, callback ) {
334 if ( !id ) return false;
335 if ( !backends.search.records[ id ] ) {
336 callback( { error: _( "Invalid record" ) } );
340 callback( backends.search.records[ id ] );
346 function setSource(parts) {
347 state.backend = parts[0];
348 state.recordID = parts[1];
349 state.canSave = backends[ state.backend ].save != null;
350 state.saveBackend = state.canSave ? state.backend : 'catalog';
352 var backend = backends[state.backend];
354 document.location.hash = '#' + parts[0] + '/' + parts[1];
356 $('#title').text( backend.titleForRecord.replace( '{ID}', parts[1] ) );
358 $.each( backend.links || [], function( i, link ) {
359 $('#title').append(' <a target="_blank" href="' + link.href.replace( '{ID}', parts[1] ) + '">(' + link.title + ')</a>' );
361 $( 'title', document.head ).html( _("Koha › Cataloging › ") + backend.titleForRecord.replace( '{ID}', parts[1] ) );
362 $('#save-record span').text( backends[ state.saveBackend ].saveLabel );
365 function saveRecord( recid, editor, callback ) {
366 var parts = recid.split('/');
367 if ( parts.length != 2 ) return false;
369 if ( !backends[ parts[0] ] || !backends[ parts[0] ].save ) return false;
371 editor.removeErrors();
372 var record = editor.getRecord();
374 if ( record.errors ) {
375 state.saving = false;
376 callback( { error: 'syntax', errors: record.errors } );
380 var errors = KohaBackend.ValidateRecord( '', record );
381 if ( errors.length ) {
382 state.saving = false;
383 callback( { error: 'invalid', errors: errors } );
387 backends[ parts[0] ].save( parts[1], record, function(data) {
388 state.saving = false;
390 if (data.newRecord) {
391 var record = new MARC.Record();
392 record.loadMARCXML(data.newRecord);
393 record.frameworkcode = data.newRecord.frameworkcode;
394 editor.displayRecord( record );
398 setSource(data.newId);
400 setSource( [ state.backend, state.recordID ] );
403 if (callback) callback( data );
407 function loadRecord( recid, editor, callback ) {
408 var parts = recid.split('/');
409 if ( parts.length != 2 ) return false;
411 if ( !backends[ parts[0] ] || !backends[ parts[0] ].get ) return false;
413 backends[ parts[0] ].get( parts[1], function( record ) {
414 if ( !record.error ) {
415 var remove_control_num = [% IF Koha.Preference('autoControlNumber') == 'OFF' %] 0 [% ELSE %] 1 [% END %];
416 if( remove_control_num ){ record.removeField("001"); }
417 editor.displayRecord( record );
421 if (callback) callback(record);
427 function openRecord( recid, editor, callback ) {
428 return loadRecord( recid, editor, function ( record ) {
429 setSource( recid.split('/') );
431 if (callback) callback( record );
436 function showAdvancedSearch() {
437 $('#advanced-search-servers').empty();
438 $.each( z3950Servers, function( index, server ) {
439 $('#advanced-search-servers').append( '<li data-server-id="' + index + '"><label><input class="search-toggle-server" type="checkbox"' + ( server.checked ? ' checked="checked">' : '>' ) + server.name + '</label></li>' );
441 $('#advanced-search-ui').modal('show');
444 function startAdvancedSearch() {
447 $('#advanced-search-ui .search-box').each( function() {
448 if ( !this.value ) return;
450 terms.push( [ $(this).data('qualifier'), this.value ] );
453 if ( !terms.length ) return;
455 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
456 $('#advanced-search-ui').modal('hide');
457 $("#search-overlay").show();
462 function showResultsBox(data) {
463 $('#search-top-pages, #search-bottom-pages').find('nav').empty();
464 $('#searchresults thead tr').empty();
465 $('#searchresults tbody').empty();
466 $('#search-serversinfo').empty().append('<li>' + _("Loading...") + '</li>');
467 $('#search-results-ui').modal('show');
470 function showSearchSorting( sort_key, sort_direction ) {
471 var $th = $('#searchresults thead tr th[data-sort-label="' + sort_key + '"]');
472 $th.parent().find( 'th[data-sort-label]' ).attr( 'class', 'sorting' );
474 if ( sort_direction == 'asc' ) {
476 $th.attr( 'class', 'sorting_asc' );
479 $th.attr( 'class', 'sorting_desc' );
483 function showSearchResults( editor, data ) {
484 backends.search.records = {};
486 $('#searchresults thead tr').empty();
487 $('#searchresults tbody').empty();
488 $('#search-serversinfo').empty();
490 $.each( z3950Servers, function( index, server ) {
491 var num_fetched = data.num_fetched[server.server_id];
493 if ( data.errors[server.server_id] ) {
494 num_fetched = data.errors[server.server_id];
495 } else if ( num_fetched == null ) {
497 } else if ( num_fetched < data.num_hits[server.server_id] ) {
501 $('#search-serversinfo').append( '<li data-server-id="' + index + '"><label><input class="search-toggle-server" type="checkbox"' + ( server.checked ? ' checked="checked">' : '>' ) + server.name + ' (' + num_fetched + ')' + '</label></li>' );
504 var seenColumns = {};
506 $.each( data.hits, function( undef, hit ) {
507 $.each( hit.metadata, function(key) {
508 seenColumns[key] = true;
512 $('#searchresults thead tr').append('<th>' + _("Source") + '</th>');
514 $.each( z3950Labels, function( undef, label ) {
515 if ( seenColumns[ label[0] ] ) {
516 $('#searchresults thead tr').append( '<th class="sorting" data-sort-label="' + label[0] + '">' + label[1] + '</th>' );
520 showSearchSorting( data.sort_key, data.sort_direction );
522 $('#searchresults thead tr').append('<th>' + _("Tools") + '</th>');
524 var bibnumMap = KohaBackend.GetSubfieldForKohaField('biblio.biblionumber');
525 $.each( data.hits, function( undef, hit ) {
526 backends.search.records[ hit.server + ':' + hit.index ] = hit.record;
528 switch ( hit.server ) {
529 case 'koha:biblioserver':
530 var bibnumField = hit.record.field( bibnumMap[0] );
532 if ( bibnumField && bibnumField.hasSubfield( bibnumMap[1] ) ) {
533 hit.id = 'catalog/' + bibnumField.subfield( bibnumMap[1] );
537 // Otherwise, fallthrough
540 hit.id = 'search/' + hit.server + ':' + hit.index;
544 var server_name = hit.servername == 'koha:biblioserver' ? _("Local catalog") : hit.servername;
545 result += '<td class="sourcecol">' + server_name + '</td>';
547 $.each( z3950Labels, function( undef, label ) {
548 if ( !seenColumns[ label[0] ] ) return;
550 if ( hit.metadata[ label[0] ] ) {
551 result += '<td class="infocol">' + hit.metadata[ label[0] ] + '</td>';
553 result += '<td class="infocol"> </td>';
557 result += '<td class="toolscol"><ul><li><a href="#" class="marc-link">' + _("View MARC") + '</a></li>';
558 result += '<li><a href="#" class="open-link">' + ( hit.server == 'koha:biblioserver' ? _("Edit") : _("Import") ) + '</a></li>';
559 if ( state.canSave ) result += '<li><a href="#" class="substitute-link" title="' + _("Replace the current record's contents") + '">' + _("Substitute") + '</a></li>';
560 result += '</ul></td></tr>';
562 var $tr = $( result );
563 $tr.find( '.marc-link' ).click( function() {
564 var $info_columns = $tr.find( '.infocol' );
565 var $marc_column = $tr.find( '.marccol' );
567 if ( !$marc_column.length ) {
568 $marc_column = $( '<td class="marccol" colspan="' + $info_columns.length + '"></td>' ).insertAfter( $info_columns.eq(-1) ).hide();
569 CodeMirror.runMode( TextMARC.RecordToText( hit.record ), 'marc', $marc_column[0] );
572 if ( $marc_column.is(':visible') ) {
573 $tr.find('.marc-link').text( _("View MARC") );
574 $info_columns.show();
577 $tr.find('.marc-link').text( _("Hide MARC") );
579 $info_columns.hide();
584 $tr.find( '.open-link' ).click( function() {
585 $( '#search-results-ui' ).modal('hide');
586 openRecord( hit.id, editor );
590 $tr.find( '.substitute-link' ).click( function() {
591 $( '#search-results-ui' ).modal('hide');
592 loadRecord( hit.id, editor );
596 $('#searchresults tbody').append( $tr );
600 var cur_page = data.offset / data.page_size;
601 var max_page = Math.ceil( data.total_fetched / data.page_size ) - 1;
603 if ( cur_page != 0 ) {
604 pages.push( '<li><a class="search-nav" href="#" data-offset="' + (data.offset - data.page_size) + '"><span aria-hidden="true">«</span> ' + _("Previous") + '</a></li>' );
607 for ( var page = Math.max( 0, cur_page - 9 ); page <= Math.min( max_page, cur_page + 9 ); page++ ) {
608 if ( page == cur_page ) {
609 pages.push( ' <li class="active"><a href="#">' + ( page + 1 ) + '</a></li>' );
611 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + ( page * data.page_size ) + '">' + ( page + 1 ) + '</a></li>' );
615 if ( cur_page < max_page ) {
616 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + (data.offset + data.page_size) + '">' + _("Next") + ' <span aria-hidden="true">»</span></a></li>' );
619 $( '#search-top-pages, #search-bottom-pages' ).find( 'nav' ).html( pages.length > 1 ? ( '<ul class="pagination pagination-sm">' + pages.join( '' ) + '</ul>' ) : '' );
621 var $overlay = $('#search-overlay');
622 $overlay.find('span').text(_("Loading"));
623 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
625 if ( data.activeclients ) {
626 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
629 $overlay.find('.bar').css( { display: 'block', width: '100%' } );
631 $('#searchresults')[0].focus();
635 function invalidateSearchResults() {
636 var $overlay = $('#search-overlay');
637 $overlay.find('span').text(_("Search expired, please try again"));
638 $overlay.find('.bar').css( { display: 'none' } );
642 function handleSearchError(error) {
643 if (error.code == 1) {
644 invalidateSearchResults();
647 humanMsg.displayMsg( '<h3>' + _("Internal search error") + '</h3><p>' + error.responseText + '</p><p>' + _("Please refresh the page and try again.") + '</p>', { className: 'humanError' } );
651 function handleSearchInitError(error) {
652 $('#quicksearch-overlay').fadeIn().find('p').text(error);
655 // Preference functions
656 function showPreference( pref ) {
657 var value = Preferences.user[pref];
661 $( '#set-field-widgets' ).text( value ? _("Show fields verbatim") : _("Show helpers for fixed and coded fields") );
664 $( '#editor .CodeMirror' ).css( { fontFamily: value } );
668 $( '#editor .CodeMirror' ).css( { fontSize: value } );
672 // Macros loaded on first show of modal
674 case 'selected_search_targets':
675 $.each( z3950Servers, function( index, server ) {
676 var saved_val = Preferences.user.selected_search_targets[server.server_id];
678 if ( saved_val != null ) server.checked = saved_val;
684 function bindPreference( editor, pref ) {
685 function _addHandler( sel, event, handler ) {
686 $( sel ).on( event, function (e) {
688 handler( e, Preferences.user[pref] );
689 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
690 showPreference(pref);
696 _addHandler( '#set-field-widgets', 'click', function( e, oldValue ) {
697 editor.setUseWidgets( Preferences.user.fieldWidgets = !Preferences.user.fieldWidgets );
701 _addHandler( '#prefs-menu .set-font', 'click', function( e, oldValue ) {
702 Preferences.user.font = $( e.target ).css( 'font-family' );
706 _addHandler( '#prefs-menu .set-fontSize', 'click', function( e, oldValue ) {
707 Preferences.user.fontSize = $( e.target ).css( 'font-size' );
710 case 'selected_search_targets':
711 $( document ).on( 'change', 'input.search-toggle-server', function() {
712 var server_id = $( this ).closest('li').data('server-id');
713 Preferences.user.selected_search_targets[server_id] = this.checked;
714 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
720 function displayPreferences( editor ) {
721 $.each( Preferences.user, function( pref, value ) {
722 showPreference( pref );
723 bindPreference( editor, pref );
728 var canCreatePublic = "[% CAN_user_editcatalogue_create_shared_macros | html %]";
729 var canDeletePublic = "[% CAN_user_editcatalogue_delete_shared_macros | html %]";
731 function deleteMacro( id ){
732 $( '#macro-list' ).empty();
733 var shared = macroEditor.activeMacroShared;
734 var id = macroEditor.activeMacroId;
735 macroEditor.activeMacroId = null;
736 api_url = "/api/v1/advanced_editor/macros/";
737 if( shared ) { api_url += "shared/" }
741 contentType: "application/json",
744 .then(function(result) {
745 humanMsg.displayAlert( _("Macro successfully deleted") );
748 .fail(function(err) {
750 if( err.status == "404" ){
751 err_message = "Macro not found";
752 } else if ( err.status == "403" ){
753 err_message = _("You do not have permission to delete this macro");
755 err_message = _("There was a problem, please check the logs");
757 humanMsg.displayAlert( _("Failed to delete macro: " + err_message), { className: 'humanError' } );
762 function loadMacro( name, id, shared ) {
763 $( '#macro-list li' ).removeClass( 'active' );
764 $(".macro_shared").prop("checked",false).hide();
765 $("#delete-macro").prop("disabled",true);
766 macroEditor.setOption( 'readOnly', false );
767 macroEditor.activeMacro = name;
768 macroEditor.activeMacroId = id;
771 macroEditor.setValue( '' );
774 $( '#macro-list li[data-name="' + name + '"][data-id="' + id + '"]' ).addClass( 'active' );
775 api_url = "/api/v1/advanced_editor/macros/";
776 if( shared ) { api_url += "shared/" }
780 contentType: "application/json",
783 .then(function(result) {
784 macroEditor.setValue( result.macro_text );
785 $(".macro_shared").show();
787 $(".macro_shared").prop("checked",true);
788 if( canCreatePublic ){
789 macroEditor.setOption( 'readOnly', false );
791 macroEditor.setOption( 'readOnly', true );
793 if( canDeletePublic ){
794 $("#delete-macro").prop("disabled",false);
797 macroEditor.setOption( 'readOnly', false );
798 $("#delete-macro").prop("disabled",false);
800 macroEditor.activeMacroShared = result.shared;
802 .fail(function(err) {
804 if( err.status == "404" ){
805 err_message = "Macro not found";
806 } else if ( err.status == "403" ){
807 err_message = _("You do not have permission to access this macro");
809 err_message = _("There was a problem, please check the logs");
811 humanMsg.displayAlert( _("Failed to load macros: ") + err_message, { className: 'humanError' } );
816 function convertOldMacros(){
817 $("#convert-macros").remove();
818 var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
819 return $.extend( { name: name }, macro );
821 macro_list.sort( function( a, b ) {
822 return a.name.localeCompare(b.name);
824 $.each( macro_list, function( index, macro ) {
826 url: "/api/v1/advanced_editor/macros/",
828 contentType: "application/json",
829 data: JSON.stringify({
831 patron_id: [% logged_in_user.borrowernumber | html %],
832 macro_text: macro.contents,
837 .then(function(undef, result) {
838 delete Preferences.user.macros[macro.name];
839 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
840 if( index == macro_list.length -1 ){
845 .fail(function(err) {
847 if( err.status == "403" ){
848 err_message = _("You do not have permission to create this macro");
850 err_message = _("There was a problem, please check the logs");
852 humanMsg.displayAlert( _("Failed to create macro: ") + err_message, { className: 'humanError' } );
857 function showSavedMacros( macros ) {
858 var scrollTop = $('#macro-list').scrollTop();
859 $( '#macro-list' ).empty();
860 $("#convert-macros").remove();
861 if( Object.keys(Preferences.user.macros).length ){
862 $convert = $( '<button class="btn btn-default" id="convert-macros" title="'+_("Convert browser storage macros")+'><i class="fa fa-adjust" aria-hidden="true"></i> Convert old macros</button>' );
863 $convert.click( function(){
864 if( !confirm( _("This will retrieve macros stored in the brower, save them in the database, and delete them from the browser. Proceed?") ) ){
869 $("#macro-toolbar").prepend($convert);
872 url: "/api/v1/advanced_editor/macros/",
874 contentType: "application/json",
877 .then(function(result) {
878 $.each(result,function( undef, macro ){
879 var $li = $( '<li data-name="' + macro.name + '" data-id="' + macro.macro_id + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
880 if ( macro.macro_id == macroEditor.activeMacroId ) $li.addClass( 'active' );
881 $li.click( function() {
882 loadMacro(macro.name, macro.macro_id, macro.shared);
885 $('#macro-list').append($li);
888 .fail(function(err) {
889 var err_message = _("There was a problem, please check the logs");
890 humanMsg.displayAlert( _("Failed to load macros: ") + err_message, { className: 'humanError' } );
892 var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
893 $new_li.click( function() {
894 // TODO: make this a bit less retro
895 var name = prompt(_("Please enter the name for the new macro:"));
898 // if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
900 url: "/api/v1/advanced_editor/macros/",
902 contentType: "application/json",
903 data: JSON.stringify({
905 patron_id: [% logged_in_user.borrowernumber | html %],
911 .then(function(result) {
913 loadMacro( result.name, result.macro_id );
915 .fail(function(err) {
917 if( err.status == "403" ){
918 err_message = _("You do not have permission to access this macro");
920 err_message = _("There was a problem, please check the logs");
922 humanMsg.displayAlert( _("Failed to create macro: ") + err_message, { className: 'humanError' } );
925 $('#macro-list').append($new_li);
926 $('#macro-list').scrollTop(scrollTop);
929 function saveMacro(shared) {
930 var name = macroEditor.activeMacro;
931 var macro_id = macroEditor.activeMacroId;
932 var was_shared = macroEditor.activeMacroShared;
934 if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() && was_shared == shared ) return;
936 macroEditor.savedGeneration = macroEditor.changeGeneration();
937 api_url = "/api/v1/advanced_editor/macros/";
938 if( shared || was_shared ) { api_url += "shared/" }
941 url: api_url + macro_id,
943 contentType: "application/json",
944 data: JSON.stringify({
946 patron_id: [% logged_in_user.borrowernumber | html %],
947 macro_text: macroEditor.getValue(),
952 .then(function(result) {
953 $('#macro-save-message').text(_("Saved"));
954 macroEditor.activeMacroShared = shared;
957 .fail(function(err) {
959 if( err.status == "404" ){
960 err_message = _("Macro not found");
961 } else if ( err.status ="403" ){
962 err_message = _("You do not have permission to access this macro");
964 err_message = _("There was a problem, please check the logs");
966 humanMsg.displayAlert( _("Failed to save macro: ") + err_message, { className: 'humanError' } );
970 $(".macro_shared").change(function(){
978 // END Macro functions
980 $(document).ready( function() {
982 editor = new MARCEditor( {
983 onCursorActivity: function() {
984 $('#status-tag-info').empty();
985 $('#status-subfield-info').empty();
987 var field = editor.getCurrentField();
988 var cur = editor.getCursor();
990 if ( !field ) return;
992 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
993 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
996 $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> ' + taginfo.lib );
998 var subfield = field.getSubfieldAt( cur.ch );
999 if ( !subfield ) return;
1001 var subfieldinfo = taginfo.subfields[ subfield.code ];
1002 $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
1004 if ( subfieldinfo ) {
1005 $('#status-subfield-info').append( subfieldinfo.lib );
1007 $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
1010 $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
1013 position: function (elt) { $(elt).insertAfter('#toolbar') },
1016 // Automatically detect resizes and change the height of the editor and position of modals.
1017 var resizeTimer = null;
1018 function onResize() {
1019 if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
1022 var pos = $('#editor .CodeMirror').offset();
1023 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
1025 $('.modal-body').each( function() {
1026 $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
1031 $( '#macro-ui' ).on( 'shown.bs.modal', function() {
1032 if ( macroEditor ) return;
1034 macroEditor = CodeMirror(
1035 $('#macro-editor')[0],
1038 'Ctrl-D': function( cm ) {
1039 var cur = cm.getCursor();
1041 cm.replaceRange( "‡", cur, null );
1050 macroEditor.on( 'change', function( cm, change ) {
1051 $('#macro-save-message').empty();
1052 if ( change.origin == 'setValue' ) return;
1054 if ( saveTimeout ) clearTimeout( saveTimeout );
1055 saveTimeout = setTimeout( function() {
1056 saveMacro(macroEditor.activeMacroShared);
1065 var saveableBackends = [];
1066 $.each( backends, function( id, backend ) {
1067 if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
1069 saveableBackends.sort();
1070 $.each( saveableBackends, function( undef, backend ) {
1071 $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
1075 $( '#save-record, #save-dropdown a' ).click( function() {
1076 $( '#save-record' ).find('i').attr( 'class', 'fa fa-spinner fa-spin' ).siblings( 'span' ).text( _("Saving...") );
1078 function finishCb(result) {
1079 if ( result.error == 'syntax' ) {
1080 humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
1081 } else if ( result.error == 'invalid' ) {
1082 humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
1083 } else if ( result.error ) {
1084 humanMsg.displayAlert( _("Something went wrong, cannot save"), { className: 'humanError' } );
1085 } else if ( !result.error ) {
1086 humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
1089 $.each( result.errors || [], function( undef, error ) {
1090 switch ( error.type ) {
1092 editor.addError( error.line, _("Invalid tag number") );
1094 case 'noIndicators':
1095 editor.addError( error.line, _("Invalid indicators") );
1098 editor.addError( error.line, _("Tag has no subfields") );
1101 editor.addError( null, _("Missing mandatory tag: ") + error.tag );
1103 case 'missingSubfield':
1104 if ( error.subfield == '@' ) {
1105 editor.addError( error.line, _("Missing control field contents") );
1107 editor.addError( error.line, _("Missing mandatory subfield: ‡") + error.subfield );
1110 case 'unrepeatableTag':
1111 editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
1113 case 'unrepeatableSubfield':
1114 editor.addError( error.line, _("Subfield ‡") + error.subfield + _(" cannot be repeated") );
1116 case 'itemTagUnsupported':
1117 editor.addError( error.line, _("Item tags cannot currently be saved") );
1122 $( '#save-record' ).find('i').attr( 'class', 'fa fa-hdd-o' );
1124 if ( result.error ) {
1125 // Reset backend info
1126 setSource( [ state.backend, state.recordID ] );
1130 var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
1131 if ( state.backend == backend ) {
1132 saveRecord( backend + '/' + state.recordID, editor, finishCb );
1134 saveRecord( backend + '/', editor, finishCb );
1140 $('#import-records').click( function() {
1141 $('#import-records-input')
1143 .change( function() {
1144 if ( !this.files || !this.files.length ) return;
1146 var file = this.files[0];
1147 var reader = new FileReader();
1149 reader.onload = function() {
1150 var record = new MARC.Record();
1152 if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
1153 record.loadISO2709( reader.result );
1154 } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
1155 record.loadMARCXML( reader.result );
1157 humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
1161 if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
1163 editor.displayRecord( record );
1166 reader.readAsText( file );
1173 $('#open-macros').click( function() {
1174 $('#macro-ui').modal('show');
1179 $('#run-macro').click( function() {
1180 var result = Macros.Run( editor, 'rancor', macroEditor.getValue() );
1182 if ( !result.errors.length ) {
1183 $('#macro-ui').modal('hide');
1184 editor.focus(); //Return cursor to editor after macro run
1189 $.each( result.errors, function() {
1190 var error = '<strong>' + _("Line ") + (this.line + 1) + ':</strong> ';
1192 switch ( this.error ) {
1193 case 'failed': error += _("failed to run"); break;
1194 case 'unrecognized': error += _("unrecognized command"); break;
1200 humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
1205 $('#delete-macro').click( function() {
1206 if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
1208 loadMacro( undefined );
1213 $( '#switch-editor' ).click( function() {
1214 if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
1216 Cookies.set( "catalogue_editor_[% logged_in_user.borrowernumber | html %]", "basic", { expires: 365, path: '/', sameSite: 'Lax'} );
1218 if ( state.backend == 'catalog' ) {
1219 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
1220 } else if ( state.backend == 'new' ) {
1221 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
1223 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
1227 $( '#show-advanced-search' ).click( function() {
1228 showAdvancedSearch();
1233 $('#advanced-search').submit( function() {
1234 startAdvancedSearch();
1239 $( document ).on( 'click', 'a.search-nav', function() {
1240 if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1241 $("#search-overlay").show();
1247 $( document ).on( 'click', 'th[data-sort-label]', function() {
1250 if ( $( this ).hasClass( 'sorting_asc' ) ) {
1256 if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1257 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1259 $("#search-overlay").show();
1265 $( document ).on( 'change', 'input.search-toggle-server', function() {
1266 var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1267 server.checked = this.checked;
1269 if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1270 $("#search-overlay").show();
1278 $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1279 $(this).modal({ show: false });
1282 var $quicksearch = $('#quicksearch fieldset');
1283 $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1284 position: 'absolute',
1285 top: $quicksearch.offset().top,
1286 left: $quicksearch.offset().left,
1287 height: $quicksearch.outerHeight(),
1288 width: $quicksearch.outerWidth(),
1289 }).appendTo(document.body).hide();
1291 var prevAlerts = [];
1292 humanMsg.logMsg = function(msg, options) {
1293 $('#show-alerts').popover('hide');
1294 prevAlerts.unshift('<li>' + msg + '</li>');
1295 prevAlerts.splice(5, 999); // Truncate old messages
1298 $('#show-alerts').popover({
1300 placement: 'bottom',
1301 content: function() {
1302 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1306 $('#show-shortcuts').popover({
1308 placement: 'bottom',
1309 content: function() {
1310 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1314 $('#new-record' ).click( function() {
1315 if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1317 openRecord( 'new/', editor );
1321 window.onbeforeunload = function() {
1322 if(editor.modified )
1325 { return undefined; }
1328 $('a.change-framework').click( function() {
1329 $("#loading").show();
1330 editor.setFrameworkCode(
1331 $(this).data( 'frameworkcode' ),
1333 function ( error ) {
1334 if ( typeof error !== 'undefined' ) {
1335 humanMsg.displayAlert( _("Failed to change framework"), { className: 'humanError' } );
1337 $('#loading').hide();
1343 Preferences.Load( [% logged_in_user.borrowernumber || 0 | html %] );
1344 displayPreferences(editor);
1345 makeAuthorisedValueWidgets( '' );
1348 onresults: function(data) { showSearchResults( editor, data ) },
1349 onerror: handleSearchError,
1352 function finishCb( data ) {
1354 humanMsg.displayAlert( data.error );
1355 openRecord( 'new/', editor, finishCb );
1358 Resources.GetAll().done( function() {
1359 $("#loading").hide();
1360 $( window ).resize( onResize ).resize();
1365 if ( "[% auth_forwarded_hash | html %]" ) {
1366 document.location.hash = "[% auth_forwarded_hash | html %]";
1369 if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1370 openRecord( 'new/', editor, finishCb );
1376 <!-- / cateditor-ui.inc -->