3 [% Asset.js("lib/codemirror/codemirror-compressed.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 $( '#macro-format' ).val( macro.format || 'its' );
710 if ( macro.history ) macroEditor.setHistory( macro.history );
713 function storeMacro( name, macro ) {
715 Preferences.user.macros[name] = macro;
717 delete Preferences.user.macros[name];
720 Preferences.Save( [% logged_in_user.borrowernumber | html %] );
723 function showSavedMacros( macros ) {
724 var scrollTop = $('#macro-list').scrollTop();
725 $( '#macro-list' ).empty();
726 var macro_list = $.map( Preferences.user.macros, function( macro, name ) {
727 return $.extend( { name: name }, macro );
729 macro_list.sort( function( a, b ) {
730 return a.name.localeCompare(b.name);
732 $.each( macro_list, function( undef, macro ) {
733 var $li = $( '<li data-name="' + macro.name + '"><a href="#">' + macro.name + '</a><ol class="macro-info"></ol></li>' );
734 $li.click( function() {
735 loadMacro(macro.name);
738 if ( macro.name == macroEditor.activeMacro ) $li.addClass( 'active' );
739 var modified = macro.modified && new Date(macro.modified);
740 $li.find( '.macro-info' ).append(
741 '<li><span class="label">' + _("Last changed:") + '</span>' +
742 ( modified ? ( modified.toLocaleDateString() + ', ' + modified.toLocaleTimeString() ) : _("never") ) + '</li>'
744 $('#macro-list').append($li);
746 var $new_li = $( '<li class="new-macro"><a href="#">' + _("New macro...") + '</a></li>' );
747 $new_li.click( function() {
748 // TODO: make this a bit less retro
749 var name = prompt(_("Please enter the name for the new macro:"));
752 if ( !Preferences.user.macros[name] ) storeMacro( name, { format: "rancor", contents: "" } );
756 $('#macro-list').append($new_li);
757 $('#macro-list').scrollTop(scrollTop);
760 function saveMacro() {
761 var name = macroEditor.activeMacro;
763 if ( !name || macroEditor.savedGeneration == macroEditor.changeGeneration() ) return;
765 macroEditor.savedGeneration = macroEditor.changeGeneration();
766 storeMacro( name, { contents: macroEditor.getValue(), modified: (new Date()).valueOf(), history: macroEditor.getHistory(), format: $('#macro-format').val() } );
767 $('#macro-save-message').text(_("Saved"));
771 $(document).ready( function() {
773 editor = new MARCEditor( {
774 onCursorActivity: function() {
775 $('#status-tag-info').empty();
776 $('#status-subfield-info').empty();
778 var field = editor.getCurrentField();
779 var cur = editor.getCursor();
781 if ( !field ) return;
783 var taginfo = KohaBackend.GetTagInfo( '', field.tag );
784 $('#status-tag-info').html( '<strong>' + field.tag + ':</strong> ' );
787 $('#status-tag-info').append( '<a href="' + getFieldHelpURL( field.tag ) + '" target="_blank" class="show-field-help" title="' + _("Show help for this tag") + '">[?]</a> ' + taginfo.lib );
789 var subfield = field.getSubfieldAt( cur.ch );
790 if ( !subfield ) return;
792 var subfieldinfo = taginfo.subfields[ subfield.code ];
793 $('#status-subfield-info').html( '<strong>‡' + subfield.code + ':</strong> ' );
795 if ( subfieldinfo ) {
796 $('#status-subfield-info').append( subfieldinfo.lib );
798 $('#status-subfield-info').append( '<em>' + _("Unknown subfield") + '</em>' );
801 $('#status-tag-info').append( '<em>' + _("Unknown tag") + '</em>' );
804 position: function (elt) { $(elt).insertAfter('#toolbar') },
807 // Automatically detect resizes and change the height of the editor and position of modals.
808 var resizeTimer = null;
809 function onResize() {
810 if ( resizeTimer == null ) resizeTimer = setTimeout( function() {
813 var pos = $('#editor .CodeMirror').offset();
814 $('#editor .CodeMirror').height( $(window).height() - pos.top - 24 - $('#changelanguage').height() ); // 24 is hardcoded value but works well
816 $('.modal-body').each( function() {
817 $(this).height( $(window).height() * .8 - $(this).prevAll('.modal-header').height() );
822 $( '#macro-ui' ).on( 'shown.bs.modal', function() {
823 if ( macroEditor ) return;
825 macroEditor = CodeMirror(
826 $('#macro-editor')[0],
829 'Ctrl-D': function( cm ) {
830 var cur = cm.getCursor();
832 cm.replaceRange( "‡", cur, null );
841 macroEditor.on( 'change', function( cm, change ) {
842 $('#macro-save-message').empty();
843 if ( change.origin == 'setValue' ) return;
845 if ( saveTimeout ) clearTimeout( saveTimeout );
846 saveTimeout = setTimeout( function() {
856 var saveableBackends = [];
857 $.each( backends, function( id, backend ) {
858 if ( backend.save ) saveableBackends.push( [ backend.saveLabel, id ] );
860 saveableBackends.sort();
861 $.each( saveableBackends, function( undef, backend ) {
862 $( '#save-dropdown' ).append( '<li><a href="#" data-backend="' + backend[1] + '">' + backend[0] + '</a></li>' );
865 var macro_format_list = $.map( Macros.formats, function( format, name ) {
866 return $.extend( { name: name }, format );
868 macro_format_list.sort( function( a, b ) {
869 return a.description.localeCompare(b.description);
871 $.each( macro_format_list, function() {
872 $('#macro-format').append( '<option value="' + this.name + '">' + this.description + '</option>' );
876 $( '#save-record, #save-dropdown a' ).click( function() {
877 $( '#save-record' ).find('i').attr( 'class', 'fa fa-spinner fa-spin' ).siblings( 'span' ).text( _("Saving...") );
879 function finishCb(result) {
880 if ( result.error == 'syntax' ) {
881 humanMsg.displayAlert( _("Incorrect syntax, cannot save"), { className: 'humanError' } );
882 } else if ( result.error == 'invalid' ) {
883 humanMsg.displayAlert( _("Record structure invalid, cannot save"), { className: 'humanError' } );
884 } else if ( !result.error ) {
885 humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
888 $.each( result.errors || [], function( undef, error ) {
889 switch ( error.type ) {
891 editor.addError( error.line, _("Invalid tag number") );
894 editor.addError( error.line, _("Invalid indicators") );
897 editor.addError( error.line, _("Tag has no subfields") );
900 editor.addError( null, _("Missing mandatory tag: ") + error.tag );
902 case 'missingSubfield':
903 if ( error.subfield == '@' ) {
904 editor.addError( error.line, _("Missing control field contents") );
906 editor.addError( error.line, _("Missing mandatory subfield: ‡") + error.subfield );
909 case 'unrepeatableTag':
910 editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
912 case 'unrepeatableSubfield':
913 editor.addError( error.line, _("Subfield ‡") + error.subfield + _(" cannot be repeated") );
915 case 'itemTagUnsupported':
916 editor.addError( error.line, _("Item tags cannot currently be saved") );
921 $( '#save-record' ).find('i').attr( 'class', 'fa fa-hdd-o' );
923 if ( result.error ) {
924 // Reset backend info
925 setSource( [ state.backend, state.recordID ] );
929 var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
930 if ( state.backend == backend ) {
931 saveRecord( backend + '/' + state.recordID, editor, finishCb );
933 saveRecord( backend + '/', editor, finishCb );
939 $('#import-records').click( function() {
940 $('#import-records-input')
942 .change( function() {
943 if ( !this.files || !this.files.length ) return;
945 var file = this.files[0];
946 var reader = new FileReader();
948 reader.onload = function() {
949 var record = new MARC.Record();
951 if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
952 record.loadISO2709( reader.result );
953 } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
954 record.loadMARCXML( reader.result );
956 humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
960 if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
962 editor.displayRecord( record );
965 reader.readAsText( file );
972 $('#open-macros').click( function() {
973 $('#macro-ui').modal('show');
978 $('#run-macro').click( function() {
979 var result = Macros.Run( editor, $('#macro-format').val(), macroEditor.getValue() );
981 if ( !result.errors.length ) {
982 $('#macro-ui').modal('hide');
983 editor.focus(); //Return cursor to editor after macro run
988 $.each( result.errors, function() {
989 var error = '<b>' + _("Line ") + (this.line + 1) + ':</b> ';
991 switch ( this.error ) {
992 case 'failed': error += _("failed to run"); break;
993 case 'unrecognized': error += _("unrecognized command"); break;
999 humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
1004 $('#delete-macro').click( function() {
1005 if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
1007 storeMacro( macroEditor.activeMacro, undefined );
1009 loadMacro( undefined );
1014 $( '#switch-editor' ).click( function() {
1015 if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
1017 $.cookie( 'catalogue_editor_[% logged_in_user.borrowernumber | html %]', 'basic', { expires: 365, path: '/' } );
1019 if ( state.backend == 'catalog' ) {
1020 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
1021 } else if ( state.backend == 'new' ) {
1022 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
1024 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
1028 $( '#show-advanced-search' ).click( function() {
1029 showAdvancedSearch();
1034 $('#advanced-search').submit( function() {
1035 startAdvancedSearch();
1040 $( document ).on( 'click', 'a.search-nav', function() {
1041 if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1042 $("#search-overlay").show();
1048 $( document ).on( 'click', 'th[data-sort-label]', function() {
1051 if ( $( this ).hasClass( 'sorting_asc' ) ) {
1057 if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1058 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1060 $("#search-overlay").show();
1066 $( document ).on( 'change', 'input.search-toggle-server', function() {
1067 var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1068 server.checked = this.checked;
1070 if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1071 $("#search-overlay").show();
1079 $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1080 $(this).modal({ show: false });
1083 var $quicksearch = $('#quicksearch fieldset');
1084 $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1085 position: 'absolute',
1086 top: $quicksearch.offset().top,
1087 left: $quicksearch.offset().left,
1088 height: $quicksearch.outerHeight(),
1089 width: $quicksearch.outerWidth(),
1090 }).appendTo(document.body).hide();
1092 var prevAlerts = [];
1093 humanMsg.logMsg = function(msg, options) {
1094 $('#show-alerts').popover('hide');
1095 prevAlerts.unshift('<li>' + msg + '</li>');
1096 prevAlerts.splice(5, 999); // Truncate old messages
1099 $('#show-alerts').popover({
1101 placement: 'bottom',
1102 content: function() {
1103 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1107 $('#show-shortcuts').popover({
1109 placement: 'bottom',
1110 content: function() {
1111 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1115 $('#new-record' ).click( function() {
1116 if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1118 openRecord( 'new/', editor );
1122 window.onbeforeunload = function() {
1123 if(editor.modified )
1126 { return undefined; }
1129 $('a.change-framework').click( function() {
1130 $("#loading").show();
1131 editor.setFrameworkCode(
1132 $(this).data( 'frameworkcode' ),
1134 function ( error ) {
1135 if ( typeof error !== 'undefined' ) {
1136 humanMsg.displayAlert( _("Failed to change framework"), { className: 'humanError' } );
1138 $('#loading').hide();
1144 Preferences.Load( [% logged_in_user.borrowernumber || 0 | html %] );
1145 displayPreferences(editor);
1146 makeAuthorisedValueWidgets( '' );
1149 onresults: function(data) { showSearchResults( editor, data ) },
1150 onerror: handleSearchError,
1153 function finishCb( data ) {
1155 humanMsg.displayAlert( data.error );
1156 openRecord( 'new/', editor, finishCb );
1159 Resources.GetAll().done( function() {
1160 $("#loading").hide();
1161 $( window ).resize( onResize ).resize();
1166 if ( "[% auth_forwarded_hash | html %]" ) {
1167 document.location.hash = "[% auth_forwarded_hash | html %]";
1170 if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1171 openRecord( 'new/', editor, finishCb );