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