Files
docmd-io--docmd/packages/plugins/threads/src/client/components/threads-identity.ts
T
wehub-resource-sync 6db8fca185
docmd CI verification / verify (push) Failing after 0s
chore: import upstream snapshot with attribution
2026-07-13 12:31:55 +08:00

167 lines
4.9 KiB
TypeScript

import { LitElement, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { t } from '../lib/i18n';
import {
getAuthor, setAuthor,
getEmail, setEmail,
getGithub, setGithub,
getAvatarUrl, setAvatarUrl,
computeAvatarUrl,
computeAuthorKey, setAuthorKey,
} from '../lib/identity';
import { upsertAuthor } from '../lib/api';
@customElement('threads-identity')
export class ThreadsIdentity extends LitElement {
override createRenderRoot() { return this; }
@state() private name = '';
@state() private email = '';
@state() private github = '';
@state() private avatarUrl = '';
@state() private panelOpen = false;
private outsideClickHandler = (e: MouseEvent) => {
if (!this.contains(e.target as Node)) {
this.panelOpen = false;
}
};
override connectedCallback(): void {
super.connectedCallback();
this.name = getAuthor() || '';
this.email = getEmail() || '';
this.github = getGithub() || '';
this.avatarUrl = getAvatarUrl() || '';
// If we have identity info but no avatar yet, compute one
if (!this.avatarUrl && (this.email || this.github)) {
this.refreshAvatar();
}
document.addEventListener('mousedown', this.outsideClickHandler);
}
override disconnectedCallback(): void {
super.disconnectedCallback();
document.removeEventListener('mousedown', this.outsideClickHandler);
}
private togglePanel(): void {
this.panelOpen = !this.panelOpen;
}
private async handleSave(): Promise<void> {
const nameInput = this.querySelector<HTMLInputElement>('#identity-name');
const emailInput = this.querySelector<HTMLInputElement>('#identity-email');
const githubInput = this.querySelector<HTMLInputElement>('#identity-github');
if (nameInput) {
const val = nameInput.value.trim();
this.name = val;
if (val) setAuthor(val);
}
if (emailInput) {
const val = emailInput.value.trim();
this.email = val;
setEmail(val);
}
if (githubInput) {
const val = githubInput.value.trim().replace(/^@/, '');
this.github = val;
setGithub(val);
}
await this.refreshAvatar();
// Compute and save author key
const authorKey = computeAuthorKey(this.name, this.github);
setAuthorKey(authorKey);
// Sync to server authors.json
try {
await upsertAuthor(authorKey, this.name, this.avatarUrl);
} catch {
// Server may not be available (e.g. static site); ignore
}
this.panelOpen = false;
}
private async refreshAvatar(): Promise<void> {
const url = await computeAvatarUrl(this.email, this.github);
this.avatarUrl = url;
setAvatarUrl(url);
}
private handleCancel(): void {
this.panelOpen = false;
}
override render() {
const initial = this.name ? this.name.charAt(0).toUpperCase() : '?';
return html`
<button
class="threads-identity-btn"
title=${t('discussionIdentity')}
@click=${this.togglePanel}
>
${this.avatarUrl
? html`<img class="threads-identity-avatar" src=${this.avatarUrl} alt=${this.name} />`
: html`<span class="threads-identity-initial">${initial}</span>`
}
</button>
${this.panelOpen ? html`
<div class="threads-identity-panel">
<div class="threads-identity-preview">
${this.avatarUrl
? html`<img class="threads-identity-preview-img" src=${this.avatarUrl} alt=${this.name} />`
: html`<div class="threads-identity-preview-placeholder">${initial}</div>`
}
</div>
<label class="threads-identity-label">
${t('displayName')}
<input
id="identity-name"
class="threads-identity-input"
type="text"
.value=${this.name}
placeholder=${t('yourName')}
/>
</label>
<label class="threads-identity-label">
${t('githubUsername')}
<input
id="identity-github"
class="threads-identity-input"
type="text"
.value=${this.github}
placeholder="octocat"
/>
</label>
<label class="threads-identity-label">
${t('gravatarEmail')}
<input
id="identity-email"
class="threads-identity-input"
type="email"
.value=${this.email}
placeholder="you@example.com"
/>
</label>
<p class="threads-identity-hint">
${t('avatarHint')}
</p>
<div class="threads-identity-actions">
<button class="threads-identity-cancel" @click=${this.handleCancel}>${t('cancel')}</button>
<button class="threads-identity-save" @click=${this.handleSave}>${t('save')}</button>
</div>
</div>
` : ''}
`;
}
}