Allow for custom additions to webroot
This commit is contained in:
@@ -4,6 +4,7 @@ registration.yaml
|
|||||||
ooye.db*
|
ooye.db*
|
||||||
events.db*
|
events.db*
|
||||||
backfill.db*
|
backfill.db*
|
||||||
|
custom-webroot
|
||||||
|
|
||||||
# Automatically generated
|
# Automatically generated
|
||||||
node_modules
|
node_modules
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ Whether you read those or not, I'm more than happy to help you 1-on-1 with codin
|
|||||||
|
|
||||||
# Dependency justification
|
# Dependency justification
|
||||||
|
|
||||||
Total transitive production dependencies: 137
|
Total transitive production dependencies: 134
|
||||||
|
|
||||||
### <font size="+2">🦕</font>
|
### <font size="+2">🦕</font>
|
||||||
|
|
||||||
@@ -119,8 +119,8 @@ Total transitive production dependencies: 137
|
|||||||
* (0) entities: Looks fine. No dependencies.
|
* (0) entities: Looks fine. No dependencies.
|
||||||
* (0) get-relative-path: Looks fine. No dependencies.
|
* (0) get-relative-path: Looks fine. No dependencies.
|
||||||
* (1) heatsync: Module hot-reloader that I trust.
|
* (1) heatsync: Module hot-reloader that I trust.
|
||||||
* (1) js-yaml: Will be removed in the future after registration.yaml is converted to JSON.
|
|
||||||
* (0) lru-cache: For holding unused nonce in memory and letting them be overwritten later if never used.
|
* (0) lru-cache: For holding unused nonce in memory and letting them be overwritten later if never used.
|
||||||
|
* (0) mime-type: File extension to mime type mapping that's already pulled in by stream-mime-type.
|
||||||
* (0) prettier-bytes: It does what I want and has no dependencies.
|
* (0) prettier-bytes: It does what I want and has no dependencies.
|
||||||
* (0) snowtransfer: Discord API library with bring-your-own-caching that I trust.
|
* (0) snowtransfer: Discord API library with bring-your-own-caching that I trust.
|
||||||
* (0) try-to-catch: Not strictly necessary, but it's already pulled in by supertape, so I may as well.
|
* (0) try-to-catch: Not strictly necessary, but it's already pulled in by supertape, so I may as well.
|
||||||
|
|||||||
Generated
+2
@@ -33,6 +33,7 @@
|
|||||||
"heatsync": "^2.7.2",
|
"heatsync": "^2.7.2",
|
||||||
"htmx.org": "^2.0.4",
|
"htmx.org": "^2.0.4",
|
||||||
"lru-cache": "^11.0.2",
|
"lru-cache": "^11.0.2",
|
||||||
|
"mime-types": "^2.1.35",
|
||||||
"prettier-bytes": "^1.0.4",
|
"prettier-bytes": "^1.0.4",
|
||||||
"sharp": "^0.34.5",
|
"sharp": "^0.34.5",
|
||||||
"snowtransfer": "^0.17.1",
|
"snowtransfer": "^0.17.1",
|
||||||
@@ -2073,6 +2074,7 @@
|
|||||||
"version": "2.1.35",
|
"version": "2.1.35",
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mime-db": "1.52.0"
|
"mime-db": "1.52.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
"heatsync": "^2.7.2",
|
"heatsync": "^2.7.2",
|
||||||
"htmx.org": "^2.0.4",
|
"htmx.org": "^2.0.4",
|
||||||
"lru-cache": "^11.0.2",
|
"lru-cache": "^11.0.2",
|
||||||
|
"mime-types": "^2.1.35",
|
||||||
"prettier-bytes": "^1.0.4",
|
"prettier-bytes": "^1.0.4",
|
||||||
"sharp": "^0.34.5",
|
"sharp": "^0.34.5",
|
||||||
"snowtransfer": "^0.17.1",
|
"snowtransfer": "^0.17.1",
|
||||||
|
|||||||
@@ -31,7 +31,15 @@ function addGlobals(obj) {
|
|||||||
*/
|
*/
|
||||||
function render(event, filename, locals) {
|
function render(event, filename, locals) {
|
||||||
const path = join(__dirname, "pug", filename)
|
const path = join(__dirname, "pug", filename)
|
||||||
|
return renderPath(event, path, locals)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("h3").H3Event} event
|
||||||
|
* @param {string} path
|
||||||
|
* @param {Record<string, any>} locals
|
||||||
|
*/
|
||||||
|
function renderPath(event, path, locals) {
|
||||||
function compile() {
|
function compile() {
|
||||||
try {
|
try {
|
||||||
const template = compileFile(path, {pretty})
|
const template = compileFile(path, {pretty})
|
||||||
@@ -89,4 +97,5 @@ function createRoute(router, url, filename) {
|
|||||||
|
|
||||||
module.exports.addGlobals = addGlobals
|
module.exports.addGlobals = addGlobals
|
||||||
module.exports.render = render
|
module.exports.render = render
|
||||||
|
module.exports.renderPath = renderPath
|
||||||
module.exports.createRoute = createRoute
|
module.exports.createRoute = createRoute
|
||||||
|
|||||||
+12
-10
@@ -41,16 +41,18 @@ block body
|
|||||||
= ` Set up self-service`
|
= ` Set up self-service`
|
||||||
|
|
||||||
.s-prose
|
.s-prose
|
||||||
h2 What is this?
|
block bridge-info
|
||||||
p #[a(href="https://gitdab.com/cadence/out-of-your-element") Out Of Your Element] is a bridge between the Discord and Matrix chat apps. It lets people on both platforms chat with each other without needing to get everyone on the same app.
|
h2 What is this?
|
||||||
p Just chat like usual, and the bridge will forward messages back and forth between the two platforms, so everyone sees the whole conversation.
|
p #[a(href="https://gitdab.com/cadence/out-of-your-element") Out Of Your Element] is a bridge between the Discord and Matrix chat apps. It lets people on both platforms chat with each other without needing to get everyone on the same app.
|
||||||
p All kinds of content are supported, including pictures, threads, emojis, and @mentions.
|
p Just chat like usual, and the bridge will forward messages back and forth between the two platforms, so everyone sees the whole conversation.
|
||||||
p It's really easy to set up, even if you only have Discord. Just add the bot to your server, and it'll make everything available on Matrix automatically.
|
p All kinds of content are supported, including pictures, threads, emojis, and @mentions.
|
||||||
|
p It's really easy to set up, even if you only have Discord. Just add the bot to your server, and it'll make everything available on Matrix automatically.
|
||||||
|
|
||||||
if locked
|
if locked
|
||||||
h2 This is a private instance
|
block locked-info
|
||||||
p Anybody can run their own instance of the Out Of Your Element software. The person running this instance has made it private, so you can't add it to your server just yet. If you know who's in charge of #{reg.ooye.server_name}, ask them for the password.
|
h2 This is a private instance
|
||||||
|
p Anybody can run their own instance of the Out Of Your Element software. The person running this instance has made it private, so you can't add it to your server just yet. If you know who's in charge of #{reg.ooye.server_name}, ask them for the password.
|
||||||
|
|
||||||
h2 Run your own instance
|
h2 Run your own instance
|
||||||
p You can still use Out Of Your Element by running your own copy of the software, but this requires some technical skill.
|
p You can still use Out Of Your Element by running your own copy of the software, but this requires some technical skill.
|
||||||
p To get started, #[a(href="https://gitdab.com/cadence/out-of-your-element/src/branch/main/docs/get-started.md") check the installation instructions.]
|
p To get started, #[a(href="https://gitdab.com/cadence/out-of-your-element/src/branch/main/docs/get-started.md") check the installation instructions.]
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
mixin guild(guild)
|
mixin guild-menuitem(guild)
|
||||||
|
- let bridgedRoomCount = from("channel_room").selectUnsafe("count(*) as count").where({guild_id: guild.id}).and("AND thread_parent IS NULL").get().count
|
||||||
|
li(role="menuitem")
|
||||||
|
a.s-topbar--item.s-user-card.d-flex.p4(href=rel(`/guild?guild_id=${guild.id}`) class={"bg-purple-200": bridgedRoomCount === 0, "h:bg-purple-300": bridgedRoomCount === 0})
|
||||||
|
+guild(guild, bridgedRoomCount)
|
||||||
|
|
||||||
|
mixin guild(guild, bridgedRoomCount)
|
||||||
span.s-avatar.s-avatar__32.s-user-card--avatar
|
span.s-avatar.s-avatar__32.s-user-card--avatar
|
||||||
if guild.icon
|
if guild.icon
|
||||||
img.s-avatar--image(src=`https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.png?size=32` alt="")
|
img.s-avatar--image(src=`https://cdn.discordapp.com/icons/${guild.id}/${guild.icon}.png?size=32` alt="")
|
||||||
@@ -6,8 +12,12 @@ mixin guild(guild)
|
|||||||
.s-avatar--letter.bg-silver-400.bar-md(aria-hidden="true")= guild.name[0]
|
.s-avatar--letter.bg-silver-400.bar-md(aria-hidden="true")= guild.name[0]
|
||||||
.s-user-card--info.ai-start
|
.s-user-card--info.ai-start
|
||||||
strong= guild.name
|
strong= guild.name
|
||||||
ul.s-user-card--awards
|
if bridgedRoomCount != null
|
||||||
li #{discord.guildChannelMap.get(guild.id).filter(c => [0, 5, 15, 16].includes(discord.channels.get(c).type)).length} channels
|
ul.s-user-card--awards
|
||||||
|
if bridgedRoomCount
|
||||||
|
li #{bridgedRoomCount} bridged rooms
|
||||||
|
else
|
||||||
|
li.fc-purple Not yet linked
|
||||||
|
|
||||||
mixin define-theme(name, h, s, l)
|
mixin define-theme(name, h, s, l)
|
||||||
style.
|
style.
|
||||||
@@ -58,6 +68,8 @@ html(lang="en")
|
|||||||
title Out Of Your Element
|
title Out Of Your Element
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
link(rel="stylesheet" type="text/css" href=rel("/static/stacks.min.css"))
|
link(rel="stylesheet" type="text/css" href=rel("/static/stacks.min.css"))
|
||||||
|
//- Please use responsibly!!!!!
|
||||||
|
link(rel="stylesheet" type="text/css" href=rel("/custom.css"))
|
||||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 80%22><text y=%22.83em%22 font-size=%2283%22>💬</text></svg>">
|
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 80%22><text y=%22.83em%22 font-size=%2283%22>💬</text></svg>">
|
||||||
meta(name="htmx-config" content='{"requestClass":"is-loading"}')
|
meta(name="htmx-config" content='{"requestClass":"is-loading"}')
|
||||||
style.
|
style.
|
||||||
@@ -79,6 +91,14 @@ html(lang="en")
|
|||||||
.s-btn__dropdown:has(+ :popover-open) {
|
.s-btn__dropdown:has(+ :popover-open) {
|
||||||
background-color: var(--theme-topbar-item-background-hover, var(--black-200)) !important;
|
background-color: var(--theme-topbar-item-background-hover, var(--black-200)) !important;
|
||||||
}
|
}
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
body.theme-system .s-popover {
|
||||||
|
--_po-bg: var(--black-100);
|
||||||
|
--_po-bc: var(--bc-light);
|
||||||
|
--_po-bs: var(--bs-lg);
|
||||||
|
--_po-arrow-fc: var(--black-100);
|
||||||
|
}
|
||||||
|
}
|
||||||
+define-themed-button("matrix", "black")
|
+define-themed-button("matrix", "black")
|
||||||
body.themed.theme-system
|
body.themed.theme-system
|
||||||
header.s-topbar
|
header.s-topbar
|
||||||
@@ -114,9 +134,7 @@ html(lang="en")
|
|||||||
.s-popover--content.overflow-y-auto.overflow-x-hidden
|
.s-popover--content.overflow-y-auto.overflow-x-hidden
|
||||||
ul.s-menu(role="menu")
|
ul.s-menu(role="menu")
|
||||||
each guild in [...managed].map(id => discord.guilds.get(id)).filter(g => g).sort((a, b) => a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1)
|
each guild in [...managed].map(id => discord.guilds.get(id)).filter(g => g).sort((a, b) => a.name.toLowerCase() < b.name.toLowerCase() ? -1 : 1)
|
||||||
li(role="menuitem")
|
+guild-menuitem(guild)
|
||||||
a.s-topbar--item.s-user-card.d-flex.p4(href=rel(`/guild?guild_id=${guild.id}`))
|
|
||||||
+guild(guild)
|
|
||||||
//- Body
|
//- Body
|
||||||
.mx-auto.w100.wmx9.py24.px8.fs-body1#content
|
.mx-auto.w100.wmx9.py24.px8.fs-body1#content
|
||||||
block body
|
block body
|
||||||
|
|||||||
+76
-22
@@ -4,13 +4,14 @@ const assert = require("assert")
|
|||||||
const fs = require("fs")
|
const fs = require("fs")
|
||||||
const {join} = require("path")
|
const {join} = require("path")
|
||||||
const h3 = require("h3")
|
const h3 = require("h3")
|
||||||
const {defineEventHandler, defaultContentType, getRequestHeader, setResponseHeader, handleCacheHeaders} = h3
|
const mimeTypes = require("mime-types")
|
||||||
|
const {defineEventHandler, defaultContentType, getRequestHeader, setResponseHeader, handleCacheHeaders, serveStatic} = h3
|
||||||
const icons = require("@stackoverflow/stacks-icons")
|
const icons = require("@stackoverflow/stacks-icons")
|
||||||
const DiscordTypes = require("discord-api-types/v10")
|
const DiscordTypes = require("discord-api-types/v10")
|
||||||
const dUtils = require("../discord/utils")
|
const dUtils = require("../discord/utils")
|
||||||
const reg = require("../matrix/read-registration")
|
const reg = require("../matrix/read-registration")
|
||||||
|
|
||||||
const {sync, discord, as, select} = require("../passthrough")
|
const {sync, discord, as, select, from} = require("../passthrough")
|
||||||
/** @type {import("./pug-sync")} */
|
/** @type {import("./pug-sync")} */
|
||||||
const pugSync = sync.require("./pug-sync")
|
const pugSync = sync.require("./pug-sync")
|
||||||
/** @type {import("../matrix/utils")} */
|
/** @type {import("../matrix/utils")} */
|
||||||
@@ -19,21 +20,7 @@ const {id} = require("../../addbot")
|
|||||||
|
|
||||||
// Pug
|
// Pug
|
||||||
|
|
||||||
pugSync.addGlobals({id, h3, discord, select, DiscordTypes, dUtils, mUtils, icons, reg: reg.reg})
|
pugSync.addGlobals({id, h3, discord, select, from, DiscordTypes, dUtils, mUtils, icons, reg: reg.reg})
|
||||||
pugSync.createRoute(as.router, "/", "home.pug")
|
|
||||||
pugSync.createRoute(as.router, "/ok", "ok.pug")
|
|
||||||
|
|
||||||
// Routes
|
|
||||||
|
|
||||||
sync.require("./routes/download-matrix")
|
|
||||||
sync.require("./routes/download-discord")
|
|
||||||
sync.require("./routes/guild-settings")
|
|
||||||
sync.require("./routes/guild")
|
|
||||||
sync.require("./routes/info")
|
|
||||||
sync.require("./routes/link")
|
|
||||||
sync.require("./routes/log-in-with-matrix")
|
|
||||||
sync.require("./routes/oauth")
|
|
||||||
sync.require("./routes/password")
|
|
||||||
|
|
||||||
// Files
|
// Files
|
||||||
|
|
||||||
@@ -65,12 +52,79 @@ as.router.get("/static/htmx.js", defineEventHandler({
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
as.router.get("/icon.png", defineEventHandler(event => {
|
|
||||||
handleCacheHeaders(event, {maxAge: 86400})
|
|
||||||
return fs.promises.readFile(join(__dirname, "../../docs/img/icon.png"))
|
|
||||||
}))
|
|
||||||
|
|
||||||
as.router.get("/download/file/poll-star-avatar.png", defineEventHandler(event => {
|
as.router.get("/download/file/poll-star-avatar.png", defineEventHandler(event => {
|
||||||
handleCacheHeaders(event, {maxAge: 86400})
|
handleCacheHeaders(event, {maxAge: 86400})
|
||||||
return fs.promises.readFile(join(__dirname, "../../docs/img/poll-star-avatar.png"))
|
return fs.promises.readFile(join(__dirname, "../../docs/img/poll-star-avatar.png"))
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// Custom files
|
||||||
|
|
||||||
|
const publicDir = "custom-webroot"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {h3.H3Event} event
|
||||||
|
* @param {boolean} fallthrough
|
||||||
|
*/
|
||||||
|
function tryStatic(event, fallthrough) {
|
||||||
|
return serveStatic(event, {
|
||||||
|
indexNames: ["/index.html", "/index.pug"],
|
||||||
|
fallthrough,
|
||||||
|
getMeta: async id => {
|
||||||
|
// Check
|
||||||
|
const stats = await fs.promises.stat(join(publicDir, id)).catch(() => {});
|
||||||
|
if (!stats || !stats.isFile()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Pug
|
||||||
|
if (id.match(/\.pug$/)) {
|
||||||
|
defaultContentType(event, "text/html; charset=utf-8")
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
// Everything else
|
||||||
|
else {
|
||||||
|
const mime = mimeTypes.lookup(id)
|
||||||
|
if (typeof mime === "string") defaultContentType(event, mime)
|
||||||
|
return {
|
||||||
|
size: stats.size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getContents: id => {
|
||||||
|
if (id.match(/\.pug$/)) {
|
||||||
|
const path = join(publicDir, id)
|
||||||
|
return pugSync.renderPath(event, path, {})
|
||||||
|
} else {
|
||||||
|
return fs.promises.readFile(join(publicDir, id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
as.router.get("/**", defineEventHandler(event => {
|
||||||
|
return tryStatic(event, false)
|
||||||
|
}))
|
||||||
|
|
||||||
|
as.router.get("/", defineEventHandler(async event => {
|
||||||
|
return (await tryStatic(event, true)) || pugSync.render(event, "home.pug", {})
|
||||||
|
}))
|
||||||
|
|
||||||
|
as.router.get("/icon.png", defineEventHandler(async event => {
|
||||||
|
const s = await tryStatic(event, true)
|
||||||
|
if (s) return s
|
||||||
|
handleCacheHeaders(event, {maxAge: 86400})
|
||||||
|
return fs.promises.readFile(join(__dirname, "../../docs/img/icon.png"))
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Routes
|
||||||
|
|
||||||
|
pugSync.createRoute(as.router, "/ok", "ok.pug")
|
||||||
|
|
||||||
|
sync.require("./routes/download-matrix")
|
||||||
|
sync.require("./routes/download-discord")
|
||||||
|
sync.require("./routes/guild-settings")
|
||||||
|
sync.require("./routes/guild")
|
||||||
|
sync.require("./routes/info")
|
||||||
|
sync.require("./routes/link")
|
||||||
|
sync.require("./routes/log-in-with-matrix")
|
||||||
|
sync.require("./routes/oauth")
|
||||||
|
sync.require("./routes/password")
|
||||||
|
|||||||
Reference in New Issue
Block a user