/** * Basic sample plugin inserting references elements into CKEditor editing area. * * Based on https://github.com/andykirk/CKEditorFootnotes Version 1.0.9 * */ // Register the plugin within the editor. CKEDITOR.plugins.add( 'effective_references', { reference_ids: [], requires: 'widget', // The plugin initialization logic goes inside this method. init: function(editor) { // Allow `cite` to be editable: CKEDITOR.dtd.$editable['cite'] = 1; // Add some CSS tweaks: var css = '.references{background:#eee; padding:1px 15px;} .references cite{font-style: normal;}'; CKEDITOR.addCss(css); var $this = this; // Force a reorder on startup to make sure all vars are set: (e.g. references store): editor.on('instanceReady', function(evt) { $this.reorderMarkers(editor); }); // Add the reorder change event: editor.on('change', function(evt) { // Copy the references_store as we may be doing a cut: if(!evt.editor.references_tmp) { evt.editor.references_tmp = evt.editor.references_store; } // Prevent no selection errors: if (!evt.editor.getSelection().getStartElement()) { return; } // Don't reorder the markers if editing a cite: var reference_section = evt.editor.getSelection().getStartElement().getAscendant('section'); if (reference_section && reference_section.$.className.indexOf('references') != -1) { return; } // SetTimeout seems to be necessary (it's used in the core but can't be 100% sure why) setTimeout(function(){ $this.reorderMarkers(editor); }, 0 ); }); // Build the initial references widget editables definition: var prefix = editor.config.referencesPrefix ? '-' + editor.config.referencesPrefix : ''; var def = { header: { selector: 'header > *', //allowedContent: '' allowedContent: 'strong em span sub sup;' } }; var contents = jQuery('
' + editor.element.$.textContent + '
') , l = contents.find('.references li').length , i = 1; for (i; i <= l; i++) { def['reference_' + i] = {selector: '#reference' + prefix + '-' + i + ' cite', allowedContent: 'a[href]; cite[*](*); strong em span br'}; } // Register the references widget. editor.widgets.add('references', { // Minimum HTML which is required by this widget to work. requiredContent: 'section(references)', // Check the elements that need to be converted to widgets. upcast: function(element) { return element.name == 'section' && element.hasClass('references'); }, editables: def }); // Register the referencemarker widget. editor.widgets.add('referencemarker', { // Minimum HTML which is required by this widget to work. requiredContent: 'sup[data-reference-id]', // Check the elements that need to be converted to widgets. upcast: function(element) { return element.name == 'sup' && element.attributes['data-reference-id'] != 'undefined'; } }); // Define an editor command that opens our dialog. editor.addCommand('references', new CKEDITOR.dialogCommand('referencesDialog', { // @TODO: This needs work: allowedContent: 'section[*](*);header[*](*);li[*];a[*];cite(*)[*];sup[*]', requiredContent: 'section[*](*);header[*](*);li[*];a[*];cite(*)[*];sup[*]' })); // Create a toolbar button that executes the above command. editor.ui.addButton('EffectiveReferences', { // The text part of the button (if available) and tooptip. label: 'Insert References', // The command to execute on click. command: 'references', // The button placement in the toolbar (toolbar group name). toolbar: 'insert' }); // Register our dialog file. this.path is the plugin folder path. CKEDITOR.dialog.add('referencesDialog', this.path + 'dialogs/references.js'); }, editorContents: function(editor) { if (editor.config.referencesEditorSelector) { return jQuery(eval(editor.config.referencesEditorSelector)).contents() } else { return jQuery('#' + editor.id + '_contents iframe').contents().find('body'); } }, build: function(reference, is_new, editor) { if (is_new) { // Generate new id: reference_id = this.generateReferenceId(); } else { // Existing reference id passed: reference_id = reference; } // Insert the marker: var reference_marker = 'X'; editor.insertHtml(reference_marker); if (is_new) { editor.fire('lockSnapshot'); this.addReference(this.buildReference(reference_id, reference, false, editor), editor); editor.fire('unlockSnapshot'); } this.reorderMarkers(editor); }, buildReference: function(reference_id, reference_text, data, editor) { data ? data : false; var links = ''; var letters = 'abcdefghijklmnopqrstuvwxyz'; var order = data ? data.order.indexOf(reference_id) + 1 : 1; var prefix = editor.config.referencesPrefix ? '-' + editor.config.referencesPrefix : ''; if (data && data.occurrences[reference_id] == 1) { links = '^ '; } else if (data && data.occurrences[reference_id] > 1) { var i = 0 , l = data.occurrences[reference_id] , n = l; for (i; i < l; i++) { links += '' + letters.charAt(i) + ''; if (i < l-1) { links += ', '; } else { links += ' '; } } } reference = '
  • ' + links + '' + reference_text + '
  • '; return reference; }, addReference: function(reference, editor) { $contents = this.editorContents(editor); $references = $contents.find('.references'); if ($references.length == 0) { var container = '

    References

      ' + reference + '
    '; // Move cursor to end of content: var range = editor.createRange(); range.moveToElementEditEnd(range.root); editor.getSelection().selectRanges([range]); // Insert the container: editor.insertHtml(container); } else { $references.find('ol').append(reference); } return; }, generateReferenceId: function() { var id = Math.random().toString(36).substr(2, 5); while (jQuery.inArray(id, this.reference_ids) != -1) { id = String(this.generateReferenceId()); } this.reference_ids.push(id); return id; }, reorderMarkers: function(editor) { editor.fire('lockSnapshot'); var prefix = editor.config.referencesPrefix ? '-' + editor.config.referencesPrefix : ''; $contents = this.editorContents(editor); var data = { order: [], occurrences: {} }; // Check that there's a references section. If it's been deleted the markers are useless: if ($contents.find('.references').length == 0) { $contents.find('sup[data-reference-id]').remove(); editor.fire('unlockSnapshot'); return; } // Find all the markers in the document: var $markers = $contents.find('sup[data-reference-id]'); // If there aren't any, remove the References container: if ($markers.length == 0) { $contents.find('.references').parent().remove(); editor.fire('unlockSnapshot'); return; } // Otherwise reorder the markers: $markers.each(function(){ var reference_id = jQuery(this).attr('data-reference-id') , marker_ref , n = data.order.indexOf(reference_id); // If this is the markers first occurrence: if (n == -1) { // Store the id: data.order.push(reference_id); n = data.order.length; data.occurrences[reference_id] = 1; marker_ref = n + '-1'; } else { // Otherwise increment the number of occurrences: // (increment n due to zero-index array) n++; data.occurrences[reference_id]++; marker_ref = n + '-' + data.occurrences[reference_id]; } // Replace the marker contents: var marker = '[' + n + ']'; jQuery(this).html(marker); }); // Prepare the references_store object: editor.references_store = {}; // Then rebuild the References content to match marker order: var references = ''; var reference_text = ''; var i = 0 , l = data.order.length; for (i; i < l; i++) { reference_id = data.order[i]; reference_text = $contents.find('.references [data-reference-id=' + reference_id + '] cite').html(); // If the references text can't be found in the editor, it may be in the tmp store // following a cut: if (!reference_text) { reference_text = editor.references_tmp[reference_id]; } references += this.buildReference(reference_id, reference_text, data, editor); // Store the references for later use (post cut/paste): editor.references_store[reference_id] = reference_text; } // Insert the references into the list: $contents.find('.references ol').html(references); // Next we need to reinstate the 'editable' properties of the references. // (we have to do this individually due to Widgets 'fireOnce' for editable selectors) var el = $contents.find('.references') , reference_widget; // So first we need to find the right Widget instance: // (I hope there's a better way of doing this but I can't find one) for (i in editor.widgets.instances) { if (editor.widgets.instances[i].name == 'references') { reference_widget = editor.widgets.instances[i]; break; } } // Then we `initEditable` each reference, giving it a unique selector: for (i in data.order) { n = parseInt(i) + 1; reference_widget.initEditable('reference_' + n, {selector: '#reference' + prefix + '-' + n +' cite', allowedContent: 'a[href]; cite[*](*); em strong span'}); } editor.fire('unlockSnapshot'); return; } });