import {LitElement, html, render, unsafeHTML} from './lit-all.min.js';
import * as tfrpc from '/static/tfrpc.js';
import * as tfutils from './tf-utils.js';
import * as emojis from './emojis.js';
import {styles} from './tf-styles.js';
class TfMessageElement extends LitElement {
static get properties() {
return {
whoami: {type: String},
message: {type: Object},
users: {type: Object},
drafts: {type: Object},
format: {type: String},
blog_data: {type: String},
expanded: {type: Object},
};
}
static styles = styles;
constructor() {
super();
let self = this;
this.whoami = null;
this.message = {};
this.users = {};
this.drafts = {};
this.format = 'message';
this.expanded = {};
}
show_reply() {
let event = new CustomEvent('tf-draft', {
bubbles: true,
composed: true,
detail: {
id: this.message?.id,
draft: {
encrypt_to: this.message?.decrypted?.recps,
},
},
});
this.dispatchEvent(event);
}
discard_reply() {
this.dispatchEvent(
new CustomEvent('tf-draft', {
bubbles: true,
composed: true,
detail: {id: this.id, draft: undefined},
})
);
}
show_reactions() {
let modal = document.getElementById('reactions_modal');
modal.users = this.users;
modal.votes = this.message?.votes || [];
}
render_votes() {
function normalize_expression(expression) {
if (expression === 'Like' || !expression) {
return '👍';
} else if (expression === 'Unlike') {
return '👎';
} else if (expression === 'heart') {
return '❤️';
} else {
return expression;
}
}
return html`
${(this.message.votes || []).map(
(vote) => html`
${normalize_expression(vote.content.vote.expression)}
`
)}
`;
}
render_raw() {
let raw = {
id: this.message?.id,
previous: this.message?.previous,
author: this.message?.author,
sequence: this.message?.sequence,
timestamp: this.message?.timestamp,
hash: this.message?.hash,
content: this.message?.content,
signature: this.message?.signature,
};
return html`
${JSON.stringify(raw, null, 2)}
`;
}
vote(emoji) {
let reaction = emoji;
let message = this.message.id;
if (
confirm(
'Are you sure you want to react with ' +
reaction +
' to ' +
message +
'?'
)
) {
tfrpc.rpc
.appendMessage(this.whoami, {
type: 'vote',
vote: {
link: message,
value: 1,
expression: reaction,
},
})
.catch(function (error) {
alert(error?.message);
});
}
}
react(event) {
emojis.picker((x) => this.vote(x), null, this.whoami);
}
show_image(link) {
let div = document.createElement('div');
div.style.left = 0;
div.style.top = 0;
div.style.width = '100%';
div.style.height = '100%';
div.style.position = 'fixed';
div.style.background = '#000';
div.style.zIndex = 100;
div.style.display = 'grid';
let img = document.createElement('img');
img.src = link;
img.style.maxWidth = '100%';
img.style.maxHeight = '100%';
img.style.display = 'block';
img.style.margin = 'auto';
img.style.objectFit = 'contain';
img.style.width = '100%';
div.appendChild(img);
function image_close(event) {
document.body.removeChild(div);
window.removeEventListener('keydown', image_close);
}
div.onclick = image_close;
window.addEventListener('keydown', image_close);
document.body.appendChild(div);
}
body_click(event) {
if (event.srcElement.tagName == 'IMG') {
this.show_image(event.srcElement.src);
} else if (
event.srcElement.tagName == 'DIV' &&
event.srcElement.classList.contains('img_caption')
) {
let next = event.srcElement.nextSibling;
if (next.style.display == 'block') {
next.style.display = 'none';
} else {
next.style.display = 'block';
}
}
}
render_mention(mention) {
if (!mention?.link || typeof mention.link != 'string') {
return html` ${JSON.stringify(mention)} `;
} else if (
mention?.link?.startsWith('&') &&
mention?.type?.startsWith('image/')
) {
return html`
this.show_image('/' + mention.link + '/view')}
/>
`;
} else if (
mention.link?.startsWith('&') &&
mention.name?.startsWith('audio:')
) {
return html`
`;
} else if (
mention.link?.startsWith('&') &&
mention.name?.startsWith('video:')
) {
return html`
`;
} else if (
mention.link?.startsWith('&') &&
mention?.type === 'application/tildefriends'
) {
return html` 😎 ${mention.name} `;
} else if (mention.link?.startsWith('%') || mention.link?.startsWith('@')) {
return html` ${mention.name} `;
} else if (mention.link?.startsWith('#')) {
return html` ${mention.link} `;
} else if (
Object.keys(mention).length == 2 &&
mention.link &&
mention.name
) {
return html` ${mention.name} `;
} else {
return html`
${JSON.stringify(mention, null, 2)} `;
}
}
render_mentions() {
let mentions = this.message?.content?.mentions || [];
mentions = mentions.filter(
(x) => this.message?.content?.text?.indexOf(x.link) === -1
);
if (mentions.length) {
let self = this;
return html`
Mentions
${mentions.map((x) => self.render_mention(x))}
`;
}
}
total_child_messages(message) {
if (!message.child_messages) {
return 0;
}
let total = message.child_messages.length;
for (let m of message.child_messages) {
total += this.total_child_messages(m);
}
return total;
}
set_expanded(expanded, tag) {
this.dispatchEvent(
new CustomEvent('tf-expand', {
bubbles: true,
composed: true,
detail: {id: (this.message.id || '') + (tag || ''), expanded: expanded},
})
);
}
toggle_expanded(tag) {
this.set_expanded(
!this.expanded[(this.message.id || '') + (tag || '')],
tag
);
}
render_children() {
let self = this;
if (this.message.child_messages?.length) {
if (!this.expanded[this.message.id]) {
return html` self.set_expanded(true)}
>
+ ${this.total_child_messages(this.message) + ' More'}
`;
} else {
return html` self.set_expanded(false)}
>
Collapse ${(this.message.child_messages || []).map(
(x) =>
html` `
)}`;
}
}
}
render_channels() {
let content = this.message?.content;
if (this?.messsage?.decrypted?.type == 'post') {
content = this.message.decrypted;
}
let channels = [];
if (typeof content.channel === 'string') {
channels.push(`#${content.channel}`);
}
if (Array.isArray(content.mentions)) {
for (let mention of content.mentions) {
if (typeof mention?.link === 'string' && mention.link.startsWith('#')) {
channels.push(mention.link);
}
}
}
return channels.map((x) => html` `);
}
render() {
let content = this.message?.content;
if (this.message?.decrypted?.type == 'post') {
content = this.message.decrypted;
}
let self = this;
let raw_button;
switch (this.format) {
case 'raw':
if (content?.type == 'post' || content?.type == 'blog') {
raw_button = html` (self.format = 'md')}
>
Markdown
`;
} else {
raw_button = html` (self.format = 'message')}
>
Message
`;
}
break;
case 'md':
raw_button = html` (self.format = 'message')}
>
Message
`;
break;
case 'decrypted':
raw_button = html` (self.format = 'raw')}
>
Raw
`;
break;
default:
if (this.message.decrypted) {
raw_button = html` (self.format = 'decrypted')}
>
Decrypted
`;
} else {
raw_button = html` (self.format = 'raw')}
>
Raw
`;
}
break;
}
function small_frame(inner) {
let body;
return html`
% ${new Date(
self.message.timestamp
).toLocaleString()}
${raw_button} ${self.format == 'raw' ? self.render_raw() : inner}
${self.render_votes()}
`;
}
if (this.message?.type === 'contact_group') {
return html`
${this.message.messages.map(
(x) =>
html` `
)}
`;
} else if (this.message.placeholder) {
return html`
${this.message.id}
(placeholder)
${this.render_votes()}
${(this.message.child_messages || []).map(
(x) => html`
`
)}
`;
} else if (typeof (content?.type === 'string')) {
if (content.type == 'about') {
let name;
let image;
let description;
if (content.name !== undefined) {
name = html`Name: ${content.name}
`;
}
if (content.image !== undefined) {
image = html`
`;
}
if (content.description !== undefined) {
description = html`
${unsafeHTML(tfutils.markdown(content.description))}
`;
}
let update =
content.about == this.message.author
? html`Updated profile.
`
: html`
Updated profile for
.
`;
return small_frame(html` ${update} ${name} ${image} ${description} `);
} else if (content.type == 'contact') {
return html`
is
${content.blocking === true
? 'blocking'
: content.blocking === false
? 'no longer blocking'
: content.following === true
? 'following'
: content.following === false
? 'no longer following'
: '?'}
`;
} else if (content.type == 'post') {
let reply =
this.drafts[this.message?.id] !== undefined
? html`
`
: html`
Reply
`;
let self = this;
let body;
switch (this.format) {
case 'raw':
body = this.render_raw();
break;
case 'md':
body = html`${content.text}
`;
break;
case 'message':
body = unsafeHTML(tfutils.markdown(content.text));
break;
case 'decrypted':
body = html`
${JSON.stringify(content, null, 2)} `;
break;
}
let content_warning = html`
this.toggle_expanded(':cw')}
>
${content.contentWarning}
`;
let content_html = html`
${this.render_channels()}
${body}
${this.render_mentions()}
`;
let payload = content.contentWarning
? self.expanded[(this.message.id || '') + ':cw']
? html` ${content_warning} ${content_html} `
: content_warning
: content_html;
let is_encrypted = this.message?.decrypted
? html`🔓 `
: undefined;
let style_background = this.message?.decrypted
? 'background-color: rgba(255, 0, 0, 0.2)'
: 'backdrop-filter: brightness(1.2)';
return html`
${is_encrypted}
%
${new Date(this.message.timestamp).toLocaleString()}
${raw_button}
${payload} ${this.render_votes()}
${reply}
React
${this.render_children()}
`;
} else if (content.type === 'issue') {
let is_encrypted = this.message?.decrypted
? html`🔓 `
: undefined;
let style_background = this.message?.decrypted
? 'background: rgba(255, 0, 0, 0.2)'
: 'backdrop-filter: brightness(1.2)';
return html`
${is_encrypted}
%
${new Date(this.message.timestamp).toLocaleString()}
${raw_button}
${content.text} ${this.render_votes()}
React
${this.render_children()}
`;
} else if (content.type === 'blog') {
let self = this;
tfrpc.rpc.get_blob(content.blog).then(function (data) {
self.blog_data = data;
});
let payload = this.expanded[(this.message.id || '') + ':blog']
? html`
${this.blog_data
? unsafeHTML(tfutils.markdown(this.blog_data))
: 'Loading...'}
`
: undefined;
let body;
switch (this.format) {
case 'raw':
body = this.render_raw();
break;
case 'md':
body = content.summary;
break;
case 'message':
body = html`
self.toggle_expanded(':blog')}>
${content.title}
${content.summary}
${payload}
`;
break;
}
let reply =
this.drafts[this.message?.id] !== undefined
? html`
`
: html`
Reply
`;
return html`
%
${new Date(this.message.timestamp).toLocaleString()}
${raw_button}
${body}
${this.render_mentions()}
${reply}
React
${this.render_votes()} ${this.render_children()}
`;
} else if (content.type === 'pub') {
return small_frame(
html`
🍻
${content.address.host}:${content.address.port}
`
);
} else if (content.type === 'channel') {
return small_frame(html`
`);
} else if (typeof this.message.content == 'string') {
if (this.message?.decrypted) {
if (this.format == 'decrypted') {
return small_frame(
html`🔓
${JSON.stringify(this.message.decrypted, null, 2)} `
);
} else {
return small_frame(
html`🔓
${this.message.decrypted.type}
`
);
}
} else {
return small_frame(html`🔒 `);
}
} else {
return small_frame(html`type : ${content.type}
`);
}
} else {
return small_frame(this.render_raw());
}
}
}
customElements.define('tf-message', TfMessageElement);