added static and framed external rendering (untested)

This commit is contained in:
2026-02-24 23:20:56 -07:00
parent 8c2eb1bf80
commit 4652569aee
7 changed files with 138 additions and 5 deletions
+3 -1
View File
@@ -63,6 +63,7 @@ type AmConfig struct {
Title string `yaml:"title"`
Text string `yaml:"text"`
} `yaml:"userAgreement"`
ExternalPath string `yaml:"externalPath"`
} `yaml:"site"`
Database struct {
Driver string `yaml:"driver"`
@@ -212,7 +213,7 @@ func overlayOptionFlag(loaded, defaulted bool) bool {
/* overlayConfig takes two configuration structures and overlays them to create the third.
* Parameters:
* dest - Points to the destination copnfiguration structure.
* dest - Points to the destination configuration structure.
* loaded - Points to the loaded configuration structure.
* defaults - Points to the default configuration structure.
*/
@@ -225,6 +226,7 @@ func overlayConfig(dest *AmConfig, loaded *AmConfig, defaults *AmConfig) {
dest.Site.SessionExpire = overlayString(loaded.Site.SessionExpire, defaults.Site.SessionExpire)
dest.Site.UserAgreement.Title = overlayString(loaded.Site.UserAgreement.Title, defaults.Site.UserAgreement.Title)
dest.Site.UserAgreement.Text = overlayString(loaded.Site.UserAgreement.Text, defaults.Site.UserAgreement.Text)
dest.Site.ExternalPath = overlayString(loaded.Site.ExternalPath, defaults.Site.ExternalPath)
dest.Database.Driver = overlayString(loaded.Database.Driver, defaults.Database.Driver)
dest.Database.Dsn = overlayString(loaded.Database.Dsn, defaults.Database.Dsn)
dest.Defaults.Language = overlayString(loaded.Defaults.Language, defaults.Defaults.Language)
+1
View File
@@ -17,6 +17,7 @@ site:
title: "Amsterdam User Agreement"
text: >
Text of this agreement is TBD.
externalPath: ""
database:
driver: "mysql"
dsn: "amsdb:x00yes2k@tcp(localhost)/amsterdam?parseTime=true&loc=UTC"
+4 -4
View File
@@ -17,7 +17,7 @@ require (
github.com/labstack/gommon v0.4.2
github.com/sirupsen/logrus v1.9.3
github.com/tkuchiki/go-timezone v0.2.3
golang.org/x/text v0.30.0
golang.org/x/text v0.34.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -29,9 +29,9 @@ require (
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
golang.org/x/crypto v0.38.0 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect
golang.org/x/net v0.40.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/net v0.50.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/time v0.11.0 // indirect
)
+8
View File
@@ -57,17 +57,25 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+9
View File
@@ -52,6 +52,15 @@ func setupEcho() *echo.Echo {
uiset := []echo.MiddlewareFunc{ui.SessionStoreInjector, ui.ContextCreator, ui.IPBanTest, ui.CookieLoginTest}
e.RouteNotFound("/*", ui.AmWrap(AmNotFoundHandler), uiset...)
if config.GlobalConfig.Site.ExternalPath != "" {
root, err := os.OpenRoot(config.GlobalConfig.Site.ExternalPath)
if err != nil {
panic(err)
}
fs := root.FS()
e.StaticFS("/ext", fs)
e.GET("/fext/*", ui.AmWrap(ui.AmStaticFramePage(fs, "/fext/")), uiset...)
}
e.Match(GetAndPost, "/TODO/*", ui.AmWrap(NotImplPage), uiset...)
e.GET("/img/*", ui.AmServeImage)
if config.GlobalConfig.Rendering.VeniceCompatibleImageURLs {
+93
View File
@@ -11,11 +11,15 @@
package ui
import (
"bytes"
"embed"
"io"
"io/fs"
"net/http"
"strings"
"github.com/labstack/echo/v4"
"golang.org/x/net/html"
)
//go:embed static/*
@@ -29,3 +33,92 @@ func AmStaticFileHandler() echo.HandlerFunc {
}
return echo.WrapHandler(http.StripPrefix("/static/", http.FileServer(http.FS(fsys))))
}
// extractPlainText extracts all plain text from a HTML tree node.
func extractPlainText(n *html.Node) string {
var sb strings.Builder
var walk func(*html.Node)
walk = func(n *html.Node) {
if n.Type == html.TextNode {
sb.WriteString(n.Data)
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
walk(c)
}
}
walk(n)
return sb.String()
}
// extractInnerHTML extracts the inner HTML from a HTML tree node.
func extractInnerHTML(n *html.Node) string {
var buf bytes.Buffer
for c := n.FirstChild; c != nil; c = c.NextSibling {
html.Render(&buf, c)
}
return buf.String()
}
// breakUpHTML extracts the title and body from an HTML page.
func breakUpHTML(r io.Reader) (string, string, error) {
doc, err := html.Parse(r)
if err != nil {
return "", "", err
}
title := ""
body := ""
var traverse func(*html.Node)
traverse = func(n *html.Node) {
if n.Type == html.ElementNode {
switch n.Data {
case "title":
body = extractPlainText(n)
case "body":
body = extractInnerHTML(n)
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
traverse(c)
}
}
traverse(doc)
return title, body, nil
}
/* AmStaticFramePage generates a handler that will serve up data from an external filesystem "framed" inside
* the frame with the template engine.
* Parameters:
* staticFS - The filesystem to serve from.
* prefix - The prefix to be stripped off pathnames to feed to the filesystem.
* Returns:
* An AmPageFunc suitable for wrapping and adding to the Echo handlers.
*/
func AmStaticFramePage(staticFS fs.FS, prefix string) AmPageFunc {
return func(ctxt AmContext) (string, any) {
fname := ctxt.URLPath()
if strings.HasPrefix(fname, prefix) {
fname = fname[len(prefix):]
} else {
return "error", "invalid path name"
}
mtype := mimeTypeFromFilename(fname)
ctxt.VarMap().Set("mimeType", mtype)
switch mtype {
case "text/html":
f, err := staticFS.Open(fname)
if err != nil {
return "error", err
}
defer f.Close()
title, body, err := breakUpHTML(f)
if err != nil {
return "error", err
}
ctxt.SetFrameTitle(title)
ctxt.VarMap().Set("title", title)
ctxt.VarMap().Set("data", body)
return "framed", "extern.jet"
}
return "error", "Unknown MIME Type: " + mtype
}
}
+20
View File
@@ -0,0 +1,20 @@
{*
* Amsterdam Web Communities System
* Copyright (c) 2025-2026 Erbosoft Metaverse Design Solutions, All Rights Reserved
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*}
<div class="p-4">
<!-- Page Title -->
<div class="mb-6">
<h1 class="text-blue-800 text-4xl font-bold mb-2">{{ title | raw }}</h1>
<hr class="border-2 border-gray-400 w-4/5 mb-6">
</div>
{{ if mimeType == "text/html" }}
{{ data | raw }}
{{ end }}
</div>