From 94c8ecb64845dc2ea3b02bba01ac9293063d18fc Mon Sep 17 00:00:00 2001 From: Mika Hiltunen Date: Sat, 16 Mar 2024 11:08:06 +0200 Subject: [PATCH] Add theme support --- components/bookmark_add_form.html | 42 +++++++++ components/bookmark_delete_form.html | 12 +++ components/bookmark_edit_form.html | 42 +++++++++ components/headers.html | 15 +++ components/login_form.html | 12 +++ .../app.js => components/scripts/admin.js | 2 - .../assets => components/scripts}/api.js | 0 config/config.go | 2 +- docs/THEMES.md | 93 +++++++++++++++++++ handlers/admin_handlers.go | 30 ++++++ handlers/utils.go | 21 ++++- main.go | 3 +- templates/default/admin_bookmark_add.html | 42 +-------- templates/default/admin_bookmark_delete.html | 14 +-- templates/default/admin_bookmark_edit.html | 43 +-------- templates/default/base.html | 12 +-- templates/default/index.html | 4 +- templates/default/login.html | 12 +-- 18 files changed, 278 insertions(+), 123 deletions(-) create mode 100644 components/bookmark_add_form.html create mode 100644 components/bookmark_delete_form.html create mode 100644 components/bookmark_edit_form.html create mode 100644 components/headers.html create mode 100644 components/login_form.html rename templates/default/assets/app.js => components/scripts/admin.js (99%) rename {templates/default/assets => components/scripts}/api.js (100%) create mode 100644 docs/THEMES.md diff --git a/components/bookmark_add_form.html b/components/bookmark_add_form.html new file mode 100644 index 0000000..baf1268 --- /dev/null +++ b/components/bookmark_add_form.html @@ -0,0 +1,42 @@ +{{ define "bookmark_add_form" }} +
+
+ +
+ + +
+ + {{ if index .Errors "url" }} + {{ index .Errors "url" }} + {{ end }} +
+ +
+ + + {{ if index .Errors "title" }} + {{ index .Errors "title" }} + {{ end }} +
+ +
+ + +
+ +
+ + +
+ +
+ + +
    +
    + + +
    +{{ end }} \ No newline at end of file diff --git a/components/bookmark_delete_form.html b/components/bookmark_delete_form.html new file mode 100644 index 0000000..98ca3b7 --- /dev/null +++ b/components/bookmark_delete_form.html @@ -0,0 +1,12 @@ +{{ define "bookmark_delete_form" }} +
    +

    Are you sure you want to delete the following bookmark?

    + +
    +

    {{ .Bookmark.Title }}

    + {{ if .Bookmark.Description }}

    {{ .Bookmark.Description }}

    {{ end }} +
    + + +
    +{{ end }} \ No newline at end of file diff --git a/components/bookmark_edit_form.html b/components/bookmark_edit_form.html new file mode 100644 index 0000000..9f5b4a6 --- /dev/null +++ b/components/bookmark_edit_form.html @@ -0,0 +1,42 @@ +{{ define "bookmark_edit_form" }} +
    +
    + + + {{ if index .Errors "url" }} + {{ index .Errors "url" }} + {{ end }} +
    + +
    + + + {{ if index .Errors "title" }} + {{ index .Errors "title" }} + {{ end }} +
    + +
    + + +
    + +
    +
    + +
    + +
    +
    + +
    + +
    + +
    +
      +
      + + +
      +{{ end }} \ No newline at end of file diff --git a/components/headers.html b/components/headers.html new file mode 100644 index 0000000..ba5a435 --- /dev/null +++ b/components/headers.html @@ -0,0 +1,15 @@ +{{ define "headers" }} + + + + + + + + + + + + + +{{ end }} \ No newline at end of file diff --git a/components/login_form.html b/components/login_form.html new file mode 100644 index 0000000..67b748b --- /dev/null +++ b/components/login_form.html @@ -0,0 +1,12 @@ +{{ define "login_form" }} +
      +
      + +
      + + +
      + {{ if .Error }}

      {{ .Error }}

      {{ end }} +
      +
      +{{ end }} \ No newline at end of file diff --git a/templates/default/assets/app.js b/components/scripts/admin.js similarity index 99% rename from templates/default/assets/app.js rename to components/scripts/admin.js index 5b4e943..e95d783 100644 --- a/templates/default/assets/app.js +++ b/components/scripts/admin.js @@ -15,9 +15,7 @@ let tagSuggestions; window.onload = () => { baseUrl = document.querySelector("base").attributes.getNamedItem("href").value; - scrape = document.getElementById("scrape"); - url = document.getElementById("url"); title = document.getElementById("title"); description = document.getElementById("description"); diff --git a/templates/default/assets/api.js b/components/scripts/api.js similarity index 100% rename from templates/default/assets/api.js rename to components/scripts/api.js diff --git a/config/config.go b/config/config.go index 8aec4ab..f0c35ab 100644 --- a/config/config.go +++ b/config/config.go @@ -19,7 +19,7 @@ type AppConfig struct { Secret string `yaml:"secret"` Port int `yaml:"port"` PageSize int `yaml:"page_size"` - Template string `yaml:"template"` + Theme string `yaml:"theme"` CheckInterval int `yaml:"check_interval,omitempty"` CheckRunOnStartup bool `yaml:"check_on_app_start,omitempty"` GotifyEnabled bool `yaml:"gotify_enabled,omitempty"` diff --git a/docs/THEMES.md b/docs/THEMES.md new file mode 100644 index 0000000..a53c1ac --- /dev/null +++ b/docs/THEMES.md @@ -0,0 +1,93 @@ +# Themes +You can customize the interface by creating a new theme. Themes are located in the `templates` directory. Each theme must have its own subdirectory. By default, the app uses the [default](/templates/default/) theme. The theme can be changed in the `config.yml` file by changing the `theme` configuration value. + +If you are creating a new theme, it is probably easiest to use the `default` template as a base. Any static assets required by the theme must be in the `assets` subdirectory. + + +## Available template variables + +| Variable | Type | Description +|------------------ | ------------- | ------------------------------------------------------- +| .SiteName | string | Site name, defined in the config +| .Description | string | Site description, defined in the config +| .BaseURL | string | Site base URL, defined in the config +| .Title | string | Title of the current page +| .CurrentURL | string | URL requested to access the current view +| .IsAuthenticated | bool | Boolean indicating if user is authenticated +| .Bookmarks | [[]Bookmark](#bookmark) | List of bookmarks visible in the view +| .Tags | []string | List of all tags +| .TextFilter | string | Current search term +| .Pages | []Page | List of available pages for paginated content +| .BrokenBookmarks | []Bookmark | List of broken bookmarks (only available for authenticated users) + +## Types + +### Bookmark +| Field | Type | Description +|------------------ | ------------- | ------------------------------------------------------- +| .ID | int64 | Bookmark ID +| .URL | string | Bookmark URL +| .Title | string | Bookmark title +| .Description | string | Bookmark description +| .IsPrivate | bool | Is bookmark private +| .Created | time.Time | Bookmark creation datetime +| .Tags | []string | Bookmark tags +| .IsWorking | bool | Is bookmark working + +## Components +Components render elements, that are necessary for the app to work. You should use these instead of building your own. Otherwise some features may not work. + + +| Components | Usage +|------------| -------- +| bookmark_add_form | Form for adding new bookmarks +| bookmark_edit_form | Form for editing bookmarks +| bookmark_delete_form | Form for deleting bookmarks +| login_form | Form for logging in +| headers | Required headers used in `` section + +How to use: +``` +{{ template "feed-links" . }} +``` + +## Functions + +### feedUrl (feedType string) +Returns a feed URL. Supported `feedType` values: +- rss +- atom +- json + +How to use: +```html + +``` + +### anchorUrl (id string) +Returns an URL pointing to a specific element on the page + +How to use: +```html +Go to Search +... + +``` + +### paginationUrl (pageNumber int) +return an URL pointing to a specific page of the paginated content + +How to use: +```html + +``` \ No newline at end of file diff --git a/handlers/admin_handlers.go b/handlers/admin_handlers.go index 9644c85..b6489bb 100644 --- a/handlers/admin_handlers.go +++ b/handlers/admin_handlers.go @@ -33,6 +33,12 @@ func (h *Handler) HandlePrivateBookmarks(w http.ResponseWriter, r *http.Request) return } + brokenBookmarks, err := h.bookmarkRepo.GetBrokenBookmarks() + if err != nil { + h.internalServerError(w, "Failed to fetch broken bookmarks", err) + return + } + data := templateData{ SiteName: h.appConf.SiteName, Description: h.appConf.Description, @@ -44,6 +50,7 @@ func (h *Handler) HandlePrivateBookmarks(w http.ResponseWriter, r *http.Request) Bookmarks: bookmarkResult.Bookmarks, Tags: allTags, Pages: h.getPages(page, bookmarkResult.PageCount), + BrokenBookmarks: brokenBookmarks, } h.parseTemplateWithFunc("index.html", r, w, data) @@ -91,6 +98,12 @@ func (h *Handler) HandleBookmarkAdd(w http.ResponseWriter, r *http.Request) { return } + brokenBookmarks, err := h.bookmarkRepo.GetBrokenBookmarks() + if err != nil { + h.internalServerError(w, "Failed to fetch broken bookmarks", err) + return + } + data := adminTemplateData{ templateData: templateData{ SiteName: h.appConf.SiteName, @@ -99,6 +112,7 @@ func (h *Handler) HandleBookmarkAdd(w http.ResponseWriter, r *http.Request) { BaseURL: h.appConf.BaseURL, CurrentURL: h.getCurrentURL(r, h.appConf), IsAuthenticated: isAuthenticated, + BrokenBookmarks: brokenBookmarks, }, Errors: make(map[string]string), Bookmark: &bookmarks.Bookmark{}, @@ -170,6 +184,12 @@ func (h *Handler) HandleBookmarkEdit(w http.ResponseWriter, r *http.Request) { return } + brokenBookmarks, err := h.bookmarkRepo.GetBrokenBookmarks() + if err != nil { + h.internalServerError(w, "Failed to fetch broken bookmarks", err) + return + } + data := adminTemplateData{ templateData: templateData{ SiteName: h.appConf.SiteName, @@ -178,6 +198,7 @@ func (h *Handler) HandleBookmarkEdit(w http.ResponseWriter, r *http.Request) { BaseURL: h.appConf.BaseURL, CurrentURL: h.getCurrentURL(r, h.appConf), IsAuthenticated: isAuthenticated, + BrokenBookmarks: brokenBookmarks, }, Errors: make(map[string]string), Bookmark: bookmark, @@ -250,6 +271,14 @@ func (h *Handler) HandleBookmarkDelete(w http.ResponseWriter, r *http.Request) { return } + // TODO: There is no need to fetch broken bookmarks every time. + // Just fetch a boolean indicating if broken bookmarks exists + brokenBookmarks, err := h.bookmarkRepo.GetBrokenBookmarks() + if err != nil { + h.internalServerError(w, "Failed to fetch broken bookmarks", err) + return + } + data := adminTemplateData{ templateData: templateData{ SiteName: h.appConf.SiteName, @@ -258,6 +287,7 @@ func (h *Handler) HandleBookmarkDelete(w http.ResponseWriter, r *http.Request) { BaseURL: h.appConf.BaseURL, CurrentURL: h.getCurrentURL(r, h.appConf), IsAuthenticated: isAuthenticated, + BrokenBookmarks: brokenBookmarks, }, Bookmark: bookmark, } diff --git a/handlers/utils.go b/handlers/utils.go index b701325..6545fae 100644 --- a/handlers/utils.go +++ b/handlers/utils.go @@ -6,6 +6,7 @@ import ( "log" "net/http" "net/url" + "os" "strconv" "strings" @@ -27,6 +28,22 @@ func (h *Handler) getBookmarksWithPagination(isAuthenticated bool, q, tags strin } func (h *Handler) parseTemplateWithFunc(templateFile string, r *http.Request, w http.ResponseWriter, data any) { + templateFiles := []string{ + h.getTemplateFile("base.html"), + h.getTemplateFile(templateFile), + } + + entries, err := os.ReadDir("components") + if err != nil { + h.internalServerError(w, "Reading UI components failed", err) + return + } + for _, entry := range entries { + if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".html") { + templateFiles = append(templateFiles, fmt.Sprintf("components/%s", entry.Name())) + } + } + t, err := template.New(""). Funcs(template.FuncMap{ "paginationUrl": func(pageNumber int) string { @@ -48,7 +65,7 @@ func (h *Handler) parseTemplateWithFunc(templateFile string, r *http.Request, w "anchorUrl": func(id string) string { return h.getAnchorURL(r, id) }, - }).ParseFiles(h.getTemplateFile("base.html"), h.getTemplateFile(templateFile)) + }).ParseFiles(templateFiles...) if err != nil { h.internalServerError(w, fmt.Sprintf("Failed to parse template %s", templateFile), err) return @@ -62,7 +79,7 @@ func (h *Handler) parseTemplateWithFunc(templateFile string, r *http.Request, w } func (h *Handler) getTemplateFile(filename string) string { - return fmt.Sprintf("templates/%s/%s", h.appConf.Template, filename) + return fmt.Sprintf("templates/%s/%s", h.appConf.Theme, filename) } func (h *Handler) isAuthenticated(r *http.Request) bool { diff --git a/main.go b/main.go index e42507b..717360f 100644 --- a/main.go +++ b/main.go @@ -68,7 +68,8 @@ func main() { r.Get("/api/metadata", handler.HandleAPIMetadata) r.Get("/api/tags", handler.HandleAPITags) - handler.ServeFiles(r, "/assets", http.Dir(fmt.Sprintf("templates/%s/assets", appConf.Template))) + handler.ServeFiles(r, "/assets", http.Dir(fmt.Sprintf("templates/%s/assets", appConf.Theme))) + handler.ServeFiles(r, "/scripts", http.Dir("components/scripts")) log.Printf("Server address: http://localhost:%d", appConf.Port) err = http.ListenAndServe(fmt.Sprintf(":%d", appConf.Port), r) diff --git a/templates/default/admin_bookmark_add.html b/templates/default/admin_bookmark_add.html index d931b10..badb10b 100644 --- a/templates/default/admin_bookmark_add.html +++ b/templates/default/admin_bookmark_add.html @@ -4,47 +4,7 @@

      Add a new bookmark

      -
      -
      - -
      - - -
      - - {{ if index .Errors "url" }} - {{ index .Errors "url" }} - {{ end }} -
      - -
      - - - {{ if index .Errors "title" }} - {{ index .Errors "title" }} - {{ end }} -
      - -
      - - -
      - -
      - - -
      - -
      - - -
        -
        - - -
        + {{ template "bookmark_add_form" . }}
        - {{ end }} \ No newline at end of file diff --git a/templates/default/admin_bookmark_delete.html b/templates/default/admin_bookmark_delete.html index 30402b8..a80e844 100644 --- a/templates/default/admin_bookmark_delete.html +++ b/templates/default/admin_bookmark_delete.html @@ -2,19 +2,9 @@ {{ define "content" }}
        -

        Delete bookmark

        +

        Delete a bookmark

        -
        -

        Are you sure you want to delete the following bookmark?

        - -
        -

        {{ .Bookmark.Title }}

        - {{ if .Bookmark.Description }}

        {{ .Bookmark.Description }}

        {{ end }} -
        - - -
        - + {{ template "bookmark_delete_form" . }}
        {{ end }} diff --git a/templates/default/admin_bookmark_edit.html b/templates/default/admin_bookmark_edit.html index 0222afc..24777ba 100644 --- a/templates/default/admin_bookmark_edit.html +++ b/templates/default/admin_bookmark_edit.html @@ -2,48 +2,9 @@ {{ define "content" }}
        -

        Edit bookmark

        +

        Edit a bookmark

        -
        -
        - - - {{ if index .Errors "url" }} - {{ index .Errors "url" }} - {{ end }} -
        - -
        - - - {{ if index .Errors "title" }} - {{ index .Errors "title" }} - {{ end }} -
        - -
        - - -
        - -
        -
        - -
        - -
        -
        - -
        - -
        - -
        -
          -
          - - -
          + {{ template "bookmark_edit_form" . }}
          {{ end }} \ No newline at end of file diff --git a/templates/default/base.html b/templates/default/base.html index de10782..6067dc1 100644 --- a/templates/default/base.html +++ b/templates/default/base.html @@ -6,19 +6,11 @@ - - - - {{ .Title }} | {{ .SiteName }} - - - - - - + + {{ template "headers" . }}