refactor(entries): fold the secret toggle into a per-field row menu
The lock button sat next to the eye and both masked the value, so they read as the same control. The eye now carries the state - cyan ring when the field is stored as secret - and flipping the flag moved into a row menu alongside Remove field, one deliberate step away from the everyday reveal. The row drops from six columns to five. The menu reuses the card kebab classes, so closing it on an outside click is already handled.
This commit is contained in:
@@ -4275,38 +4275,21 @@ function buildCustomFieldRow(field, idx, rerender) {
|
||||
if (valueInput.tagName !== 'INPUT') return;
|
||||
valueInput.type = valueInput.type === 'password' ? 'text' : 'password';
|
||||
});
|
||||
if (!field.is_secret || (valueInput.tagName !== 'INPUT' && !field.multiline))
|
||||
eye.style.visibility = 'hidden';
|
||||
|
||||
// Secret-flag toggle (chip-style) — flips both the storage flag and
|
||||
// the visible input type.
|
||||
const secretBtn = el('button', {
|
||||
class: 'so-custom-secret-toggle' + (field.is_secret ? ' is-on' : ''),
|
||||
type: 'button',
|
||||
title: field.is_secret
|
||||
? 'Stored as secret — masked every time it is opened. Click to make it public.'
|
||||
: 'Public field — shown in clear. Click to store it as a secret.',
|
||||
}, field.is_secret ? '🔒' : '👁');
|
||||
secretBtn.addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
field.is_secret = !field.is_secret;
|
||||
// Update this row in place. rerender() rebuilds every row from scratch
|
||||
// (list.innerHTML = ''), which the user sees as the panel flashing —
|
||||
// and it would drop the focus and the auto-sized height too.
|
||||
secretBtn.classList.toggle('is-on', field.is_secret);
|
||||
secretBtn.textContent = field.is_secret ? '🔒' : '👁';
|
||||
secretBtn.title = field.is_secret
|
||||
? 'Stored as secret — masked every time it is opened. Click to make it public.'
|
||||
: 'Public field — shown in clear. Click to store it as a secret.';
|
||||
// The secret flag no longer has a button of its own: two adjacent toggles
|
||||
// that both masked the value read as duplicates. The eye alone carries the
|
||||
// state (cyan ring = stored as secret) and flipping it lives in the row
|
||||
// menu, one deliberate step away from the everyday reveal.
|
||||
function applySecretState() {
|
||||
eye.classList.toggle('is-secret', !!field.is_secret);
|
||||
eye.style.visibility =
|
||||
(!field.is_secret || (valueInput.tagName !== 'INPUT' && !field.multiline))
|
||||
? 'hidden' : '';
|
||||
if (field.multiline)
|
||||
valueInput.classList.toggle('is-masked', field.is_secret);
|
||||
else if (valueInput.tagName === 'INPUT')
|
||||
valueInput.type = field.is_secret ? 'password' : 'text';
|
||||
eye.style.visibility =
|
||||
(!field.is_secret || (valueInput.tagName !== 'INPUT' && !field.multiline))
|
||||
? 'hidden' : '';
|
||||
soDirtyCheck();
|
||||
});
|
||||
}
|
||||
|
||||
const copyBtn = el('button', { class: 'icon-btn icon-btn-sm', type: 'button',
|
||||
title: 'Copy value' });
|
||||
@@ -4320,22 +4303,56 @@ function buildCustomFieldRow(field, idx, rerender) {
|
||||
toast('Copied' + clipClearSuffix());
|
||||
});
|
||||
|
||||
const delBtn = el('button', { class: 'icon-btn icon-btn-sm', type: 'button',
|
||||
title: 'Remove field' });
|
||||
delBtn.appendChild(icon('i-x'));
|
||||
delBtn.addEventListener('click', ev => {
|
||||
// Row menu — reuses the card kebab classes, so the document-level
|
||||
// "click outside .entry-kebab-wrap closes it" handler covers it for free.
|
||||
const kebabWrap = el('div', { class: 'entry-kebab-wrap' });
|
||||
const kebabBtn = el('button', { class: 'entry-kebab', type: 'button',
|
||||
title: 'Field options' });
|
||||
kebabBtn.appendChild(icon('i-more'));
|
||||
const kebabMenu = el('div', { class: 'entry-kebab-menu' });
|
||||
kebabBtn.addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
$$('.entry-kebab-menu.is-open').forEach(m => {
|
||||
if (m !== kebabMenu) m.classList.remove('is-open');
|
||||
});
|
||||
kebabMenu.classList.toggle('is-open');
|
||||
});
|
||||
|
||||
const secretItem = el('button', { class: 'kebab-item', type: 'button' });
|
||||
const paintSecretItem = () => {
|
||||
secretItem.textContent = field.is_secret ? 'Make public' : 'Store as secret';
|
||||
};
|
||||
paintSecretItem();
|
||||
secretItem.addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
field.is_secret = !field.is_secret;
|
||||
// In place, never rerender(): rebuilding the list flashes the panel and
|
||||
// drops both the focus and a multiline field's auto-sized height.
|
||||
applySecretState();
|
||||
paintSecretItem();
|
||||
kebabMenu.classList.remove('is-open');
|
||||
soDirtyCheck();
|
||||
});
|
||||
kebabMenu.appendChild(secretItem);
|
||||
|
||||
const delItem = el('button', { class: 'kebab-item is-danger', type: 'button' },
|
||||
'Remove field');
|
||||
delItem.addEventListener('click', ev => {
|
||||
ev.stopPropagation();
|
||||
soState.customFields.splice(idx, 1);
|
||||
rerender();
|
||||
soDirtyCheck();
|
||||
});
|
||||
kebabMenu.appendChild(delItem);
|
||||
kebabWrap.appendChild(kebabBtn);
|
||||
kebabWrap.appendChild(kebabMenu);
|
||||
|
||||
row.appendChild(labelInput);
|
||||
row.appendChild(valueSlot);
|
||||
row.appendChild(eye);
|
||||
row.appendChild(secretBtn);
|
||||
row.appendChild(copyBtn);
|
||||
row.appendChild(delBtn);
|
||||
row.appendChild(kebabWrap);
|
||||
applySecretState();
|
||||
return row;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user