mirror of
https://github.com/fastmail/Squire.git
synced 2025-01-05 06:10:07 -05:00
parent
b69a1635de
commit
249ea93c13
4 changed files with 66 additions and 132 deletions
|
@ -41,6 +41,12 @@ Advanced usage
|
||||||
|
|
||||||
If you load the library into a top-level document (rather than an iframe), it will not turn the page into an editable document, but will instead add a function named `Squire` to the global scope. Call `new Squire( document )`, with the `document` from an iframe to instantiate multiple rich text areas on the same page efficiently.
|
If you load the library into a top-level document (rather than an iframe), it will not turn the page into an editable document, but will instead add a function named `Squire` to the global scope. Call `new Squire( document )`, with the `document` from an iframe to instantiate multiple rich text areas on the same page efficiently.
|
||||||
|
|
||||||
|
### Setting the default block style
|
||||||
|
|
||||||
|
By default, the editor will use a `<div>` for blank lines, as most users have been conditioned by Microsoft Word to expect <kbd>Enter</kbd> to act like pressing <kbd>return</kbd> on a typewriter. If you would like to use `<p>` tags (or anything else) for the default block type instead, then after calling `var editor = new Squire( document )` (or getting your reference to the ready-made `editor` instance if using the simple setup), set `editor.defaultBlockTag = 'P';`.
|
||||||
|
|
||||||
|
You can also set an object of attributes to apply to each default block node by setting the *defaultBlockProperties* property, e.g. `editor.defaultBlockProperties = { style: 'font-size: 16px;' }`.
|
||||||
|
|
||||||
License
|
License
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
|
@ -1131,7 +1131,8 @@ function Squire ( doc ) {
|
||||||
this.addEventListener( 'keyup', this._keyUpDetectChange );
|
this.addEventListener( 'keyup', this._keyUpDetectChange );
|
||||||
}
|
}
|
||||||
|
|
||||||
this.defaultBlockProperties = undefined;
|
this.defaultBlockTag = 'DIV';
|
||||||
|
this.defaultBlockProperties = null;
|
||||||
|
|
||||||
// IE sometimes fires the beforepaste event twice; make sure it is not run
|
// IE sometimes fires the beforepaste event twice; make sure it is not run
|
||||||
// again before our after paste function is called.
|
// again before our after paste function is called.
|
||||||
|
@ -1191,7 +1192,8 @@ proto.createElement = function ( tag, props, children ) {
|
||||||
|
|
||||||
proto.createDefaultBlock = function ( children ) {
|
proto.createDefaultBlock = function ( children ) {
|
||||||
return fixCursor(
|
return fixCursor(
|
||||||
this.createElement( 'DIV', this.defaultBlockProperties, children )
|
this.createElement(
|
||||||
|
this.defaultBlockTag, this.defaultBlockProperties, children )
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1902,29 +1904,29 @@ proto.changeFormat = function ( add, remove, range, partial ) {
|
||||||
// --- Block formatting ---
|
// --- Block formatting ---
|
||||||
|
|
||||||
var tagAfterSplit = {
|
var tagAfterSplit = {
|
||||||
DIV: 'DIV',
|
|
||||||
PRE: 'DIV',
|
|
||||||
H1: 'DIV',
|
|
||||||
H2: 'DIV',
|
|
||||||
H3: 'DIV',
|
|
||||||
H4: 'DIV',
|
|
||||||
H5: 'DIV',
|
|
||||||
H6: 'DIV',
|
|
||||||
P: 'DIV',
|
|
||||||
DT: 'DD',
|
DT: 'DD',
|
||||||
DD: 'DT',
|
DD: 'DT',
|
||||||
LI: 'LI'
|
LI: 'LI'
|
||||||
};
|
};
|
||||||
|
|
||||||
var splitBlock = function ( block, node, offset ) {
|
var splitBlock = function ( self, block, node, offset ) {
|
||||||
var splitTag = tagAfterSplit[ block.nodeName ],
|
var splitTag = tagAfterSplit[ block.nodeName ],
|
||||||
|
splitProperties = null,
|
||||||
nodeAfterSplit = split( node, offset, block.parentNode );
|
nodeAfterSplit = split( node, offset, block.parentNode );
|
||||||
|
|
||||||
|
if ( !splitTag ) {
|
||||||
|
splitTag = self.defaultBlockTag;
|
||||||
|
splitProperties = self.defaultBlockProperties;
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure the new node is the correct type.
|
// Make sure the new node is the correct type.
|
||||||
if ( nodeAfterSplit.nodeName !== splitTag ) {
|
if ( !hasTagAttributes( nodeAfterSplit, splitTag, splitProperties ) ) {
|
||||||
block = createElement( nodeAfterSplit.ownerDocument, splitTag );
|
block = createElement( nodeAfterSplit.ownerDocument,
|
||||||
|
splitTag, splitProperties );
|
||||||
|
if ( nodeAfterSplit.dir ) {
|
||||||
block.className = nodeAfterSplit.dir === 'rtl' ? 'dir-rtl' : '';
|
block.className = nodeAfterSplit.dir === 'rtl' ? 'dir-rtl' : '';
|
||||||
block.dir = nodeAfterSplit.dir;
|
block.dir = nodeAfterSplit.dir;
|
||||||
|
}
|
||||||
replaceWith( nodeAfterSplit, block );
|
replaceWith( nodeAfterSplit, block );
|
||||||
block.appendChild( empty( nodeAfterSplit ) );
|
block.appendChild( empty( nodeAfterSplit ) );
|
||||||
nodeAfterSplit = block;
|
nodeAfterSplit = block;
|
||||||
|
@ -2533,8 +2535,8 @@ var cleanupBRs = function ( root ) {
|
||||||
|
|
||||||
proto._ensureBottomLine = function () {
|
proto._ensureBottomLine = function () {
|
||||||
var body = this._body,
|
var body = this._body,
|
||||||
div = body.lastChild;
|
last = body.lastChild;
|
||||||
if ( !div || div.nodeName !== 'DIV' || !isBlock( div ) ) {
|
if ( !last || last.nodeName !== this.defaultBlockTag || !isBlock( last ) ) {
|
||||||
body.appendChild( this.createDefaultBlock() );
|
body.appendChild( this.createDefaultBlock() );
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -2764,7 +2766,7 @@ var afterDelete = function ( self, range ) {
|
||||||
|
|
||||||
var keyHandlers = {
|
var keyHandlers = {
|
||||||
enter: function ( self, event, range ) {
|
enter: function ( self, event, range ) {
|
||||||
var block, parent, tag, splitTag, nodeAfterSplit;
|
var block, parent, nodeAfterSplit;
|
||||||
|
|
||||||
// We handle this ourselves
|
// We handle this ourselves
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -2784,15 +2786,10 @@ var keyHandlers = {
|
||||||
}
|
}
|
||||||
|
|
||||||
block = getStartBlockOfRange( range );
|
block = getStartBlockOfRange( range );
|
||||||
if ( block && ( parent = getNearest( block, 'LI' ) ) ) {
|
|
||||||
block = parent;
|
|
||||||
}
|
|
||||||
tag = block ? block.nodeName : 'DIV';
|
|
||||||
splitTag = tagAfterSplit[ tag ];
|
|
||||||
|
|
||||||
// If this is a malformed bit of document, just play it safe
|
// If this is a malformed bit of document or in a table;
|
||||||
// and insert a <br>.
|
// just play it safe and insert a <br>.
|
||||||
if ( !block ) {
|
if ( !block || /^T[HD]$/.test( block.nodeName ) ) {
|
||||||
insertNodeInRange( range, self.createElement( 'BR' ) );
|
insertNodeInRange( range, self.createElement( 'BR' ) );
|
||||||
range.collapse( false );
|
range.collapse( false );
|
||||||
self.setSelection( range );
|
self.setSelection( range );
|
||||||
|
@ -2800,43 +2797,9 @@ var keyHandlers = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to wrap the contents in divs.
|
// If in a list, we'll split the LI instead.
|
||||||
var splitNode = range.startContainer,
|
if ( parent = getNearest( block, 'LI' ) ) {
|
||||||
splitOffset = range.startOffset,
|
block = parent;
|
||||||
replacement;
|
|
||||||
if ( !splitTag ) {
|
|
||||||
// If the selection point is inside the block, we're going to
|
|
||||||
// rewrite it so our saved reference points won't be valid.
|
|
||||||
// Pick a node at a deeper point in the tree to avoid this.
|
|
||||||
if ( splitNode === block ) {
|
|
||||||
splitNode = splitOffset ?
|
|
||||||
splitNode.childNodes[ splitOffset - 1 ] : null;
|
|
||||||
splitOffset = 0;
|
|
||||||
if ( splitNode ) {
|
|
||||||
if ( splitNode.nodeName === 'BR' ) {
|
|
||||||
splitNode = splitNode.nextSibling;
|
|
||||||
} else {
|
|
||||||
splitOffset = getLength( splitNode );
|
|
||||||
}
|
|
||||||
if ( !splitNode || splitNode.nodeName === 'BR' ) {
|
|
||||||
replacement = fixCursor( self.createElement( 'DIV' ) );
|
|
||||||
if ( splitNode ) {
|
|
||||||
block.replaceChild( replacement, splitNode );
|
|
||||||
} else {
|
|
||||||
block.appendChild( replacement );
|
|
||||||
}
|
|
||||||
splitNode = replacement;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fixContainer( block );
|
|
||||||
splitTag = 'DIV';
|
|
||||||
if ( !splitNode ) {
|
|
||||||
splitNode = block.firstChild;
|
|
||||||
}
|
|
||||||
range.setStart( splitNode, splitOffset );
|
|
||||||
range.setEnd( splitNode, splitOffset );
|
|
||||||
block = getStartBlockOfRange( range );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !block.textContent ) {
|
if ( !block.textContent ) {
|
||||||
|
@ -2851,7 +2814,8 @@ var keyHandlers = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, split at cursor point.
|
// Otherwise, split at cursor point.
|
||||||
nodeAfterSplit = splitBlock( block, splitNode, splitOffset );
|
nodeAfterSplit = splitBlock( self, block,
|
||||||
|
range.startContainer, range.startOffset );
|
||||||
|
|
||||||
// Clean up any empty inlines if we hit enter at the beginning of the
|
// Clean up any empty inlines if we hit enter at the beginning of the
|
||||||
// block
|
// block
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -53,7 +53,8 @@ function Squire ( doc ) {
|
||||||
this.addEventListener( 'keyup', this._keyUpDetectChange );
|
this.addEventListener( 'keyup', this._keyUpDetectChange );
|
||||||
}
|
}
|
||||||
|
|
||||||
this.defaultBlockProperties = undefined;
|
this.defaultBlockTag = 'DIV';
|
||||||
|
this.defaultBlockProperties = null;
|
||||||
|
|
||||||
// IE sometimes fires the beforepaste event twice; make sure it is not run
|
// IE sometimes fires the beforepaste event twice; make sure it is not run
|
||||||
// again before our after paste function is called.
|
// again before our after paste function is called.
|
||||||
|
@ -113,7 +114,8 @@ proto.createElement = function ( tag, props, children ) {
|
||||||
|
|
||||||
proto.createDefaultBlock = function ( children ) {
|
proto.createDefaultBlock = function ( children ) {
|
||||||
return fixCursor(
|
return fixCursor(
|
||||||
this.createElement( 'DIV', this.defaultBlockProperties, children )
|
this.createElement(
|
||||||
|
this.defaultBlockTag, this.defaultBlockProperties, children )
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -824,29 +826,29 @@ proto.changeFormat = function ( add, remove, range, partial ) {
|
||||||
// --- Block formatting ---
|
// --- Block formatting ---
|
||||||
|
|
||||||
var tagAfterSplit = {
|
var tagAfterSplit = {
|
||||||
DIV: 'DIV',
|
|
||||||
PRE: 'DIV',
|
|
||||||
H1: 'DIV',
|
|
||||||
H2: 'DIV',
|
|
||||||
H3: 'DIV',
|
|
||||||
H4: 'DIV',
|
|
||||||
H5: 'DIV',
|
|
||||||
H6: 'DIV',
|
|
||||||
P: 'DIV',
|
|
||||||
DT: 'DD',
|
DT: 'DD',
|
||||||
DD: 'DT',
|
DD: 'DT',
|
||||||
LI: 'LI'
|
LI: 'LI'
|
||||||
};
|
};
|
||||||
|
|
||||||
var splitBlock = function ( block, node, offset ) {
|
var splitBlock = function ( self, block, node, offset ) {
|
||||||
var splitTag = tagAfterSplit[ block.nodeName ],
|
var splitTag = tagAfterSplit[ block.nodeName ],
|
||||||
|
splitProperties = null,
|
||||||
nodeAfterSplit = split( node, offset, block.parentNode );
|
nodeAfterSplit = split( node, offset, block.parentNode );
|
||||||
|
|
||||||
|
if ( !splitTag ) {
|
||||||
|
splitTag = self.defaultBlockTag;
|
||||||
|
splitProperties = self.defaultBlockProperties;
|
||||||
|
}
|
||||||
|
|
||||||
// Make sure the new node is the correct type.
|
// Make sure the new node is the correct type.
|
||||||
if ( nodeAfterSplit.nodeName !== splitTag ) {
|
if ( !hasTagAttributes( nodeAfterSplit, splitTag, splitProperties ) ) {
|
||||||
block = createElement( nodeAfterSplit.ownerDocument, splitTag );
|
block = createElement( nodeAfterSplit.ownerDocument,
|
||||||
|
splitTag, splitProperties );
|
||||||
|
if ( nodeAfterSplit.dir ) {
|
||||||
block.className = nodeAfterSplit.dir === 'rtl' ? 'dir-rtl' : '';
|
block.className = nodeAfterSplit.dir === 'rtl' ? 'dir-rtl' : '';
|
||||||
block.dir = nodeAfterSplit.dir;
|
block.dir = nodeAfterSplit.dir;
|
||||||
|
}
|
||||||
replaceWith( nodeAfterSplit, block );
|
replaceWith( nodeAfterSplit, block );
|
||||||
block.appendChild( empty( nodeAfterSplit ) );
|
block.appendChild( empty( nodeAfterSplit ) );
|
||||||
nodeAfterSplit = block;
|
nodeAfterSplit = block;
|
||||||
|
@ -1455,8 +1457,8 @@ var cleanupBRs = function ( root ) {
|
||||||
|
|
||||||
proto._ensureBottomLine = function () {
|
proto._ensureBottomLine = function () {
|
||||||
var body = this._body,
|
var body = this._body,
|
||||||
div = body.lastChild;
|
last = body.lastChild;
|
||||||
if ( !div || div.nodeName !== 'DIV' || !isBlock( div ) ) {
|
if ( !last || last.nodeName !== this.defaultBlockTag || !isBlock( last ) ) {
|
||||||
body.appendChild( this.createDefaultBlock() );
|
body.appendChild( this.createDefaultBlock() );
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -1686,7 +1688,7 @@ var afterDelete = function ( self, range ) {
|
||||||
|
|
||||||
var keyHandlers = {
|
var keyHandlers = {
|
||||||
enter: function ( self, event, range ) {
|
enter: function ( self, event, range ) {
|
||||||
var block, parent, tag, splitTag, nodeAfterSplit;
|
var block, parent, nodeAfterSplit;
|
||||||
|
|
||||||
// We handle this ourselves
|
// We handle this ourselves
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -1706,15 +1708,10 @@ var keyHandlers = {
|
||||||
}
|
}
|
||||||
|
|
||||||
block = getStartBlockOfRange( range );
|
block = getStartBlockOfRange( range );
|
||||||
if ( block && ( parent = getNearest( block, 'LI' ) ) ) {
|
|
||||||
block = parent;
|
|
||||||
}
|
|
||||||
tag = block ? block.nodeName : 'DIV';
|
|
||||||
splitTag = tagAfterSplit[ tag ];
|
|
||||||
|
|
||||||
// If this is a malformed bit of document, just play it safe
|
// If this is a malformed bit of document or in a table;
|
||||||
// and insert a <br>.
|
// just play it safe and insert a <br>.
|
||||||
if ( !block ) {
|
if ( !block || /^T[HD]$/.test( block.nodeName ) ) {
|
||||||
insertNodeInRange( range, self.createElement( 'BR' ) );
|
insertNodeInRange( range, self.createElement( 'BR' ) );
|
||||||
range.collapse( false );
|
range.collapse( false );
|
||||||
self.setSelection( range );
|
self.setSelection( range );
|
||||||
|
@ -1722,43 +1719,9 @@ var keyHandlers = {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We need to wrap the contents in divs.
|
// If in a list, we'll split the LI instead.
|
||||||
var splitNode = range.startContainer,
|
if ( parent = getNearest( block, 'LI' ) ) {
|
||||||
splitOffset = range.startOffset,
|
block = parent;
|
||||||
replacement;
|
|
||||||
if ( !splitTag ) {
|
|
||||||
// If the selection point is inside the block, we're going to
|
|
||||||
// rewrite it so our saved reference points won't be valid.
|
|
||||||
// Pick a node at a deeper point in the tree to avoid this.
|
|
||||||
if ( splitNode === block ) {
|
|
||||||
splitNode = splitOffset ?
|
|
||||||
splitNode.childNodes[ splitOffset - 1 ] : null;
|
|
||||||
splitOffset = 0;
|
|
||||||
if ( splitNode ) {
|
|
||||||
if ( splitNode.nodeName === 'BR' ) {
|
|
||||||
splitNode = splitNode.nextSibling;
|
|
||||||
} else {
|
|
||||||
splitOffset = getLength( splitNode );
|
|
||||||
}
|
|
||||||
if ( !splitNode || splitNode.nodeName === 'BR' ) {
|
|
||||||
replacement = fixCursor( self.createElement( 'DIV' ) );
|
|
||||||
if ( splitNode ) {
|
|
||||||
block.replaceChild( replacement, splitNode );
|
|
||||||
} else {
|
|
||||||
block.appendChild( replacement );
|
|
||||||
}
|
|
||||||
splitNode = replacement;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fixContainer( block );
|
|
||||||
splitTag = 'DIV';
|
|
||||||
if ( !splitNode ) {
|
|
||||||
splitNode = block.firstChild;
|
|
||||||
}
|
|
||||||
range.setStart( splitNode, splitOffset );
|
|
||||||
range.setEnd( splitNode, splitOffset );
|
|
||||||
block = getStartBlockOfRange( range );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !block.textContent ) {
|
if ( !block.textContent ) {
|
||||||
|
@ -1773,7 +1736,8 @@ var keyHandlers = {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, split at cursor point.
|
// Otherwise, split at cursor point.
|
||||||
nodeAfterSplit = splitBlock( block, splitNode, splitOffset );
|
nodeAfterSplit = splitBlock( self, block,
|
||||||
|
range.startContainer, range.startOffset );
|
||||||
|
|
||||||
// Clean up any empty inlines if we hit enter at the beginning of the
|
// Clean up any empty inlines if we hit enter at the beginning of the
|
||||||
// block
|
// block
|
||||||
|
|
Loading…
Reference in a new issue