From fabe06e339ddcf1643fa33fdbfd88dc25464ea90 Mon Sep 17 00:00:00 2001 From: Kailash Nadh Date: Sat, 18 Dec 2021 15:38:42 +0530 Subject: [PATCH] Add support for custom CSS/JS in settings for admin and public pages. This feature was originally authored by @sweetppro in PR #438. However, since the PR ended up in an unclean state with multiple master merges (instead of rebase) from the upstream, there are several commits that are out of order and can can no longer be be squashed for a clean feature merge. This commit aggregates the changes from the original PR and applies the following fixes on top of it. - Add custom admin JS box to appearance UI. - Refactor i18n language strings. - Add handlers and migrations for the new `appearance.admin.custom_js` field. - Fix migration version to `v2.1.0` - Load custom appearance CSS/JS bytes into global constants during boot instead of making a DB call on every request. - Fix and canonicalize URIs from `/api/custom*` to `/public/*.css` and `/admin/*.css`. Add proxy paths to yarn proxy config. - Remove redundant HTTP handlers for different custom appearance files and refactor into a single handler `serveCustomApperance()` - Fix content-type and UTF8 encoding headers for different file types. - Fix incorrect registration of public facing custom CSS/JS handlers in the authenticated admin URI group. - Fix merge conflicts in `Settings.vue`. - Minor HTML and style fixes. - Remove the `AppearanceEditor` component and use the existing `HTMLEditor` component instead. - Add `language` prop to the `HTMLEditor` component. Co-authored-by: SweetPPro --- cmd/handlers.go | 40 +++++++++++++ cmd/init.go | 14 ++++- cmd/settings.go | 5 ++ cmd/upgrade.go | 1 + frontend/public/index.html | 2 + frontend/src/assets/style.scss | 4 ++ frontend/src/components/HTMLEditor.vue | 6 +- frontend/src/views/Settings.vue | 6 ++ frontend/src/views/settings/appearance.vue | 66 ++++++++++++++++++++++ frontend/vue.config.js | 5 +- i18n/cs-cz.json | 7 +++ i18n/de.json | 7 +++ i18n/en.json | 7 +++ i18n/es.json | 7 +++ i18n/fr.json | 7 +++ i18n/hu.json | 7 +++ i18n/it.json | 7 +++ i18n/ml.json | 7 +++ i18n/nl.json | 7 +++ i18n/pl.json | 7 +++ i18n/pt-BR.json | 7 +++ i18n/pt.json | 7 +++ i18n/ro.json | 7 +++ i18n/ru.json | 7 +++ i18n/tr.json | 7 +++ internal/migrations/v2.1.0.go | 23 ++++++++ schema.sql | 6 +- static/public/templates/index.html | 2 + 28 files changed, 280 insertions(+), 5 deletions(-) create mode 100644 frontend/src/views/settings/appearance.vue create mode 100644 internal/migrations/v2.1.0.go diff --git a/cmd/handlers.go b/cmd/handlers.go index 2cd7fa9..63c6ba1 100644 --- a/cmd/handlers.go +++ b/cmd/handlers.go @@ -55,6 +55,8 @@ func initHTTPHandlers(e *echo.Echo, app *App) { return c.Render(http.StatusOK, "home", publicTpl{Title: "listmonk"}) }) g.GET(path.Join(adminRoot, ""), handleAdminPage) + g.GET(path.Join(adminRoot, "/custom.css"), serveCustomApperance("admin.custom_css")) + g.GET(path.Join(adminRoot, "/custom.js"), serveCustomApperance("admin.custom_js")) g.GET(path.Join(adminRoot, "/*"), handleAdminPage) // API endpoints. @@ -142,6 +144,7 @@ func initHTTPHandlers(e *echo.Echo, app *App) { e.POST("/webhooks/service/:service", handleBounceWebhook) } + // /public/static/* file server is registered in initHTTPServer(). // Public subscriber facing views. e.GET("/subscription/form", handleSubscriptionFormPage) e.POST("/subscription/form", handleSubscriptionForm) @@ -161,6 +164,10 @@ func initHTTPHandlers(e *echo.Echo, app *App) { "campUUID", "subUUID"))) e.GET("/campaign/:campUUID/:subUUID/px.png", noIndex(validateUUID(handleRegisterCampaignView, "campUUID", "subUUID"))) + + e.GET("/public/custom.css", serveCustomApperance("public.custom_css")) + e.GET("/public/custom.js", serveCustomApperance("public.custom_js")) + // Public health API endpoint. e.GET("/health", handleHealthCheck) } @@ -182,6 +189,39 @@ func handleHealthCheck(c echo.Context) error { return c.JSON(http.StatusOK, okResp{true}) } +// serveCustomApperance serves the given custom CSS/JS apperance blob +// meant for customizing public and admin pages from the admin settings UI. +func serveCustomApperance(name string) echo.HandlerFunc { + return func(c echo.Context) error { + var ( + app = c.Get("app").(*App) + + out []byte + hdr string + ) + + switch name { + case "admin.custom_css": + out = app.constants.Appearance.AdminCSS + hdr = "text/css; charset=utf-8" + + case "admin.custom_js": + out = app.constants.Appearance.AdminJS + hdr = "application/javascript; charset=utf-8" + + case "public.custom_css": + out = app.constants.Appearance.PublicCSS + hdr = "text/css; charset=utf-8" + + case "public.custom_js": + out = app.constants.Appearance.PublicJS + hdr = "application/javascript; charset=utf-8" + } + + return c.Blob(http.StatusOK, hdr, out) + } +} + // basicAuth middleware does an HTTP BasicAuth authentication for admin handlers. func basicAuth(username, password string, c echo.Context) (bool, error) { app := c.Get("app").(*App) diff --git a/cmd/init.go b/cmd/init.go index 8cd6d40..179d1da 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -68,6 +68,13 @@ type constants struct { AdminUsername []byte `koanf:"admin_username"` AdminPassword []byte `koanf:"admin_password"` + Appearance struct { + AdminCSS []byte `koanf:"admin.custom_css"` + AdminJS []byte `koanf:"admin.custom_js"` + PublicCSS []byte `koanf:"public.custom_css"` + PublicJS []byte `koanf:"public.custom_js"` + } + UnsubURL string LinkTrackURL string ViewTrackURL string @@ -293,7 +300,10 @@ func initConstants() *constants { lo.Fatalf("error loading app config: %v", err) } if err := ko.Unmarshal("privacy", &c.Privacy); err != nil { - lo.Fatalf("error loading app config: %v", err) + lo.Fatalf("error loading app.privacy config: %v", err) + } + if err := ko.UnmarshalWithConf("appearance", &c.Appearance, koanf.UnmarshalConf{FlatPaths: true}); err != nil { + lo.Fatalf("error loading app.appearance config: %v", err) } c.RootURL = strings.TrimRight(c.RootURL, "/") @@ -622,7 +632,7 @@ func initHTTPServer(app *App) *echo.Echo { fSrv := app.fs.FileServer() // Public (subscriber) facing static files. - srv.GET("/public/*", echo.WrapHandler(fSrv)) + srv.GET("/public/static/*", echo.WrapHandler(fSrv)) // Admin (frontend) facing static files. srv.GET("/admin/static/*", echo.WrapHandler(fSrv)) diff --git a/cmd/settings.go b/cmd/settings.go index 323a09f..2ae9fbf 100644 --- a/cmd/settings.go +++ b/cmd/settings.go @@ -105,6 +105,11 @@ type settings struct { TLSSkipVerify bool `json:"tls_skip_verify"` ScanInterval string `json:"scan_interval"` } `json:"bounce.mailboxes"` + + AdminCustomCSS string `json:"appearance.admin.custom_css"` + AdminCustomJS string `json:"appearance.admin.custom_js"` + PublicCustomCSS string `json:"appearance.public.custom_css"` + PublicCustomJS string `json:"appearance.public.custom_js"` } var ( diff --git a/cmd/upgrade.go b/cmd/upgrade.go index a27bc91..46b590a 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -31,6 +31,7 @@ var migList = []migFunc{ {"v0.9.0", migrations.V0_9_0}, {"v1.0.0", migrations.V1_0_0}, {"v2.0.0", migrations.V2_0_0}, + {"v2.1.0", migrations.V2_1_0}, } // upgrade upgrades the database to the current version by running SQL migration files diff --git a/frontend/public/index.html b/frontend/public/index.html index b0b0bb0..f4ea307 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -5,6 +5,8 @@ + + <%= htmlWebpackPlugin.options.title %> diff --git a/frontend/src/assets/style.scss b/frontend/src/assets/style.scss index 0e0fc12..720b2b7 100644 --- a/frontend/src/assets/style.scss +++ b/frontend/src/assets/style.scss @@ -802,6 +802,10 @@ section.analytics { .box { margin-bottom: 30px; } + .html-editor { + height: auto; + min-height: 350px; + } } /* Logs */ diff --git a/frontend/src/components/HTMLEditor.vue b/frontend/src/components/HTMLEditor.vue index 00d28f8..7ab5077 100644 --- a/frontend/src/components/HTMLEditor.vue +++ b/frontend/src/components/HTMLEditor.vue @@ -9,6 +9,10 @@ import { colors } from '../constants'; export default { props: { value: String, + language: { + type: String, + default: 'html', + }, disabled: Boolean, }, @@ -38,7 +42,7 @@ export default { this.$refs.htmlEditor.appendChild(el); this.flask = new CodeFlask(el.shadowRoot.getElementById('area'), { - language: 'html', + language: this.$props.language, lineNumbers: false, styleParent: el.shadowRoot, readonly: this.disabled, diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue index 8d7891d..0f01d13 100644 --- a/frontend/src/views/Settings.vue +++ b/frontend/src/views/Settings.vue @@ -49,6 +49,10 @@ + + + + @@ -66,6 +70,7 @@ import MediaSettings from './settings/media.vue'; import SmtpSettings from './settings/smtp.vue'; import BounceSettings from './settings/bounces.vue'; import MessengerSettings from './settings/messengers.vue'; +import AppearanceSettings from './settings/appearance.vue'; const dummyPassword = ' '.repeat(8); @@ -78,6 +83,7 @@ export default Vue.extend({ SmtpSettings, BounceSettings, MessengerSettings, + AppearanceSettings, }, data() { diff --git a/frontend/src/views/settings/appearance.vue b/frontend/src/views/settings/appearance.vue new file mode 100644 index 0000000..aa2e078 --- /dev/null +++ b/frontend/src/views/settings/appearance.vue @@ -0,0 +1,66 @@ + + + diff --git a/frontend/vue.config.js b/frontend/vue.config.js index 1d40c67..a5950bb 100644 --- a/frontend/vue.config.js +++ b/frontend/vue.config.js @@ -29,7 +29,10 @@ module.exports = { '^/$': { target: process.env.LISTMONK_API_URL || 'http://127.0.0.1:9000' }, - '^/(api|webhooks|subscription|public)': { + '^/(api|webhooks|subscription|public|health)': { + target: process.env.LISTMONK_API_URL || 'http://127.0.0.1:9000' + }, + '^/(admin\/custom\.(css|js))': { target: process.env.LISTMONK_API_URL || 'http://127.0.0.1:9000' } } diff --git a/i18n/cs-cz.json b/i18n/cs-cz.json index b54fb21..26fd41c 100644 --- a/i18n/cs-cz.json +++ b/i18n/cs-cz.json @@ -295,6 +295,13 @@ "public.unsubbedInfo": "Odběr jste zrušili úspěšně.", "public.unsubbedTitle": "Zrušen odběr", "public.unsubscribeTitle": "Zrušit odběr ze seznamu adresátů", + "settings.appearance.adminHelp": "Custom CSS to apply to the admin UI.", + "settings.appearance.adminName": "Admin", + "settings.appearance.customCSS": "Custom CSS", + "settings.appearance.customJS": "Custom JavaScript", + "settings.appearance.name": "Appearance", + "settings.appearance.publicHelp": "Custom CSS and JavaScript to apply to the public pages.", + "settings.appearance.publicName": "Public", "settings.bounces.action": "Akce", "settings.bounces.blocklist": "Seznam blokovaných", "settings.bounces.count": "Počet případů nedoručitelnosti", diff --git a/i18n/de.json b/i18n/de.json index f164464..663fc25 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -295,6 +295,13 @@ "public.unsubbedInfo": "Du wurdest erfolgreich abgemeldet", "public.unsubbedTitle": "Abgemeldet", "public.unsubscribeTitle": "Von E-Mail Liste abmelden.", + "settings.appearance.adminHelp": "Custom CSS to apply to the admin UI.", + "settings.appearance.adminName": "Admin", + "settings.appearance.customCSS": "Custom CSS", + "settings.appearance.customJS": "Custom JavaScript", + "settings.appearance.name": "Appearance", + "settings.appearance.publicHelp": "Custom CSS and JavaScript to apply to the public pages.", + "settings.appearance.publicName": "Public", "settings.bounces.action": "Aktion", "settings.bounces.blocklist": "Sperrliste", "settings.bounces.count": "Bounce Anzahl", diff --git a/i18n/en.json b/i18n/en.json index 99d9c49..c578918 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -295,6 +295,13 @@ "public.unsubbedInfo": "You have unsubscribed successfully.", "public.unsubbedTitle": "Unsubscribed", "public.unsubscribeTitle": "Unsubscribe from mailing list", + "settings.appearance.adminHelp": "Custom CSS to apply to the admin UI.", + "settings.appearance.adminName": "Admin", + "settings.appearance.customCSS": "Custom CSS", + "settings.appearance.customJS": "Custom JavaScript", + "settings.appearance.name": "Appearance", + "settings.appearance.publicHelp": "Custom CSS and JavaScript to apply to the public pages.", + "settings.appearance.publicName": "Public", "settings.bounces.action": "Action", "settings.bounces.blocklist": "Blocklist", "settings.bounces.count": "Bounce count", diff --git a/i18n/es.json b/i18n/es.json index 3abcf8b..865922d 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -295,6 +295,13 @@ "public.unsubbedInfo": "Ud. se ha des-subscrito de forma satisfactoria", "public.unsubbedTitle": "Des-subscrito.", "public.unsubscribeTitle": "Des-subscribirse de una lista de correo", + "settings.appearance.adminHelp": "Custom CSS to apply to the admin UI.", + "settings.appearance.adminName": "Admin", + "settings.appearance.customCSS": "Custom CSS", + "settings.appearance.customJS": "Custom JavaScript", + "settings.appearance.name": "Appearance", + "settings.appearance.publicHelp": "Custom CSS and JavaScript to apply to the public pages.", + "settings.appearance.publicName": "Public", "settings.bounces.action": "Acción", "settings.bounces.blocklist": "Lista de bloqueo", "settings.bounces.count": "Conteo de rebotes", diff --git a/i18n/fr.json b/i18n/fr.json index e99a583..9d370db 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -295,6 +295,13 @@ "public.unsubbedInfo": "Vous vous êtes désabonné·e avec succès.", "public.unsubbedTitle": "Désabonné·e", "public.unsubscribeTitle": "Se désabonner de la liste de diffusion", + "settings.appearance.adminHelp": "Custom CSS to apply to the admin UI.", + "settings.appearance.adminName": "Admin", + "settings.appearance.customCSS": "Custom CSS", + "settings.appearance.customJS": "Custom JavaScript", + "settings.appearance.name": "Appearance", + "settings.appearance.publicHelp": "Custom CSS and JavaScript to apply to the public pages.", + "settings.appearance.publicName": "Public", "settings.bounces.action": "Action", "settings.bounces.blocklist": "Liste de bloquage", "settings.bounces.count": "Comptage des rebonds", diff --git a/i18n/hu.json b/i18n/hu.json index 9e76551..fbf258c 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -295,6 +295,13 @@ "public.unsubbedInfo": "Sikeresen leiratkozott.", "public.unsubbedTitle": "Leiratkozott", "public.unsubscribeTitle": "Leiratkozás a levelezőlistáról", + "settings.appearance.adminHelp": "Custom CSS to apply to the admin UI.", + "settings.appearance.adminName": "Admin", + "settings.appearance.customCSS": "Custom CSS", + "settings.appearance.customJS": "Custom JavaScript", + "settings.appearance.name": "Appearance", + "settings.appearance.publicHelp": "Custom CSS and JavaScript to apply to the public pages.", + "settings.appearance.publicName": "Public", "settings.bounces.action": "Action", "settings.bounces.blocklist": "Tiltólista", "settings.bounces.count": "Visszapattanások száma", diff --git a/i18n/it.json b/i18n/it.json index d55d4f3..4c96d37 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -295,6 +295,13 @@ "public.unsubbedInfo": "La cancellazione è avvenuta con successo.", "public.unsubbedTitle": "Iscrizione annullata", "public.unsubscribeTitle": "Cancella l'iscrizione dalla newsletter", + "settings.appearance.adminHelp": "Custom CSS to apply to the admin UI.", + "settings.appearance.adminName": "Admin", + "settings.appearance.customCSS": "Custom CSS", + "settings.appearance.customJS": "Custom JavaScript", + "settings.appearance.name": "Appearance", + "settings.appearance.publicHelp": "Custom CSS and JavaScript to apply to the public pages.", + "settings.appearance.publicName": "Public", "settings.bounces.action": "Action", "settings.bounces.blocklist": "Blocklist", "settings.bounces.count": "Bounce count", diff --git a/i18n/ml.json b/i18n/ml.json index f72e863..d468ced 100644 --- a/i18n/ml.json +++ b/i18n/ml.json @@ -295,6 +295,13 @@ "public.unsubbedInfo": "നിങ്ങൾ വരിക്കാരനല്ലാതായി", "public.unsubbedTitle": "വരിക്കാരനല്ലാതാകുക", "public.unsubscribeTitle": "മെയിലിങ് ലിസ്റ്റിന്റെ വരിക്കാരനല്ലാതാകുക", + "settings.appearance.adminHelp": "Custom CSS to apply to the admin UI.", + "settings.appearance.adminName": "Admin", + "settings.appearance.customCSS": "Custom CSS", + "settings.appearance.customJS": "Custom JavaScript", + "settings.appearance.name": "Appearance", + "settings.appearance.publicHelp": "Custom CSS and JavaScript to apply to the public pages.", + "settings.appearance.publicName": "Public", "settings.bounces.action": "Action", "settings.bounces.blocklist": "Blocklist", "settings.bounces.count": "Bounce count", diff --git a/i18n/nl.json b/i18n/nl.json index 1b20a9a..961ca9d 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -295,6 +295,13 @@ "public.unsubbedInfo": "Je bent met succes uitgeschreven.", "public.unsubbedTitle": "Uitgeschreven", "public.unsubscribeTitle": "Uitschrijven van mailinglijst", + "settings.appearance.adminHelp": "Custom CSS to apply to the admin UI.", + "settings.appearance.adminName": "Admin", + "settings.appearance.customCSS": "Custom CSS", + "settings.appearance.customJS": "Custom JavaScript", + "settings.appearance.name": "Appearance", + "settings.appearance.publicHelp": "Custom CSS and JavaScript to apply to the public pages.", + "settings.appearance.publicName": "Public", "settings.bounces.action": "Actie", "settings.bounces.blocklist": "Geblokkeerd", "settings.bounces.count": "Aantal bounces", diff --git a/i18n/pl.json b/i18n/pl.json index 401cd7e..3305a4d 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -295,6 +295,13 @@ "public.unsubbedInfo": "Pomyślnie odsubskrybowano", "public.unsubbedTitle": "Odsubskrybowano", "public.unsubscribeTitle": "Wypisz się z listy mailingowej", + "settings.appearance.adminHelp": "Custom CSS to apply to the admin UI.", + "settings.appearance.adminName": "Admin", + "settings.appearance.customCSS": "Custom CSS", + "settings.appearance.customJS": "Custom JavaScript", + "settings.appearance.name": "Appearance", + "settings.appearance.publicHelp": "Custom CSS and JavaScript to apply to the public pages.", + "settings.appearance.publicName": "Public", "settings.bounces.action": "Akcja", "settings.bounces.blocklist": "Lista zablokowanych", "settings.bounces.count": "Liczba odbić", diff --git a/i18n/pt-BR.json b/i18n/pt-BR.json index e75bfc5..0351313 100644 --- a/i18n/pt-BR.json +++ b/i18n/pt-BR.json @@ -295,6 +295,13 @@ "public.unsubbedInfo": "Você cancelou a inscrição com sucesso.", "public.unsubbedTitle": "Inscrição cancelada", "public.unsubscribeTitle": "Cancelar inscrição na lista de e-mails", + "settings.appearance.adminHelp": "Custom CSS to apply to the admin UI.", + "settings.appearance.adminName": "Admin", + "settings.appearance.customCSS": "Custom CSS", + "settings.appearance.customJS": "Custom JavaScript", + "settings.appearance.name": "Appearance", + "settings.appearance.publicHelp": "Custom CSS and JavaScript to apply to the public pages.", + "settings.appearance.publicName": "Public", "settings.bounces.action": "Action", "settings.bounces.blocklist": "Blocklist", "settings.bounces.count": "Bounce count", diff --git a/i18n/pt.json b/i18n/pt.json index e11cb33..3137edb 100644 --- a/i18n/pt.json +++ b/i18n/pt.json @@ -295,6 +295,13 @@ "public.unsubbedInfo": "A sua subscrição foi cancelada com sucesso.", "public.unsubbedTitle": "Subscrição cancelada", "public.unsubscribeTitle": "Cancelar subscrição da lista de emails", + "settings.appearance.adminHelp": "Custom CSS to apply to the admin UI.", + "settings.appearance.adminName": "Admin", + "settings.appearance.customCSS": "Custom CSS", + "settings.appearance.customJS": "Custom JavaScript", + "settings.appearance.name": "Appearance", + "settings.appearance.publicHelp": "Custom CSS and JavaScript to apply to the public pages.", + "settings.appearance.publicName": "Public", "settings.bounces.action": "Action", "settings.bounces.blocklist": "Blocklist", "settings.bounces.count": "Bounce count", diff --git a/i18n/ro.json b/i18n/ro.json index a629fc9..e3e319d 100644 --- a/i18n/ro.json +++ b/i18n/ro.json @@ -295,6 +295,13 @@ "public.unsubbedInfo": "Te-ai dezabonat cu succes.", "public.unsubbedTitle": "Dezabonat", "public.unsubscribeTitle": "Dezabonează-te de la lista de discuții", + "settings.appearance.adminHelp": "Custom CSS to apply to the admin UI.", + "settings.appearance.adminName": "Admin", + "settings.appearance.customCSS": "Custom CSS", + "settings.appearance.customJS": "Custom JavaScript", + "settings.appearance.name": "Appearance", + "settings.appearance.publicHelp": "Custom CSS and JavaScript to apply to the public pages.", + "settings.appearance.publicName": "Public", "settings.bounces.action": "Acțiune", "settings.bounces.blocklist": "Lista de blocare", "settings.bounces.count": "Numarul de respingeri", diff --git a/i18n/ru.json b/i18n/ru.json index 1c40fc3..9f6d473 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -295,6 +295,13 @@ "public.unsubbedInfo": "Вы были отписаны.", "public.unsubbedTitle": "Отписано", "public.unsubscribeTitle": "Отписаться от списков рассылки", + "settings.appearance.adminHelp": "Custom CSS to apply to the admin UI.", + "settings.appearance.adminName": "Admin", + "settings.appearance.customCSS": "Custom CSS", + "settings.appearance.customJS": "Custom JavaScript", + "settings.appearance.name": "Appearance", + "settings.appearance.publicHelp": "Custom CSS and JavaScript to apply to the public pages.", + "settings.appearance.publicName": "Public", "settings.bounces.action": "Action", "settings.bounces.blocklist": "Blocklist", "settings.bounces.count": "Bounce count", diff --git a/i18n/tr.json b/i18n/tr.json index e9adbf0..9770280 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -295,6 +295,13 @@ "public.unsubbedInfo": "Başarı ile üyeliğinizi bitirdiniz.", "public.unsubbedTitle": "Üyelik bitirildi.", "public.unsubscribeTitle": "e-posta listesi üyeliğini bitir", + "settings.appearance.adminHelp": "Custom CSS to apply to the admin UI.", + "settings.appearance.adminName": "Admin", + "settings.appearance.customCSS": "Custom CSS", + "settings.appearance.customJS": "Custom JavaScript", + "settings.appearance.name": "Appearance", + "settings.appearance.publicHelp": "Custom CSS and JavaScript to apply to the public pages.", + "settings.appearance.publicName": "Public", "settings.bounces.action": "Action", "settings.bounces.blocklist": "Blocklist", "settings.bounces.count": "Bounce count", diff --git a/internal/migrations/v2.1.0.go b/internal/migrations/v2.1.0.go new file mode 100644 index 0000000..a15f03b --- /dev/null +++ b/internal/migrations/v2.1.0.go @@ -0,0 +1,23 @@ +package migrations + +import ( + "github.com/jmoiron/sqlx" + "github.com/knadh/koanf" + "github.com/knadh/stuffbin" +) + +// V2_1_0 performs the DB migrations for v.2.1.0. +func V2_1_0(db *sqlx.DB, fs stuffbin.FileSystem, ko *koanf.Koanf) error { + if _, err := db.Exec(` + INSERT INTO settings (key, value) VALUES + ('appearance.admin.custom_css', '""'), + ('appearance.admin.custom_js', '""'), + ('appearance.public.custom_css', '""'), + ('appearance.public.custom_js', '""') + ON CONFLICT DO NOTHING; + `); err != nil { + return err + } + + return nil +} diff --git a/schema.sql b/schema.sql index 0029e06..b64a1db 100644 --- a/schema.sql +++ b/schema.sql @@ -218,7 +218,11 @@ INSERT INTO settings (key, value) VALUES ('bounce.sendgrid_enabled', 'false'), ('bounce.sendgrid_key', '""'), ('bounce.mailboxes', - '[{"enabled":false, "type": "pop", "host":"pop.yoursite.com","port":995,"auth_protocol":"userpass","username":"username","password":"password","return_path": "bounce@listmonk.yoursite.com","scan_interval":"15m","tls_enabled":true,"tls_skip_verify":false}]'); + '[{"enabled":false, "type": "pop", "host":"pop.yoursite.com","port":995,"auth_protocol":"userpass","username":"username","password":"password","return_path": "bounce@listmonk.yoursite.com","scan_interval":"15m","tls_enabled":true,"tls_skip_verify":false}]'), + ('appearance.admin.custom_css', '""'), + ('appearance.admin.custom_js', '""'), + ('appearance.public.custom_css', '""'), + ('appearance.public.custom_js', '""'); -- bounces DROP TABLE IF EXISTS bounces CASCADE; diff --git a/static/public/templates/index.html b/static/public/templates/index.html index 507476c..d310d92 100644 --- a/static/public/templates/index.html +++ b/static/public/templates/index.html @@ -7,6 +7,8 @@ + + {{ if ne .FaviconURL "" }}