author: Sylvain Viollon
date: Thu Aug 01 13:23:01 2013 +0200
revision: 1100:d9990380386e in Products.SilvaExternalSources
branch: 2.4
details: https://hg.infrae.com/Products.SilvaExternalSources?cmd=changeset;node=d9990380386e
modified: Products/SilvaExternalSources/editor/static/plugins/silvaexternalsource/dialogs/source.js Products/SilvaExternalSources/editor/static/plugins/silvaexternalsource/plugin.js
added:
removed:
log: Investigate a better selection method for non-editable block and
navigation around it.
diffstat:
Products/SilvaExternalSources/editor/static/plugins/silvaexternalsource/dialogs/source.js | 41 +-
Products/SilvaExternalSources/editor/static/plugins/silvaexternalsource/plugin.js | 245 +++++++--
2 files changed, 218 insertions(+), 68 deletions(-)
diffs (487 lines):
diff -r 1e314c446fcf -r d9990380386e Products/SilvaExternalSources/editor/static/plugins/silvaexternalsource/dialogs/source.js
--- a/Products/SilvaExternalSources/editor/static/plugins/silvaexternalsource/dialogs/source.js Wed Jul 24 18:49:38 2013 +0200
+++ b/Products/SilvaExternalSources/editor/static/plugins/silvaexternalsource/dialogs/source.js Thu Aug 01 13:23:01 2013 +0200
@@ -1,12 +1,15 @@
(function($, CKEDITOR) {
+ var API = CKEDITOR.plugins.silvaexternalsource,
+ LIST_SOURCES_REST_URL = '++rest++Products.SilvaExternalSources.source.availables',
+ VALIDATE_REST_URL = '++rest++Products.SilvaExternalSources.source.validate',
+ PARAMETERS_REST_URL = '++rest++Products.SilvaExternalSources.source.parameters';
- var API = CKEDITOR.plugins.silvaexternalsource;
- var LIST_SOURCES_REST_URL = '++rest++Products.SilvaExternalSources.source.availables';
- var VALIDATE_REST_URL = '++rest++Products.SilvaExternalSources.source.validate';
- var PARAMETERS_REST_URL = '++rest++Products.SilvaExternalSources.source.parameters';
-
+ /**
+ * This object let you focus fields that cames from the template
+ * rendered on the server containing External Source paramters.
+ **/
var ParameterFocusable = function(field, dialog) {
var element = new CKEDITOR.dom.element(field),
element_type = element.getAttribute('type'),
@@ -38,11 +41,17 @@
return focusable;
};
- var rest_url = function(url) {
+ var get_rest_url = function(url) {
return $('#content-url').attr('href') + '/' + url;
};
- var update_focus = function($container, dialog, start_index) {
+ /**
+ * This function takes a jQuery element and a dialog and will
+ * create declare to the dialog the focus order of any form field
+ * found inside the element. start_index is the based index on
+ * which to base the focus order.
+ **/
+ var update_focus_list = function($container, dialog, start_index) {
var len, i = 0,
parameter_list = [start_index, 0];
@@ -69,11 +78,15 @@
dialog.currentFocusIndex = 0;
};
+ /**
+ * Load inside the given jQuery element the parameters. This is
+ * used inside the given dialog.
+ **/
var load_parameters = function($container, parameters, dialog, start_index) {
// Fetch the parameters form.
$container.html('<p>Fetching source parameters from server ...</p>');
$.ajax({
- url: rest_url(PARAMETERS_REST_URL),
+ url: get_rest_url(PARAMETERS_REST_URL),
data: parameters,
dataType: 'json',
type: 'POST',
@@ -85,7 +98,7 @@
$container.html(data.parameters);
$form = $container.children('form');
$form.trigger('load-smiform', {form: $form, container: $form});
- update_focus($form, dialog._, start_index);
+ update_focus_list($form, dialog._, start_index);
},
error: function() {
$container.html('');
@@ -145,7 +158,7 @@
parameters.push({'name': 'source_inline', 'value': 1});
$.ajax({
- url: rest_url(VALIDATE_REST_URL),
+ url: get_rest_url(VALIDATE_REST_URL),
data: parameters,
dataType: 'json',
type: 'POST',
@@ -247,7 +260,7 @@
this.add('Select a source to add', '');
this.setValue('');
$.getJSON(
- rest_url(LIST_SOURCES_REST_URL),
+ get_rest_url(LIST_SOURCES_REST_URL),
function(sources) {
for (var i=0; i < sources.length; i++) {
self.add(sources[i].title, sources[i].identifier);
@@ -333,9 +346,9 @@
this.parts.title.setText($.trim(/^[^:]*/.exec(this.parts.title.getText())));
},
onOk: function() {
- var data = {};
- var editor = this.getParentEditor();
- var source = API.getSelectedSource(editor);
+ var data = {},
+ editor = this.getParentEditor(),
+ source = API.getSelectedSource(editor);
this.commitContent(data);
diff -r 1e314c446fcf -r d9990380386e Products/SilvaExternalSources/editor/static/plugins/silvaexternalsource/plugin.js
--- a/Products/SilvaExternalSources/editor/static/plugins/silvaexternalsource/plugin.js Wed Jul 24 18:49:38 2013 +0200
+++ b/Products/SilvaExternalSources/editor/static/plugins/silvaexternalsource/plugin.js Thu Aug 01 13:23:01 2013 +0200
@@ -1,75 +1,123 @@
(function(CKEDITOR, $) {
+ /**
+ * Plugin for External Sources. External Sources inside the editor
+ * are represented like this:
+ *
+ * <span class="inline-container silva-readonly-element">
+ * <div class="alignment external-source">
+ * <div class="external-source-preview" />
+ * </div>
+ * </span>
+ *
+ * The outer span and inner div are added by the editor but should
+ * be removed when saving. contenteditable is set to false on the
+ * external source div.
+ */
CKEDITOR.plugins.silvaexternalsource = {
+ isSourceWrapper: function(element) {
+ // Return true if the element is an external source wrapper.
+ if (element != null &&
+ element.type == CKEDITOR.NODE_ELEMENT &&
+ element.is('span') &&
+ element.hasClass('inline-container')) {
+ return true;
+ }
+ return false;
+ },
isSource: function(element) {
+ // Return true if the element is a external source div.
if (element != null &&
+ element.type == CKEDITOR.NODE_ELEMENT &&
element.is('div') &&
element.hasClass('external-source')) {
return true;
};
return false;
},
- isInsideASource: function(element) {
- var first_try = true;
+ findSource: function(element) {
+ // Return a source element if the given element is
+ // instance a source.
+ var initial = true,
+ container = element;
- while (element != null) {
- element = element.getAscendant('div', first_try);
- if (CKEDITOR.plugins.silvaexternalsource.isSource(element)) {
- return element;
- }
- first_try = false;
+ // Try to see if we are inside a source.
+ while (container != null) {
+ container = container.getAscendant('div', initial);
+ if (API.isSource(container)) {
+ return container;
+ };
+ initial = false;
+ };
+ // Try to see if we have the wraper instead.
+ container = element.getAscendant('span', true);
+ if (API.isSourceWrapper(container)) {
+ var children = container.getChildren(),
+ i, len, child;
+ for (i=0, len=children.count(); i < len; i++) {
+ child = children.getItem(i);
+ if (API.isSource(child)) {
+ return child;
+ };
+ };
};
return null;
},
- getSelectedSource: function(editor, select_element) {
- var selection = editor.getSelection();
- var element = null;
- var base = null;
+ getSelectedSource: function(editor, no_selection) {
+ // Return the currently selected source.
+ var selection = editor.getSelection(),
+ source = null,
+ wrapper = null,
+ base = null;
+ if (selection === null) {
+ return null;
+ };
if (selection.getType() == CKEDITOR.SELECTION_ELEMENT) {
base = selection.getSelectedElement();
} else {
base = selection.getStartElement();
};
- element = CKEDITOR.plugins.silvaexternalsource.isInsideASource(base);
- if (element != null) {
- if (element.$ !== base.$) {
- var target = element;
+ source = API.findSource(base);
+ if (source != null) {
+ wrapper = source.getParent();
+ if (!API.isSourceWrapper(wrapper)) {
+ wrapper = source;
+ };
+ // Select the wrapper if needed.
+ if (!no_selection && wrapper.$ !== base.$) {
var range = new CKEDITOR.dom.range(editor.document);
- if (CKEDITOR.env.gecko) {
- target = target.getParent();
- };
- range.selectNodeContents(target);
+ range.selectNodeContents(wrapper);
range.select();
};
- return element;
+ return source;
};
return null;
},
- loadPreview: function(element, editor) {
+ loadPreview: function($element, editor) {
// element is a JQuery element. Editor a CKEditor one.
- var info = element.attr('data-silva-settings');
- var $preview = element.find('.external-source-preview');
+ var info = $element.attr('data-silva-settings');
+ var $preview = $element.find('.external-source-preview');
var content_url = $('#content-url').attr('href');
var extra_info = [];
// We load an existing code source. Add instance and text.
- if (element.attr('data-silva-instance') != undefined) {
+ if ($element.attr('data-silva-instance') != undefined) {
extra_info.push({
'name': 'source_instance',
- 'value': element.attr('data-silva-instance')
+ 'value': $element.attr('data-silva-instance')
});
extra_info.push({
'name': 'source_text',
'value': editor.name
});
- } else if (element.attr('data-silva-name') != undefined) {
+ } else if ($element.attr('data-silva-name') != undefined) {
// This is a new code source.
extra_info.push({
'name': 'source_name',
- 'value': element.attr('data-silva-name')
+ 'value': $element.attr('data-silva-name')
});
};
// Merge all preview information together.
@@ -89,9 +137,9 @@
var is_dirty = editor.checkDirty();
if (!$preview.length) {
- element.empty(); //CKEditor adds an br in empty container.
+ $element.empty(); //CKEditor adds an br in empty container.
$preview = $('<div class="external-source-preview"></div');
- $preview.appendTo(element);
+ $preview.appendTo($element);
$preview.delegate('a', 'click', function(event) {
event.stopPropagation();
event.preventDefault();
@@ -129,9 +177,18 @@
CKEDITOR.removeExternalSourceCommand = function() {};
CKEDITOR.removeExternalSourceCommand.prototype = {
exec: function(editor) {
- var source = API.getSelectedSource(editor, false);
+ var source = API.getSelectedSource(editor, true),
+ wrapper;
+
if (source !== null) {
- source.getParent().remove();
+ wrapper = source.getParent();
+ if (API.isSourceWrapper(wrapper)) {
+ // Remove the source and the wrapper.
+ wrapper.remove();
+ } else {
+ // Wrapper is missing (drag and drop abuse).
+ source.remove();
+ };
}
},
startDisabled: true
@@ -203,9 +260,9 @@
});
});
editor.on('selectionChange', function(event) {
- var element = API.getSelectedSource(editor);
- var command_edit = editor.getCommand('silvaexternalsource');
- var command_remove = editor.getCommand('silvaremoveexternalsource');
+ var element = API.getSelectedSource(editor),
+ command_edit = editor.getCommand('silvaexternalsource'),
+ command_remove = editor.getCommand('silvaremoveexternalsource');
if (element !== null) {
command_edit.setState(CKEDITOR.TRISTATE_ON);
@@ -215,13 +272,74 @@
command_remove.setState(CKEDITOR.TRISTATE_DISABLED);
};
});
- editor.on('doubleclick', function(event){
+ editor.on('doubleclick', function(event) {
var element = API.getSelectedSource(editor);
if (element !== null) {
event.data.dialog = 'silvaexternalsourceedit';
};
});
+ editor.on('key', function(event) {
+ if (editor.mode != 'wysiwyg')
+ return;
+
+ var code = event.data.keyCode;
+ // Improve the navigation before and after the code source with the arrows.
+ if (code in {37:1, 38:1, 39:1, 40:1}) {
+ setTimeout(function() {
+ var source = API.getSelectedSource(editor, true),
+ parent = null;
+
+ if (source !== null) {
+ parent = source.getParent();
+ if (!API.isSourceWrapper(parent)) {
+ parent = source;
+ };
+ var target,
+ range,
+ selection = editor.getSelection(),
+ at_the_end;
+
+ if (code in {37:1, 38:1}) {
+ target = parent.getPrevious();
+ if (target === null) {
+ target = editor.document.createElement('p');
+ target.insertBefore(parent);
+ };
+ at_the_end = true;
+ } else {
+ target = parent.getNext();
+ if (target === null) {
+ target = editor.document.createElement('p');
+ target.insertAfter(parent);
+ };
+ at_the_end = false;
+ };
+ selection.unlock();
+ if (!CKEDITOR.env.ie) {
+ range = new CKEDITOR.dom.range(editor.document);
+ range.moveToPosition(target, at_the_end ?
+ CKEDITOR.POSITION_BEFORE_END:
+ CKEDITOR.POSITION_AFTER_START);
+ target.scrollIntoView();
+ } else {
+ range = editor.document.$.body.createTextRange();
+ range.moveToElementText(target.$);
+ range.collapse();
+ range.scrollIntoView();
+ };
+ selection.selectRanges([range]);
+ selection.lock();
+ editor.forceNextSelectionCheck();
+ editor.selectionChange(true);
+ setTimeout(function() {
+ selection.unlock();
+ }, 100);
+ };
+ }, 25);
+ };
+ });
+
// Dialog
CKEDITOR.dialog.add('silvaexternalsourcenew', this.path + 'dialogs/source.js');
@@ -248,7 +366,7 @@
};
if (editor.contextMenu) {
editor.contextMenu.addListener(function(element, selection) {
- if (API.isInsideASource(element)) {
+ if (API.findSource(element) !== null) {
return {
silvaexternalsource: CKEDITOR.TRISTATE_OFF,
silvaremoveexternalsource: CKEDITOR.TRISTATE_OFF
@@ -261,9 +379,9 @@
},
afterInit: function(editor) {
// Input / Output transformations
- var dataProcessor = editor.dataProcessor;
- var dataFilter = dataProcessor && dataProcessor.dataFilter;
- var htmlFilter = dataProcessor && dataProcessor.htmlFilter;
+ var dataProcessor = editor.dataProcessor,
+ dataFilter = dataProcessor && dataProcessor.dataFilter,
+ htmlFilter = dataProcessor && dataProcessor.htmlFilter;
var remove = function(attributes, name) {
// Remove an attribute from an object.
@@ -271,22 +389,23 @@
delete attributes[name];
};
};
+
var is_source = function(element) {
- // Test if the given element is an image div.
+ // Test if the given element is an external source div.
return (element &&
element.name == 'div' &&
element.attributes['class'] != null &&
element.attributes['class'].match('^external-source'));
};
- var is_container = function(element) {
- // Test if the given element is an image div.
+ var is_wrapper = function(element) {
+ // Test if the given element is a temporary container.
return (element &&
- element.name == 'span' &&
+ element.name == 'span' &&
element.attributes['class'] != null &&
element.attributes['class'].match('^inline-container'));
};
var is_preview = function(element) {
- // Test if the given element is an image div.
+ // Test if the given element is a external source preview div.
return (element &&
element.name == 'div' &&
element.attributes['class'] == 'external-source-preview');
@@ -298,21 +417,32 @@
elements: {
div: function(element) {
if (is_source(element)) {
- var attributes = element.attributes;
- var parent = element.parent;
- var parse_alignment = /^external-source (.*)$/.exec(
- element.attributes['class']);
- var alignment = 'default';
+ var attributes = element.attributes,
+ parent = element.parent,
+ parse_alignment = /^external-source (.*)$/.exec(
+ element.attributes['class']),
+ alignment = 'default';
if (parse_alignment !== null) {
alignment = parse_alignment[1];
};
attributes['contenteditable'] = 'false';
element.children = [];
- if (!is_container(parent)) {
+ if (!is_wrapper(parent)) {
var container = new CKEDITOR.htmlParser.element(
'span', {'class': 'inline-container ' + alignment});
- container.children = [element];
+ if (CKEDITOR.env.webkit) {
+ // To help the selection in
+ // Chrome we add a space before
+ // and after each source.
+ container.attributes['contenteditable'] = 'false';
+ container.children = [
+ new CKEDITOR.htmlParser.text(''),
+ element,
+ new CKEDITOR.htmlParser.text('')];
+ } else {
+ container.children = [element];
+ };
container.parent = parent;
element.parent = container;
return container;
@@ -328,8 +458,15 @@
htmlFilter.addRules({
elements: {
span: function(element) {
- if (is_container(element)) {
- return element.children[0];
+ var i, len;
+
+ if (is_wrapper(element)) {
+ for (i=0, len=element.children.length; i < len; i++) {
+ if (is_source(element.children[i])) {
+ return element.children[i];
+ };
+ };
+ return false;
};
return null;
},
lmpx.com only provides a reader for public news (NNTP) servers. It is not
affiliated with the servers or forums shown here and is not responsible for
the content of articles, which is written by their respective authors.