-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Sending URL Preview Bundles (MSC 4095) #34150
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
7d5158d
aba88dd
c683459
95b9402
659ac5c
e934938
7da1589
2575cdf
4a67924
baa2159
24fd7da
c297cd0
e1160f1
e9bdfd7
8c9e9e6
9bb8e9b
9ee43a4
58874c3
33507b3
2231506
3d5bc42
77fe2ab
4530182
ca37033
1965934
3b5d067
5d2b324
1a677e8
20e95b2
4d8a0b3
a137e1b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| /* | ||
| * Copyright 2026 Element Creations Ltd. | ||
| * | ||
| * SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial | ||
| * Please see LICENSE files in the repository root for full details. | ||
| */ | ||
|
|
||
| import { | ||
| type EncryptedFile, | ||
| type RoomMessageEventContent as SdkRoomMessageEventContent, | ||
| } from "matrix-js-sdk/src/types"; | ||
|
|
||
| /** | ||
| * Bundled URL previews in MSC-4095 | ||
| * | ||
| * @see https://github.com/matrix-org/matrix-spec-proposals/pull/4095 | ||
| */ | ||
| interface UnstableBundledUrlPreviews { | ||
| "com.beeper.linkpreviews"?: UnstableBundledUrlPreviewSingle[]; | ||
| } | ||
|
|
||
| /** | ||
| * Single item in bundled URL previews in MSC4095 | ||
| * | ||
| * @see https://github.com/matrix-org/matrix-spec-proposals/pull/4095 | ||
| */ | ||
| export interface UnstableBundledUrlPreviewSingle { | ||
| "matched_url": string; | ||
| "beeper:image:encryption"?: EncryptedFile; | ||
| "matrix:image:size"?: number; | ||
| "og:image"?: string; | ||
| "og:url"?: string; | ||
| "og:image:width"?: number; | ||
| "og:image:height"?: number; | ||
| "og:image:type"?: string; | ||
| "og:title"?: string; | ||
| "og:description"?: string; | ||
| } | ||
|
|
||
| export type RoomMessageEventContent = SdkRoomMessageEventContent & UnstableBundledUrlPreviews; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,7 @@ | |
| Please see LICENSE files in the repository root for full details. | ||
| */ | ||
|
|
||
| import React, { type JSX, createRef, type ReactNode } from "react"; | ||
| import React, { type JSX, createRef, type ReactNode, useEffect } from "react"; | ||
| import classNames from "classnames"; | ||
| import { | ||
| type IEventRelation, | ||
|
|
@@ -19,6 +19,7 @@ | |
| import { Tooltip } from "@vector-im/compound-web"; | ||
| import { logger } from "matrix-js-sdk/src/logger"; | ||
| import { LockOffIcon, SendSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; | ||
| import { useCreateAutoDisposedViewModel } from "@element-hq/web-shared-components"; | ||
|
|
||
| import { _t } from "../../../languageHandler"; | ||
| import { MatrixClientPeg } from "../../../MatrixClientPeg"; | ||
|
|
@@ -50,12 +51,19 @@ | |
| import { isLocalRoom } from "../../../utils/localRoom/isLocalRoom"; | ||
| import { type VoiceMessageRecording } from "../../../audio/VoiceMessageRecording"; | ||
| import { SendWysiwygComposer, sendMessage, getConversionFunctions } from "./wysiwyg_composer/"; | ||
| import { type MatrixClientProps, withMatrixClientHOC } from "../../../contexts/MatrixClientContext"; | ||
| import { | ||
| type MatrixClientProps, | ||
| withMatrixClientHOC, | ||
| useMatrixClientContext, | ||
| } from "../../../contexts/MatrixClientContext"; | ||
| import { UIFeature } from "../../../settings/UIFeature"; | ||
| import { formatTimeLeft } from "../../../DateUtils"; | ||
| import RoomReplacedSvg from "../../../../res/img/room_replaced.svg"; | ||
| import { MessageComposerUrlPreviewWrapper } from "./MessageComposerUrlPreview"; | ||
| import { Type } from "../../../editor/parts"; | ||
| import { MessageComposerUrlPreviewViewModel } from "../../../viewmodels/composer/MessageComposerUrlPreviewViewModel"; | ||
| import { useScopedRoomContext } from "../../../contexts/ScopedRoomContext"; | ||
| import PlatformPeg from "../../../PlatformPeg"; | ||
| import { useSettingValue } from "../../../hooks/useSettings"; | ||
|
|
||
| // The prefix used when persisting editor drafts to localstorage. | ||
| export const WYSIWYG_EDITOR_STATE_STORAGE_PREFIX = "mx_wysiwyg_state_"; | ||
|
|
@@ -88,6 +96,7 @@ | |
| relation?: IEventRelation; | ||
| e2eStatus?: E2EStatus; | ||
| compact?: boolean; | ||
| urlPreviewVm: MessageComposerUrlPreviewViewModel; | ||
| } | ||
|
|
||
| interface IState { | ||
|
|
@@ -103,8 +112,6 @@ | |
| isWysiwygLabEnabled: boolean; | ||
| isRichTextEnabled: boolean; | ||
| initialComposerContent: string; | ||
| // Specifically for generating previews only. | ||
| urlPreviewComposerContent: string; | ||
| } | ||
|
|
||
| type WysiwygComposerState = { | ||
|
|
@@ -146,7 +153,6 @@ | |
| this.state = { | ||
| isComposerEmpty: initialComposerContent?.length === 0, | ||
| composerContent: initialComposerContent, | ||
| urlPreviewComposerContent: initialComposerContent, | ||
| haveRecording: false, | ||
| recordingTimeLeftSeconds: undefined, // when set to a number, shows a toast | ||
| isMenuOpen: false, | ||
|
|
@@ -395,7 +401,7 @@ | |
| }; | ||
|
|
||
| private sendMessage = async (): Promise<void> => { | ||
| this.setState({ urlPreviewComposerContent: "" }); | ||
| this.props.urlPreviewVm.updateWithText({ content: "", debounced: false }); | ||
| if (this.state.haveRecording && this.voiceRecordingButton.current) { | ||
| // There shouldn't be any text message to send when a voice recording is active, so | ||
| // just send out the voice recording. | ||
|
|
@@ -418,25 +424,25 @@ | |
| roomContext: this.context, | ||
| relation, | ||
| replyToEvent, | ||
| urlPreviewSnapshot: this.props.urlPreviewVm.getSnapshot(), | ||
| }); | ||
| } | ||
| }; | ||
|
|
||
| private onChange = (model: EditorModel): void => { | ||
| this.props.urlPreviewVm.updateWithText({ | ||
| content: model.contentPlainText, | ||
| debounced: true, | ||
| }); | ||
| this.setState({ | ||
| urlPreviewComposerContent: model | ||
| .serializeParts() | ||
| .filter((part) => part.type === Type.Plain) | ||
| .map((part) => part.text) | ||
| .join(" "), | ||
| isComposerEmpty: model.isEmpty, | ||
| }); | ||
| }; | ||
|
|
||
| private onWysiwygChange = (content: string): void => { | ||
| this.props.urlPreviewVm.updateWithText({ content, debounced: true }); | ||
| this.setState({ | ||
| composerContent: content, | ||
| urlPreviewComposerContent: content, | ||
| isComposerEmpty: content?.length === 0, | ||
| }); | ||
| }; | ||
|
|
@@ -601,6 +607,7 @@ | |
| onChange={this.onChange} | ||
| disabled={this.state.haveRecording} | ||
| toggleStickerPickerOpen={this.toggleStickerPickerOpen} | ||
| urlPreviewVm={this.props.urlPreviewVm} | ||
| /> | ||
| ); | ||
| } | ||
|
|
@@ -686,7 +693,10 @@ | |
| return ( | ||
| <div className={classes} ref={this.ref} role="region" aria-label={_t("a11y|message_composer")}> | ||
| <div className="mx_MessageComposer_wrapper"> | ||
| <MessageComposerUrlPreviewWrapper content={this.state.urlPreviewComposerContent} /> | ||
| <MessageComposerUrlPreviewWrapper | ||
| content={this.props.urlPreviewVm.getSnapshot().content} | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prefer to use |
||
| urlPreviewVm={this.props.urlPreviewVm} | ||
| /> | ||
| <UserIdentityWarning room={this.props.room} key={this.props.room.roomId} /> | ||
| <ReplyPreview | ||
| replyToEvent={this.props.replyToEvent} | ||
|
|
@@ -735,4 +745,24 @@ | |
| } | ||
|
|
||
| const MessageComposerWithMatrixClient = withMatrixClientHOC(MessageComposer); | ||
| export default MessageComposerWithMatrixClient; | ||
|
|
||
| export default function MessageComposerWrapper(props: Omit<IProps, "mxClient" | "urlPreviewVm">): JSX.Element { | ||
|
Check warning on line 749 in apps/web/src/components/views/rooms/MessageComposer.tsx
|
||
| const { showUrlPreview } = useScopedRoomContext("showUrlPreview"); | ||
| const client = useMatrixClientContext(); | ||
| const urlPreviewBundle = useSettingValue("feature_msc4095_url_preview_bundle"); | ||
| const urlPreviewVm = useCreateAutoDisposedViewModel( | ||
| () => | ||
| new MessageComposerUrlPreviewViewModel({ | ||
| client, | ||
| visible: showUrlPreview, | ||
| showTooltips: PlatformPeg.get()?.needsUrlTooltips() ?? true, | ||
| urlPreviewBundle, | ||
| }), | ||
| ); | ||
|
|
||
| useEffect(() => { | ||
| void urlPreviewVm.updateUrlPreviewVisible(showUrlPreview); | ||
|
Check failure on line 764 in apps/web/src/components/views/rooms/MessageComposer.tsx
|
||
| }, [urlPreviewVm, showUrlPreview]); | ||
|
|
||
| return <MessageComposerWithMatrixClient {...props} urlPreviewVm={urlPreviewVm} />; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.