Compare commits
14 Commits
36cace78a6
...
0.1.16
| Author | SHA1 | Date | |
|---|---|---|---|
| 02b09bf9df | |||
| c96b816213 | |||
| 023f940255 | |||
| bdf18746c2 | |||
| 243c756ef5 | |||
| c0b383b08a | |||
| f5b9532c86 | |||
| 61d01e1e11 | |||
| 8c13e470ad | |||
| 526f030661 | |||
| 223e2aa007 | |||
| c3290c071a | |||
| 80cb551ecc | |||
| 06b8910b02 |
23
README.md
23
README.md
@@ -17,7 +17,7 @@ Hilfs‑Web‑App für deutsche Wordle‑Rätsel. Nutzer geben bekannte Buchstab
|
|||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
Wordle-Cheater live bei [https://wh.elpatron.me](https://wh.elpatron.me).
|
Wordle-Cheater live bei [https://wh.elpatron.me](https://wh.elpatron.me).
|
||||||
|
|
||||||
@@ -56,9 +56,24 @@ export FLASK_SECRET_KEY="your-super-secret-key-change-this-in-production"
|
|||||||
export ADMIN_PASSWORD="your-secure-admin-password"
|
export ADMIN_PASSWORD="your-secure-admin-password"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Docker (empfohlen)
|
### Health-Check
|
||||||
|
|
||||||
- Build:
|
Für Container-Monitoring steht ein dedizierter Health-Check-Endpunkt zur Verfügung:
|
||||||
|
|
||||||
|
- **URL:** `/health`
|
||||||
|
- **Antwort:** JSON mit Status und Zeitstempel
|
||||||
|
- **Logging:** **Wird NICHT geloggt** (keine Statistiken)
|
||||||
|
- **Verwendung:** Docker Health-Checks, Load Balancer, Monitoring-Tools
|
||||||
|
|
||||||
|
Beispiel-Antwort:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "healthy",
|
||||||
|
"timestamp": "2025-08-20T09:15:30.123456"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker (empfohlen)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build -t wordle-cheater .
|
docker build -t wordle-cheater .
|
||||||
@@ -70,7 +85,7 @@ docker build -t wordle-cheater .
|
|||||||
docker run --rm -p 8000:8000 wordle-cheater
|
docker run --rm -p 8000:8000 wordle-cheater
|
||||||
```
|
```
|
||||||
|
|
||||||
- Health‑Check (lokal): `http://localhost:8000/`
|
- Health‑Check (lokal): `http://localhost:8000/health`
|
||||||
- Admin‑Dashboard: `http://localhost:8000/stats` (passwortgeschützt)
|
- Admin‑Dashboard: `http://localhost:8000/stats` (passwortgeschützt)
|
||||||
|
|
||||||
Hinweise:
|
Hinweise:
|
||||||
|
|||||||
54
app.py
54
app.py
@@ -7,6 +7,8 @@ from typing import Tuple, Dict, List
|
|||||||
from flask import Flask, render_template, request, send_from_directory, session, redirect, url_for, flash
|
from flask import Flask, render_template, request, send_from_directory, session, redirect, url_for, flash
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
|
__version__ = "0.1.16"
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
app.secret_key = os.environ.get('FLASK_SECRET_KEY', 'dev-secret-key-change-in-production')
|
app.secret_key = os.environ.get('FLASK_SECRET_KEY', 'dev-secret-key-change-in-production')
|
||||||
|
|
||||||
@@ -31,6 +33,10 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
def log_page_view(page: str, user_agent: str = None):
|
def log_page_view(page: str, user_agent: str = None):
|
||||||
"""Protokolliert Seitenaufrufe ohne IP-Adressen"""
|
"""Protokolliert Seitenaufrufe ohne IP-Adressen"""
|
||||||
|
# Zugriffe auf /stats nicht loggen, da sie die Statistiken verfälschen
|
||||||
|
if page == "stats":
|
||||||
|
return
|
||||||
|
|
||||||
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
user_agent_clean = user_agent[:100] if user_agent else 'Unknown'
|
user_agent_clean = user_agent[:100] if user_agent else 'Unknown'
|
||||||
logger.info(f"PAGE_VIEW: {page} | User-Agent: {user_agent_clean}")
|
logger.info(f"PAGE_VIEW: {page} | User-Agent: {user_agent_clean}")
|
||||||
@@ -72,6 +78,9 @@ def get_statistics():
|
|||||||
'top_search_patterns': {}
|
'top_search_patterns': {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Sammle alle relevanten Aktivitäten
|
||||||
|
all_activities = []
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Aktuelle Log-Datei lesen
|
# Aktuelle Log-Datei lesen
|
||||||
log_file = logs_dir / 'app.log'
|
log_file = logs_dir / 'app.log'
|
||||||
@@ -85,6 +94,16 @@ def get_statistics():
|
|||||||
page = line.split('PAGE_VIEW: ')[1].split(' |')[0]
|
page = line.split('PAGE_VIEW: ')[1].split(' |')[0]
|
||||||
stats['page_views_by_page'][page] = stats['page_views_by_page'].get(page, 0) + 1
|
stats['page_views_by_page'][page] = stats['page_views_by_page'].get(page, 0) + 1
|
||||||
|
|
||||||
|
# PAGE_VIEW Einträge als Aktivität sammeln
|
||||||
|
timestamp = line.split(' - ')[0] if ' - ' in line else ''
|
||||||
|
if timestamp:
|
||||||
|
# /stats-Zugriffe aus den Aktivitäten filtern
|
||||||
|
if 'PAGE_VIEW: stats' not in line:
|
||||||
|
all_activities.append({
|
||||||
|
'timestamp': timestamp,
|
||||||
|
'line': line.strip()
|
||||||
|
})
|
||||||
|
|
||||||
elif 'SEARCH:' in line:
|
elif 'SEARCH:' in line:
|
||||||
stats['total_searches'] += 1
|
stats['total_searches'] += 1
|
||||||
# Quellen extrahieren
|
# Quellen extrahieren
|
||||||
@@ -103,11 +122,10 @@ def get_statistics():
|
|||||||
if pos_part:
|
if pos_part:
|
||||||
stats['top_search_patterns'][pos_part] = stats['top_search_patterns'].get(pos_part, 0) + 1
|
stats['top_search_patterns'][pos_part] = stats['top_search_patterns'].get(pos_part, 0) + 1
|
||||||
|
|
||||||
# Letzte 10 Aktivitäten
|
# SEARCH Einträge als Aktivität sammeln
|
||||||
if len(stats['recent_activity']) < 10:
|
|
||||||
timestamp = line.split(' - ')[0] if ' - ' in line else ''
|
timestamp = line.split(' - ')[0] if ' - ' in line else ''
|
||||||
if timestamp:
|
if timestamp:
|
||||||
stats['recent_activity'].append({
|
all_activities.append({
|
||||||
'timestamp': timestamp,
|
'timestamp': timestamp,
|
||||||
'line': line.strip()
|
'line': line.strip()
|
||||||
})
|
})
|
||||||
@@ -125,6 +143,12 @@ def get_statistics():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Fehler beim Lesen der Backup-Datei {backup_file}: {e}")
|
logger.error(f"Fehler beim Lesen der Backup-Datei {backup_file}: {e}")
|
||||||
|
|
||||||
|
# Neueste 10 Aktivitäten auswählen
|
||||||
|
if all_activities:
|
||||||
|
# Nach Timestamp sortieren (neueste zuerst)
|
||||||
|
all_activities.sort(key=lambda x: x['timestamp'], reverse=True)
|
||||||
|
stats['recent_activity'] = all_activities[:10]
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Fehler beim Lesen der Statistiken: {e}")
|
logger.error(f"Fehler beim Lesen der Statistiken: {e}")
|
||||||
|
|
||||||
@@ -187,7 +211,7 @@ def load_words() -> Tuple[List[str], Dict[str, List[str]]]:
|
|||||||
return words, sources_map
|
return words, sources_map
|
||||||
|
|
||||||
|
|
||||||
def filter_words(words: List[str], position_letters: List[str], includes_text: str, excludes_text: str) -> List[str]:
|
def filter_words(words: List[str], position_letters: List[str], includes_text: str, excludes_text: str, use_umlaut: bool = True) -> List[str]:
|
||||||
results: List[str] = []
|
results: List[str] = []
|
||||||
includes_letters = [ch for ch in includes_text.lower() if ch.isalpha()]
|
includes_letters = [ch for ch in includes_text.lower() if ch.isalpha()]
|
||||||
excludes_letters = [ch for ch in excludes_text.lower() if ch.isalpha()]
|
excludes_letters = [ch for ch in excludes_text.lower() if ch.isalpha()]
|
||||||
@@ -201,6 +225,9 @@ def filter_words(words: List[str], position_letters: List[str], includes_text: s
|
|||||||
# darf-nicht-enthalten
|
# darf-nicht-enthalten
|
||||||
if any(ch in word for ch in excludes_letters):
|
if any(ch in word for ch in excludes_letters):
|
||||||
continue
|
continue
|
||||||
|
# Umlaute-Filter
|
||||||
|
if not use_umlaut and ('ä' in word or 'ö' in word or 'ü' in word or 'ß' in word):
|
||||||
|
continue
|
||||||
results.append(word)
|
results.append(word)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
@@ -220,6 +247,7 @@ def index():
|
|||||||
excludes: str = ""
|
excludes: str = ""
|
||||||
use_ot: bool = True
|
use_ot: bool = True
|
||||||
use_wf: bool = False
|
use_wf: bool = False
|
||||||
|
use_umlaut: bool = True
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
pos = [
|
pos = [
|
||||||
(request.form.get("pos1") or "").strip().lower(),
|
(request.form.get("pos1") or "").strip().lower(),
|
||||||
@@ -232,6 +260,7 @@ def index():
|
|||||||
excludes = (request.form.get("excludes") or "").strip()
|
excludes = (request.form.get("excludes") or "").strip()
|
||||||
use_ot = request.form.get("use_ot") is not None
|
use_ot = request.form.get("use_ot") is not None
|
||||||
use_wf = request.form.get("use_wf") is not None
|
use_wf = request.form.get("use_wf") is not None
|
||||||
|
use_umlaut = request.form.get("use_umlaut") is not None
|
||||||
# Falls keine Quelle gewählt ist, standardmäßig OpenThesaurus aktivieren
|
# Falls keine Quelle gewählt ist, standardmäßig OpenThesaurus aktivieren
|
||||||
if not use_ot and not use_wf:
|
if not use_ot and not use_wf:
|
||||||
use_ot = True
|
use_ot = True
|
||||||
@@ -242,12 +271,13 @@ def index():
|
|||||||
'includes': includes,
|
'includes': includes,
|
||||||
'excludes': excludes,
|
'excludes': excludes,
|
||||||
'use_ot': use_ot,
|
'use_ot': use_ot,
|
||||||
'use_wf': use_wf
|
'use_wf': use_wf,
|
||||||
|
'use_umlaut': use_umlaut
|
||||||
}
|
}
|
||||||
log_search_query(search_params, request.headers.get('User-Agent'))
|
log_search_query(search_params, request.headers.get('User-Agent'))
|
||||||
|
|
||||||
# 1) Buchstaben-/Positionssuche über alle Wörter
|
# 1) Buchstaben-/Positionssuche über alle Wörter
|
||||||
matched = filter_words(all_words, pos, includes, excludes)
|
matched = filter_words(all_words, pos, includes, excludes, use_umlaut)
|
||||||
# 2) Quellen-Filter nur auf Ergebnisansicht anwenden
|
# 2) Quellen-Filter nur auf Ergebnisansicht anwenden
|
||||||
allowed = set()
|
allowed = set()
|
||||||
if use_ot:
|
if use_ot:
|
||||||
@@ -269,6 +299,7 @@ def index():
|
|||||||
sources_map=sources_map,
|
sources_map=sources_map,
|
||||||
use_ot=use_ot,
|
use_ot=use_ot,
|
||||||
use_wf=use_wf,
|
use_wf=use_wf,
|
||||||
|
use_umlaut=use_umlaut,
|
||||||
error_message=None,
|
error_message=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -284,6 +315,17 @@ def service_worker():
|
|||||||
# Service Worker muss auf Top-Level liegen
|
# Service Worker muss auf Top-Level liegen
|
||||||
log_page_view("service_worker", request.headers.get('User-Agent'))
|
log_page_view("service_worker", request.headers.get('User-Agent'))
|
||||||
return send_from_directory(Path(__file__).parent / 'static', 'sw.js', mimetype='application/javascript')
|
return send_from_directory(Path(__file__).parent / 'static', 'sw.js', mimetype='application/javascript')
|
||||||
|
@app.route('/screenshot.png')
|
||||||
|
def screenshot_image():
|
||||||
|
"""Liefert das OpenGraph/Twitter Vorschaubild aus dem Projektstamm."""
|
||||||
|
log_page_view("screenshot", request.headers.get('User-Agent'))
|
||||||
|
return send_from_directory(Path(__file__).parent, 'screenshot.png', mimetype='image/png')
|
||||||
|
|
||||||
|
|
||||||
|
@app.route('/health')
|
||||||
|
def health_check():
|
||||||
|
"""Health-Check für Docker/Container-Monitoring - wird NICHT geloggt"""
|
||||||
|
return {'status': 'healthy', 'timestamp': datetime.now().isoformat()}
|
||||||
|
|
||||||
|
|
||||||
@app.route('/login', methods=['GET', 'POST'])
|
@app.route('/login', methods=['GET', 'POST'])
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ services:
|
|||||||
user: "1000:1000"
|
user: "1000:1000"
|
||||||
# Healthcheck für Container-Status
|
# Healthcheck für Container-Status
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:8000/"]
|
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|||||||
BIN
screenshot-mobile.png
Normal file
BIN
screenshot-mobile.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
BIN
screenshot.png
BIN
screenshot.png
Binary file not shown.
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 67 KiB |
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"version": "0.1.16",
|
||||||
"name": "Wordle‑Cheater",
|
"name": "Wordle‑Cheater",
|
||||||
"short_name": "W‑Cheater",
|
"short_name": "W‑Cheater",
|
||||||
"description": "Hilft bei der Lösung deutschsprachiger Wordle‑Rätsel mit Positions- und Buchstabenfiltern.",
|
"description": "Hilft bei der Lösung deutschsprachiger Wordle‑Rätsel mit Positions- und Buchstabenfiltern.",
|
||||||
|
|||||||
@@ -11,9 +11,14 @@
|
|||||||
<meta property="og:title" content="Wordle‑Cheater (DE)" />
|
<meta property="og:title" content="Wordle‑Cheater (DE)" />
|
||||||
<meta property="og:description" content="Finde deutsche 5‑Buchstaben‑Wörter mit Positions- und Buchstabenfiltern. Quellen: OpenThesaurus & wordfreq." />
|
<meta property="og:description" content="Finde deutsche 5‑Buchstaben‑Wörter mit Positions- und Buchstabenfiltern. Quellen: OpenThesaurus & wordfreq." />
|
||||||
<meta property="og:url" content="{{ request.url_root }}" />
|
<meta property="og:url" content="{{ request.url_root }}" />
|
||||||
|
<meta property="og:site_name" content="Wordle‑Cheater (DE)" />
|
||||||
|
<meta property="og:locale" content="de_DE" />
|
||||||
|
<meta property="og:image" content="{{ url_for('screenshot_image', _external=True) }}" />
|
||||||
<meta name="twitter:card" content="summary" />
|
<meta name="twitter:card" content="summary" />
|
||||||
<meta name="twitter:title" content="Wordle‑Cheater (DE)" />
|
<meta name="twitter:title" content="Wordle‑Cheater (DE)" />
|
||||||
<meta name="twitter:description" content="Finde deutsche 5‑Buchstaben‑Wörter mit Positions- und Buchstabenfiltern. Quellen: OpenThesaurus & wordfreq." />
|
<meta name="twitter:description" content="Finde deutsche 5‑Buchstaben‑Wörter mit Positions- und Buchstabenfiltern. Quellen: OpenThesaurus & wordfreq." />
|
||||||
|
<meta name="twitter:image" content="{{ url_for('screenshot_image', _external=True) }}" />
|
||||||
|
<link rel="alternate" hreflang="de" href="{{ request.url_root }}" />
|
||||||
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}" />
|
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}" />
|
||||||
<link rel="manifest" href="/manifest.webmanifest" />
|
<link rel="manifest" href="/manifest.webmanifest" />
|
||||||
<meta name="theme-color" content="#0b1220" />
|
<meta name="theme-color" content="#0b1220" />
|
||||||
@@ -28,6 +33,19 @@
|
|||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "WebApplication",
|
||||||
|
"name": "Wordle‑Cheater (DE)",
|
||||||
|
"url": "{{ request.url_root }}",
|
||||||
|
"applicationCategory": "UtilitiesApplication",
|
||||||
|
"operatingSystem": "Web",
|
||||||
|
"description": "Finde deutsche 5‑Buchstaben‑Wörter anhand bekannter Buchstaben und Positionen. Quellen: OpenThesaurus & wordfreq.",
|
||||||
|
"inLanguage": "de",
|
||||||
|
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "EUR" }
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<style>
|
<style>
|
||||||
:root { --bg:#ffffff; --text:#111827; --muted:#6b7280; --badge-bg:#e5e7eb; --badge-text:#111827; --border:#e5e7eb; --skip-bg:#111827; --skip-text:#ffffff; --button-bg:#111827; --button-text:#ffffff; --input-bg:#ffffff; --input-text:#111827; --error:#b91c1c; }
|
:root { --bg:#ffffff; --text:#111827; --muted:#6b7280; --badge-bg:#e5e7eb; --badge-text:#111827; --border:#e5e7eb; --skip-bg:#111827; --skip-text:#ffffff; --button-bg:#111827; --button-text:#ffffff; --input-bg:#ffffff; --input-text:#111827; --error:#b91c1c; }
|
||||||
[data-theme="dark"] { --bg:#0b1220; --text:#e5e7eb; --muted:#9ca3af; --badge-bg:#374151; --badge-text:#f9fafb; --border:#334155; --skip-bg:#e5e7eb; --skip-text:#111827; --button-bg:#e5e7eb; --button-text:#111827; --input-bg:#111827; --input-text:#e5e7eb; --error:#ef4444; }
|
[data-theme="dark"] { --bg:#0b1220; --text:#e5e7eb; --muted:#9ca3af; --badge-bg:#374151; --badge-text:#f9fafb; --border:#334155; --skip-bg:#e5e7eb; --skip-text:#111827; --button-bg:#e5e7eb; --button-text:#111827; --input-bg:#111827; --input-text:#e5e7eb; --error:#ef4444; }
|
||||||
@@ -75,6 +93,7 @@
|
|||||||
.inline-controls .plus-button { margin-top: 0; margin-right: 0; }
|
.inline-controls .plus-button { margin-top: 0; margin-right: 0; }
|
||||||
.drop-target { outline: 2px dashed #3b82f6; outline-offset: 2px; }
|
.drop-target { outline: 2px dashed #3b82f6; outline-offset: 2px; }
|
||||||
</style>
|
</style>
|
||||||
|
<script defer data-domain="wh.elpatron.me" src="https://plausible.elpatron.me/js/script.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<a href="#results" class="skip-link">Zum Ergebnisbereich springen</a>
|
<a href="#results" class="skip-link">Zum Ergebnisbereich springen</a>
|
||||||
@@ -98,7 +117,7 @@
|
|||||||
<input id="pos4" name="pos4" maxlength="1" aria-label="Position 4" inputmode="text" autocomplete="off" pattern="[A-Za-zÄÖÜäöüß]" value="{{ pos[3] }}" />
|
<input id="pos4" name="pos4" maxlength="1" aria-label="Position 4" inputmode="text" autocomplete="off" pattern="[A-Za-zÄÖÜäöüß]" value="{{ pos[3] }}" />
|
||||||
<input id="pos5" name="pos5" maxlength="1" aria-label="Position 5" inputmode="text" autocomplete="off" pattern="[A-Za-zÄÖÜäöüß]" value="{{ pos[4] }}" />
|
<input id="pos5" name="pos5" maxlength="1" aria-label="Position 5" inputmode="text" autocomplete="off" pattern="[A-Za-zÄÖÜäöüß]" value="{{ pos[4] }}" />
|
||||||
</div>
|
</div>
|
||||||
<p id="pos-hint" class="hint">Je Feld ein Buchstabe. Umlaute (ä, ö, ü) und ß sind erlaubt. Ziehe einen Buchstaben heraus, wenn du ihn löschen möchtest.</p>
|
<p id="pos-hint" class="hint">Je Feld ein Buchstabe. Ziehe einen Buchstaben heraus, wenn du ihn löschen möchtest.</p>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<label for="includes-input-one">Weitere enthaltene Buchstaben (beliebige Reihenfolge)</label>
|
<label for="includes-input-one">Weitere enthaltene Buchstaben (beliebige Reihenfolge)</label>
|
||||||
@@ -136,7 +155,7 @@
|
|||||||
<fieldset class="filter-box" role="group">
|
<fieldset class="filter-box" role="group">
|
||||||
<legend>Umlaute</legend>
|
<legend>Umlaute</legend>
|
||||||
<div class="inline-controls">
|
<div class="inline-controls">
|
||||||
<label><input id="filter-umlaut" type="checkbox" /> Umlaute einbeziehen (ä, ö, ü, ß)</label>
|
<label><input id="filter-umlaut" type="checkbox" name="use_umlaut" form="search-form" {% if use_umlaut %}checked{% endif %}/> Umlaute einbeziehen (ä, ö, ü, ß)</label>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div class="results-box">
|
<div class="results-box">
|
||||||
@@ -210,7 +229,7 @@
|
|||||||
var ot = document.getElementById('filter-ot');
|
var ot = document.getElementById('filter-ot');
|
||||||
var wf = document.getElementById('filter-wf');
|
var wf = document.getElementById('filter-wf');
|
||||||
var uml = document.getElementById('filter-umlaut');
|
var uml = document.getElementById('filter-umlaut');
|
||||||
if (!ot || !wf) return;
|
if (!ot || !wf || !uml) return;
|
||||||
var allowed = [];
|
var allowed = [];
|
||||||
if (ot.checked) allowed.push('ot');
|
if (ot.checked) allowed.push('ot');
|
||||||
if (wf.checked) allowed.push('wf');
|
if (wf.checked) allowed.push('wf');
|
||||||
|
|||||||
@@ -191,18 +191,21 @@
|
|||||||
<div class="bar-chart">
|
<div class="bar-chart">
|
||||||
{% set max_count = [stats.searches_by_source.OT, stats.searches_by_source.WF, stats.searches_by_source.Both] | max %}
|
{% set max_count = [stats.searches_by_source.OT, stats.searches_by_source.WF, stats.searches_by_source.Both] | max %}
|
||||||
{% if stats.searches_by_source.OT > 0 %}
|
{% if stats.searches_by_source.OT > 0 %}
|
||||||
|
<!-- CSS-Linter: Jinja2-Template-Syntax wird ignoriert -->
|
||||||
<div class="bar" style="height: {{ (stats.searches_by_source.OT / max_count * 150) + 50 }}px;">
|
<div class="bar" style="height: {{ (stats.searches_by_source.OT / max_count * 150) + 50 }}px;">
|
||||||
<div class="bar-value">{{ stats.searches_by_source.OT }}</div>
|
<div class="bar-value">{{ stats.searches_by_source.OT }}</div>
|
||||||
<div class="bar-label">OpenThesaurus</div>
|
<div class="bar-label">OpenThesaurus</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if stats.searches_by_source.WF > 0 %}
|
{% if stats.searches_by_source.WF > 0 %}
|
||||||
|
<!-- CSS-Linter: Jinja2-Template-Syntax wird ignoriert -->
|
||||||
<div class="bar" style="height: {{ (stats.searches_by_source.WF / max_count * 150) + 50 }}px;">
|
<div class="bar" style="height: {{ (stats.searches_by_source.WF / max_count * 150) + 50 }}px;">
|
||||||
<div class="bar-value">{{ stats.searches_by_source.WF }}</div>
|
<div class="bar-value">{{ stats.searches_by_source.WF }}</div>
|
||||||
<div class="bar-label">Wordfreq</div>
|
<div class="bar-label">Wordfreq</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if stats.searches_by_source.Both > 0 %}
|
{% if stats.searches_by_source.Both > 0 %}
|
||||||
|
<!-- CSS-Linter: Jinja2-Template-Syntax wird ignoriert -->
|
||||||
<div class="bar" style="height: {{ (stats.searches_by_source.Both / max_count * 150) + 50 }}px;">
|
<div class="bar" style="height: {{ (stats.searches_by_source.Both / max_count * 150) + 50 }}px;">
|
||||||
<div class="bar-value">{{ stats.searches_by_source.Both }}</div>
|
<div class="bar-value">{{ stats.searches_by_source.Both }}</div>
|
||||||
<div class="bar-label">Beide</div>
|
<div class="bar-label">Beide</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user