Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Development line **Morse**. No GitHub Release until explicitly cut — see [`doc

### Added

- **NestUI 1.0** — Settings → Appearance toggle for an alternate soft system design (`data-ui-skin=nest`) via `shared/floke-kit` tokens; layout unchanged; accent preserved.
- **Chat Markdown** — GFM formatting in regular messages (bold/lists/code/links) via sanitized `marked` + DOMPurify; release notes sheet fills the window with improved tables/images.
- **Vitest** core suite — UDP announce sign/verify round-trip, TCP line framing, EN/RU i18n key parity (`npm test` in CI).
- **NSIS Setup wizard** — network tips page, richer welcome/finish, uninstall optional AppData wipe, publish repo `krwg/blip`.
- **Release notes Markdown** — Settings → Updates renders GitHub release bodies (tables, images, links); click a release for a top sheet with the full notes.
Expand Down
2 changes: 2 additions & 0 deletions main/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ const DEFAULT_CONFIG = {

chatFontScale: 1,

uiSkin: 'pixel',

typingSoundEnabled: false,

idleAwayMinutes: 5,
Expand Down
9 changes: 9 additions & 0 deletions renderer/appearance.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,12 @@ export function labelBg(id) {
return label === key ? id : label;
}

export const UI_SKINS = ['pixel', 'nest'];

export function normalizeUiSkin(raw) {
return raw === 'nest' ? 'nest' : 'pixel';
}

export function normalizeCustomAccentHex(raw) {
const s = String(raw || '').trim();
if (/^#[0-9A-Fa-f]{6}$/.test(s)) return s.toLowerCase();
Expand Down Expand Up @@ -150,11 +156,13 @@ export function applyAppearance(config) {
const accent = normalizeAccentId(config?.accentId, config?.themeId);
const bg = normalizeBgId(config?.animatedBgId);
const effective = resolveEffectiveTheme(mode);
const skin = normalizeUiSkin(config?.uiSkin);

html.dataset.themeMode = mode;
html.dataset.theme = effective;
html.dataset.accent = accent;
html.dataset.animatedBg = bg;
html.dataset.uiSkin = skin;
delete html.dataset.callWindow;
html.dataset.reactiveBg =
config?.reactiveBackground === true && bg !== 'none' ? '1' : '0';
Expand Down Expand Up @@ -189,6 +197,7 @@ export function applyCallWindowAppearance(config) {
html.dataset.animatedBg = 'none';
html.dataset.callWindow = '1';
html.dataset.reactiveBg = '0';
html.dataset.uiSkin = normalizeUiSkin(config?.uiSkin);
applyCustomAccentVars(html, config);
syncReducedMotion(config);
}
Expand Down
1 change: 1 addition & 0 deletions renderer/call-window.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<title>BLIP — Call</title>
<link rel="stylesheet" href="./themes.css" />
<link rel="stylesheet" href="./styles.css" />
<link rel="stylesheet" href="./nest-ui.css" />
<link rel="stylesheet" href="./trust.css" />
<style>
#call-shell { display: flex; flex-direction: column; height: 100%; }
Expand Down
104 changes: 65 additions & 39 deletions renderer/chat-message-content.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

import { t } from './i18n.js';
import { appendLinkifiedText } from './linkify.js';
import { chatMarkdownToHtml, bindMarkdownInteractions } from './release-markdown.js';
import { formatFileSize } from './file-transfer.js';
import { formatTransferSpeed } from './file-transfer-speed.js';
import { isImageAttachment, isVideoAttachment, isMediaPlaceholderText } from './chat-attachments.js';
Expand All @@ -15,6 +16,70 @@ function openExternalUrl(url) {
if (window.blip?.openExternal) void window.blip.openExternal(url);
}

function looksLikeMarkdown(text) {
const s = String(text || '');
if (!s.trim()) return false;
return /(^|\n)\s{0,3}(#{1,4}\s|[-*+]\s|\d+\.\s|>\s|```|~~~)|(\*\*|__|~~|`[^`]+`|\[[^\]]+\]\([^)]+\))/.test(
s,
);
}

function appendMarkdownOrPlain(parent, text) {
const src = String(text || '');
if (!src) return;
if (!looksLikeMarkdown(src)) {
appendLinkifiedText(parent, src, openExternalUrl);
return;
}
const html = chatMarkdownToHtml(src);
if (!html) {
appendLinkifiedText(parent, src, openExternalUrl);
return;
}
parent.classList.add('chat-md');
parent.innerHTML = html;
bindMarkdownInteractions(parent, { openExternal: openExternalUrl, allowImages: false });
}

function appendTextWithEmbeds(block, text) {
const src = String(text || '').trim();
if (!src) return;
const yts = findYoutubeInText(src);
if (!yts.length) {
const span = document.createElement('span');
span.className = 'chat-text';
appendMarkdownOrPlain(span, src);
block.appendChild(span);
return;
}

let last = 0;
for (const yt of yts) {
if (yt.index > last) {
const chunk = src.slice(last, yt.index).trim();
if (chunk) {
const span = document.createElement('span');
span.className = 'chat-text';
appendMarkdownOrPlain(span, chunk);
block.appendChild(span);
}
}
appendYoutubeEmbed(block, yt, {
onPlay: () => openYoutubeViewer(yt.id),
});
last = yt.index + yt.raw.length;
}
if (last < src.length) {
const tail = src.slice(last).trim();
if (tail) {
const span = document.createElement('span');
span.className = 'chat-text';
appendMarkdownOrPlain(span, tail);
block.appendChild(span);
}
}
}

export function appendForwardBlock(block, forwardFrom) {
if (!forwardFrom) return;
const box = document.createElement('div');
Expand Down Expand Up @@ -200,45 +265,6 @@ function appendVideoBubble(block, attachment) {
block.appendChild(btn);
}

function appendTextWithEmbeds(block, text) {
const src = String(text || '').trim();
if (!src) return;
const yts = findYoutubeInText(src);
if (!yts.length) {
const span = document.createElement('span');
span.className = 'chat-text';
appendLinkifiedText(span, src, openExternalUrl);
block.appendChild(span);
return;
}

let last = 0;
for (const yt of yts) {
if (yt.index > last) {
const chunk = src.slice(last, yt.index).trim();
if (chunk) {
const span = document.createElement('span');
span.className = 'chat-text';
appendLinkifiedText(span, chunk, openExternalUrl);
block.appendChild(span);
}
}
appendYoutubeEmbed(block, yt, {
onPlay: () => openYoutubeViewer(yt.id),
});
last = yt.index + yt.raw.length;
}
if (last < src.length) {
const tail = src.slice(last).trim();
if (tail) {
const span = document.createElement('span');
span.className = 'chat-text';
appendLinkifiedText(span, tail, openExternalUrl);
block.appendChild(span);
}
}
}

export function appendChatMessageBody(block, m, opts = {}) {
if (m.forwardFrom) appendForwardBlock(block, m.forwardFrom);
if (m.forwardFrom) appendForwardSeedNotice(block, m.forwardFrom, opts);
Expand Down
1 change: 1 addition & 0 deletions renderer/group-call-window.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<title>BLIP — Group call</title>
<link rel="stylesheet" href="./themes.css" />
<link rel="stylesheet" href="./styles.css" />
<link rel="stylesheet" href="./nest-ui.css" />
<link rel="stylesheet" href="./groups-ui.css" />
<link rel="stylesheet" href="./trust.css" />
<style>
Expand Down
6 changes: 6 additions & 0 deletions renderer/i18n.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,9 @@ const locales = {
'settings.appearance': 'Look & background',
'settings.appearance_theme': 'Theme',
'settings.appearance_accent': 'Color accent',
'settings.nest_ui': 'NestUI 1.0',
'settings.nest_ui_hint':
'Alternate UI design: soft system look instead of the pixel mesh skin. Same layout and controls — only the visual language changes.',
'settings.bg_animated': 'Animated background',
'settings.bg_animated_mesh_hint': 'Ember and Rift scenes require MESH+.',
'settings.bg_art': 'Art background',
Expand Down Expand Up @@ -1293,6 +1296,9 @@ const locales = {
'settings.appearance': 'Внешний вид и фон',
'settings.appearance_theme': 'Тема',
'settings.appearance_accent': 'Цветовой акцент',
'settings.nest_ui': 'NestUI 1.0',
'settings.nest_ui_hint':
'Другой дизайн интерфейса: мягкий системный вид вместо пиксельной сетки. Раскладка и элементы те же — меняется только визуальный язык.',
'settings.bg_animated': 'Анимированный фон',
'settings.bg_animated_mesh_hint': 'Сцены «Угли» и «Разлом» — только с МЭШ ПЛЮС.',
'settings.bg_art': 'Арт-фон',
Expand Down
3 changes: 2 additions & 1 deletion renderer/index.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" data-ui-skin="pixel">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
Expand All @@ -8,6 +8,7 @@
<link rel="stylesheet" href="./themes.css" />
<link rel="stylesheet" href="./wallpaper-art.css" />
<link rel="stylesheet" href="./styles.css" />
<link rel="stylesheet" href="./nest-ui.css" />
<link rel="stylesheet" href="./groups-ui.css" />
<link rel="stylesheet" href="./trust.css" />
</head>
Expand Down
Loading
Loading