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/advancededitormacros/";
706 if( shared ) { api_url += "shared/" }
710 contentType: "application/json",
713 .then(function(result) {
714 humanMsg.displayAlert( _("Macro successfully deleted") );
717 .fail(function(err) {
718 humanMsg.displayAlert( _("Failed to delete macro:") + err.responseText, { className: 'humanError' } );
723 function loadMacro( name, id, shared ) {
724 $( '#macro-list li' ).removeClass( 'active' );
725 $(".macro_shared").prop("checked",false).hide();
726 $("#delete-macro").prop("disabled",true);
727 macroEditor.setOption( 'readOnly', false );
728 macroEditor.activeMacro = name;
729 macroEditor.activeMacroId = id;
732 macroEditor.setValue( '' );
735 $( '#macro-list li[data-name="' + name + '"][data-id="' + id + '"]' ).addClass( 'active' );
736 api_url = "/api/v1/advancededitormacros/";
737 if( shared ) { api_url += "shared/" }
741 contentType: "application/json",
744 .then(function(result) {
745 macroEditor.setValue( result.macro_text );
746 $(".macro_shared").show();
748 $(".macro_shared").prop("checked",true);
749 if( canCreatePublic ){
750 macroEditor.setOption( 'readOnly', false );
752 macroEditor.setOption( 'readOnly', true );
754 if( canDeletePublic ){
755 $("#delete-macro").prop("disabled",false);
758 macroEditor.setOption( 'readOnly', false );
759 $("#delete-macro").prop("disabled",false);
761 macroEditor.activeMacroShared = result.shared;
763 .fail(function(err) {
764 humanMsg.displayAlert( _("Failed to load macros:") + err.responseText, { className: 'humanError' } );
769 function convertOldMacros(){
770 $("#convert-macros").remove();
771 var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
772 return $.extend( { name: name }, macro );
774 macro_list.sort( function( a, b ) {
775 return a.name.localeCompare(b.name);
777 $.each( macro_list, function( index, macro ) {
779 url: "/api/v1/advancededitormacros/",
781 contentType: "application/json",
782 data: JSON.stringify({
784 patron_id: [% logged_in_user.borrowernumber | html %],
785 macro_text: macro.contents,
790 .then(function(undef, result) {
791 delete Preferences.user.macros[macro.name];
792 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
793 if( index == macro_list.length -1 ){
798 .fail(function(err) {
799 humanMsg.displayAlert( _("Failed to create macro:") + err.responseText, { className: 'humanError' } );
804 function showSavedMacros( macros ) {
805 var scrollTop = $('#macro-list').scrollTop();
806 $( '#macro-list' ).empty();
807 $("#convert-macros").remove();
808 if( Object.keys(Preferences.user.macros).length ){
809 $convert = $( '<button class="btn btn-default" id="convert-macros" title="Convert browser storage macros"><i class="fa fa-adjust"></i> Convert old macros</button>' );
810 $convert.click( function(){
811 if( !confirm( _("This will retrieve macros stored in the brower, save them in the database, and delete them from the browser. Proceed?") ) ){
816 $("#macro-toolbar").prepend($convert);
819 url: "/api/v1/advancededitormacros/",
821 contentType: "application/json",
824 .then(function(result) {
825 $.each(result,function( undef, macro ){
826 var $li = $( '<li data-name="' + macro.name + '" data-id="' + macro.macro_id + '"><a href="#">'+ macro.macro_id + ' - ' + macro.name + '</a><ol class="macro-info"></ol></li>' );
827 if ( macro.macro_id == macroEditor.activeMacroId ) $li.addClass( 'active' );
828 $li.click( function() {
829 loadMacro(macro.name, macro.macro_id, macro.shared);
832 $('#macro-list').append($li);
835 .fail(function(err) {
836 humanMsg.displayAlert( _("Failed to load macros:") + err.responseText, { className: 'humanError' } );
838 var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
839 $new_li.click( function() {
840 // TODO: make this a bit less retro
841 var name = prompt(_("Please enter the name for the new macro:"));
844 // if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
846 url: "/api/v1/advancededitormacros/",
848 contentType: "application/json",
849 data: JSON.stringify({
851 patron_id: [% logged_in_user.borrowernumber | html %],
857 .then(function(undef, result) {
859 loadMacro( result.name, result.macro_id );
861 .fail(function(err) {
862 humanMsg.displayAlert( _("Failed to create macro:") + err.responseText, { className: 'humanError' } );
865 $('#macro-list').append($new_li);
866 $('#macro-list').scrollTop(scrollTop);
869 function saveMacro(shared) {
870 var name = macroEditor.activeMacro;
871 var macro_id = macroEditor.activeMacroId;
872 var was_shared = macroEditor.activeMacroShared;
874 if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() && was_shared == shared ) return;
876 macroEditor.savedGeneration = macroEditor.changeGeneration();
877 api_url = "/api/v1/advancededitormacros/";
878 if( shared || was_shared ) { api_url += "shared/" }
881 url: api_url + macro_id,
883 contentType: "application/json",
884 data: JSON.stringify({
886 patron_id: [% logged_in_user.borrowernumber | html %],
887 macro_text: macroEditor.getValue(),
892 .then(function(result) {
893 $('#macro-save-message').text(_("Saved"));
894 macroEditor.activeMacroShared = shared;
897 .fail(function(err) {
898 humanMsg.displayAlert( _("Failed to save macro:") + err.responseText, { className: 'humanError' } );
902 $(".macro_shared").change(function(){
910 // END Macro functions
912 $(document).ready( function() {
914 editor = new MARCEditor( {
915 onCursorActivity: function() {
916 $('#status-tag-info').empty();
917 $('#status-subfield-info').empty();
919 var field = editor.getCurrentField();
920 var cur = editor.getCursor();
922 if ( !field ) return;
924 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
925 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
928 $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> ' + taginfo.lib );
930 var subfield = field.getSubfieldAt( cur.ch );
931 if ( !subfield ) return;
933 var subfieldinfo = taginfo.subfields[ subfield.code ];
934 $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
936 if ( subfieldinfo ) {
937 $('#status-subfield-info').append( subfieldinfo.lib );
939 $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
942 $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
945 position: function (elt) { $(elt).insertAfter('#toolbar') },
948 // Automatically detect resizes and change the height of the editor and position of modals.
949 var resizeTimer = null;
950 function onResize() {
951 if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
954 var pos = $('#editor .CodeMirror').offset();
955 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
957 $('.modal-body').each( function() {
958 $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
963 $( '#macro-ui' ).on( 'shown.bs.modal', function() {
964 if ( macroEditor ) return;
966 macroEditor = CodeMirror(
967 $('#macro-editor')[0],
970 'Ctrl-D': function( cm ) {
971 var cur = cm.getCursor();
973 cm.replaceRange( "‡", cur, null );
982 macroEditor.on( 'change', function( cm, change ) {
983 $('#macro-save-message').empty();
984 if ( change.origin == 'setValue' ) return;
986 if ( saveTimeout ) clearTimeout( saveTimeout );
987 saveTimeout = setTimeout( function() {
988 saveMacro(macroEditor.activeMacroShared);
997 var saveableBackends = [];
998 $.each( backends, function( id, backend ) {
999 if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
1001 saveableBackends.sort();
1002 $.each( saveableBackends, function( undef, backend ) {
1003 $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
1007 $( '#save-record, #save-dropdown a' ).click( function() {
1008 $( '#save-record' ).find('i').attr( 'class', 'fa fa-spinner fa-spin' ).siblings( 'span' ).text( _("Saving...") );
1010 function finishCb(result) {
1011 if ( result.error == 'syntax' ) {
1012 humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
1013 } else if ( result.error == 'invalid' ) {
1014 humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
1015 } else if ( result.error ) {
1016 humanMsg.displayAlert( _("Something went wrong, cannot save"), { className: 'humanError' } );
1017 } else if ( !result.error ) {
1018 humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
1021 $.each( result.errors || [], function( undef, error ) {
1022 switch ( error.type ) {
1024 editor.addError( error.line, _("Invalid tag number") );
1026 case 'noIndicators':
1027 editor.addError( error.line, _("Invalid indicators") );
1030 editor.addError( error.line, _("Tag has no subfields") );
1033 editor.addError( null, _("Missing mandatory tag: ") + error.tag );
1035 case 'missingSubfield':
1036 if ( error.subfield == '@' ) {
1037 editor.addError( error.line, _("Missing control field contents") );
1039 editor.addError( error.line, _("Missing mandatory subfield: ‡") + error.subfield );
1042 case 'unrepeatableTag':
1043 editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
1045 case 'unrepeatableSubfield':
1046 editor.addError( error.line, _("Subfield ‡") + error.subfield + _(" cannot be repeated") );
1048 case 'itemTagUnsupported':
1049 editor.addError( error.line, _("Item tags cannot currently be saved") );
1054 $( '#save-record' ).find('i').attr( 'class', 'fa fa-hdd-o' );
1056 if ( result.error ) {
1057 // Reset backend info
1058 setSource( [ state.backend, state.recordID ] );
1062 var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
1063 if ( state.backend == backend ) {
1064 saveRecord( backend + '/' + state.recordID, editor, finishCb );
1066 saveRecord( backend + '/', editor, finishCb );
1072 $('#import-records').click( function() {
1073 $('#import-records-input')
1075 .change( function() {
1076 if ( !this.files || !this.files.length ) return;
1078 var file = this.files[0];
1079 var reader = new FileReader();
1081 reader.onload = function() {
1082 var record = new MARC.Record();
1084 if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
1085 record.loadISO2709( reader.result );
1086 } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
1087 record.loadMARCXML( reader.result );
1089 humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
1093 if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
1095 editor.displayRecord( record );
1098 reader.readAsText( file );
1105 $('#open-macros').click( function() {
1106 $('#macro-ui').modal('show');
1111 $('#run-macro').click( function() {
1112 var result = Macros.Run( editor, 'rancor', macroEditor.getValue() );
1114 if ( !result.errors.length ) {
1115 $('#macro-ui').modal('hide');
1116 editor.focus(); //Return cursor to editor after macro run
1121 $.each( result.errors, function() {
1122 var error = '<b>' + _("Line ") + (this.line + 1) + ':</b> ';
1124 switch ( this.error ) {
1125 case 'failed': error += _("failed to run"); break;
1126 case 'unrecognized': error += _("unrecognized command"); break;
1132 humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
1137 $('#delete-macro').click( function() {
1138 if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
1140 loadMacro( undefined );
1145 $( '#switch-editor' ).click( function() {
1146 if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
1148 $.cookie( 'catalogue_editor_[% logged_in_user.borrowernumber | html %]', 'basic', { expires: 365, path: '/' } );
1150 if ( state.backend == 'catalog' ) {
1151 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
1152 } else if ( state.backend == 'new' ) {
1153 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
1155 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
1159 $( '#show-advanced-search' ).click( function() {
1160 showAdvancedSearch();
1165 $('#advanced-search').submit( function() {
1166 startAdvancedSearch();
1171 $( document ).on( 'click', 'a.search-nav', function() {
1172 if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1173 $("#search-overlay").show();
1179 $( document ).on( 'click', 'th[data-sort-label]', function() {
1182 if ( $( this ).hasClass( 'sorting_asc' ) ) {
1188 if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1189 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1191 $("#search-overlay").show();
1197 $( document ).on( 'change', 'input.search-toggle-server', function() {
1198 var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1199 server.checked = this.checked;
1201 if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1202 $("#search-overlay").show();
1210 $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1211 $(this).modal({ show: false });
1214 var $quicksearch = $('#quicksearch fieldset');
1215 $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1216 position: 'absolute',
1217 top: $quicksearch.offset().top,
1218 left: $quicksearch.offset().left,
1219 height: $quicksearch.outerHeight(),
1220 width: $quicksearch.outerWidth(),
1221 }).appendTo(document.body).hide();
1223 var prevAlerts = [];
1224 humanMsg.logMsg = function(msg, options) {
1225 $('#show-alerts').popover('hide');
1226 prevAlerts.unshift('<li>' + msg + '</li>');
1227 prevAlerts.splice(5, 999); // Truncate old messages
1230 $('#show-alerts').popover({
1232 placement: 'bottom',
1233 content: function() {
1234 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1238 $('#show-shortcuts').popover({
1240 placement: 'bottom',
1241 content: function() {
1242 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1246 $('#new-record' ).click( function() {
1247 if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1249 openRecord( 'new/', editor );
1253 window.onbeforeunload = function() {
1254 if(editor.modified )
1257 { return undefined; }
1260 $('a.change-framework').click( function() {
1261 $("#loading").show();
1262 editor.setFrameworkCode(
1263 $(this).data( 'frameworkcode' ),
1265 function ( error ) {
1266 if ( typeof error !== 'undefined' ) {
1267 humanMsg.displayAlert( _("Failed to change framework"), { className: 'humanError' } );
1269 $('#loading').hide();
1275 Preferences.Load( [% logged_in_user.borrowernumber || 0 | html %] );
1276 displayPreferences(editor);
1277 makeAuthorisedValueWidgets( '' );
1280 onresults: function(data) { showSearchResults( editor, data ) },
1281 onerror: handleSearchError,
1284 function finishCb( data ) {
1286 humanMsg.displayAlert( data.error );
1287 openRecord( 'new/', editor, finishCb );
1290 Resources.GetAll().done( function() {
1291 $("#loading").hide();
1292 $( window ).resize( onResize ).resize();
1297 if ( "[% auth_forwarded_hash | html %]" ) {
1298 document.location.hash = "[% auth_forwarded_hash | html %]";
1301 if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1302 openRecord( 'new/', editor, finishCb );
1308 <!-- / cateditor-ui.inc -->