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 %]',
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;
255 KohaBackend.GetRecord( id, callback );
257 save: function( id, record, done ) {
258 function finishCb( data ) {
259 done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
262 KohaBackend.CreateRecord( record, finishCb );
266 titleForRecord: _("Editing catalog record #{ID}"),
268 { title: _("view"), href: "/cgi-bin/koha/catalogue/detail.pl?biblionumber={ID}" },
269 { title: _("edit items"), href: "/cgi-bin/koha/cataloguing/additem.pl?biblionumber={ID}" },
271 saveLabel: _("Save to catalog"),
272 get: function( id, callback ) {
273 if ( !id ) return false;
275 KohaBackend.GetRecord( id, callback );
277 save: function( id, record, done ) {
278 function finishCb( data ) {
279 done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
283 KohaBackend.SaveRecord( id, record, finishCb );
285 KohaBackend.CreateRecord( record, finishCb );
290 saveLabel: _("Save as MARC (.mrc) file"),
291 save: function( id, record, done ) {
292 saveAs( new Blob( [record.toISO2709()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.mrc' );
298 saveLabel: _("Save as MARCXML (.xml) file"),
299 save: function( id, record, done ) {
300 saveAs( new Blob( [record.toXML()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.xml' );
306 titleForRecord: _("Editing search result"),
307 get: function( id, callback ) {
308 if ( !id ) return false;
309 if ( !backends.search.records[ id ] ) {
310 callback( { error: _( "Invalid record" ) } );
314 callback( backends.search.records[ id ] );
320 function setSource(parts) {
321 state.backend = parts[0];
322 state.recordID = parts[1];
323 state.canSave = backends[ state.backend ].save != null;
324 state.saveBackend = state.canSave ? state.backend : 'catalog';
326 var backend = backends[state.backend];
328 document.location.hash = '#' + parts[0] + '/' + parts[1];
330 $('#title').text( backend.titleForRecord.replace( '{ID}', parts[1] ) );
332 $.each( backend.links || [], function( i, link ) {
333 $('#title').append(' <a target="_blank" href="' + link.href.replace( '{ID}', parts[1] ) + '">(' + link.title + ')</a>' );
335 $( 'title', document.head ).html( _("Koha › Cataloging › ") + backend.titleForRecord.replace( '{ID}', parts[1] ) );
336 $('#save-record span').text( backends[ state.saveBackend ].saveLabel );
339 function saveRecord( recid, editor, callback ) {
340 var parts = recid.split('/');
341 if ( parts.length != 2 ) return false;
343 if ( !backends[ parts[0] ] || !backends[ parts[0] ].save ) return false;
345 editor.removeErrors();
346 var record = editor.getRecord();
348 if ( record.errors ) {
349 state.saving = false;
350 callback( { error: 'syntax', errors: record.errors } );
354 var errors = KohaBackend.ValidateRecord( '', record );
355 if ( errors.length ) {
356 state.saving = false;
357 callback( { error: 'invalid', errors: errors } );
361 backends[ parts[0] ].save( parts[1], record, function(data) {
362 state.saving = false;
364 if (data.newRecord) {
365 var record = new MARC.Record();
366 record.loadMARCXML(data.newRecord);
367 record.frameworkcode = data.newRecord.frameworkcode;
368 editor.displayRecord( record );
372 setSource(data.newId);
374 setSource( [ state.backend, state.recordID ] );
377 if (callback) callback( data );
381 function loadRecord( recid, editor, callback ) {
382 var parts = recid.split('/');
383 if ( parts.length != 2 ) return false;
385 if ( !backends[ parts[0] ] || !backends[ parts[0] ].get ) return false;
387 backends[ parts[0] ].get( parts[1], function( record ) {
388 if ( !record.error ) {
389 editor.displayRecord( record );
393 if (callback) callback(record);
399 function openRecord( recid, editor, callback ) {
400 return loadRecord( recid, editor, function ( record ) {
401 setSource( recid.split('/') );
403 if (callback) callback( record );
408 function showAdvancedSearch() {
409 $('#advanced-search-servers').empty();
410 $.each( z3950Servers, function( index, server ) {
411 $('#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>' );
413 $('#advanced-search-ui').modal('show');
416 function startAdvancedSearch() {
419 $('#advanced-search-ui .search-box').each( function() {
420 if ( !this.value ) return;
422 terms.push( [ $(this).data('qualifier'), this.value ] );
425 if ( !terms.length ) return;
427 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
428 $('#advanced-search-ui').modal('hide');
429 $("#search-overlay").show();
434 function showResultsBox(data) {
435 $('#search-top-pages, #search-bottom-pages').find('nav').empty();
436 $('#searchresults thead tr').empty();
437 $('#searchresults tbody').empty();
438 $('#search-serversinfo').empty().append('<li>' + _("Loading...") + '</li>');
439 $('#search-results-ui').modal('show');
442 function showSearchSorting( sort_key, sort_direction ) {
443 var $th = $('#searchresults thead tr th[data-sort-label="' + sort_key + '"]');
444 $th.parent().find( 'th[data-sort-label]' ).attr( 'class', 'sorting' );
446 if ( sort_direction == 'asc' ) {
448 $th.attr( 'class', 'sorting_asc' );
451 $th.attr( 'class', 'sorting_desc' );
455 function showSearchResults( editor, data ) {
456 backends.search.records = {};
458 $('#searchresults thead tr').empty();
459 $('#searchresults tbody').empty();
460 $('#search-serversinfo').empty();
462 $.each( z3950Servers, function( index, server ) {
463 var num_fetched = data.num_fetched[server.server_id];
465 if ( data.errors[server.server_id] ) {
466 num_fetched = data.errors[server.server_id];
467 } else if ( num_fetched == null ) {
469 } else if ( num_fetched < data.num_hits[server.server_id] ) {
473 $('#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>' );
476 var seenColumns = {};
478 $.each( data.hits, function( undef, hit ) {
479 $.each( hit.metadata, function(key) {
480 seenColumns[key] = true;
484 $('#searchresults thead tr').append('<th>' + _("Source") + '</th>');
486 $.each( z3950Labels, function( undef, label ) {
487 if ( seenColumns[ label[0] ] ) {
488 $('#searchresults thead tr').append( '<th class="sorting" data-sort-label="' + label[0] + '">' + label[1] + '</th>' );
492 showSearchSorting( data.sort_key, data.sort_direction );
494 $('#searchresults thead tr').append('<th>' + _("Tools") + '</th>');
496 var bibnumMap = KohaBackend.GetSubfieldForKohaField('biblio.biblionumber');
497 $.each( data.hits, function( undef, hit ) {
498 backends.search.records[ hit.server + ':' + hit.index ] = hit.record;
500 switch ( hit.server ) {
501 case 'koha:biblioserver':
502 var bibnumField = hit.record.field( bibnumMap[0] );
504 if ( bibnumField && bibnumField.hasSubfield( bibnumMap[1] ) ) {
505 hit.id = 'catalog/' + bibnumField.subfield( bibnumMap[1] );
509 // Otherwise, fallthrough
512 hit.id = 'search/' + hit.server + ':' + hit.index;
516 var server_name = hit.servername == 'koha:biblioserver' ? _("Local catalog") : hit.servername;
517 result += '<td class="sourcecol">' + server_name + '</td>';
519 $.each( z3950Labels, function( undef, label ) {
520 if ( !seenColumns[ label[0] ] ) return;
522 if ( hit.metadata[ label[0] ] ) {
523 result += '<td class="infocol">' + hit.metadata[ label[0] ] + '</td>';
525 result += '<td class="infocol"> </td>';
529 result += '<td class="toolscol"><ul><li><a href="#" class="marc-link">' + _("View MARC") + '</a></li>';
530 result += '<li><a href="#" class="open-link">' + ( hit.server == 'koha:biblioserver' ? _("Edit") : _("Import") ) + '</a></li>';
531 if ( state.canSave ) result += '<li><a href="#" class="substitute-link" title="' + _("Replace the current record's contents") + '">' + _("Substitute") + '</a></li>';
532 result += '</ul></td></tr>';
534 var $tr = $( result );
535 $tr.find( '.marc-link' ).click( function() {
536 var $info_columns = $tr.find( '.infocol' );
537 var $marc_column = $tr.find( '.marccol' );
539 if ( !$marc_column.length ) {
540 $marc_column = $( '<td class="marccol" colspan="' + $info_columns.length + '"></td>' ).insertAfter( $info_columns.eq(-1) ).hide();
541 CodeMirror.runMode( TextMARC.RecordToText( hit.record ), 'marc', $marc_column[0] );
544 if ( $marc_column.is(':visible') ) {
545 $tr.find('.marc-link').text( _("View MARC") );
546 $info_columns.show();
549 $tr.find('.marc-link').text( _("Hide MARC") );
551 $info_columns.hide();
556 $tr.find( '.open-link' ).click( function() {
557 $( '#search-results-ui' ).modal('hide');
558 openRecord( hit.id, editor );
562 $tr.find( '.substitute-link' ).click( function() {
563 $( '#search-results-ui' ).modal('hide');
564 loadRecord( hit.id, editor );
568 $('#searchresults tbody').append( $tr );
572 var cur_page = data.offset / data.page_size;
573 var max_page = Math.ceil( data.total_fetched / data.page_size ) - 1;
575 if ( cur_page != 0 ) {
576 pages.push( '<li><a class="search-nav" href="#" data-offset="' + (data.offset - data.page_size) + '"><span aria-hidden="true">«</span> ' + _("Previous") + '</a></li>' );
579 for ( var page = Math.max( 0, cur_page - 9 ); page <= Math.min( max_page, cur_page + 9 ); page++ ) {
580 if ( page == cur_page ) {
581 pages.push( ' <li class="active"><a href="#">' + ( page + 1 ) + '</a></li>' );
583 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + ( page * data.page_size ) + '">' + ( page + 1 ) + '</a></li>' );
587 if ( cur_page < max_page ) {
588 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + (data.offset + data.page_size) + '">' + _("Next") + ' <span aria-hidden="true">»</span></a></li>' );
591 $( '#search-top-pages, #search-bottom-pages' ).find( 'nav' ).html( pages.length > 1 ? ( '<ul class="pagination pagination-sm">' + pages.join( '' ) + '</ul>' ) : '' );
593 var $overlay = $('#search-overlay');
594 $overlay.find('span').text(_("Loading"));
595 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
597 if ( data.activeclients ) {
598 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
601 $overlay.find('.bar').css( { display: 'block', width: '100%' } );
603 $('#searchresults')[0].focus();
607 function invalidateSearchResults() {
608 var $overlay = $('#search-overlay');
609 $overlay.find('span').text(_("Search expired, please try again"));
610 $overlay.find('.bar').css( { display: 'none' } );
614 function handleSearchError(error) {
615 if (error.code == 1) {
616 invalidateSearchResults();
619 humanMsg.displayMsg( '<h3>' + _("Internal search error") + '</h3><p>' + error.responseText + '</p><p>' + _("Please refresh the page and try again.") + '</p>', { className: 'humanError' } );
623 function handleSearchInitError(error) {
624 $('#quicksearch-overlay').fadeIn().find('p').text(error);
627 // Preference functions
628 function showPreference( pref ) {
629 var value = Preferences.user[pref];
633 $( '#set-field-widgets' ).text( value ? _("Show fields verbatim") : _("Show helpers for fixed and coded fields") );
636 $( '#editor .CodeMirror' ).css( { fontFamily: value } );
640 $( '#editor .CodeMirror' ).css( { fontSize: value } );
644 // Macros loaded on first show of modal
646 case 'selected_search_targets':
647 $.each( z3950Servers, function( index, server ) {
648 var saved_val = Preferences.user.selected_search_targets[server.server_id];
650 if ( saved_val != null ) server.checked = saved_val;
656 function bindPreference( editor, pref ) {
657 function _addHandler( sel, event, handler ) {
658 $( sel ).on( event, function (e) {
660 handler( e, Preferences.user[pref] );
661 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
662 showPreference(pref);
668 _addHandler( '#set-field-widgets', 'click', function( e, oldValue ) {
669 editor.setUseWidgets( Preferences.user.fieldWidgets = !Preferences.user.fieldWidgets );
673 _addHandler( '#prefs-menu .set-font', 'click', function( e, oldValue ) {
674 Preferences.user.font = $( e.target ).css( 'font-family' );
678 _addHandler( '#prefs-menu .set-fontSize', 'click', function( e, oldValue ) {
679 Preferences.user.fontSize = $( e.target ).css( 'font-size' );
682 case 'selected_search_targets':
683 $( document ).on( 'change', 'input.search-toggle-server', function() {
684 var server_id = $( this ).closest('li').data('server-id');
685 Preferences.user.selected_search_targets[server_id] = this.checked;
686 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
692 function displayPreferences( editor ) {
693 $.each( Preferences.user, function( pref, value ) {
694 showPreference( pref );
695 bindPreference( editor, pref );
700 var canCreatePublic = "[% CAN_user_editcatalogue_create_shared_macros | html %]";
701 var canDeletePublic = "[% CAN_user_editcatalogue_delete_shared_macros | html %]";
703 function deleteMacro( id ){
704 $( '#macro-list' ).empty();
705 var shared = macroEditor.activeMacroShared;
706 var id = macroEditor.activeMacroId;
707 macroEditor.activeMacroId = null;
708 api_url = "/api/v1/advanced_editor/macros/";
709 if( shared ) { api_url += "shared/" }
713 contentType: "application/json",
716 .then(function(result) {
717 humanMsg.displayAlert( _("Macro successfully deleted") );
720 .fail(function(err) {
722 if( err.status == "404" ){
723 err_message = "Macro not found";
724 } else if ( err.status == "403" ){
725 err_message = _("You do not have permission to delete this macro");
727 err_message = _("There was a problem, please check the logs");
729 humanMsg.displayAlert( _("Failed to delete macro: " + err_message), { className: 'humanError' } );
734 function loadMacro( name, id, shared ) {
735 $( '#macro-list li' ).removeClass( 'active' );
736 $(".macro_shared").prop("checked",false).hide();
737 $("#delete-macro").prop("disabled",true);
738 macroEditor.setOption( 'readOnly', false );
739 macroEditor.activeMacro = name;
740 macroEditor.activeMacroId = id;
743 macroEditor.setValue( '' );
746 $( '#macro-list li[data-name="' + name + '"][data-id="' + id + '"]' ).addClass( 'active' );
747 api_url = "/api/v1/advanced_editor/macros/";
748 if( shared ) { api_url += "shared/" }
752 contentType: "application/json",
755 .then(function(result) {
756 macroEditor.setValue( result.macro_text );
757 $(".macro_shared").show();
759 $(".macro_shared").prop("checked",true);
760 if( canCreatePublic ){
761 macroEditor.setOption( 'readOnly', false );
763 macroEditor.setOption( 'readOnly', true );
765 if( canDeletePublic ){
766 $("#delete-macro").prop("disabled",false);
769 macroEditor.setOption( 'readOnly', false );
770 $("#delete-macro").prop("disabled",false);
772 macroEditor.activeMacroShared = result.shared;
774 .fail(function(err) {
776 if( err.status == "404" ){
777 err_message = "Macro not found";
778 } else if ( err.status == "403" ){
779 err_message = _("You do not have permission to access this macro");
781 err_message = _("There was a problem, please check the logs");
783 humanMsg.displayAlert( _("Failed to load macros: ") + err_message, { className: 'humanError' } );
788 function convertOldMacros(){
789 $("#convert-macros").remove();
790 var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
791 return $.extend( { name: name }, macro );
793 macro_list.sort( function( a, b ) {
794 return a.name.localeCompare(b.name);
796 $.each( macro_list, function( index, macro ) {
798 url: "/api/v1/advanced_editor/macros/",
800 contentType: "application/json",
801 data: JSON.stringify({
803 patron_id: [% logged_in_user.borrowernumber | html %],
804 macro_text: macro.contents,
809 .then(function(undef, result) {
810 delete Preferences.user.macros[macro.name];
811 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
812 if( index == macro_list.length -1 ){
817 .fail(function(err) {
819 if( err.status == "403" ){
820 err_message = _("You do not have permission to create this macro");
822 err_message = _("There was a problem, please check the logs");
824 humanMsg.displayAlert( _("Failed to create macro: ") + err_message, { className: 'humanError' } );
829 function showSavedMacros( macros ) {
830 var scrollTop = $('#macro-list').scrollTop();
831 $( '#macro-list' ).empty();
832 $("#convert-macros").remove();
833 if( Object.keys(Preferences.user.macros).length ){
834 $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>' );
835 $convert.click( function(){
836 if( !confirm( _("This will retrieve macros stored in the brower, save them in the database, and delete them from the browser. Proceed?") ) ){
841 $("#macro-toolbar").prepend($convert);
844 url: "/api/v1/advanced_editor/macros/",
846 contentType: "application/json",
849 .then(function(result) {
850 $.each(result,function( undef, macro ){
851 var $li = $( '<li data-name="' + macro.name + '" data-id="' + macro.macro_id + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
852 if ( macro.macro_id == macroEditor.activeMacroId ) $li.addClass( 'active' );
853 $li.click( function() {
854 loadMacro(macro.name, macro.macro_id, macro.shared);
857 $('#macro-list').append($li);
860 .fail(function(err) {
861 var err_message = _("There was a problem, please check the logs");
862 humanMsg.displayAlert( _("Failed to load macros: ") + err_message, { className: 'humanError' } );
864 var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
865 $new_li.click( function() {
866 // TODO: make this a bit less retro
867 var name = prompt(_("Please enter the name for the new macro:"));
870 // if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
872 url: "/api/v1/advanced_editor/macros/",
874 contentType: "application/json",
875 data: JSON.stringify({
877 patron_id: [% logged_in_user.borrowernumber | html %],
883 .then(function(undef, result) {
885 loadMacro( result.name, result.macro_id );
887 .fail(function(err) {
889 if( err.status == "403" ){
890 err_message = _("You do not have permission to access this macro");
892 err_message = _("There was a problem, please check the logs");
894 humanMsg.displayAlert( _("Failed to create macro: ") + err_message, { className: 'humanError' } );
897 $('#macro-list').append($new_li);
898 $('#macro-list').scrollTop(scrollTop);
901 function saveMacro(shared) {
902 var name = macroEditor.activeMacro;
903 var macro_id = macroEditor.activeMacroId;
904 var was_shared = macroEditor.activeMacroShared;
906 if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() && was_shared == shared ) return;
908 macroEditor.savedGeneration = macroEditor.changeGeneration();
909 api_url = "/api/v1/advanced_editor/macros/";
910 if( shared || was_shared ) { api_url += "shared/" }
913 url: api_url + macro_id,
915 contentType: "application/json",
916 data: JSON.stringify({
918 patron_id: [% logged_in_user.borrowernumber | html %],
919 macro_text: macroEditor.getValue(),
924 .then(function(result) {
925 $('#macro-save-message').text(_("Saved"));
926 macroEditor.activeMacroShared = shared;
929 .fail(function(err) {
931 if( err.status == "404" ){
932 err_message = _("Macro not found");
933 } else if ( err.status ="403" ){
934 err_message = _("You do not have permission to access this macro");
936 err_message = _("There was a problem, please check the logs");
938 humanMsg.displayAlert( _("Failed to save macro: ") + err_message, { className: 'humanError' } );
942 $(".macro_shared").change(function(){
950 // END Macro functions
952 $(document).ready( function() {
954 editor = new MARCEditor( {
955 onCursorActivity: function() {
956 $('#status-tag-info').empty();
957 $('#status-subfield-info').empty();
959 var field = editor.getCurrentField();
960 var cur = editor.getCursor();
962 if ( !field ) return;
964 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
965 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
968 $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> ' + taginfo.lib );
970 var subfield = field.getSubfieldAt( cur.ch );
971 if ( !subfield ) return;
973 var subfieldinfo = taginfo.subfields[ subfield.code ];
974 $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
976 if ( subfieldinfo ) {
977 $('#status-subfield-info').append( subfieldinfo.lib );
979 $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
982 $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
985 position: function (elt) { $(elt).insertAfter('#toolbar') },
988 // Automatically detect resizes and change the height of the editor and position of modals.
989 var resizeTimer = null;
990 function onResize() {
991 if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
994 var pos = $('#editor .CodeMirror').offset();
995 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
997 $('.modal-body').each( function() {
998 $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
1003 $( '#macro-ui' ).on( 'shown.bs.modal', function() {
1004 if ( macroEditor ) return;
1006 macroEditor = CodeMirror(
1007 $('#macro-editor')[0],
1010 'Ctrl-D': function( cm ) {
1011 var cur = cm.getCursor();
1013 cm.replaceRange( "‡", cur, null );
1022 macroEditor.on( 'change', function( cm, change ) {
1023 $('#macro-save-message').empty();
1024 if ( change.origin == 'setValue' ) return;
1026 if ( saveTimeout ) clearTimeout( saveTimeout );
1027 saveTimeout = setTimeout( function() {
1028 saveMacro(macroEditor.activeMacroShared);
1037 var saveableBackends = [];
1038 $.each( backends, function( id, backend ) {
1039 if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
1041 saveableBackends.sort();
1042 $.each( saveableBackends, function( undef, backend ) {
1043 $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
1047 $( '#save-record, #save-dropdown a' ).click( function() {
1048 $( '#save-record' ).find('i').attr( 'class', 'fa fa-spinner fa-spin' ).siblings( 'span' ).text( _("Saving...") );
1050 function finishCb(result) {
1051 if ( result.error == 'syntax' ) {
1052 humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
1053 } else if ( result.error == 'invalid' ) {
1054 humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
1055 } else if ( result.error ) {
1056 humanMsg.displayAlert( _("Something went wrong, cannot save"), { className: 'humanError' } );
1057 } else if ( !result.error ) {
1058 humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
1061 $.each( result.errors || [], function( undef, error ) {
1062 switch ( error.type ) {
1064 editor.addError( error.line, _("Invalid tag number") );
1066 case 'noIndicators':
1067 editor.addError( error.line, _("Invalid indicators") );
1070 editor.addError( error.line, _("Tag has no subfields") );
1073 editor.addError( null, _("Missing mandatory tag: ") + error.tag );
1075 case 'missingSubfield':
1076 if ( error.subfield == '@' ) {
1077 editor.addError( error.line, _("Missing control field contents") );
1079 editor.addError( error.line, _("Missing mandatory subfield: ‡") + error.subfield );
1082 case 'unrepeatableTag':
1083 editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
1085 case 'unrepeatableSubfield':
1086 editor.addError( error.line, _("Subfield ‡") + error.subfield + _(" cannot be repeated") );
1088 case 'itemTagUnsupported':
1089 editor.addError( error.line, _("Item tags cannot currently be saved") );
1094 $( '#save-record' ).find('i').attr( 'class', 'fa fa-hdd-o' );
1096 if ( result.error ) {
1097 // Reset backend info
1098 setSource( [ state.backend, state.recordID ] );
1102 var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
1103 if ( state.backend == backend ) {
1104 saveRecord( backend + '/' + state.recordID, editor, finishCb );
1106 saveRecord( backend + '/', editor, finishCb );
1112 $('#import-records').click( function() {
1113 $('#import-records-input')
1115 .change( function() {
1116 if ( !this.files || !this.files.length ) return;
1118 var file = this.files[0];
1119 var reader = new FileReader();
1121 reader.onload = function() {
1122 var record = new MARC.Record();
1124 if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
1125 record.loadISO2709( reader.result );
1126 } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
1127 record.loadMARCXML( reader.result );
1129 humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
1133 if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
1135 editor.displayRecord( record );
1138 reader.readAsText( file );
1145 $('#open-macros').click( function() {
1146 $('#macro-ui').modal('show');
1151 $('#run-macro').click( function() {
1152 var result = Macros.Run( editor, 'rancor', macroEditor.getValue() );
1154 if ( !result.errors.length ) {
1155 $('#macro-ui').modal('hide');
1156 editor.focus(); //Return cursor to editor after macro run
1161 $.each( result.errors, function() {
1162 var error = '<strong>' + _("Line ") + (this.line + 1) + ':</strong> ';
1164 switch ( this.error ) {
1165 case 'failed': error += _("failed to run"); break;
1166 case 'unrecognized': error += _("unrecognized command"); break;
1172 humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
1177 $('#delete-macro').click( function() {
1178 if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
1180 loadMacro( undefined );
1185 $( '#switch-editor' ).click( function() {
1186 if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
1188 $.cookie( 'catalogue_editor_[% logged_in_user.borrowernumber | html %]', 'basic', { expires: 365, path: '/' } );
1190 if ( state.backend == 'catalog' ) {
1191 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
1192 } else if ( state.backend == 'new' ) {
1193 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
1195 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
1199 $( '#show-advanced-search' ).click( function() {
1200 showAdvancedSearch();
1205 $('#advanced-search').submit( function() {
1206 startAdvancedSearch();
1211 $( document ).on( 'click', 'a.search-nav', function() {
1212 if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1213 $("#search-overlay").show();
1219 $( document ).on( 'click', 'th[data-sort-label]', function() {
1222 if ( $( this ).hasClass( 'sorting_asc' ) ) {
1228 if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1229 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1231 $("#search-overlay").show();
1237 $( document ).on( 'change', 'input.search-toggle-server', function() {
1238 var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1239 server.checked = this.checked;
1241 if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1242 $("#search-overlay").show();
1250 $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1251 $(this).modal({ show: false });
1254 var $quicksearch = $('#quicksearch fieldset');
1255 $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1256 position: 'absolute',
1257 top: $quicksearch.offset().top,
1258 left: $quicksearch.offset().left,
1259 height: $quicksearch.outerHeight(),
1260 width: $quicksearch.outerWidth(),
1261 }).appendTo(document.body).hide();
1263 var prevAlerts = [];
1264 humanMsg.logMsg = function(msg, options) {
1265 $('#show-alerts').popover('hide');
1266 prevAlerts.unshift('<li>' + msg + '</li>');
1267 prevAlerts.splice(5, 999); // Truncate old messages
1270 $('#show-alerts').popover({
1272 placement: 'bottom',
1273 content: function() {
1274 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1278 $('#show-shortcuts').popover({
1280 placement: 'bottom',
1281 content: function() {
1282 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1286 $('#new-record' ).click( function() {
1287 if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1289 openRecord( 'new/', editor );
1293 window.onbeforeunload = function() {
1294 if(editor.modified )
1297 { return undefined; }
1300 $('a.change-framework').click( function() {
1301 $("#loading").show();
1302 editor.setFrameworkCode(
1303 $(this).data( 'frameworkcode' ),
1305 function ( error ) {
1306 if ( typeof error !== 'undefined' ) {
1307 humanMsg.displayAlert( _("Failed to change framework"), { className: 'humanError' } );
1309 $('#loading').hide();
1315 Preferences.Load( [% logged_in_user.borrowernumber || 0 | html %] );
1316 displayPreferences(editor);
1317 makeAuthorisedValueWidgets( '' );
1320 onresults: function(data) { showSearchResults( editor, data ) },
1321 onerror: handleSearchError,
1324 function finishCb( data ) {
1326 humanMsg.displayAlert( data.error );
1327 openRecord( 'new/', editor, finishCb );
1330 Resources.GetAll().done( function() {
1331 $("#loading").hide();
1332 $( window ).resize( onResize ).resize();
1337 if ( "[% auth_forwarded_hash | html %]" ) {
1338 document.location.hash = "[% auth_forwarded_hash | html %]";
1341 if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1342 openRecord( 'new/', editor, finishCb );
1348 <!-- / cateditor-ui.inc -->