Add prefers-color-scheme support (#601)

* Add prefers-color-scheme support

This should fix <https://github.com/omarroth/invidious/issues/559>.
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
<https://stackoverflow.com/a/4679754>).
This commit is contained in:
psvenk 2019-08-15 16:29:55 +00:00 committed by Omar Roth
parent 19eceb4ecc
commit f54fbd057e
24 changed files with 215 additions and 84 deletions

View File

@ -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.href = 'javascript:void(0);';
toggle_theme.addEventListener('click', function () { 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 url = '/toggle_theme?redirect=false';
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
@ -11,18 +11,23 @@ toggle_theme.addEventListener('click', function () {
xhr.open('GET', url, true); xhr.open('GET', url, true);
set_mode(dark_mode); set_mode(dark_mode);
localStorage.setItem('dark_mode', dark_mode); window.localStorage.setItem('dark_mode', dark_mode ? 'dark' : 'light');
xhr.send(); xhr.send();
}); });
window.addEventListener('storage', function (e) { window.addEventListener('storage', function (e) {
if (e.key == 'dark_mode') { if (e.key === 'dark_mode') {
var dark_mode = e.newValue === 'true'; update_mode(e.newValue);
set_mode(dark_mode);
} }
}); });
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) { function set_mode (bool) {
document.getElementById('dark_theme').media = !bool ? 'none' : ''; document.getElementById('dark_theme').media = !bool ? 'none' : '';
document.getElementById('light_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'); 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)
}

View File

@ -68,7 +68,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: ": "عرض الملاحظات فى الفيديوهات تلقائيا فى القنوات المشترك بها فقط ؟",

View File

@ -68,7 +68,11 @@
"Show related videos: ": "Ähnliche Videos anzeigen? ", "Show related videos: ": "Ähnliche Videos anzeigen? ",
"Show annotations by default: ": "Standardmäßig Anmerkungen anzeigen? ", "Show annotations by default: ": "Standardmäßig Anmerkungen anzeigen? ",
"Visual preferences": "Anzeigeeinstellungen", "Visual preferences": "Anzeigeeinstellungen",
"Player style: ": "",
"Dark mode: ": "Nachtmodus: ", "Dark mode: ": "Nachtmodus: ",
"Theme: ": "",
"dark": "",
"light": "",
"Thin mode: ": "Schlanker Modus: ", "Thin mode: ": "Schlanker Modus: ",
"Subscription preferences": "Abonnementeinstellungen", "Subscription preferences": "Abonnementeinstellungen",
"Show annotations by default for subscribed channels: ": "Anmerkungen für abonnierte Kanäle standardmäßig anzeigen? ", "Show annotations by default for subscribed channels: ": "Anmerkungen für abonnierte Kanäle standardmäßig anzeigen? ",

View File

@ -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: ": "Προβολή σημειώσεων μόνο για κανάλια στα οποία είστε συνδρομητής; ",

View File

@ -74,7 +74,11 @@
"Show related videos: ": "Show related videos: ", "Show related videos: ": "Show related videos: ",
"Show annotations by default: ": "Show annotations by default: ", "Show annotations by default: ": "Show annotations by default: ",
"Visual preferences": "Visual preferences", "Visual preferences": "Visual preferences",
"Player style: ": "",
"Dark mode: ": "Dark mode: ", "Dark mode: ": "Dark mode: ",
"Theme: ": "",
"dark": "",
"light": "",
"Thin mode: ": "Thin mode: ", "Thin mode: ": "Thin mode: ",
"Subscription preferences": "Subscription preferences", "Subscription preferences": "Subscription preferences",
"Show annotations by default for subscribed channels: ": "Show annotations by default for subscribed channels? ", "Show annotations by default for subscribed channels: ": "Show annotations by default for subscribed channels? ",

View File

@ -68,7 +68,11 @@
"Show related videos: ": "Ĉu montri rilatajn videojn? ", "Show related videos: ": "Ĉu montri rilatajn videojn? ",
"Show annotations by default: ": "Ĉu montri prinotojn defaŭlte? ", "Show annotations by default: ": "Ĉu montri prinotojn defaŭlte? ",
"Visual preferences": "Vidaj preferoj", "Visual preferences": "Vidaj preferoj",
"Player style: ": "",
"Dark mode: ": "Malhela reĝimo: ", "Dark mode: ": "Malhela reĝimo: ",
"Theme: ": "",
"dark": "",
"light": "",
"Thin mode: ": "Maldika reĝimo: ", "Thin mode: ": "Maldika reĝimo: ",
"Subscription preferences": "Abonaj agordoj", "Subscription preferences": "Abonaj agordoj",
"Show annotations by default for subscribed channels: ": "Ĉu montri prinotojn defaŭlte por abonitaj kanaloj? ", "Show annotations by default for subscribed channels: ": "Ĉu montri prinotojn defaŭlte por abonitaj kanaloj? ",

View File

@ -68,7 +68,11 @@
"Show related videos: ": "¿Mostrar vídeos relacionados? ", "Show related videos: ": "¿Mostrar vídeos relacionados? ",
"Show annotations by default: ": "¿Mostrar anotaciones por defecto? ", "Show annotations by default: ": "¿Mostrar anotaciones por defecto? ",
"Visual preferences": "Preferencias visuales", "Visual preferences": "Preferencias visuales",
"Player style: ": "",
"Dark mode: ": "Modo oscuro: ", "Dark mode: ": "Modo oscuro: ",
"Theme: ": "",
"dark": "",
"light": "",
"Thin mode: ": "Modo compacto: ", "Thin mode: ": "Modo compacto: ",
"Subscription preferences": "Preferencias de la suscripción", "Subscription preferences": "Preferencias de la suscripción",
"Show annotations by default for subscribed channels: ": "¿Mostrar anotaciones por defecto para los canales suscritos? ", "Show annotations by default for subscribed channels: ": "¿Mostrar anotaciones por defecto para los canales suscritos? ",

View File

@ -68,7 +68,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: ": "",

View File

@ -68,7 +68,11 @@
"Show related videos: ": "Voir les vidéos liées : ", "Show related videos: ": "Voir les vidéos liées : ",
"Show annotations by default: ": "Voir les annotations par défaut : ", "Show annotations by default: ": "Voir les annotations par défaut : ",
"Visual preferences": "Préférences du site", "Visual preferences": "Préférences du site",
"Player style: ": "",
"Dark mode: ": "Mode Sombre : ", "Dark mode: ": "Mode Sombre : ",
"Theme: ": "",
"dark": "",
"light": "",
"Thin mode: ": "Mode Simplifié : ", "Thin mode: ": "Mode Simplifié : ",
"Subscription preferences": "Préférences de la page d'abonnements", "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 : ", "Show annotations by default for subscribed channels: ": "Voir les annotations par défaut sur les chaînes suivies : ",

View File

@ -68,7 +68,11 @@
"Show related videos: ": "Sýna tengd myndbönd? ", "Show related videos: ": "Sýna tengd myndbönd? ",
"Show annotations by default: ": "Á að sýna glósur sjálfgefið? ", "Show annotations by default: ": "Á að sýna glósur sjálfgefið? ",
"Visual preferences": "Sjónrænar stillingar", "Visual preferences": "Sjónrænar stillingar",
"Player style: ": "",
"Dark mode: ": "Myrkur ham: ", "Dark mode: ": "Myrkur ham: ",
"Theme: ": "",
"dark": "",
"light": "",
"Thin mode: ": "Þunnt ham: ", "Thin mode: ": "Þunnt ham: ",
"Subscription preferences": "Áskriftarstillingar", "Subscription preferences": "Áskriftarstillingar",
"Show annotations by default for subscribed channels: ": "Á að sýna glósur sjálfgefið fyrir áskriftarrásir? ", "Show annotations by default for subscribed channels: ": "Á að sýna glósur sjálfgefið fyrir áskriftarrásir? ",

View File

@ -68,7 +68,11 @@
"Show related videos: ": "Mostra video correlati? ", "Show related videos: ": "Mostra video correlati? ",
"Show annotations by default: ": "Mostra le annotazioni per impostazione predefinita? ", "Show annotations by default: ": "Mostra le annotazioni per impostazione predefinita? ",
"Visual preferences": "Preferenze grafiche", "Visual preferences": "Preferenze grafiche",
"Player style: ": "",
"Dark mode: ": "Tema scuro: ", "Dark mode: ": "Tema scuro: ",
"Theme: ": "",
"dark": "",
"light": "",
"Thin mode: ": "Modalità per connessioni lente: ", "Thin mode: ": "Modalità per connessioni lente: ",
"Subscription preferences": "Preferenze iscrizioni", "Subscription preferences": "Preferenze iscrizioni",
"Show annotations by default for subscribed channels: ": "", "Show annotations by default for subscribed channels: ": "",

View File

@ -68,7 +68,11 @@
"Show related videos: ": "Vis relaterte videoer? ", "Show related videos: ": "Vis relaterte videoer? ",
"Show annotations by default: ": "Vis merknader som forvalg? ", "Show annotations by default: ": "Vis merknader som forvalg? ",
"Visual preferences": "Visuelle innstillinger", "Visual preferences": "Visuelle innstillinger",
"Player style: ": "",
"Dark mode: ": "Mørk drakt: ", "Dark mode: ": "Mørk drakt: ",
"Theme: ": "",
"dark": "",
"light": "",
"Thin mode: ": "Tynt modus: ", "Thin mode: ": "Tynt modus: ",
"Subscription preferences": "Abonnementsinnstillinger", "Subscription preferences": "Abonnementsinnstillinger",
"Show annotations by default for subscribed channels: ": "Vis merknader som forvalg for kanaler det abonneres på? ", "Show annotations by default for subscribed channels: ": "Vis merknader som forvalg for kanaler det abonneres på? ",

View File

@ -68,7 +68,11 @@
"Show related videos: ": "Gerelateerde video's tonen? ", "Show related videos: ": "Gerelateerde video's tonen? ",
"Show annotations by default: ": "Standaard annotaties tonen? ", "Show annotations by default: ": "Standaard annotaties tonen? ",
"Visual preferences": "Visuele instellingen", "Visual preferences": "Visuele instellingen",
"Player style: ": "",
"Dark mode: ": "Donkere modus: ", "Dark mode: ": "Donkere modus: ",
"Theme: ": "",
"dark": "",
"light": "",
"Thin mode: ": "Smalle modus: ", "Thin mode: ": "Smalle modus: ",
"Subscription preferences": "Abonnementsinstellingen", "Subscription preferences": "Abonnementsinstellingen",
"Show annotations by default for subscribed channels: ": "Standaard annotaties tonen voor geabonneerde kanalen? ", "Show annotations by default for subscribed channels: ": "Standaard annotaties tonen voor geabonneerde kanalen? ",

View File

@ -68,7 +68,11 @@
"Show related videos: ": "Pokaż powiązane filmy? ", "Show related videos: ": "Pokaż powiązane filmy? ",
"Show annotations by default: ": "", "Show annotations by default: ": "",
"Visual preferences": "Preferencje Wizualne", "Visual preferences": "Preferencje Wizualne",
"Player style: ": "",
"Dark mode: ": "Ciemny motyw: ", "Dark mode: ": "Ciemny motyw: ",
"Theme: ": "",
"dark": "",
"light": "",
"Thin mode: ": "Tryb minimalny: ", "Thin mode: ": "Tryb minimalny: ",
"Subscription preferences": "Preferencje subskrybcji", "Subscription preferences": "Preferencje subskrybcji",
"Show annotations by default for subscribed channels: ": "", "Show annotations by default for subscribed channels: ": "",

View File

@ -68,7 +68,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: ": "Всегда показывать аннотации в видео каналов, на которые вы подписаны? ",

View File

@ -68,7 +68,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: ": "Завжди показувати анотації у відео каналів, на які ви підписані? ",

View File

@ -68,7 +68,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: ": "在订阅频道的视频默认显示注释?",

View File

@ -267,8 +267,7 @@ before_all do |env|
end end
end end
dark_mode = env.params.query["dark_mode"]? || preferences.dark_mode.to_s dark_mode = convert_theme(env.params.query["dark_mode"]?) || preferences.dark_mode.to_s
dark_mode = dark_mode == "true"
thin_mode = env.params.query["thin_mode"]? || preferences.thin_mode.to_s thin_mode = env.params.query["thin_mode"]? || preferences.thin_mode.to_s
thin_mode = thin_mode == "true" thin_mode = thin_mode == "true"
@ -1528,8 +1527,7 @@ post "/preferences" do |env|
locale ||= CONFIG.default_user_preferences.locale locale ||= CONFIG.default_user_preferences.locale
dark_mode = env.params.body["dark_mode"]?.try &.as(String) dark_mode = env.params.body["dark_mode"]?.try &.as(String)
dark_mode ||= "off" dark_mode ||= CONFIG.default_user_preferences.dark_mode
dark_mode = dark_mode == "on"
thin_mode = env.params.body["thin_mode"]?.try &.as(String) thin_mode = env.params.body["thin_mode"]?.try &.as(String)
thin_mode ||= "off" thin_mode ||= "off"
@ -1553,6 +1551,7 @@ post "/preferences" do |env|
notifications_only ||= "off" notifications_only ||= "off"
notifications_only = notifications_only == "on" notifications_only = notifications_only == "on"
# Convert to JSON and back again to take advantage of converters used for compatability
preferences = Preferences.from_json({ preferences = Preferences.from_json({
annotations: annotations, annotations: annotations,
annotations_subscribed: annotations_subscribed, annotations_subscribed: annotations_subscribed,
@ -1648,12 +1647,27 @@ get "/toggle_theme" do |env|
if user = env.get? "user" if user = env.get? "user"
user = user.as(User) user = user.as(User)
preferences = user.preferences 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 else
preferences = env.get("preferences").as(Preferences) 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 preferences = preferences.to_json
if Kemal.config.ssl || config.https_only if Kemal.config.ssl || config.https_only
@ -2026,7 +2040,7 @@ post "/data_control" do |env|
env.response.puts %(<meta http-equiv="refresh" content="0; url=#{referer}">) env.response.puts %(<meta http-equiv="refresh" content="0; url=#{referer}">)
env.response.puts %(<link rel="stylesheet" href="/css/ionicons.min.css?v=#{ASSET_COMMIT}">) env.response.puts %(<link rel="stylesheet" href="/css/ionicons.min.css?v=#{ASSET_COMMIT}">)
env.response.puts %(<link rel="stylesheet" href="/css/default.css?v=#{ASSET_COMMIT}">) env.response.puts %(<link rel="stylesheet" href="/css/default.css?v=#{ASSET_COMMIT}">)
if env.get("preferences").as(Preferences).dark_mode if env.get("preferences").as(Preferences).dark_mode == "dark"
env.response.puts %(<link rel="stylesheet" href="/css/darktheme.css?v=#{ASSET_COMMIT}">) env.response.puts %(<link rel="stylesheet" href="/css/darktheme.css?v=#{ASSET_COMMIT}">)
else else
env.response.puts %(<link rel="stylesheet" href="/css/lighttheme.css?v=#{ASSET_COMMIT}">) env.response.puts %(<link rel="stylesheet" href="/css/lighttheme.css?v=#{ASSET_COMMIT}">)

View File

@ -24,6 +24,27 @@ end
struct ConfigPreferences struct ConfigPreferences
module StringToArray 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) def self.to_yaml(value : Array(String), yaml : YAML::Nodes::Builder)
yaml.sequence do yaml.sequence do
value.each do |element| value.each do |element|
@ -44,11 +65,11 @@ struct ConfigPreferences
node.raise "Expected scalar, not #{item.class}" node.raise "Expected scalar, not #{item.class}"
end end
result << item.value result << HTML.escape(item.value[0, 100])
end end
rescue ex rescue ex
if node.is_a?(YAML::Nodes::Scalar) if node.is_a?(YAML::Nodes::Scalar)
result = [node.value, ""] result = [HTML.escape(node.value[0, 100]), ""]
else else
result = ["", ""] result = ["", ""]
end end
@ -58,6 +79,53 @@ struct ConfigPreferences
end end
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({ yaml_mapping({
annotations: {type: Bool, default: false}, annotations: {type: Bool, default: false},
annotations_subscribed: {type: Bool, default: false}, annotations_subscribed: {type: Bool, default: false},
@ -66,7 +134,7 @@ struct ConfigPreferences
comments: {type: Array(String), default: ["youtube", ""], converter: StringToArray}, comments: {type: Array(String), default: ["youtube", ""], converter: StringToArray},
continue: {type: Bool, default: false}, continue: {type: Bool, default: false},
continue_autoplay: {type: Bool, default: true}, 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}, latest_only: {type: Bool, default: false},
listen: {type: Bool, default: false}, listen: {type: Bool, default: false},
local: {type: Bool, default: false}, local: {type: Bool, default: false},

View File

@ -4,7 +4,7 @@ def Object.from_json(string_or_io, default) : self
new parser, default new parser, default
end end
# Adds configurable 'default' to # Adds configurable 'default'
macro patched_json_mapping(_properties_, strict = false) macro patched_json_mapping(_properties_, strict = false)
{% for key, value in _properties_ %} {% for key, value in _properties_ %}
{% _properties_[key] = {type: value} unless value.is_a?(HashLiteral) || value.is_a?(NamedTupleLiteral) %} {% _properties_[key] = {type: value} unless value.is_a?(HashLiteral) || value.is_a?(NamedTupleLiteral) %}

View File

@ -356,3 +356,16 @@ def parse_range(range)
return 0_i64, nil return 0_i64, nil
end end
def convert_theme(theme)
case theme
when "true"
"dark"
when "false"
"light"
when "", nil
nil
else
theme
end
end

View File

@ -31,62 +31,6 @@ struct User
end end
struct Preferences 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 module ProcessString
def self.to_json(value : String, json : JSON::Builder) def self.to_json(value : String, json : JSON::Builder)
json.string value json.string value
@ -127,11 +71,11 @@ struct Preferences
annotations: {type: Bool, default: CONFIG.default_user_preferences.annotations}, annotations: {type: Bool, default: CONFIG.default_user_preferences.annotations},
annotations_subscribed: {type: Bool, default: CONFIG.default_user_preferences.annotations_subscribed}, annotations_subscribed: {type: Bool, default: CONFIG.default_user_preferences.annotations_subscribed},
autoplay: {type: Bool, default: CONFIG.default_user_preferences.autoplay}, autoplay: {type: Bool, default: CONFIG.default_user_preferences.autoplay},
captions: {type: Array(String), default: CONFIG.default_user_preferences.captions, 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: StringToArray}, comments: {type: Array(String), default: CONFIG.default_user_preferences.comments, converter: ConfigPreferences::StringToArray},
continue: {type: Bool, default: CONFIG.default_user_preferences.continue}, continue: {type: Bool, default: CONFIG.default_user_preferences.continue},
continue_autoplay: {type: Bool, default: CONFIG.default_user_preferences.continue_autoplay}, 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}, latest_only: {type: Bool, default: CONFIG.default_user_preferences.latest_only},
listen: {type: Bool, default: CONFIG.default_user_preferences.listen}, listen: {type: Bool, default: CONFIG.default_user_preferences.listen},
local: {type: Bool, default: CONFIG.default_user_preferences.local}, local: {type: Bool, default: CONFIG.default_user_preferences.local},

View File

@ -122,8 +122,12 @@ function update_value(element) {
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<label for="dark_mode"><%= translate(locale, "Dark mode: ") %></label> <label for="dark_mode"><%= translate(locale, "Theme: ") %></label>
<input name="dark_mode" id="dark_mode" type="checkbox" <% if preferences.dark_mode %>checked<% end %>> <select name="dark_mode" id="dark_mode">
<% {"", "light", "dark"}.each do |option| %>
<option value="<%= option %>" <% if preferences.dark_mode == option %> selected <% end %>><%= translate(locale, option) %></option>
<% end %>
</select>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">

View File

@ -18,13 +18,14 @@
<link rel="stylesheet" href="/css/grids-responsive-min.css?v=<%= ASSET_COMMIT %>"> <link rel="stylesheet" href="/css/grids-responsive-min.css?v=<%= ASSET_COMMIT %>">
<link rel="stylesheet" href="/css/ionicons.min.css?v=<%= ASSET_COMMIT %>"> <link rel="stylesheet" href="/css/ionicons.min.css?v=<%= ASSET_COMMIT %>">
<link rel="stylesheet" href="/css/default.css?v=<%= ASSET_COMMIT %>"> <link rel="stylesheet" href="/css/default.css?v=<%= ASSET_COMMIT %>">
<link rel="stylesheet" href="/css/darktheme.css?v=<%= ASSET_COMMIT %>" id="dark_theme" <% if !env.get("preferences").as(Preferences).dark_mode %>media="none"<% end %>> <link rel="stylesheet" href="/css/darktheme.css?v=<%= ASSET_COMMIT %>" id="dark_theme" <% if env.get("preferences").as(Preferences).dark_mode != "dark" %>media="none"<% end %>>
<link rel="stylesheet" href="/css/lighttheme.css?v=<%= ASSET_COMMIT %>" id="light_theme" <% if env.get("preferences").as(Preferences).dark_mode %>media="none"<% end %>> <link rel="stylesheet" href="/css/lighttheme.css?v=<%= ASSET_COMMIT %>" id="light_theme" <% if env.get("preferences").as(Preferences).dark_mode == "dark" %>media="none"<% end %>>
</head> </head>
<% locale = LOCALES[env.get("preferences").as(Preferences).locale]? %> <% locale = LOCALES[env.get("preferences").as(Preferences).locale]? %>
<body> <body>
<span style="display:none" id="dark_mode_pref"><%= env.get("preferences").as(Preferences).dark_mode %></span>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-md-2-24"></div> <div class="pure-u-1 pure-u-md-2-24"></div>
<div class="pure-u-1 pure-u-md-20-24"> <div class="pure-u-1 pure-u-md-20-24">
@ -43,7 +44,7 @@
<% if env.get? "user" %> <% if env.get? "user" %>
<div class="pure-u-1-4"> <div class="pure-u-1-4">
<a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading"> <a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
<% if env.get("preferences").as(Preferences).dark_mode %> <% if env.get("preferences").as(Preferences).dark_mode == "dark" %>
<i class="icon ion-ios-sunny"></i> <i class="icon ion-ios-sunny"></i>
<% else %> <% else %>
<i class="icon ion-ios-moon"></i> <i class="icon ion-ios-moon"></i>
@ -76,7 +77,7 @@
<% else %> <% else %>
<div class="pure-u-1-3"> <div class="pure-u-1-3">
<a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading"> <a id="toggle_theme" href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
<% if env.get("preferences").as(Preferences).dark_mode %> <% if env.get("preferences").as(Preferences).dark_mode == "dark" %>
<i class="icon ion-ios-sunny"></i> <i class="icon ion-ios-sunny"></i>
<% else %> <% else %>
<i class="icon ion-ios-moon"></i> <i class="icon ion-ios-moon"></i>