getting things ready for release
This commit is contained in:
@@ -22,3 +22,6 @@ tmp/
|
|||||||
# Documentation
|
# Documentation
|
||||||
README.md
|
README.md
|
||||||
LICENSE
|
LICENSE
|
||||||
|
|
||||||
|
postgres/
|
||||||
|
f0ckm-data/
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
|
f0ckm-data/
|
||||||
node_modules/
|
node_modules/
|
||||||
logs/*.log
|
logs/*.log
|
||||||
config.json
|
config.json
|
||||||
@@ -11,3 +12,5 @@ tmp/*
|
|||||||
tools
|
tools
|
||||||
public/a
|
public/a
|
||||||
public/tag_cache
|
public/tag_cache
|
||||||
|
.env
|
||||||
|
config.json
|
||||||
102
build.bash
Normal file
102
build.bash
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# f0ckm build script - compatible version
|
||||||
|
# Targets: master, dev
|
||||||
|
|
||||||
|
# Detect docker compose command
|
||||||
|
if docker compose version >/dev/null 2>&1; then
|
||||||
|
DOCKER_COMPOSE="docker compose"
|
||||||
|
elif docker-compose version >/dev/null 2>&1; then
|
||||||
|
DOCKER_COMPOSE="docker-compose"
|
||||||
|
else
|
||||||
|
echo "Error: Neither 'docker compose' nor 'docker-compose' found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Parse arguments
|
||||||
|
TARGET="master"
|
||||||
|
CACHE_FLAG=""
|
||||||
|
BRANCH_TARGET=""
|
||||||
|
|
||||||
|
for arg in "$@"; do
|
||||||
|
case $arg in
|
||||||
|
master|dev|stg)
|
||||||
|
TARGET=$arg
|
||||||
|
;;
|
||||||
|
--no-cache)
|
||||||
|
CACHE_FLAG="--no-cache"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
if [ -z "$BRANCH_TARGET" ]; then
|
||||||
|
BRANCH_TARGET=$arg
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# Check if we are in a git repo
|
||||||
|
if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
|
||||||
|
echo "Error: not a git repository"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Set Image name and branch defaults
|
||||||
|
IMAGE_NAME="f0ckm"
|
||||||
|
|
||||||
|
if [ "$TARGET" == "master" ]; then
|
||||||
|
echo "--- PRODUCTION DEPLOYMENT (master) ---"
|
||||||
|
BRANCH_TARGET="master"
|
||||||
|
elif [ "$TARGET" == "stg" ]; then
|
||||||
|
echo "--- STAGING DEPLOYMENT ---"
|
||||||
|
BRANCH_TARGET="dev"
|
||||||
|
else
|
||||||
|
echo "--- LOCAL DEVELOPMENT (dev) ---"
|
||||||
|
BRANCH_TARGET="dev"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Checkout branch
|
||||||
|
echo "Ensuring branch: ${BRANCH_TARGET}"
|
||||||
|
git checkout "${BRANCH_TARGET}" 2>/dev/null
|
||||||
|
|
||||||
|
# Pull latest changes
|
||||||
|
echo "Pulling latest changes for branch: $(git rev-parse --abbrev-ref HEAD)"
|
||||||
|
git pull
|
||||||
|
|
||||||
|
# Get git short revision
|
||||||
|
REV=$(git rev-parse --short HEAD)
|
||||||
|
TAG="f0ckm-${TARGET}-${REV}"
|
||||||
|
|
||||||
|
echo "Building image: ${IMAGE_NAME}:${TAG} for environment: ${TARGET} (Cache: ${CACHE_FLAG:-enabled})"
|
||||||
|
|
||||||
|
# Build the image
|
||||||
|
docker build $CACHE_FLAG --build-arg GIT_HASH=${TAG} -f Dockerfile -t ${IMAGE_NAME}:${TAG} .
|
||||||
|
|
||||||
|
# Also tag it as latest
|
||||||
|
echo "Tagging as ${IMAGE_NAME}:latest"
|
||||||
|
docker tag ${IMAGE_NAME}:${TAG} ${IMAGE_NAME}:latest
|
||||||
|
|
||||||
|
# Update .env
|
||||||
|
if grep -q "^F0CKM_TAG=" .env; then
|
||||||
|
sed -i "s/^F0CKM_TAG=.*/F0CKM_TAG=${TAG}/" .env
|
||||||
|
else
|
||||||
|
[ -s .env ] && [ -n "$(tail -c 1 .env)" ] && echo "" >> .env
|
||||||
|
echo "F0CKM_TAG=${TAG}" >> .env
|
||||||
|
fi
|
||||||
|
|
||||||
|
if grep -q "^F0CKM_IMAGE=" .env; then
|
||||||
|
sed -i "s|^F0CKM_IMAGE=.*|F0CKM_IMAGE=${IMAGE_NAME}|" .env
|
||||||
|
else
|
||||||
|
[ -s .env ] && [ -n "$(tail -c 1 .env)" ] && echo "" >> .env
|
||||||
|
echo "F0CKM_IMAGE=${IMAGE_NAME}" >> .env
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Deployment
|
||||||
|
echo "Restarting services with $DOCKER_COMPOSE..."
|
||||||
|
$DOCKER_COMPOSE down
|
||||||
|
$DOCKER_COMPOSE up -d
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
echo "Cleaning up old docker resources..."
|
||||||
|
docker system prune -af
|
||||||
|
|
||||||
|
echo "Successfully updated ${TARGET} with tag ${TAG}"
|
||||||
@@ -46,7 +46,9 @@
|
|||||||
"enable_global_chat": true,
|
"enable_global_chat": true,
|
||||||
"enable_danmaku": true,
|
"enable_danmaku": true,
|
||||||
"private_messages": true,
|
"private_messages": true,
|
||||||
"halls_enabled": true,
|
"halls_enabled": false,
|
||||||
|
"userhalls_enabled": false,
|
||||||
|
"abyss_enabled": false,
|
||||||
"meme_creator": true,
|
"meme_creator": true,
|
||||||
|
|
||||||
"web_url_upload": true,
|
"web_url_upload": true,
|
||||||
@@ -71,7 +73,7 @@
|
|||||||
"enable_autoplay": false,
|
"enable_autoplay": false,
|
||||||
"enable_swiping": true,
|
"enable_swiping": true,
|
||||||
"enable_profile_description": true,
|
"enable_profile_description": true,
|
||||||
"use_ententeich": true,
|
"user_alternative_infobox": false,
|
||||||
"enable_swf": true,
|
"enable_swf": true,
|
||||||
"swf_thumb": "/s/img/swf.png",
|
"swf_thumb": "/s/img/swf.png",
|
||||||
|
|
||||||
|
|||||||
81
docker-compose.yml
Normal file
81
docker-compose.yml
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
services:
|
||||||
|
f0ckm:
|
||||||
|
container_name: f0ckm
|
||||||
|
user: "${UID:-1000}:${GID:-1000}"
|
||||||
|
image: ${f0ckm_IMAGE:-f0ckm}:${f0ckm_TAG:-latest}
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
- GIT_HASH=${GIT_HASH:-unknown}
|
||||||
|
networks:
|
||||||
|
- f0ckm-net
|
||||||
|
volumes:
|
||||||
|
- ./config.json:/opt/f0bm/config.json
|
||||||
|
- ./f0ckm-data/a/:/opt/f0bm/public/a/
|
||||||
|
- ./f0ckm-data/b/:/opt/f0bm/public/b/
|
||||||
|
- ./f0ckm-data/t/:/opt/f0bm/public/t/
|
||||||
|
- ./f0ckm-data/deleted/:/opt/f0bm/deleted/
|
||||||
|
- ./f0ckm-data/pending/:/opt/f0bm/pending/
|
||||||
|
- ./f0ckm-data/emojis/:/opt/f0bm/public/s/emojis/
|
||||||
|
- ./f0ckm-data/memes/:/opt/f0bm/public/memes/
|
||||||
|
- ./f0ckm-data/ca/:/opt/f0bm/public/ca/
|
||||||
|
- ./f0ckm-data/tmp/:/opt/f0bm/tmp/
|
||||||
|
- ./f0ckm-data/logs/:/opt/f0bm/logs/
|
||||||
|
- ./f0ckm-data/tag_cache/:/opt/f0bm/public/tag_cache/
|
||||||
|
- ./f0ckm-data/fonts/:/opt/f0bm/public/s/fonts/
|
||||||
|
- ./f0ckm-data/hall_cache/:/opt/f0bm/public/hall_cache/
|
||||||
|
- ./f0ckm-data/hall_custom/:/opt/f0bm/public/hall_custom/
|
||||||
|
- ./f0ckm-data/manifest.json:/opt/f0bm/public/manifest.json
|
||||||
|
|
||||||
|
environment:
|
||||||
|
- GIT_HASH=${f0ckm_TAG:-unknown}
|
||||||
|
ports:
|
||||||
|
- "1337:1337"
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
f0ckm-db:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
f0ckm-db:
|
||||||
|
container_name: f0ckm-db
|
||||||
|
image: postgres:17
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: f0ckm
|
||||||
|
POSTGRES_USER: f0ckm
|
||||||
|
POSTGRES_PASSWORD: f0ckm
|
||||||
|
PGDATA: /data/postgres
|
||||||
|
volumes:
|
||||||
|
- ./postgres:/data/postgres
|
||||||
|
ports:
|
||||||
|
- "5454:5432"
|
||||||
|
networks:
|
||||||
|
- f0ckm-net
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||||||
|
interval: 3s
|
||||||
|
timeout: 3s
|
||||||
|
retries: 5
|
||||||
|
start_period: 5s
|
||||||
|
start_interval: 1s
|
||||||
|
|
||||||
|
# anubis:
|
||||||
|
# image: ghcr.io/techarohq/anubis:latest
|
||||||
|
# container_name: anubis
|
||||||
|
# ports:
|
||||||
|
# - "3000:3000"
|
||||||
|
# environment:
|
||||||
|
# - BIND=:3000
|
||||||
|
# - TARGET=http://f0ckm:1337
|
||||||
|
# - DIFFICULTY=15
|
||||||
|
# - POLICY_FNAME=/policy.yaml
|
||||||
|
# volumes:
|
||||||
|
# - ./botPolicy.yaml:/policy.yaml:ro
|
||||||
|
# networks:
|
||||||
|
# - f0ckm-net
|
||||||
|
# restart: unless-stopped
|
||||||
|
|
||||||
|
networks:
|
||||||
|
f0ckm-net:
|
||||||
|
driver: bridge
|
||||||
@@ -77,7 +77,7 @@ CREATE FUNCTION public.fill_normalized() RETURNS trigger
|
|||||||
LANGUAGE plpgsql
|
LANGUAGE plpgsql
|
||||||
AS $$
|
AS $$
|
||||||
begin
|
begin
|
||||||
NEW.normalized = slugify(NEW.tag);
|
NEW.normalized = public.slugify(NEW.tag);
|
||||||
return NEW;
|
return NEW;
|
||||||
end$$;
|
end$$;
|
||||||
|
|
||||||
@@ -186,7 +186,7 @@ CREATE FUNCTION public.slugify(v text) RETURNS text
|
|||||||
LANGUAGE plpgsql
|
LANGUAGE plpgsql
|
||||||
AS $$
|
AS $$
|
||||||
BEGIN
|
BEGIN
|
||||||
RETURN trim(BOTH '-' FROM regexp_replace(lower(unaccent(trim(v))), '[\u0000-\u002f \u003a-\u0040\u005b-\u0060\u007b-\u00bf]+', '', 'gi'));
|
RETURN trim(BOTH '-' FROM regexp_replace(lower(public.unaccent(trim(v))), '[\u0000-\u002f \u003a-\u0040\u005b-\u0060\u007b-\u00bf]+', '', 'gi'));
|
||||||
END;
|
END;
|
||||||
$$;
|
$$;
|
||||||
|
|
||||||
@@ -202,9 +202,9 @@ CREATE FUNCTION public.trg_update_xd_score() RETURNS trigger
|
|||||||
AS $$
|
AS $$
|
||||||
BEGIN
|
BEGIN
|
||||||
IF TG_OP = 'DELETE' THEN
|
IF TG_OP = 'DELETE' THEN
|
||||||
PERFORM update_item_xd_score(OLD.item_id);
|
PERFORM public.update_item_xd_score(OLD.item_id);
|
||||||
ELSE
|
ELSE
|
||||||
PERFORM update_item_xd_score(NEW.item_id);
|
PERFORM public.update_item_xd_score(NEW.item_id);
|
||||||
END IF;
|
END IF;
|
||||||
RETURN NULL;
|
RETURN NULL;
|
||||||
END;
|
END;
|
||||||
@@ -222,7 +222,7 @@ CREATE FUNCTION public.unaccent_text(text) RETURNS text
|
|||||||
AS $_$
|
AS $_$
|
||||||
-- unaccent is STABLE, but the indexes must use IMMUTABLE functions.
|
-- unaccent is STABLE, but the indexes must use IMMUTABLE functions.
|
||||||
-- comment this line out when calling pg_dump.
|
-- comment this line out when calling pg_dump.
|
||||||
SELECT unaccent($1);
|
SELECT public.unaccent($1);
|
||||||
|
|
||||||
-- Uncomment this line when calling pg_dump.
|
-- Uncomment this line when calling pg_dump.
|
||||||
--SELECT ''::text;
|
--SELECT ''::text;
|
||||||
@@ -1171,7 +1171,8 @@ CREATE TABLE public.user_options (
|
|||||||
quote_emojis boolean DEFAULT true NOT NULL,
|
quote_emojis boolean DEFAULT true NOT NULL,
|
||||||
embed_youtube_in_comments boolean DEFAULT true NOT NULL,
|
embed_youtube_in_comments boolean DEFAULT true NOT NULL,
|
||||||
hide_koepfe boolean DEFAULT false NOT NULL,
|
hide_koepfe boolean DEFAULT false NOT NULL,
|
||||||
language text
|
language text,
|
||||||
|
use_alternative_infobox boolean DEFAULT false
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
@@ -2426,3 +2427,46 @@ GRANT ALL ON SCHEMA public TO PUBLIC;
|
|||||||
|
|
||||||
\unrestrict ifoUZevi3oYdI7OmgFxUaco0kNV6kdlFS55QWa8PuaWXA3AY2nPUcs8ekmXvMEU
|
\unrestrict ifoUZevi3oYdI7OmgFxUaco0kNV6kdlFS55QWa8PuaWXA3AY2nPUcs8ekmXvMEU
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Data for Name: site_settings; Type: TABLE DATA; Schema: public; Owner: f0ckm
|
||||||
|
--
|
||||||
|
|
||||||
|
INSERT INTO public.site_settings (key, value) VALUES
|
||||||
|
('motd', 'Hello World!'),
|
||||||
|
('manual_approval', 'true'),
|
||||||
|
('min_tags', '1'),
|
||||||
|
('registration_open', 'false'),
|
||||||
|
('trusted_uploads', '0'),
|
||||||
|
('about_text', 'About'),
|
||||||
|
('rules_text', 'foobar'),
|
||||||
|
('terms_text', 'baz')
|
||||||
|
ON CONFLICT (key) DO NOTHING;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Data for Name: halls; Type: TABLE DATA; Schema: public; Owner: f0ckm
|
||||||
|
--
|
||||||
|
|
||||||
|
INSERT INTO public.halls (name, slug, rating) VALUES
|
||||||
|
('SFW', 'sfw', 'sfw'),
|
||||||
|
('NSFW', 'nsfw', 'nsfw')
|
||||||
|
ON CONFLICT (slug) DO NOTHING;
|
||||||
|
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Core Tags Data
|
||||||
|
--
|
||||||
|
|
||||||
|
INSERT INTO public.tags (id, tag, normalized) VALUES
|
||||||
|
(1, 'sfw', 'sfw'),
|
||||||
|
(2, 'nsfw', 'nsfw'),
|
||||||
|
(3, 'nsfp', 'nsfp'),
|
||||||
|
(4, 'nsfl', 'nsfl')
|
||||||
|
ON CONFLICT (id) DO UPDATE SET tag = EXCLUDED.tag, normalized = EXCLUDED.normalized;
|
||||||
|
|
||||||
|
-- Ensure nsfp filter table has the correct IDs
|
||||||
|
INSERT INTO public.tags_nsfp (id) VALUES (3), (4)
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
-- Fix sequence
|
||||||
|
SELECT setval('public.tags_id_seq', (SELECT MAX(id) FROM public.tags));
|
||||||
|
|||||||
@@ -1,14 +1,5 @@
|
|||||||
/* =============================================
|
/* f0ckwork beyond what was ever imagined, spiced up with 3928484 spices */
|
||||||
f0ckm.css - Unified Stylesheet
|
|
||||||
Merged: f0ck.css + w0bm.css + view styles
|
|
||||||
Born anew => f0ckm.css
|
|
||||||
============================================= */
|
|
||||||
|
|
||||||
/* f0ckwork omega */
|
|
||||||
/* written by sirx for f0ck.me */
|
|
||||||
/* use whatever you like */
|
|
||||||
/* once upon a time this was a stiefelstrapse! but no more! */
|
/* once upon a time this was a stiefelstrapse! but no more! */
|
||||||
/* Licensed under wtfpl */
|
|
||||||
|
|
||||||
html[theme='f0ck'] {
|
html[theme='f0ck'] {
|
||||||
--accent: #9f0;
|
--accent: #9f0;
|
||||||
@@ -1445,7 +1436,7 @@ body.sidebar-right-hidden .global-sidebar-right {
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.layout-legacy .item-layout-container .ententeich-block {
|
body.layout-legacy .item-layout-container .user-infobox-block {
|
||||||
width: 800px;
|
width: 800px;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
@@ -10967,15 +10958,15 @@ textarea#profile_description {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* =============================================
|
/* =============================================
|
||||||
ENTENTEICH PROFILE STYLES
|
USER ALTERNATIVE INFOBOX STYLES
|
||||||
============================================= */
|
============================================= */
|
||||||
|
|
||||||
/* When ENTEN profile is active, expand .blahlol to full metadata width */
|
/* When user infobox is active, expand .blahlol to full metadata width */
|
||||||
.blahlol:has(.ententeich-block) {
|
.blahlol:has(.user-infobox-block) {
|
||||||
grid-column: 1 / -1;
|
grid-column: 1 / -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ententeich-block {
|
.user-infobox-block {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
background: rgba(0,0,0,0.2);
|
background: rgba(0,0,0,0.2);
|
||||||
@@ -10990,43 +10981,43 @@ textarea#profile_description {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.enten-avatar {
|
.user-infobox-avatar {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.enten-avatar img {
|
.user-infobox-avatar img {
|
||||||
width: 64px;
|
width: 64px;
|
||||||
height: 64px;
|
height: 64px;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.enten-info {
|
.user-infobox-info {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.enten-header {
|
.user-infobox-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.enten-username {
|
.user-infobox-username {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: var(--author-accent, inherit) !important;
|
color: var(--author-accent, inherit) !important;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.enten-timestamp {
|
.user-infobox-timestamp {
|
||||||
font-size: 0.8em;
|
font-size: 0.8em;
|
||||||
color: #888;
|
color: #888;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.enten-description {
|
.user-infobox-description {
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
|
|||||||
@@ -13,10 +13,10 @@
|
|||||||
"mod": "mod",
|
"mod": "mod",
|
||||||
"settings": "Einstellungen",
|
"settings": "Einstellungen",
|
||||||
"logout": "Abmelden",
|
"logout": "Abmelden",
|
||||||
"notifications": "Benachrichtigungen",
|
"notifications": "Nuttis",
|
||||||
"mark_all_read": "Alle als gelesen markieren",
|
"mark_all_read": "Alle als gelesen markieren",
|
||||||
"no_notifications": "Keine neuen Benachrichtigungen",
|
"no_notifications": "Keine neuen Nuttis",
|
||||||
"view_all_notifications": "Alle anzeigen",
|
"view_all_notifications": "Alle Nuttis anzeigen",
|
||||||
"notif_tab_user": "Benutzer",
|
"notif_tab_user": "Benutzer",
|
||||||
"notif_tab_system": "System",
|
"notif_tab_system": "System",
|
||||||
"manage_subscriptions": "Abonnements verwalten",
|
"manage_subscriptions": "Abonnements verwalten",
|
||||||
@@ -127,6 +127,8 @@
|
|||||||
"show_motd": "Nachricht des Tages (MOTD) anzeigen",
|
"show_motd": "Nachricht des Tages (MOTD) anzeigen",
|
||||||
"modern_layout": "Modernes Layout",
|
"modern_layout": "Modernes Layout",
|
||||||
"modern_layout_hint": "3-Spalten-Layout",
|
"modern_layout_hint": "3-Spalten-Layout",
|
||||||
|
"alternative_infobox": "Alternativer Autor-Infoblock",
|
||||||
|
"alternative_infobox_hint": "Zeigt einen erweiterten Autor-Block mit Avatar und Bio auf Beitragsseiten",
|
||||||
"disable_autoplay": "Automatische Wiedergabe deaktivieren",
|
"disable_autoplay": "Automatische Wiedergabe deaktivieren",
|
||||||
"disable_autoplay_hint": "Verhindert die automatische Wiedergabe von Videos und Audio",
|
"disable_autoplay_hint": "Verhindert die automatische Wiedergabe von Videos und Audio",
|
||||||
"disable_swiping": "Wischen deaktivieren",
|
"disable_swiping": "Wischen deaktivieren",
|
||||||
@@ -267,7 +269,7 @@
|
|||||||
},
|
},
|
||||||
"comments": {
|
"comments": {
|
||||||
"write_comment": "Kommentar schreiben...",
|
"write_comment": "Kommentar schreiben...",
|
||||||
"post": "Senden",
|
"post": "Abschnalzen",
|
||||||
"cancel": "Abbrechen"
|
"cancel": "Abbrechen"
|
||||||
},
|
},
|
||||||
"upload_btn": {
|
"upload_btn": {
|
||||||
@@ -416,7 +418,7 @@
|
|||||||
"loading": "Gespräche werden geladen…",
|
"loading": "Gespräche werden geladen…",
|
||||||
"decrypting": "Nachrichten werden entschlüsselt…",
|
"decrypting": "Nachrichten werden entschlüsselt…",
|
||||||
"input_placeholder": "Nachricht schreiben…",
|
"input_placeholder": "Nachricht schreiben…",
|
||||||
"send": "Senden"
|
"send": "Abschnalzen"
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"message_btn": "Nachricht",
|
"message_btn": "Nachricht",
|
||||||
|
|||||||
@@ -127,6 +127,8 @@
|
|||||||
"show_motd": "Show Message of the Day (MOTD)",
|
"show_motd": "Show Message of the Day (MOTD)",
|
||||||
"modern_layout": "Modern layout",
|
"modern_layout": "Modern layout",
|
||||||
"modern_layout_hint": "3 Column Layout",
|
"modern_layout_hint": "3 Column Layout",
|
||||||
|
"alternative_infobox": "Alternative Author Infobox",
|
||||||
|
"alternative_infobox_hint": "Show a rich author card with avatar and bio on item pages",
|
||||||
"disable_autoplay": "Disable Autoplay",
|
"disable_autoplay": "Disable Autoplay",
|
||||||
"disable_autoplay_hint": "Prevent videos and audio from playing automatically",
|
"disable_autoplay_hint": "Prevent videos and audio from playing automatically",
|
||||||
"disable_swiping": "Disable Swiping",
|
"disable_swiping": "Disable Swiping",
|
||||||
|
|||||||
@@ -127,6 +127,8 @@
|
|||||||
"show_motd": "Toon Bericht van de Dag (MOTD)",
|
"show_motd": "Toon Bericht van de Dag (MOTD)",
|
||||||
"modern_layout": "Moderne layout",
|
"modern_layout": "Moderne layout",
|
||||||
"modern_layout_hint": "Indeling met 3 kolommen",
|
"modern_layout_hint": "Indeling met 3 kolommen",
|
||||||
|
"alternative_infobox": "Alternatief auteur-informatievak",
|
||||||
|
"alternative_infobox_hint": "Toont een uitgebreide auteurkaart met avatar en bio op itempagina's",
|
||||||
"disable_autoplay": "Automatisch afspelen uitschakelen",
|
"disable_autoplay": "Automatisch afspelen uitschakelen",
|
||||||
"disable_autoplay_hint": "Voorkomen dat video's en audio automatisch worden afgespeeld",
|
"disable_autoplay_hint": "Voorkomen dat video's en audio automatisch worden afgespeeld",
|
||||||
"disable_swiping": "Swipen uitschakelen",
|
"disable_swiping": "Swipen uitschakelen",
|
||||||
|
|||||||
@@ -127,6 +127,8 @@
|
|||||||
"show_motd": "Nachricht des Tages (NdT) anzeigen",
|
"show_motd": "Nachricht des Tages (NdT) anzeigen",
|
||||||
"modern_layout": "Modernes Layout",
|
"modern_layout": "Modernes Layout",
|
||||||
"modern_layout_hint": "3-Spalten-Layout",
|
"modern_layout_hint": "3-Spalten-Layout",
|
||||||
|
"alternative_infobox": "Alternativer Autor-Infoblock",
|
||||||
|
"alternative_infobox_hint": "Zeigt einen erweiterten Autor-Block mit Avatar und Bio auf Beitragsseiten",
|
||||||
"disable_autoplay": "Automatische Wiedergabe deaktivieren",
|
"disable_autoplay": "Automatische Wiedergabe deaktivieren",
|
||||||
"disable_autoplay_hint": "Vermeiden Sie das automatische Abspielen von Videos und Tondateien",
|
"disable_autoplay_hint": "Vermeiden Sie das automatische Abspielen von Videos und Tondateien",
|
||||||
"disable_swiping": "Wischen deaktivieren",
|
"disable_swiping": "Wischen deaktivieren",
|
||||||
|
|||||||
@@ -593,6 +593,23 @@ export default router => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update alternative infobox preference (per-user toggle for the rich author block)
|
||||||
|
group.put(/\/alternative_infobox/, lib.loggedin, async (req, res) => {
|
||||||
|
const use_alternative_infobox = req.post.use_alternative_infobox === true || req.post.use_alternative_infobox === 'true';
|
||||||
|
try {
|
||||||
|
await db`
|
||||||
|
update user_options
|
||||||
|
set use_alternative_infobox = ${use_alternative_infobox}
|
||||||
|
where user_id = ${+req.session.id}
|
||||||
|
`;
|
||||||
|
if (req.session) req.session.use_alternative_infobox = use_alternative_infobox;
|
||||||
|
return res.json({ success: true, use_alternative_infobox }, 200);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Update alternative_infobox error:', e);
|
||||||
|
return res.json({ success: false, msg: 'Error updating preference' }, 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Update per-user language preference
|
// Update per-user language preference
|
||||||
group.put(/\/language/, lib.loggedin, async (req, res) => {
|
group.put(/\/language/, lib.loggedin, async (req, res) => {
|
||||||
if (cfg.websrv.allow_language_change === false) {
|
if (cfg.websrv.allow_language_change === false) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import fs from "fs/promises";
|
|||||||
export default (router, tpl) => {
|
export default (router, tpl) => {
|
||||||
// Main Halls Overview
|
// Main Halls Overview
|
||||||
router.get(/^\/halls$/, async (req, res) => {
|
router.get(/^\/halls$/, async (req, res) => {
|
||||||
|
if (cfg.websrv.halls_enabled === false) return res.reply({ code: 404, body: tpl.render('error', { message: 'Not found', tmp: null }, req) });
|
||||||
const mode = req.mode ?? 0;
|
const mode = req.mode ?? 0;
|
||||||
const excludedTags = req.session ? (req.session.excluded_tags || []) : [];
|
const excludedTags = req.session ? (req.session.excluded_tags || []) : [];
|
||||||
|
|
||||||
|
|||||||
@@ -202,6 +202,11 @@ export default (router, tpl) => {
|
|||||||
const tRouteStart = Date.now();
|
const tRouteStart = Date.now();
|
||||||
const mode = req.params.itemid ? 'item' : 'index';
|
const mode = req.params.itemid ? 'item' : 'index';
|
||||||
|
|
||||||
|
// Feature flag guards for disabled features
|
||||||
|
if (cfg.websrv.halls_enabled === false && req.params.hall) {
|
||||||
|
return res.reply({ code: 404, body: tpl.render('error', { message: 'Not found', tmp: null }, req) });
|
||||||
|
}
|
||||||
|
|
||||||
// Auto-persist strict mode from URL to session if it's there
|
// Auto-persist strict mode from URL to session if it's there
|
||||||
if (req.session && (req.query?.strict !== undefined || req.url.qs?.strict !== undefined)) {
|
if (req.session && (req.query?.strict !== undefined || req.url.qs?.strict !== undefined)) {
|
||||||
req.session.strict_mode = (req.query?.strict === '1' || req.url.qs?.strict === '1');
|
req.session.strict_mode = (req.query?.strict === '1' || req.url.qs?.strict === '1');
|
||||||
@@ -341,6 +346,8 @@ export default (router, tpl) => {
|
|||||||
data.current_hall_slug = (data.tmp && data.tmp.hall && typeof data.tmp.hall === 'object') ? data.tmp.hall.slug : (data.tmp && data.tmp.hall ? data.tmp.hall : '');
|
data.current_hall_slug = (data.tmp && data.tmp.hall && typeof data.tmp.hall === 'object') ? data.tmp.hall.slug : (data.tmp && data.tmp.hall ? data.tmp.hall : '');
|
||||||
data.current_user_hall_slug = (data.tmp && data.tmp.userHall && typeof data.tmp.userHall === 'object') ? data.tmp.userHall.slug : (data.tmp && data.tmp.userHall ? data.tmp.userHall : '');
|
data.current_user_hall_slug = (data.tmp && data.tmp.userHall && typeof data.tmp.userHall === 'object') ? data.tmp.userHall.slug : (data.tmp && data.tmp.userHall ? data.tmp.userHall : '');
|
||||||
data.current_user_hall_owner = (data.tmp && data.tmp.userHallOwner) ? data.tmp.userHallOwner : '';
|
data.current_user_hall_owner = (data.tmp && data.tmp.userHallOwner) ? data.tmp.userHallOwner : '';
|
||||||
|
// Per-user alternative infobox preference overrides the site-wide config default
|
||||||
|
if (session) data.user_alternative_infobox = !!session.use_alternative_infobox;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import lib from "../lib.mjs";
|
|||||||
export default (router, tpl) => {
|
export default (router, tpl) => {
|
||||||
// Serve the scroller page
|
// Serve the scroller page
|
||||||
router.get(/^\/abyss\/?$/, async (req, res) => {
|
router.get(/^\/abyss\/?$/, async (req, res) => {
|
||||||
|
if (cfg.websrv.abyss_enabled === false) return res.reply({ code: 404, body: tpl.render('error', { message: 'Not found', tmp: null }, req) });
|
||||||
if (cfg.websrv.private_society && !req.session) {
|
if (cfg.websrv.private_society && !req.session) {
|
||||||
return res.reply({ code: 502, body: '<html><body>502 Bad Gateway</body></html>' });
|
return res.reply({ code: 502, body: '<html><body>502 Bad Gateway</body></html>' });
|
||||||
}
|
}
|
||||||
@@ -26,6 +27,7 @@ export default (router, tpl) => {
|
|||||||
// Lightweight meta refresh — returns live counts + tags for a batch of item IDs
|
// Lightweight meta refresh — returns live counts + tags for a batch of item IDs
|
||||||
// GET /api/v2/scroller/meta?ids=1,2,3
|
// GET /api/v2/scroller/meta?ids=1,2,3
|
||||||
router.get(/^\/api\/v2\/scroller\/meta\/?$/, async (req, res) => {
|
router.get(/^\/api\/v2\/scroller\/meta\/?$/, async (req, res) => {
|
||||||
|
if (cfg.websrv.abyss_enabled === false) return res.reply({ code: 404, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ success: false }) });
|
||||||
if (cfg.websrv.private_society && !req.session) {
|
if (cfg.websrv.private_society && !req.session) {
|
||||||
return res.reply({ code: 502, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}) });
|
return res.reply({ code: 502, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}) });
|
||||||
}
|
}
|
||||||
@@ -66,6 +68,7 @@ export default (router, tpl) => {
|
|||||||
|
|
||||||
// Tag autocomplete endpoint
|
// Tag autocomplete endpoint
|
||||||
router.get(/^\/api\/v2\/scroller\/tags\/?$/, async (req, res) => {
|
router.get(/^\/api\/v2\/scroller\/tags\/?$/, async (req, res) => {
|
||||||
|
if (cfg.websrv.abyss_enabled === false) return res.reply({ code: 404, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify([]) });
|
||||||
if (cfg.websrv.private_society && !req.session) {
|
if (cfg.websrv.private_society && !req.session) {
|
||||||
return res.reply({ code: 502, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify([]) });
|
return res.reply({ code: 502, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify([]) });
|
||||||
}
|
}
|
||||||
@@ -97,6 +100,11 @@ export default (router, tpl) => {
|
|||||||
|
|
||||||
// JSON API: returns a batch of items for the scroller
|
// JSON API: returns a batch of items for the scroller
|
||||||
router.get(/^\/api\/v2\/scroller\/feed\/?$/, async (req, res) => {
|
router.get(/^\/api\/v2\/scroller\/feed\/?$/, async (req, res) => {
|
||||||
|
if (cfg.websrv.abyss_enabled === false) return res.reply({
|
||||||
|
code: 404,
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ success: false, items: [] })
|
||||||
|
});
|
||||||
if (cfg.websrv.private_society && !req.session) {
|
if (cfg.websrv.private_society && !req.session) {
|
||||||
return res.reply({
|
return res.reply({
|
||||||
code: 502,
|
code: 502,
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ export default (router, tpl) => {
|
|||||||
|
|
||||||
// List halls for a user
|
// List halls for a user
|
||||||
router.get(/^\/user\/(?<owner>[^/]+)\/halls\/?$/, async (req, res) => {
|
router.get(/^\/user\/(?<owner>[^/]+)\/halls\/?$/, async (req, res) => {
|
||||||
|
if (cfg.websrv.userhalls_enabled === false) return res.reply({ code: 404, body: tpl.render('error', { message: 'Not found', tmp: null }, req) });
|
||||||
const ownerName = decodeURIComponent(req.params.owner);
|
const ownerName = decodeURIComponent(req.params.owner);
|
||||||
const mode = req.mode ?? 0;
|
const mode = req.mode ?? 0;
|
||||||
const excludedTags = req.session ? (req.session.excluded_tags || []) : [];
|
const excludedTags = req.session ? (req.session.excluded_tags || []) : [];
|
||||||
@@ -79,6 +80,7 @@ export default (router, tpl) => {
|
|||||||
|
|
||||||
// Item grid for a user hall
|
// Item grid for a user hall
|
||||||
router.get(/^\/user\/(?<owner>[^/]+)\/hall\/(?<slug>[^/]+)(?:\/p\/(?<page>\d+))?\/?$/, async (req, res) => {
|
router.get(/^\/user\/(?<owner>[^/]+)\/hall\/(?<slug>[^/]+)(?:\/p\/(?<page>\d+))?\/?$/, async (req, res) => {
|
||||||
|
if (cfg.websrv.userhalls_enabled === false) return res.reply({ code: 404, body: tpl.render('error', { message: 'Not found', tmp: null }, req) });
|
||||||
const ownerName = decodeURIComponent(req.params.owner);
|
const ownerName = decodeURIComponent(req.params.owner);
|
||||||
const slug = decodeURIComponent(req.params.slug);
|
const slug = decodeURIComponent(req.params.slug);
|
||||||
|
|
||||||
@@ -124,6 +126,7 @@ export default (router, tpl) => {
|
|||||||
|
|
||||||
// Single item within a user hall
|
// Single item within a user hall
|
||||||
router.get(/^\/user\/(?<owner>[^/]+)\/hall\/(?<slug>[^/]+)\/(?<itemid>\d+)\/?$/, async (req, res) => {
|
router.get(/^\/user\/(?<owner>[^/]+)\/hall\/(?<slug>[^/]+)\/(?<itemid>\d+)\/?$/, async (req, res) => {
|
||||||
|
if (cfg.websrv.userhalls_enabled === false) return res.reply({ code: 404, body: tpl.render('error', { message: 'Not found', tmp: null }, req) });
|
||||||
const ownerName = decodeURIComponent(req.params.owner);
|
const ownerName = decodeURIComponent(req.params.owner);
|
||||||
const slug = decodeURIComponent(req.params.slug);
|
const slug = decodeURIComponent(req.params.slug);
|
||||||
|
|
||||||
@@ -249,6 +252,7 @@ export default (router, tpl) => {
|
|||||||
|
|
||||||
// ── API: list own halls (for modal) ────────────────────────────────────────
|
// ── API: list own halls (for modal) ────────────────────────────────────────
|
||||||
router.get(/^\/api\/v2\/me\/halls\/?$/, async (req, res) => {
|
router.get(/^\/api\/v2\/me\/halls\/?$/, async (req, res) => {
|
||||||
|
if (cfg.websrv.userhalls_enabled === false) return res.writeHead(404, { 'Content-Type': 'application/json' }).end(JSON.stringify({ success: false }));
|
||||||
if (!requireLogin(req, res)) return;
|
if (!requireLogin(req, res)) return;
|
||||||
try {
|
try {
|
||||||
const halls = await f0cklib.getUserHalls(req.session.id, 3, [], req.session.id);
|
const halls = await f0cklib.getUserHalls(req.session.id, 3, [], req.session.id);
|
||||||
@@ -261,6 +265,7 @@ export default (router, tpl) => {
|
|||||||
|
|
||||||
// ── API: create hall ────────────────────────────────────────────────────────
|
// ── API: create hall ────────────────────────────────────────────────────────
|
||||||
router.post(/^\/api\/v2\/me\/halls\/?$/, async (req, res) => {
|
router.post(/^\/api\/v2\/me\/halls\/?$/, async (req, res) => {
|
||||||
|
if (cfg.websrv.userhalls_enabled === false) return res.writeHead(404, { 'Content-Type': 'application/json' }).end(JSON.stringify({ success: false }));
|
||||||
if (!requireLogin(req, res)) return;
|
if (!requireLogin(req, res)) return;
|
||||||
const name = (req.post.name || '').trim();
|
const name = (req.post.name || '').trim();
|
||||||
const slug = slugify(req.post.slug || name);
|
const slug = slugify(req.post.slug || name);
|
||||||
@@ -279,6 +284,7 @@ export default (router, tpl) => {
|
|||||||
|
|
||||||
// ── API: update hall ────────────────────────────────────────────────────────
|
// ── API: update hall ────────────────────────────────────────────────────────
|
||||||
router.patch(/^\/api\/v2\/me\/halls\/(?<slug>[^/]+)\/?$/, async (req, res) => {
|
router.patch(/^\/api\/v2\/me\/halls\/(?<slug>[^/]+)\/?$/, async (req, res) => {
|
||||||
|
if (cfg.websrv.userhalls_enabled === false) return res.writeHead(404, { 'Content-Type': 'application/json' }).end(JSON.stringify({ success: false }));
|
||||||
if (!requireLogin(req, res)) return;
|
if (!requireLogin(req, res)) return;
|
||||||
const slug = decodeURIComponent(req.params.slug);
|
const slug = decodeURIComponent(req.params.slug);
|
||||||
const { name, slug: newSlugRaw, description, is_private } = req.post;
|
const { name, slug: newSlugRaw, description, is_private } = req.post;
|
||||||
@@ -297,6 +303,7 @@ export default (router, tpl) => {
|
|||||||
|
|
||||||
// ── API: delete hall ────────────────────────────────────────────────────────
|
// ── API: delete hall ────────────────────────────────────────────────────────
|
||||||
router.delete(/^\/api\/v2\/me\/halls\/(?<slug>[^/]+)\/?$/, async (req, res) => {
|
router.delete(/^\/api\/v2\/me\/halls\/(?<slug>[^/]+)\/?$/, async (req, res) => {
|
||||||
|
if (cfg.websrv.userhalls_enabled === false) return res.writeHead(404, { 'Content-Type': 'application/json' }).end(JSON.stringify({ success: false }));
|
||||||
if (!requireLogin(req, res)) return;
|
if (!requireLogin(req, res)) return;
|
||||||
const slug = decodeURIComponent(req.params.slug);
|
const slug = decodeURIComponent(req.params.slug);
|
||||||
|
|
||||||
|
|||||||
@@ -134,8 +134,15 @@ process.on('uncaughtException', err => {
|
|||||||
self._trigger.set(trigger.name, new self.trigger(trigger));
|
self._trigger.set(trigger.name, new self.trigger(trigger));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Initial halls cache
|
// Initial halls cache (only if halls are enabled)
|
||||||
|
if (cfg.websrv.halls_enabled !== false) {
|
||||||
await updateHallsCache();
|
await updateHallsCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log feature flags
|
||||||
|
console.log(`[BOOT] Halls: ${cfg.websrv.halls_enabled !== false ? 'ENABLED' : 'DISABLED'}`);
|
||||||
|
console.log(`[BOOT] UserHalls: ${cfg.websrv.userhalls_enabled !== false ? 'ENABLED' : 'DISABLED'}`);
|
||||||
|
console.log(`[BOOT] Abyss: ${cfg.websrv.abyss_enabled !== false ? 'ENABLED' : 'DISABLED'}`);
|
||||||
|
|
||||||
//console.timeEnd("loading");
|
//console.timeEnd("loading");
|
||||||
|
|
||||||
@@ -227,7 +234,7 @@ process.on('uncaughtException', err => {
|
|||||||
|
|
||||||
if (req.cookies.session) {
|
if (req.cookies.session) {
|
||||||
const user = await db`
|
const user = await db`
|
||||||
select "user".id, "user".login, "user".user, "user".admin, "user".is_moderator, "user".banned, "user".ban_reason, "user".ban_expires, "user".force_password_change, "user_sessions".id as sess_id, "user_sessions".csrf_token, "user_options".mode, "user_options".theme, "user_options".fullscreen, "user_options".excluded_tags, "user_options".avatar, "user_options".avatar_file, "user_options".show_motd, "user_options".strict_mode, "user_options".show_background, "user_options".use_new_layout, "user_options".username_color, "user_options".font, "user_options".disable_autoplay, "user_options".disable_swiping, "user_options".description, "user_options".display_name, COALESCE("user_options".min_xd_score, 0) as min_xd_score, "user_options".ruffle_volume, "user_options".ruffle_background, "user_options".quote_emojis, "user_options".embed_youtube_in_comments, "user_options".hide_koepfe, "user_options".language
|
select "user".id, "user".login, "user".user, "user".admin, "user".is_moderator, "user".banned, "user".ban_reason, "user".ban_expires, "user".force_password_change, "user_sessions".id as sess_id, "user_sessions".csrf_token, "user_options".mode, "user_options".theme, "user_options".fullscreen, "user_options".excluded_tags, "user_options".avatar, "user_options".avatar_file, "user_options".show_motd, "user_options".strict_mode, "user_options".show_background, "user_options".use_new_layout, "user_options".username_color, "user_options".font, "user_options".disable_autoplay, "user_options".disable_swiping, "user_options".description, "user_options".display_name, COALESCE("user_options".min_xd_score, 0) as min_xd_score, "user_options".ruffle_volume, "user_options".ruffle_background, "user_options".quote_emojis, "user_options".embed_youtube_in_comments, "user_options".hide_koepfe, "user_options".language, COALESCE("user_options".use_alternative_infobox, false) as use_alternative_infobox
|
||||||
from "user_sessions"
|
from "user_sessions"
|
||||||
left join "user" on "user".id = "user_sessions".user_id
|
left join "user" on "user".id = "user_sessions".user_id
|
||||||
left join "user_options" on "user_options".user_id = "user_sessions".user_id
|
left join "user_options" on "user_options".user_id = "user_sessions".user_id
|
||||||
@@ -416,9 +423,9 @@ process.on('uncaughtException', err => {
|
|||||||
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) return;
|
if (['GET', 'HEAD', 'OPTIONS'].includes(req.method)) return;
|
||||||
if (['/login', '/register', '/api/v2/upload', '/api/v2/settings/uploadAvatar', '/api/v2/admin/memes', '/api/v2/admin/emojis', '/api/v2/meta/extract-file', '/api/v2/meta/strip-gps', '/api/v2/scroller/external/rehost-meta'].includes(req.url.pathname)) return;
|
if (['/login', '/register', '/api/v2/upload', '/api/v2/settings/uploadAvatar', '/api/v2/admin/memes', '/api/v2/admin/emojis', '/api/v2/meta/extract-file', '/api/v2/meta/strip-gps', '/api/v2/scroller/external/rehost-meta'].includes(req.url.pathname)) return;
|
||||||
// Hall manager routes are handled by bypass middleware with their own session auth
|
// Hall manager routes are handled by bypass middleware with their own session auth
|
||||||
if (req.url.pathname.match(/^\/api\/v2\/admin\/halls(\/|$)/)) return;
|
if (cfg.websrv.halls_enabled !== false && req.url.pathname.match(/^\/api\/v2\/admin\/halls(\/|$)/)) return;
|
||||||
// User hall image upload is handled by bypass middleware below
|
// User hall image upload is handled by bypass middleware below
|
||||||
if (req.url.pathname.match(/^\/api\/v2\/me\/halls\/[^/]+\/image$/)) return;
|
if (cfg.websrv.userhalls_enabled !== false && req.url.pathname.match(/^\/api\/v2\/me\/halls\/[^/]+\/image$/)) return;
|
||||||
if (!validateCsrf(req, res)) return;
|
if (!validateCsrf(req, res)) return;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -484,6 +491,7 @@ process.on('uncaughtException', err => {
|
|||||||
|
|
||||||
// Bypass middleware for hall image uploads (multipart — needs raw body)
|
// Bypass middleware for hall image uploads (multipart — needs raw body)
|
||||||
app.use(async (req, res) => {
|
app.use(async (req, res) => {
|
||||||
|
if (cfg.websrv.halls_enabled === false) return;
|
||||||
const hallImgMatch = req.url.pathname.match(/^\/api\/v2\/admin\/halls\/([^/]+)\/image$/);
|
const hallImgMatch = req.url.pathname.match(/^\/api\/v2\/admin\/halls\/([^/]+)\/image$/);
|
||||||
if (hallImgMatch) {
|
if (hallImgMatch) {
|
||||||
console.error('[BOOT] [HALL BYPASS] Image path hit:', req.method, req.url.pathname, 'cookies:', JSON.stringify(Object.keys(req.cookies || {})));
|
console.error('[BOOT] [HALL BYPASS] Image path hit:', req.method, req.url.pathname, 'cookies:', JSON.stringify(Object.keys(req.cookies || {})));
|
||||||
@@ -518,6 +526,7 @@ process.on('uncaughtException', err => {
|
|||||||
|
|
||||||
// Bypass middleware for user hall image uploads (multipart — raw body needed)
|
// Bypass middleware for user hall image uploads (multipart — raw body needed)
|
||||||
app.use(async (req, res) => {
|
app.use(async (req, res) => {
|
||||||
|
if (cfg.websrv.userhalls_enabled === false) return;
|
||||||
const userHallImgMatch = req.url.pathname.match(/^\/api\/v2\/me\/halls\/([^/]+)\/image$/);
|
const userHallImgMatch = req.url.pathname.match(/^\/api\/v2\/me\/halls\/([^/]+)\/image$/);
|
||||||
if (userHallImgMatch && req.method === 'POST') {
|
if (userHallImgMatch && req.method === 'POST') {
|
||||||
console.error('[BOOT] [USER_HALL BYPASS] Image upload:', req.url.pathname);
|
console.error('[BOOT] [USER_HALL BYPASS] Image upload:', req.url.pathname);
|
||||||
@@ -701,6 +710,9 @@ process.on('uncaughtException', err => {
|
|||||||
get rules_text() { return getRulesText(); },
|
get rules_text() { return getRulesText(); },
|
||||||
get terms_text() { return getTermsText(); },
|
get terms_text() { return getTermsText(); },
|
||||||
get halls() { return getHalls(); },
|
get halls() { return getHalls(); },
|
||||||
|
halls_enabled: cfg.websrv.halls_enabled !== false,
|
||||||
|
userhalls_enabled: cfg.websrv.userhalls_enabled !== false,
|
||||||
|
abyss_enabled: cfg.websrv.abyss_enabled !== false,
|
||||||
smtp_enabled: !!(cfg.smtp && cfg.smtp.enabled && cfg.smtp.mail_reset_password),
|
smtp_enabled: !!(cfg.smtp && cfg.smtp.enabled && cfg.smtp.mail_reset_password),
|
||||||
show_background_cfg: cfg.websrv.background !== false,
|
show_background_cfg: cfg.websrv.background !== false,
|
||||||
allowed_mimes: Object.keys(cfg.mimes).concat([...new Set(Object.values(cfg.mimes))].map(ext => `.${ext}`)).join(','),
|
allowed_mimes: Object.keys(cfg.mimes).concat([...new Set(Object.values(cfg.mimes))].map(ext => `.${ext}`)).join(','),
|
||||||
@@ -727,7 +739,7 @@ process.on('uncaughtException', err => {
|
|||||||
get default_layout() { return getDefaultLayout(); },
|
get default_layout() { return getDefaultLayout(); },
|
||||||
show_koepfe: !!cfg.websrv.show_koepfe,
|
show_koepfe: !!cfg.websrv.show_koepfe,
|
||||||
allow_language_change: cfg.websrv.allow_language_change !== false,
|
allow_language_change: cfg.websrv.allow_language_change !== false,
|
||||||
use_ententeich: !!cfg.websrv.use_ententeich,
|
user_alternative_infobox: !!cfg.websrv.user_alternative_infobox,
|
||||||
enable_xd_score: !!cfg.websrv.enable_xd_score,
|
enable_xd_score: !!cfg.websrv.enable_xd_score,
|
||||||
enable_swf: !!cfg.websrv.enable_swf,
|
enable_swf: !!cfg.websrv.enable_swf,
|
||||||
enable_danmaku: cfg.websrv.enable_danmaku !== false,
|
enable_danmaku: cfg.websrv.enable_danmaku !== false,
|
||||||
|
|||||||
@@ -64,10 +64,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="blahlol">
|
<div class="blahlol">
|
||||||
@if(use_ententeich)
|
@if(user_alternative_infobox)
|
||||||
<div class="ententeich-block" style="--author-accent: @if(item.author_color){{ item.author_color }}@else var(--acent) @endif; --author-border: @if(item.author_color){{ item.author_color }}@else var(--accent) @endif;">
|
<div class="user-infobox-block" style="--author-accent: @if(item.author_color){{ item.author_color }}@else var(--acent) @endif; --author-border: @if(item.author_color){{ item.author_color }}@else var(--accent) @endif;">
|
||||||
|
|
||||||
<div class="enten-avatar">
|
<div class="user-infobox-avatar">
|
||||||
<a href="/user/{{ (item.username || '').toLowerCase() }}">
|
<a href="/user/{{ (item.username || '').toLowerCase() }}">
|
||||||
@if(item.author_avatar_file)
|
@if(item.author_avatar_file)
|
||||||
<img src="/a/{{ item.author_avatar_file }}" />
|
<img src="/a/{{ item.author_avatar_file }}" />
|
||||||
@@ -78,14 +78,14 @@
|
|||||||
@endif
|
@endif
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="enten-info">
|
<div class="user-infobox-info">
|
||||||
<div class="enten-header">
|
<div class="user-infobox-header">
|
||||||
<div class="enten-username-container">
|
<div class="user-infobox-username-container">
|
||||||
<a href="/user/{{ (item.username || '').toLowerCase() }}" tooltip="ID: {{ item.author_id }}" class="enten-username">{!! item.author_display_name || item.username !!}</a>
|
<a href="/user/{{ (item.username || '').toLowerCase() }}" tooltip="ID: {{ item.author_id }}" class="user-infobox-username">{!! item.author_display_name || item.username !!}</a>
|
||||||
</div>
|
</div>
|
||||||
<span class="enten-timestamp"><time class="timeago" tooltip="{{ item.timestamp.timefull }}">{{item.timestamp.timeago }}</time></span>
|
<span class="user-infobox-timestamp"><time class="timeago" tooltip="{{ item.timestamp.timefull }}">{{item.timestamp.timeago }}</time></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="enten-description">
|
<div class="user-infobox-description">
|
||||||
{!! item.author_description || '' !!}
|
{!! item.author_description || '' !!}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -94,16 +94,16 @@
|
|||||||
|
|
||||||
<span class="badge badge-dark">
|
<span class="badge badge-dark">
|
||||||
|
|
||||||
<a href="/{{ item.id }}" class="id-link" @if(use_ententeich)style="display:none"@endif>{{ item.id }}</a>
|
<a href="/{{ item.id }}" class="id-link" @if(user_alternative_infobox)style="display:none"@endif>{{ item.id }}</a>
|
||||||
@if(item.src.short)@if(!use_ententeich) — @endif<a href="{{ item.src.long }}" target="_blank">{{ item.src.short }}</a>@endif
|
@if(item.src.short)@if(!user_alternative_infobox) — @endif<a href="{{ item.src.long }}" target="_blank">{{ item.src.short }}</a>@endif
|
||||||
@if(session && !use_ententeich) — [<a id="a_username" data-username="{!! item.username || '' !!}" href="/user/{{ (item.username || '').toLowerCase() }}" @if(item.author_id) tooltip="ID: {{ item.author_id }}" @endif @if(item.author_color) style="color: {{ item.author_color }}" @endif>{!! item.author_display_name || item.username || 'unknown' !!}</a>] @endif
|
@if(session && !user_alternative_infobox) — [<a id="a_username" data-username="{!! item.username || '' !!}" href="/user/{{ (item.username || '').toLowerCase() }}" @if(item.author_id) tooltip="ID: {{ item.author_id }}" @endif @if(item.author_color) style="color: {{ item.author_color }}" @endif>{!! item.author_display_name || item.username || 'unknown' !!}</a>] @endif
|
||||||
@if(item.is_oc)@if(!use_ententeich || item.src.short) — @endif<span class="oc-badge" tooltip="Original Content">OC</span>@endif
|
@if(item.is_oc)@if(!user_alternative_infobox || item.src.short) — @endif<span class="oc-badge" tooltip="Original Content">OC</span>@endif
|
||||||
</span>
|
</span>
|
||||||
@if(!use_ententeich) — <span class="badge badge-dark"><time class="timeago" tooltip="{{ item.timestamp.timefull }}">{{item.timestamp.timeago }}</time></span> — @endif
|
@if(!user_alternative_infobox) — <span class="badge badge-dark"><time class="timeago" tooltip="{{ item.timestamp.timefull }}">{{item.timestamp.timeago }}</time></span> — @endif
|
||||||
@if(item.primaryHall)
|
@if(halls_enabled && item.primaryHall)
|
||||||
<span class="badge hall-badge-wrap">
|
<span class="badge hall-badge-wrap">
|
||||||
<a href="/h/{{ item.primaryHall.slug }}" class="hall-badge-primary">{{ item.primaryHall.name }}</a>@if(is_mod_or_admin) <a class="remove-from-hall" href="#" data-hall="{{ item.primaryHall.slug }}" title="Remove from hall"><i class="fa-solid fa-xmark"></i></a>@endif@if(item.otherHalls && item.otherHalls.length)<span class="hall-overflow-pill">+{{ item.otherHalls.length }}<span class="hall-overflow-tooltip">@each(item.otherHalls as oh)<a href="/h/{{ oh.slug }}">{{ oh.name }}</a>@endeach</span></span>@endif
|
<a href="/h/{{ item.primaryHall.slug }}" class="hall-badge-primary">{{ item.primaryHall.name }}</a>@if(is_mod_or_admin) <a class="remove-from-hall" href="#" data-hall="{{ item.primaryHall.slug }}" title="Remove from hall"><i class="fa-solid fa-xmark"></i></a>@endif@if(item.otherHalls && item.otherHalls.length)<span class="hall-overflow-pill">+{{ item.otherHalls.length }}<span class="hall-overflow-tooltip">@each(item.otherHalls as oh)<a href="/h/{{ oh.slug }}">{{ oh.name }}</a>@endeach</span></span>@endif
|
||||||
</span>@if(!use_ententeich) —@endif
|
</span>@if(!user_alternative_infobox) —@endif
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
|
||||||
@@ -117,7 +117,9 @@
|
|||||||
@endif
|
@endif
|
||||||
<i class="iconset {{ isSubscribed ? 'fa-solid' : 'fa-regular' }} fa-bell" id="subscribe-btn" data-item-id="{{ item.id }}" title="{{ isSubscribed ? 'Subscribed' : 'Subscribe' }}"></i>
|
<i class="iconset {{ isSubscribed ? 'fa-solid' : 'fa-regular' }} fa-bell" id="subscribe-btn" data-item-id="{{ item.id }}" title="{{ isSubscribed ? 'Subscribed' : 'Subscribe' }}"></i>
|
||||||
<i class="iconset fa-solid fa-triangle-exclamation report-item-btn" data-item-id="{{ item.id }}" title="Report this post"></i>
|
<i class="iconset fa-solid fa-triangle-exclamation report-item-btn" data-item-id="{{ item.id }}" title="Report this post"></i>
|
||||||
|
@if(halls_enabled)
|
||||||
<i class="iconset fa-solid fa-building-columns" id="a_hall" data-item-id="{{ item.id }}" data-halls="{{ halls_slugs }}" data-user-halls="{{ user_halls_slugs }}" data-current-hall="{{ (tmp.hall && typeof tmp.hall === 'object') ? tmp.hall.slug : (tmp.hall || '') }}" data-current-user-hall="{{ (tmp.userHall && typeof tmp.userHall === 'object') ? tmp.userHall.slug : (tmp.userHall || '') }}" data-current-user-hall-owner="{{ tmp.userHallOwner || '' }}" title="Add to Hall"></i>
|
<i class="iconset fa-solid fa-building-columns" id="a_hall" data-item-id="{{ item.id }}" data-halls="{{ halls_slugs }}" data-user-halls="{{ user_halls_slugs }}" data-current-hall="{{ (tmp.hall && typeof tmp.hall === 'object') ? tmp.hall.slug : (tmp.hall || '') }}" data-current-user-hall="{{ (tmp.userHall && typeof tmp.userHall === 'object') ? tmp.userHall.slug : (tmp.userHall || '') }}" data-current-user-hall-owner="{{ tmp.userHallOwner || '' }}" title="Add to Hall"></i>
|
||||||
|
@endif
|
||||||
@if(can_manage_item)
|
@if(can_manage_item)
|
||||||
<i class="iconset {{ item.is_oc ? 'fa-solid' : 'fa-regular' }} fa-star" id="a_oc" data-item-id="{{ item.id }}" data-is-oc="{{ item.is_oc }}" title="{{ item.is_oc ? 'Remove OC status' : 'Mark as OC' }}"></i>
|
<i class="iconset {{ item.is_oc ? 'fa-solid' : 'fa-regular' }} fa-star" id="a_oc" data-item-id="{{ item.id }}" data-is-oc="{{ item.is_oc }}" title="{{ item.is_oc ? 'Remove OC status' : 'Mark as OC' }}"></i>
|
||||||
@if(can_extract_meta)
|
@if(can_extract_meta)
|
||||||
|
|||||||
@@ -109,7 +109,7 @@
|
|||||||
<span class="badge badge-dark">
|
<span class="badge badge-dark">
|
||||||
<a href="/{{ item.id }}" class="id-link">{{ item.id }}</a>@if(item.src.short) — <a href="{{ item.src.long }}" target="_blank">{{ item.src.short }}</a>@endif @if(session) — [<a id="a_username" data-username="{!! item.username || '' !!}" href="/user/{{ item_username_lower }}" @if(item.author_id) tooltip="ID: {{ item.author_id }}" @endif @if(item.author_color) style="color: {{ item.author_color }}" @endif>{!! item.author_display_name || item.username || 'unknown' !!}</a>] @endif @if(item.is_oc) — <span class="oc-badge" tooltip="Original Content">OC</span>@endif
|
<a href="/{{ item.id }}" class="id-link">{{ item.id }}</a>@if(item.src.short) — <a href="{{ item.src.long }}" target="_blank">{{ item.src.short }}</a>@endif @if(session) — [<a id="a_username" data-username="{!! item.username || '' !!}" href="/user/{{ item_username_lower }}" @if(item.author_id) tooltip="ID: {{ item.author_id }}" @endif @if(item.author_color) style="color: {{ item.author_color }}" @endif>{!! item.author_display_name || item.username || 'unknown' !!}</a>] @endif @if(item.is_oc) — <span class="oc-badge" tooltip="Original Content">OC</span>@endif
|
||||||
</span> —
|
</span> —
|
||||||
@if(item.primaryHall)
|
@if(halls_enabled && item.primaryHall)
|
||||||
<span class="badge hall-badge-wrap">
|
<span class="badge hall-badge-wrap">
|
||||||
<a href="/h/{{ item.primaryHall.slug }}" class="hall-badge-primary">{{ item.primaryHall.name }}</a>@if(is_mod_or_admin) <a class="remove-from-hall" href="#" data-hall="{{ item.primaryHall.slug }}" title="Remove from hall"><i class="fa-solid fa-xmark"></i></a>@endif@if(item.otherHalls && item.otherHalls.length)<span class="hall-overflow-pill">+{{ item.otherHalls.length }}<span class="hall-overflow-tooltip">@each(item.otherHalls as oh)<a href="/h/{{ oh.slug }}">{{ oh.name }}</a>@endeach</span></span>@endif
|
<a href="/h/{{ item.primaryHall.slug }}" class="hall-badge-primary">{{ item.primaryHall.name }}</a>@if(is_mod_or_admin) <a class="remove-from-hall" href="#" data-hall="{{ item.primaryHall.slug }}" title="Remove from hall"><i class="fa-solid fa-xmark"></i></a>@endif@if(item.otherHalls && item.otherHalls.length)<span class="hall-overflow-pill">+{{ item.otherHalls.length }}<span class="hall-overflow-tooltip">@each(item.otherHalls as oh)<a href="/h/{{ oh.slug }}">{{ oh.name }}</a>@endeach</span></span>@endif
|
||||||
</span> —
|
</span> —
|
||||||
@@ -131,7 +131,9 @@
|
|||||||
<i class="iconset fa-solid fa-image" id="a_rethumb" data-item-id="{{ item.id }}" title="Re-upload Thumbnail"></i>
|
<i class="iconset fa-solid fa-image" id="a_rethumb" data-item-id="{{ item.id }}" title="Re-upload Thumbnail"></i>
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
|
@if(halls_enabled)
|
||||||
<i class="iconset fa-solid fa-building-columns" id="a_hall" data-item-id="{{ item.id }}" data-halls="{{ halls_slugs }}" data-user-halls="{{ user_halls_slugs }}" data-current-hall="{{ current_hall_slug }}" data-current-user-hall="{{ current_user_hall_slug }}" data-current-user-hall-owner="{{ current_user_hall_owner }}" title="Add to Hall"></i>
|
<i class="iconset fa-solid fa-building-columns" id="a_hall" data-item-id="{{ item.id }}" data-halls="{{ halls_slugs }}" data-user-halls="{{ user_halls_slugs }}" data-current-hall="{{ current_hall_slug }}" data-current-user-hall="{{ current_user_hall_slug }}" data-current-user-hall-owner="{{ current_user_hall_owner }}" title="Add to Hall"></i>
|
||||||
|
@endif
|
||||||
@if(is_mod_or_admin)
|
@if(is_mod_or_admin)
|
||||||
<i class="iconset fa-solid fa-thumbtack{{ item.is_pinned ? ' active' : '' }}" id="a_pin" data-pinned="{{ item.is_pinned }}" title="{{ item.is_pinned ? 'Unpin from main' : 'Pin to main' }}"></i>
|
<i class="iconset fa-solid fa-thumbtack{{ item.is_pinned ? ' active' : '' }}" id="a_pin" data-pinned="{{ item.is_pinned }}" title="{{ item.is_pinned ? 'Unpin from main' : 'Pin to main' }}"></i>
|
||||||
<i class="iconset fa-solid fa-xmark" id="a_delete" title="Delete"></i>
|
<i class="iconset fa-solid fa-xmark" id="a_delete" title="Delete"></i>
|
||||||
|
|||||||
@@ -76,6 +76,15 @@
|
|||||||
</label>
|
</label>
|
||||||
<small class="text-muted" style="margin-left: 25px;">{{ t('settings.modern_layout_hint') }}</small>
|
<small class="text-muted" style="margin-left: 25px;">{{ t('settings.modern_layout_hint') }}</small>
|
||||||
</div>
|
</div>
|
||||||
|
@if(!session.use_new_layout)
|
||||||
|
<div class="setting-item" style="margin-top: 15px;">
|
||||||
|
<label for="alternative_infobox_toggle" style="cursor: pointer; display: flex; align-items: center; gap: 10px;">
|
||||||
|
<input type="checkbox" id="alternative_infobox_toggle" @if(session.use_alternative_infobox === true) checked @endif>
|
||||||
|
<span>{{ t('settings.alternative_infobox') }}</span>
|
||||||
|
</label>
|
||||||
|
<small class="text-muted" style="margin-left: 25px;">{{ t('settings.alternative_infobox_hint') }}</small>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
<div class="setting-item" style="margin-top: 15px;">
|
<div class="setting-item" style="margin-top: 15px;">
|
||||||
<label for="disable_autoplay_toggle" style="cursor: pointer; display: flex; align-items: center; gap: 10px;">
|
<label for="disable_autoplay_toggle" style="cursor: pointer; display: flex; align-items: center; gap: 10px;">
|
||||||
<input type="checkbox" id="disable_autoplay_toggle" @if(session.disable_autoplay === true) checked @endif>
|
<input type="checkbox" id="disable_autoplay_toggle" @if(session.disable_autoplay === true) checked @endif>
|
||||||
@@ -195,23 +204,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
@endif
|
@endif
|
||||||
@if(enable_xd_score)
|
|
||||||
<fieldset style="border: 1px solid var(--nav-border-color); padding: 10px; border-radius: 4px; margin-bottom: 15px;">
|
|
||||||
<legend style="width: auto; padding: 0 5px; font-size: 1.1em; font-weight: bold;">{{ t('settings.content_filters') }}</legend>
|
|
||||||
<div class="setting-item" style="margin-bottom: 10px;">
|
|
||||||
<label for="min_xd_score_input" style="display: block; margin-bottom: 5px; font-weight: bold;">{{ t('settings.min_xd_score') }}</label>
|
|
||||||
<div style="display: flex; align-items: center; gap: 10px; flex-wrap: wrap;">
|
|
||||||
<input type="range" id="min_xd_score_input" min="0" max="100" step="1" value="{{ session.min_xd_score || 0 }}" class="xd-slider" style="flex: 1; min-width: 140px;">
|
|
||||||
<span id="xd_score_val" class="xd-slider-val">{{ session.min_xd_score || 0 }}</span>
|
|
||||||
<span id="xd_score_tier_label" class="xd-score-badge" style="display: none;"></span>
|
|
||||||
<button type="button" id="btn-save-min-xd-score" class="button" style="padding: 5px 10px; font-size: 0.85em;">{{ t('settings.save') }}</button>
|
|
||||||
<button type="button" id="btn-reset-min-xd-score" class="button button-secondary" style="padding: 5px 10px; font-size: 0.85em;">{{ t('settings.reset') }}</button>
|
|
||||||
</div>
|
|
||||||
<small class="text-muted">{{ t('settings.min_xd_score_hint') }}</small>
|
|
||||||
<div id="xd-score-status" class="avatar-status"></div>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
<h2>{{ t('settings.account') }}</h2>
|
<h2>{{ t('settings.account') }}</h2>
|
||||||
<div class="account-settings-wrapper" style="background: rgba(0,0,0,0.1); padding: 20px; border-radius: 4px; border: 1px solid var(--nav-border-color); margin-bottom: 30px;">
|
<div class="account-settings-wrapper" style="background: rgba(0,0,0,0.1); padding: 20px; border-radius: 4px; border: 1px solid var(--nav-border-color); margin-bottom: 30px;">
|
||||||
|
|||||||
@@ -18,13 +18,17 @@
|
|||||||
@if(meme_creator)
|
@if(meme_creator)
|
||||||
<a href="/meme">{{ t('nav.meme') }}</a>
|
<a href="/meme">{{ t('nav.meme') }}</a>
|
||||||
@endif
|
@endif
|
||||||
|
@if(halls_enabled)
|
||||||
<div class="nav-user-dropdown nav-halls-dropdown">
|
<div class="nav-user-dropdown nav-halls-dropdown">
|
||||||
<a href="/halls" class="nav-halls-btn" title="{{ t('nav.halls') }}">
|
<a href="/halls" class="nav-halls-btn" title="{{ t('nav.halls') }}">
|
||||||
<i class="fa-solid fa-building-columns"></i>
|
<i class="fa-solid fa-building-columns"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
@endif
|
||||||
<a href="/tags" title="{{ t('nav.tags') }}"><i class="fa-solid fa-tag"></i></a>
|
<a href="/tags" title="{{ t('nav.tags') }}"><i class="fa-solid fa-tag"></i></a>
|
||||||
|
@if(abyss_enabled)
|
||||||
<a href="/abyss" title="{{ t('nav.abyss') }}"><i class="fa-solid fa-dice-d6"></i></a>
|
<a href="/abyss" title="{{ t('nav.abyss') }}"><i class="fa-solid fa-dice-d6"></i></a>
|
||||||
|
@endif
|
||||||
<a href="#" id="nav-search-btn" title="{{ t('nav.search') }}"><i class="fa-solid fa-magnifying-glass"></i></a>
|
<a href="#" id="nav-search-btn" title="{{ t('nav.search') }}"><i class="fa-solid fa-magnifying-glass"></i></a>
|
||||||
@if(!/^\/\d$/.test(url.pathname))
|
@if(!/^\/\d$/.test(url.pathname))
|
||||||
<a href="/random" id="nav-random" title="{{ t('nav.random') }}"><i class="fa-solid fa-shuffle"></i></a>
|
<a href="/random" id="nav-random" title="{{ t('nav.random') }}"><i class="fa-solid fa-shuffle"></i></a>
|
||||||
@@ -50,7 +54,9 @@
|
|||||||
</button>
|
</button>
|
||||||
<div class="nav-user-menu" id="nav-user-menu">
|
<div class="nav-user-menu" id="nav-user-menu">
|
||||||
<a href="/user/{!! session.user.toLowerCase() !!}">{{ t('nav.profile') }}</a>
|
<a href="/user/{!! session.user.toLowerCase() !!}">{{ t('nav.profile') }}</a>
|
||||||
|
@if(userhalls_enabled)
|
||||||
<a href="/user/{!! session.user.toLowerCase() !!}/halls">{{ t('nav.my_halls') }}</a>
|
<a href="/user/{!! session.user.toLowerCase() !!}/halls">{{ t('nav.my_halls') }}</a>
|
||||||
|
@endif
|
||||||
<a href="/user/{{ session.user.toLowerCase() }}/favs" class="mobile-only">{{ t('nav.favs') }}</a>
|
<a href="/user/{{ session.user.toLowerCase() }}/favs" class="mobile-only">{{ t('nav.favs') }}</a>
|
||||||
@if(session.admin)
|
@if(session.admin)
|
||||||
<a href="/admin">Admin
|
<a href="/admin">Admin
|
||||||
@@ -152,6 +158,7 @@
|
|||||||
<div class="nav-collapse" id="navbarContent">
|
<div class="nav-collapse" id="navbarContent">
|
||||||
<div class="nav-links">
|
<div class="nav-links">
|
||||||
<a href="/tags" title="Tags"><i class="fa-solid fa-tag"></i></a>
|
<a href="/tags" title="Tags"><i class="fa-solid fa-tag"></i></a>
|
||||||
|
@if(halls_enabled)
|
||||||
<div class="">
|
<div class="">
|
||||||
<a href="/halls" class="nav-halls-btn" title="Halls">
|
<a href="/halls" class="nav-halls-btn" title="Halls">
|
||||||
<i class="fa-solid fa-building-columns"></i>
|
<i class="fa-solid fa-building-columns"></i>
|
||||||
@@ -167,8 +174,10 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a href="/tags" title="Tags"><i class="fa-solid fa-tag"></i></a>
|
@endif
|
||||||
|
@if(abyss_enabled)
|
||||||
<a href="/abyss" title="Abyss"><i class="fa-solid fa-dice-d6"></i></a>
|
<a href="/abyss" title="Abyss"><i class="fa-solid fa-dice-d6"></i></a>
|
||||||
|
@endif
|
||||||
@if(!/^\/\d$/.test(url.pathname))
|
@if(!/^\/\d$/.test(url.pathname))
|
||||||
<a href="/random" id="nav-random" title="Random"><i class="fa-solid fa-shuffle"></i></a>
|
<a href="/random" id="nav-random" title="Random"><i class="fa-solid fa-shuffle"></i></a>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
Reference in New Issue
Block a user