Files
ThreadNet-Web/apps/web/src/components/views/rooms/MessageComposerFormatBar.tsx
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

152 lines
5.3 KiB
TypeScript
Raw Normal View History

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
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
*/
import React, { createRef, type JSX } from "react";
import classNames from "classnames";
import {
BoldIcon,
ItalicIcon,
StrikethroughIcon,
InlineCodeIcon,
QuoteIcon,
LinkIcon,
} from "@vector-im/compound-design-tokens/assets/web/icons";
import { _t } from "../../../languageHandler";
import { RovingAccessibleButton } from "../../../accessibility/RovingTabIndex";
import Toolbar from "../../../accessibility/Toolbar";
2019-09-05 12:10:45 +02:00
export enum Formatting {
Bold = "bold",
Italics = "italics",
Strikethrough = "strikethrough",
Code = "code",
Quote = "quote",
InsertLink = "insert_link",
}
2019-09-05 12:10:45 +02: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;
public constructor(props: IProps) {
super(props);
2021-06-29 13:11:58 +01:00
this.state = { visible: false };
}
public render(): React.ReactNode {
const classes = classNames("mx_MessageComposerFormatBar", {
mx_MessageComposerFormatBar_shown: this.state.visible,
});
return (
<Toolbar className={classes} ref={this.formatBarRef} aria-label={_t("composer|formatting_toolbar_label")}>
<FormatButton
label={_t("composer|format_bold")}
onClick={() => this.props.onAction(Formatting.Bold)}
icon={<BoldIcon />}
shortcut={this.props.shortcuts.bold}
visible={this.state.visible}
/>
<FormatButton
label={_t("composer|format_italics")}
onClick={() => this.props.onAction(Formatting.Italics)}
icon={<ItalicIcon />}
shortcut={this.props.shortcuts.italics}
visible={this.state.visible}
/>
<FormatButton
label={_t("composer|format_strikethrough")}
onClick={() => this.props.onAction(Formatting.Strikethrough)}
icon={<StrikethroughIcon />}
visible={this.state.visible}
/>
<FormatButton
label={_t("composer|format_code_block")}
onClick={() => this.props.onAction(Formatting.Code)}
icon={<InlineCodeIcon />}
shortcut={this.props.shortcuts.code}
visible={this.state.visible}
/>
<FormatButton
label={_t("action|quote")}
onClick={() => this.props.onAction(Formatting.Quote)}
icon={<QuoteIcon />}
shortcut={this.props.shortcuts.quote}
visible={this.state.visible}
/>
<FormatButton
label={_t("composer|format_insert_link")}
onClick={() => this.props.onAction(Formatting.InsertLink)}
icon={<LinkIcon />}
shortcut={this.props.shortcuts.insert_link}
visible={this.state.visible}
2022-12-12 12:24:14 +01:00
/>
</Toolbar>
2019-09-05 12:10:45 +02:00
);
}
public showAt(selectionRect: DOMRect): void {
if (!this.formatBarRef.current?.parentElement) return;
2021-06-29 13:11:58 +01:00
this.setState({ visible: true });
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
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
}
public hide(): void {
2021-06-29 13:11:58 +01:00
this.setState({ visible: false });
2019-09-05 12:10:45 +02:00
}
}
interface IFormatButtonProps {
label: string;
icon: JSX.Element;
shortcut?: string;
visible?: boolean;
onClick(): void;
}
2019-09-05 12:10:45 +02:00
class FormatButton extends React.PureComponent<IFormatButtonProps> {
public render(): React.ReactNode {
// 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 (
<RovingAccessibleButton
element="button"
type="button"
2020-07-17 18:43:42 +01:00
onClick={this.props.onClick}
aria-label={this.props.label}
2020-07-17 18:43:42 +01:00
title={this.props.label}
caption={this.props.shortcut}
className="mx_MessageComposerFormatBar_button"
>
{this.props.icon}
</RovingAccessibleButton>
2019-09-05 12:10:45 +02:00
);
}
}