Render matrix.to user links as pills
This commit is contained in:
parent
78d7d556e4
commit
038b101ed7
6 changed files with 70 additions and 2 deletions
|
@ -1,4 +1,5 @@
|
||||||
import { linkify } from "./linkify/linkify.js";
|
import { linkify } from "./linkify/linkify.js";
|
||||||
|
import { getIdentifierColorNumber, avatarInitials } from "../../../avatar.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse text into parts such as newline, links and text.
|
* Parse text into parts such as newline, links and text.
|
||||||
|
@ -92,6 +93,24 @@ export class ImagePart {
|
||||||
get type() { return "image"; }
|
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 {
|
export class LinkPart {
|
||||||
constructor(url, inlines) {
|
constructor(url, inlines) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
|
|
|
@ -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 { linkify } from "./linkify/linkify.js";
|
||||||
|
import { parsePillLink } from "./pills.js"
|
||||||
|
|
||||||
/* At the time of writing (Jul 1 2021), Matrix Spec recommends
|
/* At the time of writing (Jul 1 2021), Matrix Spec recommends
|
||||||
* allowing the following HTML tags:
|
* allowing the following HTML tags:
|
||||||
|
@ -24,6 +25,10 @@ class Deserializer {
|
||||||
// TODO Not equivalent to `node.href`!
|
// TODO Not equivalent to `node.href`!
|
||||||
// Add another HTMLParseResult method?
|
// Add another HTMLParseResult method?
|
||||||
const href = this.result.getAttributeValue(node, "href");
|
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);
|
return new LinkPart(href, children);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
12
src/domain/session/room/timeline/pills.js
Normal file
12
src/domain/session/room/timeline/pills.js
Normal 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 }
|
||||||
|
}
|
||||||
|
}
|
|
@ -76,3 +76,11 @@ limitations under the License.
|
||||||
line-height: var(--avatar-size);
|
line-height: var(--avatar-size);
|
||||||
font-size: calc(var(--avatar-size) * 0.6);
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -216,6 +216,21 @@ only loads when the top comes into view*/
|
||||||
padding: 10px;
|
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 {
|
.Timeline_message.unsent .Timeline_messageBody {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {StaticView} from "../../../general/StaticView.js";
|
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";
|
import {BaseMessageView} from "./BaseMessageView.js";
|
||||||
|
|
||||||
export class TextMessageView extends BaseMessageView {
|
export class TextMessageView extends BaseMessageView {
|
||||||
|
@ -53,6 +53,14 @@ function renderImage(imagePart) {
|
||||||
return tag.img(attributes, []);
|
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
|
* Map from part to function that outputs DOM for the part
|
||||||
*/
|
*/
|
||||||
|
@ -63,6 +71,7 @@ const formatFunction = {
|
||||||
code: codePart => tag.code({}, text(codePart.text)),
|
code: codePart => tag.code({}, text(codePart.text)),
|
||||||
text: textPart => text(textPart.text),
|
text: textPart => text(textPart.text),
|
||||||
link: linkPart => tag.a({ href: linkPart.url, target: "_blank", rel: "noopener" }, renderParts(linkPart.inlines)),
|
link: linkPart => tag.a({ href: linkPart.url, target: "_blank", rel: "noopener" }, renderParts(linkPart.inlines)),
|
||||||
|
pill: renderPill,
|
||||||
format: formatPart => tag[formatPart.format]({}, renderParts(formatPart.children)),
|
format: formatPart => tag[formatPart.format]({}, renderParts(formatPart.children)),
|
||||||
list: renderList,
|
list: renderList,
|
||||||
image: renderImage,
|
image: renderImage,
|
||||||
|
|
Reference in a new issue