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