From fd753c13183ba25db3e56f5280e5b9e4028c13c6 Mon Sep 17 00:00:00 2001 From: dev Date: Sat, 12 Sep 2026 10:05:02 +0300 Subject: [PATCH] Add cookie notice with settings, jfif preview support, and share page title from link name --- db/init.sql | 2 ++ db/migration.sql | 2 ++ docker-compose.yml | 38 ++++++++++++++++++------------------- public/js/files.js | 2 +- public/js/journal.js | 2 +- public/js/settings.js | 4 +++- public/js/share.js | 44 ++++++++++++++++++++++++++++++++----------- public/js/trash.js | 2 +- public/settings.html | 6 ++++++ public/share.html | 10 ++++++++++ server.js | 4 ++-- 11 files changed, 80 insertions(+), 36 deletions(-) diff --git a/db/init.sql b/db/init.sql index eecb0ad..4084b72 100644 --- a/db/init.sql +++ b/db/init.sql @@ -73,6 +73,8 @@ INSERT INTO settings (key, value) VALUES ('share_show_student_names', 'true') ON CONFLICT (key) DO NOTHING; INSERT INTO settings (key, value) VALUES ('share_show_group_photos', 'true') ON CONFLICT (key) DO NOTHING; +INSERT INTO settings (key, value) VALUES ('cookie_notice_text', 'Этот сайт использует cookie-файлы для корректной работы. Продолжая просмотр, вы соглашаетесь с их использованием.') +ON CONFLICT (key) DO NOTHING; CREATE TABLE IF NOT EXISTS share_links ( id SERIAL PRIMARY KEY, diff --git a/db/migration.sql b/db/migration.sql index 4c2d413..2550774 100644 --- a/db/migration.sql +++ b/db/migration.sql @@ -110,6 +110,8 @@ ON CONFLICT (key) DO NOTHING; DELETE FROM settings WHERE key = 'share_anonymize_names'; INSERT INTO settings (key, value) VALUES ('share_show_group_photos', 'true') ON CONFLICT (key) DO NOTHING; +INSERT INTO settings (key, value) VALUES ('cookie_notice_text', 'Этот сайт использует cookie-файлы для корректной работы. Продолжая просмотр, вы соглашаетесь с их использованием.') +ON CONFLICT (key) DO NOTHING; DO $$ BEGIN diff --git a/docker-compose.yml b/docker-compose.yml index c1add30..5edd95a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,25 +43,25 @@ services: # Публикация через Tailscale (Serve / Funnel) без проброса портов. # Приложение доступно по https://whatido..ts.net - tailscale: - image: tailscale/tailscale:latest - hostname: whatido - restart: unless-stopped - network_mode: host - cap_add: - - NET_ADMIN - - SYS_MODULE - environment: - SSL_CERT_FILE: /etc/tailscale/app-certs/cert.pem - volumes: - - /var/lib/tailscale:/var/lib/tailscale - - /dev/net/tun:/dev/net/tun - - /lib/modules:/lib/modules:ro - - ./certs:/etc/tailscale/app-certs:ro - - ./start-tailscale.sh:/start-tailscale.sh:ro - command: ["/bin/sh", "/start-tailscale.sh"] - depends_on: - - app +# tailscale: +# image: tailscale/tailscale:latest +# hostname: whatido +# restart: unless-stopped +# network_mode: host +# cap_add: +# - NET_ADMIN +# - SYS_MODULE +# environment: +# SSL_CERT_FILE: /etc/tailscale/app-certs/cert.pem +# volumes: +# - /var/lib/tailscale:/var/lib/tailscale +# - /dev/net/tun:/dev/net/tun +# - /lib/modules:/lib/modules:ro +# - ./certs:/etc/tailscale/app-certs:ro +# - ./start-tailscale.sh:/start-tailscale.sh:ro +# command: ["/bin/sh", "/start-tailscale.sh"] +# depends_on: +# - app text-corrector: diff --git a/public/js/files.js b/public/js/files.js index 422b6e0..d2f1ba8 100644 --- a/public/js/files.js +++ b/public/js/files.js @@ -21,7 +21,7 @@ function setMode(m) { } function isImageFile(name) { - return /\.(jpe?g|png|gif|webp|bmp|avif|svg|ico)$/i.test(name || ''); + return /\.(jpe?g|jfif|png|gif|webp|bmp|avif|svg|ico)$/i.test(name || ''); } function humanSize(b) { diff --git a/public/js/journal.js b/public/js/journal.js index 05c4e7b..866c90e 100644 --- a/public/js/journal.js +++ b/public/js/journal.js @@ -21,7 +21,7 @@ async function loadLinkSettings() { } function isImageFile(name) { - return /\.(jpe?g|png|gif|webp|bmp|avif|svg|ico)$/i.test(name || ''); + return /\.(jpe?g|jfif|png|gif|webp|bmp|avif|svg|ico)$/i.test(name || ''); } function mainPhotoPath(e) { diff --git a/public/js/settings.js b/public/js/settings.js index 32f67e5..5df71cf 100644 --- a/public/js/settings.js +++ b/public/js/settings.js @@ -1,4 +1,4 @@ -const DIRTY_FIELDS = ['spamInterval', 'footerLeft', 'footerRight', 'aiPrompt', 'aiAutoCheck', 'shareShowMessage', 'shareShowDate', 'shareShowNames', 'shareShowPhotos']; +const DIRTY_FIELDS = ['spamInterval', 'footerLeft', 'footerRight', 'aiPrompt', 'aiAutoCheck', 'shareShowMessage', 'shareShowDate', 'shareShowNames', 'shareShowPhotos', 'cookieNoticeText']; function fieldValue(el) { return el.type === 'checkbox' ? String(el.checked) : el.value; @@ -51,6 +51,7 @@ async function loadSettings() { document.getElementById('shareShowDate').checked = s.share_show_entry_date === 'true'; document.getElementById('shareShowNames').checked = s.share_show_student_names !== 'false'; document.getElementById('shareShowPhotos').checked = s.share_show_group_photos !== 'false'; + document.getElementById('cookieNoticeText').value = s.cookie_notice_text || ''; snapshotDirty(); loadAiQueue(); if (stRes.ok) { @@ -166,6 +167,7 @@ async function saveSettings() { share_show_entry_date: document.getElementById('shareShowDate').checked ? 'true' : 'false', share_show_student_names: document.getElementById('shareShowNames').checked ? 'true' : 'false', share_show_group_photos: document.getElementById('shareShowPhotos').checked ? 'true' : 'false', + cookie_notice_text: document.getElementById('cookieNoticeText').value.trim(), } }) }); if (res.ok) { diff --git a/public/js/share.js b/public/js/share.js index 706ac6b..7a7c6dc 100644 --- a/public/js/share.js +++ b/public/js/share.js @@ -1,7 +1,7 @@ function esc(s){return String(s==null?'':s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"')} function fmt(d){return d?d.split('-').reverse().join('.'):'весь период'} function showImg(src){document.getElementById('lightboxImg').src=src;document.getElementById('lightbox').classList.add('open')} -function isImageFile(name){return /\.(jpe?g|png|gif|webp|bmp|avif|svg|ico)$/i.test(name||'')} +function isImageFile(name){return /\.(jpe?g|jfif|png|gif|webp|bmp|avif|svg|ico)$/i.test(name||'')} function thumbSrc(p){ if(!p)return p; if(p.startsWith('/uploads/'))return '/uploads/thumb/'+p.slice('/uploads/'.length); @@ -84,7 +84,7 @@ function startCountdown(expiresAt) { } function fileType(name){ - if(/\.(jpe?g|png|gif|webp|bmp|avif|svg|ico)$/i.test(name||'')) return 'image'; + if(/\.(jpe?g|jfif|png|gif|webp|bmp|avif|svg|ico)$/i.test(name||'')) return 'image'; if(/\.(mp3|wav|ogg|flac|m4a|aac|opus)$/i.test(name||'')) return 'music'; if(/\.(pptx?|odp|key)$/i.test(name||'')) return 'presentation'; if(/\.(pdf|docx?|xlsx?|txt|rtf|odt|ods|csv|md)$/i.test(name||'')) return 'document'; @@ -185,7 +185,7 @@ function showError(msg) { } function renderShare(data, password) { - const heading = data.group_name || data.name || 'Записи занятий'; + const heading = (data.name && String(data.name).trim()) || data.group_name || 'Записи занятий'; document.title = heading; document.getElementById('title').textContent = heading; const chips = []; @@ -239,22 +239,25 @@ function renderShare(data, password) { const isAnonymized = !data.show_student_names; const showStudentMessage = !!data.show_student_message; const showEntryDate = !!data.show_entry_date; - grid.innerHTML = data.entries.map(e => ` + grid.innerHTML = data.entries.map(e => { + const body = [ + !isAnonymized ? `
${esc(e.student_name)}
` : '', + !isAnonymized && e.group_name ? `
${esc(e.group_name)}
` : '', + showStudentMessage && e.description ? `
${esc(e.description)}
` : '', + filesHTML(e.files, password, currentToken) + ].join(''); + return `
${e.photo_path ? `` : `
`}
-
- ${!isAnonymized ? `
${esc(e.student_name)}
` : ''} - ${!isAnonymized && e.group_name ? `
${esc(e.group_name)}
` : ''} - ${showStudentMessage && e.description ? `
${esc(e.description)}
` : ''} - ${filesHTML(e.files, password, currentToken)} -
+ ${body ? `
${body}
` : ''} ${showEntryDate ? `
${new Date(e.created_at).toLocaleString('ru',{day:'2-digit',month:'2-digit',year:'numeric',hour:'2-digit',minute:'2-digit'})}
` : ''}
- `).join(''); + `; + }).join(''); } document.getElementById('lightbox').addEventListener('click', () => document.getElementById('lightbox').classList.remove('open')); @@ -268,3 +271,22 @@ document.addEventListener('click', e => { (async()=>{ await loadShare(); })(); + +(async function(){ + const notice = document.getElementById('cookieNotice'); + const acceptBtn = document.getElementById('cookieAcceptBtn'); + const COOKIE_KEY = 'whatido_cookie_consent'; + if (!notice || !acceptBtn || localStorage.getItem(COOKIE_KEY)) return; + try { + const res = await fetch('/api/public-settings'); + const s = await res.json(); + const text = (s.cookie_notice_text || '').trim(); + if (!text) return; + notice.querySelector('p').textContent = text; + notice.classList.remove('hidden'); + } catch (e) { return; } + acceptBtn.addEventListener('click', () => { + localStorage.setItem(COOKIE_KEY, '1'); + notice.classList.add('hidden'); + }); +})(); diff --git a/public/js/trash.js b/public/js/trash.js index 82e4fab..ce5c4db 100644 --- a/public/js/trash.js +++ b/public/js/trash.js @@ -4,7 +4,7 @@ let page = 1; let totalEntries = 0; function isImageFile(name) { - return /\.(jpe?g|png|gif|webp|bmp|avif|svg|ico)$/i.test(name || ''); + return /\.(jpe?g|jfif|png|gif|webp|bmp|avif|svg|ico)$/i.test(name || ''); } function filesHTML(files) { diff --git a/public/settings.html b/public/settings.html index dec7571..501afbb 100644 --- a/public/settings.html +++ b/public/settings.html @@ -81,6 +81,12 @@ +
+
+ + +
+
diff --git a/public/share.html b/public/share.html index 2380a4d..40b9515 100644 --- a/public/share.html +++ b/public/share.html @@ -75,6 +75,12 @@ body{font-family:'Segoe UI',system-ui,-apple-system,Roboto,sans-serif;background .countdown.warn .cd-text strong{color:#E65100} .countdown.urgent{background:#FFEBEE} .countdown.urgent .cd-text strong{color:#C62828} + .cookie-notice{position:fixed;bottom:24px;right:24px;z-index:150;max-width:340px;background:var(--card-bg);border-radius:16px;padding:18px 20px;box-shadow:0 8px 30px rgba(20,30,40,.18);font-size:.9rem;line-height:1.5;color:#3a3f4a} + .cookie-notice.hidden{display:none} + .cookie-notice p{margin-bottom:14px} + .cookie-notice button{width:100%;padding:10px 14px;background:var(--yellow);color:var(--dark);border:none;border-radius:12px;font-size:.9rem;font-weight:700;cursor:pointer} + .cookie-notice button:hover{opacity:.9} + @media(max-width:480px){.cookie-notice{left:24px;right:24px;max-width:none}} @@ -93,6 +99,10 @@ body{font-family:'Segoe UI',system-ui,-apple-system,Roboto,sans-serif;background
⏳
+
diff --git a/server.js b/server.js index 716efe2..d56363f 100644 --- a/server.js +++ b/server.js @@ -872,7 +872,7 @@ app.get('/api/settings', requireAdmin, async (_, res) => { app.get('/api/public-settings', apiLimiter, async (_, res) => { const out = await cacheWrap('public-settings', PUBLIC_TTL_MS, async () => { - const keys = ['footer_left', 'footer_right', 'share_show_student_message', 'share_show_entry_date', 'share_show_student_names', 'share_show_group_photos']; + const keys = ['footer_left', 'footer_right', 'share_show_student_message', 'share_show_entry_date', 'share_show_student_names', 'share_show_group_photos', 'cookie_notice_text']; const result = {}; for (const k of keys) result[k] = await getSetting(k, ''); return result; @@ -2308,7 +2308,7 @@ app.post('/api/entries/:id/files', requireAuth, (req, res, next) => { }); function isImageName(name) { - return /\.(jpe?g|png|gif|webp|bmp|avif|ico)$/i.test(name || ''); + return /\.(jpe?g|jfif|png|gif|webp|bmp|avif|ico)$/i.test(name || ''); } app.get('/api/files', requireAuth, async (req, res) => {