updating post

This commit is contained in:
tsukino
2023-12-31 17:37:09 -08:00
parent 030a7c3957
commit 25975541f5
7 changed files with 192 additions and 113 deletions

View File

@@ -60,17 +60,22 @@ export class Observable<ObservableValue = any> {
}
}
subscribe = (subscription: Subscription<ObservableValue>) => {
subscribe = (
subscription: Subscription<ObservableValue>,
leading = false,
) => {
const currIndex = this.#subscriptions.indexOf(subscription);
if (currIndex === -1) {
this.#subscriptions.push(subscription);
// const sub = subscription;
// if (typeof sub === 'function') {
// sub(this.$);
// } else if (typeof sub !== 'function' && sub.next) {
// sub.next(this.$);
// }
if (leading) {
const sub = subscription;
if (typeof sub === 'function') {
sub(this.$);
} else if (typeof sub !== 'function' && sub.next) {
sub.next(this.$);
}
}
}
return () => {

View File

@@ -18,6 +18,10 @@ export class CustomElement extends HTMLElement implements ICustomElement {
#mounted = false;
#lastAttrUpdated = 0;
#attrUpdateTimeout: any;
#lastPainted = 0;
#paintTimeout: any;
$?: any;
effects: any[][] = [];
#selectors = new Map<string, Element>();
@@ -45,8 +49,8 @@ export class CustomElement extends HTMLElement implements ICustomElement {
return this.children[index];
}
listen(store: Observable) {
const cb = store.subscribe(this.#update);
listen(store: Observable, leading = false) {
const cb = store.subscribe(this.#update, leading);
this.#unsubscribes.push(cb);
}
@@ -66,7 +70,7 @@ export class CustomElement extends HTMLElement implements ICustomElement {
return this.#selectors.get(selector);
}
const el = this.shadowRoot.querySelector(selector);
const el = this.shadowRoot!.querySelector(selector);
if (el) {
this.#selectors.set(selector, el);
@@ -103,15 +107,40 @@ export class CustomElement extends HTMLElement implements ICustomElement {
};
#update = async () => {
if (this.update) await this.update();
this.unsubscribe();
await this.#subscribe();
const now = Date.now();
requestAnimationFrame(async () => {
const timeSince = now - this.#lastPainted;
const wait = 100;
const later = async () => {
if (this.#paintTimeout) {
clearTimeout(this.#paintTimeout);
this.#paintTimeout = null;
}
this.#lastPainted = now;
if (this.update) await this.update();
this.unsubscribe();
await this.#subscribe();
};
if (timeSince > wait) {
await later();
} else {
if (this.#paintTimeout) {
clearTimeout(this.#paintTimeout);
}
this.#paintTimeout = setTimeout(later, Math.max(0, wait - timeSince));
}
this.#lastPainted = now;
});
};
attributeChangedCallback(key: string, ov: string, nv: string) {
if (nv === ov) return;
requestAnimationFrame(async () => {
return requestAnimationFrame(async () => {
const now = Date.now();
const timeSince = now - this.#lastAttrUpdated;
const wait = 100;
@@ -137,6 +166,7 @@ export class CustomElement extends HTMLElement implements ICustomElement {
Math.max(0, wait - timeSince),
);
}
this.#lastAttrUpdated = now;
});
}
@@ -467,20 +497,17 @@ class UIRouter {
this.$pathname.subscribe(this.update);
}
#refreshPath = (evt?: any) => {
console.log(evt?.type, evt);
#refreshPath = () => {
this.$pathname.$ = window.location.pathname;
};
#init() {
if (!this.#hasInit) {
window.addEventListener('popstate', (evt) => {
console.log('popstate');
this.#refreshPath(evt);
window.addEventListener('popstate', () => {
this.#refreshPath();
});
window.addEventListener('DOMContentLoaded', (evt) => {
console.log('DOMContentLoadedstate');
this.#refreshPath(evt);
window.addEventListener('DOMContentLoaded', () => {
this.#refreshPath();
});
this.#hasInit = true;
}

View File

@@ -242,12 +242,11 @@ profile-image {
--border-radius: .25rem;
--opacity: .6;
--color: var(--slate-400);
cursor: pointer;
img {
width: var(--text-base);
height: var(--text-base);
transition:
filter 200ms ease-in-out;
}
&:hover,

View File

@@ -5,7 +5,13 @@ import {
h,
register,
} from '../../../lib/ui.ts';
import { format, fromNow, userId, userName } from '../../utils/misc.ts';
import {
debounce,
format,
fromNow,
userId,
userName,
} from '../../utils/misc.ts';
import CommentIcon from '../../../static/icons/comment.svg';
import RepostIcon from '../../../static/icons/repost.svg';
import RepostSlate300Icon from '../../../static/icons/repost-slate-300.svg';
@@ -37,31 +43,32 @@ export default class PostCard extends CustomElement {
async subscribe(): Promise<void> {
this.listen($signer.$ecdsa);
this.listen($editor.reference);
if (!this.state.hash) return;
const post = $node.$posts.get(this.state.hash);
this.listen(post);
if (post.$) {
const user = $node.$users.get(post.$.creator);
this.listen(user);
}
this.listen(post);
const repost = $node.getRepostRef(this.state.hash);
const tpostHash = repost?.$?.hash || this.state.hash;
const tpost = $node.$posts.get(tpostHash);
const messageId = tpost.$?.messageId;
if (!tpostHash || !messageId) return;
$node.$replies.get(messageId).subscribe(this.update);
$node.$posts.get(tpostHash).subscribe(this.update);
$node.$postmetas.get(messageId).subscribe(this.update);
this.listen($node.$posts.get(tpostHash));
this.listen($node.$postmetas.get(messageId));
}
update = async () => {
if (!this.shadowRoot) return;
const post = $node.getPost(this.state.hash);
const repost =
post?.subtype === PostSubtype.Repost && post.reference

View File

@@ -30,7 +30,7 @@ export default class App extends CustomElement {
hash,
onclick: () => {
const repost = $node.getRepostRef(hash);
const newHash = repost?.hash || hash;
const newHash = repost?.$?.hash || hash;
const post = $node.getPost(newHash);
const [creator, postHash] =
$node.getPost(hash)!.messageId.split('/') || [];

View File

@@ -1,6 +1,5 @@
import {
boolAttr,
connect,
CustomElement,
h,
register,
@@ -15,60 +14,107 @@ import { Observable } from '../../../lib/state.ts';
import '../../components/Post';
import '../../components/LeftSidebar';
// @connect(() => {
// return {
// pathname: Router.$pathname,
// reference: $editor.reference,
// };
// })
export default class PostView extends CustomElement {
css = css.toString();
parents = new Observable<string[]>([]);
// async onupdated() {
// const [, creator, , h] = Router.pathname.split('/');
// const repost = $node.getRepostRef(h);
// const messageId = !repost ? creator + '/' + h : repost.messageId;
// const hash = repost?.hash || h;
//
// useEffect(
// async () => {
// // $node.$replies.get(messageId).subscribe(this._update);
// // $node.$posts.get(hash).subscribe(this._update);
// const parents = await $node.getParents(hash);
// this.parents.$ = parents;
// },
// [hash, this.$.parents.$.join('+')],
// this,
// );
// }
renderParents(): VNode {
return h(
'div.parents',
...this.parents.$.map((parent: string) => {
const [creator, hash] = parent.split('/');
const parentHash = hash || creator;
// @ts-ignore
return h('post-card.parent', {
...boolAttr('parent', true),
hash: parentHash,
onclick: () => {
const url = `/${creator}/status/${hash}`;
$editor.reference.$ = parent;
$node.getReplies(parent);
Router.go(url);
},
});
}),
);
async onmount() {
const [, , , h] = Router.pathname.split('/');
const repost = $node.getRepostRef(h);
const hash = repost?.$?.hash || h;
this.parents.$ = await $node.getParents(hash);
}
// async update(): Promise<void> {
// const [, , , hash] = Router.pathname.split('/');
// this.query('div.posts > post-card')!.setAttribute('hash', hash);
// }
async subscribe(): Promise<void> {
const [, creator, , h] = Router.pathname.split('/');
const repost = $node.getRepostRef(h);
const messageId = !repost ? creator + '/' + h : repost?.$?.messageId;
const hash = repost?.$?.hash || h;
this.listen(this.parents);
this.listen(Router.$pathname);
this.listen($node.$posts.get(hash));
if (repost?.$) {
this.listen($node.$posts.get(repost.$.hash));
}
if (messageId) {
this.listen($node.$replies.get(messageId));
}
}
renderParents(): VNode[] {
return this.parents.$.map((parent: string) => {
const [creator, hash] = parent.split('/');
const parentHash = hash || creator;
// @ts-ignore
return h('post-card.parent', {
...boolAttr('parent', true),
hash: parentHash,
onclick: () => {
const url = `/${creator}/status/${hash}`;
$editor.reference.$ = parent;
$node.getReplies(parent);
Router.go(url);
},
});
});
}
async update(): Promise<void> {
const [, , , hash] = Router.pathname.split('/');
const [, , , h] = Router.pathname.split('/');
const repost = $node.getRepostRef(h);
this.parents.$ = await $node.getParents(repost?.$?.hash || h);
this.query('div.posts > post-card')!.setAttribute('hash', hash);
this.updateParents();
this.updateReplies();
}
updateReplies() {
const oldReplies = Array.from(this.query('div.replies')!.children).slice();
const newReplies = this.renderReplies().map((node) => node.createElement());
const maxlen = Math.max(oldReplies.length, newReplies.length);
for (let i = 0; i < maxlen; i++) {
const oldEl = oldReplies[i];
const newEl = newReplies[i]?.children[0];
if (!oldEl && newEl) {
this.query('div.replies')!.append(newReplies[i]);
} else if (oldEl && newEl) {
if (oldEl.getAttribute('hash') !== newEl.getAttribute('hash')) {
// oldEl.setAttribute('hash', newEl.getAttribute('hash') || '');
oldEl.replaceWith(newReplies[i]);
}
} else if (oldEl && !newEl) {
this.query('div.replies')!.removeChild(oldEl);
}
}
}
updateParents() {
const oldParents = Array.from(this.query('div.parents')!.children).slice();
const newParents = this.renderParents().map((node) => node.createElement());
const maxlen = Math.max(oldParents.length, newParents.length);
for (let i = 0; i < maxlen; i++) {
const oldEl = oldParents[i];
const newEl = newParents[i]?.children[0];
if (!oldEl && newEl) {
this.query('div.parents')!.append(newParents[i]);
} else if (oldEl && newEl) {
if (oldEl.getAttribute('hash') !== newEl.getAttribute('hash')) {
oldEl.setAttribute('hash', newEl.getAttribute('hash') || '');
}
} else if (oldEl && !newEl) {
this.query('div.parents')!.removeChild(oldEl);
}
}
}
render() {
const [, , , hash] = Router.pathname.split('/');
@@ -78,41 +124,38 @@ export default class PostView extends CustomElement {
h('left-sidebar'),
h(
'div.posts',
// this.renderParents(),
h('div.parents', this.renderParents()),
h(`post-card`, {
...boolAttr('comfortable', true),
...boolAttr('displayparent', true),
hash,
}),
// this.renderReplies(),
h('div.replies', this.renderReplies()),
h('div.posts__bottom'),
),
h('div.sidebar'),
);
}
renderReplies = () => {
renderReplies = (): VNode[] => {
const [, creator, , hash] = Router.pathname.split('/');
const repost = $node.getRepostRef(hash);
const messageId = repost ? repost.messageId : creator + '/' + hash;
const replies = $node.getReplies(messageId);
const messageId = repost ? repost.$?.messageId : creator + '/' + hash;
const replies = $node.$replies.get(messageId!).$ || [];
return h(
'div.replies',
replies?.map((mid) => {
const [c, _h] = mid.split('/');
// @ts-ignore
return h('post-card.reply', {
hash: _h || c,
onclick: () => {
const url = c ? `/${c}/status/${_h}` : `/${_h}`;
$editor.reference.$ = mid;
$node.getReplies(mid);
Router.go(url);
},
});
}),
);
return replies.map((mid) => {
const [c, _h] = mid.split('/');
// @ts-ignore
return h('post-card.reply', {
hash: _h || c,
onclick: () => {
const url = c ? `/${c}/status/${_h}` : `/${_h}`;
$editor.reference.$ = mid;
$node.getReplies(mid);
Router.go(url);
},
});
});
};
}

View File

@@ -119,17 +119,6 @@ export class NodeStore {
return store.$;
};
async #updateReplies(messageId: string) {
const replies = await this.node.db.db.getReplies(messageId);
const $replies = this.$replies.get(messageId);
$replies.$ = replies.map((p) => {
if (this.$posts.get(p.hash).$?.hex !== p.hex) {
this.$posts.set(p.hash, p);
}
return p.messageId;
});
}
async getParents(hash: string, list: string[] = []): Promise<string[]> {
const p = this.getPost(hash);
const post = p || (await this.node.db.db.getMessage<Post>(hash));
@@ -165,6 +154,8 @@ export class NodeStore {
const repostHash = rpHash || rpCreator;
if (repostHash) {
console.log(repostHash, $node.$posts.get(repostHash));
return $node.$posts.get(repostHash);
}
@@ -172,8 +163,15 @@ export class NodeStore {
}
getReplies = (messageId: string) => {
this.#updateReplies(messageId);
const $replies = this.$replies.get(messageId);
this.node.db.db.getReplies(messageId).then((replies) => {
$replies.$ = replies.map((p) => {
if (this.$posts.get(p.hash).$?.hex !== p.hex) {
this.$posts.set(p.hash, p);
}
return p.messageId;
});
});
return $replies.$;
};