diff --git a/res/css/views/rooms/_MessageComposerFormatBar.scss b/res/css/views/rooms/_MessageComposerFormatBar.scss index b02afac079a..ce7aed2deea 100644 --- a/res/css/views/rooms/_MessageComposerFormatBar.scss +++ b/res/css/views/rooms/_MessageComposerFormatBar.scss @@ -16,7 +16,7 @@ limitations under the License. .mx_MessageComposerFormatBar { display: none; - width: calc(32px * 5); + width: calc(32px * 6); height: 32px; position: absolute; cursor: pointer; @@ -87,6 +87,11 @@ limitations under the License. .mx_MessageComposerFormatBar_buttonIconCode::after { mask-image: url('$(res)/img/element-icons/room/format-bar/code.svg'); } + + .mx_MessageComposerFormatBar_buttonIconInsertLink::after { + mask-image: url('$(res)/img/element-icons/link.svg'); + mask-size: 18px; + } } .mx_MessageComposerFormatBar_buttonTooltip { diff --git a/src/components/views/rooms/BasicMessageComposer.tsx b/src/components/views/rooms/BasicMessageComposer.tsx index 4d13fab190b..7f4f3c8ddf8 100644 --- a/src/components/views/rooms/BasicMessageComposer.tsx +++ b/src/components/views/rooms/BasicMessageComposer.tsx @@ -28,6 +28,7 @@ import { formatRangeAsCode, toggleInlineFormat, replaceRangeAndMoveCaret, + formatRangeAsLink, } from '../../../editor/operations'; import { getCaretOffsetAndText, getRangeForSelection } from '../../../editor/dom'; import Autocomplete, { generateCompletionDomId } from '../rooms/Autocomplete'; @@ -706,6 +707,9 @@ export default class BasicMessageEditor extends React.Component case Formatting.Quote: formatRangeAsQuote(range); break; + case Formatting.InsertLink: + formatRangeAsLink(range); + break; } }; diff --git a/src/components/views/rooms/MessageComposerFormatBar.tsx b/src/components/views/rooms/MessageComposerFormatBar.tsx index 7839b89c79a..37164502398 100644 --- a/src/components/views/rooms/MessageComposerFormatBar.tsx +++ b/src/components/views/rooms/MessageComposerFormatBar.tsx @@ -27,6 +27,7 @@ export enum Formatting { Strikethrough = "strikethrough", Code = "code", Quote = "quote", + InsertLink = "insert_link", } interface IProps { @@ -57,6 +58,7 @@ export default class MessageComposerFormatBar extends React.PureComponent this.props.onAction(Formatting.Strikethrough)} icon="Strikethrough" visible={this.state.visible} /> this.props.onAction(Formatting.Code)} icon="Code" visible={this.state.visible} /> this.props.onAction(Formatting.Quote)} icon="Quote" shortcut={this.props.shortcuts.quote} visible={this.state.visible} /> + this.props.onAction(Formatting.InsertLink)} icon="InsertLink" visible={this.state.visible} /> ); } diff --git a/src/editor/operations.ts b/src/editor/operations.ts index 2ff09ccce69..85c0b783aa1 100644 --- a/src/editor/operations.ts +++ b/src/editor/operations.ts @@ -32,13 +32,13 @@ export function replaceRangeAndExpandSelection(range: Range, newParts: Part[]): }); } -export function replaceRangeAndMoveCaret(range: Range, newParts: Part[]): void { +export function replaceRangeAndMoveCaret(range: Range, newParts: Part[], offset = 0): void { const { model } = range; model.transform(() => { const oldLen = range.length; const addedLen = range.replace(newParts); const firstOffset = range.start.asOffset(model); - const lastOffset = firstOffset.add(oldLen + addedLen); + const lastOffset = firstOffset.add(oldLen + addedLen + offset); return lastOffset.asPosition(model); }); } @@ -103,6 +103,15 @@ export function formatRangeAsCode(range: Range): void { replaceRangeAndExpandSelection(range, parts); } +export function formatRangeAsLink(range: Range) { + const { model, parts } = range; + const { partCreator } = model; + parts.unshift(partCreator.plain("[")); + parts.push(partCreator.plain("]()")); + // We set offset to -1 here so that the caret lands between the brackets + replaceRangeAndMoveCaret(range, parts, -1); +} + // parts helper methods const isBlank = part => !part.text || !/\S/.test(part.text); const isNL = part => part.type === Type.Newline; diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 68f4fca1835..11d3e44915a 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1606,6 +1606,7 @@ "Strikethrough": "Strikethrough", "Code block": "Code block", "Quote": "Quote", + "Insert link": "Insert link", "Only the two of you are in this conversation, unless either of you invites anyone to join.": "Only the two of you are in this conversation, unless either of you invites anyone to join.", "This is the beginning of your direct message history with .": "This is the beginning of your direct message history with .", "Topic: %(topic)s (edit)": "Topic: %(topic)s (edit)",