Fold each group of settings into an open card

The long help for the download template and the date format now folds shut.
This commit is contained in:
tim
2026-10-03 08:09:41 +02:00
parent e38c8d655a
commit 42e7287389
4 changed files with 126 additions and 75 deletions
+3 -3
View File
@@ -227,9 +227,9 @@ export function globalRulesCard() {
.catch(showError); .catch(showError);
return h( return h(
'section', 'details',
{ class: 'card' }, { class: 'card', open: true },
h('h2', {}, 'Global replacement rules'), h('summary', {}, h('h2', {}, 'Global replacement rules')),
h( h(
'p', 'p',
{ class: 'hint' }, { class: 'hint' },
+93 -71
View File
@@ -9,6 +9,9 @@ const DEFAULTS_INI = 'https://github.com/JimmXinu/FanFicFare/blob/main/fanficfar
const TEMPLATE_DOCS = 'https://manual.calibre-ebook.com/template_lang.html'; const TEMPLATE_DOCS = 'https://manual.calibre-ebook.com/template_lang.html';
const DATE_CODES = 'https://docs.python.org/3/library/datetime.html#format-codes'; const DATE_CODES = 'https://docs.python.org/3/library/datetime.html#format-codes';
const code = (text) => h('code', {}, text); const code = (text) => h('code', {}, text);
// A card of settings that folds shut. It starts open.
const card = (title, ...children) =>
h('details', { class: 'card', open: true }, h('summary', {}, h('h2', {}, title)), ...children);
export function settingsPage(main, _params, config) { export function settingsPage(main, _params, config) {
document.title = 'Settings · Bindery'; document.title = 'Settings · Bindery';
@@ -104,11 +107,9 @@ export function settingsPage(main, _params, config) {
fill( fill(
main, main,
h( h('h1', {}, 'Settings'),
'section', card(
{ class: 'card' }, 'Recheck recent chapters',
h('h1', {}, 'Settings'),
h('h2', {}, 'Recheck recent chapters'),
h( h(
'div', 'div',
{ class: 'controls' }, { class: 'controls' },
@@ -124,10 +125,8 @@ export function settingsPage(main, _params, config) {
), ),
saveButton(() => ({ recent_chapters: recent.value === '' ? 0 : Number(recent.value) })), saveButton(() => ({ recent_chapters: recent.value === '' ? 0 : Number(recent.value) })),
), ),
config.cwa_enabled && h( config.cwa_enabled && card(
'section', 'Uploads to CWA',
{ class: 'card' },
h('h2', {}, 'Uploads to CWA'),
h('label', {}, linkBooks, ' Link books to CWA library'), h('label', {}, linkBooks, ' Link books to CWA library'),
h( h(
'p', 'p',
@@ -161,73 +160,100 @@ export function settingsPage(main, _params, config) {
}, },
), ),
), ),
h( card(
'section', 'Book Downloads',
{ class: 'card' },
h('h2', {}, 'Book Downloads'),
h('div', { class: 'controls' }, h('label', { for: 'download-template' }, 'File name template'), template), h('div', { class: 'controls' }, h('label', { for: 'download-template' }, 'File name template'), template),
h( h(
'p', 'p',
{ class: 'hint' }, { class: 'hint' },
'This template gives the file name of a book that you download from a story page. It follows the ', 'This template gives the file name of a book that you download from a story page. To go back to the ' +
externalLink(TEMPLATE_DOCS, 'template rules of Calibre'), 'default, ',
' for file names. Bindery replaces each field in braces with the value of the book, and adds .epub. ' + code('{title} - {authors}'),
'A field with no value becomes empty text. To go back to the default, {title} - {authors}, clear the field ' + ', clear the field and save.',
'and save. ' + ),
'For example, ', h(
code('{series:|| - }{series_index:0>2s|| - }{title} - {authors}'), 'details',
' gives "Saga, The - 02 - Book, The - Ann Writer.epub".', { class: 'help' },
h('summary', {}, 'File name template help'),
h(
'p',
{ class: 'hint' },
'The template follows the ',
externalLink(TEMPLATE_DOCS, 'template rules of Calibre'),
' for file names. Bindery replaces each field in braces with the value of the book, and adds .epub. ' +
'A field with no value becomes empty text. For example, ',
code('{series:|| - }{series_index:0>2s|| - }{title} - {authors}'),
' gives "Saga, The - 02 - Book, The - Ann Writer.epub".',
),
h('p', { class: 'hint' }, 'You can use these fields:'),
h(
'dl',
{ class: 'fields' },
Object.entries(config.download_fields).map(([name, text]) => [h('dt', {}, code(`{${name}}`)), h('dd', {}, text)]),
),
h(
'ul',
{ class: 'hint' },
h('li', {}, code('{title:.20s}'), ' gives the first 20 characters of the title.'),
h('li', {}, code('{author_sort[0]}'), ' gives the first character of the author sort string.'),
h(
'li',
{},
code('{series:|| - }'),
' adds " - " after the series, but only for a book with a series. The form is ',
code('{field:format|text before|text after}'),
'.',
),
h('li', {}, code('{{'), ' and ', code('}}'), ' give braces.'),
h(
'li',
{},
'The title and the series move a leading "The", "A" or "An" to the end, as Calibre does. ' +
'"The Book" becomes "Book, The".',
),
h(
'li',
{},
'A download has one file name. If the template has folders, Bindery uses the part after the last ',
code('/'),
'.',
),
h(
'li',
{},
'Bindery does not support template functions, such as ',
code('{title:uppercase()}'),
', or the program modes of Calibre.',
),
h('li', {}, 'If the template fails or gives no name for a book, Bindery names the file after the title.'),
),
), ),
h('div', { class: 'controls' }, h('label', { for: 'download-date-format' }, 'Date format'), dateFormat), h('div', { class: 'controls' }, h('label', { for: 'download-date-format' }, 'Date format'), dateFormat),
h( h(
'p', 'p',
{ class: 'hint' }, { class: 'hint' },
'The form of the dates in {pubdate}, {timestamp} and {last_modified}. %d gives the day, %b the month name, ' + 'The form of the dates in {pubdate}, {timestamp} and {last_modified}. To go back to the default, ',
'%m the month number and %Y the year. See the ', code('%b, %Y'),
externalLink(DATE_CODES, 'list of all codes'), ', clear the field and save.',
'. To go back to the default, %b, %Y, clear the field and save. The default gives "May, 2020".',
),
h('p', { class: 'hint' }, 'You can use these fields:'),
h(
'dl',
{ class: 'fields' },
Object.entries(config.download_fields).map(([name, text]) => [h('dt', {}, code(`{${name}}`)), h('dd', {}, text)]),
), ),
h( h(
'ul', 'details',
{ class: 'hint' }, { class: 'help' },
h('li', {}, code('{title:.20s}'), ' gives the first 20 characters of the title.'), h('summary', {}, 'Date format help'),
h('li', {}, code('{author_sort[0]}'), ' gives the first character of the author sort string.'),
h( h(
'li', 'p',
{}, { class: 'hint' },
code('{series:|| - }'), code('%d'),
' adds " - " after the series, but only for a book with a series. The form is ', ' gives the day, ',
code('{field:format|text before|text after}'), code('%b'),
'.', ' the month name, ',
code('%m'),
' the month number and ',
code('%Y'),
' the year. See the ',
externalLink(DATE_CODES, 'list of all codes'),
'. The default gives "May, 2020". A / in a date becomes _, as in Calibre.',
), ),
h('li', {}, code('{{'), ' and ', code('}}'), ' give braces.'),
h(
'li',
{},
'The title and the series move a leading "The", "A" or "An" to the end, as Calibre does. ' +
'"The Book" becomes "Book, The".',
),
h(
'li',
{},
'A download has one file name. If the template has folders, Bindery uses the part after the last ',
code('/'),
'.',
),
h(
'li',
{},
'Bindery does not support template functions, such as ',
code('{title:uppercase()}'),
', or the program modes of Calibre.',
),
h('li', {}, 'If the template fails or gives no name for a book, Bindery names the file after the title.'),
), ),
saveButton( saveButton(
() => ({ download_template: template.value, download_date_format: dateFormat.value }), () => ({ download_template: template.value, download_date_format: dateFormat.value }),
@@ -238,10 +264,8 @@ export function settingsPage(main, _params, config) {
}, },
), ),
), ),
h( card(
'section', 'Jobs list',
{ class: 'card' },
h('h2', {}, 'Jobs list'),
h( h(
'div', 'div',
{ class: 'controls' }, { class: 'controls' },
@@ -274,10 +298,8 @@ export function settingsPage(main, _params, config) {
})), })),
), ),
globalRulesCard(), globalRulesCard(),
h( card(
'section', 'FanFicFare settings (personal.ini)',
{ class: 'card' },
h('h2', {}, 'FanFicFare settings (personal.ini)'),
h( h(
'p', 'p',
{ class: 'hint' }, { class: 'hint' },
+10
View File
@@ -1063,6 +1063,16 @@ textarea {
margin-top: 12px; margin-top: 12px;
} }
/* Help that folds shut under a setting, such as the help for the download template. */
.help {
margin: 0 0 12px;
> summary {
color: var(--muted);
font-size: var(--text-sm);
}
}
/* Toasts */ /* Toasts */
#toasts { #toasts {
+20 -1
View File
@@ -55,7 +55,7 @@ for (const value of ['7', '']) {
await settle(); await settle();
api.on('PUT', 'settings', {}); api.on('PUT', 'settings', {});
field(main, 'recent-chapters').value = value; field(main, 'recent-chapters').value = value;
await click(section(main, 'Settings'), 'Save'); await click(section(main, 'Recheck recent chapters'), 'Save');
assert.deepEqual(api.sent('PUT', 'settings')[0].body, { recent_chapters: value === '' ? 0 : 7 }); assert.deepEqual(api.sent('PUT', 'settings')[0].body, { recent_chapters: value === '' ? 0 : 7 });
assert.match(ui.toasts.textContent, /Saved/); assert.match(ui.toasts.textContent, /Saved/);
}); });
@@ -109,6 +109,25 @@ test('the jobs list settings load and save all four fields', async () => {
assert.match(ui.toasts.textContent, /Saved/); assert.match(ui.toasts.textContent, /Saved/);
}); });
test('each group of settings is an open card, and the download help starts closed', async () => {
settingsPage(main, {}, config);
await settle();
const cards = main.children.filter((el) => el.tagName !== 'h1');
assert.deepEqual(cards.map((el) => [el.tagName, el.className, el.open, el.children[0].textContent]), [
['details', 'card', true, 'Recheck recent chapters'],
['details', 'card', true, 'Uploads to CWA'],
['details', 'card', true, 'Book Downloads'],
['details', 'card', true, 'Jobs list'],
['details', 'card', true, 'Global replacement rules'],
['details', 'card', true, 'FanFicFare settings (personal.ini)'],
]);
const help = nodes(section(main, 'Book Downloads')).filter((el) => el.className === 'help');
assert.deepEqual(help.map((el) => [el.tagName, el.open, el.children[0].textContent]), [
['details', undefined, 'File name template help'],
['details', undefined, 'Date format help'],
]);
});
test('the download template loads, lists its fields, and saves the stored form', async () => { test('the download template loads, lists its fields, and saves the stored form', async () => {
api.on('GET', 'settings', { api.on('GET', 'settings', {
recent_chapters: 0, personal_ini: '', download_template: '{title}', download_date_format: '%b, %Y', recent_chapters: 0, personal_ini: '', download_template: '{title}', download_date_format: '%b, %Y',