fix(app): copy selected message text without markdown (#2759)

This commit is contained in:
Hampus authored and GitHub committed 2026-09-14 14:53:53 +02:00
1 parent 91a2604e9e
commit 7d56481aba
4 files changed
+38 -13

No files matched your search

@@ -27,6 +27,7 @@ import {parse} from '@app/features/messaging/components/markdown/renderers';
import {MarkdownContext} from '@app/features/messaging/components/markdown/renderers/RendererTypes'; import {MarkdownContext} from '@app/features/messaging/components/markdown/renderers/RendererTypes';
import MessageEdit from '@app/features/messaging/state/MessageEdit'; import MessageEdit from '@app/features/messaging/state/MessageEdit';
import {hasStyleableMessageText} from '@app/features/messaging/utils/FailedMessageDisplayUtils'; import {hasStyleableMessageText} from '@app/features/messaging/utils/FailedMessageDisplayUtils';
import {buildMessageContentCopyText} from '@app/features/messaging/utils/MessageCopyTextUtils';
import { import {
buildExistingAttachmentEditReferences, buildExistingAttachmentEditReferences,
canSubmitEmptyMessageEdit, canSubmitEmptyMessageEdit,
@@ -140,6 +141,16 @@ export const UserMessage = observer(() => {
}), }),
[message.id, message.channelId, message.mentionChannels], [message.id, message.channelId, message.mentionChannels],
); );
const contentCopyText = useMemo(
() =>
buildMessageContentCopyText(astNodes, {
channelId: message.channelId,
messageId: message.id,
mentionChannels: message.mentionChannels,
i18n,
}),
[astNodes, message.id, message.channelId, message.mentionChannels, i18n.locale],
);
const shouldHideContent = const shouldHideContent =
UserSettings.getRenderEmbeds() && UserSettings.getRenderEmbeds() &&
message.embeds.length > 0 && message.embeds.length > 0 &&
@@ -281,7 +292,7 @@ export const UserMessage = observer(() => {
className={clsx(markupStyles.markup)} className={clsx(markupStyles.markup)}
data-search-highlight-scope="message" data-search-highlight-scope="message"
data-flx="channel.user-message.render-message-content.div" data-flx="channel.user-message.render-message-content.div"
{...messageContentCopyBlockProps(message.content)} {...messageContentCopyBlockProps(contentCopyText)}
> >
<SafeMarkdown <SafeMarkdown
content={message.content} content={message.content}
@@ -315,6 +326,7 @@ export const UserMessage = observer(() => {
shouldShowEditingInput, shouldShowEditingInput,
shouldHideContent, shouldHideContent,
markdownOptions, markdownOptions,
contentCopyText,
message, message,
message.content, message.content,
message.id, message.id,
@@ -415,7 +427,7 @@ export const UserMessage = observer(() => {
className={clsx(markupStyles.markup)} className={clsx(markupStyles.markup)}
data-search-highlight-scope="message" data-search-highlight-scope="message"
data-flx="channel.user-message.div" data-flx="channel.user-message.div"
{...messageContentCopyBlockProps(message.content)} {...messageContentCopyBlockProps(contentCopyText)}
> >
<SafeMarkdown <SafeMarkdown
content={message.content} content={message.content}
@@ -244,7 +244,7 @@ function renderTableRow(
export function TableRenderer({node, id, renderChildren, options}: RendererProps<TableNode>): React.ReactElement { export function TableRenderer({node, id, renderChildren, options}: RendererProps<TableNode>): React.ReactElement {
const copyText = renderTableNodeToMarkdown(node, { const copyText = renderTableNodeToMarkdown(node, {
channelId: options.channelId, channelId: options.channelId,
preserveMarkdown: true, preserveMarkdown: false,
includeEmojiNames: true, includeEmojiNames: true,
i18n: options.i18n, i18n: options.i18n,
}); });
@@ -3,7 +3,12 @@
import {MarkdownContext} from '@app/features/messaging/components/markdown/renderers/RendererTypes'; import {MarkdownContext} from '@app/features/messaging/components/markdown/renderers/RendererTypes';
import type {Message} from '@app/features/messaging/models/MessagingMessage'; import type {Message} from '@app/features/messaging/models/MessagingMessage';
import {getParserFlagsForContext} from '@app/features/messaging/utils/markdown/MarkdownParserFlags'; import {getParserFlagsForContext} from '@app/features/messaging/utils/markdown/MarkdownParserFlags';
import {parseAndRenderToPlaintext} from '@app/features/messaging/utils/markdown/Plaintext'; import {
type PlaintextRenderOptions,
parseAndRenderToPlaintext,
renderAstToPlaintext,
} from '@app/features/messaging/utils/markdown/Plaintext';
import type {Node} from '@app/features/messaging/utils/markdown/parser/Nodes';
import * as DateUtils from '@app/features/user/utils/DateFormatting'; import * as DateUtils from '@app/features/user/utils/DateFormatting';
import {MessageEmbedTypes} from '@fluxer/constants/src/ChannelConstants'; import {MessageEmbedTypes} from '@fluxer/constants/src/ChannelConstants';
import type {MessageEmbed} from '@fluxer/schema/src/domains/message/EmbedSchemas'; import type {MessageEmbed} from '@fluxer/schema/src/domains/message/EmbedSchemas';
@@ -102,6 +107,17 @@ function buildOmittedEmbedUrls(content?: string | null, renderedContent?: string
return urls; return urls;
} }
function createPlaintextCopyOptions(context: MarkdownCopyContext): PlaintextRenderOptions {
return {
channelId: context.channelId,
preserveMarkdown: false,
includeEmojiNames: true,
includeLinkUrls: true,
mentionChannels: context.mentionChannels,
i18n: context.i18n,
};
}
function renderMarkdownCopyText( function renderMarkdownCopyText(
content: string | undefined | null, content: string | undefined | null,
parserFlags: number, parserFlags: number,
@@ -110,14 +126,11 @@ function renderMarkdownCopyText(
if (!content) { if (!content) {
return ''; return '';
} }
return parseAndRenderToPlaintext(content, parserFlags, { return parseAndRenderToPlaintext(content, parserFlags, createPlaintextCopyOptions(context));
channelId: context.channelId, }
preserveMarkdown: false,
includeEmojiNames: true, export function buildMessageContentCopyText(nodes: Array<Node>, context: MarkdownCopyContext): string {
includeLinkUrls: true, return normaliseCopyBlock(renderAstToPlaintext(nodes, createPlaintextCopyOptions(context)));
mentionChannels: context.mentionChannels,
i18n: context.i18n,
});
} }
function buildAttachmentCopyText(attachment: MessageAttachment): string { function buildAttachmentCopyText(attachment: MessageAttachment): string {
@@ -44,7 +44,7 @@ describe('Message selection copy utils', () => {
expect(buildMessageSelectionCopyTextForRange({rootElement: root, selectionRange: range})).toBe(tableCopyText); expect(buildMessageSelectionCopyTextForRange({rootElement: root, selectionRange: range})).toBe(tableCopyText);
}); });
it('does not duplicate a block message header when a bot badge is selected with the body', () => { it('does not duplicate a block message header when a bot badge is selected with the body', () => {
const messageContent = '## App Canary Deployed\n\nVersion: `2026.519.3`\nImage: `2026.519.3`'; const messageContent = 'App Canary Deployed\n\nVersion: 2026.519.3\nImage: 2026.519.3';
document.body.innerHTML = [ document.body.innerHTML = [
'<div data-message-selection-root="true">', '<div data-message-selection-root="true">',
'<div data-message-id="message-1" data-is-group-start="true">', '<div data-message-id="message-1" data-is-group-start="true">',