@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. 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.1.1",
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, sin editor ni footer)
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 _editable() { return (this.getAttribute('mode') || 'edit') !== 'view' }
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
- <div class="name">${this._esc(name)}</div>
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