Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
fa872e9899 | |||
afea1f1090 | |||
d02597b4fd | |||
119339c0a3 | |||
c1cf78bf96 | |||
8602210a5c | |||
a8f513973e |
6
.gitignore
vendored
6
.gitignore
vendored
@ -10,4 +10,8 @@ package-lock.json
|
||||
|
||||
# exclude this very large css file, it can be
|
||||
# built when needed with `npm run build:tailwind`
|
||||
src/client/public/css/tailwind.css
|
||||
src/client/public/css/tailwind.css
|
||||
|
||||
# Contains bundled js files built from typescript
|
||||
# they can be very large, build with `npm run build:client`
|
||||
src/client/public/bundles
|
14
CHANGELOG.md
14
CHANGELOG.md
@ -2,4 +2,18 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [0.1.1](https://gitea.cor.bz/corbz/relay/compare/v0.1.0...v0.1.1) (2025-04-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* connect routers for accessing the new guild tabs ([d02597b](https://gitea.cor.bz/corbz/relay/commit/d02597b4fd9a5b040b0c499b726b1812a4144ac7))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add tab helper for highlighting the active guild tab ([119339c](https://gitea.cor.bz/corbz/relay/commit/119339c0a3c7a3d0d63db2be91f07d06798a7133))
|
||||
|
||||
## 0.1.0 (2025-04-22)
|
||||
|
||||
Initial version
|
@ -1,3 +1,6 @@
|
||||
// This file is for building client-side typescript found
|
||||
// in './src/client/typescript' to './src/client/public/bundles'
|
||||
|
||||
import { build } from "esbuild";
|
||||
import glob from "fast-glob";
|
||||
|
||||
@ -6,7 +9,7 @@ const entryPoints = await glob("./src/client/typescript/**/*");
|
||||
build({
|
||||
entryPoints,
|
||||
// outBase: "",
|
||||
outdir: "./src/client/public/js",
|
||||
outdir: "./src/client/public/bundles",
|
||||
bundle: true,
|
||||
target: ["es6"],
|
||||
format: "iife",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "relay",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
@ -37,8 +37,11 @@
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@preline/datatable": "^3.0.0",
|
||||
"@preline/dropdown": "^3.0.1",
|
||||
"@prisma/client": "^6.6.0",
|
||||
"@tailwindcss/forms": "^0.5.10",
|
||||
"datatables.net-dt": "^2.2.2",
|
||||
"discord.js": "^14.18.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"ejs": "^3.1.10",
|
||||
|
@ -7,7 +7,9 @@ import engine from "ejs-mate";
|
||||
|
||||
import "@bot/bot";
|
||||
import homeRouter from "@server/routers/home.router";
|
||||
import guildRouter from "@server/routers/guild.router";
|
||||
import { attachGuilds } from "@server/middleware/attachGuilds";
|
||||
import { guildTabHelper } from "@server/middleware/guildTabHelper";
|
||||
|
||||
const app = express();
|
||||
|
||||
@ -18,6 +20,7 @@ app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
app.use("/static", express.static(path.resolve(__dirname, "client/public")));
|
||||
|
||||
app.use("/guild", attachGuilds, guildTabHelper, guildRouter);
|
||||
app.use("/", attachGuilds, homeRouter);
|
||||
|
||||
const HOST = process.env.HOST || "localhost";
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,7 +1,3 @@
|
||||
import { HSOverlay } from 'preline';
|
||||
import $ from 'jquery';
|
||||
|
||||
$(document).ready(() => {
|
||||
console.log("ready!");
|
||||
HSOverlay;
|
||||
});
|
5
src/client/views/guild/content.ejs
Normal file
5
src/client/views/guild/content.ejs
Normal file
@ -0,0 +1,5 @@
|
||||
<% layout("layout/base") -%>
|
||||
|
||||
<%- include("header") -%>
|
||||
|
||||
Content page placeholder
|
5
src/client/views/guild/feeds.ejs
Normal file
5
src/client/views/guild/feeds.ejs
Normal file
@ -0,0 +1,5 @@
|
||||
<% layout("layout/base") -%>
|
||||
|
||||
<%- include("header") -%>
|
||||
|
||||
Feeds page placeholder
|
5
src/client/views/guild/filters.ejs
Normal file
5
src/client/views/guild/filters.ejs
Normal file
@ -0,0 +1,5 @@
|
||||
<% layout("layout/base") -%>
|
||||
|
||||
<%- include("header") -%>
|
||||
|
||||
Filters page placeholder
|
65
src/client/views/guild/header.ejs
Normal file
65
src/client/views/guild/header.ejs
Normal file
@ -0,0 +1,65 @@
|
||||
<div>
|
||||
<div class="relative pt-5 before:w-full before:h-[200px] before:-z-10 before:top-0 before:start-0 before:absolute">
|
||||
<div class="flex sm:items-center gap-5 p-4 sm:p-6 pb-1!">
|
||||
|
||||
<div class="shrink-0">
|
||||
<div class="relative">
|
||||
<% if (guild.icon) { %>
|
||||
<img src="<%= guild.iconURL() %>" class="rounded-md sm:rounded-lg size-16 sm:size-20" alt="">
|
||||
<% } else { %>
|
||||
<div class="size-16 sm:size-20 rounded-md sm:rounded-lg flex shrink-0 justify-center items-center bg-white dark:bg-neutral-800">
|
||||
<span class="text-2xl"><%= guild.nameAcronym %></span>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grow">
|
||||
<h1 class="text-2xl md:text-3xl font-semibold text-gray-800 dark:text-neutral-200">
|
||||
<%= guild.name %>
|
||||
</h1>
|
||||
<ul class="flex flex-wrap items-center gap-3 mt-2">
|
||||
<li class="relative">
|
||||
<span class="text-sm">ID:</span>
|
||||
<span class="text-sm inline-flex items-center gap-2 text-gray-800 dark:text-neutral-200"><%= guild.id %></span>
|
||||
</li>
|
||||
<li class="relative">
|
||||
<span class="text-sm">Members:</span>
|
||||
<span class="text-sm inline-flex items-center gap-2 text-gray-800 dark:text-neutral-200"><%= guild.memberCount %></span>
|
||||
</li>
|
||||
<li class="relative">
|
||||
<span class="text-sm">Channels:</span>
|
||||
<span class="text-sm inline-flex items-center gap-2 text-gray-800 dark:text-neutral-200"><%= guild.channels.channelCountWithoutThreads %></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="ms-auto flex flex-row flex-wrap gap-2">
|
||||
<button type="button" class="py-2 px-3 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-gray-200 bg-white text-gray-800 shadow-xs hover:bg-gray-50 focus:outline-hidden focus:bg-gray-50 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-700 dark:text-white dark:hover:bg-neutral-700 dark:focus:bg-neutral-700">
|
||||
<svg class="shrink-0 size-4" viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
||||
Validate channels
|
||||
</button>
|
||||
<button type="button" class="py-2 px-3 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-gray-200 bg-white text-gray-800 shadow-xs hover:bg-gray-50 focus:outline-hidden focus:bg-gray-50 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-700 dark:text-white dark:hover:bg-neutral-700 dark:focus:bg-neutral-700">
|
||||
<svg class="shrink-0 size-4" viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
|
||||
Copy data
|
||||
</button>
|
||||
<button type="button" class="py-2 px-3 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-gray-200 bg-white text-red-500 shadow-xs hover:bg-gray-50 focus:outline-hidden focus:bg-gray-50 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-900 dark:border-neutral-700 dark:hover:bg-neutral-800 dark:focus:bg-neutral-800">
|
||||
<svg class="shrink-0 size-4" viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg>
|
||||
Remove bot
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col justify-center items-center mx-auto p-4 sm:p-6">
|
||||
<div class="flex flex-row w-full pb-1 whitespace-nowrap overflow-x-auto overflow-y-hidden">
|
||||
<!-- <a href="/guild/<%= guild.id %>" class="inline-flex items-center gap-2 whitespace-nowrap rounded-lg px-3 py-2 text-sm text-gray-800 dark:text-neutral-200 dark:hover:text-neutral-400 dark:focus:text-neutral-400 <%= !isNaN(+guildPage) ? 'bg-white dark:bg-neutral-800 dark:border-neutral-700! border shadow-xs' : 'mx-[1px]' %>">Overview</a> -->
|
||||
<a href="/guild/<%= guild.id %>/feeds" class="inline-flex items-center gap-2 whitespace-nowrap rounded-lg px-3 py-2 text-sm text-blue-500 dark:text-blue-400 dark:hover:text-blue-500 dark:focus:text-blue-500 <%= guildPage === 'feeds' ? 'text-gray-800 dark:text-neutral-200 dark:hover:text-neutral-400 dark:focus:text-neutral-400 bg-white dark:bg-neutral-800 dark:border-neutral-700! border shadow-xs' : 'mx-[1px]' %>">Feeds</a>
|
||||
<a href="/guild/<%= guild.id %>/filters" class="inline-flex items-center gap-2 whitespace-nowrap rounded-lg px-3 py-2 text-sm text-blue-500 dark:text-blue-400 dark:hover:text-blue-500 dark:focus:text-blue-500 <%= guildPage === 'filters' ? 'text-gray-800 dark:text-neutral-200 dark:hover:text-neutral-400 dark:focus:text-neutral-400 bg-white dark:bg-neutral-800 dark:border-neutral-700! border shadow-xs' : 'mx-[1px]' %>">Filters</a>
|
||||
<a href="/guild/<%= guild.id %>/styles" class="inline-flex items-center gap-2 whitespace-nowrap rounded-lg px-3 py-2 text-sm text-blue-500 dark:text-blue-400 dark:hover:text-blue-500 dark:focus:text-blue-500 <%= guildPage === 'styles' ? 'text-gray-800 dark:text-neutral-200 dark:hover:text-neutral-400 dark:focus:text-neutral-400 bg-white dark:bg-neutral-800 dark:border-neutral-700! border shadow-xs' : 'mx-[1px]' %>">Styles</a>
|
||||
<a href="/guild/<%= guild.id %>/content" class="inline-flex items-center gap-2 whitespace-nowrap rounded-lg px-3 py-2 text-sm text-blue-500 dark:text-blue-400 dark:hover:text-blue-500 dark:focus:text-blue-500 <%= guildPage === 'content' ? 'text-gray-800 dark:text-neutral-200 dark:hover:text-neutral-400 dark:focus:text-neutral-400 bg-white dark:bg-neutral-800 dark:border-neutral-700! border shadow-xs' : 'mx-[1px]' %>">Content</a>
|
||||
<!-- <a href="#" class="inline-flex items-center gap-2 whitespace-nowrap rounded-lg px-3 py-2 text-sm text-blue-500 dark:text-blue-400 dark:hover:text-blue-500 dark:focus:text-blue-500">Settings</a> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
5
src/client/views/guild/styles.ejs
Normal file
5
src/client/views/guild/styles.ejs
Normal file
@ -0,0 +1,5 @@
|
||||
<% layout("layout/base") -%>
|
||||
|
||||
<%- include("header") -%>
|
||||
|
||||
Styles page placeholder
|
@ -13,7 +13,7 @@
|
||||
<%- body -%>
|
||||
</div>
|
||||
|
||||
<script src="/static/js/main.js"></script>
|
||||
<script src="/static/bundles/main.js"></script>
|
||||
<%- block("scripts").toString() %>
|
||||
</body>
|
||||
</html>
|
19
src/server/controllers/guild/content.controller.ts
Normal file
19
src/server/controllers/guild/content.controller.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Request, Response } from "express";
|
||||
import { client as bot } from "@bot/bot";
|
||||
|
||||
export const get = async (request: Request, response: Response) => {
|
||||
const guildId = request.params.guildId;
|
||||
const guild = bot.guilds.cache.get(guildId);
|
||||
|
||||
if (!guild) {
|
||||
response.status(404).send("404: guild not found");
|
||||
return;
|
||||
}
|
||||
|
||||
response.render("guild/content", {
|
||||
title: `${guild.name} - Relay`,
|
||||
guild: guild
|
||||
});
|
||||
};
|
||||
|
||||
export default { get }
|
19
src/server/controllers/guild/feed.controller.ts
Normal file
19
src/server/controllers/guild/feed.controller.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Request, Response } from "express";
|
||||
import { client as bot } from "@bot/bot";
|
||||
|
||||
export const get = async (request: Request, response: Response) => {
|
||||
const guildId = request.params.guildId;
|
||||
const guild = bot.guilds.cache.get(guildId);
|
||||
|
||||
if (!guild) {
|
||||
response.status(404).send("404: guild not found");
|
||||
return;
|
||||
}
|
||||
|
||||
response.render("guild/feeds", {
|
||||
title: `${guild.name} - Relay`,
|
||||
guild: guild
|
||||
});
|
||||
};
|
||||
|
||||
export default { get }
|
19
src/server/controllers/guild/filter.controller.ts
Normal file
19
src/server/controllers/guild/filter.controller.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Request, Response } from "express";
|
||||
import { client as bot } from "@bot/bot";
|
||||
|
||||
export const get = async (request: Request, response: Response) => {
|
||||
const guildId = request.params.guildId;
|
||||
const guild = bot.guilds.cache.get(guildId);
|
||||
|
||||
if (!guild) {
|
||||
response.status(404).send("404: guild not found");
|
||||
return;
|
||||
}
|
||||
|
||||
response.render("guild/filters", {
|
||||
title: `${guild.name} - Relay`,
|
||||
guild: guild
|
||||
});
|
||||
};
|
||||
|
||||
export default { get }
|
19
src/server/controllers/guild/style.controller.ts
Normal file
19
src/server/controllers/guild/style.controller.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Request, Response } from "express";
|
||||
import { client as bot } from "@bot/bot";
|
||||
|
||||
export const get = async (request: Request, response: Response) => {
|
||||
const guildId = request.params.guildId;
|
||||
const guild = bot.guilds.cache.get(guildId);
|
||||
|
||||
if (!guild) {
|
||||
response.status(404).send("404: guild not found");
|
||||
return;
|
||||
}
|
||||
|
||||
response.render("guild/styles", {
|
||||
title: `${guild.name} - Relay`,
|
||||
guild: guild
|
||||
});
|
||||
};
|
||||
|
||||
export default { get }
|
@ -1,6 +1,9 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
import { client as bot } from "@bot/bot";
|
||||
|
||||
// The purpose of this middleware is to attach an object containing cached
|
||||
// Discord servers to the response, which is accessible in the DOM.
|
||||
// This is primarily used for rendering servers on the sidebar.
|
||||
export const attachGuilds = (_request: Request, response: Response, next: NextFunction) => {
|
||||
response.locals.guilds = bot.guilds.cache.map(guild => guild);
|
||||
next();
|
||||
|
10
src/server/middleware/guildTabHelper.ts
Normal file
10
src/server/middleware/guildTabHelper.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { Request, Response, NextFunction } from "express";
|
||||
|
||||
// This middleware returns the last segment of the url path.
|
||||
// It's used to identify which tab to highlight as active on
|
||||
// the guild view at '/guild/<id>/<page>'
|
||||
export const guildTabHelper = (request: Request, response: Response, next: NextFunction) => {
|
||||
const currentPath = request.path.split("/")
|
||||
response.locals.guildPage = currentPath[currentPath.length - 1]
|
||||
next();
|
||||
}
|
21
src/server/routers/guild.router.ts
Normal file
21
src/server/routers/guild.router.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { Request, Response, Router } from "express";
|
||||
|
||||
import feedController from "@server/controllers/guild/feed.controller";
|
||||
import filterController from "@server/controllers/guild/filter.controller";
|
||||
import styleController from "@server/controllers/guild/style.controller";
|
||||
import contentController from "@server/controllers/guild/content.controller";
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get("/:guildId", (request: Request, response: Response) => {
|
||||
const guildId = request.params.guildId;
|
||||
response.redirect(`/guild/${guildId}/feeds`);
|
||||
return;
|
||||
});
|
||||
|
||||
router.get("/:guildId/feeds", feedController.get);
|
||||
router.get("/:guildId/filters", filterController.get);
|
||||
router.get("/:guildId/styles", styleController.get);
|
||||
router.get("/:guildId/content", contentController.get);
|
||||
|
||||
export default router;
|
Loading…
x
Reference in New Issue
Block a user