2019-09-05 12:10:45 +02:00
|
|
|
/*
|
2024-09-09 14:57:16 +01:00
|
|
|
Copyright 2024 New Vector Ltd.
|
|
|
|
|
Copyright 2019-2021 The Matrix.org Foundation C.I.C.
|
2019-09-05 12:10:45 +02:00
|
|
|
|
2025-01-06 11:18:54 +00:00
|
|
|
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
2024-09-09 14:57:16 +01:00
|
|
|
Please see LICENSE files in the repository root for full details.
|
2019-09-05 12:10:45 +02:00
|
|
|
*/
|
|
|
|
|
|
2021-06-30 13:01:26 +01:00
|
|
|
import React, { createRef } from "react";
|
2019-10-02 14:31:42 +02:00
|
|
|
import classNames from "classnames";
|
2021-06-30 13:01:26 +01:00
|
|
|
|
|
|
|
|
import { _t } from "../../../languageHandler";
|
2024-05-17 12:29:30 +02:00
|
|
|
import { RovingAccessibleButton } from "../../../accessibility/RovingTabIndex";
|
2023-04-12 14:58:38 +01:00
|
|
|
import Toolbar from "../../../accessibility/Toolbar";
|
2019-09-05 12:10:45 +02:00
|
|
|
|
2021-06-30 13:01:26 +01:00
|
|
|
export enum Formatting {
|
|
|
|
|
Bold = "bold",
|
|
|
|
|
Italics = "italics",
|
|
|
|
|
Strikethrough = "strikethrough",
|
|
|
|
|
Code = "code",
|
|
|
|
|
Quote = "quote",
|
2021-10-25 11:56:55 +02:00
|
|
|
InsertLink = "insert_link",
|
2021-06-30 13:01:26 +01:00
|
|
|
}
|
2019-09-05 12:10:45 +02:00
|
|
|
|
2021-06-30 13:01:26 +01:00
|
|
|
interface IProps {
|
|
|
|
|
shortcuts: Partial<Record<Formatting, string>>;
|
|
|
|
|
onAction(action: Formatting): void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface IState {
|
|
|
|
|
visible: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export default class MessageComposerFormatBar extends React.PureComponent<IProps, IState> {
|
|
|
|
|
private readonly formatBarRef = createRef<HTMLDivElement>();
|
2024-11-29 12:56:52 +01:00
|
|
|
/**
|
|
|
|
|
* The height of the format bar in pixels.
|
|
|
|
|
* Height 32px + 2px border
|
|
|
|
|
* @private
|
|
|
|
|
*/
|
|
|
|
|
private readonly BAR_HEIGHT = 34;
|
2021-06-30 13:01:26 +01:00
|
|
|
|
2022-12-16 12:29:59 +00:00
|
|
|
public constructor(props: IProps) {
|
2019-10-02 14:31:42 +02:00
|
|
|
super(props);
|
2021-06-29 13:11:58 +01:00
|
|
|
this.state = { visible: false };
|
2019-10-02 14:31:42 +02:00
|
|
|
}
|
|
|
|
|
|
2023-02-13 17:01:43 +00:00
|
|
|
public render(): React.ReactNode {
|
2019-10-02 14:31:42 +02:00
|
|
|
const classes = classNames("mx_MessageComposerFormatBar", {
|
|
|
|
|
mx_MessageComposerFormatBar_shown: this.state.visible,
|
|
|
|
|
});
|
2021-06-30 13:01:26 +01:00
|
|
|
return (
|
2023-09-27 17:15:22 +01:00
|
|
|
<Toolbar className={classes} ref={this.formatBarRef} aria-label={_t("composer|formatting_toolbar_label")}>
|
2021-06-30 13:01:26 +01:00
|
|
|
<FormatButton
|
2023-08-31 08:35:34 +01:00
|
|
|
label={_t("composer|format_bold")}
|
2021-06-30 13:01:26 +01:00
|
|
|
onClick={() => this.props.onAction(Formatting.Bold)}
|
|
|
|
|
icon="Bold"
|
|
|
|
|
shortcut={this.props.shortcuts.bold}
|
|
|
|
|
visible={this.state.visible}
|
|
|
|
|
/>
|
|
|
|
|
<FormatButton
|
2023-09-27 17:15:22 +01:00
|
|
|
label={_t("composer|format_italics")}
|
2021-06-30 13:01:26 +01:00
|
|
|
onClick={() => this.props.onAction(Formatting.Italics)}
|
|
|
|
|
icon="Italic"
|
|
|
|
|
shortcut={this.props.shortcuts.italics}
|
|
|
|
|
visible={this.state.visible}
|
|
|
|
|
/>
|
|
|
|
|
<FormatButton
|
2023-08-31 08:35:34 +01:00
|
|
|
label={_t("composer|format_strikethrough")}
|
2021-06-30 13:01:26 +01:00
|
|
|
onClick={() => this.props.onAction(Formatting.Strikethrough)}
|
|
|
|
|
icon="Strikethrough"
|
|
|
|
|
visible={this.state.visible}
|
|
|
|
|
/>
|
2022-03-16 10:46:07 +01:00
|
|
|
<FormatButton
|
2023-08-31 08:35:34 +01:00
|
|
|
label={_t("composer|format_code_block")}
|
2022-03-16 10:46:07 +01:00
|
|
|
onClick={() => this.props.onAction(Formatting.Code)}
|
|
|
|
|
icon="Code"
|
|
|
|
|
shortcut={this.props.shortcuts.code}
|
|
|
|
|
visible={this.state.visible}
|
2021-06-30 13:01:26 +01:00
|
|
|
/>
|
|
|
|
|
<FormatButton
|
2023-08-22 20:55:15 +01:00
|
|
|
label={_t("action|quote")}
|
2021-06-30 13:01:26 +01:00
|
|
|
onClick={() => this.props.onAction(Formatting.Quote)}
|
|
|
|
|
icon="Quote"
|
|
|
|
|
shortcut={this.props.shortcuts.quote}
|
2022-03-16 10:46:07 +01:00
|
|
|
visible={this.state.visible}
|
|
|
|
|
/>
|
|
|
|
|
<FormatButton
|
2023-09-27 17:15:22 +01:00
|
|
|
label={_t("composer|format_insert_link")}
|
2022-03-16 10:46:07 +01:00
|
|
|
onClick={() => this.props.onAction(Formatting.InsertLink)}
|
|
|
|
|
icon="InsertLink"
|
|
|
|
|
shortcut={this.props.shortcuts.insert_link}
|
2021-06-30 13:01:26 +01:00
|
|
|
visible={this.state.visible}
|
2022-12-12 12:24:14 +01:00
|
|
|
/>
|
2023-04-12 14:58:38 +01:00
|
|
|
</Toolbar>
|
2019-09-05 12:10:45 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-30 13:01:26 +01:00
|
|
|
public showAt(selectionRect: DOMRect): void {
|
2023-04-20 09:49:10 +01:00
|
|
|
if (!this.formatBarRef.current?.parentElement) return;
|
2021-06-30 13:01:26 +01:00
|
|
|
|
2021-06-29 13:11:58 +01:00
|
|
|
this.setState({ visible: true });
|
2021-06-30 13:01:26 +01:00
|
|
|
const parentRect = this.formatBarRef.current.parentElement.getBoundingClientRect();
|
|
|
|
|
this.formatBarRef.current.style.left = `${selectionRect.left - parentRect.left}px`;
|
2024-11-29 12:56:52 +01:00
|
|
|
const halfBarHeight = this.BAR_HEIGHT / 2; // used to center the bar
|
2022-11-18 14:28:11 +01:00
|
|
|
const offset = halfBarHeight + 2; // makes sure the bar won't cover selected text
|
|
|
|
|
const offsetLimit = halfBarHeight + offset;
|
|
|
|
|
const position = Math.max(selectionRect.top - parentRect.top - offsetLimit, -offsetLimit);
|
|
|
|
|
this.formatBarRef.current.style.top = `${position}px`;
|
2019-09-05 12:10:45 +02:00
|
|
|
}
|
|
|
|
|
|
2021-06-30 13:01:26 +01:00
|
|
|
public hide(): void {
|
2021-06-29 13:11:58 +01:00
|
|
|
this.setState({ visible: false });
|
2019-09-05 12:10:45 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-06-30 13:01:26 +01:00
|
|
|
interface IFormatButtonProps {
|
|
|
|
|
label: string;
|
|
|
|
|
icon: string;
|
|
|
|
|
shortcut?: string;
|
|
|
|
|
visible?: boolean;
|
|
|
|
|
onClick(): void;
|
|
|
|
|
}
|
2019-09-05 12:10:45 +02:00
|
|
|
|
2021-06-30 13:01:26 +01:00
|
|
|
class FormatButton extends React.PureComponent<IFormatButtonProps> {
|
2023-02-13 17:01:43 +00:00
|
|
|
public render(): React.ReactNode {
|
2019-09-05 12:10:45 +02:00
|
|
|
const className = `mx_MessageComposerFormatBar_button mx_MessageComposerFormatBar_buttonIcon${this.props.icon}`;
|
2019-09-05 13:22:42 +02:00
|
|
|
|
2022-02-25 08:20:06 -05:00
|
|
|
// element="button" and type="button" are necessary for the buttons to work on WebKit,
|
|
|
|
|
// otherwise the text is deselected before onClick can ever be called
|
2019-09-05 12:10:45 +02:00
|
|
|
return (
|
2024-05-17 12:29:30 +02:00
|
|
|
<RovingAccessibleButton
|
2022-02-25 08:20:06 -05:00
|
|
|
element="button"
|
|
|
|
|
type="button"
|
2020-07-17 18:43:42 +01:00
|
|
|
onClick={this.props.onClick}
|
2024-04-30 11:35:58 +02:00
|
|
|
aria-label={this.props.label}
|
2020-07-17 18:43:42 +01:00
|
|
|
title={this.props.label}
|
2024-04-30 11:35:58 +02:00
|
|
|
caption={this.props.shortcut}
|
2020-07-17 18:43:42 +01:00
|
|
|
className={className}
|
|
|
|
|
/>
|
2019-09-05 12:10:45 +02:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|