mirror of
https://github.com/wassname/talk.git
synced 2026-07-02 09:22:01 +08:00
IE Fixes
This commit is contained in:
@@ -14,7 +14,7 @@ import {
|
||||
selectEndOfNode,
|
||||
isSelectionInside,
|
||||
} from './lib/dom';
|
||||
import API from './lib/api';
|
||||
import createAPI from './lib/api';
|
||||
import Undo from './lib/undo';
|
||||
import bowser from 'bowser';
|
||||
import throttle from 'lodash/throttle';
|
||||
@@ -24,7 +24,15 @@ class RTE extends React.Component {
|
||||
ref = null;
|
||||
|
||||
// Our "plugins" api.
|
||||
api = null;
|
||||
api = createAPI(
|
||||
() => this.ref.htmlEl,
|
||||
() => this.handleChange(),
|
||||
() => this.undo.canUndo(),
|
||||
() => this.undo.canRedo(),
|
||||
() => this.handleUndo(),
|
||||
() => this.handleRedo(),
|
||||
() => this.focused
|
||||
);
|
||||
|
||||
// Instance of undo stack.
|
||||
undo = new Undo();
|
||||
@@ -36,6 +44,7 @@ class RTE extends React.Component {
|
||||
focus = () => this.ref.htmlEl.focus();
|
||||
|
||||
unmounted = false;
|
||||
focused = false;
|
||||
|
||||
// Should be called on every change to feed
|
||||
// our Undo stack. We save the innerHTML and if available
|
||||
@@ -65,19 +74,7 @@ class RTE extends React.Component {
|
||||
}
|
||||
|
||||
// Ref to react-contenteditable.
|
||||
handleRef = ref => (
|
||||
(this.ref = ref),
|
||||
(this.api =
|
||||
ref &&
|
||||
new API(
|
||||
this.ref.htmlEl,
|
||||
this.handleChange,
|
||||
() => this.undo.canUndo(),
|
||||
() => this.undo.canRedo(),
|
||||
this.handleUndo,
|
||||
this.handleRedo
|
||||
))
|
||||
);
|
||||
handleRef = ref => (this.ref = ref);
|
||||
|
||||
forEachButton(callback) {
|
||||
Object.keys(this.buttonsRef).map(k => callback(this.buttonsRef[k]));
|
||||
@@ -101,7 +98,6 @@ class RTE extends React.Component {
|
||||
}
|
||||
|
||||
handleChange = () => {
|
||||
this.handleSelectionChange();
|
||||
this.props.onChange({
|
||||
text: this.ref.htmlEl.innerText,
|
||||
html: this.ref.htmlEl.innerHTML,
|
||||
@@ -148,6 +144,16 @@ class RTE extends React.Component {
|
||||
}
|
||||
};
|
||||
|
||||
handleFocus = () => {
|
||||
this.focused = true;
|
||||
};
|
||||
|
||||
handleBlur = () => {
|
||||
this.focused = false;
|
||||
// Sometimes the onselect event doesn't fire on blur.
|
||||
this.handleSelectionChange();
|
||||
};
|
||||
|
||||
// We intercept pasting, so that we
|
||||
// force text/plain content.
|
||||
handlePaste = e => {
|
||||
@@ -165,18 +171,14 @@ class RTE extends React.Component {
|
||||
return false;
|
||||
};
|
||||
|
||||
handleMouseUp = () => {
|
||||
setTimeout(() => !this.unmounted && this.handleSelectionChange());
|
||||
};
|
||||
|
||||
handleKeyDown = e => {
|
||||
// IE has issues not firing the onChange event.
|
||||
if (bowser.msie) {
|
||||
setTimeout(() => !this.unmounted && this.handleChange);
|
||||
setTimeout(() => !this.unmounted && this.handleChange());
|
||||
}
|
||||
|
||||
// Undo Redo
|
||||
if (e.key === 'z' && e.metaKey) {
|
||||
// Undo Redo 'Z'
|
||||
if (e.key === 'z' && (e.metaKey || e.ctrlKey)) {
|
||||
if (e.shiftKey) {
|
||||
this.handleRedo();
|
||||
} else {
|
||||
@@ -202,14 +204,6 @@ class RTE extends React.Component {
|
||||
}
|
||||
};
|
||||
|
||||
handleKeyUp = () => {
|
||||
// IE has issues not firing the onChange event.
|
||||
if (bowser.msie) {
|
||||
setTimeout(() => !this.unmounted && this.handleChange);
|
||||
}
|
||||
this.handleSelectionChange();
|
||||
};
|
||||
|
||||
restoreCheckpoint(html, node, range) {
|
||||
if (node && range) {
|
||||
// We need to clone it, otherwise we'll mutate
|
||||
@@ -312,6 +306,9 @@ class RTE extends React.Component {
|
||||
onKeyUp={this.handleKeyUp}
|
||||
onPaste={this.handlePaste}
|
||||
onCut={this.handleCut}
|
||||
onFocus={this.handleFocus}
|
||||
onBlur={this.handleBlur}
|
||||
onSelect={this.handleSelectionChange}
|
||||
className={classNames.content}
|
||||
ref={this.handleRef}
|
||||
html={value}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from '../lib/dom';
|
||||
|
||||
function execCommand() {
|
||||
const bq = findIntersecting('BLOCKQUOTE');
|
||||
const bq = findIntersecting('BLOCKQUOTE', this.container);
|
||||
if (bq) {
|
||||
outdentNode(bq, true);
|
||||
} else {
|
||||
@@ -34,16 +34,16 @@ function execCommand() {
|
||||
}
|
||||
|
||||
function isActive() {
|
||||
return !!findIntersecting('BLOCKQUOTE');
|
||||
return this.focused && !!findIntersecting('BLOCKQUOTE', this.container);
|
||||
}
|
||||
|
||||
const onEnter = node => {
|
||||
function onEnter(node) {
|
||||
if (node.tagName !== 'BLOCKQUOTE') {
|
||||
return;
|
||||
}
|
||||
insertNewLineAfterNode(node, true);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
const Blockquote = createToggle(execCommand, { onEnter, isActive });
|
||||
|
||||
|
||||
@@ -1,9 +1,32 @@
|
||||
import createToggle from '../factories/createToggle';
|
||||
import { findIntersecting } from '../lib/dom';
|
||||
|
||||
const execCommand = () => document.execCommand('bold');
|
||||
const isActive = () => document.queryCommandState('bold');
|
||||
const boldTags = ['B', 'STRONG'];
|
||||
|
||||
const Bold = createToggle(execCommand, { isActive });
|
||||
function execCommand() {
|
||||
return document.execCommand('bold');
|
||||
}
|
||||
|
||||
function isActive() {
|
||||
return this.focused && document.queryCommandState('bold');
|
||||
}
|
||||
function isDisabled() {
|
||||
if (!this.focused) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disable whenever the bold styling came from a different
|
||||
// tag than those we control.
|
||||
return !!findIntersecting(
|
||||
n =>
|
||||
n.nodeName !== '#text' &&
|
||||
window.getComputedStyle(n).getPropertyValue('font-weight') === 'bold' &&
|
||||
!boldTags.includes(n.tagName),
|
||||
this.container
|
||||
);
|
||||
}
|
||||
|
||||
const Bold = createToggle(execCommand, { isActive, isDisabled });
|
||||
|
||||
Bold.defaultProps = {
|
||||
children: 'Bold',
|
||||
|
||||
@@ -1,9 +1,30 @@
|
||||
import createToggle from '../factories/createToggle';
|
||||
import { findIntersecting } from '../lib/dom';
|
||||
|
||||
const execCommand = () => document.execCommand('italic');
|
||||
const isActive = () => document.queryCommandState('italic');
|
||||
const italicTags = ['I', 'EM'];
|
||||
|
||||
const Italic = createToggle(execCommand, { isActive });
|
||||
function execCommand() {
|
||||
return document.execCommand('italic');
|
||||
}
|
||||
function isActive() {
|
||||
return this.focused && document.queryCommandState('italic');
|
||||
}
|
||||
function isDisabled() {
|
||||
if (!this.focused) {
|
||||
return false;
|
||||
}
|
||||
// Disable whenever the italic styling came from a different
|
||||
// tag than those we control.
|
||||
return !!findIntersecting(
|
||||
n =>
|
||||
n.nodeName !== '#text' &&
|
||||
window.getComputedStyle(n).getPropertyValue('font-style') === 'italic' &&
|
||||
!italicTags.includes(n.tagName),
|
||||
this.container
|
||||
);
|
||||
}
|
||||
|
||||
const Italic = createToggle(execCommand, { isActive, isDisabled });
|
||||
|
||||
Italic.defaultProps = {
|
||||
children: 'Italic',
|
||||
|
||||
@@ -39,5 +39,5 @@
|
||||
.button:disabled{
|
||||
color: #bbb;
|
||||
cursor: default;
|
||||
background-color: inherit;
|
||||
background: none;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,37 @@
|
||||
import { isSelectionInside } from './dom';
|
||||
|
||||
/**
|
||||
* An instance of API is passed to all the buttons to
|
||||
* interact with RTE, which servers as a clean abstraction.
|
||||
*/
|
||||
export default class API {
|
||||
constructor(container, onChange, canUndo, canRedo, undo, redo) {
|
||||
this.container = container;
|
||||
this.broadcastChange = onChange;
|
||||
this.canUndo = canUndo;
|
||||
this.canRedo = canRedo;
|
||||
this.undo = undo;
|
||||
this.redo = redo;
|
||||
}
|
||||
focus() {
|
||||
this.container.focus();
|
||||
}
|
||||
function createAPI(
|
||||
getContainer,
|
||||
broadcastChange,
|
||||
canUndo,
|
||||
canRedo,
|
||||
undo,
|
||||
redo,
|
||||
getFocused
|
||||
) {
|
||||
return {
|
||||
broadcastChange,
|
||||
canUndo,
|
||||
canRedo,
|
||||
undo,
|
||||
redo,
|
||||
get focused() {
|
||||
return getFocused();
|
||||
},
|
||||
get container() {
|
||||
return getContainer();
|
||||
},
|
||||
focus() {
|
||||
this.container.focus();
|
||||
},
|
||||
isSelectionInside() {
|
||||
return isSelectionInside(getContainer());
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export default createAPI;
|
||||
|
||||
@@ -7,6 +7,9 @@ export function findAncestor(node, tagOrCallback, limitTo) {
|
||||
typeof tagOrCallback === 'function'
|
||||
? tagOrCallback
|
||||
: n => n.tagName === tagOrCallback;
|
||||
if (node.isSameNode(limitTo)) {
|
||||
return null;
|
||||
}
|
||||
while (node.parentNode) {
|
||||
node = node.parentNode;
|
||||
if (callback(node)) {
|
||||
@@ -216,6 +219,9 @@ export function getSelectionRange() {
|
||||
|
||||
// Adds a 'br' marker at the end of the node.
|
||||
function ensureEndMarker(node) {
|
||||
if (!isBlockElement(node)) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
!node.lastChild ||
|
||||
node.lastChild.tagName !== 'BR' ||
|
||||
@@ -465,9 +471,10 @@ export function outdentNode(node, changeSelection) {
|
||||
|
||||
function cloneNodeAndRangeHelper(node, range, rangeCloned) {
|
||||
const nodeCloned = node.cloneNode(false);
|
||||
node.childNodes.forEach(n =>
|
||||
nodeCloned.appendChild(cloneNodeAndRangeHelper(n, range, rangeCloned))
|
||||
);
|
||||
for (let i = 0; i < node.childNodes.length; i++) {
|
||||
const n = node.childNodes[i];
|
||||
nodeCloned.appendChild(cloneNodeAndRangeHelper(n, range, rangeCloned));
|
||||
}
|
||||
if (range.startContainer === node) {
|
||||
rangeCloned.setStart(nodeCloned, range.startOffset);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user