diff --git a/res/css/_components.scss b/res/css/_components.scss
index fb6058df00..213d0d714c 100644
--- a/res/css/_components.scss
+++ b/res/css/_components.scss
@@ -146,6 +146,7 @@
@import "./views/rooms/_MemberInfo.scss";
@import "./views/rooms/_MemberList.scss";
@import "./views/rooms/_MessageComposer.scss";
+@import "./views/rooms/_MessageComposerFormatBar.scss";
@import "./views/rooms/_PinnedEventTile.scss";
@import "./views/rooms/_PinnedEventsPanel.scss";
@import "./views/rooms/_PresenceLabel.scss";
diff --git a/res/css/views/rooms/_BasicMessageComposer.scss b/res/css/views/rooms/_BasicMessageComposer.scss
index e897352d7e..b32a44219a 100644
--- a/res/css/views/rooms/_BasicMessageComposer.scss
+++ b/res/css/views/rooms/_BasicMessageComposer.scss
@@ -73,69 +73,4 @@ limitations under the License.
position: relative;
height: 0;
}
-
- .mx_BasicMessageComposer_formatBar {
- display: none;
- width: calc(26px * 5);
- height: 24px;
- position: absolute;
- cursor: pointer;
- border-radius: 4px;
- background-color: $message-action-bar-bg-color;
- user-select: none;
-
- &.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/css/views/rooms/_MessageComposerFormatBar.scss b/res/css/views/rooms/_MessageComposerFormatBar.scss
new file mode 100644
index 0000000000..f56214224d
--- /dev/null
+++ b/res/css/views/rooms/_MessageComposerFormatBar.scss
@@ -0,0 +1,86 @@
+/*
+Copyright 2019 The Matrix.org Foundation C.I.C.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+.mx_MessageComposerFormatBar {
+ display: none;
+ width: calc(26px * 5);
+ height: 24px;
+ position: absolute;
+ cursor: pointer;
+ border-radius: 4px;
+ background-color: $message-action-bar-bg-color;
+ user-select: none;
+
+ &.mx_MessageComposerFormatBar_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_MessageComposerFormatBar_button {
+ width: 27px;
+ height: 24px;
+ box-sizing: border-box;
+ }
+
+ .mx_MessageComposerFormatBar_button::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_MessageComposerFormatBar_buttonIconBold::after {
+ mask-image: url('$(res)/img/format/bold.svg');
+ }
+
+ .mx_MessageComposerFormatBar_buttonIconItalic::after {
+ mask-image: url('$(res)/img/format/italics.svg');
+ }
+
+ .mx_MessageComposerFormatBar_buttonIconStrikethrough::after {
+ mask-image: url('$(res)/img/format/strikethrough.svg');
+ }
+
+ .mx_MessageComposerFormatBar_buttonIconQuote::after {
+ mask-image: url('$(res)/img/format/quote.svg');
+ }
+
+ .mx_MessageComposerFormatBar_buttonIconCode::after {
+ mask-image: url('$(res)/img/format/code.svg');
+ }
+}
+
+.mx_MessageComposerFormatBar_buttonTooltip {
+ white-space: nowrap;
+ font-size: 12px;
+ font-weight: 600;
+}
diff --git a/src/components/views/rooms/BasicMessageComposer.js b/src/components/views/rooms/BasicMessageComposer.js
index e0468e9969..b37552da2a 100644
--- a/src/components/views/rooms/BasicMessageComposer.js
+++ b/src/components/views/rooms/BasicMessageComposer.js
@@ -35,7 +35,7 @@ import TypingStore from "../../../stores/TypingStore";
import EMOJIBASE from 'emojibase-data/en/compact.json';
import SettingsStore from "../../../settings/SettingsStore";
import EMOTICON_REGEX from 'emojibase-regex/emoticon';
-import { _t } from '../../../languageHandler';
+import sdk from '../../../index';
const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s$');
@@ -251,31 +251,13 @@ export default class BasicMessageEditor extends React.Component {
if (this._hasTextSelected && selection.isCollapsed) {
this._hasTextSelected = false;
if (this._formatBarRef) {
- this._formatBarRef.classList.remove("mx_BasicMessageComposer_formatBar_shown");
+ this._formatBarRef.hide();
}
} 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`;
- // 12 is half the height of the bar (e.g. to center it) and 16 is an offset that felt ok.
- this._formatBarRef.style.top = `${selectionRect.top - topOffset - 16 - 12}px`;
+ this._formatBarRef.showAt(selectionRect);
}
}
}
@@ -431,40 +413,28 @@ export default class BasicMessageEditor extends React.Component {
return caretPosition;
}
- _wrapSelectionAsInline(prefix, suffix = prefix) {
+ _onFormatAction = (action) => {
const range = getRangeForSelection(
this._editorRef,
this.props.model,
document.getSelection());
- formatInline(range, prefix, suffix);
- }
-
- _formatBold = () => {
- this._wrapSelectionAsInline("**");
- }
-
- _formatItalic = () => {
- this._wrapSelectionAsInline("*");
- }
-
- _formatStrikethrough = () => {
- this._wrapSelectionAsInline("", "");
- }
-
- _formatQuote = () => {
- const range = getRangeForSelection(
- this._editorRef,
- this.props.model,
- document.getSelection());
- formatRangeAsQuote(range);
- }
-
- _formatCode = () => {
- const range = getRangeForSelection(
- this._editorRef,
- this.props.model,
- document.getSelection());
- formatRangeAsCode(range);
+ switch (action) {
+ case "bold":
+ formatInline(range, "**");
+ break;
+ case "italics":
+ formatInline(range, "*");
+ break;
+ case "strikethrough":
+ formatInline(range, "", "");
+ break;
+ case "code":
+ formatRangeAsCode(range);
+ break;
+ case "quote":
+ formatRangeAsQuote(range);
+ break;
+ }
}
render() {
@@ -486,15 +456,12 @@ export default class BasicMessageEditor extends React.Component {
const classes = classNames("mx_BasicMessageComposer", {
"mx_BasicMessageComposer_input_error": this.state.showVisualBell,
});
+
+ const MessageComposerFormatBar = sdk.getComponent('rooms.MessageComposerFormatBar');
+
return (