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