refactor
This commit is contained in:
@@ -15,21 +15,6 @@ func IdentifyPopover(docID string, m *models.DocumentMetadata) g.Node {
|
||||
return nil
|
||||
}
|
||||
|
||||
if m.Error != nil {
|
||||
return ui.Popover(h.Div(
|
||||
h.Class("flex flex-col gap-2"),
|
||||
h.H3(
|
||||
h.Class("text-lg font-bold text-center"),
|
||||
g.Text("Error"),
|
||||
),
|
||||
h.Div(
|
||||
h.Class("bg-gray-100 dark:bg-gray-900 p-2"),
|
||||
h.P(g.Text(*m.Error)),
|
||||
),
|
||||
ui.LinkButton(g.Text("Back to Document"), fmt.Sprintf("/documents/%s", docID)),
|
||||
))
|
||||
}
|
||||
|
||||
return ui.Popover(h.Div(
|
||||
h.Class("flex flex-col gap-2"),
|
||||
h.H3(
|
||||
@@ -1,57 +0,0 @@
|
||||
package layout
|
||||
|
||||
import (
|
||||
g "maragu.dev/gomponents"
|
||||
h "maragu.dev/gomponents/html"
|
||||
"reichard.io/antholume/web/pages"
|
||||
)
|
||||
|
||||
type LayoutOptions struct {
|
||||
SearchEnabled bool
|
||||
IsAdmin bool
|
||||
Username string
|
||||
Version string
|
||||
}
|
||||
|
||||
func Layout(p pages.Page, opts LayoutOptions) g.Node {
|
||||
return h.Doctype(
|
||||
h.HTML(
|
||||
g.Attr("lang", "en"),
|
||||
Head(p.Route().Title()),
|
||||
h.Body(
|
||||
g.Attr("class", "bg-gray-100 dark:bg-gray-800 text-black dark:text-white"),
|
||||
Navigation(p.Route(), &opts),
|
||||
Base(p.Render()),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func Head(routeTitle string) g.Node {
|
||||
return h.Head(
|
||||
h.Title("AnthoLume - "+routeTitle),
|
||||
h.Meta(g.Attr("charset", "utf-8")),
|
||||
h.Meta(g.Attr("name", "viewport"), g.Attr("content", "width=device-width, initial-scale=0.9, user-scalable=no, viewport-fit=cover")),
|
||||
h.Meta(g.Attr("name", "apple-mobile-web-app-capable"), g.Attr("content", "yes")),
|
||||
h.Meta(g.Attr("name", "apple-mobile-web-app-status-bar-style"), g.Attr("content", "black-translucent")),
|
||||
h.Meta(g.Attr("name", "theme-color"), g.Attr("content", "#F3F4F6"), g.Attr("media", "(prefers-color-scheme: light)")),
|
||||
h.Meta(g.Attr("name", "theme-color"), g.Attr("content", "#1F2937"), g.Attr("media", "(prefers-color-scheme: dark)")),
|
||||
h.Link(g.Attr("rel", "manifest"), g.Attr("href", "/manifest.json")),
|
||||
h.Link(g.Attr("rel", "stylesheet"), g.Attr("href", "/assets/index.css")),
|
||||
h.Link(g.Attr("rel", "stylesheet"), g.Attr("href", "/assets/tailwind.css")),
|
||||
h.Script(g.Attr("src", "/assets/lib/idb-keyval.min.js")),
|
||||
h.Script(g.Attr("src", "/assets/common.js")),
|
||||
h.Script(g.Attr("src", "/assets/index.js")),
|
||||
)
|
||||
}
|
||||
|
||||
func Base(body g.Node) g.Node {
|
||||
return h.Main(
|
||||
g.Attr("class", "relative overflow-hidden"),
|
||||
h.Div(
|
||||
g.Attr("id", "container"),
|
||||
g.Attr("class", "h-[100dvh] px-4 overflow-auto md:px-6 lg:ml-48"),
|
||||
body,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
package layout
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
g "maragu.dev/gomponents"
|
||||
h "maragu.dev/gomponents/html"
|
||||
"reichard.io/antholume/web/assets"
|
||||
"reichard.io/antholume/web/pages"
|
||||
)
|
||||
|
||||
const (
|
||||
active = "border-purple-500 dark:text-white"
|
||||
inactive = "border-transparent text-gray-400 hover:text-gray-800 dark:hover:text-gray-100"
|
||||
)
|
||||
|
||||
func Navigation(currentRoute pages.PageRoute, opts *LayoutOptions) g.Node {
|
||||
return h.Div(
|
||||
g.Attr("class", "flex items-center justify-between w-full h-16"),
|
||||
Sidebar(currentRoute, opts),
|
||||
h.H1(g.Attr("class", "text-xl font-bold px-6 lg:ml-44"), g.Text(currentRoute.Title())),
|
||||
Dropdown(opts.Username),
|
||||
)
|
||||
}
|
||||
|
||||
func Sidebar(currentRoute pages.PageRoute, opts *LayoutOptions) g.Node {
|
||||
links := []g.Node{
|
||||
navLink(currentRoute, pages.HomePage, "/", "home"),
|
||||
navLink(currentRoute, pages.DocumentsPage, "/documents", "documents"),
|
||||
navLink(currentRoute, pages.ProgressPage, "/progress", "activity"),
|
||||
navLink(currentRoute, pages.ActivityPage, "/activity", "activity"),
|
||||
}
|
||||
if opts.SearchEnabled {
|
||||
links = append(links, navLink(currentRoute, pages.SearchPage, "/search", "search"))
|
||||
}
|
||||
if opts.IsAdmin {
|
||||
links = append(links, adminLinks(currentRoute))
|
||||
}
|
||||
|
||||
return h.Div(
|
||||
g.Attr("id", "mobile-nav-button"),
|
||||
g.Attr("class", "flex flex-col z-40 relative ml-6"),
|
||||
hamburgerIcon(),
|
||||
h.Div(
|
||||
g.Attr("id", "menu"),
|
||||
g.Attr("class", "fixed -ml-6 h-full w-56 lg:w-48 bg-white dark:bg-gray-700 shadow-lg"),
|
||||
h.Div(
|
||||
g.Attr("class", "h-16 flex justify-end lg:justify-around"),
|
||||
h.P(g.Attr("class", "text-xl font-bold text-right my-auto pr-8 lg:pr-0"), g.Text("AnthoLume")),
|
||||
),
|
||||
h.Div(links...),
|
||||
h.A(
|
||||
g.Attr("href", "https://gitea.va.reichard.io/evan/AnthoLume"),
|
||||
g.Attr("target", "_blank"),
|
||||
g.Attr("class", "flex flex-col gap-2 justify-center items-center p-6 w-full absolute bottom-0 text-black dark:text-white"),
|
||||
assets.Icon("gitea", 20),
|
||||
h.Span(g.Attr("class", "text-xs"), g.Text(opts.Version)),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func navLink(currentRoute, linkRoute pages.PageRoute, path, icon string) g.Node {
|
||||
class := inactive
|
||||
if currentRoute == linkRoute {
|
||||
class = active
|
||||
}
|
||||
return h.A(
|
||||
g.Attr("class", "flex items-center justify-start w-full p-2 pl-6 my-2 transition-colors duration-200 border-l-4 "+class),
|
||||
h.Href(path),
|
||||
assets.Icon(icon, 20),
|
||||
h.Span(g.Attr("class", "mx-4 text-sm font-normal"), g.Text(linkRoute.Title())),
|
||||
)
|
||||
}
|
||||
|
||||
func adminLinks(currentRoute pages.PageRoute) g.Node {
|
||||
routeID := string(currentRoute)
|
||||
|
||||
class := inactive
|
||||
if strings.HasPrefix(routeID, "admin") {
|
||||
class = active
|
||||
}
|
||||
|
||||
children := g.If(strings.HasPrefix(routeID, "admin"),
|
||||
g.Group([]g.Node{
|
||||
subNavLink(currentRoute, pages.AdminGeneralPage, "/admin"),
|
||||
subNavLink(currentRoute, pages.AdminImportPage, "/admin/import"),
|
||||
subNavLink(currentRoute, pages.AdminUsersPage, "/admin/users"),
|
||||
subNavLink(currentRoute, pages.AdminLogsPage, "/admin/logs"),
|
||||
}),
|
||||
)
|
||||
|
||||
return h.Div(
|
||||
g.Attr("class", "flex flex-col gap-4 p-2 pl-6 my-2 transition-colors duration-200 border-l-4 "+class),
|
||||
h.A(
|
||||
g.Attr("href", "/admin"),
|
||||
g.Attr("class", "flex justify-start w-full"),
|
||||
assets.Icon("settings", 20),
|
||||
h.Span(g.Attr("class", "mx-4 text-sm font-normal"), g.Text("Admin")),
|
||||
),
|
||||
children,
|
||||
)
|
||||
}
|
||||
|
||||
func subNavLink(currentRoute, linkRoute pages.PageRoute, path string) g.Node {
|
||||
class := inactive
|
||||
if currentRoute == linkRoute {
|
||||
class = active
|
||||
}
|
||||
|
||||
pageTitle := linkRoute.Title()
|
||||
if splitString := strings.Split(pageTitle, " - "); len(splitString) > 1 {
|
||||
pageTitle = splitString[1]
|
||||
}
|
||||
|
||||
return h.A(
|
||||
g.Attr("class", class),
|
||||
g.Attr("href", path),
|
||||
g.Attr("style", "padding-left:1.75em"),
|
||||
h.Span(g.Attr("class", "mx-4 text-sm font-normal"), g.Text(pageTitle)),
|
||||
)
|
||||
}
|
||||
|
||||
func hamburgerIcon() g.Node {
|
||||
return g.Group([]g.Node{
|
||||
h.Input(g.Attr("type", "checkbox"), g.Attr("class", "absolute lg:hidden z-50 -top-2 w-7 h-7 opacity-0 cursor-pointer")),
|
||||
h.Span(g.Attr("class", "lg:hidden bg-black dark:bg-white w-7 h-0.5 z-40 mt-0.5")),
|
||||
h.Span(g.Attr("class", "lg:hidden bg-black dark:bg-white w-7 h-0.5 z-40 mt-1")),
|
||||
h.Span(g.Attr("class", "lg:hidden bg-black dark:bg-white w-7 h-0.5 z-40 mt-1")),
|
||||
})
|
||||
}
|
||||
|
||||
func Dropdown(username string) g.Node {
|
||||
return h.Div(
|
||||
g.Attr("class", "relative flex items-center justify-end w-full p-4"),
|
||||
h.Input(g.Attr("type", "checkbox"), g.Attr("id", "user-dropdown-button"), g.Attr("class", "hidden")),
|
||||
h.Div(
|
||||
g.Attr("id", "user-dropdown"),
|
||||
g.Attr("class", "transition duration-200 z-20 absolute right-4 top-16 pt-4"),
|
||||
h.Div(
|
||||
g.Attr("class", "w-40 origin-top-right bg-white rounded-md shadow-lg dark:shadow-gray-800 dark:bg-gray-700 ring-1 ring-black ring-opacity-5"),
|
||||
h.Div(
|
||||
g.Attr("class", "py-1"),
|
||||
dropdownItem("/settings", "Settings"),
|
||||
dropdownItem("/local", "Offline"),
|
||||
dropdownItem("/logout", "Logout"),
|
||||
),
|
||||
),
|
||||
),
|
||||
h.Label(
|
||||
g.Attr("for", "user-dropdown-button"),
|
||||
h.Div(
|
||||
g.Attr("class", "flex items-center gap-2 text-md py-4 cursor-pointer"),
|
||||
assets.Icon("user", 20),
|
||||
h.Span(g.Text(username)),
|
||||
assets.Icon("dropdown", 20),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func dropdownItem(href, text string) g.Node {
|
||||
return h.A(
|
||||
g.Attr("href", href),
|
||||
g.Attr("class", "block px-4 py-2 text-md text-gray-700 hover:bg-gray-100 hover:text-gray-900 dark:text-gray-100 dark:hover:text-white dark:hover:bg-gray-600"),
|
||||
g.Text(text),
|
||||
)
|
||||
}
|
||||
25
web/components/ui/notification.go
Normal file
25
web/components/ui/notification.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
g "maragu.dev/gomponents"
|
||||
h "maragu.dev/gomponents/html"
|
||||
"reichard.io/antholume/pkg/sliceutils"
|
||||
"reichard.io/antholume/web/models"
|
||||
)
|
||||
|
||||
func Notifications(notifications []*models.Notification) g.Node {
|
||||
if len(notifications) == 0 {
|
||||
return nil
|
||||
}
|
||||
return h.Div(
|
||||
h.Class("fixed flex flex-col gap-2 bottom-0 right-0 p-2 sm:p-4 text-white dark:text-black"),
|
||||
g.Group(sliceutils.Map(notifications, notificationNode)),
|
||||
)
|
||||
}
|
||||
|
||||
func notificationNode(n *models.Notification) g.Node {
|
||||
return h.Div(
|
||||
h.Class("bg-gray-600 dark:bg-gray-400 px-4 py-2 rounded-lg shadow-lg w-64 animate-notification"),
|
||||
h.P(g.Text(n.Content)),
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user