security hardening, team invites, granular locking, view counts, board subscriptions, scheduled changelog, mentions, recovery codes, accessibility and hover states

This commit is contained in:
2026-03-21 17:37:01 +02:00
parent f07eddf29e
commit 5ba25fb956
142 changed files with 30397 additions and 2287 deletions

View File

@@ -0,0 +1,92 @@
(function() {
'use strict';
var scripts = document.querySelectorAll('script[data-echoboard]');
for (var i = 0; i < scripts.length; i++) {
var s = scripts[i];
if (s.getAttribute('data-rendered')) continue;
s.setAttribute('data-rendered', '1');
var board = s.getAttribute('data-board');
var baseUrl = s.getAttribute('data-url') || s.src.replace(/\/embed\.js.*$/, '');
var theme = s.getAttribute('data-theme') || 'dark';
var limit = s.getAttribute('data-limit') || '10';
var sort = s.getAttribute('data-sort') || 'top';
var height = parseInt(s.getAttribute('data-height') || '500', 10) || 500;
var mode = s.getAttribute('data-mode') || 'inline';
var label = s.getAttribute('data-label') || 'Feedback';
var position = s.getAttribute('data-position') || 'right';
if (!board) continue;
var params = '?theme=' + encodeURIComponent(theme) +
'&limit=' + encodeURIComponent(limit) +
'&sort=' + encodeURIComponent(sort);
var src = baseUrl + '/embed/' + encodeURIComponent(board) + params;
if (mode === 'button') {
// Floating feedback button mode
var isRight = position !== 'left';
var isDark = theme === 'dark';
var accent = '#F59E0B';
// Button
var btn = document.createElement('button');
btn.textContent = label;
btn.style.cssText = 'position:fixed;bottom:24px;' + (isRight ? 'right' : 'left') + ':24px;' +
'z-index:999998;padding:10px 20px;border:none;border-radius:24px;cursor:pointer;' +
'font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;font-size:14px;font-weight:600;' +
'background:' + accent + ';color:#161616;box-shadow:0 4px 16px rgba(0,0,0,0.3);' +
'transition:transform 0.2s ease,box-shadow 0.2s ease;';
btn.onmouseenter = function() { btn.style.transform = 'scale(1.05)'; btn.style.boxShadow = '0 6px 24px rgba(0,0,0,0.4)'; };
btn.onmouseleave = function() { btn.style.transform = 'scale(1)'; btn.style.boxShadow = '0 4px 16px rgba(0,0,0,0.3)'; };
// Popup container
var popup = document.createElement('div');
popup.style.cssText = 'position:fixed;bottom:80px;' + (isRight ? 'right' : 'left') + ':24px;' +
'z-index:999999;width:380px;max-width:calc(100vw - 48px);height:' + height + 'px;max-height:calc(100vh - 120px);' +
'border-radius:12px;overflow:hidden;' +
'box-shadow:0 8px 40px rgba(0,0,0,0.4);border:1px solid ' + (isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.08)') + ';' +
'display:none;transform:translateY(12px);opacity:0;transition:transform 0.25s ease,opacity 0.25s ease;';
var popupIframe = document.createElement('iframe');
popupIframe.style.cssText = 'width:100%;height:100%;border:none;';
popupIframe.setAttribute('title', 'Echoboard - ' + board);
popupIframe.setAttribute('sandbox', 'allow-scripts');
popup.appendChild(popupIframe);
var open = false;
btn.onclick = function() {
if (!open) {
if (!popupIframe.src) popupIframe.src = src;
popup.style.display = 'block';
// force reflow
popup.offsetHeight;
popup.style.transform = 'translateY(0)';
popup.style.opacity = '1';
open = true;
} else {
popup.style.transform = 'translateY(12px)';
popup.style.opacity = '0';
setTimeout(function() { popup.style.display = 'none'; }, 250);
open = false;
}
};
document.body.appendChild(btn);
document.body.appendChild(popup);
} else {
// Inline embed mode (default)
var iframe = document.createElement('iframe');
iframe.src = src;
iframe.style.width = '100%';
iframe.style.height = height + 'px';
iframe.style.border = 'none';
iframe.style.borderRadius = '8px';
iframe.style.overflow = 'hidden';
iframe.setAttribute('loading', 'lazy');
iframe.setAttribute('title', 'Echoboard - ' + board);
iframe.setAttribute('sandbox', 'allow-scripts');
s.parentNode.insertBefore(iframe, s.nextSibling);
}
}
})();