mirror of
https://github.com/NebulaServices/Nebula.git
synced 2025-05-13 12:00:01 -04:00
Add gruvbox theme
This commit is contained in:
parent
0b5bc42587
commit
06dbe99cae
11 changed files with 50 additions and 384 deletions
|
@ -6,9 +6,17 @@ import { epoxyPath } from "@mercuryworkshop/epoxy-transport";
|
||||||
import { libcurlPath } from "@mercuryworkshop/libcurl-transport";
|
import { libcurlPath } from "@mercuryworkshop/libcurl-transport";
|
||||||
import { uvPath } from "@titaniumnetwork-dev/ultraviolet";
|
import { uvPath } from "@titaniumnetwork-dev/ultraviolet";
|
||||||
import icon from "astro-icon";
|
import icon from "astro-icon";
|
||||||
import { defineConfig } from "astro/config";
|
import { defineConfig, envField } from "astro/config";
|
||||||
import { viteStaticCopy } from "vite-plugin-static-copy";
|
import { viteStaticCopy } from "vite-plugin-static-copy";
|
||||||
|
import { version } from "./package.json";
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
experimental: {
|
||||||
|
env: {
|
||||||
|
schema: {
|
||||||
|
VERSION: envField.string({ context: 'client', access: 'public', optional: true, default: version })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
integrations: [tailwind(), icon(), svelte()],
|
integrations: [tailwind(), icon(), svelte()],
|
||||||
vite: {
|
vite: {
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|
BIN
database_assets/image/com.nebula.gruvbox.jpeg
Normal file
BIN
database_assets/image/com.nebula.gruvbox.jpeg
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.6 KiB |
Binary file not shown.
Before Width: | Height: | Size: 4.9 KiB |
Binary file not shown.
Before Width: | Height: | Size: 100 KiB |
|
@ -1,19 +0,0 @@
|
||||||
:root {
|
|
||||||
--background-primary: rgba(0, 0, 0, 0);
|
|
||||||
--background-lighter: #000;
|
|
||||||
--navbar-color: #000;
|
|
||||||
--navbar-height: 60px;
|
|
||||||
--navbar-text-color: greenyellow;
|
|
||||||
--navbar-link-color: greenyellow;
|
|
||||||
--navbar-link-hover-color: green;
|
|
||||||
--navbar-font: "Roboto";
|
|
||||||
--input-text-color: greenyellow;
|
|
||||||
--input-placeholder-color: white;
|
|
||||||
--input-background-color: #000;
|
|
||||||
--input-border-color: greenyellow;
|
|
||||||
--input-border-size: 1.3px;
|
|
||||||
--navbar-logo-filter: none;
|
|
||||||
--dropdown-option-hover-color: #312a49;
|
|
||||||
--tab-color: var(--black);
|
|
||||||
--border-color: greenyellow;
|
|
||||||
}
|
|
22
database_assets/styles/com.nebula.gruvbox.css
Normal file
22
database_assets/styles/com.nebula.gruvbox.css
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
:root {
|
||||||
|
--background-primary: #282828;
|
||||||
|
--background-lighter: #3c3836;
|
||||||
|
--navbar-color: #504945;
|
||||||
|
--navbar-height: 60px;
|
||||||
|
--navbar-text-color: #fbf1c7;
|
||||||
|
--navbar-link-color: #ebdbb2;
|
||||||
|
--navbar-link-hover-color: #fabd2f;
|
||||||
|
--navbar-font: "Roboto", sans-serif;
|
||||||
|
--input-text-color: #b8bb26;
|
||||||
|
--input-placeholder-color: #928374;
|
||||||
|
--input-background-color: #1d2021;
|
||||||
|
--input-border-color: #b8bb26;
|
||||||
|
--input-border-size: 1.3px;
|
||||||
|
--navbar-logo-filter: none;
|
||||||
|
--dropdown-option-hover-color: #665c54;
|
||||||
|
--tab-color: #1d2021;
|
||||||
|
--border-color: #b8bb26;
|
||||||
|
--highlight-color: #fe8019;
|
||||||
|
--accent-color: #83a598;
|
||||||
|
--secondary-text-color: #d3869b;
|
||||||
|
}
|
Binary file not shown.
345
server.js
345
server.js
|
@ -1,345 +0,0 @@
|
||||||
import fs from "fs";
|
|
||||||
import { createServer } from "node:http";
|
|
||||||
import path from "path";
|
|
||||||
import { fileURLToPath } from "url";
|
|
||||||
import {
|
|
||||||
createRammerhead,
|
|
||||||
routeRhRequest,
|
|
||||||
routeRhUpgrade,
|
|
||||||
shouldRouteRh
|
|
||||||
} from "@rubynetwork/rammerhead";
|
|
||||||
import express from "express";
|
|
||||||
import multer from "multer";
|
|
||||||
import { DataTypes, Sequelize } from "sequelize";
|
|
||||||
import wisp from "wisp-server-node";
|
|
||||||
import { handler as ssrHandler } from "./dist/server/entry.mjs";
|
|
||||||
|
|
||||||
const config = JSON.parse(fs.readFileSync("config.json", "utf8"));
|
|
||||||
const __filename = fileURLToPath(import.meta.url);
|
|
||||||
const __dirname = path.dirname(__filename);
|
|
||||||
//create the rh server.
|
|
||||||
const rh = createRammerhead({
|
|
||||||
logLevel: "debug",
|
|
||||||
reverseProxy: true,
|
|
||||||
disableLocalStorageSync: false,
|
|
||||||
disableHttp2: false
|
|
||||||
});
|
|
||||||
const app = express();
|
|
||||||
const publicPath = "dist/client";
|
|
||||||
const sequelize = new Sequelize("database", "user", "password", {
|
|
||||||
host: "localhost",
|
|
||||||
dialect: "sqlite",
|
|
||||||
logging: false,
|
|
||||||
// SQLite only
|
|
||||||
storage: "database.sqlite"
|
|
||||||
});
|
|
||||||
|
|
||||||
// Auth middleware
|
|
||||||
function auth_psk(req, res, next) {
|
|
||||||
if (!config.marketplace_enabled) {
|
|
||||||
let err = "Marketplace is disabled!";
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (req.headers.psk !== config.marketplace_psk) {
|
|
||||||
let err = "Bad PSK!";
|
|
||||||
console.log("Bad psk");
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
return next();
|
|
||||||
}
|
|
||||||
|
|
||||||
var image_storage = multer.diskStorage({
|
|
||||||
destination: function (req, file, cb) {
|
|
||||||
cb(null, "database_assets/image");
|
|
||||||
},
|
|
||||||
filename: function (req, file, cb) {
|
|
||||||
cb(null, file.originalname); //Appending extension
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var video_storage = multer.diskStorage({
|
|
||||||
destination: function (req, file, cb) {
|
|
||||||
cb(null, "database_assets/video");
|
|
||||||
},
|
|
||||||
filename: function (req, file, cb) {
|
|
||||||
cb(null, file.originalname); //Appending extension
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var style_storage = multer.diskStorage({
|
|
||||||
destination: function (req, file, cb) {
|
|
||||||
cb(null, "database_assets/styles");
|
|
||||||
},
|
|
||||||
filename: function (req, file, cb) {
|
|
||||||
cb(null, file.originalname); //Appending extension
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var script_storage = multer.diskStorage({
|
|
||||||
destination: function (req, file, cb) {
|
|
||||||
cb(null, "database_assets/scripts");
|
|
||||||
},
|
|
||||||
filename: function (req, file, cb) {
|
|
||||||
cb(null, file.originalname); //Appending extension
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var image_upload = multer({ storage: image_storage });
|
|
||||||
var video_upload = multer({ storage: video_storage });
|
|
||||||
var style_upload = multer({ storage: style_storage });
|
|
||||||
var script_upload = multer({ storage: script_storage });
|
|
||||||
|
|
||||||
const catalog_assets = sequelize.define("catalog_assets", {
|
|
||||||
package_name: {
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
unique: true
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
type: DataTypes.TEXT
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
type: DataTypes.TEXT
|
|
||||||
},
|
|
||||||
author: {
|
|
||||||
type: DataTypes.TEXT
|
|
||||||
},
|
|
||||||
image: {
|
|
||||||
type: DataTypes.TEXT
|
|
||||||
},
|
|
||||||
tags: {
|
|
||||||
type: DataTypes.JSON,
|
|
||||||
allowNull: true
|
|
||||||
},
|
|
||||||
version: {
|
|
||||||
type: DataTypes.TEXT
|
|
||||||
},
|
|
||||||
background_image: {
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
allowNull: true
|
|
||||||
},
|
|
||||||
background_video: {
|
|
||||||
type: DataTypes.TEXT,
|
|
||||||
allowNull: true
|
|
||||||
},
|
|
||||||
payload: {
|
|
||||||
type: DataTypes.TEXT
|
|
||||||
},
|
|
||||||
type: {
|
|
||||||
type: DataTypes.TEXT
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use(express.json());
|
|
||||||
|
|
||||||
app.get("/api", function (request, reply) {
|
|
||||||
reply.send({ hello: "world" });
|
|
||||||
});
|
|
||||||
|
|
||||||
// This API returns a list of the assets in the database (SW plugins and themes).
|
|
||||||
// It also returns the number of pages in the database.
|
|
||||||
// It can take a `?page=x` argument to display a different page, with a limit of 20 assets per page.
|
|
||||||
app.get("/api/catalog-assets", async (request, reply) => {
|
|
||||||
try {
|
|
||||||
const page = parseInt(request.query.page, 10) || 1; // default to page 1
|
|
||||||
|
|
||||||
const totalItems = await catalog_assets.count();
|
|
||||||
|
|
||||||
if (page < 1) {
|
|
||||||
reply.status(400).send({ error: "Page must be a positive number!" });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const offset = (page - 1) * 20;
|
|
||||||
|
|
||||||
const db_assets = await catalog_assets.findAll({
|
|
||||||
offset: offset,
|
|
||||||
limit: 20
|
|
||||||
});
|
|
||||||
|
|
||||||
const assets = db_assets.reduce((acc, asset) => {
|
|
||||||
acc[asset.package_name] = {
|
|
||||||
title: asset.title,
|
|
||||||
description: asset.description,
|
|
||||||
author: asset.author,
|
|
||||||
image: asset.image,
|
|
||||||
tags: asset.tags,
|
|
||||||
version: asset.version,
|
|
||||||
background_image: asset.background_image,
|
|
||||||
background_video: asset.background_video,
|
|
||||||
payload: asset.payload,
|
|
||||||
type: asset.type
|
|
||||||
};
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
reply.send({ assets, pages: Math.ceil(totalItems / 20) });
|
|
||||||
} catch (error) {
|
|
||||||
reply.status(500).send({ error: "There was an error" });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// This API returns data about a single package.
|
|
||||||
app.get("/api/packages/:package", async (request, reply) => {
|
|
||||||
try {
|
|
||||||
console.log(request.params.package);
|
|
||||||
|
|
||||||
const package_row = await catalog_assets.findOne({
|
|
||||||
where: { package_name: request.params.package }
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!package_row) {
|
|
||||||
return reply.status(404).send({ error: "Package not found!" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const details = {
|
|
||||||
title: package_row.get("title"),
|
|
||||||
description: package_row.get("description"),
|
|
||||||
image: package_row.get("image"),
|
|
||||||
author: package_row.get("author"),
|
|
||||||
tags: package_row.get("tags"),
|
|
||||||
version: package_row.get("version"),
|
|
||||||
background_image: package_row.get("background_image"),
|
|
||||||
background_video: package_row.get("background_video"),
|
|
||||||
payload: package_row.get("payload"),
|
|
||||||
type: package_row.get("type")
|
|
||||||
};
|
|
||||||
reply.send(details);
|
|
||||||
} catch (error) {
|
|
||||||
reply.status(500).send({ error: "There was an error" });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// This API is responsible for image uploads
|
|
||||||
// PSK authentication required.
|
|
||||||
app.post("/api/upload-image", auth_psk, image_upload.single("file"), (req, res) => {
|
|
||||||
console.log("Request file:", req.file);
|
|
||||||
|
|
||||||
if (!req.file) {
|
|
||||||
return res.status(400).json({ error: "No file uploaded" });
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(req.file.originalname);
|
|
||||||
res.json({
|
|
||||||
message: "File uploaded successfully",
|
|
||||||
filename: req.file.originalname
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// This API is responsible for video uploads
|
|
||||||
// PSK authentication required.
|
|
||||||
app.post("/api/upload-video", auth_psk, video_upload.single("file"), (req, res) => {
|
|
||||||
if (!req.file) {
|
|
||||||
return res.status(400).json({ error: "No file uploaded" });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
message: "File uploaded successfully",
|
|
||||||
filename: req.file.originalname
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// This API is responsible for stylesheet uploads
|
|
||||||
// PSK authentication required.
|
|
||||||
app.post("/api/upload-style", auth_psk, style_upload.single("file"), (req, res) => {
|
|
||||||
if (!req.file) {
|
|
||||||
return res.status(400).json({ error: "No file uploaded" });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
message: "File uploaded successfully",
|
|
||||||
filename: req.file.originalname
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// This API is responsible for script/plugin uploads
|
|
||||||
// PSK authentication required.
|
|
||||||
app.post("/api/upload-script", auth_psk, script_upload.single("file"), (req, res) => {
|
|
||||||
if (!req.file) {
|
|
||||||
return res.status(400).json({ error: "No file uploaded" });
|
|
||||||
}
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
message: "File uploaded successfully",
|
|
||||||
filename: req.file.originalname
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// This API is responsible for creating packages in the database.
|
|
||||||
// PSK authentication required.
|
|
||||||
app.post("/api/create-package", auth_psk, async function (req, res) {
|
|
||||||
console.log(req.body);
|
|
||||||
await catalog_assets.create({
|
|
||||||
package_name: req.body.uuid,
|
|
||||||
title: req.body.title,
|
|
||||||
image: req.body.image_path,
|
|
||||||
author: req.body.author,
|
|
||||||
version: req.body.version,
|
|
||||||
description: req.body.description,
|
|
||||||
tags: req.body.tags,
|
|
||||||
payload: req.body.payload,
|
|
||||||
background_video: req.body.background_video_path,
|
|
||||||
background_image: req.body.background_image_path,
|
|
||||||
type: req.body.type
|
|
||||||
});
|
|
||||||
res.send({ hello: "world" });
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use("/images/", express.static("./database_assets/image"));
|
|
||||||
app.use("/videos/", express.static("./database_assets/video"));
|
|
||||||
app.use("/styles/", express.static("./database_assets/styles"));
|
|
||||||
app.use("/scripts/", express.static("./database_assets/scripts"));
|
|
||||||
app.use(ssrHandler);
|
|
||||||
app.use(express.static(publicPath));
|
|
||||||
|
|
||||||
//await catalog_assets.create({
|
|
||||||
// package_name: "com.nebula.cybermonay",
|
|
||||||
// title: "Cyber Monay",
|
|
||||||
// image: "cyber_monay.jpg",
|
|
||||||
// author: "Nebula Services",
|
|
||||||
// version: "1.0.0",
|
|
||||||
// description: 'A parody of the famous "Cyber Monay" hack!',
|
|
||||||
// tags: ["Hacking", "Animated", "Funny"],
|
|
||||||
// payload: "com.nebula.cybermonay.css",
|
|
||||||
// background_video: "cyber_monay_test.mp4",
|
|
||||||
//type: "theme",
|
|
||||||
//});
|
|
||||||
|
|
||||||
// await catalog_assets.create({
|
|
||||||
// package_name: "com.neptune.neptune",
|
|
||||||
// title: "Neptune",
|
|
||||||
// image: "neptune.webp",
|
|
||||||
// author: "Neptune",
|
|
||||||
// version: "1.0.0",
|
|
||||||
// description: "Neptune image",
|
|
||||||
// tags: ["Image", "Funny"],
|
|
||||||
// payload: "neptune.css",
|
|
||||||
// background_image: "neptune.webp",
|
|
||||||
// type: "theme",
|
|
||||||
// });
|
|
||||||
|
|
||||||
catalog_assets.sync();
|
|
||||||
const server = createServer();
|
|
||||||
|
|
||||||
server.on("request", (req, res) => {
|
|
||||||
res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
|
|
||||||
res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
|
|
||||||
if (shouldRouteRh(req)) {
|
|
||||||
routeRhRequest(rh, req, res);
|
|
||||||
} else {
|
|
||||||
app(req, res);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.on("upgrade", (req, socket, head) => {
|
|
||||||
if (shouldRouteRh(req)) {
|
|
||||||
routeRhUpgrade(rh, req, socket, head);
|
|
||||||
} else if (req.url.endsWith("/wisp/")) {
|
|
||||||
wisp.routeRequest(req, socket, head);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
server.listen({
|
|
||||||
port: 8080
|
|
||||||
});
|
|
|
@ -31,15 +31,14 @@ async function setupDB(db: ModelStatic<CatalogModel>) {
|
||||||
//We have some packages that need to be installed if they aren't.
|
//We have some packages that need to be installed if they aren't.
|
||||||
const items: Items[] = [
|
const items: Items[] = [
|
||||||
{
|
{
|
||||||
package_name: 'com.nebula.cybermonay',
|
package_name: 'com.nebula.gruvbox',
|
||||||
title: 'Cyber Monay',
|
title: 'Gruvbox',
|
||||||
image: 'cyber_monay.jpg',
|
image: 'com.nebula.gruvbox.jpeg',
|
||||||
author: 'Nebula Services',
|
author: 'Nebula Services',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: 'A parody of the famous "Cyber Monay" hack!',
|
description: 'The gruvbox theme',
|
||||||
tags: ["Hacking", "Animated", "Funny"],
|
tags: ["Theme", "Simple"],
|
||||||
payload: "com.nebula.cybermonay.css",
|
payload: "com.nebula.gruvbox.css",
|
||||||
background_video: "cyber_monay_test.mp4",
|
|
||||||
type: 'theme'
|
type: 'theme'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -9,22 +9,17 @@ export function getStaticPaths() {
|
||||||
export const prerender = true;
|
export const prerender = true;
|
||||||
const lang = getLangFromUrl(Astro.url);
|
const lang = getLangFromUrl(Astro.url);
|
||||||
const t = useTranslations(lang);
|
const t = useTranslations(lang);
|
||||||
|
import { VERSION } from "astro:env/client";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="Nebula">
|
<Layout title="Nebula">
|
||||||
<div
|
<div class="flex flex-wrap mt-16 justify-center content-center w-full bg-primary fixed inset-0 h-[calc(100%-4rem)] z-0 flex-col items-center">
|
||||||
class="flex flex-wrap mt-16 justify-center content-center w-full bg-primary fixed inset-0 h-[calc(100%-4rem)] z-0 flex-col items-center"
|
<div class="w-full flex flex-col justify-center items-center content-center h-2/4">
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="w-full flex flex-col justify-center items-center content-center h-2/4"
|
|
||||||
>
|
|
||||||
<div class="flex flex-row items-center mb-8">
|
<div class="flex flex-row items-center mb-8">
|
||||||
<div class="h-32 w-32 fill-navbar-text-color">
|
<div class="h-32 w-32 fill-navbar-text-color">
|
||||||
<Logo />
|
<Logo />
|
||||||
</div>
|
</div>
|
||||||
<h1
|
<h1 class="font-roboto whitespace-nowrap font-bold text-navbar-text-color sm:visible text-5xl sm:text-7xl roboto">
|
||||||
class="font-roboto whitespace-nowrap font-bold text-navbar-text-color sm:visible text-5xl sm:text-7xl roboto"
|
|
||||||
>
|
|
||||||
nebula.
|
nebula.
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
|
@ -42,7 +37,11 @@ const t = useTranslations(lang);
|
||||||
<iframe
|
<iframe
|
||||||
id="neb-iframe"
|
id="neb-iframe"
|
||||||
class="hidden z-100 w-full h-full absolute top-0 bottom-0 bg-primary"
|
class="hidden z-100 w-full h-full absolute top-0 bottom-0 bg-primary"
|
||||||
></iframe>
|
></iframe>
|
||||||
|
<div id="version" class="flex flex-row w-full absolute bottom-4 pr-4 pl-4 text-text-color h-6 justify-between">
|
||||||
|
<p> Version: { VERSION } </p>
|
||||||
|
<p> © Nebula Services 2024 </p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Layout>
|
</Layout>
|
||||||
<script>
|
<script>
|
||||||
|
@ -146,9 +145,10 @@ const t = useTranslations(lang);
|
||||||
`https://api.duckduckgo.com/ac?q=${encodeURIComponent(value)}&format=json`
|
`https://api.duckduckgo.com/ac?q=${encodeURIComponent(value)}&format=json`
|
||||||
);
|
);
|
||||||
const data = await resp.json();
|
const data = await resp.json();
|
||||||
if (data) {
|
const filteredData = data.slice(0,8) //Trim to only about 8 results. Any more and our omnibox dies
|
||||||
|
if (filteredData) {
|
||||||
omnibox.innerHTML = "";
|
omnibox.innerHTML = "";
|
||||||
data.map((results: Suggestion) => {
|
filteredData.map((results: Suggestion) => {
|
||||||
let span = document.createElement("span");
|
let span = document.createElement("span");
|
||||||
let pTag = document.createElement("p");
|
let pTag = document.createElement("p");
|
||||||
span.classList.add(
|
span.classList.add(
|
||||||
|
|
|
@ -3,6 +3,7 @@ const { packageName } = Astro.params;
|
||||||
import Layout from "@layouts/Layout.astro";
|
import Layout from "@layouts/Layout.astro";
|
||||||
const response = await fetch(new URL("/api/packages/" + packageName, Astro.url));
|
const response = await fetch(new URL("/api/packages/" + packageName, Astro.url));
|
||||||
const assetsJson = await response.json();
|
const assetsJson = await response.json();
|
||||||
|
import { Icon } from "astro-icon/components";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={`Package: ${packageName}`}>
|
<Layout title={`Package: ${packageName}`}>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue