summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config/config.example.toml15
-rwxr-xr-xpublic/css/style.css9
-rwxr-xr-xsrc/app.ts2
-rw-r--r--src/lib/config.ts23
-rw-r--r--src/routes/static.ts34
-rw-r--r--src/util/config.ts19
-rw-r--r--src/util/markdown.ts16
-rw-r--r--static/privacy-policy.md1
-rwxr-xr-xviews/layouts/main.handlebars23
-rw-r--r--views/static.handlebars10
10 files changed, 125 insertions, 27 deletions
diff --git a/config/config.example.toml b/config/config.example.toml
index 1ffbeb7..8f5a09d 100644
--- a/config/config.example.toml
+++ b/config/config.example.toml
@@ -31,3 +31,18 @@ smtp_password = ""
[sendgrid]
api_key = ""
+
+# Links to static pages (for example a privacy policy) or an external community page,
+# which will be displayed in the footer.
+# If paths begin with a slash, they are treated as internal and will open the specified
+# Markdown or text file. If they are absolute (begin with https://), they will simply
+# link to the specified URL.
+
+# [[static_pages]]
+# title = "Privacy Policy"
+# path = "/privacy"
+# filename = "privacy-policy.md"
+
+# [[static_pages]]
+# title = "External Link"
+# path = "https://example.com"
diff --git a/public/css/style.css b/public/css/style.css
index 0f149e8..8e6322e 100755
--- a/public/css/style.css
+++ b/public/css/style.css
@@ -54,6 +54,10 @@ body, html {
padding: 5px 0;
}
+#footerContainer p {
+ margin-bottom: 0.25rem;
+}
+
#sidebar {
background: #f5f5f5;
border-bottom: 2px solid #e0e0e0;
@@ -396,3 +400,8 @@ label:not(.form-check-label) {
input[type="datetime-local"] {
max-width: 20rem;
}
+
+article.static-page header {
+ margin-bottom: 1rem;
+ border-bottom: 1px solid #e0e0e0;
+}
diff --git a/src/app.ts b/src/app.ts
index c43f31d..3370d27 100755
--- a/src/app.ts
+++ b/src/app.ts
@@ -6,6 +6,7 @@ import frontend from "./routes/frontend.js";
import activitypub from "./routes/activitypub.js";
import event from "./routes/event.js";
import group from "./routes/group.js";
+import staticPages from "./routes/static.js";
import { initEmailService } from "./lib/email.js";
@@ -53,6 +54,7 @@ app.use(express.json({ type: "application/json" }));
app.use(express.urlencoded({ extended: true }));
// Router //
+app.use("/", staticPages);
app.use("/", frontend);
app.use("/", activitypub);
app.use("/", event);
diff --git a/src/lib/config.ts b/src/lib/config.ts
index 7b35b98..7366b59 100644
--- a/src/lib/config.ts
+++ b/src/lib/config.ts
@@ -2,6 +2,12 @@ import fs from "fs";
import toml from "toml";
import { exitWithError } from "./process.js";
+interface StaticPage {
+ title: string;
+ path: string;
+ filename: string;
+}
+
interface GathioConfig {
general: {
domain: string;
@@ -25,9 +31,21 @@ interface GathioConfig {
sendgrid?: {
api_key: string;
};
+ static_pages: StaticPage[];
+}
+
+interface FrontendConfig {
+ domain: string;
+ siteName: string;
+ isFederated: boolean;
+ emailLogoUrl: string;
+ showKofi: boolean;
+ showInstanceInformation: boolean;
+ staticPages: StaticPage[];
+ version: string;
}
-export const publicConfig = () => {
+export const frontendConfig = (): FrontendConfig => {
const config = getConfig();
return {
domain: config.general.domain,
@@ -35,6 +53,9 @@ export const publicConfig = () => {
isFederated: config.general.is_federated,
emailLogoUrl: config.general.email_logo_url,
showKofi: config.general.show_kofi,
+ showInstanceInformation: config.static_pages.length > 0,
+ staticPages: config.static_pages,
+ version: process.env.npm_package_version || "unknown",
};
};
diff --git a/src/routes/static.ts b/src/routes/static.ts
new file mode 100644
index 0000000..f57d1db
--- /dev/null
+++ b/src/routes/static.ts
@@ -0,0 +1,34 @@
+import { Router, Request, Response } from "express";
+import fs from "fs";
+import getConfig, { frontendConfig } from "../lib/config.js";
+import { markdownToSanitizedHTML } from "../util/markdown.js";
+
+const config = getConfig();
+const router = Router();
+
+config.static_pages
+ .filter((page) => page.path?.startsWith("/") && page.filename)
+ .forEach((page) => {
+ router.get(page.path, (_: Request, res: Response) => {
+ try {
+ if (fs.existsSync(`./static/${page.filename}`)) {
+ const fileBody = fs.readFileSync(
+ `./static/${page.filename}`,
+ "utf-8",
+ );
+ const parsed = markdownToSanitizedHTML(fileBody);
+ return res.render("static", {
+ title: page.title,
+ content: parsed,
+ ...frontendConfig(),
+ });
+ }
+ return res.status(404).render("404", frontendConfig());
+ } catch (err) {
+ console.error(err);
+ return res.status(404).render("404", frontendConfig());
+ }
+ });
+ });
+
+export default router;
diff --git a/src/util/config.ts b/src/util/config.ts
deleted file mode 100644
index d1fd05b..0000000
--- a/src/util/config.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import getConfig from "../lib/config.js";
-
-const config = getConfig();
-
-interface FrontendConfig {
- domain: string;
- email: string;
- siteName: string;
- showKofi: boolean;
- isFederated: boolean;
-}
-
-export const frontendConfig = (): FrontendConfig => ({
- domain: config.general.domain,
- email: config.general.email,
- siteName: config.general.site_name,
- showKofi: config.general.show_kofi,
- isFederated: config.general.is_federated,
-});
diff --git a/src/util/markdown.ts b/src/util/markdown.ts
index 9f5d384..bab50bd 100644
--- a/src/util/markdown.ts
+++ b/src/util/markdown.ts
@@ -1,7 +1,6 @@
-// Extra marked renderer (used to render plaintext event description for page metadata)
-// Adapted from https://dustinpfister.github.io/2017/11/19/nodejs-marked/
-
import { marked } from "marked";
+import { JSDOM } from "jsdom";
+import DOMPurify from "dompurify";
// ? to ? helper
function htmlEscapeToText(text: string) {
@@ -14,6 +13,9 @@ function htmlEscapeToText(text: string) {
});
}
+// Extra marked renderer (used to render plaintext event description for page metadata)
+// Adapted from https://dustinpfister.github.io/2017/11/19/nodejs-marked/
+
export const renderPlain = () => {
var render = new marked.Renderer();
// render just the text of a link, strong, em
@@ -42,3 +44,11 @@ export const renderPlain = () => {
};
return render;
};
+
+export const markdownToSanitizedHTML = (markdown: string) => {
+ const html = marked.parse(markdown);
+ const window = new JSDOM("").window;
+ const purify = DOMPurify(window);
+ const clean = purify.sanitize(html);
+ return clean;
+};
diff --git a/static/privacy-policy.md b/static/privacy-policy.md
new file mode 100644
index 0000000..507ef47
--- /dev/null
+++ b/static/privacy-policy.md
@@ -0,0 +1 @@
+This is an example privacy policy. You should edit this file - feel free to take inspiration from the [gath.io instance privacy policy](https://gath.io/privacy).
diff --git a/views/layouts/main.handlebars b/views/layouts/main.handlebars
index daa5a37..fb493a4 100755
--- a/views/layouts/main.handlebars
+++ b/views/layouts/main.handlebars
@@ -68,12 +68,27 @@
{{{body}}}
</div>
<div id="footerContainer">
- <small class="text-muted">
- Version 1.3.0 &middot; <a href="https://github.com/lowercasename/gathio">GitHub</a> &middot; Made with <i class="far fa-heart"></i> by <a href="https://raphaelkabo.com">Raphael</a><br />
- </small>
+ {{#if showInstanceInformation}}
+ <p class="small text-muted">
+ <strong>{{domain}}</strong>
+ {{#each staticPages}}
+ {{#if @first}}
+ &middot;
+ {{/if}}
+
+ <a href="{{this.path}}">{{this.title}}</a>
+
+ {{#unless @last}}
+ &middot;
+ {{/unless}}
+ {{/each}}
+ </p>
+ {{/if}}
+ <p class="small text-muted">
+ <strong>Gathio</strong> version {{version}} &middot; <a href="https://github.com/lowercasename/gathio">GitHub</a> &middot; Made with <i class="far fa-heart"></i> by <a href="https://raphaelkabo.com">Raphael</a> and <a href="https://github.com/lowercasename/gathio/graphs/contributors">contributors</a>
+ </p>
</div>
</div>
</div>
</div>
- </body>
</html>
diff --git a/views/static.handlebars b/views/static.handlebars
new file mode 100644
index 0000000..d28d8f2
--- /dev/null
+++ b/views/static.handlebars
@@ -0,0 +1,10 @@
+
+<article class="static-page">
+ <header>
+ <h1>{{title}}</h1>
+ </header>
+ <main>
+ {{{content}}}
+ </main>
+</article>
+