Bug 18762: Remove warnings from xt/author/valid-templates.t
[koha.git] / koha-tmpl / intranet-tmpl / prog / en / includes / cateditor-ui.inc
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>
17
18 [% IF marcflavour == 'MARC21' %]
19 [% PROCESS 'cateditor-widgets-marc21.inc' %]
20 [% ELSE %]
21 <script>var editorWidgets = {};</script>
22 [% END %]
23
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     };
40
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     ];
54
55     var state = {
56         backend: '',
57         saveBackend: 'catalog',
58         recordID: undefined
59     };
60
61     var editor;
62     var macroEditor;
63
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;
70
71                 var defaultvalue = subfield.defaultvalue || authvals[0].value;
72
73                 Widget.Register( tag + subfield, {
74                     init: function() {
75                         var $result = $( '<span class="subfield-widget"></span>' );
76
77                         return $result[0];
78                     },
79                     postCreate: function() {
80                         var value = defaultvalue;
81                         var widget = this;
82
83                         $.each( authvals, function() {
84                             if ( this.value == widget.text ) {
85                                 value = this.value;
86                             }
87                         } );
88
89                         this.setText( value );
90
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 );
97
98                         $node.change( $.proxy( function() {
99                             this.setText( $node.val() );
100                         }, this ) );
101                     },
102                     makeTemplate: function() {
103                         return defaultvalue;
104                     },
105                 } );
106             } );
107         } );
108     }
109
110     function bindGlobalKeys() {
111         shortcut.add( 'ctrl+s', function(event) {
112             $( '#save-record' ).click();
113
114             event.preventDefault();
115         } );
116
117         shortcut.add( 'alt+ctrl+k', function(event) {
118             $( '#search-by-keywords' ).focus();
119
120             return false;
121         } );
122
123         shortcut.add( 'alt+ctrl+a', function(event) {
124             $( '#search-by-author' ).focus();
125
126             return false;
127         } );
128
129         shortcut.add( 'alt+ctrl+i', function(event) {
130             $( '#search-by-isbn' ).focus();
131
132             return false;
133         } );
134
135         shortcut.add( 'alt+ctrl+t', function(event) {
136             $( '#search-by-title' ).focus();
137
138             return false;
139         } );
140
141         shortcut.add( 'ctrl+h', function() {
142             var field = editor.getCurrentField();
143
144             if ( !field ) return;
145
146             window.open( getFieldHelpURL( field.tag ) );
147         } );
148
149         $('#quicksearch .search-box').each( function() {
150             shortcut.add( 'enter', $.proxy( function() {
151                 var terms = [];
152
153                 $('#quicksearch .search-box').each( function() {
154                     if ( !this.value ) return;
155
156                     terms.push( [ $(this).data('qualifier'), this.value ] );
157                 } );
158
159                 if ( !terms.length ) return;
160
161                 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
162                     $("#search-overlay").show();
163                     showResultsBox();
164                 }
165
166                 return false;
167             }, this), { target: this, type: 'keypress' } );
168         } );
169     }
170
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;
195
196                 return url;
197             }
198         [% END %]
199     }
200
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 );
208
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 );
217
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;
230
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                 }
237
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' );
249
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' );
257
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                 }
269
270                 callback( backends.search.records[ id ] );
271             },
272             records: {},
273         },
274     };
275
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';
281
282         var backend = backends[state.backend];
283
284         document.location.hash = '#' + parts[0] + '/' + parts[1];
285
286         $('#title').text( backend.titleForRecord.replace( '{ID}', parts[1] ) );
287
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     }
294
295     function saveRecord( recid, editor, callback ) {
296         var parts = recid.split('/');
297         if ( parts.length != 2 ) return false;
298
299         if ( !backends[ parts[0] ] || !backends[ parts[0] ].save ) return false;
300
301         editor.removeErrors();
302         var record = editor.getRecord();
303
304         if ( record.errors ) {
305             state.saving = false;
306             callback( { error: 'syntax', errors: record.errors } );
307             return;
308         }
309
310         var errors = KohaBackend.ValidateRecord( '', record );
311         if ( errors.length ) {
312             state.saving = false;
313             callback( { error: 'invalid', errors: errors } );
314             return;
315         }
316
317         backends[ parts[0] ].save( parts[1], record, function(data) {
318             state.saving = false;
319
320             if (data.newRecord) {
321                 var record = new MARC.Record();
322                 record.loadMARCXML(data.newRecord);
323                 editor.displayRecord( record );
324             }
325
326             if (data.newId) {
327                 setSource(data.newId);
328             } else {
329                 setSource( [ state.backend, state.recordID ] );
330             }
331
332             if (callback) callback( data );
333         } );
334     }
335
336     function loadRecord( recid, editor, callback ) {
337         var parts = recid.split('/');
338         if ( parts.length != 2 ) return false;
339
340         if ( !backends[ parts[0] ] || !backends[ parts[0] ].get ) return false;
341
342         backends[ parts[0] ].get( parts[1], function( record ) {
343             if ( !record.error ) {
344                 editor.displayRecord( record );
345                 editor.focus();
346             }
347
348             if (callback) callback(record);
349         } );
350
351         return true;
352     }
353
354     function openRecord( recid, editor, callback ) {
355         return loadRecord( recid, editor, function ( record ) {
356             setSource( recid.split('/') );
357
358             if (callback) callback( record );
359         } );
360     }
361
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     }
370
371     function startAdvancedSearch() {
372         var terms = [];
373
374         $('#advanced-search-ui .search-box').each( function() {
375             if ( !this.value ) return;
376
377             terms.push( [ $(this).data('qualifier'), this.value ] );
378         } );
379
380         if ( !terms.length ) return;
381
382         if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
383             $('#advanced-search-ui').modal('hide');
384             $("#search-overlay").show();
385             showResultsBox();
386         }
387     }
388
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     }
396
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' );
400
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     }
409
410     function showSearchResults( editor, data ) {
411         backends.search.records = {};
412
413         $('#searchresults thead tr').empty();
414         $('#searchresults tbody').empty();
415         $('#search-serversinfo').empty();
416
417         $.each( z3950Servers, function( server_id, server ) {
418             var num_fetched = data.num_fetched[server_id];
419
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             }
427
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         } );
430
431         var seenColumns = {};
432
433         $.each( data.hits, function( undef, hit ) {
434             $.each( hit.metadata, function(key) {
435                 seenColumns[key] = true;
436             } );
437         } );
438
439         $('#searchresults thead tr').append('<th>' + _("Source") + '</th>');
440
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         } );
446
447         showSearchSorting( data.sort_key, data.sort_direction );
448
449         $('#searchresults thead tr').append('<th>' + _("Tools") + '</th>');
450
451         var bibnumMap = KohaBackend.GetSubfieldForKohaField('biblio.biblionumber');
452         $.each( data.hits, function( undef, hit ) {
453             backends.search.records[ hit.server + ':' + hit.index ] = hit.record;
454
455             switch ( hit.server ) {
456                 case 'koha:biblioserver':
457                     var bibnumField = hit.record.field( bibnumMap[0] );
458
459                     if ( bibnumField && bibnumField.hasSubfield( bibnumMap[1] ) ) {
460                         hit.id = 'catalog/' + bibnumField.subfield( bibnumMap[1] );
461                         break;
462                     }
463
464                     // Otherwise, fallthrough
465
466                 default:
467                     hit.id = 'search/' + hit.server + ':' + hit.index;
468             }
469
470             var result = '<tr>';
471             result += '<td class="sourcecol">' + z3950Servers[ hit.server ].name + '</td>';
472
473             $.each( z3950Labels, function( undef, label ) {
474                 if ( !seenColumns[ label[0] ] ) return;
475
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             } );
482
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>';
487
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' );
492
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                 }
497
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                 }
507
508                 return false;
509             } );
510             $tr.find( '.open-link' ).click( function() {
511                 $( '#search-results-ui' ).modal('hide');
512                 openRecord( hit.id, editor );
513
514                 return false;
515             } );
516             $tr.find( '.substitute-link' ).click( function() {
517                 $( '#search-results-ui' ).modal('hide');
518                 loadRecord( hit.id, editor );
519
520                 return false;
521             } );
522             $('#searchresults tbody').append( $tr );
523         } );
524
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;
528
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         }
532
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         }
540
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         }
544
545         $( '#search-top-pages, #search-bottom-pages' ).find( 'nav' ).html( pages.length > 1 ? ( '<ul class="pagination pagination-sm">' + pages.join( '' ) + '</ul>' ) : '' );
546
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 ) + '%' } );
550
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     }
560
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     }
567
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     }
576
577     function handleSearchInitError(error) {
578         $('#quicksearch-overlay').fadeIn().find('p').text(error);
579     }
580
581     // Preference functions
582     function showPreference( pref ) {
583         var value = Preferences.user[pref];
584
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];
603
604                     if ( saved_val != null ) server.checked = saved_val;
605                 } );
606                 break;
607         }
608     }
609
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         }
619
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     }
645
646     function displayPreferences( editor ) {
647         $.each( Preferences.user, function( pref, value ) {
648             showPreference( pref );
649             bindPreference( editor, pref );
650         } );
651     }
652
653     //> Macro functions
654     function loadMacro( name ) {
655         $( '#macro-list li' ).removeClass( 'active' );
656         macroEditor.activeMacro = name;
657
658         if ( !name ) {
659             macroEditor.setValue( '' );
660             return;
661         }
662
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     }
670
671     function storeMacro( name, macro ) {
672         if ( macro ) {
673             Preferences.user.macros[name] = macro;
674         } else {
675             delete Preferences.user.macros[name];
676         }
677
678         Preferences.Save( [% USER_INFO.borrowernumber %] );
679     }
680
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;
709
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     }
717
718     function saveMacro() {
719         var name = macroEditor.activeMacro;
720
721         if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() ) return;
722
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     }
728
729     $(document).ready( function() {
730         // Editor setup
731         editor = new MARCEditor( {
732             onCursorActivity: function() {
733                 $('#status-tag-info').empty();
734                 $('#status-subfield-info').empty();
735
736                 var field = editor.getCurrentField();
737                 var cur = editor.getCursor();
738
739                 if ( !field ) return;
740
741                 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
742                 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
743
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 );
746
747                     var subfield = field.getSubfieldAt( cur.ch );
748                     if ( !subfield ) return;
749
750                     var subfieldinfo = taginfo.subfields[ subfield.code ];
751                     $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
752
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         } );
764
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;
770
771                 var pos = $('#editor .CodeMirror').position();
772                 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
773
774                 $('.modal-body').each( function() {
775                     $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
776                 } );
777             }, 100);
778         }
779
780         $( '#macro-ui' ).on( 'shown.bs.modal', function() {
781             if ( macroEditor ) return;
782
783             macroEditor = CodeMirror(
784                 $('#macro-editor')[0],
785                 {
786                     extraKeys: {
787                         'Ctrl-D': function( cm ) {
788                             var cur = cm.getCursor();
789
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;
802
803                 if ( saveTimeout ) clearTimeout( saveTimeout );
804                 saveTimeout = setTimeout( function() {
805                     saveMacro();
806
807                     saveTimeout = null;
808                 }, 500 );
809             } );
810
811             showSavedMacros();
812         } );
813
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         } );
822
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         } );
832
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...") );
836
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                 }
845
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                 } );
878
879                 $( '#save-record' ).find('i').attr( 'class', 'fa fa-hdd-o' );
880
881                 if ( result.error ) {
882                     // Reset backend info
883                     setSource( [ state.backend, state.recordID ] );
884                 }
885             }
886
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             }
893
894             return false;
895         } );
896
897         $('#import-records').click( function() {
898             $('#import-records-input')
899                 .off('change')
900                 .change( function() {
901                     if ( !this.files || !this.files.length ) return;
902
903                     var file = this.files[0];
904                     var reader = new FileReader();
905
906                     reader.onload = function() {
907                         var record = new MARC.Record();
908
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                         }
917
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' } );
919
920                         editor.displayRecord( record );
921                     };
922
923                     reader.readAsText( file );
924                 } )
925                 .click();
926
927             return false;
928         } );
929
930         $('#open-macros').click( function() {
931             $('#macro-ui').modal('show');
932
933             return false;
934         } );
935
936         $('#run-macro').click( function() {
937             var result = Macros.Run( editor, $('#macro-format').val(), macroEditor.getValue() );
938
939             if ( !result.errors.length ) {
940                 $('#macro-ui').modal('hide');
941                 editor.focus(); //Return cursor to editor after macro run
942                 return false;
943             }
944
945             var errors = [];
946             $.each( result.errors, function() {
947                 var error = '<b>' + _("Line ") + (this.line + 1) + ':</b> ';
948
949                 switch ( this.error ) {
950                     case 'failed': error += _("failed to run"); break;
951                     case 'unrecognized': error += _("unrecognized command"); break;
952                 }
953
954                 errors.push(error);
955             } );
956
957             humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
958
959             return false;
960         } );
961
962         $('#delete-macro').click( function() {
963             if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
964
965             storeMacro( macroEditor.activeMacro, undefined );
966             showSavedMacros();
967             loadMacro( undefined );
968
969             return false;
970         } );
971
972         $( '#switch-editor' ).click( function() {
973             if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
974
975             $.cookie( 'catalogue_editor_[% USER_INFO.borrowernumber %]', 'basic', { expires: 365, path: '/' } );
976
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         } );
985
986         $( '#show-advanced-search' ).click( function() {
987             showAdvancedSearch();
988
989             return false;
990         } );
991
992         $('#advanced-search').submit( function() {
993             startAdvancedSearch();
994
995             return false;
996         } );
997
998         $( document ).on( 'click', 'a.search-nav', function() {
999             if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1000                 $("#search-overlay").show();
1001             }
1002
1003             return false;
1004         });
1005
1006         $( document ).on( 'click', 'th[data-sort-label]', function() {
1007             var direction;
1008
1009             if ( $( this ).hasClass( 'sorting_asc' ) ) {
1010                 direction = 'desc';
1011             } else {
1012                 direction = 'asc';
1013             }
1014
1015             if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1016                 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1017
1018                 $("#search-overlay").show();
1019             }
1020
1021             return false;
1022         });
1023
1024         $( document ).on( 'change', 'input.search-toggle-server', function() {
1025             var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1026             server.checked = this.checked;
1027
1028             if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1029                 $("#search-overlay").show();
1030             }
1031         } );
1032
1033         // Key bindings
1034         bindGlobalKeys();
1035
1036         // Setup UI
1037         $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1038             $(this).modal({ show: false });
1039         } );
1040
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();
1049
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         };
1056
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         });
1064
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         });
1072
1073         $('#new-record' ).click( function() {
1074             if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1075
1076             openRecord( 'new/', editor );
1077             return false;
1078         } );
1079
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         } );
1089
1090         function finishCb( data ) {
1091             if ( data.error ) openRecord( 'new/', editor, finishCb );
1092
1093             Resources.GetAll().done( function() {
1094                 $("#loading").hide();
1095                 $( window ).resize( onResize ).resize();
1096                 editor.focus();
1097             } );
1098         }
1099
1100         if ( "[% auth_forwarded_hash %]" ) {
1101             document.location.hash = "[% auth_forwarded_hash %]";
1102         }
1103
1104         if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1105             openRecord( 'new/', editor, finishCb );
1106         }
1107     } );
1108 } )();
1109
1110 </script>