Skip to content

Commit

Permalink
Atoms with cursor movement, reparsing
Browse files Browse the repository at this point in the history
  • Loading branch information
rlivsey authored and mixonic committed Feb 2, 2016
1 parent 573453a commit 5020b91
Show file tree
Hide file tree
Showing 13 changed files with 444 additions and 41 deletions.
8 changes: 6 additions & 2 deletions src/js/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -622,8 +622,12 @@ class Editor {
if (range.direction === DIRECTION.BACKWARD) {
position = range.head;
}
if (position.section.isCardSection) {
nextPosition = position.move(key.direction);
nextPosition = position.move(key.direction);
if (
position.section.isCardSection ||
(position.marker && position.marker.isAtom) ||
(nextPosition && nextPosition.marker && nextPosition.marker.isAtom)
) {
if (nextPosition) {
let newRange;
if (key.isShift()) {
Expand Down
4 changes: 2 additions & 2 deletions src/js/models/_markerable.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ export default class Markerable extends Section {
this.tagName = tagName;
this.markers = new LinkedList({
adoptItem: m => {
assert(`Cannot insert non-marker into markerable (was: ${m.type})`,
m.isMarker);
assert(`Can only insert markers and atoms into markerable (was: ${m.type})`,
m.isMarker || m.isAtom);
m.section = m.parent = this;
},
freeItem: m => m.section = m.parent = null
Expand Down
3 changes: 2 additions & 1 deletion src/js/models/atom.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ export default class Atom extends LinkedItem {
this.value = value;
this.payload = payload;
this.type = ATOM_TYPE;
this.isAtom = true;
this.length = 1;

this.markups = [];
markups.forEach(m => this.addMarkup(m));
}
}

mixin(Atom, MarkuperableMixin);
mixin(Atom, MarkuperableMixin);
60 changes: 43 additions & 17 deletions src/js/parsers/dom.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
NO_BREAK_SPACE,
TAB_CHARACTER
TAB_CHARACTER,
ATOM_CLASS_NAME
} from '../renderers/editor-dom';
import {
MARKUP_SECTION_TYPE,
Expand All @@ -10,6 +11,8 @@ import {
import {
isTextNode,
isCommentNode,
isElementNode,
getAttributes,
normalizeTagName
} from '../utils/dom-utils';
import {
Expand All @@ -19,7 +22,6 @@ import {
import { TAB } from 'mobiledoc-kit/utils/characters';

import SectionParser from 'mobiledoc-kit/parsers/section';
import { getAttributes, walkTextNodes } from '../utils/dom-utils';
import Markup from 'mobiledoc-kit/models/markup';

const GOOGLE_DOCS_CONTAINER_ID_REGEX = /^docs\-internal\-guid/;
Expand Down Expand Up @@ -66,6 +68,26 @@ function trim(str) {
return str.replace(/^\s+/, '').replace(/\s+$/, '');
}

function walkMarkerableNodes(parent, callback) {
let currentNode = parent;

if (
isTextNode(currentNode) ||
(
isElementNode(currentNode) &&
currentNode.classList.contains(ATOM_CLASS_NAME)
)
) {
callback(currentNode);
} else {
currentNode = currentNode.firstChild;
while (currentNode) {
walkMarkerableNodes(currentNode, callback);
currentNode = currentNode.nextSibling;
}
}
}

/**
* Parses DOM element -> Post
*/
Expand Down Expand Up @@ -169,30 +191,34 @@ export default class DOMParser {
}

_reparseSectionContainingMarkers(section, renderTree) {
const element = section.renderNode.element;
let element = section.renderNode.element;
let seenRenderNodes = [];
let previousMarker;

walkTextNodes(element, (textNode) => {
const text = transformHTMLText(textNode.textContent);
let markups = this.collectMarkups(textNode, element);

walkMarkerableNodes(element, (node) => {
let marker;

let renderNode = renderTree.getElementRenderNode(textNode);
let renderNode = renderTree.getElementRenderNode(node);
if (renderNode) {
if (text.length) {
if (renderNode.postNode.isMarker) {
let text = transformHTMLText(node.textContent);
let markups = this.collectMarkups(node, element);
if (text.length) {
marker = renderNode.postNode;
marker.value = text;
marker.markups = markups;
} else {
renderNode.scheduleForRemoval();
}
} else if (renderNode.postNode.isAtom) {
marker = renderNode.postNode;
marker.value = text;
marker.markups = markups;
} else {
renderNode.scheduleForRemoval();
}
} else {
} else if (isTextNode(node)) {
let text = transformHTMLText(node.textContent);
let markups = this.collectMarkups(node, element);
marker = this.builder.createMarker(text, markups);

renderNode = renderTree.buildRenderNode(marker);
renderNode.element = textNode;
renderNode.element = node;
renderNode.markClean();
section.renderNode.markDirty();

Expand All @@ -201,7 +227,7 @@ export default class DOMParser {
section.renderNode.childNodes.insertAfter(renderNode, previousRenderNode);

let parentNodeCount = marker.closedMarkups.length;
let nextMarkerElement = textNode.parentNode;
let nextMarkerElement = node.parentNode;
while (parentNodeCount--) {
nextMarkerElement = nextMarkerElement.parentNode;
}
Expand Down
36 changes: 28 additions & 8 deletions src/js/renderers/editor-dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const NO_BREAK_SPACE = '\u00A0';
export const TAB_CHARACTER = '\u2003';
export const SPACE = ' ';
export const ZWNJ = '\u200c';
export const ATOM_CLASS_NAME = '-mobiledoc-kit__atom';

function createElementFromMarkup(doc, markup) {
let element = doc.createElement(markup.tagName);
Expand Down Expand Up @@ -58,7 +59,7 @@ function renderHTMLText(marker) {
text = text.substr(0, text.length - 1) + NO_BREAK_SPACE;
}
if (startsWithSpace(text) &&
(!marker.prev || endsWithSpace(marker.prev.value))) {
(!marker.prev || (marker.prev.isMarker && endsWithSpace(marker.prev.value)))) {
text = NO_BREAK_SPACE + text.substr(1);
}
return text;
Expand Down Expand Up @@ -114,17 +115,29 @@ function renderCard() {
function renderAtom(element, previousRenderNode) {
let atomElement = document.createElement('span');
atomElement.contentEditable = false;
addClassName(atomElement, '-mobiledoc-kit__atom');

let wrapper = document.createElement('span');
addClassName(wrapper, ATOM_CLASS_NAME);
let headTextNode = document.createTextNode('\u200c');
let tailTextNode = document.createTextNode('\u200c');
wrapper.appendChild(headTextNode);
wrapper.appendChild(atomElement);
wrapper.appendChild(tailTextNode);

if (previousRenderNode) {
let previousSibling = previousRenderNode.element;
let previousSiblingPenultimate = penultimateParentOf(previousSibling, element);
element.insertBefore(atomElement, previousSiblingPenultimate.nextSibling);
element.insertBefore(wrapper, previousSiblingPenultimate.nextSibling);
} else {
element.insertBefore(atomElement, element.firstChild);
element.insertBefore(wrapper, element.firstChild);
}

return atomElement;
return {
wrapper,
atomElement,
headTextNode,
tailTextNode
};
}

function getNextMarkerElement(renderNode) {
Expand Down Expand Up @@ -396,8 +409,13 @@ class Visitor {
parentElement = renderNode.parent.element;
}

const {editor, options} = this;
const atomElement = renderAtom(parentElement, renderNode.prev);
const { editor, options } = this;
const {
wrapper,
atomElement,
headTextNode,
tailTextNode
} = renderAtom(parentElement, renderNode.prev);
const atom = this._findAtom(atomModel.name);

const atomNode = new AtomNode(
Expand All @@ -407,7 +425,9 @@ class Visitor {
atomNode.render();

renderNode.atomNode = atomNode;
renderNode.element = atomElement;
renderNode.element = wrapper;
renderNode.headTextNode = headTextNode;
renderNode.tailTextNode = tailTextNode;
}
}

Expand Down
17 changes: 13 additions & 4 deletions src/js/utils/cursor.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ const Cursor = class Cursor {
}

_findNodeForPosition(position) {
const { section } = position;
let { section } = position;
let node, offset;
if (section.isCardSection) {
offset = 0;
Expand All @@ -94,9 +94,18 @@ const Cursor = class Cursor {
node = section.renderNode.element;
offset = 0;
} else {
const {marker, offsetInMarker} = position;
node = marker.renderNode.element;
offset = offsetInMarker;
let {marker, offsetInMarker} = position;
if (marker.isAtom) {
offset = 0;
if (offsetInMarker === 0) {
node = marker.renderNode.headTextNode;
} else {
node = marker.renderNode.tailTextNode;
}
} else {
node = marker.renderNode.element;
offset = offsetInMarker;
}
}

return {node, offset};
Expand Down
34 changes: 32 additions & 2 deletions src/js/utils/cursor/position.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {
isTextNode, findOffsetInElement
isTextNode
} from 'mobiledoc-kit/utils/dom-utils';
import { DIRECTION } from 'mobiledoc-kit/utils/key';
import assert from 'mobiledoc-kit/utils/assert';
Expand All @@ -14,6 +14,7 @@ function findParentSectionFromNode(renderTree, node) {
}

function findOffsetInSection(section, node, offset) {
<<<<<<< HEAD
if (section.isMarkerable) {
return findOffsetInElement(section.renderNode.element, node, offset);
} else {
Expand All @@ -27,6 +28,34 @@ function findOffsetInSection(section, node, offset) {
}
return 0;
}
=======
if (section.isCardSection) {
let wrapperNode = section.renderNode.element;
let endTextNode = wrapperNode.lastChild;
return (node === endTextNode) ? 1 : 0;
} else {
let offsetInSection = 0;
let marker = section.markers.head;
while (marker) {
let markerNode = marker.renderNode.element;
if (markerNode === node) {
return offsetInSection + offset;
} else if (marker.isAtom) {
if (marker.renderNode.headTextNode === node) {
return offsetInSection;
} else if (marker.renderNode.tailTextNode === node) {
return offsetInSection + 1;
}
}

offsetInSection += marker.length;
marker = marker.next;
}

return offsetInSection;
}

>>>>>>> Atoms with cursor movement, reparsing
}

const Position = class Position {
Expand Down Expand Up @@ -169,7 +198,7 @@ const Position = class Position {
assert('Could not find parent section from element node', !!section);

if (section.isCardSection) {
// Selections in cards are usually made on a text node containing a &zwnj;
// Selections in cards are usually made on a text node containing a &zwnj;
// on one side or the other of the card but some scenarios (Firefox) will result in
// selecting the card's wrapper div. If the offset is 2 we've selected
// the final zwnj and should consider the cursor at the end of the card (offset 1). Otherwise,
Expand All @@ -190,6 +219,7 @@ const Position = class Position {
*/
get markerPosition() {
assert('Cannot get markerPosition without a section', !!this.section);
assert('cannot get markerPosition of a non-markerable', !!this.section.isMarkerable);
return this.section.markerPositionAtOffset(this.offset);
}

Expand Down
5 changes: 5 additions & 0 deletions src/js/utils/dom-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ function isCommentNode(node) {
return node.nodeType === NODE_TYPES.COMMENT;
}

function isElementNode(node) {
return node.nodeType === ELEMENT_NODE_TYPE;
}

// perform a pre-order tree traversal of the dom, calling `callbackFn(node)`
// for every node for which `conditionFn(node)` is true
function walkDOM(topNode, callbackFn=()=>{}, conditionFn=()=>true) {
Expand Down Expand Up @@ -121,6 +125,7 @@ export {
normalizeTagName,
isTextNode,
isCommentNode,
isElementNode,
parseHTML,
findOffsetInElement
};
Loading

0 comments on commit 5020b91

Please sign in to comment.