Y.namespace('M.atto_indent').Button = Y.Base.create('button', Y.M.editor_atto.EditorPlugin, [], {
initializer: function() {
+
this.addButton({
- // This is adding a <blockquote> which is not ideal but that is the easiest to put in place
- // for now. When disabling the styleWithCSS, some browser will use <blockquote> so we cannot
- // rely on it for <div>s, and that would not work when indenting lists either....
- // Handling it ourselves is even worse as it would require to get a parent and wrap
- // a div with a margin around it. Considering that multiple <p> should end up in the
- // same <div>, that table cells should not be wrapped, and that lists work differently too.
icon: 'e/increase_indent',
title: 'indent',
buttonName: 'indent',
- callback: function() {
- document.execCommand('indent', false, null);
-
- // Some browsers add style attributes to the blockquote, let's get rid of them.
- // It is really tricky to figure out what blockquote was just added, so removing
- // the styles on all of them seems OK.
- // Eg. Chrome changes the selection after adding the blockquote, so we cannot target it.
- // IE adds a dir attribute to the blockquote too, but it's probably OK to leave it...
- this.editor.all('blockquote').removeAttribute('style');
-
- // Mark the text as having been updated.
- this.markUpdated();
- }
+ callback: this.indent
});
- this.addBasicButton({
- exec: 'outdent',
+ this.addButton({
icon: 'e/decrease_indent',
- title: 'outdent'
+ title: 'outdent',
+ buttonName: 'outdent',
+ callback: this.outdent
});
+ },
+
+ /**
+ * Indents the currently selected content.
+ *
+ * @method indent
+ */
+ indent: function() {
+ // Save the current selection - we want to restore this.
+ var selection = rangy.saveSelection(),
+ blockquotes = this.editor.all('blockquote'),
+ count = blockquotes.size();
+
+ // Mark all existing block quotes in case the user has actually added some.
+ blockquotes.addClass('pre-existing');
+
+ // Run the indent command.
+ document.execCommand('indent', false, null);
+
+ // Get all blockquotes, both existing and new.
+ blockquotes = this.editor.all('blockquote');
+
+ if (blockquotes.size() !== count) {
+ // There are new block quotes, the indent exec has wrapped some content in block quotes in order
+ // to indent the selected content.
+ // We don't want blockquotes, we're going to convert them to divs.
+ this.replaceBlockquote(this.editor);
+ // Finally restore the seelction. The content has changed - sometimes this works - but not always :(
+ rangy.restoreSelection(selection);
+ } else if (blockquotes.size() > 0) {
+ // There were no new blockquotes, this happens if the user is indenting/outdenting a list.
+ blockquotes.removeClass('pre-existing');
+ }
+
+ // Remove the selection markers - a clean up really.
+ rangy.removeMarkers(selection);
+
+ // Mark the text as having been updated.
+ this.markUpdated();
+ },
+
+ /**
+ * Outdents the currently selected content.
+ *
+ * @method outdent
+ */
+ outdent: function() {
+ // Save the selection we will want to restore it.
+ var selection = rangy.saveSelection(),
+ blockquotes = this.editor.all('blockquote'),
+ count = blockquotes.size();
+
+ // Mark existing blockquotes so that we don't convert them later.
+ blockquotes.addClass('pre-existing');
+
+ // Replace all div indents with blockquote indents so that we can rely on the browser functionality.
+ this.replaceEditorIndents(this.editor);
+
+ // Restore the users selection - otherwise the next outdent operation won't work!
+ rangy.restoreSelection(selection);
+ // And save it once more.
+ selection = rangy.saveSelection();
+
+ // Outdent.
+ document.execCommand('outdent', false, null);
+
+ // Get all blockquotes so that we can work out what happened.
+ blockquotes = this.editor.all('blockquote');
+
+ if (blockquotes.size() !== count) {
+ // The number of blockquotes hasn't changed.
+ // This occurs when the user has outdented a list item.
+ this.replaceBlockquote(this.editor);
+ rangy.restoreSelection(selection);
+ } else if (blockquotes.size() > 0) {
+ // The number of blockquotes is the same and is more than 0 we just need to clean up the class
+ // we added to mark pre-existing blockquotes.
+ blockquotes.removeClass('pre-existing');
+ }
+
+ // Clean up any left over selection markers.
+ rangy.removeMarkers(selection);
+
+ // Mark the text as having been updated.
+ this.markUpdated();
+ },
+
+ /**
+ * Replaces all blockquotes within an editor with div indents.
+ * @method replaceBlockquote
+ * @param Editor editor
+ */
+ replaceBlockquote: function(editor) {
+ editor.all('blockquote').setAttribute('data-iterate', true);
+ var blockquote = editor.one('blockquote'),
+ margindir = (Y.one('body.dir-ltr')) ? 'marginLeft' : 'marginRight';
+ while (blockquote) {
+ blockquote.removeAttribute('data-iterate');
+ if (blockquote.hasClass('pre-existing')) {
+ blockquote.removeClass('pre-existing');
+ } else {
+ var clone = Y.Node.create('<div></div>').setAttrs(blockquote.getAttrs()).setStyle(margindir, '30px').addClass('editor-indent');
+ // We use childNodes here because we are interested in both type 1 and 3 child nodes.
+ var children = blockquote.getDOMNode().childNodes, child;
+ while (child = children[0]) {
+ clone.append(child);
+ }
+ blockquote.replace(clone);
+ }
+ blockquote = editor.one('blockquote[data-iterate]');
+ }
+ },
+
+ /**
+ * Replaces all div indents with blockquotes.
+ * @method replaceEditorIndents
+ * @param Editor editor
+ */
+ replaceEditorIndents: function(editor) {
+ // We use the editor-indent class because it is preserved between saves.
+ var indent = editor.one('div.editor-indent');
+ while (indent) {
+ var clone = Y.Node.create('<blockquote></blockquote>').setAttrs(indent.getAttrs()).removeClass('editor-indent');
+ // We use childNodes here because we are interested in both type 1 and 3 child nodes.
+ var children = indent.getDOMNode().childNodes, child;
+ while (child = children[0]) {
+ clone.append(child);
+ }
+ indent.replace(clone);
+ indent = editor.one('div.editor-indent');
+ }
}
});