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