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 var canCreatePublic = "[% CAN_user_editcatalogue_create_shared_macros | html %]";
698 var canDeletePublic = "[% CAN_user_editcatalogue_delete_shared_macros | html %]";
700 function deleteMacro( id ){
701 $( '#macro-list' ).empty();
702 var shared = macroEditor.activeMacroShared;
703 var id = macroEditor.activeMacroId;
704 macroEditor.activeMacroId = null;
705 api_url = "/api/v1/advanced_editor/macros/";
706 if( shared ) { api_url += "shared/" }
710 contentType: "application/json",
713 .then(function(result) {
714 humanMsg.displayAlert( _("Macro successfully deleted") );
717 .fail(function(err) {
719 if( err.status == "404" ){
720 err_message = "Macro not found";
721 } else if ( err.status == "403" ){
722 err_message = _("You do not have permission to delete this macro");
724 err_message = _("There was a problem, please check the logs");
726 humanMsg.displayAlert( _("Failed to delete macro: " + err_message), { className: 'humanError' } );
731 function loadMacro( name, id, shared ) {
732 $( '#macro-list li' ).removeClass( 'active' );
733 $(".macro_shared").prop("checked",false).hide();
734 $("#delete-macro").prop("disabled",true);
735 macroEditor.setOption( 'readOnly', false );
736 macroEditor.activeMacro = name;
737 macroEditor.activeMacroId = id;
740 macroEditor.setValue( '' );
743 $( '#macro-list li[data-name="' + name + '"][data-id="' + id + '"]' ).addClass( 'active' );
744 api_url = "/api/v1/advanced_editor/macros/";
745 if( shared ) { api_url += "shared/" }
749 contentType: "application/json",
752 .then(function(result) {
753 macroEditor.setValue( result.macro_text );
754 $(".macro_shared").show();
756 $(".macro_shared").prop("checked",true);
757 if( canCreatePublic ){
758 macroEditor.setOption( 'readOnly', false );
760 macroEditor.setOption( 'readOnly', true );
762 if( canDeletePublic ){
763 $("#delete-macro").prop("disabled",false);
766 macroEditor.setOption( 'readOnly', false );
767 $("#delete-macro").prop("disabled",false);
769 macroEditor.activeMacroShared = result.shared;
771 .fail(function(err) {
773 if( err.status == "404" ){
774 err_message = "Macro not found";
775 } else if ( err.status == "403" ){
776 err_message = _("You do not have permission to access this macro");
778 err_message = _("There was a problem, please check the logs");
780 humanMsg.displayAlert( _("Failed to load macros: ") + err_message, { className: 'humanError' } );
785 function convertOldMacros(){
786 $("#convert-macros").remove();
787 var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
788 return $.extend( { name: name }, macro );
790 macro_list.sort( function( a, b ) {
791 return a.name.localeCompare(b.name);
793 $.each( macro_list, function( index, macro ) {
795 url: "/api/v1/advanced_editor/macros/",
797 contentType: "application/json",
798 data: JSON.stringify({
800 patron_id: [% logged_in_user.borrowernumber | html %],
801 macro_text: macro.contents,
806 .then(function(undef, result) {
807 delete Preferences.user.macros[macro.name];
808 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
809 if( index == macro_list.length -1 ){
814 .fail(function(err) {
816 if( err.status == "403" ){
817 err_message = _("You do not have permission to create this macro");
819 err_message = _("There was a problem, please check the logs");
821 humanMsg.displayAlert( _("Failed to create macro: ") + err_message, { className: 'humanError' } );
826 function showSavedMacros( macros ) {
827 var scrollTop = $('#macro-list').scrollTop();
828 $( '#macro-list' ).empty();
829 $("#convert-macros").remove();
830 if( Object.keys(Preferences.user.macros).length ){
831 $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>' );
832 $convert.click( function(){
833 if( !confirm( _("This will retrieve macros stored in the brower, save them in the database, and delete them from the browser. Proceed?") ) ){
838 $("#macro-toolbar").prepend($convert);
841 url: "/api/v1/advanced_editor/macros/",
843 contentType: "application/json",
846 .then(function(result) {
847 $.each(result,function( undef, macro ){
848 var $li = $( '<li data-name="' + macro.name + '" data-id="' + macro.macro_id + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
849 if ( macro.macro_id == macroEditor.activeMacroId ) $li.addClass( 'active' );
850 $li.click( function() {
851 loadMacro(macro.name, macro.macro_id, macro.shared);
854 $('#macro-list').append($li);
857 .fail(function(err) {
858 var err_message = _("There was a problem, please check the logs");
859 humanMsg.displayAlert( _("Failed to load macros: ") + err_message, { className: 'humanError' } );
861 var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
862 $new_li.click( function() {
863 // TODO: make this a bit less retro
864 var name = prompt(_("Please enter the name for the new macro:"));
867 // if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
869 url: "/api/v1/advanced_editor/macros/",
871 contentType: "application/json",
872 data: JSON.stringify({
874 patron_id: [% logged_in_user.borrowernumber | html %],
880 .then(function(undef, result) {
882 loadMacro( result.name, result.macro_id );
884 .fail(function(err) {
886 if( err.status == "403" ){
887 err_message = _("You do not have permission to access this macro");
889 err_message = _("There was a problem, please check the logs");
891 humanMsg.displayAlert( _("Failed to create macro: ") + err_message, { className: 'humanError' } );
894 $('#macro-list').append($new_li);
895 $('#macro-list').scrollTop(scrollTop);
898 function saveMacro(shared) {
899 var name = macroEditor.activeMacro;
900 var macro_id = macroEditor.activeMacroId;
901 var was_shared = macroEditor.activeMacroShared;
903 if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() && was_shared == shared ) return;
905 macroEditor.savedGeneration = macroEditor.changeGeneration();
906 api_url = "/api/v1/advanced_editor/macros/";
907 if( shared || was_shared ) { api_url += "shared/" }
910 url: api_url + macro_id,
912 contentType: "application/json",
913 data: JSON.stringify({
915 patron_id: [% logged_in_user.borrowernumber | html %],
916 macro_text: macroEditor.getValue(),
921 .then(function(result) {
922 $('#macro-save-message').text(_("Saved"));
923 macroEditor.activeMacroShared = shared;
926 .fail(function(err) {
928 if( err.status == "404" ){
929 err_message = _("Macro not found");
930 } else if ( err.status ="403" ){
931 err_message = _("You do not have permission to access this macro");
933 err_message = _("There was a problem, please check the logs");
935 humanMsg.displayAlert( _("Failed to save macro: ") + err_message, { className: 'humanError' } );
939 $(".macro_shared").change(function(){
947 // END Macro functions
949 $(document).ready( function() {
951 editor = new MARCEditor( {
952 onCursorActivity: function() {
953 $('#status-tag-info').empty();
954 $('#status-subfield-info').empty();
956 var field = editor.getCurrentField();
957 var cur = editor.getCursor();
959 if ( !field ) return;
961 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
962 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
965 $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> ' + taginfo.lib );
967 var subfield = field.getSubfieldAt( cur.ch );
968 if ( !subfield ) return;
970 var subfieldinfo = taginfo.subfields[ subfield.code ];
971 $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
973 if ( subfieldinfo ) {
974 $('#status-subfield-info').append( subfieldinfo.lib );
976 $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
979 $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
982 position: function (elt) { $(elt).insertAfter('#toolbar') },
985 // Automatically detect resizes and change the height of the editor and position of modals.
986 var resizeTimer = null;
987 function onResize() {
988 if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
991 var pos = $('#editor .CodeMirror').offset();
992 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
994 $('.modal-body').each( function() {
995 $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
1000 $( '#macro-ui' ).on( 'shown.bs.modal', function() {
1001 if ( macroEditor ) return;
1003 macroEditor = CodeMirror(
1004 $('#macro-editor')[0],
1007 'Ctrl-D': function( cm ) {
1008 var cur = cm.getCursor();
1010 cm.replaceRange( "‡", cur, null );
1019 macroEditor.on( 'change', function( cm, change ) {
1020 $('#macro-save-message').empty();
1021 if ( change.origin == 'setValue' ) return;
1023 if ( saveTimeout ) clearTimeout( saveTimeout );
1024 saveTimeout = setTimeout( function() {
1025 saveMacro(macroEditor.activeMacroShared);
1034 var saveableBackends = [];
1035 $.each( backends, function( id, backend ) {
1036 if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
1038 saveableBackends.sort();
1039 $.each( saveableBackends, function( undef, backend ) {
1040 $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
1044 $( '#save-record, #save-dropdown a' ).click( function() {
1045 $( '#save-record' ).find('i').attr( 'class', 'fa fa-spinner fa-spin' ).siblings( 'span' ).text( _("Saving...") );
1047 function finishCb(result) {
1048 if ( result.error == 'syntax' ) {
1049 humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
1050 } else if ( result.error == 'invalid' ) {
1051 humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
1052 } else if ( result.error ) {
1053 humanMsg.displayAlert( _("Something went wrong, cannot save"), { className: 'humanError' } );
1054 } else if ( !result.error ) {
1055 humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
1058 $.each( result.errors || [], function( undef, error ) {
1059 switch ( error.type ) {
1061 editor.addError( error.line, _("Invalid tag number") );
1063 case 'noIndicators':
1064 editor.addError( error.line, _("Invalid indicators") );
1067 editor.addError( error.line, _("Tag has no subfields") );
1070 editor.addError( null, _("Missing mandatory tag: ") + error.tag );
1072 case 'missingSubfield':
1073 if ( error.subfield == '@' ) {
1074 editor.addError( error.line, _("Missing control field contents") );
1076 editor.addError( error.line, _("Missing mandatory subfield: ‡") + error.subfield );
1079 case 'unrepeatableTag':
1080 editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
1082 case 'unrepeatableSubfield':
1083 editor.addError( error.line, _("Subfield ‡") + error.subfield + _(" cannot be repeated") );
1085 case 'itemTagUnsupported':
1086 editor.addError( error.line, _("Item tags cannot currently be saved") );
1091 $( '#save-record' ).find('i').attr( 'class', 'fa fa-hdd-o' );
1093 if ( result.error ) {
1094 // Reset backend info
1095 setSource( [ state.backend, state.recordID ] );
1099 var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
1100 if ( state.backend == backend ) {
1101 saveRecord( backend + '/' + state.recordID, editor, finishCb );
1103 saveRecord( backend + '/', editor, finishCb );
1109 $('#import-records').click( function() {
1110 $('#import-records-input')
1112 .change( function() {
1113 if ( !this.files || !this.files.length ) return;
1115 var file = this.files[0];
1116 var reader = new FileReader();
1118 reader.onload = function() {
1119 var record = new MARC.Record();
1121 if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
1122 record.loadISO2709( reader.result );
1123 } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
1124 record.loadMARCXML( reader.result );
1126 humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
1130 if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
1132 editor.displayRecord( record );
1135 reader.readAsText( file );
1142 $('#open-macros').click( function() {
1143 $('#macro-ui').modal('show');
1148 $('#run-macro').click( function() {
1149 var result = Macros.Run( editor, 'rancor', macroEditor.getValue() );
1151 if ( !result.errors.length ) {
1152 $('#macro-ui').modal('hide');
1153 editor.focus(); //Return cursor to editor after macro run
1158 $.each( result.errors, function() {
1159 var error = '<b>' + _("Line ") + (this.line + 1) + ':</b> ';
1161 switch ( this.error ) {
1162 case 'failed': error += _("failed to run"); break;
1163 case 'unrecognized': error += _("unrecognized command"); break;
1169 humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
1174 $('#delete-macro').click( function() {
1175 if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
1177 loadMacro( undefined );
1182 $( '#switch-editor' ).click( function() {
1183 if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
1185 $.cookie( 'catalogue_editor_[% logged_in_user.borrowernumber | html %]', 'basic', { expires: 365, path: '/' } );
1187 if ( state.backend == 'catalog' ) {
1188 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
1189 } else if ( state.backend == 'new' ) {
1190 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
1192 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
1196 $( '#show-advanced-search' ).click( function() {
1197 showAdvancedSearch();
1202 $('#advanced-search').submit( function() {
1203 startAdvancedSearch();
1208 $( document ).on( 'click', 'a.search-nav', function() {
1209 if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1210 $("#search-overlay").show();
1216 $( document ).on( 'click', 'th[data-sort-label]', function() {
1219 if ( $( this ).hasClass( 'sorting_asc' ) ) {
1225 if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1226 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1228 $("#search-overlay").show();
1234 $( document ).on( 'change', 'input.search-toggle-server', function() {
1235 var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1236 server.checked = this.checked;
1238 if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1239 $("#search-overlay").show();
1247 $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1248 $(this).modal({ show: false });
1251 var $quicksearch = $('#quicksearch fieldset');
1252 $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1253 position: 'absolute',
1254 top: $quicksearch.offset().top,
1255 left: $quicksearch.offset().left,
1256 height: $quicksearch.outerHeight(),
1257 width: $quicksearch.outerWidth(),
1258 }).appendTo(document.body).hide();
1260 var prevAlerts = [];
1261 humanMsg.logMsg = function(msg, options) {
1262 $('#show-alerts').popover('hide');
1263 prevAlerts.unshift('<li>' + msg + '</li>');
1264 prevAlerts.splice(5, 999); // Truncate old messages
1267 $('#show-alerts').popover({
1269 placement: 'bottom',
1270 content: function() {
1271 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1275 $('#show-shortcuts').popover({
1277 placement: 'bottom',
1278 content: function() {
1279 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1283 $('#new-record' ).click( function() {
1284 if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1286 openRecord( 'new/', editor );
1290 window.onbeforeunload = function() {
1291 if(editor.modified )
1294 { return undefined; }
1297 $('a.change-framework').click( function() {
1298 $("#loading").show();
1299 editor.setFrameworkCode(
1300 $(this).data( 'frameworkcode' ),
1302 function ( error ) {
1303 if ( typeof error !== 'undefined' ) {
1304 humanMsg.displayAlert( _("Failed to change framework"), { className: 'humanError' } );
1306 $('#loading').hide();
1312 Preferences.Load( [% logged_in_user.borrowernumber || 0 | html %] );
1313 displayPreferences(editor);
1314 makeAuthorisedValueWidgets( '' );
1317 onresults: function(data) { showSearchResults( editor, data ) },
1318 onerror: handleSearchError,
1321 function finishCb( data ) {
1323 humanMsg.displayAlert( data.error );
1324 openRecord( 'new/', editor, finishCb );
1327 Resources.GetAll().done( function() {
1328 $("#loading").hide();
1329 $( window ).resize( onResize ).resize();
1334 if ( "[% auth_forwarded_hash | html %]" ) {
1335 document.location.hash = "[% auth_forwarded_hash | html %]";
1338 if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1339 openRecord( 'new/', editor, finishCb );
1345 <!-- / cateditor-ui.inc -->