mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[chore/frontend] Reorder JS a little bit to avoid visible text changes (#4039)
This commit is contained in:
@ -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{
|
||||||
|
@ -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{
|
||||||
|
@ -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 {
|
||||||
|
@ -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.
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
@ -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: [
|
||||||
|
Reference in New Issue
Block a user