]> littlesong.place Git - littlesongplace.git/commitdiff
HTML cleanup
authorChris Fulljames <christianfulljames@gmail.com>
Sat, 22 Feb 2025 19:32:38 +0000 (14:32 -0500)
committerChris Fulljames <christianfulljames@gmail.com>
Sat, 22 Feb 2025 19:32:38 +0000 (14:32 -0500)
main.py
static/nav.js [new file with mode: 0644]
templates/base.html
templates/index.html
templates/playlist.html
templates/profile.html
templates/song-list.html
templates/song.html
templates/songs-by-tag.html

diff --git a/main.py b/main.py
index 1d9dd7746f394ce91556ff20bd1f8f4da505e264..5ff19ffb518021d5188605492c7ad1e84389f5d1 100644 (file)
--- a/main.py
+++ b/main.py
@@ -60,8 +60,7 @@ if "DATA_DIR" in os.environ:
 def index():
     users = [row["username"] for row in query_db("select username from users order by username asc")]
     songs = Song.get_latest(50)
-    song_list = render_template("song-list.html", songs=songs)
-    return render_template("index.html", users=users, song_list=song_list)
+    return render_template("index.html", users=users, songs=songs)
 
 @app.get("/signup")
 def signup_get():
@@ -180,7 +179,7 @@ def users_profile(profile_username):
             playlists=plist_data,
             songs=songs,
             user_has_pfp=(get_user_images_path(profile_userid)/"pfp.jpg").exists(),
-            song_list=render_template("song-list.html", songs=songs, is_profile_song_list=True))
+            is_profile_song_list=True)
 
 @app.post("/edit-profile")
 def edit_profile():
@@ -346,8 +345,8 @@ def update_song():
     yt_url = request.form["song-url"] if "song-url" in request.form else None
     title = request.form["title"]
     description = request.form["description"]
-    tags = [t.strip() for t in request.form["tags"].split(",")]
-    collaborators = [c.strip() for c in request.form["collabs"].split(",")]
+    tags = [t.strip() for t in request.form["tags"].split(",") if t]
+    collaborators = [c.strip() for c in request.form["collabs"].split(",") if c]
 
     # Make sure song exists and the logged-in user owns it
     song_data = query_db("select * from songs where songid = ?", [songid], one=True)
@@ -394,8 +393,8 @@ def create_song():
     yt_url = request.form["song-url"] if "song-url" in request.form else None
     title = request.form["title"]
     description = request.form["description"]
-    tags = [t.strip() for t in request.form["tags"].split(",")]
-    collaborators = [c.strip() for c in request.form["collabs"].split(",")]
+    tags = [t.strip() for t in request.form["tags"].split(",") if t]
+    collaborators = [c.strip() for c in request.form["collabs"].split(",") if c]
 
     with tempfile.NamedTemporaryFile(delete=False) as tmp_file:
         passed = convert_song(tmp_file, file, yt_url)
@@ -518,7 +517,7 @@ def song(userid, songid):
             user_data = query_db("select * from users where userid = ?", [userid], one=True)
             return render_template(
                     "song.html",
-                    song_list=render_template("song-list.html", songs=[song]),
+                    songs=[song],
                     song=song,
                     bgcolor=user_data["bgcolor"],
                     fgcolor=user_data["fgcolor"],
@@ -542,11 +541,7 @@ def songs():
     else:
         songs = []
 
-    return render_template(
-            "songs-by-tag.html",
-            user=user,
-            tag=tag,
-            song_list=render_template("song-list.html", songs=songs))
+    return render_template("songs-by-tag.html", user=user, tag=tag, songs=songs)
 
 @app.route("/comment", methods=["GET", "POST"])
 def comment():
@@ -874,8 +869,7 @@ def playlists(playlistid):
             bgcolor=plist_data["bgcolor"],
             fgcolor=plist_data["fgcolor"],
             accolor=plist_data["accolor"],
-            songs=songs,
-            song_list=render_template("song-list.html", songs=songs))
+            songs=songs)
 
 def flash_and_log(msg, category=None):
     flash(msg, category)
@@ -1068,8 +1062,8 @@ class Song:
         tags, collabs = cls._get_info_for_songs(songs_data)
         songs = []
         for sd in songs_data:
-            song_tags = [t["tag"] for t in tags[sd["songid"]]]
-            song_collabs = [c["name"] for c in collabs[sd["songid"]]]
+            song_tags = [t["tag"] for t in tags[sd["songid"]] if t["tag"]]
+            song_collabs = [c["name"] for c in collabs[sd["songid"]] if c["name"]]
             created = datetime.fromisoformat(sd["created"]).astimezone().strftime("%Y-%m-%d")
             user_has_pfp = (get_user_images_path(sd["userid"])/"pfp.jpg").exists()
             songs.append(cls(sd["songid"], sd["userid"], sd["username"], sd["title"], sanitize_user_text(sd["description"]), created, song_tags, song_collabs, user_has_pfp))
diff --git a/static/nav.js b/static/nav.js
new file mode 100644 (file)
index 0000000..4787f5d
--- /dev/null
@@ -0,0 +1,180 @@
+document.addEventListener("DOMContentLoaded", (e) => {
+
+    // Handle link clicks with AJAX
+    document.querySelectorAll("a").forEach((anchor) => {
+        anchor.removeEventListener("click", onLinkClick);
+        anchor.addEventListener("click", onLinkClick);
+    });
+
+    // Handle form submissions with AJAX
+    document.querySelectorAll("form").forEach((form) => {
+        form.removeEventListener("submit", onFormSubmit);
+        form.addEventListener("submit", onFormSubmit);
+    });
+
+    // Update colors
+    var mainDiv = document.getElementById("main");
+    var rootStyle = document.documentElement.style;
+    rootStyle.setProperty("--yellow", mainDiv.dataset.bgcolor);
+    rootStyle.setProperty("--black", mainDiv.dataset.fgcolor);
+    rootStyle.setProperty("--purple", mainDiv.dataset.accolor);
+
+    updateImageColors();
+
+    // Update navbar based on logged-in status
+    var username = mainDiv.dataset.username;
+    var loggedIn = username ? true : false;
+    document.querySelectorAll(".nav-logged-in").forEach((e) => {e.hidden = !loggedIn;});
+    document.querySelectorAll(".nav-logged-out").forEach((e) => {e.hidden = loggedIn;});
+    if (loggedIn) {
+        document.getElementById("logged-in-status").innerText = `Signed in as ${username}`;
+    }
+
+    // Update activity indicator status
+    checkForNewActivity();
+});
+
+function onLinkClick(event) {
+    var targetUrl = new URL(event.currentTarget.href);
+    if (urlIsOnSameSite(targetUrl)) {
+        event.preventDefault();
+        event.stopPropagation();
+        fetch(targetUrl, {redirect: "follow"}).then(handleAjaxResponse);
+    }
+}
+
+function onFormSubmit(event) {
+    var targetUrl = new URL(event.target.action);
+    if (urlIsOnSameSite(targetUrl)) {
+        event.preventDefault();
+        event.stopPropagation();
+        var formData = new FormData(event.target);
+        fetch(targetUrl, {redirect: "follow", body: formData, method: event.target.method})
+            .then(handleAjaxResponse);
+    }
+}
+
+function urlIsOnSameSite(targetUrl) {
+    var currentUrl = new URL(window.location.href);
+    return targetUrl.origin === currentUrl.origin;
+}
+
+async function handleAjaxResponse(response) {
+    // Update URL in browser window, minus request-type field
+    var url = new URL(response.url);
+    url.searchParams.delete("request-type");
+
+    // Get page content from XML response
+    var text = await response.text();
+    window.history.pushState(text, "", url);
+
+    updatePageState(text);
+}
+
+// Populate page state from history stack when user navigates back
+window.addEventListener("popstate", (event) => updatePageState(event.state));
+
+function updatePageState(data) {
+    // Replace the contents of the current page with those from data
+
+    if (!data) {
+        fetch(window.location.href, {redirect: "follow"}).then(handleAjaxResponse);
+        return;
+    }
+    var parser = new DOMParser();
+    data = parser.parseFromString(data, "text/html");
+
+    // Update main body content
+    var newMainDiv = data.getElementById("main");
+    var oldMainDiv = document.getElementById("main");
+    document.body.replaceChild(newMainDiv, oldMainDiv);
+
+    // Update flashed messages
+    var newFlashes = data.getElementById("flashes-container");
+    var oldFlashes = document.getElementById("flashes-container");
+    oldFlashes.parentElement.replaceChild(newFlashes, oldFlashes);
+
+    // Update page title
+    document.title = data.title;
+
+    // Load inline scripts (DOMParser disables these by default)
+    var scripts = document.getElementById("main").getElementsByTagName("script");
+    for (const script of scripts) {
+        var newScript = document.createElement("script");
+        newScript.type = script.type;
+        newScript.text = script.text;
+        script.parentElement.replaceChild(newScript, script);
+    }
+
+    // Delete old color picker (will be recreated on DOMContentLoaded)
+    document.getElementById("clr-picker").remove();
+
+    // Trigger event to signal new page has loaded
+    var event = new Event("DOMContentLoaded");
+    document.dispatchEvent(event);
+}
+
+async function checkForNewActivity() {
+    // Query the server to see if the user has new activity
+
+    // Only check for activity if user is logged in
+    var mainDiv = document.getElementById("main");
+    var username = mainDiv.dataset.username;
+    if (!username) {
+        return;
+    }
+
+    // Logged in - make the activity status request
+    const indicator = document.getElementById("activity-indicator")
+    const response = await fetch("/new-activity");
+    if (!response.ok) {
+        console.log(`Failed to get activity: ${response.status}`);
+    }
+    const json = await response.json();
+    indicator.hidden = !json.new_activity;
+}
+
+// Check for new activity every 10s
+setInterval(checkForNewActivity, 10000);
+
+function customImage(element) {
+    // Customize an image by performing a palette swap on the .gif
+    // file.  The source element must contain a data-img-b64 attribute
+    // containing the base64 representation of a .gif file.  The byte
+    // indexes match .gifs from Aseprite, and may not work for all
+    // .gif files.
+
+    var style = window.getComputedStyle(document.body);
+    var bgcolor = style.getPropertyValue("--yellow");
+    var accolor = style.getPropertyValue("--purple");
+
+    // Convert base64 string to Uint8Array so we can modify it
+    var data = atob(element.dataset.imgB64);
+    var bytes = Uint8Array.from(data, c => c.charCodeAt(0));
+
+    // Replace background color palette bytes in gif file
+    bytes[16] = parseInt(bgcolor.substring(1, 3), 16);
+    bytes[17] = parseInt(bgcolor.substring(3, 5), 16);
+    bytes[18] = parseInt(bgcolor.substring(5, 7), 16);
+
+    // Replace foreground color palette bytes in gif file
+    bytes[19] = parseInt(accolor.substring(1, 3), 16);
+    bytes[20] = parseInt(accolor.substring(3, 5), 16);
+    bytes[21] = parseInt(accolor.substring(5, 7), 16);
+
+    // Convert Uint8Array back to base64 so we can use it in a src string
+    data = btoa(String.fromCharCode(...bytes));
+
+    // Embed base64 in a data string that can be used as an img src.
+    return `data:image/gif;base64, ${data}`;
+}
+
+function updateImageColors() {
+    // Perform a palette swap on all gifs based on current page colors
+    document.querySelectorAll(".img-data").forEach(e => {
+        document.querySelectorAll(`.${e.id}`).forEach(t => {
+            t.src = customImage(e);
+        });
+    });
+}
+
index 2f7c97cedbbd60b33d869d6ba0064bb1d82d962b..93fb69aaa7e8500488008b7cdbed1b5829f7495e 100644 (file)
@@ -2,9 +2,10 @@
 <html>
     <head>
         <title>{% block title %}{% endblock %}</title>
-        <link rel="stylesheet" href="/static/styles.css"/>
-        <link rel="icon" type="image/x-icon" href="/static/lsp_notes.png"/>
-        <script src="/static/player.js"></script>
+        <link rel="stylesheet" href="/static/styles.css?v=1"/>
+        <link rel="icon" type="image/x-icon" href="/static/lsp_notes.png?v=1"/>
+        <script src="/static/player.js?v=1"></script>
+        <script src="/static/nav.js?v=1"></script>
         <meta name="viewport" content="width=device-width, initial-scale=1">
 
         <!-- Include coloris library for color picker -->
         <script src="/static/coloris.min.js"></script>
 
         <!-- Page-specific head fields -->
-        {% block head %}
-        {% endblock %}
-        <script>
-            document.addEventListener("DOMContentLoaded", (e) => {
-
-                // Handle link clicks with AJAX
-                document.querySelectorAll("a").forEach((anchor) => {
-                    anchor.removeEventListener("click", onLinkClick);
-                    anchor.addEventListener("click", onLinkClick);
-                });
-
-                // Handle form submissions with AJAX
-                document.querySelectorAll("form").forEach((form) => {
-                    form.removeEventListener("submit", onFormSubmit);
-                    form.addEventListener("submit", onFormSubmit);
-                });
-            });
-
-            function onLinkClick(event) {
-                var targetUrl = new URL(event.currentTarget.href);
-                if (urlIsOnSameSite(targetUrl)) {
-                    event.preventDefault();
-                    event.stopPropagation();
-                    fetch(targetUrl, {redirect: "follow"}).then(handleAjaxResponse);
-                }
-            }
-
-            function onFormSubmit(event) {
-                var targetUrl = new URL(event.target.action);
-                if (urlIsOnSameSite(targetUrl)) {
-                    event.preventDefault();
-                    event.stopPropagation();
-                    var formData = new FormData(event.target);
-                    fetch(targetUrl, {redirect: "follow", body: formData, method: event.target.method})
-                        .then(handleAjaxResponse);
-                }
-            }
-
-            function urlIsOnSameSite(targetUrl) {
-                var currentUrl = new URL(window.location.href);
-                return targetUrl.origin === currentUrl.origin;
-            }
-
-            async function handleAjaxResponse(response) {
-                // Update URL in browser window, minus request-type field
-                var url = new URL(response.url);
-                url.searchParams.delete("request-type");
-
-                // Get page content from XML response
-                var text = await response.text();
-                window.history.pushState(text, "", url);
-
-                updatePageState(text);
-            }
-
-            function updatePageState(data) {
-                if (!data) {
-                    fetch(window.location.href, {redirect: "follow"}).then(handleAjaxResponse);
-                    return;
-                }
-                var parser = new DOMParser();
-                data = parser.parseFromString(data, "text/html");
-                var newMainDiv = data.getElementById("main");
-                var oldMainDiv = document.getElementById("main");
-                document.body.replaceChild(newMainDiv, oldMainDiv);
-                document.title = data.title;
-
-                // Load inline scripts (DOMParser disables these by default)
-                var scripts = document.getElementById("main").getElementsByTagName("script");
-                for (const script of scripts) {
-                    var newScript = document.createElement("script");
-                    newScript.type = script.type;
-                    newScript.text = script.text;
-                    script.parentElement.replaceChild(newScript, script);
-                }
-
-                // Delete old color picker (will be recreated on DOMContentLoaded)
-                document.getElementById("clr-picker").remove();
-
-                // Trigger event to signal new page has loaded
-                var event = new Event("DOMContentLoaded");
-                document.dispatchEvent(event);
-
-                // Refresh navbar in case logged-in status changed
-                updateNavbar(newMainDiv.dataset.username);
-            }
-
-            window.addEventListener("popstate", (event) => updatePageState(event.state));
-
-            function updateNavbar(username) {
-                var loggedIn = username ? true : false;
-                document.querySelectorAll(".nav-logged-in").forEach((e) => {e.hidden = !loggedIn;});
-                document.querySelectorAll(".nav-logged-out").forEach((e) => {e.hidden = loggedIn;});
-                if (loggedIn) {
-                    document.getElementById("logged-in-status").innerText = `Signed in as ${username}`;
-                }
-            }
-
-            document.addEventListener("DOMContentLoaded", (event) => {
-                var mainDiv = document.getElementById("main");
-                var rootStyle = document.documentElement.style;
-                rootStyle.setProperty("--yellow", mainDiv.dataset.bgcolor);
-                rootStyle.setProperty("--black", mainDiv.dataset.fgcolor);
-                rootStyle.setProperty("--purple", mainDiv.dataset.accolor);
-
-                updateImageColors();
-            });
-        </script>
+        {%- block head %}{% endblock %}
     </head>
     <body>
 
         <!-- Embedded image data -->
         {{ gif_data|safe }}
 
-        <script>
-
-            function customImage(element) {
-                // Customize an image by performing a palette swap on the .gif
-                // file.  The source element must contain a data-img-b64 attribute
-                // containing the base64 representation of a .gif file.  The byte
-                // indexes match .gifs from Aseprite, and may not work for all
-                // .gif files.
-
-                var style = window.getComputedStyle(document.body);
-                var bgcolor = style.getPropertyValue("--yellow");
-                var accolor = style.getPropertyValue("--purple");
-
-                // Convert base64 string to Uint8Array so we can modify it
-                var data = atob(element.dataset.imgB64);
-                var bytes = Uint8Array.from(data, c => c.charCodeAt(0));
-
-                // Replace background color palette bytes in gif file
-                bytes[16] = parseInt(bgcolor.substring(1, 3), 16);
-                bytes[17] = parseInt(bgcolor.substring(3, 5), 16);
-                bytes[18] = parseInt(bgcolor.substring(5, 7), 16);
-
-                // Replace foreground color palette bytes in gif file
-                bytes[19] = parseInt(accolor.substring(1, 3), 16);
-                bytes[20] = parseInt(accolor.substring(3, 5), 16);
-                bytes[21] = parseInt(accolor.substring(5, 7), 16);
-
-                // Convert Uint8Array back to base64 so we can use it in a src string
-                data = btoa(String.fromCharCode(...bytes));
-
-                // Embed base64 in a data string that can be used as an img src.
-                return `data:image/gif;base64, ${data}`;
-            }
-
-            function updateImageColors() {
-                // Perform a palette swap on all gifs based on current page colors
-                document.querySelectorAll(".img-data").forEach(e => {
-                    document.querySelectorAll(`.${e.id}`).forEach(t => {
-                        t.src = customImage(e);
-                    });
-                });
-            }
-        </script>
-
         <div class="page-header">
             <div style="text-align: center;">
                 <img class="title-image littlesongplace02">
             </div>
         </div>
 
-        {% if "username" in session %}
-        <!-- Periodically update activity status indicator -->
-        <script>
-            updateNavbar("{{ session["username"] }}");
-
-            async function checkForNewActivity() {
-                const indicator = document.getElementById("activity-indicator")
-                const response = await fetch("/new-activity");
-                if (!response.ok) {
-                    console.log(`Failed to get activity: ${response.status}`);
-                }
-                const json = await response.json();
-                indicator.hidden = !json.new_activity;
-            }
-
-            // Check for new activity every 10s
-            setInterval(checkForNewActivity, 10000);
-            checkForNewActivity();  // Check immediately
-        </script>
-        {% endif %}
-
-        <!-- Stite Status Messages -->
-        {% with messages = get_flashed_messages(with_categories=True) %}
-        {% if messages %}
-        <div class="flashes">
-            <ul>
-                {% for category, message in messages %}
-                <li class="flash-msg {{ category }}">{{ message }}</li>
-                {% endfor %}
-            </ul>
+        <!-- Flashed Status Messages -->
+        <div id="flashes-container">
+            {% with messages = get_flashed_messages(with_categories=True) -%}
+            {% if messages  -%}
+            <div class="flashes">
+                <ul>
+                    {% for category, message in messages %}
+                    <li class="flash-msg {{ category }}">{{ message }}</li>
+                    {% endfor %}
+                </ul>
+            </div>
+            {%- endif %}
+            {%- endwith %}
         </div>
-        {% endif %}
-        {% endwith %}
 
         <!-- Page-Specific Content -->
-        <div class="main" id="main" data-bgcolor="{{ bgcolor }}" data-fgcolor="{{ fgcolor }}" data-accolor="{{ accolor }}" data-username="{{ session['username'] }}">
-        {% block body %}
-        {% endblock %}
-        </div>
+        <div class="main" id="main" data-bgcolor="{{ bgcolor }}" data-fgcolor="{{ fgcolor }}" data-accolor="{{ accolor }}" data-username="{{ session['username'] }}">{% block body %}{% endblock %}</div>
 
         <!-- Padding to prevent player from obscuring content -->
         <div id="scroll-padding"></div>
index f04887acec58b51746ab149985e7b6ae7c9e5bef..461c44aa4fa1a989b61d5f4728979405c1bae996 100644 (file)
@@ -26,6 +26,6 @@ better, I'm open to any thoughts or feedback you have!  Just send me
 
 <h2>Recently Uploaded Songs</h2>
 <p>Listen to all the newest tunes!</p>
-{{ song_list|safe }}
+{% include "song-list.html" %}
 
 {% endblock %}
index 8bc18687d819d49e85a11f3c008003cf56d77ea2..e0aed6396acb66df2644f3d9160d487bb0ad3f0a 100644 (file)
@@ -42,7 +42,7 @@ function hidePlaylistEditor() {
 
 {%- endif %}
 
-{{ song_list|safe }}
+{% include "song-list.html" %}
 
 {% if session["userid"] == userid -%}
 <!-- Drag-and-drop playlist editor -->
index 97874f1f328af4e79337aa34041df205b7df08bc..9a9c8eff4367b635361cd99618c0005ee7b19912 100644 (file)
 {% endif %}
 
 <!-- Song List -->
-{{ song_list|safe }}
+{% include "song-list.html" %}
 
 {% endblock %}
 
index 5efc46c30fbc06add5c1a7b2b86dbd0106a8a6a3..1f53140db6bf8a2834900eae305f006f3c6d81e3 100644 (file)
@@ -3,9 +3,9 @@
     <div class="song" data-song="{{ song.json() }}">
         <div class="song-main">
             <!-- Profile Picture -->
-            {% if song.user_has_pfp %}
+            {% if song.user_has_pfp -%}
             <img class="small-pfp" src="/pfp/{{ song.userid }}" onerror="this.style.display = 'none'" />
-            {% endif %}
+            {%- endif %}
 
             <div class="song-info">
 
                     -
                 </div>
 
-                <!-- Song Artist -->
+                <!-- Song Artist(s) -->
                 <div class="song-artist">
                     <a href="/users/{{ song.username }}" class="profile-link">{{ song.username }}</a>
 
-                <!-- Song Collaborators -->
+                    <!-- Song Collaborators -->
                     {% for collab in song.collaborators %}
-                        {% if collab.startswith("@") %}
-                        <a href="/users/{{ collab[1:] }}" class="profile-link">{{ collab[1:] }}</a>
-                        {% else %}
-                        <span class="collab-name">{{ collab }}</span>
-                        {% endif %}
+                    {% if collab.startswith("@") -%}
+                    <a href="/users/{{ collab[1:] }}" class="profile-link">{{ collab[1:] }}</a>
+                    {%- else -%}
+                    <span class="collab-name">{{ collab }}</span>
+                    {%- endif %}
                     {% endfor %}
                 </div>
             </div>
             <div class="song-buttons">
 
+                {% if session["userid"] == song.userid and is_profile_song_list -%}
                 <!-- Owner-Specific Buttons (Edit/Delete) -->
-                {% if session["userid"] == song.userid and is_profile_song_list %}
                 <a href="/edit-song?songid={{ song.songid }}" class="song-list-button">
                     <img class="lsp_btn_edit02" alt="Edit">
                 </a>
                 <a href="/delete-song/{{ song.songid }}" onclick="return confirm(&#34;Are you sure you want to delete this song?&#34;)" class="song-list-button">
                     <img class="lsp_btn_delete02" alt="Delete">
                 </a>
-                {% endif %}
+                {%- endif %}
 
                 <!-- Details Button -->
-                <button onclick="return showDetails(event)" class="song-list-button">
+                <button onclick="return showDetails(event)" class="song-list-button details-toggle">
                     <img class="lsp_btn_show02" alt="Show Details">
                 </button>
 
@@ -56,7 +56,7 @@
         </div>
 
         <div class="song-details" {% if request.endpoint != 'song' %}hidden{% endif %}>
-            {% if current_user_playlists %}
+            {% if current_user_playlists -%}
             <!-- Add to Playlist Buttons -->
             <div class="song-playlist-controls">
                 <form action="/append-to-playlist" method="post">
                     </select>
                 </form>
             </div>
-            {% endif %}
+            {%- endif %}
 
+            {% if song.description -%}
             <!-- Song Description -->
-            {% if song.description %}
             <div class="song-description">{{ (song.description.replace("\n", "<br>"))|safe  }}</div>
-            {% endif %}
+            {%- endif %}
 
+            {% if song.tags -%}
             <!-- Song Tags -->
             <div class="song-tags">
                 Tags:
@@ -83,6 +84,7 @@
                 <a href="/songs?user={{ song.username }}&tag={{ tag }}">{{ tag }}</a>
                 {% endfor %}
             </div>
+            {%- endif %}
 
             <div class="song-date">
                 Uploaded {{ song.created }}
 function showDetails(event) {
     var songElement = event.target.closest(".song");
     var songDetails = songElement.querySelector(".song-details");
+    var detailsToggle = songElement.querySelector(".details-toggle img");
     if (songDetails.hidden) {
         // Show details
         songDetails.hidden = false;
-        event.target.alt = "Hide Details";
-        event.target.className = "lsp_btn_hide02";
-        event.target.src = customImage(document.getElementById("lsp_btn_hide02"));
+        detailsToggle.alt = "Hide Details";
+        detailsToggle.className = "lsp_btn_hide02";
+        detailsToggle.src = customImage(document.getElementById("lsp_btn_hide02"));
     }
     else {
         // Hide details
         songDetails.hidden = true;
-        event.target.alt = "Show Details";
-        event.target.className = "lsp_btn_show02";
-        event.target.src = customImage(document.getElementById("lsp_btn_show02"));
+        detailsToggle.alt = "Show Details";
+        detailsToggle.className = "lsp_btn_show02";
+        detailsToggle.src = customImage(document.getElementById("lsp_btn_show02"));
     }
     return false;
 }
index d993b91d7350852e464a9e11cd3247cefa662917..032421addf405c529d03f6449d7fa7058c3bdab6 100644 (file)
@@ -9,6 +9,6 @@
 
 {% block body %}
 
-{{ song_list|safe }}
+{% include "song-list.html" %}
 
 {% endblock %}
index a8e3dbac7d0a7666f269be2dbd559632f710c3c7..55cb11c9994dda066abacddc3c6a2c87c4d1a19e 100644 (file)
@@ -18,6 +18,6 @@
     </div>
 {% endif %}
 
-{{ song_list|safe }}
+{% include "song-list.html" %}
 
 {% endblock %}