feat: numbered element chips for picker, multi-select support
All checks were successful
Deploy to dev.bl.pixeldev.eu / deploy (push) Successful in 3s
All checks were successful
Deploy to dev.bl.pixeldev.eu / deploy (push) Successful in 3s
Replace the old single-snippet textarea append with a chip-based UI: - Each picked element gets a monotonically increasing number [1], [2], … - A chip appears below the textarea: [N] selector ✕ - The token [element N] is inserted at the cursor position in the textarea - Multiple elements can be picked in one session - Chips can be removed (number is never re-used or re-ordered) - On submit, element snippets are appended to the issue body as named reference blocks so the HTML context is still captured Closes #6
This commit is contained in:
parent
7a593d082a
commit
1afd2a0e4f
83
server.js
83
server.js
@ -183,10 +183,17 @@ app.get(['/widget.js', BASE + '/widget.js'], (req, res) => {
|
||||
.fb-picker-btn { background:#1a2a1a; border:1px solid #2a4a2a; color:#6fcf6f; font-size:.78rem;
|
||||
padding:.4rem .7rem; border-radius:6px; cursor:pointer; white-space:nowrap; flex-shrink:0; }
|
||||
.fb-picker-btn:hover { background:#223322; }
|
||||
.fb-picker-hint { color:#444; font-size:.75rem; }
|
||||
.fb-snippet-preview { background:#0a0a0a; border:1px solid #2a2a2a; border-radius:6px;
|
||||
padding:.5rem .7rem; font-size:.72rem; color:#7c9ef5; font-family:monospace;
|
||||
white-space:pre-wrap; word-break:break-all; max-height:80px; overflow-y:auto; }
|
||||
/* Picked-elements chip list */
|
||||
#_fb-picked-list { display:flex; flex-wrap:wrap; gap:.4rem; }
|
||||
#_fb-picked-list:empty { display:none; }
|
||||
.fb-picked-chip { display:inline-flex; align-items:center; gap:.35rem; background:#0d1f0d;
|
||||
border:1px solid #2a4a2a; border-radius:6px; padding:.28rem .55rem; font-size:.75rem;
|
||||
color:#6fcf6f; font-family:monospace; }
|
||||
.fb-chip-num { color:#4a9f4a; font-weight:700; }
|
||||
.fb-chip-label { color:#aaa; max-width:140px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
||||
.fb-chip-remove { background:none; border:none; color:#555; cursor:pointer; font-size:.7rem;
|
||||
padding:0; line-height:1; }
|
||||
.fb-chip-remove:hover { color:#f57c7c; }
|
||||
/* Picker overlay */
|
||||
#_fb-picker-shield { display:none; position:fixed; inset:0; z-index:199999; cursor:crosshair; }
|
||||
#_fb-picker-shield.active { display:block; }
|
||||
@ -240,11 +247,8 @@ app.get(['/widget.js', BASE + '/widget.js'], (req, res) => {
|
||||
</div>
|
||||
<div class="fb-picker-row">
|
||||
<button class="fb-picker-btn" id="_fb-pick-btn">🎯 Pick element</button>
|
||||
<span class="fb-picker-hint" id="_fb-pick-hint">Click to reference a page element</span>
|
||||
</div>
|
||||
<div id="_fb-snippet-wrap" style="display:none;">
|
||||
<div class="fb-snippet-preview" id="_fb-snippet"></div>
|
||||
</div>
|
||||
<div id="_fb-picked-list"></div>
|
||||
<button class="fb-btn" id="_fb-submit">Submit Issue</button>
|
||||
<div class="fb-status" id="_fb-status"></div>
|
||||
</div>
|
||||
@ -302,7 +306,8 @@ app.get(['/widget.js', BASE + '/widget.js'], (req, res) => {
|
||||
// ── Picker mode ───────────────────────────────────────────────────────────
|
||||
var pickerActive = false;
|
||||
var pickerHighlighted = null;
|
||||
var capturedSnippet = '';
|
||||
var nextPickNum = 1;
|
||||
var pickedElements = {}; // { [num]: { selector, snippet } }
|
||||
|
||||
function getSelector(el) {
|
||||
if (!el || el === document.body) return 'body';
|
||||
@ -332,7 +337,30 @@ app.get(['/widget.js', BASE + '/widget.js'], (req, res) => {
|
||||
toast.classList.add('active');
|
||||
}
|
||||
|
||||
function deactivatePicker(snippet) {
|
||||
function insertAtCursor(ta, text) {
|
||||
var start = ta.selectionStart != null ? ta.selectionStart : ta.value.length;
|
||||
var end = ta.selectionEnd != null ? ta.selectionEnd : ta.value.length;
|
||||
ta.value = ta.value.slice(0, start) + text + ta.value.slice(end);
|
||||
ta.selectionStart = ta.selectionEnd = start + text.length;
|
||||
}
|
||||
|
||||
function addPickedChip(num, selector, snippet) {
|
||||
var list = document.getElementById('_fb-picked-list');
|
||||
var chip = document.createElement('span');
|
||||
chip.className = 'fb-picked-chip';
|
||||
chip.dataset.num = num;
|
||||
chip.innerHTML =
|
||||
'<span class="fb-chip-num">[' + num + ']</span>' +
|
||||
'<span class="fb-chip-label">' + esc(selector) + '</span>' +
|
||||
'<button class="fb-chip-remove" title="Remove">✕</button>';
|
||||
chip.querySelector('.fb-chip-remove').addEventListener('click', function() {
|
||||
delete pickedElements[num];
|
||||
chip.remove();
|
||||
});
|
||||
list.appendChild(chip);
|
||||
}
|
||||
|
||||
function deactivatePicker(snippet, selector) {
|
||||
pickerActive = false;
|
||||
shield.classList.remove('active');
|
||||
toast.classList.remove('active');
|
||||
@ -342,14 +370,14 @@ app.get(['/widget.js', BASE + '/widget.js'], (req, res) => {
|
||||
}
|
||||
overlay.classList.add('open');
|
||||
if (snippet) {
|
||||
capturedSnippet = snippet;
|
||||
document.getElementById('_fb-snippet').textContent = snippet;
|
||||
document.getElementById('_fb-snippet-wrap').style.display = '';
|
||||
document.getElementById('_fb-pick-hint').textContent = '✓ Element captured — you can pick again';
|
||||
// Append to description
|
||||
var num = nextPickNum++;
|
||||
pickedElements[num] = { selector: selector, snippet: snippet };
|
||||
addPickedChip(num, selector, snippet);
|
||||
// Insert reference token at cursor in the textarea
|
||||
var desc = document.getElementById('_fb-desc');
|
||||
var tag = '\\n\\n**Picked element:**\\n\`\`\`html\\n' + snippet + '\\n\`\`\`';
|
||||
if (!desc.value.includes(tag)) desc.value += tag;
|
||||
var token = '[element ' + num + ']';
|
||||
insertAtCursor(desc, token);
|
||||
desc.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@ -383,15 +411,15 @@ app.get(['/widget.js', BASE + '/widget.js'], (req, res) => {
|
||||
shield.style.display = '';
|
||||
if (el && !el.closest('#_fb-picker-toast')) {
|
||||
el.classList.remove('_fb-hover-highlight');
|
||||
deactivatePicker(getSnippet(el));
|
||||
deactivatePicker(getSnippet(el), getSelector(el));
|
||||
} else {
|
||||
deactivatePicker(null);
|
||||
deactivatePicker(null, null);
|
||||
}
|
||||
});
|
||||
|
||||
// Esc cancels picker
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape' && pickerActive) deactivatePicker(null);
|
||||
if (e.key === 'Escape' && pickerActive) deactivatePicker(null, null);
|
||||
});
|
||||
|
||||
// ── Submit ────────────────────────────────────────────────────────────────
|
||||
@ -403,9 +431,16 @@ app.get(['/widget.js', BASE + '/widget.js'], (req, res) => {
|
||||
var btn = document.getElementById('_fb-submit');
|
||||
if (!title) { status.style.color='#f57c7c'; status.textContent='Title is required.'; return; }
|
||||
btn.disabled = true; status.style.color='#888'; status.textContent='Submitting…';
|
||||
// Append element snippets as a reference block
|
||||
var snippetBlock = '';
|
||||
Object.keys(pickedElements).forEach(function(num) {
|
||||
var pe = pickedElements[num];
|
||||
snippetBlock += '\\n\\n**[element ' + num + ']** `' + pe.selector + '`\\n\`\`\`html\\n' + pe.snippet + '\\n\`\`\`';
|
||||
});
|
||||
var fullBody = body + snippetBlock;
|
||||
fetch(SELF + '/api/feedback', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ title, body, repo: cfg.repo, source_url: window.location.href, assignee: assignee || undefined })
|
||||
body: JSON.stringify({ title, body: fullBody, repo: cfg.repo, source_url: window.location.href, assignee: assignee || undefined })
|
||||
}).then(function(r) { return r.json(); }).then(function(d) {
|
||||
if (d.ok) {
|
||||
status.style.color='#6fcf6f';
|
||||
@ -413,9 +448,9 @@ app.get(['/widget.js', BASE + '/widget.js'], (req, res) => {
|
||||
document.getElementById('_fb-title').value = '';
|
||||
document.getElementById('_fb-desc').value = '';
|
||||
document.getElementById('_fb-assignee').value = '';
|
||||
document.getElementById('_fb-snippet-wrap').style.display = 'none';
|
||||
document.getElementById('_fb-pick-hint').textContent = 'Click to reference a page element';
|
||||
capturedSnippet = '';
|
||||
document.getElementById('_fb-picked-list').innerHTML = '';
|
||||
nextPickNum = 1;
|
||||
pickedElements = {};
|
||||
btn.disabled = false;
|
||||
setTimeout(function() { status.textContent=''; }, 3000);
|
||||
} else {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user