aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/database/migrations.go5
-rw-r--r--internal/locale/translations/de_DE.json1
-rw-r--r--internal/locale/translations/el_EL.json1
-rw-r--r--internal/locale/translations/en_US.json1
-rw-r--r--internal/locale/translations/es_ES.json1
-rw-r--r--internal/locale/translations/fi_FI.json1
-rw-r--r--internal/locale/translations/fr_FR.json1
-rw-r--r--internal/locale/translations/hi_IN.json1
-rw-r--r--internal/locale/translations/id_ID.json1
-rw-r--r--internal/locale/translations/it_IT.json1
-rw-r--r--internal/locale/translations/ja_JP.json1
-rw-r--r--internal/locale/translations/nl_NL.json1
-rw-r--r--internal/locale/translations/pl_PL.json1
-rw-r--r--internal/locale/translations/pt_BR.json1
-rw-r--r--internal/locale/translations/ru_RU.json1
-rw-r--r--internal/locale/translations/tr_TR.json1
-rw-r--r--internal/locale/translations/uk_UA.json1
-rw-r--r--internal/locale/translations/zh_CN.json1
-rw-r--r--internal/locale/translations/zh_TW.json1
-rw-r--r--internal/model/user.go6
-rw-r--r--internal/storage/user.go69
-rw-r--r--internal/template/functions.go3
-rw-r--r--internal/template/templates/common/layout.html13
-rw-r--r--internal/template/templates/views/settings.html3
-rw-r--r--internal/ui/form/settings.go3
-rw-r--r--internal/ui/settings_show.go1
26 files changed, 89 insertions, 32 deletions
diff --git a/internal/database/migrations.go b/internal/database/migrations.go
index f7c2bd06..2c0939da 100644
--- a/internal/database/migrations.go
+++ b/internal/database/migrations.go
@@ -942,4 +942,9 @@ var migrations = []func(tx *sql.Tx) error{
_, err = tx.Exec(sql)
return err
},
+ func(tx *sql.Tx) (err error) {
+ sql := `ALTER TABLE users ADD COLUMN custom_js text not null default '';`
+ _, err = tx.Exec(sql)
+ return err
+ },
}
diff --git a/internal/locale/translations/de_DE.json b/internal/locale/translations/de_DE.json
index c8debd4e..82c508ee 100644
--- a/internal/locale/translations/de_DE.json
+++ b/internal/locale/translations/de_DE.json
@@ -391,6 +391,7 @@
"form.prefs.label.gesture_nav": "Geste zum Navigieren zwischen Einträgen",
"form.prefs.label.show_reading_time": "Geschätzte Lesezeit für Artikel anzeigen",
"form.prefs.label.custom_css": "Benutzerdefiniertes CSS",
+ "form.prefs.label.custom_js": "Benutzerdefiniertes JS",
"form.prefs.label.entry_order": "Artikel-Sortierspalte",
"form.prefs.label.default_home_page": "Standard-Startseite",
"form.prefs.label.categories_sorting_order": "Kategorie-Sortierung",
diff --git a/internal/locale/translations/el_EL.json b/internal/locale/translations/el_EL.json
index d9e6d9d5..5f805546 100644
--- a/internal/locale/translations/el_EL.json
+++ b/internal/locale/translations/el_EL.json
@@ -391,6 +391,7 @@
"form.prefs.label.gesture_nav": "Χειρονομία για πλοήγηση μεταξύ των καταχωρήσεων",
"form.prefs.label.show_reading_time": "Εμφάνιση εκτιμώμενου χρόνου ανάγνωσης για άρθρα",
"form.prefs.label.custom_css": "Προσαρμοσμένο CSS",
+ "form.prefs.label.custom_js": "Προσαρμοσμένο JS",
"form.prefs.label.entry_order": "Στήλη ταξινόμησης εισόδου",
"form.prefs.label.default_home_page": "Προεπιλεγμένη αρχική σελίδα",
"form.prefs.label.categories_sorting_order": "Ταξινόμηση κατηγοριών",
diff --git a/internal/locale/translations/en_US.json b/internal/locale/translations/en_US.json
index 1af4ed0b..8c7f65ae 100644
--- a/internal/locale/translations/en_US.json
+++ b/internal/locale/translations/en_US.json
@@ -391,6 +391,7 @@
"form.prefs.label.gesture_nav": "Gesture to navigate between entries",
"form.prefs.label.show_reading_time": "Show estimated reading time for entries",
"form.prefs.label.custom_css": "Custom CSS",
+ "form.prefs.label.custom_js": "Custom JS",
"form.prefs.label.entry_order": "Entry sorting column",
"form.prefs.label.default_home_page": "Default home page",
"form.prefs.label.categories_sorting_order": "Categories sorting",
diff --git a/internal/locale/translations/es_ES.json b/internal/locale/translations/es_ES.json
index d57b77d7..1e76897e 100644
--- a/internal/locale/translations/es_ES.json
+++ b/internal/locale/translations/es_ES.json
@@ -391,6 +391,7 @@
"form.prefs.label.gesture_nav": "Gesto para navegar entre entradas",
"form.prefs.label.show_reading_time": "Mostrar el tiempo estimado de lectura de los artículos",
"form.prefs.label.custom_css": "CSS personalizado",
+ "form.prefs.label.custom_js": "JS personalizado",
"form.prefs.label.entry_order": "Columna de clasificación de artículos",
"form.prefs.label.default_home_page": "Página de inicio por defecto",
"form.prefs.label.categories_sorting_order": "Clasificación por categorías",
diff --git a/internal/locale/translations/fi_FI.json b/internal/locale/translations/fi_FI.json
index 533b7c87..e543dfd3 100644
--- a/internal/locale/translations/fi_FI.json
+++ b/internal/locale/translations/fi_FI.json
@@ -391,6 +391,7 @@
"form.prefs.label.gesture_nav": "Ele siirtyäksesi merkintöjen välillä",
"form.prefs.label.show_reading_time": "Näytä artikkeleiden arvioitu lukuaika",
"form.prefs.label.custom_css": "Mukautettu CSS",
+ "form.prefs.label.custom_js": "Mukautettu JS",
"form.prefs.label.entry_order": "Lajittele sarakkeen mukaan",
"form.prefs.label.default_home_page": "Oletusarvoinen etusivu",
"form.prefs.label.categories_sorting_order": "Kategorioiden lajittelu",
diff --git a/internal/locale/translations/fr_FR.json b/internal/locale/translations/fr_FR.json
index 9d34bc92..d54e689c 100644
--- a/internal/locale/translations/fr_FR.json
+++ b/internal/locale/translations/fr_FR.json
@@ -391,6 +391,7 @@
"form.prefs.label.gesture_nav": "Geste pour naviguer entre les entrées",
"form.prefs.label.show_reading_time": "Afficher le temps de lecture estimé des articles",
"form.prefs.label.custom_css": "Feuille de style personnalisée",
+ "form.prefs.label.custom_js": "Script personnalisée",
"form.prefs.label.entry_order": "Colonne de tri des entrées",
"form.prefs.label.default_home_page": "Page d'accueil par défaut",
"form.prefs.label.categories_sorting_order": "Colonne de tri des catégories",
diff --git a/internal/locale/translations/hi_IN.json b/internal/locale/translations/hi_IN.json
index 37b225eb..026f3e4c 100644
--- a/internal/locale/translations/hi_IN.json
+++ b/internal/locale/translations/hi_IN.json
@@ -391,6 +391,7 @@
"form.prefs.label.gesture_nav": "प्रविष्टियों के बीच नेविगेट करने के लिए इशारा",
"form.prefs.label.show_reading_time": "विषय के लिए अनुमानित पढ़ने का समय दिखाएं",
"form.prefs.label.custom_css": "कस्टम सीएसएस",
+ "form.prefs.label.custom_js": "कस्टम जेएस",
"form.prefs.label.entry_order": "प्रवेश छँटाई कॉलम",
"form.prefs.label.default_home_page": "डिफ़ॉल्ट होमपेज़",
"form.prefs.label.categories_sorting_order": "श्रेणियाँ छँटाई",
diff --git a/internal/locale/translations/id_ID.json b/internal/locale/translations/id_ID.json
index 540c8344..75cef1fc 100644
--- a/internal/locale/translations/id_ID.json
+++ b/internal/locale/translations/id_ID.json
@@ -381,6 +381,7 @@
"form.prefs.label.gesture_nav": "Isyarat untuk menavigasi antar entri",
"form.prefs.label.show_reading_time": "Tampilkan perkiraan waktu baca untuk artikel",
"form.prefs.label.custom_css": "Modifikasi CSS",
+ "form.prefs.label.custom_js": "Modifikasi JS",
"form.prefs.label.entry_order": "Pengurutan Kolom Entri",
"form.prefs.label.default_home_page": "Beranda Baku",
"form.prefs.label.categories_sorting_order": "Pengurutan Kategori",
diff --git a/internal/locale/translations/it_IT.json b/internal/locale/translations/it_IT.json
index 5d63868f..930b80ba 100644
--- a/internal/locale/translations/it_IT.json
+++ b/internal/locale/translations/it_IT.json
@@ -391,6 +391,7 @@
"form.prefs.label.gesture_nav": "Gesto per navigare tra le voci",
"form.prefs.label.show_reading_time": "Mostra il tempo di lettura stimato per gli articoli",
"form.prefs.label.custom_css": "CSS personalizzati",
+ "form.prefs.label.custom_js": "JS personalizzati",
"form.prefs.label.entry_order": "Colonna di ordinamento delle voci",
"form.prefs.label.default_home_page": "Pagina iniziale predefinita",
"form.prefs.label.categories_sorting_order": "Ordinamento delle categorie",
diff --git a/internal/locale/translations/ja_JP.json b/internal/locale/translations/ja_JP.json
index 0c3ce2f5..ad4cceee 100644
--- a/internal/locale/translations/ja_JP.json
+++ b/internal/locale/translations/ja_JP.json
@@ -381,6 +381,7 @@
"form.prefs.label.gesture_nav": "エントリ間を移動するジェスチャー",
"form.prefs.label.show_reading_time": "記事の推定読書時間を表示する",
"form.prefs.label.custom_css": "カスタム CSS",
+ "form.prefs.label.custom_js": "カスタム JS",
"form.prefs.label.entry_order": "記事の表示順の基準",
"form.prefs.label.default_home_page": "デフォルトのトップページ",
"form.prefs.label.categories_sorting_order": "カテゴリの表示順",
diff --git a/internal/locale/translations/nl_NL.json b/internal/locale/translations/nl_NL.json
index 49503c8a..0e278c1a 100644
--- a/internal/locale/translations/nl_NL.json
+++ b/internal/locale/translations/nl_NL.json
@@ -391,6 +391,7 @@
"form.prefs.label.gesture_nav": "Gebaar om tussen artikelen te navigeren",
"form.prefs.label.show_reading_time": "Toon geschatte leestijd van artikelen",
"form.prefs.label.custom_css": "Aangepaste CSS",
+ "form.prefs.label.custom_js": "Aangepaste JS",
"form.prefs.label.entry_order": "Artikelen sorteren",
"form.prefs.label.default_home_page": "Startpagina",
"form.prefs.label.categories_sorting_order": "Volgorde categorieën",
diff --git a/internal/locale/translations/pl_PL.json b/internal/locale/translations/pl_PL.json
index 54a46fe9..0dd6ae04 100644
--- a/internal/locale/translations/pl_PL.json
+++ b/internal/locale/translations/pl_PL.json
@@ -401,6 +401,7 @@
"form.prefs.select.tap": "Podwójne wciśnięcie",
"form.prefs.select.swipe": "Trzepnąć",
"form.prefs.label.custom_css": "Niestandardowy CSS",
+ "form.prefs.label.custom_js": "Niestandardowy JS",
"form.prefs.label.entry_order": "Kolumna sortowania wpisów",
"form.prefs.label.default_home_page": "Domyślna strona główna",
"form.prefs.label.categories_sorting_order": "Sortowanie kategorii",
diff --git a/internal/locale/translations/pt_BR.json b/internal/locale/translations/pt_BR.json
index ab9a7bb5..0e10d728 100644
--- a/internal/locale/translations/pt_BR.json
+++ b/internal/locale/translations/pt_BR.json
@@ -391,6 +391,7 @@
"form.prefs.label.gesture_nav": "Gesto para navegar entre as entradas",
"form.prefs.label.show_reading_time": "Mostrar tempo estimado de leitura de artigos",
"form.prefs.label.custom_css": "CSS customizado",
+ "form.prefs.label.custom_js": "JS customizado",
"form.prefs.label.entry_order": "Coluna de Ordenação de Entrada",
"form.prefs.label.default_home_page": "Página inicial predefinida",
"form.prefs.label.categories_sorting_order": "Classificação das categorias",
diff --git a/internal/locale/translations/ru_RU.json b/internal/locale/translations/ru_RU.json
index b3db3795..a040d879 100644
--- a/internal/locale/translations/ru_RU.json
+++ b/internal/locale/translations/ru_RU.json
@@ -401,6 +401,7 @@
"form.prefs.label.gesture_nav": "Жест для перехода между статьями",
"form.prefs.label.show_reading_time": "Показать примерное время чтения статей",
"form.prefs.label.custom_css": "Пользовательский CSS",
+ "form.prefs.label.custom_js": "Пользовательский JS",
"form.prefs.label.entry_order": "Столбец сортировки статей",
"form.prefs.label.default_home_page": "Домашняя страница по умолчанию",
"form.prefs.label.categories_sorting_order": "Сортировка категорий",
diff --git a/internal/locale/translations/tr_TR.json b/internal/locale/translations/tr_TR.json
index cd4c6ff4..c9f55c5d 100644
--- a/internal/locale/translations/tr_TR.json
+++ b/internal/locale/translations/tr_TR.json
@@ -293,6 +293,7 @@
"form.prefs.label.categories_sorting_order": "Kategori sıralaması",
"form.prefs.label.cjk_reading_speed": "Çince, Korece ve Japonca için okuma hızı (dakika başına karakter)",
"form.prefs.label.custom_css": "Özel CSS",
+ "form.prefs.label.custom_js": "Özel JS",
"form.prefs.label.default_home_page": "Varsayılan ana sayfa",
"form.prefs.label.default_reading_speed": "Diğer diller için okuma hızı (dakika başına kelime)",
"form.prefs.label.display_mode": "Progressive Web App (PWA) görüntüleme modu",
diff --git a/internal/locale/translations/uk_UA.json b/internal/locale/translations/uk_UA.json
index b1cacf37..91027cb7 100644
--- a/internal/locale/translations/uk_UA.json
+++ b/internal/locale/translations/uk_UA.json
@@ -401,6 +401,7 @@
"form.prefs.label.gesture_nav": "Жест для переходу між записами",
"form.prefs.label.show_reading_time": "Показувати приблизний час читання для записів",
"form.prefs.label.custom_css": "Спеціальний CSS",
+ "form.prefs.label.custom_js": "Спеціальний JS",
"form.prefs.label.entry_order": "Стовпець сортування записів",
"form.prefs.label.default_home_page": "Домашня сторінка за умовчанням",
"form.prefs.label.categories_sorting_order": "Сортування за категоріями",
diff --git a/internal/locale/translations/zh_CN.json b/internal/locale/translations/zh_CN.json
index efce46fb..2942847e 100644
--- a/internal/locale/translations/zh_CN.json
+++ b/internal/locale/translations/zh_CN.json
@@ -381,6 +381,7 @@
"form.prefs.label.gesture_nav": "在条目之间导航的手势",
"form.prefs.label.show_reading_time": "显示文章的预计阅读时间",
"form.prefs.label.custom_css": "自定义 CSS",
+ "form.prefs.label.custom_js": "自定义 JS",
"form.prefs.label.entry_order": "文章排序依据",
"form.prefs.label.default_home_page": "默认主页",
"form.prefs.label.categories_sorting_order": "分类排序",
diff --git a/internal/locale/translations/zh_TW.json b/internal/locale/translations/zh_TW.json
index cc7d480b..b9505aac 100644
--- a/internal/locale/translations/zh_TW.json
+++ b/internal/locale/translations/zh_TW.json
@@ -381,6 +381,7 @@
"form.prefs.label.gesture_nav": "在條目之間導航的手勢",
"form.prefs.label.show_reading_time": "顯示文章的預計閱讀時間",
"form.prefs.label.custom_css": "自定義 CSS",
+ "form.prefs.label.custom_js": "自定義 JS",
"form.prefs.label.entry_order": "文章排序依據",
"form.prefs.label.default_home_page": "預設主頁",
"form.prefs.label.categories_sorting_order": "分類排序",
diff --git a/internal/model/user.go b/internal/model/user.go
index 9a3428e9..ba14b99d 100644
--- a/internal/model/user.go
+++ b/internal/model/user.go
@@ -21,6 +21,7 @@ type User struct {
EntryDirection string `json:"entry_sorting_direction"`
EntryOrder string `json:"entry_sorting_order"`
Stylesheet string `json:"stylesheet"`
+ CustomJS string `json:"custom_js"`
GoogleID string `json:"google_id"`
OpenIDConnectID string `json:"openid_connect_id"`
EntriesPerPage int `json:"entries_per_page"`
@@ -60,6 +61,7 @@ type UserModificationRequest struct {
EntryDirection *string `json:"entry_sorting_direction"`
EntryOrder *string `json:"entry_sorting_order"`
Stylesheet *string `json:"stylesheet"`
+ CustomJS *string `json:"custom_js"`
GoogleID *string `json:"google_id"`
OpenIDConnectID *string `json:"openid_connect_id"`
EntriesPerPage *int `json:"entries_per_page"`
@@ -118,6 +120,10 @@ func (u *UserModificationRequest) Patch(user *User) {
user.Stylesheet = *u.Stylesheet
}
+ if u.CustomJS != nil {
+ user.CustomJS = *u.CustomJS
+ }
+
if u.GoogleID != nil {
user.GoogleID = *u.GoogleID
}
diff --git a/internal/storage/user.go b/internal/storage/user.go
index 94bf4b6d..43baf0e1 100644
--- a/internal/storage/user.go
+++ b/internal/storage/user.go
@@ -83,6 +83,7 @@ func (s *Storage) CreateUser(userCreationRequest *model.UserCreationRequest) (*m
entry_swipe,
gesture_nav,
stylesheet,
+ custom_js,
google_id,
openid_connect_id,
display_mode,
@@ -124,6 +125,7 @@ func (s *Storage) CreateUser(userCreationRequest *model.UserCreationRequest) (*m
&user.EntrySwipe,
&user.GestureNav,
&user.Stylesheet,
+ &user.CustomJS,
&user.GoogleID,
&user.OpenIDConnectID,
&user.DisplayMode,
@@ -184,21 +186,22 @@ func (s *Storage) UpdateUser(user *model.User) error {
entry_swipe=$11,
gesture_nav=$12,
stylesheet=$13,
- google_id=$14,
- openid_connect_id=$15,
- display_mode=$16,
- entry_order=$17,
- default_reading_speed=$18,
- cjk_reading_speed=$19,
- default_home_page=$20,
- categories_sorting_order=$21,
- mark_read_on_view=$22,
- mark_read_on_media_player_completion=$23,
- media_playback_rate=$24,
- block_filter_entry_rules=$25,
- keep_filter_entry_rules=$26
+ custom_js=$14,
+ google_id=$15,
+ openid_connect_id=$16,
+ display_mode=$17,
+ entry_order=$18,
+ default_reading_speed=$19,
+ cjk_reading_speed=$20,
+ default_home_page=$21,
+ categories_sorting_order=$22,
+ mark_read_on_view=$23,
+ mark_read_on_media_player_completion=$24,
+ media_playback_rate=$25,
+ block_filter_entry_rules=$26,
+ keep_filter_entry_rules=$27
WHERE
- id=$27
+ id=$28
`
_, err = s.db.Exec(
@@ -216,6 +219,7 @@ func (s *Storage) UpdateUser(user *model.User) error {
user.EntrySwipe,
user.GestureNav,
user.Stylesheet,
+ user.CustomJS,
user.GoogleID,
user.OpenIDConnectID,
user.DisplayMode,
@@ -249,21 +253,22 @@ func (s *Storage) UpdateUser(user *model.User) error {
entry_swipe=$10,
gesture_nav=$11,
stylesheet=$12,
- google_id=$13,
- openid_connect_id=$14,
- display_mode=$15,
- entry_order=$16,
- default_reading_speed=$17,
- cjk_reading_speed=$18,
- default_home_page=$19,
- categories_sorting_order=$20,
- mark_read_on_view=$21,
- mark_read_on_media_player_completion=$22,
- media_playback_rate=$23,
- block_filter_entry_rules=$24,
- keep_filter_entry_rules=$25
+ custom_js=$13,
+ google_id=$14,
+ openid_connect_id=$15,
+ display_mode=$16,
+ entry_order=$17,
+ default_reading_speed=$18,
+ cjk_reading_speed=$19,
+ default_home_page=$20,
+ categories_sorting_order=$21,
+ mark_read_on_view=$22,
+ mark_read_on_media_player_completion=$23,
+ media_playback_rate=$24,
+ block_filter_entry_rules=$25,
+ keep_filter_entry_rules=$26
WHERE
- id=$26
+ id=$27
`
_, err := s.db.Exec(
@@ -280,6 +285,7 @@ func (s *Storage) UpdateUser(user *model.User) error {
user.EntrySwipe,
user.GestureNav,
user.Stylesheet,
+ user.CustomJS,
user.GoogleID,
user.OpenIDConnectID,
user.DisplayMode,
@@ -332,6 +338,7 @@ func (s *Storage) UserByID(userID int64) (*model.User, error) {
gesture_nav,
last_login_at,
stylesheet,
+ custom_js,
google_id,
openid_connect_id,
display_mode,
@@ -371,6 +378,7 @@ func (s *Storage) UserByUsername(username string) (*model.User, error) {
gesture_nav,
last_login_at,
stylesheet,
+ custom_js,
google_id,
openid_connect_id,
display_mode,
@@ -410,6 +418,7 @@ func (s *Storage) UserByField(field, value string) (*model.User, error) {
gesture_nav,
last_login_at,
stylesheet,
+ custom_js,
google_id,
openid_connect_id,
display_mode,
@@ -456,6 +465,7 @@ func (s *Storage) UserByAPIKey(token string) (*model.User, error) {
u.gesture_nav,
u.last_login_at,
u.stylesheet,
+ u.custom_js,
u.google_id,
u.openid_connect_id,
u.display_mode,
@@ -496,6 +506,7 @@ func (s *Storage) fetchUser(query string, args ...interface{}) (*model.User, err
&user.GestureNav,
&user.LastLoginAt,
&user.Stylesheet,
+ &user.CustomJS,
&user.GoogleID,
&user.OpenIDConnectID,
&user.DisplayMode,
@@ -608,6 +619,7 @@ func (s *Storage) Users() (model.Users, error) {
gesture_nav,
last_login_at,
stylesheet,
+ custom_js,
google_id,
openid_connect_id,
display_mode,
@@ -649,6 +661,7 @@ func (s *Storage) Users() (model.Users, error) {
&user.GestureNav,
&user.LastLoginAt,
&user.Stylesheet,
+ &user.CustomJS,
&user.GoogleID,
&user.OpenIDConnectID,
&user.DisplayMode,
diff --git a/internal/template/functions.go b/internal/template/functions.go
index a084db86..3b088d20 100644
--- a/internal/template/functions.go
+++ b/internal/template/functions.go
@@ -56,6 +56,9 @@ func (f *funcMap) Map() template.FuncMap {
"safeCSS": func(str string) template.CSS {
return template.CSS(str)
},
+ "safeJS": func(str string) template.JS {
+ return template.JS(str)
+ },
"noescape": func(str string) template.HTML {
return template.HTML(str)
},
diff --git a/internal/template/templates/common/layout.html b/internal/template/templates/common/layout.html
index c8579ed0..55a6263f 100644
--- a/internal/template/templates/common/layout.html
+++ b/internal/template/templates/common/layout.html
@@ -34,10 +34,15 @@
<link rel="stylesheet" type="text/css" href="{{ route "stylesheet" "name" .theme "checksum" .theme_checksum }}">
- {{ if and .user .user.Stylesheet }}
- {{ $stylesheetNonce := nonce }}
- <meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src * data:; media-src *; frame-src *; style-src 'self' 'nonce-{{ $stylesheetNonce }}'; require-trusted-types-for 'script'; trusted-types ttpolicy;">
- <style nonce="{{ $stylesheetNonce }}">{{ .user.Stylesheet | safeCSS }}</style>
+ {{ if .user }}
+ {{ $cspNonce := nonce }}
+ <meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src * data:; media-src *; frame-src *; style-src 'self'{{ if .user.Stylesheet }} 'nonce-{{ $cspNonce }}'{{ end }}{{ if .user.CustomJS }}; script-src 'self' 'nonce-{{ $cspNonce }}'{{ end }}; require-trusted-types-for 'script'; trusted-types ttpolicy;">
+ {{ if .user.Stylesheet }}
+ <style nonce="{{ $cspNonce }}">{{ .user.Stylesheet | safeCSS }}</style>
+ {{ end }}
+ {{ if .user.CustomJS }}
+ <script type="module" nonce="{{ $cspNonce }}">{{ .user.CustomJS | safeJS }}</script>
+ {{ end }}
{{ else }}
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src * data:; media-src *; frame-src *; require-trusted-types-for 'script'; trusted-types ttpolicy;">
{{ end }}
diff --git a/internal/template/templates/views/settings.html b/internal/template/templates/views/settings.html
index 99ac5863..535c5a1a 100644
--- a/internal/template/templates/views/settings.html
+++ b/internal/template/templates/views/settings.html
@@ -210,6 +210,9 @@
<label for="form-custom-css">{{t "form.prefs.label.custom_css" }}</label>
<textarea id="form-custom-css" name="custom_css" cols="40" rows="10" spellcheck="false">{{ .form.CustomCSS }}</textarea>
+ <label for="form-custom-js">{{t "form.prefs.label.custom_js" }}</label>
+ <textarea id="form-custom-js" name="custom_js" cols="40" rows="10" spellcheck="false">{{ .form.CustomJS }}</textarea>
+
<div class="buttons">
<button type="submit" class="button button-primary" data-label-loading="{{ t "form.submit.saving" }}">{{ t "action.update" }}</button>
</div>
diff --git a/internal/ui/form/settings.go b/internal/ui/form/settings.go
index 8b79ba88..cf18bd2e 100644
--- a/internal/ui/form/settings.go
+++ b/internal/ui/form/settings.go
@@ -36,6 +36,7 @@ type SettingsForm struct {
KeyboardShortcuts bool
ShowReadingTime bool
CustomCSS string
+ CustomJS string
EntrySwipe bool
GestureNav string
DisplayMode string
@@ -99,6 +100,7 @@ func (s *SettingsForm) Merge(user *model.User) *model.User {
user.KeyboardShortcuts = s.KeyboardShortcuts
user.ShowReadingTime = s.ShowReadingTime
user.Stylesheet = s.CustomCSS
+ user.CustomJS = s.CustomJS
user.EntrySwipe = s.EntrySwipe
user.GestureNav = s.GestureNav
user.DisplayMode = s.DisplayMode
@@ -180,6 +182,7 @@ func NewSettingsForm(r *http.Request) *SettingsForm {
KeyboardShortcuts: r.FormValue("keyboard_shortcuts") == "1",
ShowReadingTime: r.FormValue("show_reading_time") == "1",
CustomCSS: r.FormValue("custom_css"),
+ CustomJS: r.FormValue("custom_js"),
EntrySwipe: r.FormValue("entry_swipe") == "1",
GestureNav: r.FormValue("gesture_nav"),
DisplayMode: r.FormValue("display_mode"),
diff --git a/internal/ui/settings_show.go b/internal/ui/settings_show.go
index 4cc79171..72e1f5ab 100644
--- a/internal/ui/settings_show.go
+++ b/internal/ui/settings_show.go
@@ -33,6 +33,7 @@ func (h *handler) showSettingsPage(w http.ResponseWriter, r *http.Request) {
KeyboardShortcuts: user.KeyboardShortcuts,
ShowReadingTime: user.ShowReadingTime,
CustomCSS: user.Stylesheet,
+ CustomJS: user.CustomJS,
EntrySwipe: user.EntrySwipe,
GestureNav: user.GestureNav,
DisplayMode: user.DisplayMode,