summaryrefslogtreecommitdiffci
path: root/views/pages
diff options
from:
to:
context:
space:
mode:
authorGravatar SayaAndy <montferrat@tuta.io> 2025-08-11 16:52:48 +0700
committerGravatar SayaAndy <montferrat@tuta.io> 2025-08-11 16:52:48 +0700
commitea1f83df820baa14dcd904d581ab2bb0976b172d (patch)
tree61322010ecdd59534cc1d02ff1c29d815d07152c /views/pages
parent15356da2c3e3544d757591a2f6e68367bf2d137f (diff)
downloadweb-ea1f83df820baa14dcd904d581ab2bb0976b172d.tar.gz
web-ea1f83df820baa14dcd904d581ab2bb0976b172d.zip
feat: redo blog catalogue with same layout as blog-pagev0.5.0
feat: implement localization feat: filters and sort in blog catalogue feat: one-page search feat: ram theme refactor: generate blog catalogue and blog page out of one layout refactor: have one layout for both mobile & desktop (aspect ratio dependable) refactor: use own template manager to use templates separately
Diffstat (limited to 'views/pages')
-rw-r--r--views/pages/blog-catalogue.html140
-rw-r--r--views/pages/blog-page.html70
2 files changed, 210 insertions, 0 deletions
diff --git a/views/pages/blog-catalogue.html b/views/pages/blog-catalogue.html
new file mode 100644
index 0000000..6fe1c82
--- /dev/null
+++ b/views/pages/blog-catalogue.html
@@ -0,0 +1,140 @@
+{{ define "body" }}
+<div class="flex flex-row ar-lt-0.8:flex-col flex-1 text-[1vmax]">
+ <form hx-get="/api/v1/blog-search" hx-vals='{"lang": "{{ .Lang }}"}' hx-target=".blog-cards" hx-swap="innerHTML"
+ class="tags-list ar-gt-0.8:min-w-[10vh] ar-gt-0.8:max-w-[20vh] ar-gt-0.8:w-fit ar-lt-0.8:w-[100%] flex shrink-0 flex-col ar-gt-0.8:mr-[1.2vmin]">
+ <fieldset>
+ <legend class="font-bold w-[100%] text-center">{{ .L.BlogSearch.TagsHeader }}</legend>
+ <div class="flex flex-col ar-lt-0.8:grid grid-cols-5 grid-flow-row-dense gap-[5%]">
+ <div>
+ <label class="font-bold"><input type="checkbox" id="tagsAllCheckbox" onclick="selectAll();"> {{ .L.BlogSearch.ChooseAllTags }}</label>
+ </div>
+ {{- range .Tags }}
+ <div>
+ <label><input type="checkbox" id="tag{{ . }}Checkbox" name="tags[]" value="{{ . }}" onclick="contextAll();" {{ if contains $.QueryTags . }}checked{{ end }}> {{ . }}</label>
+ </div>
+ {{- end }}
+ </div>
+ </fieldset>
+ <fieldset class="mt-[0.8vmin]">
+ <legend class="font-bold w-[100%] text-center">{{ .L.BlogSearch.OrderByHeader }}</legend>
+ <div class="flex flex-col ar-lt-0.8:grid grid-cols-3 grid-rows-2 grid-flow-col gap-[5%]">
+ <div>
+ <label><input type="radio" id="sortTitleAsc" name="sort" value="titleAsc" {{ if eq .QuerySort "titleAsc" }}checked{{ end }}> {{ .L.BlogSearch.TitleOrdered }} <i class="fas fa-arrow-down-a-z"></i></label>
+ </div>
+ <div>
+ <label><input type="radio" id="sortTitleDesc" name="sort" value="titleDesc" {{ if eq .QuerySort "titleDesc" }}checked{{ end }}> {{ .L.BlogSearch.TitleOrdered }} <i class="fas fa-arrow-down-z-a"></i></label>
+ </div>
+ <div>
+ <label><input type="radio" id="sortActionDateAsc" name="sort" value="actionDateAsc" {{ if eq .QuerySort "actionDateAsc" }}checked{{ end }}> {{ .L.BlogSearch.ActionDateOrdered }} <i class="fas fa-arrow-down-1-9"></i></label>
+ </div>
+ <div>
+ <label><input type="radio" id="sortActionDateDesc" name="sort" value="actionDateDesc" {{ if eq .QuerySort "actionDateDesc" }}checked{{ end }}> {{ .L.BlogSearch.ActionDateOrdered }} <i class="fas fa-arrow-down-9-1"></i></label>
+ </div>
+ <div>
+ <label><input type="radio" id="sortPublicationDateAsc" name="sort" value="publicationDateAsc" {{ if eq .QuerySort "publicationDateAsc" }}checked{{ end }}> {{ .L.BlogSearch.PublicationDateOrdered }} <i class="fas fa-arrow-down-1-9"></i></label>
+ </div>
+ <div>
+ <label><input type="radio" id="sortPublicationDateDesc" name="sort" value="publicationDateDesc" {{ if eq .QuerySort "publicationDateDesc" }}checked{{ end }}> {{ .L.BlogSearch.PublicationDateOrdered }} <i class="fas fa-arrow-down-9-1"></i></label>
+ </div>
+ </div>
+ </fieldset>
+ <div class="flex flex-row bg-background-light my-[2vmin] p-[0.4vmin] rounded-[0.4vmin] justify-items-center">
+ <i onclick="defaultSearchParams(); cleanHrefParams();" class="fas fa-rotate-left flex-1 text-center text-[1.5vmax] hover:bg-background-dark cursor-pointer transition-colors duration-300"></i>
+ <div class="w-[0.5vmin] bg-background-dark grow-0"></div>
+ <button class="flex-1" type="submit">
+ <i onclick="setHrefParams();" class="fas fa-magnifying-glass text-center text-[1.5vmax] hover:bg-background-dark cursor-pointer transition-colors duration-300"></i>
+ </button>
+ </div>
+ </form>
+
+ <div class="w-[0.5vh] border-r-[0.3vh] border-dotted border-main-dark bg-background-dark"></div>
+
+ <div hx-get="/api/v1/blog-search" hx-vals='{"lang": "{{ .Lang }}"}' hx-target="this" hx-swap="innerHTML" hx-trigger="load" hx-include=".tags-list"
+ class="blog-cards grow flex flex-col overflow-y-auto bg-background-light inset-shadow p-[2.4vmin]">
+ Loading...
+ </div>
+</div>
+{{ end }}
+
+{{ define "footer" }}
+<script src="https://f003.backblazeb2.com/file/sayana-static/libs/htmx/2.0.6/htmx.min.js"></script>
+
+<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) {
+ selectAll();
+ }
+ });
+</script>
+
+<script>
+ function selectAll() {
+ const tagsContainers = document.querySelectorAll('form.tags-list');
+ tagsContainers.forEach(tagsContainer => {
+ const selectAllCheckbox = tagsContainer.querySelector('#tagsAllCheckbox');
+ const checkboxes = tagsContainer.querySelectorAll('input[type="checkbox"]:not([id="tagsAllCheckbox"])');
+ checkboxes.forEach(checkbox => {
+ checkbox.checked = selectAllCheckbox.checked;
+ });
+ });
+ }
+
+ function contextAll() {
+ const tagsContainers = document.querySelectorAll('form.tags-list');
+ tagsContainers.forEach(tagsContainer => {
+ const selectAllCheckbox = tagsContainer.querySelector('#tagsAllCheckbox');
+ const checkboxes = tagsContainer.querySelectorAll('input[type="checkbox"]:not([id="tagsAllCheckbox"])');
+ var areAllChecked = true;
+ checkboxes.forEach(checkbox => {
+ if (!checkbox.checked) areAllChecked = false;
+ });
+ selectAllCheckbox.checked = areAllChecked;
+ });
+ }
+
+ function defaultSearchParams() {
+ const tagsContainers = document.querySelectorAll('form.tags-list');
+ tagsContainers.forEach(tagsContainer => {
+ const checkboxes = tagsContainer.querySelectorAll('input[type="checkbox"]');
+ checkboxes.forEach(checkbox => {
+ checkbox.checked = true;
+ });
+ const sortRadio = tagsContainer.querySelector('#sortTitleAsc');
+ sortRadio.checked = true;
+ });
+ }
+
+ function cleanHrefParams() {
+ const url = new URL(window.location.href);
+ url.search = '';
+ window.history.pushState({}, '', url.toString());
+ }
+
+ function setHrefParams() {
+ const url = new URL(window.location.href);
+ url.search = '';
+
+ const tagsContainers = document.querySelectorAll('form.tags-list');
+ tagsContainers.forEach(tagsContainer => {
+ const checkboxes = tagsContainer.querySelectorAll('input[type="checkbox"]');
+ checkboxes.forEach(checkbox => {
+ if (checkbox.checked)
+ url.searchParams.append('tags[]', checkbox.value);
+ });
+ const sortRadio = tagsContainer.querySelector('input[type="radio"]:checked');
+ url.searchParams.set('sort', sortRadio.value);
+ });
+
+ window.history.pushState({}, '', url.toString());
+ }
+
+ document.addEventListener('DOMContentLoaded', contextAll);
+</script>
+
+{{ end }}
diff --git a/views/pages/blog-page.html b/views/pages/blog-page.html
new file mode 100644
index 0000000..5f001d6
--- /dev/null
+++ b/views/pages/blog-page.html
@@ -0,0 +1,70 @@
+{{ define "header" }}
+<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">
+{{- if .MapLocationX }}
+<link rel="stylesheet" href="https://f003.backblazeb2.com/file/sayana-static/libs/leaflet/1.9.4/leaflet.css">
+{{- end }}
+
+<style>
+ .map-container .leaflet-container {
+ width: 100% !important;
+ height: 100% !important;
+ }
+</style>
+{{ end }}
+
+{{ define "body" }}
+<div class="bg-background-light flex flex-col grow overflow-y-auto inset-shadow p-[2.4vmin] ar-lt-0.8:max-h-[80%]">
+ {{ .ParsedMarkdown }}
+{{- if .MapLocationX }}
+ <hr class="border-t-4 border-dotted border-main-dark mt-[0.8vmin] mb-[0.8vmin] w-[80%] ml-auto mr-auto">
+ <div id="location-map" class="relative p-[0.8vmin] flex-none ml-auto mr-auto w-[60%] h-[40vh]"></div>
+ <hr class="border-t-4 border-dotted border-main-dark mt-[0.8vmin] mb-[0.8vmin] w-[80%] ml-auto mr-auto">
+{{- end }}
+</div>
+{{ end }}
+
+{{ define "footer" }}
+<script src="https://f003.backblazeb2.com/file/sayana-static/libs/lightgallery/2.8.3/lightgallery.min.js"></script>
+<script src="https://f003.backblazeb2.com/file/sayana-static/libs/lightgallery/2.8.3/plugins/lg-zoom.min.js"></script>
+<script src="https://f003.backblazeb2.com/file/sayana-static/libs/lightgallery/2.8.3/plugins/lg-thumbnail.min.js"></script>
+{{- if .MapLocationX }}
+<script src="https://f003.backblazeb2.com/file/sayana-static/libs/leaflet/1.9.4/leaflet.js"></script>
+
+<script>
+ function initLocationMap(id, x, y, relativeErrorMeters = 0) {
+ var map = L.map(id).setView([x, y], 13);
+
+ L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
+ maxZoom: 19,
+ 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]).addTo(map);
+ }
+
+ document.addEventListener('DOMContentLoaded', () => {initLocationMap(
+ 'location-map',
+ {{ .MapLocationX }},
+ {{ .MapLocationY }},
+ {{ .MapLocationAreaMeters }}
+ )});
+
+ window.addEventListener('resize', function() {
+ setTimeout(() => {
+ if (window.location-map) window.location-map.invalidateSize();
+ }, 100);
+ });
+</script>
+{{- end }}
+{{ end }} \ No newline at end of file