diff options
| -rw-r--r-- | config/config.example.toml | 15 | ||||
| -rwxr-xr-x | public/css/style.css | 9 | ||||
| -rwxr-xr-x | src/app.ts | 2 | ||||
| -rw-r--r-- | src/lib/config.ts | 23 | ||||
| -rw-r--r-- | src/routes/static.ts | 34 | ||||
| -rw-r--r-- | src/util/config.ts | 19 | ||||
| -rw-r--r-- | src/util/markdown.ts | 16 | ||||
| -rw-r--r-- | static/privacy-policy.md | 1 | ||||
| -rwxr-xr-x | views/layouts/main.handlebars | 23 | ||||
| -rw-r--r-- | views/static.handlebars | 10 | 
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; +} @@ -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 · <a href="https://github.com/lowercasename/gathio">GitHub</a> · 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}} +                    · +                  {{/if}} + +                  <a href="{{this.path}}">{{this.title}}</a> + +                  {{#unless @last}} +                    · +                  {{/unless}} +                {{/each}} +              </p> +            {{/if}} +            <p class="small text-muted"> +              <strong>Gathio</strong> version {{version}} · <a href="https://github.com/lowercasename/gathio">GitHub</a> · 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> +  | 
