Skip to content

Commit

Permalink
Add Editor#applyMarkupToSelection, change bold command to use it
Browse files Browse the repository at this point in the history
  • Loading branch information
bantic committed Aug 5, 2015
1 parent 897e35a commit f3e99c6
Show file tree
Hide file tree
Showing 12 changed files with 347 additions and 37 deletions.
28 changes: 20 additions & 8 deletions src/js/commands/bold.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
import TextFormatCommand from './text-format';
import { getSelectionBlockTagName } from '../utils/selection-utils';
import { inherit } from 'content-kit-utils';
import Markup from '../models/markup';
import {
any
} from '../utils/array-utils';

var RegExpHeadingTag = /^(h1|h2|h3|h4|h5|h6)$/i;

function BoldCommand() {
function BoldCommand(editor) {
TextFormatCommand.call(this, {
name: 'bold',
tag: 'strong',
mappedTags: ['b'],
button: '<i class="ck-icon-bold"></i>'
});
this.editor = editor;
}
inherit(BoldCommand, TextFormatCommand);

BoldCommand.prototype.exec = function() {
// Don't allow executing bold command on heading tags
if (!RegExpHeadingTag.test(getSelectionBlockTagName())) {
BoldCommand._super.prototype.exec.call(this);
}
const markup = Markup.ofType('b');
this.editor.applyMarkupToSelection(markup);
};

BoldCommand.prototype.isActive = function() {
let val = any(this.editor.activeMarkers, m => {
return any(this.mappedTags, tag => m.hasMarkup(tag));
});
return val;
};

BoldCommand.prototype.unexec = function() {
const markup = Markup.ofType('b');
this.editor.removeMarkupFromSelection(markup);
};

export default BoldCommand;
122 changes: 116 additions & 6 deletions src/js/editor/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ const defaults = {
// in tests
stickyToolbar: false, // !!('ontouchstart' in window),
textFormatCommands: [
new BoldCommand(),
new ItalicCommand(),
new LinkCommand()
],
Expand Down Expand Up @@ -190,10 +189,14 @@ function makeButtons(editor) {
const quoteCommand = new QuoteCommand(editor);
const quoteButton = new ReversibleToolbarButton(quoteCommand, editor);

const boldCommand = new BoldCommand(editor);
const boldButton = new ReversibleToolbarButton(boldCommand, editor);

return [
headingButton,
subheadingButton,
quoteButton
quoteButton,
boldButton
];
}

Expand Down Expand Up @@ -399,7 +402,10 @@ class Editor {
const markerRenderNode = leftRenderNode;
const marker = markerRenderNode.postNode;
const section = marker.section;
const [leftMarker, rightMarker] = marker.split(leftOffset);
const newMarkers = marker.split(leftOffset);

// FIXME rightMarker is not guaranteed to be there
let [leftMarker, rightMarker] = newMarkers;

section.insertMarkerAfter(leftMarker, marker);
markerRenderNode.scheduleForRemoval();
Expand Down Expand Up @@ -458,11 +464,100 @@ class Editor {
this.hasSelection();
}

getActiveSections() {
const cursor = this.cursor;
return cursor.activeSections;
/*
* @return {Array} of markers that are "inside the split"
*/
splitMarkersFromSelection() {
const {
startMarker,
leftOffset:startMarkerOffset,
endMarker,
rightOffset:endMarkerOffset,
startSection,
endSection
} = this.cursor.offsets;

let selectedMarkers = [];

startMarker.renderNode.scheduleForRemoval();
endMarker.renderNode.scheduleForRemoval();

if (startMarker === endMarker) {
let newMarkers = startSection.splitMarker(
startMarker, startMarkerOffset, endMarkerOffset
);
selectedMarkers = this.markersInOffset(newMarkers, startMarkerOffset, endMarkerOffset);
} else {
let newStartMarkers = startSection.splitMarker(startMarker, startMarkerOffset);
let selectedStartMarkers = this.markersInOffset(newStartMarkers, startMarkerOffset);

let newEndMarkers = endSection.splitMarker(endMarker, endMarkerOffset);
let selectedEndMarkers = this.markersInOffset(newEndMarkers, 0, endMarkerOffset);

let newStartMarker = selectedStartMarkers[0],
newEndMarker = selectedEndMarkers[selectedEndMarkers.length - 1];

this.post.markersFrom(newStartMarker, newEndMarker, m => selectedMarkers.push(m));
}

return selectedMarkers;
}

markersInOffset(markers, startOffset, endOffset) {
let offset = 0;
let foundMarkers = [];
let toEnd = endOffset === undefined;
if (toEnd) { endOffset = 0; }

markers.forEach(marker => {
if (toEnd) {
endOffset += marker.length;
}

if (offset >= startOffset && offset < endOffset) {
foundMarkers.push(marker);
}

offset += marker.length;
});

return foundMarkers;
}

applyMarkupToSelection(markup) {
const markers = this.splitMarkersFromSelection();
markers.forEach(marker => {
marker.addMarkup(markup);
marker.section.renderNode.markDirty();
});

this.rerender();
this.selectMarkers(markers);
this.didUpdate();
}

removeMarkupFromSelection(markup) {
const markers = this.activeMarkers;
// FIXME-NEXT Now we need to ensure we are using the singleton
// markup for the 'B' tag
// in order to get http://localhost:4200/tests/?testId=8cb07cab
// to pass
markers.forEach(marker => {
marker.removeMarkup(markup);
marker.section.renderNode.markDirty();
});

this.rerender();
this.selectMarkers(markers);
this.didUpdate();
}

selectMarkers(markers) {
this.cursor.selectMarkers(markers);
this.hasSelection();
}


get cursor() {
return new Cursor(this);
}
Expand Down Expand Up @@ -615,6 +710,21 @@ class Editor {
return this.cursor.activeSections;
}

get activeMarkers() {
const {
startMarker,
endMarker,
} = this.cursor.offsets;

if (!(startMarker && endMarker)) {
return [];
}

let activeMarkers = [];
this.post.markersFrom(startMarker, endMarker, m => activeMarkers.push(m));
return activeMarkers;
}

/*
* Clear the markups from each of the section's markers
*/
Expand Down
32 changes: 30 additions & 2 deletions src/js/models/cursor.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,14 @@ export default class Cursor {
get offsets() {
let leftNode, rightNode,
leftOffset, rightOffset;
const { anchorNode, focusNode, anchorOffset, focusOffset } = this.selection;
const selection = this.selection;
const { anchorNode, focusNode, anchorOffset, focusOffset } = selection;
const { rangeCount } = selection;
const range = rangeCount > 0 && selection.getRangeAt(0);

if (!range) {
return {};
}

const position = anchorNode.compareDocumentPosition(focusNode);

Expand All @@ -54,13 +61,23 @@ export default class Cursor {
const leftRenderNode = this.renderTree.elements.get(leftNode),
rightRenderNode = this.renderTree.elements.get(rightNode);

const startMarker = leftRenderNode && leftRenderNode.postNode,
endMarker = rightRenderNode && rightRenderNode.postNode;

const startSection = startMarker && startMarker.section;
const endSection = endMarker && endMarker.section;

return {
leftNode,
rightNode,
leftOffset,
rightOffset,
leftRenderNode,
rightRenderNode
rightRenderNode,
startMarker,
endMarker,
startSection,
endSection
};
}

Expand Down Expand Up @@ -116,6 +133,17 @@ export default class Cursor {
this.moveToNode(startNode, startOffset, endNode, endOffset);
}

selectMarkers(markers) {
const startMarker = markers[0],
endMarker = markers[markers.length - 1];

const startNode = startMarker.renderNode.element,
endNode = endMarker.renderNode.element;
const startOffset = 0, endOffset = endMarker.length;

this.moveToNode(startNode, startOffset, endNode, endOffset);
}

moveToNode(node, offset=0, endNode=node, endOffset=offset) {
let r = document.createRange();
r.setStart(node, offset);
Expand Down
35 changes: 25 additions & 10 deletions src/js/models/marker.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ const Marker = class Marker {

removeMarkup(markup) {
const index = this.markups.indexOf(markup);
if (index === -1) { throw new Error('Cannot remove markup that is not there.'); }

this.markups.splice(index, 1);
if (index !== -1) {
this.markups.splice(index, 1);
}
}

// delete the character at this offset,
Expand Down Expand Up @@ -72,14 +72,27 @@ const Marker = class Marker {
return joined;
}

split(offset) {
const [m1, m2] = [
new Marker(this.value.substr(0, offset)),
new Marker(this.value.substr(offset))
];
this.markups.forEach(m => {m1.addMarkup(m); m2.addMarkup(m);});
split(offset=0, endOffset=this.length) {
let markers = [];

if (offset !== 0) {
markers.push(
new Marker(this.value.substring(0, offset))
);
}

markers.push(
new Marker(this.value.substring(offset, endOffset))
);

if (endOffset < this.length) {
markers.push(
new Marker(this.value.substring(endOffset))
);
}

return [m1, m2];
this.markups.forEach(mu => markers.forEach(m => m.addMarkup(mu)));
return markers;
}

get openedMarkups() {
Expand All @@ -88,6 +101,8 @@ const Marker = class Marker {
}
let i;
for (i=0; i<this.markups.length; i++) {
// FIXME this should iterate everything and return all things that are not
// on this marker -- it assumes the markups are always in the same order
if (this.markups[i] !== this.previousSibling.markups[i]) {
return this.markups.slice(i);
}
Expand Down
30 changes: 30 additions & 0 deletions src/js/models/markup-section.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,29 @@ export default class Section {
this.tagName = DEFAULT_TAG_NAME;
}

/**
* Splits the marker at the offset (until the endOffset, if given)
* into 1, 2, or 3 markers and replaces the existing marker
* with the new ones
*/
splitMarker(marker, offset, endOffset=marker.length) {
const newMarkers = marker.split(offset, endOffset);
this.replaceMarker(marker, newMarkers);
return newMarkers;
}

replaceMarker(oldMarker, newMarkers=[]) {
let previousMarker = oldMarker;

let i = newMarkers.length;
while (i--) {
let currentMarker = newMarkers[i];
this.insertMarkerAfter(currentMarker, previousMarker);
}

this.removeMarker(oldMarker);
}

prependMarker(marker) {
marker.section = this;
this.markers.unshift(marker);
Expand Down Expand Up @@ -129,4 +152,11 @@ export default class Section {
return this.markers[leftInclusive ? i : i-1];
}
}

get nextSibling() {
const index = this.post.sections.indexOf(this);
if (index !== -1) {
return this.post.sections[index+1];
}
}
}
Loading

0 comments on commit f3e99c6

Please sign in to comment.