review-handoff-plugin 1.0.0 → 1.0.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/component/review-toolbar.js +181 -45
- package/package.json +1 -1
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
let reviewId = null
|
|
10
10
|
let pins = []
|
|
11
|
+
let replies = {} // { [pinId]: Reply[] }
|
|
11
12
|
let mode = 'pointer'
|
|
12
13
|
let panelOpen = false
|
|
13
14
|
let activePanel = null // 'threads' | 'handoff'
|
|
@@ -15,6 +16,7 @@
|
|
|
15
16
|
let handoffData = null
|
|
16
17
|
let handoffHistory = []
|
|
17
18
|
let handoffLoading = false
|
|
19
|
+
let replyingTo = null // pinId being replied to
|
|
18
20
|
|
|
19
21
|
async function sbFetch(path, opts = {}) {
|
|
20
22
|
const res = await fetch(`${SUPABASE_URL}/rest/v1/${path}`, {
|
|
@@ -46,7 +48,12 @@
|
|
|
46
48
|
const css = `
|
|
47
49
|
#rh-overlay{position:fixed;inset:0;z-index:2147483640;pointer-events:none}
|
|
48
50
|
#rh-overlay.active{pointer-events:all;cursor:crosshair}
|
|
49
|
-
|
|
51
|
+
#rh-popover{position:absolute;width:260px;background:#1c1c1f;border:1px solid rgba(255,255,255,.12);border-radius:12px;padding:14px;box-shadow:0 8px 32px rgba(0,0,0,.5);z-index:2147483647;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;display:none}
|
|
52
|
+
#rh-popover input,#rh-popover textarea{width:100%;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.1);border-radius:8px;color:#fff;font-size:13px;padding:8px 10px;font-family:inherit;box-sizing:border-box;outline:none;margin-bottom:8px}
|
|
53
|
+
#rh-popover textarea{resize:none}
|
|
54
|
+
#rh-popover input::placeholder,#rh-popover textarea::placeholder{color:rgba(255,255,255,.3)}
|
|
55
|
+
#rh-popover .rh-form-actions{display:flex;gap:8px;margin-top:4px}
|
|
56
|
+
.rh-pin{position:absolute;width:28px;height:28px;border-radius:50% 50% 50% 0;background:#6366f1;border:2px solid #fff;transform:rotate(-45deg);cursor:pointer;pointer-events:all;box-shadow:0 2px 8px rgba(0,0,0,.3);z-index:2147483641;display:flex;align-items:center;justify-content:center}
|
|
50
57
|
.rh-pin.resolved{background:#22c55e}
|
|
51
58
|
.rh-pin span{transform:rotate(45deg);color:#fff;font-size:11px;font-weight:700;font-family:-apple-system,sans-serif}
|
|
52
59
|
#rh-panel{position:fixed;top:0;right:0;width:300px;height:100vh;background:#18181b;border-left:1px solid rgba(255,255,255,.08);z-index:2147483645;display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;box-shadow:-4px 0 24px rgba(0,0,0,.4);transform:translateX(100%);transition:transform .2s ease}
|
|
@@ -61,11 +68,25 @@
|
|
|
61
68
|
.rh-badge.resolved{background:#22c55e}
|
|
62
69
|
.rh-author{color:rgba(255,255,255,.5);font-size:11px;margin:0}
|
|
63
70
|
.rh-body{color:rgba(255,255,255,.85);font-size:13px;line-height:1.5;margin:0}
|
|
64
|
-
.rh-
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
.rh-card-actions{display:flex;gap:6px;margin-top:8px;align-items:center}
|
|
72
|
+
.rh-status-btn{font-size:11px;padding:4px 8px;border-radius:6px;border:1px solid rgba(255,255,255,.25);background:rgba(255,255,255,.06);color:rgba(255,255,255,.8);cursor:pointer;font-family:inherit}
|
|
73
|
+
.rh-status-btn:hover{background:rgba(255,255,255,.12);color:#fff}
|
|
74
|
+
.rh-reply-btn{font-size:11px;padding:4px 8px;border-radius:6px;border:1px solid rgba(99,102,241,.3);background:rgba(99,102,241,.08);color:#a5b4fc;cursor:pointer;font-family:inherit}
|
|
75
|
+
.rh-reply-btn:hover{background:rgba(99,102,241,.18);color:#c7d2fe}
|
|
76
|
+
.rh-replies{margin-top:10px;padding-top:10px;border-top:1px solid rgba(255,255,255,.06);display:flex;flex-direction:column;gap:6px}
|
|
77
|
+
.rh-reply{background:rgba(255,255,255,.03);border-radius:8px;padding:8px 10px}
|
|
78
|
+
.rh-reply-author{color:rgba(255,255,255,.4);font-size:10px;margin:0 0 3px}
|
|
79
|
+
.rh-reply-body{color:rgba(255,255,255,.75);font-size:12px;line-height:1.5;margin:0}
|
|
80
|
+
.rh-reply-form{margin-top:10px;padding-top:10px;border-top:1px solid rgba(255,255,255,.06)}
|
|
81
|
+
.rh-reply-form input,.rh-reply-form textarea{width:100%;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.1);border-radius:7px;color:#fff;font-size:12px;padding:7px 9px;font-family:inherit;box-sizing:border-box;outline:none;margin-bottom:6px}
|
|
82
|
+
.rh-reply-form textarea{resize:none}
|
|
83
|
+
.rh-reply-form input::placeholder,.rh-reply-form textarea::placeholder{color:rgba(255,255,255,.25)}
|
|
84
|
+
.rh-reply-actions{display:flex;gap:6px}
|
|
85
|
+
.rh-reply-cancel{flex:1;padding:6px;border-radius:7px;border:1px solid rgba(255,255,255,.25);background:rgba(255,255,255,.06);color:rgba(255,255,255,.8);font-size:12px;cursor:pointer;font-family:inherit}
|
|
86
|
+
.rh-reply-send{flex:1;padding:6px;border-radius:7px;border:none;background:#6366f1;color:#fff;font-size:12px;font-weight:600;cursor:pointer;font-family:inherit}
|
|
87
|
+
.rh-reply-send:disabled{opacity:.5;cursor:not-allowed}
|
|
67
88
|
.rh-form-actions{display:flex;gap:8px;margin-top:8px}
|
|
68
|
-
.rh-btn-cancel{flex:1;padding:8px;border-radius:8px;border:1px solid rgba(255,255,255,.
|
|
89
|
+
.rh-btn-cancel{flex:1;padding:8px;border-radius:8px;border:1px solid rgba(255,255,255,.25);background:rgba(255,255,255,.06);color:rgba(255,255,255,.8);font-size:13px;cursor:pointer;font-family:inherit}
|
|
69
90
|
.rh-btn-save{flex:1;padding:8px;border-radius:8px;border:none;background:#6366f1;color:#fff;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit}
|
|
70
91
|
.rh-btn-save:disabled{opacity:.5;cursor:not-allowed}
|
|
71
92
|
#rh-toolbar{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);z-index:2147483646;display:flex;align-items:center;gap:4px;background:#1c1c1f;border:1px solid rgba(255,255,255,.1);border-radius:14px;padding:6px 10px;box-shadow:0 8px 32px rgba(0,0,0,.5);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif}
|
|
@@ -104,13 +125,10 @@
|
|
|
104
125
|
overlay.id = 'rh-overlay'
|
|
105
126
|
overlay.addEventListener('click', (e) => {
|
|
106
127
|
if (mode !== 'comment') return
|
|
107
|
-
const x = (e.
|
|
108
|
-
const y = (e.
|
|
128
|
+
const x = (e.pageX / document.documentElement.clientWidth) * 100
|
|
129
|
+
const y = (e.pageY / document.documentElement.scrollHeight) * 100
|
|
109
130
|
pendingPos = { x, y }
|
|
110
|
-
|
|
111
|
-
document.getElementById('rh-comment-form').style.display = 'block'
|
|
112
|
-
document.getElementById('rh-textarea').value = ''
|
|
113
|
-
document.getElementById('rh-textarea').focus()
|
|
131
|
+
showPopover(e.clientX, e.clientY)
|
|
114
132
|
})
|
|
115
133
|
document.body.appendChild(overlay)
|
|
116
134
|
|
|
@@ -124,17 +142,24 @@
|
|
|
124
142
|
</div>
|
|
125
143
|
<div id="rh-pins-list"></div>
|
|
126
144
|
<div id="rh-handoff-content" style="display:none"></div>
|
|
127
|
-
<div id="rh-comment-form" style="display:none">
|
|
128
|
-
<textarea id="rh-textarea" rows="3" placeholder="Digite seu comentário…"></textarea>
|
|
129
|
-
<div class="rh-form-actions">
|
|
130
|
-
<button class="rh-btn-cancel" id="rh-cancel">Cancelar</button>
|
|
131
|
-
<button class="rh-btn-save" id="rh-save">Salvar</button>
|
|
132
|
-
</div>
|
|
133
|
-
</div>
|
|
134
145
|
`
|
|
135
146
|
document.body.appendChild(panel)
|
|
136
147
|
|
|
137
148
|
document.getElementById('rh-panel-close').onclick = closePanel
|
|
149
|
+
|
|
150
|
+
// Popover (new comment)
|
|
151
|
+
const popover = document.createElement('div')
|
|
152
|
+
popover.id = 'rh-popover'
|
|
153
|
+
popover.innerHTML = `
|
|
154
|
+
<input id="rh-author-input" type="text" placeholder="Seu nome (opcional)" />
|
|
155
|
+
<textarea id="rh-textarea" rows="3" placeholder="Digite seu comentário…"></textarea>
|
|
156
|
+
<div class="rh-form-actions">
|
|
157
|
+
<button class="rh-btn-cancel" id="rh-cancel">Cancelar</button>
|
|
158
|
+
<button class="rh-btn-save" id="rh-save">Salvar</button>
|
|
159
|
+
</div>
|
|
160
|
+
`
|
|
161
|
+
document.body.appendChild(popover)
|
|
162
|
+
|
|
138
163
|
document.getElementById('rh-cancel').onclick = cancelComment
|
|
139
164
|
document.getElementById('rh-save').onclick = handleSave
|
|
140
165
|
|
|
@@ -173,7 +198,7 @@
|
|
|
173
198
|
document.getElementById('rh-panel-title').textContent = which === 'handoff' ? 'Handoff' : 'Comentários'
|
|
174
199
|
document.getElementById('rh-pins-list').style.display = which === 'threads' ? 'flex' : 'none'
|
|
175
200
|
document.getElementById('rh-handoff-content').style.display = which === 'handoff' ? 'flex' : 'none'
|
|
176
|
-
|
|
201
|
+
cancelComment()
|
|
177
202
|
if (which === 'handoff') renderHandoff()
|
|
178
203
|
updateToolbar()
|
|
179
204
|
}
|
|
@@ -181,15 +206,31 @@
|
|
|
181
206
|
function closePanel() {
|
|
182
207
|
panelOpen = false
|
|
183
208
|
activePanel = null
|
|
209
|
+
replyingTo = null
|
|
184
210
|
document.getElementById('rh-panel').classList.remove('open')
|
|
185
211
|
cancelComment()
|
|
186
212
|
updateToolbar()
|
|
187
213
|
}
|
|
188
214
|
|
|
215
|
+
function showPopover(clientX, clientY) {
|
|
216
|
+
const pop = document.getElementById('rh-popover')
|
|
217
|
+
document.getElementById('rh-author-input').value = ''
|
|
218
|
+
document.getElementById('rh-textarea').value = ''
|
|
219
|
+
const pw = 260, ph = 160
|
|
220
|
+
let left = clientX + window.scrollX + 16
|
|
221
|
+
let top = clientY + window.scrollY + 16
|
|
222
|
+
if (clientX + pw + 20 > window.innerWidth) left = clientX + window.scrollX - pw - 8
|
|
223
|
+
if (clientY + ph + 20 > window.innerHeight) top = clientY + window.scrollY - ph - 8
|
|
224
|
+
pop.style.left = left + 'px'
|
|
225
|
+
pop.style.top = top + 'px'
|
|
226
|
+
pop.style.display = 'block'
|
|
227
|
+
document.getElementById('rh-textarea').focus()
|
|
228
|
+
}
|
|
229
|
+
|
|
189
230
|
function cancelComment() {
|
|
190
231
|
pendingPos = null
|
|
191
|
-
const
|
|
192
|
-
if (
|
|
232
|
+
const pop = document.getElementById('rh-popover')
|
|
233
|
+
if (pop) pop.style.display = 'none'
|
|
193
234
|
mode = 'pointer'
|
|
194
235
|
document.getElementById('rh-overlay').classList.remove('active')
|
|
195
236
|
updateToolbar()
|
|
@@ -206,8 +247,8 @@
|
|
|
206
247
|
pins.forEach((pin, i) => {
|
|
207
248
|
const el = document.createElement('div')
|
|
208
249
|
el.className = 'rh-pin' + (pin.status === 'resolved' ? ' resolved' : '')
|
|
209
|
-
el.style.left = `calc(${pin.x_percent}
|
|
210
|
-
el.style.top = `calc(${pin.y_percent}
|
|
250
|
+
el.style.left = `calc(${pin.x_percent / 100 * document.documentElement.clientWidth}px - 14px)`
|
|
251
|
+
el.style.top = `calc(${pin.y_percent / 100 * document.documentElement.scrollHeight}px - 14px)`
|
|
211
252
|
el.innerHTML = `<span>${i + 1}</span>`
|
|
212
253
|
el.onclick = (e) => { e.stopPropagation(); openPanel('threads') }
|
|
213
254
|
document.body.appendChild(el)
|
|
@@ -223,23 +264,100 @@
|
|
|
223
264
|
list.innerHTML = '<div id="rh-empty">Nenhum comentário ainda.<br>Ative o modo comentário e clique na tela.</div>'
|
|
224
265
|
return
|
|
225
266
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
${
|
|
267
|
+
|
|
268
|
+
list.innerHTML = pins.map((pin, i) => {
|
|
269
|
+
const pinReplies = replies[pin.id] || []
|
|
270
|
+
const repliesHTML = pinReplies.length > 0 ? `
|
|
271
|
+
<div class="rh-replies">
|
|
272
|
+
${pinReplies.map(r => `
|
|
273
|
+
<div class="rh-reply">
|
|
274
|
+
<p class="rh-reply-author">${r.author_name}</p>
|
|
275
|
+
<p class="rh-reply-body">${r.body}</p>
|
|
276
|
+
</div>
|
|
277
|
+
`).join('')}
|
|
232
278
|
</div>
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
279
|
+
` : ''
|
|
280
|
+
|
|
281
|
+
const replyFormHTML = replyingTo === pin.id ? `
|
|
282
|
+
<div class="rh-reply-form" id="rh-reply-form-${pin.id}">
|
|
283
|
+
<input type="text" placeholder="Seu nome (opcional)" id="rh-reply-author-${pin.id}" />
|
|
284
|
+
<textarea rows="2" placeholder="Sua resposta…" id="rh-reply-body-${pin.id}"></textarea>
|
|
285
|
+
<div class="rh-reply-actions">
|
|
286
|
+
<button class="rh-reply-cancel" data-pin="${pin.id}">Cancelar</button>
|
|
287
|
+
<button class="rh-reply-send" data-pin="${pin.id}">Enviar</button>
|
|
288
|
+
</div>
|
|
289
|
+
</div>
|
|
290
|
+
` : ''
|
|
291
|
+
|
|
292
|
+
return `
|
|
293
|
+
<div class="rh-card" data-pin-id="${pin.id}">
|
|
294
|
+
<div class="rh-card-header">
|
|
295
|
+
<div class="rh-badge ${pin.status === 'resolved' ? 'resolved' : ''}">${i + 1}</div>
|
|
296
|
+
<p class="rh-author">${pin.author_name || 'Anônimo'}</p>
|
|
297
|
+
${pin.status === 'resolved' ? '<p class="rh-author" style="margin-left:auto;color:#22c55e">✓ resolvido</p>' : ''}
|
|
298
|
+
</div>
|
|
299
|
+
<p class="rh-body">${pin.body}</p>
|
|
300
|
+
<div class="rh-card-actions">
|
|
301
|
+
<button class="rh-status-btn" data-id="${pin.id}" data-status="${pin.status}">
|
|
302
|
+
${pin.status === 'open' ? 'Marcar resolvido' : 'Reabrir'}
|
|
303
|
+
</button>
|
|
304
|
+
<button class="rh-reply-btn" data-pin="${pin.id}">↩ Responder</button>
|
|
305
|
+
${pinReplies.length > 0 ? `<span style="color:rgba(255,255,255,.3);font-size:11px;margin-left:auto">${pinReplies.length} resp.</span>` : ''}
|
|
306
|
+
</div>
|
|
307
|
+
${repliesHTML}
|
|
308
|
+
${replyFormHTML}
|
|
309
|
+
</div>
|
|
310
|
+
`
|
|
311
|
+
}).join('')
|
|
239
312
|
|
|
240
313
|
list.querySelectorAll('.rh-status-btn').forEach(btn => {
|
|
241
314
|
btn.onclick = () => toggleStatus(btn.dataset.id, btn.dataset.status)
|
|
242
315
|
})
|
|
316
|
+
|
|
317
|
+
list.querySelectorAll('.rh-reply-btn').forEach(btn => {
|
|
318
|
+
btn.onclick = () => {
|
|
319
|
+
replyingTo = replyingTo === btn.dataset.pin ? null : btn.dataset.pin
|
|
320
|
+
renderPinsList()
|
|
321
|
+
if (replyingTo) {
|
|
322
|
+
setTimeout(() => {
|
|
323
|
+
document.getElementById(`rh-reply-body-${replyingTo}`)?.focus()
|
|
324
|
+
}, 50)
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
list.querySelectorAll('.rh-reply-cancel').forEach(btn => {
|
|
330
|
+
btn.onclick = () => { replyingTo = null; renderPinsList() }
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
list.querySelectorAll('.rh-reply-send').forEach(btn => {
|
|
334
|
+
btn.onclick = () => sendReply(btn.dataset.pin)
|
|
335
|
+
})
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function sendReply(pinId) {
|
|
339
|
+
const bodyEl = document.getElementById(`rh-reply-body-${pinId}`)
|
|
340
|
+
const authorEl = document.getElementById(`rh-reply-author-${pinId}`)
|
|
341
|
+
const body = bodyEl?.value.trim()
|
|
342
|
+
if (!body) return
|
|
343
|
+
|
|
344
|
+
const authorName = authorEl?.value.trim() || 'Anônimo'
|
|
345
|
+
const sendBtn = document.querySelector(`.rh-reply-send[data-pin="${pinId}"]`)
|
|
346
|
+
if (sendBtn) { sendBtn.disabled = true; sendBtn.textContent = 'Enviando…' }
|
|
347
|
+
|
|
348
|
+
const data = await sbFetch('replies?select=*', {
|
|
349
|
+
method: 'POST',
|
|
350
|
+
prefer: 'return=representation',
|
|
351
|
+
body: JSON.stringify({ pin_id: pinId, author_name: authorName, body }),
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
if (data?.[0]) {
|
|
355
|
+
if (!replies[pinId]) replies[pinId] = []
|
|
356
|
+
replies[pinId].push(data[0])
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
replyingTo = null
|
|
360
|
+
renderPinsList()
|
|
243
361
|
}
|
|
244
362
|
|
|
245
363
|
function renderHandoff() {
|
|
@@ -322,10 +440,7 @@
|
|
|
322
440
|
const res = await fetch(`${API_BASE}/api/handoff`, {
|
|
323
441
|
method: 'POST',
|
|
324
442
|
headers: { 'Content-Type': 'application/json' },
|
|
325
|
-
body: JSON.stringify({
|
|
326
|
-
vercelUrl: location.origin,
|
|
327
|
-
reviewId,
|
|
328
|
-
}),
|
|
443
|
+
body: JSON.stringify({ vercelUrl: location.origin, reviewId }),
|
|
329
444
|
})
|
|
330
445
|
const data = await res.json()
|
|
331
446
|
if (!res.ok) throw new Error(data.error)
|
|
@@ -343,6 +458,7 @@
|
|
|
343
458
|
if (!pendingPos || !reviewId) return
|
|
344
459
|
const body = document.getElementById('rh-textarea').value.trim()
|
|
345
460
|
if (!body) return
|
|
461
|
+
const authorName = document.getElementById('rh-author-input').value.trim() || 'Anônimo'
|
|
346
462
|
const btn = document.getElementById('rh-save')
|
|
347
463
|
btn.disabled = true
|
|
348
464
|
btn.textContent = 'Salvando…'
|
|
@@ -354,14 +470,19 @@
|
|
|
354
470
|
x_percent: pendingPos.x,
|
|
355
471
|
y_percent: pendingPos.y,
|
|
356
472
|
body,
|
|
357
|
-
author_name:
|
|
473
|
+
author_name: authorName,
|
|
358
474
|
status: 'open',
|
|
359
475
|
}),
|
|
360
476
|
})
|
|
361
477
|
pins.push(data[0])
|
|
478
|
+
replies[data[0].id] = []
|
|
362
479
|
renderPins()
|
|
363
480
|
renderPinsList()
|
|
364
|
-
|
|
481
|
+
document.getElementById('rh-popover').style.display = 'none'
|
|
482
|
+
pendingPos = null
|
|
483
|
+
mode = 'pointer'
|
|
484
|
+
document.getElementById('rh-overlay').classList.remove('active')
|
|
485
|
+
updateToolbar()
|
|
365
486
|
btn.disabled = false
|
|
366
487
|
btn.textContent = 'Salvar'
|
|
367
488
|
}
|
|
@@ -383,12 +504,27 @@
|
|
|
383
504
|
try {
|
|
384
505
|
const url = (location.origin + location.pathname).replace(/\/+$/, '') || location.origin
|
|
385
506
|
reviewId = await getOrCreateReview(url)
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
507
|
+
|
|
508
|
+
const [pinsData, repliesData, hist] = await Promise.all([
|
|
509
|
+
sbFetch(`pins?review_id=eq.${reviewId}&order=created_at.asc`),
|
|
510
|
+
sbFetch(`replies?pin_id=in.(select id from pins where review_id=eq.${reviewId})&order=created_at.asc`),
|
|
511
|
+
fetch(`${API_BASE}/api/handoff?reviewId=${reviewId}`).then(r => r.json()).catch(() => []),
|
|
512
|
+
])
|
|
513
|
+
|
|
514
|
+
pins = pinsData ?? []
|
|
515
|
+
|
|
516
|
+
// Group replies by pin_id
|
|
517
|
+
replies = {}
|
|
518
|
+
pins.forEach(p => { replies[p.id] = [] })
|
|
519
|
+
if (repliesData && !repliesData.error) {
|
|
520
|
+
repliesData.forEach(r => {
|
|
521
|
+
if (replies[r.pin_id]) replies[r.pin_id].push(r)
|
|
522
|
+
})
|
|
523
|
+
}
|
|
524
|
+
|
|
390
525
|
handoffHistory = hist ?? []
|
|
391
526
|
if (handoffHistory.length > 0) handoffData = handoffHistory[0].data
|
|
527
|
+
|
|
392
528
|
buildUI()
|
|
393
529
|
renderPins()
|
|
394
530
|
} catch (e) {
|