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