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 %]
12 [% FOREACH shortcut IN shortcuts -%]
13 var [% shortcut.shortcut_name | html %] = "[% shortcut.shortcut_keys | html %]";
16 [%- FOREACH authtag = authtags -%]
17 [% authtag.tagfield | html %]: {
18 subfield: '[% authtag.tagsubfield | html %]',
19 authtypecode: '[% authtag.authtypecode | html %]',
24 baseUrl: '[% interface | html %]/lib/koha/cateditor/',
27 marcflavour: '[% marcflavour | html %]',
28 themelang: '[% themelang | html %]',
35 [% IF marcflavour == 'MARC21' %]
36 [% PROCESS 'cateditor-widgets-marc21.inc' %]
38 <script>var editorWidgets = {};</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 ) {
44 'koha:biblioserver': {
45 name: _("Local catalog"),
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 %],
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
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") ],
76 saveBackend: 'catalog',
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, {
94 var $result = $( '<span class="subfield-widget"></span>' );
98 postCreate: function() {
99 var value = defaultvalue;
102 $.each( authvals, function() {
103 if ( this.value == widget.text ) {
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>' );
115 $node.val( this.text );
117 $node.change( $.proxy( function() {
118 this.setText( $node.val() );
121 makeTemplate: function() {
129 function bindGlobalKeys() {
130 shortcut.add( 'ctrl+s', function(event) {
131 $( '#save-record' ).click();
133 event.preventDefault();
136 shortcut.add( 'alt+ctrl+k', function(event) {
137 $( '#search-by-keywords' ).focus();
142 shortcut.add( 'alt+ctrl+a', function(event) {
143 $( '#search-by-author' ).focus();
148 shortcut.add( 'alt+ctrl+i', function(event) {
149 $( '#search-by-isbn' ).focus();
154 shortcut.add( 'alt+ctrl+t', function(event) {
155 $( '#search-by-title' ).focus();
160 shortcut.add( 'ctrl+h', function() {
161 var field = editor.getCurrentField();
163 if ( !field ) return;
165 window.open( getFieldHelpURL( field.tag ) );
168 $('#quicksearch .search-box').each( function() {
169 shortcut.add( 'enter', $.proxy( function() {
172 $('#quicksearch .search-box').each( function() {
173 if ( !this.value ) return;
175 terms.push( [ $(this).data('qualifier'), this.value ] );
178 if ( !terms.length ) return;
180 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
181 $("#search-overlay").show();
186 }, this), { target: this, type: 'keypress' } );
190 function getFieldHelpURL( tag ) {
191 [% IF Koha.Preference('marcfielddocurl') %]
192 var docurl = "[% Koha.Preference('marcfielddocurl').replace('"','"') | html %]";
193 docurl = docurl.replace("{MARC}", "[% marcflavour | html %]");
194 docurl = docurl.replace("{FIELD}", ""+tag);
195 docurl = docurl.replace("{LANG}", "[% lang | html %]");
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";
205 return "http://www.loc.gov/marc/bibliographic/bd9xx.html";
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
213 if ( tag == '000' ) {
214 return "http://archive.ifla.org/VI/3/p1996-1/uni.htm";
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;
229 titleForRecord: _("Editing new record"),
230 get: function( id, callback ) {
231 record = new MARC.Record();
232 KohaBackend.FillRecord( '', record );
238 titleForRecord: _("Editing new full record"),
239 get: function( id, callback ) {
240 record = new MARC.Record();
241 KohaBackend.FillRecord( '', record, true );
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 );
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 ] } );
259 KohaBackend.CreateRecord( record, finishCb );
263 titleForRecord: _("Editing catalog record #{ID}"),
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}" },
268 saveLabel: _("Save to catalog"),
269 get: function( id, callback ) {
270 if ( !id ) return false;
272 KohaBackend.GetRecord( id, callback );
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 ] } );
280 KohaBackend.SaveRecord( id, record, finishCb );
282 KohaBackend.CreateRecord( record, finishCb );
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' );
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' );
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" ) } );
311 callback( backends.search.records[ id ] );
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>' );
332 $( 'title', document.head ).html( _("Koha › Cataloging › ") + backend.titleForRecord.replace( '{ID}', parts[1] ) );
333 $('#save-record span').text( backends[ state.saveBackend ].saveLabel );
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 } );
351 var errors = KohaBackend.ValidateRecord( '', record );
352 if ( errors.length ) {
353 state.saving = false;
354 callback( { error: 'invalid', errors: errors } );
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 );
369 setSource(data.newId);
371 setSource( [ state.backend, state.recordID ] );
374 if (callback) callback( data );
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 );
390 if (callback) callback(record);
396 function openRecord( recid, editor, callback ) {
397 return loadRecord( recid, editor, function ( record ) {
398 setSource( recid.split('/') );
400 if (callback) callback( record );
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>' );
410 $('#advanced-search-ui').modal('show');
413 function startAdvancedSearch() {
416 $('#advanced-search-ui .search-box').each( function() {
417 if ( !this.value ) return;
419 terms.push( [ $(this).data('qualifier'), this.value ] );
422 if ( !terms.length ) return;
424 if ( Search.Run( z3950Servers, Search.JoinTerms(terms) ) ) {
425 $('#advanced-search-ui').modal('hide');
426 $("#search-overlay").show();
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');
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' ) {
445 $th.attr( 'class', 'sorting_asc' );
448 $th.attr( 'class', 'sorting_desc' );
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 ) {
466 } else if ( num_fetched < data.num_hits[server_id] ) {
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>' );
473 var seenColumns = {};
475 $.each( data.hits, function( undef, hit ) {
476 $.each( hit.metadata, function(key) {
477 seenColumns[key] = true;
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>' );
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] );
506 // Otherwise, fallthrough
509 hit.id = 'search/' + hit.server + ':' + hit.index;
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>';
521 result += '<td class="infocol"> </td>';
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] );
540 if ( $marc_column.is(':visible') ) {
541 $tr.find('.marc-link').text( _("View MARC") );
542 $info_columns.show();
545 $tr.find('.marc-link').text( _("Hide MARC") );
547 $info_columns.hide();
552 $tr.find( '.open-link' ).click( function() {
553 $( '#search-results-ui' ).modal('hide');
554 openRecord( hit.id, editor );
558 $tr.find( '.substitute-link' ).click( function() {
559 $( '#search-results-ui' ).modal('hide');
560 loadRecord( hit.id, editor );
564 $('#searchresults tbody').append( $tr );
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">«</span> ' + _("Previous") + '</a></li>' );
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>' );
579 pages.push( ' <li><a class="search-nav" href="#" data-offset="' + ( page * data.page_size ) + '">' + ( page + 1 ) + '</a></li>' );
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">»</span></a></li>' );
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 ) + '%' } );
597 $overlay.find('.bar').css( { display: 'block', width: '100%' } );
599 $('#searchresults')[0].focus();
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' } );
610 function handleSearchError(error) {
611 if (error.code == 1) {
612 invalidateSearchResults();
615 humanMsg.displayMsg( '<h3>' + _("Internal search error") + '</h3><p>' + error.responseText + '</p><p>' + _("Please refresh the page and try again.") + '</p>', { className: 'humanError' } );
619 function handleSearchInitError(error) {
620 $('#quicksearch-overlay').fadeIn().find('p').text(error);
623 // Preference functions
624 function showPreference( pref ) {
625 var value = Preferences.user[pref];
629 $( '#set-field-widgets' ).text( value ? _("Show fields verbatim") : _("Show helpers for fixed and coded fields") );
632 $( '#editor .CodeMirror' ).css( { fontFamily: value } );
636 $( '#editor .CodeMirror' ).css( { fontSize: value } );
640 // Macros loaded on first show of modal
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;
652 function bindPreference( editor, pref ) {
653 function _addHandler( sel, event, handler ) {
654 $( sel ).on( event, function (e) {
656 handler( e, Preferences.user[pref] );
657 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
658 showPreference(pref);
664 _addHandler( '#set-field-widgets', 'click', function( e, oldValue ) {
665 editor.setUseWidgets( Preferences.user.fieldWidgets = !Preferences.user.fieldWidgets );
669 _addHandler( '#prefs-menu .set-font', 'click', function( e, oldValue ) {
670 Preferences.user.font = $( e.target ).css( 'font-family' );
674 _addHandler( '#prefs-menu .set-fontSize', 'click', function( e, oldValue ) {
675 Preferences.user.fontSize = $( e.target ).css( 'font-size' );
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 %] );
688 function displayPreferences( editor ) {
689 $.each( Preferences.user, function( pref, value ) {
690 showPreference( pref );
691 bindPreference( editor, pref );
696 function loadMacro( name ) {
697 $( '#macro-list li' ).removeClass( 'active' );
698 macroEditor.activeMacro = name;
701 macroEditor.setValue( '' );
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 if ( macro.history ) macroEditor.setHistory( macro.history );
712 function storeMacro( name, macro ) {
714 Preferences.user.macros[name] = macro;
716 delete Preferences.user.macros[name];
719 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
722 function showSavedMacros( macros ) {
723 var scrollTop = $('#macro-list').scrollTop();
724 $( '#macro-list' ).empty();
725 var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
726 return $.extend( { name: name }, macro );
728 macro_list.sort( function( a, b ) {
729 return a.name.localeCompare(b.name);
731 $.each( macro_list, function( undef, macro ) {
732 var $li = $( '<li data-name="' + macro.name + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
733 $li.click( function() {
734 loadMacro(macro.name);
737 if ( macro.name == macroEditor.activeMacro ) $li.addClass( 'active' );
738 var modified = macro.modified && new Date(macro.modified);
739 $li.find( '.macro-info' ).append(
740 '<li><span class="label">' + _("Last changed:") + '</span>' +
741 ( modified ? ( modified.toLocaleDateString() + ', ' + modified.toLocaleTimeString() ) : _("never") ) + '</li>'
743 $('#macro-list').append($li);
745 var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
746 $new_li.click( function() {
747 // TODO: make this a bit less retro
748 var name = prompt(_("Please enter the name for the new macro:"));
751 if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
755 $('#macro-list').append($new_li);
756 $('#macro-list').scrollTop(scrollTop);
759 function saveMacro() {
760 var name = macroEditor.activeMacro;
762 if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() ) return;
764 macroEditor.savedGeneration = macroEditor.changeGeneration();
765 // storeMacro( name, { contents: macroEditor.getValue(), modified: (new Date()).valueOf(), history: macroEditor.getHistory(), format: $('#macro-format').val() } );
766 $('#macro-save-message').text(_("Saved"));
770 $(document).ready( function() {
772 editor = new MARCEditor( {
773 onCursorActivity: function() {
774 $('#status-tag-info').empty();
775 $('#status-subfield-info').empty();
777 var field = editor.getCurrentField();
778 var cur = editor.getCursor();
780 if ( !field ) return;
782 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
783 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
786 $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> ' + taginfo.lib );
788 var subfield = field.getSubfieldAt( cur.ch );
789 if ( !subfield ) return;
791 var subfieldinfo = taginfo.subfields[ subfield.code ];
792 $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
794 if ( subfieldinfo ) {
795 $('#status-subfield-info').append( subfieldinfo.lib );
797 $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
800 $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
803 position: function (elt) { $(elt).insertAfter('#toolbar') },
806 // Automatically detect resizes and change the height of the editor and position of modals.
807 var resizeTimer = null;
808 function onResize() {
809 if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
812 var pos = $('#editor .CodeMirror').offset();
813 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
815 $('.modal-body').each( function() {
816 $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
821 $( '#macro-ui' ).on( 'shown.bs.modal', function() {
822 if ( macroEditor ) return;
824 macroEditor = CodeMirror(
825 $('#macro-editor')[0],
828 'Ctrl-D': function( cm ) {
829 var cur = cm.getCursor();
831 cm.replaceRange( "‡", cur, null );
840 macroEditor.on( 'change', function( cm, change ) {
841 $('#macro-save-message').empty();
842 if ( change.origin == 'setValue' ) return;
844 if ( saveTimeout ) clearTimeout( saveTimeout );
845 saveTimeout = setTimeout( function() {
855 var saveableBackends = [];
856 $.each( backends, function( id, backend ) {
857 if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
859 saveableBackends.sort();
860 $.each( saveableBackends, function( undef, backend ) {
861 $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
865 $( '#save-record, #save-dropdown a' ).click( function() {
866 $( '#save-record' ).find('i').attr( 'class', 'fa fa-spinner fa-spin' ).siblings( 'span' ).text( _("Saving...") );
868 function finishCb(result) {
869 if ( result.error == 'syntax' ) {
870 humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
871 } else if ( result.error == 'invalid' ) {
872 humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
873 } else if ( result.error ) {
874 humanMsg.displayAlert( _("Something went wrong, cannot save"), { className: 'humanError' } );
875 } else if ( !result.error ) {
876 humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
879 $.each( result.errors || [], function( undef, error ) {
880 switch ( error.type ) {
882 editor.addError( error.line, _("Invalid tag number") );
885 editor.addError( error.line, _("Invalid indicators") );
888 editor.addError( error.line, _("Tag has no subfields") );
891 editor.addError( null, _("Missing mandatory tag: ") + error.tag );
893 case 'missingSubfield':
894 if ( error.subfield == '@' ) {
895 editor.addError( error.line, _("Missing control field contents") );
897 editor.addError( error.line, _("Missing mandatory subfield: ‡") + error.subfield );
900 case 'unrepeatableTag':
901 editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
903 case 'unrepeatableSubfield':
904 editor.addError( error.line, _("Subfield ‡") + error.subfield + _(" cannot be repeated") );
906 case 'itemTagUnsupported':
907 editor.addError( error.line, _("Item tags cannot currently be saved") );
912 $( '#save-record' ).find('i').attr( 'class', 'fa fa-hdd-o' );
914 if ( result.error ) {
915 // Reset backend info
916 setSource( [ state.backend, state.recordID ] );
920 var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
921 if ( state.backend == backend ) {
922 saveRecord( backend + '/' + state.recordID, editor, finishCb );
924 saveRecord( backend + '/', editor, finishCb );
930 $('#import-records').click( function() {
931 $('#import-records-input')
933 .change( function() {
934 if ( !this.files || !this.files.length ) return;
936 var file = this.files[0];
937 var reader = new FileReader();
939 reader.onload = function() {
940 var record = new MARC.Record();
942 if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
943 record.loadISO2709( reader.result );
944 } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
945 record.loadMARCXML( reader.result );
947 humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
951 if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
953 editor.displayRecord( record );
956 reader.readAsText( file );
963 $('#open-macros').click( function() {
964 $('#macro-ui').modal('show');
969 $('#run-macro').click( function() {
970 var result = Macros.Run( editor, 'rancor', macroEditor.getValue() );
972 if ( !result.errors.length ) {
973 $('#macro-ui').modal('hide');
974 editor.focus(); //Return cursor to editor after macro run
979 $.each( result.errors, function() {
980 var error = '<b>' + _("Line ") + (this.line + 1) + ':</b> ';
982 switch ( this.error ) {
983 case 'failed': error += _("failed to run"); break;
984 case 'unrecognized': error += _("unrecognized command"); break;
990 humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
995 $('#delete-macro').click( function() {
996 if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
998 storeMacro( macroEditor.activeMacro, undefined );
1000 loadMacro( undefined );
1005 $( '#switch-editor' ).click( function() {
1006 if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
1008 $.cookie( 'catalogue_editor_[% logged_in_user.borrowernumber | html %]', 'basic', { expires: 365, path: '/' } );
1010 if ( state.backend == 'catalog' ) {
1011 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
1012 } else if ( state.backend == 'new' ) {
1013 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
1015 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
1019 $( '#show-advanced-search' ).click( function() {
1020 showAdvancedSearch();
1025 $('#advanced-search').submit( function() {
1026 startAdvancedSearch();
1031 $( document ).on( 'click', 'a.search-nav', function() {
1032 if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1033 $("#search-overlay").show();
1039 $( document ).on( 'click', 'th[data-sort-label]', function() {
1042 if ( $( this ).hasClass( 'sorting_asc' ) ) {
1048 if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1049 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1051 $("#search-overlay").show();
1057 $( document ).on( 'change', 'input.search-toggle-server', function() {
1058 var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1059 server.checked = this.checked;
1061 if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1062 $("#search-overlay").show();
1070 $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1071 $(this).modal({ show: false });
1074 var $quicksearch = $('#quicksearch fieldset');
1075 $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1076 position: 'absolute',
1077 top: $quicksearch.offset().top,
1078 left: $quicksearch.offset().left,
1079 height: $quicksearch.outerHeight(),
1080 width: $quicksearch.outerWidth(),
1081 }).appendTo(document.body).hide();
1083 var prevAlerts = [];
1084 humanMsg.logMsg = function(msg, options) {
1085 $('#show-alerts').popover('hide');
1086 prevAlerts.unshift('<li>' + msg + '</li>');
1087 prevAlerts.splice(5, 999); // Truncate old messages
1090 $('#show-alerts').popover({
1092 placement: 'bottom',
1093 content: function() {
1094 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1098 $('#show-shortcuts').popover({
1100 placement: 'bottom',
1101 content: function() {
1102 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1106 $('#new-record' ).click( function() {
1107 if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1109 openRecord( 'new/', editor );
1113 window.onbeforeunload = function() {
1114 if(editor.modified )
1117 { return undefined; }
1120 $('a.change-framework').click( function() {
1121 $("#loading").show();
1122 editor.setFrameworkCode(
1123 $(this).data( 'frameworkcode' ),
1125 function ( error ) {
1126 if ( typeof error !== 'undefined' ) {
1127 humanMsg.displayAlert( _("Failed to change framework"), { className: 'humanError' } );
1129 $('#loading').hide();
1135 Preferences.Load( [% logged_in_user.borrowernumber || 0 | html %] );
1136 displayPreferences(editor);
1137 makeAuthorisedValueWidgets( '' );
1140 onresults: function(data) { showSearchResults( editor, data ) },
1141 onerror: handleSearchError,
1144 function finishCb( data ) {
1146 humanMsg.displayAlert( data.error );
1147 openRecord( 'new/', editor, finishCb );
1150 Resources.GetAll().done( function() {
1151 $("#loading").hide();
1152 $( window ).resize( onResize ).resize();
1157 if ( "[% auth_forwarded_hash | html %]" ) {
1158 document.location.hash = "[% auth_forwarded_hash | html %]";
1161 if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1162 openRecord( 'new/', editor, finishCb );