initial hookup editor code with react component

pull/21833/head
Bruno Windels 2019-05-07 16:27:09 +02:00
parent 9f98a6c0e6
commit 76bb56a2bf
4 changed files with 75 additions and 18 deletions

View File

@ -17,12 +17,28 @@ limitations under the License.
.mx_MessageEditor {
border-radius: 4px;
background-color: #f3f8fd;
padding: 10px;
padding: 11px 13px 7px 56px;
.editor {
border-radius: 4px;
border: solid 1px #e9edf1;
background-color: #ffffff;
padding: 10px;
span {
display: inline-block;
padding: 0 5px;
border-radius: 4px;
color: white;
}
span.user-pill {
background: red;
}
span.room-pill {
background: green;
}
}
.buttons {
@ -39,4 +55,12 @@ limitations under the License.
font-weight: 600;
}
}
.model {
background: lightgrey;
padding: 5px;
display: block;
white-space: pre;
font-size: 12px;
}
}

View File

@ -18,6 +18,9 @@ import sdk from '../../../index';
import {_t} from '../../../languageHandler';
import PropTypes from 'prop-types';
import dis from '../../../dispatcher';
import EditorModel from '../../../editor/model';
import {PlainPart} from '../../../editor/parts';
import {getCaretOffset, setCaretPosition} from '../../../editor/caret';
import {MatrixEvent, MatrixClient} from 'matrix-js-sdk';
export default class MessageEditor extends React.Component {
@ -34,8 +37,24 @@ export default class MessageEditor extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {};
const body = this.props.event.getContent().body;
this.model = new EditorModel();
this.model.update(body, undefined, {offset: body.length});
this.state = {
parts: this.model.serializeParts(),
};
this._onCancelClicked = this._onCancelClicked.bind(this);
this._onInput = this._onInput.bind(this);
}
_onInput(event) {
const editor = event.target;
const caretOffset = getCaretOffset(editor);
const caret = this.model.update(editor.textContent, event.inputType, caretOffset);
const parts = this.model.serializeParts();
this.setState({parts}, () => {
setCaretPosition(editor, caret);
});
}
_onCancelClicked() {
@ -43,14 +62,24 @@ export default class MessageEditor extends React.Component {
}
render() {
const parts = this.state.parts.map((p, i) => {
const key = `${i}-${p.type}`;
switch (p.type) {
case "plain": return p.text;
case "room-pill": return (<span key={key} className="room-pill">{p.text}</span>);
case "user-pill": return (<span key={key} className="user-pill">{p.text}</span>);
}
});
const modelOutput = JSON.stringify(this.state.parts, undefined, 2);
const AccessibleButton = sdk.getComponent('elements.AccessibleButton');
return <div className="mx_MessageEditor">
<div className="editor" contentEditable="true">
{this.props.event.getContent().body}
<div className="editor" contentEditable="true" tabIndex="1" suppressContentEditableWarning={true} onInput={this._onInput}>
{parts}
</div>
<div className="buttons">
<AccessibleButton onClick={this._onCancelClicked}>{_t("Cancel")}</AccessibleButton>
</div>
<code className="model">{modelOutput}</code>
</div>;
}
}

View File

@ -14,21 +14,21 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
export function getCaretPosition(editor) {
export function getCaretOffset(editor) {
const sel = document.getSelection();
const atNodeEnd = sel.focusOffset === sel.focusNode.textContent.length;
let position = sel.focusOffset;
let offset = sel.focusOffset;
let node = sel.focusNode;
// when deleting the last character of a node,
// the caret gets reported as being after the focusOffset-th node,
// with the focusNode being the editor
if (node === editor) {
let position = 0;
let offset = 0;
for (let i = 0; i < sel.focusOffset; ++i) {
position += editor.childNodes[i].textContent.length;
offset += editor.childNodes[i].textContent.length;
}
return {position, atNodeEnd: false};
return {offset, atNodeEnd: false};
}
// first make sure we're at the level of a direct child of editor
@ -36,7 +36,7 @@ export function getCaretPosition(editor) {
// include all preceding siblings of the non-direct editor children
while (node.previousSibling) {
node = node.previousSibling;
position += node.textContent.length;
offset += node.textContent.length;
}
// then move up
// I guess technically there could be preceding text nodes in the parents here as well,
@ -48,13 +48,13 @@ export function getCaretPosition(editor) {
// now include the text length of all preceding direct editor children
while (node.previousSibling) {
node = node.previousSibling;
position += node.textContent.length;
offset += node.textContent.length;
}
{
const {focusOffset, focusNode} = sel;
console.log("selection", {focusOffset, focusNode, position, atNodeEnd});
}
return {position, atNodeEnd};
// {
// const {focusOffset, focusNode} = sel;
// console.log("selection", {focusOffset, focusNode, position, atNodeEnd});
// }
return {offset, atNodeEnd};
}
export function setCaretPosition(editor, caretPosition) {

View File

@ -40,18 +40,22 @@ export default class EditorModel {
return this._parts;
}
serializeParts() {
return this._parts.map(({type, text}) => {return {type, text};});
}
_diff(newValue, inputType, caret) {
if (inputType === "deleteByDrag") {
return diffDeletion(this._previousValue, newValue);
} else {
return diffAtCaret(this._previousValue, newValue, caret.position);
return diffAtCaret(this._previousValue, newValue, caret.offset);
}
}
update(newValue, inputType, caret) {
const diff = this._diff(newValue, inputType, caret);
const position = this._positionForOffset(diff.at, caret.atNodeEnd);
console.log("update at", {position, diff});
console.log("update at", {position, diff, newValue, prevValue: this._previousValue});
if (diff.removed) {
this._removeText(position, diff.removed.length);
}