Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 23 additions & 26 deletions public/js/admin-app-form.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { clr as rc, el, inp as inpById, q as qSel, qa, qi, tgt } from '/js/utils.js?v=26566e09';
import { clr as rc, el, inp as inpById, q as qSel, qa, qi, tgt } from '/js/utils.js?v=8ca7ce3c';
import { html, raw, setHtml } from '/js/html.js?v=c71f8903';
import { loadLocalIcons, resolveIcon, iconChain, cdnIconName } from '/js/icons.js?v=69c2b9bd';
import { state } from '/js/admin-state.js?v=c23e6346';
import { isDockBlocked, DOCK_MAX, clearsStoredSecret } from '/js/admin-logic.js?v=ddfc6f80';
import { t } from '/js/i18n.js?v=d056c9c5';
import { isDockBlocked, DOCK_MAX, clearsStoredSecret } from '/js/admin-logic.js?v=d17394da';
import { t } from '/js/i18n.js?v=83239bf4';
import {
toast,
ag,
Expand All @@ -13,9 +13,9 @@ import {
initInlineEdit,
setTogDisabled,
wireChecklist,
} from '/js/admin-shared.js?v=e9afcefb';
import { MAX_LABELS } from '/js/badge-logic.js?v=c6430afc';
import { renderColorControl, BADGE_SWATCHES, BADGE_DEFAULT } from '/js/admin-color-control.js?v=32111fda';
} from '/js/admin-shared.js?v=1d330931';
import { MAX_LABELS } from '/js/badge-logic.js?v=41a929ac';
import { renderColorControl, BADGE_SWATCHES, BADGE_DEFAULT } from '/js/admin-color-control.js?v=bfd0955a';
import { badgeErrorAdvice, TONE } from '/js/admin-error.js?v=10f3cdb1';

export function buildFolderForm(body, item) {
Expand Down Expand Up @@ -43,7 +43,7 @@ export function buildFolderForm(body, item) {
<span class="rl">${t('folder.name')}</span>
<span class="rv${item?.label ? '' : ' is-ph'}">${item?.label || t('folder.namePh')}</span>
<input id="f-fname" type="text" value="${item?.label || ''}" style="display:none">
<button class="pe" type="button" aria-label="Edit folder name">${raw(PE_SVG)}</button>
<button class="pe" type="button">${raw(PE_SVG)}</button>
</div>
<div class="row">
<span class="rl">${t('folder.addApps')}</span>
Expand All @@ -52,7 +52,7 @@ export function buildFolderForm(body, item) {
<span id="folder-apps-label">${t('folder.selectApps')}</span>
${raw(CHEV_SVG)}
</button>
<ul class="row-dd-list checklist" id="folder-apps-list" role="listbox" aria-multiselectable="true" aria-label="Apps in this folder" hidden>${opts}</ul>
<ul class="row-dd-list checklist" id="folder-apps-list" role="listbox" aria-multiselectable="true" aria-label="${t('folder.appsInFolder')}" hidden>${opts}</ul>
</div>
</div>
</div>
Expand Down Expand Up @@ -165,7 +165,7 @@ export function buildAppForm(body, item) {
<div id="auth-row-wrap">
<div class="row"><span class="rl">${t('app.authentication')}</span>${tog('auth-en', !!(act.params || act.headers), t('app.authentication'))}</div>
<div id="auth-sub" ${act.params?.length || act.headers?.length ? '' : 'hidden'}>
<div class="row kv-hdr"><span class="rl">Add to URL <span class="rl-sub">(query params)</span></span></div>
<div class="row kv-hdr"><span class="rl">${t('app.addToUrl')} <span class="rl-sub">(query params)</span></span></div>
<div id="bpar-rows" class="kv-rows"></div>
<div class="row kv-hdr"><span class="rl">${t('app.addToHeader')}</span></div>
<div id="bhdr-rows" class="kv-rows"></div>
Expand All @@ -178,7 +178,7 @@ export function buildAppForm(body, item) {
<div id="act-labels"></div>
<p class="grp-tip" id="act-label-order">${t('app.labelOrderTip')}</p>
<div class="albl-add-wrap"><button type="button" class="albl-add" id="act-add-label">${t('app.addLabel')}</button></div>
<p class="grp-tip" id="act-label-max" hidden>${t('app.labelMax', { n: MAX_LABELS })}</p>
<p class="grp-tip" id="act-label-max" hidden>${t('app.labelMax', { count: MAX_LABELS })}</p>
<div id="act-combine-row" class="bprow-hidden">
<div class="grp"><div class="row"><span class="rl">${t('app.combineValues')}</span>${tog('act-combine', !!act.combine || !!state.slegacySum, t('app.combineValues'))}</div></div>
<p class="grp-tip" id="act-combine-tip">${t('app.combineTip')}</p>
Expand Down Expand Up @@ -256,9 +256,7 @@ export function buildAppForm(body, item) {
el('act-add-label')?.addEventListener('click', addActLabel);
const savedStatus = el('bst');
if (savedStatus && state.spaths.length)
savedStatus.textContent = t(state.spaths.length === 1 ? 'app.labelsSaved' : 'app.labelsSavedPlural', {
n: state.spaths.length,
});
savedStatus.textContent = t('app.labelsSaved', { count: state.spaths.length });
syncActMode();
}

Expand Down Expand Up @@ -388,10 +386,10 @@ function renderActLabels(host) {
setHtml(
card,
html`${_valueSelect(i, path)}
${_ieRow(`ie-albl-name-${i}`, _optRow(t('app.labelText')), `albl-name-${i}`, l.name, t('app.labelPh'), 'text', t('app.labelText'))}
${_ieRow(`ie-albl-name-${i}`, _optRow(t('app.labelText')), `albl-name-${i}`, l.name, t('app.labelPh'))}
<div id="albl-col-slot-${i}"></div>
${_ieRow(`ie-albl-unit-${i}`, _optRow(t('app.unit')), `albl-unit-${i}`, l.unit, t('app.unitPh'), 'text', t('app.unit'))}
${_ieRow(`ie-albl-min-${i}`, _optRow(t('app.badgeMin')), `albl-min-${i}`, l.min, t('app.badgeMinPh'), 'number', t('app.badgeMin'))}`,
${_ieRow(`ie-albl-unit-${i}`, _optRow(t('app.unit')), `albl-unit-${i}`, l.unit, t('app.unitPh'))}
${_ieRow(`ie-albl-min-${i}`, _optRow(t('app.badgeMin')), `albl-min-${i}`, l.min, t('app.badgeMinPh'), 'number')}`,
);
host.appendChild(card);
initInlineEdit(`ie-albl-name-${i}`, `albl-name-${i}`, { placeholder: t('app.labelPh') });
Expand Down Expand Up @@ -430,7 +428,7 @@ const GRIP_SVG =
function addActLabel() {
captureActLabels();
if (state.spaths.length >= MAX_LABELS) {
toast(t('app.labelMax', { n: MAX_LABELS }), 'err');
toast(t('app.labelMax', { count: MAX_LABELS }), 'err');
return;
}
const free = actValueOptions().find(o => !state.spaths.includes(o.path));
Expand Down Expand Up @@ -490,12 +488,11 @@ function wireActLabelDrag(host) {
}
}

/** The inline-edit row markup. `label` may carry markup, so the button's
accessible name comes from `aria`: an attribute cannot hold an element. */
function _ieRow(rowId, label, inpId, val, ph, type = 'text', aria) {
/** The inline-edit row markup. The pencil's accessible name is set by
initInlineEdit, from the row's own label. */
function _ieRow(rowId, label, inpId, val, ph, type = 'text') {
const has = val != null && val !== '';
const name = aria == null ? String(label) : aria;
return html`<div class="row ie-row" id="${rowId}"><span class="rl">${label}</span><span class="rv${has ? '' : ' is-ph'}">${has ? val : ph}</span><input id="${inpId}" type="${type}" value="${val || ''}" style="display:none"><button class="pe" type="button" aria-label="Edit ${name}">${raw(PE_SVG)}</button></div>`;
return html`<div class="row ie-row" id="${rowId}"><span class="rl">${label}</span><span class="rv${has ? '' : ' is-ph'}">${has ? val : ph}</span><input id="${inpId}" type="${type}" value="${val || ''}" style="display:none"><button class="pe" type="button">${raw(PE_SVG)}</button></div>`;
}

function wireIcon() {
Expand Down Expand Up @@ -752,10 +749,10 @@ function kvRowEl(host, rows, row, ph) {
setHtml(
el,
html`
<input class="kv-k" type="text" placeholder="Key" value="${row.key}" aria-label="Header key">
<input class="kv-v" type="${row.secret ? 'password' : 'text'}" placeholder="${valPh}" value="${row.value}" autocomplete="off" aria-label="Header value">
<label class="kv-cred" title="Store this value as a credential: hidden after saving and never exported. Unticking clears the stored value."><input type="checkbox" ${row.secret ? 'checked' : ''} aria-label="${t('app.secret')}"><span class="kv-box"></span><span class="kv-cred-lbl">${t('app.secret')}</span></label>
<button class="kv-del" type="button" aria-label="Remove">✕</button>`,
<input class="kv-k" type="text" placeholder="${t('app.headerKeyPh')}" value="${row.key}" aria-label="${t('app.headerKey')}">
<input class="kv-v" type="${row.secret ? 'password' : 'text'}" placeholder="${valPh}" value="${row.value}" autocomplete="off" aria-label="${t('app.headerValue')}">
<label class="kv-cred" title="${t('app.secretTip')}"><input type="checkbox" ${row.secret ? 'checked' : ''} aria-label="${t('app.secret')}"><span class="kv-box"></span><span class="kv-cred-lbl">${t('app.secret')}</span></label>
<button class="kv-del" type="button" aria-label="${t('widgetCfg.remove')}">✕</button>`,
);
const kEl = qi('.kv-k', el),
vEl = qi('.kv-v', el),
Expand Down
6 changes: 3 additions & 3 deletions public/js/admin-auth.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ag, ap } from '/js/admin-shared.js?v=e9afcefb';
import { t } from '/js/i18n.js?v=d056c9c5';
import { ag, ap } from '/js/admin-shared.js?v=1d330931';
import { t } from '/js/i18n.js?v=83239bf4';
import { pwStrength } from '/js/password-strength.js?v=42f45ac7';
import { el, inp as inpById, qa } from '/js/utils.js?v=26566e09';
import { el, inp as inpById, qa } from '/js/utils.js?v=8ca7ce3c';

export async function checkAuth(onLogin) {
try {
Expand Down
16 changes: 8 additions & 8 deletions public/js/admin-color-control.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PE_SVG, initInlineEdit, toast } from '/js/admin-shared.js?v=e9afcefb';
import { t } from '/js/i18n.js?v=d056c9c5';
import { PE_SVG, initInlineEdit, toast } from '/js/admin-shared.js?v=1d330931';
import { t } from '/js/i18n.js?v=83239bf4';
import { html, raw, setHtml } from '/js/html.js?v=c71f8903';
import { qa, q } from '/js/utils.js?v=26566e09';
import { qa, q } from '/js/utils.js?v=8ca7ce3c';

const CC_SWATCHES = ['#1c1c1e', '#8e8e93', '#f2f2f7', '#ff393c', '#ffcd00', '#35c759', '#0289ff', '#cb30df'];
export const BADGE_DEFAULT = '#1e6ef4';
Expand Down Expand Up @@ -127,11 +127,11 @@ export function renderColorControl(
const swatch = h =>
html`<button type="button" class="cc-swatch" data-v="${h}" style="background:${h}" aria-label="${h}"></button>`;
const swatches = semantic
? html`<button type="button" class="cc-swatch cc-sem" data-v="dark" style="background:#1c1c1e" title="Dark (theme)" aria-label="Dark"></button>
<button type="button" class="cc-swatch cc-sem" data-v="light" style="background:#f2f2f7" title="Light (theme)" aria-label="Light"></button>
<button type="button" class="cc-swatch cc-rainbow" data-v="custom" aria-label="Custom color"></button>
? html`<button type="button" class="cc-swatch cc-sem" data-v="dark" style="background:#1c1c1e" title="${t('appearance.themeDark')}" aria-label="${t('appearance.displayDark')}"></button>
<button type="button" class="cc-swatch cc-sem" data-v="light" style="background:#f2f2f7" title="${t('appearance.themeLight')}" aria-label="${t('appearance.displayLight')}"></button>
<button type="button" class="cc-swatch cc-rainbow" data-v="custom" aria-label="${t('appearance.customColor')}"></button>
${['#ff393c', '#ffcd00', '#35c759', '#0289ff', '#cb30df'].map(swatch)}`
: html`<button type="button" class="cc-swatch cc-rainbow" data-v="custom" aria-label="Custom color"></button>
: html`<button type="button" class="cc-swatch cc-rainbow" data-v="custom" aria-label="${t('appearance.customColor')}"></button>
${swatchColors.map(swatch)}`;
const wrap = document.createElement('div');
const slider = (label, cls, id, max, val, lo, hi) => html`
Expand All @@ -143,7 +143,7 @@ export function renderColorControl(
${slider('Hue', 'hsb-range hsb-hue', `${idPrefix}-h`, 360, init.h, _ccIco.hueLo, _ccIco.hueHi)}
${slider('Saturation', 'hsb-range', `${idPrefix}-s`, 100, init.s, _ccIco.satLo, _ccIco.satHi)}
${slider('Brightness', 'hsb-range', `${idPrefix}-v`, 100, init.v, _ccIco.brLo, _ccIco.brHi)}
<div class="row ie-row cc-tune" id="${idPrefix}-code-row"><span class="rl">Color Code</span><span class="rv is-ph">#rrggbb or any CSS color</span><input id="${idPrefix}-hex" type="text" style="display:none"><button class="pe" type="button" aria-label="Edit color code">${raw(PE_SVG)}</button></div>`,
<div class="row ie-row cc-tune" id="${idPrefix}-code-row"><span class="rl">${t('appearance.colorCode')}</span><span class="rv is-ph">#rrggbb or any CSS color</span><input id="${idPrefix}-hex" type="text" style="display:none"><button class="pe" type="button">${raw(PE_SVG)}</button></div>`,
);
const rows = /** @type {HTMLElement[]} */ ([...wrap.children]);
rows.forEach(r => container.appendChild(r));
Expand Down
4 changes: 0 additions & 4 deletions public/js/admin-logic.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,10 +251,6 @@ export function rejectionLines(rejections, { withName = true } = {}) {
return out;
}

export function refusedNoticeKey(count) {
return count === 1 ? 'widgetCfg.refused' : 'widgetCfg.refusedPlural';
}

/* Long errors need longer than short confirmations. 60ms per character is
about 16 characters a second, under a slow reading speed. */
export function toastMs(message) {
Expand Down
8 changes: 4 additions & 4 deletions public/js/admin-settings.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { toast, ag, ap } from '/js/admin-shared.js?v=e9afcefb';
import { toast, ag, ap } from '/js/admin-shared.js?v=1d330931';
import { pwStrength } from '/js/password-strength.js?v=42f45ac7';
import { t } from '/js/i18n.js?v=d056c9c5';
import { shouldWritePassword, settingsSaveBlocker, clearsStoredPassword, BLOCK } from '/js/admin-logic.js?v=ddfc6f80';
import { el, inp, q, qa, setUserText } from '/js/utils.js?v=26566e09';
import { t } from '/js/i18n.js?v=83239bf4';
import { shouldWritePassword, settingsSaveBlocker, clearsStoredPassword, BLOCK } from '/js/admin-logic.js?v=d17394da';
import { el, inp, q, qa, setUserText } from '/js/utils.js?v=8ca7ce3c';

/* Mirrors the server's rule: auth cannot be switched on with no password. */
let _passwordSet = false;
Expand Down
10 changes: 7 additions & 3 deletions public/js/admin-shared.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* Stateless helpers shared by the admin modules. Mutable state stays out. */
import { nextActiveIndex, recoversSession, toastHoldMs } from '/js/admin-logic.js?v=ddfc6f80';
import { el, qa, q } from '/js/utils.js?v=26566e09';
import { t } from '/js/i18n.js?v=d056c9c5';
import { nextActiveIndex, recoversSession, toastHoldMs } from '/js/admin-logic.js?v=d17394da';
import { el, qa, q } from '/js/utils.js?v=8ca7ce3c';
import { t } from '/js/i18n.js?v=83239bf4';

export const API = '';

Expand Down Expand Up @@ -155,6 +155,10 @@ export function initInlineEdit(rowId, inputId, { type = 'text', placeholder = ''
if (labelEl) {
if (!labelEl.id) labelEl.id = `${rowId}-rl`;
inp.setAttribute('aria-labelledby', labelEl.id);
/* The pencil opens this row, so it is named after this row. Taking the name
from the label keeps the two in one language, and in step when either
changes. */
pen.setAttribute('aria-label', t('common.editNamed', { name: labelEl.textContent.trim() }));
}
row.insertBefore(inp, pen);

Expand Down
Loading