@closerclick/closer-click-profile 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.js +93 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@closerclick/closer-click-profile",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Web Component (custom element) <closer-click-profile> reutilizable por cualquier app del ecosistema Closer Click: tarjeta de perfil + reputación (confianza/afinidad, web-of-trust, reputación de la red). Autohosteado, Shadow DOM, temable por CSS vars.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
package/src/index.js
CHANGED
|
@@ -33,7 +33,9 @@
|
|
|
33
33
|
* name nombre/nick a mostrar
|
|
34
34
|
* since timestamp ms del primer contacto ("conocido desde")
|
|
35
35
|
* online booleano: muestra el punto de en-línea
|
|
36
|
-
* mode 'edit' (default) | 'view' (solo lectura
|
|
36
|
+
* mode 'edit' (default, calificar a un peer) | 'view' (solo lectura)
|
|
37
|
+
* | 'self' (TU perfil: nombre editable que se guarda en el vault,
|
|
38
|
+
* sin calificación; conserva los paneles de reputación)
|
|
37
39
|
* modal booleano: envuelve la tarjeta en backdrop + header/footer
|
|
38
40
|
* heading título del header (override)
|
|
39
41
|
* lang 'es' | 'en' | 'auto' (default 'auto')
|
|
@@ -42,6 +44,7 @@
|
|
|
42
44
|
* Métodos: el.reload()
|
|
43
45
|
* Eventos (bubbles, composed):
|
|
44
46
|
* 'cc-profile-rate' detail { pubkey, indicators, notes } (tras guardar)
|
|
47
|
+
* 'cc-profile-name' detail { pubkey, name } (mode="self", tras guardar tu nombre)
|
|
45
48
|
* 'cc-profile-close'
|
|
46
49
|
* 'cc-profile-refresh' detail { pubkey } (botón ↻ del web-of-trust)
|
|
47
50
|
*/
|
|
@@ -78,6 +81,11 @@ const I18N = {
|
|
|
78
81
|
saving: 'Guardando…',
|
|
79
82
|
close: 'Cerrar',
|
|
80
83
|
saveError: 'Error al guardar',
|
|
84
|
+
headingSelf: 'Mi perfil',
|
|
85
|
+
editName: 'Tu nombre visible',
|
|
86
|
+
nickPh: 'Tu nombre',
|
|
87
|
+
saveName: 'Guardar',
|
|
88
|
+
nameSaved: '✓ Guardado',
|
|
81
89
|
labels: ['Sin calificar', 'Sospechoso', 'Dudoso', 'Confiable', 'Muy confiable', 'De total confianza'],
|
|
82
90
|
},
|
|
83
91
|
en: {
|
|
@@ -111,6 +119,11 @@ const I18N = {
|
|
|
111
119
|
saving: 'Saving…',
|
|
112
120
|
close: 'Close',
|
|
113
121
|
saveError: 'Save error',
|
|
122
|
+
headingSelf: 'My profile',
|
|
123
|
+
editName: 'Your display name',
|
|
124
|
+
nickPh: 'Your name',
|
|
125
|
+
saveName: 'Save',
|
|
126
|
+
nameSaved: '✓ Saved',
|
|
114
127
|
labels: ['Unrated', 'Suspicious', 'Doubtful', 'Trustworthy', 'Very trustworthy', 'Fully trusted'],
|
|
115
128
|
},
|
|
116
129
|
}
|
|
@@ -211,6 +224,18 @@ const STYLE = `
|
|
|
211
224
|
}
|
|
212
225
|
.identity-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
213
226
|
.name { font-family: var(--_font-headline); font-weight: 600; font-size: 17px; color: var(--_text); }
|
|
227
|
+
/* ----- Editor de tu propio nombre (mode="self") ----- */
|
|
228
|
+
.nick-edit { display: flex; flex-direction: column; gap: 6px; }
|
|
229
|
+
.nick-label { font-size: 12px; color: var(--_muted); }
|
|
230
|
+
.nick-row { display: flex; gap: 8px; align-items: center; }
|
|
231
|
+
.nick-input {
|
|
232
|
+
flex: 1; min-width: 0; font: inherit; font-size: 15px; color: var(--_text);
|
|
233
|
+
background: var(--ccp-input-bg, #fff); border: 1px solid var(--_border); border-radius: 8px;
|
|
234
|
+
padding: 8px 10px;
|
|
235
|
+
}
|
|
236
|
+
.nick-input:focus { outline: none; border-color: var(--_accent); }
|
|
237
|
+
.nick-save { white-space: nowrap; }
|
|
238
|
+
.nick-saved { font-size: 12px; color: var(--_online); }
|
|
214
239
|
.pubkey {
|
|
215
240
|
background: var(--_bg-3); padding: 2px 8px; border-radius: 6px;
|
|
216
241
|
font-family: var(--_font-mono); font-size: 11.5px; color: var(--_muted);
|
|
@@ -309,6 +334,9 @@ class CloserClickProfile extends HTMLElement {
|
|
|
309
334
|
this._hoverAfin = 0
|
|
310
335
|
this._saving = false
|
|
311
336
|
this._error = ''
|
|
337
|
+
this._savingName = false
|
|
338
|
+
this._nameSaved = false
|
|
339
|
+
this._nameErr = ''
|
|
312
340
|
this._loadToken = 0
|
|
313
341
|
this._onKeydown = this._onKeydown.bind(this)
|
|
314
342
|
}
|
|
@@ -346,7 +374,12 @@ class CloserClickProfile extends HTMLElement {
|
|
|
346
374
|
}
|
|
347
375
|
get _t() { return I18N[this._lang] }
|
|
348
376
|
get _pubkey() { return this.getAttribute('pubkey') || '' }
|
|
349
|
-
get
|
|
377
|
+
get _mode() { return (this.getAttribute('mode') || 'edit').toLowerCase() }
|
|
378
|
+
// Editor de calificación (confianza/afinidad/notas): solo en 'edit'.
|
|
379
|
+
get _editable() { return this._mode === 'edit' }
|
|
380
|
+
// 'self' = tu propio perfil: nombre editable (se escribe al vault), sin
|
|
381
|
+
// calificación (no te calificas a ti mismo).
|
|
382
|
+
get _self() { return this._mode === 'self' }
|
|
350
383
|
|
|
351
384
|
_resetState() {
|
|
352
385
|
this._my = { confianza: 0, afinidad: 0, notes: '' }
|
|
@@ -357,6 +390,8 @@ class CloserClickProfile extends HTMLElement {
|
|
|
357
390
|
this._hover = 0
|
|
358
391
|
this._hoverAfin = 0
|
|
359
392
|
this._error = ''
|
|
393
|
+
this._nameSaved = false
|
|
394
|
+
this._nameErr = ''
|
|
360
395
|
}
|
|
361
396
|
|
|
362
397
|
/* ---- carga de datos vía provider ---- */
|
|
@@ -418,6 +453,32 @@ class CloserClickProfile extends HTMLElement {
|
|
|
418
453
|
}
|
|
419
454
|
}
|
|
420
455
|
|
|
456
|
+
// Guarda tu nombre visible (mode="self") en el vault vía provider.setMyName.
|
|
457
|
+
async _saveName() {
|
|
458
|
+
if (this._savingName) return
|
|
459
|
+
const p = this._provider
|
|
460
|
+
const input = this.shadowRoot.querySelector('.nick-input')
|
|
461
|
+
if (!input || !p || typeof p.setMyName !== 'function') return
|
|
462
|
+
const name = (input.value || '').trim()
|
|
463
|
+
if (!name) return
|
|
464
|
+
if (name === (this.getAttribute('name') || '')) { this._nameSaved = true; this._render(); return }
|
|
465
|
+
this._savingName = true
|
|
466
|
+
this._nameErr = ''
|
|
467
|
+
this._render()
|
|
468
|
+
try {
|
|
469
|
+
await p.setMyName(name)
|
|
470
|
+
this._savingName = false
|
|
471
|
+
this._nameSaved = true
|
|
472
|
+
this._emit('cc-profile-name', { pubkey: this._pubkey, name })
|
|
473
|
+
this.setAttribute('name', name) // refleja el nombre nuevo (re-render por attributeChangedCallback)
|
|
474
|
+
this._render()
|
|
475
|
+
} catch (e) {
|
|
476
|
+
this._savingName = false
|
|
477
|
+
this._nameErr = (e && e.message) || this._t.saveError
|
|
478
|
+
this._render()
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
421
482
|
_close() { this._emit('cc-profile-close', { pubkey: this._pubkey }) }
|
|
422
483
|
_refresh() { this._emit('cc-profile-refresh', { pubkey: this._pubkey }); this.reload() }
|
|
423
484
|
_emit(type, detail) { this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true })) }
|
|
@@ -471,7 +532,7 @@ class CloserClickProfile extends HTMLElement {
|
|
|
471
532
|
const name = this.getAttribute('name') || t.contact
|
|
472
533
|
const since = this.getAttribute('since')
|
|
473
534
|
const online = this.hasAttribute('online') && this.getAttribute('online') !== 'false'
|
|
474
|
-
const heading = this.getAttribute('heading') || (editable ? t.headingEdit : t.headingView)
|
|
535
|
+
const heading = this.getAttribute('heading') || (this._self ? t.headingSelf : editable ? t.headingEdit : t.headingView)
|
|
475
536
|
|
|
476
537
|
const confLabel = t.labels[this._hover || this._my.confianza] || t.labels[0]
|
|
477
538
|
|
|
@@ -483,7 +544,16 @@ class CloserClickProfile extends HTMLElement {
|
|
|
483
544
|
${online ? '<span class="online-dot"></span>' : ''}
|
|
484
545
|
</div>
|
|
485
546
|
<div class="identity-text">
|
|
486
|
-
|
|
547
|
+
${this._self ? `
|
|
548
|
+
<label class="nick-edit">
|
|
549
|
+
<span class="nick-label">${this._esc(t.editName)}</span>
|
|
550
|
+
<div class="nick-row">
|
|
551
|
+
<input class="nick-input" type="text" maxlength="40" value="${this._esc(name === t.contact ? '' : name)}" placeholder="${this._esc(t.nickPh)}" />
|
|
552
|
+
<button type="button" class="btn primary nick-save" data-savename ${this._savingName ? 'disabled' : ''}>${this._esc(this._savingName ? t.saving : t.saveName)}</button>
|
|
553
|
+
</div>
|
|
554
|
+
${this._nameSaved ? `<span class="nick-saved">${this._esc(t.nameSaved)}</span>` : ''}
|
|
555
|
+
${this._nameErr ? `<span class="error">${this._esc(this._nameErr)}</span>` : ''}
|
|
556
|
+
</label>` : `<div class="name">${this._esc(name)}</div>`}
|
|
487
557
|
<code class="pubkey">${this._esc(this._shortKey(pk))}</code>
|
|
488
558
|
${since ? `<div class="since">${this._esc(t.knownSince)} ${this._esc(this._fmtDate(since))}</div>` : ''}
|
|
489
559
|
</div>
|
|
@@ -632,6 +702,15 @@ class CloserClickProfile extends HTMLElement {
|
|
|
632
702
|
const refresh = q('[data-refresh]'); if (refresh) refresh.addEventListener('click', () => this._refresh())
|
|
633
703
|
const reload = q('[data-reload]'); if (reload) reload.addEventListener('click', () => this.reload())
|
|
634
704
|
|
|
705
|
+
if (this._self) {
|
|
706
|
+
const savename = q('[data-savename]'); if (savename) savename.addEventListener('click', () => this._saveName())
|
|
707
|
+
const nickInput = q('.nick-input')
|
|
708
|
+
if (nickInput) {
|
|
709
|
+
nickInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); this._saveName() } })
|
|
710
|
+
nickInput.addEventListener('input', () => { this._nameSaved = false; this._nameErr = '' })
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
635
714
|
if (this._editable) {
|
|
636
715
|
qa('[data-star]').forEach(btn => {
|
|
637
716
|
const kind = btn.getAttribute('data-star')
|
|
@@ -734,6 +813,16 @@ export function createVaultProfileProvider({ identity, reputation } = {}) {
|
|
|
734
813
|
async rate(pubkey, indicators, notes) {
|
|
735
814
|
return reputation.rate(pubkey, indicators, { notes })
|
|
736
815
|
},
|
|
816
|
+
|
|
817
|
+
// --- Tu propia identidad (para mode="self") ---
|
|
818
|
+
myPubkey,
|
|
819
|
+
getMyName() { return (identity && identity.me && identity.me.nickname) || null },
|
|
820
|
+
async setMyName(name) {
|
|
821
|
+
if (!identity || typeof identity.setMyNickname !== 'function') {
|
|
822
|
+
throw new Error('closer-click-profile: identity.setMyNickname no disponible')
|
|
823
|
+
}
|
|
824
|
+
return identity.setMyNickname(name)
|
|
825
|
+
},
|
|
737
826
|
}
|
|
738
827
|
}
|
|
739
828
|
|