Redesign link previews (#33061)

* Commit design update

* Add figma links

* Check in other changes

* revert accidental change

* Iterative update

* linting n test fiddles

* linting

* Cleanup

* update snaps

* Move URL previews to new home

* Fix paths

* compress img

* Add back all the stories

* Improved rendering

* Fixup

* Update previews again

* lint

* update stories

* Update snaps again

* More screenshots

* Also these

* Update snaps

* include site name

* Update snaps again

* Use a scale so the images don't go blur

* update snaps again

* Update snaps

* remove mistaken playwright cfg

* update pw snaps

* update snap

* update previews

* Update with new designs

* Update screenshots
This commit is contained in:
Will Hunt
2026-04-22 13:23:24 +00:00
committed by GitHub
parent 2d16498fe6
commit 9df7182c0c
53 changed files with 1427 additions and 943 deletions
@@ -125,6 +125,32 @@ describe("UrlPreviewGroupViewModel", () => {
await vm.updateEventElement(msg);
expect(vm.getSnapshot()).toMatchSnapshot();
});
it.each<Partial<IPreviewUrlResponse>>([
{ "matrix:image:size": 8191 },
{ "og:image:width": 95 },
{ "og:image:height": 95 },
])("should preview a URL with a site icon", async (extraResp) => {
const { vm, client } = getViewModel();
client.getUrlPreview.mockResolvedValueOnce({
"og:title": "This is an example!",
"og:type": "document",
"og:url": "https://example.org",
"og:image": IMAGE_MXC,
"og:image:height": 128,
"og:image:width": 128,
"matrix:image:size": 8193,
...extraResp,
});
// eslint-disable-next-line no-restricted-properties
client.mxcUrlToHttp.mockImplementation((url) => {
expect(url).toEqual(IMAGE_MXC);
return "https://example.org/image/src";
});
const msg = document.createElement("div");
msg.innerHTML = '<a href="https://example.org">Test</a>';
await vm.updateEventElement(msg);
expect(vm.getSnapshot().previews[0].siteIcon).toBeTruthy();
});
it("should ignore media when mediaVisible is false", async () => {
const { vm, client } = getViewModel({ mediaVisible: false, visible: true });
client.getUrlPreview.mockResolvedValueOnce({
@@ -200,6 +226,41 @@ describe("UrlPreviewGroupViewModel", () => {
expect(vm.getSnapshot()).toMatchSnapshot();
});
describe("calculates author", () => {
it("should use the profile:username if provided", async () => {
const { vm, client } = getViewModel();
client.getUrlPreview.mockResolvedValueOnce({ ...BASIC_PREVIEW_OGDATA, "profile:username": "my username" });
const msg = document.createElement("div");
msg.innerHTML = '<a href="https://example.org">Test</a>';
await vm.updateEventElement(msg);
expect(vm.getSnapshot().previews[0].author).toEqual("my username");
});
it("should use author if the og:type is an article", async () => {
const { vm, client } = getViewModel();
client.getUrlPreview.mockResolvedValueOnce({
...BASIC_PREVIEW_OGDATA,
"og:type": "article",
"article:author": "my name",
});
const msg = document.createElement("div");
msg.innerHTML = '<a href="https://example.org">Test</a>';
await vm.updateEventElement(msg);
expect(vm.getSnapshot().previews[0].author).toEqual("my name");
});
it("should NOT use author if the author is a URL", async () => {
const { vm, client } = getViewModel();
client.getUrlPreview.mockResolvedValueOnce({
...BASIC_PREVIEW_OGDATA,
"og:type": "article",
"article:author": "https://junk.example.org/foo",
});
const msg = document.createElement("div");
msg.innerHTML = '<a href="https://example.org">Test</a>';
await vm.updateEventElement(msg);
expect(vm.getSnapshot().previews[0].author).toBeUndefined();
});
});
it.each([
{ text: "", href: "", hasPreview: false },
{ text: "test", href: "noprotocol.example.org", hasPreview: false },
@@ -232,7 +293,7 @@ describe("UrlPreviewGroupViewModel", () => {
// API *may* return a string, so check we parse correctly.
"og:image:height": "500" as unknown as number,
"og:image:width": 500,
"matrix:image:size": 1024,
"matrix:image:size": 10000,
"og:image": IMAGE_MXC,
},
])("handles different kinds of opengraph responses %s", async (og) => {
@@ -251,4 +312,25 @@ describe("UrlPreviewGroupViewModel", () => {
await vm.updateEventElement(msg);
expect(vm.getSnapshot().previews[0]).toMatchSnapshot();
});
it.each<string>(["og:video", "og:video:type", "og:audio"])("detects playable links via %s", async (property) => {
const { vm, client } = getViewModel();
// eslint-disable-next-line no-restricted-properties
client.mxcUrlToHttp.mockImplementation((url, width) => {
expect(url).toEqual(IMAGE_MXC);
if (width) {
return "https://example.org/image/thumb";
}
return "https://example.org/image/src";
});
client.getUrlPreview.mockResolvedValueOnce({
...BASIC_PREVIEW_OGDATA,
"og:image": IMAGE_MXC,
[property]: "anything",
});
const msg = document.createElement("div");
msg.innerHTML = `<a href="https://example.org">test</a>`;
await vm.updateEventElement(msg);
expect(vm.getSnapshot().previews[0].image?.playable).toEqual(true);
});
});
@@ -2,61 +2,73 @@
exports[`UrlPreviewGroupViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:description': 'A description',\\n 'og:title': ''\\n} 1`] = `
{
"author": undefined,
"description": undefined,
"image": undefined,
"link": "https://example.org",
"showTooltipOnLink": undefined,
"siteName": undefined,
"showTooltipOnLink": false,
"siteIcon": undefined,
"siteName": "example.org",
"title": "A description",
}
`;
exports[`UrlPreviewGroupViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:site_name': 'Site name',\\n 'og:title': ''\\n} 1`] = `
{
"author": undefined,
"description": undefined,
"image": undefined,
"link": "https://example.org",
"showTooltipOnLink": undefined,
"siteName": undefined,
"showTooltipOnLink": false,
"siteIcon": undefined,
"siteName": "Site name",
"title": "Site name",
}
`;
exports[`UrlPreviewGroupViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:title': 'Basic title'\\n} 1`] = `
{
"author": undefined,
"description": undefined,
"image": undefined,
"link": "https://example.org",
"showTooltipOnLink": undefined,
"siteName": undefined,
"showTooltipOnLink": false,
"siteIcon": undefined,
"siteName": "example.org",
"title": "Basic title",
}
`;
exports[`UrlPreviewGroupViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:title': 'Cool blog',\\n 'og:site_name': 'Cool site'\\n} 1`] = `
{
"author": undefined,
"description": undefined,
"image": undefined,
"link": "https://example.org",
"showTooltipOnLink": undefined,
"showTooltipOnLink": false,
"siteIcon": undefined,
"siteName": "Cool site",
"title": "Cool blog",
}
`;
exports[`UrlPreviewGroupViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:title': 'Media test',\\n 'og:image:height': '500',\\n 'og:image:width': 500,\\n 'matrix:image:size': 1024,\\n 'og:image': 'mxc://example.org/abc'\\n} 1`] = `
exports[`UrlPreviewGroupViewModel handles different kinds of opengraph responses {\\n 'og:url': 'https://example.org',\\n 'og:type': 'document',\\n 'og:title': 'Media test',\\n 'og:image:height': '500',\\n 'og:image:width': 500,\\n 'matrix:image:size': 10000,\\n 'og:image': 'mxc://example.org/abc'\\n} 1`] = `
{
"author": undefined,
"description": undefined,
"image": {
"fileSize": 1024,
"height": 100,
"alt": undefined,
"fileSize": 10000,
"height": 478,
"imageFull": "https://example.org/image/src",
"imageThumb": "https://example.org/image/thumb",
"width": 100,
"playable": false,
"width": 478,
},
"link": "https://example.org",
"showTooltipOnLink": undefined,
"siteName": undefined,
"showTooltipOnLink": false,
"siteIcon": undefined,
"siteName": "example.org",
"title": "Media test",
}
`;
@@ -67,10 +79,12 @@ exports[`UrlPreviewGroupViewModel should deduplicate multiple versions of the sa
"overPreviewLimit": false,
"previews": [
{
"author": undefined,
"description": "This is a description",
"image": undefined,
"link": "https://example.org",
"showTooltipOnLink": undefined,
"showTooltipOnLink": false,
"siteIcon": undefined,
"siteName": "Example.org",
"title": "This is an example!",
},
@@ -96,10 +110,12 @@ exports[`UrlPreviewGroupViewModel should handle being hidden and shown by the us
"overPreviewLimit": false,
"previews": [
{
"author": undefined,
"description": "This is a description",
"image": undefined,
"link": "https://example.org",
"showTooltipOnLink": undefined,
"showTooltipOnLink": false,
"siteIcon": undefined,
"siteName": "Example.org",
"title": "This is an example!",
},
@@ -135,11 +151,13 @@ exports[`UrlPreviewGroupViewModel should ignore media when mediaVisible is false
"overPreviewLimit": false,
"previews": [
{
"author": undefined,
"description": undefined,
"image": undefined,
"link": "https://example.org",
"showTooltipOnLink": undefined,
"siteName": undefined,
"showTooltipOnLink": false,
"siteIcon": undefined,
"siteName": "example.org",
"title": "This is an example!",
},
],
@@ -154,17 +172,21 @@ exports[`UrlPreviewGroupViewModel should preview a URL with media 1`] = `
"overPreviewLimit": false,
"previews": [
{
"author": undefined,
"description": undefined,
"image": {
"alt": undefined,
"fileSize": 10000,
"height": 100,
"height": 128,
"imageFull": "https://example.org/image/src",
"imageThumb": "https://example.org/image/thumb",
"width": 100,
"playable": false,
"width": 128,
},
"link": "https://example.org",
"showTooltipOnLink": undefined,
"siteName": undefined,
"showTooltipOnLink": false,
"siteIcon": undefined,
"siteName": "example.org",
"title": "This is an example!",
},
],
@@ -179,10 +201,12 @@ exports[`UrlPreviewGroupViewModel should preview a single valid URL 1`] = `
"overPreviewLimit": false,
"previews": [
{
"author": undefined,
"description": "This is a description",
"image": undefined,
"link": "https://example.org",
"showTooltipOnLink": undefined,
"showTooltipOnLink": false,
"siteIcon": undefined,
"siteName": "Example.org",
"title": "This is an example!",
},