Bug 31863: Account for changelanguage element when adjusting editor height
[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' %] false [% ELSE %] true [% 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, false, 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' %] false [% ELSE %] true [% 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                 if ( $('#changelanguage').length ) {
1024                     $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
1025                 } else {
1026                     $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 );
1027                 }
1028                 $('.modal-body').each( function() {
1029                     $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
1030                 } );
1031             }, 100);
1032         }
1033
1034         $( '#macro-ui' ).on( 'shown.bs.modal', function() {
1035             if ( macroEditor ) return;
1036
1037             macroEditor = CodeMirror(
1038                 $('#macro-editor')[0],
1039                 {
1040                     extraKeys: {
1041                         'Ctrl-D': function( cm ) {
1042                             var cur = cm.getCursor();
1043
1044                             cm.replaceRange( "‡", cur, null );
1045                         },
1046                     },
1047                     mode: 'null',
1048                     lineNumbers: true,
1049                     readOnly: true,
1050                 }
1051             );
1052             var saveTimeout;
1053             macroEditor.on( 'change', function( cm, change ) {
1054                 $('#macro-save-message').empty();
1055                 if ( change.origin == 'setValue' ) return;
1056
1057                 if ( saveTimeout ) clearTimeout( saveTimeout );
1058                 saveTimeout = setTimeout( function() {
1059                     saveMacro(macroEditor.activeMacroShared);
1060
1061                     saveTimeout = null;
1062                 }, 500 );
1063             } );
1064
1065             showSavedMacros();
1066         } );
1067
1068         var saveableBackends = [];
1069         $.each( backends, function( id, backend ) {
1070             if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
1071         } );
1072         saveableBackends.sort();
1073         $.each( saveableBackends, function( undef, backend ) {
1074             $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
1075         } );
1076
1077         // Click bindings
1078         $( '#save-record, #save-dropdown a' ).click( function() {
1079              $( '#save-record' ).find('i').attr( 'class', 'fa fa-spinner fa-spin' ).siblings( 'span' ).text( _("Saving...") );
1080
1081             function finishCb(result) {
1082                 if ( result.error == 'syntax' ) {
1083                     humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
1084                 } else if ( result.error == 'invalid' ) {
1085                     humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
1086                 } else if ( result.error ) {
1087                     humanMsg.displayAlert( _("Something went wrong, cannot save"), { className: 'humanError' } );
1088                 } else if ( !result.error ) {
1089                     humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
1090                 }
1091
1092                 $.each( result.errors || [], function( undef, error ) {
1093                     switch ( error.type ) {
1094                         case 'noTag':
1095                             editor.addError( error.line, _("Invalid tag number") );
1096                             break;
1097                         case 'noIndicators':
1098                             editor.addError( error.line, _("Invalid indicators") );
1099                             break;
1100                         case 'noSubfields':
1101                             editor.addError( error.line, _("Tag has no subfields") );
1102                             break;
1103                         case 'missingTag':
1104                             editor.addError( null, _("Missing mandatory tag: ") + error.tag );
1105                             break;
1106                         case 'missingSubfield':
1107                             if ( error.subfield == '@' ) {
1108                                 editor.addError( error.line, _("Missing control field contents") );
1109                             } else {
1110                                 editor.addError( error.line, _("Missing mandatory subfield: â€¡") + error.subfield );
1111                             }
1112                             break;
1113                         case 'unrepeatableTag':
1114                             editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
1115                             break;
1116                         case 'unrepeatableSubfield':
1117                             editor.addError( error.line, _("Subfield â€¡") + error.subfield + _(" cannot be repeated") );
1118                             break;
1119                         case 'itemTagUnsupported':
1120                             editor.addError( error.line, _("Item tags cannot currently be saved") );
1121                             break;
1122                     }
1123                 } );
1124
1125                 $( '#save-record' ).find('i').attr( 'class', 'fa fa-hdd-o' );
1126
1127                 if ( result.error ) {
1128                     // Reset backend info
1129                     setSource( [ state.backend, state.recordID ] );
1130                 }
1131             }
1132
1133             var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
1134             if ( state.backend == backend ) {
1135                 saveRecord( backend + '/' + state.recordID, editor, finishCb );
1136             } else {
1137                 saveRecord( backend + '/', editor, finishCb );
1138             }
1139
1140             return false;
1141         } );
1142
1143         $('#import-records').click( function() {
1144             $('#import-records-input')
1145                 .off('change')
1146                 .change( function() {
1147                     if ( !this.files || !this.files.length ) return;
1148
1149                     var file = this.files[0];
1150                     var reader = new FileReader();
1151
1152                     reader.onload = function() {
1153                         var record = new MARC.Record();
1154
1155                         if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
1156                             record.loadISO2709( reader.result );
1157                         } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
1158                             record.loadMARCXML( reader.result );
1159                         } else {
1160                             humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
1161                             return;
1162                         }
1163
1164                         if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
1165
1166                         editor.displayRecord( record );
1167                     };
1168
1169                     reader.readAsText( file );
1170                 } )
1171                 .click();
1172
1173             return false;
1174         } );
1175
1176         $('#open-macros').click( function() {
1177             $('#macro-ui').modal('show');
1178
1179             return false;
1180         } );
1181
1182         $('#run-macro').click( function() {
1183             var result = Macros.Run( editor, 'rancor', macroEditor.getValue() );
1184
1185             if ( !result.errors.length ) {
1186                 $('#macro-ui').modal('hide');
1187                 editor.focus(); //Return cursor to editor after macro run
1188                 return false;
1189             }
1190
1191             var errors = [];
1192             $.each( result.errors, function() {
1193                 var error = '<strong>' + _("Line ") + (this.line + 1) + ':</strong> ';
1194
1195                 switch ( this.error ) {
1196                     case 'failed': error += _("failed to run"); break;
1197                     case 'unrecognized': error += _("unrecognized command"); break;
1198                 }
1199
1200                 errors.push(error);
1201             } );
1202
1203             humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
1204
1205             return false;
1206         } );
1207
1208         $('#delete-macro').click( function() {
1209             if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
1210             deleteMacro();
1211             loadMacro( undefined );
1212
1213             return false;
1214         } );
1215
1216         $( '#switch-editor' ).click( function() {
1217             if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
1218
1219             Cookies.set( "catalogue_editor_[% logged_in_user.borrowernumber | html %]", "basic", { expires: 365, path: '/', sameSite: 'Lax'} );
1220
1221             if ( state.backend == 'catalog' ) {
1222                 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
1223             } else if ( state.backend == 'new' ) {
1224                 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
1225             } else {
1226                 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
1227             }
1228         } );
1229
1230         $( '#show-advanced-search' ).click( function() {
1231             showAdvancedSearch();
1232
1233             return false;
1234         } );
1235
1236         $('#advanced-search').submit( function() {
1237             startAdvancedSearch();
1238
1239             return false;
1240         } );
1241
1242         $( document ).on( 'click', 'a.search-nav', function() {
1243             if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1244                 $("#search-overlay").show();
1245             }
1246
1247             return false;
1248         });
1249
1250         $( document ).on( 'click', 'th[data-sort-label]', function() {
1251             var direction;
1252
1253             if ( $( this ).hasClass( 'sorting_asc' ) ) {
1254                 direction = 'desc';
1255             } else {
1256                 direction = 'asc';
1257             }
1258
1259             if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1260                 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1261
1262                 $("#search-overlay").show();
1263             }
1264
1265             return false;
1266         });
1267
1268         $( document ).on( 'change', 'input.search-toggle-server', function() {
1269             var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1270             server.checked = this.checked;
1271
1272             if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1273                 $("#search-overlay").show();
1274             }
1275         } );
1276
1277         // Key bindings
1278         bindGlobalKeys();
1279
1280         // Setup UI
1281         $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1282             $(this).modal({ show: false });
1283         } );
1284
1285         var $quicksearch = $('#quicksearch fieldset');
1286         $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1287             position: 'absolute',
1288             top: $quicksearch.offset().top,
1289             left: $quicksearch.offset().left,
1290             height: $quicksearch.outerHeight(),
1291             width: $quicksearch.outerWidth(),
1292         }).appendTo(document.body).hide();
1293
1294         var prevAlerts = [];
1295         humanMsg.logMsg = function(msg, options) {
1296             $('#show-alerts').popover('hide');
1297             prevAlerts.unshift('<li>' + msg + '</li>');
1298             prevAlerts.splice(5, 999); // Truncate old messages
1299         };
1300
1301         $('#show-alerts').popover({
1302             html: true,
1303             placement: 'bottom',
1304             content: function() {
1305                 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1306             },
1307         });
1308
1309         $('#show-shortcuts').popover({
1310             html: true,
1311             placement: 'bottom',
1312             content: function() {
1313                 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1314             },
1315         });
1316
1317         $('#new-record' ).click( function() {
1318             if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1319
1320             openRecord( 'new/', editor );
1321             return false;
1322         } );
1323
1324         window.onbeforeunload = function() {
1325             if(editor.modified )
1326                 { return 1; }
1327             else
1328                 { return undefined; }
1329         };
1330
1331         $('a.change-framework').click( function() {
1332             $("#loading").show();
1333             editor.setFrameworkCode(
1334                 $(this).data( 'frameworkcode' ),
1335                 true,
1336                 function ( error ) {
1337                     if ( typeof error !== 'undefined' ) {
1338                         humanMsg.displayAlert( _("Failed to change framework"), { className: 'humanError' } );
1339                     }
1340                     $('#loading').hide();
1341                 }
1342             );
1343         } );
1344
1345         // Start editor
1346         Preferences.Load( [% logged_in_user.borrowernumber || 0 | html %] );
1347         displayPreferences(editor);
1348         makeAuthorisedValueWidgets( '' );
1349         Search.Init( {
1350             page_size: 20,
1351             onresults: function(data) { showSearchResults( editor, data ) },
1352             onerror: handleSearchError,
1353         } );
1354
1355         function finishCb( data ) {
1356             if ( data.error ) {
1357                 humanMsg.displayAlert( data.error );
1358                 openRecord( 'new/', editor, finishCb );
1359             }
1360
1361             Resources.GetAll().done( function() {
1362                 $("#loading").hide();
1363                 $( window ).resize( onResize ).resize();
1364                 editor.focus();
1365             } );
1366         }
1367
1368         if ( "[% auth_forwarded_hash | html %]" ) {
1369             document.location.hash = "[% auth_forwarded_hash | html %]";
1370         }
1371
1372         if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1373             openRecord( 'new/', editor, finishCb );
1374         }
1375     } );
1376 } )();
1377
1378 </script>
1379 <!-- / cateditor-ui.inc -->