parent
bcac7fc375
commit
0273eab753
8 changed files with 127 additions and 8 deletions
|
|
@ -71,6 +71,7 @@ const el = {
|
||||||
addGuildBtn: document.getElementById("add-guild-btn"),
|
addGuildBtn: document.getElementById("add-guild-btn"),
|
||||||
guildTitle: document.getElementById("guild-title"),
|
guildTitle: document.getElementById("guild-title"),
|
||||||
createInviteBtn: document.getElementById("create-invite-btn"),
|
createInviteBtn: document.getElementById("create-invite-btn"),
|
||||||
|
inviteCopiedBadge: document.getElementById("invite-copied-badge"),
|
||||||
|
|
||||||
// Channels
|
// Channels
|
||||||
channelList: document.getElementById("channel-list"),
|
channelList: document.getElementById("channel-list"),
|
||||||
|
|
@ -536,11 +537,27 @@ async function createInviteLink() {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ max_uses: 50, expires_in_hours: 24 }),
|
body: JSON.stringify({ max_uses: 50, expires_in_hours: 24 }),
|
||||||
});
|
});
|
||||||
const link = `${location.origin}/?invite=${encodeURIComponent(invite.code)}`;
|
// Use the backend URL if available, otherwise fallback to current origin
|
||||||
|
const base = API_BASE_URL ? new URL(API_BASE_URL).origin : location.origin;
|
||||||
|
const link = `${base}/?invite=${encodeURIComponent(invite.code)}`;
|
||||||
|
|
||||||
|
// Use Electron's native clipboard API if available (defined in preload.js)
|
||||||
|
if (window.electronAPI && window.electronAPI.copyToClipboard) {
|
||||||
|
try {
|
||||||
|
await window.electronAPI.copyToClipboard(link);
|
||||||
|
showCopiedBadge();
|
||||||
|
return;
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Electron clipboard write failed:", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to navigator.clipboard
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(link);
|
await navigator.clipboard.writeText(link);
|
||||||
alert(`Invite link copied:\n${link}`);
|
showCopiedBadge();
|
||||||
} catch {
|
} catch (err) {
|
||||||
|
console.error("Navigator clipboard write failed:", err);
|
||||||
prompt("Copy invite link:", link);
|
prompt("Copy invite link:", link);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
@ -548,6 +565,13 @@ async function createInviteLink() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showCopiedBadge() {
|
||||||
|
el.inviteCopiedBadge.classList.remove("hidden");
|
||||||
|
setTimeout(() => {
|
||||||
|
el.inviteCopiedBadge.classList.add("hidden");
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
// --- Logic ---
|
// --- Logic ---
|
||||||
|
|
||||||
async function loadGuilds() {
|
async function loadGuilds() {
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@
|
||||||
<header class="sidebar-header clickable" id="guild-header">
|
<header class="sidebar-header clickable" id="guild-header">
|
||||||
<h2 id="guild-title">No server selected</h2>
|
<h2 id="guild-title">No server selected</h2>
|
||||||
<div class="sidebar-header-actions">
|
<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">
|
<button id="create-invite-btn" class="header-action-btn" title="Create Invite Link">
|
||||||
<i data-lucide="link-2"></i>
|
<i data-lucide="link-2"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
5
desktop/preload.js
Normal file
5
desktop/preload.js
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
const { contextBridge, ipcRenderer } = require('electron');
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('electronAPI', {
|
||||||
|
copyToClipboard: (text) => ipcRenderer.invoke('clipboard-write', text)
|
||||||
|
});
|
||||||
|
|
@ -354,6 +354,43 @@ select {
|
||||||
color: var(--text-strong);
|
color: var(--text-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.copied-badge {
|
||||||
|
background: var(--brand);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.4px;
|
||||||
|
animation: fadeInOut 2s ease forwards;
|
||||||
|
pointer-events: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
order: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInOut {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
15% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
85% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-4px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.header-action-btn i {
|
.header-action-btn i {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
|
|
|
||||||
14
main.js
14
main.js
|
|
@ -1,4 +1,4 @@
|
||||||
const { app, BrowserWindow, session, desktopCapturer } = require('electron');
|
const { app, BrowserWindow, session, desktopCapturer, ipcMain, clipboard } = require('electron');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const url = require('url');
|
const url = require('url');
|
||||||
|
|
||||||
|
|
@ -14,20 +14,26 @@ function createWindow() {
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
webSecurity: false, // Required for cross-origin fetch/ws from file:// with cookies
|
webSecurity: false, // Required for cross-origin fetch/ws from file:// with cookies
|
||||||
session: sess
|
session: sess,
|
||||||
|
preload: path.join(__dirname, 'desktop', 'preload.js')
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('clipboard-write', (event, text) => {
|
||||||
|
clipboard.writeText(text);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
// Auto-approve media permissions (camera, microphone)
|
// Auto-approve media permissions (camera, microphone)
|
||||||
sess.setPermissionCheckHandler((webContents, permission) => {
|
sess.setPermissionCheckHandler((webContents, permission) => {
|
||||||
if (permission === 'media') {
|
if (permission === 'media' || permission === 'clipboard-read' || permission === 'clipboard-write') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
sess.setPermissionRequestHandler((webContents, permission, callback) => {
|
sess.setPermissionRequestHandler((webContents, permission, callback) => {
|
||||||
if (permission === 'media') {
|
if (permission === 'media' || permission === 'clipboard-read' || permission === 'clipboard-write') {
|
||||||
callback(true);
|
callback(true);
|
||||||
} else {
|
} else {
|
||||||
callback(false);
|
callback(false);
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ const el = {
|
||||||
addGuildBtn: document.getElementById("add-guild-btn"),
|
addGuildBtn: document.getElementById("add-guild-btn"),
|
||||||
guildTitle: document.getElementById("guild-title"),
|
guildTitle: document.getElementById("guild-title"),
|
||||||
createInviteBtn: document.getElementById("create-invite-btn"),
|
createInviteBtn: document.getElementById("create-invite-btn"),
|
||||||
|
inviteCopiedBadge: document.getElementById("invite-copied-badge"),
|
||||||
|
|
||||||
// Channels
|
// Channels
|
||||||
channelList: document.getElementById("channel-list"),
|
channelList: document.getElementById("channel-list"),
|
||||||
|
|
@ -510,7 +511,7 @@ async function createInviteLink() {
|
||||||
const link = `${location.origin}/?invite=${encodeURIComponent(invite.code)}`;
|
const link = `${location.origin}/?invite=${encodeURIComponent(invite.code)}`;
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(link);
|
await navigator.clipboard.writeText(link);
|
||||||
alert(`Invite link copied:\n${link}`);
|
showCopiedBadge();
|
||||||
} catch {
|
} catch {
|
||||||
prompt("Copy invite link:", link);
|
prompt("Copy invite link:", link);
|
||||||
}
|
}
|
||||||
|
|
@ -519,6 +520,13 @@ async function createInviteLink() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showCopiedBadge() {
|
||||||
|
el.inviteCopiedBadge.classList.remove("hidden");
|
||||||
|
setTimeout(() => {
|
||||||
|
el.inviteCopiedBadge.classList.add("hidden");
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
// --- Logic ---
|
// --- Logic ---
|
||||||
|
|
||||||
async function loadGuilds() {
|
async function loadGuilds() {
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@
|
||||||
<header class="sidebar-header clickable" id="guild-header">
|
<header class="sidebar-header clickable" id="guild-header">
|
||||||
<h2 id="guild-title">No server selected</h2>
|
<h2 id="guild-title">No server selected</h2>
|
||||||
<div class="sidebar-header-actions">
|
<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">
|
<button id="create-invite-btn" class="header-action-btn" title="Create Invite Link">
|
||||||
<i data-lucide="link-2"></i>
|
<i data-lucide="link-2"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -354,6 +354,43 @@ select {
|
||||||
color: var(--text-strong);
|
color: var(--text-strong);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.copied-badge {
|
||||||
|
background: var(--brand);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.4px;
|
||||||
|
animation: fadeInOut 2s ease forwards;
|
||||||
|
pointer-events: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
order: -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInOut {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
15% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
85% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-4px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.header-action-btn i {
|
.header-action-btn i {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue