Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,4 @@ dist/
lib/

plans/
/.codex/
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@
const [videoBackgroundEffectsFeatureEnabled, setVideoBackgroundEffectsFeatureEnabled] = useState(
window.wire?.app?.debug?.isVideoBackgroundEffectsFeatureEnabled() ?? false,
);
const [isMessagePreprocessingDisabled, setIsMessagePreprocessingDisabled] = useState(
window.wire?.app?.debug?.isMessagePreprocessingDisabled() ?? false,

Check warning on line 52 in apps/webapp/src/script/components/ConfigToolbar/ConfigToolbar.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=wireapp_wire-webapp&issues=AZ4MEthfJxyiBuPnmlDZ&open=AZ4MEthfJxyiBuPnmlDZ&pullRequest=21275
);
const [coreCryptoLevel, setCoreCryptoLevel] = useState<CoreCryptoLogLevel>(CoreCryptoLogLevel.Info);

// Toggle config tool on 'cmd/ctrl + shift + 2'
Expand Down Expand Up @@ -264,6 +267,23 @@
);
};

const renderMessagePreprocessingSwitch = () => {
return (
<div style={{marginBottom: '10px'}}>
Comment thread
MohamadJaara marked this conversation as resolved.
Outdated
<label htmlFor="message-preprocessing-checkbox" style={{display: 'block', fontWeight: 'bold'}}>
DISABLE MESSAGE PREPROCESSING
</label>
<Switch
id="message-preprocessing-checkbox"
checked={isMessagePreprocessingDisabled}
onToggle={isChecked => {
setIsMessagePreprocessingDisabled(window.wire?.app?.debug?.disableMessagePreprocessing(isChecked) === true);

Check warning on line 280 in apps/webapp/src/script/components/ConfigToolbar/ConfigToolbar.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=wireapp_wire-webapp&issues=AZ4MEthfJxyiBuPnmlDa&open=AZ4MEthfJxyiBuPnmlDa&pullRequest=21275
}}
/>
</div>
);
};

const renderCoreCryptoLogLevelSelect = () => {
const options: Array<{label: string; value: CoreCryptoLogLevel}> = [
{label: 'Off', value: CoreCryptoLogLevel.Off},
Expand Down Expand Up @@ -351,6 +371,10 @@

<hr />

<div>{renderMessagePreprocessingSwitch()}</div>

<hr />

<div>{renderCoreCryptoLogLevelSelect()}</div>

<hr />
Expand Down
25 changes: 21 additions & 4 deletions apps/webapp/src/script/components/InputBar/InputBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
*
*/

import {useCallback, useRef, useState} from 'react';
import {useCallback, useEffect, useRef, useState} from 'react';

import {amplify} from 'amplify';
import cx from 'classnames';
Expand Down Expand Up @@ -45,6 +45,7 @@
import {EventName} from 'Repositories/tracking/EventName';
import {CONVERSATION_TYPING_INDICATOR_MODE} from 'Repositories/user/TypingIndicatorMode';
import {useKoSubscribableChildren} from 'Util/componentUtil';
import {DISABLE_MESSAGE_PREPROCESSING_EVENT, isMessagePreprocessingDisabled} from 'Util/debugMessagePreprocessingUtil';
import {t} from 'Util/localizerUtil';
import {TIME_IN_MILLIS} from 'Util/timeUtil';

Expand Down Expand Up @@ -148,6 +149,7 @@
* It's directly derived from the editor state
*/
const [messageContent, setMessageContent] = useState<MessageContent>({text: ''});
const [disableMessagePreprocessing, setDisableMessagePreprocessing] = useState(isMessagePreprocessingDisabled);

const formatToolbar = useFormatToolbar();

Expand Down Expand Up @@ -206,6 +208,19 @@
() => propertiesRepository.getPreference(PROPERTIES_TYPE.INTERFACE.MARKDOWN_PREVIEW),
WebAppEvents.PROPERTIES.UPDATE.INTERFACE.MARKDOWN_PREVIEW,
);
const effectiveShowMarkdownPreview = showMarkdownPreview && !disableMessagePreprocessing;

useEffect(() => {
const handleMessagePreprocessingChange = (event: Event) => {
setDisableMessagePreprocessing((event as CustomEvent<boolean>).detail ?? isMessagePreprocessingDisabled());
Comment thread
MohamadJaara marked this conversation as resolved.
Outdated
};

window.addEventListener(DISABLE_MESSAGE_PREPROCESSING_EVENT, handleMessagePreprocessingChange);

Check warning on line 218 in apps/webapp/src/script/components/InputBar/InputBar.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=wireapp_wire-webapp&issues=AZ4MEtZBJxyiBuPnmlDX&open=AZ4MEtZBJxyiBuPnmlDX&pullRequest=21275

return () => {
window.removeEventListener(DISABLE_MESSAGE_PREPROCESSING_EVENT, handleMessagePreprocessingChange);

Check warning on line 221 in apps/webapp/src/script/components/InputBar/InputBar.tsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `globalThis` over `window`.

See more on https://sonarcloud.io/project/issues?id=wireapp_wire-webapp&issues=AZ4MEtZBJxyiBuPnmlDY&open=AZ4MEtZBJxyiBuPnmlDY&pullRequest=21275
};
}, []);

const {
editedMessage,
Expand All @@ -232,6 +247,7 @@
editorRef,
pastedFile: fileHandling.pastedFile,
sendPastedFile: fileHandling.sendPastedFile,
disableMessagePreprocessing,
});

if (fileHandling.pastedFile && !!isCellsEnabled) {
Expand Down Expand Up @@ -281,7 +297,7 @@
<div
className={cx(`conversation-input-bar__input input-bar-container`, {
[`conversation-input-bar__input--editing`]: isEditing,
'input-bar-container--with-toolbar': formatToolbar.open && showMarkdownPreview,
'input-bar-container--with-toolbar': formatToolbar.open && effectiveShowMarkdownPreview,
'input-bar-container--with-files': !!files.length,
})}
>
Expand All @@ -303,7 +319,7 @@
editedMessage={editedMessage}
inputPlaceholder={inputPlaceholder}
hasLocalEphemeralTimer={hasLocalEphemeralTimer}
showMarkdownPreview={showMarkdownPreview}
showMarkdownPreview={effectiveShowMarkdownPreview}
formatToolbar={formatToolbar}
onSetup={editor => {
editorRef.current = editor;
Expand All @@ -321,6 +337,7 @@
getMentionCandidates={getMentionCandidates}
saveDraftState={draftState.save}
loadDraftState={draftState.load}
disableMessagePreprocessing={disableMessagePreprocessing}
replaceEmojis={shouldReplaceEmoji}
>
{!!files.length && <FilePreviews files={files} conversationQualifiedId={conversation.qualifiedId} />}
Expand All @@ -332,7 +349,7 @@
messageContent={messageContent}
isEditing={isEditing}
isSendingDisabled={isSendingDisabled}
showMarkdownPreview={showMarkdownPreview}
showMarkdownPreview={effectiveShowMarkdownPreview}
showGiphyButton={giphy.showGiphyButton}
formatToolbar={formatToolbar}
emojiPicker={emojiPicker}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ interface InputBarEditorProps {
getMentionCandidates: (search?: string | null) => User[];
saveDraftState: (editorState: string, plainMessage: string, replyId?: string) => void;
loadDraftState: () => Promise<DraftState>;
disableMessagePreprocessing: boolean;
replaceEmojis: boolean;
children: React.ReactNode;
}
Expand All @@ -70,6 +71,7 @@ export const InputBarEditor = ({
getMentionCandidates,
saveDraftState,
loadDraftState,
disableMessagePreprocessing,
replaceEmojis,
children,
}: InputBarEditorProps) => {
Expand All @@ -83,6 +85,7 @@ export const InputBarEditor = ({
onEscape={onEscape}
onArrowUp={onArrowUp}
getMentionCandidates={getMentionCandidates}
disableMessagePreprocessing={disableMessagePreprocessing}
replaceEmojis={replaceEmojis}
placeholder={inputPlaceholder}
onUpdate={onUpdate}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,12 @@ import {ListItemTabIndentationPlugin} from './plugins/ListIndentationPlugin/List
import {ListMaxIndentLevelPlugin} from './plugins/ListMaxIndentLevelPlugin/ListMaxIndentLevelPlugin';
import {MentionsPlugin} from './plugins/MentionsPlugin';
import {PastePlugin} from './plugins/PastePlugin/PastePlugin';
import {PlainTextPastePlugin} from './plugins/PlainTextPastePlugin';
import {ReplaceCarriageReturnPlugin} from './plugins/ReplaceCarriageReturnPlugin/ReplaceCarriageReturnPlugin';
import {SendPlugin} from './plugins/SendPlugin/SendPlugin';
import {markdownTransformers} from './utils/markdownTransformers';
import {parseMentions} from './utils/parseMentions';
import {transformMessage} from './utils/transformMessage';
import {getRawMessageText, transformMessage} from './utils/transformMessage';
import {useEditorDraftState} from './utils/useEditorDraftState';

interface RichTextEditorProps {
Expand All @@ -69,6 +70,7 @@ interface RichTextEditorProps {
hasLocalEphemeralTimer: boolean;
showFormatToolbar: boolean;
showMarkdownPreview: boolean;
disableMessagePreprocessing: boolean;
getMentionCandidates: (search?: string | null) => User[];
saveDraftState: (editor: string, plainMessage: string, replyId?: string) => void;
loadDraftState: () => Promise<DraftState>;
Expand All @@ -89,6 +91,7 @@ export const RichTextEditor = ({
editedMessage,
showFormatToolbar,
showMarkdownPreview,
disableMessagePreprocessing,
onUpdate,
saveDraftState,
loadDraftState,
Expand All @@ -108,6 +111,7 @@ export const RichTextEditor = ({
editorRef,
saveDraftState,
replaceEmojis,
disableMessagePreprocessing,
});

const handleChange = (editorState: EditorState) => {
Expand All @@ -116,13 +120,14 @@ export const RichTextEditor = ({
return;
}

const markdown = $convertToMarkdownString(markdownTransformers, undefined, true);

const text = transformMessage({replaceEmojis, markdown});
const markdown = disableMessagePreprocessing
? ''
: $convertToMarkdownString(markdownTransformers, undefined, true);
const text = disableMessagePreprocessing ? getRawMessageText() : transformMessage({replaceEmojis, markdown});

onUpdate({
text,
mentions: parseMentions(editorRef.current, markdown, getMentionCandidates()),
mentions: parseMentions(editorRef.current, text, getMentionCandidates()),
});

saveDraft();
Expand All @@ -145,11 +150,11 @@ export const RichTextEditor = ({
/>
<DraftStatePlugin loadDraftState={loadDraftState} />
<EditedMessagePlugin message={editedMessage} showMarkdownPreview={showMarkdownPreview} />
<EmojiPickerPlugin openStateRef={emojiPickerOpen} />
{!disableMessagePreprocessing && <EmojiPickerPlugin openStateRef={emojiPickerOpen} />}
<HistoryPlugin />

{replaceEmojis && <ReplaceEmojiPlugin />}
<ReplaceCarriageReturnPlugin />
{replaceEmojis && !disableMessagePreprocessing && <ReplaceEmojiPlugin />}
{!disableMessagePreprocessing && <ReplaceCarriageReturnPlugin />}

{showMarkdownPreview && (
<>
Expand Down Expand Up @@ -177,12 +182,15 @@ export const RichTextEditor = ({
<OnChangePlugin onChange={handleChange} ignoreSelectionChange />
<SendPlugin
onSend={() => {
if (!mentionsOpen.current && !emojiPickerOpen.current) {
if (!mentionsOpen.current && (disableMessagePreprocessing || !emojiPickerOpen.current)) {
onSend();
}
}}
/>
<PastePlugin getMentionCandidates={getMentionCandidates} isPreviewMode={showMarkdownPreview} />
{!disableMessagePreprocessing && (
<PastePlugin getMentionCandidates={getMentionCandidates} isPreviewMode={showMarkdownPreview} />
)}
{disableMessagePreprocessing && <PlainTextPastePlugin />}
Comment thread
MohamadJaara marked this conversation as resolved.
Outdated
</div>
</div>
{showFormatToolbar && showMarkdownPreview && (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Wire
* Copyright (C) 2026 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*/

import {useCallback, useEffect} from 'react';

import {useLexicalComposerContext} from '@lexical/react/LexicalComposerContext';
import {$getSelection, $isRangeSelection, COMMAND_PRIORITY_HIGH, PASTE_COMMAND} from 'lexical';

export const PlainTextPastePlugin = (): null => {
const [editor] = useLexicalComposerContext();

const handlePaste = useCallback(
(event: ClipboardEvent) => {
const plainText = event.clipboardData?.getData('text/plain');

if (plainText === undefined) {
return false;
}

editor.update(() => {
const selection = $getSelection();

if ($isRangeSelection(selection)) {
selection.insertText(plainText);
}
});

event.preventDefault();
return true;
},
[editor],
);

useEffect(() => {
return editor.registerCommand(PASTE_COMMAND, handlePaste, COMMAND_PRIORITY_HIGH);
}, [editor, handlePaste]);

return null;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Wire
* Copyright (C) 2026 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
*/

export * from './PlainTextPastePlugin';
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,17 @@
*
*/

import {$getRoot} from 'lexical';

import {findAndTransformEmoji} from '../plugins/InlineEmojiReplacementPlugin';

export const transformMessage = ({replaceEmojis, markdown}: {replaceEmojis: boolean; markdown: string}) => {
return replaceEmojis ? findAndTransformEmoji(markdown) : markdown;
};

export const getRawMessageText = () => {
return $getRoot()
.getChildren()
.map(node => node.getTextContent())
.join('\n');
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,32 +24,40 @@ import {LexicalEditor} from 'lexical';
import {useDebouncedCallback} from 'use-debounce';

import {markdownTransformers} from './markdownTransformers';
import {transformMessage} from './transformMessage';
import {getRawMessageText, transformMessage} from './transformMessage';

const DRAFT_SAVE_DELAY = 800;

interface UseEditorDraftStateProps {
editorRef: RefObject<LexicalEditor | null>;
saveDraftState: (editorState: string, plainMessage: string, replyId?: string) => void;
replaceEmojis: boolean;
disableMessagePreprocessing: boolean;
}

export const useEditorDraftState = ({editorRef, saveDraftState, replaceEmojis}: UseEditorDraftStateProps) => {
export const useEditorDraftState = ({
editorRef,
saveDraftState,
replaceEmojis,
disableMessagePreprocessing,
}: UseEditorDraftStateProps) => {
const saveDraft = useCallback(() => {
const editor = editorRef.current;
if (!editor) {
return;
}

editor.getEditorState().read(() => {
const markdown = $convertToMarkdownString(markdownTransformers, undefined, true);
const markdown = disableMessagePreprocessing
? ''
: $convertToMarkdownString(markdownTransformers, undefined, true);
saveDraftState(
JSON.stringify(editor.getEditorState().toJSON()),
transformMessage({replaceEmojis, markdown}),
disableMessagePreprocessing ? getRawMessageText() : transformMessage({replaceEmojis, markdown}),
undefined,
);
});
}, [editorRef, saveDraftState, replaceEmojis]);
}, [editorRef, saveDraftState, replaceEmojis, disableMessagePreprocessing]);

const debouncedSaveDraftState = useDebouncedCallback(saveDraft, DRAFT_SAVE_DELAY);

Expand Down
Loading
Loading