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;
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 var recname = 'record.mrc';
294 recname = 'bib-'+state.recordID+'.mrc';
297 [% IF (Koha.Preference('DefaultSaveRecordFileID') == 'controlnumber') %]
298 var controlnumfield = record.field('001');
299 if(controlnumfield) {
300 recname = 'record-'+controlnumfield.subfield('@')+'.mrc';
303 saveAs( new Blob( [record.toISO2709()], { 'type': 'application/octet-stream;charset=utf-8' } ), recname );
309 saveLabel: _("Save as MARCXML (.xml) file"),
310 save: function( id, record, done ) {
311 var recname = 'record.xml';
313 recname = 'bib-'+state.recordID+'.xml';
316 [% IF (Koha.Preference('DefaultSaveRecordFileID') == 'controlnumber') %]
317 var controlnumfield = record.field('001');
318 if(controlnumfield) {
319 recname = 'record-'+controlnumfield.subfield('@')+'.xml';
322 saveAs( new Blob( [record.toXML()], { 'type': 'application/octet-stream;charset=utf-8' } ), recname );
328 titleForRecord: _("Editing search result"),
329 get: function( id, callback ) {
330 if ( !id ) return false;
331 if ( !backends.search.records[ id ] ) {
332 callback( { error: _( "Invalid record" ) } );
336 callback( backends.search.records[ id ] );
342 function setSource(parts) {
343 state.backend = parts[0];
344 state.recordID = parts[1];
345 state.canSave = backends[ state.backend ].save != null;
346 state.saveBackend = state.canSave ? state.backend : 'catalog';
348 var backend = backends[state.backend];
350 document.location.hash = '#' + parts[0] + '/' + parts[1];
352 $('#title').text( backend.titleForRecord.replace( '{ID}', parts[1] ) );
354 $.each( backend.links || [], function( i, link ) {
355 $('#title').append(' <a target="_blank" href="' + link.href.replace( '{ID}', parts[1] ) + '">(' + link.title + ')</a>' );
357 $( 'title', document.head ).html( _("Koha › Cataloging › ") + backend.titleForRecord.replace( '{ID}', parts[1] ) );
358 $('#save-record span').text( backends[ state.saveBackend ].saveLabel );
361 function saveRecord( recid, editor, callback ) {
362 var parts = recid.split('/');
363 if ( parts.length != 2 ) return false;
365 if ( !backends[ parts[0] ] || !backends[ parts[0] ].save ) return false;
367 editor.removeErrors();
368 var record = editor.getRecord();
370 if ( record.errors ) {
371 state.saving = false;
372 callback( { error: 'syntax', errors: record.errors } );
376 var errors = KohaBackend.ValidateRecord( '', record );
377 if ( errors.length ) {
378 state.saving = false;
379 callback( { error: 'invalid', errors: errors } );
383 backends[ parts[0] ].save( parts[1], record, function(data) {
384 state.saving = false;
386 if (data.newRecord) {
387 var record = new MARC.Record();
388 record.loadMARCXML(data.newRecord);
389 record.frameworkcode = data.newRecord.frameworkcode;
390 editor.displayRecord( record );
394 setSource(data.newId);
396 setSource( [ state.backend, state.recordID ] );
399 if (callback) callback( data );
403 function loadRecord( recid, editor, callback ) {
404 var parts = recid.split('/');
405 if ( parts.length != 2 ) return false;
407 if ( !backends[ parts[0] ] || !backends[ parts[0] ].get ) return false;
409 backends[ parts[0] ].get( parts[1], function( record ) {
410 if ( !record.error ) {
411 editor.displayRecord( record );
415 if (callback) callback(record);
421 function openRecord( recid, editor, callback ) {
422 return loadRecord( recid, editor, function ( record ) {
423 setSource( recid.split('/') );
425 if (callback) callback( record );
430 function showAdvancedSearch() {
431 $('#advanced-search-servers').empty();
432 $.each( z3950Servers, function( index, server ) {
433 $('#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>' );
435 $('#advanced-search-ui').modal('show');
438 function startAdvancedSearch() {
441 $('#advanced-search-ui .search-box').each( function() {
442 if ( !this.value ) return;
444 terms.push( [ $(this).data('qualifier'), this.value ] );
447 if ( !terms.length ) return;
449 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
450 $('#advanced-search-ui').modal('hide');
451 $("#search-overlay").show();
456 function showResultsBox(data) {
457 $('#search-top-pages, #search-bottom-pages').find('nav').empty();
458 $('#searchresults thead tr').empty();
459 $('#searchresults tbody').empty();
460 $('#search-serversinfo').empty().append('<li>' + _("Loading...") + '</li>');
461 $('#search-results-ui').modal('show');
464 function showSearchSorting( sort_key, sort_direction ) {
465 var $th = $('#searchresults thead tr th[data-sort-label="' + sort_key + '"]');
466 $th.parent().find( 'th[data-sort-label]' ).attr( 'class', 'sorting' );
468 if ( sort_direction == 'asc' ) {
470 $th.attr( 'class', 'sorting_asc' );
473 $th.attr( 'class', 'sorting_desc' );
477 function showSearchResults( editor, data ) {
478 backends.search.records = {};
480 $('#searchresults thead tr').empty();
481 $('#searchresults tbody').empty();
482 $('#search-serversinfo').empty();
484 $.each( z3950Servers, function( index, server ) {
485 var num_fetched = data.num_fetched[server.server_id];
487 if ( data.errors[server.server_id] ) {
488 num_fetched = data.errors[server.server_id];
489 } else if ( num_fetched == null ) {
491 } else if ( num_fetched < data.num_hits[server.server_id] ) {
495 $('#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>' );
498 var seenColumns = {};
500 $.each( data.hits, function( undef, hit ) {
501 $.each( hit.metadata, function(key) {
502 seenColumns[key] = true;
506 $('#searchresults thead tr').append('<th>' + _("Source") + '</th>');
508 $.each( z3950Labels, function( undef, label ) {
509 if ( seenColumns[ label[0] ] ) {
510 $('#searchresults thead tr').append( '<th class="sorting" data-sort-label="' + label[0] + '">' + label[1] + '</th>' );
514 showSearchSorting( data.sort_key, data.sort_direction );
516 $('#searchresults thead tr').append('<th>' + _("Tools") + '</th>');
518 var bibnumMap = KohaBackend.GetSubfieldForKohaField('biblio.biblionumber');
519 $.each( data.hits, function( undef, hit ) {
520 backends.search.records[ hit.server + ':' + hit.index ] = hit.record;
522 switch ( hit.server ) {
523 case 'koha:biblioserver':
524 var bibnumField = hit.record.field( bibnumMap[0] );
526 if ( bibnumField && bibnumField.hasSubfield( bibnumMap[1] ) ) {
527 hit.id = 'catalog/' + bibnumField.subfield( bibnumMap[1] );
531 // Otherwise, fallthrough
534 hit.id = 'search/' + hit.server + ':' + hit.index;
538 var server_name = hit.servername == 'koha:biblioserver' ? _("Local catalog") : hit.servername;
539 result += '<td class="sourcecol">' + server_name + '</td>';
541 $.each( z3950Labels, function( undef, label ) {
542 if ( !seenColumns[ label[0] ] ) return;
544 if ( hit.metadata[ label[0] ] ) {
545 result += '<td class="infocol">' + hit.metadata[ label[0] ] + '</td>';
547 result += '<td class="infocol"> </td>';
551 result += '<td class="toolscol"><ul><li><a href="#" class="marc-link">' + _("View MARC") + '</a></li>';
552 result += '<li><a href="#" class="open-link">' + ( hit.server == 'koha:biblioserver' ? _("Edit") : _("Import") ) + '</a></li>';
553 if ( state.canSave ) result += '<li><a href="#" class="substitute-link" title="' + _("Replace the current record's contents") + '">' + _("Substitute") + '</a></li>';
554 result += '</ul></td></tr>';
556 var $tr = $( result );
557 $tr.find( '.marc-link' ).click( function() {
558 var $info_columns = $tr.find( '.infocol' );
559 var $marc_column = $tr.find( '.marccol' );
561 if ( !$marc_column.length ) {
562 $marc_column = $( '<td class="marccol" colspan="' + $info_columns.length + '"></td>' ).insertAfter( $info_columns.eq(-1) ).hide();
563 CodeMirror.runMode( TextMARC.RecordToText( hit.record ), 'marc', $marc_column[0] );
566 if ( $marc_column.is(':visible') ) {
567 $tr.find('.marc-link').text( _("View MARC") );
568 $info_columns.show();
571 $tr.find('.marc-link').text( _("Hide MARC") );
573 $info_columns.hide();
578 $tr.find( '.open-link' ).click( function() {
579 $( '#search-results-ui' ).modal('hide');
580 openRecord( hit.id, editor );
584 $tr.find( '.substitute-link' ).click( function() {
585 $( '#search-results-ui' ).modal('hide');
586 loadRecord( hit.id, editor );
590 $('#searchresults tbody').append( $tr );
594 var cur_page = data.offset / data.page_size;
595 var max_page = Math.ceil( data.total_fetched / data.page_size ) - 1;
597 if ( cur_page != 0 ) {
598 pages.push( '<li><a class="search-nav" href="#" data-offset="' + (data.offset - data.page_size) + '"><span aria-hidden="true">«</span> ' + _("Previous") + '</a></li>' );
601 for ( var page = Math.max( 0, cur_page - 9 ); page <= Math.min( max_page, cur_page + 9 ); page++ ) {
602 if ( page == cur_page ) {
603 pages.push( ' <li class="active"><a href="#">' + ( page + 1 ) + '</a></li>' );
605 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + ( page * data.page_size ) + '">' + ( page + 1 ) + '</a></li>' );
609 if ( cur_page < max_page ) {
610 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + (data.offset + data.page_size) + '">' + _("Next") + ' <span aria-hidden="true">»</span></a></li>' );
613 $( '#search-top-pages, #search-bottom-pages' ).find( 'nav' ).html( pages.length > 1 ? ( '<ul class="pagination pagination-sm">' + pages.join( '' ) + '</ul>' ) : '' );
615 var $overlay = $('#search-overlay');
616 $overlay.find('span').text(_("Loading"));
617 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
619 if ( data.activeclients ) {
620 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
623 $overlay.find('.bar').css( { display: 'block', width: '100%' } );
625 $('#searchresults')[0].focus();
629 function invalidateSearchResults() {
630 var $overlay = $('#search-overlay');
631 $overlay.find('span').text(_("Search expired, please try again"));
632 $overlay.find('.bar').css( { display: 'none' } );
636 function handleSearchError(error) {
637 if (error.code == 1) {
638 invalidateSearchResults();
641 humanMsg.displayMsg( '<h3>' + _("Internal search error") + '</h3><p>' + error.responseText + '</p><p>' + _("Please refresh the page and try again.") + '</p>', { className: 'humanError' } );
645 function handleSearchInitError(error) {
646 $('#quicksearch-overlay').fadeIn().find('p').text(error);
649 // Preference functions
650 function showPreference( pref ) {
651 var value = Preferences.user[pref];
655 $( '#set-field-widgets' ).text( value ? _("Show fields verbatim") : _("Show helpers for fixed and coded fields") );
658 $( '#editor .CodeMirror' ).css( { fontFamily: value } );
662 $( '#editor .CodeMirror' ).css( { fontSize: value } );
666 // Macros loaded on first show of modal
668 case 'selected_search_targets':
669 $.each( z3950Servers, function( index, server ) {
670 var saved_val = Preferences.user.selected_search_targets[server.server_id];
672 if ( saved_val != null ) server.checked = saved_val;
678 function bindPreference( editor, pref ) {
679 function _addHandler( sel, event, handler ) {
680 $( sel ).on( event, function (e) {
682 handler( e, Preferences.user[pref] );
683 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
684 showPreference(pref);
690 _addHandler( '#set-field-widgets', 'click', function( e, oldValue ) {
691 editor.setUseWidgets( Preferences.user.fieldWidgets = !Preferences.user.fieldWidgets );
695 _addHandler( '#prefs-menu .set-font', 'click', function( e, oldValue ) {
696 Preferences.user.font = $( e.target ).css( 'font-family' );
700 _addHandler( '#prefs-menu .set-fontSize', 'click', function( e, oldValue ) {
701 Preferences.user.fontSize = $( e.target ).css( 'font-size' );
704 case 'selected_search_targets':
705 $( document ).on( 'change', 'input.search-toggle-server', function() {
706 var server_id = $( this ).closest('li').data('server-id');
707 Preferences.user.selected_search_targets[server_id] = this.checked;
708 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
714 function displayPreferences( editor ) {
715 $.each( Preferences.user, function( pref, value ) {
716 showPreference( pref );
717 bindPreference( editor, pref );
722 var canCreatePublic = "[% CAN_user_editcatalogue_create_shared_macros | html %]";
723 var canDeletePublic = "[% CAN_user_editcatalogue_delete_shared_macros | html %]";
725 function deleteMacro( id ){
726 $( '#macro-list' ).empty();
727 var shared = macroEditor.activeMacroShared;
728 var id = macroEditor.activeMacroId;
729 macroEditor.activeMacroId = null;
730 api_url = "/api/v1/advanced_editor/macros/";
731 if( shared ) { api_url += "shared/" }
735 contentType: "application/json",
738 .then(function(result) {
739 humanMsg.displayAlert( _("Macro successfully deleted") );
742 .fail(function(err) {
744 if( err.status == "404" ){
745 err_message = "Macro not found";
746 } else if ( err.status == "403" ){
747 err_message = _("You do not have permission to delete this macro");
749 err_message = _("There was a problem, please check the logs");
751 humanMsg.displayAlert( _("Failed to delete macro: " + err_message), { className: 'humanError' } );
756 function loadMacro( name, id, shared ) {
757 $( '#macro-list li' ).removeClass( 'active' );
758 $(".macro_shared").prop("checked",false).hide();
759 $("#delete-macro").prop("disabled",true);
760 macroEditor.setOption( 'readOnly', false );
761 macroEditor.activeMacro = name;
762 macroEditor.activeMacroId = id;
765 macroEditor.setValue( '' );
768 $( '#macro-list li[data-name="' + name + '"][data-id="' + id + '"]' ).addClass( 'active' );
769 api_url = "/api/v1/advanced_editor/macros/";
770 if( shared ) { api_url += "shared/" }
774 contentType: "application/json",
777 .then(function(result) {
778 macroEditor.setValue( result.macro_text );
779 $(".macro_shared").show();
781 $(".macro_shared").prop("checked",true);
782 if( canCreatePublic ){
783 macroEditor.setOption( 'readOnly', false );
785 macroEditor.setOption( 'readOnly', true );
787 if( canDeletePublic ){
788 $("#delete-macro").prop("disabled",false);
791 macroEditor.setOption( 'readOnly', false );
792 $("#delete-macro").prop("disabled",false);
794 macroEditor.activeMacroShared = result.shared;
796 .fail(function(err) {
798 if( err.status == "404" ){
799 err_message = "Macro not found";
800 } else if ( err.status == "403" ){
801 err_message = _("You do not have permission to access this macro");
803 err_message = _("There was a problem, please check the logs");
805 humanMsg.displayAlert( _("Failed to load macros: ") + err_message, { className: 'humanError' } );
810 function convertOldMacros(){
811 $("#convert-macros").remove();
812 var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
813 return $.extend( { name: name }, macro );
815 macro_list.sort( function( a, b ) {
816 return a.name.localeCompare(b.name);
818 $.each( macro_list, function( index, macro ) {
820 url: "/api/v1/advanced_editor/macros/",
822 contentType: "application/json",
823 data: JSON.stringify({
825 patron_id: [% logged_in_user.borrowernumber | html %],
826 macro_text: macro.contents,
831 .then(function(undef, result) {
832 delete Preferences.user.macros[macro.name];
833 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
834 if( index == macro_list.length -1 ){
839 .fail(function(err) {
841 if( err.status == "403" ){
842 err_message = _("You do not have permission to create this macro");
844 err_message = _("There was a problem, please check the logs");
846 humanMsg.displayAlert( _("Failed to create macro: ") + err_message, { className: 'humanError' } );
851 function showSavedMacros( macros ) {
852 var scrollTop = $('#macro-list').scrollTop();
853 $( '#macro-list' ).empty();
854 $("#convert-macros").remove();
855 if( Object.keys(Preferences.user.macros).length ){
856 $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>' );
857 $convert.click( function(){
858 if( !confirm( _("This will retrieve macros stored in the brower, save them in the database, and delete them from the browser. Proceed?") ) ){
863 $("#macro-toolbar").prepend($convert);
866 url: "/api/v1/advanced_editor/macros/",
868 contentType: "application/json",
871 .then(function(result) {
872 $.each(result,function( undef, macro ){
873 var $li = $( '<li data-name="' + macro.name + '" data-id="' + macro.macro_id + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
874 if ( macro.macro_id == macroEditor.activeMacroId ) $li.addClass( 'active' );
875 $li.click( function() {
876 loadMacro(macro.name, macro.macro_id, macro.shared);
879 $('#macro-list').append($li);
882 .fail(function(err) {
883 var err_message = _("There was a problem, please check the logs");
884 humanMsg.displayAlert( _("Failed to load macros: ") + err_message, { className: 'humanError' } );
886 var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
887 $new_li.click( function() {
888 // TODO: make this a bit less retro
889 var name = prompt(_("Please enter the name for the new macro:"));
892 // if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
894 url: "/api/v1/advanced_editor/macros/",
896 contentType: "application/json",
897 data: JSON.stringify({
899 patron_id: [% logged_in_user.borrowernumber | html %],
905 .then(function(result) {
907 loadMacro( result.name, result.macro_id );
909 .fail(function(err) {
911 if( err.status == "403" ){
912 err_message = _("You do not have permission to access this macro");
914 err_message = _("There was a problem, please check the logs");
916 humanMsg.displayAlert( _("Failed to create macro: ") + err_message, { className: 'humanError' } );
919 $('#macro-list').append($new_li);
920 $('#macro-list').scrollTop(scrollTop);
923 function saveMacro(shared) {
924 var name = macroEditor.activeMacro;
925 var macro_id = macroEditor.activeMacroId;
926 var was_shared = macroEditor.activeMacroShared;
928 if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() && was_shared == shared ) return;
930 macroEditor.savedGeneration = macroEditor.changeGeneration();
931 api_url = "/api/v1/advanced_editor/macros/";
932 if( shared || was_shared ) { api_url += "shared/" }
935 url: api_url + macro_id,
937 contentType: "application/json",
938 data: JSON.stringify({
940 patron_id: [% logged_in_user.borrowernumber | html %],
941 macro_text: macroEditor.getValue(),
946 .then(function(result) {
947 $('#macro-save-message').text(_("Saved"));
948 macroEditor.activeMacroShared = shared;
951 .fail(function(err) {
953 if( err.status == "404" ){
954 err_message = _("Macro not found");
955 } else if ( err.status ="403" ){
956 err_message = _("You do not have permission to access this macro");
958 err_message = _("There was a problem, please check the logs");
960 humanMsg.displayAlert( _("Failed to save macro: ") + err_message, { className: 'humanError' } );
964 $(".macro_shared").change(function(){
972 // END Macro functions
974 $(document).ready( function() {
976 editor = new MARCEditor( {
977 onCursorActivity: function() {
978 $('#status-tag-info').empty();
979 $('#status-subfield-info').empty();
981 var field = editor.getCurrentField();
982 var cur = editor.getCursor();
984 if ( !field ) return;
986 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
987 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
990 $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> ' + taginfo.lib );
992 var subfield = field.getSubfieldAt( cur.ch );
993 if ( !subfield ) return;
995 var subfieldinfo = taginfo.subfields[ subfield.code ];
996 $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
998 if ( subfieldinfo ) {
999 $('#status-subfield-info').append( subfieldinfo.lib );
1001 $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
1004 $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
1007 position: function (elt) { $(elt).insertAfter('#toolbar') },
1010 // Automatically detect resizes and change the height of the editor and position of modals.
1011 var resizeTimer = null;
1012 function onResize() {
1013 if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
1016 var pos = $('#editor .CodeMirror').offset();
1017 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
1019 $('.modal-body').each( function() {
1020 $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
1025 $( '#macro-ui' ).on( 'shown.bs.modal', function() {
1026 if ( macroEditor ) return;
1028 macroEditor = CodeMirror(
1029 $('#macro-editor')[0],
1032 'Ctrl-D': function( cm ) {
1033 var cur = cm.getCursor();
1035 cm.replaceRange( "‡", cur, null );
1044 macroEditor.on( 'change', function( cm, change ) {
1045 $('#macro-save-message').empty();
1046 if ( change.origin == 'setValue' ) return;
1048 if ( saveTimeout ) clearTimeout( saveTimeout );
1049 saveTimeout = setTimeout( function() {
1050 saveMacro(macroEditor.activeMacroShared);
1059 var saveableBackends = [];
1060 $.each( backends, function( id, backend ) {
1061 if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
1063 saveableBackends.sort();
1064 $.each( saveableBackends, function( undef, backend ) {
1065 $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
1069 $( '#save-record, #save-dropdown a' ).click( function() {
1070 $( '#save-record' ).find('i').attr( 'class', 'fa fa-spinner fa-spin' ).siblings( 'span' ).text( _("Saving...") );
1072 function finishCb(result) {
1073 if ( result.error == 'syntax' ) {
1074 humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
1075 } else if ( result.error == 'invalid' ) {
1076 humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
1077 } else if ( result.error ) {
1078 humanMsg.displayAlert( _("Something went wrong, cannot save"), { className: 'humanError' } );
1079 } else if ( !result.error ) {
1080 humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
1083 $.each( result.errors || [], function( undef, error ) {
1084 switch ( error.type ) {
1086 editor.addError( error.line, _("Invalid tag number") );
1088 case 'noIndicators':
1089 editor.addError( error.line, _("Invalid indicators") );
1092 editor.addError( error.line, _("Tag has no subfields") );
1095 editor.addError( null, _("Missing mandatory tag: ") + error.tag );
1097 case 'missingSubfield':
1098 if ( error.subfield == '@' ) {
1099 editor.addError( error.line, _("Missing control field contents") );
1101 editor.addError( error.line, _("Missing mandatory subfield: ‡") + error.subfield );
1104 case 'unrepeatableTag':
1105 editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
1107 case 'unrepeatableSubfield':
1108 editor.addError( error.line, _("Subfield ‡") + error.subfield + _(" cannot be repeated") );
1110 case 'itemTagUnsupported':
1111 editor.addError( error.line, _("Item tags cannot currently be saved") );
1116 $( '#save-record' ).find('i').attr( 'class', 'fa fa-hdd-o' );
1118 if ( result.error ) {
1119 // Reset backend info
1120 setSource( [ state.backend, state.recordID ] );
1124 var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
1125 if ( state.backend == backend ) {
1126 saveRecord( backend + '/' + state.recordID, editor, finishCb );
1128 saveRecord( backend + '/', editor, finishCb );
1134 $('#import-records').click( function() {
1135 $('#import-records-input')
1137 .change( function() {
1138 if ( !this.files || !this.files.length ) return;
1140 var file = this.files[0];
1141 var reader = new FileReader();
1143 reader.onload = function() {
1144 var record = new MARC.Record();
1146 if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
1147 record.loadISO2709( reader.result );
1148 } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
1149 record.loadMARCXML( reader.result );
1151 humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
1155 if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
1157 editor.displayRecord( record );
1160 reader.readAsText( file );
1167 $('#open-macros').click( function() {
1168 $('#macro-ui').modal('show');
1173 $('#run-macro').click( function() {
1174 var result = Macros.Run( editor, 'rancor', macroEditor.getValue() );
1176 if ( !result.errors.length ) {
1177 $('#macro-ui').modal('hide');
1178 editor.focus(); //Return cursor to editor after macro run
1183 $.each( result.errors, function() {
1184 var error = '<strong>' + _("Line ") + (this.line + 1) + ':</strong> ';
1186 switch ( this.error ) {
1187 case 'failed': error += _("failed to run"); break;
1188 case 'unrecognized': error += _("unrecognized command"); break;
1194 humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
1199 $('#delete-macro').click( function() {
1200 if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
1202 loadMacro( undefined );
1207 $( '#switch-editor' ).click( function() {
1208 if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
1210 Cookies.set( "catalogue_editor_[% logged_in_user.borrowernumber | html %]", "basic", { expires: 365, path: '/' } );
1212 if ( state.backend == 'catalog' ) {
1213 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
1214 } else if ( state.backend == 'new' ) {
1215 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
1217 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
1221 $( '#show-advanced-search' ).click( function() {
1222 showAdvancedSearch();
1227 $('#advanced-search').submit( function() {
1228 startAdvancedSearch();
1233 $( document ).on( 'click', 'a.search-nav', function() {
1234 if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1235 $("#search-overlay").show();
1241 $( document ).on( 'click', 'th[data-sort-label]', function() {
1244 if ( $( this ).hasClass( 'sorting_asc' ) ) {
1250 if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1251 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1253 $("#search-overlay").show();
1259 $( document ).on( 'change', 'input.search-toggle-server', function() {
1260 var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1261 server.checked = this.checked;
1263 if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1264 $("#search-overlay").show();
1272 $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1273 $(this).modal({ show: false });
1276 var $quicksearch = $('#quicksearch fieldset');
1277 $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1278 position: 'absolute',
1279 top: $quicksearch.offset().top,
1280 left: $quicksearch.offset().left,
1281 height: $quicksearch.outerHeight(),
1282 width: $quicksearch.outerWidth(),
1283 }).appendTo(document.body).hide();
1285 var prevAlerts = [];
1286 humanMsg.logMsg = function(msg, options) {
1287 $('#show-alerts').popover('hide');
1288 prevAlerts.unshift('<li>' + msg + '</li>');
1289 prevAlerts.splice(5, 999); // Truncate old messages
1292 $('#show-alerts').popover({
1294 placement: 'bottom',
1295 content: function() {
1296 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1300 $('#show-shortcuts').popover({
1302 placement: 'bottom',
1303 content: function() {
1304 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1308 $('#new-record' ).click( function() {
1309 if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1311 openRecord( 'new/', editor );
1315 window.onbeforeunload = function() {
1316 if(editor.modified )
1319 { return undefined; }
1322 $('a.change-framework').click( function() {
1323 $("#loading").show();
1324 editor.setFrameworkCode(
1325 $(this).data( 'frameworkcode' ),
1327 function ( error ) {
1328 if ( typeof error !== 'undefined' ) {
1329 humanMsg.displayAlert( _("Failed to change framework"), { className: 'humanError' } );
1331 $('#loading').hide();
1337 Preferences.Load( [% logged_in_user.borrowernumber || 0 | html %] );
1338 displayPreferences(editor);
1339 makeAuthorisedValueWidgets( '' );
1342 onresults: function(data) { showSearchResults( editor, data ) },
1343 onerror: handleSearchError,
1346 function finishCb( data ) {
1348 humanMsg.displayAlert( data.error );
1349 openRecord( 'new/', editor, finishCb );
1352 Resources.GetAll().done( function() {
1353 $("#loading").hide();
1354 $( window ).resize( onResize ).resize();
1359 if ( "[% auth_forwarded_hash | html %]" ) {
1360 document.location.hash = "[% auth_forwarded_hash | html %]";
1363 if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1364 openRecord( 'new/', editor, finishCb );
1370 <!-- / cateditor-ui.inc -->