Skip to content

Commit daa0bb8

Browse files
authored
Merge pull request #42 from typecode/issue-9
#9 - Avoid creating unnecessary new lines when toggling lists
2 parents f880537 + 484bf4a commit daa0bb8

File tree

10 files changed

+105
-167
lines changed

10 files changed

+105
-167
lines changed

src/scripts/modules/BaseFormatter.js

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,10 @@ const BaseFormatter = Module({
4646
methods: {
4747
init () {
4848
const { mediator } = this;
49+
4950
validTags = mediator.get('config:toolbar:validTags');
5051
blockTags = mediator.get('config:toolbar:blockTags');
51-
listTags = mediator.get('config:toolbar:listTags');
52+
listTags = mediator.get('config:toolbar:listTags');
5253
},
5354

5455
/**
@@ -106,6 +107,7 @@ const BaseFormatter = Module({
106107
formatDefault () {
107108
const { mediator } = this;
108109
const rootElem = mediator.get('selection:rootelement');
110+
109111
mediator.exec('commands:format:default');
110112
this.removeStyledSpans(rootElem);
111113
},
@@ -122,22 +124,19 @@ const BaseFormatter = Module({
122124
this.ensureRootElems(rootElem);
123125
this.removeStyleAttributes(rootElem);
124126
this.removeEmptyNodes(rootElem, { recursive: true });
125-
126-
// -----
127-
128-
// this.removeBrNodes(rootElem);
129-
// // this.removeEmptyNodes(rootElem);
130-
// this.removeFontTags(rootElem);
131-
// this.removeStyledSpans(rootElem);
132-
// this.clearEntities(rootElem);
133-
// this.removeZeroWidthSpaces(rootElem);
134-
// this.defaultOrphanedTextNodes(rootElem);
135-
// this.removeEmptyNodes(rootElem, { recursive: true });
136127
},
137128

138129
/**
139130
* PRIVATE METHODS:
140131
*/
132+
cloneNodes (rootElement) {
133+
let clonedNodes = [];
134+
rootElement.childNodes.forEach((node) => {
135+
clonedNodes.push(node.cloneNode(true));
136+
});
137+
return clonedNodes;
138+
},
139+
141140
injectHooks (rootElement) {
142141
while (!/\w+/.test(rootElement.firstChild.textContent)) {
143142
DOM.removeNode(rootElement.firstChild);
@@ -306,7 +305,6 @@ const BaseFormatter = Module({
306305

307306
defaultOrphanedTextNodes (rootElem) {
308307
const { childNodes } = rootElem;
309-
310308
for (let i = 0; i < childNodes.length; i++) {
311309
let childNode = childNodes[i];
312310
if (childNode.nodeType === Node.TEXT_NODE && /\w+/.test(childNode.textContent)) {

src/scripts/modules/BlockFormatter.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ const BlockFormatter = Module({
8484
},
8585

8686
cleanupBlockquote (rootElem) {
87-
const blockquoteParagraphs = rootElem.querySelectorAll('BLOCKQUOTE P');
87+
const blockquoteParagraphs = rootElem.querySelectorAll('blockquote p');
8888
blockquoteParagraphs.forEach((paragraph) => {
8989
DOM.unwrap(paragraph);
9090
});

src/scripts/modules/ContentEditable.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,6 @@ const ContentEditable = Module({
152152
} else {
153153
let currentSelection = mediator.get('selection:current');
154154
let currentRange = mediator.get('selection:range');
155-
156155
currentRange.deleteContents();
157156

158157
let tmpContainer = document.createElement('container');

src/scripts/modules/ListFormatter.js

Lines changed: 5 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,11 @@ const ListFormatter = Module({
4646
process (opts) {
4747
const { mediator } = this;
4848
const canvasDoc = mediator.get('canvas:document');
49-
let toggle = false;
5049

5150
mediator.exec('canvas:cache:selection');
51+
5252
switch (opts.style) {
5353
case 'ordered':
54-
toggle = mediator.get('selection:in:or:contains', ['OL']);
5554
if (mediator.get('selection:in:or:contains', ['UL'])) {
5655
mediator.exec('commands:exec', {
5756
command: 'insertUnorderedList',
@@ -63,8 +62,8 @@ const ListFormatter = Module({
6362
contextDocument: canvasDoc
6463
});
6564
break;
65+
6666
case 'unordered':
67-
toggle = mediator.get('selection:in:or:contains', ['UL']);
6867
if (mediator.get('selection:in:or:contains', ['OL'])) {
6968
mediator.exec('commands:exec', {
7069
command: 'insertOrderedList',
@@ -76,12 +75,14 @@ const ListFormatter = Module({
7675
contextDocument: canvasDoc
7776
});
7877
break;
78+
7979
case 'outdent':
8080
mediator.exec('commands:exec', {
8181
command: 'outdent',
8282
contextDocument: canvasDoc
8383
});
8484
break;
85+
8586
case 'indent':
8687
mediator.exec('commands:exec', {
8788
command: 'indent',
@@ -90,14 +91,7 @@ const ListFormatter = Module({
9091
break;
9192
}
9293

93-
if (toggle) {
94-
// mediator.exec('canvas:select:cachedSelection');
95-
this.postProcessToggle(opts);
96-
} else {
97-
mediator.exec('canvas:select:ensure:offsets');
98-
}
99-
100-
// mediator.exec('canvas:select:cachedSelection');
94+
mediator.exec('canvas:select:ensure:offsets');
10195
},
10296

10397
commit () {
@@ -132,130 +126,6 @@ const ListFormatter = Module({
132126
}
133127
},
134128

135-
prepListItemsForToggle () {
136-
const { mediator } = this;
137-
138-
const canvasDoc = mediator.get('canvas:document');
139-
const canvasBody = mediator.get('canvas:body');
140-
141-
const {
142-
anchorNode,
143-
focusNode,
144-
} = mediator.get('canvas:selection');
145-
146-
const anchorLiNode = DOM.getClosest(anchorNode, 'LI', canvasBody);
147-
const focusLiNode = DOM.getClosest(focusNode, 'LI', canvasBody);
148-
149-
mediator.exec('canvas:cache:selection');
150-
151-
let selectedLiNodes = [anchorLiNode];
152-
let nextLiNode = anchorLiNode.nextSibling;
153-
while (nextLiNode && nextLiNode !== focusLiNode) {
154-
selectedLiNodes.push(nextLiNode);
155-
nextLiNode = nextLiNode.nextSibling;
156-
}
157-
selectedLiNodes.push(focusLiNode);
158-
159-
selectedLiNodes.forEach((selectedLiNode) => {
160-
let contentWrapper = canvasDoc.createElement('span');
161-
selectedLiNode.appendChild(contentWrapper);
162-
while (selectedLiNode.firstChild !== contentWrapper) {
163-
contentWrapper.appendChild(selectedLiNode.firstChild);
164-
}
165-
});
166-
167-
mediator.exec('canvas:select:cachedSelection');
168-
169-
return;
170-
// const canvasBody = mediator.get('canvas:body');
171-
// const canvasDoc = mediator.get('canvas:document');
172-
//
173-
// let rootBlock = anchorNode;
174-
// while(rootBlock.parentNode !== canvasBody) {
175-
// rootBlock = rootBlock.parentNode;
176-
// }
177-
//
178-
// const liNodes = rootBlock.querySelectorAll('li');
179-
// liNodes.forEach((liNode) => {
180-
// let pNode = canvasDoc.createElement('span');
181-
// liNode.appendChild(pNode);
182-
// while (liNode.firstChild !== pNode) {
183-
// pNode.appendChild(liNode.firstChild);
184-
// }
185-
// });
186-
},
187-
188-
postProcessToggle () {
189-
const { mediator } = this;
190-
// return;
191-
192-
const canvasDoc = mediator.get('canvas:document');
193-
const canvasBody = mediator.get('canvas:body');
194-
195-
mediator.exec('canvas:cache:selection');
196-
197-
const {
198-
anchorNode,
199-
focusNode
200-
} = mediator.get('canvas:selection');
201-
202-
const walkToRoot = function (node) {
203-
let rootNode = node;
204-
while ( rootNode.parentNode !== canvasBody ) {
205-
rootNode = rootNode.parentNode;
206-
}
207-
return rootNode;
208-
};
209-
210-
const anchorRootNode = walkToRoot(anchorNode);
211-
const focusRootNode = walkToRoot(focusNode);
212-
213-
let currentNode = anchorRootNode;
214-
let currentParagraph;
215-
216-
const createParagraph = function () {
217-
currentParagraph = canvasDoc.createElement('p');
218-
DOM.insertBefore(currentParagraph, currentNode);
219-
};
220-
221-
const handleBrNode = function (brNode) {
222-
createParagraph();
223-
currentNode = brNode.nextSibling;
224-
DOM.removeNode(brNode);
225-
};
226-
227-
const handleDivNode = function (divNode) {
228-
createParagraph();
229-
currentNode = divNode.nextSibling;
230-
while (divNode.firstChild) {
231-
currentParagraph.appendChild(divNode.firstChild);
232-
}
233-
DOM.removeNode(divNode);
234-
};
235-
236-
createParagraph();
237-
238-
while (currentNode !== focusRootNode) {
239-
if (currentNode.nodeName === 'BR') {
240-
handleBrNode(currentNode);
241-
} else if (currentNode.nodeName === 'DIV') {
242-
handleDivNode(currentNode);
243-
} else {
244-
let orphanedNode = currentNode;
245-
currentNode = currentNode.nextSibling;
246-
currentParagraph.appendChild(orphanedNode);
247-
}
248-
}
249-
250-
if (focusRootNode.nodeName === 'DIV') {
251-
handleDivNode(focusRootNode);
252-
} else {
253-
currentParagraph.appendChild(focusRootNode);
254-
}
255-
256-
mediator.exec('canvas:select:cachedSelection');
257-
},
258-
259129
cleanupListDOM (rootElem) {
260130
const listContainers = rootElem.querySelectorAll('OL, UL');
261131

src/scripts/modules/Selection.js

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ const Selection = Module({
201201

202202
getAnchorNode () {
203203
const currentSelection = this.getCurrentSelection();
204-
return currentSelection.anchorNode;
204+
return currentSelection && currentSelection.anchorNode;
205205
},
206206

207207
getCommonAncestor () {
@@ -293,14 +293,49 @@ const Selection = Module({
293293
endContainer,
294294
endOffset
295295
} = this.getCurrentRange();
296+
296297
let startCoordinates = [];
297298
let endCoordinates = [];
299+
let startPrefixTrimLength = 0;
300+
let endPrefixTrimLength = 0;
301+
let endSuffixTrimLength = 0;
298302

299303
const startTrimmablePrefix = startContainer.textContent.match(/^(\r?\n|\r)?(\s+)?/);
300304
const endTrimmablePrefix = endContainer.textContent.match(/^(\r?\n|\r)?(\s+)?/);
305+
const endTrimmableSuffix = endContainer.textContent.match(/(\r?\n|\r)?(\s+)?$/);
306+
const startTrimmableSides = DOM.trimmableSides(startContainer.firstChild ? startContainer.firstChild : startContainer);
307+
const endTrimmableSides = DOM.trimmableSides(endContainer.lastChild ? endContainer.lastChild : endContainer);
308+
309+
if (startTrimmablePrefix && startTrimmablePrefix[0].length) {
310+
startPrefixTrimLength += startTrimmablePrefix[0].length;
311+
if (!startTrimmableSides.left) {
312+
startPrefixTrimLength -= startTrimmablePrefix[0].match(/\s/) ? 1 : 0;
313+
}
314+
startOffset -= startPrefixTrimLength;
315+
if (endContainer === startContainer) {
316+
endOffset -= startPrefixTrimLength;
317+
}
318+
}
319+
320+
if (endTrimmablePrefix && endTrimmablePrefix[0].length && endContainer !== startContainer) {
321+
endPrefixTrimLength += endTrimmablePrefix[0].length;
322+
if (!endTrimmableSides.left) {
323+
endPrefixTrimLength -= endTrimmablePrefix[0].match(/\s/) ? 1 : 0;
324+
}
325+
endOffset -= endPrefixTrimLength;
326+
}
327+
328+
if (endTrimmableSuffix && endTrimmableSuffix[0].length) {
329+
endSuffixTrimLength += endTrimmableSuffix[0].length;
330+
if (!endTrimmableSides.right) {
331+
endSuffixTrimLength -= endTrimmableSuffix[0].match(/\s/) ? 1 : 0;
332+
}
333+
let trimmedTextLength = endContainer.textContent.length - endPrefixTrimLength - endSuffixTrimLength;
334+
endOffset = Math.min(trimmedTextLength, endOffset);
335+
}
301336

302-
startOffset -= startTrimmablePrefix ? startTrimmablePrefix[0].length : 0;
303-
endOffset -= endTrimmablePrefix ? endTrimmablePrefix[0].length : 0;
337+
startOffset = Math.max(0, startOffset);
338+
endOffset = Math.min(endContainer.textContent.length, endOffset);
304339

305340
startCoordinates.unshift(startOffset);
306341
endCoordinates.unshift(endOffset);
@@ -331,7 +366,6 @@ const Selection = Module({
331366
}
332367

333368
const isIn = DOM.isIn(anchorNode, selectors, rootEl);
334-
335369
if (isIn) {
336370
return isIn;
337371
}
@@ -341,8 +375,8 @@ const Selection = Module({
341375
let contains = false;
342376

343377
if (rangeFrag.childNodes.length) {
344-
selectors.forEach(selector => {
345-
contains = contains || rangeFrag.childNodes[0].nodeName === selector;
378+
rangeFrag.childNodes.forEach(childNode => {
379+
contains = contains || selectors.indexOf(childNode.nodeName) > -1;
346380
});
347381
}
348382

@@ -365,6 +399,7 @@ const Selection = Module({
365399
if (anchorNode.nodeType !== Node.ELEMENT_NODE) {
366400
anchorNode = anchorNode.parentNode;
367401
}
402+
368403
if (focusNode.nodeType !== Node.ELEMENT_NODE) {
369404
focusNode = focusNode.parentNode;
370405
}

0 commit comments

Comments
 (0)