412 lines
21 KiB
HTML
412 lines
21 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}diora — Radio{% endblock %}
|
|
|
|
{% block content %}
|
|
|
|
<!-- ===== NOW PLAYING BAR ===== -->
|
|
<section class="now-playing-bar" id="now-playing-bar">
|
|
<div class="now-playing-info">
|
|
<span class="now-playing-station" id="now-playing-station">— kein Sender —</span>
|
|
<span class="now-playing-track" id="now-playing-track"></span>
|
|
</div>
|
|
<div class="now-playing-controls">
|
|
<button class="btn btn-play" id="play-stop-btn" onclick="togglePlayStop()" style="display:none;">▶ Abspielen</button>
|
|
<label class="volume-label">
|
|
<span>Lautst.</span>
|
|
<input type="range" id="volume" min="0" max="255" value="102" class="volume-slider">
|
|
<input type="number" id="volume-num" min="0" max="255" value="102" class="volume-num">
|
|
</label>
|
|
<button class="btn btn-save" id="save-station-btn" style="display:none;" onclick="saveCurrentStation()">★ Speichern</button>
|
|
<button class="btn-icon" id="creamfresh-vote-up-btn" style="display:none;" onclick="creamfreshVote('up')" title="Gefällt mir">👍</button>
|
|
<button class="btn-icon" id="creamfresh-vote-down-btn" style="display:none;" onclick="creamfreshVote('down')" title="Gefällt mir nicht">👎</button>
|
|
<button class="btn-icon" id="dnd-btn" onclick="toggleDND()" title="Fokusmodus (blendet die Oberfläche aus, Esc beendet ihn)">⊙</button>
|
|
<button class="btn-icon" id="focus-station-btn" onclick="openRadioSidebar()" title="Radio">◉</button>
|
|
</div>
|
|
<div class="timer-widget" id="timer-widget">
|
|
<span class="timer-phase" id="timer-phase-label">Fokus</span>
|
|
<span class="timer-display" id="timer-display">25:00</span>
|
|
<button class="btn-icon" id="timer-toggle-btn" onclick="toggleTimer()" title="Timer starten/anhalten">▶</button>
|
|
<button class="btn-icon" id="timer-reset-btn" onclick="resetTimer()" title="Timer zurücksetzen">↺</button>
|
|
<span class="focus-today" id="focus-today-widget" style="display:none;"></span>
|
|
<button class="btn-icon dnd-only" id="dnd-light-btn" onclick="toggleDNDLight()" title="Schwarzer Hintergrund an/aus">☼</button>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ===== AFFILIATE / TRACK INFO ===== -->
|
|
<section class="affiliate-section" id="affiliate-section" style="display:none;"{% if not amazon_enabled %} data-disabled="true"{% endif %}>
|
|
<img class="affiliate-artwork" id="affiliate-artwork" src="" alt="Albumcover">
|
|
<div class="affiliate-info">
|
|
<div class="affiliate-track" id="affiliate-track-name"></div>
|
|
<div class="affiliate-artist" id="affiliate-artist-name"></div>
|
|
<div class="affiliate-album" id="affiliate-album-name"></div>
|
|
<a class="btn btn-amazon" id="affiliate-amazon-link" href="#" target="_blank" rel="noopener noreferrer">
|
|
Bei Amazon Music kaufen
|
|
</a>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ===== TABS ===== -->
|
|
<div class="tabs" id="tabs">
|
|
{# Order = TOP_TABS (static/js/app/20-tabs.js). Home needs an account: hidden, but kept in place for the order. #}
|
|
<button class="tab-btn" onclick="showTab('home')"{% if not user.is_authenticated %} style="display:none;"{% endif %}>Home</button>
|
|
<button class="tab-btn active" onclick="showTab('radio')">Radio</button>
|
|
<button class="tab-btn" onclick="showTab('focus')">Fokus</button>
|
|
<button class="tab-btn" onclick="showTab('podcasts')">Podcasts</button>
|
|
<button class="tab-btn" onclick="showTab('books')">Bücher</button>
|
|
<button class="tab-btn" onclick="showTab('feeds')"{% if not user.is_authenticated %} style="display:none;"{% endif %}>Feeds</button>
|
|
<button class="tab-btn" onclick="showTab('ambient')">Ambient</button>
|
|
</div>
|
|
|
|
<!-- ===== RADIO TAB ===== -->
|
|
<section class="tab-panel" id="tab-radio">
|
|
<div class="tabs sub-tabs" id="radio-sub-tabs">
|
|
<button class="tab-btn" onclick="showRadioTab('search')">Suche</button>
|
|
<button class="tab-btn active" onclick="showRadioTab('saved')">Gespeichert</button>
|
|
<button class="tab-btn" onclick="showRadioTab('history')">Verlauf</button>
|
|
</div>
|
|
|
|
<!-- ===== SEARCH SUB-PANEL ===== -->
|
|
<div class="sub-tab-panel" id="tab-search" style="display:none;">
|
|
<div class="search-bar">
|
|
<input type="text" id="search-input" class="search-input" placeholder="Auf radio-browser.info suchen … oder Stream-Adresse einfügen" onkeydown="if(event.key==='Enter') doSearch()">
|
|
<button class="btn" onclick="doSearch()">Suchen</button>
|
|
</div>
|
|
<div class="mood-chips" id="mood-chips"></div>
|
|
<div id="curated-lists" class="curated-lists-container"></div>
|
|
<div id="search-status" class="status-msg"></div>
|
|
<table class="data-table" id="search-results-table" style="display:none;">
|
|
<thead>
|
|
<tr>
|
|
<th>Name</th>
|
|
<th>Bitrate</th>
|
|
<th>Land</th>
|
|
<th>Tags</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="search-results-body"></tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- ===== SAVED SUB-PANEL ===== -->
|
|
<div class="sub-tab-panel" id="tab-saved">
|
|
{% if featured_stations %}
|
|
<div class="featured-section">
|
|
<p class="featured-label">★ Empfohlen</p>
|
|
<ul class="featured-list">
|
|
{% for s in featured_stations %}
|
|
<li>
|
|
{% if s.favicon_url %}<img src="{{ s.favicon_url }}" class="station-favicon" alt="">{% endif %}
|
|
<button class="btn btn-sm" onclick="playStation('{{ s.url|escapejs }}', '{{ s.name|escapejs }}', null)">
|
|
▶ {{ s.name }}
|
|
</button>
|
|
{% if s.description %}<span class="muted">{{ s.description }}</span>{% endif %}
|
|
</li>
|
|
{% endfor %}
|
|
</ul>
|
|
</div>
|
|
{% endif %}
|
|
{% if user.is_authenticated %}
|
|
<div id="recommendations" class="recommendations-section">
|
|
<!-- populated by JS -->
|
|
</div>
|
|
<div class="import-bar">
|
|
<label class="btn btn-sm" for="m3u-file-input" title=".m3u / .m3u8 aus der Desktop-App importieren">
|
|
⇧ M3U importieren
|
|
</label>
|
|
<input type="file" id="m3u-file-input" accept=".m3u,.m3u8" style="display:none;" onchange="importM3U(this)">
|
|
<span id="import-status" class="muted"></span>
|
|
</div>
|
|
<table class="data-table" id="saved-table">
|
|
<thead>
|
|
<tr>
|
|
<th title="Favorit">★</th>
|
|
<th>Name</th>
|
|
<th>Bitrate</th>
|
|
<th>Land</th>
|
|
<th title="Notizen">✎</th>
|
|
<th></th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="saved-tbody">
|
|
{% for station in saved_stations %}
|
|
<tr id="saved-row-{{ station.id }}" data-id="{{ station.id }}" data-url="{{ station.url }}" data-name="{{ station.name }}">
|
|
<td>
|
|
<button class="btn-icon fav-btn {% if station.is_favorite %}active{% endif %}"
|
|
onclick="toggleFav({{ station.id }})"
|
|
title="Favorit an/aus">★</button>
|
|
</td>
|
|
<td class="station-name-cell">{{ station.name }}</td>
|
|
<td>{{ station.bitrate }}</td>
|
|
<td>{{ station.country }}</td>
|
|
<td class="notes-cell" onclick="editNotes({{ station.id }}, this.textContent.trim())" title="{{ station.notes|default:'' }}" style="cursor:pointer; color:#666; max-width:120px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">{{ station.notes }}</td>
|
|
<td>
|
|
<button class="btn btn-sm"
|
|
onclick="playStation('{{ station.url }}', '{{ station.name|escapejs }}', {{ station.id }})">
|
|
▶ Abspielen
|
|
</button>
|
|
</td>
|
|
<td>
|
|
<button class="btn btn-sm btn-danger"
|
|
onclick="removeStation({{ station.id }})">
|
|
Entfernen
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr id="saved-empty-row"><td colspan="7" class="empty-msg">Noch keine gespeicherten Sender.</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% else %}
|
|
<p class="auth-prompt">
|
|
<a href="{% url 'login' %}">Anmelden</a> oder <a href="{% url 'register' %}">registrieren</a>,
|
|
um Sender zu speichern und zwischen Geräten abzugleichen.
|
|
</p>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- ===== HISTORY SUB-PANEL ===== -->
|
|
<div class="sub-tab-panel" id="tab-history" style="display:none;">
|
|
<table class="data-table" id="history-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Zeit</th>
|
|
<th>Sender</th>
|
|
<th>Titel</th>
|
|
<th>♬</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="history-tbody">
|
|
{% for entry in history %}
|
|
<tr data-id="{{ entry.id }}">
|
|
<td class="history-time">{{ entry.played_at|slice:":16"|cut:"T" }}</td>
|
|
<td>{{ entry.station_name }}</td>
|
|
<td>{{ entry.track }}</td>
|
|
<td>{% if entry.scrobbled %}<span title="An Last.fm gescrobbelt">✓</span>{% endif %}</td>
|
|
<td><button class="btn-delete-history" onclick="deleteHistoryEntry({{ entry.id }}, this)" title="Entfernen">✕</button></td>
|
|
</tr>
|
|
{% empty %}
|
|
<tr id="history-empty-row"><td colspan="5" class="empty-msg">Noch kein Verlauf.</td></tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ===== FOCUS TAB ===== -->
|
|
<section class="tab-panel" id="tab-focus" style="display:none;">
|
|
<table class="data-table" id="focus-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Zeit</th>
|
|
<th>Sender</th>
|
|
<th>Dauer</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="focus-tbody">
|
|
<tr><td colspan="3" class="empty-msg">Noch keine Fokus-Sitzungen. Starte den Timer!</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</section>
|
|
|
|
<!-- ===== PODCASTS TAB ===== -->
|
|
<section class="tab-panel" id="tab-podcasts" style="display:none;">
|
|
{% if user.is_authenticated %}
|
|
{# Filled by static/js/podcasts-web.js with the app's podcast page (static/podcasts/). #}
|
|
<p class="muted dpw-loading">Podcasts werden geladen …</p>
|
|
{% else %}
|
|
<p class="auth-prompt">
|
|
<a href="{% url 'login' %}">Anmelden</a> oder <a href="{% url 'register' %}">registrieren</a>,
|
|
um Podcasts zu abonnieren.
|
|
</p>
|
|
{% endif %}
|
|
</section>
|
|
|
|
<!-- ===== BOOKS TAB ===== -->
|
|
<section class="tab-panel" id="tab-books" style="display:none;">
|
|
{% if user.is_authenticated %}
|
|
<div id="book-upload-area">
|
|
<div class="book-drop-zone" id="book-drop-zone">
|
|
<span>.epub oder .pdf hierher ziehen oder <label for="book-file-input" style="cursor:pointer;text-decoration:underline;">auswählen</label></span>
|
|
<input type="file" id="book-file-input" accept=".epub,.pdf" multiple style="display:none;" onchange="bookFileSelected(this)">
|
|
<span id="book-upload-status" class="muted"></span>
|
|
</div>
|
|
<button type="button" class="btn" style="margin:0.5rem 0;" onclick="toggleCloudImport()">☁ Aus Cloud importieren</button>
|
|
<div id="cloud-browser" class="cloud-browser" style="display:none;">
|
|
<div class="cloud-browser-head">
|
|
<select id="cloud-source-select" onchange="_onCloudSourceChange(this.value)"></select>
|
|
<button type="button" class="btn" onclick="closeCloudImport()">Schließen</button>
|
|
</div>
|
|
<div id="cloud-path" class="cloud-path"></div>
|
|
<ul id="cloud-entries" class="cloud-entries"></ul>
|
|
<span id="cloud-status" class="muted"></span>
|
|
</div>
|
|
</div>
|
|
<label class="book-list-filter">
|
|
<input type="checkbox" id="book-show-read-toggle" onchange="_onBookShowReadToggle(this.checked)">
|
|
Gelesene Bücher anzeigen
|
|
</label>
|
|
<div id="book-list" class="book-list"></div>
|
|
{% else %}
|
|
<p class="auth-prompt">
|
|
<a href="{% url 'login' %}">Anmelden</a> oder <a href="{% url 'register' %}">registrieren</a>,
|
|
um den verschlüsselten E-Book-Reader zu nutzen.
|
|
</p>
|
|
{% endif %}
|
|
</section>
|
|
|
|
<!-- ===== HOME TAB ===== -->
|
|
{% comment %}Filled by static/js/home.js: the book in reading, the next episode, stations, Ambient.{% endcomment %}
|
|
<section class="tab-panel" id="tab-home" style="display:none;"></section>
|
|
|
|
<!-- ===== FEEDS TAB ===== -->
|
|
{% comment %}Audiobooks (podcasts marked as one, docs/audiobooks.md): filled by static/js/audiobooks.js.{% endcomment %}
|
|
{% if user.is_authenticated %}<section class="tab-panel" id="tab-audiobooks" style="display:none;"></section>{% endif %}
|
|
{% comment %}Abos & Quellen, every subscription in one place (docs/sources.md): filled by static/js/sources.js.{% endcomment %}
|
|
{% if user.is_authenticated %}<section class="tab-panel" id="tab-sources" style="display:none;"></section>{% endif %}
|
|
{% comment %}Projekte, e.g. Forschung (docs/projects.md): filled by static/js/projects.js.{% endcomment %}
|
|
{% if user.is_authenticated %}<section class="tab-panel" id="tab-projekte" style="display:none;"></section>{% endif %}
|
|
{% comment %}The search across diora (docs/search.md): filled by static/js/search.js.{% endcomment %}
|
|
{% if user.is_authenticated %}<section class="tab-panel" id="tab-suche" style="display:none;"></section>{% endif %}
|
|
{% comment %}The feed reader, news and blogs: filled by static/js/feeds.js.{% endcomment %}
|
|
<section class="tab-panel" id="tab-feeds" style="display:none;"></section>
|
|
|
|
<!-- ===== AMBIENT TAB ===== -->
|
|
{% comment %}"Drift", a generative ambient synthesizer (static/ambient/), in a frame of its own: its
|
|
script and styles stay apart from the page's. Loaded the first time the tab opens.{% endcomment %}
|
|
<section class="tab-panel" id="tab-ambient" style="display:none;">
|
|
<iframe id="ambient-frame" class="ambient-frame" title="Ambient" allow="autoplay; clipboard-write"
|
|
data-src="/static/ambient/index.html?embed=1&bg=%23000000"></iframe>
|
|
</section>
|
|
|
|
<!-- ===== READER OVERLAY ===== -->
|
|
<div id="reader-overlay" class="reader-overlay" style="display:none;">
|
|
<div class="reader-header">
|
|
<span id="reader-title" class="reader-title"></span>
|
|
<div class="reader-header-actions">
|
|
<span class="reader-progress-wrap">
|
|
<input type="text" inputmode="decimal" id="reader-progress-input" class="volume-num" value="0" style="display:none;">
|
|
<span id="reader-progress-suffix" class="muted"></span>
|
|
</span>
|
|
<button class="btn-icon" id="reader-search-btn" onclick="toggleReaderSearch()" title="Suchen">⌕</button>
|
|
<button class="btn-icon" id="reader-settings-btn" onclick="toggleSettingsPanel()" title="Schrift & Layout">⚙</button>
|
|
<select id="reader-tts-lang" class="tts-lang-select" title="Vorlese-Sprache" onchange="setTtsLang(this.value)">
|
|
<option value="de">DE</option>
|
|
<option value="en">EN</option>
|
|
</select>
|
|
<button class="btn-icon" id="reader-tts-btn" onclick="toggleReadAloud()" title="Vorlesen">▶</button>
|
|
<button class="btn-icon" id="reader-bookmark-btn" onclick="addBookmark()" title="Lesezeichen setzen">★</button>
|
|
<button class="btn-icon" id="reader-bm-list-btn" onclick="openBookmarksSidebar()" title="Lesezeichen">▤</button>
|
|
<button class="btn-icon" id="reader-toc-btn" onclick="openTocSidebar()" title="Inhaltsverzeichnis">≡</button>
|
|
<button class="btn-icon" id="reader-margin-btn" onclick="toggleAnnotationsMargin()" title="Notizen & Markierungen">✎</button>
|
|
<button class="btn-icon marker-mode-btn reader-marker-btn-mobile" id="reader-marker-btn-mobile" onclick="toggleMarkerMode()" title="Text markieren">✒</button>
|
|
<button class="btn-icon" id="reader-reset-pos-btn" onclick="saveReaderProgress(true)" title="Diese Position als Lesefortschritt setzen (überschreibt gespeicherten Fortschritt)">⚑</button>
|
|
<button class="btn-icon" onclick="closeReader()" title="Schließen (Esc)">✕</button>
|
|
</div>
|
|
</div>
|
|
<div id="reader-body-row" class="reader-body-row">
|
|
<aside id="reader-margin" class="reader-margin">
|
|
<div class="reader-margin-header">
|
|
<span class="reader-margin-title">Notizen</span>
|
|
<span class="reader-margin-header-actions">
|
|
<button class="btn-icon marker-mode-btn" id="reader-marker-btn" onclick="toggleMarkerMode()" title="Text markieren">✒</button>
|
|
<button class="btn-icon" id="reader-margin-list-btn" onclick="openAnnotationsSidebar()" title="Alle Markierungen & Notizen auflisten und hinspringen">▦</button>
|
|
<button class="btn-icon" id="reader-margin-export-btn" onclick="exportAnnotations()" title="Als Textdatei exportieren">⭳</button>
|
|
</span>
|
|
</div>
|
|
<div id="reader-margin-markers" class="reader-margin-markers"></div>
|
|
</aside>
|
|
<div id="reader-content" class="reader-content"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ===== SIDEBAR ===== -->
|
|
<div id="sidebar-overlay" class="sidebar-overlay" onclick="closeSidebar()" style="display:none;"></div>
|
|
<aside id="sidebar" class="sidebar">
|
|
<div class="sidebar-header">
|
|
<span id="sidebar-title" class="sidebar-title"></span>
|
|
<button class="btn-icon sidebar-close" onclick="closeSidebar()" title="Schließen">✕</button>
|
|
</div>
|
|
<div id="sidebar-body" class="sidebar-body"></div>
|
|
</aside>
|
|
|
|
<!-- ===== MODAL DIALOG (replaces native alert/confirm/prompt) ===== -->
|
|
<div id="modal-overlay" class="modal-overlay" style="display:none;"></div>
|
|
<div id="modal-dialog" class="modal-dialog" style="display:none;" role="alertdialog" aria-modal="true">
|
|
<p id="modal-message" class="modal-message"></p>
|
|
<input type="text" id="modal-input" class="search-input modal-input" style="display:none;">
|
|
<div class="modal-actions">
|
|
<button type="button" id="modal-cancel-btn" class="btn" style="display:none;">Abbrechen</button>
|
|
<button type="button" id="modal-ok-btn" class="btn btn-primary">OK</button>
|
|
</div>
|
|
</div>
|
|
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script>
|
|
// Pass Django context into JS
|
|
const INITIAL_SAVED = {{ saved_stations_json|safe }};
|
|
const INITIAL_FEATURED = {{ featured_stations_json|safe }};
|
|
const IS_AUTHENTICATED = {{ user.is_authenticated|yesno:"true,false" }};
|
|
window.USER_ID = {{ user.id|default:"null" }};
|
|
let USER_FOCUS_STATION = {{ focus_station_json|safe }};
|
|
</script>
|
|
{# app.js, split by area (docs/refactoring.md): classic scripts sharing one global scope, in this order. #}
|
|
<script src="/static/js/nav.js"></script>
|
|
<script src="/static/js/folders.js"></script>
|
|
<script src="/static/js/covers.js"></script>
|
|
<script src="/static/js/gencover.js"></script>
|
|
<script src="/static/js/app/01-core.js"></script>
|
|
<script src="/static/js/app/10-radio.js"></script>
|
|
<script src="/static/js/app/14-focus.js"></script>
|
|
<script src="/static/js/app/16-stations.js"></script>
|
|
<script src="/static/js/app/20-tabs.js"></script>
|
|
<script src="/static/js/app/24-ambient.js"></script>
|
|
<script src="/static/js/app/26-sidebar.js"></script>
|
|
<script src="/static/js/app/28-serviceworker.js"></script>
|
|
<script src="/static/js/app/29-m3u.js"></script>
|
|
<script src="/static/js/app/30-contrast.js"></script>
|
|
<script src="/static/js/app/32-crypto.js"></script>
|
|
<script src="/static/js/app/33-unlock.js"></script>
|
|
<script src="/static/js/app/34-wallpaper.js"></script>
|
|
<script src="/static/js/app/40-epub.js"></script>
|
|
<script src="/static/js/app/44-books.js"></script>
|
|
<script src="/static/js/app/46-readpos.js"></script>
|
|
<script src="/static/js/app/48-book-cache.js"></script>
|
|
<script src="/static/js/app/50-reader-state.js"></script>
|
|
<script src="/static/js/app/52-book-list.js"></script>
|
|
<script src="/static/js/app/53-library.js"></script>
|
|
<script src="/static/js/app/54-issues.js"></script>
|
|
<script src="/static/js/app/55-library-tools.js"></script>
|
|
<script src="/static/js/app/56-cloud-import.js"></script>
|
|
<script src="/static/js/app/57-cloud-inbox.js"></script>
|
|
<script src="/static/js/app/58-pdf.js"></script>
|
|
<script src="/static/js/app/60-tts.js"></script>
|
|
<script src="/static/js/app/62-reader.js"></script>
|
|
<script src="/static/js/app/63-pages.js"></script>
|
|
<script src="/static/js/app/64-reader-settings.js"></script>
|
|
<script src="/static/js/app/65-article.js"></script>
|
|
<script src="/static/js/app/66-pdf-paginated.js"></script>
|
|
<script src="/static/js/app/68-bookmarks.js"></script>
|
|
<script src="/static/js/app/70-search.js"></script>
|
|
<script src="/static/js/app/72-highlights.js"></script>
|
|
<script src="/static/js/app/74-margin.js"></script>
|
|
<script src="/static/js/app/76-export.js"></script>
|
|
<script src="/static/js/app/78-footnotes.js"></script>
|
|
<script src="/static/js/app/80-radio-sidebar.js"></script>
|
|
<script src="/static/js/app/99-init.js"></script>
|
|
{% if user.is_authenticated %}{# The KI features (docs/ai.md): the user's own key, calls from the device. #}
|
|
<script type="module" src="/static/js/ai.js"></script>
|
|
<script type="module" src="/static/js/podcast-article.js"></script>{% endif %}
|
|
<script src="/static/js/podcasts-web.js"></script>
|
|
<script src="/static/js/home.js"></script>
|
|
<script src="/static/js/feeds.js"></script>
|
|
<script src="/static/js/audiobooks.js"></script>
|
|
<script src="/static/js/sources.js"></script>
|
|
<script src="/static/js/search.js"></script>
|
|
<script src="/static/js/projects.js"></script>
|
|
{% endblock %}
|