Render matrix.to user links as pills

This commit is contained in:
Danila Fedorin 2021-07-14 17:57:53 -07:00
parent 78d7d556e4
commit 038b101ed7
6 changed files with 70 additions and 2 deletions

View file

@ -1,4 +1,5 @@
import { linkify } from "./linkify/linkify.js";
import { getIdentifierColorNumber, avatarInitials } from "../../../avatar.js";
/**
* Parse text into parts such as newline, links and text.
@ -92,6 +93,24 @@ export class ImagePart {
get type() { return "image"; }
};
export class PillPart {
constructor(id, href, children) {
this.id = id;
this.href = href;
this.children = children;
}
get type() { return "pill"; }
get avatarColorNumber() {
return getIdentifierColorNumber(this.id);
}
get avatarInitials() {
return avatarInitials(this.id);
}
}
export class LinkPart {
constructor(url, inlines) {
this.url = url;

View file

@ -1,5 +1,6 @@
import { MessageBody, HeaderBlock, ListBlock, CodeBlock, FormatPart, NewLinePart, RulePart, TextPart, LinkPart, ImagePart } from "./MessageBody.js"
import { MessageBody, HeaderBlock, ListBlock, CodeBlock, PillPart, FormatPart, NewLinePart, RulePart, TextPart, LinkPart, ImagePart } from "./MessageBody.js"
import { linkify } from "./linkify/linkify.js";
import { parsePillLink } from "./pills.js"
/* At the time of writing (Jul 1 2021), Matrix Spec recommends
* allowing the following HTML tags:
@ -24,6 +25,10 @@ class Deserializer {
// TODO Not equivalent to `node.href`!
// Add another HTMLParseResult method?
const href = this.result.getAttributeValue(node, "href");
const pillData = href && parsePillLink(href);
if (pillData && pillData.userId) {
return new PillPart(pillData.userId, href, children);
}
return new LinkPart(href, children);
}

View file

@ -0,0 +1,12 @@
const baseUrl = 'https://matrix.to';
const linkPrefix = `${baseUrl}/#/`;
export function parsePillLink(link) {
if (!link.startsWith(linkPrefix)) {
return null;
}
const contents = link.substring(linkPrefix.length);
if (contents[0] === '@') {
return { userId: contents }
}
}

View file

@ -76,3 +76,11 @@ limitations under the License.
line-height: var(--avatar-size);
font-size: calc(var(--avatar-size) * 0.6);
}
.hydrogen .avatar.size-12 {
--avatar-size: 12px;
width: var(--avatar-size);
height: var(--avatar-size);
line-height: var(--avatar-size);
font-size: calc(var(--avatar-size) * 0.6);
}

View file

@ -216,6 +216,21 @@ only loads when the top comes into view*/
padding: 10px;
}
.Timeline_messageBody .pill {
padding: 0px 5px 0px 5px;
border-radius: 15px;
background-color: #f6f6f6;
border: 1px solid rgb(206, 206, 206);
text-decoration: none;
display: inline-flex;
align-items: center;
}
.Timeline_messageBody .pill div.avatar {
display: inline-block;
margin-right: 3px;
}
.Timeline_message.unsent .Timeline_messageBody {
color: #ccc;
}

View file

@ -15,7 +15,7 @@ limitations under the License.
*/
import {StaticView} from "../../../general/StaticView.js";
import {tag, text} from "../../../general/html.js";
import {tag, text, classNames} from "../../../general/html.js";
import {BaseMessageView} from "./BaseMessageView.js";
export class TextMessageView extends BaseMessageView {
@ -53,6 +53,14 @@ function renderImage(imagePart) {
return tag.img(attributes, []);
}
function renderPill(pillPart) {
const classes = `avatar size-12 usercolor${pillPart.avatarColorNumber}`;
const avatar = tag.div({class: classes}, text(pillPart.avatarInitials));
const children = renderParts(pillPart.children);
children.unshift(avatar);
return tag.a({ class: "pill", href: pillPart.href }, children);
}
/**
* Map from part to function that outputs DOM for the part
*/
@ -63,6 +71,7 @@ const formatFunction = {
code: codePart => tag.code({}, text(codePart.text)),
text: textPart => text(textPart.text),
link: linkPart => tag.a({ href: linkPart.url, target: "_blank", rel: "noopener" }, renderParts(linkPart.inlines)),
pill: renderPill,
format: formatPart => tag[formatPart.format]({}, renderParts(formatPart.children)),
list: renderList,
image: renderImage,