From 4a4bc1215a00c361f9f2df4ba3cf07c794f24f7b Mon Sep 17 00:00:00 2001 From: Leonardo Balter Date: Wed, 18 Feb 2015 17:25:36 -0500 Subject: [PATCH 01/16] [WIP]: First clear unit tests --- tests/new.js | 18 ++++++++++++ tests/single.html | 74 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 tests/new.js create mode 100644 tests/single.html diff --git a/tests/new.js b/tests/new.js new file mode 100644 index 0000000..3d0e34b --- /dev/null +++ b/tests/new.js @@ -0,0 +1,18 @@ +QUnit.module("Basics", { + beforeEach: function() { + this.element = $("#test-element"); + } +}); + +QUnit.test("simple usage", function(assert) { + this.element.Arte(); + assert.strictEqual(this.element.html(), + "
Please enter text ...
"); +}); + +QUnit.test("ArteJS returns something", function(assert) { + var object = this.element.Arte(); + + // Breaking + assert.ok(object instanceof Arte, "Object is an instance of Arte"); +}); diff --git a/tests/single.html b/tests/single.html new file mode 100644 index 0000000..415e9e2 --- /dev/null +++ b/tests/single.html @@ -0,0 +1,74 @@ + + + + + Rich text commands unit test suite + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+ + From d92a5d79dc499878f3680973692e7f84b872d81b Mon Sep 17 00:00:00 2001 From: Leonardo Balter Date: Fri, 20 Feb 2015 17:02:04 -0500 Subject: [PATCH 02/16] Update new tests --- Gruntfile.js | 2 +- tests/all.html | 6 +++--- tests/new.js | 26 ++++++++++++++++++-------- tests/single.html | 42 +++++++++++++++++++++--------------------- 4 files changed, 43 insertions(+), 33 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 2f3a7d2..90697d9 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -90,7 +90,7 @@ module.exports = function(grunt) { } }, all: ["tests/all.html"], - single: ["tests/index.html"] + single: ["tests/single.html"] }, copy: { qunit: { diff --git a/tests/all.html b/tests/all.html index a29f9b3..c138d52 100644 --- a/tests/all.html +++ b/tests/all.html @@ -12,9 +12,9 @@ diff --git a/tests/new.js b/tests/new.js index 3d0e34b..3b39bd9 100644 --- a/tests/new.js +++ b/tests/new.js @@ -1,18 +1,28 @@ -QUnit.module("Basics", { +QUnit.module("initialization", { beforeEach: function() { this.element = $("#test-element"); } }); -QUnit.test("simple usage", function(assert) { +QUnit.test("basic", function(assert) { this.element.Arte(); - assert.strictEqual(this.element.html(), - "
Please enter text ...
"); + assert.ok(this.element.prop("contenteditable"), "turns into a contenteditable item"); + assert.strictEqual(this.element.text(), "Please enter text ...", "set's default text"); + assert.strictEqual(this.element.height(), 200, "set's basic height"); }); -QUnit.test("ArteJS returns something", function(assert) { - var object = this.element.Arte(); +QUnit.test("no element", function(assert) { + var result = $("#i-dont-exist").Arte(); + assert.equal(result, false, "an empty jQuery object doesn't create an Arte object"); +}); + +QUnit.test("multiple elements", function(assert) { + var multiple = $("
").addClass("foo"); + + multiple.Arte(); - // Breaking - assert.ok(object instanceof Arte, "Object is an instance of Arte"); + assert.strictEqual(multiple.first().html(), + "
Please enter text ...
", + "only the first element is affected"); + assert.strictEqual(multiple.last().html(), "", "the last element is not affected"); }); diff --git a/tests/single.html b/tests/single.html index 415e9e2..32f5d25 100644 --- a/tests/single.html +++ b/tests/single.html @@ -32,32 +32,32 @@ - - + + - - - - - - + + + + + + - - - + + + - - - - - + + + + + - - - - - + + + + + From ced8f6e2c0c7dd6ab96f66118416640d78e222e7 Mon Sep 17 00:00:00 2001 From: Leonardo Balter Date: Mon, 23 Feb 2015 16:58:39 -0500 Subject: [PATCH 03/16] test TextArea command return --- tests/new.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/new.js b/tests/new.js index 3b39bd9..38f0ccf 100644 --- a/tests/new.js +++ b/tests/new.js @@ -26,3 +26,11 @@ QUnit.test("multiple elements", function(assert) { "only the first element is affected"); assert.strictEqual(multiple.last().html(), "", "the last element is not affected"); }); + +QUnit.test("commands", function(assert) { + var arte = this.element.Arte(); + + arte.Arte("bold"); + + assert.equal(arte.Arte("bold"), undefined); +}); From 1f633ac546b04988141f7bd92b3c791b8349e950 Mon Sep 17 00:00:00 2001 From: Chris Kwan Date: Mon, 23 Feb 2015 18:21:52 -0500 Subject: [PATCH 04/16] Adding some initial comments / inline documentation --- src/editor/core/Arte.js | 6 +++++- src/editor/core/Commands.js | 4 +++- src/editor/core/TextArea.js | 38 +++++++++++++++++++++++++++++++++---- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/editor/core/Arte.js b/src/editor/core/Arte.js index b5aac5d..e122e7d 100644 --- a/src/editor/core/Arte.js +++ b/src/editor/core/Arte.js @@ -1,12 +1,16 @@ -/** +/** * @fileoverview jQuery wrapper around the Rich text editor * Usage: * 1) $(selector).Arte() * Converts the matched elements into rich text editor using default options or returns and existing instance + * @returns {jQuery selector of Arte.TextArea} * 2) $(selector).Arte({ options }); * Converts the matched elements into rich text editor using the options supplied or returns and existing instance + * @returns {jQuery selector of Arte.TextArea} * 3) $(selector).Arte(command, arguments) * Execute a rich text command with arguments + * @returns {jQuery selector of return value of commands called} + * */ (function($) { $.Arte = $.Arte || {}; diff --git a/src/editor/core/Commands.js b/src/editor/core/Commands.js index 3807352..c15c566 100644 --- a/src/editor/core/Commands.js +++ b/src/editor/core/Commands.js @@ -41,8 +41,10 @@ return attrType; }; - /* + /** * Executes a rich text command + * @param {String} commandName - name of the command to call + * @param {Object} options - options for the command */ var exec = function(commandName, options) { var commandOptions = constructCommandOptions.call(this, commandName, options); diff --git a/src/editor/core/TextArea.js b/src/editor/core/TextArea.js index 7c9827f..4f6a626 100644 --- a/src/editor/core/TextArea.js +++ b/src/editor/core/TextArea.js @@ -170,7 +170,12 @@ }; $.extend($.Arte.TextArea.prototype, { - // Get innerHtml of the contentEditable element + + /** + * Get or Set innerHtml of the contentEditable element + * @params {string} value string to set innerHTML of element to + * @returns {string} returns'innerHTML' of the contentEditable element if in rich text mode or 'value' of the element if in plaintext mode if getting value, otherwise returns nothing + */ "value": function(value) { var constants = $.Arte.constants; var prop = this.editorType === constants.editorTypes.richText ? "innerHTML" : "value"; @@ -184,6 +189,7 @@ return; } + //TODO should we return this so that we can chain things? this.el[prop] = value; this._currentOuterValue = this._container.innerHTML; this.triggerEvent(constants.eventNames.onvaluechange, { @@ -191,7 +197,12 @@ src: "external" }); }, - // Get outerHtml of the contentEditable element + + /** + * Gets or sets outerHtml of the contentEditable element + * @params {string} value html string to set outerHTML of element to + * @returns {string} returns 'outerHTML' of the contentEditable element with contentEditable tag removed + */ "outerValue": function(value) { if (typeof(value) === "undefined") { var clone = this.$element.clone(); @@ -204,6 +215,10 @@ this.el.setAttribute("class", newElement.getAttribute("class") || ""); this.value(newElement.innerHTML); }, + + /** + * Calls a focus event on the contentEditable element, moves the cursor to the end of that element, and fires an onselectionchange event + */ "focus": function() { var me = this; var focusHandler = function() { @@ -214,13 +229,23 @@ me.$el.on("focus", focusHandler); me.$el.focus(); }, + + /** + * Triggers the event passed in on the contentEditable element with data provided + * @params {string} name - name of the event you want to trigger + * @params {object} data - extra parameters to pass into the event handler + */ "triggerEvent": function(name, data) { this.$element.trigger(name, $.extend(data, { textArea: this })); }, + + /** + * Removes Arte from this element (Converts the rich text editor to non-editable state and remove rich text state information) + * @params {Object} options - pass in 'removeContent' in options object to also clear the element of all text and formatting + */ "destroy": function(options) { - // Converts the rich text editor to non-editable state and remove rich text state information this.$element.removeData("Arte"); this.$element.removeAttr($.Arte.configuration.textFieldIdentifier); this.$element.off(); @@ -233,12 +258,17 @@ this.$element.empty(); } }, + /** - * on/off methods to support attaching events handler using a rich text instance + * Listen to events on the Arte.TextAreas element (same as adding events directly to the element) */ on: function(type, handler) { this.$element.on(type, handler); }, + + /** + * Stop listening to events on the Arte.TextAreas element (same as adding events directly to the element) + */ off: function(type, handler) { this.$element.off(type, handler); } From ccb52fd64bd6ae229a3d78559c8b2a5eb1546486 Mon Sep 17 00:00:00 2001 From: Chris Kwan Date: Mon, 23 Feb 2015 18:40:41 -0500 Subject: [PATCH 05/16] Fixing some JSCS errors --- src/editor/core/TextArea.js | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/editor/core/TextArea.js b/src/editor/core/TextArea.js index 4f6a626..edca243 100644 --- a/src/editor/core/TextArea.js +++ b/src/editor/core/TextArea.js @@ -170,12 +170,13 @@ }; $.extend($.Arte.TextArea.prototype, { - - /** - * Get or Set innerHtml of the contentEditable element - * @params {string} value string to set innerHTML of element to - * @returns {string} returns'innerHTML' of the contentEditable element if in rich text mode or 'value' of the element if in plaintext mode if getting value, otherwise returns nothing - */ + /** + * Get or Set innerHtml of the contentEditable element + * @params {string} value string to set innerHTML of element to + * @returns {string} returns 'innerHTML' of the contentEditable element + * if in rich text mode or 'value' of the element if in plaintext mode + * if getting value, otherwise returns nothing + */ "value": function(value) { var constants = $.Arte.constants; var prop = this.editorType === constants.editorTypes.richText ? "innerHTML" : "value"; @@ -197,7 +198,7 @@ src: "external" }); }, - + /** * Gets or sets outerHtml of the contentEditable element * @params {string} value html string to set outerHTML of element to @@ -215,9 +216,10 @@ this.el.setAttribute("class", newElement.getAttribute("class") || ""); this.value(newElement.innerHTML); }, - + /** - * Calls a focus event on the contentEditable element, moves the cursor to the end of that element, and fires an onselectionchange event + * Calls a focus event on the contentEditable element, moves the cursor + * to the end of that element, and fires an onselectionchange event */ "focus": function() { var me = this; @@ -229,7 +231,7 @@ me.$el.on("focus", focusHandler); me.$el.focus(); }, - + /** * Triggers the event passed in on the contentEditable element with data provided * @params {string} name - name of the event you want to trigger @@ -240,7 +242,7 @@ textArea: this })); }, - + /** * Removes Arte from this element (Converts the rich text editor to non-editable state and remove rich text state information) * @params {Object} options - pass in 'removeContent' in options object to also clear the element of all text and formatting @@ -258,14 +260,14 @@ this.$element.empty(); } }, - + /** * Listen to events on the Arte.TextAreas element (same as adding events directly to the element) */ on: function(type, handler) { this.$element.on(type, handler); }, - + /** * Stop listening to events on the Arte.TextAreas element (same as adding events directly to the element) */ From 99fc922ae5837d5333a0b1973f3f2a9ba7750b67 Mon Sep 17 00:00:00 2001 From: Leonardo Balter Date: Tue, 24 Feb 2015 11:48:53 -0500 Subject: [PATCH 06/16] Whitespace --- src/editor/core/TextArea.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/editor/core/TextArea.js b/src/editor/core/TextArea.js index edca243..5d8baef 100644 --- a/src/editor/core/TextArea.js +++ b/src/editor/core/TextArea.js @@ -1,4 +1,4 @@ -/// dependencies: Arte.js +/// dependencies: Arte.js (function($) { $.Arte = $.Arte || {}; $.Arte.TextArea = function(options) { @@ -70,7 +70,7 @@ } me._container.appendChild(me.el); me.$el = $(me.el); - // Use an existing DIV or TEXTAREA if it already exists + // Use an existing DIV or TEXTAREA if it already exists } else { me.el = me._container.childNodes[0]; if (me.el.tagName === "DIV") { @@ -170,19 +170,19 @@ }; $.extend($.Arte.TextArea.prototype, { - /** - * Get or Set innerHtml of the contentEditable element - * @params {string} value string to set innerHTML of element to - * @returns {string} returns 'innerHTML' of the contentEditable element - * if in rich text mode or 'value' of the element if in plaintext mode - * if getting value, otherwise returns nothing - */ + /** + * Get or Set innerHtml of the contentEditable element + * @params {string} value string to set innerHTML of element to + * @returns {string} returns 'innerHTML' of the contentEditable element + * if in rich text mode or 'value' of the element if in plaintext mode + * if getting value, otherwise returns nothing + */ "value": function(value) { var constants = $.Arte.constants; var prop = this.editorType === constants.editorTypes.richText ? "innerHTML" : "value"; var currentValue = this.el[prop]; - if (typeof(value) === "undefined") { + if (typeof (value) === "undefined") { return currentValue; } @@ -205,7 +205,7 @@ * @returns {string} returns 'outerHTML' of the contentEditable element with contentEditable tag removed */ "outerValue": function(value) { - if (typeof(value) === "undefined") { + if (typeof (value) === "undefined") { var clone = this.$element.clone(); clone.children().removeAttr("contenteditable"); return clone.html(); @@ -219,7 +219,7 @@ /** * Calls a focus event on the contentEditable element, moves the cursor - * to the end of that element, and fires an onselectionchange event + * to the end of that element, and fires an onselectionchange event */ "focus": function() { var me = this; @@ -268,7 +268,7 @@ this.$element.on(type, handler); }, - /** + /** * Stop listening to events on the Arte.TextAreas element (same as adding events directly to the element) */ off: function(type, handler) { From 8b0a94e958a132d12e68a8a3395652bd3ec6e86a Mon Sep 17 00:00:00 2001 From: Leonardo Balter Date: Tue, 24 Feb 2015 12:56:32 -0500 Subject: [PATCH 07/16] Fix init tests --- src/editor/core/Arte.js | 11 +++++---- tests/new.js | 49 ++++++++++++++++++++++++++++++----------- 2 files changed, 43 insertions(+), 17 deletions(-) diff --git a/src/editor/core/Arte.js b/src/editor/core/Arte.js index e122e7d..ad800ec 100644 --- a/src/editor/core/Arte.js +++ b/src/editor/core/Arte.js @@ -17,7 +17,11 @@ $.fn.Arte = function(options, args) { var result = []; rangy.init(); - this.each(function() { + + if (!this.length) { + return this; + } + return this.map(function() { var $this = $(this); var editor = $this.data("Arte"); if (options && typeof(options) === "string") { @@ -32,7 +36,7 @@ } var returnValue = editor[methodName].call(editor, args); - result.push(returnValue); + return returnValue; } else { // If $this is not a rich text editor, construct the editor if (!editor) { @@ -41,9 +45,8 @@ editor = new $.Arte.TextArea(options); $this.data("Arte", editor); } - result.push(editor); + return editor; } }); - return $(result); }; })(jQuery); diff --git a/tests/new.js b/tests/new.js index 38f0ccf..1e9e87a 100644 --- a/tests/new.js +++ b/tests/new.js @@ -11,26 +11,49 @@ QUnit.test("basic", function(assert) { assert.strictEqual(this.element.height(), 200, "set's basic height"); }); -QUnit.test("no element", function(assert) { - var result = $("#i-dont-exist").Arte(); - assert.equal(result, false, "an empty jQuery object doesn't create an Arte object"); -}); - QUnit.test("multiple elements", function(assert) { - var multiple = $("
").addClass("foo"); + var elem, + multiple = $("
") + .addClass("foo") + // Appends the new elements so we get a proper height value + .appendTo(this.element); multiple.Arte(); - assert.strictEqual(multiple.first().html(), - "
Please enter text ...
", - "only the first element is affected"); - assert.strictEqual(multiple.last().html(), "", "the last element is not affected"); + elem = multiple.first(); + + assert.ok(elem.prop("contenteditable"), "turns into a contenteditable item"); + assert.strictEqual(elem.text(), "Please enter text ...", "set's default text"); + assert.strictEqual(elem.height(), 200, "set's basic height"); + + elem = multiple.last(); + + assert.ok(elem.prop("contenteditable"), "turns into a contenteditable item"); + assert.strictEqual(elem.text(), "Please enter text ...", "set's default text"); + assert.strictEqual(elem.height(), 200, "set's basic height"); +}); + +QUnit.test("no element", function(assert) { + var elem = $("#i-dont-exist"); + var result = elem.Arte(); + assert.equal(result, elem, "an empty jQuery object doesn't create an Arte object"); +}); + +QUnit.module("commands", { + beforeEach: function() { + this.element = $("#test-element"); + } }); -QUnit.test("commands", function(assert) { - var arte = this.element.Arte(); +QUnit.test("bold", function(assert) { + var arte; + + arte = this.element.Arte(); + + arte.text("foo"); arte.Arte("bold"); - assert.equal(arte.Arte("bold"), undefined); + assert.equal(arte.html(), undefined); + assert.equal(this.element.html(), undefined); }); From ed143ed5d2642a7dea56fb2c1d325105142c7064 Mon Sep 17 00:00:00 2001 From: gshenar Date: Tue, 24 Feb 2015 14:05:20 -0500 Subject: [PATCH 08/16] more documentation updates --- src/editor/core/Commands.js | 9 ++++++- src/editor/core/PluginManager.js | 8 +++---- src/editor/core/Util.js | 24 ++++++++++++------- .../lib/rangy-extensions/rangy-extensions.js | 10 ++++---- 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/editor/core/Commands.js b/src/editor/core/Commands.js index c15c566..c549d59 100644 --- a/src/editor/core/Commands.js +++ b/src/editor/core/Commands.js @@ -80,7 +80,14 @@ return defaultValue[attrType]; } }; - + + /** + * Get the tagName for the given command + * @param {String} commandName - name of the command get the tagName for + * @param {String} attrType - attrType to use if the command contains an applierTagName + * @param {Object} options - an object you can supply a tagName on if you already have it so you can just return that + * @return {String} return tagName for the given command or if there is none just return default inline/block tag based on command type + */ var getTagNameOrDefault = function(commandName, attrType, options) { if (options && options.tagName) { return options.tagName; diff --git a/src/editor/core/PluginManager.js b/src/editor/core/PluginManager.js index ba6fdb7..07f7a60 100644 --- a/src/editor/core/PluginManager.js +++ b/src/editor/core/PluginManager.js @@ -13,16 +13,16 @@ register: function(name, plugin) { this.plugins[name] = plugin; }, - /* + /** * Initializes the plugin * @param {Arte} an instance of Arte */ init: function(richTextEditor) { richTextEditor.pluginInstances = richTextEditor.pluginInstances || []; for (var pluginName in this.plugins) { - var pluginInstanse = new this.plugins[pluginName](); - pluginInstanse.init(richTextEditor); - richTextEditor.pluginInstances.push(pluginInstanse); + var pluginInstance = new this.plugins[pluginName](); + pluginInstance.init(richTextEditor); + richTextEditor.pluginInstances.push(pluginInstance); } } }; diff --git a/src/editor/core/Util.js b/src/editor/core/Util.js index a0f46d7..2829ca9 100644 --- a/src/editor/core/Util.js +++ b/src/editor/core/Util.js @@ -4,8 +4,10 @@ (function($) { $.Arte = $.Arte || {}; $.Arte.util = { - /* + /** * Ensure that if there is a user selection, it is inside of the selected element. + * @param {jQuery object} jElement - the element to check the selection against + * @return {boolean} whether or not there is a selection in the given element */ isSelectionInElement: function(jElement) { var selection = rangy.getSelection(); @@ -13,7 +15,7 @@ return range && (range.startContainer === jElement.get(0) || jElement.has(range.startContainer).length !== 0); }, - /* + /** * Move cursor to the end of the input element * @param {htmlElement} element */ @@ -26,7 +28,7 @@ selection.setSingleRange(range); }, - /* + /** * Evaluates if all elements in the collection match the condition specified in the callback function * @param {Array|Object} collection of objects to evaluate * @param {function} callback a function that returns true/false for each object @@ -40,7 +42,7 @@ }); return result; }, - /* + /** * Evaluates if any elements in the collection match the condition specified in the callback function * @param {Array|Object} collection of objects to evaluate * @param {function} callback a function that returns true/false for each object @@ -54,7 +56,7 @@ }); return result; }, - /* + /** * Filter a collection based on the result of the callback function * @param {Array|Object} collection of objects to evaluate * @param {function} callback a function that returns true/false for each object @@ -71,6 +73,8 @@ }, /** * Returns selected text nodes + * @param {boolean} allowCollapsedSelection - whether or not to return the parent of the node with the cursor if the selection is collapsed + * @return {Array} array of selected text nodes */ getSelectedTextNodes: function(allowCollapsedSelection) { var selectedNodes = $(); @@ -88,7 +92,7 @@ return selectedNodes; } - // In case we don"t have a valid selection, + // In case we don't have a valid selection, range = rangy.createRangyRange(); range.selectNodeContents(this.$el.get(0)); selectedNodes = rangy.util.getTextNodes(range); @@ -99,8 +103,10 @@ } return selectedNodes; }, - /* - * Identify the ArteJS command configuration from className, styleName or tagName + /** + * Identify the ArteJS command configuration from className, styleName or tagName. + * @param {Object} options - an object containing either a commandName, className, styleName, or tagName attribute + * @return {Object} returns the command configuration that matched to the parameter passed in or null if no commands are found */ getCommandConfig: function(options) { var result = null; @@ -115,7 +121,7 @@ return configuration.commands[options.commandName]; } - /* Infer the command from the properties in the options. */ + // Infer the command from the properties in the options. for (var command in configuration.commands) { var commandConfig = configuration.commands[command]; diff --git a/src/editor/lib/rangy-extensions/rangy-extensions.js b/src/editor/lib/rangy-extensions/rangy-extensions.js index 668d5ab..8ffb98c 100644 --- a/src/editor/lib/rangy-extensions/rangy-extensions.js +++ b/src/editor/lib/rangy-extensions/rangy-extensions.js @@ -190,19 +190,19 @@ $(document).ready(function() { * @return rangyRange object */ - function createRangeFromElements(startElement, endElement, excludStartEndElements) { + function createRangeFromElements(startElement, endElement, excludeStartEndElements) { var range = rangy.createRangyRange(); - var startOp = excludStartEndElements ? "setStartAfter" : "setStartBefore"; - var endop = excludStartEndElements ? "setEndBefore" : "setEndAfter"; + var startOp = excludeStartEndElements ? "setStartAfter" : "setStartBefore"; + var endOp = excludeStartEndElements ? "setEndBefore" : "setEndAfter"; range[startOp](startElement); - range[endop](endElement); + range[endOp](endElement); return range; } /** * Get non-whitespace text nodes * @param {rangyRange} Range object - * @return Collection of matched text nodes + * @return {array} array of matched text nodes */ function getTextNodes(range) { if (!range.collapsed) { From 44323574880c82b931ee8bfd79fb4999cedcedb5 Mon Sep 17 00:00:00 2001 From: Leonardo Balter Date: Tue, 24 Feb 2015 14:44:07 -0500 Subject: [PATCH 09/16] Remove unnecessary line --- src/editor/core/Arte.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/editor/core/Arte.js b/src/editor/core/Arte.js index ad800ec..d804d61 100644 --- a/src/editor/core/Arte.js +++ b/src/editor/core/Arte.js @@ -15,7 +15,6 @@ (function($) { $.Arte = $.Arte || {}; $.fn.Arte = function(options, args) { - var result = []; rangy.init(); if (!this.length) { From 512bf3643f49e472bcdff6f5b15158cb687f1204 Mon Sep 17 00:00:00 2001 From: Leonardo Balter Date: Tue, 24 Feb 2015 14:46:41 -0500 Subject: [PATCH 10/16] fix tests jshint check in --- tests/new.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/new.js b/tests/new.js index 1e9e87a..d0ea0f0 100644 --- a/tests/new.js +++ b/tests/new.js @@ -12,8 +12,8 @@ QUnit.test("basic", function(assert) { }); QUnit.test("multiple elements", function(assert) { - var elem, - multiple = $("
") + var elem; + var multiple = $("
") .addClass("foo") // Appends the new elements so we get a proper height value .appendTo(this.element); From 13353e33dd2c442dae19152ee927706ebd15219e Mon Sep 17 00:00:00 2001 From: Leonardo Balter Date: Tue, 24 Feb 2015 14:47:04 -0500 Subject: [PATCH 11/16] Intro usage of commands and options --- README.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/README.md b/README.md index 0e746d0..e03217c 100644 --- a/README.md +++ b/README.md @@ -4,3 +4,58 @@ ArteJS [![Build Status](https://secure.travis-ci.org/vistaprint/ArteJS.png?branc ArteJS is a powerful, extensible, configurable, flexible and cross-browser rich text editor with a simple API that produces consistent, valid and concise html. [ArteJS Examples / Documentation](http://vistaprint.github.io/ArteJS/ "ArteJS Examples / Documentation") + +### Options + +You can initialize your Arte editor with some custom options, passing a literal object in the first argument. + +```js +$("#editor").Arte(options); +``` + +The available options are listed below with their default values: + +```js +$("#editor").Arte({ + + // Set of initial styles applied to rich text editor + styles: { + "min-height": "200px", + "height": "inherit" + }, + + // Collection of classes applied to rich text editor + classes: [], + + // Initial value of the text editor + value: "Please enter text ...", + + // Editor Type: plain text or rich text + editorType: "plainText", + + // attach event handlers + on: null +}); +``` + +### Commands + +After you initialize your Arte editor, you can call commands: + +```js +var arte = $("#editor").Arte(options); + +// make the selection bold +arte.Arte("bold"); + +// set the fontsize of the selection to 12px +arte.Arte("fontSize", "12px"); + +// Alternative: + +arte.each(function() { + this.bold(); + this.fontSize("12px"); +}); + +``` From 8851af9908f2aec54c578eeffb6813749f5402c9 Mon Sep 17 00:00:00 2001 From: Leonardo Balter Date: Tue, 24 Feb 2015 15:14:51 -0500 Subject: [PATCH 12/16] Fix JSCS issues --- src/editor/core/Commands.js | 4 ++-- src/editor/core/Util.js | 13 ++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/editor/core/Commands.js b/src/editor/core/Commands.js index c549d59..35874b8 100644 --- a/src/editor/core/Commands.js +++ b/src/editor/core/Commands.js @@ -1,4 +1,4 @@ -/// @dependencies: Arte.js, TextArea.js +/// @dependencies: Arte.js, TextArea.js /** * @fileoverview extends Arte prototype to add rich text commands */ @@ -80,7 +80,7 @@ return defaultValue[attrType]; } }; - + /** * Get the tagName for the given command * @param {String} commandName - name of the command get the tagName for diff --git a/src/editor/core/Util.js b/src/editor/core/Util.js index 2829ca9..2d73c28 100644 --- a/src/editor/core/Util.js +++ b/src/editor/core/Util.js @@ -1,4 +1,4 @@ -/** +/** * @fileoverview: Various utility functions */ (function($) { @@ -73,7 +73,8 @@ }, /** * Returns selected text nodes - * @param {boolean} allowCollapsedSelection - whether or not to return the parent of the node with the cursor if the selection is collapsed + * @param {boolean} allowCollapsedSelection - whether or not to return the parent of the node + * with the cursor if the selection is collapsed * @return {Array} array of selected text nodes */ getSelectedTextNodes: function(allowCollapsedSelection) { @@ -104,9 +105,11 @@ return selectedNodes; }, /** - * Identify the ArteJS command configuration from className, styleName or tagName. - * @param {Object} options - an object containing either a commandName, className, styleName, or tagName attribute - * @return {Object} returns the command configuration that matched to the parameter passed in or null if no commands are found + * Identify the ArteJS command configuration from className, styleName or tagName. + * @param {Object} options - an object containing either a commandName, className, styleName, + * or tagName attribute + * @return {Object} returns the command configuration that matched to the parameter passed in or null + * if no commands are found */ getCommandConfig: function(options) { var result = null; From 4982db405106d6f9b5253e93f0cef1d84d06dc3f Mon Sep 17 00:00:00 2001 From: Leonardo Balter Date: Tue, 24 Feb 2015 17:44:48 -0500 Subject: [PATCH 13/16] Fix typo --- src/editor/core/Configuration.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/core/Configuration.js b/src/editor/core/Configuration.js index e048609..518e661 100644 --- a/src/editor/core/Configuration.js +++ b/src/editor/core/Configuration.js @@ -1,4 +1,4 @@ -/* +/* * This file lists the configuration and constants used by ArteJS */ (function($) { @@ -113,7 +113,7 @@ commandAttrType: constants.commandAttrType.styleName, /* - * An attributed added to the dom element to identify that dom element as rich text field + * An attribute added to the dom element to identify that dom element as rich text field */ textFieldIdentifier: "rteTextField", From edd9cde41896e62ed09c934cf2f30493f082b5b3 Mon Sep 17 00:00:00 2001 From: Leonardo Balter Date: Wed, 25 Feb 2015 00:20:56 -0500 Subject: [PATCH 14/16] Revert new tests --- Gruntfile.js | 2 +- tests/all.html | 6 ++-- tests/new.js | 59 ------------------------------------- tests/single.html | 74 ----------------------------------------------- 4 files changed, 4 insertions(+), 137 deletions(-) delete mode 100644 tests/new.js delete mode 100644 tests/single.html diff --git a/Gruntfile.js b/Gruntfile.js index 90697d9..2f3a7d2 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -90,7 +90,7 @@ module.exports = function(grunt) { } }, all: ["tests/all.html"], - single: ["tests/single.html"] + single: ["tests/index.html"] }, copy: { qunit: { diff --git a/tests/all.html b/tests/all.html index c138d52..a29f9b3 100644 --- a/tests/all.html +++ b/tests/all.html @@ -12,9 +12,9 @@ diff --git a/tests/new.js b/tests/new.js deleted file mode 100644 index d0ea0f0..0000000 --- a/tests/new.js +++ /dev/null @@ -1,59 +0,0 @@ -QUnit.module("initialization", { - beforeEach: function() { - this.element = $("#test-element"); - } -}); - -QUnit.test("basic", function(assert) { - this.element.Arte(); - assert.ok(this.element.prop("contenteditable"), "turns into a contenteditable item"); - assert.strictEqual(this.element.text(), "Please enter text ...", "set's default text"); - assert.strictEqual(this.element.height(), 200, "set's basic height"); -}); - -QUnit.test("multiple elements", function(assert) { - var elem; - var multiple = $("
") - .addClass("foo") - // Appends the new elements so we get a proper height value - .appendTo(this.element); - - multiple.Arte(); - - elem = multiple.first(); - - assert.ok(elem.prop("contenteditable"), "turns into a contenteditable item"); - assert.strictEqual(elem.text(), "Please enter text ...", "set's default text"); - assert.strictEqual(elem.height(), 200, "set's basic height"); - - elem = multiple.last(); - - assert.ok(elem.prop("contenteditable"), "turns into a contenteditable item"); - assert.strictEqual(elem.text(), "Please enter text ...", "set's default text"); - assert.strictEqual(elem.height(), 200, "set's basic height"); -}); - -QUnit.test("no element", function(assert) { - var elem = $("#i-dont-exist"); - var result = elem.Arte(); - assert.equal(result, elem, "an empty jQuery object doesn't create an Arte object"); -}); - -QUnit.module("commands", { - beforeEach: function() { - this.element = $("#test-element"); - } -}); - -QUnit.test("bold", function(assert) { - var arte; - - arte = this.element.Arte(); - - arte.text("foo"); - - arte.Arte("bold"); - - assert.equal(arte.html(), undefined); - assert.equal(this.element.html(), undefined); -}); diff --git a/tests/single.html b/tests/single.html deleted file mode 100644 index 32f5d25..0000000 --- a/tests/single.html +++ /dev/null @@ -1,74 +0,0 @@ - - - - - Rich text commands unit test suite - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- -
- -
-
- - From 16e9793ba2f78387818c88ae0fb73fe76c9658fc Mon Sep 17 00:00:00 2001 From: Gal Shenar Date: Thu, 26 Feb 2015 10:22:41 -0500 Subject: [PATCH 15/16] added some more inline documentation --- src/editor/core/Commands.js | 51 +++++++++++++++---- .../jquery-dom-manipulation.js | 2 +- src/editor/plugins/InsertCommand.js | 5 ++ src/editor/plugins/StateDetector.js | 8 ++- 4 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/editor/core/Commands.js b/src/editor/core/Commands.js index c549d59..e465e28 100644 --- a/src/editor/core/Commands.js +++ b/src/editor/core/Commands.js @@ -11,12 +11,19 @@ var commandAttr = constants.commandAttrType; /** * Determine the command attribute using the options + * @param {String} commandName - name of the command get attrType for + * @param {Object} options + * @param {Object} [options.commandAttrType] - pass a commandAttrType in the options if you want to use it + * @param {Object} [options.styleName] - will use styleName as the attrType if you pass one in + * @param {Object} [options.className] - will use className as the attrType if you pass one in + * @param {Object} [options.tagName] - will use tagName as the attrType if you pass one in + * @return {String} returns the attrType in this order + 1. If it was defined in the config for the command use that + 2. If no options object then return commandAttrType object from configuration + 3. If options defines a commandAttrType use that + 4. If a styleName/className/tagName is passed in options use the styleName/className/tagName properties from $.Arte.constants as the attrType */ var commandAttrType = function(commandName, options) { - // commandAttrType is selected based on the following precedence - // 1) As defined in the options - // 2) Infer from the options - // 3) Use default var commandConfig = configuration.commands[commandName]; if (commandConfig.commandAttrType) { return commandConfig.commandAttrType; @@ -44,7 +51,7 @@ /** * Executes a rich text command * @param {String} commandName - name of the command to call - * @param {Object} options - options for the command + * @param {Object} options */ var exec = function(commandName, options) { var commandOptions = constructCommandOptions.call(this, commandName, options); @@ -60,7 +67,17 @@ $.Arte.RichTextCommandApplier.createAndExecute(commandOptions); this.triggerEvent(constants.eventNames.oncommand, commandOptions); }; - + + /** + * Get the correct command value based on the given parameters + * @param {String} commandName - name of the command get the value for + * @param {String} attrType - attrType you are using the command with + * @param {Object|String} options - either an options object or it can just be a string containing the commandValue + * @param {Object} [options.styleValue] - if attrType == styleName and you pass this in then styleValue will be the the commandValue + * @param {Object} [options.className] - if attrType == className and you pass this in then className will be the the commandValue + * @param {Object} [options.commandValue] - if you pass in a commandValue that will be returned + * @return {String} try to return commandValue using options that were passed in, if none are found return the default value for the command specified in the configuration + */ var getCommandValueOrDefault = function(commandName, attrType, options) { if (options && attrType === commandAttr.styleName && options.styleValue) { return options.styleValue; @@ -84,8 +101,9 @@ /** * Get the tagName for the given command * @param {String} commandName - name of the command get the tagName for - * @param {String} attrType - attrType to use if the command contains an applierTagName - * @param {Object} options - an object you can supply a tagName on if you already have it so you can just return that + * @param {String} attrType - attrType you are using the command with - used to find the right tag (for example bold uses the B tag if we want to apply tags or a SPAN if applying styles/classes) + * @param {Object} options + * @param {String} options.tagName - if you already know the tagName and put it in the options it will be returned back * @return {String} return tagName for the given command or if there is none just return default inline/block tag based on command type */ var getTagNameOrDefault = function(commandName, attrType, options) { @@ -107,7 +125,13 @@ return commandConfig.commandType === constants.commandType.inline ? configuration.defaultInlineTag : configuration.defaultBlockTag; }; - + + /** + * Creates command options to use for the command based on the configuration + * @param {String} commandName - name of the command generate the options for + * @param {Object} options - a object containing various options + * @return {Object} return tagName for the given command or if there is none just return default inline/block tag based on command type + */ var constructCommandOptions = function(commandName, options) { var attr = commandAttrType(commandName, options); var commandConfig = configuration.commands[commandName]; @@ -193,7 +217,14 @@ "textAlign": function(options) { exec.apply(this, ["textAlign", options]); }, - // Apply the styles/classes to the content editable element + /** + * Toggles styles + * @param {Object} [options] + * @param {jQuery} [options.element] - pass in your own element or use the rich text area by default + * @param {Object} [options.styleName] - name of style you would like to toggle on the element (e.g. font-weight) + * @param {Object} [options.styleValue] - value for the styleName defined above that you would like to toggle on the element (e.g. bold) + * @param {Object} [options.className] - name of css class you would like to toggle on the element (e.g. arte-font-weight) + */ "toggleStyleOnElement": function(options) { var element = options.element || this.$el; if (options && options.styleName) { diff --git a/src/editor/lib/jquery-extensions/jquery-dom-manipulation.js b/src/editor/lib/jquery-extensions/jquery-dom-manipulation.js index c79f59d..95ad8d8 100644 --- a/src/editor/lib/jquery-extensions/jquery-dom-manipulation.js +++ b/src/editor/lib/jquery-extensions/jquery-dom-manipulation.js @@ -157,7 +157,7 @@ * Create a dom element with options * @param {object} a set of dom element creation options * tagName, attr, styleName+styleValue, className - * @return {jNode} A jQuery element + * @return {jQuery} A jQuery element */ createContainer: function(options) { var tagName = options.applierTagName || "div"; diff --git a/src/editor/plugins/InsertCommand.js b/src/editor/plugins/InsertCommand.js index 02da12f..95de585 100644 --- a/src/editor/plugins/InsertCommand.js +++ b/src/editor/plugins/InsertCommand.js @@ -4,6 +4,11 @@ (function(pluginManager) { var InsertCommand = function() { var publicApi = { + /** + * Appends content to end of element if there is no selection. If there is a selection insert content at cursor position and replace all content in selection. Selects new content when done. + * @param {Object} options - options object will be passed into the onbeforeinsert/onafterinsert events + * @param {Object} options.commandValue - the html you want to insert + */ insert: function(options) { $.extend(options, { execute: true diff --git a/src/editor/plugins/StateDetector.js b/src/editor/plugins/StateDetector.js index 6231abf..2926518 100644 --- a/src/editor/plugins/StateDetector.js +++ b/src/editor/plugins/StateDetector.js @@ -76,6 +76,11 @@ // Extend the prototype of the TextArea to expose the public API $.extend($.Arte.TextArea.prototype, { + /** + * Same as getAllStates however will only return the states that are applied to every node in the selection + * @param {String} [commandName] If provided, only result the state of the given command (ie: fontFamily, bold, etc) + * @return {Object} returns object containing each commandName and the value only if it is applied to every single node in the selection (if there are multiple nodes in the selection and they have different values for the given command then the value will be false/null) + */ getState: function(commandName) { var selectedNodes = $.Arte.util.getSelectedTextNodes.apply(this, [true]); return getSelectedNodesState(selectedNodes, commandName); @@ -84,7 +89,8 @@ /** * Get an array of all the states found within the current selection * (ie: if the current selection has both a bold and a non-bold component, get two results representing that) - * @param {commandName} string. Optional. If provided, only result the state of the given command (ie: fontFamily, bold, etc) + * @param {String} [commandName] If provided, only result the state of the given command (ie: fontFamily, bold, etc) + * @return {Array} returns array containing an object for each selected node that lists out the commandName and it's value for that node */ getAllStates: function(commandName) { var selectedNodes = $.Arte.util.getSelectedTextNodes.apply(this, [true]); From d6c9b727703072703a9fbd248502c253086838ed Mon Sep 17 00:00:00 2001 From: Chris Kwan Date: Thu, 26 Feb 2015 16:41:05 -0500 Subject: [PATCH 16/16] Fix JSCS errors --- src/editor/core/Commands.js | 26 +++++++++++++++----------- src/editor/plugins/InsertCommand.js | 4 +++- src/editor/plugins/StateDetector.js | 9 ++++++--- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/editor/core/Commands.js b/src/editor/core/Commands.js index b071f89..a18dc6f 100644 --- a/src/editor/core/Commands.js +++ b/src/editor/core/Commands.js @@ -17,11 +17,12 @@ * @param {Object} [options.styleName] - will use styleName as the attrType if you pass one in * @param {Object} [options.className] - will use className as the attrType if you pass one in * @param {Object} [options.tagName] - will use tagName as the attrType if you pass one in - * @return {String} returns the attrType in this order - 1. If it was defined in the config for the command use that - 2. If no options object then return commandAttrType object from configuration - 3. If options defines a commandAttrType use that - 4. If a styleName/className/tagName is passed in options use the styleName/className/tagName properties from $.Arte.constants as the attrType + * @return {String} returns the attrType in this order + * 1. If it was defined in the config for the command use that + * 2. If no options object then return commandAttrType object from configuration + * 3. If options defines a commandAttrType use that + * 4. If a styleName/className/tagName is passed in options use the styleName/className/tagName + * properties from $.Arte.constants as the attrType */ var commandAttrType = function(commandName, options) { var commandConfig = configuration.commands[commandName]; @@ -59,7 +60,7 @@ commandOptions.execute = true; this.triggerEvent(constants.eventNames.onbeforecommand, commandOptions); - if (!commandOptions.execute) { // The client requested the cancelation of this command + if (!commandOptions.execute) { // The client requested the cancellation of this command return; } @@ -67,7 +68,7 @@ $.Arte.RichTextCommandApplier.createAndExecute(commandOptions); this.triggerEvent(constants.eventNames.oncommand, commandOptions); }; - + /** * Get the correct command value based on the given parameters * @param {String} commandName - name of the command get the value for @@ -76,7 +77,8 @@ * @param {Object} [options.styleValue] - if attrType == styleName and you pass this in then styleValue will be the the commandValue * @param {Object} [options.className] - if attrType == className and you pass this in then className will be the the commandValue * @param {Object} [options.commandValue] - if you pass in a commandValue that will be returned - * @return {String} try to return commandValue using options that were passed in, if none are found return the default value for the command specified in the configuration + * @return {String} try to return commandValue using options that were passed in, + * if none are found return the default value for the command specified in the configuration */ var getCommandValueOrDefault = function(commandName, attrType, options) { if (options && attrType === commandAttr.styleName && options.styleValue) { @@ -101,7 +103,8 @@ /** * Get the tagName for the given command * @param {String} commandName - name of the command get the tagName for - * @param {String} attrType - attrType you are using the command with - used to find the right tag (for example bold uses the B tag if we want to apply tags or a SPAN if applying styles/classes) + * @param {String} attrType - attrType you are using the command with - used to find the right tag + * (for example bold uses the B tag if we want to apply tags or a SPAN if applying styles/classes) * @param {Object} options * @param {String} options.tagName - if you already know the tagName and put it in the options it will be returned back * @return {String} return tagName for the given command or if there is none just return default inline/block tag based on command type @@ -125,7 +128,7 @@ return commandConfig.commandType === constants.commandType.inline ? configuration.defaultInlineTag : configuration.defaultBlockTag; }; - + /** * Creates command options to use for the command based on the configuration * @param {String} commandName - name of the command generate the options for @@ -222,7 +225,8 @@ * @param {Object} [options] * @param {jQuery} [options.element] - pass in your own element or use the rich text area by default * @param {Object} [options.styleName] - name of style you would like to toggle on the element (e.g. font-weight) - * @param {Object} [options.styleValue] - value for the styleName defined above that you would like to toggle on the element (e.g. bold) + * @param {Object} [options.styleValue] - value for the styleName defined above + * that you would like to toggle on the element (e.g. bold) * @param {Object} [options.className] - name of css class you would like to toggle on the element (e.g. arte-font-weight) */ "toggleStyleOnElement": function(options) { diff --git a/src/editor/plugins/InsertCommand.js b/src/editor/plugins/InsertCommand.js index 95de585..4374a55 100644 --- a/src/editor/plugins/InsertCommand.js +++ b/src/editor/plugins/InsertCommand.js @@ -5,7 +5,9 @@ var InsertCommand = function() { var publicApi = { /** - * Appends content to end of element if there is no selection. If there is a selection insert content at cursor position and replace all content in selection. Selects new content when done. + * Appends content to end of element if there is no selection. + * If there is a selection insert content at cursor position and replace all content in selection. + * Selects new content when done. * @param {Object} options - options object will be passed into the onbeforeinsert/onafterinsert events * @param {Object} options.commandValue - the html you want to insert */ diff --git a/src/editor/plugins/StateDetector.js b/src/editor/plugins/StateDetector.js index 2926518..4c77ba8 100644 --- a/src/editor/plugins/StateDetector.js +++ b/src/editor/plugins/StateDetector.js @@ -79,7 +79,9 @@ /** * Same as getAllStates however will only return the states that are applied to every node in the selection * @param {String} [commandName] If provided, only result the state of the given command (ie: fontFamily, bold, etc) - * @return {Object} returns object containing each commandName and the value only if it is applied to every single node in the selection (if there are multiple nodes in the selection and they have different values for the given command then the value will be false/null) + * @return {Object} returns object containing each commandName and the value only if it is applied + * to every single node in the selection (if there are multiple nodes in the selection + * and they have different values for the given command then the value will be false/null) */ getState: function(commandName) { var selectedNodes = $.Arte.util.getSelectedTextNodes.apply(this, [true]); @@ -89,8 +91,9 @@ /** * Get an array of all the states found within the current selection * (ie: if the current selection has both a bold and a non-bold component, get two results representing that) - * @param {String} [commandName] If provided, only result the state of the given command (ie: fontFamily, bold, etc) - * @return {Array} returns array containing an object for each selected node that lists out the commandName and it's value for that node + * @param {String} [commandName] If provided, only return the state of the given command (ie: fontFamily, bold, etc) + * @return {Array} returns array containing an object for each selected node + * that lists out the commandName and it's value for that node */ getAllStates: function(commandName) { var selectedNodes = $.Arte.util.getSelectedTextNodes.apply(this, [true]);