summaryrefslogtreecommitdiff
path: root/views
diff options
from:
to:
context:
space:
mode:
authorGravatar SayaAndy <saya.andy@posteo.com> 2025-08-31 18:12:51 +0700
committerGravatar SayaAndy <saya.andy@posteo.com> 2025-08-31 18:12:51 +0700
commit145e7e05f3aa6adbc71a0db5e7da160e626bacd3 (patch)
tree6864bf01bbeeeadbf06122b9ab5d993cffaf4062 /views
parent5eb7b64b3da54e769cc6e52fd6918e4b465b8c0a (diff)
downloadweb-145e7e05f3aa6adbc71a0db5e7da160e626bacd3.tar.gz
web-145e7e05f3aa6adbc71a0db5e7da160e626bacd3.zip
feat: seamless single page application
Diffstat (limited to 'views')
-rw-r--r--views/layouts/general-page.html94
-rw-r--r--views/pages/blog-catalogue.html36
-rw-r--r--views/pages/blog-page.html79
-rw-r--r--views/partials/catalogue-blog-cards.html5
-rw-r--r--views/partials/general-page-bottom-embeds.html1
-rw-r--r--views/partials/general-page-header.html2
-rw-r--r--views/partials/general-page-top-embeds.html1
7 files changed, 133 insertions, 85 deletions
diff --git a/views/layouts/general-page.html b/views/layouts/general-page.html
index 4e352f4..e9a6c0f 100644
--- a/views/layouts/general-page.html
+++ b/views/layouts/general-page.html
@@ -8,7 +8,6 @@
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Spectral:ital,wght@0,200;0,300;0,400;0,500;0,600;0,700;0,800;1,200;1,300;1,400;1,500;1,600;1,700;1,800&family=Patua+One&display=swap" rel="stylesheet">
- {{ block "top-embeds" . }}{{ end }}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/lightgallery/2.8.3/css/lightgallery.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/lightgallery/2.8.3/css/lg-zoom.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/lightgallery/2.8.3/css/lg-thumbnail.min.css">
@@ -19,6 +18,16 @@
<body data-theme="ram" class="font-spectral text-main-dark overflow-hidden bg-interlocked-hexagons h-screen flex flex-row ar-lt-0.8:flex-col">
<script>
+ function changeUrl(path, toAppend = false) {
+ const urlParams = new URLSearchParams(window.location.search);
+ const newPathname = location.pathname.endsWith('/') ? location.pathname.slice(0, -1) : location.pathname;
+ const newPath = toAppend ? `${newPathname}${path}` : `${path}`;
+ history.pushState({}, '', `${newPath}?${urlParams.toString()}`);
+ window.dispatchEvent(new Event('popstate'));
+ };
+ </script>
+
+ <script>
function sfc32(a, b, c, d) {
return function() {
a |= 0; b |= 0; c |= 0; d |= 0;
@@ -144,9 +153,10 @@
switchTheme(savedTheme);
</script>
- <script>
- const galleryMap = new Map();
- </script>
+ <div id="general-page-top-embeds" class="flex"
+ hx-get="/api/v1/general-page/top-embeds{{ if .QueryString }}?{{ .QueryString }}{{ end }}" hx-trigger="load" hx-target="this" hx-swap="innerHTML">
+ </div>
+ <div id="general-page-top-embeds-trigger" hx-get="/api/v1/general-page/top-embeds{{ if .QueryString }}?{{ .QueryString }}{{ end }}" hx-trigger="popstate from:window" hx-target="#general-page-top-embeds" hx-swap="innerHTML"></div>
<script>
const clientTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
@@ -157,8 +167,8 @@
saya.today
</div>
<div class="text-[3vw] ar-lt-0.8:text-[5vh] text-stroke-(--sidebar-stroke-color) text-stroke-[0.1vw] flex flex-col ar-lt-0.8:flex-row gap-0 relative z-20">
- <i onclick="location.href='/';" class="fas fa-home w-[5vw] ar-lt-0.8:w-[8vh] h-[5vw] ar-lt-0.8:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i>
- <i onclick="location.href='/{{ .Lang }}/blog';" class="fas fa-search w-[5vw] ar-lt-0.8:w-[8vh] h-[5vw] ar-lt-0.8:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i>
+ <i onclick="location.href='/{{ .Lang }}';" class="fas fa-home w-[5vw] ar-lt-0.8:w-[8vh] h-[5vw] ar-lt-0.8:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i>
+ <i onclick="changeUrl('/{{ .Lang }}/blog');" class="fas fa-search w-[5vw] ar-lt-0.8:w-[8vh] h-[5vw] ar-lt-0.8:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i>
<i onclick="location.href='/{{ .Lang }}/map';" class="fas fa-map w-[5vw] ar-lt-0.8:w-[8vh] h-[5vw] ar-lt-0.8:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i>
<i onclick="toggleThemeWheel();" id="theme-button" class="fas fa-swatchbook w-[5vw] ar-lt-0.8:w-[8vh] h-[5vw] ar-lt-0.8:h-[8vh] content-center text-center text-sidebar hover:bg-background-dark cursor-pointer transition-colors duration-300 flex rounded-lg"></i>
</div>
@@ -175,18 +185,21 @@
<div class="ar-gt-0.8:min-w-fit ar-gt-0.8:max-w-[100vh] flex flex-col bg-background-dark relative pt-[1.6vmin] ml-[1.6vmin] pr-[1.6vmin]">
<div id="general-page-header" class="flex flex-col"
- hx-get="/api/v1/general-page/header" hx-trigger="load" hx-target="this" hx-swap="innerHTML">
+ hx-get="/api/v1/general-page/header{{ if .QueryString }}?{{ .QueryString }}{{ end }}" hx-trigger="load" hx-target="this" hx-swap="innerHTML">
</div>
+ <div id="general-page-header-trigger" hx-get="/api/v1/general-page/header{{ if .QueryString }}?{{ .QueryString }}{{ end }}" hx-trigger="popstate from:window" hx-target="#general-page-header" hx-swap="innerHTML"></div>
<hr class="border-t-4 border-dotted border-main-dark mb-[0.8vmin]">
<div id="general-page-body" class="flex flex-col max-h-[calc(100%-7vmax)] ar-lt-0.8:max-h-[calc(100%-25vh)] grow"
- hx-get="/api/v1/general-page/body" hx-trigger="load" hx-target="this" hx-swap="innerHTML">
+ hx-get="/api/v1/general-page/body{{ if .QueryString }}?{{ .QueryString }}{{ end }}" hx-trigger="load" hx-target="this" hx-swap="innerHTML">
</div>
+ <div id="general-page-body-trigger" hx-get="/api/v1/general-page/body{{ if .QueryString }}?{{ .QueryString }}{{ end }}" hx-trigger="popstate from:window" hx-target="#general-page-body" hx-swap="innerHTML"></div>
<div id="general-page-footer" class="flex flex-row mt-[0.8vmin] mb-[0.4vmin]"
- hx-get="/api/v1/general-page/footer" hx-trigger="load" hx-target="this" hx-swap="innerHTML">
+ hx-get="/api/v1/general-page/footer{{ if .QueryString }}?{{ .QueryString }}{{ end }}" hx-trigger="load" hx-target="this" hx-swap="innerHTML">
</div>
+ <div id="general-page-footer-trigger" hx-get="/api/v1/general-page/footer{{ if .QueryString }}?{{ .QueryString }}{{ end }}" hx-trigger="popstate from:window" hx-target="#general-page-footer" hx-swap="innerHTML"></div>
<hr class="border-t-4 border-dotted border-main-dark mb-[0.8vmin]">
</div>
@@ -200,6 +213,11 @@
<script src="https://f003.backblazeb2.com/file/sayana-static/libs/lightgallery/2.8.3/plugins/lg-thumbnail.min.js"></script>
<script src="https://f003.backblazeb2.com/file/sayana-static/libs/leaflet/1.9.4/leaflet.js"></script>
+ <div id="general-page-bottom-embeds" class="flex"
+ hx-get="/api/v1/general-page/bottom-embeds{{ if .QueryString }}?{{ .QueryString }}{{ end }}" hx-trigger="load" hx-target="this" hx-swap="innerHTML">
+ </div>
+ <div id="general-page-bottom-embeds-trigger" hx-get="/api/v1/general-page/bottom-embeds{{ if .QueryString }}?{{ .QueryString }}{{ end }}" hx-trigger="popstate from:window" hx-target="#general-page-bottom-embeds" hx-swap="innerHTML"></div>
+
<script>
const themeWheel = document.getElementById('theme-wheel');
const themeButton = document.getElementById('theme-button');
@@ -319,63 +337,5 @@
});
</script>
- <script>
- var map;
-
- function initLocationMap(id, x, y, relativeErrorMeters = 0) {
- map = L.map(id).setView([x, y], 8);
-
- L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
- maxZoom: 18,
- attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
- }).addTo(map);
-
- if (relativeErrorMeters != 0) {
- var circle = L.circle([x, y], {
- color: '#8a9d8a',
- fillColor: '#8a9d8a',
- fillOpacity: 0.15,
- radius: relativeErrorMeters
- }).addTo(map);
- }
-
- var marker = L.marker([x, y], {
- title: '{{ .Title }}',
- }).addTo(map)
- .bindPopup('<span><b>{{ .Title }}</b><br><a href="https://www.openstreetmap.org/#map=13/{{ .MapLocationX }}/{{ .MapLocationY }}">{{ .MapLocationX }}, {{ .MapLocationY }}</a></span>')
- .openPopup();
- }
-
- document.addEventListener('htmx:afterRequest', (e) => {
- if (e.detail.xhr.status == 404 || e.detail.successful != true) {
- return console.error(e);
- }
- if (e.detail.target.id == 'general-page-body') {
- initLocationMap(
- 'map-container',
- {{ .MapLocationX }},
- {{ .MapLocationY }},
- {{ .MapLocationAreaMeters }}
- );
- }
- });
-
- let galleryResizeTimeout;
- window.addEventListener('resize', function() {
- clearTimeout(galleryResizeTimeout);
- galleryResizeTimeout = setTimeout(() => {
- if (map !== undefined) {
- map.invalidateSize();
- }
- var oldGalleryMap = new Map(galleryMap);
- oldGalleryMap.forEach((createFunc, g, map) => {
- galleryMap.delete(g);
- g.destroy();
- createFunc();
- });
- }, 300);
- });
- </script>
-
</body>
</html>
diff --git a/views/pages/blog-catalogue.html b/views/pages/blog-catalogue.html
index ac2fb7d..a5f5256 100644
--- a/views/pages/blog-catalogue.html
+++ b/views/pages/blog-catalogue.html
@@ -98,6 +98,7 @@
function cleanHrefParams() {
const url = new URL(window.location.href);
url.search = '';
+
window.history.pushState({}, '', url.toString());
}
@@ -137,22 +138,27 @@
window.history.pushState({}, '', url.toString());
}
- document.addEventListener('DOMContentLoaded', contextAll);
-</script>
+ document.addEventListener('htmx:afterRequest', (e) => {
+ if (e.detail.xhr.status == 404 || e.detail.successful != true) {
+ return console.error(e);
+ }
+ if (e.detail.target.id == 'general-page-body') {
+ contextAll();
-<script>
- const tagsContainers = document.querySelectorAll('form.tags-list');
- tagsContainers.forEach(tagsContainer => {
- const checkboxes = tagsContainer.querySelectorAll('input[type="checkbox"]');
- anyChecked = false;
- checkboxes.forEach(checkbox => {
- anyChecked = checkbox.checked ? true : anyChecked;
- });
- if (!anyChecked) {
- const selectAllCheckbox = tagsContainer.querySelector('#tagsAllCheckbox');
- selectAllCheckbox.checked = true;
- selectAll();
+ const tagsContainers = document.querySelectorAll('form.tags-list');
+ tagsContainers.forEach(tagsContainer => {
+ const checkboxes = tagsContainer.querySelectorAll('input[type="checkbox"]');
+ anyChecked = false;
+ checkboxes.forEach(checkbox => {
+ anyChecked = checkbox.checked ? true : anyChecked;
+ });
+ if (!anyChecked) {
+ const selectAllCheckbox = tagsContainer.querySelector('#tagsAllCheckbox');
+ selectAllCheckbox.checked = true;
+ selectAll();
+ }
+ });
}
- });
+ }, {once: true});
</script>
{{ end }}
diff --git a/views/pages/blog-page.html b/views/pages/blog-page.html
index d8fb118..e68fcab 100644
--- a/views/pages/blog-page.html
+++ b/views/pages/blog-page.html
@@ -1,3 +1,18 @@
+{{ define "top-embeds" }}
+<script>
+ var galleryMap = new Map();
+ var map;
+
+ window.addEventListener('popout', (e) => {
+ map.remove();
+ galleryMap.forEach((_, g, m) => {
+ g.destroy();
+ });
+ galleryMap.clear();
+ }, {once: true});
+</script>
+{{ end }}
+
{{ define "body" }}
<div class="bg-background-light flex flex-col inset-shadow p-[2.4vmin] overflow-y-auto">
{{ .ParsedMarkdown }}
@@ -5,6 +20,43 @@
<hr class="border-t-4 border-dotted border-main-dark mt-[0.8vmin] mb-[0.8vmin] w-[80%] ml-auto mr-auto">
<div id="map-container" class="relative p-[0.8vmin] flex-none ml-auto mr-auto w-[60%] ar-lt-0.8:w-[80%] h-[40vh] ar-lt-0.8:h-[30vh]"></div>
<hr class="border-t-4 border-dotted border-main-dark mt-[0.8vmin] mb-[0.8vmin] w-[80%] ml-auto mr-auto">
+
+ <script>
+ function initLocationMap(id, x, y, relativeErrorMeters = 0) {
+ map = L.map(id).setView([x, y], 8);
+
+ L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
+ maxZoom: 18,
+ attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
+ }).addTo(map);
+
+ if (relativeErrorMeters != 0) {
+ var circle = L.circle([x, y], {
+ color: '#8a9d8a',
+ fillColor: '#8a9d8a',
+ fillOpacity: 0.15,
+ radius: relativeErrorMeters
+ }).addTo(map);
+ }
+
+ var marker = L.marker([x, y], {
+ title: '{{ .Title }}',
+ }).addTo(map)
+ .bindPopup('<span><b>{{ .Title }}</b><br><a href="https://www.openstreetmap.org/#map=13/{{ .MapLocationX }}/{{ .MapLocationY }}">{{ .MapLocationX }}, {{ .MapLocationY }}</a></span>')
+ .openPopup();
+ }
+
+ document.addEventListener('htmx:afterRequest', (e) => {
+ if (e.detail.target.id == 'general-page-body') {
+ initLocationMap(
+ 'map-container',
+ {{ .MapLocationX }},
+ {{ .MapLocationY }},
+ {{ .MapLocationAreaMeters }}
+ );
+ }
+ }, {once: true});
+ </script>
{{- end }}
</div>
{{ end }}
@@ -12,3 +64,30 @@
{{ define "footer" }}
<div hx-get="/api/v1/like" hx-target="this" hx-swap="outerHTML" hx-trigger="load"></div>
{{ end }}
+
+{{ define "bottom-embeds" }}
+<script>
+ let galleryResizeTimeout;
+
+ function resizeCb() {
+ clearTimeout(galleryResizeTimeout);
+ galleryResizeTimeout = setTimeout(() => {
+ if (map !== undefined) {
+ map.invalidateSize();
+ }
+ var oldGalleryMap = new Map(galleryMap);
+ oldGalleryMap.forEach((createFunc, g, map) => {
+ galleryMap.delete(g);
+ g.destroy();
+ createFunc();
+ });
+ }, 300);
+ }
+
+ window.addEventListener('resize', resizeCb);
+
+ document.addEventListener('popout', (e) => {
+ window.removeEventListener('resize', resizeCb, {once: true});
+ });
+</script>
+{{ end }} \ No newline at end of file
diff --git a/views/partials/catalogue-blog-cards.html b/views/partials/catalogue-blog-cards.html
index 35c7ad7..06d99d5 100644
--- a/views/partials/catalogue-blog-cards.html
+++ b/views/partials/catalogue-blog-cards.html
@@ -1,10 +1,10 @@
{{- range .BlogPages }}
<hr class="first-of-type:hidden border-t-[0.3vh] my-[0.8vmin] border-dotted border-main-dark w-[80%] mx-auto">
<div class="m-[0.4vmin] flex flex-row justify-center justify-items-center">
- <img onclick="location.href='{{ .ArticleLink }}';" class="cursor-pointer object-cover rounded-[10%] select-none w-[10vmin] h-[10vmin] aspect-square my-[0.4vmin] mr-[0.8vmin]" src="https://f003.backblazeb2.com/file/sayana-photos/webp-320p/{{ .Thumbnail }}.webp">
+ <img onclick="changeUrl('{{ .ArticleLink }}');" class="cursor-pointer object-cover rounded-[10%] select-none w-[10vmin] h-[10vmin] aspect-square my-[0.4vmin] mr-[0.8vmin]" src="https://f003.backblazeb2.com/file/sayana-photos/webp-320p/{{ .Thumbnail }}.webp">
<div class="grow my-[0.4vmin] mx-[0.8vmin] flex flex-col justify-center">
<div class="flex flex-row gap-[5%] max-w-[100%]">
- <a href="{{ .ArticleLink }}" class="flex-9/12 grow-0 block text-[1vmax] cursor-pointer">
+ <a onclick="changeUrl('{{ .ArticleLink }}');" class="flex-9/12 grow-0 block text-[1vmax] cursor-pointer">
<span class="font-extrabold">{{ .Title }}</span>
<span class="font-extrabold select-none text-secondary">//</span>
<span class="italic text-secondary">{{ .ActionDate }}</span>
@@ -12,6 +12,7 @@
<i class="fas fa-thumbs-up w-[1vmax] h-[1vmax]"></i>
<span class="italic text-secondary">{{ .LikeCount }}</span>
</a>
+ <div hx-trigger="click from:#user_search_bar" hx-get="/endpoint1" hx-target="#users-list"></div>
<p class="flex-3/12 grow-0 text-[1vmax] italic text-secondary text-right">{{ .PublishedTime }}</p>
</div>
<p class="text-[1vmax] text-main-dark">{{ .ShortDescription }}</p>
diff --git a/views/partials/general-page-bottom-embeds.html b/views/partials/general-page-bottom-embeds.html
new file mode 100644
index 0000000..4c36b26
--- /dev/null
+++ b/views/partials/general-page-bottom-embeds.html
@@ -0,0 +1 @@
+{{ block "bottom-embeds" . }}{{ end }} \ No newline at end of file
diff --git a/views/partials/general-page-header.html b/views/partials/general-page-header.html
index 66cfecf..63803bd 100644
--- a/views/partials/general-page-header.html
+++ b/views/partials/general-page-header.html
@@ -1,6 +1,6 @@
<div {{ if .PublishedDate }} hx-get="/api/v1/tz" hx-vals='js:{timestamp: "{{ .PublishedDate }}", tz: clientTimeZone}' hx-target="#published-date" hx-swap="innerHTML" hx-trigger="load" {{ end }}
class="flex flex-row mb-[0.4vmin]">
- <i onclick="location.pathname += location.pathname.endsWith('/') ? '../' : '/../';" class="fas fa-circle-left hover:bg-main-dark hover:bg-opacity-10 cursor-pointer transition-colors duration-300 rounded-md text-[2vmax] mt-auto mb-auto px-[0.8vmin]"></i>
+ <i onclick="changeUrl('/../', true);" class="fas fa-circle-left hover:bg-main-dark hover:bg-opacity-10 cursor-pointer transition-colors duration-300 rounded-md text-[2vmax] mt-auto mb-auto px-[0.8vmin]"></i>
<p class="text-[2vmax] font-spectral italic font-extrabold select-none mt-auto pl-[0.8vmin] pr-[1.2vmin]">//</p>
<p class="text-[2vmax] font-spectral italic text-left mt-auto">{{ .Title }}</p>
<p id="published-date" class="grow text-[1.5vmax] font-spectral text-secondary text-right mt-auto">{{ .PublishedDate }}</p>
diff --git a/views/partials/general-page-top-embeds.html b/views/partials/general-page-top-embeds.html
new file mode 100644
index 0000000..1407b50
--- /dev/null
+++ b/views/partials/general-page-top-embeds.html
@@ -0,0 +1 @@
+{{ block "top-embeds" . }}{{ end }} \ No newline at end of file