From 62454a291dd5acdd573b600c6ab87baa32742294 Mon Sep 17 00:00:00 2001 From: Andy Kirk Date: Mon, 21 Jul 2014 15:37:58 +0100 Subject: [PATCH] Initial Commit --- README.md | 6 +- footnotes/dialogs/footnotes.js | 152 ++++++++++++++++++ footnotes/icons/footnotes.png | Bin 0 -> 402 bytes footnotes/plugin.js | 273 +++++++++++++++++++++++++++++++++ 4 files changed, 429 insertions(+), 2 deletions(-) create mode 100644 footnotes/dialogs/footnotes.js create mode 100644 footnotes/icons/footnotes.png create mode 100644 footnotes/plugin.js diff --git a/README.md b/README.md index 20ca506..9aa32e5 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -ckeditor-footnotes +CKEditorFootnotes ================== -Footnotes plugin for CKEditor +Footnotes plugin for CKEditor. + +Demo: http://demo.gridlight-design.co.uk/ckeditor-footnotes.html diff --git a/footnotes/dialogs/footnotes.js b/footnotes/dialogs/footnotes.js new file mode 100644 index 0000000..d48352f --- /dev/null +++ b/footnotes/dialogs/footnotes.js @@ -0,0 +1,152 @@ +/** + * The footnotes dialog definition. + * + * Created out of the CKEditor Plugin SDK: + * http://docs.ckeditor.com/#!/guide/plugin_sdk_sample_1 + */ + +// Dialog definition. +CKEDITOR.dialog.add( 'footnotesDialog', function( editor ) { + return { + editor_name: false, + // Basic properties of the dialog window: title, minimum size. + title: 'Manage Footnotes', + minWidth: 400, + minHeight: 200, + footnotes_el: false, + + // Dialog window contents definition. + contents: [ + { + // Definition of the Basic Settings dialog tab (page). + id: 'tab-basic', + label: 'Basic Settings', + + // The tab contents. + elements: [ + { + // Text input field for the footnotes text. + type: 'textarea', + id: 'new_footnote', + class: 'footnote_text', + label: 'New Footnote', + inputStyle: 'height: 100px', + }, + { + // Text input field for the footnotes title (explanation). + type: 'text', + id: 'footnote_id', + name: 'footnote_id', + label: 'No existing footnotes', + + + // Called by the main setupContent call on dialog initialization. + setup: function( element ) { + var dialog = this.getDialog(); + $el = jQuery('#' + this.domId); + + dialog.footnotes_el = $el; + + editor = dialog.getParentEditor(); + // Dynamically add existing footnotes: + $footnotes = jQuery('#' + editor.id + '_contents iframe').contents().find('#footnotes ol'); + $this = this; + + if ($footnotes.length > 0) { + if ($el.find('p').length == 0) { + $el.append('

OR: Choose footnote:

    '); + } else { + $el.find('ol').empty(); + } + + var radios = ''; + $footnotes.find('li').each(function(){ + $item = jQuery(this); + var footnote_id = $item.attr('data-footnote-id'); + radios += '
  1. '; + }); + + $el.children('label,div').css('display', 'none'); + $el.find('ol').html(radios); + $el.find(':radio').change(function(){ + $el.find(':text').val(jQuery(this).val()); + }); + + } else { + $el.children('div').css('display', 'none'); + } + } + } + ] + }, + ], + + // Invoked when the dialog is loaded. + onShow: function() { + this.setupContent(); + + var dialog = this; + CKEDITOR.on( 'instanceLoaded', function( evt ) { + dialog.editor_name = evt.editor.name; + } ); + + + CKEDITOR.replaceAll( function( textarea, config ) { + if (!textarea.className.match(/footnote_text/)) { + return false; + } + + config.toolbarGroups = [ + { name: 'editing', groups: [ 'undo', 'find', 'selection', 'spellchecker' ] }, + { name: 'clipboard', groups: [ 'clipboard' ] }, + { name: 'basicstyles', groups: [ 'basicstyles', 'cleanup' ] }, + { name: 'links' } + ] + config.allowedContent = 'b i; a[!href]'; + config.autoParagraph = false; + config.height = 80; + config.resize_enabled = false; + config.autoGrow_minHeight = 80; + config.extraPlugins = ''; + + config.on = { + focus: function( evt ){ + var $editor_el = jQuery('#' + evt.editor.id + '_contents'); + $editor_el.parents('tr').next().find(':checked').attr('checked', false); + $editor_el.parents('tr').next().find(':text').val(''); + } + }; + return true; + }); + + }, + + // This method is invoked once a user clicks the OK button, confirming the dialog. + onOk: function() { + var dialog = this; + + var footnote_editor = CKEDITOR.instances[dialog.editor_name]; + var footnote_id = dialog.getValueOf('tab-basic', 'footnote_id'); + + editor.fire('saveSnapshot'); + + if (footnote_id == '') { + // No existing id selected, check for new footnote: + var new_footnote = footnote_editor.getData(); + if (new_footnote == '') { + // Nothing entered, so quit: + return; + } else { + // Insert new footnote: + editor.plugins.footnotes.build(editor, new_footnote, true); + } + } else { + // Insert existing footnote: + editor.plugins.footnotes.build(editor, footnote_id, false); + } + // Destroy the editor so it's rebuilt properly next time: + footnote_editor.destroy(); + return; + } + }; +}); \ No newline at end of file diff --git a/footnotes/icons/footnotes.png b/footnotes/icons/footnotes.png new file mode 100644 index 0000000000000000000000000000000000000000..77157440b13aea4705697b239b955a8e3cf7af1e GIT binary patch literal 402 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbKJOS+@4BLl^g zR#s+aX6owdMn*<5GBS2{cJ}u6tgNg$Iy!oKdQwtS1_lNuCMKeyqDo3iKr{Y>L4CB& zDea==0dm_n3*A`Xm#f&y$pJW5J2K;a__YD!vKO2@dATpYC+m^vJL9794J zdOSm>NGbtM' + editor.element.$.textContent + '') + , l = contents.find('#footnotes li').length + , i = 1; + for (i; i <= l; i++) { + def['footnote_' + i] = {selector: '#footnote-' + i +' cite', allowedContent: 'a[href]; cite[*](*); b i em strong span'}; + } + + // Register the footnotes widget. + editor.widgets.add('footnotes', { + + // Minimum HTML which is required by this widget to work. + requiredContent: 'section(footnotes)', + + // Check the elements that need to be converted to widgets. + upcast: function(element) { + return element.name == 'section' && element.hasClass('footnotes'); + }, + + editables: def + }); + + // Register the footnotemarker widget. + editor.widgets.add('footnotemarker', { + + // Minimum HTML which is required by this widget to work. + requiredContent: 'sup[data-footnote-id]', + + // Check the elements that need to be converted to widgets. + upcast: function(element) { + return element.name == 'sup' && element.attributes['data-footnote-id'] != 'undefined'; + }, + }); + + // Define an editor command that opens our dialog. + editor.addCommand('footnotes', new CKEDITOR.dialogCommand('footnotesDialog', { + // @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('Footnotes', { + + // The text part of the button (if available) and tooptip. + label: 'Insert Footnotes', + + // The command to execute on click. + command: 'footnotes', + + // 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('footnotesDialog', this.path + 'dialogs/footnotes.js'); + }, + + + build: function(editor, footnote, is_new) { + editor.fire('lockSnapshot'); + + if (is_new) { + // Generate new id: + footnote_id = this.generateFootnoteId(); + } else { + // Existing footnote id passed: + footnote_id = footnote; + } + + // Insert the marker: + var footnote_marker = 'X'; + + editor.fire('unlockSnapshot'); + editor.insertHtml(footnote_marker); + + if (is_new) { + this.addFootnote(editor, this.buildFootnote(footnote_id, footnote)); + } + + this.reorderMarkers(); + }, + + buildFootnote: function(footnote_id, footnote_text, data) { + data ? data : false; + var links = ''; + var letters = 'abcdefghijklmnopqrstuvwxyz'; + var order = data ? data.order.indexOf(footnote_id) + 1 + : 1; + if (data && data.occurrences[footnote_id] == 1) { + links = '^ '; + } else if (data && data.occurrences[footnote_id] > 1) { + var i = 0 + , l = data.occurrences[footnote_id] + , n = l; + for (i; i < l; i++) { + links += '' + letters.charAt(i) + ''; + if (i < l-1) { + links += ', '; + } else { + links += ' '; + } + } + } + footnote = '
  2. ' + links + '' + footnote_text + '
  3. '; + return footnote; + }, + + addFootnote: function(editor, footnote) { + $contents = jQuery('#' + editor.id + '_contents iframe').contents().find('body'); + $footnotes = $contents.find('#footnotes'); + + if ($footnotes.length == 0) { + var container = '

    Footnotes

      ' + footnote + '
    '; + // 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 { + $footnotes.find('ol').append(footnote); + } + return; + }, + + generateFootnoteId: function() { + var id = Math.random().toString(36).substr(2, 5); + while (jQuery.inArray(id, this.footnote_ids) != -1) { + id = String(this.generateFootnoteId()); + } + this.footnote_ids.push(id); + return id; + }, + + reorderMarkers: function() { + this.editor.fire('lockSnapshot'); + editor = this.editor; + $contents = jQuery('#' + editor.id + '_contents iframe').contents().find('body'); + var data = { + order: [], + occurrences: {} + }; + + // Check that there's a footnotes section. If it's been deleted the markers are useless: + if ($contents.find('#footnotes').length == 0) { + $contents.find('sup[data-footnote-id]').remove(); + return; + } + + // Find all the markers in the document: + var $markers = $contents.find('sup[data-footnote-id]'); + // If there aren't any, remove the Footnotes container: + if ($markers.length == 0) { + $contents.find('#footnotes').remove(); + return; + } + + // Otherwise reorder the markers: + $markers.each(function(){ + var footnote_id = jQuery(this).attr('data-footnote-id') + , marker_ref + , n = data.order.indexOf(footnote_id); + // If there isn't a matching footnote, remove the marker: + //console.log($contents.find('#footnote-' + (n + 1)).length); + //console.log('#footnote-' + (n + 1)); + /*if ($contents.find('#footnote-' + (n + 1)).length == 0) { + //jQuery(this).remove(); + return; + }*/ + + // If this is the markers first occurrence: + if (n == -1) { + // Store the id: + data.order.push(footnote_id); + n = data.order.length; + data.occurrences[footnote_id] = 1; + marker_ref = n + '-1'; + } else { + // Otherwise increment the number of occurrences: + // (increment n due to zero-index array) + n++; + data.occurrences[footnote_id]++; + marker_ref = n + '-' + data.occurrences[footnote_id]; + } + // Replace the marker contents: + var marker = '[' + n + ']'; + jQuery(this).html(marker); + }); + + // Then rebuild the Footnotes content to match marker order: + var footnotes = ''; + var footnote_text = ''; + for (i in data.order) { + footnote_id = data.order[i]; + footnote_text = $contents.find('#footnotes [data-footnote-id=' + footnote_id + '] cite').html(); + footnotes += this.buildFootnote(footnote_id, footnote_text, data); + } + $contents.find('#footnotes ol').html(footnotes); + + // Next we need to reinstate the 'editable' properties of the footnotes. + // (we have to do this individually due to Widgets 'fireOnce' for editable selectors) + var footnote_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 this.editor.widgets.instances) { + if (this.editor.widgets.instances[i].name == 'footnotes') { + footnote_widget = this.editor.widgets.instances[i]; + break; + } + } + // Then we `initEditable` each footnote, giving it a unique selector: + for (i in data.order) { + n = parseInt(i) + 1; + footnote_widget.initEditable('footnote_' + n, {selector: '#footnote-' + n +' cite', allowedContent: 'a[href]; cite[*](*); b i span'}); + } + + this.editor.fire('unlockSnapshot'); + return; + } +});