I think this added following and blocking.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@3975 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
parent
ee1e1b11af
commit
52f5bb408f
@ -1 +1 @@
|
|||||||
{"type":"tildefriends-app","files":{"app.js":"&rLwYqurncmnUyGeWY+FLEGS2EIJmqw2cutl1gyGiVSk=.sha256","index.md":"&082vPjenwI6mL2vXwQDVEFquyl2jW9t767sGuCFvVNA=.sha256","todo.md":"&u4lmFlYFB5zQNfVXVB8t8NMT2jFAeE8ivWfwIiiTKxQ=.sha256","structure.md":"&T+CBfT9XP6ooKFvD1ZCI9hsutqsNIamfBxtAho0HtlU=.sha256","guide.md":"&SgnGL0+rjetY2o9A2+lVRbNvHIkqKwMnZr9gXWneIlc=.sha256","id_refactor.md":"&8yoYd14gX2Z3ppktVrPYf4qR78fuwAlvrtsWkSCkWUA=.sha256","ssb.md":"&WMvYIpp4CMZACwXJlX8HMDplJ+XeJB04BYf8zasrL4g=.sha256"}}
|
{"type":"tildefriends-app","files":{"app.js":"&rLwYqurncmnUyGeWY+FLEGS2EIJmqw2cutl1gyGiVSk=.sha256","index.md":"&082vPjenwI6mL2vXwQDVEFquyl2jW9t767sGuCFvVNA=.sha256","todo.md":"&+z52vxpbZs5+HoLnQoDNkYt4objcPwF7F1PIwvZ3E3k=.sha256","structure.md":"&T+CBfT9XP6ooKFvD1ZCI9hsutqsNIamfBxtAho0HtlU=.sha256","guide.md":"&SgnGL0+rjetY2o9A2+lVRbNvHIkqKwMnZr9gXWneIlc=.sha256","id_refactor.md":"&8yoYd14gX2Z3ppktVrPYf4qR78fuwAlvrtsWkSCkWUA=.sha256","ssb.md":"&WMvYIpp4CMZACwXJlX8HMDplJ+XeJB04BYf8zasrL4g=.sha256"}}
|
@ -9,7 +9,6 @@
|
|||||||
- update README
|
- update README
|
||||||
- update docs
|
- update docs
|
||||||
- audit + document API exposed to apps
|
- audit + document API exposed to apps
|
||||||
- emoji reaction picker
|
|
||||||
- fix weird HTTP warnings
|
- fix weird HTTP warnings
|
||||||
- ssb from child process?
|
- ssb from child process?
|
||||||
- channels
|
- channels
|
||||||
@ -26,6 +25,8 @@
|
|||||||
- jwt for session tokens
|
- jwt for session tokens
|
||||||
|
|
||||||
## Maybe Done
|
## Maybe Done
|
||||||
|
- linkify https://...
|
||||||
|
- emoji reaction picker
|
||||||
- expose loads of stats
|
- expose loads of stats
|
||||||
- confirm posting all new messages
|
- confirm posting all new messages
|
||||||
- multiple identities per user, in database
|
- multiple identities per user, in database
|
||||||
|
@ -1 +1 @@
|
|||||||
{"type":"tildefriends-app","files":{"app.js":"&Y01AAZJWUjOXzzcIPHTzeEWvgrBsBgcL34QcNdOtLpA=.sha256","lit-all.min.js":"&N4A12AsifdQgwdpII0SFtG513BfoLpmPjdJ9VTDftpg=.sha256","index.html":"&NQfp81Ve+FpMPRzPS1UcoXEkn7BW+yz/XArGQbLSmPg=.sha256","script.js":"&vnCSRIvjb0kS+QOmkJP+ISB6wJdXDp/lOn6FJn2esKk=.sha256","lit-all.min.js.map":"&oFY9wO4MnujgfGNGv4VggHc5V5JwX4C8csqKZ6KJYbE=.sha256","tf-id-picker.js":"&ewIlLZNhaHm2dztxqj2Ft38WZkNPQxYfOGBrwTDUhds=.sha256","tf-app.js":"&HOqvQvHjzGv94YSqPQWVOr9fTNMVRZk+vO7Dd+/LcEA=.sha256","tf-message.js":"&E98rTMtN1Ok3gBVbe54uqv6P45wHoMicdA/+gHVP7BM=.sha256","tf-user.js":"&hsIveVMRVMRNJfrTN1hkVQgO4VdRurMATfV2EXnIk/0=.sha256","tf-utils.js":"&MPINm55jkpz2rrNbwsYl09PKGvbgL3nwgBy6CMQkSnw=.sha256","commonmark.min.js":"&bfBaMLU19d1p/vPBF9hlARqDX002KXG/UOfxOahZhe4=.sha256","tf-compose.js":"&oo0iWvT+c2rU91zWpBIfPePRzmU8qmSnVOm+QCQqG/I=.sha256","emojis.json":"&h3P4pez+AI4aYdsN0dJ3pbUEFR0276t9AM20caj/W/s=.sha256","emojis.js":"&htPMi2z6Bmgi3f9jCnECCDZRCHACnDRjOl1kgPm+W80=.sha256","tf-styles.js":"&BkvFkMpGyL0DYP6FISFKR4pe6ZBOp8t6tQEzWZ4IQYs=.sha256","tf-profile.js":"&OmDTn4Bhu6kV4PzJ0wfaExyuLOO/7bPmbRNHD5yp02w=.sha256"}}
|
{"type":"tildefriends-app","files":{"app.js":"&b8IFBOMDtcvY5XNtUQIUeoE+++/TO8LDp86xNFIaux8=.sha256","lit-all.min.js":"&N4A12AsifdQgwdpII0SFtG513BfoLpmPjdJ9VTDftpg=.sha256","index.html":"&WH8A5tF25xlfPDGei2TCQc2/HJFJf5DuRN1GRSYQhhk=.sha256","script.js":"&diQfpbxjgd/jSPnIoAoWT75B8Pll1I5JYXhu+/phj9k=.sha256","lit-all.min.js.map":"&oFY9wO4MnujgfGNGv4VggHc5V5JwX4C8csqKZ6KJYbE=.sha256","tf-id-picker.js":"&ewIlLZNhaHm2dztxqj2Ft38WZkNPQxYfOGBrwTDUhds=.sha256","tf-app.js":"&4Z6k1bR9LUPUZGyJTEKOqPkNKqHtnvG8ScgkFoSTf1Y=.sha256","tf-message.js":"&KE1fWTqPMZR0yIRXPBGy8u1chR6LTguSK6swo+lFgE4=.sha256","tf-user.js":"&L6+7BnBq+UOoTMO6o8+u5JFTl0UBtCPDw8bb8ppDrkA=.sha256","tf-utils.js":"&N2yKZwFnb2GbPeipgQtu6xFvezENNOgud9G7EhCQ/K0=.sha256","commonmark.min.js":"&bfBaMLU19d1p/vPBF9hlARqDX002KXG/UOfxOahZhe4=.sha256","tf-compose.js":"&oo0iWvT+c2rU91zWpBIfPePRzmU8qmSnVOm+QCQqG/I=.sha256","emojis.json":"&h3P4pez+AI4aYdsN0dJ3pbUEFR0276t9AM20caj/W/s=.sha256","emojis.js":"&htPMi2z6Bmgi3f9jCnECCDZRCHACnDRjOl1kgPm+W80=.sha256","tf-styles.js":"&BkvFkMpGyL0DYP6FISFKR4pe6ZBOp8t6tQEzWZ4IQYs=.sha256","tf-profile.js":"&vRKjsnYvOiHCQahzEfznCvP5YDwUPtltlpWf+pxwZ1Y=.sha256","commonmark-linkify.js":"&X+hNNkmSRvKY86khyAun+cXksquXbMakZdINbGbx30g=.sha256","tf-connections.js":"&YUD4n/r95AwD2fA63HHE2eQt4E/27gF+4/MYrdvoasw=.sha256"}}
|
@ -18,6 +18,15 @@ tfrpc.register(async function databaseSet(key, value) {
|
|||||||
tfrpc.register(async function getIdentities() {
|
tfrpc.register(async function getIdentities() {
|
||||||
return ssb.getIdentities();
|
return ssb.getIdentities();
|
||||||
});
|
});
|
||||||
|
tfrpc.register(async function getAllIdentities() {
|
||||||
|
return ssb.getAllIdentities();
|
||||||
|
});
|
||||||
|
tfrpc.register(async function getBroadcasts() {
|
||||||
|
return ssb.getBroadcasts();
|
||||||
|
});
|
||||||
|
tfrpc.register(async function getConnections() {
|
||||||
|
return ssb.connections();
|
||||||
|
});
|
||||||
tfrpc.register(async function query(sql, args) {
|
tfrpc.register(async function query(sql, args) {
|
||||||
let result = [];
|
let result = [];
|
||||||
await ssb.sqlStream(sql, args, function callback(row) {
|
await ssb.sqlStream(sql, args, function callback(row) {
|
||||||
@ -46,6 +55,13 @@ tfrpc.register(async function store_blob(blob) {
|
|||||||
}
|
}
|
||||||
return await ssb.blobStore(blob);
|
return await ssb.blobStore(blob);
|
||||||
});
|
});
|
||||||
|
ssb.addEventListener('broadcasts', async function() {
|
||||||
|
await tfrpc.rpc.set('broadcasts', await ssb.getBroadcasts());
|
||||||
|
});
|
||||||
|
|
||||||
|
core.register('onConnectionsChanged', async function() {
|
||||||
|
await tfrpc.rpc.set('connections', await ssb.connections());
|
||||||
|
});
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
if (typeof(database) !== 'undefined') {
|
if (typeof(database) !== 'undefined') {
|
||||||
|
91
apps/cory/ssblit/commonmark-linkify.js
Normal file
91
apps/cory/ssblit/commonmark-linkify.js
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
function textNode(text) {
|
||||||
|
const node = new commonmark.Node("text", undefined);
|
||||||
|
node.literal = text;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
function linkNode(text, url) {
|
||||||
|
const urlNode = new commonmark.Node("link", undefined);
|
||||||
|
urlNode.destination = url;
|
||||||
|
urlNode.appendChild(textNode(text));
|
||||||
|
|
||||||
|
return urlNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitMatches(text, regexp) {
|
||||||
|
// Regexp must be sticky.
|
||||||
|
regexp = new RegExp(regexp, "gm");
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
let match = regexp.exec(text);
|
||||||
|
while (match) {
|
||||||
|
const matchText = match[0];
|
||||||
|
|
||||||
|
if (match.index > i) {
|
||||||
|
result.push([text.substring(i, match.index), false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push([matchText, true]);
|
||||||
|
i = match.index + matchText.length;
|
||||||
|
|
||||||
|
match = regexp.exec(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < text.length) {
|
||||||
|
result.push([text.substring(i, text.length), false]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlRegexp = new RegExp("https?://[^ ]+[^ .,]");
|
||||||
|
|
||||||
|
function splitURLs(textNodes) {
|
||||||
|
const text = textNodes.map(n => n.literal).join("");
|
||||||
|
const parts = splitMatches(text, urlRegexp);
|
||||||
|
|
||||||
|
return parts.map(part => {
|
||||||
|
if (part[1]) {
|
||||||
|
return linkNode(part[0], part[0]);
|
||||||
|
} else {
|
||||||
|
return textNode(part[0]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function transform(parsed) {
|
||||||
|
const walker = parsed.walker();
|
||||||
|
let event;
|
||||||
|
|
||||||
|
let nodes = [];
|
||||||
|
while ((event = walker.next())) {
|
||||||
|
const node = event.node;
|
||||||
|
if (event.entering && node.type === "text") {
|
||||||
|
nodes.push(node);
|
||||||
|
} else {
|
||||||
|
if (nodes.length > 0) {
|
||||||
|
splitURLs(nodes)
|
||||||
|
.reverse()
|
||||||
|
.forEach(newNode => {
|
||||||
|
nodes[0].insertAfter(newNode);
|
||||||
|
});
|
||||||
|
|
||||||
|
nodes.forEach(n => n.unlink());
|
||||||
|
nodes = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodes.length > 0) {
|
||||||
|
splitURLs(nodes)
|
||||||
|
.reverse()
|
||||||
|
.forEach(newNode => {
|
||||||
|
nodes[0].insertAfter(newNode);
|
||||||
|
});
|
||||||
|
nodes.forEach(n => n.unlink());
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsed;
|
||||||
|
}
|
@ -2,12 +2,14 @@
|
|||||||
<html style="color: #fff">
|
<html style="color: #fff">
|
||||||
<head>
|
<head>
|
||||||
<title>Tilde Friends</title>
|
<title>Tilde Friends</title>
|
||||||
|
<base target="_top">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1>Tilde Friends</h1>
|
<h1>Tilde Friends</h1>
|
||||||
<tf-app/>
|
<tf-app/>
|
||||||
<script>window.litDisableBundleWarning = true;</script>
|
<script>window.litDisableBundleWarning = true;</script>
|
||||||
<script src="commonmark.min.js"></script>
|
<script src="commonmark.min.js"></script>
|
||||||
|
<script src="commonmark-linkify.js" type="module"></script>
|
||||||
<script src="script.js" type="module"></script>
|
<script src="script.js" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -7,3 +7,4 @@ import * as tf_message from './tf-message.js';
|
|||||||
import * as tf_user from './tf-user.js';
|
import * as tf_user from './tf-user.js';
|
||||||
import * as tf_compose from './tf-compose.js';
|
import * as tf_compose from './tf-compose.js';
|
||||||
import * as tf_profile from './tf-profile.js';
|
import * as tf_profile from './tf-profile.js';
|
||||||
|
import * as tf_connections from './tf-connections.js';
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import {LitElement, html, css} from './lit-all.min.js';
|
import {LitElement, html, css} from './lit-all.min.js';
|
||||||
import * as tfrpc from '/static/tfrpc.js';
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
|
import {styles} from './tf-styles.js';
|
||||||
|
|
||||||
class TfElement extends LitElement {
|
class TfElement extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
@ -12,9 +13,14 @@ class TfElement extends LitElement {
|
|||||||
status: {type: Array},
|
status: {type: Array},
|
||||||
hash: {type: String},
|
hash: {type: String},
|
||||||
unread: {type: Array},
|
unread: {type: Array},
|
||||||
|
tab: {type: String},
|
||||||
|
broadcasts: {type: Array},
|
||||||
|
connections: {type: Array},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static styles = styles;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
let self = this;
|
let self = this;
|
||||||
@ -27,7 +33,12 @@ class TfElement extends LitElement {
|
|||||||
this.hash = '#';
|
this.hash = '#';
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.unread = [];
|
this.unread = [];
|
||||||
|
this.tab = 'news';
|
||||||
|
this.broadcasts = [];
|
||||||
|
this.connections = [];
|
||||||
tfrpc.rpc.getIdentities().then(ids => { self.ids = ids || [] });
|
tfrpc.rpc.getIdentities().then(ids => { self.ids = ids || [] });
|
||||||
|
tfrpc.rpc.getBroadcasts().then(b => { self.broadcasts = b || [] });
|
||||||
|
tfrpc.rpc.getConnections().then(c => { self.connections = c || [] });
|
||||||
tfrpc.rpc.getHash().then(hash => self.hash = hash || '#');
|
tfrpc.rpc.getHash().then(hash => self.hash = hash || '#');
|
||||||
tfrpc.register(function hashChanged(hash) {
|
tfrpc.register(function hashChanged(hash) {
|
||||||
self.hash = hash;
|
self.hash = hash;
|
||||||
@ -36,6 +47,13 @@ class TfElement extends LitElement {
|
|||||||
tfrpc.register(async function notifyNewMessage(id) {
|
tfrpc.register(async function notifyNewMessage(id) {
|
||||||
await self.fetch_new_message(id);
|
await self.fetch_new_message(id);
|
||||||
});
|
});
|
||||||
|
tfrpc.register(function set(name, value) {
|
||||||
|
if (name === 'broadcasts') {
|
||||||
|
self.broadcasts = value;
|
||||||
|
} else if (name === 'connections') {
|
||||||
|
self.connections = value;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async contacts_internal(id, last_row_id, following, max_row_id) {
|
async contacts_internal(id, last_row_id, following, max_row_id) {
|
||||||
@ -288,6 +306,9 @@ class TfElement extends LitElement {
|
|||||||
function link_message(message) {
|
function link_message(message) {
|
||||||
if (message.content.type === 'vote') {
|
if (message.content.type === 'vote') {
|
||||||
let parent = self.ensure_message(message.content.vote.link);
|
let parent = self.ensure_message(message.content.vote.link);
|
||||||
|
if (!parent.votes) {
|
||||||
|
parent.votes = [];
|
||||||
|
}
|
||||||
parent.votes.push(message);
|
parent.votes.push(message);
|
||||||
message.parent_message = message.content.vote.link;
|
message.parent_message = message.content.vote.link;
|
||||||
} else if (message.content.type == 'post') {
|
} else if (message.content.type == 'post') {
|
||||||
@ -373,8 +394,11 @@ class TfElement extends LitElement {
|
|||||||
if (this.loading || (!this.whoami && this.ids.length)) {
|
if (this.loading || (!this.whoami && this.ids.length)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let load_button = this.renderRoot.getElementById('load_button');
|
||||||
this.loading = true;
|
this.loading = true;
|
||||||
this.renderRoot.getElementById('load_button').disabled = true;
|
if (load_button) {
|
||||||
|
load_button.disabled = true;
|
||||||
|
}
|
||||||
this.status = [];
|
this.status = [];
|
||||||
this.messages = [];
|
this.messages = [];
|
||||||
this.messages_by_id = {};
|
this.messages_by_id = {};
|
||||||
@ -392,7 +416,9 @@ class TfElement extends LitElement {
|
|||||||
await this.finalize_messages();
|
await this.finalize_messages();
|
||||||
this.record_status('done');
|
this.record_status('done');
|
||||||
this.status = [];
|
this.status = [];
|
||||||
this.renderRoot.getElementById('load_button').disabled = false;
|
if (load_button) {
|
||||||
|
load_button.disabled = false;
|
||||||
|
}
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -402,9 +428,16 @@ class TfElement extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
let self = this;
|
||||||
|
let tabs = html`
|
||||||
|
<div>
|
||||||
|
<input type="button" value="News" ?disabled=${self.tab == 'news'} @click=${event => self.tab = 'news'}></input>
|
||||||
|
<input type="button" value="Connections" ?disabled=${self.tab == 'connections'} @click=${event => self.tab = 'connections'}></input>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
let profile = this.hash.startsWith('#@') ?
|
let profile = this.hash.startsWith('#@') ?
|
||||||
html`<tf-profile id=${this.hash.substring(1)} .users=${this.users}></tf-profile>` : undefined;
|
html`<tf-profile id=${this.hash.substring(1)} whoami=${this.whoami} .users=${this.users}></tf-profile>` : undefined;
|
||||||
return html`
|
let news = html`
|
||||||
<tf-id-picker id="picker" .ids=${this.ids} @change=${this._handle_whoami_changed}></tf-id-picker>
|
<tf-id-picker id="picker" .ids=${this.ids} @change=${this._handle_whoami_changed}></tf-id-picker>
|
||||||
<button id="load_button" @click=${this.load}>Load</button>
|
<button id="load_button" @click=${this.load}>Load</button>
|
||||||
<a target="_top" href="#" ?hidden=${this.hash.length <= 1}>🏠Home</a>
|
<a target="_top" href="#" ?hidden=${this.hash.length <= 1}>🏠Home</a>
|
||||||
@ -415,6 +448,14 @@ class TfElement extends LitElement {
|
|||||||
${profile}
|
${profile}
|
||||||
${this.messages?.map(x => html`<tf-message .message=${x} whoami=${this.whoami} .users=${this.users}></tf-message>`)}
|
${this.messages?.map(x => html`<tf-message .message=${x} whoami=${this.whoami} .users=${this.users}></tf-message>`)}
|
||||||
`;
|
`;
|
||||||
|
if (this.tab === 'news') {
|
||||||
|
return html`${tabs}${news}`;
|
||||||
|
} else if (this.tab === 'connections') {
|
||||||
|
return html`
|
||||||
|
${tabs}
|
||||||
|
<tf-connections .users=${this.users} .connections=${this.connections} .broadcasts=${this.broadcasts}></tf-connections>
|
||||||
|
`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
57
apps/cory/ssblit/tf-connections.js
Normal file
57
apps/cory/ssblit/tf-connections.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import {LitElement, html} from './lit-all.min.js';
|
||||||
|
import * as tfrpc from '/static/tfrpc.js';
|
||||||
|
|
||||||
|
class TfConnectionsElement extends LitElement {
|
||||||
|
static get properties() {
|
||||||
|
return {
|
||||||
|
broadcasts: {type: Array},
|
||||||
|
identities: {type: Array},
|
||||||
|
connections: {type: Array},
|
||||||
|
users: {type: Object},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
let self = this;
|
||||||
|
this.broadcasts = [];
|
||||||
|
this.identities = [];
|
||||||
|
this.connections = [];
|
||||||
|
this.users = {};
|
||||||
|
tfrpc.rpc.getAllIdentities().then(function(identities) {
|
||||||
|
self.identities = identities || [];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_emit_change() {
|
||||||
|
let changed_event = new Event('change', {
|
||||||
|
srcElement: this,
|
||||||
|
});
|
||||||
|
this.dispatchEvent(changed_event);
|
||||||
|
}
|
||||||
|
|
||||||
|
changed(event) {
|
||||||
|
this.selected = event.srcElement.value;
|
||||||
|
tfrpc.rpc.localStorageSet('whoami', this.selected);
|
||||||
|
this._emit_change();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return html`
|
||||||
|
<h2>Broadcasts</h2>
|
||||||
|
<ul>
|
||||||
|
${this.broadcasts.map(x => html`<li><tf-user id=${x.pubkey} .users=${this.users}></tf-user></li>`)}
|
||||||
|
</ul>
|
||||||
|
<h2>Connections</h2>
|
||||||
|
<ul>
|
||||||
|
${this.connections.map(x => html`<li><tf-user id=${x} .users=${this.users}></tf-user></li>`)}
|
||||||
|
</ul>
|
||||||
|
<h2>Local Accounts</h2>
|
||||||
|
<ul>
|
||||||
|
${this.identities.map(x => html`<li><tf-user id=${x} .users=${this.users}></tf-user></li>`)}
|
||||||
|
</ul>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define('tf-connections', TfConnectionsElement);
|
@ -35,6 +35,10 @@ class TfMessageElement extends LitElement {
|
|||||||
function normalize_expression(expression) {
|
function normalize_expression(expression) {
|
||||||
if (expression === 'Like' || !expression) {
|
if (expression === 'Like' || !expression) {
|
||||||
return '👍';
|
return '👍';
|
||||||
|
} else if (expression === 'Unlike') {
|
||||||
|
return '👎';
|
||||||
|
} else if (expression === 'heart') {
|
||||||
|
return '❤️';
|
||||||
} else {
|
} else {
|
||||||
return expression;
|
return expression;
|
||||||
}
|
}
|
||||||
@ -132,6 +136,10 @@ class TfMessageElement extends LitElement {
|
|||||||
unsafeHTML(tfutils.markdown(content.text));
|
unsafeHTML(tfutils.markdown(content.text));
|
||||||
return html`
|
return html`
|
||||||
<style>
|
<style>
|
||||||
|
code {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
}
|
||||||
img {
|
img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
@ -6,8 +6,11 @@ import {styles} from './tf-styles.js';
|
|||||||
class TfProfileElement extends LitElement {
|
class TfProfileElement extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
|
editing: {type: Object},
|
||||||
|
whoami: {type: String},
|
||||||
id: {type: String},
|
id: {type: String},
|
||||||
users: {type: Object},
|
users: {type: Object},
|
||||||
|
size: {type: Number},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -16,20 +19,160 @@ class TfProfileElement extends LitElement {
|
|||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
let self = this;
|
let self = this;
|
||||||
|
this.editing = null;
|
||||||
|
this.whoami = null;
|
||||||
this.id = null;
|
this.id = null;
|
||||||
this.users = {};
|
this.users = {};
|
||||||
|
this.size = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
render_raw() {
|
modify(change) {
|
||||||
return html`<div style="white-space: pre-wrap">${JSON.stringify(this.message, null, 2)}</div>`
|
tfrpc.rpc.appendMessage(this.whoami,
|
||||||
|
Object.assign({
|
||||||
|
type: 'contact',
|
||||||
|
contact: this.id,
|
||||||
|
}, change)).catch(function(error) {
|
||||||
|
alert(error?.message);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
follow() {
|
||||||
|
this.modify({following: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
unfollow() {
|
||||||
|
this.modify({following: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
block() {
|
||||||
|
this.modify({blocking: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
unblock() {
|
||||||
|
this.modify({blocking: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
edit() {
|
||||||
|
let original = this.users[this.id];
|
||||||
|
this.editing = {
|
||||||
|
name: original.name,
|
||||||
|
description: original.description,
|
||||||
|
image: original.image,
|
||||||
|
};
|
||||||
|
console.log(this.editing);
|
||||||
|
}
|
||||||
|
|
||||||
|
save_edits() {
|
||||||
|
let self = this;
|
||||||
|
let message = {
|
||||||
|
type: 'about',
|
||||||
|
about: this.whoami,
|
||||||
|
};
|
||||||
|
for (let key of Object.keys(this.editing)) {
|
||||||
|
if (this.editing[key] !== this.users[this.id][key]) {
|
||||||
|
message[key] = this.editing[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tfrpc.rpc.appendMessage(this.whoami, message).then(function() {
|
||||||
|
self.editing = null;
|
||||||
|
}).catch(function(error) {
|
||||||
|
alert(error?.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
discard_edits() {
|
||||||
|
this.editing = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
attach_image() {
|
||||||
|
let self = this;
|
||||||
|
let input = document.createElement('input');
|
||||||
|
input.type = 'file';
|
||||||
|
input.onchange = function(event) {
|
||||||
|
let file = event.target.files[0];
|
||||||
|
file.arrayBuffer().then(function(buffer) {
|
||||||
|
let bin = Array.from(new Uint8Array(buffer));
|
||||||
|
return tfrpc.rpc.store_blob(bin);
|
||||||
|
}).then(function(id) {
|
||||||
|
self.editing = Object.assign({}, self.editing, {image: id});
|
||||||
|
console.log(self.editing);
|
||||||
|
}).catch(function(e) {
|
||||||
|
alert(e.message);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
input.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
let self = this;
|
||||||
let profile = this.users[this.id] || {};
|
let profile = this.users[this.id] || {};
|
||||||
|
tfrpc.rpc.query(
|
||||||
|
`SELECT SUM(LENGTH(content)) AS size FROM messages WHERE author = ?`,
|
||||||
|
[this.id]).then(function(result) {
|
||||||
|
self.size = result[0].size;
|
||||||
|
});
|
||||||
|
let edit;
|
||||||
|
let follow;
|
||||||
|
let block;
|
||||||
|
if (this.id === this.whoami) {
|
||||||
|
if (this.editing) {
|
||||||
|
edit = html`
|
||||||
|
<input type="button" value="Save Profile" @click=${this.save_edits}></input>
|
||||||
|
<input type="button" value="Discard" @click=${this.discard_edits}></input>
|
||||||
|
`;
|
||||||
|
} else {
|
||||||
|
edit = html`<input type="button" value="Edit Profile" @click=${this.edit}></input>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.id !== this.whoami &&
|
||||||
|
this.users[this.whoami]?.following) {
|
||||||
|
follow =
|
||||||
|
this.users[this.whoami].following[this.id] ?
|
||||||
|
html`<input type="button" value="Unfollow" @click=${this.unfollow}></input>` :
|
||||||
|
html`<input type="button" value="Follow" @click=${this.follow}></input>`;
|
||||||
|
}
|
||||||
|
if (this.id !== this.whoami &&
|
||||||
|
this.users[this.whoami]?.blocking) {
|
||||||
|
block =
|
||||||
|
this.users[this.whoami].blocking[this.id] ?
|
||||||
|
html`<input type="button" value="Unblock" @click=${this.unblock}></input>` :
|
||||||
|
html`<input type="button" value="Block" @click=${this.block}></input>`;
|
||||||
|
}
|
||||||
|
let edit_profile = this.editing ? html`
|
||||||
|
<div style="flex: 1 0 50%">
|
||||||
|
<div>
|
||||||
|
<label for="name">Name:</label>
|
||||||
|
<input type="text" id="name" value=${this.editing.name} @input=${event => this.editing = Object.assign({}, this.editing, {name: event.srcElement.value})}></input>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div><label for="description">Description:</label></div>
|
||||||
|
<textarea id="description" @input=${event => this.editing = Object.assign({}, this.editing, {description: event.srcElement.value})}>${this.editing.description}</textarea>
|
||||||
|
</div>
|
||||||
|
<input type="button" value="Attach Image" @click=${this.attach_image}></input>
|
||||||
|
</div>` : null;
|
||||||
|
let image = typeof(profile.image) == 'string' ? profile.image : profile.image?.link;
|
||||||
|
image = this.editing?.image ?? image;
|
||||||
|
let description = this.editing?.description ?? profile.description;
|
||||||
return html`<div style="border: 2px solid black; background-color: rgba(255, 255, 255, 0.2); padding: 16px">
|
return html`<div style="border: 2px solid black; background-color: rgba(255, 255, 255, 0.2); padding: 16px">
|
||||||
<tf-user id=${this.id} .users=${this.users}></tf-user>
|
<tf-user id=${this.id} .users=${this.users}></tf-user> (${tfutils.human_readable_size(this.size)})
|
||||||
<div><img src=${'/' + profile.image + '/view'} style="width: 256px; height: auto"></img></div>
|
<div style="display: flex; flex-direction: row">
|
||||||
<div>${unsafeHTML(tfutils.markdown(profile.description))}</div>
|
${edit_profile}
|
||||||
|
<div style="flex: 1 0 50%">
|
||||||
|
<div><img src=${'/' + image + '/view'} style="width: 256px; height: auto"></img></div>
|
||||||
|
<div>${unsafeHTML(tfutils.markdown(description))}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
Following ${Object.keys(profile.following || {}).length} identities.
|
||||||
|
Followed by ${Object.values(self.users).filter(x => (x.following || {})[self.id]).length} identities.
|
||||||
|
Blocking ${Object.keys(profile.blocking || {}).length} identities.
|
||||||
|
Blocked by ${Object.values(self.users).filter(x => (x.blocking || {})[self.id]).length} identities.
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
${edit}
|
||||||
|
${follow}
|
||||||
|
${block}
|
||||||
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,8 @@ class TfUserElement extends LitElement {
|
|||||||
image = typeof(image) == 'string' ? image : image?.link;
|
image = typeof(image) == 'string' ? image : image?.link;
|
||||||
return html`
|
return html`
|
||||||
<div style="display: inline-block; font-weight: bold">
|
<div style="display: inline-block; font-weight: bold">
|
||||||
<img style="width: 2em; height: 2em; vertical-align: middle; border-radius: 50%" src="${'/' + image + '/view'}">
|
<img style="width: 2em; height: 2em; vertical-align: middle; border-radius: 50%" ?hidden=${image === undefined} src="${image ? '/' + image + '/view' : undefined}">
|
||||||
<a target="_top" href=${'#' + this.id}>${this.users[this.id].name}</a>
|
<a target="_top" href=${'#' + this.id}>${this.users[this.id].name ?? this.id}</a>
|
||||||
</div>`;
|
</div>`;
|
||||||
} else {
|
} else {
|
||||||
return html`
|
return html`
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
|
import * as linkify from './commonmark-linkify.js';
|
||||||
|
|
||||||
export function markdown(md) {
|
export function markdown(md) {
|
||||||
var reader = new commonmark.Parser({safe: true});
|
var reader = new commonmark.Parser({safe: true});
|
||||||
var writer = new commonmark.HtmlRenderer();
|
var writer = new commonmark.HtmlRenderer();
|
||||||
var parsed = reader.parse(md || '');
|
var parsed = reader.parse(md || '');
|
||||||
|
parsed = linkify.transform(parsed);
|
||||||
var walker = parsed.walker();
|
var walker = parsed.walker();
|
||||||
var event, node;
|
var event, node;
|
||||||
while ((event = walker.next())) {
|
while ((event = walker.next())) {
|
||||||
@ -27,3 +30,17 @@ export function markdown(md) {
|
|||||||
}
|
}
|
||||||
return writer.render(parsed);
|
return writer.render(parsed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function human_readable_size(bytes) {
|
||||||
|
let v = bytes;
|
||||||
|
let u = 'B';
|
||||||
|
for (let unit of ['kB', 'MB', 'GB']) {
|
||||||
|
if (v > 1024) {
|
||||||
|
v /= 1024;
|
||||||
|
u = unit;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return `${Math.round(v * 10) / 10} ${u}`;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user