Merge remote-tracking branch 'origin/master' into development
This commit is contained in:
commit
3d8c8c1ec7
3
.gitignore
vendored
3
.gitignore
vendored
@ -10,5 +10,6 @@
|
|||||||
/search.db
|
/search.db
|
||||||
/access_log.db
|
/access_log.db
|
||||||
/access_log.db.bak
|
/access_log.db.bak
|
||||||
/folder_config.json
|
/folder_permission_config.json
|
||||||
|
/folder_mount_config.json
|
||||||
/.env
|
/.env
|
||||||
2
auth.py
2
auth.py
@ -14,7 +14,7 @@ def require_secret(f):
|
|||||||
def decorated_function(*args, **kwargs):
|
def decorated_function(*args, **kwargs):
|
||||||
global folder_config
|
global folder_config
|
||||||
if not folder_config:
|
if not folder_config:
|
||||||
with open('folder_config.json') as file:
|
with open('folder_permission_config.json') as file:
|
||||||
folder_config = json.load(file)
|
folder_config = json.load(file)
|
||||||
|
|
||||||
def is_valid(config_item, provided_secret):
|
def is_valid(config_item, provided_secret):
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"secret" : "dev_key_f83745ft0g5rg3",
|
|
||||||
"validity" : "31.12.2030",
|
|
||||||
"folders": [
|
|
||||||
{
|
|
||||||
"foldername": "My Folder",
|
|
||||||
"folderpath": "\\\\path\\if\\using\\windows"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@ -16,11 +16,12 @@ access_log_db.row_factory = sqlite3.Row
|
|||||||
def init_db():
|
def init_db():
|
||||||
"""Initializes the database with the required schema."""
|
"""Initializes the database with the required schema."""
|
||||||
cursor = search_db.cursor()
|
cursor = search_db.cursor()
|
||||||
# Create table with the new 'hitcount' column.
|
# Create table with the new 'hitcount' and 'basefolder' columns.
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
CREATE TABLE IF NOT EXISTS files (
|
CREATE TABLE IF NOT EXISTS files (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
relative_path TEXT,
|
relative_path TEXT,
|
||||||
|
basefolder TEXT,
|
||||||
filename TEXT,
|
filename TEXT,
|
||||||
filetype TEXT,
|
filetype TEXT,
|
||||||
transcript TEXT,
|
transcript TEXT,
|
||||||
@ -29,12 +30,17 @@ def init_db():
|
|||||||
)
|
)
|
||||||
''')
|
''')
|
||||||
search_db.commit()
|
search_db.commit()
|
||||||
# If the table already existed, try to add the 'hitcount' column.
|
# If the table already existed, try to add the new columns.
|
||||||
try:
|
try:
|
||||||
cursor.execute("ALTER TABLE files ADD COLUMN hitcount INTEGER DEFAULT 0")
|
cursor.execute("ALTER TABLE files ADD COLUMN hitcount INTEGER DEFAULT 0")
|
||||||
except sqlite3.OperationalError:
|
except sqlite3.OperationalError:
|
||||||
# Likely the column already exists, so we ignore this error.
|
# Likely the column already exists, so we ignore this error.
|
||||||
pass
|
pass
|
||||||
|
try:
|
||||||
|
cursor.execute("ALTER TABLE files ADD COLUMN basefolder TEXT")
|
||||||
|
except sqlite3.OperationalError:
|
||||||
|
# Likely the column already exists, so we ignore this error.
|
||||||
|
pass
|
||||||
search_db.commit()
|
search_db.commit()
|
||||||
|
|
||||||
def scan_dir(directory):
|
def scan_dir(directory):
|
||||||
@ -70,13 +76,14 @@ def updatefileindex():
|
|||||||
for config in config_data:
|
for config in config_data:
|
||||||
for folder in config.get("folders", []):
|
for folder in config.get("folders", []):
|
||||||
foldername = folder.get("foldername")
|
foldername = folder.get("foldername")
|
||||||
|
print(f"Processing folder: {foldername}")
|
||||||
raw_folderpath = folder.get("folderpath")
|
raw_folderpath = folder.get("folderpath")
|
||||||
norm_folderpath = os.path.normpath(raw_folderpath)
|
norm_folderpath = os.path.normpath(raw_folderpath)
|
||||||
# Precompute the length of the base folder path (plus one for the separator)
|
# Precompute the length of the base folder path (plus one for the separator)
|
||||||
base_len = len(norm_folderpath) + 1
|
base_len = len(norm_folderpath) + 1
|
||||||
|
|
||||||
# Accumulate scanned file data and keys for this base folder.
|
# Accumulate scanned file data and keys for this base folder.
|
||||||
scanned_files = [] # Each entry: (relative_path, filename, filetype, transcript, hitcount)
|
scanned_files = [] # Each entry: (relative_path, basefolder, filename, filetype, transcript, hitcount)
|
||||||
current_keys = set()
|
current_keys = set()
|
||||||
|
|
||||||
for entry in scan_dir(norm_folderpath):
|
for entry in scan_dir(norm_folderpath):
|
||||||
@ -106,7 +113,7 @@ def updatefileindex():
|
|||||||
# Retrieve the hit count for this file.
|
# Retrieve the hit count for this file.
|
||||||
hit_count = get_hit_count(relative_path)
|
hit_count = get_hit_count(relative_path)
|
||||||
|
|
||||||
scanned_files.append((relative_path, entry.name, filetype, transcript, hit_count))
|
scanned_files.append((relative_path, foldername, entry.name, filetype, transcript, hit_count))
|
||||||
current_keys.add((relative_path, entry.name))
|
current_keys.add((relative_path, entry.name))
|
||||||
|
|
||||||
# Remove database entries for files under this base folder that are no longer on disk.
|
# Remove database entries for files under this base folder that are no longer on disk.
|
||||||
@ -120,7 +127,7 @@ def updatefileindex():
|
|||||||
|
|
||||||
# Bulk write the scanned files using INSERT OR REPLACE.
|
# Bulk write the scanned files using INSERT OR REPLACE.
|
||||||
cursor.executemany(
|
cursor.executemany(
|
||||||
"INSERT OR REPLACE INTO files (relative_path, filename, filetype, transcript, hitcount) VALUES (?, ?, ?, ?, ?)",
|
"INSERT OR REPLACE INTO files (relative_path, basefolder, filename, filetype, transcript, hitcount) VALUES (?, ?, ?, ?, ?, ?)",
|
||||||
scanned_files
|
scanned_files
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -1,46 +1,36 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Configuration Section
|
# Configuration Section (Loaded from config.json)
|
||||||
###############################################################################
|
###############################################################################
|
||||||
# Server 1 Configuration
|
|
||||||
SERVER1_SSH_USER="root"
|
|
||||||
SERVER1_SSH_SERVER="bethaus-speyer.de"
|
|
||||||
SERVER1_SSH_SERVER_PORT=1122 # Remote SSH server port for Server 1
|
|
||||||
SERVER1_REMOTE_NFS_PORT=2049 # Remote NFS server port (usually 2049)
|
|
||||||
SERVER1_LOCAL_PORT_BASE=2022 # Base local port for SSH tunnel (will add index offset)
|
|
||||||
|
|
||||||
# Define multiple mount configurations for Server 1
|
CONFIG_FILE="folder_mount_config.json"
|
||||||
# Each index corresponds to a mount point and NFS share.
|
|
||||||
SERVER1_MOUNT_POINTS=(
|
|
||||||
"/mnt/Gottesdienste Speyer"
|
|
||||||
"/mnt/Besondere Gottesdienste"
|
|
||||||
"/mnt/Liedersammlung"
|
|
||||||
"/mnt/app_share"
|
|
||||||
)
|
|
||||||
SERVER1_NFS_SHARES=(
|
|
||||||
"/volume1/Aufnahme-stereo/010 Gottesdienste ARCHIV"
|
|
||||||
"/volume1/Aufnahme-stereo/013 Besondere Gottesdienste"
|
|
||||||
"/volume1/Aufnahme-stereo/014 Liedersammlung"
|
|
||||||
"/volume1/app_share"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Server 2 Configuration
|
# Ensure jq is installed before proceeding.
|
||||||
SERVER2_SSH_USER="root"
|
if ! command -v jq >/dev/null 2>&1; then
|
||||||
SERVER2_SSH_SERVER="bethaus-schwegenheim.de"
|
echo "[ERROR] 'jq' is not installed. Please install jq and try again."
|
||||||
SERVER2_SSH_SERVER_PORT=1122 # Remote SSH server port for Server 2
|
exit 1
|
||||||
SERVER2_REMOTE_NFS_PORT=2049 # Remote NFS server port
|
fi
|
||||||
SERVER2_LOCAL_PORT_BASE=3022 # Base local port for SSH tunnel (will add index offset)
|
|
||||||
|
|
||||||
# Define multiple mount configurations for Server 2
|
# Load Server 1 configuration
|
||||||
SERVER2_MOUNT_POINTS=(
|
SERVER1_SSH_USER=$(jq -r '.SERVER1.SSH_USER' "$CONFIG_FILE")
|
||||||
"/mnt/Gottesdienste Schwegenheim"
|
SERVER1_SSH_SERVER=$(jq -r '.SERVER1.SSH_SERVER' "$CONFIG_FILE")
|
||||||
)
|
SERVER1_SSH_SERVER_PORT=$(jq -r '.SERVER1.SSH_SERVER_PORT' "$CONFIG_FILE")
|
||||||
SERVER2_NFS_SHARES=(
|
SERVER1_REMOTE_NFS_PORT=$(jq -r '.SERVER1.REMOTE_NFS_PORT' "$CONFIG_FILE")
|
||||||
"/volume1/Aufnahme-stereo/010 Gottesdienste ARCHIV"
|
SERVER1_LOCAL_PORT_BASE=$(jq -r '.SERVER1.LOCAL_PORT_BASE' "$CONFIG_FILE")
|
||||||
)
|
readarray -t SERVER1_MOUNT_POINTS < <(jq -r '.SERVER1.MOUNT_POINTS[]' "$CONFIG_FILE")
|
||||||
|
readarray -t SERVER1_NFS_SHARES < <(jq -r '.SERVER1.NFS_SHARES[]' "$CONFIG_FILE")
|
||||||
|
|
||||||
# List of server identifiers (must match the prefix of configuration variables)
|
# Load Server 2 configuration
|
||||||
|
SERVER2_SSH_USER=$(jq -r '.SERVER2.SSH_USER' "$CONFIG_FILE")
|
||||||
|
SERVER2_SSH_SERVER=$(jq -r '.SERVER2.SSH_SERVER' "$CONFIG_FILE")
|
||||||
|
SERVER2_SSH_SERVER_PORT=$(jq -r '.SERVER2.SSH_SERVER_PORT' "$CONFIG_FILE")
|
||||||
|
SERVER2_REMOTE_NFS_PORT=$(jq -r '.SERVER2.REMOTE_NFS_PORT' "$CONFIG_FILE")
|
||||||
|
SERVER2_LOCAL_PORT_BASE=$(jq -r '.SERVER2.LOCAL_PORT_BASE' "$CONFIG_FILE")
|
||||||
|
readarray -t SERVER2_MOUNT_POINTS < <(jq -r '.SERVER2.MOUNT_POINTS[]' "$CONFIG_FILE")
|
||||||
|
readarray -t SERVER2_NFS_SHARES < <(jq -r '.SERVER2.NFS_SHARES[]' "$CONFIG_FILE")
|
||||||
|
|
||||||
|
# Define list of server identifiers (must match JSON keys)
|
||||||
SERVERS=("SERVER1" "SERVER2")
|
SERVERS=("SERVER1" "SERVER2")
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
47
search.py
47
search.py
@ -1,5 +1,6 @@
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
from flask import Flask, render_template, request, request, jsonify
|
from flask import Flask, render_template, request, request, jsonify, session
|
||||||
|
import os
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
@ -10,30 +11,57 @@ search_db.row_factory = sqlite3.Row
|
|||||||
|
|
||||||
def searchcommand():
|
def searchcommand():
|
||||||
query = request.form.get("query", "").strip()
|
query = request.form.get("query", "").strip()
|
||||||
include_transcript = request.form.get("includeTranscript") == "true" or request.form.get("includeTranscript") == "on"
|
category = request.form.get("category", "").strip()
|
||||||
|
include_transcript = request.form.get("includeTranscript") in ["true", "on"]
|
||||||
words = [w for w in query.split() if w]
|
words = [w for w in query.split() if w]
|
||||||
cursor = search_db.cursor()
|
cursor = search_db.cursor()
|
||||||
|
|
||||||
|
allowed_basefolders = list(session['folders'].keys())
|
||||||
|
print("Allowed base folders:", allowed_basefolders)
|
||||||
|
|
||||||
if not include_transcript:
|
if not include_transcript:
|
||||||
# Simple search: all words must be in either relative_path or filename.
|
|
||||||
conditions = []
|
conditions = []
|
||||||
params = []
|
params = []
|
||||||
|
# Apply query words to relative_path and filename
|
||||||
for word in words:
|
for word in words:
|
||||||
conditions.append("(relative_path LIKE ? OR filename LIKE ?)")
|
conditions.append("(relative_path LIKE ? OR filename LIKE ?)")
|
||||||
params.extend([f"%{word}%", f"%{word}%"])
|
params.extend([f"%{word}%", f"%{word}%"])
|
||||||
|
# Search category in filename
|
||||||
|
if category:
|
||||||
|
conditions.append("(filename LIKE ?)")
|
||||||
|
params.extend([f"%{category}%"])
|
||||||
|
# Only include rows where basefolder is in allowed_basefolders
|
||||||
|
if allowed_basefolders:
|
||||||
|
placeholders = ",".join("?" for _ in allowed_basefolders)
|
||||||
|
conditions.append(f"basefolder IN ({placeholders})")
|
||||||
|
params.extend(allowed_basefolders)
|
||||||
|
|
||||||
sql = "SELECT * FROM files"
|
sql = "SELECT * FROM files"
|
||||||
if conditions:
|
if conditions:
|
||||||
sql += " WHERE " + " AND ".join(conditions)
|
sql += " WHERE " + " AND ".join(conditions)
|
||||||
|
sql += " ORDER BY hitcount DESC"
|
||||||
cursor.execute(sql, params)
|
cursor.execute(sql, params)
|
||||||
raw_results = cursor.fetchall()
|
raw_results = cursor.fetchall()
|
||||||
results = [dict(row) for row in raw_results]
|
results = [dict(row) for row in raw_results]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Advanced search: include transcript. Count transcript hits.
|
# Advanced search: include transcript. Count transcript hits.
|
||||||
conditions = []
|
conditions = []
|
||||||
params = []
|
params = []
|
||||||
|
# Apply query words for filename and transcript
|
||||||
for word in words:
|
for word in words:
|
||||||
conditions.append("(relative_path LIKE ? OR filename LIKE ? OR transcript LIKE ?)")
|
conditions.append("(filename LIKE ? OR transcript LIKE ?)")
|
||||||
params.extend([f"%{word}%", f"%{word}%", f"%{word}%"])
|
params.extend([f"%{word}%", f"%{word}%"])
|
||||||
|
# Search category in filename
|
||||||
|
if category:
|
||||||
|
conditions.append("(filename LIKE ?)")
|
||||||
|
params.extend([f"%{category}%"])
|
||||||
|
# Only include rows where basefolder is in allowed_basefolders
|
||||||
|
if allowed_basefolders:
|
||||||
|
placeholders = ",".join("?" for _ in allowed_basefolders)
|
||||||
|
conditions.append(f"basefolder IN ({placeholders})")
|
||||||
|
params.extend(allowed_basefolders)
|
||||||
|
|
||||||
sql = "SELECT * FROM files"
|
sql = "SELECT * FROM files"
|
||||||
if conditions:
|
if conditions:
|
||||||
sql += " WHERE " + " AND ".join(conditions)
|
sql += " WHERE " + " AND ".join(conditions)
|
||||||
@ -46,13 +74,16 @@ def searchcommand():
|
|||||||
transcript = result.get("transcript") or ""
|
transcript = result.get("transcript") or ""
|
||||||
total_hits = sum(transcript.lower().count(word.lower()) for word in words)
|
total_hits = sum(transcript.lower().count(word.lower()) for word in words)
|
||||||
result["transcript_hits"] = total_hits
|
result["transcript_hits"] = total_hits
|
||||||
result["transcript"] = None # Remove full transcript if needed.
|
result.pop("transcript")
|
||||||
results.append(result)
|
results.append(result)
|
||||||
# Sort results so files with more transcript hits are on top.
|
# Sort so that files with more transcript hits appear first
|
||||||
results.sort(key=lambda x: x["transcript_hits"], reverse=True)
|
results.sort(key=lambda x: x["transcript_hits"], reverse=True)
|
||||||
|
|
||||||
|
results = results[:100]
|
||||||
return jsonify(results=results)
|
return jsonify(results=results)
|
||||||
|
|
||||||
def search():
|
def search():
|
||||||
return render_template('search.html')
|
title_short = os.environ.get('TITLE_SHORT', 'Default Title')
|
||||||
|
title_long = os.environ.get('TITLE_LONG', 'Default Title')
|
||||||
|
return render_template("search.html", title_short=title_short, title_long=title_long)
|
||||||
|
|
||||||
|
|||||||
@ -84,13 +84,13 @@ li {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* mouse symbol for links */
|
/* mouse symbol for links */
|
||||||
div.directory-item, li.directory-item, li.file-item,
|
div.directory-item, li.directory-item, li.file-item, li.link-item,
|
||||||
div.directory-item a, li.directory-item a, li.file-item a {
|
div.directory-item a, li.directory-item a, li.file-item a, li.link-item a {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Directory Items (in a list) */
|
/* Directory Items (in a list) */
|
||||||
.directory-item {
|
.directory-item, .link-item {
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,6 +122,7 @@ div.directory-item a, li.directory-item a, li.file-item a {
|
|||||||
|
|
||||||
/* Link Styles */
|
/* Link Styles */
|
||||||
.directory-link,
|
.directory-link,
|
||||||
|
.link-link,
|
||||||
a.play-file {
|
a.play-file {
|
||||||
color: #34495e;
|
color: #34495e;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|||||||
@ -63,6 +63,9 @@ function renderContent(data) {
|
|||||||
data.directories.forEach(dir => {
|
data.directories.forEach(dir => {
|
||||||
contentHTML += `<li class="directory-item">📁 <a href="#" class="directory-link" data-path="${dir.path}">${dir.name}</a></li>`;
|
contentHTML += `<li class="directory-item">📁 <a href="#" class="directory-link" data-path="${dir.path}">${dir.name}</a></li>`;
|
||||||
});
|
});
|
||||||
|
if (data.breadcrumbs.length == 1) {
|
||||||
|
contentHTML += `<li class="link-item" onclick="window.location.href='/search'">🔎 <a href="/search" class="link-link">Suche</a></li>`;
|
||||||
|
}
|
||||||
contentHTML += '</ul>';
|
contentHTML += '</ul>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,73 +8,108 @@
|
|||||||
<meta property="og:image" content="https://app.bethaus-speyer.de/static/icons/logo-200x200.png" />
|
<meta property="og:image" content="https://app.bethaus-speyer.de/static/icons/logo-200x200.png" />
|
||||||
<meta property="og:url" content="https://app.bethaus-speyer.de" />
|
<meta property="og:url" content="https://app.bethaus-speyer.de" />
|
||||||
|
|
||||||
<title>Dateisuche</title>
|
<title>{{ title_short }}</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||||
<meta name="description" content="... uns aber, die wir gerettet werden, ist es eine Gotteskraft.">
|
<meta name="description" content="... uns aber, die wir gerettet werden, ist es eine Gotteskraft.">
|
||||||
<meta name="author" content="Bethaus Speyer">
|
<meta name="author" content="Bethaus Speyer">
|
||||||
<link rel="icon" href="/static/icons/logo-192x192.png" type="image/png" sizes="192x192">
|
<link rel="icon" href="/static/icons/logo-192x192.png" type="image/png" sizes="192x192">
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Web App Manifest -->
|
||||||
|
<link rel="manifest" href="{{ url_for('static', filename='manifest.json') }}">
|
||||||
|
|
||||||
|
<!-- Android Theme Color -->
|
||||||
|
<meta name="theme-color" content="#34495e">
|
||||||
|
|
||||||
|
<!-- Apple-specific tags -->
|
||||||
|
<link rel="touch-icon" href="{{ url_for('static', filename='icons/icon-192x192.png') }}">
|
||||||
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="mobile-web-app-status-bar-style" content="default">
|
||||||
|
<meta name="mobile-web-app-title" content="Gottesdienste">
|
||||||
|
|
||||||
|
<!-- Your CSS -->
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='app.css') }}">
|
||||||
|
|
||||||
<!-- Bootstrap CSS for modern styling -->
|
<!-- Bootstrap CSS for modern styling -->
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='app.css') }}">
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body { background-color: #f8f9fa; }
|
||||||
background-color: #f8f9fa;
|
.site-header { width: 100%; }
|
||||||
}
|
.card { margin-bottom: 20px; }
|
||||||
.search-container {
|
|
||||||
margin-top: 50px;
|
|
||||||
}
|
|
||||||
.card {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="site-header">
|
<header class="site-header">
|
||||||
<a href="#">
|
<a href="/">
|
||||||
<img src="/static/logoW.png" alt="Logo" class="logo">
|
<img src="/static/logoW.png" alt="Logo" class="logo">
|
||||||
</a>
|
</a>
|
||||||
<h1>Suche</h1>
|
<h1>{{ title_long }}</h1>
|
||||||
</header>
|
</header>
|
||||||
<div class="container search-container">
|
<div class="container search-container">
|
||||||
|
<h1>Suche</h1>
|
||||||
<form id="searchForm" method="post" class="mb-4">
|
<form id="searchForm" method="post" class="mb-4">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label for="query" class="form-label">Suchwörter:</label>
|
<label for="query" class="form-label">Suchwörter:</label>
|
||||||
<input type="text" id="query" name="query" class="form-control" required>
|
<input type="text" id="query" name="query" class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Radio Button for Kategorie -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label">Kategorie:</label>
|
||||||
|
<div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<!-- "Alles" is default if nothing is selected -->
|
||||||
|
<input class="form-check-input" type="radio" name="category" id="none" value="" checked>
|
||||||
|
<label class="form-check-label" for="none">Alles</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="category" id="lied" value="Lied">
|
||||||
|
<label class="form-check-label" for="lied">Lied</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="category" id="gedicht" value="Gedicht">
|
||||||
|
<label class="form-check-label" for="gedicht">Gedicht</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="category" id="predigt" value="Predigt">
|
||||||
|
<label class="form-check-label" for="predigt">Predigt</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="category" id="chor" value="chor">
|
||||||
|
<label class="form-check-label" for="chor">Chor</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-check-inline">
|
||||||
|
<input class="form-check-input" type="radio" name="category" id="orchester" value="orchester">
|
||||||
|
<label class="form-check-label" for="orchester">Orchester</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Checkbox for transcript search -->
|
||||||
<div class="form-check mb-3">
|
<div class="form-check mb-3">
|
||||||
<input type="checkbox" class="form-check-input" id="includeTranscript" name="includeTranscript">
|
<input type="checkbox" class="form-check-input" id="includeTranscript" name="includeTranscript">
|
||||||
<label class="form-check-label" for="includeTranscript">Im Transkript suchen</label>
|
<label class="form-check-label" for="includeTranscript">Im Transkript suchen</label>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">Suchen</button>
|
<button type="submit" class="btn btn-primary">Suchen</button>
|
||||||
|
<!-- Clear Button -->
|
||||||
|
<button type="button" id="clearBtn" class="btn btn-secondary ms-2">neue Suche</button>
|
||||||
|
<!-- Back Button -->
|
||||||
|
<button type="button" id="backBtn" class="btn btn-secondary ms-2">zurück</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Container for AJAX-loaded results -->
|
<!-- Container for AJAX-loaded results -->
|
||||||
<div id="results"></div>
|
<div id="results"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.getElementById('searchForm').addEventListener('submit', function(e) {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
e.preventDefault();
|
|
||||||
const query = document.getElementById('query').value.trim();
|
|
||||||
const includeTranscript = document.getElementById('includeTranscript').checked;
|
|
||||||
|
|
||||||
// Prepare form data
|
// Function to render search results from a response object
|
||||||
const formData = new FormData();
|
function renderResults(data) {
|
||||||
formData.append('query', query);
|
|
||||||
formData.append('includeTranscript', includeTranscript);
|
|
||||||
|
|
||||||
fetch('/searchcommand', {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData
|
|
||||||
})
|
|
||||||
.then(response => response.json())
|
|
||||||
.then(data => {
|
|
||||||
const resultsDiv = document.getElementById('results');
|
const resultsDiv = document.getElementById('results');
|
||||||
resultsDiv.innerHTML = ''; // Clear previous results
|
resultsDiv.innerHTML = ''; // Clear previous results
|
||||||
|
|
||||||
if (data.results && data.results.length > 0) {
|
if (data.results && data.results.length > 0) {
|
||||||
data.results.forEach(file => {
|
data.results.forEach(file => {
|
||||||
// Create a card element for each result
|
|
||||||
const card = document.createElement('div');
|
const card = document.createElement('div');
|
||||||
card.className = 'card';
|
card.className = 'card';
|
||||||
card.innerHTML = `
|
card.innerHTML = `
|
||||||
@ -83,7 +118,10 @@ document.getElementById('searchForm').addEventListener('submit', function(e) {
|
|||||||
<a href="/path/${file.relative_path}" target="_blank">${file.filename}</a>
|
<a href="/path/${file.relative_path}" target="_blank">${file.filename}</a>
|
||||||
</h5>
|
</h5>
|
||||||
<h6 class="card-subtitle mb-2 text-muted">${file.relative_path}</h6>
|
<h6 class="card-subtitle mb-2 text-muted">${file.relative_path}</h6>
|
||||||
${file.transcript_hits !== undefined ? `<p class="card-text">Treffer im Transkript: ${file.transcript_hits}</p>` : ''}
|
${ file.transcript_hits !== undefined
|
||||||
|
? `<p class="card-text">Treffer im Transkript: ${file.transcript_hits}</p>`
|
||||||
|
: `<p class="card-text">Downloads: ${file.hitcount}</p>`
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
resultsDiv.appendChild(card);
|
resultsDiv.appendChild(card);
|
||||||
@ -91,10 +129,117 @@ document.getElementById('searchForm').addEventListener('submit', function(e) {
|
|||||||
} else {
|
} else {
|
||||||
resultsDiv.innerHTML = '<p>No results found.</p>';
|
resultsDiv.innerHTML = '<p>No results found.</p>';
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore previous search response if available from localStorage
|
||||||
|
const previousResponse = localStorage.getItem("searchResponse");
|
||||||
|
if (previousResponse) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(previousResponse);
|
||||||
|
renderResults(data);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error parsing searchResponse from localStorage:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore previous search word (Suchwort) if available
|
||||||
|
const previousQuery = localStorage.getItem("searchQuery");
|
||||||
|
if (previousQuery) {
|
||||||
|
document.getElementById('query').value = previousQuery;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore previous selected category if available, otherwise default remains "Alles"
|
||||||
|
const previousCategory = localStorage.getItem("searchCategory");
|
||||||
|
if (previousCategory !== null) {
|
||||||
|
const radio = document.querySelector('input[name="category"][value="' + previousCategory + '"]');
|
||||||
|
if (radio) {
|
||||||
|
radio.checked = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore the checkbox state for "Im Transkript suchen"
|
||||||
|
const previousIncludeTranscript = localStorage.getItem("searchIncludeTranscript");
|
||||||
|
if (previousIncludeTranscript !== null) {
|
||||||
|
document.getElementById('includeTranscript').checked = (previousIncludeTranscript === 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Form submission event
|
||||||
|
document.getElementById('searchForm').addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const query = document.getElementById('query').value.trim();
|
||||||
|
const includeTranscript = document.getElementById('includeTranscript').checked;
|
||||||
|
|
||||||
|
// Get the selected category radio button, if any
|
||||||
|
const categoryRadio = document.querySelector('input[name="category"]:checked');
|
||||||
|
const category = categoryRadio ? categoryRadio.value : '';
|
||||||
|
|
||||||
|
// Prevent accidental re-selection of already selected radio buttons
|
||||||
|
const radios = document.querySelectorAll('input[name="category"]');
|
||||||
|
radios.forEach(radio => {
|
||||||
|
radio.addEventListener('mousedown', function(e) {
|
||||||
|
this.wasChecked = this.checked;
|
||||||
|
});
|
||||||
|
radio.addEventListener('click', function(e) {
|
||||||
|
if (this.wasChecked) {
|
||||||
|
this.checked = false;
|
||||||
|
this.wasChecked = false;
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prepare form data for the fetch request
|
||||||
|
// Send the query and the category as separate parameters.
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('query', query);
|
||||||
|
formData.append('category', category);
|
||||||
|
formData.append('includeTranscript', includeTranscript);
|
||||||
|
|
||||||
|
fetch('/searchcommand', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
})
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
// Render the results
|
||||||
|
renderResults(data);
|
||||||
|
// Store the raw response in localStorage
|
||||||
|
try {
|
||||||
|
localStorage.setItem("searchResponse", JSON.stringify(data));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error saving searchResponse to localStorage:', e);
|
||||||
|
}
|
||||||
|
// Save the search word, selected category, and checkbox state in localStorage
|
||||||
|
localStorage.setItem("searchQuery", query);
|
||||||
|
localStorage.setItem("searchCategory", category);
|
||||||
|
localStorage.setItem("searchIncludeTranscript", includeTranscript);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear button event handler
|
||||||
|
document.getElementById('clearBtn').addEventListener('click', function() {
|
||||||
|
// Remove stored items
|
||||||
|
localStorage.removeItem("searchResponse");
|
||||||
|
localStorage.removeItem("searchQuery");
|
||||||
|
localStorage.removeItem("searchCategory");
|
||||||
|
localStorage.removeItem("searchIncludeTranscript");
|
||||||
|
// Reset form fields to defaults
|
||||||
|
document.getElementById('query').value = '';
|
||||||
|
document.querySelector('input[name="category"][value=""]').checked = true;
|
||||||
|
const otherRadios = document.querySelectorAll('input[name="category"]:not([value=""])');
|
||||||
|
otherRadios.forEach(radio => radio.checked = false);
|
||||||
|
document.getElementById('includeTranscript').checked = false;
|
||||||
|
// Clear the results div
|
||||||
|
document.getElementById('results').innerHTML = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Back button event handler - redirect to the root path
|
||||||
|
document.getElementById('backBtn').addEventListener('click', function() {
|
||||||
|
window.location.href = '/';
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user