Remove Hirad and Hiril scripts

301216829a5bc35afc635b764db6a340205ca9f4
Alexis Sellier committed ago 1 parent 2e54bc69
AGENTS.md +0 -2
23 23
go.mod           Module: forge
24 24
static/
25 25
  style.css      Single stylesheet, embedded at compile time
26 26
  radiant.svg    Logo, embedded
27 27
  fonts/         5 TTF files (RethinkSans, IBM Plex Mono), embedded
28 -
  js/            Syntax highlighting (hirad.js, hiril.js), embedded
29 28
templates/
30 29
  layout.html    Base layout (header, nav, footer, wraps all pages)
31 30
  index.html     Repository list page
32 31
  home.html      Repo home: branch selector + file tree + content viewer
33 32
  log.html       Paginated commit log
64 63
/:repo/tree/:ref/path...  -> handleTree        (file/dir browser)
65 64
/:repo/commit/:hash       -> handleCommit      (commit detail + diff)
66 65
/:repo/raw/:ref/path...   -> handleRaw         (raw file download)
67 66
/style.css                 -> serveCSS
68 67
/radiant.svg               -> serveLogo
69 -
/js/*                      -> serveJS
70 68
/fonts/*                   -> serveFont
71 69
```
72 70
73 71
Static assets are registered on the mux directly; everything else goes through `route()`.
74 72
handler.go +0 -17
61 61
	w.Header().Set("Content-Type", "image/svg+xml")
62 62
	w.Header().Set("Cache-Control", "public, max-age=86400")
63 63
	w.Write(logoContent)
64 64
}
65 65
66 -
func (s *server) serveJS(w http.ResponseWriter, r *http.Request) {
67 -
	name := strings.TrimPrefix(r.URL.Path, "/js/")
68 -
	var data []byte
69 -
	switch name {
70 -
	case "hirad.js":
71 -
		data = hiradJS
72 -
	case "hiril.js":
73 -
		data = hirilJS
74 -
	default:
75 -
		http.NotFound(w, r)
76 -
		return
77 -
	}
78 -
	w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
79 -
	w.Header().Set("Cache-Control", "public, max-age=3600")
80 -
	w.Write(data)
81 -
}
82 -
83 66
func (s *server) serveFont(w http.ResponseWriter, r *http.Request) {
84 67
	name := strings.TrimPrefix(r.URL.Path, "/fonts/")
85 68
	data, err := fontsFS.ReadFile("static/fonts/" + name)
86 69
	if err != nil {
87 70
		http.NotFound(w, r)
handler_test.go +0 -20
163 163
func getMux(t *testing.T, srv *server, path string) *httptest.ResponseRecorder {
164 164
	t.Helper()
165 165
	mux := http.NewServeMux()
166 166
	mux.HandleFunc("/style.css", srv.serveCSS)
167 167
	mux.HandleFunc("/radiant.svg", srv.serveLogo)
168 -
	mux.HandleFunc("/js/", srv.serveJS)
169 168
	mux.HandleFunc("/fonts/", srv.serveFont)
170 169
	mux.HandleFunc("/", srv.route)
171 170
	req := httptest.NewRequest(http.MethodGet, path, nil)
172 171
	w := httptest.NewRecorder()
173 172
	mux.ServeHTTP(w, req)
276 275
	if w.Body.Len() == 0 {
277 276
		t.Error("empty logo body")
278 277
	}
279 278
}
280 279
281 -
func TestServeJS_Known(t *testing.T) {
282 -
	srv := testServer(t)
283 -
	for _, name := range []string{"hirad.js", "hiril.js"} {
284 -
		w := getMux(t, srv, "/js/"+name)
285 -
		assertCode(t, w, 200)
286 -
		assertHeader(t, w, "Content-Type", "javascript")
287 -
		assertHeader(t, w, "Cache-Control", "public")
288 -
		if w.Body.Len() == 0 {
289 -
			t.Errorf("empty body for %s", name)
290 -
		}
291 -
	}
292 -
}
293 -
294 -
func TestServeJS_Unknown(t *testing.T) {
295 -
	srv := testServer(t)
296 -
	w := getMux(t, srv, "/js/unknown.js")
297 -
	assertCode(t, w, 404)
298 -
}
299 -
300 280
func TestServeFont_Known(t *testing.T) {
301 281
	srv := testServer(t)
302 282
	w := getMux(t, srv, "/fonts/RethinkSans.ttf")
303 283
	assertCode(t, w, 200)
304 284
	assertHeader(t, w, "Content-Type", "font/ttf")
file.go +0 -0
old.go +0 -0
new.go +0 -0
image.png +0 -0
file.go +0 -0
a.go +0 -0
b.go +0 -0
main.go +0 -1
99 99
	}
100 100
101 101
	mux := http.NewServeMux()
102 102
	mux.HandleFunc("/style.css", srv.serveCSS)
103 103
	mux.HandleFunc("/radiant.svg", srv.serveLogo)
104 -
	mux.HandleFunc("/js/", srv.serveJS)
105 104
	mux.HandleFunc("/fonts/", srv.serveFont)
106 105
	mux.HandleFunc("/avatars/", srv.serveAvatar)
107 106
	mux.HandleFunc("/", srv.route)
108 107
109 108
	var handler http.Handler = mux
scripts/seed-forum.sh +1 -10
90 90
```
91 91
92 92
Seems straightforward to shell out to `git log --all` for the data.',
93 93
'cloudhead.io', datetime('now', '-4 days'));
94 94
95 -
-- Discussion 4: a shorter thread
96 -
INSERT INTO discussions (repo, title, body, author, created_at)
97 -
VALUES ('_site', 'Syntax highlighting for more languages?',
98 -
'Currently forge ships `hirad.js` and `hiril.js` for Radiance and RIL. Any plans to support other languages, or is the intent to keep it *minimal*?',
99 -
'bob.bsky.social', datetime('now', '-3 days'));
100 -
101 -
INSERT INTO replies (discussion_id, body, author, created_at)
102 -
VALUES (last_insert_rowid(), 'I think keeping it minimal is the right call. You can always drop your own highlighter scripts into `static/js/` and rebuild. The embed directive picks them up automatically.',
103 -
'cloudhead.io', datetime('now', '-2 days'));
104 95
105 -
-- Discussion 5: no replies yet
96 +
-- Discussion 4: no replies yet
106 97
INSERT INTO discussions (repo, title, body, author, created_at)
107 98
VALUES ('_site', 'Deploying behind nginx reverse proxy',
108 99
'I am trying to set up forge behind nginx with a `/git` base URL. I pass `-base-url /git` but static assets (CSS, fonts) are not loading. Here is what I have so far:
109 100
110 101
```
static/js/hirad.js deleted +0 -82
1 -
(function (hirad) {
2 -
//
3 -
// hirad - Radiance Syntax Highlighter
4 -
//
5 -
// Copyright (c) 2020-2025 Alexis Sellier
6 -
//
7 -
const selector = hirad || '.language-radiance';
8 -
const keywords = [
9 -
    'fn', 'if', 'else', 'for', 'while', 'break', 'switch', 'match', 'set',
10 -
    'record', 'union', 'constant', 'align', 'let', 'use', 'mod', 'module', 'case',
11 -
    'continue', 'return', 'true', 'false', 'loop', 'extern', 'panic',
12 -
    'device', 'register', 'bit', 'catch', 'throw', 'throws', 'test', 'trait', 'instance',
13 -
    'at', 'mut', 'nil', 'undefined', 'static', 'in', 'is', 'where', 'export',
14 -
    'as', 'and', 'or', 'xor', 'not', 'try', 'atomic', 'select', 'of', 'assert'
15 -
];
16 -
const types = ['bool', 'u8', 'u16', 'u32', 'u64', 'i8', 'i16', 'i32', 'i64', 'f32', 'void', 'opaque'];
17 -
18 -
// Syntax definition.
19 -
//
20 -
// The key becomes the class name of the span around the matched block of code.
21 -
const syntax = [
22 -
    ['comment', /(\/\/[^\n]*)/g],
23 -
    ['string' , /("(?:(?!").|\\.)*"|'[^']{1,2}')/g],
24 -
    ['number' , /\b(0x[0-9a-fA-F]+|0b[01]+|[0-9]+(?:\.[0-9]+)?)\b/g],
25 -
    ['ref'    , /(&|&'|\*)\b/g],
26 -
    ['delim'  , /(->|=>|\(|\)|\{|\}|\[|\])/g],
27 -
    ['builtin', /(@[a-zA-Z]+)/g],
28 -
    ['access' , /(\.|::)/g],
29 -
    ['op'     , /(=|<>|\.\.|\+|-|\*|\/|%|\?{1,2}|!{1,2}|>=?|<=?)/g],
30 -
    ['keyword', new RegExp('\\b(' + keywords.join('|') + ')\\b', 'g')],
31 -
    ['type'   , new RegExp('\\b(' + types.join('|') + ')\\b', 'g')],
32 -
];
33 -
34 -
const table = {};
35 -
36 -
// Encode ASCII characters to Braille to avoid conflicts between patterns.
37 -
const encode = (str) => {
38 -
    const encoded = [...str].map(c =>
39 -
        c.charCodeAt(0) <= 127 ? String.fromCharCode(c.charCodeAt(0) + 0x2800) : c
40 -
    ).join('');
41 -
    table[encoded] = str;
42 -
43 -
    return encoded;
44 -
};
45 -
46 -
// Decode Braille back to ASCII.
47 -
const decode = (str) =>
48 -
    table[str] || [...str].map(c => {
49 -
        const code = c.charCodeAt(0) - 0x2800;
50 -
        return code >= 0 && code <= 127 ? String.fromCharCode(code) : c;
51 -
    }).join('');
52 -
53 -
// Escape HTML special characters.
54 -
const escape = (str) =>
55 -
    str.replace(/</g, '&lt;').replace(/>/g, '&gt;');
56 -
57 -
// Highlight all matching elements.
58 -
for (const node of document.querySelectorAll(selector)) {
59 -
    for (const child of node.childNodes) {
60 -
        if (child.nodeType !== Node.TEXT_NODE) continue;
61 -
        if (/^\$\s/.test(child.nodeValue.trim())) continue; // Skip shell snippets.
62 -
63 -
        for (const [cls, re] of syntax) {
64 -
            child.nodeValue = child.nodeValue.replace(re, (_, m) =>
65 -
                '\u00ab' + encode(cls) + '\u00b7' +
66 -
                           encode(m)   +
67 -
                '\u00b7' + encode(cls) + '\u00bb'
68 -
            );
69 -
        }
70 -
    }
71 -
    node.innerHTML = node.innerHTML.replace(
72 -
        /\u00ab(.+?)\u00b7(.+?)\u00b7\1\u00bb/g,
73 -
        (_, name, value) => {
74 -
            value = value.replace(/\u00ab[^\u00b7]+\u00b7|\u00b7[^\u00bb]+\u00bb/g, '');
75 -
            return '<span class="' + decode(name) + '">'   +
76 -
                                     escape(decode(value)) +
77 -
                   '</span>';
78 -
        }
79 -
    );
80 -
}
81 -
82 -
})(window.hirad);
static/js/hiril.js deleted +0 -87
1 -
(function (hiril) {
2 -
//
3 -
// hiril - Radiance IL Syntax Highlighter
4 -
//
5 -
// Copyright (c) 2020-2025 Alexis Sellier
6 -
//
7 -
const selector = hiril || '.language-ril';
8 -
const keywords = ['fn', 'data', 'extern', 'mut', 'align'];
9 -
const instrs = [
10 -
    'reserve', 'load', 'sload', 'store', 'blit', 'copy',
11 -
    'add', 'sub', 'mul', 'sdiv', 'udiv', 'srem', 'urem', 'neg',
12 -
    'eq', 'ne', 'slt', 'sge', 'ult', 'uge',
13 -
    'and', 'or', 'xor', 'shl', 'sshr', 'ushr', 'not',
14 -
    'zext', 'sext', 'call', 'ret', 'jmp',
15 -
    'switch', 'unreachable', 'ecall', 'ebreak'
16 -
];
17 -
const branchOps = ['br.eq', 'br.ne', 'br.slt', 'br.ult'];
18 -
const dataItems = ['str', 'sym', 'undef'];
19 -
const types = ['w8', 'w16', 'w32', 'w64'];
20 -
21 -
// Syntax definition.
22 -
//
23 -
// The key becomes the class name of the span around the matched block of code.
24 -
const syntax = [
25 -
    ['comment', /(\/\/[^\n]*)/g],
26 -
    ['string' , /("[^"]*")/g],
27 -
    ['reg'    , /(%[0-9]+)/g],
28 -
    ['label'  , /(@[A-Za-z_][A-Za-z#0-9_]*)/g],
29 -
    ['symbol' , /(\$[A-Za-z_][A-Za-z0-9_]*)/g],
30 -
    ['number' , /\b(-?[0-9]+|0x[a-fA-F0-9]+)\b/g],
31 -
    ['delim'  , /([{}();:,])/g],
32 -
    ['keyword', new RegExp('\\b(' + keywords.join('|') + ')\\b', 'g')],
33 -
    ['instr'  , new RegExp('\\b(' + branchOps.join('|').replace(/\./g, '\\.') + ')\\b', 'g')],
34 -
    ['instr'  , new RegExp('\\b(' + instrs.join('|') + ')\\b', 'g')],
35 -
    ['type'   , new RegExp('\\b(' + dataItems.join('|') + ')\\b', 'g')],
36 -
    ['type'   , new RegExp('\\b(' + types.join('|') + ')\\b', 'g')],
37 -
];
38 -
39 -
const table = {};
40 -
41 -
// Encode ASCII characters to Braille to avoid conflicts between patterns.
42 -
const encode = (str) => {
43 -
    const encoded = [...str].map(c =>
44 -
        c.charCodeAt(0) <= 127 ? String.fromCharCode(c.charCodeAt(0) + 0x2800) : c
45 -
    ).join('');
46 -
    table[encoded] = str;
47 -
48 -
    return encoded;
49 -
};
50 -
51 -
// Decode Braille back to ASCII.
52 -
const decode = (str) =>
53 -
    table[str] || [...str].map(c => {
54 -
        const code = c.charCodeAt(0) - 0x2800;
55 -
        return code >= 0 && code <= 127 ? String.fromCharCode(code) : c;
56 -
    }).join('');
57 -
58 -
// Escape HTML special characters.
59 -
const escape = (str) =>
60 -
    str.replace(/</g, '&lt;').replace(/>/g, '&gt;');
61 -
62 -
// Highlight all matching elements.
63 -
for (const node of document.querySelectorAll(selector)) {
64 -
    for (const child of node.childNodes) {
65 -
        if (child.nodeType !== Node.TEXT_NODE) continue;
66 -
        if (/^\$\s/.test(child.nodeValue.trim())) continue; // Skip shell snippets.
67 -
68 -
        for (const [cls, re] of syntax) {
69 -
            child.nodeValue = child.nodeValue.replace(re, (_, m) =>
70 -
                '\u00ab' + encode(cls) + '\u00b7' +
71 -
                           encode(m)   +
72 -
                '\u00b7' + encode(cls) + '\u00bb'
73 -
            );
74 -
        }
75 -
    }
76 -
    node.innerHTML = node.innerHTML.replace(
77 -
        /\u00ab(.+?)\u00b7(.+?)\u00b7\1\u00bb/g,
78 -
        (_, name, value) => {
79 -
            value = value.replace(/\u00ab[^\u00b7]+\u00b7|\u00b7[^\u00bb]+\u00bb/g, '');
80 -
            return '<span class="' + decode(name) + '">'   +
81 -
                                     escape(decode(value)) +
82 -
                   '</span>';
83 -
        }
84 -
    );
85 -
}
86 -
87 -
})(window.hiril);
template.go +0 -6
25 25
var fontsFS embed.FS
26 26
27 27
//go:embed static/avatars/*
28 28
var avatarsFS embed.FS
29 29
30 -
//go:embed static/js/hirad.js
31 -
var hiradJS []byte
32 -
33 -
//go:embed static/js/hiril.js
34 -
var hirilJS []byte
35 -
36 30
type templateSet struct {
37 31
	templates map[string]*template.Template
38 32
}
39 33
40 34
var funcMap = template.FuncMap{
templates/layout.html +0 -2
34 34
<footer>
35 35
  <p><span class="powered-by">Powered by <a href="https://code.radiant.computer/forge">Radiant Forge</a></span>{{if and .Discussions .DevMode (not .Handle)}}<span class="dev-login"><a href="{{.BaseURL}}/dev/login?return={{.BaseURL}}/">Sign in as cloudhead.io</a></span>{{end}}<span class="copyright">&copy; 2026 Radiant Computer</span></p>
36 36
</footer>
37 37
{{end}}
38 38
</div>
39 -
<script src="{{.BaseURL}}/js/hirad.js"></script>
40 -
<script src="{{.BaseURL}}/js/hiril.js"></script>
41 39
</body>
42 40
</html>