diff --git a/.gitignore b/.gitignore index 8a779ad..c46cbe5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ homepage +.env diff --git a/bookmark.go b/bookmark.go index 649bbf0..cc2a4b9 100644 --- a/bookmark.go +++ b/bookmark.go @@ -5,8 +5,8 @@ import ( "strings" ) -var ( - Bookmarks = []Bookmark{ +func DefaultBookmarks() []Bookmark { + return []Bookmark{ { Title: "DuckDuckGo", Image: "https://duckduckgo.com/assets/logo_homepage.alt.v108.svg", @@ -14,130 +14,22 @@ var ( Color: "#e37151", }, { - Title: "Cloud", - Image: "https://cloud.tordarus.net/svg/core/logo/logo?color=ffffff&v=1", - Link: "https://cloud.tordarus.net/", - Color: "#007ec2", + Title: "Google Dark", + Image: "https://www.google.com/images/branding/googlelogo/2x/googlelogo_light_color_272x92dp.png", + Link: "https://google.com/", + Color: "#202124", }, { - Title: "ProtonMail", - Image: "https://protonmail.com/images/pm-logo-white.svg", - Link: "https://protonmail.com/", - Color: "#253163", - }, - { - Title: "Anilist", - Image: "https://anilist.co/img/icons/icon.svg", - Link: "https://anilist.co/", - Color: "#2b2d42", - IconPadding: "0.5em", - }, - { - Title: "Torrents", - Image: "/transmission.png", - Link: "https://torrents.tordarus.net/", - Color: "#e7402c", - }, - { - Title: "Nyaa.si", - Image: "https://progsoft.net/images/nyaa-v2-icon-a591b5ae622eb38f31c00297cf9b53fd88c058ef.png", - Link: "https://nyaa.si/", - Color: "#e7402c", - ImageSize: "111%", - IconPadding: "0em", - }, - { - Title: "Adblock", - Image: "/pihole.svg", - Link: "https://adblock.tordarus.net/", - Color: "#272c30", - }, - { - Title: "Postbank", - Image: "https://wintouch.de/wp-content/uploads/2014/06/Postbank-Icon.png", - Link: "https://meine.postbank.de/", - Color: "#ffcc00", - }, - { - Title: "DKB", - Image: "https://vdiv-sa.de/wp-content/uploads/2016/08/DKB_AG_web_RGB.jpg", - Link: "https://www.dkb.de/", + Title: "Google Light", + Image: "https://www.google.de/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png", + Link: "https://google.com/", Color: "#ffffff", }, - { - Title: "Finanzen", - Image: "https://docs.firefly-iii.org/img/logo.png", - Link: "https://finance.tordarus.net", - Color: "#ffffff", - ImageSize: "cover", - IconPadding: "0em", - }, - { - Title: "YouTube", - Image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/42/YouTube_light_icon_%282017%29.svg/1280px-YouTube_light_icon_%282017%29.svg.png", - Color: "#ff0000", - Link: "https://www.youtube.com/", - IconPadding: "1.5em", - }, - { - Title: "Netflix", - Image: "https://1000logos.net/wp-content/uploads/2017/05/emblem-Netflix.jpg", - Color: "#000000", - Link: "https://www.netflix.com/", - }, - { - Title: "Jisho", - Image: "https://assets.jisho.org/assets/jisho-logo-v4@2x-7330091c079b9dd59601401b052b52e103978221c8fb6f5e22406d871fcc746a.png", - Link: "https://jisho.org/", - Color: "#ffffff", - }, - { - Title: "Wadoku", - Image: "https://www.wadoku.de/img/wadoku_logo.svg", - Link: "https://www.wadoku.de/", - Color: "#ffffff", - }, - { - Title: "Übersetzer", - Image: "https://tools.avans.nl/tools/image/wZkdyaMblN.jpg", - Link: "https://www.deepl.com/translator", - Color: "#042b48", - ImageSize: "cover", - IconPadding: "0em", - }, - { - Title: "WaniKani", - Image: "https://assets.wanikani.com/assets/logo--retro-colors-b79775af8773b5a416e4ec3fae02e62d391b7e88e57f9fe0c2e4997a3383b002.png", - Link: "https://www.wanikani.com/", - Color: "#ffffff", - }, - { - Title: "Passwörter", - Image: "https://raw.githubusercontent.com/dani-garcia/vaultwarden/main/resources/vaultwarden-logo-white.svg", - Link: "https://pw.tordarus.net", - Color: "#175ddc", - }, - { - Title: "Projekte", - Image: "/gitea.png", //"https://git.tordarus.net/assets/img/logo.svg", - Link: "https://git.tordarus.net", - Color: "#609926", - ImageSize: "cover", - IconPadding: "0em", - }, - { - Title: "Regexr", - Image: "https://raw.githubusercontent.com/gskinner/regexr/master/dev/icons/RegExr.svg", - Link: "https://regex.tordarus.net", - Color: "#101112", - ImageSize: "contain", - IconPadding: "0em", - }, { Title: "Wikipedia", Image: "https://upload.wikimedia.org/wikipedia/commons/8/80/Wikipedia-logo-v2.svg", - Link: "https://www.wikipedia.org/", Color: "#ffffff", + Link: "https://www.wikipedia.org/", }, { Title: "Reddit", @@ -148,67 +40,64 @@ var ( IconPadding: "0em", }, { - Title: "Tagesschau", - Image: "https://m47ch.com/wp-content/uploads/2010/12/Tagesschau.png", - Link: "https://www.tagesschau.de/", - Color: "#003f8c", + Title: "YouTube", + Image: "https://upload.wikimedia.org/wikipedia/commons/thumb/4/42/YouTube_light_icon_%282017%29.svg/1280px-YouTube_light_icon_%282017%29.svg.png", + Link: "https://www.youtube.com/", + Color: "#ff0000", + IconPadding: "1.5em", }, { - Title: "Golem", - Image: "https://www.golem.de/staticrl/images/golem-logo-opt2.svg", - Link: "https://www.golem.de/", + Title: "Netflix", + Image: "https://1000logos.net/wp-content/uploads/2017/05/emblem-Netflix.jpg", + Link: "https://www.netflix.com/", Color: "#000000", }, { - Title: "Comico", - Image: "https://play-lh.googleusercontent.com/xZ5bfg-XfyEmM1641bXduE27w44OmyWeVspg626wRYF0ejAgZAvsRKP4sECblfmnpg=w512", - Link: "https://comico.jp/", - Color: "#f40000", + Title: "Übersetzer", + Image: "https://tools.avans.nl/tools/image/wZkdyaMblN.jpg", + Link: "https://www.deepl.com/translator", + Color: "#042b48", ImageSize: "cover", IconPadding: "0em", }, { - Title: "Manganelo", - Image: "https://manganato.com/themes/hm/images/logo.png", - Link: "https://manganato.com/", - Color: "#ffffff", - ImageSize: "contain", - IconPadding: "0em", + Title: "Cloud", + Image: "https://cloud.tordarus.net/svg/core/logo/logo?color=ffffff\u0026v=1", + Link: "https://cloud.tordarus.net/", + Color: "#007ec2", }, { - Title: "Manga List", - Image: "https://www.firstcomicsnews.com/wp-content/uploads/2020/09/manga-logo.png", - Link: "https://manga.tordarus.net/", - Color: "#be151b", + Title: "Passwörter", + Image: "https://raw.githubusercontent.com/dani-garcia/vaultwarden/main/resources/vaultwarden-logo-white.svg", + Link: "https://pw.tordarus.net", + Color: "#175ddc", }, } -) - -type Bookmark struct { - Title string - Image string - ImageSize string - IconPadding string - Color string - Link string - HideBorder bool - Order int } -func ParseBookmarks() { - // set default values - for i := range Bookmarks { - if Bookmarks[i].Color == "" { - Bookmarks[i].Color = DefaultSettings.Background - } - if Bookmarks[i].ImageSize == "" { - Bookmarks[i].ImageSize = "contain" - } - } +type Bookmark struct { + Title string `json:"title"` + Image string `json:"image"` + ImageSize string `json:"image_size"` + IconPadding string `json:"icon_padding"` + Color string `json:"color"` + Link string `json:"link"` + HideBorder bool `json:"hide_border"` + Order int `json:"order"` +} - sort.SliceStable(Bookmarks, func(i, j int) bool { - return Bookmarks[i].Order < Bookmarks[j].Order - }) +func (b *Bookmark) GetColor() string { + if b.Color == "" { + b.Color = "transparent" + } + return b.Color +} + +func (b *Bookmark) GetImageSize() string { + if b.ImageSize == "" { + b.ImageSize = "contain" + } + return b.ImageSize } func (b *Bookmark) SmallLink() string { @@ -218,3 +107,13 @@ func (b *Bookmark) SmallLink() string { lnk = strings.TrimSuffix(lnk, "/") return lnk } + +func Reorder(bookmarks []Bookmark) { + sort.SliceStable(bookmarks, func(i, j int) bool { + return bookmarks[i].Order < bookmarks[j].Order + }) + + for index := range bookmarks { + bookmarks[index].Order = index * 10 + } +} diff --git a/bookmarks.json b/bookmarks.json new file mode 100644 index 0000000..eb513ed --- /dev/null +++ b/bookmarks.json @@ -0,0 +1,262 @@ +[ + { + "title": "DuckDuckGo", + "image": "https://duckduckgo.com/assets/logo_homepage.alt.v108.svg", + "image_size": "", + "icon_padding": "", + "color": "#e37151", + "link": "https://duckduckgo.com/", + "hide_border": false, + "order": 0 + }, + { + "title": "Cloud", + "image": "https://cloud.tordarus.net/svg/core/logo/logo?color=ffffff\u0026v=1", + "image_size": "", + "icon_padding": "", + "color": "#007ec2", + "link": "https://cloud.tordarus.net/", + "hide_border": false, + "order": 0 + }, + { + "title": "ProtonMail", + "image": "https://protonmail.com/images/pm-logo-white.svg", + "image_size": "", + "icon_padding": "", + "color": "#253163", + "link": "https://protonmail.com/", + "hide_border": false, + "order": 0 + }, + { + "title": "Anilist", + "image": "https://anilist.co/img/icons/icon.svg", + "image_size": "", + "icon_padding": "0.5em", + "color": "#2b2d42", + "link": "https://anilist.co/", + "hide_border": false, + "order": 0 + }, + { + "title": "Torrents", + "image": "/transmission.png", + "image_size": "", + "icon_padding": "", + "color": "#e7402c", + "link": "https://torrents.tordarus.net/", + "hide_border": false, + "order": 0 + }, + { + "title": "Nyaa.si", + "image": "https://progsoft.net/images/nyaa-v2-icon-a591b5ae622eb38f31c00297cf9b53fd88c058ef.png", + "image_size": "111%", + "icon_padding": "0em", + "color": "#e7402c", + "link": "https://nyaa.si/", + "hide_border": false, + "order": 0 + }, + { + "title": "Adblock", + "image": "/pihole.svg", + "image_size": "", + "icon_padding": "", + "color": "#272c30", + "link": "https://adblock.tordarus.net/", + "hide_border": false, + "order": 0 + }, + { + "title": "Postbank", + "image": "https://wintouch.de/wp-content/uploads/2014/06/Postbank-Icon.png", + "image_size": "", + "icon_padding": "", + "color": "#ffcc00", + "link": "https://meine.postbank.de/", + "hide_border": false, + "order": 0 + }, + { + "title": "DKB", + "image": "https://vdiv-sa.de/wp-content/uploads/2016/08/DKB_AG_web_RGB.jpg", + "image_size": "", + "icon_padding": "", + "color": "#ffffff", + "link": "https://www.dkb.de/", + "hide_border": false, + "order": 0 + }, + { + "title": "Finanzen", + "image": "https://docs.firefly-iii.org/img/logo.png", + "image_size": "cover", + "icon_padding": "0em", + "color": "#ffffff", + "link": "https://finance.tordarus.net", + "hide_border": false, + "order": 0 + }, + { + "title": "YouTube", + "image": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/42/YouTube_light_icon_%282017%29.svg/1280px-YouTube_light_icon_%282017%29.svg.png", + "image_size": "", + "icon_padding": "1.5em", + "color": "#ff0000", + "link": "https://www.youtube.com/", + "hide_border": false, + "order": 0 + }, + { + "title": "Netflix", + "image": "https://1000logos.net/wp-content/uploads/2017/05/emblem-Netflix.jpg", + "image_size": "", + "icon_padding": "", + "color": "#000000", + "link": "https://www.netflix.com/", + "hide_border": false, + "order": 0 + }, + { + "title": "Jisho", + "image": "https://assets.jisho.org/assets/jisho-logo-v4@2x-7330091c079b9dd59601401b052b52e103978221c8fb6f5e22406d871fcc746a.png", + "image_size": "", + "icon_padding": "", + "color": "#ffffff", + "link": "https://jisho.org/", + "hide_border": false, + "order": 0 + }, + { + "title": "Wadoku", + "image": "https://www.wadoku.de/img/wadoku_logo.svg", + "image_size": "", + "icon_padding": "", + "color": "#ffffff", + "link": "https://www.wadoku.de/", + "hide_border": false, + "order": 0 + }, + { + "title": "Übersetzer", + "image": "https://tools.avans.nl/tools/image/wZkdyaMblN.jpg", + "image_size": "cover", + "icon_padding": "0em", + "color": "#042b48", + "link": "https://www.deepl.com/translator", + "hide_border": false, + "order": 0 + }, + { + "title": "WaniKani", + "image": "https://assets.wanikani.com/assets/logo--retro-colors-b79775af8773b5a416e4ec3fae02e62d391b7e88e57f9fe0c2e4997a3383b002.png", + "image_size": "", + "icon_padding": "", + "color": "#ffffff", + "link": "https://www.wanikani.com/", + "hide_border": false, + "order": 0 + }, + { + "title": "Passwörter", + "image": "https://raw.githubusercontent.com/dani-garcia/vaultwarden/main/resources/vaultwarden-logo-white.svg", + "image_size": "", + "icon_padding": "", + "color": "#175ddc", + "link": "https://pw.tordarus.net", + "hide_border": false, + "order": 0 + }, + { + "title": "Projekte", + "image": "/gitea.png", + "image_size": "cover", + "icon_padding": "0em", + "color": "#609926", + "link": "https://git.tordarus.net", + "hide_border": false, + "order": 0 + }, + { + "title": "Regexr", + "image": "https://raw.githubusercontent.com/gskinner/regexr/master/dev/icons/RegExr.svg", + "image_size": "contain", + "icon_padding": "0em", + "color": "#101112", + "link": "https://regex.tordarus.net", + "hide_border": false, + "order": 0 + }, + { + "title": "Wikipedia", + "image": "https://upload.wikimedia.org/wikipedia/commons/8/80/Wikipedia-logo-v2.svg", + "image_size": "", + "icon_padding": "", + "color": "#ffffff", + "link": "https://www.wikipedia.org/", + "hide_border": false, + "order": 0 + }, + { + "title": "Reddit", + "image": "https://www.redditinc.com/assets/images/site/reddit-logo.png", + "image_size": "cover", + "icon_padding": "0em", + "color": "#ff4300", + "link": "https://www.reddit.com/", + "hide_border": false, + "order": 0 + }, + { + "title": "Tagesschau", + "image": "https://m47ch.com/wp-content/uploads/2010/12/Tagesschau.png", + "image_size": "", + "icon_padding": "", + "color": "#003f8c", + "link": "https://www.tagesschau.de/", + "hide_border": false, + "order": 0 + }, + { + "title": "Golem", + "image": "https://www.golem.de/staticrl/images/golem-logo-opt2.svg", + "image_size": "", + "icon_padding": "", + "color": "#000000", + "link": "https://www.golem.de/", + "hide_border": false, + "order": 0 + }, + { + "title": "Comico", + "image": "https://play-lh.googleusercontent.com/xZ5bfg-XfyEmM1641bXduE27w44OmyWeVspg626wRYF0ejAgZAvsRKP4sECblfmnpg=w512", + "image_size": "cover", + "icon_padding": "0em", + "color": "#f40000", + "link": "https://comico.jp/", + "hide_border": false, + "order": 0 + }, + { + "title": "Manganelo", + "image": "https://manganato.com/themes/hm/images/logo.png", + "image_size": "contain", + "icon_padding": "0em", + "color": "#ffffff", + "link": "https://manganato.com/", + "hide_border": false, + "order": 0 + }, + { + "title": "Manga List", + "image": "https://www.firstcomicsnews.com/wp-content/uploads/2020/09/manga-logo.png", + "image_size": "", + "icon_padding": "", + "color": "#be151b", + "link": "https://manga.tordarus.net/", + "hide_border": false, + "order": 0 + } +] diff --git a/go.mod b/go.mod index 2de472d..def0d41 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,10 @@ module git.tordarus.net/Tordarus/homepage go 1.18 -require golang.org/x/text v0.3.7 // indirect +require ( + git.milar.in/milarin/slices v0.0.4 + github.com/gorilla/sessions v1.2.1 + golang.org/x/text v0.3.7 +) + +require github.com/gorilla/securecookie v1.1.1 // indirect diff --git a/go.sum b/go.sum index 1f78e03..e4d3fb1 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,8 @@ +git.milar.in/milarin/slices v0.0.4 h1:z92jgsKcnLPLfgXkTVCzH2XXesfXzhe0Osx+PkfCHVI= +git.milar.in/milarin/slices v0.0.4/go.mod h1:NOr53AOeur/qscu/FBj3lsFR262PNYBccLYSTCAXRk4= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= diff --git a/main.go b/main.go index 17dcf43..6c108e7 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,9 @@ package main import ( "embed" + "encoding/gob" + "encoding/hex" + "encoding/json" "errors" "flag" "fmt" @@ -9,9 +12,13 @@ import ( "io" "net/http" "net/url" + "os" "strings" _ "embed" + + "git.milar.in/milarin/slices" + "github.com/gorilla/sessions" ) var ( @@ -27,12 +34,15 @@ var ( StaticFS embed.FS Templates *template.Template + + SessionStore = sessions.NewCookieStore(Must(hex.DecodeString(os.Getenv("SESSION_KEY")))) ) func main() { - flag.Parse() + gob.Register([]Bookmark{}) + gob.Register(&Settings{}) - ParseBookmarks() + flag.Parse() if tmpl, err := template.New("homepage").ParseFS(TemplateFS, "templates/*"); err == nil { Templates = tmpl @@ -41,8 +51,11 @@ func main() { } http.HandleFunc("/", handler) + http.HandleFunc("/customize", customize) + http.HandleFunc("/save-changes", saveChanges) http.HandleFunc("/search", search) - http.HandleFunc("/style.css", style) + http.HandleFunc("/style.css", ProvideFile("static/style.css", "text/css")) + http.HandleFunc("/customize.js", ProvideFile("static/customize.js", "application/javascript")) http.HandleFunc("/icons.ttf", ProvideFile("static/icons.ttf", "font/ttf")) http.HandleFunc("/transmission.png", ProvideFile("static/transmission.png", "image/png")) http.HandleFunc("/pihole.svg", ProvideFile("static/pihole.svg", "image/svg+xml")) @@ -53,10 +66,12 @@ func main() { } func handler(w http.ResponseWriter, r *http.Request) { + session, _ := SessionStore.Get(r, "settings") + data := &TmplData{ text: GetText(r), - Bookmarks: Bookmarks, - Settings: DefaultSettings, + Bookmarks: GetValueDefault(session, "bookmarks", DefaultBookmarks()), + Settings: GetValueDefault(session, "settings", DefaultSettings()), } if err := Templates.ExecuteTemplate(w, "index.html", data); err != nil { @@ -64,22 +79,6 @@ func handler(w http.ResponseWriter, r *http.Request) { } } -func style(w http.ResponseWriter, r *http.Request) { - file, err := TemplateFS.Open("templates/style.css") - if err != nil { - fmt.Println(err) - w.WriteHeader(http.StatusInternalServerError) - return - } - defer file.Close() - - w.Header().Add("Content-Type", "text/css") - - if err := Templates.ExecuteTemplate(w, "style.css", DefaultSettings); err != nil { - panic(err) - } -} - func ProvideFile(path, contentType string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { file, err := StaticFS.Open(path) @@ -95,6 +94,9 @@ func ProvideFile(path, contentType string) http.HandlerFunc { } func search(w http.ResponseWriter, r *http.Request) { + session, _ := SessionStore.Get(r, "settings") + settings := GetValueDefault(session, "settings", DefaultSettings()) + if err := r.ParseForm(); err != nil { fmt.Println(err) w.WriteHeader(http.StatusInternalServerError) @@ -109,12 +111,62 @@ func search(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusMovedPermanently) } else { // search string - w.Header().Add("Location", fmt.Sprintf("https://duckduckgo.com/?q=%s", query)) + w.Header().Add("Location", fmt.Sprintf(settings.Search, query)) w.WriteHeader(http.StatusMovedPermanently) } } +func customize(w http.ResponseWriter, r *http.Request) { + session, _ := SessionStore.Get(r, "settings") + + text := GetText(r) + + bookmarks := slices.Map(GetValueDefault(session, "bookmarks", DefaultBookmarks()), func(b Bookmark) BookmarkData { + return BookmarkData{ + Bookmark: &b, + text: text, + } + }) + + data := &CustomizeData{ + text: text, + Bookmarks: bookmarks, + Settings: GetValueDefault(session, "settings", DefaultSettings()), + } + + if err := Templates.ExecuteTemplate(w, "customize.html", data); err != nil { + panic(err) + } +} + +func saveChanges(w http.ResponseWriter, r *http.Request) { + if r.Method != "POST" { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + session, _ := SessionStore.Get(r, "settings") + + sessionData := &SessionData{} + err := json.NewDecoder(r.Body).Decode(&sessionData) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + fmt.Println(err) + return + } + + Reorder(sessionData.Bookmarks) + session.Values["bookmarks"] = sessionData.Bookmarks + session.Values["settings"] = sessionData.Settings + + if err := session.Save(r, w); err != nil { + fmt.Println(err) + w.WriteHeader(http.StatusInternalServerError) + return + } +} + func ParseURI(uri string) (*url.URL, error) { if !strings.HasPrefix(uri, "http://") && !strings.HasPrefix(uri, "https://") { uri = "https://" + uri diff --git a/session_data.go b/session_data.go new file mode 100644 index 0000000..a97dd59 --- /dev/null +++ b/session_data.go @@ -0,0 +1,6 @@ +package main + +type SessionData struct { + Bookmarks []Bookmark `json:"bookmarks"` + Settings *Settings `json:"settings"` +} diff --git a/settings.go b/settings.go index 8b46360..62fb63e 100644 --- a/settings.go +++ b/settings.go @@ -1,19 +1,17 @@ package main type Settings struct { - Background string - IconMargin string - IconPadding string - IconWidth string - IconHeight string - BorderRadius string + Background string `json:"background_color"` + Foreground string `json:"foreground_color"` + Search string `json:"search_query"` + BorderRadius string `json:"border_radius"` } -var DefaultSettings = &Settings{ - Background: "#2b2a33", - IconMargin: "0.5em", - IconPadding: "1em", - IconWidth: "10em", - IconHeight: "10em", - BorderRadius: "0.15em", +func DefaultSettings() *Settings { + return &Settings{ + Background: "#2b2a33", + Foreground: "#ffffff", + Search: "https://duckduckgo.com/?q=%s", + BorderRadius: "1.5%", + } } diff --git a/static/customize.js b/static/customize.js new file mode 100644 index 0000000..76f7cab --- /dev/null +++ b/static/customize.js @@ -0,0 +1,135 @@ +window.addEventListener("load", main); + +function main() { + const bgColor = document.querySelector("#bg-color"); + const fgColor = document.querySelector("#fg-color"); + const borderRadius = document.querySelector("#border-radius"); + + bgColor.addEventListener("change", () => document.body.style.backgroundColor = bgColor.value); + fgColor.addEventListener("change", () => document.body.style.color = fgColor.value); + borderRadius.addEventListener("change", () => document.querySelector(":root").style.setProperty("--bookmark-border-radius", borderRadius.value)); + + const save = document.querySelector("#button"); + save.addEventListener("click", saveChanges); + + const addBookmark = document.querySelector("#add-bookmark"); + const firstBookmark = document.querySelector(".customizer"); + + addBookmark.style.width = `${firstBookmark.offsetWidth}px`; + addBookmark.style.height = `${firstBookmark.offsetHeight}px`; + addBookmark.style.lineHeight = `${firstBookmark.offsetHeight}px`; + + addBookmark.addEventListener("click", () => { + const bookmark = firstBookmark.cloneNode(true); + initBookmark(bookmark); + clearBookmarkData(bookmark); + document.querySelector("#customize-container").insertBefore(bookmark, addBookmark); + }); + + document.querySelectorAll(".customizer").forEach(initBookmark); +} + +function saveChanges() { + const bookmarks = []; + document.querySelectorAll(".customizer").forEach(element => bookmarks.push(buildDataForBoomark(element))); + + const settings = { + background_color: document.querySelector("#bg-color").value, + foreground_color: document.querySelector("#fg-color").value, + search_query: document.querySelector("#search-string").value, + border_radius: document.querySelector("#border-radius").value + }; + + const data = { bookmarks, settings } + + fetch("/save-changes", { + method: "POST", + body: JSON.stringify(data) + }).then(() => { + window.location = "/"; + }) +} + +function buildDataForBoomark(element) { + const form = element.querySelector("form"); + + const title = form.querySelector("#bookmark-name"); + const link = form.querySelector("#bookmark-link"); + const image = form.querySelector("#bookmark-image"); + const imageSize = form.querySelector("#bookmark-imagesize"); + const iconPadding = form.querySelector("#bookmark-iconpadding"); + const color = form.querySelector("#bookmark-color"); + const hideBorder = form.querySelector("#bookmark-border"); + const order = form.querySelector("#bookmark-order"); + + return { + title: title.value, + image: image.value, + image_size: imageSize.value, + icon_padding: iconPadding.value, + color: color.value, + link: link.value, + hide_border: hideBorder.checked, + order: parseInt(order.value) + }; +} + +function clearBookmarkData(bookmark) { + const a = bookmark.querySelector(".bookmark"); + const form = bookmark.querySelector("form"); + const bg = a.querySelector(".background"); + + const title = form.querySelector("#bookmark-name"); + const link = form.querySelector("#bookmark-link"); + const image = form.querySelector("#bookmark-image"); + const imageSize = form.querySelector("#bookmark-imagesize"); + const iconPadding = form.querySelector("#bookmark-iconpadding"); + const color = form.querySelector("#bookmark-color"); + const hideBorder = form.querySelector("#bookmark-border"); + const order = form.querySelector("#bookmark-order"); + + let orderValue = 0; + document.querySelectorAll(".customizer").forEach(element => { + orderValue = Math.max(orderValue, element.querySelector("#bookmark-order").value); + }); + + title.value = ""; + link.value = ""; + image.value = ""; + imageSize.value = "contain"; + iconPadding.value = ""; + color.value = "#ffffff"; + hideBorder.value = false; + order.value = orderValue + 10; + bg.style.backgroundImage = ""; + a.style.backgroundColor = "#ffffff"; +} + +function initBookmark(bookmark) { + const a = bookmark.querySelector(".bookmark"); + const del = bookmark.querySelector(".delete"); + const form = bookmark.querySelector("form"); + const bg = a.querySelector(".background"); + + const title = form.querySelector("#bookmark-name"); + const link = form.querySelector("#bookmark-link"); + const image = form.querySelector("#bookmark-image"); + const imageSize = form.querySelector("#bookmark-imagesize"); + const iconPadding = form.querySelector("#bookmark-iconpadding"); + const color = form.querySelector("#bookmark-color"); + const hideBorder = form.querySelector("#bookmark-border"); + + del.addEventListener("click", () => { + const question = title.value == "" ? "Do you want to delete this bookmark?" : `Do you want to delete '${title.value}'?` + if (confirm(question)) { + bookmark.parentElement.removeChild(bookmark); + } + }); + + link.addEventListener("change", () => a.setAttribute("href", link.value)); + image.addEventListener("change", () => bg.style.backgroundImage = `url(${image.value})`); + imageSize.addEventListener("change", () => bg.style.backgroundSize = imageSize.value); + iconPadding.addEventListener("change", () => a.style.padding = iconPadding.value); + color.addEventListener("change", () => a.style.backgroundColor = color.value); + hideBorder.addEventListener("change", () => a.style.boxShadow = hideBorder.value ? "" : "0px 0px 3px black"); +} diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..aafc308 --- /dev/null +++ b/static/style.css @@ -0,0 +1,315 @@ +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: url("/icons.ttf"); + src: local('Material Icons'), + local('MaterialIcons-Regular'), + url("/icons.ttf") format('truetype'); +} + +.material-icons { + transform: translateY(3px); + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; /* Preferred icon size */ + display: inline-block; + line-height: 1; + text-transform: none; + letter-spacing: normal; + word-wrap: normal; + white-space: nowrap; + direction: ltr; + + /* Support for all WebKit browsers. */ + + /* Support for Safari and Chrome. */ + text-rendering: optimizeLegibility; + + /* Support for Firefox. */ + -moz-osx-font-smoothing: grayscale; + + /* Support for IE. */ + font-feature-settings: 'liga'; +} + +:root { + --bookmark-width: 10em; + --bookmark-height: 10em; + --bookmark-margin: 0.5em; + --bookmark-padding: 1em; +} + +* { + margin: 0; + padding: 0; +} + +html, body { + width: 100%; + min-height: 100vh; + font-size: 100%; +} + +body { + background-color: #2b2a33; +} + +main { + width: 100%; + min-height: 100vh; +} + +#main-container { + display: flex; + flex-direction: column; + justify-content: center; + margin-bottom: 0.5em; +} + +#bookmark-container { + width: 100%; + display: flex; + flex-wrap: wrap; + justify-content: center; + align-content: center; +} + +#search { + margin: 3em auto; + max-width: calc(100% - 2em); +} + +#search form { + --input-font-size: 1.2em; + --width: 30rem; + --height: 2.5rem; + + position: relative; + display: block; + width: var(--width); + height: var(--height); + + max-width: 100%; + + background-color: rgba(0, 0, 0, 0.5); + border: 1px solid black; + border-radius: 0.25em; +} + +#search form input[type="search"] { + position: absolute; + top: 0; + left: 0; + display: inline-block; + width: calc(100% - var(--height)); + height: var(--height); + border: 0; + padding: 0 0.5em; + box-sizing: border-box; + outline: 0; + color: inherit; + background-color: transparent; + transition: all 0.2s ease-in-out; + font-size: var(--input-font-size); +} + +#search form label[for="submit"] { + position: absolute; + top: 0; + right: 0; + display: inline-block; + width: var(--height); + height: var(--height); + margin-left: 0; + margin-right: 0; + box-sizing: border-box; + + transition: all 0.2s ease-in-out; + font-size: var(--input-font-size); + cursor: pointer; +} + +#search form label[for="submit"] span { + transform: translate(0.3em, 0.3em); +} + +#search form input[type="submit"] { + display: none; +} + +.bookmark { + display: block; + box-sizing: border-box; + transition: all 0.2s ease-in-out; + + width: var(--bookmark-width); + height: var(--bookmark-height); + margin: var(--bookmark-margin); + padding: var(--bookmark-padding); + border-radius: var(--bookmark-border-radius); +} + +.bookmark:hover { + transform: scale(1.1, 1.1); + transition: all 0.2s ease-in-out; +} + +.background { + width: 100%; + height: 100%; + background-position: center; + background-size: contain; + background-repeat: no-repeat; + + border-radius: var(--bookmark-border-radius); +} + +@media screen and (max-width: 30em) { + .bookmark { + width: calc(var(--bookmark-width) * 0.5); + height: calc(var(--bookmark-height) * 0.5); + padding: calc(var(--bookmark-padding) * 0.5); + } +} + +#button { + display: block; + position: absolute; + top: 0.25em; + right: 0.5em; + color: white; +} + +#customize-container { + display: flex; + flex-direction: row; + flex-wrap: wrap; + align-items: flex-start; + justify-content: center; +} + +.customizer { + margin: 1em; + position: relative; + + display: flex; + flex-direction: row; + justify-content: center; + + background-color: rgba(255, 255, 255, 0.15); + border-radius: 0.25em; +} + +.customizer > form { + margin: 0.5em; + display: grid; + grid-template-columns: auto auto; + grid-auto-rows: max-content; + gap: 0.25em; +} + +form label { + margin-right: 0.25em; +} + +.customizer > form input { + background-color: rgba(0, 0, 0, 0.25); + border: 1px solid black; + color: inherit; + padding: 0.25em; + border-radius: 0.2em; +} + +.customizer > form input:not([type="checkbox"]):not([type="file"]):not([type="color"]):focus { + outline-style: solid; + outline-color: #aaa; +} + +#add-bookmark { + margin: 1em; + color: white; + background-color: darkgreen; + text-align: center; + border-radius: 0.25em; + cursor: pointer; + transition: all 0.25s ease-in-out; + + width: 618px; + height: 259px; + line-height: 259px; +} + +#add-bookmark:hover, +#add-bookmark:focus { + background-color: green; + transition: all 0.25s ease-in-out; +} + +#add-bookmark .material-icons { + font-size: 8em; + line-height: inherit; +} + +.delete { + background-color: #cc0000; + color: white; + position: absolute; + bottom: 0.5em; + left: 0.5em; + width: 2em; + height: 2em; + text-align: center; + border-radius: 0.25em; + box-shadow: 0px 0px 3px #333; + cursor: pointer; + transition: all 0.25s ease-in-out; +} + +.delete:hover, +.delete:focus { + transform: scale(1.25); + transition: all 0.25s ease-in-out; +} + +.fixed { + position: fixed !important; +} + +#top-panel { + background-color: rgba(255, 255, 255, 0.15); + padding: 1em; + border-bottom: 1px solid black; +} + +#top-panel h1 { + text-align: center; + margin-bottom: 0.5em; +} + +#top-panel > form { + max-width: 30em; + display: grid; + grid-template-columns: auto auto; + grid-auto-rows: max-content; + gap: 0.25em; + margin: 0 auto; +} + +#top-panel > form input { + background-color: rgba(0, 0, 0, 0.25); + border: 1px solid black; + color: inherit; + padding: 0.25em; + border-radius: 0.2em; +} + +#top-panel > form input:not([type="checkbox"]):not([type="file"]):not([type="color"]):focus { + outline-style: solid; + outline-color: #aaa; +} + +#top-panel > form > * { + display: block; +} \ No newline at end of file diff --git a/templates/bookmark.html b/templates/bookmark.html index 3de357a..03fd5f2 100644 --- a/templates/bookmark.html +++ b/templates/bookmark.html @@ -1,3 +1,3 @@ - -
-
\ No newline at end of file + +
+
diff --git a/templates/customize-bookmark.html b/templates/customize-bookmark.html new file mode 100644 index 0000000..8df0b7c --- /dev/null +++ b/templates/customize-bookmark.html @@ -0,0 +1,33 @@ +
+ +
+
+ + delete + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+
diff --git a/templates/customize.html b/templates/customize.html new file mode 100644 index 0000000..75f7b9d --- /dev/null +++ b/templates/customize.html @@ -0,0 +1,70 @@ + + + + + + + {{ .Translate "Customize Homepage" }} + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + save + +
+

{{ .Translate "Settings" }}

+
+ + + + + + + + + + + +
+
+
+ {{ range .Bookmarks }} + {{ template "customize-bookmark.html" . }} + {{ end }} + + add +
+
+ + diff --git a/templates/index.html b/templates/index.html index 23cfe5f..26f562c 100644 --- a/templates/index.html +++ b/templates/index.html @@ -6,11 +6,24 @@ {{ .Translate "Homepage" }} - + + + -
+
+ + settings +
- \ No newline at end of file + diff --git a/templates/style.css b/templates/style.css deleted file mode 100644 index 34eda97..0000000 --- a/templates/style.css +++ /dev/null @@ -1,175 +0,0 @@ -@font-face { - font-family: 'Material Icons'; - font-style: normal; - font-weight: 400; - src: url("/icons.ttf"); - src: local('Material Icons'), - local('MaterialIcons-Regular'), - url("/icons.ttf") format('truetype'); -} - -.material-icons { - transform: translateY(3px); - font-family: 'Material Icons'; - font-weight: normal; - font-style: normal; - font-size: 24px; /* Preferred icon size */ - display: inline-block; - line-height: 1; - text-transform: none; - letter-spacing: normal; - word-wrap: normal; - white-space: nowrap; - direction: ltr; - - /* Support for all WebKit browsers. */ - -webkit-font-smoothing: antialiased; - /* Support for Safari and Chrome. */ - text-rendering: optimizeLegibility; - - /* Support for Firefox. */ - -moz-osx-font-smoothing: grayscale; - - /* Support for IE. */ - font-feature-settings: 'liga'; -} - -* { - margin: 0; - padding: 0; -} - -html, body { - width: 100%; - min-height: 100vh; - font-size: 100%; -} - -body { - background-color: {{ .Background }}; -} - -main { - width: 100%; - min-height: 100vh; - display: flex; - flex-direction: column; - justify-content: center; - margin-bottom: 0.5em; -} - -#bookmark-container { - width: 100%; - display: flex; - flex-wrap: wrap; - justify-content: center; - align-content: center; -} - -#search { - margin: 3em auto; - max-width: calc(100% - 2em); -} - -#search form { - --input-font-size: 1.2em; - --width: 30rem; - --height: 2.5rem; - - position: relative; - display: block; - width: var(--width); - height: var(--height); - - max-width: 100%; -} - -#search form input[type="search"] { - position: absolute; - top: 0; - left: 0; - display: inline-block; - width: calc(100% - var(--height)); - height: var(--height); - border: 1px solid black; - padding: 0 0.5em; - box-sizing: border-box; - outline: 0; - background-color: black; - color: white; - transition: all 0.2s ease-in-out; - border-radius: 0.25em 0 0 0.25em; - font-size: var(--input-font-size); -} - -#search form input[type="search"]:focus { - box-shadow: 0px 0px 3px gray; - transition: all 0.2s ease-in-out; -} - -#search form input[type="search"]:focus + label[for="submit"] { - background-color: #111; - box-shadow: 0px 0px 3px gray; - transition: all 0.2s ease-in-out; -} - -#search form label[for="submit"] { - position: absolute; - top: 0; - right: 0; - display: inline-block; - width: var(--height); - height: var(--height); - margin-left: 0; - border: 1px solid black; - box-sizing: border-box; - background-color: black; - color: white; - transition: all 0.2s ease-in-out; - border-radius: 0 0.25em 0.25em 0; - font-size: var(--input-font-size); - cursor: pointer; -} - -#search form label[for="submit"] span { - transform: translate(0.3em, 0.3em); -} - -#search form input[type="submit"] { - display: none; -} - -.bookmark { - display: block; - box-sizing: border-box; - transition: all 0.2s ease-in-out; - - width: {{ .IconWidth }}; - height: {{ .IconHeight }}; - margin: {{ .IconMargin }}; - padding: {{ .IconPadding }}; - border-radius: {{ .BorderRadius }}; -} - -.bookmark:hover { - transform: scale(1.1, 1.1); - transition: all 0.2s ease-in-out; -} - -.background { - width: 100%; - height: 100%; - background-position: center; - background-size: contain; - background-repeat: no-repeat; - - border-radius: {{ .BorderRadius }}; -} - -@media screen and (max-width: 30em) { - .bookmark { - width: calc({{ .IconWidth }} * 0.5); - height: calc({{ .IconHeight }} * 0.5); - padding: calc({{ .IconPadding }} * 0.5); - } -} \ No newline at end of file diff --git a/text.go b/text.go index edac8d6..7ad7870 100644 --- a/text.go +++ b/text.go @@ -22,22 +22,37 @@ var ( Languages = map[language.Tag]Text{ language.English: { Strings: map[string]string{ - "Homepage": "Homepage", - "Search": "Search", + "Homepage": "Homepage", + "Search": "Search", + "Search engine": "Search engine", + "Settings": "Settings", + "Background color": "Background color", + "Foreground color": "Foreground color", + "Border radius": "Border radius", }, }, language.German: { Strings: map[string]string{ - "Homepage": "Homepage", - "Search": "Suche", + "Homepage": "Homepage", + "Search": "Suche", + "Search engine": "Suchmaschine", + "Settings": "Einstellungen", + "Background color": "Hintergrundfarbe", + "Foreground color": "Textfarbe", + "Border radius": "Randradius", }, }, language.Japanese: { Strings: map[string]string{ - "Homepage": "ホームページ", - "Search": "検索", + "Homepage": "ホームページ", + "Search": "検索", + "Search engine": "検索エンジン", + "Settings": "設定", + "Background color": "背景色", + "Foreground color": "描画色", + "Border radius": "境界半径", }, }, } diff --git a/tmpl_data.go b/tmpl_data.go index 31f2c97..cf94cc1 100644 --- a/tmpl_data.go +++ b/tmpl_data.go @@ -13,3 +13,26 @@ func (d *TmplData) Translate(str string) string { func (d *TmplData) Language() string { return d.text.Language } + +type CustomizeData struct { + text *Text + Bookmarks []BookmarkData + Settings *Settings +} + +func (d *CustomizeData) Translate(str string) string { + return d.text.Translate(str) +} + +func (d *CustomizeData) Language() string { + return d.text.Language +} + +type BookmarkData struct { + *Bookmark + text *Text +} + +func (bd *BookmarkData) Translate(str string) string { + return bd.text.Translate(str) +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..14cea34 --- /dev/null +++ b/utils.go @@ -0,0 +1,35 @@ +package main + +import ( + "github.com/gorilla/sessions" +) + +func Must[T any](value T, err error) T { + if err != nil { + panic(err) + } + return value +} + +func GetValue[K any, T any](session *sessions.Session, key K) (T, bool) { + value, ok := session.Values[key] + if !ok && value == nil { + return *new(T), false + } + + castedValue, ok := value.(T) + if !ok { + return *new(T), false + } + + return castedValue, true +} + +func GetValueDefault[K any, T any](session *sessions.Session, key K, defaultValue T) T { + v, ok := GetValue[K, T](session, key) + if !ok { + return defaultValue + } + + return v +}