diff --git a/config_example.json b/config_example.json index 4b1bb2f..0b05a0e 100644 --- a/config_example.json +++ b/config_example.json @@ -99,6 +99,7 @@ "show_mime_picker": true, "embed_youtube_in_comments": true, "allow_comment_deletion": false, + "enable_comment_polls": false, "show_content_warning": true, "default_comment_display_mode": 1, "phrases": [ diff --git a/migrations/f0ckm_schema.sql b/migrations/f0ckm_schema.sql index 9391291..cc50336 100644 --- a/migrations/f0ckm_schema.sql +++ b/migrations/f0ckm_schema.sql @@ -2842,4 +2842,35 @@ CREATE TABLE IF NOT EXISTS public.wordfilter ( created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); +-- Comment Polls +CREATE TABLE IF NOT EXISTS public.comment_polls ( + id SERIAL PRIMARY KEY, + comment_id INTEGER NOT NULL REFERENCES public.comments(id) ON DELETE CASCADE, + question TEXT NOT NULL, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + expires_at TIMESTAMP WITH TIME ZONE DEFAULT NULL, + UNIQUE(comment_id) +); + +CREATE TABLE IF NOT EXISTS public.comment_poll_options ( + id SERIAL PRIMARY KEY, + poll_id INTEGER NOT NULL REFERENCES public.comment_polls(id) ON DELETE CASCADE, + option_text TEXT NOT NULL, + display_order SMALLINT NOT NULL DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS public.comment_poll_votes ( + id SERIAL PRIMARY KEY, + poll_id INTEGER NOT NULL REFERENCES public.comment_polls(id) ON DELETE CASCADE, + option_id INTEGER NOT NULL REFERENCES public.comment_poll_options(id) ON DELETE CASCADE, + user_id INTEGER NOT NULL REFERENCES public."user"(id) ON DELETE CASCADE, + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + UNIQUE(poll_id, user_id) +); + +CREATE INDEX IF NOT EXISTS idx_comment_polls_comment_id ON public.comment_polls(comment_id); +CREATE INDEX IF NOT EXISTS idx_comment_poll_options_poll ON public.comment_poll_options(poll_id); +CREATE INDEX IF NOT EXISTS idx_comment_poll_votes_poll ON public.comment_poll_votes(poll_id); +CREATE INDEX IF NOT EXISTS idx_comment_poll_votes_user ON public.comment_poll_votes(user_id); + \unrestrict RMNKNzVQLV2ZcwmM3bmhglTot5nRoju9FmRyi3eUMfNy6iJUBfHRIgXnbrpJikG diff --git a/public/s/css/f0ckm.css b/public/s/css/f0ckm.css index 670e313..13273da 100644 --- a/public/s/css/f0ckm.css +++ b/public/s/css/f0ckm.css @@ -2606,6 +2606,223 @@ body.layout-legacy #comments-container.faded-out { max-width: 350px; } +/* ─── Poll Button ─────────────────────────────────────────────────────────── */ + +.comment-poll-btn { + background: none; + border: none; + color: var(--white); + cursor: pointer; + padding: 4px 8px; + display: flex; + align-items: center; + justify-content: center; + transition: color 0.15s; + font-size: 14px; + opacity: 0.7; +} + +.comment-poll-btn:hover, +.comment-poll-btn.active { + opacity: 1; + color: var(--accent); +} + +/* ─── Poll Builder ────────────────────────────────────────────────────────── */ + +.poll-builder { + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.1); + border-bottom: none; + padding: 8px; + display: flex; + flex-direction: column; + gap: 6px; + animation: pollBuilderIn 0.15s ease; +} + +@keyframes pollBuilderIn { + from { opacity: 0; transform: translateY(-4px); } + to { opacity: 1; transform: translateY(0); } +} + +.poll-question-input, +.poll-option-input { + width: 100%; + background: rgba(0,0,0,0.2); + border: 1px solid rgba(255,255,255,0.1); + color: var(--white); + padding: 5px 8px; + font-family: var(--font, inherit); + font-size: 0.85em; + box-sizing: border-box; +} + +.poll-question-input:focus, +.poll-option-input:focus { + outline: none; + border-color: var(--accent); +} + +.poll-question-input { + font-weight: 600; +} + +.poll-options-list { + display: flex; + flex-direction: column; + gap: 4px; +} + +.poll-builder-actions { + display: flex; + gap: 6px; + align-items: center; +} + +.poll-add-option-btn, +.poll-remove-btn { + background: none; + border: 1px solid rgba(255,255,255,0.15); + color: #aaa; + cursor: pointer; + padding: 3px 10px; + font-size: 0.8em; + display: inline-flex; + align-items: center; + gap: 4px; + transition: color 0.15s, border-color 0.15s; +} + +.poll-add-option-btn:hover { + color: var(--accent); + border-color: var(--accent); +} + +.poll-remove-btn:hover { + color: #ff4444; + border-color: #ff4444; +} + +/* ─── Poll Widget (rendered in comments) ─────────────────────────────────── */ + +.comment-poll { + margin-top: 8px; + background: rgba(255, 255, 255, 0.03); + border: 1px solid rgba(255, 255, 255, 0.09); + padding: 10px; + border-radius: 0; +} + +.poll-question { + font-weight: 600; + font-size: 0.9em; + margin-bottom: 8px; + color: var(--white); +} + +.poll-options { + display: flex; + flex-direction: column; + gap: 5px; + margin-bottom: 8px; +} + +.poll-option { + position: relative; + overflow: hidden; + display: flex; + align-items: center; + gap: 6px; + padding: 5px 8px; + background: rgba(255,255,255,0.04); + border: 1px solid rgba(255,255,255,0.07); + user-select: none; + min-height: 30px; + transition: border-color 0.15s; +} + +.poll-option-clickable { + cursor: pointer; +} + +.poll-option-clickable:hover { + border-color: rgba(255,255,255,0.18); + background: rgba(255,255,255,0.07); +} + +.poll-option-bar { + position: absolute; + left: 0; + top: 0; + bottom: 0; + background: var(--accent); + opacity: 0.18; + transition: width 0.4s ease; + pointer-events: none; +} + +.poll-option-voted .poll-option-bar { + opacity: 0.28; +} + +.poll-option-text { + position: relative; + flex: 1; + font-size: 0.85em; + color: var(--white); +} + +.poll-option-pct { + position: relative; + font-size: 0.78em; + color: #888; + font-family: monospace; + white-space: nowrap; +} + +.poll-vote-check { + position: relative; + color: var(--accent); + font-size: 0.75em; + flex-shrink: 0; +} + +.poll-footer { + display: flex; + align-items: center; + gap: 8px; + font-size: 0.78em; + color: #666; +} + +.poll-total { + font-family: monospace; +} + +.poll-expired-badge { + background: rgba(255,255,255,0.08); + border: 1px solid rgba(255,255,255,0.1); + padding: 1px 6px; + font-size: 0.85em; + color: #888; +} + +.poll-delete-btn { + background: none; + border: none; + color: #555; + cursor: pointer; + padding: 0; + font-size: 0.85em; + margin-left: auto; + transition: color 0.15s; +} + +.poll-delete-btn:hover { + color: #ff4444; +} + .comments-list { display: flex; diff --git a/public/s/js/comments.js b/public/s/js/comments.js index 93ff609..e9a6cc9 100644 --- a/public/s/js/comments.js +++ b/public/s/js/comments.js @@ -2047,6 +2047,7 @@ class CommentSystem {