ssb: Merge in the new very work in progress channels interface.
All checks were successful
Build Tilde Friends / Build-All (push) Successful in 16m1s

This commit is contained in:
Cory McWilliams 2024-11-30 15:05:14 -05:00
parent 53044696ba
commit cd2c2587ae
6 changed files with 319 additions and 118 deletions

View File

@ -16,7 +16,9 @@ class TfElement extends LitElement {
following: {type: Array}, following: {type: Array},
users: {type: Object}, users: {type: Object},
ids: {type: Array}, ids: {type: Array},
tags: {type: Array}, channels: {type: Array},
channels_unread: {type: Object},
channels_latest: {type: Object},
}; };
} }
@ -33,7 +35,9 @@ class TfElement extends LitElement {
this.following = []; this.following = [];
this.users = {}; this.users = {};
this.loaded = false; this.loaded = false;
this.tags = []; this.channels = [];
this.channels_unread = {};
this.channels_latest = {};
tfrpc.rpc.getBroadcasts().then((b) => { tfrpc.rpc.getBroadcasts().then((b) => {
self.broadcasts = b || []; self.broadcasts = b || [];
}); });
@ -64,6 +68,27 @@ class TfElement extends LitElement {
let ids = (await tfrpc.rpc.getIdentities()) || []; let ids = (await tfrpc.rpc.getIdentities()) || [];
this.whoami = whoami ?? (ids.length ? ids[0] : undefined); this.whoami = whoami ?? (ids.length ? ids[0] : undefined);
this.ids = ids; this.ids = ids;
let channels = await tfrpc.rpc.query(`
SELECT
content ->> 'channel' AS channel,
content ->> 'subscribed' AS subscribed
FROM
messages
WHERE
author = ? AND
content ->> 'type' = 'channel'
ORDER BY sequence
`, [this.whoami]);
let channel_map = {};
for (let row of channels) {
if (row.subscribed) {
channel_map[row.channel] = true;
} else {
delete channel_map[row.channel];
}
}
this.channels = Object.keys(channel_map);
} }
set_hash(hash) { set_hash(hash) {
@ -195,33 +220,9 @@ class TfElement extends LitElement {
} }
} }
async load_recent_tags() {
let start = new Date();
this.tags = await tfrpc.rpc.query(
`
WITH
recent AS (SELECT id, json(content) AS content FROM messages
WHERE messages.timestamp > ? AND json_extract(content, '$.type') = 'post'
ORDER BY timestamp DESC LIMIT 1024),
recent_channels AS (SELECT recent.id, '#' || json_extract(content, '$.channel') AS tag
FROM recent
WHERE json_extract(content, '$.channel') IS NOT NULL),
recent_mentions AS (SELECT recent.id, json_extract(mention.value, '$.link') AS tag
FROM recent, json_each(recent.content, '$.mentions') AS mention
WHERE json_valid(mention.value) AND tag LIKE '#%'),
combined AS (SELECT id, tag FROM recent_channels UNION ALL SELECT id, tag FROM recent_mentions),
by_message AS (SELECT DISTINCT id, tag FROM combined)
SELECT tag, COUNT(*) AS count FROM by_message GROUP BY tag ORDER BY count DESC LIMIT 10
`,
[new Date() - 7 * 24 * 60 * 60 * 1000]
);
console.log('tags took', (new Date() - start) / 1000.0, 'seconds');
}
async load() { async load() {
let whoami = this.whoami; let whoami = this.whoami;
let tags = this.load_recent_tags(); let following = await tfrpc.rpc.following([whoami], 2);
let following = await tfrpc.rpc.following([whoami], 3);
let users = {}; let users = {};
let by_count = []; let by_count = [];
for (let [id, v] of Object.entries(following)) { for (let [id, v] of Object.entries(following)) {
@ -233,6 +234,17 @@ class TfElement extends LitElement {
}; };
by_count.push({count: v.of, id: id}); by_count.push({count: v.of, id: id});
} }
let channels = tfrpc.rpc.query(`
SELECT channels.value AS channel, MAX(messages.rowid) AS rowid FROM messages
JOIN json_each(?1) AS channels ON messages.content ->> 'channel' = channels.value
JOIN json_each(?2) AS following ON messages.author = following.value
WHERE messages.content ->> 'type' = 'post' AND messages.content ->> 'root' IS NULL
GROUP by channel
UNION
SELECT '' AS channel, MAX(messages.rowid) AS rowid FROM messages
JOIN json_each(?2) AS following ON messages.author = following.value
`, [JSON.stringify(this.channels), JSON.stringify(Object.keys(following))]);
this.channels_unread = JSON.parse((await tfrpc.rpc.databaseGet('unread')) ?? '{}');
console.log(by_count.sort((x, y) => y.count - x.count).slice(0, 20)); console.log(by_count.sort((x, y) => y.count - x.count).slice(0, 20));
let start_time = new Date(); let start_time = new Date();
users = await this.fetch_about(Object.keys(following).sort(), users); users = await this.fetch_about(Object.keys(following).sort(), users);
@ -243,14 +255,24 @@ class TfElement extends LitElement {
Object.keys(users).length, Object.keys(users).length,
'users' 'users'
); );
channels = await channels;
this.channels_latest = Object.fromEntries(channels.map(x => [x.channel, x.rowid]));
console.log('CHANNELS', channels);
this.following = Object.keys(following); this.following = Object.keys(following);
this.users = users; this.users = users;
await tags;
console.log(`load finished ${whoami} => ${this.whoami}`); console.log(`load finished ${whoami} => ${this.whoami}`);
this.whoami = whoami; this.whoami = whoami;
this.loaded = whoami; this.loaded = whoami;
} }
channel_set_unread(event) {
console.log(event.detail.channel ?? '', event.detail.unread);
this.channels_unread[event.detail.channel ?? ''] = event.detail.unread;
this.channels_unread = Object.assign({}, this.channels_unread);
console.log(this.channels_unread);
tfrpc.rpc.databaseSet('unread', JSON.stringify(this.channels_unread));
}
render_tab() { render_tab() {
let following = this.following; let following = this.following;
let users = this.users; let users = this.users;
@ -265,6 +287,10 @@ class TfElement extends LitElement {
.unread=${this.unread} .unread=${this.unread}
@refresh=${() => (this.unread = [])} @refresh=${() => (this.unread = [])}
?loading=${this.loading} ?loading=${this.loading}
.channels=${this.channels}
.channels_latest=${this.channels_latest}
.channels_unread=${this.channels_unread}
@channelsetunread=${this.channel_set_unread}
></tf-tab-news> ></tf-tab-news>
`; `;
} else if (this.tab === 'connections') { } else if (this.tab === 'connections') {
@ -344,7 +370,7 @@ class TfElement extends LitElement {
}; };
let tabs = html` let tabs = html`
<div class="w3-bar w3-theme-l1"> <div class="w3-bar w3-theme-l1" style="position: sticky; top: 0">
<button <button
class="w3-bar-item w3-button w3-circle w3-ripple" class="w3-bar-item w3-button w3-circle w3-ripple"
@click=${this.refresh} @click=${this.refresh}
@ -385,13 +411,8 @@ class TfElement extends LitElement {
class="w3-theme-dark" class="w3-theme-dark"
> >
${tabs} ${tabs}
<div style="padding: 8px">
${this.tags.map(
(x) => html`<tf-tag tag=${x.tag} count=${x.count}></tf-tag>`
)}
${contents} ${contents}
</div> </div>
</div>
`; `;
} }
} }

View File

@ -14,6 +14,7 @@ class TfComposeElement extends LitElement {
apps: {type: Object}, apps: {type: Object},
drafts: {type: Object}, drafts: {type: Object},
author: {type: String}, author: {type: String},
channel: {type: String},
}; };
} }
@ -196,6 +197,7 @@ class TfComposeElement extends LitElement {
let message = { let message = {
type: 'post', type: 'post',
text: edit.innerText, text: edit.innerText,
channel: this.channel,
}; };
if (this.root || this.branch) { if (this.root || this.branch) {
message.root = this.root; message.root = this.root;
@ -535,6 +537,9 @@ class TfComposeElement extends LitElement {
class="w3-card-4 w3-theme-d4 w3-padding-small" class="w3-card-4 w3-theme-d4 w3-padding-small"
style="box-sizing: border-box" style="box-sizing: border-box"
> >
${this.channel !== undefined ?
html`<p>To #${this.channel}:</p>` :
undefined}
${this.render_encrypt()} ${this.render_encrypt()}
<div class="w3-container w3-padding-small"> <div class="w3-container w3-padding-small">
<div class="w3-half"> <div class="w3-half">

View File

@ -14,6 +14,8 @@ class TfMessageElement extends LitElement {
format: {type: String}, format: {type: String},
blog_data: {type: String}, blog_data: {type: String},
expanded: {type: Object}, expanded: {type: Object},
channel: {type: String},
channel_unread: {type: Number},
}; };
} }
@ -28,6 +30,7 @@ class TfMessageElement extends LitElement {
this.drafts = {}; this.drafts = {};
this.format = 'message'; this.format = 'message';
this.expanded = {}; this.expanded = {};
this.channel_unread = -1;
} }
show_reply() { show_reply() {
@ -312,12 +315,25 @@ ${JSON.stringify(mention, null, 2)}</pre
.users=${this.users} .users=${this.users}
.drafts=${this.drafts} .drafts=${this.drafts}
.expanded=${this.expanded} .expanded=${this.expanded}
channel=${this.channel}
channel_unread=${this.channel_unread}
></tf-message>` ></tf-message>`
)}`; )}`;
} }
} }
} }
mark_read() {
this.dispatchEvent(new CustomEvent('channelsetunread', {
bubbles: true,
composed: true,
detail: {
channel: this.channel,
unread: this.message.rowid + 1,
},
}));
}
render_channels() { render_channels() {
let content = this.message?.content; let content = this.message?.content;
if (this?.messsage?.decrypted?.type == 'post') { if (this?.messsage?.decrypted?.type == 'post') {
@ -344,7 +360,7 @@ ${JSON.stringify(mention, null, 2)}</pre
} }
let class_background = this.message?.decrypted let class_background = this.message?.decrypted
? 'w3-pale-red' ? 'w3-pale-red'
: 'w3-theme-d4'; : (this.message?.rowid >= this.channel_unread ? 'w3-theme-d2' : 'w3-theme-d4');
let self = this; let self = this;
let raw_button; let raw_button;
switch (this.format) { switch (this.format) {
@ -423,6 +439,8 @@ ${JSON.stringify(mention, null, 2)}</pre
.users=${self.users} .users=${self.users}
.drafts=${self.drafts} .drafts=${self.drafts}
.expanded=${self.expanded} .expanded=${self.expanded}
channel=${this.channel}
channel_unread=${this.channel_unread}
></tf-message> ></tf-message>
` `
)} )}
@ -442,6 +460,8 @@ ${JSON.stringify(mention, null, 2)}</pre
.users=${this.users} .users=${this.users}
.drafts=${this.drafts} .drafts=${this.drafts}
.expanded=${this.expanded} .expanded=${this.expanded}
channel=${this.channel}
channel_unread=${this.channel_unread}
></tf-message>` ></tf-message>`
)} )}
</div>`; </div>`;
@ -463,6 +483,8 @@ ${JSON.stringify(mention, null, 2)}</pre
.users=${this.users} .users=${this.users}
.drafts=${this.drafts} .drafts=${this.drafts}
.expanded=${this.expanded} .expanded=${this.expanded}
channel=${this.channel}
channel_unread=${this.channel_unread}
></tf-message> ></tf-message>
` `
)} )}
@ -618,6 +640,11 @@ ${JSON.stringify(content, null, 2)}</pre
<button class="w3-button w3-theme-d1" @click=${this.react}> <button class="w3-button w3-theme-d1" @click=${this.react}>
React React
</button> </button>
${!content.root ?
html`
<button class="w3-button w3-theme-d1" @click=${this.mark_read}>Set Read Here</button>
` :
undefined}
</p> </p>
${this.render_children()} ${this.render_children()}
</div> </div>

View File

@ -11,6 +11,8 @@ class TfNewsElement extends LitElement {
following: {type: Array}, following: {type: Array},
drafts: {type: Object}, drafts: {type: Object},
expanded: {type: Object}, expanded: {type: Object},
channel: {type: String},
channel_unread: {type: Number},
}; };
} }
@ -25,6 +27,7 @@ class TfNewsElement extends LitElement {
this.following = []; this.following = [];
this.drafts = {}; this.drafts = {};
this.expanded = {}; this.expanded = {};
this.channel_unread = -1;
} }
process_messages(messages) { process_messages(messages) {
@ -179,7 +182,7 @@ class TfNewsElement extends LitElement {
this.finalize_messages(messages_by_id) this.finalize_messages(messages_by_id)
); );
return html` return html`
<div style="display: flex; flex-direction: column"> <div>
${final_messages.map( ${final_messages.map(
(x) => (x) =>
html`<tf-message html`<tf-message
@ -189,6 +192,8 @@ class TfNewsElement extends LitElement {
.drafts=${this.drafts} .drafts=${this.drafts}
.expanded=${this.expanded} .expanded=${this.expanded}
collapsed="true" collapsed="true"
channel=${this.channel}
channel_unread=${this.channel_unread}
></tf-message>` ></tf-message>`
)} )}
</div> </div>

View File

@ -12,6 +12,9 @@ class TfTabNewsFeedElement extends LitElement {
messages: {type: Array}, messages: {type: Array},
drafts: {type: Object}, drafts: {type: Object},
expanded: {type: Object}, expanded: {type: Object},
channels_unread: {type: Object},
loading: {type: Number},
time_range: {type: Array},
}; };
} }
@ -26,19 +29,25 @@ class TfTabNewsFeedElement extends LitElement {
this.following = []; this.following = [];
this.drafts = {}; this.drafts = {};
this.expanded = {}; this.expanded = {};
this.start_time = new Date().valueOf() - 24 * 60 * 60 * 1000; this.channels_unread = {};
this.start_time = (new Date()).valueOf();
this.time_range = [0, 0];
} }
async fetch_messages() { channel() {
return this.hash.startsWith('##') ? this.hash.substring(2) : '';
}
async fetch_messages(start_time, end_time) {
if (this.hash.startsWith('#@')) { if (this.hash.startsWith('#@')) {
let r = await tfrpc.rpc.query( let r = await tfrpc.rpc.query(
` `
WITH mine AS (SELECT id, previous, author, sequence, timestamp, hash, json(content) AS content, signature WITH mine AS (SELECT rowid, id, previous, author, sequence, timestamp, hash, json(content) AS content, signature
FROM messages FROM messages
WHERE messages.author = ? WHERE messages.author = ?
ORDER BY sequence DESC ORDER BY sequence DESC
LIMIT 20) LIMIT 20)
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM mine FROM mine
JOIN messages_refs ON mine.id = messages_refs.ref JOIN messages_refs ON mine.id = messages_refs.ref
JOIN messages ON messages_refs.message = messages.id JOIN messages ON messages_refs.message = messages.id
@ -62,24 +71,27 @@ class TfTabNewsFeedElement extends LitElement {
`, `,
[this.hash.substring(1)] [this.hash.substring(1)]
); );
} else { } else if (this.hash.startsWith('##')) {
let promises = []; let promises = [];
const k_following_limit = 256; const k_following_limit = 256;
for (let i = 0; i < this.following.length; i += k_following_limit) { for (let i = 0; i < this.following.length; i += k_following_limit) {
promises.push( promises.push(
tfrpc.rpc.query( tfrpc.rpc.query(
` `
WITH news AS (SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature WITH news AS (SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages FROM messages
JOIN json_each(?) AS following ON messages.author = following.value JOIN json_each(?) AS following ON messages.author = following.value
WHERE messages.timestamp > ? AND messages.timestamp < ? WHERE
messages.timestamp > ? AND
messages.timestamp < ? AND
messages.content ->> 'channel' = ?
ORDER BY messages.timestamp DESC) ORDER BY messages.timestamp DESC)
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news FROM news
JOIN messages_refs ON news.id = messages_refs.ref JOIN messages_refs ON news.id = messages_refs.ref
JOIN messages ON messages_refs.message = messages.id JOIN messages ON messages_refs.message = messages.id
UNION UNION
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news FROM news
JOIN messages_refs ON news.id = messages_refs.message JOIN messages_refs ON news.id = messages_refs.message
JOIN messages ON messages_refs.ref = messages.id JOIN messages ON messages_refs.ref = messages.id
@ -88,12 +100,42 @@ class TfTabNewsFeedElement extends LitElement {
`, `,
[ [
JSON.stringify(this.following.slice(i, i + k_following_limit)), JSON.stringify(this.following.slice(i, i + k_following_limit)),
this.start_time, start_time,
/* end_time,
** Don't show messages more than a day into the future to prevent this.hash.substring(2),
** messages with far-future timestamps from staying at the top forever. ]
*/ )
new Date().valueOf() + 24 * 60 * 60 * 1000, );
}
return [].concat(...(await Promise.all(promises)));
} else {
let promises = [];
const k_following_limit = 256;
for (let i = 0; i < this.following.length; i += k_following_limit) {
promises.push(
tfrpc.rpc.query(
`
WITH news AS (SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM messages
JOIN json_each(?) AS following ON messages.author = following.value
WHERE messages.timestamp > ? AND messages.timestamp < ?
ORDER BY messages.timestamp DESC)
SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.ref
JOIN messages ON messages_refs.message = messages.id
UNION
SELECT messages.rowid, messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.message
JOIN messages ON messages_refs.ref = messages.id
UNION
SELECT news.* FROM news
`,
[
JSON.stringify(this.following.slice(i, i + k_following_limit)),
start_time,
end_time,
] ]
) )
); );
@ -103,31 +145,19 @@ class TfTabNewsFeedElement extends LitElement {
} }
async load_more() { async load_more() {
this.loading++;
try {
let more = [];
while (!more.length) {
let last_start_time = this.start_time; let last_start_time = this.start_time;
this.start_time = last_start_time - 24 * 60 * 60 * 1000; this.start_time = last_start_time - 7 * 24 * 60 * 60 * 1000;
let more = await tfrpc.rpc.query( more = await this.fetch_messages(this.start_time, last_start_time);
` this.time_range = [this.start_time, this.time_range[1]];
WITH news AS (SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature }
FROM messages
JOIN json_each(?) AS following ON messages.author = following.value
WHERE messages.timestamp > ?
AND messages.timestamp <= ?
ORDER BY messages.timestamp DESC)
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.ref
JOIN messages ON messages_refs.message = messages.id
UNION
SELECT messages.id, messages.previous, messages.author, messages.sequence, messages.timestamp, messages.hash, json(messages.content) AS content, messages.signature
FROM news
JOIN messages_refs ON news.id = messages_refs.message
JOIN messages ON messages_refs.ref = messages.id
UNION
SELECT news.* FROM news
`,
[JSON.stringify(this.following), this.start_time, last_start_time]
);
this.messages = await this.decrypt([...more, ...this.messages]); this.messages = await this.decrypt([...more, ...this.messages]);
} finally {
this.loading--;
}
} }
async decrypt(messages) { async decrypt(messages) {
@ -160,6 +190,51 @@ class TfTabNewsFeedElement extends LitElement {
this.messages = await this.decrypt([...messages, ...this.messages]); this.messages = await this.decrypt([...messages, ...this.messages]);
} }
async load_messages() {
let self = this;
this.loading = true;
let messages = [];
try {
this.messages = [];
this._messages_hash = this.hash;
this._messages_following = this.following;
let now = new Date().valueOf();
let start_time = now - 24 * 60 * 60 * 1000;
this.start_time = start_time;
this.time_range = [this.start_time, now + 24 * 60 * 60 * 1000];
messages = await this.fetch_messages(this.time_range[0], this.time_range[1]);
messages = await this.decrypt(messages);
if (!messages.length) {
let more = [];
while (!more.length && start_time >= 0) {
let last_start_time = start_time;
start_time = last_start_time - 7 * 24 * 60 * 60 * 1000;
more = await this.fetch_messages(start_time, last_start_time);
}
this.time_range = [start_time, this.time_range[1]];
messages = await this.decrypt([...more, ...this.messages]);
}
} finally {
this.loading = false;
}
this.messages = messages;
console.log(`loading messages done for ${self.whoami}`);
}
mark_all_read() {
let newest = this.messages.reduce((accumulator, current) => Math.max(accumulator, current.rowid), -1);
if (newest >= 0) {
this.dispatchEvent(new CustomEvent('channelsetunread', {
bubbles: true,
composed: true,
detail: {
channel: this.channel(),
unread: newest + 1,
},
}));
}
}
render() { render() {
if ( if (
!this.messages || !this.messages ||
@ -169,27 +244,17 @@ class TfTabNewsFeedElement extends LitElement {
console.log( console.log(
`loading messages for ${this.whoami} (following ${this.following.length})` `loading messages for ${this.whoami} (following ${this.following.length})`
); );
let self = this; this.load_messages();
this.messages = [];
this._messages_hash = this.hash;
this._messages_following = this.following;
this.fetch_messages()
.then(this.decrypt.bind(this))
.then(function (messages) {
self.messages = messages;
console.log(`loading mesages done for ${self.whoami}`);
})
.catch(function (error) {
alert(JSON.stringify(error, null, 2));
});
} }
let more; let more;
if (!this.hash.startsWith('#@') && !this.hash.startsWith('#%')) { if (!this.hash.startsWith('#@') && !this.hash.startsWith('#%')) {
more = html` more = html`
<p> <p>
<button class="w3-button w3-theme-d1" @click=${this.load_more}> <button class="w3-button w3-theme-d1" @click=${this.mark_all_read}>Mark All Read</button>
<button ?disabled=${this.loading} class="w3-button w3-theme-d1" @click=${this.load_more}>
Load More Load More
</button> </button>
<span>Showing ${new Date(this.time_range[0]).toLocaleDateString()} - ${new Date(this.time_range[1]).toLocaleDateString()}.</span>
</p> </p>
`; `;
} }
@ -202,6 +267,8 @@ class TfTabNewsFeedElement extends LitElement {
.following=${this.following} .following=${this.following}
.drafts=${this.drafts} .drafts=${this.drafts}
.expanded=${this.expanded} .expanded=${this.expanded}
channel=${this.channel()}
channel_unread=${this.channels_unread?.[this.channel()]}
></tf-news> ></tf-news>
${more} ${more}
`; `;

View File

@ -13,6 +13,9 @@ class TfTabNewsElement extends LitElement {
drafts: {type: Object}, drafts: {type: Object},
expanded: {type: Object}, expanded: {type: Object},
loading: {type: Boolean}, loading: {type: Boolean},
channels: {type: Array},
channels_unread: {type: Object},
channels_latest: {type: Object},
}; };
} }
@ -29,6 +32,9 @@ class TfTabNewsElement extends LitElement {
this.cache = {}; this.cache = {};
this.drafts = {}; this.drafts = {};
this.expanded = {}; this.expanded = {};
this.channels_unread = {};
this.channels_latest = {};
this.channels = [];
tfrpc.rpc.localStorageGet('drafts').then(function (d) { tfrpc.rpc.localStorageGet('drafts').then(function (d) {
self.drafts = JSON.parse(d || '{}'); self.drafts = JSON.parse(d || '{}');
}); });
@ -106,6 +112,47 @@ class TfTabNewsElement extends LitElement {
} }
} }
unread_status(channel) {
if (this.channels_latest[channel] &&
(this.channels_unread[channel] === undefined ||
this.channels_unread[channel] < this.channels_latest[channel])) {
return '🔵';
}
}
show_sidebar() {
this.renderRoot.getElementById('sidebar').style.display = 'block';
this.renderRoot.getElementById('main').style.marginLeft = '2in';
this.renderRoot.getElementById('show_sidebar').style.display = 'none';
}
hide_sidebar() {
this.renderRoot.getElementById('sidebar').style.display = 'none';
this.renderRoot.getElementById('main').style.marginLeft = '0';
this.renderRoot.getElementById('show_sidebar').style.display = 'block';
}
async channel_toggle_subscribed() {
let channel = this.hash.substring(2);
let subscribed = this.channels.indexOf(channel) != -1;
subscribed = !subscribed;
await tfrpc.rpc.appendMessage(this.whoami, {
type: 'channel',
channel: channel,
subscribed: subscribed,
});
if (subscribed) {
this.channels = [].concat([channel], this.channels).sort();
} else {
this.channels = this.channels.filter(x => x != channel);
}
}
channel() {
return this.hash.startsWith('##') ? this.hash.substring(2) : undefined;
}
render() { render() {
let profile = this.hash.startsWith('#@') let profile = this.hash.startsWith('#@')
? html`<tf-profile ? html`<tf-profile
@ -129,13 +176,39 @@ class TfTabNewsElement extends LitElement {
</div>`; </div>`;
} }
return html` return html`
<p class="w3-bar"> <div class="w3-sidebar w3-bar-block w3-theme-d1" style="width: 2in; left: 0" id="sidebar">
<div class="w3-right w3-button" @click=${this.hide_sidebar}>&times;</div>
${this.hash.startsWith('##') && this.channels.indexOf(this.hash.substring(2)) == -1 ?
html`
<div class="w3-bar-item w3-theme-d2">Viewing</div>
<a href="#" class="w3-bar-item w3-button" style="font-weight: bold">${this.hash.substring(2)}</a>
` :
undefined}
<div class="w3-bar-item w3-theme-d2">Channels</div>
<a href="#" class="w3-bar-item w3-button" style=${this.hash == '#' ? 'font-weight: bold' : undefined}>general ${this.unread_status('')}</a>
${this.channels.map(x => html`
<a
href=${'#' + encodeURIComponent('#' + x)}
class="w3-bar-item w3-button"
style=${this.hash == '##' + x ? 'font-weight: bold' : undefined}>#${x} ${this.unread_status(x)}</a>
`)}
</div>
<div style="margin-left: 2in; padding: 8px" id="main">
<div id="show_sidebar" class="w3-left w3-button" style="display: none" @click=${this.show_sidebar}>&#9776;</div>
<p>
<button <button
class="w3-bar-item w3-button w3-theme-d1" class="w3-button w3-theme-d1"
@click=${this.show_more} @click=${this.show_more}
> >
${this.new_messages_text()} ${this.new_messages_text()}
</button> </button>
${this.hash.startsWith('##') ?
html`
<button class="w3-button w3-theme-d1" @click=${this.channel_toggle_subscribed}>
${this.channels.indexOf(this.hash.substring(2)) != -1 ? 'Unsubscribe from #' : 'Subscribe to #'}${this.hash.substring(2)}
</button>
` :
undefined}
</p> </p>
<div class="w3-bar"> <div class="w3-bar">
Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>! Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>!
@ -148,6 +221,7 @@ class TfTabNewsElement extends LitElement {
.users=${this.users} .users=${this.users}
.drafts=${this.drafts} .drafts=${this.drafts}
@tf-draft=${this.draft} @tf-draft=${this.draft}
.channel=${this.channel()}
></tf-compose> ></tf-compose>
</div> </div>
${profile} ${profile}
@ -161,7 +235,9 @@ class TfTabNewsElement extends LitElement {
.expanded=${this.expanded} .expanded=${this.expanded}
@tf-draft=${this.draft} @tf-draft=${this.draft}
@tf-expand=${this.on_expand} @tf-expand=${this.on_expand}
.channels_unread=${this.channels_unread}
></tf-tab-news-feed> ></tf-tab-news-feed>
</div>
`; `;
} }
} }