mirror of
https://github.com/ancsemi/Haven
synced 2026-04-21 13:37:41 +00:00
v2.9.6: custom ToS, unpin bug fix, Android popup fixes
This commit is contained in:
parent
508c42ef5c
commit
4b9e16d7b6
14 changed files with 90 additions and 24 deletions
12
CHANGELOG.md
12
CHANGELOG.md
|
|
@ -11,6 +11,18 @@ Format follows [Keep a Changelog](https://keepachangelog.com/). Haven uses [Sema
|
|||
|
||||
---
|
||||
|
||||
## [2.9.6] — 2026-04-07
|
||||
|
||||
### Added
|
||||
- **Custom Terms of Service** — admins can now add custom terms that appear above the default Haven ToS on the login page. Set via a new textarea in Admin Settings. Supports plain text with paragraph breaks, max 50,000 characters. Leave empty to show only the default ToS. (#5229)
|
||||
|
||||
### Fixed
|
||||
- **Unpin message visual bug** — unpinning a message while viewing the pinned messages panel no longer leaves the pin border on the message. The pinned panel item is also removed in real time and the count updates. (#5228)
|
||||
- **Android app popup "Don't show this again"** — the checkbox now persists correctly across sessions. Previously the v3 migration flag used sessionStorage, causing dismissals to reset on every new session.
|
||||
- **Android app popup layout** — moved the "NOW AVAILABLE" badge above the title instead of inline, and centered the title text.
|
||||
|
||||
---
|
||||
|
||||
## [2.9.5] — 2026-04-07
|
||||
|
||||
### Changed
|
||||
|
|
|
|||
|
|
@ -1415,12 +1415,12 @@
|
|||
</div>
|
||||
|
||||
<div class="download-card fade-in">
|
||||
<h2>⬡ Haven Server — v2.9.5</h2>
|
||||
<h2>⬡ Haven Server — v2.9.6</h2>
|
||||
<p class="download-version">Latest stable release · Windows, macOS & Linux · ~5 MB</p>
|
||||
|
||||
<div class="download-btn-group">
|
||||
<a href="https://github.com/ancsemi/Haven/archive/refs/tags/v2.9.5.zip" class="btn btn-primary download-main">
|
||||
<span class="icon">⬇</span> Download v2.9.5 (.zip)
|
||||
<a href="https://github.com/ancsemi/Haven/archive/refs/tags/v2.9.6.zip" class="btn btn-primary download-main">
|
||||
<span class="icon">⬇</span> Download v2.9.6 (.zip)
|
||||
</a>
|
||||
<div class="download-alt-links">
|
||||
<a href="https://github.com/ancsemi/Haven" target="_blank">⛭ View on GitHub</a>
|
||||
|
|
@ -1437,7 +1437,11 @@
|
|||
<div class="version-list">
|
||||
<div class="version-list-inner">
|
||||
<div class="version-item">
|
||||
<div><span class="v-name">v2.9.5</span><span class="v-tag latest">Latest</span></div>
|
||||
<div><span class="v-name">v2.9.6</span><span class="v-tag latest">Latest</span></div>
|
||||
<a href="https://github.com/ancsemi/Haven/archive/refs/tags/v2.9.6.zip">Download →</a>
|
||||
</div>
|
||||
<div class="version-item">
|
||||
<div><span class="v-name">v2.9.5</span> — License changed to AGPL-3.0</div>
|
||||
<a href="https://github.com/ancsemi/Haven/archive/refs/tags/v2.9.5.zip">Download →</a>
|
||||
</div>
|
||||
<div class="version-item">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "haven",
|
||||
"version": "2.9.5",
|
||||
"version": "2.9.6",
|
||||
"description": "Haven — self-hosted private chat for your server, your rules",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "server.js",
|
||||
|
|
|
|||
|
|
@ -1574,6 +1574,12 @@
|
|||
<p class="muted-text" data-i18n="settings.admin.no_bots">No bots configured</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Custom Terms of Service (Admin only) -->
|
||||
<div class="admin-settings" id="section-custom-tos" style="margin-top:10px; padding-top:10px; border-top:1px solid var(--border);">
|
||||
<h5 class="settings-section-subtitle">⚖️ <span data-i18n="settings.admin.custom_tos_title">Custom Terms of Service</span></h5>
|
||||
<small class="settings-hint" data-i18n="settings.admin.custom_tos_hint">Add custom terms that appear before the default ToS on the login page. Supports plain text. Leave empty to show only the default Haven ToS.</small>
|
||||
<textarea id="custom-tos-input" class="settings-textarea" rows="6" maxlength="50000" placeholder="Enter your custom terms of service..." style="margin-top:8px;width:100%;resize:vertical;min-height:80px;font-size:12px;padding:8px;border-radius:6px;border:1px solid var(--border);background:var(--bg-secondary);color:var(--text-primary);font-family:inherit;"></textarea>
|
||||
</div>
|
||||
<!-- Discord Import (Admin only) -->
|
||||
<div class="admin-settings" id="section-import" style="margin-top:10px; padding-top:10px; border-top:1px solid var(--border);">
|
||||
<h5 class="settings-section-subtitle">📦 <span data-i18n="settings.admin.import_title">Discord Import</span></h5>
|
||||
|
|
@ -2464,7 +2470,8 @@
|
|||
<div class="modal-overlay" id="android-beta-modal" style="display:none">
|
||||
<div class="modal android-beta-promo">
|
||||
<div class="android-beta-promo-icon">📱</div>
|
||||
<h3 class="android-beta-promo-title"><span data-i18n="modals.android_beta.title">Amni-Haven Android</span> <span class="android-beta-promo-badge" data-i18n="modals.android_beta.badge" style="background:var(--accent-bright,#4caf50)">NOW AVAILABLE</span></h3>
|
||||
<span class="android-beta-promo-badge" data-i18n="modals.android_beta.badge" style="background:var(--accent-bright,#4caf50)">NOW AVAILABLE</span>
|
||||
<h3 class="android-beta-promo-title"><span data-i18n="modals.android_beta.title">Amni-Haven Android</span></h3>
|
||||
<p class="android-beta-promo-subtitle" data-i18n="modals.android_beta.subtitle">A native Android app for Haven, built from the ground up by Amnibro!</p>
|
||||
<ul class="android-beta-promo-features">
|
||||
<li><span class="promo-check">✓</span> <span data-i18n="modals.android_beta.feature_native">Native Android experience</span></li>
|
||||
|
|
|
|||
|
|
@ -3389,12 +3389,10 @@ html.rgb-cycling *::after {
|
|||
font-weight: 700;
|
||||
color: var(--text-primary);
|
||||
margin: 0 0 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
.android-beta-promo-badge {
|
||||
display: inline-block;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
|
|
@ -3403,6 +3401,7 @@ html.rgb-cycling *::after {
|
|||
color: #fff;
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.android-beta-promo-subtitle {
|
||||
font-size: 14px;
|
||||
|
|
|
|||
|
|
@ -178,8 +178,10 @@
|
|||
<!-- EULA Modal -->
|
||||
<div class="modal-overlay" id="eula-modal" style="display:none">
|
||||
<div class="modal eula-modal">
|
||||
<h3>âš–ï¸ Terms of Service & Release of Liability Agreement</h3>
|
||||
<div class="eula-content">
|
||||
<h3>âš–ï¸ Terms of Service & Release of Liability Agreement</h3> <div id="custom-tos-section" style="display:none">
|
||||
<div class="eula-content" id="custom-tos-content" style="margin-bottom:0;border-bottom:1px solid var(--border,#444);padding-bottom:16px;"></div>
|
||||
<p style="text-align:center;font-size:12px;color:var(--text-muted,#888);margin:12px 0;">── Default Haven Terms of Service ──</p>
|
||||
</div> <div class="eula-content">
|
||||
<p><strong>HAVEN — TERMS OF SERVICE, END USER LICENSE AGREEMENT & RELEASE OF LIABILITY</strong></p>
|
||||
<p><em>Effective Date: February 12, 2026 | Version 2.0</em></p>
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,17 @@
|
|||
const titleEl = document.getElementById('server-title');
|
||||
if (titleEl) titleEl.textContent = d.server_title;
|
||||
}
|
||||
if (d.custom_tos) {
|
||||
const section = document.getElementById('custom-tos-section');
|
||||
const content = document.getElementById('custom-tos-content');
|
||||
if (section && content) {
|
||||
// Render as plain text with paragraph breaks
|
||||
content.innerHTML = d.custom_tos.split(/\n\n+/).map(p =>
|
||||
'<p>' + p.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/\n/g,'<br>') + '</p>'
|
||||
).join('');
|
||||
section.style.display = 'block';
|
||||
}
|
||||
}
|
||||
}).catch(() => {});
|
||||
|
||||
// ── EULA ─────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -451,8 +451,11 @@ _snapshotAdminSettings() {
|
|||
max_emoji_kb: this.serverSettings.max_emoji_kb || '256',
|
||||
max_poll_options: this.serverSettings.max_poll_options || '10',
|
||||
update_banner_admin_only: this.serverSettings.update_banner_admin_only || 'false',
|
||||
default_theme: this.serverSettings.default_theme || ''
|
||||
default_theme: this.serverSettings.default_theme || '',
|
||||
custom_tos: this.serverSettings.custom_tos || ''
|
||||
};
|
||||
const tosEl = document.getElementById('custom-tos-input');
|
||||
if (tosEl) tosEl.value = this._adminSnapshot.custom_tos;
|
||||
// Load webhooks list for admin preview
|
||||
if (this.user?.isAdmin) {
|
||||
this.socket.emit('get-webhooks');
|
||||
|
|
@ -546,6 +549,12 @@ _saveAdminSettings() {
|
|||
changed = true;
|
||||
}
|
||||
|
||||
const customTos = document.getElementById('custom-tos-input')?.value.trim() || '';
|
||||
if (customTos !== (snap.custom_tos || '')) {
|
||||
this.socket.emit('update-server-setting', { key: 'custom_tos', value: customTos });
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
this._showToast(t('settings.admin.settings_saved'), 'success');
|
||||
} else {
|
||||
|
|
@ -583,6 +592,8 @@ _cancelAdminSettings() {
|
|||
if (uba) uba.checked = snap.update_banner_admin_only === 'true';
|
||||
const dt = document.getElementById('default-theme-select');
|
||||
if (dt) dt.value = snap.default_theme || '';
|
||||
const ct = document.getElementById('custom-tos-input');
|
||||
if (ct) ct.value = snap.custom_tos || '';
|
||||
}
|
||||
document.getElementById('settings-modal').style.display = 'none';
|
||||
},
|
||||
|
|
|
|||
|
|
@ -679,7 +679,7 @@ _renderPinnedPanel(pins) {
|
|||
list.querySelectorAll('.pinned-item').forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
const msgId = item.dataset.msgId;
|
||||
const msgEl = document.querySelector(`[data-msg-id="${msgId}"]`);
|
||||
const msgEl = document.querySelector(`#messages [data-msg-id="${msgId}"]`);
|
||||
if (msgEl) {
|
||||
msgEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
msgEl.classList.add('highlight-flash');
|
||||
|
|
|
|||
|
|
@ -175,12 +175,12 @@ _initDesktopAppBanner() {
|
|||
_initAndroidBetaBanner() {
|
||||
// ── v3 migration: Android app is now a full release; reset dismissals so
|
||||
// users who dismissed the old closed-beta popup see the new announcement ──
|
||||
if (!sessionStorage.getItem('_ab_v3_migrated')) {
|
||||
if (!localStorage.getItem('_ab_v3_migrated')) {
|
||||
localStorage.removeItem('haven_android_beta_banner_dismissed');
|
||||
localStorage.removeItem('haven_android_beta_promo_dismissed');
|
||||
localStorage.removeItem('haven_ab_banner_nodisplay');
|
||||
localStorage.removeItem('haven_ab_promo_nodisplay');
|
||||
sessionStorage.setItem('_ab_v3_migrated', '1');
|
||||
localStorage.setItem('_ab_v3_migrated', '1');
|
||||
}
|
||||
|
||||
// ── Top-bar banner ──
|
||||
|
|
|
|||
|
|
@ -922,7 +922,7 @@ _setupSocketListeners() {
|
|||
// ── Pin / Unpin ──────────────────────────────────
|
||||
this.socket.on('message-pinned', (data) => {
|
||||
if (data.channelCode === this.currentChannel) {
|
||||
const msgEl = document.querySelector(`[data-msg-id="${data.messageId}"]`);
|
||||
const msgEl = document.querySelector(`#messages [data-msg-id="${data.messageId}"]`);
|
||||
if (msgEl) {
|
||||
msgEl.classList.add('pinned');
|
||||
msgEl.dataset.pinned = '1';
|
||||
|
|
@ -941,7 +941,7 @@ _setupSocketListeners() {
|
|||
|
||||
this.socket.on('message-unpinned', (data) => {
|
||||
if (data.channelCode === this.currentChannel) {
|
||||
const msgEl = document.querySelector(`[data-msg-id="${data.messageId}"]`);
|
||||
const msgEl = document.querySelector(`#messages [data-msg-id="${data.messageId}"]`);
|
||||
if (msgEl) {
|
||||
msgEl.classList.remove('pinned');
|
||||
delete msgEl.dataset.pinned;
|
||||
|
|
@ -951,6 +951,17 @@ _setupSocketListeners() {
|
|||
const unpinBtn = msgEl.querySelector('[data-action="unpin"]');
|
||||
if (unpinBtn) { unpinBtn.dataset.action = 'pin'; unpinBtn.title = 'Pin'; }
|
||||
}
|
||||
// Remove from pinned panel if it's open
|
||||
const pinnedItem = document.querySelector(`#pinned-panel .pinned-item[data-msg-id="${data.messageId}"]`);
|
||||
if (pinnedItem) {
|
||||
pinnedItem.remove();
|
||||
const count = document.getElementById('pinned-count');
|
||||
const remaining = document.querySelectorAll('#pinned-list .pinned-item').length;
|
||||
count.textContent = `📌 ${t(remaining !== 1 ? 'pinned_panel.count_other' : 'pinned_panel.count_one', { count: remaining })}`;
|
||||
if (remaining === 0) {
|
||||
document.getElementById('pinned-list').innerHTML = `<p class="muted-text" style="padding:12px">${t('pinned_panel.no_messages')}</p>`;
|
||||
}
|
||||
}
|
||||
this._appendSystemMessage(`📌 ${t('header.messages.message_unpinned')}`);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -597,9 +597,11 @@ app.get('/api/public-config', (req, res) => {
|
|||
const db = getDb();
|
||||
const themeRow = db.prepare("SELECT value FROM server_settings WHERE key = 'default_theme'").get();
|
||||
const titleRow = db.prepare("SELECT value FROM server_settings WHERE key = 'server_title'").get();
|
||||
const tosRow = db.prepare("SELECT value FROM server_settings WHERE key = 'custom_tos'").get();
|
||||
res.json({
|
||||
default_theme: themeRow?.value || '',
|
||||
server_title: titleRow?.value || ''
|
||||
server_title: titleRow?.value || '',
|
||||
custom_tos: tosRow?.value || ''
|
||||
});
|
||||
} catch {
|
||||
res.json({ default_theme: '', server_title: '' });
|
||||
|
|
|
|||
|
|
@ -4424,7 +4424,7 @@ function setupSocketHandlers(io, db) {
|
|||
const key = typeof data.key === 'string' ? data.key.trim() : '';
|
||||
const value = typeof data.value === 'string' ? data.value.trim() : '';
|
||||
|
||||
const allowedKeys = ['member_visibility', 'cleanup_enabled', 'cleanup_max_age_days', 'cleanup_max_size_mb', 'giphy_api_key', 'server_name', 'server_title', 'server_icon', 'permission_thresholds', 'tunnel_enabled', 'tunnel_provider', 'server_code', 'max_upload_mb', 'max_poll_options', 'max_sound_kb', 'max_emoji_kb', 'setup_wizard_complete', 'update_banner_admin_only', 'default_theme', 'channel_sort_mode', 'channel_cat_order', 'channel_cat_sort', 'channel_tag_sorts'];
|
||||
const allowedKeys = ['member_visibility', 'cleanup_enabled', 'cleanup_max_age_days', 'cleanup_max_size_mb', 'giphy_api_key', 'server_name', 'server_title', 'server_icon', 'permission_thresholds', 'tunnel_enabled', 'tunnel_provider', 'server_code', 'max_upload_mb', 'max_poll_options', 'max_sound_kb', 'max_emoji_kb', 'setup_wizard_complete', 'update_banner_admin_only', 'default_theme', 'channel_sort_mode', 'channel_cat_order', 'channel_cat_sort', 'channel_tag_sorts', 'custom_tos'];
|
||||
if (!allowedKeys.includes(key)) return;
|
||||
|
||||
if (key === 'member_visibility' && !['all', 'online', 'none'].includes(value)) return;
|
||||
|
|
@ -4487,6 +4487,9 @@ function setupSocketHandlers(io, db) {
|
|||
const validThemes = ['', 'haven', 'discord', 'matrix', 'fallout', 'ffx', 'ice', 'nord', 'darksouls', 'eldenring', 'bloodborne', 'cyberpunk', 'lotr', 'abyss', 'scripture', 'chapel', 'gospel', 'tron', 'halo', 'dracula', 'win95'];
|
||||
if (!validThemes.includes(value)) return;
|
||||
}
|
||||
if (key === 'custom_tos') {
|
||||
if (value.length > 50000) return;
|
||||
}
|
||||
if (key === 'server_code') {
|
||||
// Server code is managed via generate/rotate events, not directly
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1415,12 +1415,12 @@
|
|||
</div>
|
||||
|
||||
<div class="download-card fade-in">
|
||||
<h2>⬡ Haven Server — v2.9.5</h2>
|
||||
<h2>⬡ Haven Server — v2.9.6</h2>
|
||||
<p class="download-version">Latest stable release · Windows, macOS & Linux · ~5 MB</p>
|
||||
|
||||
<div class="download-btn-group">
|
||||
<a href="https://github.com/ancsemi/Haven/archive/refs/tags/v2.9.5.zip" class="btn btn-primary download-main">
|
||||
<span class="icon">⬇</span> Download v2.9.5 (.zip)
|
||||
<a href="https://github.com/ancsemi/Haven/archive/refs/tags/v2.9.6.zip" class="btn btn-primary download-main">
|
||||
<span class="icon">⬇</span> Download v2.9.6 (.zip)
|
||||
</a>
|
||||
<div class="download-alt-links">
|
||||
<a href="https://github.com/ancsemi/Haven" target="_blank">⛭ View on GitHub</a>
|
||||
|
|
@ -1437,7 +1437,11 @@
|
|||
<div class="version-list">
|
||||
<div class="version-list-inner">
|
||||
<div class="version-item">
|
||||
<div><span class="v-name">v2.9.5</span><span class="v-tag latest">Latest</span></div>
|
||||
<div><span class="v-name">v2.9.6</span><span class="v-tag latest">Latest</span></div>
|
||||
<a href="https://github.com/ancsemi/Haven/archive/refs/tags/v2.9.6.zip">Download →</a>
|
||||
</div>
|
||||
<div class="version-item">
|
||||
<div><span class="v-name">v2.9.5</span> — License changed to AGPL-3.0</div>
|
||||
<a href="https://github.com/ancsemi/Haven/archive/refs/tags/v2.9.5.zip">Download →</a>
|
||||
</div>
|
||||
<div class="version-item">
|
||||
|
|
|
|||
Loading…
Reference in a new issue