Add a setting to use the system theme
This commit is contained in:
parent
790d19cd25
commit
45f6762529
@ -575,7 +575,8 @@ impl FormatRenderer for Context {
|
|||||||
settings(
|
settings(
|
||||||
self.shared.static_root_path.as_deref().unwrap_or("./"),
|
self.shared.static_root_path.as_deref().unwrap_or("./"),
|
||||||
&self.shared.resource_suffix,
|
&self.shared.resource_suffix,
|
||||||
),
|
&self.shared.style_files,
|
||||||
|
)?,
|
||||||
&style_files,
|
&style_files,
|
||||||
);
|
);
|
||||||
self.shared.fs.write(&settings_file, v.as_bytes())?;
|
self.shared.fs.write(&settings_file, v.as_bytes())?;
|
||||||
@ -810,6 +811,7 @@ themePicker.onblur = handleThemeButtonsBlur;
|
|||||||
but.textContent = item;
|
but.textContent = item;
|
||||||
but.onclick = function(el) {{
|
but.onclick = function(el) {{
|
||||||
switchTheme(currentTheme, mainTheme, item, true);
|
switchTheme(currentTheme, mainTheme, item, true);
|
||||||
|
useSystemTheme(false);
|
||||||
}};
|
}};
|
||||||
but.onblur = handleThemeButtonsBlur;
|
but.onblur = handleThemeButtonsBlur;
|
||||||
themes.appendChild(but);
|
themes.appendChild(but);
|
||||||
@ -1343,12 +1345,25 @@ impl AllTypes {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum Setting {
|
enum Setting {
|
||||||
Section { description: &'static str, sub_settings: Vec<Setting> },
|
Section {
|
||||||
Entry { js_data_name: &'static str, description: &'static str, default_value: bool },
|
description: &'static str,
|
||||||
|
sub_settings: Vec<Setting>,
|
||||||
|
},
|
||||||
|
Toggle {
|
||||||
|
js_data_name: &'static str,
|
||||||
|
description: &'static str,
|
||||||
|
default_value: bool,
|
||||||
|
},
|
||||||
|
Select {
|
||||||
|
js_data_name: &'static str,
|
||||||
|
description: &'static str,
|
||||||
|
default_value: &'static str,
|
||||||
|
options: Vec<(String, String)>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Setting {
|
impl Setting {
|
||||||
fn display(&self) -> String {
|
fn display(&self, root_path: &str, suffix: &str) -> String {
|
||||||
match *self {
|
match *self {
|
||||||
Setting::Section { ref description, ref sub_settings } => format!(
|
Setting::Section { ref description, ref sub_settings } => format!(
|
||||||
"<div class='setting-line'>\
|
"<div class='setting-line'>\
|
||||||
@ -1356,9 +1371,9 @@ impl Setting {
|
|||||||
<div class='sub-settings'>{}</div>
|
<div class='sub-settings'>{}</div>
|
||||||
</div>",
|
</div>",
|
||||||
description,
|
description,
|
||||||
sub_settings.iter().map(|s| s.display()).collect::<String>()
|
sub_settings.iter().map(|s| s.display(root_path, suffix)).collect::<String>()
|
||||||
),
|
),
|
||||||
Setting::Entry { ref js_data_name, ref description, ref default_value } => format!(
|
Setting::Toggle { ref js_data_name, ref description, ref default_value } => format!(
|
||||||
"<div class='setting-line'>\
|
"<div class='setting-line'>\
|
||||||
<label class='toggle'>\
|
<label class='toggle'>\
|
||||||
<input type='checkbox' id='{}' {}>\
|
<input type='checkbox' id='{}' {}>\
|
||||||
@ -1370,13 +1385,40 @@ impl Setting {
|
|||||||
if *default_value { " checked" } else { "" },
|
if *default_value { " checked" } else { "" },
|
||||||
description,
|
description,
|
||||||
),
|
),
|
||||||
|
Setting::Select {
|
||||||
|
ref js_data_name,
|
||||||
|
ref description,
|
||||||
|
ref default_value,
|
||||||
|
ref options,
|
||||||
|
} => format!(
|
||||||
|
"<div class='setting-line'>\
|
||||||
|
<div>{}</div>\
|
||||||
|
<label class='select-wrapper'>\
|
||||||
|
<select id='{}' autocomplete='off'>{}</select>\
|
||||||
|
<img src='{}down-arrow{}.svg' alt='Select item'>\
|
||||||
|
</label>\
|
||||||
|
</div>",
|
||||||
|
description,
|
||||||
|
js_data_name,
|
||||||
|
options
|
||||||
|
.iter()
|
||||||
|
.map(|opt| format!(
|
||||||
|
"<option value=\"{}\" {}>{}</option>",
|
||||||
|
opt.0,
|
||||||
|
if &opt.0 == *default_value { "selected" } else { "" },
|
||||||
|
opt.1,
|
||||||
|
))
|
||||||
|
.collect::<String>(),
|
||||||
|
root_path,
|
||||||
|
suffix,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<(&'static str, &'static str, bool)> for Setting {
|
impl From<(&'static str, &'static str, bool)> for Setting {
|
||||||
fn from(values: (&'static str, &'static str, bool)) -> Setting {
|
fn from(values: (&'static str, &'static str, bool)) -> Setting {
|
||||||
Setting::Entry { js_data_name: values.0, description: values.1, default_value: values.2 }
|
Setting::Toggle { js_data_name: values.0, description: values.1, default_value: values.2 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1389,9 +1431,39 @@ impl<T: Into<Setting>> From<(&'static str, Vec<T>)> for Setting {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn settings(root_path: &str, suffix: &str) -> String {
|
fn settings(root_path: &str, suffix: &str, themes: &[StylePath]) -> Result<String, Error> {
|
||||||
|
let theme_names: Vec<(String, String)> = themes
|
||||||
|
.iter()
|
||||||
|
.map(|entry| {
|
||||||
|
let theme =
|
||||||
|
try_none!(try_none!(entry.path.file_stem(), &entry.path).to_str(), &entry.path)
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
Ok((theme.clone(), theme))
|
||||||
|
})
|
||||||
|
.collect::<Result<_, Error>>()?;
|
||||||
|
|
||||||
// (id, explanation, default value)
|
// (id, explanation, default value)
|
||||||
let settings: &[Setting] = &[
|
let settings: &[Setting] = &[
|
||||||
|
(
|
||||||
|
"Theme preferences",
|
||||||
|
vec![
|
||||||
|
Setting::from(("use-system-theme", "Use system theme", true)),
|
||||||
|
Setting::Select {
|
||||||
|
js_data_name: "preferred-dark-theme",
|
||||||
|
description: "Preferred dark theme",
|
||||||
|
default_value: "dark",
|
||||||
|
options: theme_names.clone(),
|
||||||
|
},
|
||||||
|
Setting::Select {
|
||||||
|
js_data_name: "preferred-light-theme",
|
||||||
|
description: "Preferred light theme",
|
||||||
|
default_value: "light",
|
||||||
|
options: theme_names,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
(
|
(
|
||||||
"Auto-hide item declarations",
|
"Auto-hide item declarations",
|
||||||
vec![
|
vec![
|
||||||
@ -1413,16 +1485,17 @@ fn settings(root_path: &str, suffix: &str) -> String {
|
|||||||
("line-numbers", "Show line numbers on code examples", false).into(),
|
("line-numbers", "Show line numbers on code examples", false).into(),
|
||||||
("disable-shortcuts", "Disable keyboard shortcuts", false).into(),
|
("disable-shortcuts", "Disable keyboard shortcuts", false).into(),
|
||||||
];
|
];
|
||||||
format!(
|
|
||||||
|
Ok(format!(
|
||||||
"<h1 class='fqn'>\
|
"<h1 class='fqn'>\
|
||||||
<span class='in-band'>Rustdoc settings</span>\
|
<span class='in-band'>Rustdoc settings</span>\
|
||||||
</h1>\
|
</h1>\
|
||||||
<div class='settings'>{}</div>\
|
<div class='settings'>{}</div>\
|
||||||
<script src='{}settings{}.js'></script>",
|
<script src='{}settings{}.js'></script>",
|
||||||
settings.iter().map(|s| s.display()).collect::<String>(),
|
settings.iter().map(|s| s.display(root_path, suffix)).collect::<String>(),
|
||||||
root_path,
|
root_path,
|
||||||
suffix
|
suffix
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl Context {
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.setting-line > div {
|
.setting-line > div {
|
||||||
max-width: calc(100% - 74px);
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
@ -30,6 +29,45 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.select-wrapper {
|
||||||
|
float: right;
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
height: 27px;
|
||||||
|
min-width: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-wrapper select {
|
||||||
|
appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
|
||||||
|
background: none;
|
||||||
|
border: 2px solid #ccc;
|
||||||
|
padding-right: 28px;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-wrapper img {
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
|
||||||
|
background: #ccc;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
width: 28px;
|
||||||
|
padding: 0px 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-wrapper select option {
|
||||||
|
color: initial;
|
||||||
|
}
|
||||||
|
|
||||||
.slider {
|
.slider {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -2,8 +2,16 @@
|
|||||||
/* global getCurrentValue, updateLocalStorage */
|
/* global getCurrentValue, updateLocalStorage */
|
||||||
|
|
||||||
(function () {
|
(function () {
|
||||||
function changeSetting(settingName, isEnabled) {
|
function changeSetting(settingName, value) {
|
||||||
updateLocalStorage('rustdoc-' + settingName, isEnabled);
|
updateLocalStorage('rustdoc-' + settingName, value);
|
||||||
|
|
||||||
|
switch (settingName) {
|
||||||
|
case 'preferred-dark-theme':
|
||||||
|
case 'preferred-light-theme':
|
||||||
|
case 'use-system-theme':
|
||||||
|
updateSystemTheme();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSettingValue(settingName) {
|
function getSettingValue(settingName) {
|
||||||
@ -11,20 +19,37 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setEvents() {
|
function setEvents() {
|
||||||
var elems = document.getElementsByClassName("slider");
|
var elems = {
|
||||||
if (!elems || elems.length === 0) {
|
toggles: document.getElementsByClassName("slider"),
|
||||||
return;
|
selects: document.getElementsByClassName("select-wrapper")
|
||||||
}
|
};
|
||||||
for (var i = 0; i < elems.length; ++i) {
|
|
||||||
var toggle = elems[i].previousElementSibling;
|
if (elems.toggles && elems.toggles.length > 0) {
|
||||||
var settingId = toggle.id;
|
for (var i = 0; i < elems.toggles.length; ++i) {
|
||||||
var settingValue = getSettingValue(settingId);
|
var toggle = elems.toggles[i].previousElementSibling;
|
||||||
if (settingValue !== null) {
|
var settingId = toggle.id;
|
||||||
toggle.checked = settingValue === "true";
|
var settingValue = getSettingValue(settingId);
|
||||||
|
if (settingValue !== null) {
|
||||||
|
toggle.checked = settingValue === "true";
|
||||||
|
}
|
||||||
|
toggle.onchange = function() {
|
||||||
|
changeSetting(this.id, this.checked);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (elems.selects && elems.selects.length > 0) {
|
||||||
|
for (var i = 0; i < elems.selects.length; ++i) {
|
||||||
|
var select = elems.selects[i].getElementsByTagName('select')[0];
|
||||||
|
var settingId = select.id;
|
||||||
|
var settingValue = getSettingValue(settingId);
|
||||||
|
if (settingValue !== null) {
|
||||||
|
select.value = settingValue;
|
||||||
|
}
|
||||||
|
select.onchange = function() {
|
||||||
|
changeSetting(this.id, this.value);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
toggle.onchange = function() {
|
|
||||||
changeSetting(this.id, this.checked);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,11 +118,71 @@ function switchTheme(styleElem, mainStyleElem, newTheme, saveTheme) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSystemValue() {
|
function useSystemTheme(value) {
|
||||||
var property = getComputedStyle(document.documentElement).getPropertyValue('content');
|
if (value === undefined) {
|
||||||
return property.replace(/[\"\']/g, "");
|
value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLocalStorage("rustdoc-use-system-theme", value);
|
||||||
|
|
||||||
|
// update the toggle if we're on the settings page
|
||||||
|
var toggle = document.getElementById("use-system-theme");
|
||||||
|
if (toggle && toggle instanceof HTMLInputElement) {
|
||||||
|
toggle.checked = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switchTheme(currentTheme, mainTheme,
|
var updateSystemTheme = (function() {
|
||||||
getCurrentValue("rustdoc-theme") || getSystemValue() || "light",
|
if (!window.matchMedia) {
|
||||||
false);
|
// fallback to the CSS computed value
|
||||||
|
return function() {
|
||||||
|
let cssTheme = getComputedStyle(document.documentElement)
|
||||||
|
.getPropertyValue('content');
|
||||||
|
|
||||||
|
switchTheme(
|
||||||
|
currentTheme,
|
||||||
|
mainTheme,
|
||||||
|
JSON.parse(cssTheme) || light,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// only listen to (prefers-color-scheme: dark) because light is the default
|
||||||
|
var mql = window.matchMedia("(prefers-color-scheme: dark)");
|
||||||
|
|
||||||
|
function handlePreferenceChange(mql) {
|
||||||
|
// maybe the user has disabled the setting in the meantime!
|
||||||
|
if (getCurrentValue("rustdoc-use-system-theme") !== "false") {
|
||||||
|
var lightTheme = getCurrentValue("rustdoc-preferred-light-theme") || "light";
|
||||||
|
var darkTheme = getCurrentValue("rustdoc-preferred-dark-theme") || "dark";
|
||||||
|
|
||||||
|
if (mql.matches) {
|
||||||
|
// prefers a dark theme
|
||||||
|
switchTheme(currentTheme, mainTheme, darkTheme, true);
|
||||||
|
} else {
|
||||||
|
// prefers a light theme, or has no preference
|
||||||
|
switchTheme(currentTheme, mainTheme, lightTheme, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// note: we save the theme so that it doesn't suddenly change when
|
||||||
|
// the user disables "use-system-theme" and reloads the page or
|
||||||
|
// navigates to another page
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mql.addListener(handlePreferenceChange);
|
||||||
|
|
||||||
|
return function() {
|
||||||
|
handlePreferenceChange(mql);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (getCurrentValue("rustdoc-use-system-theme") !== "false" && window.matchMedia) {
|
||||||
|
// call the function to initialize the theme at least once!
|
||||||
|
updateSystemTheme();
|
||||||
|
} else {
|
||||||
|
switchTheme(currentTheme, mainTheme,
|
||||||
|
getCurrentValue("rustdoc-theme") || "light",
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user