From d4c18e63bb2e8685862b44fad5712ff77d910038 Mon Sep 17 00:00:00 2001 From: Konrad Neitzel Date: Mon, 28 Sep 2026 20:12:02 +0200 Subject: [PATCH] Load the Nextcloud theme in App navigation and name its core icons. The shell paints the instance wallpaper by default, and ExApps pick menu icons from NextcloudIcons instead of raw paths. Co-authored-by: Cursor --- README.md | 2 +- icons.go | 93 ++++++++++++++++++++++++++++ navigation.go | 150 ++++++++++++++++++++++++++++++++++++++++----- navigation_test.go | 47 ++++++++++++++ 4 files changed, 276 insertions(+), 16 deletions(-) create mode 100644 icons.go diff --git a/README.md b/README.md index 262858f..284ce3d 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Import: `gitea.neitzel.de/konrad/go-nc-exapp` (package `gonexapp`). - **Groups** — Users and Groups reads: `UserGroups`, `GroupMembers`, `ListGroups` (no search/paging). Directory calls (`GroupMembers` / `ListGroups`) run as the Credentials user and need an admin or subadmin - **Access Gate** — optional Required Groups enforcement (`Wrap` + `Check`), English denied HTML for browsers (200 + `frame-ancestors 'self'`), positive membership cache; default skip for lifecycle paths and **`/js/`** top-menu scripts; env helpers for `REQUIRED_GROUPS` / `REQUIRED_GROUPS_CACHE_SECONDS` - **Top Menu visibility** — `TopMenuAdminRequired` helper for deploy env `TOP_MENU_ADMIN_REQUIRED` (`0` / `1` for AppAPI top-menu OCS) -- **App navigation** — optional Files-style shell (`AppNavigation.Handler`). The ExApp supplies the tree, the page for each item, and an optional header. The selected item is the `item` query parameter (override with `SelectKey`). Other query parameters, including a Visit folder, are left in place. Not mounting the handler keeps the ExApp’s own page. Lifecycle routes, HaRP startup, and Top Menu registration stay in the ExApp. +- **App navigation** — optional Files-style shell (`AppNavigation.Handler`). The ExApp supplies the tree, the page for each item, and an optional header. The selected item is the `item` query parameter (override with `SelectKey`). Other query parameters, including a Visit folder, are left in place. By default the shell loads the Nextcloud theme stylesheets, paints `--image-background`, and copies the surrounding page’s `data-theme-*` markers; set `DisableTheme` to skip the stylesheets. An item may set `Icon` to a same-origin image URL. `NextcloudIcons` names the core SVGs Nextcloud already serves (`NextcloudIcons.Folder`, `NextcloudIcons.Password`, and the rest). Not mounting the handler keeps the ExApp’s own page. Lifecycle routes, HaRP startup, and Top Menu registration stay in the ExApp. **Excluded** diff --git a/icons.go b/icons.go new file mode 100644 index 0000000..f9eecd4 --- /dev/null +++ b/icons.go @@ -0,0 +1,93 @@ +package gonexapp + +// NextcloudIcons are SVG paths served by Nextcloud core under /core/img/. +// The library does not ship the files. Set Item.Icon to one of these. +// Dark mode inverts them with Nextcloud’s --background-invert-if-dark filter. +var NextcloudIcons = nextcloudIcons{ + Folder: "/core/img/filetypes/folder.svg", + FolderShared: "/core/img/filetypes/folder-shared.svg", + FolderPublic: "/core/img/filetypes/folder-public.svg", + FolderStarred: "/core/img/filetypes/folder-starred.svg", + FolderEncrypted: "/core/img/filetypes/folder-encrypted.svg", + File: "/core/img/filetypes/file.svg", + Text: "/core/img/filetypes/text.svg", + Image: "/core/img/filetypes/image.svg", + Audio: "/core/img/filetypes/audio.svg", + Video: "/core/img/filetypes/video.svg", + PDF: "/core/img/filetypes/application-pdf.svg", + Document: "/core/img/filetypes/x-office-document.svg", + Spreadsheet: "/core/img/filetypes/x-office-spreadsheet.svg", + Presentation: "/core/img/filetypes/x-office-presentation.svg", + Files: "/core/img/places/files.svg", + Home: "/core/img/places/home.svg", + Calendar: "/core/img/places/calendar.svg", + Contacts: "/core/img/places/contacts.svg", + Add: "/core/img/actions/add.svg", + Delete: "/core/img/actions/delete.svg", + Edit: "/core/img/actions/edit.svg", + Rename: "/core/img/actions/rename.svg", + Download: "/core/img/actions/download.svg", + Upload: "/core/img/actions/upload.svg", + Share: "/core/img/actions/share.svg", + Search: "/core/img/actions/search.svg", + Settings: "/core/img/actions/settings.svg", + Info: "/core/img/actions/info.svg", + History: "/core/img/actions/history.svg", + Password: "/core/img/actions/password.svg", + Confirm: "/core/img/actions/confirm.svg", + Close: "/core/img/actions/close.svg", + Star: "/core/img/actions/star.svg", + User: "/core/img/actions/user.svg", + Group: "/core/img/actions/group.svg", + Mail: "/core/img/actions/mail.svg", + Menu: "/core/img/actions/menu.svg", + External: "/core/img/actions/external.svg", + Filter: "/core/img/actions/filter.svg", + Recent: "/core/img/actions/recent.svg", + Tag: "/core/img/actions/tag.svg", +} + +// nextcloudIcons is the named set of core SVGs. Fields are paths, not image bytes. +type nextcloudIcons struct { + Folder string + FolderShared string + FolderPublic string + FolderStarred string + FolderEncrypted string + File string + Text string + Image string + Audio string + Video string + PDF string + Document string + Spreadsheet string + Presentation string + Files string + Home string + Calendar string + Contacts string + Add string + Delete string + Edit string + Rename string + Download string + Upload string + Share string + Search string + Settings string + Info string + History string + Password string + Confirm string + Close string + Star string + User string + Group string + Mail string + Menu string + External string + Filter string + Recent string + Tag string +} diff --git a/navigation.go b/navigation.go index f37c0f6..236fd1a 100644 --- a/navigation.go +++ b/navigation.go @@ -7,13 +7,15 @@ import ( // shellCSP lets the ExApp iframe render this document. AppAPI blanks a frame // whose response omits frame-ancestors. -const shellCSP = "default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:" +const shellCSP = "default-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; font-src 'self' data:" // Item is one App navigation entry. The ExApp chooses the ids and labels. // Children may nest. The shell does not interpret the ids. +// Icon is an optional same-origin image URL. NextcloudIcons names the core SVGs. type Item struct { ID string Label string + Icon string Children []Item } @@ -31,6 +33,9 @@ type AppNavigation struct { SelectKey string // Title is the document title. Empty means "App navigation". Title string + // DisableTheme skips the Nextcloud theme stylesheets. The zero value + // loads the active theme. + DisableTheme bool } // Handler serves the shell. The selected item is the SelectKey query parameter. @@ -99,6 +104,7 @@ func (n AppNavigation) serve(w http.ResponseWriter, r *http.Request) { Page: template.HTML(page), Missing: msg, Title: title, + Theme: !n.DisableTheme, }) } @@ -128,11 +134,13 @@ type shellView struct { Page template.HTML Missing string Title string + Theme bool } type itemView struct { ID string Label string + Icon string Href string Current bool HasChildren bool @@ -146,6 +154,7 @@ func viewItems(r *http.Request, items []Item, selected string, href func(*http.R out = append(out, itemView{ ID: it.ID, Label: it.Label, + Icon: it.Icon, Href: href(r, it.ID), Current: it.ID == selected, HasChildren: len(children) > 0, @@ -160,18 +169,128 @@ var shellTmpl = template.Must(template.New("shell").Parse(` {{.Title}} +{{if .Theme}} + + + + +{{end}} @@ -179,8 +298,8 @@ var shellTmpl = template.Must(template.New("shell").Parse(`
-