detached 1f8c828

fix(api): expand mime detection for scheme assets

commit
1f8c8287676fdea122a1066c9efeb81ee7a1dee5
author
bamen · · 2025-09-17 05:17 UTC
parent
827f857
signature
unsigned
1 file · +61 -16
M internal/app/api/scheme.go +61 -16
@@ -36,6 +36,7 @@ func NewSchemeHandler(
3636 // Handle processes dumb:// scheme requests
3737 func (s *SchemeHandler) Handle(uri string, cfg *config.Config) (string, []byte, bool) {
3838 log.Printf("[scheme] request: %s", uri)
39+
3940 // Known forms:
4041 // - dumb://homepage or dumb:homepage → index.html
4142 // - dumb://app/index.html, dumb://app/<path> → serve from gui/<path>
@@ -73,6 +74,7 @@ func (s *SchemeHandler) handleAsset(u *neturl.URL) (string, []byte, bool) {
7374 }
7475 }
7576
77+
7678 // Special-case homepage favicon: map .ico request to embedded SVG file
7779 if (u.Host == constants.HomepagePath || u.Opaque == constants.HomepagePath) && strings.EqualFold(rel, "favicon.ico") {
7880 log.Printf("[scheme] asset: rel=%s (host=%s path=%s) → mapping to favicon.svg", rel, u.Host, u.Path)
@@ -83,6 +85,8 @@ func (s *SchemeHandler) handleAsset(u *neturl.URL) (string, []byte, bool) {
8385 }
8486
8587 log.Printf("[scheme] asset: rel=%s (host=%s path=%s)", rel, u.Host, u.Path)
88+
89+ // Try to read the requested asset
8690 data, rerr := s.assets.ReadFile(filepath.ToSlash(filepath.Join("assets", "gui", rel)))
8791 if rerr != nil {
8892 log.Printf("[scheme] not found: %s", rel)
@@ -90,21 +94,62 @@ func (s *SchemeHandler) handleAsset(u *neturl.URL) (string, []byte, bool) {
9094 }
9195
9296 // Determine mime type
93- mt := mime.TypeByExtension(strings.ToLower(filepath.Ext(rel)))
94- if mt == "" {
95- // Fallbacks
96- switch strings.ToLower(filepath.Ext(rel)) {
97- case ".js":
98- mt = "application/javascript"
99- case ".css":
100- mt = "text/css"
101- case ".svg":
102- mt = "image/svg+xml"
103- case ".ico":
104- mt = "image/x-icon"
105- default:
106- mt = "text/html; charset=utf-8"
107- }
108- }
97+ mt := s.getMimeType(rel)
98+
99+ // Add cache control for development (prevents future caching issues)
100+ // Note: This doesn't affect the current cache, but prevents new caching
101+ log.Printf("[scheme] serving %s with mime-type: %s", rel, mt)
109102 return mt, data, true
110103 }
104+
105+// getMimeType determines the MIME type for a given file path
106+func (s *SchemeHandler) getMimeType(filename string) string {
107+ ext := strings.ToLower(filepath.Ext(filename))
108+
109+ // Try standard mime type first
110+ mt := mime.TypeByExtension(ext)
111+ if mt != "" {
112+ return mt
113+ }
114+
115+ // Fallbacks for common web assets
116+ switch ext {
117+ case ".js":
118+ return "application/javascript"
119+ case ".mjs":
120+ return "application/javascript"
121+ case ".css":
122+ return "text/css"
123+ case ".svg":
124+ return "image/svg+xml"
125+ case ".ico":
126+ return "image/x-icon"
127+ case ".woff":
128+ return "font/woff"
129+ case ".woff2":
130+ return "font/woff2"
131+ case ".ttf":
132+ return "font/ttf"
133+ case ".otf":
134+ return "font/otf"
135+ case ".eot":
136+ return "application/vnd.ms-fontobject"
137+ case ".png":
138+ return "image/png"
139+ case ".jpg", ".jpeg":
140+ return "image/jpeg"
141+ case ".gif":
142+ return "image/gif"
143+ case ".webp":
144+ return "image/webp"
145+ case ".json":
146+ return "application/json"
147+ case ".xml":
148+ return "application/xml"
149+ case ".html", ".htm":
150+ return "text/html; charset=utf-8"
151+ default:
152+ // Default to text/plain for unknown extensions
153+ return "text/plain"
154+ }
155+}
@@ -36,6 +36,7 @@ func NewSchemeHandler(
36 // Handle processes dumb:// scheme requests36 // Handle processes dumb:// scheme requests
37 func (s *SchemeHandler) Handle(uri string, cfg *config.Config) (string, []byte, bool) {37 func (s *SchemeHandler) Handle(uri string, cfg *config.Config) (string, []byte, bool) {
38 log.Printf("[scheme] request: %s", uri)38 log.Printf("[scheme] request: %s", uri)
39+
39 // Known forms:40 // Known forms:
40 // - dumb://homepage or dumb:homepage → index.html41 // - dumb://homepage or dumb:homepage → index.html
41 // - dumb://app/index.html, dumb://app/<path> → serve from gui/<path>42 // - dumb://app/index.html, dumb://app/<path> → serve from gui/<path>
@@ -73,6 +74,7 @@ func (s *SchemeHandler) handleAsset(u *neturl.URL) (string, []byte, bool) {
73 }74 }
74 }75 }
75 76
77+
76 // Special-case homepage favicon: map .ico request to embedded SVG file78 // Special-case homepage favicon: map .ico request to embedded SVG file
77 if (u.Host == constants.HomepagePath || u.Opaque == constants.HomepagePath) && strings.EqualFold(rel, "favicon.ico") {79 if (u.Host == constants.HomepagePath || u.Opaque == constants.HomepagePath) && strings.EqualFold(rel, "favicon.ico") {
78 log.Printf("[scheme] asset: rel=%s (host=%s path=%s) → mapping to favicon.svg", rel, u.Host, u.Path)80 log.Printf("[scheme] asset: rel=%s (host=%s path=%s) → mapping to favicon.svg", rel, u.Host, u.Path)
@@ -83,6 +85,8 @@ func (s *SchemeHandler) handleAsset(u *neturl.URL) (string, []byte, bool) {
83 }85 }
84 86
85 log.Printf("[scheme] asset: rel=%s (host=%s path=%s)", rel, u.Host, u.Path)87 log.Printf("[scheme] asset: rel=%s (host=%s path=%s)", rel, u.Host, u.Path)
88+
89+ // Try to read the requested asset
86 data, rerr := s.assets.ReadFile(filepath.ToSlash(filepath.Join("assets", "gui", rel)))90 data, rerr := s.assets.ReadFile(filepath.ToSlash(filepath.Join("assets", "gui", rel)))
87 if rerr != nil {91 if rerr != nil {
88 log.Printf("[scheme] not found: %s", rel)92 log.Printf("[scheme] not found: %s", rel)
@@ -90,21 +94,62 @@ func (s *SchemeHandler) handleAsset(u *neturl.URL) (string, []byte, bool) {
90 }94 }
91 95
92 // Determine mime type96 // Determine mime type
93- mt := mime.TypeByExtension(strings.ToLower(filepath.Ext(rel)))97+ mt := s.getMimeType(rel)
94- if mt == "" {98+
95- // Fallbacks99+ // Add cache control for development (prevents future caching issues)
96- switch strings.ToLower(filepath.Ext(rel)) {100+ // Note: This doesn't affect the current cache, but prevents new caching
97- case ".js":101+ log.Printf("[scheme] serving %s with mime-type: %s", rel, mt)
98- mt = "application/javascript"
99- case ".css":
100- mt = "text/css"
101- case ".svg":
102- mt = "image/svg+xml"
103- case ".ico":
104- mt = "image/x-icon"
105- default:
106- mt = "text/html; charset=utf-8"
107- }
108- }
109 return mt, data, true102 return mt, data, true
110 }103 }
104+
105+// getMimeType determines the MIME type for a given file path
106+func (s *SchemeHandler) getMimeType(filename string) string {
107+ ext := strings.ToLower(filepath.Ext(filename))
108+
109+ // Try standard mime type first
110+ mt := mime.TypeByExtension(ext)
111+ if mt != "" {
112+ return mt
113+ }
114+
115+ // Fallbacks for common web assets
116+ switch ext {
117+ case ".js":
118+ return "application/javascript"
119+ case ".mjs":
120+ return "application/javascript"
121+ case ".css":
122+ return "text/css"
123+ case ".svg":
124+ return "image/svg+xml"
125+ case ".ico":
126+ return "image/x-icon"
127+ case ".woff":
128+ return "font/woff"
129+ case ".woff2":
130+ return "font/woff2"
131+ case ".ttf":
132+ return "font/ttf"
133+ case ".otf":
134+ return "font/otf"
135+ case ".eot":
136+ return "application/vnd.ms-fontobject"
137+ case ".png":
138+ return "image/png"
139+ case ".jpg", ".jpeg":
140+ return "image/jpeg"
141+ case ".gif":
142+ return "image/gif"
143+ case ".webp":
144+ return "image/webp"
145+ case ".json":
146+ return "application/json"
147+ case ".xml":
148+ return "application/xml"
149+ case ".html", ".htm":
150+ return "text/html; charset=utf-8"
151+ default:
152+ // Default to text/plain for unknown extensions
153+ return "text/plain"
154+ }
155+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close