mirror of
https://github.com/fastmail/Squire.git
synced 2025-01-02 20:50:17 -05:00
Add support for <pre>/<code> formatting
This commit is contained in:
parent
e07150192f
commit
625d10139e
6 changed files with 445 additions and 6 deletions
21
Demo.html
21
Demo.html
|
@ -59,6 +59,16 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
}
|
}
|
||||||
|
pre {
|
||||||
|
white-space: pre-wrap;word-wrap: break-word;overflow-wrap: break-word;border-radius: 3px;border: 1px solid #ccc; padding: 7px 10px; background: #f6f6f6; font-family: menlo, consolas, monospace; font-size: 90%; }
|
||||||
|
code {
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
padding: 1px 3px;
|
||||||
|
background: #f6f6f6;
|
||||||
|
font-family: menlo, consolas, monospace;
|
||||||
|
font-size: 90%;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -93,6 +103,9 @@
|
||||||
<span id="removeList">Unlist</span>
|
<span id="removeList">Unlist</span>
|
||||||
<span id="increaseListLevel">Increase list level</span>
|
<span id="increaseListLevel">Increase list level</span>
|
||||||
<span id="decreaseListLevel">Decrease list level</span>
|
<span id="decreaseListLevel">Decrease list level</span>
|
||||||
|
|
||||||
|
<span id="code">Code</span>
|
||||||
|
<span id="removeCode">Uncode</span>
|
||||||
|
|
||||||
<span id="insertImage" class="prompt">Insert image</span>
|
<span id="insertImage" class="prompt">Insert image</span>
|
||||||
<span id="setHTML" class="prompt">Set HTML</span>
|
<span id="setHTML" class="prompt">Set HTML</span>
|
||||||
|
@ -112,7 +125,13 @@
|
||||||
ul: {'class': 'UL'},
|
ul: {'class': 'UL'},
|
||||||
ol: {'class': 'OL'},
|
ol: {'class': 'OL'},
|
||||||
li: {'class': 'listItem'},
|
li: {'class': 'listItem'},
|
||||||
a: {'target': '_blank'}
|
a: {'target': '_blank'},
|
||||||
|
pre: {
|
||||||
|
style: 'border-radius:3px;border:1px solid #ccc;padding:7px 10px;background:#f6f6f6;font-family:menlo,consolas,monospace;font-size:90%;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;'
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
style: 'border-radius:3px;border:1px solid #ccc;padding:1px 3px;background:#f6f6f6;font-family:menlo,consolas,monospace;font-size:90%;'
|
||||||
|
},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
Squire.prototype.makeHeader = function() {
|
Squire.prototype.makeHeader = function() {
|
||||||
|
|
20
README.md
20
README.md
|
@ -109,7 +109,7 @@ Attach an event listener to the editor. The handler can be either a function or
|
||||||
* **cursor**: The user cleared their selection or moved the cursor to a
|
* **cursor**: The user cleared their selection or moved the cursor to a
|
||||||
different position.
|
different position.
|
||||||
* **undoStateChange**: The availability of undo and/or redo has changed. The event object has two boolean properties, `canUndo` and `canRedo` to let you know the new state.
|
* **undoStateChange**: The availability of undo and/or redo has changed. The event object has two boolean properties, `canUndo` and `canRedo` to let you know the new state.
|
||||||
* **willPaste**: The user is pasting content into the document. The content that will be inserted is available as the `fragment` property on the event object. You can modify this fragment in your event handler to change what will be pasted. You can also call the `preventDefault` on the event object to cancel the paste operation.
|
* **willPaste**: The user is pasting content into the document. The content that will be inserted is available as either the `fragment` property on the event object, or the `text` property for plain text being inserted into a `<pre>`. You can modify this text/fragment in your event handler to change what will be pasted. You can also call the `preventDefault` on the event object to cancel the paste operation.
|
||||||
|
|
||||||
The method takes two arguments:
|
The method takes two arguments:
|
||||||
|
|
||||||
|
@ -460,6 +460,24 @@ Decreases by 1 the nesting level of any at-least-partially selected blocks which
|
||||||
|
|
||||||
Returns self (the Squire instance).
|
Returns self (the Squire instance).
|
||||||
|
|
||||||
|
### code
|
||||||
|
|
||||||
|
If no selection, or selection across blocks, converts the block to a `<pre>` to format the text as fixed-width. If a selection within a single block is present, wraps that in `<code>` tags for inline formatting instead.
|
||||||
|
|
||||||
|
Returns self (the Squire instance).
|
||||||
|
|
||||||
|
### removeCode
|
||||||
|
|
||||||
|
If inside a `<pre>`, converts that to the default block type instead. Otherwise, removes any `<code>` tags.
|
||||||
|
|
||||||
|
Returns self (the Squire instance).
|
||||||
|
|
||||||
|
### toggleCode
|
||||||
|
|
||||||
|
If inside a `<pre>` or `<code>`, calls `removeCode()`, otherwise callse `code()`.
|
||||||
|
|
||||||
|
Returns self (the Squire instance).
|
||||||
|
|
||||||
### removeAllFormatting
|
### removeAllFormatting
|
||||||
|
|
||||||
Removes all formatting from the selection. Block elements (list items, table cells, etc.) are kept as separate blocks.
|
Removes all formatting from the selection. Block elements (list items, table cells, etc.) are kept as separate blocks.
|
||||||
|
|
|
@ -1417,7 +1417,7 @@ var afterDelete = function ( self, range ) {
|
||||||
var keyHandlers = {
|
var keyHandlers = {
|
||||||
enter: function ( self, event, range ) {
|
enter: function ( self, event, range ) {
|
||||||
var root = self._root;
|
var root = self._root;
|
||||||
var block, parent, nodeAfterSplit;
|
var block, parent, node, offset, nodeAfterSplit;
|
||||||
|
|
||||||
// We handle this ourselves
|
// We handle this ourselves
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -1438,6 +1438,54 @@ var keyHandlers = {
|
||||||
|
|
||||||
block = getStartBlockOfRange( range, root );
|
block = getStartBlockOfRange( range, root );
|
||||||
|
|
||||||
|
// Inside a PRE, insert literal newline, unless on blank line.
|
||||||
|
if ( block && ( parent = getNearest( block, root, 'PRE' ) ) ) {
|
||||||
|
moveRangeBoundariesDownTree( range );
|
||||||
|
node = range.startContainer;
|
||||||
|
offset = range.startOffset;
|
||||||
|
if ( node.nodeType !== TEXT_NODE ) {
|
||||||
|
node = self._doc.createTextNode( '' );
|
||||||
|
parent.insertBefore( node, parent.firstChild );
|
||||||
|
}
|
||||||
|
// If blank line: split and insert default block
|
||||||
|
if ( !event.shiftKey &&
|
||||||
|
( node.data.charAt( offset - 1 ) === '\n' ||
|
||||||
|
rangeDoesStartAtBlockBoundary( range, root ) ) &&
|
||||||
|
( node.data.charAt( offset ) === '\n' ||
|
||||||
|
rangeDoesEndAtBlockBoundary( range, root ) ) ) {
|
||||||
|
node.deleteData( offset && offset - 1, offset ? 2 : 1 );
|
||||||
|
nodeAfterSplit =
|
||||||
|
split( node, offset && offset - 1, root, root );
|
||||||
|
node = nodeAfterSplit.previousSibling;
|
||||||
|
if ( !node.textContent ) {
|
||||||
|
detach( node );
|
||||||
|
}
|
||||||
|
node = self.createDefaultBlock();
|
||||||
|
nodeAfterSplit.parentNode.insertBefore( node, nodeAfterSplit );
|
||||||
|
if ( !nodeAfterSplit.textContent ) {
|
||||||
|
detach( nodeAfterSplit );
|
||||||
|
}
|
||||||
|
range.setStart( node, 0 );
|
||||||
|
} else {
|
||||||
|
node.insertData( offset, '\n' );
|
||||||
|
fixCursor( parent, root );
|
||||||
|
// Firefox bug: if you set the selection in the text node after
|
||||||
|
// the new line, it draws the cursor before the line break still
|
||||||
|
// but if you set the selection to the equivalent position
|
||||||
|
// in the parent, it works.
|
||||||
|
if ( node.length === offset + 1 ) {
|
||||||
|
range.setStartAfter( node );
|
||||||
|
} else {
|
||||||
|
range.setStart( node, offset + 1 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
range.collapse( true );
|
||||||
|
self.setSelection( range );
|
||||||
|
self._updatePath( range, true );
|
||||||
|
self._docWasChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If this is a malformed bit of document or in a table;
|
// If this is a malformed bit of document or in a table;
|
||||||
// just play it safe and insert a <br>.
|
// just play it safe and insert a <br>.
|
||||||
if ( !block || event.shiftKey || /^T[HD]$/.test( block.nodeName ) ) {
|
if ( !block || event.shiftKey || /^T[HD]$/.test( block.nodeName ) ) {
|
||||||
|
@ -1777,6 +1825,7 @@ keyHandlers[ ctrlKey + 'shift-8' ] = mapKeyTo( 'makeUnorderedList' );
|
||||||
keyHandlers[ ctrlKey + 'shift-9' ] = mapKeyTo( 'makeOrderedList' );
|
keyHandlers[ ctrlKey + 'shift-9' ] = mapKeyTo( 'makeOrderedList' );
|
||||||
keyHandlers[ ctrlKey + '[' ] = mapKeyTo( 'decreaseQuoteLevel' );
|
keyHandlers[ ctrlKey + '[' ] = mapKeyTo( 'decreaseQuoteLevel' );
|
||||||
keyHandlers[ ctrlKey + ']' ] = mapKeyTo( 'increaseQuoteLevel' );
|
keyHandlers[ ctrlKey + ']' ] = mapKeyTo( 'increaseQuoteLevel' );
|
||||||
|
keyHandlers[ ctrlKey + 'd' ] = mapKeyTo( 'toggleCode' );
|
||||||
keyHandlers[ ctrlKey + 'y' ] = mapKeyTo( 'redo' );
|
keyHandlers[ ctrlKey + 'y' ] = mapKeyTo( 'redo' );
|
||||||
keyHandlers[ ctrlKey + 'z' ] = mapKeyTo( 'undo' );
|
keyHandlers[ ctrlKey + 'z' ] = mapKeyTo( 'undo' );
|
||||||
keyHandlers[ ctrlKey + 'shift-z' ] = mapKeyTo( 'redo' );
|
keyHandlers[ ctrlKey + 'shift-z' ] = mapKeyTo( 'redo' );
|
||||||
|
@ -4390,6 +4439,38 @@ var escapeHTMLFragement = function ( text ) {
|
||||||
};
|
};
|
||||||
|
|
||||||
proto.insertPlainText = function ( plainText, isPaste ) {
|
proto.insertPlainText = function ( plainText, isPaste ) {
|
||||||
|
var range = this.getSelection();
|
||||||
|
if ( range.collapsed &&
|
||||||
|
getNearest( range.startContainer, this._root, 'PRE' ) ) {
|
||||||
|
var node = range.startContainer;
|
||||||
|
var offset = range.startOffset;
|
||||||
|
var text, event;
|
||||||
|
if ( !node || node.nodeType !== TEXT_NODE ) {
|
||||||
|
text = this._doc.createTextNode( '' );
|
||||||
|
node.insertBefore( text, node.childNodes[ offset ] );
|
||||||
|
node = text;
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
event = {
|
||||||
|
text: plainText,
|
||||||
|
preventDefault: function () {
|
||||||
|
this.defaultPrevented = true;
|
||||||
|
},
|
||||||
|
defaultPrevented: false
|
||||||
|
};
|
||||||
|
if ( isPaste ) {
|
||||||
|
this.fireEvent( 'willPaste', event );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !event.defaultPrevented ) {
|
||||||
|
plainText = event.text;
|
||||||
|
node.insertData( offset, plainText );
|
||||||
|
range.setStart( node, offset + plainText.length );
|
||||||
|
range.collapse( true );
|
||||||
|
}
|
||||||
|
this.setSelection( range );
|
||||||
|
return this;
|
||||||
|
}
|
||||||
var lines = plainText.split( '\n' );
|
var lines = plainText.split( '\n' );
|
||||||
var config = this._config;
|
var config = this._config;
|
||||||
var tag = config.blockTag;
|
var tag = config.blockTag;
|
||||||
|
@ -4574,6 +4655,126 @@ proto.setTextDirection = function ( direction ) {
|
||||||
return this.focus();
|
return this.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
var addPre = function ( frag ) {
|
||||||
|
var root = this._root;
|
||||||
|
var document = this._doc;
|
||||||
|
var output = document.createDocumentFragment();
|
||||||
|
var walker = getBlockWalker( frag, root );
|
||||||
|
var node;
|
||||||
|
// 1. Extract inline content; drop all blocks and contains.
|
||||||
|
while (( node = walker.nextNode() )) {
|
||||||
|
// 2. Replace <br> with \n in content
|
||||||
|
var nodes = node.querySelectorAll( 'BR' );
|
||||||
|
var brBreaksLine = [];
|
||||||
|
var l = nodes.length;
|
||||||
|
var i, br;
|
||||||
|
|
||||||
|
// Must calculate whether the <br> breaks a line first, because if we
|
||||||
|
// have two <br>s next to each other, after the first one is converted
|
||||||
|
// to a block split, the second will be at the end of a block and
|
||||||
|
// therefore seem to not be a line break. But in its original context it
|
||||||
|
// was, so we should also convert it to a block split.
|
||||||
|
for ( i = 0; i < l; i += 1 ) {
|
||||||
|
brBreaksLine[i] = isLineBreak( nodes[i], false );
|
||||||
|
}
|
||||||
|
while ( l-- ) {
|
||||||
|
br = nodes[l];
|
||||||
|
if ( !brBreaksLine[l] ) {
|
||||||
|
detach( br );
|
||||||
|
} else {
|
||||||
|
replaceWith( br, document.createTextNode( '\n' ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 3. Remove <code>; its format clashes with <pre>
|
||||||
|
nodes = node.querySelectorAll( 'CODE' );
|
||||||
|
l = nodes.length;
|
||||||
|
while ( l-- ) {
|
||||||
|
detach( nodes[l] );
|
||||||
|
}
|
||||||
|
if ( output.childNodes.length ) {
|
||||||
|
output.appendChild( document.createTextNode( '\n' ) );
|
||||||
|
}
|
||||||
|
output.appendChild( empty( node ) );
|
||||||
|
}
|
||||||
|
// 4. Replace nbsp with regular sp
|
||||||
|
walker = new TreeWalker( output, SHOW_TEXT );
|
||||||
|
while (( node = walker.nextNode() )) {
|
||||||
|
node.data = node.data.replace( / /g, ' ' ); // nbsp -> sp
|
||||||
|
}
|
||||||
|
output.normalize();
|
||||||
|
return fixCursor( this.createElement( 'PRE',
|
||||||
|
this._config.tagAttributes.pre, [
|
||||||
|
output
|
||||||
|
]), root );
|
||||||
|
};
|
||||||
|
|
||||||
|
var removePre = function ( frag ) {
|
||||||
|
var document = this._doc;
|
||||||
|
var root = this._root;
|
||||||
|
var pres = frag.querySelectorAll( 'PRE' );
|
||||||
|
var l = pres.length;
|
||||||
|
var pre, walker, node, value, contents, index;
|
||||||
|
while ( l-- ) {
|
||||||
|
pre = pres[l];
|
||||||
|
walker = new TreeWalker( pre, SHOW_TEXT );
|
||||||
|
while (( node = walker.nextNode() )) {
|
||||||
|
value = node.data;
|
||||||
|
value = value.replace( / (?= )/g, ' ' ); // sp -> nbsp
|
||||||
|
contents = document.createDocumentFragment();
|
||||||
|
while (( index = value.indexOf( '\n' ) ) > -1 ) {
|
||||||
|
contents.appendChild(
|
||||||
|
document.createTextNode( value.slice( 0, index ) )
|
||||||
|
);
|
||||||
|
contents.appendChild( document.createElement( 'BR' ) );
|
||||||
|
value = value.slice( index + 1 );
|
||||||
|
}
|
||||||
|
node.parentNode.insertBefore( contents, node );
|
||||||
|
node.data = value;
|
||||||
|
}
|
||||||
|
fixContainer( pre, root );
|
||||||
|
replaceWith( pre, empty( pre ) );
|
||||||
|
}
|
||||||
|
return frag;
|
||||||
|
};
|
||||||
|
|
||||||
|
proto.code = function () {
|
||||||
|
var range = this.getSelection();
|
||||||
|
if ( range.collapsed || isContainer( range.commonAncestorContainer ) ) {
|
||||||
|
this.modifyBlocks( addPre, range );
|
||||||
|
} else {
|
||||||
|
this.changeFormat({
|
||||||
|
tag: 'CODE',
|
||||||
|
attributes: this._config.tagAttributes.code
|
||||||
|
}, null, range );
|
||||||
|
}
|
||||||
|
return this.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
proto.removeCode = function () {
|
||||||
|
var range = this.getSelection();
|
||||||
|
var ancestor = range.commonAncestorContainer;
|
||||||
|
var inPre = getNearest( ancestor, this._root, 'PRE' );
|
||||||
|
if ( inPre ) {
|
||||||
|
this.modifyBlocks( removePre, range );
|
||||||
|
} else {
|
||||||
|
this.changeFormat( null, { tag: 'CODE' }, range );
|
||||||
|
}
|
||||||
|
return this.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
proto.toggleCode = function () {
|
||||||
|
if ( this.hasFormat( 'PRE' ) || this.hasFormat( 'CODE' ) ) {
|
||||||
|
this.removeCode();
|
||||||
|
} else {
|
||||||
|
this.code();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
function removeFormatting ( self, root, clean ) {
|
function removeFormatting ( self, root, clean ) {
|
||||||
var node, next;
|
var node, next;
|
||||||
for ( node = root.firstChild; node; node = next ) {
|
for ( node = root.firstChild; node; node = next ) {
|
||||||
|
|
File diff suppressed because one or more lines are too long
152
source/Editor.js
152
source/Editor.js
|
@ -1917,6 +1917,38 @@ var escapeHTMLFragement = function ( text ) {
|
||||||
};
|
};
|
||||||
|
|
||||||
proto.insertPlainText = function ( plainText, isPaste ) {
|
proto.insertPlainText = function ( plainText, isPaste ) {
|
||||||
|
var range = this.getSelection();
|
||||||
|
if ( range.collapsed &&
|
||||||
|
getNearest( range.startContainer, this._root, 'PRE' ) ) {
|
||||||
|
var node = range.startContainer;
|
||||||
|
var offset = range.startOffset;
|
||||||
|
var text, event;
|
||||||
|
if ( !node || node.nodeType !== TEXT_NODE ) {
|
||||||
|
text = this._doc.createTextNode( '' );
|
||||||
|
node.insertBefore( text, node.childNodes[ offset ] );
|
||||||
|
node = text;
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
event = {
|
||||||
|
text: plainText,
|
||||||
|
preventDefault: function () {
|
||||||
|
this.defaultPrevented = true;
|
||||||
|
},
|
||||||
|
defaultPrevented: false
|
||||||
|
};
|
||||||
|
if ( isPaste ) {
|
||||||
|
this.fireEvent( 'willPaste', event );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !event.defaultPrevented ) {
|
||||||
|
plainText = event.text;
|
||||||
|
node.insertData( offset, plainText );
|
||||||
|
range.setStart( node, offset + plainText.length );
|
||||||
|
range.collapse( true );
|
||||||
|
}
|
||||||
|
this.setSelection( range );
|
||||||
|
return this;
|
||||||
|
}
|
||||||
var lines = plainText.split( '\n' );
|
var lines = plainText.split( '\n' );
|
||||||
var config = this._config;
|
var config = this._config;
|
||||||
var tag = config.blockTag;
|
var tag = config.blockTag;
|
||||||
|
@ -2101,6 +2133,126 @@ proto.setTextDirection = function ( direction ) {
|
||||||
return this.focus();
|
return this.focus();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
|
var addPre = function ( frag ) {
|
||||||
|
var root = this._root;
|
||||||
|
var document = this._doc;
|
||||||
|
var output = document.createDocumentFragment();
|
||||||
|
var walker = getBlockWalker( frag, root );
|
||||||
|
var node;
|
||||||
|
// 1. Extract inline content; drop all blocks and contains.
|
||||||
|
while (( node = walker.nextNode() )) {
|
||||||
|
// 2. Replace <br> with \n in content
|
||||||
|
var nodes = node.querySelectorAll( 'BR' );
|
||||||
|
var brBreaksLine = [];
|
||||||
|
var l = nodes.length;
|
||||||
|
var i, br;
|
||||||
|
|
||||||
|
// Must calculate whether the <br> breaks a line first, because if we
|
||||||
|
// have two <br>s next to each other, after the first one is converted
|
||||||
|
// to a block split, the second will be at the end of a block and
|
||||||
|
// therefore seem to not be a line break. But in its original context it
|
||||||
|
// was, so we should also convert it to a block split.
|
||||||
|
for ( i = 0; i < l; i += 1 ) {
|
||||||
|
brBreaksLine[i] = isLineBreak( nodes[i], false );
|
||||||
|
}
|
||||||
|
while ( l-- ) {
|
||||||
|
br = nodes[l];
|
||||||
|
if ( !brBreaksLine[l] ) {
|
||||||
|
detach( br );
|
||||||
|
} else {
|
||||||
|
replaceWith( br, document.createTextNode( '\n' ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 3. Remove <code>; its format clashes with <pre>
|
||||||
|
nodes = node.querySelectorAll( 'CODE' );
|
||||||
|
l = nodes.length;
|
||||||
|
while ( l-- ) {
|
||||||
|
detach( nodes[l] );
|
||||||
|
}
|
||||||
|
if ( output.childNodes.length ) {
|
||||||
|
output.appendChild( document.createTextNode( '\n' ) );
|
||||||
|
}
|
||||||
|
output.appendChild( empty( node ) );
|
||||||
|
}
|
||||||
|
// 4. Replace nbsp with regular sp
|
||||||
|
walker = new TreeWalker( output, SHOW_TEXT );
|
||||||
|
while (( node = walker.nextNode() )) {
|
||||||
|
node.data = node.data.replace( / /g, ' ' ); // nbsp -> sp
|
||||||
|
}
|
||||||
|
output.normalize();
|
||||||
|
return fixCursor( this.createElement( 'PRE',
|
||||||
|
this._config.tagAttributes.pre, [
|
||||||
|
output
|
||||||
|
]), root );
|
||||||
|
};
|
||||||
|
|
||||||
|
var removePre = function ( frag ) {
|
||||||
|
var document = this._doc;
|
||||||
|
var root = this._root;
|
||||||
|
var pres = frag.querySelectorAll( 'PRE' );
|
||||||
|
var l = pres.length;
|
||||||
|
var pre, walker, node, value, contents, index;
|
||||||
|
while ( l-- ) {
|
||||||
|
pre = pres[l];
|
||||||
|
walker = new TreeWalker( pre, SHOW_TEXT );
|
||||||
|
while (( node = walker.nextNode() )) {
|
||||||
|
value = node.data;
|
||||||
|
value = value.replace( / (?= )/g, ' ' ); // sp -> nbsp
|
||||||
|
contents = document.createDocumentFragment();
|
||||||
|
while (( index = value.indexOf( '\n' ) ) > -1 ) {
|
||||||
|
contents.appendChild(
|
||||||
|
document.createTextNode( value.slice( 0, index ) )
|
||||||
|
);
|
||||||
|
contents.appendChild( document.createElement( 'BR' ) );
|
||||||
|
value = value.slice( index + 1 );
|
||||||
|
}
|
||||||
|
node.parentNode.insertBefore( contents, node );
|
||||||
|
node.data = value;
|
||||||
|
}
|
||||||
|
fixContainer( pre, root );
|
||||||
|
replaceWith( pre, empty( pre ) );
|
||||||
|
}
|
||||||
|
return frag;
|
||||||
|
};
|
||||||
|
|
||||||
|
proto.code = function () {
|
||||||
|
var range = this.getSelection();
|
||||||
|
if ( range.collapsed || isContainer( range.commonAncestorContainer ) ) {
|
||||||
|
this.modifyBlocks( addPre, range );
|
||||||
|
} else {
|
||||||
|
this.changeFormat({
|
||||||
|
tag: 'CODE',
|
||||||
|
attributes: this._config.tagAttributes.code
|
||||||
|
}, null, range );
|
||||||
|
}
|
||||||
|
return this.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
proto.removeCode = function () {
|
||||||
|
var range = this.getSelection();
|
||||||
|
var ancestor = range.commonAncestorContainer;
|
||||||
|
var inPre = getNearest( ancestor, this._root, 'PRE' );
|
||||||
|
if ( inPre ) {
|
||||||
|
this.modifyBlocks( removePre, range );
|
||||||
|
} else {
|
||||||
|
this.changeFormat( null, { tag: 'CODE' }, range );
|
||||||
|
}
|
||||||
|
return this.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
proto.toggleCode = function () {
|
||||||
|
if ( this.hasFormat( 'PRE' ) || this.hasFormat( 'CODE' ) ) {
|
||||||
|
this.removeCode();
|
||||||
|
} else {
|
||||||
|
this.code();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---
|
||||||
|
|
||||||
function removeFormatting ( self, root, clean ) {
|
function removeFormatting ( self, root, clean ) {
|
||||||
var node, next;
|
var node, next;
|
||||||
for ( node = root.firstChild; node; node = next ) {
|
for ( node = root.firstChild; node; node = next ) {
|
||||||
|
|
|
@ -146,7 +146,7 @@ var afterDelete = function ( self, range ) {
|
||||||
var keyHandlers = {
|
var keyHandlers = {
|
||||||
enter: function ( self, event, range ) {
|
enter: function ( self, event, range ) {
|
||||||
var root = self._root;
|
var root = self._root;
|
||||||
var block, parent, nodeAfterSplit;
|
var block, parent, node, offset, nodeAfterSplit;
|
||||||
|
|
||||||
// We handle this ourselves
|
// We handle this ourselves
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -167,6 +167,54 @@ var keyHandlers = {
|
||||||
|
|
||||||
block = getStartBlockOfRange( range, root );
|
block = getStartBlockOfRange( range, root );
|
||||||
|
|
||||||
|
// Inside a PRE, insert literal newline, unless on blank line.
|
||||||
|
if ( block && ( parent = getNearest( block, root, 'PRE' ) ) ) {
|
||||||
|
moveRangeBoundariesDownTree( range );
|
||||||
|
node = range.startContainer;
|
||||||
|
offset = range.startOffset;
|
||||||
|
if ( node.nodeType !== TEXT_NODE ) {
|
||||||
|
node = self._doc.createTextNode( '' );
|
||||||
|
parent.insertBefore( node, parent.firstChild );
|
||||||
|
}
|
||||||
|
// If blank line: split and insert default block
|
||||||
|
if ( !event.shiftKey &&
|
||||||
|
( node.data.charAt( offset - 1 ) === '\n' ||
|
||||||
|
rangeDoesStartAtBlockBoundary( range, root ) ) &&
|
||||||
|
( node.data.charAt( offset ) === '\n' ||
|
||||||
|
rangeDoesEndAtBlockBoundary( range, root ) ) ) {
|
||||||
|
node.deleteData( offset && offset - 1, offset ? 2 : 1 );
|
||||||
|
nodeAfterSplit =
|
||||||
|
split( node, offset && offset - 1, root, root );
|
||||||
|
node = nodeAfterSplit.previousSibling;
|
||||||
|
if ( !node.textContent ) {
|
||||||
|
detach( node );
|
||||||
|
}
|
||||||
|
node = self.createDefaultBlock();
|
||||||
|
nodeAfterSplit.parentNode.insertBefore( node, nodeAfterSplit );
|
||||||
|
if ( !nodeAfterSplit.textContent ) {
|
||||||
|
detach( nodeAfterSplit );
|
||||||
|
}
|
||||||
|
range.setStart( node, 0 );
|
||||||
|
} else {
|
||||||
|
node.insertData( offset, '\n' );
|
||||||
|
fixCursor( parent, root );
|
||||||
|
// Firefox bug: if you set the selection in the text node after
|
||||||
|
// the new line, it draws the cursor before the line break still
|
||||||
|
// but if you set the selection to the equivalent position
|
||||||
|
// in the parent, it works.
|
||||||
|
if ( node.length === offset + 1 ) {
|
||||||
|
range.setStartAfter( node );
|
||||||
|
} else {
|
||||||
|
range.setStart( node, offset + 1 );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
range.collapse( true );
|
||||||
|
self.setSelection( range );
|
||||||
|
self._updatePath( range, true );
|
||||||
|
self._docWasChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If this is a malformed bit of document or in a table;
|
// If this is a malformed bit of document or in a table;
|
||||||
// just play it safe and insert a <br>.
|
// just play it safe and insert a <br>.
|
||||||
if ( !block || event.shiftKey || /^T[HD]$/.test( block.nodeName ) ) {
|
if ( !block || event.shiftKey || /^T[HD]$/.test( block.nodeName ) ) {
|
||||||
|
@ -506,6 +554,7 @@ keyHandlers[ ctrlKey + 'shift-8' ] = mapKeyTo( 'makeUnorderedList' );
|
||||||
keyHandlers[ ctrlKey + 'shift-9' ] = mapKeyTo( 'makeOrderedList' );
|
keyHandlers[ ctrlKey + 'shift-9' ] = mapKeyTo( 'makeOrderedList' );
|
||||||
keyHandlers[ ctrlKey + '[' ] = mapKeyTo( 'decreaseQuoteLevel' );
|
keyHandlers[ ctrlKey + '[' ] = mapKeyTo( 'decreaseQuoteLevel' );
|
||||||
keyHandlers[ ctrlKey + ']' ] = mapKeyTo( 'increaseQuoteLevel' );
|
keyHandlers[ ctrlKey + ']' ] = mapKeyTo( 'increaseQuoteLevel' );
|
||||||
|
keyHandlers[ ctrlKey + 'd' ] = mapKeyTo( 'toggleCode' );
|
||||||
keyHandlers[ ctrlKey + 'y' ] = mapKeyTo( 'redo' );
|
keyHandlers[ ctrlKey + 'y' ] = mapKeyTo( 'redo' );
|
||||||
keyHandlers[ ctrlKey + 'z' ] = mapKeyTo( 'undo' );
|
keyHandlers[ ctrlKey + 'z' ] = mapKeyTo( 'undo' );
|
||||||
keyHandlers[ ctrlKey + 'shift-z' ] = mapKeyTo( 'redo' );
|
keyHandlers[ ctrlKey + 'shift-z' ] = mapKeyTo( 'redo' );
|
||||||
|
|
Loading…
Reference in a new issue