From: Chris Fulljames Date: Sat, 22 Feb 2025 19:32:38 +0000 (-0500) Subject: HTML cleanup X-Git-Url: https://littlesong.place/gitweb/?a=commitdiff_plain;h=acf26e88182bb1d0509c2ac77356d54f9dee502d;p=littlesongplace.git HTML cleanup --- diff --git a/main.py b/main.py index 1d9dd77..5ff19ff 100644 --- 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 index 0000000..4787f5d --- /dev/null +++ b/static/nav.js @@ -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); + }); + }); +} + diff --git a/templates/base.html b/templates/base.html index 2f7c97c..93fb69a 100644 --- a/templates/base.html +++ b/templates/base.html @@ -2,9 +2,10 @@ {% block title %}{% endblock %} - - - + + + + @@ -12,164 +13,13 @@ - {% block head %} - {% endblock %} - + {%- block head %}{% endblock %} {{ gif_data|safe }} - - - {% if "username" in session %} - - - {% endif %} - - - {% with messages = get_flashed_messages(with_categories=True) %} - {% if messages %} -
- + +
+ {% with messages = get_flashed_messages(with_categories=True) -%} + {% if messages -%} +
+
    + {% for category, message in messages %} +
  • {{ message }}
  • + {% endfor %} +
+
+ {%- endif %} + {%- endwith %}
- {% endif %} - {% endwith %} -
- {% block body %} - {% endblock %} -
+
{% block body %}{% endblock %}
diff --git a/templates/index.html b/templates/index.html index f04887a..461c44a 100644 --- a/templates/index.html +++ b/templates/index.html @@ -26,6 +26,6 @@ better, I'm open to any thoughts or feedback you have! Just send me

Recently Uploaded Songs

Listen to all the newest tunes!

-{{ song_list|safe }} +{% include "song-list.html" %} {% endblock %} diff --git a/templates/playlist.html b/templates/playlist.html index 8bc1868..e0aed63 100644 --- a/templates/playlist.html +++ b/templates/playlist.html @@ -42,7 +42,7 @@ function hidePlaylistEditor() { {%- endif %} -{{ song_list|safe }} +{% include "song-list.html" %} {% if session["userid"] == userid -%} diff --git a/templates/profile.html b/templates/profile.html index 97874f1..9a9c8ef 100644 --- a/templates/profile.html +++ b/templates/profile.html @@ -173,7 +173,7 @@ {% endif %} -{{ song_list|safe }} +{% include "song-list.html" %} {% endblock %} diff --git a/templates/song-list.html b/templates/song-list.html index 5efc46c..1f53140 100644 --- a/templates/song-list.html +++ b/templates/song-list.html @@ -3,9 +3,9 @@
- {% if song.user_has_pfp %} + {% if song.user_has_pfp -%} - {% endif %} + {%- endif %}
@@ -17,34 +17,34 @@ -
- +
{{ song.username }} - + {% for collab in song.collaborators %} - {% if collab.startswith("@") %} - {{ collab[1:] }} - {% else %} - {{ collab }} - {% endif %} + {% if collab.startswith("@") -%} + {{ collab[1:] }} + {%- else -%} + {{ collab }} + {%- endif %} {% endfor %}
+ {% if session["userid"] == song.userid and is_profile_song_list -%} - {% if session["userid"] == song.userid and is_profile_song_list %} Edit Delete - {% endif %} + {%- endif %} - @@ -56,7 +56,7 @@
- {% if current_user_playlists %} + {% if current_user_playlists -%}
@@ -69,13 +69,14 @@
- {% endif %} + {%- endif %} + {% if song.description -%} - {% if song.description %}
{{ (song.description.replace("\n", "
"))|safe }}
- {% endif %} + {%- endif %} + {% if song.tags -%}
Tags: @@ -83,6 +84,7 @@ {{ tag }} {% endfor %}
+ {%- endif %}
Uploaded {{ song.created }} @@ -168,19 +170,20 @@ 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; } diff --git a/templates/song.html b/templates/song.html index d993b91..032421a 100644 --- a/templates/song.html +++ b/templates/song.html @@ -9,6 +9,6 @@ {% block body %} -{{ song_list|safe }} +{% include "song-list.html" %} {% endblock %} diff --git a/templates/songs-by-tag.html b/templates/songs-by-tag.html index a8e3dba..55cb11c 100644 --- a/templates/songs-by-tag.html +++ b/templates/songs-by-tag.html @@ -18,6 +18,6 @@
{% endif %} -{{ song_list|safe }} +{% include "song-list.html" %} {% endblock %}