.gitea
apps
admin
api
apps
blog
db
follow
identity
intro
issues
journal
room
sneaker
ssb
app.js
commonmark-hashtag.js
commonmark.min.js
emojis.js
emojis.json
filesaver.min.js
filesaver.min.js.map
index.html
lit-all.min.js
lit-all.min.js.map
script.js
tf-app.js
tf-compose.js
tf-message.js
tf-news.js
tf-profile.js
tf-reactions-modal.js
tf-styles.js
tf-tab-connections.js
tf-tab-news-feed.js
tf-tab-news.js
tf-tab-query.js
tf-tab-search.js
tf-tag.js
tf-user.js
tf-utils.js
tribute.css
tribute.esm.js
storage
test
todo
web
welcome
wiki
admin.json
api.json
apps.json
blog.json
db.json
follow.json
identity.json
intro.json
issues.json
journal.json
room.json
sneaker.json
ssb.json
storage.json
test.json
todo.json
web.json
welcome.json
wiki.json
core
deps
docs
metadata
src
tools
.clang-format
.dockerignore
.git-blame-ignore-revs
.gitignore
.gitmodules
.prettierignore
.prettierrc.yaml
CONTRIBUTING.md
Dockerfile
Doxyfile
GNUmakefile
LICENSE
README.md
default.nix
flake.lock
flake.nix
package-lock.json
package.json
368 lines
9.1 KiB
JavaScript
368 lines
9.1 KiB
JavaScript
import {
|
||
LitElement,
|
||
cache,
|
||
keyed,
|
||
html,
|
||
unsafeHTML,
|
||
until,
|
||
} from './lit-all.min.js';
|
||
import * as tfrpc from '/static/tfrpc.js';
|
||
import {styles} from './tf-styles.js';
|
||
|
||
class TfTabNewsElement extends LitElement {
|
||
static get properties() {
|
||
return {
|
||
whoami: {type: String},
|
||
users: {type: Object},
|
||
hash: {type: String},
|
||
following: {type: Array},
|
||
drafts: {type: Object},
|
||
expanded: {type: Object},
|
||
loading: {type: Boolean},
|
||
channels: {type: Array},
|
||
channels_unread: {type: Object},
|
||
channels_latest: {type: Object},
|
||
connections: {type: Array},
|
||
private_messages: {type: Array},
|
||
recent_reactions: {type: Array},
|
||
};
|
||
}
|
||
|
||
static styles = styles;
|
||
|
||
constructor() {
|
||
super();
|
||
let self = this;
|
||
this.whoami = null;
|
||
this.users = {};
|
||
this.hash = '#';
|
||
this.following = [];
|
||
this.cache = {};
|
||
this.drafts = {};
|
||
this.expanded = {};
|
||
this.channels_unread = {};
|
||
this.channels_latest = {};
|
||
this.channels = [];
|
||
this.connections = [];
|
||
this.recent_reactions = [];
|
||
tfrpc.rpc.localStorageGet('drafts').then(function (d) {
|
||
self.drafts = JSON.parse(d || '{}');
|
||
});
|
||
}
|
||
|
||
connectedCallback() {
|
||
super.connectedCallback();
|
||
document.body.addEventListener('keypress', this.on_keypress.bind(this));
|
||
}
|
||
|
||
disconnectedCallback() {
|
||
super.disconnectedCallback();
|
||
document.body.removeEventListener('keypress', this.on_keypress.bind(this));
|
||
}
|
||
|
||
load_latest() {
|
||
let news = this.shadowRoot?.getElementById('news');
|
||
if (news) {
|
||
news.load_latest();
|
||
}
|
||
}
|
||
|
||
draft(event) {
|
||
let id = event.detail.id || '';
|
||
let previous = this.drafts[id];
|
||
if (event.detail.draft !== undefined) {
|
||
this.drafts[id] = event.detail.draft;
|
||
} else {
|
||
delete this.drafts[id];
|
||
}
|
||
this.drafts = Object.assign({}, this.drafts);
|
||
tfrpc.rpc.localStorageSet('drafts', JSON.stringify(this.drafts));
|
||
}
|
||
|
||
on_expand(event) {
|
||
if (event.detail.expanded) {
|
||
let expand = {};
|
||
expand[event.detail.id] = true;
|
||
this.expanded = Object.assign({}, this.expanded, expand);
|
||
} else {
|
||
delete this.expanded[event.detail.id];
|
||
this.expanded = Object.assign({}, this.expanded);
|
||
}
|
||
}
|
||
|
||
on_keypress(event) {
|
||
if (event.target === document.body && event.key == '.') {
|
||
this.show_more();
|
||
}
|
||
}
|
||
|
||
unread_status(channel) {
|
||
if (channel === undefined) {
|
||
if (
|
||
Object.keys(this.channels_unread).some((x) => this.unread_status(x))
|
||
) {
|
||
return '✉️ ';
|
||
}
|
||
} else if (
|
||
this.channels_latest[channel] &&
|
||
this.channels_latest[channel] > 0 &&
|
||
(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('sidebar_overlay').style.display = 'block';
|
||
}
|
||
|
||
hide_sidebar() {
|
||
this.renderRoot.getElementById('sidebar').style.display = 'none';
|
||
this.renderRoot.getElementById('sidebar_overlay').style.display = 'none';
|
||
}
|
||
|
||
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;
|
||
}
|
||
|
||
compare_follows() {
|
||
const now = new Date().valueOf();
|
||
return function (a, b) {
|
||
return (b[1].ts > now ? -1 : b[1].ts) - (a[1].ts > now ? -1 : a[1].ts);
|
||
};
|
||
}
|
||
|
||
suggested_follows() {
|
||
/*
|
||
** Filter out people who have used future timestamps so that they aren't
|
||
** pinned at the top.
|
||
*/
|
||
let self = this;
|
||
return Object.entries(this.users)
|
||
.filter((x) => x[1].follow_depth > 1)
|
||
.sort(self.compare_follows())
|
||
.slice(0, 8)
|
||
.map((x) => x[0]);
|
||
}
|
||
|
||
render_sidebar() {
|
||
return html`
|
||
<div
|
||
class="w3-sidebar w3-bar-block w3-theme-d1 w3-collapse w3-animate-left"
|
||
style="width: 2in; left: 0; z-index: 5; box-sizing: border-box; top: 0"
|
||
id="sidebar"
|
||
>
|
||
<div
|
||
class="w3-right w3-button w3-hide-large"
|
||
@click=${this.hide_sidebar}
|
||
>
|
||
×
|
||
</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}
|
||
<h4 class="w3-bar-item w3-theme-d2">Channels</h4>
|
||
<a
|
||
href="#"
|
||
class="w3-bar-item w3-button"
|
||
style=${this.hash == '#' ? 'font-weight: bold' : undefined}
|
||
>${this.unread_status('')}general</a
|
||
>
|
||
<a
|
||
href="#@"
|
||
class="w3-bar-item w3-button"
|
||
style=${this.hash == '#@' ? 'font-weight: bold' : undefined}
|
||
>${this.unread_status('@')}@mentions</a
|
||
>
|
||
<a
|
||
href="#🔐"
|
||
class="w3-bar-item w3-button"
|
||
style=${this.hash == '#🔐' ? 'font-weight: bold' : undefined}
|
||
>${this.unread_status('🔐')}🔐private</a
|
||
>
|
||
${Object.keys(this.drafts)
|
||
.sort()
|
||
.map(
|
||
(x) => html`
|
||
<a
|
||
href=${'#' + encodeURIComponent(x)}
|
||
class="w3-bar-item w3-button"
|
||
style="text-wrap: nowrap; text-overflow: ellipsis"
|
||
>📝 ${this.drafts[x]?.text ?? x}</a
|
||
>
|
||
`
|
||
)}
|
||
${this.channels.map(
|
||
(x) => html`
|
||
<a
|
||
href=${'#' + encodeURIComponent('#' + x)}
|
||
class="w3-bar-item w3-button"
|
||
style=${this.hash == '##' + x ? 'font-weight: bold' : undefined}
|
||
>${this.unread_status(x)}#${x}</a
|
||
>
|
||
`
|
||
)}
|
||
|
||
<a class="w3-bar-item w3-theme-d2 w3-button" href="#connections">
|
||
<h4 style="margin: 0">Connections</h4>
|
||
</a>
|
||
${this.connections
|
||
.filter((x) => x.id)
|
||
.map(
|
||
(x) => html`
|
||
<tf-user
|
||
class="w3-bar-item"
|
||
style=${x.destroy_reason
|
||
? 'border-left: 4px solid red; border-right: 4px solid red'
|
||
: x.connected
|
||
? x.flags?.one_shot
|
||
? 'border-left: 4px solid blue; border-right: 4px solid blue'
|
||
: 'border-left: 4px solid green; border-right: 4px solid green'
|
||
: ''}
|
||
id=${x.id}
|
||
fallback_name=${x.host}
|
||
.users=${this.users}
|
||
></tf-user>
|
||
`
|
||
)}
|
||
<h4 class="w3-bar-item w3-theme-d2">Suggested Follows</h4>
|
||
${this.suggested_follows().map(
|
||
(x) => html`
|
||
<tf-user
|
||
class="w3-bar-item"
|
||
style="max-width: 100%"
|
||
id=${x}
|
||
.users=${this.users}
|
||
></tf-user>
|
||
`
|
||
)}
|
||
</div>
|
||
<div
|
||
class="w3-overlay"
|
||
id="sidebar_overlay"
|
||
@click=${this.hide_sidebar}
|
||
></div>
|
||
`;
|
||
}
|
||
|
||
render() {
|
||
let profile =
|
||
this.hash.startsWith('#@') && this.hash != '#@'
|
||
? keyed(
|
||
this.hash.substring(1),
|
||
html`<tf-profile
|
||
class="tf-profile"
|
||
id=${this.hash.substring(1)}
|
||
whoami=${this.whoami}
|
||
.users=${this.users}
|
||
></tf-profile>`
|
||
)
|
||
: undefined;
|
||
let edit_profile;
|
||
if (
|
||
!this.loading &&
|
||
this.users[this.whoami]?.name === undefined &&
|
||
this.hash.substring(1) != this.whoami
|
||
) {
|
||
edit_profile = html` <div
|
||
class="w3-panel w3-padding w3-round w3-card-4 w3-theme-l3"
|
||
>
|
||
ℹ️ Follow your identity link ☝️ above to edit your profile and set your
|
||
name.
|
||
</div>`;
|
||
}
|
||
return cache(html`
|
||
${this.render_sidebar()}
|
||
<div
|
||
style="margin-left: 2in; padding: 0px; top: 0; max-height: 100%; overflow: auto; contain: layout"
|
||
id="main"
|
||
class="w3-main"
|
||
>
|
||
<div style="padding: 8px">
|
||
<p>
|
||
${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>
|
||
<div>
|
||
<div
|
||
id="show_sidebar"
|
||
class="w3-button w3-hide-large"
|
||
@click=${this.show_sidebar}
|
||
>
|
||
${this.unread_status()}☰
|
||
</div>
|
||
Welcome, <tf-user id=${this.whoami} .users=${this.users}></tf-user>!
|
||
${edit_profile}
|
||
</div>
|
||
<div>
|
||
<tf-compose
|
||
id="tf-compose"
|
||
whoami=${this.whoami}
|
||
.users=${this.users}
|
||
.drafts=${this.drafts}
|
||
@tf-draft=${this.draft}
|
||
.channel=${this.channel()}
|
||
></tf-compose>
|
||
</div>
|
||
${profile}
|
||
<tf-tab-news-feed
|
||
id="news"
|
||
whoami=${this.whoami}
|
||
.users=${this.users}
|
||
.following=${this.following}
|
||
hash=${this.hash}
|
||
.drafts=${this.drafts}
|
||
.expanded=${this.expanded}
|
||
@tf-draft=${this.draft}
|
||
@tf-expand=${this.on_expand}
|
||
.channels_unread=${this.channels_unread}
|
||
.channels_latest=${this.channels_latest}
|
||
.private_messages=${this.private_messages}
|
||
.recent_reactions=${this.recent_reactions}
|
||
></tf-tab-news-feed>
|
||
</div>
|
||
</div>
|
||
`);
|
||
}
|
||
}
|
||
|
||
customElements.define('tf-tab-news', TfTabNewsElement);
|