diff --git a/lang/en/atto_styles.php b/lang/en/atto_styles.php index 1379cfe..3d518d9 100644 --- a/lang/en/atto_styles.php +++ b/lang/en/atto_styles.php @@ -23,6 +23,13 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +$string['blockmethod'] = 'Block style method'; +$string['blockmethod_desc'] = 'The method used for adding block styles, each finds the block-level element that surrounds the selection and then: +'; $string['config'] = 'Styles configuration'; $string['config_desc'] = 'Configuration for the styles widget for Atto in JSON format.
@@ -80,6 +87,9 @@
  • Bootstrap alerts
  • Bootstrap helper classes
  • '; +$string['method_adddiv'] = 'Surround'; +$string['method_replace'] = 'Replace'; +$string['method_setclass'] = 'Set class'; $string['nostyle'] = 'No style'; $string['pluginname'] = 'Styles'; $string['settings'] = 'Styles settings'; diff --git a/lib.php b/lib.php index da63637..83f5fdd 100644 --- a/lib.php +++ b/lib.php @@ -55,5 +55,7 @@ function atto_styles_params_for_js($elementid, $options, $fpoptions) { $styles = json_encode($styles); - return array('styles' => $styles); + $blockmethod = get_config('atto_styles', 'blockmethod'); + + return array('styles' => $styles, 'blockmethod' => $blockmethod); } diff --git a/settings.php b/settings.php index 9b69bbf..7ed4d8c 100644 --- a/settings.php +++ b/settings.php @@ -39,4 +39,14 @@ $desc, $default); $settings->add($setting); + $blockmethods = array( + 'replace' => get_string('method_replace', 'atto_styles'), + 'setclass' => get_string('method_setclass', 'atto_styles'), + 'adddiv' => get_string('method_adddiv', 'atto_styles'), + ); + $setting = new admin_setting_configselect('atto_styles/blockmethod', + new lang_string('blockmethod', 'atto_styles'), + new lang_string('blockmethod_desc', 'atto_styles'), + 'replace', $blockmethods); + $settings->add($setting); } diff --git a/version.php b/version.php index dbfa3ab..372e8cb 100644 --- a/version.php +++ b/version.php @@ -26,7 +26,7 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'atto_styles'; -$plugin->version = 2015051100; -$plugin->release = '2.9 (Build: 2015081800)'; -$plugin->requires = 2015051100; +$plugin->version = 2015111300; +$plugin->release = '2.8 (Build: 2015111300)'; +$plugin->requires = 2014111000; // Moodle 2.8+ $plugin->maturity = MATURITY_STABLE; diff --git a/yui/build/moodle-atto_styles-button/moodle-atto_styles-button-debug.js b/yui/build/moodle-atto_styles-button/moodle-atto_styles-button-debug.js index ac42fcf..181cc34 100644 --- a/yui/build/moodle-atto_styles-button/moodle-atto_styles-button-debug.js +++ b/yui/build/moodle-atto_styles-button/moodle-atto_styles-button-debug.js @@ -95,37 +95,33 @@ Y.namespace('M.atto_styles').Button = Y.Base.create('button', Y.M.editor_atto.Ed * @private */ _changeStyle: function(e, style) { - var eID, element, p, pstyle; + var element, node, blockMethod; + blockMethod = this.get('blockmethod'); if (style[0] === '') { element = window.getSelection().focusNode; - for (p = element; p; p = p.parentNode) { - if (p.nodeType !== 1) { - continue; - } - pstyle = window.getComputedStyle(p, null); - if (pstyle) { - p.removeAttribute('class'); - break; + if (blockMethod === 'replace') { + node = this._getParentBlockOld(element); // Find a styled element. + } else { + node = this._getParentBlock(element, true); // Find a block or list-item element. + if (blockMethod === 'adddiv') { + node = this._getDiv(node); } } - return; + if (node) { + node.removeAttribute('class'); + } } else if (style[0] === '') { - document.execCommand('formatBlock', false, '
    '); + if (blockMethod === 'replace') { + document.execCommand('formatBlock', false, '
    '); + } element = window.getSelection().focusNode; - for (p = element; p; p = p.parentNode) { - if (p.nodeType !== 1) { - continue; - } - pstyle = window.getComputedStyle(p, null); - if (pstyle) { - var displaystyle = pstyle.getPropertyValue('display'); - if (displaystyle === 'block') { - eID = p; - break; - } - } + node = this._getParentBlock(element, (blockMethod !== 'replace')); + if (blockMethod === 'adddiv') { + node = this._addDivIfNeeded(node); + } + if (node) { + node.setAttribute('class', style[1]); } - eID.setAttribute('class', style[1]); } else { var styles = style[1].split(" "); this.get('host').toggleInlineSelectionClass(styles); @@ -134,6 +130,105 @@ Y.namespace('M.atto_styles').Button = Y.Base.create('button', Y.M.editor_atto.Ed this.markUpdated(); }, + /** + * Find the nearest parent with display:block (or list-item) + * @param el Node + * @param includeListItem bool - true if list-item is a valid display type to return + * @returns Node|null + * @private + */ + _getParentBlock: function(el, includeListItem) { + var p, node, display; + for (p = el; p; p = p.parentNode) { + if (p.nodeType !== 1) { + continue; + } + node = Y.one(p); + if (node.hasClass('editor_atto_content')) { + return null; + } + display = node.getComputedStyle('display'); + if (display === 'block' || display === 'inline-block' || (includeListItem && display === 'list-item')) { + return node; + } + } + }, + + /** + * Find the nearest parent with any associated style. + * @param el Node + * @returns Node|null + * @private + */ + _getParentBlockOld: function(el) { + var p, pstyle; + for (p = el; p; p = p.parentNode) { + if (p.nodeType !== 1) { + continue; + } + pstyle = window.getComputedStyle(p, null); + if (pstyle) { + return p; + } + } + return null; + }, + + /** + * Make sure we are targetting a div element - creating one if none is found. + * @param el Node + * @returns Node|null + * @private + */ + _addDivIfNeeded: function(el) { + var parent, next, div; + if (!el) { + return null; + } + if (el.get('tagName').toLowerCase() === 'div') { + return el; // The block element is already a div - just return it. + } + parent = el.get('parentNode'); + if (parent.get('tagName').toLowerCase() === 'div') { + if (parent.get('children').size() === 1) { + return parent; // The block element is surrounded by a div - return that. + } + } + + // Need to wrap the existing element in a div. + next = el.next(); + div = Y.Node.create('
    '); + div.appendChild(el); + if (next) { + parent.insertBefore(div, next); + } else { + parent.appendChild(div); + } + return div; + }, + + /** + * Look for a div that + * @param el Node + * @returns Node|null + * @private + */ + _getDiv: function(el) { + var parent; + if (!el) { + return null; + } + if (el.get('tagName').toLowerCase() === 'div') { + return el; // The block element is already a div - just return it. + } + parent = el.get('parentNode'); + if (parent.get('tagName').toLowerCase() === 'div') { + return parent; // The block is surrounded by a div - return it. + } + + return null; // Not able to find a suitable div. + }, + hasRangeSelected: function() { var selection, range; @@ -154,6 +249,9 @@ Y.namespace('M.atto_styles').Button = Y.Base.create('button', Y.M.editor_atto.Ed */ styles: { value: {} + }, + blockmethod: { + value: 'setclass' } } }); diff --git a/yui/build/moodle-atto_styles-button/moodle-atto_styles-button-min.js b/yui/build/moodle-atto_styles-button/moodle-atto_styles-button-min.js index 9fa8509..f5551ac 100644 --- a/yui/build/moodle-atto_styles-button/moodle-atto_styles-button-min.js +++ b/yui/build/moodle-atto_styles-button/moodle-atto_styles-button-min.js @@ -1 +1 @@ -YUI.add("moodle-atto_styles-button",function(e,t){var n="atto_styles";e.namespace("M.atto_styles").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{initializer:function(){var t=this.get("styles");t=JSON.parse(t);var r=[],i,s;e.Array.each(t,function(e){i="",s="",e.type==="block"?i='':e.type!=="nostyle"&&(i='',s=''),r.push({text:s+i+e.title+"",callbackArgs:["<"+e.type+">",e.classes]})});var o=this._showToolbarMenu;this._showToolbarMenu=function(e,t){o.call(this,e,t);var n,r,i;n=this.hasRangeSelected(),r=this.menus[t.buttonClass],i=r.get("contentBox"),n?i.removeClass("disableinline"):i.addClass("disableinline")},this.addToolbarMenu({icon:"icon",iconComponent:n,buttonClass:"styles",globalItemConfig:{callback:this._changeStyle},items:r})},_changeStyle:function(e,t){var n,r,i,s;if(t[0]===""){r=window.getSelection().focusNode;for(i=r;i;i=i.parentNode){if(i.nodeType!==1)continue;s=window.getComputedStyle(i,null);if(s){i.removeAttribute("class");break}}return}if(t[0]===""){document.execCommand("formatBlock",!1,"
    "),r=window.getSelection().focusNode;for(i=r;i;i=i.parentNode){if(i.nodeType!==1)continue;s=window.getComputedStyle(i,null);if(s){var o=s.getPropertyValue("display");if(o==="block"){n=i;break}}}n.setAttribute("class",t[1])}else{var u=t[1].split(" ");this.get("host").toggleInlineSelectionClass(u)}this.markUpdated()},hasRangeSelected:function(){var e,t;return e=rangy.getSelection(),e.rangeCount?(t=e.getRangeAt(0),!t.collapsed):!1}},{ATTRS:{styles:{value:{}}}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]}); +YUI.add("moodle-atto_styles-button",function(e,t){var n="atto_styles";e.namespace("M.atto_styles").Button=e.Base.create("button",e.M.editor_atto.EditorPlugin,[],{initializer:function(){var t=this.get("styles");t=JSON.parse(t);var r=[],i,s;e.Array.each(t,function(e){i="",s="",e.type==="block"?i='':e.type!=="nostyle"&&(i='',s=''),r.push({text:s+i+e.title+"",callbackArgs:["<"+e.type+">",e.classes]})});var o=this._showToolbarMenu;this._showToolbarMenu=function(e,t){o.call(this,e,t);var n,r,i;n=this.hasRangeSelected(),r=this.menus[t.buttonClass],i=r.get("contentBox"),n?i.removeClass("disableinline"):i.addClass("disableinline")},this.addToolbarMenu({icon:"icon",iconComponent:n,buttonClass:"styles",globalItemConfig:{callback:this._changeStyle},items:r})},_changeStyle:function(e,t){var n,r,i;i=this.get("blockmethod");if(t[0]==="")n=window.getSelection().focusNode,i==="replace"?r=this._getParentBlockOld(n):(r=this._getParentBlock(n,!0),i==="adddiv"&&(r=this._getDiv(r))),r&&r.removeAttribute("class");else if(t[0]==="")i==="replace"&&document.execCommand("formatBlock",!1,"
    "),n=window.getSelection().focusNode,r=this._getParentBlock(n,i!=="replace"),i==="adddiv"&&(r=this._addDivIfNeeded(r)),r&&r.setAttribute("class",t[1]);else{var s=t[1].split(" ");this.get("host").toggleInlineSelectionClass(s)}this.markUpdated()},_getParentBlock:function(t,n){var r,i,s;for(r=t;r;r=r.parentNode){if(r.nodeType!==1)continue;i=e.one(r);if(i.hasClass("editor_atto_content"))return null;s=i.getComputedStyle("display");if(s==="block"||s==="inline-block"||n&&s==="list-item")return i}},_getParentBlockOld:function(e){var t,n;for(t=e;t;t=t.parentNode){if(t.nodeType!==1)continue;n=window.getComputedStyle(t,null);if(n)return t}return null},_addDivIfNeeded:function(t){var n,r,i;return t?t.get("tagName").toLowerCase()==="div"?t:(n=t.get("parentNode"),n.get("tagName").toLowerCase()==="div"&&n.get("children").size()===1?n:(r=t.next(),i=e.Node.create("
    "),i.appendChild(t),r?n.insertBefore(i,r):n.appendChild(i),i)):null},_getDiv:function(e){var t;return e?e.get("tagName").toLowerCase()==="div"?e:(t=e.get("parentNode"),t.get("tagName").toLowerCase()==="div"?t:null):null},hasRangeSelected:function(){var e,t;return e=rangy.getSelection(),e.rangeCount?(t=e.getRangeAt(0),!t.collapsed):!1}},{ATTRS:{styles:{value:{}},blockmethod:{value:"setclass"}}})},"@VERSION@",{requires:["moodle-editor_atto-plugin"]}); diff --git a/yui/build/moodle-atto_styles-button/moodle-atto_styles-button.js b/yui/build/moodle-atto_styles-button/moodle-atto_styles-button.js index ac42fcf..181cc34 100644 --- a/yui/build/moodle-atto_styles-button/moodle-atto_styles-button.js +++ b/yui/build/moodle-atto_styles-button/moodle-atto_styles-button.js @@ -95,37 +95,33 @@ Y.namespace('M.atto_styles').Button = Y.Base.create('button', Y.M.editor_atto.Ed * @private */ _changeStyle: function(e, style) { - var eID, element, p, pstyle; + var element, node, blockMethod; + blockMethod = this.get('blockmethod'); if (style[0] === '') { element = window.getSelection().focusNode; - for (p = element; p; p = p.parentNode) { - if (p.nodeType !== 1) { - continue; - } - pstyle = window.getComputedStyle(p, null); - if (pstyle) { - p.removeAttribute('class'); - break; + if (blockMethod === 'replace') { + node = this._getParentBlockOld(element); // Find a styled element. + } else { + node = this._getParentBlock(element, true); // Find a block or list-item element. + if (blockMethod === 'adddiv') { + node = this._getDiv(node); } } - return; + if (node) { + node.removeAttribute('class'); + } } else if (style[0] === '') { - document.execCommand('formatBlock', false, '
    '); + if (blockMethod === 'replace') { + document.execCommand('formatBlock', false, '
    '); + } element = window.getSelection().focusNode; - for (p = element; p; p = p.parentNode) { - if (p.nodeType !== 1) { - continue; - } - pstyle = window.getComputedStyle(p, null); - if (pstyle) { - var displaystyle = pstyle.getPropertyValue('display'); - if (displaystyle === 'block') { - eID = p; - break; - } - } + node = this._getParentBlock(element, (blockMethod !== 'replace')); + if (blockMethod === 'adddiv') { + node = this._addDivIfNeeded(node); + } + if (node) { + node.setAttribute('class', style[1]); } - eID.setAttribute('class', style[1]); } else { var styles = style[1].split(" "); this.get('host').toggleInlineSelectionClass(styles); @@ -134,6 +130,105 @@ Y.namespace('M.atto_styles').Button = Y.Base.create('button', Y.M.editor_atto.Ed this.markUpdated(); }, + /** + * Find the nearest parent with display:block (or list-item) + * @param el Node + * @param includeListItem bool - true if list-item is a valid display type to return + * @returns Node|null + * @private + */ + _getParentBlock: function(el, includeListItem) { + var p, node, display; + for (p = el; p; p = p.parentNode) { + if (p.nodeType !== 1) { + continue; + } + node = Y.one(p); + if (node.hasClass('editor_atto_content')) { + return null; + } + display = node.getComputedStyle('display'); + if (display === 'block' || display === 'inline-block' || (includeListItem && display === 'list-item')) { + return node; + } + } + }, + + /** + * Find the nearest parent with any associated style. + * @param el Node + * @returns Node|null + * @private + */ + _getParentBlockOld: function(el) { + var p, pstyle; + for (p = el; p; p = p.parentNode) { + if (p.nodeType !== 1) { + continue; + } + pstyle = window.getComputedStyle(p, null); + if (pstyle) { + return p; + } + } + return null; + }, + + /** + * Make sure we are targetting a div element - creating one if none is found. + * @param el Node + * @returns Node|null + * @private + */ + _addDivIfNeeded: function(el) { + var parent, next, div; + if (!el) { + return null; + } + if (el.get('tagName').toLowerCase() === 'div') { + return el; // The block element is already a div - just return it. + } + parent = el.get('parentNode'); + if (parent.get('tagName').toLowerCase() === 'div') { + if (parent.get('children').size() === 1) { + return parent; // The block element is surrounded by a div - return that. + } + } + + // Need to wrap the existing element in a div. + next = el.next(); + div = Y.Node.create('
    '); + div.appendChild(el); + if (next) { + parent.insertBefore(div, next); + } else { + parent.appendChild(div); + } + return div; + }, + + /** + * Look for a div that + * @param el Node + * @returns Node|null + * @private + */ + _getDiv: function(el) { + var parent; + if (!el) { + return null; + } + if (el.get('tagName').toLowerCase() === 'div') { + return el; // The block element is already a div - just return it. + } + parent = el.get('parentNode'); + if (parent.get('tagName').toLowerCase() === 'div') { + return parent; // The block is surrounded by a div - return it. + } + + return null; // Not able to find a suitable div. + }, + hasRangeSelected: function() { var selection, range; @@ -154,6 +249,9 @@ Y.namespace('M.atto_styles').Button = Y.Base.create('button', Y.M.editor_atto.Ed */ styles: { value: {} + }, + blockmethod: { + value: 'setclass' } } }); diff --git a/yui/src/button/js/button.js b/yui/src/button/js/button.js index 1803e3c..602ed73 100644 --- a/yui/src/button/js/button.js +++ b/yui/src/button/js/button.js @@ -93,37 +93,33 @@ Y.namespace('M.atto_styles').Button = Y.Base.create('button', Y.M.editor_atto.Ed * @private */ _changeStyle: function(e, style) { - var eID, element, p, pstyle; + var element, node, blockMethod; + blockMethod = this.get('blockmethod'); if (style[0] === '') { element = window.getSelection().focusNode; - for (p = element; p; p = p.parentNode) { - if (p.nodeType !== 1) { - continue; - } - pstyle = window.getComputedStyle(p, null); - if (pstyle) { - p.removeAttribute('class'); - break; + if (blockMethod === 'replace') { + node = this._getParentBlockOld(element); // Find a styled element. + } else { + node = this._getParentBlock(element, true); // Find a block or list-item element. + if (blockMethod === 'adddiv') { + node = this._getDiv(node); } } - return; + if (node) { + node.removeAttribute('class'); + } } else if (style[0] === '') { - document.execCommand('formatBlock', false, '
    '); + if (blockMethod === 'replace') { + document.execCommand('formatBlock', false, '
    '); + } element = window.getSelection().focusNode; - for (p = element; p; p = p.parentNode) { - if (p.nodeType !== 1) { - continue; - } - pstyle = window.getComputedStyle(p, null); - if (pstyle) { - var displaystyle = pstyle.getPropertyValue('display'); - if (displaystyle === 'block') { - eID = p; - break; - } - } + node = this._getParentBlock(element, (blockMethod !== 'replace')); + if (blockMethod === 'adddiv') { + node = this._addDivIfNeeded(node); + } + if (node) { + node.setAttribute('class', style[1]); } - eID.setAttribute('class', style[1]); } else { var styles = style[1].split(" "); this.get('host').toggleInlineSelectionClass(styles); @@ -132,6 +128,105 @@ Y.namespace('M.atto_styles').Button = Y.Base.create('button', Y.M.editor_atto.Ed this.markUpdated(); }, + /** + * Find the nearest parent with display:block (or list-item) + * @param el Node + * @param includeListItem bool - true if list-item is a valid display type to return + * @returns Node|null + * @private + */ + _getParentBlock: function(el, includeListItem) { + var p, node, display; + for (p = el; p; p = p.parentNode) { + if (p.nodeType !== 1) { + continue; + } + node = Y.one(p); + if (node.hasClass('editor_atto_content')) { + return null; + } + display = node.getComputedStyle('display'); + if (display === 'block' || display === 'inline-block' || (includeListItem && display === 'list-item')) { + return node; + } + } + }, + + /** + * Find the nearest parent with any associated style. + * @param el Node + * @returns Node|null + * @private + */ + _getParentBlockOld: function(el) { + var p, pstyle; + for (p = el; p; p = p.parentNode) { + if (p.nodeType !== 1) { + continue; + } + pstyle = window.getComputedStyle(p, null); + if (pstyle) { + return p; + } + } + return null; + }, + + /** + * Make sure we are targetting a div element - creating one if none is found. + * @param el Node + * @returns Node|null + * @private + */ + _addDivIfNeeded: function(el) { + var parent, next, div; + if (!el) { + return null; + } + if (el.get('tagName').toLowerCase() === 'div') { + return el; // The block element is already a div - just return it. + } + parent = el.get('parentNode'); + if (parent.get('tagName').toLowerCase() === 'div') { + if (parent.get('children').size() === 1) { + return parent; // The block element is surrounded by a div - return that. + } + } + + // Need to wrap the existing element in a div. + next = el.next(); + div = Y.Node.create('
    '); + div.appendChild(el); + if (next) { + parent.insertBefore(div, next); + } else { + parent.appendChild(div); + } + return div; + }, + + /** + * Look for a div that + * @param el Node + * @returns Node|null + * @private + */ + _getDiv: function(el) { + var parent; + if (!el) { + return null; + } + if (el.get('tagName').toLowerCase() === 'div') { + return el; // The block element is already a div - just return it. + } + parent = el.get('parentNode'); + if (parent.get('tagName').toLowerCase() === 'div') { + return parent; // The block is surrounded by a div - return it. + } + + return null; // Not able to find a suitable div. + }, + hasRangeSelected: function() { var selection, range; @@ -152,6 +247,9 @@ Y.namespace('M.atto_styles').Button = Y.Base.create('button', Y.M.editor_atto.Ed */ styles: { value: {} + }, + blockmethod: { + value: 'setclass' } } });