* Add quote functionality to MessageContextMenu (#29893) * Remove unused import of getSelectedText from strings utility in EventTile component * Add space after quoted text in ComposerInsert action * Add space after quoted text in MessageContextMenu test * add new line before and after the formated text
This commit is contained in:
@@ -183,6 +183,30 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the current selection is entirely within a single "mx_MTextBody" element.
|
||||
*/
|
||||
private isSelectionWithinSingleTextBody(): boolean {
|
||||
const selection = window.getSelection();
|
||||
if (!selection || selection.rangeCount === 0) return false;
|
||||
const range = selection.getRangeAt(0);
|
||||
|
||||
function getParentByClass(node: Node | null, className: string): HTMLElement | null {
|
||||
while (node) {
|
||||
if (node instanceof HTMLElement && node.classList.contains(className)) {
|
||||
return node;
|
||||
}
|
||||
node = node.parentNode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
const startTextBody = getParentByClass(range.startContainer, "mx_MTextBody");
|
||||
const endTextBody = getParentByClass(range.endContainer, "mx_MTextBody");
|
||||
|
||||
return !!startTextBody && startTextBody === endTextBody;
|
||||
}
|
||||
|
||||
private onResendReactionsClick = (): void => {
|
||||
for (const reaction of this.getUnsentReactions()) {
|
||||
Resend.resend(MatrixClientPeg.safeGet(), reaction);
|
||||
@@ -279,6 +303,24 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
||||
this.closeMenu();
|
||||
};
|
||||
|
||||
private onQuoteClick = (): void => {
|
||||
const selectedText = getSelectedText();
|
||||
if (selectedText) {
|
||||
// Format as markdown quote
|
||||
const quotedText = selectedText
|
||||
.trim()
|
||||
.split(/\r?\n/)
|
||||
.map((line) => `> ${line}`)
|
||||
.join("\n");
|
||||
dis.dispatch({
|
||||
action: Action.ComposerInsert,
|
||||
text: "\n" + quotedText + "\n\n ",
|
||||
timelineRenderingType: this.context.timelineRenderingType,
|
||||
});
|
||||
}
|
||||
this.closeMenu();
|
||||
};
|
||||
|
||||
private onEditClick = (): void => {
|
||||
editEvent(
|
||||
MatrixClientPeg.safeGet(),
|
||||
@@ -549,8 +591,10 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
||||
);
|
||||
}
|
||||
|
||||
const selectedText = getSelectedText();
|
||||
|
||||
let copyButton: JSX.Element | undefined;
|
||||
if (rightClick && getSelectedText()) {
|
||||
if (rightClick && selectedText) {
|
||||
copyButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconCopy"
|
||||
@@ -561,6 +605,18 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
||||
);
|
||||
}
|
||||
|
||||
let quoteButton: JSX.Element | undefined;
|
||||
if (rightClick && selectedText && selectedText.trim().length > 0 && this.isSelectionWithinSingleTextBody()) {
|
||||
quoteButton = (
|
||||
<IconizedContextMenuOption
|
||||
iconClassName="mx_MessageContextMenu_iconQuote"
|
||||
label={_t("action|quote")}
|
||||
triggerOnMouseDown={true}
|
||||
onClick={this.onQuoteClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
let editButton: JSX.Element | undefined;
|
||||
if (rightClick && canEditContent(cli, mxEvent)) {
|
||||
editButton = (
|
||||
@@ -630,10 +686,11 @@ export default class MessageContextMenu extends React.Component<IProps, IState>
|
||||
}
|
||||
|
||||
let nativeItemsList: JSX.Element | undefined;
|
||||
if (copyButton || copyLinkButton) {
|
||||
if (copyButton || quoteButton || copyLinkButton) {
|
||||
nativeItemsList = (
|
||||
<IconizedContextMenuOptionList>
|
||||
{copyButton}
|
||||
{quoteButton}
|
||||
{copyLinkButton}
|
||||
</IconizedContextMenuOptionList>
|
||||
);
|
||||
|
||||
@@ -64,7 +64,7 @@ import { getEventDisplayInfo } from "../../../utils/EventRenderingUtils";
|
||||
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
|
||||
import { MediaEventHelper } from "../../../utils/MediaEventHelper";
|
||||
import { type ButtonEvent } from "../elements/AccessibleButton";
|
||||
import { copyPlaintext, getSelectedText } from "../../../utils/strings";
|
||||
import { copyPlaintext } from "../../../utils/strings";
|
||||
import { DecryptionFailureTracker } from "../../../DecryptionFailureTracker";
|
||||
import RedactedBody from "../messages/RedactedBody";
|
||||
import { type ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||
@@ -840,10 +840,8 @@ export class UnwrappedEventTile extends React.Component<EventTileProps, IState>
|
||||
// Electron layer (webcontents-handler.ts)
|
||||
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;
|
||||
// Return if we're in a browser and click either an a tag, as in those cases we want to use the native browser menu
|
||||
if (!PlatformPeg.get()?.allowOverridingNativeContextMenus() && anchorElement) return;
|
||||
|
||||
// We don't want to show the menu when editing a message
|
||||
if (this.props.editState) return;
|
||||
|
||||
Reference in New Issue
Block a user