mirror of
https://github.com/smartfrigde/armcord.git
synced 2026-08-19 14:23:23 +00:00
Compare commits
No commits in common. "758b97e63e909779b8f8555845098dcfb23a31d8" and "642533f8007a37067d612c03d8f3871c9d52b68a" have entirely different histories.
758b97e63e
...
642533f800
10 changed files with 159 additions and 457 deletions
11
assets/app/js/monacoLoader.js
Normal file
11
assets/app/js/monacoLoader.js
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -46,7 +46,6 @@
|
|||
"electron": "43.2.0",
|
||||
"electron-builder": "26.8.1",
|
||||
"lucide-solid": "^1.16.0",
|
||||
"monaco-editor": "0.52.2",
|
||||
"rolldown": "1.0.3",
|
||||
"rollup-plugin-copy": "^3.5.0",
|
||||
"solid-js": "^1.9.14",
|
||||
|
|
|
|||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
|
|
@ -84,9 +84,6 @@ importers:
|
|||
lucide-solid:
|
||||
specifier: ^1.16.0
|
||||
version: 1.16.0(solid-js@1.9.14)
|
||||
monaco-editor:
|
||||
specifier: 0.52.2
|
||||
version: 0.52.2
|
||||
rolldown:
|
||||
specifier: 1.0.3
|
||||
version: 1.0.3
|
||||
|
|
@ -2107,9 +2104,6 @@ packages:
|
|||
mlly@1.8.2:
|
||||
resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==}
|
||||
|
||||
monaco-editor@0.52.2:
|
||||
resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
|
|
@ -4851,8 +4845,6 @@ snapshots:
|
|||
pkg-types: 1.3.1
|
||||
ufo: 1.6.4
|
||||
|
||||
monaco-editor@0.52.2: {}
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
neo-async@2.6.2: {}
|
||||
|
|
|
|||
|
|
@ -34,8 +34,6 @@ export default defineConfig([
|
|||
{ src: "src/**/**/*.js", dest: "ts-out/js/" },
|
||||
{ src: "package.json", dest: "ts-out/" },
|
||||
{ src: "assets/**/**", dest: "ts-out/assets/" },
|
||||
// Monaco AMD tree for the offline Quick CSS editor (next to editor.html)
|
||||
{ src: "node_modules/monaco-editor/min/vs/**/*", dest: "ts-out/html/monaco/vs" },
|
||||
],
|
||||
}),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -1,88 +0,0 @@
|
|||
/** True when hostname is exactly `domain` or a subdomain of it (case-insensitive). */
|
||||
export function hostnameMatches(hostname: string, domain: string): boolean {
|
||||
const host = hostname.toLowerCase();
|
||||
const target = domain.toLowerCase();
|
||||
return host === target || host.endsWith(`.${target}`);
|
||||
}
|
||||
|
||||
export function tryParseUrl(urlString: string): URL | null {
|
||||
try {
|
||||
return new URL(urlString);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** True when the frame is a YouTube embed (or Discord Activities YouTube proxy). */
|
||||
export function isYouTubeEmbedOrProxyFrame(frameUrl: string): boolean {
|
||||
const url = tryParseUrl(frameUrl);
|
||||
if (!url) return false;
|
||||
|
||||
if (hostnameMatches(url.hostname, "youtube.com") && url.pathname.includes("/embed/")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Discord Activities may proxy YouTube through *.discordsays.com (youtube.com appears in the path/query)
|
||||
if (
|
||||
hostnameMatches(url.hostname, "discordsays.com") &&
|
||||
(url.pathname.includes("youtube.com") || url.search.includes("youtube.com"))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Telemetry / noise hosts and Discord science endpoints we cancel in webRequest. */
|
||||
export function isTelemetryBlockedUrl(requestUrl: string): boolean {
|
||||
const url = tryParseUrl(requestUrl);
|
||||
if (url?.protocol !== "https:") return false;
|
||||
|
||||
if (/^\/api\/v\d+\/science(?:\/|$)/.test(url.pathname)) return true;
|
||||
if (hostnameMatches(url.hostname, "sentry.io")) return true;
|
||||
if (hostnameMatches(url.hostname, "nel.cloudflare.com")) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blob downloads for Discord calendar (.ics) use `blob:https://discord.com/<uuid>`.
|
||||
* Parse the blob payload URL so `discord.com.evil` / userinfo tricks cannot match.
|
||||
*/
|
||||
export function isDiscordIcsBlobUrl(urlString: string): boolean {
|
||||
const blobUrl = tryParseUrl(urlString);
|
||||
if (blobUrl?.protocol !== "blob:") return false;
|
||||
|
||||
const inner = tryParseUrl(blobUrl.pathname);
|
||||
if (!inner || (inner.protocol !== "https:" && inner.protocol !== "http:")) return false;
|
||||
|
||||
return hostnameMatches(inner.hostname, "discord.com");
|
||||
}
|
||||
|
||||
/** Discord stream popout windows (stable / canary / PTB). */
|
||||
export function isDiscordPopoutUrl(urlString: string): boolean {
|
||||
const url = tryParseUrl(urlString);
|
||||
if (url?.protocol !== "https:" || url.pathname !== "/popout") return false;
|
||||
|
||||
const host = url.hostname.toLowerCase();
|
||||
return host === "discord.com" || host === "canary.discord.com" || host === "ptb.discord.com";
|
||||
}
|
||||
|
||||
const DEFAULT_ALLOWED_LOCALHOST_WS_PORTS = new Set([1211, 1112, 6888]);
|
||||
|
||||
/**
|
||||
* Block stray localhost WebSocket probes, except known Legcord/local RPC ports.
|
||||
* Uses URL parsing instead of substring checks on the raw request URL.
|
||||
*/
|
||||
export function isBlockedLocalhostWebSocket(
|
||||
requestUrl: string,
|
||||
allowedPorts: ReadonlySet<number> = DEFAULT_ALLOWED_LOCALHOST_WS_PORTS,
|
||||
): boolean {
|
||||
const url = tryParseUrl(requestUrl);
|
||||
if (!url || (url.protocol !== "ws:" && url.protocol !== "wss:")) return false;
|
||||
if (url.hostname !== "127.0.0.1" && url.hostname !== "localhost") return false;
|
||||
|
||||
const port = url.port ? Number(url.port) : url.protocol === "wss:" ? 443 : 80;
|
||||
if (!Number.isFinite(port)) return true;
|
||||
return !allowedPorts.has(port);
|
||||
}
|
||||
|
|
@ -6,23 +6,20 @@
|
|||
<link rel="stylesheet" href="legcord://assets/css/editor.css">
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript" src="./monaco/vs/loader.js"></script>
|
||||
<script type="text/javascript" src="legcord://assets/js/monacoLoader.js"></script>
|
||||
<div id="editorCode"></div>
|
||||
<script>
|
||||
const cssCode = cssEditor.get;
|
||||
const monacoRoot = new URL("./monaco/", window.location.href).href;
|
||||
const vs = new URL("./monaco/vs", window.location.href).href;
|
||||
|
||||
require.config({
|
||||
paths: { vs }
|
||||
paths: { vs: "https://cdn.jsdelivr.net/npm/monaco-editor/min/vs" }
|
||||
});
|
||||
|
||||
window.MonacoEnvironment = {
|
||||
getWorkerUrl: () =>
|
||||
`data:text/javascript;charset=utf-8,${encodeURIComponent(`
|
||||
self.MonacoEnvironment = { baseUrl: ${JSON.stringify(monacoRoot)} };
|
||||
importScripts(${JSON.stringify(`${vs}/base/worker/workerMain.js`)});
|
||||
`)}`,
|
||||
getWorkerUrl: (_workerId, _label) => `data:text/javascript;charset=utf-8,${encodeURIComponent(`
|
||||
self.MonacoEnvironment = {
|
||||
baseUrl: 'https://cdn.jsdelivr.net/npm/monaco-editor/min/'
|
||||
};
|
||||
importScripts('https://cdn.jsdelivr.net/npm/monaco-editor/min/vs/base/worker/workerMain.js');`)}`
|
||||
};
|
||||
|
||||
// Monaco init
|
||||
|
|
@ -52,6 +49,8 @@
|
|||
cssEditor.set(editor.getValue());
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -14,7 +14,7 @@ export function openCssEditor(file: string) {
|
|||
preload: path.join(import.meta.dirname, "cssEditor", "preload.mjs"),
|
||||
},
|
||||
});
|
||||
void cssWindow.loadFile(path.join(import.meta.dirname, "html", "editor.html"));
|
||||
cssWindow.loadURL("legcord://html/editor.html");
|
||||
|
||||
ipcMain.on("editor-setCSS", (_event, css: string) => {
|
||||
fs.writeFileSync(file, css);
|
||||
|
|
|
|||
|
|
@ -19,13 +19,6 @@ import { navigateTo } from "../common/dom.js";
|
|||
import { forceQuit, setForceQuit } from "../common/forceQuit.js";
|
||||
import { handleCommands, passedValidArgument } from "../common/handleCommands.js";
|
||||
import { getLang } from "../common/lang.js";
|
||||
import {
|
||||
isBlockedLocalhostWebSocket,
|
||||
isDiscordIcsBlobUrl,
|
||||
isDiscordPopoutUrl,
|
||||
isTelemetryBlockedUrl,
|
||||
isYouTubeEmbedOrProxyFrame,
|
||||
} from "../common/sanitization.js";
|
||||
import { injectThemesMain } from "../common/themes.js";
|
||||
import {
|
||||
DEFAULT_WINDOW_HEIGHT,
|
||||
|
|
@ -206,7 +199,10 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void {
|
|||
return;
|
||||
}
|
||||
frame.once("dom-ready", async () => {
|
||||
if (isYouTubeEmbedOrProxyFrame(frame.url)) {
|
||||
if (
|
||||
frame.url.includes("youtube.com/embed/") ||
|
||||
(frame.url.includes("discordsays") && frame.url.includes("youtube.com"))
|
||||
) {
|
||||
await frame.executeJavaScript(readFileSync(path.join(__dirname, "assets/js/adguard.js"), "utf-8"));
|
||||
}
|
||||
});
|
||||
|
|
@ -215,14 +211,18 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void {
|
|||
// Allow about:blank (used by Vencord & Equicord QuickCss popup)
|
||||
if (url === "about:blank") return { action: "allow" };
|
||||
// Saving ics files on future events
|
||||
if (isDiscordIcsBlobUrl(url)) {
|
||||
if (url.startsWith("blob:https://discord.com/")) {
|
||||
return {
|
||||
action: "allow",
|
||||
overrideBrowserWindowOptions: { show: false },
|
||||
};
|
||||
}
|
||||
// Allow Discord stream popout
|
||||
if (isDiscordPopoutUrl(url))
|
||||
if (
|
||||
url === "https://discord.com/popout" ||
|
||||
url === "https://canary.discord.com/popout" ||
|
||||
url === "https://ptb.discord.com/popout"
|
||||
)
|
||||
return {
|
||||
action: "allow",
|
||||
overrideBrowserWindowOptions: {
|
||||
|
|
@ -267,8 +267,21 @@ function doAfterDefiningTheWindow(passedWindow: BrowserWindow): void {
|
|||
|
||||
registerCustomHandler();
|
||||
|
||||
const blockedPatterns = [
|
||||
/https:\/\/.*\/api\/v\d+\/science/,
|
||||
/https:\/\/sentry\.io\/.*/,
|
||||
/https:\/\/.*\.nel\.cloudflare\.com\/.*/,
|
||||
];
|
||||
passedWindow.webContents.session.webRequest.onBeforeRequest((details, callback) => {
|
||||
if (isTelemetryBlockedUrl(details.url) || isBlockedLocalhostWebSocket(details.url)) {
|
||||
if (blockedPatterns.some((pattern) => pattern.test(details.url))) {
|
||||
return callback({ cancel: true });
|
||||
}
|
||||
if (
|
||||
details.url.includes("ws://127.0.0.1:") &&
|
||||
!details.url.includes("127.0.0.1:1211") &&
|
||||
!details.url.includes("127.0.0.1:1112") &&
|
||||
!details.url.includes("127.0.0.1:6888")
|
||||
) {
|
||||
return callback({ cancel: true });
|
||||
}
|
||||
return callback({});
|
||||
|
|
|
|||
|
|
@ -41,60 +41,17 @@ export const AboutPopup = (props: { close: () => void }) => {
|
|||
<ModalBody>
|
||||
<div class={classes.aboutContainer}>
|
||||
<div class={classes.aboutHeader}>
|
||||
<div class={classes.aboutLogo}>
|
||||
<svg
|
||||
width="200"
|
||||
height="90"
|
||||
viewBox="0 0 2249 1020"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
fill="#111725"
|
||||
d="m102.2 705v-478.6h66.8v478.6zm375.5-196.1q-1.2-29-12.2-49-10.5-20.6-29.6-31.6-19.2-11.6-44.1-11.6-27.9 0-48.8 14.2-20.3 14.2-31.9 40-7.9 16.9-10.4 38zm-166 85.2q12.7 27 34.8 41.9 22.6 14.8 51.7 14.8 51.6 0 87-40.6l35.5 39.3q-22.7 27.8-55.2 43.2-32.5 14.9-72.6 14.9-47.6 0-83-22-35.4-21.9-55.1-60.6-19.8-39.3-19.8-90.9 0-51.6 19.8-90.3 19.7-39.4 55.1-61.3 35.4-21.9 81.3-22.6 54.6 0 87.7 25.2 33 24.5 47 69.6 13.9 45.2 9.8 107.1h-233.7q3.1 17.6 9.7 32.3zm565.9-231.6v308.3q0 49.7-19.7 85.8-19.8 36.1-55.8 56.1-36 19.4-84.7 19.4-36 0-67.9-11.6-32-12.3-60.4-33.6l28.4-50.9q22.1 18 45.3 28.3 23.2 9.7 51.7 9.7 29.6 0 51.1-12.2 22-12.9 33.6-35.5 12.2-22.6 12.2-53.6v-41.6q-13.3 23.7-34.2 38.4-27.9 19.4-67.9 19.4-41.8 0-73.2-20.7-31.3-21.3-48.8-58.7-17.4-37.4-17.4-86.4 0-48.4 16.9-84.5 17.4-36.8 48.2-57.4 31.3-21.3 71.9-21.3 40.7-0.6 69.1 19.4 21.6 14.3 35.4 38.3v-55.1zm-155 54.2q-26.1 0-46.4 14.2-20.3 13.5-31.9 38-11.1 24.5-11.6 56.1 0.5 31.6 11.6 56.2 11.6 24.5 31.3 38.7 20.3 13.5 47 13.5 26.2 0 45.9-13.5 20.3-14.2 31.3-38.7 11.6-24.6 11.6-56.2 0-32.2-11.6-56.1-11-24.5-31.3-38-19.7-14.2-45.9-14.2z"
|
||||
/>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
fill="#9538e6"
|
||||
d="m1218.3 412.8l-36.5 47.7q-16.3-18.7-38.4-29-22-10.3-49.9-10.3-26.7 0-47.6 14.2-20.9 14.2-32.5 40-11.6 25.1-11.6 58.7 0 33.5 11.6 59.3 11.6 25.2 32.5 40 20.9 14.2 47.6 14.2 29 0 52.3-10.3 23.2-11 38.9-31.6l37.1 40.6q-20.9 29-54.6 45.2-33 16.1-76.6 16.1-45.8 0-81.3-22-34.8-21.9-54.5-60.6-19.8-39.3-19.8-90.9 0-51.6 19.8-90.3 19.7-39.4 54.5-61.3 35.5-22.6 81.3-22.6 41.8 0 73.7 14.2 32.6 13.6 54 38.7zm186.9-52.9q47.6 0 83.6 21.9 36.6 22 56.9 61.3 20.3 38.7 20.3 90.3 0 51.6-20.3 91-20.3 39.3-56.9 61.2-36 22-83.6 22-47.6 0-84.2-22-36.6-21.9-56.9-61.2-20.3-39.4-20.3-91 0-51.6 20.3-90.3 20.3-39.3 56.9-61.3 36.6-21.9 84.2-21.9zm0 60q-27.9 0-49.3 14.8-21.5 14.2-33.7 40-11.6 25.8-11.6 59.4 0 34.2 11.6 60 12.2 25.8 33.7 40.6 21.4 14.2 49.3 14.2 27.9 0 48.8-14.2 21.4-14.8 33-40.6 12.2-25.8 12.2-60 0-33.6-12.2-59.4-11.6-25.8-33-40-20.9-14.8-48.8-14.8zm397.6-60v67.1q-33.1-1.9-57.5 12.9-23.8 14.2-37.1 40-10.1 20.3-12.2 45.4v179.7h-66.8v-342.5h66.8v67.7q14.6-32.2 39.4-49.6 28.5-20.7 67.4-20.7zm166.5-0.6q43 0 72.6 21.3 22.7 15.5 37.1 41.5v-195.7h66.8v478.6h-66.8v-58.5q-14.3 25.4-36.5 41.1-29.6 20.6-72 20.6-44.1 0-77.8-21.9-33.1-22.6-51.7-61.9-18.5-40-18.5-91.6 0-51.6 18.5-90.3 18.6-39.4 51.7-61.3 33.1-21.9 76.6-21.9zm15.1 60q-27.8 0-49.3 14.8-21.5 14.8-33.7 41.3-11.6 25.8-12.2 59.3 0.6 33.6 12.2 59.4 12.2 25.8 33.7 40.6 21.5 14.8 49.3 14.8 27.9 0 49.4-14.8 21.4-14.8 33.1-40.6 12.1-25.8 12.1-59.4 0-34.2-12.1-60-11.7-25.8-33.1-40.6-21.5-14.8-49.4-14.8z"
|
||||
/>
|
||||
<path
|
||||
fill="#cdc6fd"
|
||||
stroke="#111725"
|
||||
stroke-miterlimit="10"
|
||||
stroke-width="20"
|
||||
d="m528.7 775.2l-52.3 27.2c-28.7 14.9-44.2 26.8-34.2 44.9 9 18.7 27.5 12.9 56.2-2l52.4-27.3"
|
||||
/>
|
||||
<path
|
||||
fill="#cdc6fd"
|
||||
stroke="#111725"
|
||||
stroke-miterlimit="10"
|
||||
stroke-width="20"
|
||||
d="m598.2 910l-52.4 27.2c-28.6 14.9-44.1 26.8-34.1 44.9 9 18.6 27.5 12.8 56.2-2.1l52.4-27.2"
|
||||
/>
|
||||
<path
|
||||
fill="#9538e6"
|
||||
stroke="#111725"
|
||||
stroke-miterlimit="10"
|
||||
stroke-width="20"
|
||||
d="m495.6 757.8c1.7-1 27.6-14.6 30.6-16.2 34.9-18.6 94.6-32.2 140.1-4.9 25.2 15.2 38.5 36.2 48.6 54.4 9.3 19.4 18.4 41.8 16.2 71.3-4 53.1-49.6 94.2-84.9 112.1-2.9 1.5-29 14.9-30.8 15.7z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<span class={classes.aboutVersion}>
|
||||
{window.legcord?.version === "0.0.0"
|
||||
? "Dev Build"
|
||||
: `v${window.legcord?.version ?? "1.3.0"}`}
|
||||
</span>
|
||||
<h2 class={classes.aboutTitle}>Legcord</h2>
|
||||
<p class={classes.aboutDescription}>
|
||||
A free and open-source Discord client crafted by the community, for the community —
|
||||
delivering advanced features, better performance, and a modern design.
|
||||
Legcord is a free and open-source Discord client that offers a personalized experience with
|
||||
advanced features, better performance, and a modern design. Developed by the community, for
|
||||
the community.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<div class={classes.quickActions}>
|
||||
<h3 class={classes.sectionTitle}>Quick Actions</h3>
|
||||
<h3 class={classes.quickActionsTitle}>Quick Actions</h3>
|
||||
<div class={classes.quickActionsList}>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -111,30 +68,13 @@ export const AboutPopup = (props: { close: () => void }) => {
|
|||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<title>View Source Code</title>
|
||||
<polyline points="16 18 22 12 16 6" />
|
||||
<polyline points="8 6 2 12 8 18" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class={classes.quickActionButtonLabel}>
|
||||
<span class={classes.quickActionButtonTitle}>View Source Code</span>
|
||||
<span class={classes.quickActionButtonDesc}>github.com/Legcord/Legcord</span>
|
||||
</span>
|
||||
<svg
|
||||
class={classes.quickActionButtonArrow}
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<polyline points="9 18 15 12 9 6" />
|
||||
</svg>
|
||||
View Source Code
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -151,30 +91,13 @@ export const AboutPopup = (props: { close: () => void }) => {
|
|||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<title>Donate</title>
|
||||
<line x1="12" y1="1" x2="12" y2="23" />
|
||||
<path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class={classes.quickActionButtonLabel}>
|
||||
<span class={classes.quickActionButtonTitle}>Donate</span>
|
||||
<span class={classes.quickActionButtonDesc}>Support the project</span>
|
||||
</span>
|
||||
<svg
|
||||
class={classes.quickActionButtonArrow}
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<polyline points="9 18 15 12 9 6" />
|
||||
</svg>
|
||||
Donate
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -182,44 +105,19 @@ export const AboutPopup = (props: { close: () => void }) => {
|
|||
onClick={() => window.open("https://discord.gg/JatCnhKufc", "_blank")}
|
||||
>
|
||||
<span class={classes.quickActionButtonIcon}>
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
stroke="none"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor" stroke="none">
|
||||
<title>Join Discord Server</title>
|
||||
<path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515a.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0a12.64 12.64 0 0 0-.617-1.25a.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057a19.9 19.9 0 0 0 5.993 3.03a.078.078 0 0 0 .084-.028a14.09 14.09 0 0 0 1.226-1.994a.076.076 0 0 0-.041-.106a13.107 13.107 0 0 1-1.872-.892a.077.077 0 0 1-.008-.128a10.2 10.2 0 0 0 .372-.292a.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127a12.299 12.299 0 0 1-1.873.892a.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028a19.839 19.839 0 0 0 6.002-3.03a.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.956-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419c0-1.333.955-2.419 2.157-2.419c1.21 0 2.176 1.096 2.157 2.42c0 1.333-.946 2.418-2.157 2.418z" />
|
||||
</svg>
|
||||
</span>
|
||||
<span class={classes.quickActionButtonLabel}>
|
||||
<span class={classes.quickActionButtonTitle}>Join Discord Server</span>
|
||||
<span class={classes.quickActionButtonDesc}>Connect with the community</span>
|
||||
</span>
|
||||
<svg
|
||||
class={classes.quickActionButtonArrow}
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<polyline points="9 18 15 12 9 6" />
|
||||
</svg>
|
||||
Join Discord Server
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Contributors Section */}
|
||||
<div class={classes.contributorsSection}>
|
||||
<h3 class={classes.sectionTitle}>Contributors</h3>
|
||||
{!loading() && !error() && (
|
||||
<span class={classes.contributorBadge}>{contributors().length} contributors</span>
|
||||
)}
|
||||
<h3 class={classes.contributorsTitle}>Contributors</h3>
|
||||
{loading() && <div class={classes.loadingState}>Loading contributors...</div>}
|
||||
{error() && <div class={classes.errorState}>Failed to load contributors: {error()}</div>}
|
||||
{!loading() && !error() && (
|
||||
|
|
@ -249,22 +147,6 @@ export const AboutPopup = (props: { close: () => void }) => {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div class={classes.techSection}>
|
||||
<h3 class={classes.sectionTitle}>Technologies</h3>
|
||||
<div class={classes.techList}>
|
||||
<span class={classes.techTag}>Electron</span>
|
||||
<span class={classes.techTag}>SolidJS</span>
|
||||
<span class={classes.techTag}>TypeScript</span>
|
||||
<span class={classes.techTag}>Rolldown</span>
|
||||
<span class={classes.techTag}>shelter</span>
|
||||
<span class={classes.techTag}>Biome</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class={classes.aboutFooter}>
|
||||
<span>Copyright © Legcord 2024-2026 · OSL-3.0</span>
|
||||
</div>
|
||||
</div>
|
||||
</ModalBody>
|
||||
</ModalRoot>
|
||||
|
|
|
|||
|
|
@ -93,75 +93,75 @@
|
|||
min-height: auto;
|
||||
}
|
||||
|
||||
.aboutLogo {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.aboutLogo svg {
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.aboutHeader {
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
padding: 32px 24px 24px;
|
||||
background: linear-gradient(180deg, var(--background-secondary-alt) 0%, var(--background-secondary) 100%);
|
||||
margin-bottom: 32px;
|
||||
padding: 24px;
|
||||
background: var(--background-secondary);
|
||||
border: 1px solid var(--background-modifier-accent);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.aboutVersion {
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
margin: 0 0 16px 0;
|
||||
padding: 6px 14px;
|
||||
background: var(--background-tertiary);
|
||||
border-radius: 20px;
|
||||
display: inline-block;
|
||||
font-family: var(--font-mono);
|
||||
border: 1px solid var(--background-modifier-accent);
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.3px;
|
||||
.aboutTitle {
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 20px 0;
|
||||
color: var(--header-primary);
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.aboutDescription {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
font-size: 16px;
|
||||
line-height: 1.6;
|
||||
color: var(--text-muted);
|
||||
margin: 0;
|
||||
padding: 0 8px;
|
||||
max-width: 520px;
|
||||
margin: 0 0 24px 0;
|
||||
max-width: 600px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.sectionTitle {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 16px 0;
|
||||
color: var(--header-primary);
|
||||
letter-spacing: -0.2px;
|
||||
text-transform: uppercase;
|
||||
opacity: 0.7;
|
||||
.aboutVersion {
|
||||
font-size: 14px;
|
||||
color: var(--text-muted);
|
||||
margin: 0;
|
||||
padding: 10px 18px;
|
||||
background: var(--background-tertiary);
|
||||
border-radius: 8px;
|
||||
display: inline-block;
|
||||
font-family: var(--font-mono);
|
||||
border: 1px solid var(--background-modifier-accent);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.quickActions {
|
||||
margin-bottom: 24px;
|
||||
background: var(--background-secondary);
|
||||
border: 1px solid var(--background-modifier-accent);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin: 0 0 32px 0;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.quickActionsTitle {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 20px 0;
|
||||
color: var(--header-primary);
|
||||
letter-spacing: -0.3px;
|
||||
}
|
||||
|
||||
.quickActionsList {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.quickActionButton {
|
||||
background: var(--background-secondary);
|
||||
background: var(--background-secondary-alt);
|
||||
border: 1px solid var(--background-modifier-accent);
|
||||
border-radius: 10px;
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
|
|
@ -171,110 +171,73 @@
|
|||
align-items: center;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
transition: all 0.2s ease;
|
||||
transition: all 0.15s ease;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.quickActionButton::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: var(--brand-experiment);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.quickActionButton:hover {
|
||||
border-color: var(--brand-experiment);
|
||||
transform: translateX(4px);
|
||||
}
|
||||
|
||||
.quickActionButton:hover::before {
|
||||
opacity: 0.04;
|
||||
background: var(--background-tertiary);
|
||||
border-color: var(--background-modifier-hover);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.quickActionButton:active {
|
||||
transform: translateX(2px);
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.quickActionButtonIcon {
|
||||
margin-right: 12px;
|
||||
font-size: 18px;
|
||||
color: var(--interactive-normal);
|
||||
transition: color 0.15s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
background: var(--background-tertiary);
|
||||
color: var(--interactive-normal);
|
||||
flex-shrink: 0;
|
||||
transition: all 0.2s ease;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.quickActionButton:hover .quickActionButtonIcon {
|
||||
color: var(--brand-experiment);
|
||||
background: var(--background-secondary-alt);
|
||||
}
|
||||
|
||||
.quickActionButtonLabel {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.quickActionButtonTitle {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--header-primary);
|
||||
}
|
||||
|
||||
.quickActionButtonDesc {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.quickActionButtonArrow {
|
||||
margin-left: 8px;
|
||||
color: var(--text-muted);
|
||||
flex-shrink: 0;
|
||||
opacity: 0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.quickActionButton:hover .quickActionButtonArrow {
|
||||
opacity: 1;
|
||||
transform: translateX(2px);
|
||||
color: var(--brand-experiment);
|
||||
color: var(--interactive-hover);
|
||||
}
|
||||
|
||||
.contributorsSection {
|
||||
padding: 20px;
|
||||
background: var(--background-secondary);
|
||||
border: 1px solid var(--background-modifier-accent);
|
||||
border: 1px solid var(--background-accent);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.contributorsTitle {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin: 0 0 24px 0;
|
||||
color: var(--header-primary);
|
||||
text-align: center;
|
||||
letter-spacing: -0.3px;
|
||||
}
|
||||
|
||||
.contributorsGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
|
||||
gap: 10px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
padding: 0;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
max-height: 280px;
|
||||
justify-items: center;
|
||||
max-height: 40vh;
|
||||
overflow-y: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--background-modifier-accent) transparent;
|
||||
}
|
||||
|
||||
.contributorsGrid::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.contributorsGrid::-webkit-scrollbar-track {
|
||||
|
|
@ -283,7 +246,7 @@
|
|||
|
||||
.contributorsGrid::-webkit-scrollbar-thumb {
|
||||
background: var(--background-modifier-accent);
|
||||
border-radius: 2px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.contributorsGrid::-webkit-scrollbar-thumb:hover {
|
||||
|
|
@ -293,23 +256,27 @@
|
|||
.contributorCard {
|
||||
background: var(--background-primary);
|
||||
border: 1px solid var(--background-modifier-accent);
|
||||
border-radius: 10px;
|
||||
padding: 14px 12px;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
transition: all 0.2s ease;
|
||||
transition: all 0.15s ease;
|
||||
min-width: 180px;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.contributorCard:hover {
|
||||
background: var(--background-secondary-alt);
|
||||
border-color: var(--brand-experiment);
|
||||
background: var(--background-secondary);
|
||||
border-color: var(--background-modifier-hover);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.contributorLink {
|
||||
|
|
@ -323,98 +290,46 @@
|
|||
}
|
||||
|
||||
.contributorAvatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
margin-bottom: 10px;
|
||||
margin-bottom: 16px;
|
||||
max-width: 100%;
|
||||
border: 2px solid var(--background-modifier-accent);
|
||||
border: 3px solid var(--background-modifier-accent);
|
||||
object-fit: cover;
|
||||
transition: all 0.2s ease;
|
||||
transition: all 0.15s ease;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.contributorCard:hover .contributorAvatar {
|
||||
border-color: var(--brand-experiment);
|
||||
transform: scale(1.1);
|
||||
transform: scale(1.05);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.contributorName {
|
||||
font-weight: 600;
|
||||
font-size: 13px;
|
||||
margin-bottom: 4px;
|
||||
font-size: 16px;
|
||||
margin-bottom: 8px;
|
||||
color: var(--header-primary);
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
text-align: center;
|
||||
transition: color 0.2s ease;
|
||||
transition: color 0.15s ease;
|
||||
letter-spacing: -0.2px;
|
||||
}
|
||||
|
||||
.contributorCard:hover .contributorName {
|
||||
color: var(--brand-experiment);
|
||||
color: var(--interactive-hover);
|
||||
}
|
||||
|
||||
.contributorInfo {
|
||||
font-size: 11px;
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
font-weight: 400;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.contributorBadge {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--text-muted);
|
||||
background: var(--background-tertiary);
|
||||
border: 1px solid var(--background-modifier-accent);
|
||||
border-radius: 20px;
|
||||
padding: 3px 12px;
|
||||
margin-bottom: 16px;
|
||||
letter-spacing: 0.2px;
|
||||
}
|
||||
|
||||
.techSection {
|
||||
margin-top: 24px;
|
||||
padding: 20px;
|
||||
background: var(--background-secondary);
|
||||
border: 1px solid var(--background-modifier-accent);
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.techList {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.techTag {
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--text-normal);
|
||||
background: var(--background-primary);
|
||||
border: 1px solid var(--background-modifier-accent);
|
||||
border-radius: 6px;
|
||||
padding: 4px 10px;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.techTag:hover {
|
||||
border-color: var(--brand-experiment);
|
||||
color: var(--brand-experiment);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.aboutFooter {
|
||||
margin-top: 24px;
|
||||
text-align: center;
|
||||
padding: 8px 0 4px;
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
opacity: 0.7;
|
||||
letter-spacing: 0.1px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.loadingState,
|
||||
|
|
@ -433,25 +348,6 @@
|
|||
border-radius: 8px;
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
.loadingState::after {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: 16px auto 0;
|
||||
border: 3px solid var(--background-modifier-accent);
|
||||
border-top-color: var(--brand-experiment);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.modal {
|
||||
max-height: 640px;
|
||||
max-height: 500px;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue