v0.0.30: markdown rendering in web, fix scrolling
Web: - Markdown renderer: **bold**, *italic*, `code`, ```code blocks```, # headers, [links](url), > blockquotes, - lists - All message text rendered as markdown (bot messages look great now) - Fixed scroll: overflow-y: scroll + min-height: 0 on messages container - CSS for code blocks, pre, headers, blockquotes, lists - Styled: code=cyan bg, pre=dark bg+border, bold=white, italic=amber Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
10
warzone/Cargo.lock
generated
10
warzone/Cargo.lock
generated
@@ -2956,7 +2956,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-client"
|
name = "warzone-client"
|
||||||
version = "0.0.29"
|
version = "0.0.30"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"argon2",
|
"argon2",
|
||||||
@@ -2989,7 +2989,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-mule"
|
name = "warzone-mule"
|
||||||
version = "0.0.29"
|
version = "0.0.30"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
@@ -2998,7 +2998,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-protocol"
|
name = "warzone-protocol"
|
||||||
version = "0.0.29"
|
version = "0.0.30"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bincode",
|
"bincode",
|
||||||
@@ -3023,7 +3023,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-server"
|
name = "warzone-server"
|
||||||
version = "0.0.29"
|
version = "0.0.30"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
@@ -3053,7 +3053,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warzone-wasm"
|
name = "warzone-wasm"
|
||||||
version = "0.0.29"
|
version = "0.0.30"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bincode",
|
"bincode",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.0.29"
|
version = "0.0.30"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
rust-version = "1.75"
|
rust-version = "1.75"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "warzone-protocol"
|
name = "warzone-protocol"
|
||||||
version = "0.0.29"
|
version = "0.0.30"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
description = "Core crypto & wire protocol for featherChat (Warzone messenger)"
|
description = "Core crypto & wire protocol for featherChat (Warzone messenger)"
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ async fn pwa_manifest() -> impl IntoResponse {
|
|||||||
|
|
||||||
async fn service_worker() -> impl IntoResponse {
|
async fn service_worker() -> impl IntoResponse {
|
||||||
([(header::CONTENT_TYPE, "application/javascript")], r##"
|
([(header::CONTENT_TYPE, "application/javascript")], r##"
|
||||||
const CACHE = 'wz-v11';
|
const CACHE = 'wz-v12';
|
||||||
const SHELL = ['/', '/wasm/warzone_wasm.js', '/wasm/warzone_wasm_bg.wasm', '/icon.svg', '/manifest.json'];
|
const SHELL = ['/', '/wasm/warzone_wasm.js', '/wasm/warzone_wasm_bg.wasm', '/icon.svg', '/manifest.json'];
|
||||||
|
|
||||||
self.addEventListener('install', e => {
|
self.addEventListener('install', e => {
|
||||||
@@ -154,8 +154,18 @@ const WEB_HTML: &str = r##"<!DOCTYPE html>
|
|||||||
#chat-header input { background: #1a1a2e; border: 1px solid #333; color: #e6a23c; padding: 2px 6px;
|
#chat-header input { background: #1a1a2e; border: 1px solid #333; color: #e6a23c; padding: 2px 6px;
|
||||||
border-radius: 3px; font-family: inherit; font-size: 0.85em; width: 280px; }
|
border-radius: 3px; font-family: inherit; font-size: 0.85em; width: 280px; }
|
||||||
|
|
||||||
#messages { flex: 1; overflow-y: auto; padding: 8px 10px; -webkit-overflow-scrolling: touch; }
|
#messages { flex: 1; overflow-y: scroll; padding: 8px 10px; -webkit-overflow-scrolling: touch; min-height: 0; }
|
||||||
.msg { padding: 2px 0; font-size: 0.85em; white-space: pre-wrap; word-wrap: break-word; }
|
.msg { padding: 2px 0; font-size: 0.85em; white-space: pre-wrap; word-wrap: break-word; }
|
||||||
|
.msg code { background: #1a1a3e; padding: 1px 4px; border-radius: 3px; font-size: 0.95em; color: #4fc3f7; }
|
||||||
|
.msg pre { background: #0d0d20; padding: 8px; border-radius: 4px; margin: 4px 0; overflow-x: auto; border: 1px solid #222; }
|
||||||
|
.msg pre code { background: none; padding: 0; }
|
||||||
|
.msg strong, .msg b { color: #fff; }
|
||||||
|
.msg em, .msg i { color: #e6a23c; }
|
||||||
|
.msg a { color: #4fc3f7; }
|
||||||
|
.msg blockquote { border-left: 3px solid #444; padding-left: 8px; color: #888; margin: 4px 0; }
|
||||||
|
.msg ul, .msg ol { padding-left: 20px; margin: 4px 0; }
|
||||||
|
.msg h1, .msg h2, .msg h3 { color: #fff; margin: 6px 0 2px; }
|
||||||
|
.msg h1 { font-size: 1.2em; } .msg h2 { font-size: 1.1em; } .msg h3 { font-size: 1em; }
|
||||||
.msg .ts { color: #333; margin-right: 4px; }
|
.msg .ts { color: #333; margin-right: 4px; }
|
||||||
.msg .from-self { color: #4ade80; font-weight: bold; }
|
.msg .from-self { color: #4ade80; font-weight: bold; }
|
||||||
.msg .from-sys { color: #5e9ca0; font-style: italic; }
|
.msg .from-sys { color: #5e9ca0; font-style: italic; }
|
||||||
@@ -241,7 +251,7 @@ let pollTimer = null;
|
|||||||
let ws = null; // WebSocket connection
|
let ws = null; // WebSocket connection
|
||||||
let wasmReady = false;
|
let wasmReady = false;
|
||||||
|
|
||||||
const VERSION = '0.0.29';
|
const VERSION = '0.0.30';
|
||||||
let DEBUG = true; // toggle with /debug command
|
let DEBUG = true; // toggle with /debug command
|
||||||
|
|
||||||
// ── Receipt tracking ──
|
// ── Receipt tracking ──
|
||||||
@@ -737,6 +747,34 @@ function esc(s) {
|
|||||||
return d.innerHTML;
|
return d.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderMd(text) {
|
||||||
|
let s = esc(text);
|
||||||
|
// Code blocks: ```...```
|
||||||
|
s = s.replace(/```(\w*)\n?([\s\S]*?)```/g, '<pre><code>$2</code></pre>');
|
||||||
|
// Inline code: `...`
|
||||||
|
s = s.replace(/`([^`]+)`/g, '<code>$1</code>');
|
||||||
|
// Bold: **...**
|
||||||
|
s = s.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
||||||
|
// Italic: *...*
|
||||||
|
s = s.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, '<em>$1</em>');
|
||||||
|
// Headers: ### ... (at line start)
|
||||||
|
s = s.replace(/^### (.+)$/gm, '<h3>$1</h3>');
|
||||||
|
s = s.replace(/^## (.+)$/gm, '<h2>$1</h2>');
|
||||||
|
s = s.replace(/^# (.+)$/gm, '<h1>$1</h1>');
|
||||||
|
// Links: [text](url)
|
||||||
|
s = s.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
|
||||||
|
// Blockquotes: > ...
|
||||||
|
s = s.replace(/^> (.+)$/gm, '<blockquote>$1</blockquote>');
|
||||||
|
// Unordered lists: - ...
|
||||||
|
s = s.replace(/^- (.+)$/gm, '<li>$1</li>');
|
||||||
|
s = s.replace(/(<li>.*<\/li>\n?)+/g, '<ul>$&</ul>');
|
||||||
|
// Line breaks
|
||||||
|
s = s.replace(/\n/g, '<br>');
|
||||||
|
// Clean up br inside pre
|
||||||
|
s = s.replace(/<pre><code>([\s\S]*?)<\/code><\/pre>/g, (m, code) => '<pre><code>' + code.replace(/<br>/g, '\n') + '</code></pre>');
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
const PEER_COLORS = ['#e6a23c','#f56c9d','#67c7eb','#b39ddb','#ff8a65','#81c784','#ce93d8','#4fc3f7','#ffb74d','#aed581','#f06292','#4dd0e1'];
|
const PEER_COLORS = ['#e6a23c','#f56c9d','#67c7eb','#b39ddb','#ff8a65','#81c784','#ce93d8','#4fc3f7','#ffb74d','#aed581','#f06292','#4dd0e1'];
|
||||||
|
|
||||||
function peerColor(name) {
|
function peerColor(name) {
|
||||||
@@ -813,7 +851,7 @@ function addMsg(from, text, isSelf, messageId, rawHtml) {
|
|||||||
const status = (sentMsgReceipts[messageId] && sentMsgReceipts[messageId].status) || 'sent';
|
const status = (sentMsgReceipts[messageId] && sentMsgReceipts[messageId].status) || 'sent';
|
||||||
receiptHtml = ' <span class="receipt" style="color:' + receiptColor(status) + '"> ' + receiptIndicator(status) + '</span>';
|
receiptHtml = ' <span class="receipt" style="color:' + receiptColor(status) + '"> ' + receiptIndicator(status) + '</span>';
|
||||||
}
|
}
|
||||||
const bodyHtml = rawHtml ? text : makeAddressClickable(esc(text));
|
const bodyHtml = rawHtml ? text : makeAddressClickable(renderMd(text));
|
||||||
d.innerHTML = '<span class="ts">' + ts() + '</span> ' + lock + '<span style="color:' + color + ';font-weight:bold">' + makeAddressClickable(esc(from)) + '</span>: ' + bodyHtml + receiptHtml;
|
d.innerHTML = '<span class="ts">' + ts() + '</span> ' + lock + '<span style="color:' + color + ';font-weight:bold">' + makeAddressClickable(esc(from)) + '</span>: ' + bodyHtml + receiptHtml;
|
||||||
// Attach click handler for .addr spans
|
// Attach click handler for .addr spans
|
||||||
d.querySelectorAll('.addr').forEach(el => {
|
d.querySelectorAll('.addr').forEach(el => {
|
||||||
|
|||||||
Reference in New Issue
Block a user