`;
}
diff --git a/apps/ssb/tf-compose.js b/apps/ssb/tf-compose.js
index 3ea22849..3b38467c 100644
--- a/apps/ssb/tf-compose.js
+++ b/apps/ssb/tf-compose.js
@@ -14,6 +14,7 @@ class TfComposeElement extends LitElement {
apps: {type: Object},
drafts: {type: Object},
author: {type: String},
+ channel: {type: String},
};
}
@@ -196,6 +197,7 @@ class TfComposeElement extends LitElement {
let message = {
type: 'post',
text: edit.innerText,
+ channel: this.channel,
};
if (this.root || this.branch) {
message.root = this.root;
@@ -535,6 +537,9 @@ class TfComposeElement extends LitElement {
class="w3-card-4 w3-theme-d4 w3-padding-small"
style="box-sizing: border-box"
>
+ ${this.channel !== undefined ?
+ html`
diff --git a/apps/ssb/tf-message.js b/apps/ssb/tf-message.js
index ceb64d30..5701e75d 100644
--- a/apps/ssb/tf-message.js
+++ b/apps/ssb/tf-message.js
@@ -14,6 +14,8 @@ class TfMessageElement extends LitElement {
format: {type: String},
blog_data: {type: String},
expanded: {type: Object},
+ channel: {type: String},
+ channel_unread: {type: Number},
};
}
@@ -28,6 +30,7 @@ class TfMessageElement extends LitElement {
this.drafts = {};
this.format = 'message';
this.expanded = {};
+ this.channel_unread = -1;
}
show_reply() {
@@ -312,12 +315,25 @@ ${JSON.stringify(mention, null, 2)}`
)}`;
}
}
}
+ mark_read() {
+ this.dispatchEvent(new CustomEvent('channelsetunread', {
+ bubbles: true,
+ composed: true,
+ detail: {
+ channel: this.channel,
+ unread: this.message.rowid + 1,
+ },
+ }));
+ }
+
render_channels() {
let content = this.message?.content;
if (this?.messsage?.decrypted?.type == 'post') {
@@ -344,7 +360,7 @@ ${JSON.stringify(mention, null, 2)}= this.channel_unread ? 'w3-theme-d2' : 'w3-theme-d4');
let self = this;
let raw_button;
switch (this.format) {
@@ -423,6 +439,8 @@ ${JSON.stringify(mention, null, 2)}
`
)}
@@ -442,6 +460,8 @@ ${JSON.stringify(mention, null, 2)}`
)}
`;
@@ -463,6 +483,8 @@ ${JSON.stringify(mention, null, 2)}
`
)}
@@ -618,6 +640,11 @@ ${JSON.stringify(content, null, 2)}
React
+ ${!content.root ?
+ html`
+
+ ` :
+ undefined}
${this.render_children()}
diff --git a/apps/ssb/tf-news.js b/apps/ssb/tf-news.js
index 816f10a6..6645e940 100644
--- a/apps/ssb/tf-news.js
+++ b/apps/ssb/tf-news.js
@@ -11,6 +11,8 @@ class TfNewsElement extends LitElement {
following: {type: Array},
drafts: {type: Object},
expanded: {type: Object},
+ channel: {type: String},
+ channel_unread: {type: Number},
};
}
@@ -25,6 +27,7 @@ class TfNewsElement extends LitElement {
this.following = [];
this.drafts = {};
this.expanded = {};
+ this.channel_unread = -1;
}
process_messages(messages) {
@@ -179,7 +182,7 @@ class TfNewsElement extends LitElement {
this.finalize_messages(messages_by_id)
);
return html`
-
+
${final_messages.map(
(x) =>
html``
)}
diff --git a/apps/ssb/tf-tab-news-feed.js b/apps/ssb/tf-tab-news-feed.js
index f6c455d1..d93d1e06 100644
--- a/apps/ssb/tf-tab-news-feed.js
+++ b/apps/ssb/tf-tab-news-feed.js
@@ -12,6 +12,9 @@ class TfTabNewsFeedElement extends LitElement {
messages: {type: Array},
drafts: {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.drafts = {};
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('#@')) {
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
WHERE messages.author = ?
ORDER BY sequence DESC
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
JOIN messages_refs ON mine.id = messages_refs.ref
JOIN messages ON messages_refs.message = messages.id
@@ -62,24 +71,27 @@ class TfTabNewsFeedElement extends LitElement {
`,
[this.hash.substring(1)]
);
- } else {
+ } else if (this.hash.startsWith('##')) {
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.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
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)
- 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
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
+ 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
@@ -88,12 +100,42 @@ class TfTabNewsFeedElement extends LitElement {
`,
[
JSON.stringify(this.following.slice(i, i + k_following_limit)),
- this.start_time,
- /*
- ** Don't show messages more than a day into the future to prevent
- ** messages with far-future timestamps from staying at the top forever.
- */
- new Date().valueOf() + 24 * 60 * 60 * 1000,
+ start_time,
+ end_time,
+ this.hash.substring(2),
+ ]
+ )
+ );
+ }
+ 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() {
- let last_start_time = this.start_time;
- this.start_time = last_start_time - 24 * 60 * 60 * 1000;
- let more = await 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
- 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.loading++;
+ try {
+ let more = [];
+ while (!more.length) {
+ let last_start_time = this.start_time;
+ this.start_time = last_start_time - 7 * 24 * 60 * 60 * 1000;
+ more = await this.fetch_messages(this.start_time, last_start_time);
+ this.time_range = [this.start_time, this.time_range[1]];
+ }
+ this.messages = await this.decrypt([...more, ...this.messages]);
+ } finally {
+ this.loading--;
+ }
}
async decrypt(messages) {
@@ -160,6 +190,51 @@ class TfTabNewsFeedElement extends LitElement {
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() {
if (
!this.messages ||
@@ -169,27 +244,17 @@ class TfTabNewsFeedElement extends LitElement {
console.log(
`loading messages for ${this.whoami} (following ${this.following.length})`
);
- let self = this;
- 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));
- });
+ this.load_messages();
}
let more;
if (!this.hash.startsWith('#@') && !this.hash.startsWith('#%')) {
more = html`
-
`;
}
@@ -202,6 +267,8 @@ class TfTabNewsFeedElement extends LitElement {
.following=${this.following}
.drafts=${this.drafts}
.expanded=${this.expanded}
+ channel=${this.channel()}
+ channel_unread=${this.channels_unread?.[this.channel()]}
>
${more}
`;
diff --git a/apps/ssb/tf-tab-news.js b/apps/ssb/tf-tab-news.js
index 4b3fca2a..22b109d0 100644
--- a/apps/ssb/tf-tab-news.js
+++ b/apps/ssb/tf-tab-news.js
@@ -13,6 +13,9 @@ class TfTabNewsElement extends LitElement {
drafts: {type: Object},
expanded: {type: Object},
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.drafts = {};
this.expanded = {};
+ this.channels_unread = {};
+ this.channels_latest = {};
+ this.channels = [];
tfrpc.rpc.localStorageGet('drafts').then(function (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() {
let profile = this.hash.startsWith('#@')
? html`
`;
}
return html`
-
-
- ${this.new_messages_text()}
-
-
-
- Welcome,
!
- ${edit_profile}
+
-
-
+
+
+
+ ${this.new_messages_text()}
+
+ ${this.hash.startsWith('##') ?
+ html`
+
+ ${this.channels.indexOf(this.hash.substring(2)) != -1 ? 'Unsubscribe from #' : 'Subscribe to #'}${this.hash.substring(2)}
+
+ ` :
+ undefined}
+
+
+ Welcome, !
+ ${edit_profile}
+
+
+
+
+ ${profile}
+
+ @tf-expand=${this.on_expand}
+ .channels_unread=${this.channels_unread}
+ >
- ${profile}
-
`;
}
}