@closerclick/closer-click-profile 0.1.0 → 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 +170 -75
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
|
}
|
|
@@ -123,27 +136,33 @@ const AVATAR_PALETTE = [
|
|
|
123
136
|
|
|
124
137
|
const STYLE = `
|
|
125
138
|
:host {
|
|
126
|
-
/* ----- Tema
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
--ccp-
|
|
133
|
-
--
|
|
134
|
-
--
|
|
135
|
-
--ccp-
|
|
136
|
-
--ccp-
|
|
137
|
-
--ccp-
|
|
138
|
-
--ccp-
|
|
139
|
-
--ccp-
|
|
140
|
-
--
|
|
141
|
-
--
|
|
142
|
-
--
|
|
143
|
-
--
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
139
|
+
/* ----- Tema -----
|
|
140
|
+
* API pública = --ccp-* (la app las setea por :root, selector o inline).
|
|
141
|
+
* Tokens internos = --_* (los usa el componente). Cada uno resuelve:
|
|
142
|
+
* override --ccp-* → var de tema del messenger → literal.
|
|
143
|
+
* NO declaramos --ccp-* acá (eso ganaría por especificidad al override de la
|
|
144
|
+
* app); así un simple selector de la app (o en :root) funciona. */
|
|
145
|
+
--_bg: var(--ccp-bg, var(--bg-1, #faf3e7));
|
|
146
|
+
--_bg-2: var(--ccp-bg-2, var(--bg-2, #f5ede0));
|
|
147
|
+
--_bg-3: var(--ccp-bg-3, var(--bg-3, #ede2cf));
|
|
148
|
+
--_bg-4: var(--ccp-bg-4, var(--bg-4, #e0d3ba));
|
|
149
|
+
--_border: var(--ccp-border, var(--border, #d4c4a8));
|
|
150
|
+
--_text: var(--ccp-text, var(--text, #2b211a));
|
|
151
|
+
--_muted: var(--ccp-muted, var(--muted, #8a7a66));
|
|
152
|
+
--_accent: var(--ccp-accent, var(--accent, #c0392b));
|
|
153
|
+
--_accent-2: var(--ccp-accent-2, var(--accent-2, #a93226));
|
|
154
|
+
--_gold: var(--ccp-gold, var(--gold, #d4a72c));
|
|
155
|
+
--_derived: var(--ccp-derived, var(--derived, #a37a45));
|
|
156
|
+
--_online: var(--ccp-online, var(--online, #5a8a3a));
|
|
157
|
+
--_affinity: var(--ccp-affinity, var(--affinity, #2dd4bf));
|
|
158
|
+
--_input-bg: var(--ccp-input-bg, var(--input-bg, #fff));
|
|
159
|
+
--_radius: var(--ccp-radius, 16px);
|
|
160
|
+
--_font: var(--ccp-font, var(--font-body, 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif));
|
|
161
|
+
--_font-headline: var(--ccp-font-headline, var(--font-headline, var(--_font)));
|
|
162
|
+
--_font-mono: var(--ccp-font-mono, var(--font-mono, ui-monospace, Menlo, Consolas, monospace));
|
|
163
|
+
|
|
164
|
+
font-family: var(--_font);
|
|
165
|
+
color: var(--_text);
|
|
147
166
|
}
|
|
148
167
|
* { box-sizing: border-box; }
|
|
149
168
|
|
|
@@ -156,9 +175,9 @@ const STYLE = `
|
|
|
156
175
|
z-index: 2147483000;
|
|
157
176
|
}
|
|
158
177
|
.modal {
|
|
159
|
-
background: var(--
|
|
160
|
-
border: 1px solid var(--
|
|
161
|
-
border-radius: var(--
|
|
178
|
+
background: var(--_bg);
|
|
179
|
+
border: 1px solid var(--_border);
|
|
180
|
+
border-radius: var(--_radius);
|
|
162
181
|
width: 100%; max-width: 460px;
|
|
163
182
|
max-height: 92vh;
|
|
164
183
|
display: flex; flex-direction: column;
|
|
@@ -168,18 +187,18 @@ const STYLE = `
|
|
|
168
187
|
.head {
|
|
169
188
|
display: flex; align-items: center; justify-content: space-between;
|
|
170
189
|
padding: 18px 24px;
|
|
171
|
-
border-bottom: 1px solid var(--
|
|
190
|
+
border-bottom: 1px solid var(--_border);
|
|
172
191
|
}
|
|
173
192
|
.head h2 {
|
|
174
|
-
margin: 0; font-family: var(--
|
|
175
|
-
font-size: 18px; font-weight: 600; color: var(--
|
|
193
|
+
margin: 0; font-family: var(--_font-headline);
|
|
194
|
+
font-size: 18px; font-weight: 600; color: var(--_text);
|
|
176
195
|
}
|
|
177
196
|
.x {
|
|
178
197
|
background: transparent; border: 0;
|
|
179
|
-
font-size: 24px; cursor: pointer; color: var(--
|
|
198
|
+
font-size: 24px; cursor: pointer; color: var(--_muted);
|
|
180
199
|
width: 32px; height: 32px; border-radius: 8px; line-height: 1;
|
|
181
200
|
}
|
|
182
|
-
.x:hover { background: var(--
|
|
201
|
+
.x:hover { background: var(--_bg-3); color: var(--_text); }
|
|
183
202
|
|
|
184
203
|
.body { padding: 20px 24px; display: flex; flex-direction: column; gap: 20px; overflow-y: auto; }
|
|
185
204
|
:host([modal]) .body { max-height: 70vh; }
|
|
@@ -187,102 +206,114 @@ const STYLE = `
|
|
|
187
206
|
/* ----- Identity ----- */
|
|
188
207
|
.identity {
|
|
189
208
|
display: flex; gap: 14px; align-items: center;
|
|
190
|
-
background: var(--
|
|
191
|
-
border: 1px solid var(--
|
|
209
|
+
background: var(--_bg-2);
|
|
210
|
+
border: 1px solid var(--_border);
|
|
192
211
|
border-radius: 12px; padding: 14px;
|
|
193
212
|
}
|
|
194
213
|
.avatar-wrap { position: relative; flex-shrink: 0; }
|
|
195
214
|
.avatar {
|
|
196
215
|
width: 56px; height: 56px; border-radius: 50%;
|
|
197
216
|
display: flex; align-items: center; justify-content: center;
|
|
198
|
-
color: #fff; font-family: var(--
|
|
217
|
+
color: #fff; font-family: var(--_font-headline);
|
|
199
218
|
font-weight: 600; font-size: 18px;
|
|
200
219
|
}
|
|
201
220
|
.online-dot {
|
|
202
221
|
position: absolute; right: 0; bottom: 0;
|
|
203
222
|
width: 14px; height: 14px; border-radius: 50%;
|
|
204
|
-
background: var(--
|
|
223
|
+
background: var(--_online); border: 2px solid var(--_bg-2);
|
|
205
224
|
}
|
|
206
225
|
.identity-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
|
|
207
|
-
.name { font-family: var(--
|
|
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); }
|
|
208
239
|
.pubkey {
|
|
209
|
-
background: var(--
|
|
210
|
-
font-family: var(--
|
|
240
|
+
background: var(--_bg-3); padding: 2px 8px; border-radius: 6px;
|
|
241
|
+
font-family: var(--_font-mono); font-size: 11.5px; color: var(--_muted);
|
|
211
242
|
width: fit-content;
|
|
212
243
|
}
|
|
213
|
-
.since { font-size: 12px; color: var(--
|
|
244
|
+
.since { font-size: 12px; color: var(--_muted); }
|
|
214
245
|
|
|
215
246
|
/* ----- Sections ----- */
|
|
216
247
|
.section { display: flex; flex-direction: column; gap: 6px; position: relative; }
|
|
217
|
-
.section-label { font-size: 13px; font-weight: 500; color: var(--
|
|
248
|
+
.section-label { font-size: 13px; font-weight: 500; color: var(--_muted); }
|
|
218
249
|
.section-label small { font-weight: 400; }
|
|
219
250
|
.stars-row { display: flex; gap: 6px; align-items: center; }
|
|
220
251
|
.star-btn {
|
|
221
252
|
background: transparent; border: 0; font-size: 36px;
|
|
222
|
-
color: var(--
|
|
253
|
+
color: var(--_bg-4); cursor: pointer; padding: 0; line-height: 1;
|
|
223
254
|
transition: color 100ms ease-out, transform 100ms ease-out;
|
|
224
255
|
}
|
|
225
256
|
.star-btn[disabled] { cursor: default; }
|
|
226
257
|
.star-btn:not([disabled]):hover { transform: scale(1.1); }
|
|
227
|
-
.star-btn.filled { color: var(--
|
|
228
|
-
.star-btn.afin.filled { color: var(--
|
|
258
|
+
.star-btn.filled { color: var(--_gold); text-shadow: 0 1px 2px rgba(212, 167, 44, 0.35); }
|
|
259
|
+
.star-btn.afin.filled { color: var(--_affinity); text-shadow: none; }
|
|
229
260
|
|
|
230
|
-
.rating-meta { display: flex; gap: 8px; align-items: center; font-size: 14px; color: var(--
|
|
261
|
+
.rating-meta { display: flex; gap: 8px; align-items: center; font-size: 14px; color: var(--_text); margin-top: 4px; }
|
|
231
262
|
.rating-num { font-weight: 600; }
|
|
232
|
-
.rating-label { color: var(--
|
|
263
|
+
.rating-label { color: var(--_muted); }
|
|
233
264
|
.clear {
|
|
234
|
-
background: transparent; border: 0; color: var(--
|
|
265
|
+
background: transparent; border: 0; color: var(--_muted); cursor: pointer;
|
|
235
266
|
font-size: 12px; margin-left: auto; text-decoration: underline;
|
|
236
267
|
}
|
|
237
|
-
.clear:hover { color: var(--
|
|
268
|
+
.clear:hover { color: var(--_accent); }
|
|
238
269
|
|
|
239
270
|
textarea {
|
|
240
|
-
width: 100%; resize: vertical; font: inherit; color: var(--
|
|
241
|
-
background: #fff; border: 1px solid var(--
|
|
271
|
+
width: 100%; resize: vertical; font: inherit; color: var(--_text);
|
|
272
|
+
background: var(--ccp-input-bg, #fff); border: 1px solid var(--_border); border-radius: 8px;
|
|
242
273
|
padding: 8px 10px;
|
|
243
274
|
}
|
|
244
|
-
textarea:focus { outline: none; border-color: var(--
|
|
245
|
-
.counter { position: absolute; right: 8px; bottom: 8px; font-size: 11px; color: var(--
|
|
275
|
+
textarea:focus { outline: none; border-color: var(--_accent); }
|
|
276
|
+
.counter { position: absolute; right: 8px; bottom: 8px; font-size: 11px; color: var(--_muted); background: var(--ccp-input-bg, #fff); padding: 0 4px; }
|
|
246
277
|
|
|
247
278
|
/* ----- Web of Trust + Cloud ----- */
|
|
248
|
-
.panel { background: var(--
|
|
279
|
+
.panel { background: var(--_bg-3); border-radius: 12px; padding: 14px; }
|
|
249
280
|
.panel + .panel { margin-top: 12px; }
|
|
250
281
|
.panel-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
|
|
251
|
-
.panel-title { font-size: 12px; font-weight: 600; color: var(--
|
|
252
|
-
.refresh { background: transparent; border: 0; color: var(--
|
|
253
|
-
.refresh:hover { background: var(--
|
|
254
|
-
.empty { font-size: 13px; color: var(--
|
|
255
|
-
.weak { font-size: 13px; color: var(--
|
|
282
|
+
.panel-title { font-size: 12px; font-weight: 600; color: var(--_text); text-transform: uppercase; letter-spacing: 0.05em; }
|
|
283
|
+
.refresh { background: transparent; border: 0; color: var(--_muted); cursor: pointer; font-size: 16px; width: 24px; height: 24px; border-radius: 6px; }
|
|
284
|
+
.refresh:hover { background: var(--_bg-4); color: var(--_text); }
|
|
285
|
+
.empty { font-size: 13px; color: var(--_muted); font-style: italic; }
|
|
286
|
+
.weak { font-size: 13px; color: var(--_muted); }
|
|
256
287
|
.summary { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
|
|
257
288
|
.summary:last-child { margin-bottom: 0; }
|
|
258
289
|
.stars { font-size: 16px; letter-spacing: 1px; }
|
|
259
|
-
.stars.derived { color: var(--
|
|
260
|
-
.stars.afin { color: var(--
|
|
261
|
-
.num { font-family: var(--
|
|
262
|
-
.count { font-size: 12px; color: var(--
|
|
263
|
-
.ind { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--
|
|
290
|
+
.stars.derived { color: var(--_derived); }
|
|
291
|
+
.stars.afin { color: var(--_affinity); }
|
|
292
|
+
.num { font-family: var(--_font-headline); font-weight: 600; font-size: 14px; color: var(--_text); }
|
|
293
|
+
.count { font-size: 12px; color: var(--_muted); }
|
|
294
|
+
.ind { font-size: 11px; text-transform: uppercase; letter-spacing: .04em; color: var(--_muted); min-width: 64px; }
|
|
264
295
|
|
|
265
296
|
.endorsements { list-style: none; padding: 0; margin: 0; max-height: 130px; overflow-y: auto; }
|
|
266
297
|
.endorsements li {
|
|
267
298
|
display: flex; gap: 8px; align-items: center; font-size: 12.5px;
|
|
268
|
-
padding: 6px 0; border-bottom: 1px solid var(--
|
|
299
|
+
padding: 6px 0; border-bottom: 1px solid var(--_border);
|
|
269
300
|
}
|
|
270
301
|
.endorsements li:last-child { border-bottom: 0; }
|
|
271
|
-
.endorsements .key { background: var(--
|
|
272
|
-
.endorsements .r { color: var(--
|
|
273
|
-
.endorsements .when { color: var(--
|
|
302
|
+
.endorsements .key { background: var(--_bg-2); padding: 1px 6px; border-radius: 4px; font-family: var(--_font-mono); font-size: 11px; color: var(--_muted); }
|
|
303
|
+
.endorsements .r { color: var(--_derived); }
|
|
304
|
+
.endorsements .when { color: var(--_muted); margin-left: auto; font-size: 11px; }
|
|
274
305
|
|
|
275
|
-
.privacy { margin: 0; font-size: 12px; color: var(--
|
|
276
|
-
.error { margin: 0; font-size: 13px; color: var(--
|
|
306
|
+
.privacy { margin: 0; font-size: 12px; color: var(--_muted); text-align: center; line-height: 1.5; }
|
|
307
|
+
.error { margin: 0; font-size: 13px; color: var(--_accent); font-weight: 500; }
|
|
277
308
|
|
|
278
309
|
/* ----- Footer ----- */
|
|
279
|
-
.foot { display: flex; gap: 10px; justify-content: flex-end; padding: 14px 24px; background: var(--
|
|
310
|
+
.foot { display: flex; gap: 10px; justify-content: flex-end; padding: 14px 24px; background: var(--_bg-2); border-top: 1px solid var(--_border); }
|
|
280
311
|
.btn { font: inherit; font-weight: 600; padding: 9px 16px; border-radius: 10px; border: 1px solid transparent; cursor: pointer; }
|
|
281
|
-
.btn.primary { background: var(--
|
|
282
|
-
.btn.primary:hover:not(:disabled) { background: var(--
|
|
312
|
+
.btn.primary { background: var(--_accent); color: #fff; }
|
|
313
|
+
.btn.primary:hover:not(:disabled) { background: var(--_accent-2); }
|
|
283
314
|
.btn.primary:disabled { opacity: 0.6; cursor: default; }
|
|
284
|
-
.btn.secondary { background: transparent; color: var(--
|
|
285
|
-
.btn.secondary:hover { background: var(--
|
|
315
|
+
.btn.secondary { background: transparent; color: var(--_text); border-color: var(--_border); }
|
|
316
|
+
.btn.secondary:hover { background: var(--_bg-3); }
|
|
286
317
|
`
|
|
287
318
|
|
|
288
319
|
class CloserClickProfile extends HTMLElement {
|
|
@@ -303,6 +334,9 @@ class CloserClickProfile extends HTMLElement {
|
|
|
303
334
|
this._hoverAfin = 0
|
|
304
335
|
this._saving = false
|
|
305
336
|
this._error = ''
|
|
337
|
+
this._savingName = false
|
|
338
|
+
this._nameSaved = false
|
|
339
|
+
this._nameErr = ''
|
|
306
340
|
this._loadToken = 0
|
|
307
341
|
this._onKeydown = this._onKeydown.bind(this)
|
|
308
342
|
}
|
|
@@ -340,7 +374,12 @@ class CloserClickProfile extends HTMLElement {
|
|
|
340
374
|
}
|
|
341
375
|
get _t() { return I18N[this._lang] }
|
|
342
376
|
get _pubkey() { return this.getAttribute('pubkey') || '' }
|
|
343
|
-
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' }
|
|
344
383
|
|
|
345
384
|
_resetState() {
|
|
346
385
|
this._my = { confianza: 0, afinidad: 0, notes: '' }
|
|
@@ -351,6 +390,8 @@ class CloserClickProfile extends HTMLElement {
|
|
|
351
390
|
this._hover = 0
|
|
352
391
|
this._hoverAfin = 0
|
|
353
392
|
this._error = ''
|
|
393
|
+
this._nameSaved = false
|
|
394
|
+
this._nameErr = ''
|
|
354
395
|
}
|
|
355
396
|
|
|
356
397
|
/* ---- carga de datos vía provider ---- */
|
|
@@ -412,6 +453,32 @@ class CloserClickProfile extends HTMLElement {
|
|
|
412
453
|
}
|
|
413
454
|
}
|
|
414
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
|
+
|
|
415
482
|
_close() { this._emit('cc-profile-close', { pubkey: this._pubkey }) }
|
|
416
483
|
_refresh() { this._emit('cc-profile-refresh', { pubkey: this._pubkey }); this.reload() }
|
|
417
484
|
_emit(type, detail) { this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true })) }
|
|
@@ -465,7 +532,7 @@ class CloserClickProfile extends HTMLElement {
|
|
|
465
532
|
const name = this.getAttribute('name') || t.contact
|
|
466
533
|
const since = this.getAttribute('since')
|
|
467
534
|
const online = this.hasAttribute('online') && this.getAttribute('online') !== 'false'
|
|
468
|
-
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)
|
|
469
536
|
|
|
470
537
|
const confLabel = t.labels[this._hover || this._my.confianza] || t.labels[0]
|
|
471
538
|
|
|
@@ -477,7 +544,16 @@ class CloserClickProfile extends HTMLElement {
|
|
|
477
544
|
${online ? '<span class="online-dot"></span>' : ''}
|
|
478
545
|
</div>
|
|
479
546
|
<div class="identity-text">
|
|
480
|
-
|
|
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>`}
|
|
481
557
|
<code class="pubkey">${this._esc(this._shortKey(pk))}</code>
|
|
482
558
|
${since ? `<div class="since">${this._esc(t.knownSince)} ${this._esc(this._fmtDate(since))}</div>` : ''}
|
|
483
559
|
</div>
|
|
@@ -626,6 +702,15 @@ class CloserClickProfile extends HTMLElement {
|
|
|
626
702
|
const refresh = q('[data-refresh]'); if (refresh) refresh.addEventListener('click', () => this._refresh())
|
|
627
703
|
const reload = q('[data-reload]'); if (reload) reload.addEventListener('click', () => this.reload())
|
|
628
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
|
+
|
|
629
714
|
if (this._editable) {
|
|
630
715
|
qa('[data-star]').forEach(btn => {
|
|
631
716
|
const kind = btn.getAttribute('data-star')
|
|
@@ -728,6 +813,16 @@ export function createVaultProfileProvider({ identity, reputation } = {}) {
|
|
|
728
813
|
async rate(pubkey, indicators, notes) {
|
|
729
814
|
return reputation.rate(pubkey, indicators, { notes })
|
|
730
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
|
+
},
|
|
731
826
|
}
|
|
732
827
|
}
|
|
733
828
|
|