diff --git a/res/css/views/rooms/_BasicMessageComposer.scss b/res/css/views/rooms/_BasicMessageComposer.scss index bce0ecf325..23d61f5218 100644 --- a/res/css/views/rooms/_BasicMessageComposer.scss +++ b/res/css/views/rooms/_BasicMessageComposer.scss @@ -16,6 +16,8 @@ limitations under the License. */ .mx_BasicMessageComposer { + position: relative; + .mx_BasicMessageComposer_inputEmpty > :first-child::before { content: var(--placeholder); opacity: 0.333; @@ -71,4 +73,70 @@ limitations under the License. position: relative; height: 0; } + + .mx_BasicMessageComposer_formatBar { + display: none; + background-color: red; + width: calc(26px * 4); + height: 24px; + position: absolute; + cursor: pointer; + border-radius: 4px; + background: $message-action-bar-bg-color; + + &.mx_BasicMessageComposer_formatBar_shown { + display: block; + } + + > * { + white-space: nowrap; + display: inline-block; + position: relative; + border: 1px solid $message-action-bar-border-color; + margin-left: -1px; + + &:hover { + border-color: $message-action-bar-hover-border-color; + } + } + + .mx_BasicMessageComposer_formatButton { + width: 27px; + height: 24px; + box-sizing: border-box; + } + + .mx_BasicMessageComposer_formatButton::after { + content: ''; + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + mask-repeat: no-repeat; + mask-position: center; + background-color: $message-action-bar-fg-color; + } + + .mx_BasicMessageComposer_formatBold::after { + mask-image: url('$(res)/img/format/bold.svg'); + } + + .mx_BasicMessageComposer_formatItalic::after { + mask-image: url('$(res)/img/format/italics.svg'); + } + + .mx_BasicMessageComposer_formatStrikethrough::after { + mask-image: url('$(res)/img/format/strikethrough.svg'); + } + + .mx_BasicMessageComposer_formatQuote::after { + mask-image: url('$(res)/img/format/quote.svg'); + } + + .mx_BasicMessageComposer_formatCode::after { + mask-image: url('$(res)/img/format/code.svg'); + } + + } } diff --git a/res/img/format/bold.svg b/res/img/format/bold.svg new file mode 100644 index 0000000000..634d735031 --- /dev/null +++ b/res/img/format/bold.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/format/code.svg b/res/img/format/code.svg new file mode 100644 index 0000000000..0a29bcd7bd --- /dev/null +++ b/res/img/format/code.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/res/img/format/italics.svg b/res/img/format/italics.svg new file mode 100644 index 0000000000..841afadffd --- /dev/null +++ b/res/img/format/italics.svg @@ -0,0 +1,3 @@ + + + diff --git a/res/img/format/quote.svg b/res/img/format/quote.svg new file mode 100644 index 0000000000..82d3403314 --- /dev/null +++ b/res/img/format/quote.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/res/img/format/strikethrough.svg b/res/img/format/strikethrough.svg new file mode 100644 index 0000000000..fc02b0aae2 --- /dev/null +++ b/res/img/format/strikethrough.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/components/views/rooms/BasicMessageComposer.js b/src/components/views/rooms/BasicMessageComposer.js index c5661e561c..291c179e46 100644 --- a/src/components/views/rooms/BasicMessageComposer.js +++ b/src/components/views/rooms/BasicMessageComposer.js @@ -74,8 +74,10 @@ export default class BasicMessageEditor extends React.Component { }; this._editorRef = null; this._autocompleteRef = null; + this._formatBarRef = null; this._modifiedFlag = false; this._isIMEComposing = false; + this._hasTextSelected = false; } _replaceEmoticon = (caretPosition, inputType, diff) => { @@ -239,6 +241,36 @@ export default class BasicMessageEditor extends React.Component { _onSelectionChange = () => { this._refreshLastCaretIfNeeded(); + const selection = document.getSelection(); + if (this._hasTextSelected && selection.isCollapsed) { + this._hasTextSelected = false; + if (this._formatBarRef) { + this._formatBarRef.classList.remove("mx_BasicMessageComposer_formatBar_shown"); + } + } else if (!selection.isCollapsed) { + this._hasTextSelected = true; + if (this._formatBarRef) { + this._formatBarRef.classList.add("mx_BasicMessageComposer_formatBar_shown"); + const selectionRect = selection.getRangeAt(0).getBoundingClientRect(); + + let leftOffset = 0; + let node = this._formatBarRef; + while (node.offsetParent) { + node = node.offsetParent; + leftOffset += node.offsetLeft; + } + + let topOffset = 0; + node = this._formatBarRef; + while (node.offsetParent) { + node = node.offsetParent; + topOffset += node.offsetTop; + } + + this._formatBarRef.style.left = `${selectionRect.left - leftOffset}px`; + this._formatBarRef.style.top = `${selectionRect.top - topOffset - 16 - 12}px`; + } + } } _onKeyDown = (event) => { @@ -392,6 +424,25 @@ export default class BasicMessageEditor extends React.Component { return caretPosition; } + _formatBold = () => { + } + + _formatItalic = () => { + + } + + _formatStrikethrough = () => { + + } + + _formatQuote = () => { + + } + + _formatCodeBlock = () => { + + } + render() { let autoComplete; if (this.state.autoComplete) { @@ -413,6 +464,13 @@ export default class BasicMessageEditor extends React.Component { }); return (
{ autoComplete } +
this._formatBarRef = ref}> + + + + + +