security hardening, team invites, granular locking, view counts, board subscriptions, scheduled changelog, mentions, recovery codes, accessibility and hover states
This commit is contained in:
92
packages/web/public/embed.js
Normal file
92
packages/web/public/embed.js
Normal 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);
|
||||
}
|
||||
}
|
||||
})();
|
||||
22
packages/web/public/sw.js
Normal file
22
packages/web/public/sw.js
Normal file
@@ -0,0 +1,22 @@
|
||||
self.addEventListener("push", (event) => {
|
||||
let data = { title: "Echoboard", body: "New activity on your watched content" };
|
||||
try {
|
||||
data = event.data.json();
|
||||
} catch {}
|
||||
|
||||
event.waitUntil(
|
||||
self.registration.showNotification(data.title, {
|
||||
body: data.body,
|
||||
icon: data.icon || "/favicon.ico",
|
||||
data: { url: data.url },
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener("notificationclick", (event) => {
|
||||
event.notification.close();
|
||||
const raw = event.notification.data?.url || "/";
|
||||
const safe = new URL(raw, self.location.origin);
|
||||
const url = safe.origin === self.location.origin ? safe.href : "/";
|
||||
event.waitUntil(clients.openWindow(url));
|
||||
});
|
||||
Reference in New Issue
Block a user