Skip to content

Commit

Permalink
Reimplement code block to use one <pre>
Browse files Browse the repository at this point in the history
fixes #723
  • Loading branch information
jhchen committed Jul 7, 2016
1 parent 5fc3d06 commit 7cc1683
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 23 deletions.
15 changes: 5 additions & 10 deletions assets/base.styl
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,6 @@ colorItemsPerRow = 7
font-size: 0.83em
h6
font-size: 0.67em
pre
white-space: pre-wrap
a
text-decoration: underline
blockquote
Expand All @@ -128,21 +126,18 @@ colorItemsPerRow = 7
background-color: #f0f0f0
code, pre
border-radius: 3px
pre
white-space: pre-wrap
margin-bottom: 5px
margin-top: 5px
padding: 5px 10px
code
font-size: 85%
padding-bottom: 2px
padding-top: 2px
&:before, &:after
content: "\00a0"
letter-spacing: -2px
*:not(pre) + pre, pre:first-of-type
margin-top: 5px
padding-top: 5px
pre
margin-bottom: 5px
padding: 0px 10px 5px
pre + pre
margin-top: -10px
img
max-width: 100%

Expand Down
12 changes: 10 additions & 2 deletions core/editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Delta from 'rich-text/lib/delta';
import DeltaOp from 'rich-text/lib/op';
import Emitter from './emitter';
import Parchment from 'parchment';
import CodeBlock from '../formats/code';
import Block, { bubbleFormats } from '../blots/block';
import clone from 'clone';
import equal from 'deep-equal';
Expand Down Expand Up @@ -77,8 +78,15 @@ class Editor {
formatLine(index, length, formats = {}, source = Emitter.sources.API) {
this.scroll.update();
Object.keys(formats).forEach((format) => {
this.scroll.lines(index, Math.max(length, 1)).forEach(function(line) {
line.format(format, formats[format]);
let lines = this.scroll.lines(index, Math.max(length, 1));
lines.forEach((line, i) => {
if (!(line instanceof CodeBlock)) {
line.format(format, formats[format]);
} else {
let codeIndex = index - line.offset(this.scroll);
let codeLength = line.newlineIndex(codeIndex) - index + 1;
line.formatAt(codeIndex, codeLength, format, formats[format]);
}
});
});
this.scroll.optimize();
Expand Down
80 changes: 70 additions & 10 deletions formats/code.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Delta from 'rich-text/lib/delta';
import Parchment from 'parchment';
import Block from '../blots/block';
import Inline from '../blots/inline';
import TextBlot from '../blots/text';


class Code extends Inline {}
Expand All @@ -21,24 +22,83 @@ class CodeBlock extends Block {
}

delta() {
let text = this.descendants(Parchment.Leaf).map(function(leaf) {
return leaf instanceof Parchment.Text ? leaf.value() : '';
}).join('');
return new Delta().insert(text).insert('\n', this.formats());
let text = this.domNode.textContent;
if (text.endsWith('\n')) { // Should always be true
text = text.slice(0, -1);
}
return text.split('\n').reduce((delta, frag) => {
return delta.insert(frag).insert('\n', this.formats());
}, new Delta());
}

format(name, value) {
if (name === this.statics.blotName && value) return;
let [text, offset] = this.descendant(TextBlot, this.length() - 1);
if (text != null) {
text.deleteAt(text.length() - 1, 1);
}
super.format(name, value);
}

formatAt(index, length, name, value) {
if (Parchment.query(name, Parchment.Scope.BLOCK) || name === this.statics.blotName) {
super.formatAt(index, length, name, value);
if (length === 0) return;
if (Parchment.query(name, Parchment.Scope.BLOCK) == null ||
(name === this.statics.blotName && value === this.statics.formats(this.domNode))) {
return;
}
let nextNewline = this.newlineIndex(index);
if (nextNewline < 0 || nextNewline >= index + length) return;
let prevNewline = this.newlineIndex(index, true) + 1;
let isolateLength = nextNewline - prevNewline + 1;
let blot = this.isolate(prevNewline, isolateLength);
let next = blot.next;
blot.format(name, value);
if (next instanceof CodeBlock) {
next.formatAt(0, index - prevNewline + length - isolateLength, name, value);
}
}

insertAt(index, value, def) {
if (def != null) return;
let [text, offset] = this.descendant(TextBlot, index);
text.insertAt(offset, value);
}

length() {
return this.domNode.textContent.length;
}

newlineIndex(searchIndex, reverse = false) {
if (!reverse) {
let offset = this.domNode.textContent.slice(searchIndex).indexOf('\n');
return offset > -1 ? searchIndex + offset : -1;
} else {
return this.domNode.textContent.slice(0, searchIndex).lastIndexOf('\n');
}
}

optimize() {
if (!this.domNode.textContent.endsWith('\n')) {
this.appendChild(Parchment.create('text', '\n'));
}
super.optimize();
let next = this.next;
if (next != null && next.prev === this &&
next.statics.blotName === this.statics.blotName &&
this.statics.formats(this.domNode) === next.statics.formats(next.domNode)) {
next.optimize();
next.moveChildren(this);
next.remove();
}
}

replace(target) {
super.replace(target);
this.descendants(function(blot) {
return !(blot instanceof Parchment.Text);
}).forEach(function(blot) {
if (blot instanceof Parchment.Embed) {
[].slice.call(this.domNode.querySelectorAll('*')).forEach(function(node) {
let blot = Parchment.find(node);
if (blot == null) {
node.parentNode.removeChild(node);
} else if (blot instanceof Parchment.Embed) {
blot.remove();
} else {
blot.unwrap();
Expand Down
145 changes: 144 additions & 1 deletion test/unit/formats/code.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,103 @@ describe('Code', function() {
Parchment.register(CodeBlock);
});

it('newline', function() {
let editor = this.initialize(Editor, `
<pre></pre>
<p><br></p>
<pre>\n</pre>
<p><br></p>
<pre>\n\n</pre>
<p><br></p>
`);
expect(editor.scroll.domNode).toEqualHTML(`
<pre>\n</pre>
<p><br></p>
<pre>\n</pre>
<p><br></p>
<pre>\n\n</pre>
<p><br></p>
`);
});

it('default child', function() {
let editor = this.initialize(Editor, '<p><br></p>');
editor.formatLine(0, 1, { 'code-block': true });
expect(editor.scroll.domNode.innerHTML).toEqual('<pre spellcheck="false">\n</pre>');
});

it('merge', function() {
let editor = this.initialize(Editor, `
<pre>0</pre>
<pre>0</pre>
<p><br></p>
<pre>0</pre>
<pre>1\n</pre>
<p><br></p>
<pre>0</pre>
<pre>2\n\n</pre>
<p><br></p>
<pre>1\n</pre>
<pre>0</pre>
<p><br></p>
<pre>1\n</pre>
<pre>1\n</pre>
<p><br></p>
<pre>1\n</pre>
<pre>2\n\n</pre>
<p><br></p>
<pre>2\n\n</pre>
<pre>0</pre>
<p><br></p>
<pre>2\n\n</pre>
<pre>1\n</pre>
<p><br></p>
<pre>2\n\n</pre>
<pre>2\n\n</pre>
`);
editor.scroll.optimize();
expect(editor.scroll.domNode).toEqualHTML(`
<pre>0\n0\n</pre>
<p><br></p>
<pre>0\n1\n</pre>
<p><br></p>
<pre>0\n2\n\n</pre>
<p><br></p>
<pre>1\n0\n</pre>
<p><br></p>
<pre>1\n1\n</pre>
<p><br></p>
<pre>1\n2\n\n</pre>
<p><br></p>
<pre>2\n\n0\n</pre>
<p><br></p>
<pre>2\n\n1\n</pre>
<p><br></p>
<pre>2\n\n2\n\n</pre>
`);
});

it('merge multiple', function() {
let editor = this.initialize(Editor, `
<pre>0</pre>
<pre>1</pre>
<pre>2</pre>
<pre>3</pre>
`);
editor.scroll.optimize();
expect(editor.scroll.domNode).toEqualHTML(`
<pre>0\n1\n2\n3\n</pre>
`);
});

it('add', function() {
let editor = this.initialize(Editor, '<p><em>0123</em></p><p>5678</p>');
editor.formatLine(2, 5, { 'code-block': true });
expect(editor.getDelta()).toEqual(new Delta()
.insert('0123').insert('\n', { 'code-block': true })
.insert('5678').insert('\n', { 'code-block': true })
);
expect(editor.scroll.domNode.innerHTML).toEqual('<pre spellcheck="false">0123</pre><pre spellcheck="false">5678</pre>');
expect(editor.scroll.domNode.innerHTML).toEqual('<pre spellcheck="false">0123\n5678\n</pre>');
});

it('remove', function() {
Expand All @@ -33,6 +122,60 @@ describe('Code', function() {
expect(editor.scroll.domNode).toEqualHTML('<h1>0123</h1>');
});

it('replace multiple', function() {
let editor = this.initialize(Editor, { html: '<pre>01\n23\n</pre>' });
editor.formatText(0, 6, { 'header': 1 });
expect(editor.getDelta()).toEqual(new Delta()
.insert('01').insert('\n', { header: 1 })
.insert('23').insert('\n', { header: 1 })
);
expect(editor.scroll.domNode).toEqualHTML('<h1>01</h1><h1>23</h1>');
});

it('format interior line', function() {
let editor = this.initialize(Editor, { html: '<pre>01\n23\n45\n</pre>' });
editor.formatText(5, 1, { 'header': 1 });
expect(editor.getDelta()).toEqual(new Delta()
.insert('01').insert('\n', { 'code-block': true })
.insert('23').insert('\n', { 'header': 1 })
.insert('45').insert('\n', { 'code-block': true })
);
expect(editor.scroll.domNode.innerHTML).toEqual('<pre>01\n</pre><h1>23</h1><pre>45\n</pre>');
});

it('format imprecise bounds', function() {
let editor = this.initialize(Editor, { html: '<pre>01\n23\n45\n</pre>' });
editor.formatText(1, 6, { 'header': 1 });
expect(editor.getDelta()).toEqual(new Delta()
.insert('01').insert('\n', { 'header': 1 })
.insert('23').insert('\n', { 'header': 1 })
.insert('45').insert('\n', { 'code-block': true })
);
expect(editor.scroll.domNode.innerHTML).toEqual('<h1>01</h1><h1>23</h1><pre>45\n</pre>');
});

it('format without newline', function() {
let editor = this.initialize(Editor, { html: '<pre>01\n23\n45\n</pre>' });
editor.formatText(3, 1, { 'header': 1 });
expect(editor.getDelta()).toEqual(new Delta()
.insert('01').insert('\n', { 'code-block': true })
.insert('23').insert('\n', { 'code-block': true })
.insert('45').insert('\n', { 'code-block': true })
);
expect(editor.scroll.domNode.innerHTML).toEqual('<pre>01\n23\n45\n</pre>');
});

it('format line', function() {
let editor = this.initialize(Editor, { html: '<pre>01\n23\n45\n</pre>' });
editor.formatLine(3, 1, { 'header': 1 });
expect(editor.getDelta()).toEqual(new Delta()
.insert('01').insert('\n', { 'code-block': true })
.insert('23').insert('\n', { 'header': 1 })
.insert('45').insert('\n', { 'code-block': true })
);
expect(editor.scroll.domNode.innerHTML).toEqual('<pre>01\n</pre><h1>23</h1><pre>45\n</pre>');
});

it('ignore formatAt', function() {
let editor = this.initialize(Editor, '<pre>0123</pre>');
editor.formatText(1, 1, { bold: true });
Expand Down

0 comments on commit 7cc1683

Please sign in to comment.