@closerclick/closer-click-profile 0.1.1 → 0.2.1
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 +97 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@closerclick/closer-click-profile",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
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
|
}
|
|
@@ -138,6 +151,9 @@ const STYLE = `
|
|
|
138
151
|
--_muted: var(--ccp-muted, var(--muted, #8a7a66));
|
|
139
152
|
--_accent: var(--ccp-accent, var(--accent, #c0392b));
|
|
140
153
|
--_accent-2: var(--ccp-accent-2, var(--accent-2, #a93226));
|
|
154
|
+
/* Texto sobre el acento (botón primario). Default blanco; las apps con un
|
|
155
|
+
acento claro (p. ej. lima) lo ponen oscuro vía --ccp-accent-text. */
|
|
156
|
+
--_accent-text: var(--ccp-accent-text, #fff);
|
|
141
157
|
--_gold: var(--ccp-gold, var(--gold, #d4a72c));
|
|
142
158
|
--_derived: var(--ccp-derived, var(--derived, #a37a45));
|
|
143
159
|
--_online: var(--ccp-online, var(--online, #5a8a3a));
|
|
@@ -211,6 +227,18 @@ const STYLE = `
|
|
|
211
227
|
}
|
|
212
228
|
.identity-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
213
229
|
.name { font-family: var(--_font-headline); font-weight: 600; font-size: 17px; color: var(--_text); }
|
|
230
|
+
/* ----- Editor de tu propio nombre (mode="self") ----- */
|
|
231
|
+
.nick-edit { display: flex; flex-direction: column; gap: 6px; }
|
|
232
|
+
.nick-label { font-size: 12px; color: var(--_muted); }
|
|
233
|
+
.nick-row { display: flex; gap: 8px; align-items: center; }
|
|
234
|
+
.nick-input {
|
|
235
|
+
flex: 1; min-width: 0; font: inherit; font-size: 15px; color: var(--_text);
|
|
236
|
+
background: var(--ccp-input-bg, #fff); border: 1px solid var(--_border); border-radius: 8px;
|
|
237
|
+
padding: 8px 10px;
|
|
238
|
+
}
|
|
239
|
+
.nick-input:focus { outline: none; border-color: var(--_accent); }
|
|
240
|
+
.nick-save { white-space: nowrap; }
|
|
241
|
+
.nick-saved { font-size: 12px; color: var(--_online); }
|
|
214
242
|
.pubkey {
|
|
215
243
|
background: var(--_bg-3); padding: 2px 8px; border-radius: 6px;
|
|
216
244
|
font-family: var(--_font-mono); font-size: 11.5px; color: var(--_muted);
|
|
@@ -284,7 +312,7 @@ const STYLE = `
|
|
|
284
312
|
/* ----- Footer ----- */
|
|
285
313
|
.foot { display: flex; gap: 10px; justify-content: flex-end; padding: 14px 24px; background: var(--_bg-2); border-top: 1px solid var(--_border); }
|
|
286
314
|
.btn { font: inherit; font-weight: 600; padding: 9px 16px; border-radius: 10px; border: 1px solid transparent; cursor: pointer; }
|
|
287
|
-
.btn.primary { background: var(--_accent); color:
|
|
315
|
+
.btn.primary { background: var(--_accent); color: var(--_accent-text); }
|
|
288
316
|
.btn.primary:hover:not(:disabled) { background: var(--_accent-2); }
|
|
289
317
|
.btn.primary:disabled { opacity: 0.6; cursor: default; }
|
|
290
318
|
.btn.secondary { background: transparent; color: var(--_text); border-color: var(--_border); }
|
|
@@ -309,6 +337,9 @@ class CloserClickProfile extends HTMLElement {
|
|
|
309
337
|
this._hoverAfin = 0
|
|
310
338
|
this._saving = false
|
|
311
339
|
this._error = ''
|
|
340
|
+
this._savingName = false
|
|
341
|
+
this._nameSaved = false
|
|
342
|
+
this._nameErr = ''
|
|
312
343
|
this._loadToken = 0
|
|
313
344
|
this._onKeydown = this._onKeydown.bind(this)
|
|
314
345
|
}
|
|
@@ -346,7 +377,12 @@ class CloserClickProfile extends HTMLElement {
|
|
|
346
377
|
}
|
|
347
378
|
get _t() { return I18N[this._lang] }
|
|
348
379
|
get _pubkey() { return this.getAttribute('pubkey') || '' }
|
|
349
|
-
get
|
|
380
|
+
get _mode() { return (this.getAttribute('mode') || 'edit').toLowerCase() }
|
|
381
|
+
// Editor de calificación (confianza/afinidad/notas): solo en 'edit'.
|
|
382
|
+
get _editable() { return this._mode === 'edit' }
|
|
383
|
+
// 'self' = tu propio perfil: nombre editable (se escribe al vault), sin
|
|
384
|
+
// calificación (no te calificas a ti mismo).
|
|
385
|
+
get _self() { return this._mode === 'self' }
|
|
350
386
|
|
|
351
387
|
_resetState() {
|
|
352
388
|
this._my = { confianza: 0, afinidad: 0, notes: '' }
|
|
@@ -357,6 +393,8 @@ class CloserClickProfile extends HTMLElement {
|
|
|
357
393
|
this._hover = 0
|
|
358
394
|
this._hoverAfin = 0
|
|
359
395
|
this._error = ''
|
|
396
|
+
this._nameSaved = false
|
|
397
|
+
this._nameErr = ''
|
|
360
398
|
}
|
|
361
399
|
|
|
362
400
|
/* ---- carga de datos vía provider ---- */
|
|
@@ -418,6 +456,32 @@ class CloserClickProfile extends HTMLElement {
|
|
|
418
456
|
}
|
|
419
457
|
}
|
|
420
458
|
|
|
459
|
+
// Guarda tu nombre visible (mode="self") en el vault vía provider.setMyName.
|
|
460
|
+
async _saveName() {
|
|
461
|
+
if (this._savingName) return
|
|
462
|
+
const p = this._provider
|
|
463
|
+
const input = this.shadowRoot.querySelector('.nick-input')
|
|
464
|
+
if (!input || !p || typeof p.setMyName !== 'function') return
|
|
465
|
+
const name = (input.value || '').trim()
|
|
466
|
+
if (!name) return
|
|
467
|
+
if (name === (this.getAttribute('name') || '')) { this._nameSaved = true; this._render(); return }
|
|
468
|
+
this._savingName = true
|
|
469
|
+
this._nameErr = ''
|
|
470
|
+
this._render()
|
|
471
|
+
try {
|
|
472
|
+
await p.setMyName(name)
|
|
473
|
+
this._savingName = false
|
|
474
|
+
this._nameSaved = true
|
|
475
|
+
this._emit('cc-profile-name', { pubkey: this._pubkey, name })
|
|
476
|
+
this.setAttribute('name', name) // refleja el nombre nuevo (re-render por attributeChangedCallback)
|
|
477
|
+
this._render()
|
|
478
|
+
} catch (e) {
|
|
479
|
+
this._savingName = false
|
|
480
|
+
this._nameErr = (e && e.message) || this._t.saveError
|
|
481
|
+
this._render()
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
421
485
|
_close() { this._emit('cc-profile-close', { pubkey: this._pubkey }) }
|
|
422
486
|
_refresh() { this._emit('cc-profile-refresh', { pubkey: this._pubkey }); this.reload() }
|
|
423
487
|
_emit(type, detail) { this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true })) }
|
|
@@ -471,7 +535,7 @@ class CloserClickProfile extends HTMLElement {
|
|
|
471
535
|
const name = this.getAttribute('name') || t.contact
|
|
472
536
|
const since = this.getAttribute('since')
|
|
473
537
|
const online = this.hasAttribute('online') && this.getAttribute('online') !== 'false'
|
|
474
|
-
const heading = this.getAttribute('heading') || (editable ? t.headingEdit : t.headingView)
|
|
538
|
+
const heading = this.getAttribute('heading') || (this._self ? t.headingSelf : editable ? t.headingEdit : t.headingView)
|
|
475
539
|
|
|
476
540
|
const confLabel = t.labels[this._hover || this._my.confianza] || t.labels[0]
|
|
477
541
|
|
|
@@ -483,7 +547,16 @@ class CloserClickProfile extends HTMLElement {
|
|
|
483
547
|
${online ? '<span class="online-dot"></span>' : ''}
|
|
484
548
|
</div>
|
|
485
549
|
<div class="identity-text">
|
|
486
|
-
|
|
550
|
+
${this._self ? `
|
|
551
|
+
<label class="nick-edit">
|
|
552
|
+
<span class="nick-label">${this._esc(t.editName)}</span>
|
|
553
|
+
<div class="nick-row">
|
|
554
|
+
<input class="nick-input" type="text" maxlength="40" value="${this._esc(name === t.contact ? '' : name)}" placeholder="${this._esc(t.nickPh)}" />
|
|
555
|
+
<button type="button" class="btn primary nick-save" data-savename ${this._savingName ? 'disabled' : ''}>${this._esc(this._savingName ? t.saving : t.saveName)}</button>
|
|
556
|
+
</div>
|
|
557
|
+
${this._nameSaved ? `<span class="nick-saved">${this._esc(t.nameSaved)}</span>` : ''}
|
|
558
|
+
${this._nameErr ? `<span class="error">${this._esc(this._nameErr)}</span>` : ''}
|
|
559
|
+
</label>` : `<div class="name">${this._esc(name)}</div>`}
|
|
487
560
|
<code class="pubkey">${this._esc(this._shortKey(pk))}</code>
|
|
488
561
|
${since ? `<div class="since">${this._esc(t.knownSince)} ${this._esc(this._fmtDate(since))}</div>` : ''}
|
|
489
562
|
</div>
|
|
@@ -632,6 +705,15 @@ class CloserClickProfile extends HTMLElement {
|
|
|
632
705
|
const refresh = q('[data-refresh]'); if (refresh) refresh.addEventListener('click', () => this._refresh())
|
|
633
706
|
const reload = q('[data-reload]'); if (reload) reload.addEventListener('click', () => this.reload())
|
|
634
707
|
|
|
708
|
+
if (this._self) {
|
|
709
|
+
const savename = q('[data-savename]'); if (savename) savename.addEventListener('click', () => this._saveName())
|
|
710
|
+
const nickInput = q('.nick-input')
|
|
711
|
+
if (nickInput) {
|
|
712
|
+
nickInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); this._saveName() } })
|
|
713
|
+
nickInput.addEventListener('input', () => { this._nameSaved = false; this._nameErr = '' })
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
635
717
|
if (this._editable) {
|
|
636
718
|
qa('[data-star]').forEach(btn => {
|
|
637
719
|
const kind = btn.getAttribute('data-star')
|
|
@@ -734,6 +816,16 @@ export function createVaultProfileProvider({ identity, reputation } = {}) {
|
|
|
734
816
|
async rate(pubkey, indicators, notes) {
|
|
735
817
|
return reputation.rate(pubkey, indicators, { notes })
|
|
736
818
|
},
|
|
819
|
+
|
|
820
|
+
// --- Tu propia identidad (para mode="self") ---
|
|
821
|
+
myPubkey,
|
|
822
|
+
getMyName() { return (identity && identity.me && identity.me.nickname) || null },
|
|
823
|
+
async setMyName(name) {
|
|
824
|
+
if (!identity || typeof identity.setMyNickname !== 'function') {
|
|
825
|
+
throw new Error('closer-click-profile: identity.setMyNickname no disponible')
|
|
826
|
+
}
|
|
827
|
+
return identity.setMyNickname(name)
|
|
828
|
+
},
|
|
737
829
|
}
|
|
738
830
|
}
|
|
739
831
|
|