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 <sweetppro@users.noreply.github.com>
This commit is contained in:
Kailash Nadh 2021-12-18 15:38:42 +05:30
parent 920645f90e
commit fabe06e339
28 changed files with 280 additions and 5 deletions

View file

@ -55,6 +55,8 @@ func initHTTPHandlers(e *echo.Echo, app *App) {
return c.Render(http.StatusOK, "home", publicTpl{Title: "listmonk"}) return c.Render(http.StatusOK, "home", publicTpl{Title: "listmonk"})
}) })
g.GET(path.Join(adminRoot, ""), handleAdminPage) 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) g.GET(path.Join(adminRoot, "/*"), handleAdminPage)
// API endpoints. // API endpoints.
@ -142,6 +144,7 @@ func initHTTPHandlers(e *echo.Echo, app *App) {
e.POST("/webhooks/service/:service", handleBounceWebhook) e.POST("/webhooks/service/:service", handleBounceWebhook)
} }
// /public/static/* file server is registered in initHTTPServer().
// Public subscriber facing views. // Public subscriber facing views.
e.GET("/subscription/form", handleSubscriptionFormPage) e.GET("/subscription/form", handleSubscriptionFormPage)
e.POST("/subscription/form", handleSubscriptionForm) e.POST("/subscription/form", handleSubscriptionForm)
@ -161,6 +164,10 @@ func initHTTPHandlers(e *echo.Echo, app *App) {
"campUUID", "subUUID"))) "campUUID", "subUUID")))
e.GET("/campaign/:campUUID/:subUUID/px.png", noIndex(validateUUID(handleRegisterCampaignView, e.GET("/campaign/:campUUID/:subUUID/px.png", noIndex(validateUUID(handleRegisterCampaignView,
"campUUID", "subUUID"))) "campUUID", "subUUID")))
e.GET("/public/custom.css", serveCustomApperance("public.custom_css"))
e.GET("/public/custom.js", serveCustomApperance("public.custom_js"))
// Public health API endpoint. // Public health API endpoint.
e.GET("/health", handleHealthCheck) e.GET("/health", handleHealthCheck)
} }
@ -182,6 +189,39 @@ func handleHealthCheck(c echo.Context) error {
return c.JSON(http.StatusOK, okResp{true}) 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. // basicAuth middleware does an HTTP BasicAuth authentication for admin handlers.
func basicAuth(username, password string, c echo.Context) (bool, error) { func basicAuth(username, password string, c echo.Context) (bool, error) {
app := c.Get("app").(*App) app := c.Get("app").(*App)

View file

@ -68,6 +68,13 @@ type constants struct {
AdminUsername []byte `koanf:"admin_username"` AdminUsername []byte `koanf:"admin_username"`
AdminPassword []byte `koanf:"admin_password"` 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 UnsubURL string
LinkTrackURL string LinkTrackURL string
ViewTrackURL string ViewTrackURL string
@ -293,7 +300,10 @@ func initConstants() *constants {
lo.Fatalf("error loading app config: %v", err) lo.Fatalf("error loading app config: %v", err)
} }
if err := ko.Unmarshal("privacy", &c.Privacy); err != nil { 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, "/") c.RootURL = strings.TrimRight(c.RootURL, "/")
@ -622,7 +632,7 @@ func initHTTPServer(app *App) *echo.Echo {
fSrv := app.fs.FileServer() fSrv := app.fs.FileServer()
// Public (subscriber) facing static files. // Public (subscriber) facing static files.
srv.GET("/public/*", echo.WrapHandler(fSrv)) srv.GET("/public/static/*", echo.WrapHandler(fSrv))
// Admin (frontend) facing static files. // Admin (frontend) facing static files.
srv.GET("/admin/static/*", echo.WrapHandler(fSrv)) srv.GET("/admin/static/*", echo.WrapHandler(fSrv))

View file

@ -105,6 +105,11 @@ type settings struct {
TLSSkipVerify bool `json:"tls_skip_verify"` TLSSkipVerify bool `json:"tls_skip_verify"`
ScanInterval string `json:"scan_interval"` ScanInterval string `json:"scan_interval"`
} `json:"bounce.mailboxes"` } `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 ( var (

View file

@ -31,6 +31,7 @@ var migList = []migFunc{
{"v0.9.0", migrations.V0_9_0}, {"v0.9.0", migrations.V0_9_0},
{"v1.0.0", migrations.V1_0_0}, {"v1.0.0", migrations.V1_0_0},
{"v2.0.0", migrations.V2_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 // upgrade upgrades the database to the current version by running SQL migration files

View file

@ -5,6 +5,8 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" /> <meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>static/favicon.png" /> <link rel="icon" href="<%= BASE_URL %>static/favicon.png" />
<link href="<%= BASE_URL %>custom.css" rel="stylesheet" type="text/css">
<script src="<%= BASE_URL %>custom.js" async defer></script>
<title><%= htmlWebpackPlugin.options.title %></title> <title><%= htmlWebpackPlugin.options.title %></title>
</head> </head>
<body> <body>

View file

@ -802,6 +802,10 @@ section.analytics {
.box { .box {
margin-bottom: 30px; margin-bottom: 30px;
} }
.html-editor {
height: auto;
min-height: 350px;
}
} }
/* Logs */ /* Logs */

View file

@ -9,6 +9,10 @@ import { colors } from '../constants';
export default { export default {
props: { props: {
value: String, value: String,
language: {
type: String,
default: 'html',
},
disabled: Boolean, disabled: Boolean,
}, },
@ -38,7 +42,7 @@ export default {
this.$refs.htmlEditor.appendChild(el); this.$refs.htmlEditor.appendChild(el);
this.flask = new CodeFlask(el.shadowRoot.getElementById('area'), { this.flask = new CodeFlask(el.shadowRoot.getElementById('area'), {
language: 'html', language: this.$props.language,
lineNumbers: false, lineNumbers: false,
styleParent: el.shadowRoot, styleParent: el.shadowRoot,
readonly: this.disabled, readonly: this.disabled,

View file

@ -49,6 +49,10 @@
<b-tab-item :label="$t('settings.messengers.name')"> <b-tab-item :label="$t('settings.messengers.name')">
<messenger-settings :form="form" :key="key" /> <messenger-settings :form="form" :key="key" />
</b-tab-item><!-- messengers --> </b-tab-item><!-- messengers -->
<b-tab-item :label="$t('settings.appearance.name')">
<appearance-settings :form="form" :key="key" />
</b-tab-item><!-- appearance -->
</b-tabs> </b-tabs>
</section> </section>
@ -66,6 +70,7 @@ import MediaSettings from './settings/media.vue';
import SmtpSettings from './settings/smtp.vue'; import SmtpSettings from './settings/smtp.vue';
import BounceSettings from './settings/bounces.vue'; import BounceSettings from './settings/bounces.vue';
import MessengerSettings from './settings/messengers.vue'; import MessengerSettings from './settings/messengers.vue';
import AppearanceSettings from './settings/appearance.vue';
const dummyPassword = ' '.repeat(8); const dummyPassword = ' '.repeat(8);
@ -78,6 +83,7 @@ export default Vue.extend({
SmtpSettings, SmtpSettings,
BounceSettings, BounceSettings,
MessengerSettings, MessengerSettings,
AppearanceSettings,
}, },
data() { data() {

View file

@ -0,0 +1,66 @@
<template>
<div class="items">
<b-tabs :animated="false">
<b-tab-item :label="$t('settings.appearance.adminName')" label-position="on-border">
<div class="block">
{{ $t('settings.appearance.adminHelp') }}
</div>
<b-field :label="$t('settings.appearance.customCSS')" label-position="on-border">
<html-editor v-model="data['appearance.admin.custom_css']" name="body"
language="css" />
</b-field>
<b-field :label="$t('settings.appearance.customJS')" label-position="on-border">
<html-editor v-model="data['appearance.admin.custom_js']" name="body"
language="css" />
</b-field>
</b-tab-item><!-- admin -->
<b-tab-item :label="$t('settings.appearance.publicName')" label-position="on-border">
<div class="block">
{{ $t('settings.appearance.publicHelp') }}
</div>
<b-field :label="$t('settings.appearance.customCSS')" label-position="on-border">
<html-editor v-model="data['appearance.public.custom_css']" name="body"
language="css" />
</b-field>
<b-field :label="$t('settings.appearance.customJS')" label-position="on-border">
<html-editor v-model="data['appearance.public.custom_js']" name="body"
language="js" />
</b-field>
</b-tab-item><!-- public -->
</b-tabs>
</div>
</template>
<script>
import Vue from 'vue';
import { mapState } from 'vuex';
import HTMLEditor from '../../components/HTMLEditor.vue';
export default Vue.extend({
components: {
'html-editor': HTMLEditor,
},
props: {
form: {
type: Object,
},
},
data() {
return {
data: this.form,
};
},
computed: {
...mapState(['settings']),
},
});
</script>

View file

@ -29,7 +29,10 @@ module.exports = {
'^/$': { '^/$': {
target: process.env.LISTMONK_API_URL || 'http://127.0.0.1:9000' 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' target: process.env.LISTMONK_API_URL || 'http://127.0.0.1:9000'
} }
} }

View file

@ -295,6 +295,13 @@
"public.unsubbedInfo": "Odběr jste zrušili úspěšně.", "public.unsubbedInfo": "Odběr jste zrušili úspěšně.",
"public.unsubbedTitle": "Zrušen odběr", "public.unsubbedTitle": "Zrušen odběr",
"public.unsubscribeTitle": "Zrušit odběr ze seznamu adresátů", "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.action": "Akce",
"settings.bounces.blocklist": "Seznam blokovaných", "settings.bounces.blocklist": "Seznam blokovaných",
"settings.bounces.count": "Počet případů nedoručitelnosti", "settings.bounces.count": "Počet případů nedoručitelnosti",

View file

@ -295,6 +295,13 @@
"public.unsubbedInfo": "Du wurdest erfolgreich abgemeldet", "public.unsubbedInfo": "Du wurdest erfolgreich abgemeldet",
"public.unsubbedTitle": "Abgemeldet", "public.unsubbedTitle": "Abgemeldet",
"public.unsubscribeTitle": "Von E-Mail Liste abmelden.", "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.action": "Aktion",
"settings.bounces.blocklist": "Sperrliste", "settings.bounces.blocklist": "Sperrliste",
"settings.bounces.count": "Bounce Anzahl", "settings.bounces.count": "Bounce Anzahl",

View file

@ -295,6 +295,13 @@
"public.unsubbedInfo": "You have unsubscribed successfully.", "public.unsubbedInfo": "You have unsubscribed successfully.",
"public.unsubbedTitle": "Unsubscribed", "public.unsubbedTitle": "Unsubscribed",
"public.unsubscribeTitle": "Unsubscribe from mailing list", "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.action": "Action",
"settings.bounces.blocklist": "Blocklist", "settings.bounces.blocklist": "Blocklist",
"settings.bounces.count": "Bounce count", "settings.bounces.count": "Bounce count",

View file

@ -295,6 +295,13 @@
"public.unsubbedInfo": "Ud. se ha des-subscrito de forma satisfactoria", "public.unsubbedInfo": "Ud. se ha des-subscrito de forma satisfactoria",
"public.unsubbedTitle": "Des-subscrito.", "public.unsubbedTitle": "Des-subscrito.",
"public.unsubscribeTitle": "Des-subscribirse de una lista de correo", "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.action": "Acción",
"settings.bounces.blocklist": "Lista de bloqueo", "settings.bounces.blocklist": "Lista de bloqueo",
"settings.bounces.count": "Conteo de rebotes", "settings.bounces.count": "Conteo de rebotes",

View file

@ -295,6 +295,13 @@
"public.unsubbedInfo": "Vous vous êtes désabonné·e avec succès.", "public.unsubbedInfo": "Vous vous êtes désabonné·e avec succès.",
"public.unsubbedTitle": "Désabonné·e", "public.unsubbedTitle": "Désabonné·e",
"public.unsubscribeTitle": "Se désabonner de la liste de diffusion", "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.action": "Action",
"settings.bounces.blocklist": "Liste de bloquage", "settings.bounces.blocklist": "Liste de bloquage",
"settings.bounces.count": "Comptage des rebonds", "settings.bounces.count": "Comptage des rebonds",

View file

@ -295,6 +295,13 @@
"public.unsubbedInfo": "Sikeresen leiratkozott.", "public.unsubbedInfo": "Sikeresen leiratkozott.",
"public.unsubbedTitle": "Leiratkozott", "public.unsubbedTitle": "Leiratkozott",
"public.unsubscribeTitle": "Leiratkozás a levelezőlistáról", "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.action": "Action",
"settings.bounces.blocklist": "Tiltólista", "settings.bounces.blocklist": "Tiltólista",
"settings.bounces.count": "Visszapattanások száma", "settings.bounces.count": "Visszapattanások száma",

View file

@ -295,6 +295,13 @@
"public.unsubbedInfo": "La cancellazione è avvenuta con successo.", "public.unsubbedInfo": "La cancellazione è avvenuta con successo.",
"public.unsubbedTitle": "Iscrizione annullata", "public.unsubbedTitle": "Iscrizione annullata",
"public.unsubscribeTitle": "Cancella l'iscrizione dalla newsletter", "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.action": "Action",
"settings.bounces.blocklist": "Blocklist", "settings.bounces.blocklist": "Blocklist",
"settings.bounces.count": "Bounce count", "settings.bounces.count": "Bounce count",

View file

@ -295,6 +295,13 @@
"public.unsubbedInfo": "നിങ്ങൾ വരിക്കാരനല്ലാതായി", "public.unsubbedInfo": "നിങ്ങൾ വരിക്കാരനല്ലാതായി",
"public.unsubbedTitle": "വരിക്കാരനല്ലാതാകുക", "public.unsubbedTitle": "വരിക്കാരനല്ലാതാകുക",
"public.unsubscribeTitle": "മെയിലിങ് ലിസ്റ്റിന്റെ വരിക്കാരനല്ലാതാകുക", "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.action": "Action",
"settings.bounces.blocklist": "Blocklist", "settings.bounces.blocklist": "Blocklist",
"settings.bounces.count": "Bounce count", "settings.bounces.count": "Bounce count",

View file

@ -295,6 +295,13 @@
"public.unsubbedInfo": "Je bent met succes uitgeschreven.", "public.unsubbedInfo": "Je bent met succes uitgeschreven.",
"public.unsubbedTitle": "Uitgeschreven", "public.unsubbedTitle": "Uitgeschreven",
"public.unsubscribeTitle": "Uitschrijven van mailinglijst", "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.action": "Actie",
"settings.bounces.blocklist": "Geblokkeerd", "settings.bounces.blocklist": "Geblokkeerd",
"settings.bounces.count": "Aantal bounces", "settings.bounces.count": "Aantal bounces",

View file

@ -295,6 +295,13 @@
"public.unsubbedInfo": "Pomyślnie odsubskrybowano", "public.unsubbedInfo": "Pomyślnie odsubskrybowano",
"public.unsubbedTitle": "Odsubskrybowano", "public.unsubbedTitle": "Odsubskrybowano",
"public.unsubscribeTitle": "Wypisz się z listy mailingowej", "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.action": "Akcja",
"settings.bounces.blocklist": "Lista zablokowanych", "settings.bounces.blocklist": "Lista zablokowanych",
"settings.bounces.count": "Liczba odbić", "settings.bounces.count": "Liczba odbić",

View file

@ -295,6 +295,13 @@
"public.unsubbedInfo": "Você cancelou a inscrição com sucesso.", "public.unsubbedInfo": "Você cancelou a inscrição com sucesso.",
"public.unsubbedTitle": "Inscrição cancelada", "public.unsubbedTitle": "Inscrição cancelada",
"public.unsubscribeTitle": "Cancelar inscrição na lista de e-mails", "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.action": "Action",
"settings.bounces.blocklist": "Blocklist", "settings.bounces.blocklist": "Blocklist",
"settings.bounces.count": "Bounce count", "settings.bounces.count": "Bounce count",

View file

@ -295,6 +295,13 @@
"public.unsubbedInfo": "A sua subscrição foi cancelada com sucesso.", "public.unsubbedInfo": "A sua subscrição foi cancelada com sucesso.",
"public.unsubbedTitle": "Subscrição cancelada", "public.unsubbedTitle": "Subscrição cancelada",
"public.unsubscribeTitle": "Cancelar subscrição da lista de emails", "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.action": "Action",
"settings.bounces.blocklist": "Blocklist", "settings.bounces.blocklist": "Blocklist",
"settings.bounces.count": "Bounce count", "settings.bounces.count": "Bounce count",

View file

@ -295,6 +295,13 @@
"public.unsubbedInfo": "Te-ai dezabonat cu succes.", "public.unsubbedInfo": "Te-ai dezabonat cu succes.",
"public.unsubbedTitle": "Dezabonat", "public.unsubbedTitle": "Dezabonat",
"public.unsubscribeTitle": "Dezabonează-te de la lista de discuții", "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.action": "Acțiune",
"settings.bounces.blocklist": "Lista de blocare", "settings.bounces.blocklist": "Lista de blocare",
"settings.bounces.count": "Numarul de respingeri", "settings.bounces.count": "Numarul de respingeri",

View file

@ -295,6 +295,13 @@
"public.unsubbedInfo": "Вы были отписаны.", "public.unsubbedInfo": "Вы были отписаны.",
"public.unsubbedTitle": "Отписано", "public.unsubbedTitle": "Отписано",
"public.unsubscribeTitle": "Отписаться от списков рассылки", "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.action": "Action",
"settings.bounces.blocklist": "Blocklist", "settings.bounces.blocklist": "Blocklist",
"settings.bounces.count": "Bounce count", "settings.bounces.count": "Bounce count",

View file

@ -295,6 +295,13 @@
"public.unsubbedInfo": "Başarı ile üyeliğinizi bitirdiniz.", "public.unsubbedInfo": "Başarı ile üyeliğinizi bitirdiniz.",
"public.unsubbedTitle": "Üyelik bitirildi.", "public.unsubbedTitle": "Üyelik bitirildi.",
"public.unsubscribeTitle": "e-posta listesi üyeliğini bitir", "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.action": "Action",
"settings.bounces.blocklist": "Blocklist", "settings.bounces.blocklist": "Blocklist",
"settings.bounces.count": "Bounce count", "settings.bounces.count": "Bounce count",

View file

@ -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
}

View file

@ -218,7 +218,11 @@ INSERT INTO settings (key, value) VALUES
('bounce.sendgrid_enabled', 'false'), ('bounce.sendgrid_enabled', 'false'),
('bounce.sendgrid_key', '""'), ('bounce.sendgrid_key', '""'),
('bounce.mailboxes', ('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 -- bounces
DROP TABLE IF EXISTS bounces CASCADE; DROP TABLE IF EXISTS bounces CASCADE;

View file

@ -7,6 +7,8 @@
<meta name="description" content="{{ .Data.Description }}" /> <meta name="description" content="{{ .Data.Description }}" />
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<link href="/public/static/style.css" rel="stylesheet" type="text/css" /> <link href="/public/static/style.css" rel="stylesheet" type="text/css" />
<link href="/public/custom.css" rel="stylesheet" type="text/css">
<script src="/public/custom.js" async defer></script>
{{ if ne .FaviconURL "" }} {{ if ne .FaviconURL "" }}
<link rel="shortcut icon" href="{{ .FaviconURL }}" type="image/x-icon" /> <link rel="shortcut icon" href="{{ .FaviconURL }}" type="image/x-icon" />