Skip to content

Commit

Permalink
MDL-43990 Atto: Add a function for changing the formatting of the sel…
Browse files Browse the repository at this point in the history
…ected block.
  • Loading branch information
Damyon Wiese committed Mar 26, 2014
1 parent 0fa78b8 commit 34f5867
Show file tree
Hide file tree
Showing 4 changed files with 468 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,47 @@ CSS = {
*/
M.editor_atto = M.editor_atto || {

/**
* List of known block level tags.
* Taken from "https://developer.mozilla.org/en-US/docs/HTML/Block-level_elements".
*
* @type {Array}
*/
BLOCK_TAGS : [
'address',
'article',
'aside',
'audio',
'blockquote',
'canvas',
'dd',
'div',
'dl',
'fieldset',
'figcaption',
'figure',
'footer',
'form',
'h1',
'h2',
'h3',
'h4',
'h5',
'h6',
'header',
'hgroup',
'hr',
'noscript',
'ol',
'output',
'p',
'pre',
'section',
'table',
'tfoot',
'ul',
'video'],

/**
* List of attached button handlers to prevent duplicates.
*/
Expand Down Expand Up @@ -391,7 +432,9 @@ M.editor_atto = M.editor_atto || {
node = Y.one(selection.startContainer);
}

return node && node.ancestor('#' + elementid + 'editable') !== null;
var editable = M.editor_atto.get_editable_node(elementid);

return node && editable.contains(node);
},

/**
Expand Down Expand Up @@ -640,7 +683,6 @@ M.editor_atto = M.editor_atto || {

/**
* Check that a YUI node it at least partly contained by the selection.
* @param Range selection
* @param Y.Node node
* @return boolean
*/
Expand All @@ -661,13 +703,21 @@ M.editor_atto = M.editor_atto || {

/**
* Get the dom node representing the common anscestor of the selection nodes.
* @return DOMNode
* @return DOMNode or false
*/
get_selection_parent_node : function() {
var selection = M.editor_atto.get_selection();
if (selection.length > 0) {
return selection[0].commonAncestorContainer;
if (selection.length) {
selection = selection.pop();
}

if (selection.commonAncestorContainer) {
return selection.commonAncestorContainer;
} else if (selection.parentElement) {
return selection.parentElement();
}
// No selection
return false;
},

/**
Expand Down Expand Up @@ -699,6 +749,106 @@ M.editor_atto = M.editor_atto || {
selection.select();
}
}
},

/**
* Change the formatting for the current selection.
* Also changes the selection to the newly formatted block (allows applying multiple styles to a block).
*
* @param {String} elementid - The editor elementid.
* @param {String} blocktag - Change the block level tag to this. Empty string, means do not change the tag.
* @param {Object} attributes - The keys and values for attributes to be added/changed in the block tag.
* @return Y.Node - if there was a selection.
*/
format_selection_block : function(elementid, blocktag, attributes) {
// First find the nearest ancestor of the selection that is a block level element.
var selectionparentnode = M.editor_atto.get_selection_parent_node(),
boundary,
cell,
nearestblock,
newcontent,
match,
replacement;

if (!selectionparentnode) {
// No selection, nothing to format.
return;
}

boundary = M.editor_atto.get_editable_node(elementid);

selectionparentnode = Y.one(selectionparentnode);

// If there is a table cell in between the selectionparentnode and the boundary,
// move the boundary to the table cell.
// This is because we might have a table in a div, and we select some text in a cell,
// want to limit the change in style to the table cell, not the entire table (via the outer div).
cell = selectionparentnode.ancestor(function (node) {
var tagname = node.get('tagName');
if (tagname) {
tagname = tagname.toLowerCase();
}
return (node === boundary) ||
(tagname === 'td') ||
(tagname === 'th');
}, true);

if (cell) {
// Limit the scope to the table cell.
boundary = cell;
}

nearestblock = selectionparentnode.ancestor(M.editor_atto.BLOCK_TAGS.join(', '), true);
if (nearestblock) {
// Check that the block is contained by the boundary.
match = nearestblock.ancestor(function (node) {
return node === boundary;
}, false);

if (!match) {
nearestblock = false;
}
}

// No valid block element - make one.
if (!nearestblock) {
// There is no block node in the content, wrap the content in a p and use that.
newcontent = Y.Node.create('<p></p>');
boundary.get('childNodes').each(function (child) {
newcontent.append(child.remove());
});
boundary.append(newcontent);
nearestblock = newcontent;
}

// Guaranteed to have a valid block level element contained in the contenteditable region.
// Change the tag to the new block level tag.
if (blocktag && blocktag !== '') {
// Change the block level node for a new one.
replacement = Y.Node.create('<' + blocktag + '></' + blocktag + '>');
// Copy all attributes.
replacement.setAttrs(nearestblock.getAttrs());
// Copy all children.
nearestblock.get('childNodes').each(function (child) {
child.remove();
replacement.append(child);
});

nearestblock.replace(replacement);
nearestblock = replacement;
}

// Set the attributes on the block level tag.
if (attributes) {
nearestblock.setAttrs(attributes);
}

// Change the selection to the modified block. This makes sense when we might apply multiple styles
// to the block.
var selection = M.editor_atto.get_selection_from_node(nearestblock);
M.editor_atto.set_selection(selection);

return nearestblock;
}

};
Expand Down
Loading

0 comments on commit 34f5867

Please sign in to comment.