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:
+11
-14
@@ -1592,11 +1592,16 @@ input[type="range"]::-webkit-slider-thumb {
|
|||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
}
|
}
|
||||||
.so-custom-row {
|
.so-custom-row {
|
||||||
|
/* label | value | eye | copy | row menu */
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1.4fr auto auto auto auto;
|
grid-template-columns: 1fr 1.4fr auto auto auto;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
/* The row menu is absolutely positioned inside its wrap; without this the
|
||||||
|
slideover clips it or it lands under the next row. */
|
||||||
|
.so-custom-row .entry-kebab-wrap { position: relative; }
|
||||||
|
.so-custom-row .entry-kebab-menu { right: 0; }
|
||||||
.so-custom-row .so-input {
|
.so-custom-row .so-input {
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@@ -1644,21 +1649,13 @@ input[type="range"]::-webkit-slider-thumb {
|
|||||||
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
.so-combo-item:hover { background: var(--accent); color: #fff; }
|
.so-combo-item:hover { background: var(--accent); color: #fff; }
|
||||||
.so-custom-secret-toggle {
|
/* The eye carries the secret state itself — a second toggle beside it read as
|
||||||
background: var(--bg);
|
a duplicate of the reveal. Flipping the flag lives in the row menu. */
|
||||||
border: 1px solid var(--border);
|
.so-custom-row .icon-btn.is-secret {
|
||||||
border-radius: 4px;
|
|
||||||
width: 28px; height: 28px;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--text-dim);
|
|
||||||
font-size: 13px;
|
|
||||||
line-height: 1;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
.so-custom-secret-toggle.is-on {
|
|
||||||
background: var(--accent-soft);
|
background: var(--accent-soft);
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
border-color: var(--accent);
|
border: 1px solid var(--accent);
|
||||||
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
.so-custom-add {
|
.so-custom-add {
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
|
|||||||
@@ -4275,38 +4275,21 @@ function buildCustomFieldRow(field, idx, rerender) {
|
|||||||
if (valueInput.tagName !== 'INPUT') return;
|
if (valueInput.tagName !== 'INPUT') return;
|
||||||
valueInput.type = valueInput.type === 'password' ? 'text' : 'password';
|
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 secret flag no longer has a button of its own: two adjacent toggles
|
||||||
// the visible input type.
|
// that both masked the value read as duplicates. The eye alone carries the
|
||||||
const secretBtn = el('button', {
|
// state (cyan ring = stored as secret) and flipping it lives in the row
|
||||||
class: 'so-custom-secret-toggle' + (field.is_secret ? ' is-on' : ''),
|
// menu, one deliberate step away from the everyday reveal.
|
||||||
type: 'button',
|
function applySecretState() {
|
||||||
title: field.is_secret
|
eye.classList.toggle('is-secret', !!field.is_secret);
|
||||||
? 'Stored as secret — masked every time it is opened. Click to make it public.'
|
eye.style.visibility =
|
||||||
: 'Public field — shown in clear. Click to store it as a secret.',
|
(!field.is_secret || (valueInput.tagName !== 'INPUT' && !field.multiline))
|
||||||
}, field.is_secret ? '🔒' : '👁');
|
? 'hidden' : '';
|
||||||
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.';
|
|
||||||
if (field.multiline)
|
if (field.multiline)
|
||||||
valueInput.classList.toggle('is-masked', field.is_secret);
|
valueInput.classList.toggle('is-masked', field.is_secret);
|
||||||
else if (valueInput.tagName === 'INPUT')
|
else if (valueInput.tagName === 'INPUT')
|
||||||
valueInput.type = field.is_secret ? 'password' : 'text';
|
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',
|
const copyBtn = el('button', { class: 'icon-btn icon-btn-sm', type: 'button',
|
||||||
title: 'Copy value' });
|
title: 'Copy value' });
|
||||||
@@ -4320,22 +4303,56 @@ function buildCustomFieldRow(field, idx, rerender) {
|
|||||||
toast('Copied' + clipClearSuffix());
|
toast('Copied' + clipClearSuffix());
|
||||||
});
|
});
|
||||||
|
|
||||||
const delBtn = el('button', { class: 'icon-btn icon-btn-sm', type: 'button',
|
// Row menu — reuses the card kebab classes, so the document-level
|
||||||
title: 'Remove field' });
|
// "click outside .entry-kebab-wrap closes it" handler covers it for free.
|
||||||
delBtn.appendChild(icon('i-x'));
|
const kebabWrap = el('div', { class: 'entry-kebab-wrap' });
|
||||||
delBtn.addEventListener('click', ev => {
|
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();
|
ev.stopPropagation();
|
||||||
soState.customFields.splice(idx, 1);
|
soState.customFields.splice(idx, 1);
|
||||||
rerender();
|
rerender();
|
||||||
soDirtyCheck();
|
soDirtyCheck();
|
||||||
});
|
});
|
||||||
|
kebabMenu.appendChild(delItem);
|
||||||
|
kebabWrap.appendChild(kebabBtn);
|
||||||
|
kebabWrap.appendChild(kebabMenu);
|
||||||
|
|
||||||
row.appendChild(labelInput);
|
row.appendChild(labelInput);
|
||||||
row.appendChild(valueSlot);
|
row.appendChild(valueSlot);
|
||||||
row.appendChild(eye);
|
row.appendChild(eye);
|
||||||
row.appendChild(secretBtn);
|
|
||||||
row.appendChild(copyBtn);
|
row.appendChild(copyBtn);
|
||||||
row.appendChild(delBtn);
|
row.appendChild(kebabWrap);
|
||||||
|
applySecretState();
|
||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user