mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
feat(app): wrap pasted links in every composer that allows masked links (#1755)
This commit is contained in:
1 parent
d398ebc44b
commit
2083eaddd6
4 files changed
+54
-19
No files matched your search
@@ -13,7 +13,6 @@ import {
|
||||
parseComposerClipboardSlice,
|
||||
} from '@app/features/lexical/composer/ComposerClipboard';
|
||||
import type {ComposerHandle} from '@app/features/lexical/composer/ComposerHandle';
|
||||
import {resolvePastedLinkInsertion} from '@app/features/lexical/composer/ComposerLinkPaste';
|
||||
import {showAttachmentPermissionDeniedModal} from '@app/features/messaging/components/alerts/AttachmentPermissionDeniedModal';
|
||||
import {FileSizeTooLargeModal} from '@app/features/messaging/components/alerts/FileSizeTooLargeModal';
|
||||
import {TooManyAttachmentsModal} from '@app/features/messaging/components/alerts/TooManyAttachmentsModal';
|
||||
@@ -33,7 +32,7 @@ import {modal} from '@app/features/ui/commands/ModalCommands';
|
||||
import ContextMenuState from '@app/features/ui/state/ContextMenu';
|
||||
import KeyboardMode from '@app/features/ui/state/KeyboardMode';
|
||||
import Users from '@app/features/user/state/Users';
|
||||
import {$getSelection, $isRangeSelection, COMMAND_PRIORITY_HIGH, PASTE_COMMAND} from 'lexical';
|
||||
import {COMMAND_PRIORITY_HIGH, PASTE_COMMAND} from 'lexical';
|
||||
import type React from 'react';
|
||||
import {useCallback, useEffect} from 'react';
|
||||
|
||||
@@ -69,19 +68,6 @@ function createComposerPasteSlice(pastedText: string, segments: ReadonlyArray<Me
|
||||
return {display: displayParts.join(''), segments: projectedSegments};
|
||||
}
|
||||
|
||||
function $insertLinkAroundSelection(pastedText: string): boolean {
|
||||
const selection = $getSelection();
|
||||
if (!$isRangeSelection(selection) || selection.isCollapsed()) {
|
||||
return false;
|
||||
}
|
||||
const replacement = resolvePastedLinkInsertion(pastedText, selection.getTextContent());
|
||||
if (replacement === null) {
|
||||
return false;
|
||||
}
|
||||
selection.insertText(replacement);
|
||||
return true;
|
||||
}
|
||||
|
||||
export function useChannelComposerPaste({
|
||||
channel,
|
||||
handleRef,
|
||||
@@ -244,10 +230,6 @@ export function useChannelComposerPaste({
|
||||
void handlePasteExceedsLimit(pastedText);
|
||||
return true;
|
||||
}
|
||||
if ($insertLinkAroundSelection(pastedText)) {
|
||||
event.preventDefault();
|
||||
return true;
|
||||
}
|
||||
if (!insertPastedText(pastedText)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {resolvePastedLinkInsertion} from '@app/features/lexical/composer/ComposerLinkPaste';
|
||||
import {
|
||||
$createComposerSegmentNodes,
|
||||
$projectComposer,
|
||||
@@ -8,6 +9,7 @@ import {
|
||||
import {$captureSelectionOffsets, $selectComposerOffset} from '@app/features/lexical/composer/composerOffsets';
|
||||
import {$isSlashSlotNode, type SlashSlotNode} from '@app/features/lexical/composer/nodes/SlashSlotNode';
|
||||
import {COMPOSER_SLASH_SLOT_STATE_MAX_ID_LENGTH} from '@app/features/lexical/composer/SlashSlotPersistence';
|
||||
import {ParserFlags} from '@app/features/messaging/utils/markdown/parser/Enums';
|
||||
import type {MentionSegment} from '@app/features/messaging/utils/TextareaSegmentManager';
|
||||
import {mergeRegister} from '@lexical/utils';
|
||||
import {
|
||||
@@ -21,6 +23,7 @@ import {
|
||||
$isNodeSelection,
|
||||
$isRangeSelection,
|
||||
$isTextNode,
|
||||
COMMAND_PRIORITY_CRITICAL,
|
||||
COMMAND_PRIORITY_NORMAL,
|
||||
COPY_COMMAND,
|
||||
CUT_COMMAND,
|
||||
@@ -60,6 +63,7 @@ export interface ComposerClipboardSelection extends ComposerClipboardSlice {
|
||||
export interface ComposerClipboardCommandState {
|
||||
getPlainText(): boolean;
|
||||
isEditable(): boolean;
|
||||
getMarkdownParserFlags(): number;
|
||||
}
|
||||
|
||||
interface SerializedComposerClipboardPayload {
|
||||
@@ -460,6 +464,34 @@ export function registerComposerClipboardCommands(
|
||||
state: ComposerClipboardCommandState,
|
||||
): () => void {
|
||||
return mergeRegister(
|
||||
editor.registerCommand(
|
||||
PASTE_COMMAND,
|
||||
(event) => {
|
||||
if (!state.isEditable() || (state.getMarkdownParserFlags() & ParserFlags.ALLOW_MASKED_LINKS) === 0) {
|
||||
return false;
|
||||
}
|
||||
const clipboardEvent = getClipboardEvent(event);
|
||||
if (clipboardEvent == null || clipboardEvent.clipboardData == null) {
|
||||
return false;
|
||||
}
|
||||
const selection = $getSelection();
|
||||
if (!$isRangeSelection(selection) || selection.isCollapsed()) {
|
||||
return false;
|
||||
}
|
||||
const replacement = resolvePastedLinkInsertion(
|
||||
clipboardEvent.clipboardData.getData('text/plain'),
|
||||
selection.getTextContent(),
|
||||
);
|
||||
if (replacement === null) {
|
||||
return false;
|
||||
}
|
||||
selection.insertText(replacement);
|
||||
$addUpdateTag(PASTE_TAG);
|
||||
clipboardEvent.preventDefault();
|
||||
return true;
|
||||
},
|
||||
COMMAND_PRIORITY_CRITICAL,
|
||||
),
|
||||
editor.registerCommand(
|
||||
COPY_COMMAND,
|
||||
(event) => {
|
||||
|
||||
@@ -6,6 +6,10 @@ import {
|
||||
parsePastedUrl,
|
||||
resolvePastedLinkInsertion,
|
||||
} from '@app/features/lexical/composer/ComposerLinkPaste';
|
||||
import {DEFAULT_COMPOSER_MARKDOWN_FLAGS} from '@app/features/lexical/composer/markdownSpans';
|
||||
import {MarkdownContext} from '@app/features/messaging/components/markdown/renderers/RendererTypes';
|
||||
import {getParserFlagsForContext} from '@app/features/messaging/utils/markdown/MarkdownParserFlags';
|
||||
import {ParserFlags} from '@app/features/messaging/utils/markdown/parser/Enums';
|
||||
import {describe, expect, it} from 'vitest';
|
||||
|
||||
describe('parsePastedUrl', () => {
|
||||
@@ -87,3 +91,16 @@ describe('resolvePastedLinkInsertion', () => {
|
||||
expect(resolvePastedLinkInsertion('javascript:alert(1)', 'click me')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('masked link support across composer surfaces', () => {
|
||||
const surfaces: Array<[string, number]> = [
|
||||
['channel composer', DEFAULT_COMPOSER_MARKDOWN_FLAGS],
|
||||
['message edit / forward', getParserFlagsForContext(MarkdownContext.STANDARD_WITH_JUMBO)],
|
||||
['channel topic', getParserFlagsForContext(MarkdownContext.STANDARD_WITHOUT_JUMBO)],
|
||||
['user bio', getParserFlagsForContext(MarkdownContext.RESTRICTED_USER_BIO)],
|
||||
];
|
||||
|
||||
it.each(surfaces)('%s allows masked links, so pasting a link must wrap', (_name, flags) => {
|
||||
expect(flags & ParserFlags.ALLOW_MASKED_LINKS).not.toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
$wrapComposerSelection,
|
||||
} from '@app/features/lexical/composer/composerOffsets';
|
||||
import styles from '@app/features/lexical/composer/LexicalMessageComposer.module.css';
|
||||
import {DEFAULT_COMPOSER_MARKDOWN_FLAGS} from '@app/features/lexical/composer/markdownSpans';
|
||||
import {ComposerCommandNode} from '@app/features/lexical/composer/nodes/ComposerCommandNode';
|
||||
import {ComposerCustomEmojiNode} from '@app/features/lexical/composer/nodes/ComposerCustomEmojiNode';
|
||||
import {ComposerMentionNode} from '@app/features/lexical/composer/nodes/ComposerMentionNode';
|
||||
@@ -263,6 +264,8 @@ const ComposerInner = ({
|
||||
submitOnEnterRef.current = submitOnEnter;
|
||||
const plainTextRef = useRef(plainText);
|
||||
plainTextRef.current = plainText;
|
||||
const markdownParserFlagsRef = useRef(markdownParserFlags ?? DEFAULT_COMPOSER_MARKDOWN_FLAGS);
|
||||
markdownParserFlagsRef.current = markdownParserFlags ?? DEFAULT_COMPOSER_MARKDOWN_FLAGS;
|
||||
const disabledRef = useRef(disabled);
|
||||
disabledRef.current = disabled;
|
||||
const onSlashCommandStateChangeRef = useRef(onSlashCommandStateChange);
|
||||
@@ -607,6 +610,7 @@ const ComposerInner = ({
|
||||
registerComposerClipboardCommands(editor, {
|
||||
getPlainText: () => plainTextRef.current,
|
||||
isEditable: () => !disabledRef.current && editor.isEditable(),
|
||||
getMarkdownParserFlags: () => markdownParserFlagsRef.current,
|
||||
}),
|
||||
[editor],
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user