Each chapter in the right-hand menu gets a caret that unfolds its numbered sections (e.g. the prologue's 28 scenes); clicking one jumps straight to it. The chapter you're reading starts unfolded, other chapters' sections link across (/storyline/overview#section-…), and the section at the top of the pane is highlighted as you scroll. - ChapterOutline service parses each rendered chapter for its <h2 class="storyline-section"> headings, adds anchor ids and returns the outline; the controller renders all chapters once to feed the sidebar. - storyline-toc Stimulus controller: fold/unfold, in-pane jump, scroll-spy (works in the split view and in the small-screen window-scroll fallback). - Tests for the outline parser. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
104 lines
6.0 KiB
Twig
104 lines
6.0 KiB
Twig
{% extends 'base.html.twig' %}
|
|
|
|
{% block title %}{{ chapter.title }} · Storyline · dmtools{% endblock %}
|
|
|
|
{% block body_class %}storyline-chapter-page{% endblock %}
|
|
|
|
{% block body %}
|
|
<div class="storyline-split" data-controller="chapter-notes">
|
|
<div class="storyline-split-main">
|
|
<div class="row g-4 storyline-layout">
|
|
<nav class="col-lg-3 order-lg-2">
|
|
<div class="storyline-toc">
|
|
<a href="{{ path('app_storyline') }}" class="text-decoration-none small d-inline-block mb-2">← All chapters</a>
|
|
<div class="text-uppercase small text-body-secondary fw-semibold mb-2" style="letter-spacing:.08em">Chapters</div>
|
|
<ol class="list-unstyled mb-0" data-controller="storyline-toc">
|
|
{% for s, c in chapters %}
|
|
{% set open = s == slug %}
|
|
<li class="storyline-toc-item{{ open ? ' is-open' }}">
|
|
<div class="storyline-toc-row">
|
|
{% if sections[s] is not empty %}
|
|
<button type="button" class="storyline-toc-fold" data-action="storyline-toc#fold"
|
|
aria-expanded="{{ open ? 'true' : 'false' }}"
|
|
aria-label="Show sections of {{ c.title }}">▸</button>
|
|
{% else %}<span class="storyline-toc-fold"></span>{% endif %}
|
|
<a href="{{ path('app_storyline_chapter', {slug: s}) }}"
|
|
class="storyline-toc-link{{ s == slug ? ' is-active' }}">
|
|
<span class="storyline-toc-num">{{ c.tag }}</span>{{ c.title }}
|
|
</a>
|
|
</div>
|
|
{% if sections[s] is not empty %}
|
|
<ol class="storyline-toc-sections list-unstyled">
|
|
{% for sec in sections[s] %}
|
|
<li>
|
|
<a href="{{ s == slug ? '#' ~ sec.id : path('app_storyline_chapter', {slug: s}) ~ '#' ~ sec.id }}"
|
|
class="storyline-toc-sub" {% if s == slug %}data-storyline-toc-target="link"{% endif %}>
|
|
<span class="storyline-toc-num">{{ sec.num }}</span>{{ sec.title }}
|
|
</a>
|
|
</li>
|
|
{% endfor %}
|
|
</ol>
|
|
{% endif %}
|
|
</li>
|
|
{% endfor %}
|
|
</ol>
|
|
</div>
|
|
</nav>
|
|
|
|
<div class="col-lg-9 order-lg-1">
|
|
<article class="storyline-doc">
|
|
<header class="storyline-doc-head">
|
|
<div class="storyline-eyebrow">Storyline · {{ chapter.eyebrow }}</div>
|
|
<h1>{{ chapter.title }}</h1>
|
|
<p class="storyline-standfirst">{{ chapter.summary }}</p>
|
|
</header>
|
|
|
|
{{ body|raw }}
|
|
</article>
|
|
|
|
<nav class="storyline-pager">
|
|
{% if prev %}
|
|
<a href="{{ path('app_storyline_chapter', {slug: prev}) }}" class="storyline-pager-link">
|
|
<span class="storyline-pager-dir">← Previous</span>
|
|
<span class="storyline-pager-title">{{ chapters[prev].title }}</span>
|
|
</a>
|
|
{% else %}<span></span>{% endif %}
|
|
{% if next %}
|
|
<a href="{{ path('app_storyline_chapter', {slug: next}) }}" class="storyline-pager-link text-lg-end">
|
|
<span class="storyline-pager-dir">Next →</span>
|
|
<span class="storyline-pager-title">{{ chapters[next].title }}</span>
|
|
</a>
|
|
{% else %}<span></span>{% endif %}
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="storyline-notes-resize" data-chapter-notes-target="handle"
|
|
data-action="pointerdown->chapter-notes#startResize" role="separator"
|
|
aria-label="Resize notes panel" title="Drag to resize"></div>
|
|
|
|
<section class="storyline-notes-dock" data-chapter-notes-target="dock">
|
|
<div class="storyline-notes-bar">
|
|
<button type="button" class="storyline-notes-toggle" data-action="chapter-notes#toggle"
|
|
data-chapter-notes-target="toggle" aria-expanded="true">
|
|
<span class="storyline-notes-caret">▾</span> Chapter notes
|
|
</button>
|
|
{% if note.updatedAt %}
|
|
<span class="text-body-secondary small">saved {{ note.updatedAt|date('Y-m-d H:i') }}</span>
|
|
{% endif %}
|
|
</div>
|
|
<div class="storyline-notes-body">
|
|
{{ form_start(form, {attr: {class: 'storyline-notes-form'}}) }}
|
|
{{ form_errors(form.notes) }}
|
|
{{ form_widget(form.notes, {attr: {class: 'form-control storyline-notes-text'}}) }}
|
|
<div class="storyline-notes-actions">
|
|
<button class="btn btn-primary btn-sm">Save notes</button>
|
|
<span class="text-body-secondary small">This chapter only. Saved to the server.</span>
|
|
</div>
|
|
{{ form_end(form) }}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
{% endblock %}
|