1
0
mirror of https://github.com/zumbiepig/MineXLauncher.git synced 2025-06-08 04:24:49 +00:00
This commit is contained in:
zumbiepig 2024-07-25 15:09:32 -07:00
parent 42a2d0ce72
commit 2379faafdc
27 changed files with 216 additions and 205 deletions

View File

@ -5,7 +5,7 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: "weekly"
interval: 'weekly'

View File

@ -11,7 +11,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: "package.json"
node-version-file: 'package.json'
- name: Install dependencies
run: npm ci
- name: Lint

View File

@ -6,7 +6,7 @@ on:
workflow_dispatch:
concurrency:
group: "gh-pages"
group: 'gh-pages'
cancel-in-progress: false
jobs:
@ -21,7 +21,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version-file: "package.json"
node-version-file: 'package.json'
- name: Install dependencies
run: npm ci
- name: Build

View File

@ -1,4 +1,5 @@
{
"printWidth": 1000,
"useTabs": true
"useTabs": true,
"singleQuote": true
}

View File

@ -1,12 +1,4 @@
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config(eslint.configs.recommended, ...tseslint.configs.strictTypeChecked, ...tseslint.configs.stylisticTypeChecked, {
ignores: ["node_modules"],
languageOptions: {
parserOptions: {
project: true,
tsconfigRootDir: import.meta.dirname,
},
},
});
export default tseslint.config(eslint.configs.recommended, ...tseslint.configs.recommended);

View File

@ -3,7 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/resources/styles/default.css" />
<script src="/resources/scripts/main.js"></script>
<script src="/resources/scripts/main.js" defer></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-972V2NZ2ZK"></script>
<script>
@ -11,8 +11,8 @@
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "G-972V2NZ2ZK");
gtag('js', new Date());
gtag('config', 'G-972V2NZ2ZK');
</script>
<!-- End Google tag -->
</head>

View File

@ -11,8 +11,8 @@
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "G-972V2NZ2ZK");
gtag('js', new Date());
gtag('config', 'G-972V2NZ2ZK');
</script>
<!-- End Google tag -->
</head>

View File

@ -11,8 +11,8 @@
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "G-972V2NZ2ZK");
gtag('js', new Date());
gtag('config', 'G-972V2NZ2ZK');
</script>
<!-- End Google tag -->
</head>

View File

@ -11,8 +11,8 @@
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "G-972V2NZ2ZK");
gtag('js', new Date());
gtag('config', 'G-972V2NZ2ZK');
</script>
<!-- End Google tag -->
</head>

View File

@ -11,8 +11,8 @@
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "G-972V2NZ2ZK");
gtag('js', new Date());
gtag('config', 'G-972V2NZ2ZK');
</script>
<!-- End Google tag -->
</head>

View File

@ -19,15 +19,15 @@
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "G-972V2NZ2ZK");
gtag('js', new Date());
gtag('config', 'G-972V2NZ2ZK');
</script>
<!-- End Google tag -->
<style>
body {
overflow: hidden;
background-color: #1e1e1e;
background-image: url("/resources/images/gifs/loading.gif");
background-image: url('/resources/images/gifs/loading.gif');
background-repeat: no-repeat;
background-attachment: fixed;
background-position: center;
@ -35,7 +35,7 @@
}
</style>
<script>
document.addEventListener("DOMContentLoaded", function () {
document.addEventListener('DOMContentLoaded', function () {
function isMobile() {
try {
document.exitPointerLock();
@ -44,18 +44,18 @@
return true;
}
}
const iframe = document.createElement("iframe");
iframe.id = "embed";
iframe.style.position = "fixed";
iframe.style.top = "0";
iframe.style.left = "0";
iframe.style.width = "100%";
iframe.style.height = "100%";
iframe.style.border = "none";
const iframe = document.createElement('iframe');
iframe.id = 'embed';
iframe.style.position = 'fixed';
iframe.style.top = '0';
iframe.style.left = '0';
iframe.style.width = '100%';
iframe.style.height = '100%';
iframe.style.border = 'none';
if (isMobile()) {
iframe.src = "/mobile/";
iframe.src = '/mobile/';
} else {
iframe.src = "/home/game/";
iframe.src = '/home/game/';
}
document.body.appendChild(iframe);
});

View File

@ -11,8 +11,8 @@
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "G-972V2NZ2ZK");
gtag('js', new Date());
gtag('config', 'G-972V2NZ2ZK');
</script>
<!-- End Google tag -->
</head>

View File

@ -11,8 +11,8 @@
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "G-972V2NZ2ZK");
gtag('js', new Date());
gtag('config', 'G-972V2NZ2ZK');
</script>
<!-- End Google tag -->
</head>

View File

@ -12,8 +12,8 @@
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "G-972V2NZ2ZK");
gtag('js', new Date());
gtag('config', 'G-972V2NZ2ZK');
</script>
<!-- End Google tag -->
</head>

View File

@ -12,8 +12,8 @@
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "G-972V2NZ2ZK");
gtag('js', new Date());
gtag('config', 'G-972V2NZ2ZK');
</script>
<!-- End Google tag -->
</head>

View File

@ -1,6 +1,6 @@
body {
margin: 0;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #1e1e1e;
color: #fff;
overflow: hidden;
@ -188,7 +188,7 @@ nav {
display: flex;
align-items: center;
justify-content: center;
font-family: "MinecraftRegular", sans-serif;
font-family: 'MinecraftRegular', sans-serif;
font-size: 1em;
padding: 7px;
border: 2px solid #000;
@ -273,7 +273,7 @@ nav {
}
.play-button::before {
content: "";
content: '';
position: absolute;
top: 50%;
left: 50%;
@ -441,14 +441,14 @@ nav {
select {
width: 100%;
padding: 10px;
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
font-weight: bold;
color: #e0e0e0;
background-color: #08a74a;
box-shadow: 4px 4px 0px #2a2a2a, -4px -4px 0px #2a2a2a;
border-radius: 3px;
appearance: none;
background-image: url("/resources/images/icons/dropdown-selector.png"), linear-gradient(45deg, #0a8338, #086126);
background-image: url('/resources/images/icons/dropdown-selector.png'), linear-gradient(45deg, #0a8338, #086126);
background-repeat: no-repeat, repeat;
background-position: right 10px center, 0 0;
background-size: 15px auto, 100%;

View File

@ -11,8 +11,8 @@
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "G-972V2NZ2ZK");
gtag('js', new Date());
gtag('config', 'G-972V2NZ2ZK');
</script>
<!-- End Google tag -->
</head>

View File

@ -11,8 +11,8 @@
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "G-972V2NZ2ZK");
gtag('js', new Date());
gtag('config', 'G-972V2NZ2ZK');
</script>
<!-- End Google tag -->
</head>

View File

@ -11,8 +11,8 @@
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "G-972V2NZ2ZK");
gtag('js', new Date());
gtag('config', 'G-972V2NZ2ZK');
</script>
<!-- End Google tag -->
</head>

View File

@ -3,7 +3,7 @@
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="/resources/styles/default.css" />
<script src="/resources/scripts/main.js"></script>
<script src="/resources/scripts/main.js" defer></script>
<!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-972V2NZ2ZK"></script>
<script>
@ -11,8 +11,8 @@
function gtag() {
dataLayer.push(arguments);
}
gtag("js", new Date());
gtag("config", "G-972V2NZ2ZK");
gtag('js', new Date());
gtag('config', 'G-972V2NZ2ZK');
</script>
<!-- End Google tag -->
</head>

View File

@ -1,9 +1,9 @@
import { Router } from "express";
import { Router } from 'express';
const router = Router();
/* GET home page. */
router.get("/", function (req, res, next) {
res.render("index", { title: "Express" });
router.get('/', function (req, res, next) {
res.render('index', { title: 'Express' });
});
export default router;

View File

@ -1,9 +1,9 @@
import { Router } from "express";
import { Router } from 'express';
const router = Router();
/* GET users listing. */
router.get("/", function (req, res, next) {
res.send("respond with a resource");
router.get('/', function (req, res, next) {
res.send('respond with a resource');
});
export default router;

View File

@ -1,28 +1,28 @@
import createError from "http-errors";
import express, { json, urlencoded, static as serveStatic } from "express";
import { join } from "path";
import cookieParser from "cookie-parser";
import logger from "morgan";
import debug from "debug";
import { createServer } from "http";
import indexRouter from "./routes/index.js";
import usersRouter from "./routes/users.js";
import createError from 'http-errors';
import express, { json, urlencoded, static as serveStatic } from 'express';
import { join } from 'path';
import cookieParser from 'cookie-parser';
import logger from 'morgan';
import debug from 'debug';
import { createServer } from 'http';
import indexRouter from './routes/index.js';
import usersRouter from './routes/users.js';
const __dirname = import.meta.dirname;
const app = express();
app.set("views", join(__dirname, "views"));
app.set("view engine", "pug");
app.set('views', join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(logger("dev"));
app.use(logger('dev'));
app.use(json());
app.use(urlencoded({ extended: false }));
app.use(cookieParser());
app.use(serveStatic(join(__dirname, "public")));
app.use(serveStatic(join(__dirname, 'public')));
app.use("/", indexRouter);
app.use("/users", usersRouter);
app.use('/', indexRouter);
app.use('/users', usersRouter);
app.use(function (req, res, next) {
next(createError(404));
@ -30,22 +30,22 @@ app.use(function (req, res, next) {
app.use(function (err, req, res, next) {
res.locals.message = err.message;
res.locals.error = req.app.get("env") === "development" ? err : {};
res.locals.error = req.app.get('env') === 'development' ? err : {};
res.status(err.status || 500);
res.render("error");
res.render('error');
});
const debugLogger = debug("minexlauncher:server");
const debugLogger = debug('minexlauncher:server');
const port = normalizePort(process.env.PORT || "3000");
app.set("port", port);
const port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
const server = createServer(app);
server.listen(port);
server.on("error", onError);
server.on("listening", onListening);
server.on('error', onError);
server.on('listening', onListening);
function normalizePort(val) {
const port = parseInt(val, 10);
@ -59,17 +59,17 @@ function normalizePort(val) {
}
function onError(error) {
if (error.syscall !== "listen") {
if (error.syscall !== 'listen') {
throw error;
}
const bind = typeof port === "string" ? "Pipe " + port : "Port " + port;
const bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port;
switch (error.code) {
case "EACCES":
console.error(bind + " requires elevated privileges");
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case "EADDRINUSE":
console.error(bind + " is already in use");
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
@ -79,6 +79,6 @@ function onError(error) {
function onListening() {
const addr = server.address();
const bind = typeof addr === "string" ? "pipe " + addr : "port " + addr.port;
debugLogger("Listening on " + bind);
const bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port;
debugLogger('Listening on ' + bind);
}

View File

@ -1,52 +1,44 @@
let selectedVersion = "";
document.addEventListener("DOMContentLoaded", function () {
const usernameForm = document.getElementById("username-form") as HTMLFormElement;
const usernameInput = document.getElementById("username-input") as HTMLInputElement;
const profileName = document.getElementById("profile-name");
const savedUsername = cookie.get("launcher_username");
if (profileName && savedUsername) {
profileName.textContent = savedUsername;
} else if (profileName && !savedUsername) {
profileName.textContent = "Default";
}
if (profileName && window.location.pathname === "/settings/") {
usernameForm.addEventListener("submit", function (event) {
event.preventDefault();
const username = usernameInput.value.trim();
if (username) {
profileName.textContent = username;
cookie.set("launcher_username", username, 30);
}
});
}
});
const theme = {
load() {
const setTheme = cookie.get('minexlauncher.theme');
if (setTheme === null) {
theme.set('default');
} else if (setTheme !== 'default') {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = '/resources/styles/' + setTheme + '.css';
document.head.appendChild(link);
}
},
set(setTheme: string) {
cookie.set('minexlauncher.theme', setTheme, 30);
window.location.reload();
},
};
const versionSelector = {
open() {
const customOptions = document.querySelector(".custom-options");
const customSelect = document.querySelector(".custom-select");
const customOptions = document.querySelector('.custom-options');
const customSelect = document.querySelector('.custom-select');
if (customOptions && customSelect) {
customOptions.classList.add("open");
customSelect.classList.add("open");
customOptions.classList.add('open');
customSelect.classList.add('open');
}
},
close() {
const customOptions = document.querySelector(".custom-options");
const customSelect = document.querySelector(".custom-select");
const customOptions = document.querySelector('.custom-options');
const customSelect = document.querySelector('.custom-select');
if (customOptions && customSelect) {
customOptions.classList.remove("open");
customSelect.classList.remove("open");
customOptions.classList.remove('open');
customSelect.classList.remove('open');
}
},
toggle() {
const customOptions = document.querySelector(".custom-options");
const customSelect = document.querySelector(".custom-select");
const customOptions = document.querySelector('.custom-options');
const customSelect = document.querySelector('.custom-select');
if (customOptions && customSelect) {
customOptions.classList.toggle("open");
customSelect.classList.toggle("open");
customOptions.classList.toggle('open');
customSelect.classList.toggle('open');
}
},
};
@ -55,20 +47,20 @@ const game = {
play(version?: string) {
if (version) {
embed.remove();
// @ts-expect-error 1234567890
// @ts-expect-error 123
window.top.location.href = version;
} else if (selectedVersion) {
embed.remove();
// @ts-expect-error 1234567890
// @ts-expect-error 123
window.top.location.href = selectedVersion;
} else {
alert("Please select a version to play.");
alert('Please select a version to play.');
return;
}
},
select(path: string, name: string) {
selectedVersion = path;
const selector = document.querySelector(".custom-select");
const selector = document.querySelector('.custom-select');
if (selector?.textContent) {
if (name) {
selector.textContent = `Selected: ${name}`;
@ -80,13 +72,13 @@ const game = {
},
archive(client: string) {
const clients: Record<string, string> = {
"1.8.8": "18-client-version",
"1.5.2": "15-client-version",
"b1.3": "b13-client-version",
'1.8.8': '18-client-version',
'1.5.2': '15-client-version',
'b1.3': 'b13-client-version',
};
const dropdown = clients[client] ? (document.getElementById(clients[client]) as HTMLSelectElement) : null;
if (dropdown?.value) {
selectedVersion = `https://archive.eaglercraft.rip/Eaglercraft${client === "b1.3" ? "_b1.3" : `_${client}`}/client/${dropdown.value}/index.html`;
selectedVersion = `https://archive.eaglercraft.rip/Eaglercraft${client === 'b1.3' ? '_b1.3' : `_${client}`}/client/${dropdown.value}/index.html`;
game.play();
}
},
@ -94,23 +86,23 @@ const game = {
const embed = {
create() {
const iframe = document.createElement("iframe");
iframe.id = "embed";
iframe.style.position = "fixed";
iframe.style.top = "0";
iframe.style.left = "0";
iframe.style.width = "100%";
iframe.style.height = "100%";
iframe.style.border = "none";
const iframe = document.createElement('iframe');
iframe.id = 'embed';
iframe.style.position = 'fixed';
iframe.style.top = '0';
iframe.style.left = '0';
iframe.style.width = '100%';
iframe.style.height = '100%';
iframe.style.border = 'none';
if (isMobile()) {
iframe.src = "/mobile/";
iframe.src = '/mobile/';
} else {
iframe.src = "/home/";
iframe.src = '/home/';
}
document.body.appendChild(iframe);
},
remove() {
const iframe = document.getElementById("embed");
const iframe = document.getElementById('embed');
iframe?.remove();
},
};
@ -118,62 +110,64 @@ const embed = {
const navigate = {
home: {
game() {
window.location.href = "/home/game/";
window.location.href = '/home/game/';
},
clients() {
window.location.href = "/home/clients/";
window.location.href = '/home/clients/';
},
archive() {
window.location.href = "/home/archive/";
window.location.href = '/home/archive/';
},
downloads() {
window.location.href = "/home/downloads/";
window.location.href = '/home/downloads/';
},
},
mods: {
client() {
window.location.href = "/mods/client/";
window.location.href = '/mods/client/';
},
mods() {
window.location.href = "/mods/mods/";
window.location.href = '/mods/mods/';
},
resourcepacks() {
window.location.href = "/mods/resourcepacks/";
window.location.href = '/mods/resourcepacks/';
},
},
mobile() {
window.location.href = "/mobile/";
window.location.href = '/mobile/';
},
updates() {
window.location.href = "/updates/";
window.location.href = '/updates/';
},
servers() {
window.location.href = "/servers/";
window.location.href = '/servers/';
},
settings() {
window.location.href = "/settings/";
window.location.href = '/settings/';
},
};
const cookie = {
get(name: string): string | null {
const cookieArr = document.cookie.split(";");
for (const cookie of cookieArr) {
const cookiePair = cookie.split("=");
if (name === cookiePair[0]?.trim()) {
return decodeURIComponent(cookiePair[1] ?? "");
get(key: string): string | null {
for (const cookie of document.cookie.replaceAll('; ', ';').split(';')) {
const cookiePair = cookie.split('=');
if (encodeURIComponent(key) === cookiePair[0]) {
return decodeURIComponent(cookiePair[1]);
}
}
return null;
},
set(name: string, value: string, days: number) {
let expires = "";
set(key: string, value: string, days: number) {
let maxAge;
if (days) {
const date = new Date();
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
expires = "; expires=" + date.toUTCString();
maxAge = days * 60 * 60 * 24;
} else {
maxAge = 31536000;
}
document.cookie = name + "=" + (value || "") + expires + "; path=/; domain=" + window.location.hostname.replace(/^www\./, "");
document.cookie = `${encodeURIComponent(key)}=${encodeURIComponent(value)}; max-age=${maxAge}; path=/; secure`;
},
delete(key: string) {
document.cookie = `${encodeURIComponent(key)}=; max-age=0; path=/`;
},
};
@ -193,10 +187,34 @@ function isMobile(): boolean {
}
}
if (window.location.hostname === "0.0.0.0") {
versionSelector;
game;
if (window.location.hostname === '0.0.0.0') {
navigate;
query;
isMobile;
}
let selectedVersion: string;
theme.load();
document.addEventListener('DOMContentLoaded', function () {
const usernameForm = document.getElementById('username-form') as HTMLFormElement;
const usernameInput = document.getElementById('username-input') as HTMLInputElement;
const profileName = document.getElementById('profile-name');
const savedUsername = cookie.get('minexlauncher.username');
if (profileName && savedUsername) {
profileName.textContent = savedUsername;
} else if (profileName && !savedUsername) {
profileName.textContent = 'Default';
}
if (profileName && window.location.pathname === '/settings/') {
usernameForm.addEventListener('submit', function (event) {
event.preventDefault();
const username = usernameInput.value.trim();
if (username) {
cookie.set('minexlauncher.username', username, 30);
profileName.textContent = cookie.get('minexlauncher.username');
}
});
}
});

View File

@ -1,22 +1,22 @@
document.addEventListener("DOMContentLoaded", function () {
fetch("/resources/data/mods.json")
document.addEventListener('DOMContentLoaded', function () {
fetch('/resources/data/mods.json')
.then((response) => response.json())
.then((data: { mods: { [x: string]: string; icon: string; author: string; description: string }[] }) => {
const modListElement = document.querySelector(".mod-list");
const modListElement = document.querySelector('.mod-list');
data.mods.forEach((mod: { [x: string]: string; icon: string; author: string; description: string }) => {
const modItem = document.createElement("div");
modItem.classList.add("mod-item");
const modItem = document.createElement('div');
modItem.classList.add('mod-item');
modItem.innerHTML = `
<div class="mod-icon">
<img src="${mod.icon}" />
</div>
<div class="mod-details">
<h3 class="mod-name">${mod["display-name"] ?? ""}</h3>
<p class="mod-author">By <a href="${mod["author-link"] ?? ""}" target="_blank">${mod.author}</a></p>
<h3 class="mod-name">${mod['display-name'] ?? ''}</h3>
<p class="mod-author">By <a href="${mod['author-link'] ?? ''}" target="_blank">${mod.author}</a></p>
<p class="mod-description">${mod.description}</p>
<div class="mod-links">
<a href="${mod["repo-link"] ?? ""}" class="mod-link" target="_blank">Repository</a>
<a href="${mod["download-link"] ?? ""}" class="mod-link" download>Download</a>
<a href="${mod['repo-link'] ?? ''}" class="mod-link" target="_blank">Repository</a>
<a href="${mod['download-link'] ?? ''}" class="mod-link" download>Download</a>
</div>
</div>
`;
@ -24,6 +24,6 @@ document.addEventListener("DOMContentLoaded", function () {
});
})
.catch((error: unknown) => {
console.error("Error fetching mods:", error);
console.error('Error fetching mods:', error);
});
});

View File

@ -1,22 +1,22 @@
document.addEventListener("DOMContentLoaded", function () {
fetch("/resources/data/mods.json")
document.addEventListener('DOMContentLoaded', function () {
fetch('/resources/data/mods.json')
.then((response) => response.json())
.then((data: { resourcepacks: { [x: string]: string; icon: string; author: string; description: string }[] }) => {
const modListElement = document.querySelector(".mod-list");
const modListElement = document.querySelector('.mod-list');
data.resourcepacks.forEach((mod: { [x: string]: string; icon: string; author: string; description: string }) => {
const modItem = document.createElement("div");
modItem.classList.add("mod-item");
const modItem = document.createElement('div');
modItem.classList.add('mod-item');
modItem.innerHTML = `
<div class="mod-icon">
<img src="${mod.icon}" />
</div>
<div class="mod-details">
<h3 class="mod-name">${mod["display-name"] ?? ""}</h3>
<p class="mod-author">By <a href="${mod["author-link"] ?? ""}" target="_blank">${mod.author}</a></p>
<h3 class="mod-name">${mod['display-name'] ?? ''}</h3>
<p class="mod-author">By <a href="${mod['author-link'] ?? ''}" target="_blank">${mod.author}</a></p>
<p class="mod-description">${mod.description}</p>
<div class="mod-links">
<a href="${mod["repo-link"] ?? ""}" class="mod-link" target="_blank">Repository</a>
<a href="${mod["download-link"] ?? ""}" class="mod-link" download>Download</a>
<a href="${mod['repo-link'] ?? ''}" class="mod-link" target="_blank">Repository</a>
<a href="${mod['download-link'] ?? ''}" class="mod-link" download>Download</a>
</div>
</div>
`;
@ -24,6 +24,6 @@ document.addEventListener("DOMContentLoaded", function () {
});
})
.catch((error: unknown) => {
console.error("Error fetching resource packs:", error);
console.error('Error fetching resource packs:', error);
});
});

View File

@ -1,5 +1,5 @@
{
"extends": ["@tsconfig/node20/tsconfig", "@tsconfig/strictest/tsconfig"],
"extends": ["@tsconfig/node20/tsconfig"],
"compilerOptions": {
"moduleResolution": "bundler",
"module": "preserve",