Compare commits

..

2 commits

Author SHA1 Message Date
6882b49336 refactor
All checks were successful
/ upload (release) Successful in 3m32s
2026-02-27 15:57:39 +01:00
5c97cc515f fix: uploads 2026-02-27 15:51:03 +01:00
7 changed files with 360 additions and 1656 deletions

View file

@ -1,3 +1,4 @@
<!-- Generated from shared-html/index.template.html. Do not edit directly. -->
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
@ -8,7 +9,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="styles.css" /> <link rel="stylesheet" href="../static/styles.css" />
<!-- Lucide Icons --> <!-- Lucide Icons -->
<script src="https://unpkg.com/lucide@latest"></script> <script src="https://unpkg.com/lucide@latest"></script>
</head> </head>
@ -116,8 +117,7 @@
<div class="user-actions"> <div class="user-actions">
<div id="update-notifier" class="update-notifier hidden"> <div id="update-notifier" class="update-notifier hidden">
<button id="update-download-btn" title="Download Update"><i data-lucide="download"></i></button> <button id="update-download-btn" title="Download Update"><i data-lucide="download"></i></button>
<button id="update-install-btn" title="Install Update" class="hidden"><i <button id="update-install-btn" title="Install Update" class="hidden"><i data-lucide="arrow-up-circle"></i></button>
data-lucide="arrow-up-circle"></i></button>
</div> </div>
<button id="user-settings-btn" title="Settings"><i data-lucide="settings"></i></button> <button id="user-settings-btn" title="Settings"><i data-lucide="settings"></i></button>
<button id="logout-btn" title="Logout"><i data-lucide="log-out"></i></button> <button id="logout-btn" title="Logout"><i data-lucide="log-out"></i></button>
@ -144,6 +144,10 @@
<div class="chat-input-wrapper"> <div class="chat-input-wrapper">
<form id="message-form" class="message-form"> <form id="message-form" class="message-form">
<input id="media-file-input" type="file" class="hidden-file-input" accept="image/*,video/*,audio/*,.pdf,.txt,.zip,.rar,.7z,.tar,.gz,.json,.csv,.md" />
<button type="button" id="media-upload-btn" class="input-action-btn" title="Upload File">
<i data-lucide="paperclip"></i>
</button>
<button type="button" id="gif-btn" class="input-action-btn" title="Open GIF Picker"> <button type="button" id="gif-btn" class="input-action-btn" title="Open GIF Picker">
<i data-lucide="image"></i> <i data-lucide="image"></i>
</button> </button>
@ -268,4 +272,4 @@
<script type="module" src="app.js?v=20260227-shared-core-1"></script> <script type="module" src="app.js?v=20260227-shared-core-1"></script>
</body> </body>
</html> </html>

File diff suppressed because it is too large Load diff

View file

@ -10,11 +10,12 @@
}, },
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"start": "electron .", "build:html": "node scripts/generate-html.js",
"dev": "electron .", "start": "npm run build:html && electron .",
"dist": "electron-builder --publish never", "dev": "npm run build:html && electron .",
"dist:linux": "electron-builder --linux --publish never", "dist": "npm run build:html && electron-builder --publish never",
"dist:win": "electron-builder --win --publish never" "dist:linux": "npm run build:html && electron-builder --linux --publish never",
"dist:win": "npm run build:html && electron-builder --win --publish never"
}, },
"build": { "build": {
"appId": "me.flegr.chattz", "appId": "me.flegr.chattz",

53
scripts/generate-html.js Normal file
View file

@ -0,0 +1,53 @@
const fs = require('fs');
const path = require('path');
const root = path.resolve(__dirname, '..');
const templatePath = path.join(root, 'shared-html', 'index.template.html');
const template = fs.readFileSync(templatePath, 'utf8');
const variants = {
web: {
styles_href: '/static/styles.css',
app_src: '/static/app.js?v=20260227-shared-core-1',
web_downloads: `<div class="auth-downloads">
<span>Desktop downloads:</span>
<a href="/static/installers/chattz-windows.exe">Windows</a>
<a href="/static/installers/chattz-linux.rpm">Linux (RPM)</a>
</div>`,
desktop_updater: '',
outPath: path.join(root, 'static', 'index.html'),
},
desktop: {
styles_href: '../static/styles.css',
app_src: 'app.js?v=20260227-shared-core-1',
web_downloads: '',
desktop_updater: `<div id="update-notifier" class="update-notifier hidden">
<button id="update-download-btn" title="Download Update"><i data-lucide="download"></i></button>
<button id="update-install-btn" title="Install Update" class="hidden"><i data-lucide="arrow-up-circle"></i></button>
</div>`,
outPath: path.join(root, 'desktop', 'index.html'),
},
};
for (const variant of Object.values(variants)) {
let output = template;
for (const [key, value] of Object.entries(variant)) {
if (key === 'outPath') continue;
const token = `{{${key}}}`;
if (value === '') {
output = output.replace(new RegExp(`^[ \\t]*\\{\\{${key}\\}\\}[ \\t]*\\n?`, 'm'), '');
continue;
}
output = output.replaceAll(token, value);
}
if (/\{\{[a-z_]+\}\}/i.test(output)) {
throw new Error(`Unresolved template placeholders remain in ${variant.outPath}`);
}
fs.writeFileSync(
variant.outPath,
`<!-- Generated from shared-html/index.template.html. Do not edit directly. -->\n${output}`,
'utf8'
);
}

View file

@ -0,0 +1,272 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<title>Chattz</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{styles_href}}" />
<!-- Lucide Icons -->
<script src="https://unpkg.com/lucide@latest"></script>
</head>
<body>
<div class="auth-screen" id="auth-screen">
<div class="auth-card">
<div class="brand-large">C</div>
<h1>Chattz</h1>
<p>Sign in to open your servers.</p>
<button id="login-btn">Login with Authentik</button>
{{web_downloads}}
<p id="status" class="status-line"></p>
</div>
</div>
<div class="shell hidden" id="main">
<aside class="server-rail">
<div class="brand" title="Home">
<i data-lucide="message-square"></i>
</div>
<div class="separator"></div>
<div id="guild-list" class="guild-list"></div>
<button id="add-guild-btn" class="guild-pill action-pill" title="Add a Server">
<i data-lucide="plus"></i>
</button>
</aside>
<aside class="channel-sidebar">
<header class="sidebar-header clickable" id="guild-header">
<h2 id="guild-title">No server selected</h2>
<div class="sidebar-header-actions">
<span id="invite-copied-badge" class="copied-badge hidden">Copied!</span>
<button id="create-invite-btn" class="header-action-btn" title="Create Invite Link">
<i data-lucide="link-2"></i>
</button>
<i data-lucide="chevron-down"></i>
</div>
</header>
<div class="sidebar-scroll">
<section class="sidebar-group">
<div class="group-title">
<i data-lucide="chevron-down" class="group-toggle"></i>
<span>Text Channels</span>
<button id="add-text-btn" class="add-btn" type="button" title="Create Text Channel">
<i data-lucide="plus"></i>
</button>
</div>
<div id="channel-list" class="channel-list"></div>
</section>
<section class="sidebar-group">
<div class="group-title">
<i data-lucide="chevron-down" class="group-toggle"></i>
<span>Voice Channels</span>
<button id="add-voice-btn" class="add-btn" type="button" title="Create Voice Channel">
<i data-lucide="plus"></i>
</button>
</div>
<div id="voice-channel-list" class="channel-list"></div>
</section>
<section class="sidebar-group">
<div class="group-title">
<i data-lucide="chevron-down" class="group-toggle"></i>
<span>Direct Messages</span>
</div>
<div id="dm-list" class="channel-list"></div>
</section>
</div>
<div class="sidebar-footer">
<div id="voice-connection" class="voice-connection hidden">
<div class="vc-info">
<i data-lucide="signal-high" class="vc-icon"></i>
<div class="vc-text">
<span class="vc-status">Voice Connected</span>
<span id="vc-channel-name" class="vc-name">General</span>
</div>
<div class="vc-actions">
<button id="voice-video-btn" title="Turn on Camera"><i data-lucide="video-off"></i></button>
<button id="voice-screen-btn" title="Share Screen"><i data-lucide="monitor-off"></i></button>
<button id="voice-mute-btn" title="Mute"><i data-lucide="mic"></i></button>
<button id="voice-leave-btn" title="Disconnect"><i data-lucide="phone-off"></i></button>
</div>
</div>
<div id="soundboard" class="soundboard-container hidden">
<div class="soundboard-header">
<span>Sound Board</span>
<span id="add-sound-btn" class="btn-add-sound">+ Add Sound</span>
</div>
<div id="soundboard-grid" class="soundboard-grid"></div>
</div>
</div>
<div class="user-panel">
<div class="avatar-wrapper">
<div id="user-avatar" class="avatar">U</div>
<div class="status-dot online"></div>
</div>
<div class="user-info">
<div id="user-name" class="display-name">Username</div>
<div class="user-status">Online</div>
</div>
<div class="user-actions">
{{desktop_updater}}
<button id="user-settings-btn" title="Settings"><i data-lucide="settings"></i></button>
<button id="logout-btn" title="Logout"><i data-lucide="log-out"></i></button>
</div>
</div>
</div>
</aside>
<main class="chat-pane">
<header class="chat-header">
<button id="mobile-menu-btn" class="mobile-toggle-btn" title="Menu">
<i data-lucide="menu"></i>
</button>
<i data-lucide="hash" class="header-icon"></i>
<h3 id="channel-title">Select a channel</h3>
<div style="flex:1"></div>
<button id="mobile-members-btn" class="mobile-toggle-btn" title="Members">
<i data-lucide="users"></i>
</button>
</header>
<div id="video-grid" class="video-grid hidden"></div>
<div id="message-list" class="message-list"></div>
<div class="chat-input-wrapper">
<form id="message-form" class="message-form">
<input id="media-file-input" type="file" class="hidden-file-input" accept="image/*,video/*,audio/*,.pdf,.txt,.zip,.rar,.7z,.tar,.gz,.json,.csv,.md" />
<button type="button" id="media-upload-btn" class="input-action-btn" title="Upload File">
<i data-lucide="paperclip"></i>
</button>
<button type="button" id="gif-btn" class="input-action-btn" title="Open GIF Picker">
<i data-lucide="image"></i>
</button>
<input id="message-body" autocomplete="off" placeholder="Message #channel" maxlength="4000" required />
<button type="submit" class="hidden"></button>
</form>
</div>
</main>
<aside class="utility-sidebar" id="utility-sidebar">
<header class="sidebar-header">
<h2>Members</h2>
</header>
<div class="member-list-wrapper">
<div id="member-list" class="member-list"></div>
</div>
</aside>
</div>
<div id="overlay" class="overlay hidden"></div>
<!-- Modals -->
<div id="gif-picker" class="modal-container hidden">
<div class="modal gif-modal">
<div class="modal-header">
<h2>Select GIF</h2>
<button type="button" class="close-btn" id="gif-picker-close">
<i data-lucide="x"></i>
</button>
</div>
<div class="gif-search-wrapper">
<input type="text" id="gif-search-input" placeholder="Search for GIFs..." autocomplete="off">
<i data-lucide="search" class="search-icon"></i>
</div>
<div id="gif-results" class="gif-results-grid">
<!-- GIF results will be injected here -->
<div class="gif-loading hidden">Loading...</div>
</div>
</div>
</div>
<div id="modal-container" class="modal-container hidden">
<div class="modal">
<h2 id="modal-title">Create Server</h2>
<form id="guild-form" class="modal-form">
<div class="form-item">
<label for="guild-name">SERVER NAME</label>
<input id="guild-name" placeholder="My Awesome Server" maxlength="64" required />
</div>
<div class="modal-footer">
<button type="button" class="cancel-btn" id="modal-cancel">Cancel</button>
<button type="submit" class="submit-btn">Create</button>
</div>
</form>
</div>
</div>
<div id="channel-modal" class="modal-container hidden">
<div class="modal">
<h2>Create Channel</h2>
<form id="channel-form" class="modal-form">
<div class="form-item">
<label>CHANNEL TYPE</label>
<div class="radio-group">
<label class="radio-item">
<input type="radio" name="channel-kind" value="text" checked>
<div class="radio-box">
<i data-lucide="hash"></i>
<div class="radio-text">
<strong>Text</strong>
<span>Send messages, images, and GIFs.</span>
</div>
</div>
</label>
<label class="radio-item">
<input type="radio" name="channel-kind" value="voice">
<div class="radio-box">
<i data-lucide="volume-2"></i>
<div class="radio-text">
<strong>Voice</strong>
<span>Hang out together with voice and video.</span>
</div>
</div>
</label>
</div>
</div>
<div class="form-item">
<label for="channel-name">CHANNEL NAME</label>
<input id="channel-name" placeholder="new-channel" maxlength="64" required />
</div>
<div class="modal-footer">
<button type="button" class="cancel-btn" id="channel-modal-cancel">Cancel</button>
<button type="submit" class="submit-btn">Create Channel</button>
</div>
</form>
</div>
</div>
<div id="sound-modal" class="modal-container hidden">
<div class="modal">
<h2>Add Sound</h2>
<form id="sound-form" class="modal-form">
<div class="form-item">
<label for="sound-name">NAME</label>
<input id="sound-name" placeholder="Quack" maxlength="32" required />
</div>
<div class="form-item">
<label for="sound-icon">ICON (Emoji or Initials)</label>
<input id="sound-icon" placeholder="🦆" maxlength="4" required />
</div>
<div class="form-item">
<label for="sound-file">AUDIO FILE (MP3/WAV)</label>
<input id="sound-file" type="file" accept="audio/*" required />
</div>
<div class="modal-footer">
<button type="button" class="cancel-btn" id="sound-modal-cancel">Cancel</button>
<button type="submit" class="submit-btn" id="sound-submit-btn">Add Sound</button>
</div>
</form>
</div>
</div>
<script type="module" src="{{app_src}}"></script>
</body>
</html>

View file

@ -1,3 +1,4 @@
<!-- Generated from shared-html/index.template.html. Do not edit directly. -->
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">

View file

@ -1914,25 +1914,27 @@ async function init() {
} catch (err) { alert(err.message); } } catch (err) { alert(err.message); }
}; };
el.mediaUploadBtn.onclick = () => { if (el.mediaUploadBtn && el.mediaFileInput) {
if (!state.selectedTextChannelId && !state.selectedDmUserId) { el.mediaUploadBtn.onclick = () => {
alert("Select a chat first."); if (!state.selectedTextChannelId && !state.selectedDmUserId) {
return; alert("Select a chat first.");
} return;
el.mediaFileInput.click(); }
}; el.mediaFileInput.click();
};
el.mediaFileInput.onchange = async () => { el.mediaFileInput.onchange = async () => {
const file = el.mediaFileInput.files?.[0]; const file = el.mediaFileInput.files?.[0];
if (!file) return; if (!file) return;
try { try {
await uploadMediaFile(file); await uploadMediaFile(file);
} catch (err) { } catch (err) {
alert(err.message); alert(err.message);
} finally { } finally {
el.mediaFileInput.value = ""; el.mediaFileInput.value = "";
} }
}; };
}
// 4. Voice/Soundboard Controls // 4. Voice/Soundboard Controls
el.voiceVideoBtn.onclick = toggleVideo; el.voiceVideoBtn.onclick = toggleVideo;