diff --git a/abogen/web/static/prepare.js b/abogen/web/static/prepare.js index b4ff76c..bd6cfd0 100644 --- a/abogen/web/static/prepare.js +++ b/abogen/web/static/prepare.js @@ -188,6 +188,8 @@ document.addEventListener("DOMContentLoaded", () => { const enabled = row.querySelector('[data-role=chapter-enabled]'); const inputs = Array.from(row.querySelectorAll("input[type=text], select, textarea")); const warning = row.querySelector('[data-role=chapter-warning]'); + const snippet = row.querySelector('[data-role="chapter-snippet"]'); + const details = row.querySelector('[data-role="chapter-details"]'); const isChecked = enabled ? enabled.checked : true; row.dataset.disabled = isChecked ? "false" : "true"; @@ -212,6 +214,16 @@ document.addEventListener("DOMContentLoaded", () => { const select = row.querySelector("select[data-role=voice-select]"); toggleFormula(select); + if (snippet) { + snippet.hidden = !isChecked; + snippet.setAttribute("aria-hidden", isChecked ? "false" : "true"); + } + + if (details) { + details.hidden = !isChecked; + details.setAttribute("aria-hidden", isChecked ? "false" : "true"); + } + if (warning) { warning.hidden = isChecked; warning.setAttribute("aria-hidden", isChecked ? "true" : "false"); @@ -337,7 +349,15 @@ document.addEventListener("DOMContentLoaded", () => { }); const speakerItems = Array.from(form.querySelectorAll(".speaker-list__item")); - speakerItems.forEach((item) => initialiseSpeakerItem(item)); + speakerItems.forEach((item) => { + initialiseSpeakerItem(item); + const pronunciationInput = item.querySelector('[data-role="speaker-pronunciation"]'); + if (pronunciationInput) { + const sync = () => syncPronunciationPreview(item); + pronunciationInput.addEventListener("input", sync); + pronunciationInput.addEventListener("change", sync); + } + }); const activeStepInput = form.querySelector('[data-role="active-step-input"]'); const analysisButtons = Array.from(form.querySelectorAll('[data-role="submit-speaker-analysis"]')); diff --git a/abogen/web/static/speakers.js b/abogen/web/static/speakers.js index 4062446..fed4117 100644 --- a/abogen/web/static/speakers.js +++ b/abogen/web/static/speakers.js @@ -26,6 +26,21 @@ const stopCurrentPlayback = () => { } }; +const resolvePreviewText = (button) => { + const source = (button.dataset.previewSource || "").toLowerCase(); + if (source === "pronunciation") { + const container = button.closest(".speaker-list__item"); + if (container) { + const input = container.querySelector('[data-role="speaker-pronunciation"]'); + const fallback = (container.dataset.defaultPronunciation || "").trim(); + const value = (input?.value || "").trim() || fallback; + button.dataset.previewText = value; + return value; + } + } + return (button.dataset.previewText || "").trim(); +}; + audioElement.addEventListener("ended", () => { stopCurrentPlayback(); }); @@ -37,7 +52,7 @@ audioElement.addEventListener("pause", () => { }); const playPreview = async (button) => { - const text = (button.dataset.previewText || "").trim(); + const text = resolvePreviewText(button); const voice = (button.dataset.voice || "").trim(); const language = (button.dataset.language || "a").trim() || "a"; const speedRaw = button.dataset.speed || "1"; diff --git a/abogen/web/static/styles.css b/abogen/web/static/styles.css index 257ecb0..d8b7fa7 100644 --- a/abogen/web/static/styles.css +++ b/abogen/web/static/styles.css @@ -618,6 +618,16 @@ body { border: 1px solid rgba(148, 163, 184, 0.18); } +button.step-indicator__item { + cursor: pointer; + font: inherit; +} + +button.step-indicator__item:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; +} + .step-indicator__index { width: 1.75rem; height: 1.75rem; diff --git a/abogen/web/templates/index.html b/abogen/web/templates/index.html index 6142d46..93c1008 100644 --- a/abogen/web/templates/index.html +++ b/abogen/web/templates/index.html @@ -17,163 +17,7 @@ - +{% include "partials/upload_modal.html" with pending=None, readonly=False, active_step='settings' %} {% include "partials/reader_modal.html" %} diff --git a/abogen/web/templates/partials/upload_modal.html b/abogen/web/templates/partials/upload_modal.html new file mode 100644 index 0000000..9cc2a6c --- /dev/null +++ b/abogen/web/templates/partials/upload_modal.html @@ -0,0 +1,235 @@ +{% set pending = pending if pending is defined else None %} +{% set readonly = readonly if readonly is defined else False %} +{% set modal_step = (active_step if active_step is defined else 'settings') or 'settings' %} +{% set settings_dict = settings if settings is defined else {} %} +{% set is_multi = False %} +{% if pending %} + {% set is_multi = pending.speaker_mode == 'multi' %} +{% elif settings_dict %} + {% set is_multi = settings_dict.get('speaker_mode', 'single') == 'multi' %} +{% endif %} +{% set language_value = pending.language if pending else settings_dict.get('language', '') %} +{% if not language_value %} + {% set sorted_languages = options.languages|dictsort %} + {% if sorted_languages %} + {% set language_value = sorted_languages[0][0] %} + {% endif %} +{% endif %} +{% set subtitle_value = pending.subtitle_mode if pending else settings_dict.get('subtitle_mode', 'Disabled') %} +{% set generate_epub3 = pending.generate_epub3 if pending else settings_dict.get('generate_epub3', False) %} +{% set speaker_mode_value = pending.speaker_mode if pending else settings_dict.get('speaker_mode', 'single') %} +{% set chunk_level_value = pending.chunk_level if pending else settings_dict.get('chunk_level', 'paragraph') %} +{% set analysis_threshold_value = pending.speaker_analysis_threshold if pending else settings_dict.get('speaker_analysis_threshold', 3) %} +{% set chapter_delay_value = pending.chapter_intro_delay if pending else settings_dict.get('chapter_intro_delay', 0.5) %} +{% set selected_config = pending.applied_speaker_config if pending else '' %} +{% set narrator_voice = pending.voice if pending else settings_dict.get('default_voice', options.voices[0] if options.voices else '') %} +{% set narrator_profile = pending.voice_profile if pending else '' %} +{% set narrator_speed = pending.speed if pending else 1.0 %} +{% set voice_formula_value = '' %} +{% set profile_value = '__standard' %} +{% if narrator_profile %} + {% set profile_value = narrator_profile %} +{% else %} + {% if narrator_voice and ('+' in narrator_voice or '*' in narrator_voice) %} + {% set profile_value = '__formula' %} + {% set voice_formula_value = narrator_voice %} + {% else %} + {% set profile_value = '__standard' %} + {% endif %} +{% endif %} +{% if not narrator_voice and options.voices %} + {% set narrator_voice = options.voices[0] %} +{% endif %} +{% set speed_display = '%.2f'|format(narrator_speed) %} +{% set step1_state = 'is-active' if modal_step in ['settings', 'upload'] else ('is-complete' if modal_step in ['chapters', 'speakers'] else '') %} +{% set step2_state = 'is-active' if modal_step == 'chapters' else ('is-complete' if modal_step == 'speakers' else '') %} +{% set step3_state = 'is-active' if modal_step == 'speakers' else '' %} + diff --git a/abogen/web/templates/prepare_chapters.html b/abogen/web/templates/prepare_chapters.html index b451816..0ae0737 100644 --- a/abogen/web/templates/prepare_chapters.html +++ b/abogen/web/templates/prepare_chapters.html @@ -10,10 +10,12 @@ -
+ +{% include "partials/upload_modal.html" with pending=pending, readonly=True, active_step='speakers' %} {% endblock %} {% block scripts %} {{ super() }} + + {% endblock %}