Skip to content
This repository has been archived by the owner on Jun 15, 2021. It is now read-only.

Commit

Permalink
Merge pull request #97 from guardian/sc-ff-backspace-multi-para
Browse files Browse the repository at this point in the history
Fix breaking out of <p> when deleting across paragraphs in FF
  • Loading branch information
theefer committed Apr 11, 2014
2 parents 8b6cb6e + 79f06d3 commit ddecae9
Show file tree
Hide file tree
Showing 11 changed files with 341 additions and 214 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"plumber-write": "~0.2.0",
"q": "~1.0.0",
"request": "~2.33.0",
"selenium-webdriver": "~2.37.0"
"selenium-webdriver": "^2.41.0"
},
"scripts": {
"test": "node test/runner",
Expand Down
5 changes: 3 additions & 2 deletions setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ npm install
bower install

# Download the selenium JAR
SELENIUM_VERSION=2.37.0
SELENIUM_VERSION=2.41.0
SELENIUM_MINOR_VERSION=2.41
mkdir -p vendor
wget -O vendor/selenium-server-standalone-$SELENIUM_VERSION.jar \
https://selenium.googlecode.com/files/selenium-server-standalone-$SELENIUM_VERSION.jar
https://selenium-release.storage.googleapis.com/$SELENIUM_MINOR_VERSION/selenium-server-standalone-$SELENIUM_VERSION.jar
59 changes: 59 additions & 0 deletions src/dom-observer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
define([
'lodash-modern/arrays/flatten',
'lodash-modern/collections/toArray'
], function (
flatten,
toArray
) {

function observeDomChanges(el, callback) {
function notEmptyTextNode(node) {
return ! (node.nodeType === Node.TEXT_NODE && node.textContent === '');
}

function notSelectionMarkerNode(node) {
return ! (node.nodeType === Node.ELEMENT_NODE && node.className === 'scribe-marker');
}

function includeRealMutations(mutations) {
var allChangedNodes = flatten(mutations.map(function(mutation) {
var added = toArray(mutation.addedNodes);
var removed = toArray(mutation.removedNodes);
return added.concat(removed);
}));

var realChangedNodes = allChangedNodes.
filter(notEmptyTextNode).
filter(notSelectionMarkerNode);

return realChangedNodes.length > 0;
}


// Flag to avoid running recursively
var runningPostMutation = false;
var observer = new MutationObserver(function(mutations) {
if (! runningPostMutation && includeRealMutations(mutations)) {
runningPostMutation = true;

callback();

// We must yield to let any mutation we caused be triggered
// in the next cycle
setTimeout(function() {
runningPostMutation = false;
}, 0);
}
});

observer.observe(el, {
attributes: true,
childList: true,
subtree: true
});

return observer;
}

return observeDomChanges;
});
3 changes: 0 additions & 3 deletions src/plugins/core/commands.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
define([
'./commands/indent',
'./commands/insert-html',
'./commands/insert-list',
'./commands/outdent',
'./commands/redo',
Expand All @@ -9,7 +8,6 @@ define([
'./commands/undo'
], function (
indent,
insertHTML,
insertList,
outdent,
redo,
Expand All @@ -22,7 +20,6 @@ define([

return {
indent: indent,
insertHTML: insertHTML,
insertList: insertList,
outdent: outdent,
redo: redo,
Expand Down
110 changes: 0 additions & 110 deletions src/plugins/core/commands/insert-html.js

This file was deleted.

120 changes: 120 additions & 0 deletions src/plugins/core/formatters/html/enforce-p-elements.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
define([
'lodash-modern/arrays/last',
'lodash-modern/collections/contains'
], function (
last,
contains
) {

/**
* Chrome and Firefox: Upon pressing backspace inside of a P, the
* browser deletes the paragraph element, leaving the caret (and any
* content) outside of any P.
*
* Firefox: Erasing across multiple paragraphs, or outside of a
* whole paragraph (e.g. by ‘Select All’) will leave content outside
* of any P.
*
* Entering a new line in a pristine state state will insert
* `<div>`s (in Chrome) or `<br>`s (in Firefox) where previously we
* had `<p>`'s. This patches the behaviour of delete/backspace so
* that we do not end up in a pristine state.
*/

'use strict';


// TODO: not exhaustive?
var blockElementNames = ['P', 'LI', 'DIV', 'BLOCKQUOTE', 'UL', 'OL', 'H2'];
function isBlockElement(node) {
return contains(blockElementNames, node.nodeName);
}

/**
* Wrap consecutive inline elements and text nodes in a P element.
*/
function wrapChildNodes(parentNode) {
var groups = Array.prototype.reduce.call(parentNode.childNodes,
function (accumulator, binChildNode) {
var group = last(accumulator);
if (! group) {
startNewGroup();
} else {
var isBlockGroup = isBlockElement(group[0]);
if (isBlockGroup === isBlockElement(binChildNode)) {
group.push(binChildNode);
} else {
startNewGroup();
}
}

return accumulator;

function startNewGroup() {
var newGroup = [binChildNode];
accumulator.push(newGroup);
}
}, []);

var consecutiveInlineElementsAndTextNodes = groups.filter(function (group) {
var isBlockGroup = isBlockElement(group[0]);
return ! isBlockGroup;
});

consecutiveInlineElementsAndTextNodes.forEach(function (nodes) {
var pElement = document.createElement('p');
nodes[0].parentNode.insertBefore(pElement, nodes[0]);
nodes.forEach(function (node) {
pElement.appendChild(node);
});
});

parentNode._isWrapped = true;
}

// Traverse the tree, wrapping child nodes as we go.
function traverse(parentNode) {
var treeWalker = document.createTreeWalker(parentNode, NodeFilter.SHOW_ELEMENT);
var node = treeWalker.firstChild();

// FIXME: does this recurse down?

while (node) {
// TODO: At the moment we only support BLOCKQUOTEs. See failing
// tests.
if (node.nodeName === 'BLOCKQUOTE' && ! node._isWrapped) {
wrapChildNodes(node);
traverse(parentNode);
break;
}
node = treeWalker.nextSibling();
}
}


return function () {
return function (scribe) {

scribe.htmlFormatter.formatters.push(function (html) {
/**
* Ensure P mode.
*
* Wrap any orphan text nodes in a P element.
*/
// TODO: This should be configurable and also correct markup such as
// `<ul>1</ul>` to <ul><li>2</li></ul>`. See skipped tests.
// TODO: This should probably be a part of HTML Janitor, or some other
// formatter.
var bin = document.createElement('div');
bin.innerHTML = html;

wrapChildNodes(bin);
traverse(bin);

return bin.innerHTML;
});

};
};

});
51 changes: 51 additions & 0 deletions src/plugins/core/formatters/html/ensure-selectable-containers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
define(function () {

/**
* Chrome and Firefox: Block-level elements like `<p>` or `<li>`
* need to contain either text or a `<br>` to remain selectable.
*/

'use strict';

function containsChild(node, elementType) {
// FIXME: do we need to recurse further down?
for (var n = node.firstChild; n; n = n.nextSibling) {
if (n.tagName === elementType) {
return true;
}
}

return false;
}

function traverse(parentNode) {
var treeWalker = document.createTreeWalker(parentNode, NodeFilter.SHOW_ELEMENT);
var node = treeWalker.firstChild();

while (node) {
// Find any block-level container that contains neither text nor a <br>
if ((node.nodeName === 'P' || node.nodeName === 'LI') &&
(node.textContent === '') &&
(! containsChild(node, 'BR'))) {
node.appendChild(document.createElement('br'));
}
node = treeWalker.nextSibling();
}
}

return function () {
return function (scribe) {

scribe.htmlFormatter.formatters.push(function (html) {
var bin = document.createElement('div');
bin.innerHTML = html;

traverse(bin);

return bin.innerHTML;
});

};
};

});
Loading

0 comments on commit ddecae9

Please sign in to comment.