Merge branch 'develop' into 19245-improve-styling-of-search-initialization-errors

This commit is contained in:
Travis Ralston 2022-05-09 19:32:43 -06:00
commit 401e124df6
90 changed files with 562 additions and 398 deletions

View file

@ -49,7 +49,7 @@ interface IState {
// @ts-ignore - TS wants a string key, but we know better
apps: {[id: Container]: IApp[]};
resizingVertical: boolean; // true when changing the height of the apps drawer
resizingHorizontal: boolean; // true when chagning the distribution of the width between widgets
resizingHorizontal: boolean; // true when changing the distribution of the width between widgets
resizing: boolean;
}
@ -259,7 +259,7 @@ export default class AppsDrawer extends React.Component<IProps, IState> {
mx_AppsDrawer_2apps: apps.length === 2,
mx_AppsDrawer_3apps: apps.length === 3,
});
const appConatiners =
const appContainers =
<div className="mx_AppsContainer" ref={this.collectResizer}>
{ apps.map((app, i) => {
if (i < 1) return app;
@ -272,7 +272,7 @@ export default class AppsDrawer extends React.Component<IProps, IState> {
let drawer;
if (widgetIsMaxmised) {
drawer = appConatiners;
drawer = appContainers;
} else {
drawer = <PersistentVResizer
room={this.props.room}
@ -282,7 +282,7 @@ export default class AppsDrawer extends React.Component<IProps, IState> {
handleWrapperClass="mx_AppsContainer_resizerHandleContainer"
className="mx_AppsContainer_resizer"
resizeNotifier={this.props.resizeNotifier}>
{ appConatiners }
{ appContainers }
</PersistentVResizer>;
}

View file

@ -104,7 +104,7 @@ export default class AuxPanel extends React.Component<IProps, IState> {
const severity = ev.getContent().severity || "normal";
const stateKey = ev.getStateKey();
// We want a non-empty title but can accept falsey values (e.g.
// We want a non-empty title but can accept falsy values (e.g.
// zero)
if (title && value !== undefined) {
counters.push({

View file

@ -24,10 +24,11 @@ import { logger } from "matrix-js-sdk/src/logger";
import EditorModel from '../../../editor/model';
import HistoryManager from '../../../editor/history';
import { Caret, setSelection } from '../../../editor/caret';
import { formatRange, replaceRangeAndMoveCaret, toggleInlineFormat } from '../../../editor/operations';
import { formatRange, formatRangeAsLink, replaceRangeAndMoveCaret, toggleInlineFormat }
from '../../../editor/operations';
import { getCaretOffsetAndText, getRangeForSelection } from '../../../editor/dom';
import Autocomplete, { generateCompletionDomId } from '../rooms/Autocomplete';
import { getAutoCompleteCreator, Type } from '../../../editor/parts';
import { getAutoCompleteCreator, Part, Type } from '../../../editor/parts';
import { parseEvent, parsePlainTextMessage } from '../../../editor/deserialize';
import { renderModel } from '../../../editor/render';
import TypingStore from "../../../stores/TypingStore";
@ -45,6 +46,7 @@ import { ICompletion } from "../../../autocomplete/Autocompleter";
import { getKeyBindingsManager } from '../../../KeyBindingsManager';
import { ALTERNATE_KEY_NAME, KeyBindingAction } from '../../../accessibility/KeyboardShortcuts';
import { _t } from "../../../languageHandler";
import { linkify } from '../../../linkify-matrix';
// matches emoticons which follow the start of a line or whitespace
const REGEX_EMOTICON_WHITESPACE = new RegExp('(?:^|\\s)(' + EMOTICON_REGEX.source + ')\\s|:^$');
@ -90,7 +92,7 @@ function selectionEquals(a: Partial<Selection>, b: Selection): boolean {
interface IProps {
model: EditorModel;
room: Room;
threadId: string;
threadId?: string;
placeholder?: string;
label?: string;
initialCaret?: DocumentOffset;
@ -331,26 +333,32 @@ export default class BasicMessageEditor extends React.Component<IProps, IState>
private onPaste = (event: ClipboardEvent<HTMLDivElement>): boolean => {
event.preventDefault(); // we always handle the paste ourselves
if (this.props.onPaste && this.props.onPaste(event, this.props.model)) {
if (this.props.onPaste?.(event, this.props.model)) {
// to prevent double handling, allow props.onPaste to skip internal onPaste
return true;
}
const { model } = this.props;
const { partCreator } = model;
const plainText = event.clipboardData.getData("text/plain");
const partsText = event.clipboardData.getData("application/x-element-composer");
let parts;
let parts: Part[];
if (partsText) {
const serializedTextParts = JSON.parse(partsText);
const deserializedParts = serializedTextParts.map(p => partCreator.deserializePart(p));
parts = deserializedParts;
parts = serializedTextParts.map(p => partCreator.deserializePart(p));
} else {
const text = event.clipboardData.getData("text/plain");
parts = parsePlainTextMessage(text, partCreator, { shouldEscape: false });
parts = parsePlainTextMessage(plainText, partCreator, { shouldEscape: false });
}
this.modifiedFlag = true;
const range = getRangeForSelection(this.editorRef.current, model, document.getSelection());
replaceRangeAndMoveCaret(range, parts);
if (plainText && range.length > 0 && linkify.test(plainText)) {
formatRangeAsLink(range, plainText);
} else {
replaceRangeAndMoveCaret(range, parts);
}
};
private onInput = (event: Partial<InputEvent>): void => {

View file

@ -20,27 +20,27 @@ import classNames from 'classnames';
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { MenuItem } from "../../structures/ContextMenu";
import { OverflowMenuContext } from './MessageComposerButtons';
import { IconizedContextMenuOption } from '../context_menus/IconizedContextMenu';
interface ICollapsibleButtonProps extends ComponentProps<typeof MenuItem> {
title: string;
iconClassName: string;
}
export const CollapsibleButton = ({ title, children, className, ...props }: ICollapsibleButtonProps) => {
export const CollapsibleButton = ({ title, children, className, iconClassName, ...props }: ICollapsibleButtonProps) => {
const inOverflowMenu = !!useContext(OverflowMenuContext);
if (inOverflowMenu) {
return <MenuItem
return <IconizedContextMenuOption
{...props}
className={classNames("mx_CallContextMenu_item", className)}
>
{ title }
{ children }
</MenuItem>;
iconClassName={iconClassName}
label={title}
/>;
}
return <AccessibleTooltipButton
{...props}
title={title}
className={className}
className={classNames(className, iconClassName)}
>
{ children }
</AccessibleTooltipButton>;

View file

@ -212,7 +212,7 @@ interface IProps {
// whether or not to display thread info
showThreadInfo?: boolean;
// if specified and `true`, the message his behing
// if specified and `true`, the message is being
// hidden for moderation from other users but is
// displayed to the current user either because they're
// the author or they are a moderator
@ -234,7 +234,7 @@ interface IState {
// Position of the context menu
contextMenu?: {
position: Pick<DOMRect, "top" | "left" | "bottom">;
showPermalink?: boolean;
link?: string;
};
isQuoteExpanded?: boolean;
@ -842,26 +842,27 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
};
private onTimestampContextMenu = (ev: React.MouseEvent): void => {
this.showContextMenu(ev, true);
this.showContextMenu(ev, this.props.permalinkCreator?.forEvent(this.props.mxEvent.getId()));
};
private showContextMenu(ev: React.MouseEvent, showPermalink?: boolean): void {
private showContextMenu(ev: React.MouseEvent, permalink?: string): void {
const clickTarget = ev.target as HTMLElement;
// Return if message right-click context menu isn't enabled
if (!SettingsStore.getValue("feature_message_right_click_context_menu")) return;
// Return if we're in a browser and click either an a tag or we have
// selected text, as in those cases we want to use the native browser
// menu
const clickTarget = ev.target as HTMLElement;
if (
!PlatformPeg.get().allowOverridingNativeContextMenus() &&
(clickTarget.tagName === "a" || clickTarget.closest("a") || getSelectedText())
) return;
// Try to find an anchor element
const anchorElement = (clickTarget instanceof HTMLAnchorElement) ? clickTarget : clickTarget.closest("a");
// There is no way to copy non-PNG images into clipboard, so we can't
// have our own handling for copying images, so we leave it to the
// Electron layer (webcontents-handler.ts)
if (ev.target instanceof HTMLImageElement) return;
if (clickTarget instanceof HTMLImageElement) return;
// Return if we're in a browser and click either an a tag or we have
// selected text, as in those cases we want to use the native browser
// menu
if (!PlatformPeg.get().allowOverridingNativeContextMenus() && (getSelectedText() || anchorElement)) return;
// We don't want to show the menu when editing a message
if (this.props.editState) return;
@ -875,7 +876,7 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
top: ev.clientY,
bottom: ev.clientY,
},
showPermalink: showPermalink,
link: anchorElement?.href || permalink,
},
actionBarFocused: true,
});
@ -924,7 +925,7 @@ export class UnwrappedEventTile extends React.Component<IProps, IState> {
onFinished={this.onCloseMenu}
rightClick={true}
reactions={this.state.reactions}
showPermalink={this.state.contextMenu.showPermalink}
link={this.state.contextMenu.link}
/>
);
}

View file

@ -38,6 +38,7 @@ import MatrixClientContext from '../../../contexts/MatrixClientContext';
import RoomContext from '../../../contexts/RoomContext';
import { useDispatcher } from "../../../hooks/useDispatcher";
import { chromeFileInputFix } from "../../../utils/BrowserWorkarounds";
import IconizedContextMenu, { IconizedContextMenuOptionList } from '../context_menus/IconizedContextMenu';
interface IProps {
addEmoji: (emoji: string) => boolean;
@ -108,15 +109,18 @@ const MessageComposerButtons: React.FC<IProps> = (props: IProps) => {
title={_t("More options")}
/> }
{ props.isMenuOpen && (
<ContextMenu
<IconizedContextMenu
onFinished={props.toggleButtonMenu}
{...props.menuPosition}
wrapperClassName="mx_MessageComposer_Menu"
compact={true}
>
<OverflowMenuContext.Provider value={props.toggleButtonMenu}>
{ moreButtons }
<IconizedContextMenuOptionList>
{ moreButtons }
</IconizedContextMenuOptionList>
</OverflowMenuContext.Provider>
</ContextMenu>
</IconizedContextMenu>
) }
</UploadButtonContextProvider>;
};
@ -158,7 +162,6 @@ const EmojiButton: React.FC<IEmojiButtonProps> = ({ addEmoji, menuPosition }) =>
const className = classNames(
"mx_MessageComposer_button",
"mx_MessageComposer_emoji",
{
"mx_MessageComposer_button_highlight": menuDisplayed,
},
@ -169,6 +172,7 @@ const EmojiButton: React.FC<IEmojiButtonProps> = ({ addEmoji, menuPosition }) =>
return <React.Fragment>
<CollapsibleButton
className={className}
iconClassName="mx_MessageComposer_emoji"
onClick={openMenu}
title={_t("Emoji")}
/>
@ -254,7 +258,8 @@ const UploadButton = () => {
};
return <CollapsibleButton
className="mx_MessageComposer_button mx_MessageComposer_upload"
className="mx_MessageComposer_button"
iconClassName="mx_MessageComposer_upload"
onClick={onClick}
title={_t('Attachment')}
/>;
@ -266,7 +271,8 @@ function showStickersButton(props: IProps): ReactElement {
? <CollapsibleButton
id='stickersButton'
key="controls_stickers"
className="mx_MessageComposer_button mx_MessageComposer_stickers"
className="mx_MessageComposer_button"
iconClassName="mx_MessageComposer_stickers"
onClick={() => props.setStickerPickerOpen(!props.isStickerPickerOpen)}
title={props.isStickerPickerOpen ? _t("Hide stickers") : _t("Sticker")}
/>
@ -281,7 +287,8 @@ function voiceRecordingButton(props: IProps, narrow: boolean): ReactElement {
? null
: <CollapsibleButton
key="voice_message_send"
className="mx_MessageComposer_button mx_MessageComposer_voiceMessage"
className="mx_MessageComposer_button"
iconClassName="mx_MessageComposer_voiceMessage"
onClick={props.onRecordStartEndClick}
title={_t("Voice Message")}
/>
@ -345,7 +352,8 @@ class PollButton extends React.PureComponent<IPollButtonProps> {
return (
<CollapsibleButton
className="mx_MessageComposer_button mx_MessageComposer_poll"
className="mx_MessageComposer_button"
iconClassName="mx_MessageComposer_poll"
onClick={this.onCreateClick}
title={_t("Poll")}
/>