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;
|
||||
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>
|
||||
</head>
|
||||
<body>
|
||||
|
@ -93,6 +103,9 @@
|
|||
<span id="removeList">Unlist</span>
|
||||
<span id="increaseListLevel">Increase 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="setHTML" class="prompt">Set HTML</span>
|
||||
|
@ -112,7 +125,13 @@
|
|||
ul: {'class': 'UL'},
|
||||
ol: {'class': 'OL'},
|
||||
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() {
|
||||
|
|
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
|
||||
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.
|
||||
* **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:
|
||||
|
||||
|
@ -460,6 +460,24 @@ Decreases by 1 the nesting level of any at-least-partially selected blocks which
|
|||
|
||||
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
|
||||
|
||||
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 = {
|
||||
enter: function ( self, event, range ) {
|
||||
var root = self._root;
|
||||
var block, parent, nodeAfterSplit;
|
||||
var block, parent, node, offset, nodeAfterSplit;
|
||||
|
||||
// We handle this ourselves
|
||||
event.preventDefault();
|
||||
|
@ -1438,6 +1438,54 @@ var keyHandlers = {
|
|||
|
||||
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;
|
||||
// just play it safe and insert a <br>.
|
||||
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 + '[' ] = mapKeyTo( 'decreaseQuoteLevel' );
|
||||
keyHandlers[ ctrlKey + ']' ] = mapKeyTo( 'increaseQuoteLevel' );
|
||||
keyHandlers[ ctrlKey + 'd' ] = mapKeyTo( 'toggleCode' );
|
||||
keyHandlers[ ctrlKey + 'y' ] = mapKeyTo( 'redo' );
|
||||
keyHandlers[ ctrlKey + 'z' ] = mapKeyTo( 'undo' );
|
||||
keyHandlers[ ctrlKey + 'shift-z' ] = mapKeyTo( 'redo' );
|
||||
|
@ -4390,6 +4439,38 @@ var escapeHTMLFragement = function ( text ) {
|
|||
};
|
||||
|
||||
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 config = this._config;
|
||||
var tag = config.blockTag;
|
||||
|
@ -4574,6 +4655,126 @@ proto.setTextDirection = function ( direction ) {
|
|||
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 ) {
|
||||
var 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 ) {
|
||||
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 config = this._config;
|
||||
var tag = config.blockTag;
|
||||
|
@ -2101,6 +2133,126 @@ proto.setTextDirection = function ( direction ) {
|
|||
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 ) {
|
||||
var node, next;
|
||||
for ( node = root.firstChild; node; node = next ) {
|
||||
|
|
|
@ -146,7 +146,7 @@ var afterDelete = function ( self, range ) {
|
|||
var keyHandlers = {
|
||||
enter: function ( self, event, range ) {
|
||||
var root = self._root;
|
||||
var block, parent, nodeAfterSplit;
|
||||
var block, parent, node, offset, nodeAfterSplit;
|
||||
|
||||
// We handle this ourselves
|
||||
event.preventDefault();
|
||||
|
@ -167,6 +167,54 @@ var keyHandlers = {
|
|||
|
||||
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;
|
||||
// just play it safe and insert a <br>.
|
||||
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 + '[' ] = mapKeyTo( 'decreaseQuoteLevel' );
|
||||
keyHandlers[ ctrlKey + ']' ] = mapKeyTo( 'increaseQuoteLevel' );
|
||||
keyHandlers[ ctrlKey + 'd' ] = mapKeyTo( 'toggleCode' );
|
||||
keyHandlers[ ctrlKey + 'y' ] = mapKeyTo( 'redo' );
|
||||
keyHandlers[ ctrlKey + 'z' ] = mapKeyTo( 'undo' );
|
||||
keyHandlers[ ctrlKey + 'shift-z' ] = mapKeyTo( 'redo' );
|
||||
|
|
Loading…
Reference in a new issue