[chore/frontend] Reorder JS a little bit to avoid visible text changes (#4039)

This commit is contained in:
tobi
2025-04-22 12:20:54 +02:00
committed by GitHub
parent 0992ffc057
commit 075cae3d55
6 changed files with 135 additions and 116 deletions

View File

@ -249,7 +249,7 @@ func (m *Module) profileMicroblog(c *gin.Context, p *profile) {
}, },
{ {
Bottom: true, Bottom: true,
Src: jsBlurhash, Src: jsFrontendPrerender,
}, },
}, },
Extra: map[string]any{ Extra: map[string]any{
@ -323,7 +323,7 @@ func (m *Module) profileGallery(c *gin.Context, p *profile) {
}, },
{ {
Bottom: true, Bottom: true,
Src: jsBlurhash, Src: jsFrontendPrerender,
}, },
}, },
Extra: map[string]any{ Extra: map[string]any{

View File

@ -154,7 +154,7 @@ func (m *Module) threadGETHandler(c *gin.Context) {
}, },
{ {
Bottom: true, Bottom: true,
Src: jsBlurhash, Src: jsFrontendPrerender,
}, },
}, },
Extra: map[string]any{ Extra: map[string]any{

View File

@ -67,9 +67,9 @@ const (
cssSettings = distPathPrefix + "/settings-style.css" cssSettings = distPathPrefix + "/settings-style.css"
cssTag = distPathPrefix + "/tag.css" cssTag = distPathPrefix + "/tag.css"
jsFrontend = distPathPrefix + "/frontend.js" // Progressive enhancement frontend JS. jsFrontend = distPathPrefix + "/frontend.js" // Progressive enhancement frontend JS.
jsBlurhash = distPathPrefix + "/blurhash.js" // Blurhash rendering JS. jsFrontendPrerender = distPathPrefix + "/frontend_prerender.js" // Frontend JS that should run before page renders.
jsSettings = distPathPrefix + "/settings.js" // Settings panel React application. jsSettings = distPathPrefix + "/settings.js" // Settings panel React application.
) )
type Module struct { type Module struct {

View File

@ -17,6 +17,15 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
/*
WHAT SHOULD GO IN THIS FILE?
This script is loaded in the document head, and deferred + async,
so it's *usually* run after the user is already looking at the page.
Put stuff in here that doesn't shift the layout, and it doesn't really
matter whether it loads immediately. So, progressive enhancement stuff.
*/
const Photoswipe = require("photoswipe/dist/umd/photoswipe.umd.min.js"); const Photoswipe = require("photoswipe/dist/umd/photoswipe.umd.min.js");
const PhotoswipeLightbox = require("photoswipe/dist/umd/photoswipe-lightbox.umd.min.js"); const PhotoswipeLightbox = require("photoswipe/dist/umd/photoswipe-lightbox.umd.min.js");
const PhotoswipeCaptionPlugin = require("photoswipe-dynamic-caption-plugin").default; const PhotoswipeCaptionPlugin = require("photoswipe-dynamic-caption-plugin").default;
@ -165,89 +174,6 @@ lightbox.on('uiRegister', function() {
lightbox.init(); lightbox.init();
function dynamicSpoiler(className, updateFunc) {
Array.from(document.getElementsByClassName(className)).forEach((spoiler) => {
const update = updateFunc(spoiler);
if (update) {
update();
spoiler.addEventListener("toggle", update);
}
});
}
dynamicSpoiler("text-spoiler", (details) => {
const summary = details.children[0];
const button = details.querySelector(".button");
// Use button *instead of summary*
// to toggle post visibility.
summary.tabIndex = "-1";
button.tabIndex = "0";
button.setAttribute("aria-role", "button");
button.onclick = (e) => {
e.preventDefault();
return details.hasAttribute("open")
? details.removeAttribute("open")
: details.setAttribute("open", "");
};
// Let enter also trigger the button
// (for those using keyboard to navigate).
button.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
button.click();
}
});
// Change button text depending on
// whether spoiler is open or closed rn.
return () => {
button.textContent = details.open
? "Show less"
: "Show more";
};
});
dynamicSpoiler("media-spoiler", (details) => {
const summary = details.children[0];
const button = details.querySelector(".eye.button");
const video = details.querySelector(".plyr-video");
const loopingAuto = !reduceMotion.matches && video != null && video.classList.contains("gifv");
// Use button *instead of summary*
// to toggle media visibility.
summary.tabIndex = "-1";
button.tabIndex = "0";
button.setAttribute("aria-role", "button");
button.onclick = (e) => {
e.preventDefault();
return details.hasAttribute("open")
? details.removeAttribute("open")
: details.setAttribute("open", "");
};
// Let enter also trigger the button
// (for those using keyboard to navigate).
button.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
button.click();
}
});
return () => {
if (details.open) {
button.setAttribute("aria-label", "Hide media");
} else {
button.setAttribute("aria-label", "Show media");
if (video && !loopingAuto) {
video.pause();
}
}
};
});
Array.from(document.getElementsByClassName("plyr-video")).forEach((video) => { Array.from(document.getElementsByClassName("plyr-video")).forEach((video) => {
const loopingAuto = !reduceMotion.matches && video.classList.contains("gifv"); const loopingAuto = !reduceMotion.matches && video.classList.contains("gifv");
let player = new Plyr(video, { let player = new Plyr(video, {
@ -315,30 +241,6 @@ function inLightbox(element) {
lightbox.pswp.currSlide.data.attachmentId; lightbox.pswp.currSlide.data.attachmentId;
} }
// Define + reuse one DateTimeFormat (cheaper).
const dateTimeFormat = Intl.DateTimeFormat(
undefined,
{
year: 'numeric',
month: 'short',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false
},
);
// Reformat time text to browser locale.
Array.from(document.getElementsByTagName('time')).forEach(timeTag => {
const datetime = timeTag.getAttribute('datetime');
const currentText = timeTag.textContent.trim();
// Only format if current text contains precise time.
if (currentText.match(/\d{2}:\d{2}/)) {
const date = new Date(datetime);
timeTag.textContent = dateTimeFormat.format(date);
}
});
// When clicking anywhere that's not an open // When clicking anywhere that's not an open
// stats-info-more-content details dropdown, // stats-info-more-content details dropdown,
// close that open dropdown. // close that open dropdown.

View File

@ -17,8 +17,18 @@
along with this program. If not, see <http://www.gnu.org/licenses/>. along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
/*
WHAT SHOULD GO IN THIS FILE?
This script is loaded just before the end of the HTML body, so
put stuff in here that should be run *before* the user sees the page.
So, stuff that shifts the layout or causes elements to jump around.
*/
import { decode } from "blurhash"; import { decode } from "blurhash";
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
// Generate a blurhash canvas for each image for // Generate a blurhash canvas for each image for
// each blurhash container and put it in the summary. // each blurhash container and put it in the summary.
Array.from(document.getElementsByClassName('blurhash-container')).forEach(blurhashContainer => { Array.from(document.getElementsByClassName('blurhash-container')).forEach(blurhashContainer => {
@ -144,3 +154,110 @@ Array.from(document.getElementsByTagName('img')).forEach(img => {
} }
}); });
}); });
// Change the spoiler / content warning boxes from generic
// "toggle visibility" to show/hide depending on state,
// and add keyboard functionality to spoiler buttons.
function dynamicSpoiler(className, updateFunc) {
Array.from(document.getElementsByClassName(className)).forEach((spoiler) => {
const update = updateFunc(spoiler);
if (update) {
update();
spoiler.addEventListener("toggle", update);
}
});
}
dynamicSpoiler("text-spoiler", (details) => {
const summary = details.children[0];
const button = details.querySelector(".button");
// Use button *instead of summary*
// to toggle post visibility.
summary.tabIndex = "-1";
button.tabIndex = "0";
button.setAttribute("aria-role", "button");
button.onclick = (e) => {
e.preventDefault();
return details.hasAttribute("open")
? details.removeAttribute("open")
: details.setAttribute("open", "");
};
// Let enter also trigger the button
// (for those using keyboard to navigate).
button.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
button.click();
}
});
// Change button text depending on
// whether spoiler is open or closed rn.
return () => {
button.textContent = details.open
? "Show less"
: "Show more";
};
});
dynamicSpoiler("media-spoiler", (details) => {
const summary = details.children[0];
const button = details.querySelector(".eye.button");
const video = details.querySelector(".plyr-video");
const loopingAuto = !reduceMotion.matches && video != null && video.classList.contains("gifv");
// Use button *instead of summary*
// to toggle media visibility.
summary.tabIndex = "-1";
button.tabIndex = "0";
button.setAttribute("aria-role", "button");
button.onclick = (e) => {
e.preventDefault();
return details.hasAttribute("open")
? details.removeAttribute("open")
: details.setAttribute("open", "");
};
// Let enter also trigger the button
// (for those using keyboard to navigate).
button.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
button.click();
}
});
return () => {
if (details.open) {
button.setAttribute("aria-label", "Hide media");
} else {
button.setAttribute("aria-label", "Show media");
if (video && !loopingAuto) {
video.pause();
}
}
};
});
// Reformat time text to browser locale.
// Define + reuse one DateTimeFormat (cheaper).
const dateTimeFormat = Intl.DateTimeFormat(
undefined,
{
year: 'numeric',
month: 'short',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hour12: false
},
);
Array.from(document.getElementsByTagName('time')).forEach(timeTag => {
const datetime = timeTag.getAttribute('datetime');
const currentText = timeTag.textContent.trim();
// Only format if current text contains precise time.
if (currentText.match(/\d{2}:\d{2}/)) {
const date = new Date(datetime);
timeTag.textContent = dateTimeFormat.format(date);
}
});

View File

@ -64,9 +64,9 @@ skulk({
}] }]
], ],
}, },
blurhash: { frontend_prerender: {
entryFile: "blurhash", entryFile: "frontend_prerender",
outputFile: "blurhash.js", outputFile: "frontend_prerender.js",
preset: ["js"], preset: ["js"],
prodCfg: prodCfg, prodCfg: prodCfg,
transform: [ transform: [