Bug 20205: Add IDs to buttons in patron-toolbar.inc
[koha.git] / koha-tmpl / intranet-tmpl / prog / en / includes / cateditor-ui.inc
bloba6ac276afa1b0c27c0fe7d9f59ef9aad7c5fced2
1 <script src="[% interface %]/lib/codemirror/codemirror-compressed.js"></script>
2 <script src="[% interface %]/lib/filesaver.js"></script>
3 <script src="[% interface %]/lib/koha/cateditor/marc-mode.js"></script>
4 <script src="[% interface %]/lib/require.js"></script>
5 <script>
6 require.config( {
7     baseUrl: '[% interface %]/lib/koha/cateditor/',
8     config: {
9         resources: {
10             marcflavour: '[% marcflavour %]',
11             themelang: '[% themelang %]',
12         },
13     },
14     waitSeconds: 30,
15 } );
16 </script>
18 [% IF marcflavour == 'MARC21' %]
19 [% PROCESS 'cateditor-widgets-marc21.inc' %]
20 [% ELSE %]
21 <script>var editorWidgets = {};</script>
22 [% END %]
24 <script>
25 require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'preferences', 'resources', 'text-marc', 'widget' ], function( KohaBackend, Search, Macros, MARCEditor, MARC, Preferences, Resources, TextMARC, Widget ) {
26     var z3950Servers = {
27         'koha:biblioserver': {
28             name: _("Local catalog"),
29             recordtype: 'biblio',
30             checked: false,
31         },
32         [%- FOREACH server = z3950_servers -%]
33             [% server.id %]: {
34                 name: '[% server.servername %]',
35                 recordtype: '[% server.recordtype %]',
36                 checked: [% server.checked ? 'true' : 'false' %],
37             },
38         [%- END -%]
39     };
41     // The columns that should show up in a search, in order, and keyed by the corresponding <metadata> tag in the XSL and Pazpar2 config
42     var z3950Labels = [
43         [ "local_number", _("Local number") ],
44         [ "title", _("Title") ],
45         [ "series", _("Series title") ],
46         [ "author", _("Author") ],
47         [ "lccn", _("LCCN") ],
48         [ "isbn", _("ISBN") ],
49         [ "issn", _("ISSN") ],
50         [ "medium", _("Medium") ],
51         [ "edition", _("Edition") ],
52         [ "notes", _("Notes") ],
53     ];
55     var state = {
56         backend: '',
57         saveBackend: 'catalog',
58         recordID: undefined
59     };
61     var editor;
62     var macroEditor;
64     function makeAuthorisedValueWidgets( frameworkCode ) {
65         $.each( KohaBackend.GetAllTagsInfo( frameworkCode ), function( tag, tagInfo ) {
66             $.each( tagInfo.subfields, function( subfield, subfieldInfo ) {
67                 if ( !subfieldInfo.authorised_value ) return;
68                 var authvals = KohaBackend.GetAuthorisedValues( subfieldInfo.authorised_value );
69                 if ( !authvals ) return;
71                 var defaultvalue = subfield.defaultvalue || authvals[0].value;
73                 Widget.Register( tag + subfield, {
74                     init: function() {
75                         var $result = $( '<span class="subfield-widget"></span>' );
77                         return $result[0];
78                     },
79                     postCreate: function() {
80                         var value = defaultvalue;
81                         var widget = this;
83                         $.each( authvals, function() {
84                             if ( this.value == widget.text ) {
85                                 value = this.value;
86                             }
87                         } );
89                         this.setText( value );
91                         $( '<select></select>' ).appendTo( this.node );
92                         var $node = $( this.node ).find( 'select' );
93                         $.each( authvals, function( undef, authval ) {
94                             $node.append( '<option value="' + authval.value + '"' + (authval.value == value ? ' selected="selected"' : '') + '>' + authval.lib + '</option>' );
95                         } );
96                         $node.val( this.text );
98                         $node.change( $.proxy( function() {
99                             this.setText( $node.val() );
100                         }, this ) );
101                     },
102                     makeTemplate: function() {
103                         return defaultvalue;
104                     },
105                 } );
106             } );
107         } );
108     }
110     function bindGlobalKeys() {
111         shortcut.add( 'ctrl+s', function(event) {
112             $( '#save-record' ).click();
114             event.preventDefault();
115         } );
117         shortcut.add( 'alt+ctrl+k', function(event) {
118             $( '#search-by-keywords' ).focus();
120             return false;
121         } );
123         shortcut.add( 'alt+ctrl+a', function(event) {
124             $( '#search-by-author' ).focus();
126             return false;
127         } );
129         shortcut.add( 'alt+ctrl+i', function(event) {
130             $( '#search-by-isbn' ).focus();
132             return false;
133         } );
135         shortcut.add( 'alt+ctrl+t', function(event) {
136             $( '#search-by-title' ).focus();
138             return false;
139         } );
141         shortcut.add( 'ctrl+h', function() {
142             var field = editor.getCurrentField();
144             if ( !field ) return;
146             window.open( getFieldHelpURL( field.tag ) );
147         } );
149         $('#quicksearch .search-box').each( function() {
150             shortcut.add( 'enter', $.proxy( function() {
151                 var terms = [];
153                 $('#quicksearch .search-box').each( function() {
154                     if ( !this.value ) return;
156                     terms.push( [ $(this).data('qualifier'), this.value ] );
157                 } );
159                 if ( !terms.length ) return;
161                 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
162                     $("#search-overlay").show();
163                     showResultsBox();
164                 }
166                 return false;
167             }, this), { target: this, type: 'keypress' } );
168         } );
169     }
171     function getFieldHelpURL( tag ) {
172         [% IF ( marcflavour == 'MARC21' ) %]
173             if ( tag == '000' ) {
174                 return "http://www.loc.gov/marc/bibliographic/bdleader.html";
175             } else if ( tag >= '090' && tag < '100' ) {
176                 return "http://www.loc.gov/marc/bibliographic/bd09x.html";
177             } else if ( tag < '900' ) {
178                 return "http://www.loc.gov/marc/bibliographic/bd" + tag + ".html";
179             } else {
180                 return "http://www.loc.gov/marc/bibliographic/bd9xx.html";
181             }
182         [% ELSIF ( marcflavour == 'UNIMARC' ) %]
183             /* http://archive.ifla.org/VI/3/p1996-1/ is an outdated version of UNIMARC, but
184                seems to be the only version available that can be linked to per tag.  More recent
185                versions of the UNIMARC standard are available on the IFLA website only as
186                PDFs!
187             */
188             if ( tag == '000' ) {
189                return  "http://archive.ifla.org/VI/3/p1996-1/uni.htm";
190             } else {
191                 var first = tag[0];
192                 var url = "http://archive.ifla.org/VI/3/p1996-1/uni" + first + ".htm#";
193                 if ( first == '0' ) url += "b";
194                 if ( first != '9' ) url += tag;
196                 return url;
197             }
198         [% END %]
199     }
201     // Record loading
202     var backends = {
203        'new': {
204             titleForRecord: _("Editing new record"),
205             get: function( id, callback ) {
206                 record = new MARC.Record();
207                 KohaBackend.FillRecord( '', record );
209                 callback( record );
210             },
211         },
212         'new-full': {
213             titleForRecord: _("Editing new full record"),
214             get: function( id, callback ) {
215                 record = new MARC.Record();
216                 KohaBackend.FillRecord( '', record, true );
218                 callback( record );
219             },
220         },
221         'catalog': {
222             titleForRecord: _("Editing catalog record #{ID}"),
223             links: [
224                 { title: _("view"), href: "/cgi-bin/koha/catalogue/detail.pl?biblionumber={ID}" },
225                 { title: _("edit items"), href: "/cgi-bin/koha/cataloguing/additem.pl?biblionumber={ID}" },
226             ],
227             saveLabel: _("Save to catalog"),
228             get: function( id, callback ) {
229                 if ( !id ) return false;
231                 KohaBackend.GetRecord( id, callback );
232             },
233             save: function( id, record, done ) {
234                 function finishCb( data ) {
235                     done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
236                 }
238                 if ( id ) {
239                     KohaBackend.SaveRecord( id, record, finishCb );
240                 } else {
241                     KohaBackend.CreateRecord( record, finishCb );
242                 }
243             }
244         },
245         'iso2709': {
246             saveLabel: _("Save as ISO2709 (.mrc) file"),
247             save: function( id, record, done ) {
248                 saveAs( new Blob( [record.toISO2709()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.mrc' );
250                 done( {} );
251             }
252         },
253         'marcxml': {
254             saveLabel: _("Save as MARCXML (.xml) file"),
255             save: function( id, record, done ) {
256                 saveAs( new Blob( [record.toXML()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.xml' );
258                 done( {} );
259             }
260         },
261         'search': {
262             titleForRecord: _("Editing search result"),
263             get: function( id, callback ) {
264                 if ( !id ) return false;
265                 if ( !backends.search.records[ id ] ) {
266                     callback( { error: _( "Invalid record" ) } );
267                     return false;
268                 }
270                 callback( backends.search.records[ id ] );
271             },
272             records: {},
273         },
274     };
276     function setSource(parts) {
277         state.backend = parts[0];
278         state.recordID = parts[1];
279         state.canSave = backends[ state.backend ].save != null;
280         state.saveBackend = state.canSave ? state.backend : 'catalog';
282         var backend = backends[state.backend];
284         document.location.hash = '#' + parts[0] + '/' + parts[1];
286         $('#title').text( backend.titleForRecord.replace( '{ID}', parts[1] ) );
288         $.each( backend.links || [], function( i, link ) {
289             $('#title').append(' <a target="_blank" href="' + link.href.replace( '{ID}', parts[1] ) + '">(' + link.title + ')</a>' );
290         } );
291         $( 'title', document.head ).html( _("Koha &rsaquo; Cataloging &rsaquo; ") + backend.titleForRecord.replace( '{ID}', parts[1] ) );
292         $('#save-record span').text( backends[ state.saveBackend ].saveLabel );
293     }
295     function saveRecord( recid, editor, callback ) {
296         var parts = recid.split('/');
297         if ( parts.length != 2 ) return false;
299         if ( !backends[ parts[0] ] || !backends[ parts[0] ].save ) return false;
301         editor.removeErrors();
302         var record = editor.getRecord();
304         if ( record.errors ) {
305             state.saving = false;
306             callback( { error: 'syntax', errors: record.errors } );
307             return;
308         }
310         var errors = KohaBackend.ValidateRecord( '', record );
311         if ( errors.length ) {
312             state.saving = false;
313             callback( { error: 'invalid', errors: errors } );
314             return;
315         }
317         backends[ parts[0] ].save( parts[1], record, function(data) {
318             state.saving = false;
320             if (data.newRecord) {
321                 var record = new MARC.Record();
322                 record.loadMARCXML(data.newRecord);
323                 editor.displayRecord( record );
324             }
326             if (data.newId) {
327                 setSource(data.newId);
328             } else {
329                 setSource( [ state.backend, state.recordID ] );
330             }
332             if (callback) callback( data );
333         } );
334     }
336     function loadRecord( recid, editor, callback ) {
337         var parts = recid.split('/');
338         if ( parts.length != 2 ) return false;
340         if ( !backends[ parts[0] ] || !backends[ parts[0] ].get ) return false;
342         backends[ parts[0] ].get( parts[1], function( record ) {
343             if ( !record.error ) {
344                 editor.displayRecord( record );
345                 editor.focus();
346             }
348             if (callback) callback(record);
349         } );
351         return true;
352     }
354     function openRecord( recid, editor, callback ) {
355         return loadRecord( recid, editor, function ( record ) {
356             setSource( recid.split('/') );
358             if (callback) callback( record );
359         } );
360     }
362     // Search functions
363     function showAdvancedSearch() {
364         $('#advanced-search-servers').empty();
365         $.each( z3950Servers, function( server_id, server ) {
366             $('#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>' );
367         } );
368         $('#advanced-search-ui').modal('show');
369     }
371     function startAdvancedSearch() {
372         var terms = [];
374         $('#advanced-search-ui .search-box').each( function() {
375             if ( !this.value ) return;
377             terms.push( [ $(this).data('qualifier'), this.value ] );
378         } );
380         if ( !terms.length ) return;
382         if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
383             $('#advanced-search-ui').modal('hide');
384             $("#search-overlay").show();
385             showResultsBox();
386         }
387     }
389     function showResultsBox(data) {
390         $('#search-top-pages, #search-bottom-pages').find('nav').empty();
391         $('#searchresults thead tr').empty();
392         $('#searchresults tbody').empty();
393         $('#search-serversinfo').empty().append('<li>' + _("Loading...") + '</li>');
394         $('#search-results-ui').modal('show');
395     }
397     function showSearchSorting( sort_key, sort_direction ) {
398         var $th = $('#searchresults thead tr th[data-sort-label="' + sort_key + '"]');
399         $th.parent().find( 'th[data-sort-label]' ).attr( 'class', 'sorting' );
401         if ( sort_direction == 'asc' ) {
402             direction = 'asc';
403             $th.attr( 'class', 'sorting_asc' );
404         } else {
405             direction = 'desc';
406             $th.attr( 'class', 'sorting_desc' );
407         }
408     }
410     function showSearchResults( editor, data ) {
411         backends.search.records = {};
413         $('#searchresults thead tr').empty();
414         $('#searchresults tbody').empty();
415         $('#search-serversinfo').empty();
417         $.each( z3950Servers, function( server_id, server ) {
418             var num_fetched = data.num_fetched[server_id];
420             if ( data.errors[server_id] ) {
421                 num_fetched = data.errors[server_id];
422             } else if ( num_fetched == null ) {
423                 num_fetched = '-';
424             } else if ( num_fetched < data.num_hits[server_id] ) {
425                 num_fetched += '+';
426             }
428             $('#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>' );
429         } );
431         var seenColumns = {};
433         $.each( data.hits, function( undef, hit ) {
434             $.each( hit.metadata, function(key) {
435                 seenColumns[key] = true;
436             } );
437         } );
439         $('#searchresults thead tr').append('<th>' + _("Source") + '</th>');
441         $.each( z3950Labels, function( undef, label ) {
442             if ( seenColumns[ label[0] ] ) {
443                 $('#searchresults thead tr').append( '<th class="sorting" data-sort-label="' + label[0] + '">' + label[1] + '</th>' );
444             }
445         } );
447         showSearchSorting( data.sort_key, data.sort_direction );
449         $('#searchresults thead tr').append('<th>' + _("Tools") + '</th>');
451         var bibnumMap = KohaBackend.GetSubfieldForKohaField('biblio.biblionumber');
452         $.each( data.hits, function( undef, hit ) {
453             backends.search.records[ hit.server + ':' + hit.index ] = hit.record;
455             switch ( hit.server ) {
456                 case 'koha:biblioserver':
457                     var bibnumField = hit.record.field( bibnumMap[0] );
459                     if ( bibnumField && bibnumField.hasSubfield( bibnumMap[1] ) ) {
460                         hit.id = 'catalog/' + bibnumField.subfield( bibnumMap[1] );
461                         break;
462                     }
464                     // Otherwise, fallthrough
466                 default:
467                     hit.id = 'search/' + hit.server + ':' + hit.index;
468             }
470             var result = '<tr>';
471             result += '<td class="sourcecol">' + z3950Servers[ hit.server ].name + '</td>';
473             $.each( z3950Labels, function( undef, label ) {
474                 if ( !seenColumns[ label[0] ] ) return;
476                 if ( hit.metadata[ label[0] ] ) {
477                     result += '<td class="infocol">' + hit.metadata[ label[0] ] + '</td>';
478                 } else {
479                     result += '<td class="infocol">&nbsp;</td>';
480                 }
481             } );
483             result += '<td class="toolscol"><ul><li><a href="#" class="marc-link">' + _("View MARC") + '</a></li>';
484             result += '<li><a href="#" class="open-link">' + ( hit.server == 'koha:biblioserver' ? _("Edit") : _("Import") ) + '</a></li>';
485             if ( state.canSave ) result += '<li><a href="#" class="substitute-link" title="' + _("Replace the current record's contents") + '">' + _("Substitute") + '</a></li>';
486             result += '</ul></td></tr>';
488             var $tr = $( result );
489             $tr.find( '.marc-link' ).click( function() {
490                 var $info_columns = $tr.find( '.infocol' );
491                 var $marc_column = $tr.find( '.marccol' );
493                 if ( !$marc_column.length ) {
494                     $marc_column = $( '<td class="marccol" colspan="' + $info_columns.length + '"></td>' ).insertAfter( $info_columns.eq(-1) ).hide();
495                     CodeMirror.runMode( TextMARC.RecordToText( hit.record ), 'marc', $marc_column[0] );
496                 }
498                 if ( $marc_column.is(':visible') ) {
499                     $tr.find('.marc-link').text( _("View MARC") );
500                     $info_columns.show();
501                     $marc_column.hide();
502                 } else {
503                     $tr.find('.marc-link').text( _("Hide MARC") );
504                     $marc_column.show();
505                     $info_columns.hide();
506                 }
508                 return false;
509             } );
510             $tr.find( '.open-link' ).click( function() {
511                 $( '#search-results-ui' ).modal('hide');
512                 openRecord( hit.id, editor );
514                 return false;
515             } );
516             $tr.find( '.substitute-link' ).click( function() {
517                 $( '#search-results-ui' ).modal('hide');
518                 loadRecord( hit.id, editor );
520                 return false;
521             } );
522             $('#searchresults tbody').append( $tr );
523         } );
525         var pages = [];
526         var cur_page = data.offset / data.page_size;
527         var max_page = Math.ceil( data.total_fetched / data.page_size ) - 1;
529         if ( cur_page != 0 ) {
530             pages.push( '<li><a class="search-nav" href="#" data-offset="' + (data.offset - data.page_size) + '"><span aria-hidden="true">&laquo;</span> ' + _("Previous") + '</a></li>' );
531         }
533         for ( var page = Math.max( 0, cur_page - 9 ); page <= Math.min( max_page, cur_page + 9 ); page++ ) {
534             if ( page == cur_page ) {
535                 pages.push( ' <li class="active"><a href="#">' + ( page + 1 ) + '</a></li>' );
536             } else {
537                 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + ( page * data.page_size ) + '">' + ( page + 1 ) + '</a></li>' );
538             }
539         }
541         if ( cur_page < max_page ) {
542             pages.push( ' <li><a class="search-nav" href="#" data-offset="' + (data.offset + data.page_size) + '">' + _("Next") + ' <span aria-hidden="true">&raquo;</span></a></li>' );
543         }
545         $( '#search-top-pages, #search-bottom-pages' ).find( 'nav' ).html( pages.length > 1 ? ( '<ul class="pagination pagination-sm">' + pages.join( '' ) + '</ul>' ) : '' );
547         var $overlay = $('#search-overlay');
548         $overlay.find('span').text(_("Loading"));
549         $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
551         if ( data.activeclients ) {
552             $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
553             $overlay.show();
554         } else {
555             $overlay.find('.bar').css( { display: 'block', width: '100%' } );
556             $overlay.fadeOut();
557             $('#searchresults')[0].focus();
558         }
559     }
561     function invalidateSearchResults() {
562         var $overlay = $('#search-overlay');
563         $overlay.find('span').text(_("Search expired, please try again"));
564         $overlay.find('.bar').css( { display: 'none' } );
565         $overlay.show();
566     }
568     function handleSearchError(error) {
569         if (error.code == 1) {
570             invalidateSearchResults();
571             Search.Reconnect();
572         } else {
573             humanMsg.displayMsg( '<h3>' + _("Internal search error") + '</h3><p>' + error + '</p><p>' + _("Please refresh the page and try again.") + '</p>', { className: 'humanError' } );
574         }
575     }
577     function handleSearchInitError(error) {
578         $('#quicksearch-overlay').fadeIn().find('p').text(error);
579     }
581     // Preference functions
582     function showPreference( pref ) {
583         var value = Preferences.user[pref];
585         switch (pref) {
586             case 'fieldWidgets':
587                 $( '#set-field-widgets' ).text( value ? _("Show fields verbatim") : _("Show helpers for fixed and coded fields") );
588                 break;
589             case 'font':
590                 $( '#editor .CodeMirror' ).css( { fontFamily: value } );
591                 editor.refresh();
592                 break;
593             case 'fontSize':
594                 $( '#editor .CodeMirror' ).css( { fontSize: value } );
595                 editor.refresh();
596                 break;
597             case 'macros':
598                 // Macros loaded on first show of modal
599                 break;
600             case 'selected_search_targets':
601                 $.each( z3950Servers, function( server_id, server ) {
602                     var saved_val = Preferences.user.selected_search_targets[server_id];
604                     if ( saved_val != null ) server.checked = saved_val;
605                 } );
606                 break;
607         }
608     }
610     function bindPreference( editor, pref ) {
611         function _addHandler( sel, event, handler ) {
612             $( sel ).on( event, function (e) {
613                 e.preventDefault();
614                 handler( e, Preferences.user[pref] );
615                 Preferences.Save( [% USER_INFO.borrowernumber %] );
616                 showPreference(pref);
617             } );
618         }
620         switch (pref) {
621             case 'fieldWidgets':
622                 _addHandler( '#set-field-widgets', 'click', function( e, oldValue ) {
623                     editor.setUseWidgets( Preferences.user.fieldWidgets = !Preferences.user.fieldWidgets );
624                 } );
625                 break;
626             case 'font':
627                 _addHandler( '#prefs-menu .set-font', 'click', function( e, oldValue ) {
628                     Preferences.user.font = $( e.target ).css( 'font-family' );
629                 } );
630                 break;
631             case 'fontSize':
632                 _addHandler( '#prefs-menu .set-fontSize', 'click', function( e, oldValue ) {
633                     Preferences.user.fontSize = $( e.target ).css( 'font-size' );
634                 } );
635                 break;
636             case 'selected_search_targets':
637                 $( document ).on( 'change', 'input.search-toggle-server', function() {
638                     var server_id = $( this ).closest('li').data('server-id');
639                     Preferences.user.selected_search_targets[server_id] = this.checked;
640                     Preferences.Save( [% USER_INFO.borrowernumber %] );
641                 } );
642                 break;
643         }
644     }
646     function displayPreferences( editor ) {
647         $.each( Preferences.user, function( pref, value ) {
648             showPreference( pref );
649             bindPreference( editor, pref );
650         } );
651     }
653     //> Macro functions
654     function loadMacro( name ) {
655         $( '#macro-list li' ).removeClass( 'active' );
656         macroEditor.activeMacro = name;
658         if ( !name ) {
659             macroEditor.setValue( '' );
660             return;
661         }
663         $( '#macro-list li[data-name="' + name + '"]' ).addClass( 'active' );
664         var macro = Preferences.user.macros[name];
665         macroEditor.setValue( macro.contents );
666         macroEditor.setOption( 'readOnly', false );
667         $( '#macro-format' ).val( macro.format || 'its' );
668         if ( macro.history ) macroEditor.setHistory( macro.history );
669     }
671     function storeMacro( name, macro ) {
672         if ( macro ) {
673             Preferences.user.macros[name] = macro;
674         } else {
675             delete Preferences.user.macros[name];
676         }
678         Preferences.Save( [% USER_INFO.borrowernumber %] );
679     }
681     function showSavedMacros( macros ) {
682         var scrollTop = $('#macro-list').scrollTop();
683         $( '#macro-list' ).empty();
684         var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
685             return $.extend( { name: name }, macro );
686         } );
687         macro_list.sort( function( a, b ) {
688             return a.name.localeCompare(b.name);
689         } );
690         $.each( macro_list, function( undef, macro ) {
691             var $li = $( '<li data-name="' + macro.name + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
692             $li.click( function() {
693                 loadMacro(macro.name);
694                 return false;
695             } );
696             if ( macro.name == macroEditor.activeMacro ) $li.addClass( 'active' );
697             var modified = macro.modified && new Date(macro.modified);
698             $li.find( '.macro-info' ).append(
699                 '<li><span class="label">' + _("Last changed:") + '</span>' +
700                 ( modified ? ( modified.toLocaleDateString() + ', ' + modified.toLocaleTimeString() ) : _("never") ) + '</li>'
701             );
702             $('#macro-list').append($li);
703         } );
704         var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
705         $new_li.click( function() {
706             // TODO: make this a bit less retro
707             var name = prompt(_("Please enter the name for the new macro:"));
708             if (!name) return;
710             if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
711             showSavedMacros();
712             loadMacro( name );
713         } );
714         $('#macro-list').append($new_li);
715         $('#macro-list').scrollTop(scrollTop);
716     }
718     function saveMacro() {
719         var name = macroEditor.activeMacro;
721         if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() ) return;
723         macroEditor.savedGeneration = macroEditor.changeGeneration();
724         storeMacro( name, { contents: macroEditor.getValue(), modified: (new Date()).valueOf(), history: macroEditor.getHistory(), format: $('#macro-format').val() } );
725         $('#macro-save-message').text(_("Saved"));
726         showSavedMacros();
727     }
729     $(document).ready( function() {
730         // Editor setup
731         editor = new MARCEditor( {
732             onCursorActivity: function() {
733                 $('#status-tag-info').empty();
734                 $('#status-subfield-info').empty();
736                 var field = editor.getCurrentField();
737                 var cur = editor.getCursor();
739                 if ( !field ) return;
741                 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
742                 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
744                 if ( taginfo ) {
745                     $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> '  + taginfo.lib );
747                     var subfield = field.getSubfieldAt( cur.ch );
748                     if ( !subfield ) return;
750                     var subfieldinfo = taginfo.subfields[ subfield.code ];
751                     $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
753                     if ( subfieldinfo ) {
754                         $('#status-subfield-info').append( subfieldinfo.lib );
755                     } else {
756                         $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
757                     }
758                 } else {
759                     $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
760                 }
761             },
762             position: function (elt) { $(elt).insertAfter('#toolbar') },
763         } );
765         // Automatically detect resizes and change the height of the editor and position of modals.
766         var resizeTimer = null;
767         function onResize() {
768             if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
769                 resizeTimer = null;
771                 var pos = $('#editor .CodeMirror').position();
772                 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
774                 $('.modal-body').each( function() {
775                     $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
776                 } );
777             }, 100);
778         }
780         $( '#macro-ui' ).on( 'shown.bs.modal', function() {
781             if ( macroEditor ) return;
783             macroEditor = CodeMirror(
784                 $('#macro-editor')[0],
785                 {
786                     extraKeys: {
787                         'Ctrl-D': function( cm ) {
788                             var cur = cm.getCursor();
790                             cm.replaceRange( "‡", cur, null );
791                         },
792                     },
793                     mode: 'null',
794                     lineNumbers: true,
795                     readOnly: true,
796                 }
797             );
798             var saveTimeout;
799             macroEditor.on( 'change', function( cm, change ) {
800                 $('#macro-save-message').empty();
801                 if ( change.origin == 'setValue' ) return;
803                 if ( saveTimeout ) clearTimeout( saveTimeout );
804                 saveTimeout = setTimeout( function() {
805                     saveMacro();
807                     saveTimeout = null;
808                 }, 500 );
809             } );
811             showSavedMacros();
812         } );
814         var saveableBackends = [];
815         $.each( backends, function( id, backend ) {
816             if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
817         } );
818         saveableBackends.sort();
819         $.each( saveableBackends, function( undef, backend ) {
820             $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
821         } );
823         var macro_format_list = $.map( Macros.formats, function( format, name ) {
824             return $.extend( { name: name }, format );
825         } );
826         macro_format_list.sort( function( a, b ) {
827             return a.description.localeCompare(b.description);
828         } );
829         $.each( macro_format_list, function() {
830             $('#macro-format').append( '<option value="' + this.name + '">' + this.description + '</option>' );
831         } );
833         // Click bindings
834         $( '#save-record, #save-dropdown a' ).click( function() {
835             $( '#save-record' ).find('i').attr( 'class', 'fa fa-spinner' ).siblings( 'span' ).text( _("Saving...") );
837             function finishCb(result) {
838                 if ( result.error == 'syntax' ) {
839                     humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
840                 } else if ( result.error == 'invalid' ) {
841                     humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
842                 } else if ( !result.error ) {
843                     humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
844                 }
846                 $.each( result.errors || [], function( undef, error ) {
847                     switch ( error.type ) {
848                         case 'noTag':
849                             editor.addError( error.line, _("Invalid tag number") );
850                             break;
851                         case 'noIndicators':
852                             editor.addError( error.line, _("Invalid indicators") );
853                             break;
854                         case 'noSubfields':
855                             editor.addError( error.line, _("Tag has no subfields") );
856                             break;
857                         case 'missingTag':
858                             editor.addError( null, _("Missing mandatory tag: ") + error.tag );
859                             break;
860                         case 'missingSubfield':
861                             if ( error.subfield == '@' ) {
862                                 editor.addError( error.line, _("Missing control field contents") );
863                             } else {
864                                 editor.addError( error.line, _("Missing mandatory subfield: â€¡") + error.subfield );
865                             }
866                             break;
867                         case 'unrepeatableTag':
868                             editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
869                             break;
870                         case 'unrepeatableSubfield':
871                             editor.addError( error.line, _("Subfield â€¡") + error.subfield + _(" cannot be repeated") );
872                             break;
873                         case 'itemTagUnsupported':
874                             editor.addError( error.line, _("Item tags cannot currently be saved") );
875                             break;
876                     }
877                 } );
879                 $( '#save-record' ).find('i').attr( 'class', 'fa fa-hdd-o' );
881                 if ( result.error ) {
882                     // Reset backend info
883                     setSource( [ state.backend, state.recordID ] );
884                 }
885             }
887             var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
888             if ( state.backend == backend ) {
889                 saveRecord( backend + '/' + state.recordID, editor, finishCb );
890             } else {
891                 saveRecord( backend + '/', editor, finishCb );
892             }
894             return false;
895         } );
897         $('#import-records').click( function() {
898             $('#import-records-input')
899                 .off('change')
900                 .change( function() {
901                     if ( !this.files || !this.files.length ) return;
903                     var file = this.files[0];
904                     var reader = new FileReader();
906                     reader.onload = function() {
907                         var record = new MARC.Record();
909                         if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
910                             record.loadISO2709( reader.result );
911                         } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
912                             record.loadMARCXML( reader.result );
913                         } else {
914                             humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
915                             return;
916                         }
918                         if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
920                         editor.displayRecord( record );
921                     };
923                     reader.readAsText( file );
924                 } )
925                 .click();
927             return false;
928         } );
930         $('#open-macros').click( function() {
931             $('#macro-ui').modal('show');
933             return false;
934         } );
936         $('#run-macro').click( function() {
937             var result = Macros.Run( editor, $('#macro-format').val(), macroEditor.getValue() );
939             if ( !result.errors.length ) {
940                 $('#macro-ui').modal('hide');
941                 editor.focus(); //Return cursor to editor after macro run
942                 return false;
943             }
945             var errors = [];
946             $.each( result.errors, function() {
947                 var error = '<b>' + _("Line ") + (this.line + 1) + ':</b> ';
949                 switch ( this.error ) {
950                     case 'failed': error += _("failed to run"); break;
951                     case 'unrecognized': error += _("unrecognized command"); break;
952                 }
954                 errors.push(error);
955             } );
957             humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
959             return false;
960         } );
962         $('#delete-macro').click( function() {
963             if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
965             storeMacro( macroEditor.activeMacro, undefined );
966             showSavedMacros();
967             loadMacro( undefined );
969             return false;
970         } );
972         $( '#switch-editor' ).click( function() {
973             if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
975             $.cookie( 'catalogue_editor_[% USER_INFO.borrowernumber %]', 'basic', { expires: 365, path: '/' } );
977             if ( state.backend == 'catalog' ) {
978                 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
979             } else if ( state.backend == 'new' ) {
980                 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
981             } else {
982                 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
983             }
984         } );
986         $( '#show-advanced-search' ).click( function() {
987             showAdvancedSearch();
989             return false;
990         } );
992         $('#advanced-search').submit( function() {
993             startAdvancedSearch();
995             return false;
996         } );
998         $( document ).on( 'click', 'a.search-nav', function() {
999             if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1000                 $("#search-overlay").show();
1001             }
1003             return false;
1004         });
1006         $( document ).on( 'click', 'th[data-sort-label]', function() {
1007             var direction;
1009             if ( $( this ).hasClass( 'sorting_asc' ) ) {
1010                 direction = 'desc';
1011             } else {
1012                 direction = 'asc';
1013             }
1015             if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1016                 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1018                 $("#search-overlay").show();
1019             }
1021             return false;
1022         });
1024         $( document ).on( 'change', 'input.search-toggle-server', function() {
1025             var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1026             server.checked = this.checked;
1028             if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1029                 $("#search-overlay").show();
1030             }
1031         } );
1033         // Key bindings
1034         bindGlobalKeys();
1036         // Setup UI
1037         $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1038             $(this).modal({ show: false });
1039         } );
1041         var $quicksearch = $('#quicksearch fieldset');
1042         $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1043             position: 'absolute',
1044             top: $quicksearch.offset().top,
1045             left: $quicksearch.offset().left,
1046             height: $quicksearch.outerHeight(),
1047             width: $quicksearch.outerWidth(),
1048         }).appendTo(document.body).hide();
1050         var prevAlerts = [];
1051         humanMsg.logMsg = function(msg, options) {
1052             $('#show-alerts').popover('hide');
1053             prevAlerts.unshift('<li>' + msg + '</li>');
1054             prevAlerts.splice(5, 999); // Truncate old messages
1055         };
1057         $('#show-alerts').popover({
1058             html: true,
1059             placement: 'bottom',
1060             content: function() {
1061                 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1062             },
1063         });
1065         $('#show-shortcuts').popover({
1066             html: true,
1067             placement: 'bottom',
1068             content: function() {
1069                 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1070             },
1071         });
1073         $('#new-record' ).click( function() {
1074             if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1076             openRecord( 'new/', editor );
1077             return false;
1078         } );
1080         // Start editor
1081         Preferences.Load( [% USER_INFO.borrowernumber || 0 %] );
1082         displayPreferences(editor);
1083         makeAuthorisedValueWidgets( '' );
1084         Search.Init( {
1085             page_size: 20,
1086             onresults: function(data) { showSearchResults( editor, data ) },
1087             onerror: handleSearchError,
1088         } );
1090         function finishCb( data ) {
1091             if ( data.error ) openRecord( 'new/', editor, finishCb );
1093             Resources.GetAll().done( function() {
1094                 $("#loading").hide();
1095                 $( window ).resize( onResize ).resize();
1096                 editor.focus();
1097             } );
1098         }
1100         if ( "[% auth_forwarded_hash %]" ) {
1101             document.location.hash = "[% auth_forwarded_hash %]";
1102         }
1104         if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1105             openRecord( 'new/', editor, finishCb );
1106         }
1107     } );
1108 } )();
1110 </script>