Bug 14610 - Add and update scripts
[koha.git] / koha-tmpl / intranet-tmpl / prog / en / includes / cateditor-ui.inc
blob0e4c279c663160ef9af0a52edcb7d917492962d2
1 <script src="[% interface %]/lib/codemirror/codemirror-compressed.js"></script>
2 <script src="[% interface %]/lib/filesaver.js"></script>
3 <script src="[% interface %]/lib/koha/cateditor/marc-mode.js"></script>
4 <script src="[% interface %]/lib/require.js"></script>
5 <script>
6 require.config( {
7     baseUrl: '[% interface %]/lib/koha/cateditor/',
8     config: {
9         resources: {
10             marcflavour: '[% marcflavour %]',
11             themelang: '[% themelang %]',
12         },
13     },
14     waitSeconds: 30,
15 } );
16 </script>
18 [% IF marcflavour == 'MARC21' %]
19 [% PROCESS 'cateditor-widgets-marc21.inc' %]
20 [% ELSE %]
21 <script>var editorWidgets = {};</script>
22 [% END %]
24 <script>
25 require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'preferences', 'resources', 'text-marc', 'widget' ], function( KohaBackend, Search, Macros, MARCEditor, MARC, Preferences, Resources, TextMARC, Widget ) {
26     var z3950Servers = {
27         'koha:biblioserver': {
28             name: _("Local catalog"),
29             recordtype: 'biblio',
30             checked: false,
31         },
32         [%- FOREACH server = z3950_servers -%]
33             [% server.id %]: {
34                 name: '[% server.servername %]',
35                 recordtype: '[% server.recordtype %]',
36                 checked: [% server.checked ? 'true' : 'false' %],
37             },
38         [%- END -%]
39     };
41     // The columns that should show up in a search, in order, and keyed by the corresponding <metadata> tag in the XSL and Pazpar2 config
42     var z3950Labels = [
43         [ "local_number", _("Local number") ],
44         [ "title", _("Title") ],
45         [ "series", _("Series title") ],
46         [ "author", _("Author") ],
47         [ "lccn", _("LCCN") ],
48         [ "isbn", _("ISBN") ],
49         [ "issn", _("ISSN") ],
50         [ "medium", _("Medium") ],
51         [ "edition", _("Edition") ],
52         [ "notes", _("Notes") ],
53     ];
55     var state = {
56         backend: '',
57         saveBackend: 'catalog',
58         recordID: undefined
59     };
61     var editor;
62     var macroEditor;
64     function makeAuthorisedValueWidgets( frameworkCode ) {
65         $.each( KohaBackend.GetAllTagsInfo( frameworkCode ), function( tag, tagInfo ) {
66             $.each( tagInfo.subfields, function( subfield, subfieldInfo ) {
67                 if ( !subfieldInfo.authorised_value ) return;
68                 var authvals = KohaBackend.GetAuthorisedValues( subfieldInfo.authorised_value );
69                 if ( !authvals ) return;
71                 var defaultvalue = subfield.defaultvalue || authvals[0].value;
73                 Widget.Register( tag + subfield, {
74                     init: function() {
75                         var $result = $( '<span class="subfield-widget"></span>' );
77                         return $result[0];
78                     },
79                     postCreate: function() {
80                         var value = defaultvalue;
81                         var widget = this;
83                         $.each( authvals, function() {
84                             if ( this.value == widget.text ) {
85                                 value = this.value;
86                             }
87                         } );
89                         this.setText( value );
91                         $( '<select></select>' ).appendTo( this.node );
92                         var $node = $( this.node ).find( 'select' );
93                         $.each( authvals, function( undef, authval ) {
94                             $node.append( '<option value="' + authval.value + '"' + (authval.value == value ? ' selected="selected"' : '') + '>' + authval.lib + '</option>' );
95                         } );
96                         $node.val( this.text );
98                         $node.change( $.proxy( function() {
99                             this.setText( $node.val() );
100                         }, this ) );
101                     },
102                     makeTemplate: function() {
103                         return defaultvalue;
104                     },
105                 } );
106             } );
107         } );
108     }
110     function bindGlobalKeys() {
111         shortcut.add( 'ctrl+s', function(event) {
112             $( '#save-record' ).click();
114             event.preventDefault();
115         } );
117         shortcut.add( 'alt+ctrl+k', function(event) {
118             $( '#search-by-keywords' ).focus();
120             return false;
121         } );
123         shortcut.add( 'alt+ctrl+a', function(event) {
124             $( '#search-by-author' ).focus();
126             return false;
127         } );
129         shortcut.add( 'alt+ctrl+i', function(event) {
130             $( '#search-by-isbn' ).focus();
132             return false;
133         } );
135         shortcut.add( 'alt+ctrl+t', function(event) {
136             $( '#search-by-title' ).focus();
138             return false;
139         } );
141         shortcut.add( 'ctrl+h', function() {
142             var field = editor.getCurrentField();
144             if ( !field ) return;
146             window.open( getFieldHelpURL( field.tag ) );
147         } );
149         $('#quicksearch .search-box').each( function() {
150             shortcut.add( 'enter', $.proxy( function() {
151                 var terms = [];
153                 $('#quicksearch .search-box').each( function() {
154                     if ( !this.value ) return;
156                     terms.push( [ $(this).data('qualifier'), this.value ] );
157                 } );
159                 if ( !terms.length ) return;
161                 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
162                     $("#search-overlay").show();
163                     showResultsBox();
164                 }
166                 return false;
167             }, this), { target: this, type: 'keypress' } );
168         } );
169     }
171     function getFieldHelpURL( tag ) {
172         [% IF ( marcflavour == 'MARC21' ) %]
173             if ( tag == '000' ) {
174                 return "http://www.loc.gov/marc/bibliographic/bdleader.html";
175             } else if ( tag >= '090' && tag < '100' ) {
176                 return "http://www.loc.gov/marc/bibliographic/bd09x.html";
177             } else if ( tag < '900' ) {
178                 return "http://www.loc.gov/marc/bibliographic/bd" + tag + ".html";
179             } else {
180                 return "http://www.loc.gov/marc/bibliographic/bd9xx.html";
181             }
182         [% ELSIF ( marcflavour == 'UNIMARC' ) %]
183             /* http://archive.ifla.org/VI/3/p1996-1/ is an outdated version of UNIMARC, but
184                seems to be the only version available that can be linked to per tag.  More recent
185                versions of the UNIMARC standard are available on the IFLA website only as
186                PDFs!
187             */
188             if ( tag == '000' ) {
189                return  "http://archive.ifla.org/VI/3/p1996-1/uni.htm";
190             } else {
191                 var first = tag[0];
192                 var url = "http://archive.ifla.org/VI/3/p1996-1/uni" + first + ".htm#";
193                 if ( first == '0' ) url += "b";
194                 if ( first != '9' ) url += tag;
196                 return url;
197             }
198         [% END %]
199     }
201     // Record loading
202     var backends = {
203        'new': {
204             titleForRecord: _("Editing new record"),
205             get: function( id, callback ) {
206                 record = new MARC.Record();
207                 KohaBackend.FillRecord( '', record );
209                 callback( record );
210             },
211         },
212         'new-full': {
213             titleForRecord: _("Editing new full record"),
214             get: function( id, callback ) {
215                 record = new MARC.Record();
216                 KohaBackend.FillRecord( '', record, true );
218                 callback( record );
219             },
220         },
221         'catalog': {
222             titleForRecord: _("Editing catalog record #{ID}"),
223             links: [
224                 { title: _("view"), href: "/cgi-bin/koha/catalogue/detail.pl?biblionumber={ID}" },
225                 { title: _("edit items"), href: "/cgi-bin/koha/cataloguing/additem.pl?biblionumber={ID}" },
226             ],
227             saveLabel: _("Save to catalog"),
228             get: function( id, callback ) {
229                 if ( !id ) return false;
231                 KohaBackend.GetRecord( id, callback );
232             },
233             save: function( id, record, done ) {
234                 function finishCb( data ) {
235                     done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
236                 }
238                 if ( id ) {
239                     KohaBackend.SaveRecord( id, record, finishCb );
240                 } else {
241                     KohaBackend.CreateRecord( record, finishCb );
242                 }
243             }
244         },
245         'iso2709': {
246             saveLabel: _("Save as ISO2709 (.mrc) file"),
247             save: function( id, record, done ) {
248                 saveAs( new Blob( [record.toISO2709()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.mrc' );
250                 done( {} );
251             }
252         },
253         'marcxml': {
254             saveLabel: _("Save as MARCXML (.xml) file"),
255             save: function( id, record, done ) {
256                 saveAs( new Blob( [record.toXML()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.xml' );
258                 done( {} );
259             }
260         },
261         'search': {
262             titleForRecord: _("Editing search result"),
263             get: function( id, callback ) {
264                 if ( !id ) return false;
265                 if ( !backends.search.records[ id ] ) {
266                     callback( { error: _( "Invalid record" ) } );
267                     return false;
268                 }
270                 callback( backends.search.records[ id ] );
271             },
272             records: {},
273         },
274     };
276     function setSource(parts) {
277         state.backend = parts[0];
278         state.recordID = parts[1];
279         state.canSave = backends[ state.backend ].save != null;
280         state.saveBackend = state.canSave ? state.backend : 'catalog';
282         var backend = backends[state.backend];
284         document.location.hash = '#' + parts[0] + '/' + parts[1];
286         $('#title').text( backend.titleForRecord.replace( '{ID}', parts[1] ) );
288         $.each( backend.links || [], function( i, link ) {
289             $('#title').append(' <a target="_blank" href="' + link.href.replace( '{ID}', parts[1] ) + '">(' + link.title + ')</a>' );
290         } );
291         $( 'title', document.head ).html( _("Koha &rsaquo; Cataloging &rsaquo; ") + backend.titleForRecord.replace( '{ID}', parts[1] ) );
292         $('#save-record span').text( backends[ state.saveBackend ].saveLabel );
293     }
295     function saveRecord( recid, editor, callback ) {
296         var parts = recid.split('/');
297         if ( parts.length != 2 ) return false;
299         if ( !backends[ parts[0] ] || !backends[ parts[0] ].save ) return false;
301         editor.removeErrors();
302         var record = editor.getRecord();
304         if ( record.errors ) {
305             state.saving = false;
306             callback( { error: 'syntax', errors: record.errors } );
307             return;
308         }
310         var errors = KohaBackend.ValidateRecord( '', record );
311         if ( errors.length ) {
312             state.saving = false;
313             callback( { error: 'invalid', errors: errors } );
314             return;
315         }
317         backends[ parts[0] ].save( parts[1], record, function(data) {
318             state.saving = false;
320             if (data.newRecord) {
321                 var record = new MARC.Record();
322                 record.loadMARCXML(data.newRecord);
323                 editor.displayRecord( record );
324             }
326             if (data.newId) {
327                 setSource(data.newId);
328             } else {
329                 setSource( [ state.backend, state.recordID ] );
330             }
332             if (callback) callback( data );
333         } );
334     }
336     function loadRecord( recid, editor, callback ) {
337         var parts = recid.split('/');
338         if ( parts.length != 2 ) return false;
340         if ( !backends[ parts[0] ] || !backends[ parts[0] ].get ) return false;
342         backends[ parts[0] ].get( parts[1], function( record ) {
343             if ( !record.error ) {
344                 editor.displayRecord( record );
345                 editor.focus();
346             }
348             if (callback) callback(record);
349         } );
351         return true;
352     }
354     function openRecord( recid, editor, callback ) {
355         return loadRecord( recid, editor, function ( record ) {
356             setSource( recid.split('/') );
358             if (callback) callback( record );
359         } );
360     }
362     // Search functions
363     function showAdvancedSearch() {
364         $('#advanced-search-servers').empty();
365         $.each( z3950Servers, function( server_id, server ) {
366             $('#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>' );
367         } );
368         $('#advanced-search-ui').modal('show');
369     }
371     function startAdvancedSearch() {
372         var terms = [];
374         $('#advanced-search-ui .search-box').each( function() {
375             if ( !this.value ) return;
377             terms.push( [ $(this).data('qualifier'), this.value ] );
378         } );
380         if ( !terms.length ) return;
382         if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
383             $('#advanced-search-ui').modal('hide');
384             $("#search-overlay").show();
385             showResultsBox();
386         }
387     }
389     function showResultsBox(data) {
390         $('#search-top-pages, #search-bottom-pages').find('.pagination').empty();
391         $('#searchresults thead tr').empty();
392         $('#searchresults tbody').empty();
393         $('#search-serversinfo').empty().append('<li>' + _("Loading...") + '</li>');
394         $('#search-results-ui').modal('show');
395     }
397     function showSearchSorting( sort_key, sort_direction ) {
398         var $th = $('#searchresults thead tr th[data-sort-label="' + sort_key + '"]');
399         $th.parent().find( 'th[data-sort-label]' ).attr( 'class', 'sorting' );
401         if ( sort_direction == 'asc' ) {
402             direction = 'asc';
403             $th.attr( 'class', 'sorting_asc' );
404         } else {
405             direction = 'desc';
406             $th.attr( 'class', 'sorting_desc' );
407         }
408     }
410     function showSearchResults( editor, data ) {
411         backends.search.records = {};
413         $('#searchresults thead tr').empty();
414         $('#searchresults tbody').empty();
415         $('#search-serversinfo').empty();
417         $.each( z3950Servers, function( server_id, server ) {
418             var num_fetched = data.num_fetched[server_id];
420             if ( data.errors[server_id] ) {
421                 num_fetched = data.errors[server_id];
422             } else if ( num_fetched == null ) {
423                 num_fetched = '-';
424             } else if ( num_fetched < data.num_hits[server_id] ) {
425                 num_fetched += '+';
426             }
428             $('#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>' );
429         } );
431         var seenColumns = {};
433         $.each( data.hits, function( undef, hit ) {
434             $.each( hit.metadata, function(key) {
435                 seenColumns[key] = true;
436             } );
437         } );
439         $('#searchresults thead tr').append('<th>' + _("Source") + '</th>');
441         $.each( z3950Labels, function( undef, label ) {
442             if ( seenColumns[ label[0] ] ) {
443                 $('#searchresults thead tr').append( '<th class="sorting" data-sort-label="' + label[0] + '">' + label[1] + '</th>' );
444             }
445         } );
447         showSearchSorting( data.sort_key, data.sort_direction );
449         $('#searchresults thead tr').append('<th>' + _("Tools") + '</th>');
451         var bibnumMap = KohaBackend.GetSubfieldForKohaField('biblio.biblionumber');
452         $.each( data.hits, function( undef, hit ) {
453             backends.search.records[ hit.server + ':' + hit.index ] = hit.record;
455             switch ( hit.server ) {
456                 case 'koha:biblioserver':
457                     var bibnumField = hit.record.field( bibnumMap[0] );
459                     if ( bibnumField && bibnumField.hasSubfield( bibnumMap[1] ) ) {
460                         hit.id = 'catalog/' + bibnumField.subfield( bibnumMap[1] );
461                         break;
462                     }
464                     // Otherwise, fallthrough
466                 default:
467                     hit.id = 'search/' + hit.server + ':' + hit.index;
468             }
470             var result = '<tr>';
471             result += '<td class="sourcecol">' + z3950Servers[ hit.server ].name + '</td>';
473             $.each( z3950Labels, function( undef, label ) {
474                 if ( !seenColumns[ label[0] ] ) return;
476                 if ( hit.metadata[ label[0] ] ) {
477                     result += '<td class="infocol">' + hit.metadata[ label[0] ] + '</td>';
478                 } else {
479                     result += '<td class="infocol">&nbsp;</td>';
480                 }
481             } );
483             result += '<td class="toolscol"><ul><li><a href="#" class="marc-link">' + _("View MARC") + '</a></li>';
484             result += '<li><a href="#" class="open-link">' + ( hit.server == 'koha:biblioserver' ? _("Edit") : _("Import") ) + '</a></li>';
485             if ( state.canSave ) result += '<li><a href="#" class="substitute-link" title="' + _("Replace the current record's contents") + '">' + _("Substitute") + '</a></li>';
486             result += '</ul></td></tr>';
488             var $tr = $( result );
489             $tr.find( '.marc-link' ).click( function() {
490                 var $info_columns = $tr.find( '.infocol' );
491                 var $marc_column = $tr.find( '.marccol' );
493                 if ( !$marc_column.length ) {
494                     $marc_column = $( '<td class="marccol" colspan="' + $info_columns.length + '"></td>' ).insertAfter( $info_columns.eq(-1) ).hide();
495                     CodeMirror.runMode( TextMARC.RecordToText( hit.record ), 'marc', $marc_column[0] );
496                 }
498                 if ( $marc_column.is(':visible') ) {
499                     $tr.find('.marc-link').text( _("View MARC") );
500                     $info_columns.show();
501                     $marc_column.hide();
502                 } else {
503                     $tr.find('.marc-link').text( _("Hide MARC") );
504                     $marc_column.show();
505                     $info_columns.hide();
506                 }
508                 return false;
509             } );
510             $tr.find( '.open-link' ).click( function() {
511                 $( '#search-results-ui' ).modal('hide');
512                 openRecord( hit.id, editor );
514                 return false;
515             } );
516             $tr.find( '.substitute-link' ).click( function() {
517                 $( '#search-results-ui' ).modal('hide');
518                 loadRecord( hit.id, editor );
520                 return false;
521             } );
522             $('#searchresults tbody').append( $tr );
523         } );
525         var pages = [];
526         var cur_page = data.offset / data.page_size;
527         var max_page = Math.ceil( data.total_fetched / data.page_size ) - 1;
529         if ( cur_page != 0 ) {
530             pages.push( '<li><a class="search-nav" href="#" data-offset="' + (data.offset - data.page_size) + '">&laquo; ' + _("Previous") + '</a></li>' );
531         }
533         for ( var page = Math.max( 0, cur_page - 9 ); page <= Math.min( max_page, cur_page + 9 ); page++ ) {
534             if ( page == cur_page ) {
535                 pages.push( ' <li class="active"><a href="#">' + ( page + 1 ) + '</a></li>' );
536             } else {
537                 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + ( page * data.page_size ) + '">' + ( page + 1 ) + '</a></li>' );
538             }
539         }
541         if ( cur_page < max_page ) {
542             pages.push( ' <li><a class="search-nav" href="#" data-offset="' + (data.offset + data.page_size) + '">' + _("Next") + ' &raquo;</a></li>' );
543         }
545         $( '#search-top-pages, #search-bottom-pages' ).find( '.pagination' ).html( pages.length > 1 ? ( '<ul>' + pages.join( '' ) + '</ul>' ) : '' );
547         var $overlay = $('#search-overlay');
548         $overlay.find('span').text(_("Loading"));
549         $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
551         if ( data.activeclients ) {
552             $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
553             $overlay.show();
554         } else {
555             $overlay.find('.bar').css( { display: 'block', width: '100%' } );
556             $overlay.fadeOut();
557             $('#searchresults')[0].focus();
558         }
559     }
561     function invalidateSearchResults() {
562         var $overlay = $('#search-overlay');
563         $overlay.find('span').text(_("Search expired, please try again"));
564         $overlay.find('.bar').css( { display: 'none' } );
565         $overlay.show();
566     }
568     function handleSearchError(error) {
569         if (error.code == 1) {
570             invalidateSearchResults();
571             Search.Reconnect();
572         } else {
573             humanMsg.displayMsg( '<h3>' + _("Internal search error") + '</h3><p>' + error + '</p><p>' + _("Please refresh the page and try again.") + '</p>', { className: 'humanError' } );
574         }
575     }
577     function handleSearchInitError(error) {
578         $('#quicksearch-overlay').fadeIn().find('p').text(error);
579     }
581     // Preference functions
582     function showPreference( pref ) {
583         var value = Preferences.user[pref];
585         switch (pref) {
586             case 'fieldWidgets':
587                 $( '#set-field-widgets' ).text( value ? _("Show fields verbatim") : _("Show helpers for fixed and coded fields") );
588                 break;
589             case 'font':
590                 $( '#editor .CodeMirror' ).css( { fontFamily: value } );
591                 editor.refresh();
592                 break;
593             case 'fontSize':
594                 $( '#editor .CodeMirror' ).css( { fontSize: value } );
595                 editor.refresh();
596                 break;
597             case 'macros':
598                 // Macros loaded on first show of modal
599                 break;
600             case 'selected_search_targets':
601                 $.each( z3950Servers, function( server_id, server ) {
602                     var saved_val = Preferences.user.selected_search_targets[server_id];
604                     if ( saved_val != null ) server.checked = saved_val;
605                 } );
606                 break;
607         }
608     }
610     function bindPreference( editor, pref ) {
611         function _addHandler( sel, event, handler ) {
612             $( sel ).on( event, function (e) {
613                 e.preventDefault();
614                 handler( e, Preferences.user[pref] );
615                 Preferences.Save( [% USER_INFO.borrowernumber %] );
616                 showPreference(pref);
617             } );
618         }
620         switch (pref) {
621             case 'fieldWidgets':
622                 _addHandler( '#set-field-widgets', 'click', function( e, oldValue ) {
623                     editor.setUseWidgets( Preferences.user.fieldWidgets = !Preferences.user.fieldWidgets );
624                 } );
625                 break;
626             case 'font':
627                 _addHandler( '#prefs-menu .set-font', 'click', function( e, oldValue ) {
628                     Preferences.user.font = $( e.target ).css( 'font-family' );
629                 } );
630                 break;
631             case 'fontSize':
632                 _addHandler( '#prefs-menu .set-fontSize', 'click', function( e, oldValue ) {
633                     Preferences.user.fontSize = $( e.target ).css( 'font-size' );
634                 } );
635                 break;
636             case 'selected_search_targets':
637                 $( document ).on( 'change', 'input.search-toggle-server', function() {
638                     var server_id = $( this ).closest('li').data('server-id');
639                     Preferences.user.selected_search_targets[server_id] = this.checked;
640                     Preferences.Save( [% USER_INFO.borrowernumber %] );
641                 } );
642                 break;
643         }
644     }
646     function displayPreferences( editor ) {
647         $.each( Preferences.user, function( pref, value ) {
648             showPreference( pref );
649             bindPreference( editor, pref );
650         } );
651     }
653     //> Macro functions
654     function loadMacro( name ) {
655         $( '#macro-list li' ).removeClass( 'active' );
656         macroEditor.activeMacro = name;
658         if ( !name ) {
659             macroEditor.setValue( '' );
660             return;
661         }
663         $( '#macro-list li[data-name="' + name + '"]' ).addClass( 'active' );
664         var macro = Preferences.user.macros[name];
665         macroEditor.setValue( macro.contents );
666         macroEditor.setOption( 'readOnly', false );
667         $( '#macro-format' ).val( macro.format || 'its' );
668         if ( macro.history ) macroEditor.setHistory( macro.history );
669     }
671     function storeMacro( name, macro ) {
672         if ( macro ) {
673             Preferences.user.macros[name] = macro;
674         } else {
675             delete Preferences.user.macros[name];
676         }
678         Preferences.Save( [% USER_INFO.borrowernumber %] );
679     }
681     function showSavedMacros( macros ) {
682         var scrollTop = $('#macro-list').scrollTop();
683         $( '#macro-list' ).empty();
684         var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
685             return $.extend( { name: name }, macro );
686         } );
687         macro_list.sort( function( a, b ) {
688             return a.name.localeCompare(b.name);
689         } );
690         $.each( macro_list, function( undef, macro ) {
691             var $li = $( '<li data-name="' + macro.name + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
692             $li.click( function() {
693                 loadMacro(macro.name);
694                 return false;
695             } );
696             if ( macro.name == macroEditor.activeMacro ) $li.addClass( 'active' );
697             var modified = macro.modified && new Date(macro.modified);
698             $li.find( '.macro-info' ).append(
699                 '<li><span class="label">' + _("Last changed:") + '</span>' +
700                 ( modified ? ( modified.toLocaleDateString() + ', ' + modified.toLocaleTimeString() ) : _("never") ) + '</li>'
701             );
702             $('#macro-list').append($li);
703         } );
704         var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
705         $new_li.click( function() {
706             // TODO: make this a bit less retro
707             var name = prompt(_("Please enter the name for the new macro:"));
708             if (!name) return;
710             if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
711             showSavedMacros();
712             loadMacro( name );
713         } );
714         $('#macro-list').append($new_li);
715         $('#macro-list').scrollTop(scrollTop);
716     }
718     function saveMacro() {
719         var name = macroEditor.activeMacro;
721         if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() ) return;
723         macroEditor.savedGeneration = macroEditor.changeGeneration();
724         storeMacro( name, { contents: macroEditor.getValue(), modified: (new Date()).valueOf(), history: macroEditor.getHistory(), format: $('#macro-format').val() } );
725         $('#macro-save-message').text(_("Saved"));
726         showSavedMacros();
727     }
729     $(document).ready( function() {
730         // Editor setup
731         editor = new MARCEditor( {
732             onCursorActivity: function() {
733                 $('#status-tag-info').empty();
734                 $('#status-subfield-info').empty();
736                 var field = editor.getCurrentField();
737                 var cur = editor.getCursor();
739                 if ( !field ) return;
741                 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
742                 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
744                 if ( taginfo ) {
745                     $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> '  + taginfo.lib );
747                     var subfield = field.getSubfieldAt( cur.ch );
748                     if ( !subfield ) return;
750                     var subfieldinfo = taginfo.subfields[ subfield.code ];
751                     $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
753                     if ( subfieldinfo ) {
754                         $('#status-subfield-info').append( subfieldinfo.lib );
755                     } else {
756                         $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
757                     }
758                 } else {
759                     $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
760                 }
761             },
762             position: function (elt) { $(elt).insertAfter('#toolbar') },
763         } );
765         // Automatically detect resizes and change the height of the editor and position of modals.
766         var resizeTimer = null;
767         function onResize() {
768             if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
769                 resizeTimer = null;
771                 var pos = $('#editor .CodeMirror').position();
772                 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
774                 $('.modal-body').each( function() {
775                     $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
776                 } );
777             }, 100);
779             $("#advanced-search-ui, #search-results-ui, #macro-ui").css( {
780                 marginLeft: function() {
781                     return -($(this).width() / 2);
782                 }
783             } );
784         }
786         $( '#macro-ui' ).on( 'shown', function() {
787             if ( macroEditor ) return;
789             macroEditor = CodeMirror(
790                 $('#macro-editor')[0],
791                 {
792                     extraKeys: {
793                         'Ctrl-D': function( cm ) {
794                             var cur = cm.getCursor();
796                             cm.replaceRange( "‡", cur, null );
797                         },
798                     },
799                     mode: 'null',
800                     lineNumbers: true,
801                     readOnly: true,
802                 }
803             );
804             var saveTimeout;
805             macroEditor.on( 'change', function( cm, change ) {
806                 $('#macro-save-message').empty();
807                 if ( change.origin == 'setValue' ) return;
809                 if ( saveTimeout ) clearTimeout( saveTimeout );
810                 saveTimeout = setTimeout( function() {
811                     saveMacro();
813                     saveTimeout = null;
814                 }, 500 );
815             } );
817             showSavedMacros();
818         } );
820         var saveableBackends = [];
821         $.each( backends, function( id, backend ) {
822             if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
823         } );
824         saveableBackends.sort();
825         $.each( saveableBackends, function( undef, backend ) {
826             $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
827         } );
829         var macro_format_list = $.map( Macros.formats, function( format, name ) {
830             return $.extend( { name: name }, format );
831         } );
832         macro_format_list.sort( function( a, b ) {
833             return a.description.localeCompare(b.description);
834         } );
835         $.each( macro_format_list, function() {
836             $('#macro-format').append( '<option value="' + this.name + '">' + this.description + '</option>' );
837         } );
839         // Click bindings
840         $( '#save-record, #save-dropdown a' ).click( function() {
841             $( '#save-record' ).find('i').attr( 'class', 'icon-loading' ).siblings( 'span' ).text( _("Saving...") );
843             function finishCb(result) {
844                 if ( result.error == 'syntax' ) {
845                     humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
846                 } else if ( result.error == 'invalid' ) {
847                     humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
848                 } else if ( !result.error ) {
849                     humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
850                 }
852                 $.each( result.errors || [], function( undef, error ) {
853                     switch ( error.type ) {
854                         case 'noTag':
855                             editor.addError( error.line, _("Invalid tag number") );
856                             break;
857                         case 'noIndicators':
858                             editor.addError( error.line, _("Invalid indicators") );
859                             break;
860                         case 'noSubfields':
861                             editor.addError( error.line, _("Tag has no subfields") );
862                             break;
863                         case 'missingTag':
864                             editor.addError( null, _("Missing mandatory tag: ") + error.tag );
865                             break;
866                         case 'missingSubfield':
867                             if ( error.subfield == '@' ) {
868                                 editor.addError( error.line, _("Missing control field contents") );
869                             } else {
870                                 editor.addError( error.line, _("Missing mandatory subfield: â€¡") + error.subfield );
871                             }
872                             break;
873                         case 'unrepeatableTag':
874                             editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
875                             break;
876                         case 'unrepeatableSubfield':
877                             editor.addError( error.line, _("Subfield â€¡") + error.subfield + _(" cannot be repeated") );
878                             break;
879                         case 'itemTagUnsupported':
880                             editor.addError( error.line, _("Item tags cannot currently be saved") );
881                             break;
882                     }
883                 } );
885                 $( '#save-record' ).find('i').attr( 'class', 'icon-hdd' );
887                 if ( result.error ) {
888                     // Reset backend info
889                     setSource( [ state.backend, state.recordID ] );
890                 }
891             }
893             var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
894             if ( state.backend == backend ) {
895                 saveRecord( backend + '/' + state.recordID, editor, finishCb );
896             } else {
897                 saveRecord( backend + '/', editor, finishCb );
898             }
900             return false;
901         } );
903         $('#import-records').click( function() {
904             $('#import-records-input')
905                 .off('change')
906                 .change( function() {
907                     if ( !this.files || !this.files.length ) return;
909                     var file = this.files[0];
910                     var reader = new FileReader();
912                     reader.onload = function() {
913                         var record = new MARC.Record();
915                         if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
916                             record.loadISO2709( reader.result );
917                         } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
918                             record.loadMARCXML( reader.result );
919                         } else {
920                             humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
921                             return;
922                         }
924                         if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
926                         editor.displayRecord( record );
927                     };
929                     reader.readAsText( file );
930                 } )
931                 .click();
933             return false;
934         } );
936         $('#open-macros').click( function() {
937             $('#macro-ui').modal('show');
939             return false;
940         } );
942         $('#run-macro').click( function() {
943             var result = Macros.Run( editor, $('#macro-format').val(), macroEditor.getValue() );
945             if ( !result.errors.length ) {
946                 $('#macro-ui').modal('hide');
947                 return false;
948             }
950             var errors = [];
951             $.each( result.errors, function() {
952                 var error = '<b>' + _("Line ") + (this.line + 1) + ':</b> ';
954                 switch ( this.error ) {
955                     case 'failed': error += _("failed to run"); break;
956                     case 'unrecognized': error += _("unrecognized command"); break;
957                 }
959                 errors.push(error);
960             } );
962             humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
964             return false;
965         } );
967         $('#delete-macro').click( function() {
968             if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
970             storeMacro( macroEditor.activeMacro, undefined );
971             showSavedMacros();
972             loadMacro( undefined );
974             return false;
975         } );
977         $( '#switch-editor' ).click( function() {
978             if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
980             $.cookie( 'catalogue_editor_[% USER_INFO.borrowernumber %]', 'basic', { expires: 365, path: '/' } );
982             if ( state.backend == 'catalog' ) {
983                 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
984             } else if ( state.backend == 'new' ) {
985                 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
986             } else {
987                 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
988             }
989         } );
991         $( '#show-advanced-search' ).click( function() {
992             showAdvancedSearch();
994             return false;
995         } );
997         $('#advanced-search').submit( function() {
998             startAdvancedSearch();
1000             return false;
1001         } );
1003         $( document ).on( 'click', 'a.search-nav', function() {
1004             if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1005                 $("#search-overlay").show();
1006             }
1008             return false;
1009         });
1011         $( document ).on( 'click', 'th[data-sort-label]', function() {
1012             var direction;
1014             if ( $( this ).hasClass( 'sorting_asc' ) ) {
1015                 direction = 'desc';
1016             } else {
1017                 direction = 'asc';
1018             }
1020             if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1021                 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1023                 $("#search-overlay").show();
1024             }
1026             return false;
1027         });
1029         $( document ).on( 'change', 'input.search-toggle-server', function() {
1030             var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1031             server.checked = this.checked;
1033             if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1034                 $("#search-overlay").show();
1035             }
1036         } );
1038         // Key bindings
1039         bindGlobalKeys();
1041         // Setup UI
1042         $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1043             $(this).modal({ show: false });
1044         } );
1046         var $quicksearch = $('#quicksearch fieldset');
1047         $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1048             position: 'absolute',
1049             top: $quicksearch.offset().top,
1050             left: $quicksearch.offset().left,
1051             height: $quicksearch.outerHeight(),
1052             width: $quicksearch.outerWidth(),
1053         }).appendTo(document.body).hide();
1055         var prevAlerts = [];
1056         humanMsg.logMsg = function(msg, options) {
1057             $('#show-alerts').popover('hide');
1058             prevAlerts.unshift('<li>' + msg + '</li>');
1059             prevAlerts.splice(5, 999); // Truncate old messages
1060         };
1062         $('#show-alerts').popover({
1063             html: true,
1064             placement: 'bottom',
1065             content: function() {
1066                 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1067             },
1068         });
1070         $('#show-shortcuts').popover({
1071             html: true,
1072             placement: 'bottom',
1073             content: function() {
1074                 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1075             },
1076         });
1078         $('#new-record' ).click( function() {
1079             if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1081             openRecord( 'new/', editor );
1082             return false;
1083         } );
1085         // Start editor
1086         Preferences.Load( [% USER_INFO.borrowernumber || 0 %] );
1087         displayPreferences(editor);
1088         makeAuthorisedValueWidgets( '' );
1089         Search.Init( {
1090             page_size: 20,
1091             onresults: function(data) { showSearchResults( editor, data ) },
1092             onerror: handleSearchError,
1093         } );
1095         function finishCb( data ) {
1096             if ( data.error ) openRecord( 'new/', editor, finishCb );
1098             Resources.GetAll().done( function() {
1099                 $("#loading").hide();
1100                 $( window ).resize( onResize ).resize();
1101                 editor.focus();
1102             } );
1103         }
1105         if ( "[% auth_forwarded_hash %]" ) {
1106             document.location.hash = "[% auth_forwarded_hash %]";
1107         }
1109         if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1110             openRecord( 'new/', editor, finishCb );
1111         }
1112     } );
1113 } )();
1115 </script>