Bug 24193: Add CodeMirror linting of JavaScript, CSS, HTML, and YAML
[koha.git] / koha-tmpl / intranet-tmpl / prog / en / includes / cateditor-ui.inc
bloba37bd5f03f9baac0517811823c6cf843758c5418
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 <script>
12 [% FOREACH shortcut IN shortcuts -%]
13     var [% shortcut.shortcut_name | html %] = "[% shortcut.shortcut_keys | html %]";
14 [% END %]
15     var authInfo = {
16         [%- FOREACH authtag = authtags -%]
17             [% authtag.tagfield | html %]: {
18                 subfield: '[% authtag.tagsubfield | html %]',
19                 authtypecode: '[% authtag.authtypecode | html %]',
20                 },
21         [%- END -%]
22     };
23 require.config( {
24     baseUrl: '[% interface | html %]/lib/koha/cateditor/',
25     config: {
26         resources: {
27             marcflavour: '[% marcflavour | html %]',
28             themelang: '[% themelang | html %]',
29         },
30     },
31     waitSeconds: 30,
32 } );
33 </script>
35 [% IF marcflavour == 'MARC21' %]
36 [% PROCESS 'cateditor-widgets-marc21.inc' %]
37 [% ELSE %]
38 <script>var editorWidgets = {};</script>
39 [% END %]
41 <script>
42 require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'preferences', 'resources', 'text-marc', 'widget' ], function( KohaBackend, Search, Macros, MARCEditor, MARC, Preferences, Resources, TextMARC, Widget ) {
43     var z3950Servers = {
44         'koha:biblioserver': {
45             name: _("Local catalog"),
46             recordtype: 'biblio',
47             checked: false,
48         },
49         [%- FOREACH server = z3950_servers -%]
50             [% server.id | html %]: {
51                 name: '[% server.servername | html %]',
52                 recordtype: '[% server.recordtype | html %]',
53                 checked: [% server.checked ? 'true' : 'false' | html %],
54             },
55         [%- END -%]
56     };
58     // The columns that should show up in a search, in order, and keyed by the corresponding <metadata> tag in the XSL and Pazpar2 config
59     var z3950Labels = [
60         [ "local_number", _("Local number") ],
61         [ "title", _("Title") ],
62         [ "subtitle",_("Subtitle") ],
63         [ "series", _("Series title") ],
64         [ "author", _("Author") ],
65         [ "lccn", _("LCCN") ],
66         [ "isbn", _("ISBN") ],
67         [ "issn", _("ISSN") ],
68         [ "medium", _("Medium") ],
69         [ "edition", _("Edition") ],
70         [ "date", _("Published") ],
71         [ "notes", _("Notes") ],
72     ];
74     var state = {
75         backend: '',
76         saveBackend: 'catalog',
77         recordID: undefined
78     };
80     var editor;
81     var macroEditor;
83     function makeAuthorisedValueWidgets( frameworkCode ) {
84         $.each( KohaBackend.GetAllTagsInfo( frameworkCode ), function( tag, tagInfo ) {
85             $.each( tagInfo.subfields, function( subfield, subfieldInfo ) {
86                 if ( !subfieldInfo.authorised_value ) return;
87                 var authvals = KohaBackend.GetAuthorisedValues( subfieldInfo.authorised_value );
88                 if ( !authvals ) return;
90                 var defaultvalue = subfield.defaultvalue || authvals[0].value;
92                 Widget.Register( tag + subfield, {
93                     init: function() {
94                         var $result = $( '<span class="subfield-widget"></span>' );
96                         return $result[0];
97                     },
98                     postCreate: function() {
99                         var value = defaultvalue;
100                         var widget = this;
102                         $.each( authvals, function() {
103                             if ( this.value == widget.text ) {
104                                 value = this.value;
105                             }
106                         } );
108                         this.setText( value );
110                         $( '<select></select>' ).appendTo( this.node );
111                         var $node = $( this.node ).find( 'select' );
112                         $.each( authvals, function( undef, authval ) {
113                             $node.append( '<option value="' + authval.value + '"' + (authval.value == value ? ' selected="selected"' : '') + '>' + authval.lib + '</option>' );
114                         } );
115                         $node.val( this.text );
117                         $node.change( $.proxy( function() {
118                             this.setText( $node.val() );
119                         }, this ) );
120                     },
121                     makeTemplate: function() {
122                         return defaultvalue;
123                     },
124                 } );
125             } );
126         } );
127     }
129     function bindGlobalKeys() {
130         shortcut.add( 'ctrl+s', function(event) {
131             $( '#save-record' ).click();
133             event.preventDefault();
134         } );
136         shortcut.add( 'alt+ctrl+k', function(event) {
137             $( '#search-by-keywords' ).focus();
139             return false;
140         } );
142         shortcut.add( 'alt+ctrl+a', function(event) {
143             $( '#search-by-author' ).focus();
145             return false;
146         } );
148         shortcut.add( 'alt+ctrl+i', function(event) {
149             $( '#search-by-isbn' ).focus();
151             return false;
152         } );
154         shortcut.add( 'alt+ctrl+t', function(event) {
155             $( '#search-by-title' ).focus();
157             return false;
158         } );
160         shortcut.add( 'ctrl+h', function() {
161             var field = editor.getCurrentField();
163             if ( !field ) return;
165             window.open( getFieldHelpURL( field.tag ) );
166         } );
168         $('#quicksearch .search-box').each( function() {
169             shortcut.add( 'enter', $.proxy( function() {
170                 var terms = [];
172                 $('#quicksearch .search-box').each( function() {
173                     if ( !this.value ) return;
175                     terms.push( [ $(this).data('qualifier'), this.value ] );
176                 } );
178                 if ( !terms.length ) return;
180                 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
181                     $("#search-overlay").show();
182                     showResultsBox();
183                 }
185                 return false;
186             }, this), { target: this, type: 'keypress' } );
187         } );
188     }
190     function getFieldHelpURL( tag ) {
191         [% IF Koha.Preference('marcfielddocurl') %]
192             var docurl = "[% Koha.Preference('marcfielddocurl').replace('"','&quot;') | html %]";
193             docurl = docurl.replace("{MARC}", "[% marcflavour | html %]");
194             docurl = docurl.replace("{FIELD}", ""+tag);
195             docurl = docurl.replace("{LANG}", "[% lang | html %]");
196             return docurl;
197         [% ELSIF ( marcflavour == 'MARC21' ) %]
198             if ( tag == '000' ) {
199                 return "http://www.loc.gov/marc/bibliographic/bdleader.html";
200             } else if ( tag >= '090' && tag < '100' ) {
201                 return "http://www.loc.gov/marc/bibliographic/bd09x.html";
202             } else if ( tag < '900' ) {
203                 return "http://www.loc.gov/marc/bibliographic/bd" + tag + ".html";
204             } else {
205                 return "http://www.loc.gov/marc/bibliographic/bd9xx.html";
206             }
207         [% ELSIF ( marcflavour == 'UNIMARC' ) %]
208             /* http://archive.ifla.org/VI/3/p1996-1/ is an outdated version of UNIMARC, but
209                seems to be the only version available that can be linked to per tag.  More recent
210                versions of the UNIMARC standard are available on the IFLA website only as
211                PDFs!
212             */
213             if ( tag == '000' ) {
214                return  "http://archive.ifla.org/VI/3/p1996-1/uni.htm";
215             } else {
216                 var first = tag[0];
217                 var url = "http://archive.ifla.org/VI/3/p1996-1/uni" + first + ".htm#";
218                 if ( first == '0' ) url += "b";
219                 if ( first != '9' ) url += tag;
221                 return url;
222             }
223         [% END %]
224     }
226     // Record loading
227     var backends = {
228        'new': {
229             titleForRecord: _("Editing new record"),
230             get: function( id, callback ) {
231                 record = new MARC.Record();
232                 KohaBackend.FillRecord( '', record );
234                 callback( record );
235             },
236         },
237         'new-full': {
238             titleForRecord: _("Editing new full record"),
239             get: function( id, callback ) {
240                 record = new MARC.Record();
241                 KohaBackend.FillRecord( '', record, true );
243                 callback( record );
244             },
245         },
246         'duplicate': {
247             titleForRecord: _("Editing duplicate record of #{ID}"),
248             saveLabel: _("Duplicate"),
249             get: function( id, callback ) {
250                 if ( !id ) return false;
252                 KohaBackend.GetRecord( id, callback );
253             },
254             save: function( id, record, done ) {
255                 function finishCb( data ) {
256                     done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
257                 }
259                 KohaBackend.CreateRecord( record, finishCb );
260             }
261         },
262         'catalog': {
263             titleForRecord: _("Editing catalog record #{ID}"),
264             links: [
265                 { title: _("view"), href: "/cgi-bin/koha/catalogue/detail.pl?biblionumber={ID}" },
266                 { title: _("edit items"), href: "/cgi-bin/koha/cataloguing/additem.pl?biblionumber={ID}" },
267             ],
268             saveLabel: _("Save to catalog"),
269             get: function( id, callback ) {
270                 if ( !id ) return false;
272                 KohaBackend.GetRecord( id, callback );
273             },
274             save: function( id, record, done ) {
275                 function finishCb( data ) {
276                     done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
277                 }
279                 if ( id ) {
280                     KohaBackend.SaveRecord( id, record, finishCb );
281                 } else {
282                     KohaBackend.CreateRecord( record, finishCb );
283                 }
284             }
285         },
286         'iso2709': {
287             saveLabel: _("Save as MARC (.mrc) file"),
288             save: function( id, record, done ) {
289                 saveAs( new Blob( [record.toISO2709()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.mrc' );
291                 done( {} );
292             }
293         },
294         'marcxml': {
295             saveLabel: _("Save as MARCXML (.xml) file"),
296             save: function( id, record, done ) {
297                 saveAs( new Blob( [record.toXML()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.xml' );
299                 done( {} );
300             }
301         },
302         'search': {
303             titleForRecord: _("Editing search result"),
304             get: function( id, callback ) {
305                 if ( !id ) return false;
306                 if ( !backends.search.records[ id ] ) {
307                     callback( { error: _( "Invalid record" ) } );
308                     return false;
309                 }
311                 callback( backends.search.records[ id ] );
312             },
313             records: {},
314         },
315     };
317     function setSource(parts) {
318         state.backend = parts[0];
319         state.recordID = parts[1];
320         state.canSave = backends[ state.backend ].save != null;
321         state.saveBackend = state.canSave ? state.backend : 'catalog';
323         var backend = backends[state.backend];
325         document.location.hash = '#' + parts[0] + '/' + parts[1];
327         $('#title').text( backend.titleForRecord.replace( '{ID}', parts[1] ) );
329         $.each( backend.links || [], function( i, link ) {
330             $('#title').append(' <a target="_blank" href="' + link.href.replace( '{ID}', parts[1] ) + '">(' + link.title + ')</a>' );
331         } );
332         $( 'title', document.head ).html( _("Koha &rsaquo; Cataloging &rsaquo; ") + backend.titleForRecord.replace( '{ID}', parts[1] ) );
333         $('#save-record span').text( backends[ state.saveBackend ].saveLabel );
334     }
336     function saveRecord( recid, editor, callback ) {
337         var parts = recid.split('/');
338         if ( parts.length != 2 ) return false;
340         if ( !backends[ parts[0] ] || !backends[ parts[0] ].save ) return false;
342         editor.removeErrors();
343         var record = editor.getRecord();
345         if ( record.errors ) {
346             state.saving = false;
347             callback( { error: 'syntax', errors: record.errors } );
348             return;
349         }
351         var errors = KohaBackend.ValidateRecord( '', record );
352         if ( errors.length ) {
353             state.saving = false;
354             callback( { error: 'invalid', errors: errors } );
355             return;
356         }
358         backends[ parts[0] ].save( parts[1], record, function(data) {
359             state.saving = false;
361             if (data.newRecord) {
362                 var record = new MARC.Record();
363                 record.loadMARCXML(data.newRecord);
364                 record.frameworkcode = data.newRecord.frameworkcode;
365                 editor.displayRecord( record );
366             }
368             if (data.newId) {
369                 setSource(data.newId);
370             } else {
371                 setSource( [ state.backend, state.recordID ] );
372             }
374             if (callback) callback( data );
375         } );
376     }
378     function loadRecord( recid, editor, callback ) {
379         var parts = recid.split('/');
380         if ( parts.length != 2 ) return false;
382         if ( !backends[ parts[0] ] || !backends[ parts[0] ].get ) return false;
384         backends[ parts[0] ].get( parts[1], function( record ) {
385             if ( !record.error ) {
386                 editor.displayRecord( record );
387                 editor.focus();
388             }
390             if (callback) callback(record);
391         } );
393         return true;
394     }
396     function openRecord( recid, editor, callback ) {
397         return loadRecord( recid, editor, function ( record ) {
398             setSource( recid.split('/') );
400             if (callback) callback( record );
401         } );
402     }
404     // Search functions
405     function showAdvancedSearch() {
406         $('#advanced-search-servers').empty();
407         $.each( z3950Servers, function( server_id, server ) {
408             $('#advanced-search-servers').append( '<li data-server-id="' + server_id + '"><label><input class="search-toggle-server" type="checkbox"' + ( server.checked ? ' checked="checked">' : '>' ) + server.name + '</label></li>' );
409         } );
410         $('#advanced-search-ui').modal('show');
411     }
413     function startAdvancedSearch() {
414         var terms = [];
416         $('#advanced-search-ui .search-box').each( function() {
417             if ( !this.value ) return;
419             terms.push( [ $(this).data('qualifier'), this.value ] );
420         } );
422         if ( !terms.length ) return;
424         if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
425             $('#advanced-search-ui').modal('hide');
426             $("#search-overlay").show();
427             showResultsBox();
428         }
429     }
431     function showResultsBox(data) {
432         $('#search-top-pages, #search-bottom-pages').find('nav').empty();
433         $('#searchresults thead tr').empty();
434         $('#searchresults tbody').empty();
435         $('#search-serversinfo').empty().append('<li>' + _("Loading...") + '</li>');
436         $('#search-results-ui').modal('show');
437     }
439     function showSearchSorting( sort_key, sort_direction ) {
440         var $th = $('#searchresults thead tr th[data-sort-label="' + sort_key + '"]');
441         $th.parent().find( 'th[data-sort-label]' ).attr( 'class', 'sorting' );
443         if ( sort_direction == 'asc' ) {
444             direction = 'asc';
445             $th.attr( 'class', 'sorting_asc' );
446         } else {
447             direction = 'desc';
448             $th.attr( 'class', 'sorting_desc' );
449         }
450     }
452     function showSearchResults( editor, data ) {
453         backends.search.records = {};
455         $('#searchresults thead tr').empty();
456         $('#searchresults tbody').empty();
457         $('#search-serversinfo').empty();
459         $.each( z3950Servers, function( server_id, server ) {
460             var num_fetched = data.num_fetched[server_id];
462             if ( data.errors[server_id] ) {
463                 num_fetched = data.errors[server_id];
464             } else if ( num_fetched == null ) {
465                 num_fetched = '-';
466             } else if ( num_fetched < data.num_hits[server_id] ) {
467                 num_fetched += '+';
468             }
470             $('#search-serversinfo').append( '<li data-server-id="' + server_id + '"><label><input class="search-toggle-server" type="checkbox"' + ( server.checked ? ' checked="checked">' : '>' ) + server.name + ' (' + num_fetched + ')' + '</label></li>' );
471         } );
473         var seenColumns = {};
475         $.each( data.hits, function( undef, hit ) {
476             $.each( hit.metadata, function(key) {
477                 seenColumns[key] = true;
478             } );
479         } );
481         $('#searchresults thead tr').append('<th>' + _("Source") + '</th>');
483         $.each( z3950Labels, function( undef, label ) {
484             if ( seenColumns[ label[0] ] ) {
485                 $('#searchresults thead tr').append( '<th class="sorting" data-sort-label="' + label[0] + '">' + label[1] + '</th>' );
486             }
487         } );
489         showSearchSorting( data.sort_key, data.sort_direction );
491         $('#searchresults thead tr').append('<th>' + _("Tools") + '</th>');
493         var bibnumMap = KohaBackend.GetSubfieldForKohaField('biblio.biblionumber');
494         $.each( data.hits, function( undef, hit ) {
495             backends.search.records[ hit.server + ':' + hit.index ] = hit.record;
497             switch ( hit.server ) {
498                 case 'koha:biblioserver':
499                     var bibnumField = hit.record.field( bibnumMap[0] );
501                     if ( bibnumField && bibnumField.hasSubfield( bibnumMap[1] ) ) {
502                         hit.id = 'catalog/' + bibnumField.subfield( bibnumMap[1] );
503                         break;
504                     }
506                     // Otherwise, fallthrough
508                 default:
509                     hit.id = 'search/' + hit.server + ':' + hit.index;
510             }
512             var result = '<tr>';
513             result += '<td class="sourcecol">' + z3950Servers[ hit.server ].name + '</td>';
515             $.each( z3950Labels, function( undef, label ) {
516                 if ( !seenColumns[ label[0] ] ) return;
518                 if ( hit.metadata[ label[0] ] ) {
519                     result += '<td class="infocol">' + hit.metadata[ label[0] ] + '</td>';
520                 } else {
521                     result += '<td class="infocol">&nbsp;</td>';
522                 }
523             } );
525             result += '<td class="toolscol"><ul><li><a href="#" class="marc-link">' + _("View MARC") + '</a></li>';
526             result += '<li><a href="#" class="open-link">' + ( hit.server == 'koha:biblioserver' ? _("Edit") : _("Import") ) + '</a></li>';
527             if ( state.canSave ) result += '<li><a href="#" class="substitute-link" title="' + _("Replace the current record's contents") + '">' + _("Substitute") + '</a></li>';
528             result += '</ul></td></tr>';
530             var $tr = $( result );
531             $tr.find( '.marc-link' ).click( function() {
532                 var $info_columns = $tr.find( '.infocol' );
533                 var $marc_column = $tr.find( '.marccol' );
535                 if ( !$marc_column.length ) {
536                     $marc_column = $( '<td class="marccol" colspan="' + $info_columns.length + '"></td>' ).insertAfter( $info_columns.eq(-1) ).hide();
537                     CodeMirror.runMode( TextMARC.RecordToText( hit.record ), 'marc', $marc_column[0] );
538                 }
540                 if ( $marc_column.is(':visible') ) {
541                     $tr.find('.marc-link').text( _("View MARC") );
542                     $info_columns.show();
543                     $marc_column.hide();
544                 } else {
545                     $tr.find('.marc-link').text( _("Hide MARC") );
546                     $marc_column.show();
547                     $info_columns.hide();
548                 }
550                 return false;
551             } );
552             $tr.find( '.open-link' ).click( function() {
553                 $( '#search-results-ui' ).modal('hide');
554                 openRecord( hit.id, editor );
556                 return false;
557             } );
558             $tr.find( '.substitute-link' ).click( function() {
559                 $( '#search-results-ui' ).modal('hide');
560                 loadRecord( hit.id, editor );
562                 return false;
563             } );
564             $('#searchresults tbody').append( $tr );
565         } );
567         var pages = [];
568         var cur_page = data.offset / data.page_size;
569         var max_page = Math.ceil( data.total_fetched / data.page_size ) - 1;
571         if ( cur_page != 0 ) {
572             pages.push( '<li><a class="search-nav" href="#" data-offset="' + (data.offset - data.page_size) + '"><span aria-hidden="true">&laquo;</span> ' + _("Previous") + '</a></li>' );
573         }
575         for ( var page = Math.max( 0, cur_page - 9 ); page <= Math.min( max_page, cur_page + 9 ); page++ ) {
576             if ( page == cur_page ) {
577                 pages.push( ' <li class="active"><a href="#">' + ( page + 1 ) + '</a></li>' );
578             } else {
579                 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + ( page * data.page_size ) + '">' + ( page + 1 ) + '</a></li>' );
580             }
581         }
583         if ( cur_page < max_page ) {
584             pages.push( ' <li><a class="search-nav" href="#" data-offset="' + (data.offset + data.page_size) + '">' + _("Next") + ' <span aria-hidden="true">&raquo;</span></a></li>' );
585         }
587         $( '#search-top-pages, #search-bottom-pages' ).find( 'nav' ).html( pages.length > 1 ? ( '<ul class="pagination pagination-sm">' + pages.join( '' ) + '</ul>' ) : '' );
589         var $overlay = $('#search-overlay');
590         $overlay.find('span').text(_("Loading"));
591         $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
593         if ( data.activeclients ) {
594             $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
595             $overlay.show();
596         } else {
597             $overlay.find('.bar').css( { display: 'block', width: '100%' } );
598             $overlay.fadeOut();
599             $('#searchresults')[0].focus();
600         }
601     }
603     function invalidateSearchResults() {
604         var $overlay = $('#search-overlay');
605         $overlay.find('span').text(_("Search expired, please try again"));
606         $overlay.find('.bar').css( { display: 'none' } );
607         $overlay.show();
608     }
610     function handleSearchError(error) {
611         if (error.code == 1) {
612             invalidateSearchResults();
613             Search.Reconnect();
614         } else {
615             humanMsg.displayMsg( '<h3>' + _("Internal search error") + '</h3><p>' + error.responseText + '</p><p>' + _("Please refresh the page and try again.") + '</p>', { className: 'humanError' } );
616         }
617     }
619     function handleSearchInitError(error) {
620         $('#quicksearch-overlay').fadeIn().find('p').text(error);
621     }
623     // Preference functions
624     function showPreference( pref ) {
625         var value = Preferences.user[pref];
627         switch (pref) {
628             case 'fieldWidgets':
629                 $( '#set-field-widgets' ).text( value ? _("Show fields verbatim") : _("Show helpers for fixed and coded fields") );
630                 break;
631             case 'font':
632                 $( '#editor .CodeMirror' ).css( { fontFamily: value } );
633                 editor.refresh();
634                 break;
635             case 'fontSize':
636                 $( '#editor .CodeMirror' ).css( { fontSize: value } );
637                 editor.refresh();
638                 break;
639             case 'macros':
640                 // Macros loaded on first show of modal
641                 break;
642             case 'selected_search_targets':
643                 $.each( z3950Servers, function( server_id, server ) {
644                     var saved_val = Preferences.user.selected_search_targets[server_id];
646                     if ( saved_val != null ) server.checked = saved_val;
647                 } );
648                 break;
649         }
650     }
652     function bindPreference( editor, pref ) {
653         function _addHandler( sel, event, handler ) {
654             $( sel ).on( event, function (e) {
655                 e.preventDefault();
656                 handler( e, Preferences.user[pref] );
657                 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
658                 showPreference(pref);
659             } );
660         }
662         switch (pref) {
663             case 'fieldWidgets':
664                 _addHandler( '#set-field-widgets', 'click', function( e, oldValue ) {
665                     editor.setUseWidgets( Preferences.user.fieldWidgets = !Preferences.user.fieldWidgets );
666                 } );
667                 break;
668             case 'font':
669                 _addHandler( '#prefs-menu .set-font', 'click', function( e, oldValue ) {
670                     Preferences.user.font = $( e.target ).css( 'font-family' );
671                 } );
672                 break;
673             case 'fontSize':
674                 _addHandler( '#prefs-menu .set-fontSize', 'click', function( e, oldValue ) {
675                     Preferences.user.fontSize = $( e.target ).css( 'font-size' );
676                 } );
677                 break;
678             case 'selected_search_targets':
679                 $( document ).on( 'change', 'input.search-toggle-server', function() {
680                     var server_id = $( this ).closest('li').data('server-id');
681                     Preferences.user.selected_search_targets[server_id] = this.checked;
682                     Preferences.Save( [% logged_in_user.borrowernumber | html %] );
683                 } );
684                 break;
685         }
686     }
688     function displayPreferences( editor ) {
689         $.each( Preferences.user, function( pref, value ) {
690             showPreference( pref );
691             bindPreference( editor, pref );
692         } );
693     }
695     //> Macro functions
696     function loadMacro( name ) {
697         $( '#macro-list li' ).removeClass( 'active' );
698         macroEditor.activeMacro = name;
700         if ( !name ) {
701             macroEditor.setValue( '' );
702             return;
703         }
705         $( '#macro-list li[data-name="' + name + '"]' ).addClass( 'active' );
706         var macro = Preferences.user.macros[name];
707         macroEditor.setValue( macro.contents );
708         macroEditor.setOption( 'readOnly', false );
709         $( '#macro-format' ).val( macro.format || 'its' );
710         if ( macro.history ) macroEditor.setHistory( macro.history );
711     }
713     function storeMacro( name, macro ) {
714         if ( macro ) {
715             Preferences.user.macros[name] = macro;
716         } else {
717             delete Preferences.user.macros[name];
718         }
720         Preferences.Save( [% logged_in_user.borrowernumber | html %] );
721     }
723     function showSavedMacros( macros ) {
724         var scrollTop = $('#macro-list').scrollTop();
725         $( '#macro-list' ).empty();
726         var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
727             return $.extend( { name: name }, macro );
728         } );
729         macro_list.sort( function( a, b ) {
730             return a.name.localeCompare(b.name);
731         } );
732         $.each( macro_list, function( undef, macro ) {
733             var $li = $( '<li data-name="' + macro.name + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
734             $li.click( function() {
735                 loadMacro(macro.name);
736                 return false;
737             } );
738             if ( macro.name == macroEditor.activeMacro ) $li.addClass( 'active' );
739             var modified = macro.modified && new Date(macro.modified);
740             $li.find( '.macro-info' ).append(
741                 '<li><span class="label">' + _("Last changed:") + '</span>' +
742                 ( modified ? ( modified.toLocaleDateString() + ', ' + modified.toLocaleTimeString() ) : _("never") ) + '</li>'
743             );
744             $('#macro-list').append($li);
745         } );
746         var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
747         $new_li.click( function() {
748             // TODO: make this a bit less retro
749             var name = prompt(_("Please enter the name for the new macro:"));
750             if (!name) return;
752             if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
753             showSavedMacros();
754             loadMacro( name );
755         } );
756         $('#macro-list').append($new_li);
757         $('#macro-list').scrollTop(scrollTop);
758     }
760     function saveMacro() {
761         var name = macroEditor.activeMacro;
763         if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() ) return;
765         macroEditor.savedGeneration = macroEditor.changeGeneration();
766         storeMacro( name, { contents: macroEditor.getValue(), modified: (new Date()).valueOf(), history: macroEditor.getHistory(), format: $('#macro-format').val() } );
767         $('#macro-save-message').text(_("Saved"));
768         showSavedMacros();
769     }
771     $(document).ready( function() {
772         // Editor setup
773         editor = new MARCEditor( {
774             onCursorActivity: function() {
775                 $('#status-tag-info').empty();
776                 $('#status-subfield-info').empty();
778                 var field = editor.getCurrentField();
779                 var cur = editor.getCursor();
781                 if ( !field ) return;
783                 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
784                 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
786                 if ( taginfo ) {
787                     $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> '  + taginfo.lib );
789                     var subfield = field.getSubfieldAt( cur.ch );
790                     if ( !subfield ) return;
792                     var subfieldinfo = taginfo.subfields[ subfield.code ];
793                     $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
795                     if ( subfieldinfo ) {
796                         $('#status-subfield-info').append( subfieldinfo.lib );
797                     } else {
798                         $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
799                     }
800                 } else {
801                     $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
802                 }
803             },
804             position: function (elt) { $(elt).insertAfter('#toolbar') },
805         } );
807         // Automatically detect resizes and change the height of the editor and position of modals.
808         var resizeTimer = null;
809         function onResize() {
810             if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
811                 resizeTimer = null;
813                 var pos = $('#editor .CodeMirror').offset();
814                 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
816                 $('.modal-body').each( function() {
817                     $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
818                 } );
819             }, 100);
820         }
822         $( '#macro-ui' ).on( 'shown.bs.modal', function() {
823             if ( macroEditor ) return;
825             macroEditor = CodeMirror(
826                 $('#macro-editor')[0],
827                 {
828                     extraKeys: {
829                         'Ctrl-D': function( cm ) {
830                             var cur = cm.getCursor();
832                             cm.replaceRange( "‡", cur, null );
833                         },
834                     },
835                     mode: 'null',
836                     lineNumbers: true,
837                     readOnly: true,
838                 }
839             );
840             var saveTimeout;
841             macroEditor.on( 'change', function( cm, change ) {
842                 $('#macro-save-message').empty();
843                 if ( change.origin == 'setValue' ) return;
845                 if ( saveTimeout ) clearTimeout( saveTimeout );
846                 saveTimeout = setTimeout( function() {
847                     saveMacro();
849                     saveTimeout = null;
850                 }, 500 );
851             } );
853             showSavedMacros();
854         } );
856         var saveableBackends = [];
857         $.each( backends, function( id, backend ) {
858             if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
859         } );
860         saveableBackends.sort();
861         $.each( saveableBackends, function( undef, backend ) {
862             $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
863         } );
865         var macro_format_list = $.map( Macros.formats, function( format, name ) {
866             return $.extend( { name: name }, format );
867         } );
868         macro_format_list.sort( function( a, b ) {
869             return a.description.localeCompare(b.description);
870         } );
871         $.each( macro_format_list, function() {
872             $('#macro-format').append( '<option value="' + this.name + '">' + this.description + '</option>' );
873         } );
875         // Click bindings
876         $( '#save-record, #save-dropdown a' ).click( function() {
877              $( '#save-record' ).find('i').attr( 'class', 'fa fa-spinner fa-spin' ).siblings( 'span' ).text( _("Saving...") );
879             function finishCb(result) {
880                 if ( result.error == 'syntax' ) {
881                     humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
882                 } else if ( result.error == 'invalid' ) {
883                     humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
884                 } else if ( result.error ) {
885                     humanMsg.displayAlert( _("Something went wrong, cannot save"), { className: 'humanError' } );
886                 } else if ( !result.error ) {
887                     humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
888                 }
890                 $.each( result.errors || [], function( undef, error ) {
891                     switch ( error.type ) {
892                         case 'noTag':
893                             editor.addError( error.line, _("Invalid tag number") );
894                             break;
895                         case 'noIndicators':
896                             editor.addError( error.line, _("Invalid indicators") );
897                             break;
898                         case 'noSubfields':
899                             editor.addError( error.line, _("Tag has no subfields") );
900                             break;
901                         case 'missingTag':
902                             editor.addError( null, _("Missing mandatory tag: ") + error.tag );
903                             break;
904                         case 'missingSubfield':
905                             if ( error.subfield == '@' ) {
906                                 editor.addError( error.line, _("Missing control field contents") );
907                             } else {
908                                 editor.addError( error.line, _("Missing mandatory subfield: â€¡") + error.subfield );
909                             }
910                             break;
911                         case 'unrepeatableTag':
912                             editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
913                             break;
914                         case 'unrepeatableSubfield':
915                             editor.addError( error.line, _("Subfield â€¡") + error.subfield + _(" cannot be repeated") );
916                             break;
917                         case 'itemTagUnsupported':
918                             editor.addError( error.line, _("Item tags cannot currently be saved") );
919                             break;
920                     }
921                 } );
923                 $( '#save-record' ).find('i').attr( 'class', 'fa fa-hdd-o' );
925                 if ( result.error ) {
926                     // Reset backend info
927                     setSource( [ state.backend, state.recordID ] );
928                 }
929             }
931             var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
932             if ( state.backend == backend ) {
933                 saveRecord( backend + '/' + state.recordID, editor, finishCb );
934             } else {
935                 saveRecord( backend + '/', editor, finishCb );
936             }
938             return false;
939         } );
941         $('#import-records').click( function() {
942             $('#import-records-input')
943                 .off('change')
944                 .change( function() {
945                     if ( !this.files || !this.files.length ) return;
947                     var file = this.files[0];
948                     var reader = new FileReader();
950                     reader.onload = function() {
951                         var record = new MARC.Record();
953                         if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
954                             record.loadISO2709( reader.result );
955                         } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
956                             record.loadMARCXML( reader.result );
957                         } else {
958                             humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
959                             return;
960                         }
962                         if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
964                         editor.displayRecord( record );
965                     };
967                     reader.readAsText( file );
968                 } )
969                 .click();
971             return false;
972         } );
974         $('#open-macros').click( function() {
975             $('#macro-ui').modal('show');
977             return false;
978         } );
980         $('#run-macro').click( function() {
981             var result = Macros.Run( editor, $('#macro-format').val(), macroEditor.getValue() );
983             if ( !result.errors.length ) {
984                 $('#macro-ui').modal('hide');
985                 editor.focus(); //Return cursor to editor after macro run
986                 return false;
987             }
989             var errors = [];
990             $.each( result.errors, function() {
991                 var error = '<b>' + _("Line ") + (this.line + 1) + ':</b> ';
993                 switch ( this.error ) {
994                     case 'failed': error += _("failed to run"); break;
995                     case 'unrecognized': error += _("unrecognized command"); break;
996                 }
998                 errors.push(error);
999             } );
1001             humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
1003             return false;
1004         } );
1006         $('#delete-macro').click( function() {
1007             if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
1009             storeMacro( macroEditor.activeMacro, undefined );
1010             showSavedMacros();
1011             loadMacro( undefined );
1013             return false;
1014         } );
1016         $( '#switch-editor' ).click( function() {
1017             if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
1019             $.cookie( 'catalogue_editor_[% logged_in_user.borrowernumber | html %]', 'basic', { expires: 365, path: '/' } );
1021             if ( state.backend == 'catalog' ) {
1022                 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
1023             } else if ( state.backend == 'new' ) {
1024                 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
1025             } else {
1026                 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
1027             }
1028         } );
1030         $( '#show-advanced-search' ).click( function() {
1031             showAdvancedSearch();
1033             return false;
1034         } );
1036         $('#advanced-search').submit( function() {
1037             startAdvancedSearch();
1039             return false;
1040         } );
1042         $( document ).on( 'click', 'a.search-nav', function() {
1043             if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1044                 $("#search-overlay").show();
1045             }
1047             return false;
1048         });
1050         $( document ).on( 'click', 'th[data-sort-label]', function() {
1051             var direction;
1053             if ( $( this ).hasClass( 'sorting_asc' ) ) {
1054                 direction = 'desc';
1055             } else {
1056                 direction = 'asc';
1057             }
1059             if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1060                 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1062                 $("#search-overlay").show();
1063             }
1065             return false;
1066         });
1068         $( document ).on( 'change', 'input.search-toggle-server', function() {
1069             var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1070             server.checked = this.checked;
1072             if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1073                 $("#search-overlay").show();
1074             }
1075         } );
1077         // Key bindings
1078         bindGlobalKeys();
1080         // Setup UI
1081         $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1082             $(this).modal({ show: false });
1083         } );
1085         var $quicksearch = $('#quicksearch fieldset');
1086         $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1087             position: 'absolute',
1088             top: $quicksearch.offset().top,
1089             left: $quicksearch.offset().left,
1090             height: $quicksearch.outerHeight(),
1091             width: $quicksearch.outerWidth(),
1092         }).appendTo(document.body).hide();
1094         var prevAlerts = [];
1095         humanMsg.logMsg = function(msg, options) {
1096             $('#show-alerts').popover('hide');
1097             prevAlerts.unshift('<li>' + msg + '</li>');
1098             prevAlerts.splice(5, 999); // Truncate old messages
1099         };
1101         $('#show-alerts').popover({
1102             html: true,
1103             placement: 'bottom',
1104             content: function() {
1105                 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1106             },
1107         });
1109         $('#show-shortcuts').popover({
1110             html: true,
1111             placement: 'bottom',
1112             content: function() {
1113                 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1114             },
1115         });
1117         $('#new-record' ).click( function() {
1118             if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1120             openRecord( 'new/', editor );
1121             return false;
1122         } );
1124         window.onbeforeunload = function() {
1125             if(editor.modified )
1126                 { return 1; }
1127             else
1128                 { return undefined; }
1129         };
1131         $('a.change-framework').click( function() {
1132             $("#loading").show();
1133             editor.setFrameworkCode(
1134                 $(this).data( 'frameworkcode' ),
1135                 true,
1136                 function ( error ) {
1137                     if ( typeof error !== 'undefined' ) {
1138                         humanMsg.displayAlert( _("Failed to change framework"), { className: 'humanError' } );
1139                     }
1140                     $('#loading').hide();
1141                 }
1142             );
1143         } );
1145         // Start editor
1146         Preferences.Load( [% logged_in_user.borrowernumber || 0 | html %] );
1147         displayPreferences(editor);
1148         makeAuthorisedValueWidgets( '' );
1149         Search.Init( {
1150             page_size: 20,
1151             onresults: function(data) { showSearchResults( editor, data ) },
1152             onerror: handleSearchError,
1153         } );
1155         function finishCb( data ) {
1156             if ( data.error ) {
1157                 humanMsg.displayAlert( data.error );
1158                 openRecord( 'new/', editor, finishCb );
1159             }
1161             Resources.GetAll().done( function() {
1162                 $("#loading").hide();
1163                 $( window ).resize( onResize ).resize();
1164                 editor.focus();
1165             } );
1166         }
1168         if ( "[% auth_forwarded_hash | html %]" ) {
1169             document.location.hash = "[% auth_forwarded_hash | html %]";
1170         }
1172         if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1173             openRecord( 'new/', editor, finishCb );
1174         }
1175     } );
1176 } )();
1178 </script>