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 $( '#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( _("Something went wrong, cannot save"), { className: 'humanError' } );
886 } else if ( !result.error ) {
887 humanMsg.displayAlert( _("Record saved "), { className: 'humanSuccess' } );
890 $.each( result.errors || [], function( undef, error ) {
891 switch ( error.type ) {
893 editor.addError( error.line, _("Invalid tag number") );
896 editor.addError( error.line, _("Invalid indicators") );
899 editor.addError( error.line, _("Tag has no subfields") );
902 editor.addError( null, _("Missing mandatory tag: ") + error.tag );
904 case 'missingSubfield':
905 if ( error.subfield == '@' ) {
906 editor.addError( error.line, _("Missing control field contents") );
908 editor.addError( error.line, _("Missing mandatory subfield: ‡") + error.subfield );
911 case 'unrepeatableTag':
912 editor.addError( error.line, _("Tag ") + error.tag + _(" cannot be repeated") );
914 case 'unrepeatableSubfield':
915 editor.addError( error.line, _("Subfield ‡") + error.subfield + _(" cannot be repeated") );
917 case 'itemTagUnsupported':
918 editor.addError( error.line, _("Item tags cannot currently be saved") );
923 $( '#save-record' ).find('i').attr( 'class', 'fa fa-hdd-o' );
925 if ( result.error ) {
926 // Reset backend info
927 setSource( [ state.backend, state.recordID ] );
931 var backend = $( this ).data( 'backend' ) || ( state.saveBackend );
932 if ( state.backend == backend ) {
933 saveRecord( backend + '/' + state.recordID, editor, finishCb );
935 saveRecord( backend + '/', editor, finishCb );
941 $('#import-records').click( function() {
942 $('#import-records-input')
944 .change( function() {
945 if ( !this.files || !this.files.length ) return;
947 var file = this.files[0];
948 var reader = new FileReader();
950 reader.onload = function() {
951 var record = new MARC.Record();
953 if ( /\.(mrc|marc|iso|iso2709|marcstd)$/.test( file.name ) ) {
954 record.loadISO2709( reader.result );
955 } else if ( /\.(xml|marcxml)$/.test( file.name ) ) {
956 record.loadMARCXML( reader.result );
958 humanMsg.displayAlert( _("Unknown record type, cannot import"), { className: 'humanError' } );
962 if (record.marc8_corrupted) humanMsg.displayMsg( '<h3>' + _("Possible record corruption") + '</h3><p>' + _("Record not marked as UTF-8, may be corrupted") + '</p>', { className: 'humanError' } );
964 editor.displayRecord( record );
967 reader.readAsText( file );
974 $('#open-macros').click( function() {
975 $('#macro-ui').modal('show');
980 $('#run-macro').click( function() {
981 var result = Macros.Run( editor, $('#macro-format').val(), macroEditor.getValue() );
983 if ( !result.errors.length ) {
984 $('#macro-ui').modal('hide');
985 editor.focus(); //Return cursor to editor after macro run
990 $.each( result.errors, function() {
991 var error = '<b>' + _("Line ") + (this.line + 1) + ':</b> ';
993 switch ( this.error ) {
994 case 'failed': error += _("failed to run"); break;
995 case 'unrecognized': error += _("unrecognized command"); break;
1001 humanMsg.displayMsg( '<h3>' + _("Failed to run macro:") + '</h3><ul><li>' + errors.join('</li><li>') + '</li></ul>', { className: 'humanError' } );
1006 $('#delete-macro').click( function() {
1007 if ( !macroEditor.activeMacro || !confirm( _("Are you sure you want to delete this macro?") ) ) return;
1009 storeMacro( macroEditor.activeMacro, undefined );
1011 loadMacro( undefined );
1016 $( '#switch-editor' ).click( function() {
1017 if ( !confirm( _("Any changes will not be saved. Continue?") ) ) return;
1019 $.cookie( 'catalogue_editor_[% logged_in_user.borrowernumber | html %]', 'basic', { expires: 365, path: '/' } );
1021 if ( state.backend == 'catalog' ) {
1022 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl?biblionumber=' + state.recordID;
1023 } else if ( state.backend == 'new' ) {
1024 window.location = '/cgi-bin/koha/cataloguing/addbiblio.pl';
1026 humanMsg.displayAlert( _("Cannot open this record in the basic editor"), { className: 'humanError' } );
1030 $( '#show-advanced-search' ).click( function() {
1031 showAdvancedSearch();
1036 $('#advanced-search').submit( function() {
1037 startAdvancedSearch();
1042 $( document ).on( 'click', 'a.search-nav', function() {
1043 if ( Search.Fetch( { offset: $( this ).data( 'offset' ) } ) ) {
1044 $("#search-overlay").show();
1050 $( document ).on( 'click', 'th[data-sort-label]', function() {
1053 if ( $( this ).hasClass( 'sorting_asc' ) ) {
1059 if ( Search.Fetch( { sort_key: $( this ).data( 'sort-label' ), sort_direction: direction } ) ) {
1060 showSearchSorting( $( this ).data( 'sort-label' ), direction );
1062 $("#search-overlay").show();
1068 $( document ).on( 'change', 'input.search-toggle-server', function() {
1069 var server = z3950Servers[ $( this ).closest('li').data('server-id') ];
1070 server.checked = this.checked;
1072 if ( $('#search-results-ui').is( ':visible' ) && Search.Fetch() ) {
1073 $("#search-overlay").show();
1081 $("#advanced-search-ui, #search-results-ui, #macro-ui").each( function() {
1082 $(this).modal({ show: false });
1085 var $quicksearch = $('#quicksearch fieldset');
1086 $('<div id="quicksearch-overlay"><h3>' + _("Search unavailable") + '</h3> <p></p></div>').css({
1087 position: 'absolute',
1088 top: $quicksearch.offset().top,
1089 left: $quicksearch.offset().left,
1090 height: $quicksearch.outerHeight(),
1091 width: $quicksearch.outerWidth(),
1092 }).appendTo(document.body).hide();
1094 var prevAlerts = [];
1095 humanMsg.logMsg = function(msg, options) {
1096 $('#show-alerts').popover('hide');
1097 prevAlerts.unshift('<li>' + msg + '</li>');
1098 prevAlerts.splice(5, 999); // Truncate old messages
1101 $('#show-alerts').popover({
1103 placement: 'bottom',
1104 content: function() {
1105 return '<div id="alerts-container"><ul>' + prevAlerts.join('') + '</ul></div>';
1109 $('#show-shortcuts').popover({
1111 placement: 'bottom',
1112 content: function() {
1113 return '<div id="shortcuts-container">' + $('#shortcuts-contents').html() + '</div>';
1117 $('#new-record' ).click( function() {
1118 if ( editor.modified && !confirm( _("Are you sure you want to erase your changes?") ) ) return;
1120 openRecord( 'new/', editor );
1124 window.onbeforeunload = function() {
1125 if(editor.modified )
1128 { return undefined; }
1131 $('a.change-framework').click( function() {
1132 $("#loading").show();
1133 editor.setFrameworkCode(
1134 $(this).data( 'frameworkcode' ),
1136 function ( error ) {
1137 if ( typeof error !== 'undefined' ) {
1138 humanMsg.displayAlert( _("Failed to change framework"), { className: 'humanError' } );
1140 $('#loading').hide();
1146 Preferences.Load( [% logged_in_user.borrowernumber || 0 | html %] );
1147 displayPreferences(editor);
1148 makeAuthorisedValueWidgets( '' );
1151 onresults: function(data) { showSearchResults( editor, data ) },
1152 onerror: handleSearchError,
1155 function finishCb( data ) {
1157 humanMsg.displayAlert( data.error );
1158 openRecord( 'new/', editor, finishCb );
1161 Resources.GetAll().done( function() {
1162 $("#loading").hide();
1163 $( window ).resize( onResize ).resize();
1168 if ( "[% auth_forwarded_hash | html %]" ) {
1169 document.location.hash = "[% auth_forwarded_hash | html %]";
1172 if ( !document.location.hash || !openRecord( document.location.hash.slice(1), editor, finishCb ) ) {
1173 openRecord( 'new/', editor, finishCb );