Will Hunt c02db4ebb8
Port over linkifyJS to shared-components. (#32731)
* Port over linkifyJS to shared-components.

* Drop rubbish

* update lock

* quickfix test

* drop group id

* Modernize tests

* Remove stories that aren't in use.

* Complete working version

* Add copyright

* tidy up

* update lock

* Update snaps

* update snap

* undo change

* remove unused

* More test updates

* fix typo

* fix margin on preview

* move margin block

* snapupdate

* prettier

* cleanup a test mistake

* Fixup sonar issues

* Don't expose linkifyjs to applications, just provide helper functions.

* Add story for documentation.

* remove $

* Use a const

* typo

* cleanup var name

* remove console line

* Changes checkpoint

* Convert to context

* Revert unrelated change.

* more cleanup

* Add a test to cover ignoring incoming data elements

* Make tests happy

* Update tests for LinkedText

* Underlines!

* fix lock

* remove unused linkify packages

* import move

* Remove mod to remove underline

* undo

* fix snap

* another snapshot fix

* Tidy up based on review.

* fix story

* Pass in args
2026-03-12 15:54:01 +00:00

92 lines
3.2 KiB
TypeScript

/*
* Copyright 2026 Element Creations Ltd.
* Copyright 2024 New Vector Ltd.
* Copyright 2023 The Matrix.org Foundation C.I.C.
* Copyright 2021 Šimon Brandner <simon.bra.ag@gmail.com>
*
* 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 IContent, type IEventRelation, type MatrixEvent, THREAD_RELATION_TYPE } from "matrix-js-sdk/src/matrix";
import sanitizeHtml from "sanitize-html";
import { PERMITTED_URL_SCHEMES } from "@element-hq/web-shared-components";
export function getParentEventId(ev?: MatrixEvent): string | undefined {
if (!ev || ev.isRedacted()) return;
if (ev.replyEventId) {
return ev.replyEventId;
}
}
// Part of Replies fallback support
export function stripPlainReply(body: string): string {
// Removes lines beginning with `> ` until you reach one that doesn't.
const lines = body.split("\n");
while (lines.length && lines[0].startsWith("> ")) lines.shift();
// Reply fallback has a blank line after it, so remove it to prevent leading newline
if (lines[0] === "") lines.shift();
return lines.join("\n");
}
// Part of Replies fallback support - MUST NOT BE RENDERED DIRECTLY - UNSAFE HTML
export function stripHTMLReply(html: string): string {
// Sanitize the original HTML for inclusion in <mx-reply>. We allow
// any HTML, since the original sender could use special tags that we
// don't recognize, but want to pass along to any recipients who do
// recognize them -- recipients should be sanitizing before displaying
// anyways. However, we sanitize to 1) remove any mx-reply, so that we
// don't generate a nested mx-reply, and 2) make sure that the HTML is
// properly formatted (e.g. tags are closed where necessary)
return sanitizeHtml(html, {
allowedTags: false, // false means allow everything
allowedAttributes: false,
allowVulnerableTags: true, // silence xss warning, we won't be rendering directly this, so it is safe to do
// we somehow can't allow all schemes, so we allow all that we
// know of and mxc (for img tags)
allowedSchemes: [...PERMITTED_URL_SCHEMES, "mxc"],
exclusiveFilter: (frame) => frame.tag === "mx-reply",
});
}
export function makeReplyMixIn(ev?: MatrixEvent): IEventRelation {
if (!ev) return {};
const mixin: IEventRelation = {
"m.in_reply_to": {
event_id: ev.getId(),
},
};
if (ev.threadRootId) {
mixin.is_falling_back = false;
}
return mixin;
}
export function shouldDisplayReply(event: MatrixEvent): boolean {
if (event.isRedacted()) {
return false;
}
const inReplyTo = event.getWireContent()?.["m.relates_to"]?.["m.in_reply_to"];
if (!inReplyTo) {
return false;
}
const relation = event.getRelation();
if (relation?.rel_type === THREAD_RELATION_TYPE.name && relation?.is_falling_back) {
return false;
}
return !!inReplyTo.event_id;
}
export function addReplyToMessageContent(content: IContent, replyToEvent: MatrixEvent): void {
content["m.relates_to"] = {
...(content["m.relates_to"] || {}),
...makeReplyMixIn(replyToEvent),
};
}