rename config window to settings and update CLI entry points

- Replace `--config`/`subminer config` (no action) with `--settings`/`subminer settings`
- `subminer config` now requires an explicit action (`path` or `show`)
- `--settings` previously opened Yomitan; replaced by `--yomitan`
- Linux tray update installs AppImage via electron-updater instead of manual flow
- macOS update dialog activation and curl-fetch routing fixes
- Delete stale compiled artifacts (main.js, app-updater.js)
This commit is contained in:
2026-05-20 20:31:02 -07:00
parent fcd6511aa1
commit 166015897d
63 changed files with 500 additions and 5281 deletions
+4 -4
View File
@@ -7,22 +7,22 @@
http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self'; style-src 'self';"
/>
<title>SubMiner Configuration</title>
<title>SubMiner Settings</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main id="app" class="settings-shell">
<aside class="settings-nav" aria-label="Configuration categories">
<aside class="settings-nav" aria-label="Settings categories">
<div class="brand-block">
<div class="brand-title">SubMiner</div>
<div class="brand-subtitle">Configuration</div>
<div class="brand-subtitle">Settings</div>
</div>
<nav id="categoryNav" class="category-nav"></nav>
</aside>
<section class="settings-main">
<header class="settings-toolbar">
<div class="toolbar-title-block">
<h1 id="categoryTitle">Configuration</h1>
<h1 id="categoryTitle">Settings</h1>
<div id="categoryMeta" class="toolbar-meta"></div>
</div>
<div class="toolbar-actions">
+7 -4
View File
@@ -511,8 +511,12 @@ export function renderKnownWordsDecksInput(
void loadAnkiDeckFieldNames(deckName, draftUrl);
}
const row = createElement('div', 'deck-field-row');
const header = createElement('div', 'deck-field-row-header');
const usedDeckNames = new Set(Object.keys(currentDecks));
const deckSelect = createElement('select', 'config-input') as HTMLSelectElement;
const deckSelect = createElement(
'select',
'config-input deck-field-row-name',
) as HTMLSelectElement;
for (const candidateDeck of uniqueSorted([...deckNames, deckName])) {
if (candidateDeck !== deckName && usedDeckNames.has(candidateDeck)) continue;
addOption(deckSelect, candidateDeck);
@@ -534,7 +538,6 @@ export function renderKnownWordsDecksInput(
const availableFields = deckName ? (state.deckFieldNames.get(deckName) ?? []) : [];
const fieldNames = uniqueSorted([...availableFields, ...selectedFields]);
const fieldsWrap = createElement('div', 'deck-field-fields');
const fieldActions = createElement('div', 'deck-field-actions');
const checkboxList = createElement('div', 'field-checkbox-list');
@@ -569,7 +572,6 @@ export function renderKnownWordsDecksInput(
});
fieldActions.append(selectAllButton, clearButton);
fieldsWrap.append(fieldActions, checkboxList);
if (state.deckFieldNamesLoading.has(deckName)) {
const hint = createElement('div', 'control-hint');
@@ -609,7 +611,8 @@ export function renderKnownWordsDecksInput(
requestRender();
});
row.append(deckSelect, fieldsWrap, removeButton);
header.append(deckSelect, removeButton);
row.append(header, fieldActions, checkboxList);
const error = state.deckFieldNamesErrors.get(deckName);
if (error) {
const hint = createElement('div', 'control-hint error');
+3 -2
View File
@@ -1,4 +1,5 @@
import type { ConfigSettingsField, ConfigSettingsSnapshotValue } from '../types/settings';
import { toConfigDraftValue, toSettingsDisplayValue } from './settings-model';
import { parseOptionalNumberInputValue } from './input-values';
import {
configureAnkiControls,
@@ -143,7 +144,7 @@ export function renderControl(
field: ConfigSettingsField,
context: SettingsControlContext,
): HTMLElement {
const value = context.valueForField(field);
const value = toSettingsDisplayValue(field.configPath, context.valueForField(field));
if (field.control === 'keyboard-shortcut') {
return renderKeyboardInput(context, field, 'accelerator');
@@ -199,7 +200,7 @@ export function renderControl(
if (next.ok) {
input.classList.remove('invalid');
context.setFieldError(field.configPath, null);
context.updateDraft(field.configPath, next.value);
context.updateDraft(field.configPath, toConfigDraftValue(field.configPath, next.value));
} else {
input.classList.add('invalid');
context.setFieldError(field.configPath, 'Invalid number');
+10 -1
View File
@@ -6,6 +6,8 @@ import {
setDraftValue,
resetDraftPath,
getDirtyOperations,
toConfigDraftValue,
toSettingsDisplayValue,
} from './settings-model';
import type { ConfigSettingsField } from '../types/settings';
@@ -16,7 +18,7 @@ const fields: ConfigSettingsField[] = [
description: 'Pause while hovering subtitles.',
configPath: 'subtitleStyle.autoPauseVideoOnHover',
category: 'behavior',
section: 'Playback Pause Behavior',
section: 'Playback Behavior',
control: 'boolean',
defaultValue: true,
restartBehavior: 'hot-reload',
@@ -147,3 +149,10 @@ test('settings draft emits reset operations for css-editor-owned legacy style pa
},
]);
});
test('discord presence update interval displays seconds while saving milliseconds', () => {
const path = 'discordPresence.updateIntervalMs';
assert.equal(toSettingsDisplayValue(path, 3000), 3);
assert.equal(toConfigDraftValue(path, 2.5), 2500);
});
+20
View File
@@ -71,6 +71,26 @@ export function createSettingsDraft(
};
}
export function toSettingsDisplayValue(
path: string,
value: ConfigSettingsSnapshotValue,
): ConfigSettingsSnapshotValue {
if (path === 'discordPresence.updateIntervalMs' && typeof value === 'number') {
return value / 1000;
}
return value;
}
export function toConfigDraftValue(
path: string,
value: ConfigSettingsSnapshotValue,
): ConfigSettingsSnapshotValue {
if (path === 'discordPresence.updateIntervalMs' && typeof value === 'number') {
return Math.round(value * 1000);
}
return value;
}
export function setDraftValue(
draft: SettingsDraft,
path: string,
+16 -9
View File
@@ -615,17 +615,25 @@ code {
}
.deck-field-row {
display: grid;
grid-template-columns: minmax(140px, 0.75fr) minmax(220px, 1.25fr) auto;
gap: 8px;
align-items: start;
}
.deck-field-fields {
display: flex;
min-width: 0;
flex-direction: column;
gap: 6px;
gap: 8px;
padding: 12px;
border: 1px solid var(--line);
border-radius: 8px;
background: rgba(24, 25, 38, 0.4);
}
.deck-field-row-header {
display: flex;
align-items: center;
gap: 8px;
}
.deck-field-row-name {
min-width: 0;
flex: 1 1 auto;
}
.deck-field-actions {
@@ -823,7 +831,6 @@ code {
.settings-toolbar,
.field-row,
.field-control,
.deck-field-row,
.keybinding-row {
display: flex;
flex-direction: column;