3 [% Asset.js("lib/codemirror/codemirror.min.js") | $raw %]
4 [% Asset.js("lib/filesaver.js") | $raw %]
5 [% Asset.css("lib/keyboard/css/keyboard.min.css") | $raw %]
6 [% Asset.js("lib/keyboard/js/jquery.keyboard.js") | $raw %]
7 [% Asset.js("lib/keyboard/languages/all.min.js") | $raw %]
8 [% Asset.js("lib/keyboard/layouts/all.min.js") | $raw %]
9 [% Asset.js("lib/koha/cateditor/marc-mode.js") | $raw %]
10 [% Asset.js("lib/require.js") | $raw %]
11 <!-- cateditor-ui.inc -->
13 [% FOREACH shortcut IN shortcuts -%]
14 var [% shortcut.shortcut_name | html %] = "[% shortcut.shortcut_keys | html %]";
17 [%- FOREACH authtag = authtags -%]
18 [% authtag.tagfield | html %]: {
19 subfield: '[% authtag.tagsubfield | html %]',
20 authtypecode: '[% authtag.authtypecode | html %]',
25 baseUrl: '[% interface | html %]/lib/koha/cateditor/',
28 marcflavour: '[% marcflavour | html %]',
29 themelang: '[% themelang | html %]',
36 [% IF marcflavour == 'MARC21' %]
37 [% PROCESS 'cateditor-widgets-marc21.inc' %]
39 <script>var editorWidgets = {};</script>
43 require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'preferences', 'resources', 'text-marc', 'widget' ], function( KohaBackend, Search, Macros, MARCEditor, MARC, Preferences, Resources, TextMARC, Widget ) {
46 server_id: 'koha:biblioserver',
47 name: _("Local catalog"),
51 [%- FOREACH server = z3950_servers -%]
53 server_id: [% server.id | html %],
54 name: '[% server.servername | html_entity %]',
55 recordtype: '[% server.recordtype | html %]',
56 checked: [% server.checked ? 'true' : 'false' | html %],
61 // The columns that should show up in a search, in order, and keyed by the corresponding <metadata> tag in the XSL and Pazpar2 config
63 [ "local_number", _("Local number") ],
64 [ "title", _("Title") ],
65 [ "subtitle",_("Subtitle") ],
66 [ "series", _("Series title") ],
67 [ "author", _("Author") ],
68 [ "lccn", _("LCCN") ],
69 [ "isbn", _("ISBN") ],
70 [ "issn", _("ISSN") ],
71 [ "medium", _("Medium") ],
72 [ "edition", _("Edition") ],
73 [ "date", _("Published") ],
74 [ "notes", _("Notes") ],
79 saveBackend: 'catalog',
86 function makeAuthorisedValueWidgets( frameworkCode ) {
87 $.each( KohaBackend.GetAllTagsInfo( frameworkCode ), function( tag, tagInfo ) {
88 $.each( tagInfo.subfields, function( subfield, subfieldInfo ) {
89 if ( !subfieldInfo.authorised_value ) return;
90 var authvals = KohaBackend.GetAuthorisedValues( subfieldInfo.authorised_value );
91 if ( !authvals ) return;
93 var defaultvalue = subfield.defaultvalue || authvals[0].value;
95 Widget.Register( tag + subfield, {
97 var $result = $( '<span class="subfield-widget"></span>' );
101 postCreate: function() {
102 var value = defaultvalue;
105 $.each( authvals, function() {
106 if ( this.value == widget.text ) {
111 this.setText( value );
113 $( '<select></select>' ).appendTo( this.node );
114 var $node = $( this.node ).find( 'select' );
115 $.each( authvals, function( undef, authval ) {
116 $node.append( '<option value="' + authval.value + '"' + (authval.value == value ? ' selected="selected"' : '') + '>' + authval.lib + '</option>' );
118 $node.val( this.text );
120 $node.change( $.proxy( function() {
121 this.setText( $node.val() );
124 makeTemplate: function() {
132 function bindGlobalKeys() {
133 shortcut.add( 'ctrl+s', function(event) {
134 $( '#save-record' ).click();
136 event.preventDefault();
139 shortcut.add( 'alt+ctrl+k', function(event) {
140 $( '#search-by-keywords' ).focus();
145 shortcut.add( 'alt+ctrl+a', function(event) {
146 $( '#search-by-author' ).focus();
151 shortcut.add( 'alt+ctrl+i', function(event) {
152 $( '#search-by-isbn' ).focus();
157 shortcut.add( 'alt+ctrl+t', function(event) {
158 $( '#search-by-title' ).focus();
163 shortcut.add( 'ctrl+h', function() {
164 var field = editor.getCurrentField();
166 if ( !field ) return;
168 window.open( getFieldHelpURL( field.tag ) );
171 $('#quicksearch .search-box').each( function() {
172 shortcut.add( 'enter', $.proxy( function() {
175 $('#quicksearch .search-box').each( function() {
176 if ( !this.value ) return;
178 terms.push( [ $(this).data('qualifier'), this.value ] );
181 if ( !terms.length ) return;
183 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
184 $("#search-overlay").show();
189 }, this), { target: this, type: 'keypress' } );
193 function getFieldHelpURL( tag ) {
194 [% IF Koha.Preference('marcfielddocurl') %]
195 var docurl = "[% Koha.Preference('marcfielddocurl').replace('"','"') | html %]";
196 docurl = docurl.replace("{MARC}", "[% marcflavour | html %]");
197 docurl = docurl.replace("{FIELD}", ""+tag);
198 docurl = docurl.replace("{LANG}", "[% lang | html %]");
200 [% ELSIF ( marcflavour == 'MARC21' ) %]
201 if ( tag == '000' ) {
202 return "http://www.loc.gov/marc/bibliographic/bdleader.html";
203 } else if ( tag >= '090' && tag < '100' ) {
204 return "http://www.loc.gov/marc/bibliographic/bd09x.html";
205 } else if ( tag < '900' ) {
206 return "http://www.loc.gov/marc/bibliographic/bd" + tag + ".html";
208 return "http://www.loc.gov/marc/bibliographic/bd9xx.html";
210 [% ELSIF ( marcflavour == 'UNIMARC' ) %]
211 /* http://archive.ifla.org/VI/3/p1996-1/ is an outdated version of UNIMARC, but
212 seems to be the only version available that can be linked to per tag. More recent
213 versions of the UNIMARC standard are available on the IFLA website only as
216 if ( tag == '000' ) {
217 return "http://archive.ifla.org/VI/3/p1996-1/uni.htm";
220 var url = "http://archive.ifla.org/VI/3/p1996-1/uni" + first + ".htm#";
221 if ( first == '0' ) url += "b";
222 if ( first != '9' ) url += tag;
232 titleForRecord: _("Editing new record"),
233 get: function( id, callback ) {
234 record = new MARC.Record();
235 KohaBackend.FillRecord( '', record );
241 titleForRecord: _("Editing new full record"),
242 get: function( id, callback ) {
243 record = new MARC.Record();
244 KohaBackend.FillRecord( '', record, true );
250 titleForRecord: _("Editing duplicate record of #{ID}"),
251 saveLabel: _("Duplicate"),
252 get: function( id, callback ) {
253 if ( !id ) return false;
254 var remove_control_num = [% IF Koha.Preference('autoControlNumber') == 'OFF' %] 0 [% ELSE %] 1 [% END %];
256 KohaBackend.GetRecord( id, remove_control_num, callback );
258 save: function( id, record, done ) {
259 function finishCb( data ) {
260 done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
262 [% IF Koha.Preference('autoControlNumber') != 'OFF' %]
263 record.removeField("001");
266 KohaBackend.CreateRecord( record, finishCb );
270 titleForRecord: _("Editing catalog record #{ID}"),
272 { title: _("view"), href: "/cgi-bin/koha/catalogue/detail.pl?biblionumber={ID}" },
273 { title: _("edit items"), href: "/cgi-bin/koha/cataloguing/additem.pl?biblionumber={ID}" },
275 saveLabel: _("Save to catalog"),
276 get: function( id, callback ) {
277 if ( !id ) return false;
279 KohaBackend.GetRecord( id, "", callback );
281 save: function( id, record, done ) {
282 function finishCb( data ) {
283 done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
287 KohaBackend.SaveRecord( id, record, finishCb );
289 KohaBackend.CreateRecord( record, finishCb );
294 saveLabel: _("Save as MARC (.mrc) file"),
295 save: function( id, record, done ) {
296 var recname = 'record.mrc';
298 recname = 'bib-'+state.recordID+'.mrc';
301 [% IF (Koha.Preference('DefaultSaveRecordFileID') == 'controlnumber') %]
302 var controlnumfield = record.field('001');
303 if(controlnumfield) {
304 recname = 'record-'+controlnumfield.subfield('@')+'.mrc';
307 saveAs( new Blob( [record.toISO2709()], { 'type': 'application/octet-stream;charset=utf-8' } ), recname );
313 saveLabel: _("Save as MARCXML (.xml) file"),
314 save: function( id, record, done ) {
315 var recname = 'record.xml';
317 recname = 'bib-'+state.recordID+'.xml';
320 [% IF (Koha.Preference('DefaultSaveRecordFileID') == 'controlnumber') %]
321 var controlnumfield = record.field('001');
322 if(controlnumfield) {
323 recname = 'record-'+controlnumfield.subfield('@')+'.xml';
326 saveAs( new Blob( [record.toXML()], { 'type': 'application/octet-stream;charset=utf-8' } ), recname );
332 titleForRecord: _("Editing search result"),
333 get: function( id, callback ) {
334 if ( !id ) return false;
335 if ( !backends.search.records[ id ] ) {
336 callback( { error: _( "Invalid record" ) } );
340 callback( backends.search.records[ id ] );
346 function setSource(parts) {
347 state.backend = parts[0];
348 state.recordID = parts[1];
349 state.canSave = backends[ state.backend ].save != null;
350 state.saveBackend = state.canSave ? state.backend : 'catalog';
352 var backend = backends[state.backend];
354 document.location.hash = '#' + parts[0] + '/' + parts[1];
356 $('#title').text( backend.titleForRecord.replace( '{ID}', parts[1] ) );
358 $.each( backend.links || [], function( i, link ) {
359 $('#title').append(' <a target="_blank" href="' + link.href.replace( '{ID}', parts[1] ) + '">(' + link.title + ')</a>' );
361 $( 'title', document.head ).html( _("Koha › Cataloging › ") + backend.titleForRecord.replace( '{ID}', parts[1] ) );
362 $('#save-record span').text( backends[ state.saveBackend ].saveLabel );
365 function saveRecord( recid, editor, callback ) {
366 var parts = recid.split('/');
367 if ( parts.length != 2 ) return false;
369 if ( !backends[ parts[0] ] || !backends[ parts[0] ].save ) return false;
371 editor.removeErrors();
372 var record = editor.getRecord();
374 if ( record.errors ) {
375 state.saving = false;
376 callback( { error: 'syntax', errors: record.errors } );
380 var errors = KohaBackend.ValidateRecord( '', record );
381 if ( errors.length ) {
382 state.saving = false;
383 callback( { error: 'invalid', errors: errors } );
387 backends[ parts[0] ].save( parts[1], record, function(data) {
388 state.saving = false;
390 if (data.newRecord) {
391 var record = new MARC.Record();
392 record.loadMARCXML(data.newRecord);
393 record.frameworkcode = data.newRecord.frameworkcode;
394 editor.displayRecord( record );
398 setSource(data.newId);
400 setSource( [ state.backend, state.recordID ] );
403 if (callback) callback( data );
407 function loadRecord( recid, editor, callback ) {
408 var parts = recid.split('/');
409 if ( parts.length != 2 ) return false;
411 if ( !backends[ parts[0] ] || !backends[ parts[0] ].get ) return false;
413 backends[ parts[0] ].get( parts[1], function( record ) {
414 if ( !record.error ) {
415 editor.displayRecord( record );
419 if (callback) callback(record);
425 function openRecord( recid, editor, callback ) {
426 return loadRecord( recid, editor, function ( record ) {
427 setSource( recid.split('/') );
429 if (callback) callback( record );
434 function showAdvancedSearch() {
435 $('#advanced-search-servers').empty();
436 $.each( z3950Servers, function( index, server ) {
437 $('#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>' );
439 $('#advanced-search-ui').modal('show');
442 function startAdvancedSearch() {
445 $('#advanced-search-ui .search-box').each( function() {
446 if ( !this.value ) return;
448 terms.push( [ $(this).data('qualifier'), this.value ] );
451 if ( !terms.length ) return;
453 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
454 $('#advanced-search-ui').modal('hide');
455 $("#search-overlay").show();
460 function showResultsBox(data) {
461 $('#search-top-pages, #search-bottom-pages').find('nav').empty();
462 $('#searchresults thead tr').empty();
463 $('#searchresults tbody').empty();
464 $('#search-serversinfo').empty().append('<li>' + _("Loading...") + '</li>');
465 $('#search-results-ui').modal('show');
468 function showSearchSorting( sort_key, sort_direction ) {
469 var $th = $('#searchresults thead tr th[data-sort-label="' + sort_key + '"]');
470 $th.parent().find( 'th[data-sort-label]' ).attr( 'class', 'sorting' );
472 if ( sort_direction == 'asc' ) {
474 $th.attr( 'class', 'sorting_asc' );
477 $th.attr( 'class', 'sorting_desc' );
481 function showSearchResults( editor, data ) {
482 backends.search.records = {};
484 $('#searchresults thead tr').empty();
485 $('#searchresults tbody').empty();
486 $('#search-serversinfo').empty();
488 $.each( z3950Servers, function( index, server ) {
489 var num_fetched = data.num_fetched[server.server_id];
491 if ( data.errors[server.server_id] ) {
492 num_fetched = data.errors[server.server_id];
493 } else if ( num_fetched == null ) {
495 } else if ( num_fetched < data.num_hits[server.server_id] ) {
499 $('#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>' );
502 var seenColumns = {};
504 $.each( data.hits, function( undef, hit ) {
505 $.each( hit.metadata, function(key) {
506 seenColumns[key] = true;
510 $('#searchresults thead tr').append('<th>' + _("Source") + '</th>');
512 $.each( z3950Labels, function( undef, label ) {
513 if ( seenColumns[ label[0] ] ) {
514 $('#searchresults thead tr').append( '<th class="sorting" data-sort-label="' + label[0] + '">' + label[1] + '</th>' );
518 showSearchSorting( data.sort_key, data.sort_direction );
520 $('#searchresults thead tr').append('<th>' + _("Tools") + '</th>');
522 var bibnumMap = KohaBackend.GetSubfieldForKohaField('biblio.biblionumber');
523 $.each( data.hits, function( undef, hit ) {
524 backends.search.records[ hit.server + ':' + hit.index ] = hit.record;
526 switch ( hit.server ) {
527 case 'koha:biblioserver':
528 var bibnumField = hit.record.field( bibnumMap[0] );
530 if ( bibnumField && bibnumField.hasSubfield( bibnumMap[1] ) ) {
531 hit.id = 'catalog/' + bibnumField.subfield( bibnumMap[1] );
535 // Otherwise, fallthrough
538 hit.id = 'search/' + hit.server + ':' + hit.index;
542 var server_name = hit.servername == 'koha:biblioserver' ? _("Local catalog") : hit.servername;
543 result += '<td class="sourcecol">' + server_name + '</td>';
545 $.each( z3950Labels, function( undef, label ) {
546 if ( !seenColumns[ label[0] ] ) return;
548 if ( hit.metadata[ label[0] ] ) {
549 result += '<td class="infocol">' + hit.metadata[ label[0] ] + '</td>';
551 result += '<td class="infocol"> </td>';
555 result += '<td class="toolscol"><ul><li><a href="#" class="marc-link">' + _("View MARC") + '</a></li>';
556 result += '<li><a href="#" class="open-link">' + ( hit.server == 'koha:biblioserver' ? _("Edit") : _("Import") ) + '</a></li>';
557 if ( state.canSave ) result += '<li><a href="#" class="substitute-link" title="' + _("Replace the current record's contents") + '">' + _("Substitute") + '</a></li>';
558 result += '</ul></td></tr>';
560 var $tr = $( result );
561 $tr.find( '.marc-link' ).click( function() {
562 var $info_columns = $tr.find( '.infocol' );
563 var $marc_column = $tr.find( '.marccol' );
565 if ( !$marc_column.length ) {
566 $marc_column = $( '<td class="marccol" colspan="' + $info_columns.length + '"></td>' ).insertAfter( $info_columns.eq(-1) ).hide();
567 CodeMirror.runMode( TextMARC.RecordToText( hit.record ), 'marc', $marc_column[0] );
570 if ( $marc_column.is(':visible') ) {
571 $tr.find('.marc-link').text( _("View MARC") );
572 $info_columns.show();
575 $tr.find('.marc-link').text( _("Hide MARC") );
577 $info_columns.hide();
582 $tr.find( '.open-link' ).click( function() {
583 $( '#search-results-ui' ).modal('hide');
584 openRecord( hit.id, editor );
588 $tr.find( '.substitute-link' ).click( function() {
589 $( '#search-results-ui' ).modal('hide');
590 loadRecord( hit.id, editor );
594 $('#searchresults tbody').append( $tr );
598 var cur_page = data.offset / data.page_size;
599 var max_page = Math.ceil( data.total_fetched / data.page_size ) - 1;
601 if ( cur_page != 0 ) {
602 pages.push( '<li><a class="search-nav" href="#" data-offset="' + (data.offset - data.page_size) + '"><span aria-hidden="true">«</span> ' + _("Previous") + '</a></li>' );
605 for ( var page = Math.max( 0, cur_page - 9 ); page <= Math.min( max_page, cur_page + 9 ); page++ ) {
606 if ( page == cur_page ) {
607 pages.push( ' <li class="active"><a href="#">' + ( page + 1 ) + '</a></li>' );
609 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + ( page * data.page_size ) + '">' + ( page + 1 ) + '</a></li>' );
613 if ( cur_page < max_page ) {
614 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + (data.offset + data.page_size) + '">' + _("Next") + ' <span aria-hidden="true">»</span></a></li>' );
617 $( '#search-top-pages, #search-bottom-pages' ).find( 'nav' ).html( pages.length > 1 ? ( '<ul class="pagination pagination-sm">' + pages.join( '' ) + '</ul>' ) : '' );
619 var $overlay = $('#search-overlay');
620 $overlay.find('span').text(_("Loading"));
621 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
623 if ( data.activeclients ) {
624 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
627 $overlay.find('.bar').css( { display: 'block', width: '100%' } );
629 $('#searchresults')[0].focus();
633 function invalidateSearchResults() {
634 var $overlay = $('#search-overlay');
635 $overlay.find('span').text(_("Search expired, please try again"));
636 $overlay.find('.bar').css( { display: 'none' } );
640 function handleSearchError(error) {
641 if (error.code == 1) {
642 invalidateSearchResults();
645 humanMsg.displayMsg( '<h3>' + _("Internal search error") + '</h3><p>' + error.responseText + '</p><p>' + _("Please refresh the page and try again.") + '</p>', { className: 'humanError' } );
649 function handleSearchInitError(error) {
650 $('#quicksearch-overlay').fadeIn().find('p').text(error);
653 // Preference functions
654 function showPreference( pref ) {
655 var value = Preferences.user[pref];
659 $( '#set-field-widgets' ).text( value ? _("Show fields verbatim") : _("Show helpers for fixed and coded fields") );
662 $( '#editor .CodeMirror' ).css( { fontFamily: value } );
666 $( '#editor .CodeMirror' ).css( { fontSize: value } );
670 // Macros loaded on first show of modal
672 case 'selected_search_targets':
673 $.each( z3950Servers, function( index, server ) {
674 var saved_val = Preferences.user.selected_search_targets[server.server_id];
676 if ( saved_val != null ) server.checked = saved_val;
682 function bindPreference( editor, pref ) {
683 function _addHandler( sel, event, handler ) {
684 $( sel ).on( event, function (e) {
686 handler( e, Preferences.user[pref] );
687 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
688 showPreference(pref);
694 _addHandler( '#set-field-widgets', 'click', function( e, oldValue ) {
695 editor.setUseWidgets( Preferences.user.fieldWidgets = !Preferences.user.fieldWidgets );
699 _addHandler( '#prefs-menu .set-font', 'click', function( e, oldValue ) {
700 Preferences.user.font = $( e.target ).css( 'font-family' );
704 _addHandler( '#prefs-menu .set-fontSize', 'click', function( e, oldValue ) {
705 Preferences.user.fontSize = $( e.target ).css( 'font-size' );
708 case 'selected_search_targets':
709 $( document ).on( 'change', 'input.search-toggle-server', function() {
710 var server_id = $( this ).closest('li').data('server-id');
711 Preferences.user.selected_search_targets[server_id] = this.checked;
712 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
718 function displayPreferences( editor ) {
719 $.each( Preferences.user, function( pref, value ) {
720 showPreference( pref );
721 bindPreference( editor, pref );
726 var canCreatePublic = "[% CAN_user_editcatalogue_create_shared_macros | html %]";
727 var canDeletePublic = "[% CAN_user_editcatalogue_delete_shared_macros | html %]";
729 function deleteMacro( id ){
730 $( '#macro-list' ).empty();
731 var shared = macroEditor.activeMacroShared;
732 var id = macroEditor.activeMacroId;
733 macroEditor.activeMacroId = null;
734 api_url = "/api/v1/advanced_editor/macros/";
735 if( shared ) { api_url += "shared/" }
739 contentType: "application/json",
742 .then(function(result) {
743 humanMsg.displayAlert( _("Macro successfully deleted") );
746 .fail(function(err) {
748 if( err.status == "404" ){
749 err_message = "Macro not found";
750 } else if ( err.status == "403" ){
751 err_message = _("You do not have permission to delete this macro");
753 err_message = _("There was a problem, please check the logs");
755 humanMsg.displayAlert( _("Failed to delete macro: " + err_message), { className: 'humanError' } );
760 function loadMacro( name, id, shared ) {
761 $( '#macro-list li' ).removeClass( 'active' );
762 $(".macro_shared").prop("checked",false).hide();
763 $("#delete-macro").prop("disabled",true);
764 macroEditor.setOption( 'readOnly', false );
765 macroEditor.activeMacro = name;
766 macroEditor.activeMacroId = id;
769 macroEditor.setValue( '' );
772 $( '#macro-list li[data-name="' + name + '"][data-id="' + id + '"]' ).addClass( 'active' );
773 api_url = "/api/v1/advanced_editor/macros/";
774 if( shared ) { api_url += "shared/" }
778 contentType: "application/json",
781 .then(function(result) {
782 macroEditor.setValue( result.macro_text );
783 $(".macro_shared").show();
785 $(".macro_shared").prop("checked",true);
786 if( canCreatePublic ){
787 macroEditor.setOption( 'readOnly', false );
789 macroEditor.setOption( 'readOnly', true );
791 if( canDeletePublic ){
792 $("#delete-macro").prop("disabled",false);
795 macroEditor.setOption( 'readOnly', false );
796 $("#delete-macro").prop("disabled",false);
798 macroEditor.activeMacroShared = result.shared;
800 .fail(function(err) {
802 if( err.status == "404" ){
803 err_message = "Macro not found";
804 } else if ( err.status == "403" ){
805 err_message = _("You do not have permission to access this macro");
807 err_message = _("There was a problem, please check the logs");
809 humanMsg.displayAlert( _("Failed to load macros: ") + err_message, { className: 'humanError' } );
814 function convertOldMacros(){
815 $("#convert-macros").remove();
816 var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
817 return $.extend( { name: name }, macro );
819 macro_list.sort( function( a, b ) {
820 return a.name.localeCompare(b.name);
822 $.each( macro_list, function( index, macro ) {
824 url: "/api/v1/advanced_editor/macros/",
826 contentType: "application/json",
827 data: JSON.stringify({
829 patron_id: [% logged_in_user.borrowernumber | html %],
830 macro_text: macro.contents,
835 .then(function(undef, result) {
836 delete Preferences.user.macros[macro.name];
837 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
838 if( index == macro_list.length -1 ){
843 .fail(function(err) {
845 if( err.status == "403" ){
846 err_message = _("You do not have permission to create this macro");
848 err_message = _("There was a problem, please check the logs");
850 humanMsg.displayAlert( _("Failed to create macro: ") + err_message, { className: 'humanError' } );
855 function showSavedMacros( macros ) {
856 var scrollTop = $('#macro-list').scrollTop();
857 $( '#macro-list' ).empty();
858 $("#convert-macros").remove();
859 if( Object.keys(Preferences.user.macros).length ){
860 $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>' );
861 $convert.click( function(){
862 if( !confirm( _("This will retrieve macros stored in the brower, save them in the database, and delete them from the browser. Proceed?") ) ){
867 $("#macro-toolbar").prepend($convert);
870 url: "/api/v1/advanced_editor/macros/",
872 contentType: "application/json",
875 .then(function(result) {
876 $.each(result,function( undef, macro ){
877 var $li = $( '<li data-name="' + macro.name + '" data-id="' + macro.macro_id + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
878 if ( macro.macro_id == macroEditor.activeMacroId ) $li.addClass( 'active' );
879 $li.click( function() {
880 loadMacro(macro.name, macro.macro_id, macro.shared);
883 $('#macro-list').append($li);
886 .fail(function(err) {
887 var err_message = _("There was a problem, please check the logs");
888 humanMsg.displayAlert( _("Failed to load macros: ") + err_message, { className: 'humanError' } );
890 var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
891 $new_li.click( function() {
892 // TODO: make this a bit less retro
893 var name = prompt(_("Please enter the name for the new macro:"));
896 // if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
898 url: "/api/v1/advanced_editor/macros/",
900 contentType: "application/json",
901 data: JSON.stringify({
903 patron_id: [% logged_in_user.borrowernumber | html %],
909 .then(function(result) {
911 loadMacro( result.name, result.macro_id );
913 .fail(function(err) {
915 if( err.status == "403" ){
916 err_message = _("You do not have permission to access this macro");
918 err_message = _("There was a problem, please check the logs");
920 humanMsg.displayAlert( _("Failed to create macro: ") + err_message, { className: 'humanError' } );
923 $('#macro-list').append($new_li);
924 $('#macro-list').scrollTop(scrollTop);
927 function saveMacro(shared) {
928 var name = macroEditor.activeMacro;
929 var macro_id = macroEditor.activeMacroId;
930 var was_shared = macroEditor.activeMacroShared;
932 if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() && was_shared == shared ) return;
934 macroEditor.savedGeneration = macroEditor.changeGeneration();
935 api_url = "/api/v1/advanced_editor/macros/";
936 if( shared || was_shared ) { api_url += "shared/" }
939 url: api_url + macro_id,
941 contentType: "application/json",
942 data: JSON.stringify({
944 patron_id: [% logged_in_user.borrowernumber | html %],
945 macro_text: macroEditor.getValue(),
950 .then(function(result) {
951 $('#macro-save-message').text(_("Saved"));
952 macroEditor.activeMacroShared = shared;
955 .fail(function(err) {
957 if( err.status == "404" ){
958 err_message = _("Macro not found");
959 } else if ( err.status ="403" ){
960 err_message = _("You do not have permission to access this macro");
962 err_message = _("There was a problem, please check the logs");
964 humanMsg.displayAlert( _("Failed to save macro: ") + err_message, { className: 'humanError' } );
968 $(".macro_shared").change(function(){
976 // END Macro functions
978 $(document).ready( function() {
980 editor = new MARCEditor( {
981 onCursorActivity: function() {
982 $('#status-tag-info').empty();
983 $('#status-subfield-info').empty();
985 var field = editor.getCurrentField();
986 var cur = editor.getCursor();
988 if ( !field ) return;
990 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
991 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
994 $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> ' + taginfo.lib );
996 var subfield = field.getSubfieldAt( cur.ch );
997 if ( !subfield ) return;
999 var subfieldinfo = taginfo.subfields[ subfield.code ];
1000 $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
1002 if ( subfieldinfo ) {
1003 $('#status-subfield-info').append( subfieldinfo.lib );
1005 $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
1008 $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
1011 position: function (elt) { $(elt).insertAfter('#toolbar') },
1014 // Automatically detect resizes and change the height of the editor and position of modals.
1015 var resizeTimer = null;
1016 function onResize() {
1017 if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
1020 var pos = $('#editor .CodeMirror').offset();
1021 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
1023 $('.modal-body').each( function() {
1024 $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
1029 $( '#macro-ui' ).on( 'shown.bs.modal', function() {
1030 if ( macroEditor ) return;
1032 macroEditor = CodeMirror(
1033 $('#macro-editor')[0],
1036 'Ctrl-D': function( cm ) {
1037 var cur = cm.getCursor();
1039 cm.replaceRange( "‡", cur, null );
1048 macroEditor.on( 'change', function( cm, change ) {
1049 $('#macro-save-message').empty();
1050 if ( change.origin == 'setValue' ) return;
1052 if ( saveTimeout ) clearTimeout( saveTimeout );
1053 saveTimeout = setTimeout( function() {
1054 saveMacro(macroEditor.activeMacroShared);
1063 var saveableBackends = [];
1064 $.each( backends, function( id, backend ) {
1065 if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
1067 saveableBackends.sort();
1068 $.each( saveableBackends, function( undef, backend ) {
1069 $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
1073 $( '#save-record, #save-dropdown a' ).click( function() {
1074 $( '#save-record' ).find('i').attr( 'class', 'fa fa-spinner fa-spin' ).siblings( 'span' ).text( _("Saving...") );
1076 function finishCb(result) {
1077 if ( result.error == 'syntax' ) {
1078 humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
1079 } else if ( result.error == 'invalid' ) {
1080 humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
1081 } else if ( result.error ) {
1082 humanMsg.displayAlert( _("Something went wrong, cannot save"), { className: 'humanError' } );
1083 } else if ( !result.error ) {
1084 humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
1087 $.each( result.errors || [], function( undef, error ) {
1088 switch ( error.type ) {
1090 editor.addError( error.line, _("Invalid tag number") );
1092 case 'noIndicators':
1093 editor.addError( error.line, _("Invalid indicators") );
1096 editor.addError( error.line, _("Tag has no subfields") );
1099 editor.addError( null, _("Missing mandatory tag: ") + error.tag );
1101 case 'missingSubfield':
1102 if ( error.subfield == '@' ) {
1103 editor.addError( error.line, _("Missing control field contents") );
1105 editor.addError( error.line, _("Missing mandatory subfield: ‡") + error.subfield );
1108 case 'unrepeatableTag':
1109 editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
1111 case 'unrepeatableSubfield':
1112 editor.addError( error.line, _("Subfield ‡") + error.subfield + _(" cannot be repeated") );
1114 case 'itemTagUnsupported':
1115 editor.addError( error.line, _("Item tags cannot currently be saved") );
1120 $( '#save-record' ).find('i').attr( 'class', 'fa fa-hdd-o' );
1122 if ( result.error ) {
1123 // Reset backend info
1124 setSource( [ state.backend, state.recordID ] );
1128 var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
1129 if ( state.backend == backend ) {
1130 saveRecord( backend + '/' + state.recordID, editor, finishCb );
1132 saveRecord( backend + '/', editor, finishCb );
1138 $('#import-records').click( function() {
1139 $('#import-records-input')
1141 .change( function() {
1142 if ( !this.files || !this.files.length ) return;
1144 var file = this.files[0];
1145 var reader = new FileReader();
1147 reader.onload = function() {
1148 var record = new MARC.Record();
1150 if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
1151 record.loadISO2709( reader.result );
1152 } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
1153 record.loadMARCXML( reader.result );
1155 humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
1159 if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
1161 editor.displayRecord( record );
1164 reader.readAsText( file );
1171 $('#open-macros').click( function() {
1172 $('#macro-ui').modal('show');
1177 $('#run-macro').click( function() {
1178 var result = Macros.Run( editor, 'rancor', macroEditor.getValue() );
1180 if ( !result.errors.length ) {
1181 $('#macro-ui').modal('hide');
1182 editor.focus(); //Return cursor to editor after macro run
1187 $.each( result.errors, function() {
1188 var error = '<strong>' + _("Line ") + (this.line + 1) + ':</strong> ';
1190 switch ( this.error ) {
1191 case 'failed': error += _("failed to run"); break;
1192 case 'unrecognized': error += _("unrecognized command"); break;
1198 humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
1203 $('#delete-macro').click( function() {
1204 if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
1206 loadMacro( undefined );
1211 $( '#switch-editor' ).click( function() {
1212 if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
1214 Cookies.set( "catalogue_editor_[% logged_in_user.borrowernumber | html %]", "basic", { expires: 365, path: '/', sameSite: 'Lax'} );
1216 if ( state.backend == 'catalog' ) {
1217 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
1218 } else if ( state.backend == 'new' ) {
1219 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
1221 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
1225 $( '#show-advanced-search' ).click( function() {
1226 showAdvancedSearch();
1231 $('#advanced-search').submit( function() {
1232 startAdvancedSearch();
1237 $( document ).on( 'click', 'a.search-nav', function() {
1238 if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1239 $("#search-overlay").show();
1245 $( document ).on( 'click', 'th[data-sort-label]', function() {
1248 if ( $( this ).hasClass( 'sorting_asc' ) ) {
1254 if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1255 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1257 $("#search-overlay").show();
1263 $( document ).on( 'change', 'input.search-toggle-server', function() {
1264 var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1265 server.checked = this.checked;
1267 if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1268 $("#search-overlay").show();
1276 $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1277 $(this).modal({ show: false });
1280 var $quicksearch = $('#quicksearch fieldset');
1281 $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1282 position: 'absolute',
1283 top: $quicksearch.offset().top,
1284 left: $quicksearch.offset().left,
1285 height: $quicksearch.outerHeight(),
1286 width: $quicksearch.outerWidth(),
1287 }).appendTo(document.body).hide();
1289 var prevAlerts = [];
1290 humanMsg.logMsg = function(msg, options) {
1291 $('#show-alerts').popover('hide');
1292 prevAlerts.unshift('<li>' + msg + '</li>');
1293 prevAlerts.splice(5, 999); // Truncate old messages
1296 $('#show-alerts').popover({
1298 placement: 'bottom',
1299 content: function() {
1300 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1304 $('#show-shortcuts').popover({
1306 placement: 'bottom',
1307 content: function() {
1308 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1312 $('#new-record' ).click( function() {
1313 if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1315 openRecord( 'new/', editor );
1319 window.onbeforeunload = function() {
1320 if(editor.modified )
1323 { return undefined; }
1326 $('a.change-framework').click( function() {
1327 $("#loading").show();
1328 editor.setFrameworkCode(
1329 $(this).data( 'frameworkcode' ),
1331 function ( error ) {
1332 if ( typeof error !== 'undefined' ) {
1333 humanMsg.displayAlert( _("Failed to change framework"), { className: 'humanError' } );
1335 $('#loading').hide();
1341 Preferences.Load( [% logged_in_user.borrowernumber || 0 | html %] );
1342 displayPreferences(editor);
1343 makeAuthorisedValueWidgets( '' );
1346 onresults: function(data) { showSearchResults( editor, data ) },
1347 onerror: handleSearchError,
1350 function finishCb( data ) {
1352 humanMsg.displayAlert( data.error );
1353 openRecord( 'new/', editor, finishCb );
1356 Resources.GetAll().done( function() {
1357 $("#loading").hide();
1358 $( window ).resize( onResize ).resize();
1363 if ( "[% auth_forwarded_hash | html %]" ) {
1364 document.location.hash = "[% auth_forwarded_hash | html %]";
1367 if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1368 openRecord( 'new/', editor, finishCb );
1374 <!-- / cateditor-ui.inc -->