Remove Hirad and Hiril scripts
301216829a5bc35afc635b764db6a340205ca9f4
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, '<').replace(/>/g, '>'); |
|
| 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, '<').replace(/>/g, '>'); |
|
| 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">© 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> |