Compare commits
2 Commits
cd7526bd80
..
V1.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
| abc0766f16 | |||
| 4e9fe2de6a |
@@ -0,0 +1,15 @@
|
||||
Mood-Board
|
||||
Copyright (c) 2026 HNZIO
|
||||
|
||||
Licensed under the PolyForm Noncommercial License 1.0.0.
|
||||
|
||||
You may use, copy, modify, and distribute this software only for permitted
|
||||
noncommercial purposes under the terms of that license.
|
||||
|
||||
Commercial use is not allowed without a separate written agreement from the
|
||||
copyright holder.
|
||||
|
||||
Required Notice: Copyright (c) 2026 HNZIO
|
||||
|
||||
Full license text:
|
||||
https://polyformproject.org/licenses/noncommercial/1.0.0/
|
||||
@@ -32,3 +32,10 @@ Dateibasierter Stimmungstracker für LAMP/Cloudron ohne Datenbank.
|
||||
- Die Inhalte liegen absichtlich nicht in einer Datenbank, sondern in menschenlesbaren TXT-Dateien.
|
||||
- Mehrere Accounts sind möglich und verursachen hier wenig Overhead, weil jeder Nutzer nur einen eigenen Unterordner mit Tagen und Einstellungen bekommt.
|
||||
- Wenn du später Reverse Proxy oder HTTPS über Cloudron nutzt, bleiben die Daten weiterhin nur über die App erreichbar.
|
||||
|
||||
## Lizenz
|
||||
|
||||
- Dieses Projekt steht unter der `PolyForm Noncommercial License 1.0.0`.
|
||||
- Nicht-kommerzielle Nutzung ist erlaubt.
|
||||
- Kommerzielle Nutzung ist ohne separate schriftliche Freigabe nicht erlaubt.
|
||||
- Details siehe [LICENSE](/home/hnzio/Projekte/mood/LICENSE).
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 655 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
@@ -128,6 +128,34 @@ body {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.pull-refresh-indicator {
|
||||
position: fixed;
|
||||
top: max(0.85rem, env(safe-area-inset-top));
|
||||
left: 50%;
|
||||
z-index: 30;
|
||||
padding: 0.72rem 1rem;
|
||||
border-radius: 999px;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transform: translate(-50%, -0.9rem) scale(0.96);
|
||||
transition: opacity 160ms ease, transform 160ms ease;
|
||||
color: var(--muted);
|
||||
font-size: 0.92rem;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
body.is-pull-refreshing .pull-refresh-indicator,
|
||||
body.is-pull-refresh-ready .pull-refresh-indicator,
|
||||
body.is-pull-refresh-reloading .pull-refresh-indicator {
|
||||
opacity: 1;
|
||||
transform: translate(-50%, 0) scale(1);
|
||||
}
|
||||
|
||||
body.is-pull-refresh-ready .pull-refresh-indicator,
|
||||
body.is-pull-refresh-reloading .pull-refresh-indicator {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
|
||||
@@ -992,6 +992,125 @@
|
||||
return window.matchMedia("(display-mode: standalone)").matches || window.navigator.standalone === true;
|
||||
}
|
||||
|
||||
function isAppleTouchDevice() {
|
||||
return /iPhone|iPad|iPod/i.test(window.navigator.userAgent)
|
||||
|| (window.navigator.platform === "MacIntel" && window.navigator.maxTouchPoints > 1);
|
||||
}
|
||||
|
||||
function initPullToRefresh() {
|
||||
if (!isStandaloneMode() || !isAppleTouchDevice()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const indicator = document.querySelector("[data-pull-refresh-indicator]");
|
||||
const body = document.body;
|
||||
const threshold = 96;
|
||||
let isTracking = false;
|
||||
let isReady = false;
|
||||
let startY = 0;
|
||||
|
||||
const setIndicator = message => {
|
||||
if (indicator) {
|
||||
indicator.textContent = message;
|
||||
}
|
||||
};
|
||||
|
||||
const resetState = () => {
|
||||
body.classList.remove("is-pull-refreshing", "is-pull-refresh-ready");
|
||||
isTracking = false;
|
||||
isReady = false;
|
||||
startY = 0;
|
||||
setIndicator("Zum Aktualisieren ziehen");
|
||||
};
|
||||
|
||||
const scrollTop = () => Math.max(
|
||||
window.scrollY || 0,
|
||||
document.documentElement.scrollTop || 0,
|
||||
document.body.scrollTop || 0
|
||||
);
|
||||
|
||||
const canStart = target => {
|
||||
if (scrollTop() > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!(target instanceof Element)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !target.closest("input, textarea, select, button");
|
||||
};
|
||||
|
||||
window.addEventListener("touchstart", event => {
|
||||
if (event.touches.length !== 1 || !canStart(event.target)) {
|
||||
resetState();
|
||||
return;
|
||||
}
|
||||
|
||||
isTracking = true;
|
||||
startY = event.touches[0].clientY;
|
||||
}, { passive: true });
|
||||
|
||||
window.addEventListener("touchmove", event => {
|
||||
if (!isTracking || event.touches.length !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scrollTop() > 0) {
|
||||
resetState();
|
||||
return;
|
||||
}
|
||||
|
||||
const delta = event.touches[0].clientY - startY;
|
||||
|
||||
if (delta <= 0) {
|
||||
body.classList.remove("is-pull-refreshing", "is-pull-refresh-ready");
|
||||
isReady = false;
|
||||
setIndicator("Zum Aktualisieren ziehen");
|
||||
return;
|
||||
}
|
||||
|
||||
if (delta > 18) {
|
||||
body.classList.add("is-pull-refreshing");
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (delta >= threshold) {
|
||||
if (!isReady) {
|
||||
body.classList.add("is-pull-refresh-ready");
|
||||
setIndicator("Loslassen zum Aktualisieren");
|
||||
isReady = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isReady) {
|
||||
body.classList.remove("is-pull-refresh-ready");
|
||||
isReady = false;
|
||||
}
|
||||
|
||||
setIndicator("Zum Aktualisieren ziehen");
|
||||
}, { passive: false });
|
||||
|
||||
window.addEventListener("touchend", () => {
|
||||
if (!isTracking) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isReady) {
|
||||
body.classList.remove("is-pull-refreshing", "is-pull-refresh-ready");
|
||||
body.classList.add("is-pull-refresh-reloading");
|
||||
setIndicator("Wird aktualisiert ...");
|
||||
window.location.reload();
|
||||
return;
|
||||
}
|
||||
|
||||
resetState();
|
||||
}, { passive: true });
|
||||
|
||||
window.addEventListener("touchcancel", resetState, { passive: true });
|
||||
}
|
||||
|
||||
function initPushControls() {
|
||||
const panel = document.querySelector("[data-push-panel]");
|
||||
if (!panel) {
|
||||
@@ -1147,5 +1266,6 @@
|
||||
initDashboardCharts();
|
||||
initSportTypeManager();
|
||||
initPwaShell();
|
||||
initPullToRefresh();
|
||||
initPushControls();
|
||||
})();
|
||||
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -12,15 +12,15 @@
|
||||
"theme_color": "#0b1e2e",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/assets/branding/logo-mark.svg",
|
||||
"sizes": "any",
|
||||
"type": "image/svg+xml",
|
||||
"src": "/assets/branding/icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "/assets/branding/apple-touch-icon.svg",
|
||||
"sizes": "any",
|
||||
"type": "image/svg+xml",
|
||||
"src": "/assets/branding/icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -27,9 +27,11 @@ $brandSubtitle = match ($page) {
|
||||
<meta name="mood-push-public-key" content="<?= e((string) $pushPublicKey) ?>">
|
||||
<?php endif; ?>
|
||||
<title><?= e($pageTitle) ?> · Mood</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/branding/favicon.svg">
|
||||
<link rel="shortcut icon" href="/assets/branding/favicon.svg">
|
||||
<link rel="apple-touch-icon" href="/assets/branding/apple-touch-icon.svg">
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/branding/favicon.svg?v=20260412">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/branding/favicon-32.png?v=20260412">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/branding/favicon-16.png?v=20260412">
|
||||
<link rel="shortcut icon" href="/favicon.ico?v=20260412">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/branding/apple-touch-icon.png?v=20260412">
|
||||
<link rel="manifest" href="/manifest.webmanifest">
|
||||
<link rel="stylesheet" href="/assets/css/app.css">
|
||||
<script defer src="/assets/js/app.js"></script>
|
||||
@@ -37,6 +39,7 @@ $brandSubtitle = match ($page) {
|
||||
<body class="app-body page-<?= e($page) ?><?= $authUser !== null ? ' is-authenticated' : '' ?>" data-authenticated="<?= $authUser !== null ? '1' : '0' ?>"<?= isset($trackMood) ? ' data-track-mood="' . e($trackMood) . '"' : '' ?>>
|
||||
<div class="aurora aurora-one"></div>
|
||||
<div class="aurora aurora-two"></div>
|
||||
<div class="pull-refresh-indicator glass-panel" data-pull-refresh-indicator aria-hidden="true">Zum Aktualisieren ziehen</div>
|
||||
<div class="shell">
|
||||
<?php if ($authUser !== null): ?>
|
||||
<aside class="sidebar glass-panel">
|
||||
|
||||
Reference in New Issue
Block a user