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