Skip to content
This repository has been archived by the owner on Mar 8, 2019. It is now read-only.

Line breaks #545

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
1 change: 0 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ JS_FILES = src/wysihtml5.js \
src/quirks/clean_pasted_html.js \
src/quirks/ensure_proper_clearing.js \
src/quirks/get_correct_inner_html.js \
src/quirks/insert_line_break_on_return.js \
src/quirks/redraw.js \
src/selection/selection.js \
src/selection/html_applier.js \
Expand Down
10 changes: 6 additions & 4 deletions examples/advanced.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@

#toolbar,
textarea {
width: 900px;
width: 920px;
padding: 5px;
-webkit-box-sizing: border-box;
-ms-box-sizing: border-box;
Expand Down Expand Up @@ -91,6 +91,7 @@ <h1>wysihtml5 - Advanced Editor Example</h1>
<a data-wysihtml5-command="insertImage">insert image</a> |
<a data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="h1">h1</a> |
<a data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="h2">h2</a> |
<a data-wysihtml5-command="formatBlock" data-wysihtml5-command-value="p">p</a> |
<a data-wysihtml5-command="insertUnorderedList">insertUnorderedList</a> |
<a data-wysihtml5-command="insertOrderedList">insertOrderedList</a> |
<a data-wysihtml5-command="foreColor" data-wysihtml5-command-value="red">red</a> |
Expand Down Expand Up @@ -139,9 +140,10 @@ <h2>Events:</h2>
<script src="../dist/wysihtml5-0.3.0.js"></script>
<script>
var editor = new wysihtml5.Editor("textarea", {
toolbar: "toolbar",
stylesheets: "css/stylesheet.css",
parserRules: wysihtml5ParserRules
toolbar: "toolbar",
stylesheets: "css/stylesheet.css",
parserRules: wysihtml5ParserRules,
useLineBreaks: false
});

var log = document.getElementById("log");
Expand Down
5 changes: 3 additions & 2 deletions examples/simple.html
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,9 @@ <h2>Events:</h2>
<script src="../dist/wysihtml5-0.3.0.js"></script>
<script>
var editor = new wysihtml5.Editor("textarea", {
toolbar: "toolbar",
parserRules: wysihtml5ParserRules
toolbar: "toolbar",
parserRules: wysihtml5ParserRules,
useLineBreaks: false
});

var log = document.getElementById("log");
Expand Down
24 changes: 13 additions & 11 deletions src/browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ wysihtml5.browser = (function() {
* Firefox sometimes shows a huge caret in the beginning after focusing
*/
displaysCaretInEmptyContentEditableCorrectly: function() {
return !isGecko;
return isIE;
},

/**
Expand Down Expand Up @@ -167,8 +167,8 @@ wysihtml5.browser = (function() {
// When inserting unordered or ordered lists in Firefox, Chrome or Safari, the current selection or line gets
// converted into a list (<ul><li>...</li></ul>, <ol><li>...</li></ol>)
// IE and Opera act a bit different here as they convert the entire content of the current block element into a list
"insertUnorderedList": isIE || isOpera || isWebKit,
"insertOrderedList": isIE || isOpera || isWebKit
"insertUnorderedList": isIE || isWebKit,
"insertOrderedList": isIE || isWebKit
};

// Firefox throws errors for queryCommandSupported, so we have to build up our own object of supported commands
Expand Down Expand Up @@ -240,14 +240,6 @@ wysihtml5.browser = (function() {
return isGecko || isIE || isOpera;
},

/**
* When the caret is in an empty list (<ul><li>|</li></ul>) which is the first child in an contentEditable container
* pressing backspace doesn't remove the entire list as done in other browsers
*/
clearsListsInContentEditableCorrectly: function() {
return isGecko || isIE || isWebKit;
},

/**
* All browsers except Safari and Chrome automatically scroll the range/caret position into view
*/
Expand Down Expand Up @@ -335,6 +327,16 @@ wysihtml5.browser = (function() {

hasUndoInContextMenu: function() {
return isGecko || isChrome || isOpera;
},

/**
* Opera sometimes doesn't insert the node at the right position when range.insertNode(someNode)
* is used (regardless if rangy or native)
* This especially happens when the caret is positioned right after a <br> because then
* insertNode() will insert the node right before the <br>
*/
hasInsertNodeIssue: function() {
return isOpera;
}
};
})();
27 changes: 14 additions & 13 deletions src/commands/formatBlock.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
(function(wysihtml5) {
var dom = wysihtml5.dom,
DEFAULT_NODE_NAME = "DIV",
// Following elements are grouped
// when the caret is within a H1 and the H4 is invoked, the H1 should turn into H4
// instead of creating a H4 within a H1 which would result in semantically invalid html
BLOCK_ELEMENTS_GROUP = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "BLOCKQUOTE", DEFAULT_NODE_NAME];
BLOCK_ELEMENTS_GROUP = ["H1", "H2", "H3", "H4", "H5", "H6", "P", "BLOCKQUOTE", "DIV"];

/**
* Remove similiar classes (based on classRegExp)
Expand Down Expand Up @@ -141,7 +140,7 @@
composer.selection.surround(element);
_removeLineBreakBeforeAndAfter(element);
_removeLastChildIfLineBreak(element);
composer.selection.selectNode(element);
composer.selection.selectNode(element, wysihtml5.browser.displaysCaretInEmptyContentEditableCorrectly());
}

function _hasClasses(element) {
Expand All @@ -150,26 +149,28 @@

wysihtml5.commands.formatBlock = {
exec: function(composer, command, nodeName, className, classRegExp) {
var doc = composer.doc,
blockElement = this.state(composer, command, nodeName, className, classRegExp),
var doc = composer.doc,
blockElement = this.state(composer, command, nodeName, className, classRegExp),
useLineBreaks = composer.config.useLineBreaks,
defaultNodeName = useLineBreaks ? "DIV" : "P",
selectedNode;

nodeName = typeof(nodeName) === "string" ? nodeName.toUpperCase() : nodeName;

if (blockElement) {
composer.selection.executeAndRestoreSimple(function() {
if (classRegExp) {
_removeClass(blockElement, classRegExp);
}
var hasClasses = _hasClasses(blockElement);
if (!hasClasses && blockElement.nodeName === (nodeName || DEFAULT_NODE_NAME)) {
if (!hasClasses && (useLineBreaks || nodeName === "P")) {
// Insert a line break afterwards and beforewards when there are siblings
// that are not of type line break or block element
_addLineBreakBeforeAndAfter(blockElement);
dom.replaceWithChildNodes(blockElement);
} else if (hasClasses) {
// Make sure that styling is kept by renaming the element to <div> and copying over the class name
dom.renameElement(blockElement, DEFAULT_NODE_NAME);
} else {
// Make sure that styling is kept by renaming the element to a <div> or <p> and copying over the class name
dom.renameElement(blockElement, nodeName === "P" ? "DIV" : defaultNodeName);
}
});
return;
Expand All @@ -183,7 +184,7 @@
});

if (blockElement) {
composer.selection.executeAndRestoreSimple(function() {
composer.selection.executeAndRestore(function() {
// Rename current block element to new block element and add class
if (nodeName) {
blockElement = dom.renameElement(blockElement, nodeName);
Expand All @@ -197,11 +198,11 @@
}

if (composer.commands.support(command)) {
_execCommand(doc, command, nodeName || DEFAULT_NODE_NAME, className);
_execCommand(doc, command, nodeName || defaultNodeName, className);
return;
}

blockElement = doc.createElement(nodeName || DEFAULT_NODE_NAME);
blockElement = doc.createElement(nodeName || defaultNodeName);
if (className) {
blockElement.className = className;
}
Expand Down
12 changes: 6 additions & 6 deletions src/commands/insertOrderedList.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ wysihtml5.commands.insertOrderedList = {
isEmpty,
tempElement;

if (composer.commands.support(command)) {
if (!list && !otherList && composer.commands.support(command)) {
doc.execCommand(command, false, null);
return;
}
Expand All @@ -18,27 +18,27 @@ wysihtml5.commands.insertOrderedList = {
// <ol><li>foo</li><li>bar</li></ol>
// becomes:
// foo<br>bar<br>
composer.selection.executeAndRestoreSimple(function() {
wysihtml5.dom.resolveList(list);
composer.selection.executeAndRestore(function() {
wysihtml5.dom.resolveList(list, composer.config.useLineBreaks);
});
} else if (otherList) {
// Turn an unordered list into an ordered list
// <ul><li>foo</li><li>bar</li></ul>
// becomes:
// <ol><li>foo</li><li>bar</li></ol>
composer.selection.executeAndRestoreSimple(function() {
composer.selection.executeAndRestore(function() {
wysihtml5.dom.renameElement(otherList, "ol");
});
} else {
// Create list
composer.commands.exec("formatBlock", "div", tempClassName);
tempElement = doc.querySelector("." + tempClassName);
isEmpty = tempElement.innerHTML === "" || tempElement.innerHTML === wysihtml5.INVISIBLE_SPACE || tempElement.innerHTML === "<br>";
composer.selection.executeAndRestoreSimple(function() {
composer.selection.executeAndRestore(function() {
list = wysihtml5.dom.convertToList(tempElement, "ol");
});
if (isEmpty) {
composer.selection.selectNode(list.querySelector("li"));
composer.selection.selectNode(list.querySelector("li"), true);
}
}
},
Expand Down
12 changes: 6 additions & 6 deletions src/commands/insertUnorderedList.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ wysihtml5.commands.insertUnorderedList = {
isEmpty,
tempElement;

if (composer.commands.support(command)) {
if (!list && !otherList && composer.commands.support(command)) {
doc.execCommand(command, false, null);
return;
}
Expand All @@ -18,27 +18,27 @@ wysihtml5.commands.insertUnorderedList = {
// <ul><li>foo</li><li>bar</li></ul>
// becomes:
// foo<br>bar<br>
composer.selection.executeAndRestoreSimple(function() {
wysihtml5.dom.resolveList(list);
composer.selection.executeAndRestore(function() {
wysihtml5.dom.resolveList(list, composer.config.useLineBreaks);
});
} else if (otherList) {
// Turn an ordered list into an unordered list
// <ol><li>foo</li><li>bar</li></ol>
// becomes:
// <ul><li>foo</li><li>bar</li></ul>
composer.selection.executeAndRestoreSimple(function() {
composer.selection.executeAndRestore(function() {
wysihtml5.dom.renameElement(otherList, "ul");
});
} else {
// Create list
composer.commands.exec("formatBlock", "div", tempClassName);
tempElement = doc.querySelector("." + tempClassName);
isEmpty = tempElement.innerHTML === "" || tempElement.innerHTML === wysihtml5.INVISIBLE_SPACE || tempElement.innerHTML === "<br>";
composer.selection.executeAndRestoreSimple(function() {
composer.selection.executeAndRestore(function() {
list = wysihtml5.dom.convertToList(tempElement, "ul");
});
if (isEmpty) {
composer.selection.selectNode(list.querySelector("li"));
composer.selection.selectNode(list.querySelector("li"), true);
}
}
},
Expand Down
4 changes: 4 additions & 0 deletions src/dom/convert_to_list.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ wysihtml5.dom.convertToList = (function() {
currentListItem.appendChild(childNode);
}

if (childNodes.length === 0) {
_createListItem(doc, list);
}

element.parentNode.replaceChild(list, element);
return list;
}
Expand Down
19 changes: 12 additions & 7 deletions src/dom/insert_css.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,24 @@ wysihtml5.dom.insertCSS = function(rules) {

return {
into: function(doc) {
var head = doc.head || doc.getElementsByTagName("head")[0],
styleElement = doc.createElement("style");

var styleElement = doc.createElement("style");
styleElement.type = "text/css";

if (styleElement.styleSheet) {
styleElement.styleSheet.cssText = rules;
} else {
styleElement.appendChild(doc.createTextNode(rules));
}

if (head) {
head.appendChild(styleElement);

var link = doc.querySelector("head link");
if (link) {
link.parentNode.insertBefore(styleElement, link);
return;
} else {
var head = doc.querySelector("head");
if (head) {
head.appendChild(styleElement);
}
}
}
};
Expand Down
54 changes: 37 additions & 17 deletions src/dom/resolve_list.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
element.appendChild(lineBreak);
}

function resolveList(list) {
if (list.nodeName !== "MENU" && list.nodeName !== "UL" && list.nodeName !== "OL") {
function resolveList(list, useLineBreaks) {
if (!list.nodeName.match(/^(MENU|UL|OL)$/)) {
return;
}

Expand All @@ -46,26 +46,46 @@
lastChild,
isLastChild,
shouldAppendLineBreak,
paragraph,
listItem;

if (previousSibling && !_isBlockElement(previousSibling)) {
_appendLineBreak(fragment);
}

while (listItem = list.firstChild) {
lastChild = listItem.lastChild;
while (firstChild = listItem.firstChild) {
isLastChild = firstChild === lastChild;
// This needs to be done before appending it to the fragment, as it otherwise will loose style information
shouldAppendLineBreak = isLastChild && !_isBlockElement(firstChild) && !_isLineBreak(firstChild);
fragment.appendChild(firstChild);
if (shouldAppendLineBreak) {
_appendLineBreak(fragment);
if (useLineBreaks) {
// Insert line break if list is after a non-block element
if (previousSibling && !_isBlockElement(previousSibling)) {
_appendLineBreak(fragment);
}

while (listItem = (list.firstElementChild || list.firstChild)) {
lastChild = listItem.lastChild;
while (firstChild = listItem.firstChild) {
isLastChild = firstChild === lastChild;
// This needs to be done before appending it to the fragment, as it otherwise will lose style information
shouldAppendLineBreak = isLastChild && !_isBlockElement(firstChild) && !_isLineBreak(firstChild);
fragment.appendChild(firstChild);
if (shouldAppendLineBreak) {
_appendLineBreak(fragment);
}
}

listItem.parentNode.removeChild(listItem);
}
} else {
while (listItem = (list.firstElementChild || list.firstChild)) {
if (listItem.querySelector && listItem.querySelector("div, p, ul, ol, menu, blockquote, h1, h2, h3, h4, h5, h6")) {
while (firstChild = listItem.firstChild) {
fragment.appendChild(firstChild);
}
} else {
paragraph = doc.createElement("p");
while (firstChild = listItem.firstChild) {
paragraph.appendChild(firstChild);
}
fragment.appendChild(paragraph);
}
listItem.parentNode.removeChild(listItem);
}

listItem.parentNode.removeChild(listItem);
}

list.parentNode.replaceChild(fragment, list);
}

Expand Down
Loading