diff --git a/apps/cory/docs.json b/apps/cory/docs.json index 42ec69e6..12275138 100644 --- a/apps/cory/docs.json +++ b/apps/cory/docs.json @@ -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"}} \ No newline at end of file +{"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"}} \ No newline at end of file diff --git a/apps/cory/docs/todo.md b/apps/cory/docs/todo.md index 00b533b1..f2921e21 100644 --- a/apps/cory/docs/todo.md +++ b/apps/cory/docs/todo.md @@ -9,7 +9,6 @@ - update README - update docs - audit + document API exposed to apps -- emoji reaction picker - fix weird HTTP warnings - ssb from child process? - channels @@ -26,6 +25,8 @@ - jwt for session tokens ## Maybe Done +- linkify https://... +- emoji reaction picker - expose loads of stats - confirm posting all new messages - multiple identities per user, in database diff --git a/apps/cory/ssblit.json b/apps/cory/ssblit.json index 85bded97..efb93cfd 100644 --- a/apps/cory/ssblit.json +++ b/apps/cory/ssblit.json @@ -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"}} \ No newline at end of file +{"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"}} \ No newline at end of file diff --git a/apps/cory/ssblit/app.js b/apps/cory/ssblit/app.js index 39f77164..c06d8d28 100644 --- a/apps/cory/ssblit/app.js +++ b/apps/cory/ssblit/app.js @@ -18,6 +18,15 @@ tfrpc.register(async function databaseSet(key, value) { tfrpc.register(async function 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) { let result = []; await ssb.sqlStream(sql, args, function callback(row) { @@ -46,6 +55,13 @@ tfrpc.register(async function store_blob(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() { if (typeof(database) !== 'undefined') { diff --git a/apps/cory/ssblit/commonmark-linkify.js b/apps/cory/ssblit/commonmark-linkify.js new file mode 100644 index 00000000..decdbf0b --- /dev/null +++ b/apps/cory/ssblit/commonmark-linkify.js @@ -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; +} diff --git a/apps/cory/ssblit/index.html b/apps/cory/ssblit/index.html index 30f53e4c..837846f7 100644 --- a/apps/cory/ssblit/index.html +++ b/apps/cory/ssblit/index.html @@ -2,12 +2,14 @@ Tilde Friends +

Tilde Friends

+ \ No newline at end of file diff --git a/apps/cory/ssblit/script.js b/apps/cory/ssblit/script.js index ab3c3385..7c16e66f 100644 --- a/apps/cory/ssblit/script.js +++ b/apps/cory/ssblit/script.js @@ -7,3 +7,4 @@ import * as tf_message from './tf-message.js'; import * as tf_user from './tf-user.js'; import * as tf_compose from './tf-compose.js'; import * as tf_profile from './tf-profile.js'; +import * as tf_connections from './tf-connections.js'; diff --git a/apps/cory/ssblit/tf-app.js b/apps/cory/ssblit/tf-app.js index 3870d11b..38157deb 100644 --- a/apps/cory/ssblit/tf-app.js +++ b/apps/cory/ssblit/tf-app.js @@ -1,5 +1,6 @@ import {LitElement, html, css} from './lit-all.min.js'; import * as tfrpc from '/static/tfrpc.js'; +import {styles} from './tf-styles.js'; class TfElement extends LitElement { static get properties() { @@ -12,9 +13,14 @@ class TfElement extends LitElement { status: {type: Array}, hash: {type: String}, unread: {type: Array}, + tab: {type: String}, + broadcasts: {type: Array}, + connections: {type: Array}, }; } + static styles = styles; + constructor() { super(); let self = this; @@ -27,7 +33,12 @@ class TfElement extends LitElement { this.hash = '#'; this.loading = false; this.unread = []; + this.tab = 'news'; + this.broadcasts = []; + this.connections = []; 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.register(function hashChanged(hash) { self.hash = hash; @@ -36,6 +47,13 @@ class TfElement extends LitElement { tfrpc.register(async function notifyNewMessage(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) { @@ -288,6 +306,9 @@ class TfElement extends LitElement { function link_message(message) { if (message.content.type === 'vote') { let parent = self.ensure_message(message.content.vote.link); + if (!parent.votes) { + parent.votes = []; + } parent.votes.push(message); message.parent_message = message.content.vote.link; } else if (message.content.type == 'post') { @@ -373,8 +394,11 @@ class TfElement extends LitElement { if (this.loading || (!this.whoami && this.ids.length)) { return; } + let load_button = this.renderRoot.getElementById('load_button'); this.loading = true; - this.renderRoot.getElementById('load_button').disabled = true; + if (load_button) { + load_button.disabled = true; + } this.status = []; this.messages = []; this.messages_by_id = {}; @@ -392,7 +416,9 @@ class TfElement extends LitElement { await this.finalize_messages(); this.record_status('done'); this.status = []; - this.renderRoot.getElementById('load_button').disabled = false; + if (load_button) { + load_button.disabled = false; + } this.loading = false; } @@ -402,9 +428,16 @@ class TfElement extends LitElement { } render() { + let self = this; + let tabs = html` +
+ self.tab = 'news'}> + self.tab = 'connections'}> +
+ `; let profile = this.hash.startsWith('#@') ? - html`` : undefined; - return html` + html`` : undefined; + let news = html` 🏠Home @@ -415,6 +448,14 @@ class TfElement extends LitElement { ${profile} ${this.messages?.map(x => html``)} `; + if (this.tab === 'news') { + return html`${tabs}${news}`; + } else if (this.tab === 'connections') { + return html` + ${tabs} + + `; + } } } diff --git a/apps/cory/ssblit/tf-connections.js b/apps/cory/ssblit/tf-connections.js new file mode 100644 index 00000000..bea17963 --- /dev/null +++ b/apps/cory/ssblit/tf-connections.js @@ -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` +

Broadcasts

+ +

Connections

+ +

Local Accounts

+ + `; + } +} + +customElements.define('tf-connections', TfConnectionsElement); \ No newline at end of file diff --git a/apps/cory/ssblit/tf-message.js b/apps/cory/ssblit/tf-message.js index 003944d7..a02bca27 100644 --- a/apps/cory/ssblit/tf-message.js +++ b/apps/cory/ssblit/tf-message.js @@ -35,6 +35,10 @@ class TfMessageElement extends LitElement { function normalize_expression(expression) { if (expression === 'Like' || !expression) { return '👍'; + } else if (expression === 'Unlike') { + return '👎'; + } else if (expression === 'heart') { + return '❤️'; } else { return expression; } @@ -132,6 +136,10 @@ class TfMessageElement extends LitElement { unsafeHTML(tfutils.markdown(content.text)); return html`