From f54fbd057ebb8d1e9631782b6d182b403598648f Mon Sep 17 00:00:00 2001 From: psvenk <45520974+psvenk@users.noreply.github.com> Date: Thu, 15 Aug 2019 16:29:55 +0000 Subject: [PATCH] Add prefers-color-scheme support (#601) * Add prefers-color-scheme support This should fix . The cookie storage format has been changed from boolean ("true"/"false") to tri-state ("dark"/"light"/""), so that users without a cookie set will get dark mode if they have enabled the dark theme in their operating system. The code for handling the cookie state, along with the user's operating system theme, has been factored out into a new function `update_mode`, which is called both at window load and at the "storage" event listener, because the "storage" event listener is only trigerred when a change is made to the localStorage from another tab/window (for more info - see ). --- assets/js/themes.js | 37 ++++++++++--- locales/ar.json | 4 ++ locales/de.json | 4 ++ locales/el.json | 4 ++ locales/en-US.json | 4 ++ locales/eo.json | 4 ++ locales/es.json | 4 ++ locales/eu.json | 4 ++ locales/fr.json | 4 ++ locales/is.json | 4 ++ locales/it.json | 4 ++ locales/nb_NO.json | 4 ++ locales/nl.json | 4 ++ locales/pl.json | 4 ++ locales/ru.json | 4 ++ locales/uk.json | 4 ++ locales/zh-CN.json | 4 ++ src/invidious.cr | 30 ++++++++--- src/invidious/helpers/helpers.cr | 74 ++++++++++++++++++++++++-- src/invidious/helpers/patch_mapping.cr | 2 +- src/invidious/helpers/utils.cr | 13 +++++ src/invidious/users.cr | 62 ++------------------- src/invidious/views/preferences.ecr | 8 ++- src/invidious/views/template.ecr | 9 ++-- 24 files changed, 215 insertions(+), 84 deletions(-) diff --git a/assets/js/themes.js b/assets/js/themes.js index 683aea397..90a05c36e 100644 --- a/assets/js/themes.js +++ b/assets/js/themes.js @@ -1,8 +1,8 @@ -var toggle_theme = document.getElementById('toggle_theme') +var toggle_theme = document.getElementById('toggle_theme'); toggle_theme.href = 'javascript:void(0);'; toggle_theme.addEventListener('click', function () { - var dark_mode = document.getElementById('dark_theme').media == 'none'; + var dark_mode = document.getElementById('dark_theme').media === 'none'; var url = '/toggle_theme?redirect=false'; var xhr = new XMLHttpRequest(); @@ -11,19 +11,24 @@ toggle_theme.addEventListener('click', function () { xhr.open('GET', url, true); set_mode(dark_mode); - localStorage.setItem('dark_mode', dark_mode); + window.localStorage.setItem('dark_mode', dark_mode ? 'dark' : 'light'); xhr.send(); }); window.addEventListener('storage', function (e) { - if (e.key == 'dark_mode') { - var dark_mode = e.newValue === 'true'; - set_mode(dark_mode); + if (e.key === 'dark_mode') { + update_mode(e.newValue); } }); -function set_mode(bool) { +window.addEventListener('load', function () { + window.localStorage.setItem('dark_mode', document.getElementById('dark_mode_pref').textContent); + // Update localStorage if dark mode preference changed on preferences page + update_mode(window.localStorage.dark_mode); +}); + +function set_mode (bool) { document.getElementById('dark_theme').media = !bool ? 'none' : ''; document.getElementById('light_theme').media = bool ? 'none' : ''; @@ -33,3 +38,21 @@ function set_mode(bool) { toggle_theme.children[0].setAttribute('class', 'icon ion-ios-moon'); } } + +function update_mode (mode) { + if (mode === 'true' /* for backwards compatibility */ || mode === 'dark') { + // If preference for dark mode indicated + set_mode(true); + } + else if (mode === 'false' /* for backwards compaibility */ || mode === 'light') { + // If preference for light mode indicated + set_mode(false); + } + else if (document.getElementById('dark_mode_pref').textContent === '' && window.matchMedia('(prefers-color-scheme: dark)').matches) { + // If no preference indicated here and no preference indicated on the preferences page (backend), but the browser tells us that the operating system has a dark theme + set_mode(true); + } + // else do nothing, falling back to the mode defined by the `dark_mode` preference on the preferences page (backend) +} + + diff --git a/locales/ar.json b/locales/ar.json index e3716008f..f2ed450c2 100644 --- a/locales/ar.json +++ b/locales/ar.json @@ -68,7 +68,11 @@ "Show related videos: ": "عرض مقاطع الفيديو ذات الصلة؟", "Show annotations by default: ": "عرض الملاحظات فى الفيديو تلقائيا ؟", "Visual preferences": "التفضيلات المرئية", + "Player style: ": "", "Dark mode: ": "الوضع الليلى: ", + "Theme: ": "", + "dark": "", + "light": "", "Thin mode: ": "الوضع الخفيف: ", "Subscription preferences": "تفضيلات الإشتراك", "Show annotations by default for subscribed channels: ": "عرض الملاحظات فى الفيديوهات تلقائيا فى القنوات المشترك بها فقط ؟", diff --git a/locales/de.json b/locales/de.json index 33edb706c..e0ef9d679 100644 --- a/locales/de.json +++ b/locales/de.json @@ -68,7 +68,11 @@ "Show related videos: ": "Ähnliche Videos anzeigen? ", "Show annotations by default: ": "Standardmäßig Anmerkungen anzeigen? ", "Visual preferences": "Anzeigeeinstellungen", + "Player style: ": "", "Dark mode: ": "Nachtmodus: ", + "Theme: ": "", + "dark": "", + "light": "", "Thin mode: ": "Schlanker Modus: ", "Subscription preferences": "Abonnementeinstellungen", "Show annotations by default for subscribed channels: ": "Anmerkungen für abonnierte Kanäle standardmäßig anzeigen? ", diff --git a/locales/el.json b/locales/el.json index 03d255335..32f154a15 100644 --- a/locales/el.json +++ b/locales/el.json @@ -74,7 +74,11 @@ "Show related videos: ": "Προβολή σχετικών βίντεο; ", "Show annotations by default: ": "Αυτόματη προβολή σημειώσεων; :", "Visual preferences": "Προτιμήσεις εμφάνισης", + "Player style: ": "", "Dark mode: ": "Σκοτεινή λειτουργία: ", + "Theme: ": "", + "dark": "", + "light": "", "Thin mode: ": "Ελαφριά λειτουργία: ", "Subscription preferences": "Προτιμήσεις συνδρομών", "Show annotations by default for subscribed channels: ": "Προβολή σημειώσεων μόνο για κανάλια στα οποία είστε συνδρομητής; ", diff --git a/locales/en-US.json b/locales/en-US.json index 16301b1d9..580d9ead5 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -74,7 +74,11 @@ "Show related videos: ": "Show related videos: ", "Show annotations by default: ": "Show annotations by default: ", "Visual preferences": "Visual preferences", + "Player style: ": "", "Dark mode: ": "Dark mode: ", + "Theme: ": "", + "dark": "", + "light": "", "Thin mode: ": "Thin mode: ", "Subscription preferences": "Subscription preferences", "Show annotations by default for subscribed channels: ": "Show annotations by default for subscribed channels? ", diff --git a/locales/eo.json b/locales/eo.json index fe85edf21..2c22af59b 100644 --- a/locales/eo.json +++ b/locales/eo.json @@ -68,7 +68,11 @@ "Show related videos: ": "Ĉu montri rilatajn videojn? ", "Show annotations by default: ": "Ĉu montri prinotojn defaŭlte? ", "Visual preferences": "Vidaj preferoj", + "Player style: ": "", "Dark mode: ": "Malhela reĝimo: ", + "Theme: ": "", + "dark": "", + "light": "", "Thin mode: ": "Maldika reĝimo: ", "Subscription preferences": "Abonaj agordoj", "Show annotations by default for subscribed channels: ": "Ĉu montri prinotojn defaŭlte por abonitaj kanaloj? ", diff --git a/locales/es.json b/locales/es.json index fdbf2fb25..5860b882f 100644 --- a/locales/es.json +++ b/locales/es.json @@ -68,7 +68,11 @@ "Show related videos: ": "¿Mostrar vídeos relacionados? ", "Show annotations by default: ": "¿Mostrar anotaciones por defecto? ", "Visual preferences": "Preferencias visuales", + "Player style: ": "", "Dark mode: ": "Modo oscuro: ", + "Theme: ": "", + "dark": "", + "light": "", "Thin mode: ": "Modo compacto: ", "Subscription preferences": "Preferencias de la suscripción", "Show annotations by default for subscribed channels: ": "¿Mostrar anotaciones por defecto para los canales suscritos? ", diff --git a/locales/eu.json b/locales/eu.json index 5a756154d..cbdbbefc0 100644 --- a/locales/eu.json +++ b/locales/eu.json @@ -68,7 +68,11 @@ "Show related videos: ": "", "Show annotations by default: ": "", "Visual preferences": "", + "Player style: ": "", "Dark mode: ": "", + "Theme: ": "", + "dark": "", + "light": "", "Thin mode: ": "", "Subscription preferences": "", "Show annotations by default for subscribed channels: ": "", diff --git a/locales/fr.json b/locales/fr.json index 37a773f17..af561a0cc 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -68,7 +68,11 @@ "Show related videos: ": "Voir les vidéos liées : ", "Show annotations by default: ": "Voir les annotations par défaut : ", "Visual preferences": "Préférences du site", + "Player style: ": "", "Dark mode: ": "Mode Sombre : ", + "Theme: ": "", + "dark": "", + "light": "", "Thin mode: ": "Mode Simplifié : ", "Subscription preferences": "Préférences de la page d'abonnements", "Show annotations by default for subscribed channels: ": "Voir les annotations par défaut sur les chaînes suivies : ", diff --git a/locales/is.json b/locales/is.json index 43ba26e9c..808063c42 100644 --- a/locales/is.json +++ b/locales/is.json @@ -68,7 +68,11 @@ "Show related videos: ": "Sýna tengd myndbönd? ", "Show annotations by default: ": "Á að sýna glósur sjálfgefið? ", "Visual preferences": "Sjónrænar stillingar", + "Player style: ": "", "Dark mode: ": "Myrkur ham: ", + "Theme: ": "", + "dark": "", + "light": "", "Thin mode: ": "Þunnt ham: ", "Subscription preferences": "Áskriftarstillingar", "Show annotations by default for subscribed channels: ": "Á að sýna glósur sjálfgefið fyrir áskriftarrásir? ", diff --git a/locales/it.json b/locales/it.json index bf028c91a..7f532d0d3 100644 --- a/locales/it.json +++ b/locales/it.json @@ -68,7 +68,11 @@ "Show related videos: ": "Mostra video correlati? ", "Show annotations by default: ": "Mostra le annotazioni per impostazione predefinita? ", "Visual preferences": "Preferenze grafiche", + "Player style: ": "", "Dark mode: ": "Tema scuro: ", + "Theme: ": "", + "dark": "", + "light": "", "Thin mode: ": "Modalità per connessioni lente: ", "Subscription preferences": "Preferenze iscrizioni", "Show annotations by default for subscribed channels: ": "", diff --git a/locales/nb_NO.json b/locales/nb_NO.json index 589512d45..f50e22900 100644 --- a/locales/nb_NO.json +++ b/locales/nb_NO.json @@ -68,7 +68,11 @@ "Show related videos: ": "Vis relaterte videoer? ", "Show annotations by default: ": "Vis merknader som forvalg? ", "Visual preferences": "Visuelle innstillinger", + "Player style: ": "", "Dark mode: ": "Mørk drakt: ", + "Theme: ": "", + "dark": "", + "light": "", "Thin mode: ": "Tynt modus: ", "Subscription preferences": "Abonnementsinnstillinger", "Show annotations by default for subscribed channels: ": "Vis merknader som forvalg for kanaler det abonneres på? ", diff --git a/locales/nl.json b/locales/nl.json index b24b13b84..3e2c6c642 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -68,7 +68,11 @@ "Show related videos: ": "Gerelateerde video's tonen? ", "Show annotations by default: ": "Standaard annotaties tonen? ", "Visual preferences": "Visuele instellingen", + "Player style: ": "", "Dark mode: ": "Donkere modus: ", + "Theme: ": "", + "dark": "", + "light": "", "Thin mode: ": "Smalle modus: ", "Subscription preferences": "Abonnementsinstellingen", "Show annotations by default for subscribed channels: ": "Standaard annotaties tonen voor geabonneerde kanalen? ", diff --git a/locales/pl.json b/locales/pl.json index 550e664b1..1e3a2068c 100644 --- a/locales/pl.json +++ b/locales/pl.json @@ -68,7 +68,11 @@ "Show related videos: ": "Pokaż powiązane filmy? ", "Show annotations by default: ": "", "Visual preferences": "Preferencje Wizualne", + "Player style: ": "", "Dark mode: ": "Ciemny motyw: ", + "Theme: ": "", + "dark": "", + "light": "", "Thin mode: ": "Tryb minimalny: ", "Subscription preferences": "Preferencje subskrybcji", "Show annotations by default for subscribed channels: ": "", diff --git a/locales/ru.json b/locales/ru.json index f9cc1e2d9..90aa4a3b5 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -68,7 +68,11 @@ "Show related videos: ": "Показывать похожие видео? ", "Show annotations by default: ": "Всегда показывать аннотации? ", "Visual preferences": "Настройки сайта", + "Player style: ": "", "Dark mode: ": "Тёмное оформление: ", + "Theme: ": "", + "dark": "", + "light": "", "Thin mode: ": "Облегчённое оформление: ", "Subscription preferences": "Настройки подписок", "Show annotations by default for subscribed channels: ": "Всегда показывать аннотации в видео каналов, на которые вы подписаны? ", diff --git a/locales/uk.json b/locales/uk.json index 95e5798df..e537008cb 100644 --- a/locales/uk.json +++ b/locales/uk.json @@ -68,7 +68,11 @@ "Show related videos: ": "Показувати схожі відео? ", "Show annotations by default: ": "Завжди показувати анотації? ", "Visual preferences": "Налаштування сайту", + "Player style: ": "", "Dark mode: ": "Темне оформлення: ", + "Theme: ": "", + "dark": "", + "light": "", "Thin mode: ": "Полегшене оформлення: ", "Subscription preferences": "Налаштування підписок", "Show annotations by default for subscribed channels: ": "Завжди показувати анотації у відео каналів, на які ви підписані? ", diff --git a/locales/zh-CN.json b/locales/zh-CN.json index 0a3f53d97..23617d049 100644 --- a/locales/zh-CN.json +++ b/locales/zh-CN.json @@ -68,7 +68,11 @@ "Show related videos: ": "显示相关视频?", "Show annotations by default: ": "默认显示视频注释?", "Visual preferences": "视觉选项", + "Player style: ": "", "Dark mode: ": "暗色模式:", + "Theme: ": "", + "dark": "", + "light": "", "Thin mode: ": "窄页模式:", "Subscription preferences": "订阅设置", "Show annotations by default for subscribed channels: ": "在订阅频道的视频默认显示注释?", diff --git a/src/invidious.cr b/src/invidious.cr index 16695c5fd..712a408fb 100644 --- a/src/invidious.cr +++ b/src/invidious.cr @@ -267,8 +267,7 @@ before_all do |env| end end - dark_mode = env.params.query["dark_mode"]? || preferences.dark_mode.to_s - dark_mode = dark_mode == "true" + dark_mode = convert_theme(env.params.query["dark_mode"]?) || preferences.dark_mode.to_s thin_mode = env.params.query["thin_mode"]? || preferences.thin_mode.to_s thin_mode = thin_mode == "true" @@ -1528,8 +1527,7 @@ post "/preferences" do |env| locale ||= CONFIG.default_user_preferences.locale dark_mode = env.params.body["dark_mode"]?.try &.as(String) - dark_mode ||= "off" - dark_mode = dark_mode == "on" + dark_mode ||= CONFIG.default_user_preferences.dark_mode thin_mode = env.params.body["thin_mode"]?.try &.as(String) thin_mode ||= "off" @@ -1553,6 +1551,7 @@ post "/preferences" do |env| notifications_only ||= "off" notifications_only = notifications_only == "on" + # Convert to JSON and back again to take advantage of converters used for compatability preferences = Preferences.from_json({ annotations: annotations, annotations_subscribed: annotations_subscribed, @@ -1648,12 +1647,27 @@ get "/toggle_theme" do |env| if user = env.get? "user" user = user.as(User) preferences = user.preferences - preferences.dark_mode = !preferences.dark_mode - PG_DB.exec("UPDATE users SET preferences = $1 WHERE email = $2", preferences.to_json, user.email) + case preferences.dark_mode + when "dark" + preferences.dark_mode = "light" + else + preferences.dark_mode = "dark" + end + + preferences = preferences.to_json + + PG_DB.exec("UPDATE users SET preferences = $1 WHERE email = $2", preferences, user.email) else preferences = env.get("preferences").as(Preferences) - preferences.dark_mode = !preferences.dark_mode + + case preferences.dark_mode + when "dark" + preferences.dark_mode = "light" + else + preferences.dark_mode = "dark" + end + preferences = preferences.to_json if Kemal.config.ssl || config.https_only @@ -2026,7 +2040,7 @@ post "/data_control" do |env| env.response.puts %() env.response.puts %() env.response.puts %() - if env.get("preferences").as(Preferences).dark_mode + if env.get("preferences").as(Preferences).dark_mode == "dark" env.response.puts %() else env.response.puts %() diff --git a/src/invidious/helpers/helpers.cr b/src/invidious/helpers/helpers.cr index ce0ded325..03c1654cf 100644 --- a/src/invidious/helpers/helpers.cr +++ b/src/invidious/helpers/helpers.cr @@ -24,6 +24,27 @@ end struct ConfigPreferences module StringToArray + def self.to_json(value : Array(String), json : JSON::Builder) + json.array do + value.each do |element| + json.string element + end + end + end + + def self.from_json(value : JSON::PullParser) : Array(String) + begin + result = [] of String + value.read_array do + result << HTML.escape(value.read_string[0, 100]) + end + rescue ex + result = [HTML.escape(value.read_string[0, 100]), ""] + end + + result + end + def self.to_yaml(value : Array(String), yaml : YAML::Nodes::Builder) yaml.sequence do value.each do |element| @@ -44,11 +65,11 @@ struct ConfigPreferences node.raise "Expected scalar, not #{item.class}" end - result << item.value + result << HTML.escape(item.value[0, 100]) end rescue ex if node.is_a?(YAML::Nodes::Scalar) - result = [node.value, ""] + result = [HTML.escape(node.value[0, 100]), ""] else result = ["", ""] end @@ -58,6 +79,53 @@ struct ConfigPreferences end end + module BoolToString + def self.to_json(value : String, json : JSON::Builder) + json.string value + end + + def self.from_json(value : JSON::PullParser) : String + begin + result = value.read_string + + if result.empty? + CONFIG.default_user_preferences.dark_mode + else + result + end + rescue ex + result = value.read_bool + + if result + "dark" + else + "light" + end + end + end + + def self.to_yaml(value : String, yaml : YAML::Nodes::Builder) + yaml.scalar value + end + + def self.from_yaml(ctx : YAML::ParseContext, node : YAML::Nodes::Node) : String + unless node.is_a?(YAML::Nodes::Scalar) + node.raise "Expected sequence, not #{node.class}" + end + + case node.value + when "true" + "dark" + when "false" + "light" + when "" + CONFIG.default_user_preferences.dark_mode + else + node.value + end + end + end + yaml_mapping({ annotations: {type: Bool, default: false}, annotations_subscribed: {type: Bool, default: false}, @@ -66,7 +134,7 @@ struct ConfigPreferences comments: {type: Array(String), default: ["youtube", ""], converter: StringToArray}, continue: {type: Bool, default: false}, continue_autoplay: {type: Bool, default: true}, - dark_mode: {type: Bool, default: false}, + dark_mode: {type: String, default: "", converter: BoolToString}, latest_only: {type: Bool, default: false}, listen: {type: Bool, default: false}, local: {type: Bool, default: false}, diff --git a/src/invidious/helpers/patch_mapping.cr b/src/invidious/helpers/patch_mapping.cr index 8360caa61..e138aa1c9 100644 --- a/src/invidious/helpers/patch_mapping.cr +++ b/src/invidious/helpers/patch_mapping.cr @@ -4,7 +4,7 @@ def Object.from_json(string_or_io, default) : self new parser, default end -# Adds configurable 'default' to +# Adds configurable 'default' macro patched_json_mapping(_properties_, strict = false) {% for key, value in _properties_ %} {% _properties_[key] = {type: value} unless value.is_a?(HashLiteral) || value.is_a?(NamedTupleLiteral) %} diff --git a/src/invidious/helpers/utils.cr b/src/invidious/helpers/utils.cr index 69aae839d..b39f65c59 100644 --- a/src/invidious/helpers/utils.cr +++ b/src/invidious/helpers/utils.cr @@ -356,3 +356,16 @@ def parse_range(range) return 0_i64, nil end + +def convert_theme(theme) + case theme + when "true" + "dark" + when "false" + "light" + when "", nil + nil + else + theme + end +end diff --git a/src/invidious/users.cr b/src/invidious/users.cr index 35d8a49e1..8bd82bf14 100644 --- a/src/invidious/users.cr +++ b/src/invidious/users.cr @@ -31,62 +31,6 @@ struct User end struct Preferences - module StringToArray - def self.to_json(value : Array(String), json : JSON::Builder) - json.array do - value.each do |element| - json.string element - end - end - end - - def self.from_json(value : JSON::PullParser) : Array(String) - begin - result = [] of String - value.read_array do - result << HTML.escape(value.read_string[0, 100]) - end - rescue ex - result = [HTML.escape(value.read_string[0, 100]), ""] - end - - result - end - - def self.to_yaml(value : Array(String), yaml : YAML::Nodes::Builder) - yaml.sequence do - value.each do |element| - yaml.scalar element - end - end - end - - def self.from_yaml(ctx : YAML::ParseContext, node : YAML::Nodes::Node) : Array(String) - begin - unless node.is_a?(YAML::Nodes::Sequence) - node.raise "Expected sequence, not #{node.class}" - end - - result = [] of String - node.nodes.each do |item| - unless item.is_a?(YAML::Nodes::Scalar) - node.raise "Expected scalar, not #{item.class}" - end - - result << HTML.escape(item.value[0, 100]) - end - rescue ex - if node.is_a?(YAML::Nodes::Scalar) - result = [HTML.escape(node.value[0, 100]), ""] - else - result = ["", ""] - end - end - - result - end - end - module ProcessString def self.to_json(value : String, json : JSON::Builder) json.string value @@ -127,11 +71,11 @@ struct Preferences annotations: {type: Bool, default: CONFIG.default_user_preferences.annotations}, annotations_subscribed: {type: Bool, default: CONFIG.default_user_preferences.annotations_subscribed}, autoplay: {type: Bool, default: CONFIG.default_user_preferences.autoplay}, - captions: {type: Array(String), default: CONFIG.default_user_preferences.captions, converter: StringToArray}, - comments: {type: Array(String), default: CONFIG.default_user_preferences.comments, converter: StringToArray}, + captions: {type: Array(String), default: CONFIG.default_user_preferences.captions, converter: ConfigPreferences::StringToArray}, + comments: {type: Array(String), default: CONFIG.default_user_preferences.comments, converter: ConfigPreferences::StringToArray}, continue: {type: Bool, default: CONFIG.default_user_preferences.continue}, continue_autoplay: {type: Bool, default: CONFIG.default_user_preferences.continue_autoplay}, - dark_mode: {type: Bool, default: CONFIG.default_user_preferences.dark_mode}, + dark_mode: {type: String, default: CONFIG.default_user_preferences.dark_mode, converter: ConfigPreferences::BoolToString}, latest_only: {type: Bool, default: CONFIG.default_user_preferences.latest_only}, listen: {type: Bool, default: CONFIG.default_user_preferences.listen}, local: {type: Bool, default: CONFIG.default_user_preferences.local}, diff --git a/src/invidious/views/preferences.ecr b/src/invidious/views/preferences.ecr index b04bcd4d4..6ea01fba9 100644 --- a/src/invidious/views/preferences.ecr +++ b/src/invidious/views/preferences.ecr @@ -122,8 +122,12 @@ function update_value(element) {
- - checked<% end %>> + +
diff --git a/src/invidious/views/template.ecr b/src/invidious/views/template.ecr index 6272d2be7..8d8cec885 100644 --- a/src/invidious/views/template.ecr +++ b/src/invidious/views/template.ecr @@ -18,13 +18,14 @@ - media="none"<% end %>> - media="none"<% end %>> + media="none"<% end %>> + media="none"<% end %>> <% locale = LOCALES[env.get("preferences").as(Preferences).locale]? %> +