3 [% Asset.js("lib/codemirror/codemirror-compressed.js") | $raw %]
4 [% Asset.js("lib/filesaver.js") | $raw %]
5 [% Asset.js("lib/koha/cateditor/marc-mode.js") | $raw %]
6 [% Asset.js("lib/require.js") | $raw %]
9 [%- FOREACH authtag = authtags -%]
10 [% authtag.tagfield | html %]: {
11 subfield: '[% authtag.tagsubfield | html %]',
12 authtypecode: '[% authtag.authtypecode | html %]',
17 baseUrl: '[% interface | html %]/lib/koha/cateditor/',
20 marcflavour: '[% marcflavour | html %]',
21 themelang: '[% themelang | html %]',
28 [% IF marcflavour == 'MARC21' %]
29 [% PROCESS 'cateditor-widgets-marc21.inc' %]
31 <script>var editorWidgets = {};</script>
35 require( [ 'koha-backend', 'search', 'macros', 'marc-editor', 'marc-record', 'preferences', 'resources', 'text-marc', 'widget' ], function( KohaBackend, Search, Macros, MARCEditor, MARC, Preferences, Resources, TextMARC, Widget ) {
37 'koha:biblioserver': {
38 name: _("Local catalog"),
42 [%- FOREACH server = z3950_servers -%]
43 [% server.id | html %]: {
44 name: '[% server.servername | html %]',
45 recordtype: '[% server.recordtype | html %]',
46 checked: [% server.checked ? 'true' : 'false' | html %],
51 // The columns that should show up in a search, in order, and keyed by the corresponding <metadata> tag in the XSL and Pazpar2 config
53 [ "local_number", _("Local number") ],
54 [ "title", _("Title") ],
55 [ "series", _("Series title") ],
56 [ "author", _("Author") ],
57 [ "lccn", _("LCCN") ],
58 [ "isbn", _("ISBN") ],
59 [ "issn", _("ISSN") ],
60 [ "medium", _("Medium") ],
61 [ "edition", _("Edition") ],
62 [ "notes", _("Notes") ],
67 saveBackend: 'catalog',
74 function makeAuthorisedValueWidgets( frameworkCode ) {
75 $.each( KohaBackend.GetAllTagsInfo( frameworkCode ), function( tag, tagInfo ) {
76 $.each( tagInfo.subfields, function( subfield, subfieldInfo ) {
77 if ( !subfieldInfo.authorised_value ) return;
78 var authvals = KohaBackend.GetAuthorisedValues( subfieldInfo.authorised_value );
79 if ( !authvals ) return;
81 var defaultvalue = subfield.defaultvalue || authvals[0].value;
83 Widget.Register( tag + subfield, {
85 var $result = $( '<span class="subfield-widget"></span>' );
89 postCreate: function() {
90 var value = defaultvalue;
93 $.each( authvals, function() {
94 if ( this.value == widget.text ) {
99 this.setText( value );
101 $( '<select></select>' ).appendTo( this.node );
102 var $node = $( this.node ).find( 'select' );
103 $.each( authvals, function( undef, authval ) {
104 $node.append( '<option value="' + authval.value + '"' + (authval.value == value ? ' selected="selected"' : '') + '>' + authval.lib + '</option>' );
106 $node.val( this.text );
108 $node.change( $.proxy( function() {
109 this.setText( $node.val() );
112 makeTemplate: function() {
120 function bindGlobalKeys() {
121 shortcut.add( 'ctrl+s', function(event) {
122 $( '#save-record' ).click();
124 event.preventDefault();
127 shortcut.add( 'alt+ctrl+k', function(event) {
128 $( '#search-by-keywords' ).focus();
133 shortcut.add( 'alt+ctrl+a', function(event) {
134 $( '#search-by-author' ).focus();
139 shortcut.add( 'alt+ctrl+i', function(event) {
140 $( '#search-by-isbn' ).focus();
145 shortcut.add( 'alt+ctrl+t', function(event) {
146 $( '#search-by-title' ).focus();
151 shortcut.add( 'ctrl+h', function() {
152 var field = editor.getCurrentField();
154 if ( !field ) return;
156 window.open( getFieldHelpURL( field.tag ) );
159 $('#quicksearch .search-box').each( function() {
160 shortcut.add( 'enter', $.proxy( function() {
163 $('#quicksearch .search-box').each( function() {
164 if ( !this.value ) return;
166 terms.push( [ $(this).data('qualifier'), this.value ] );
169 if ( !terms.length ) return;
171 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
172 $("#search-overlay").show();
177 }, this), { target: this, type: 'keypress' } );
181 function getFieldHelpURL( tag ) {
182 [% IF Koha.Preference('marcfielddocurl') %]
183 var docurl = "[% Koha.Preference('marcfielddocurl').replace('"','"') | html %]";
184 docurl = docurl.replace("{MARC}", "[% marcflavour | html %]");
185 docurl = docurl.replace("{FIELD}", ""+tag);
186 docurl = docurl.replace("{LANG}", "[% lang | html %]");
188 [% ELSIF ( marcflavour == 'MARC21' ) %]
189 if ( tag == '000' ) {
190 return "http://www.loc.gov/marc/bibliographic/bdleader.html";
191 } else if ( tag >= '090' && tag < '100' ) {
192 return "http://www.loc.gov/marc/bibliographic/bd09x.html";
193 } else if ( tag < '900' ) {
194 return "http://www.loc.gov/marc/bibliographic/bd" + tag + ".html";
196 return "http://www.loc.gov/marc/bibliographic/bd9xx.html";
198 [% ELSIF ( marcflavour == 'UNIMARC' ) %]
199 /* http://archive.ifla.org/VI/3/p1996-1/ is an outdated version of UNIMARC, but
200 seems to be the only version available that can be linked to per tag. More recent
201 versions of the UNIMARC standard are available on the IFLA website only as
204 if ( tag == '000' ) {
205 return "http://archive.ifla.org/VI/3/p1996-1/uni.htm";
208 var url = "http://archive.ifla.org/VI/3/p1996-1/uni" + first + ".htm#";
209 if ( first == '0' ) url += "b";
210 if ( first != '9' ) url += tag;
220 titleForRecord: _("Editing new record"),
221 get: function( id, callback ) {
222 record = new MARC.Record();
223 KohaBackend.FillRecord( '', record );
229 titleForRecord: _("Editing new full record"),
230 get: function( id, callback ) {
231 record = new MARC.Record();
232 KohaBackend.FillRecord( '', record, true );
238 titleForRecord: _("Editing catalog record #{ID}"),
240 { title: _("view"), href: "/cgi-bin/koha/catalogue/detail.pl?biblionumber={ID}" },
241 { title: _("edit items"), href: "/cgi-bin/koha/cataloguing/additem.pl?biblionumber={ID}" },
243 saveLabel: _("Save to catalog"),
244 get: function( id, callback ) {
245 if ( !id ) return false;
247 KohaBackend.GetRecord( id, callback );
249 save: function( id, record, done ) {
250 function finishCb( data ) {
251 done( { error: data.error, newRecord: data.marcxml && data.marcxml[0], newId: data.biblionumber && [ 'catalog', data.biblionumber ] } );
255 KohaBackend.SaveRecord( id, record, finishCb );
257 KohaBackend.CreateRecord( record, finishCb );
262 saveLabel: _("Save as MARC (.mrc) file"),
263 save: function( id, record, done ) {
264 saveAs( new Blob( [record.toISO2709()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.mrc' );
270 saveLabel: _("Save as MARCXML (.xml) file"),
271 save: function( id, record, done ) {
272 saveAs( new Blob( [record.toXML()], { 'type': 'application/octet-stream;charset=utf-8' } ), 'record.xml' );
278 titleForRecord: _("Editing search result"),
279 get: function( id, callback ) {
280 if ( !id ) return false;
281 if ( !backends.search.records[ id ] ) {
282 callback( { error: _( "Invalid record" ) } );
286 callback( backends.search.records[ id ] );
292 function setSource(parts) {
293 state.backend = parts[0];
294 state.recordID = parts[1];
295 state.canSave = backends[ state.backend ].save != null;
296 state.saveBackend = state.canSave ? state.backend : 'catalog';
298 var backend = backends[state.backend];
300 document.location.hash = '#' + parts[0] + '/' + parts[1];
302 $('#title').text( backend.titleForRecord.replace( '{ID}', parts[1] ) );
304 $.each( backend.links || [], function( i, link ) {
305 $('#title').append(' <a target="_blank" href="' + link.href.replace( '{ID}', parts[1] ) + '">(' + link.title + ')</a>' );
307 $( 'title', document.head ).html( _("Koha › Cataloging › ") + backend.titleForRecord.replace( '{ID}', parts[1] ) );
308 $('#save-record span').text( backends[ state.saveBackend ].saveLabel );
311 function saveRecord( recid, editor, callback ) {
312 var parts = recid.split('/');
313 if ( parts.length != 2 ) return false;
315 if ( !backends[ parts[0] ] || !backends[ parts[0] ].save ) return false;
317 editor.removeErrors();
318 var record = editor.getRecord();
320 if ( record.errors ) {
321 state.saving = false;
322 callback( { error: 'syntax', errors: record.errors } );
326 var errors = KohaBackend.ValidateRecord( '', record );
327 if ( errors.length ) {
328 state.saving = false;
329 callback( { error: 'invalid', errors: errors } );
333 backends[ parts[0] ].save( parts[1], record, function(data) {
334 state.saving = false;
336 if (data.newRecord) {
337 var record = new MARC.Record();
338 record.loadMARCXML(data.newRecord);
339 record.frameworkcode = data.newRecord.frameworkcode;
340 editor.displayRecord( record );
344 setSource(data.newId);
346 setSource( [ state.backend, state.recordID ] );
349 if (callback) callback( data );
353 function loadRecord( recid, editor, callback ) {
354 var parts = recid.split('/');
355 if ( parts.length != 2 ) return false;
357 if ( !backends[ parts[0] ] || !backends[ parts[0] ].get ) return false;
359 backends[ parts[0] ].get( parts[1], function( record ) {
360 if ( !record.error ) {
361 editor.displayRecord( record );
365 if (callback) callback(record);
371 function openRecord( recid, editor, callback ) {
372 return loadRecord( recid, editor, function ( record ) {
373 setSource( recid.split('/') );
375 if (callback) callback( record );
380 function showAdvancedSearch() {
381 $('#advanced-search-servers').empty();
382 $.each( z3950Servers, function( server_id, server ) {
383 $('#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>' );
385 $('#advanced-search-ui').modal('show');
388 function startAdvancedSearch() {
391 $('#advanced-search-ui .search-box').each( function() {
392 if ( !this.value ) return;
394 terms.push( [ $(this).data('qualifier'), this.value ] );
397 if ( !terms.length ) return;
399 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
400 $('#advanced-search-ui').modal('hide');
401 $("#search-overlay").show();
406 function showResultsBox(data) {
407 $('#search-top-pages, #search-bottom-pages').find('nav').empty();
408 $('#searchresults thead tr').empty();
409 $('#searchresults tbody').empty();
410 $('#search-serversinfo').empty().append('<li>' + _("Loading...") + '</li>');
411 $('#search-results-ui').modal('show');
414 function showSearchSorting( sort_key, sort_direction ) {
415 var $th = $('#searchresults thead tr th[data-sort-label="' + sort_key + '"]');
416 $th.parent().find( 'th[data-sort-label]' ).attr( 'class', 'sorting' );
418 if ( sort_direction == 'asc' ) {
420 $th.attr( 'class', 'sorting_asc' );
423 $th.attr( 'class', 'sorting_desc' );
427 function showSearchResults( editor, data ) {
428 backends.search.records = {};
430 $('#searchresults thead tr').empty();
431 $('#searchresults tbody').empty();
432 $('#search-serversinfo').empty();
434 $.each( z3950Servers, function( server_id, server ) {
435 var num_fetched = data.num_fetched[server_id];
437 if ( data.errors[server_id] ) {
438 num_fetched = data.errors[server_id];
439 } else if ( num_fetched == null ) {
441 } else if ( num_fetched < data.num_hits[server_id] ) {
445 $('#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>' );
448 var seenColumns = {};
450 $.each( data.hits, function( undef, hit ) {
451 $.each( hit.metadata, function(key) {
452 seenColumns[key] = true;
456 $('#searchresults thead tr').append('<th>' + _("Source") + '</th>');
458 $.each( z3950Labels, function( undef, label ) {
459 if ( seenColumns[ label[0] ] ) {
460 $('#searchresults thead tr').append( '<th class="sorting" data-sort-label="' + label[0] + '">' + label[1] + '</th>' );
464 showSearchSorting( data.sort_key, data.sort_direction );
466 $('#searchresults thead tr').append('<th>' + _("Tools") + '</th>');
468 var bibnumMap = KohaBackend.GetSubfieldForKohaField('biblio.biblionumber');
469 $.each( data.hits, function( undef, hit ) {
470 backends.search.records[ hit.server + ':' + hit.index ] = hit.record;
472 switch ( hit.server ) {
473 case 'koha:biblioserver':
474 var bibnumField = hit.record.field( bibnumMap[0] );
476 if ( bibnumField && bibnumField.hasSubfield( bibnumMap[1] ) ) {
477 hit.id = 'catalog/' + bibnumField.subfield( bibnumMap[1] );
481 // Otherwise, fallthrough
484 hit.id = 'search/' + hit.server + ':' + hit.index;
488 result += '<td class="sourcecol">' + z3950Servers[ hit.server ].name + '</td>';
490 $.each( z3950Labels, function( undef, label ) {
491 if ( !seenColumns[ label[0] ] ) return;
493 if ( hit.metadata[ label[0] ] ) {
494 result += '<td class="infocol">' + hit.metadata[ label[0] ] + '</td>';
496 result += '<td class="infocol"> </td>';
500 result += '<td class="toolscol"><ul><li><a href="#" class="marc-link">' + _("View MARC") + '</a></li>';
501 result += '<li><a href="#" class="open-link">' + ( hit.server == 'koha:biblioserver' ? _("Edit") : _("Import") ) + '</a></li>';
502 if ( state.canSave ) result += '<li><a href="#" class="substitute-link" title="' + _("Replace the current record's contents") + '">' + _("Substitute") + '</a></li>';
503 result += '</ul></td></tr>';
505 var $tr = $( result );
506 $tr.find( '.marc-link' ).click( function() {
507 var $info_columns = $tr.find( '.infocol' );
508 var $marc_column = $tr.find( '.marccol' );
510 if ( !$marc_column.length ) {
511 $marc_column = $( '<td class="marccol" colspan="' + $info_columns.length + '"></td>' ).insertAfter( $info_columns.eq(-1) ).hide();
512 CodeMirror.runMode( TextMARC.RecordToText( hit.record ), 'marc', $marc_column[0] );
515 if ( $marc_column.is(':visible') ) {
516 $tr.find('.marc-link').text( _("View MARC") );
517 $info_columns.show();
520 $tr.find('.marc-link').text( _("Hide MARC") );
522 $info_columns.hide();
527 $tr.find( '.open-link' ).click( function() {
528 $( '#search-results-ui' ).modal('hide');
529 openRecord( hit.id, editor );
533 $tr.find( '.substitute-link' ).click( function() {
534 $( '#search-results-ui' ).modal('hide');
535 loadRecord( hit.id, editor );
539 $('#searchresults tbody').append( $tr );
543 var cur_page = data.offset / data.page_size;
544 var max_page = Math.ceil( data.total_fetched / data.page_size ) - 1;
546 if ( cur_page != 0 ) {
547 pages.push( '<li><a class="search-nav" href="#" data-offset="' + (data.offset - data.page_size) + '"><span aria-hidden="true">«</span> ' + _("Previous") + '</a></li>' );
550 for ( var page = Math.max( 0, cur_page - 9 ); page <= Math.min( max_page, cur_page + 9 ); page++ ) {
551 if ( page == cur_page ) {
552 pages.push( ' <li class="active"><a href="#">' + ( page + 1 ) + '</a></li>' );
554 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + ( page * data.page_size ) + '">' + ( page + 1 ) + '</a></li>' );
558 if ( cur_page < max_page ) {
559 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + (data.offset + data.page_size) + '">' + _("Next") + ' <span aria-hidden="true">»</span></a></li>' );
562 $( '#search-top-pages, #search-bottom-pages' ).find( 'nav' ).html( pages.length > 1 ? ( '<ul class="pagination pagination-sm">' + pages.join( '' ) + '</ul>' ) : '' );
564 var $overlay = $('#search-overlay');
565 $overlay.find('span').text(_("Loading"));
566 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
568 if ( data.activeclients ) {
569 $overlay.find('.bar').css( { display: 'block', width: 100 * ( 1 - data.activeclients / Search.includedServers.length ) + '%' } );
572 $overlay.find('.bar').css( { display: 'block', width: '100%' } );
574 $('#searchresults')[0].focus();
578 function invalidateSearchResults() {
579 var $overlay = $('#search-overlay');
580 $overlay.find('span').text(_("Search expired, please try again"));
581 $overlay.find('.bar').css( { display: 'none' } );
585 function handleSearchError(error) {
586 if (error.code == 1) {
587 invalidateSearchResults();
590 humanMsg.displayMsg( '<h3>' + _("Internal search error") + '</h3><p>' + error.responseText + '</p><p>' + _("Please refresh the page and try again.") + '</p>', { className: 'humanError' } );
594 function handleSearchInitError(error) {
595 $('#quicksearch-overlay').fadeIn().find('p').text(error);
598 // Preference functions
599 function showPreference( pref ) {
600 var value = Preferences.user[pref];
604 $( '#set-field-widgets' ).text( value ? _("Show fields verbatim") : _("Show helpers for fixed and coded fields") );
607 $( '#editor .CodeMirror' ).css( { fontFamily: value } );
611 $( '#editor .CodeMirror' ).css( { fontSize: value } );
615 // Macros loaded on first show of modal
617 case 'selected_search_targets':
618 $.each( z3950Servers, function( server_id, server ) {
619 var saved_val = Preferences.user.selected_search_targets[server_id];
621 if ( saved_val != null ) server.checked = saved_val;
627 function bindPreference( editor, pref ) {
628 function _addHandler( sel, event, handler ) {
629 $( sel ).on( event, function (e) {
631 handler( e, Preferences.user[pref] );
632 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
633 showPreference(pref);
639 _addHandler( '#set-field-widgets', 'click', function( e, oldValue ) {
640 editor.setUseWidgets( Preferences.user.fieldWidgets = !Preferences.user.fieldWidgets );
644 _addHandler( '#prefs-menu .set-font', 'click', function( e, oldValue ) {
645 Preferences.user.font = $( e.target ).css( 'font-family' );
649 _addHandler( '#prefs-menu .set-fontSize', 'click', function( e, oldValue ) {
650 Preferences.user.fontSize = $( e.target ).css( 'font-size' );
653 case 'selected_search_targets':
654 $( document ).on( 'change', 'input.search-toggle-server', function() {
655 var server_id = $( this ).closest('li').data('server-id');
656 Preferences.user.selected_search_targets[server_id] = this.checked;
657 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
663 function displayPreferences( editor ) {
664 $.each( Preferences.user, function( pref, value ) {
665 showPreference( pref );
666 bindPreference( editor, pref );
671 function loadMacro( name ) {
672 $( '#macro-list li' ).removeClass( 'active' );
673 macroEditor.activeMacro = name;
676 macroEditor.setValue( '' );
680 $( '#macro-list li[data-name="' + name + '"]' ).addClass( 'active' );
681 var macro = Preferences.user.macros[name];
682 macroEditor.setValue( macro.contents );
683 macroEditor.setOption( 'readOnly', false );
684 $( '#macro-format' ).val( macro.format || 'its' );
685 if ( macro.history ) macroEditor.setHistory( macro.history );
688 function storeMacro( name, macro ) {
690 Preferences.user.macros[name] = macro;
692 delete Preferences.user.macros[name];
695 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
698 function showSavedMacros( macros ) {
699 var scrollTop = $('#macro-list').scrollTop();
700 $( '#macro-list' ).empty();
701 var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
702 return $.extend( { name: name }, macro );
704 macro_list.sort( function( a, b ) {
705 return a.name.localeCompare(b.name);
707 $.each( macro_list, function( undef, macro ) {
708 var $li = $( '<li data-name="' + macro.name + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
709 $li.click( function() {
710 loadMacro(macro.name);
713 if ( macro.name == macroEditor.activeMacro ) $li.addClass( 'active' );
714 var modified = macro.modified && new Date(macro.modified);
715 $li.find( '.macro-info' ).append(
716 '<li><span class="label">' + _("Last changed:") + '</span>' +
717 ( modified ? ( modified.toLocaleDateString() + ', ' + modified.toLocaleTimeString() ) : _("never") ) + '</li>'
719 $('#macro-list').append($li);
721 var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
722 $new_li.click( function() {
723 // TODO: make this a bit less retro
724 var name = prompt(_("Please enter the name for the new macro:"));
727 if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
731 $('#macro-list').append($new_li);
732 $('#macro-list').scrollTop(scrollTop);
735 function saveMacro() {
736 var name = macroEditor.activeMacro;
738 if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() ) return;
740 macroEditor.savedGeneration = macroEditor.changeGeneration();
741 storeMacro( name, { contents: macroEditor.getValue(), modified: (new Date()).valueOf(), history: macroEditor.getHistory(), format: $('#macro-format').val() } );
742 $('#macro-save-message').text(_("Saved"));
746 $(document).ready( function() {
748 editor = new MARCEditor( {
749 onCursorActivity: function() {
750 $('#status-tag-info').empty();
751 $('#status-subfield-info').empty();
753 var field = editor.getCurrentField();
754 var cur = editor.getCursor();
756 if ( !field ) return;
758 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
759 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
762 $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> ' + taginfo.lib );
764 var subfield = field.getSubfieldAt( cur.ch );
765 if ( !subfield ) return;
767 var subfieldinfo = taginfo.subfields[ subfield.code ];
768 $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
770 if ( subfieldinfo ) {
771 $('#status-subfield-info').append( subfieldinfo.lib );
773 $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
776 $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
779 position: function (elt) { $(elt).insertAfter('#toolbar') },
782 // Automatically detect resizes and change the height of the editor and position of modals.
783 var resizeTimer = null;
784 function onResize() {
785 if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
788 var pos = $('#editor .CodeMirror').position();
789 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
791 $('.modal-body').each( function() {
792 $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
797 $( '#macro-ui' ).on( 'shown.bs.modal', function() {
798 if ( macroEditor ) return;
800 macroEditor = CodeMirror(
801 $('#macro-editor')[0],
804 'Ctrl-D': function( cm ) {
805 var cur = cm.getCursor();
807 cm.replaceRange( "‡", cur, null );
816 macroEditor.on( 'change', function( cm, change ) {
817 $('#macro-save-message').empty();
818 if ( change.origin == 'setValue' ) return;
820 if ( saveTimeout ) clearTimeout( saveTimeout );
821 saveTimeout = setTimeout( function() {
831 var saveableBackends = [];
832 $.each( backends, function( id, backend ) {
833 if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
835 saveableBackends.sort();
836 $.each( saveableBackends, function( undef, backend ) {
837 $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
840 var macro_format_list = $.map( Macros.formats, function( format, name ) {
841 return $.extend( { name: name }, format );
843 macro_format_list.sort( function( a, b ) {
844 return a.description.localeCompare(b.description);
846 $.each( macro_format_list, function() {
847 $('#macro-format').append( '<option value="' + this.name + '">' + this.description + '</option>' );
851 $( '#save-record, #save-dropdown a' ).click( function() {
852 $( '#save-record' ).find('i').attr( 'class', 'fa fa-spinner fa-spin' ).siblings( 'span' ).text( _("Saving...") );
854 function finishCb(result) {
855 if ( result.error == 'syntax' ) {
856 humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
857 } else if ( result.error == 'invalid' ) {
858 humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
859 } else if ( !result.error ) {
860 humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
863 $.each( result.errors || [], function( undef, error ) {
864 switch ( error.type ) {
866 editor.addError( error.line, _("Invalid tag number") );
869 editor.addError( error.line, _("Invalid indicators") );
872 editor.addError( error.line, _("Tag has no subfields") );
875 editor.addError( null, _("Missing mandatory tag: ") + error.tag );
877 case 'missingSubfield':
878 if ( error.subfield == '@' ) {
879 editor.addError( error.line, _("Missing control field contents") );
881 editor.addError( error.line, _("Missing mandatory subfield: ‡") + error.subfield );
884 case 'unrepeatableTag':
885 editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
887 case 'unrepeatableSubfield':
888 editor.addError( error.line, _("Subfield ‡") + error.subfield + _(" cannot be repeated") );
890 case 'itemTagUnsupported':
891 editor.addError( error.line, _("Item tags cannot currently be saved") );
896 $( '#save-record' ).find('i').attr( 'class', 'fa fa-hdd-o' );
898 if ( result.error ) {
899 // Reset backend info
900 setSource( [ state.backend, state.recordID ] );
904 var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
905 if ( state.backend == backend ) {
906 saveRecord( backend + '/' + state.recordID, editor, finishCb );
908 saveRecord( backend + '/', editor, finishCb );
914 $('#import-records').click( function() {
915 $('#import-records-input')
917 .change( function() {
918 if ( !this.files || !this.files.length ) return;
920 var file = this.files[0];
921 var reader = new FileReader();
923 reader.onload = function() {
924 var record = new MARC.Record();
926 if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
927 record.loadISO2709( reader.result );
928 } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
929 record.loadMARCXML( reader.result );
931 humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
935 if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
937 editor.displayRecord( record );
940 reader.readAsText( file );
947 $('#open-macros').click( function() {
948 $('#macro-ui').modal('show');
953 $('#run-macro').click( function() {
954 var result = Macros.Run( editor, $('#macro-format').val(), macroEditor.getValue() );
956 if ( !result.errors.length ) {
957 $('#macro-ui').modal('hide');
958 editor.focus(); //Return cursor to editor after macro run
963 $.each( result.errors, function() {
964 var error = '<b>' + _("Line ") + (this.line + 1) + ':</b> ';
966 switch ( this.error ) {
967 case 'failed': error += _("failed to run"); break;
968 case 'unrecognized': error += _("unrecognized command"); break;
974 humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
979 $('#delete-macro').click( function() {
980 if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
982 storeMacro( macroEditor.activeMacro, undefined );
984 loadMacro( undefined );
989 $( '#switch-editor' ).click( function() {
990 if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
992 $.cookie( 'catalogue_editor_[% logged_in_user.borrowernumber | html %]', 'basic', { expires: 365, path: '/' } );
994 if ( state.backend == 'catalog' ) {
995 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
996 } else if ( state.backend == 'new' ) {
997 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
999 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
1003 $( '#show-advanced-search' ).click( function() {
1004 showAdvancedSearch();
1009 $('#advanced-search').submit( function() {
1010 startAdvancedSearch();
1015 $( document ).on( 'click', 'a.search-nav', function() {
1016 if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1017 $("#search-overlay").show();
1023 $( document ).on( 'click', 'th[data-sort-label]', function() {
1026 if ( $( this ).hasClass( 'sorting_asc' ) ) {
1032 if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1033 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1035 $("#search-overlay").show();
1041 $( document ).on( 'change', 'input.search-toggle-server', function() {
1042 var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1043 server.checked = this.checked;
1045 if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1046 $("#search-overlay").show();
1054 $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1055 $(this).modal({ show: false });
1058 var $quicksearch = $('#quicksearch fieldset');
1059 $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1060 position: 'absolute',
1061 top: $quicksearch.offset().top,
1062 left: $quicksearch.offset().left,
1063 height: $quicksearch.outerHeight(),
1064 width: $quicksearch.outerWidth(),
1065 }).appendTo(document.body).hide();
1067 var prevAlerts = [];
1068 humanMsg.logMsg = function(msg, options) {
1069 $('#show-alerts').popover('hide');
1070 prevAlerts.unshift('<li>' + msg + '</li>');
1071 prevAlerts.splice(5, 999); // Truncate old messages
1074 $('#show-alerts').popover({
1076 placement: 'bottom',
1077 content: function() {
1078 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1082 $('#show-shortcuts').popover({
1084 placement: 'bottom',
1085 content: function() {
1086 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1090 $('#new-record' ).click( function() {
1091 if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1093 openRecord( 'new/', editor );
1097 window.onbeforeunload = function() {
1098 if(editor.modified )
1101 { return undefined; }
1104 $('a.change-framework').click( function() {
1105 $("#loading").show();
1106 editor.setFrameworkCode(
1107 $(this).data( 'frameworkcode' ),
1109 function ( error ) {
1110 if ( typeof error !== 'undefined' ) {
1111 humanMsg.displayAlert( _("Failed to change framework"), { className: 'humanError' } );
1113 $('#loading').hide();
1119 Preferences.Load( [% logged_in_user.borrowernumber || 0 | html %] );
1120 displayPreferences(editor);
1121 makeAuthorisedValueWidgets( '' );
1124 onresults: function(data) { showSearchResults( editor, data ) },
1125 onerror: handleSearchError,
1128 function finishCb( data ) {
1130 humanMsg.displayAlert( data.error );
1131 openRecord( 'new/', editor, finishCb );
1134 Resources.GetAll().done( function() {
1135 $("#loading").hide();
1136 $( window ).resize( onResize ).resize();
1141 if ( "[% auth_forwarded_hash | html %]" ) {
1142 document.location.hash = "[% auth_forwarded_hash | html %]";
1145 if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1146 openRecord( 'new/', editor, finishCb );