refactor(themes): use CSS specificity order for themes (#684)

The point of this PR is to make it easier to implement scrollbars (#683).

With this PR, the themes move from a body tag-based system (e.g. `body.theme-scarlet`) to a system where they simply declare global CSS and we use CSS specificity order to give us the right theme.
This commit is contained in:
Nolan Lawson 2018-11-24 00:41:36 -08:00 committed by GitHub
parent f0b3115be1
commit 48a1bd47b3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 120 additions and 141 deletions

View File

@ -34,18 +34,21 @@ function doWatch () {
chokidar.watch()
}
async function compileGlobalSass () {
let results = await Promise.all([
render({ file: defaultThemeScss, outputStyle: 'compressed' }),
render({ file: globalScss, outputStyle: 'compressed' }),
render({ file: offlineThemeScss, outputStyle: 'compressed' })
])
async function renderCss (file) {
return (await render({ file, outputStyle: 'compressed' })).css
}
let css = results.map(_ => _.css).join('')
async function compileGlobalSass () {
let mainStyle = (await Promise.all([defaultThemeScss, globalScss].map(renderCss))).join('')
let offlineStyle = (await renderCss(offlineThemeScss))
let html = await readFile(html2xxFile, 'utf8')
html = html.replace(/<style>[\s\S]+?<\/style>/,
`<style>\n/* auto-generated w/ build-sass.js */\n${css}\n</style>`)
html = html.replace(/<!-- begin inline CSS -->[\s\S]+<!-- end inline CSS -->/,
`<!-- begin inline CSS -->\n` +
`<style>\n${mainStyle}</style>\n` +
`<style media="only x" id="theOfflineStyle">\n${offlineStyle}</style>\n` +
`<!-- end inline CSS -->`
)
await writeFile(html2xxFile, html, 'utf8')
}

View File

@ -7,11 +7,11 @@ if (localStorage.store_currentInstance && localStorage.store_instanceThemes) {
let safeParse = (str) => str === 'undefined' ? undefined : JSON.parse(str)
let theme = safeParse(localStorage.store_instanceThemes)[safeParse(localStorage.store_currentInstance)]
if (theme && theme !== 'default') {
document.body.classList.add(`theme-${theme}`)
let link = document.createElement('link')
link.rel = 'stylesheet'
link.href = `/theme-${theme}.css`
document.head.appendChild(link)
// inserting before the offline <style> ensures that the offline style wins when offline
document.head.insertBefore(link, document.getElementById('theOfflineStyle'))
if (window.__themeColors[theme]) {
document.getElementById('theThemeColor').content = window.__themeColors[theme]
}

71
package-lock.json generated
View File

@ -529,7 +529,7 @@
},
"async": {
"version": "0.2.6",
"resolved": "http://registry.npmjs.org/async/-/async-0.2.6.tgz",
"resolved": "https://registry.npmjs.org/async/-/async-0.2.6.tgz",
"integrity": "sha1-rT83PZJJrjJIgVZVgryQ4VKrvWg=",
"dev": true
},
@ -629,7 +629,7 @@
"dependencies": {
"jsesc": {
"version": "1.3.0",
"resolved": "http://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz",
"integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=",
"dev": true
}
@ -815,49 +815,49 @@
},
"babel-plugin-syntax-async-functions": {
"version": "6.13.0",
"resolved": "http://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz",
"integrity": "sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU=",
"dev": true
},
"babel-plugin-syntax-async-generators": {
"version": "6.13.0",
"resolved": "http://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz",
"integrity": "sha1-a8lj67FuzLrmuStZbrfzXDQqi5o=",
"dev": true
},
"babel-plugin-syntax-class-properties": {
"version": "6.13.0",
"resolved": "http://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz",
"integrity": "sha1-1+sjt5oxf4VDlixQW4J8fWysJ94=",
"dev": true
},
"babel-plugin-syntax-decorators": {
"version": "6.13.0",
"resolved": "http://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz",
"integrity": "sha1-MSVjtNvePMgGzuPkFszurd0RrAs=",
"dev": true
},
"babel-plugin-syntax-dynamic-import": {
"version": "6.18.0",
"resolved": "http://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz",
"integrity": "sha1-jWomIpyDdFqZgqRBBRVyyqF5sdo=",
"dev": true
},
"babel-plugin-syntax-exponentiation-operator": {
"version": "6.13.0",
"resolved": "http://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz",
"integrity": "sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4=",
"dev": true
},
"babel-plugin-syntax-flow": {
"version": "6.18.0",
"resolved": "http://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz",
"integrity": "sha1-TDqyCiryaqIM0lmVw5jE63AxDI0=",
"dev": true
},
"babel-plugin-syntax-object-rest-spread": {
"version": "6.13.0",
"resolved": "http://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz",
"integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=",
"dev": true
},
@ -1555,7 +1555,7 @@
},
"bowser": {
"version": "1.6.0",
"resolved": "http://registry.npmjs.org/bowser/-/bowser-1.6.0.tgz",
"resolved": "https://registry.npmjs.org/bowser/-/bowser-1.6.0.tgz",
"integrity": "sha1-N/w4e2Fstq7zcNq01r1AK3TFxU0=",
"dev": true
},
@ -3041,7 +3041,7 @@
"dependencies": {
"source-map": {
"version": "0.1.43",
"resolved": "http://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz",
"integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=",
"dev": true,
"requires": {
@ -4480,11 +4480,6 @@
"resolved": "https://registry.npmjs.org/feature-policy/-/feature-policy-0.2.0.tgz",
"integrity": "sha512-2hGrlv6efG4hscYVZeaYjpzpT6I2OZgYqE2yDUzeAcKj2D1SH0AsEzqJNXzdoglEddcIXQQYop3lD97XpG75Jw=="
},
"fg-loadcss": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/fg-loadcss/-/fg-loadcss-2.0.1.tgz",
"integrity": "sha512-gFtSJjMMt9it0OhXz4wJQT46/LFUrJ2Db6U/fLtwClBEMEEjmVPSWSYrbGCyFwy47Cd4ULOpR+HSWXVkUKciaQ=="
},
"figgy-pudding": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz",
@ -5039,7 +5034,7 @@
"dependencies": {
"babel-plugin-transform-runtime": {
"version": "6.15.0",
"resolved": "http://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.15.0.tgz",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-runtime/-/babel-plugin-transform-runtime-6.15.0.tgz",
"integrity": "sha1-PXW02Umtga8VdXAnOEb7Wa6w1Xw=",
"dev": true,
"requires": {
@ -7437,7 +7432,7 @@
},
"kind-of": {
"version": "1.1.0",
"resolved": "http://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz",
"integrity": "sha1-FAo9LUGjbS78+pN3tiwk+ElaXEQ=",
"dev": true
}
@ -9941,7 +9936,7 @@
"dependencies": {
"globby": {
"version": "6.1.0",
"resolved": "http://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
"resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
"integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=",
"dev": true,
"requires": {
@ -9954,7 +9949,7 @@
"dependencies": {
"pify": {
"version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
}
@ -9970,7 +9965,7 @@
},
"globby": {
"version": "3.0.1",
"resolved": "http://registry.npmjs.org/globby/-/globby-3.0.1.tgz",
"resolved": "https://registry.npmjs.org/globby/-/globby-3.0.1.tgz",
"integrity": "sha1-IJSvhCHhkVIVDViT62QWsxLZoi8=",
"dev": true,
"requires": {
@ -10046,7 +10041,7 @@
},
"minimist": {
"version": "1.2.0",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
},
@ -10058,7 +10053,7 @@
},
"pify": {
"version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
},
@ -10109,7 +10104,7 @@
"dependencies": {
"babel-runtime": {
"version": "5.8.38",
"resolved": "http://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.38.tgz",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.38.tgz",
"integrity": "sha1-HAsC62MxL18If/IEUIJ7QlydTBk=",
"dev": true,
"requires": {
@ -10118,13 +10113,13 @@
},
"core-js": {
"version": "1.2.7",
"resolved": "http://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=",
"dev": true
},
"pify": {
"version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
}
@ -10175,7 +10170,7 @@
},
"lru-cache": {
"version": "2.6.3",
"resolved": "http://registry.npmjs.org/lru-cache/-/lru-cache-2.6.3.tgz",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.3.tgz",
"integrity": "sha1-UczQtPwMhDWH16VwnOTTt2Kb7cU=",
"dev": true
},
@ -10193,7 +10188,7 @@
},
"pify": {
"version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
},
@ -10242,7 +10237,7 @@
"dependencies": {
"babel-runtime": {
"version": "5.8.38",
"resolved": "http://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.38.tgz",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-5.8.38.tgz",
"integrity": "sha1-HAsC62MxL18If/IEUIJ7QlydTBk=",
"dev": true,
"requires": {
@ -10251,7 +10246,7 @@
},
"core-js": {
"version": "1.2.7",
"resolved": "http://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz",
"integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=",
"dev": true
},
@ -10269,7 +10264,7 @@
},
"pify": {
"version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
},
@ -10286,31 +10281,31 @@
},
"testcafe-reporter-json": {
"version": "2.1.0",
"resolved": "http://registry.npmjs.org/testcafe-reporter-json/-/testcafe-reporter-json-2.1.0.tgz",
"resolved": "https://registry.npmjs.org/testcafe-reporter-json/-/testcafe-reporter-json-2.1.0.tgz",
"integrity": "sha1-gLm1pt/y7h3h+R4mcHBsFHLmQAY=",
"dev": true
},
"testcafe-reporter-list": {
"version": "2.1.0",
"resolved": "http://registry.npmjs.org/testcafe-reporter-list/-/testcafe-reporter-list-2.1.0.tgz",
"resolved": "https://registry.npmjs.org/testcafe-reporter-list/-/testcafe-reporter-list-2.1.0.tgz",
"integrity": "sha1-n6ifcbl9Pf5ktDAtXiJ97mmuxrk=",
"dev": true
},
"testcafe-reporter-minimal": {
"version": "2.1.0",
"resolved": "http://registry.npmjs.org/testcafe-reporter-minimal/-/testcafe-reporter-minimal-2.1.0.tgz",
"resolved": "https://registry.npmjs.org/testcafe-reporter-minimal/-/testcafe-reporter-minimal-2.1.0.tgz",
"integrity": "sha1-Z28DVHY0FDxurzq1KGgnOkvr9CE=",
"dev": true
},
"testcafe-reporter-spec": {
"version": "2.1.1",
"resolved": "http://registry.npmjs.org/testcafe-reporter-spec/-/testcafe-reporter-spec-2.1.1.tgz",
"resolved": "https://registry.npmjs.org/testcafe-reporter-spec/-/testcafe-reporter-spec-2.1.1.tgz",
"integrity": "sha1-gVb87Q9RMkhlWa1WC8gGdkaSdew=",
"dev": true
},
"testcafe-reporter-xunit": {
"version": "2.1.0",
"resolved": "http://registry.npmjs.org/testcafe-reporter-xunit/-/testcafe-reporter-xunit-2.1.0.tgz",
"resolved": "https://registry.npmjs.org/testcafe-reporter-xunit/-/testcafe-reporter-xunit-2.1.0.tgz",
"integrity": "sha1-5tZsVyzhWvJmcGrw/WELKoQd1EM=",
"dev": true
},
@ -11054,7 +11049,7 @@
"dependencies": {
"pify": {
"version": "2.3.0",
"resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
"dev": true
},

View File

@ -64,7 +64,6 @@
"esm": "^3.0.84",
"events": "^3.0.0",
"express": "^4.16.4",
"fg-loadcss": "^2.0.1",
"file-api": "^0.10.4",
"font-awesome-svg-png": "^1.2.2",
"form-data": "^2.3.3",

View File

@ -6,6 +6,8 @@ const NOTIFY_OFFLINE_LIMIT = 1
let notifyCount = 0
let offlineStyle = process.browser && document.getElementById('theOfflineStyle')
// debounce to avoid notifying for a short connection issue
const notifyOffline = debounce(() => {
if (process.browser && !navigator.onLine && ++notifyCount <= NOTIFY_OFFLINE_LIMIT) {
@ -21,7 +23,8 @@ export function onlineObservers (store) {
let oldTheme = meta.content
store.observe('online', online => {
document.body.classList.toggle('offline', !online)
// "only x" ensures the <style> tag does not have any effect
offlineStyle.setAttribute('media', online ? 'only x' : 'all')
if (online) {
meta.content = oldTheme
} else {

View File

@ -1,19 +1,41 @@
import { loadCSS } from 'fg-loadcss'
let meta = process.browser && document.getElementById('theThemeColor')
let offlineStyle = process.browser && document.getElementById('theOfflineStyle')
function getExistingThemeLink () {
return document.head.querySelector('link[rel=stylesheet][href^="/theme-"]')
}
function resetExistingTheme () {
let existingLink = getExistingThemeLink()
if (existingLink) {
document.head.removeChild(existingLink)
}
}
function loadCSS (href) {
let existingLink = getExistingThemeLink()
let link = document.createElement('link')
link.rel = 'stylesheet'
link.href = href
link.addEventListener('load', function onload () {
link.removeEventListener('load', onload)
if (existingLink) { // remove after load to avoid flash of default theme
document.head.removeChild(existingLink)
}
})
// inserting before the offline <style> ensures that the offline style wins when offline
document.head.insertBefore(link, offlineStyle)
}
export function switchToTheme (themeName) {
let clazzList = document.body.classList
for (let i = 0; i < clazzList.length; i++) {
let clazz = clazzList.item(i)
if (clazz.startsWith('theme-')) {
clazzList.remove(clazz)
}
}
let themeColor = window.__themeColors[themeName]
meta.content = themeColor || window.__themeColors['default']
if (themeName !== 'default') {
clazzList.add(`theme-${themeName}`)
loadCSS(`/theme-${themeName}.css`)
} else {
resetExistingTheme()
}
}

View File

@ -1,5 +1,4 @@
@mixin baseTheme() {
:root {
$deemphasized-color: #666;
--button-primary-bg: #{lighten($main-theme-color, 7%)};

View File

@ -1,4 +1,4 @@
@mixin darkTheme() {
:root {
$deemphasized-color: lighten($main-bg-color, 45%);
--action-button-deemphasized-fill-color: #{$deemphasized-color};

View File

@ -10,8 +10,4 @@ $toast-bg: #333;
$focus-outline: lighten($main-theme-color, 30%);
$compose-background: lighten($main-theme-color, 32%);
@import "_base.scss";
body {
@include baseTheme();
}
@import "_base.scss";

View File

@ -11,7 +11,3 @@ $focus-outline: lighten($main-theme-color, 15%);
$compose-background: lighten($main-theme-color, 17%);
@import "_base.scss";
body.the-body.offline { /* "the-body" is a specificity hack to allow offline to always trump themes */
@include baseTheme();
}

View File

@ -13,10 +13,7 @@ $compose-background: lighten($main-theme-color, 32%);
@import "_base.scss";
@import "_dark.scss";
body.theme-cobalt {
@include baseTheme();
@include darkTheme();
:root {
--settings-list-item-text: #{$main-text-color};
--settings-list-item-text-hover: #{$main-text-color};

View File

@ -11,7 +11,3 @@ $focus-outline: lighten($main-theme-color, 30%);
$compose-background: lighten($main-theme-color, 32%);
@import "_base.scss";
body.theme-gecko {
@include baseTheme();
}

View File

@ -14,10 +14,7 @@ $compose-background: lighten($main-theme-color, 52%);
@import "_base.scss";
@import "_dark.scss";
body.theme-hacker {
@include baseTheme();
@include darkTheme();
:root {
--nav-bg: #{lighten($body-bg-color, 10%)};
--nav-a-selected-bg: #{lighten($body-bg-color, 25%)};
--nav-a-bg-hover: #{lighten($body-bg-color, 25%)};

View File

@ -11,7 +11,3 @@ $focus-outline: lighten($main-theme-color, 15%);
$compose-background: lighten($main-theme-color, 17%);
@import "_base.scss";
body.theme-hotpants {
@include baseTheme();
}

View File

@ -11,7 +11,3 @@ $focus-outline: lighten($main-theme-color, 30%);
$compose-background: lighten($main-theme-color, 32%);
@import "_base.scss";
body.theme-majesty {
@include baseTheme();
}

View File

@ -11,7 +11,3 @@ $focus-outline: lighten($main-theme-color, 30%);
$compose-background: lighten($main-theme-color, 32%);
@import "_base.scss";
body.theme-oaken {
@include baseTheme();
}

View File

@ -11,9 +11,4 @@ $focus-outline: darken($main-theme-color, 10%);
$compose-background: darken($main-theme-color, 12%);
@import "_base.scss";
@import "_dark.scss";
body.theme-ozark {
@include baseTheme();
@include darkTheme();
}
@import "_dark.scss";

View File

@ -14,10 +14,7 @@ $compose-background: lighten($main-theme-color, 52%);
@import "_base.scss";
@import "_dark.scss";
body.theme-punk {
@include baseTheme();
@include darkTheme();
:root {
--nav-bg: #{lighten($body-bg-color, 10%)};
--nav-a-selected-bg: #{lighten($body-bg-color, 25%)};
--nav-a-bg-hover: #{lighten($body-bg-color, 25%)};

View File

@ -14,10 +14,7 @@ $compose-background: lighten($main-theme-color, 52%);
@import "_base.scss";
@import "_dark.scss";
body.theme-riot {
@include baseTheme();
@include darkTheme();
:root {
--nav-bg: #{lighten($body-bg-color, 10%)};
--nav-a-selected-bg: #{lighten($body-bg-color, 25%)};
--nav-a-bg-hover: #{lighten($body-bg-color, 25%)};

View File

@ -11,7 +11,3 @@ $focus-outline: lighten($main-theme-color, 30%);
$compose-background: lighten($main-theme-color, 32%);
@import "_base.scss";
body.theme-scarlet {
@include baseTheme();
}

View File

@ -11,7 +11,3 @@ $focus-outline: lighten($main-theme-color, 50%);
$compose-background: lighten($main-theme-color, 52%);
@import "_base.scss";
body.theme-seafoam {
@include baseTheme();
}

View File

@ -14,10 +14,7 @@ $compose-background: lighten($main-theme-color, 52%);
@import "_base.scss";
@import "_dark.scss";
body.theme-sorcery {
@include baseTheme();
@include darkTheme();
:root {
--nav-bg: #{lighten($body-bg-color, 10%)};
--nav-a-selected-bg: #{lighten($body-bg-color, 25%)};
--nav-a-bg-hover: #{lighten($body-bg-color, 25%)};

View File

@ -14,13 +14,15 @@
<meta name="apple-mobile-web-app-title" content="Pinafore" >
<meta name="apple-mobile-web-app-status-bar-style" content="white" >
<style>
/* auto-generated w/ build-sass.js */
body{--button-primary-bg: #6081e6;--button-primary-text: #fff;--button-primary-border: #132c76;--button-primary-bg-active: #456ce2;--button-primary-bg-hover: #6988e7;--button-bg: #e6e6e6;--button-text: #333;--button-border: #a7a7a7;--button-bg-active: #bfbfbf;--button-bg-hover: #f2f2f2;--input-border: #dadada;--anchor-text: #4169e1;--main-bg: #fff;--body-bg: #e8edfb;--body-text-color: #333;--main-border: #dadada;--svg-fill: #4169e1;--form-bg: #f7f7f7;--form-border: #c1c1c1;--nav-bg: #4169e1;--nav-border: #214cce;--nav-a-border: #4169e1;--nav-a-selected-border: #fff;--nav-a-selected-bg: #6d8ce8;--nav-svg-fill: #fff;--nav-text-color: #fff;--nav-a-selected-border-hover: #fff;--nav-a-selected-bg-hover: #839deb;--nav-a-bg-hover: #577ae4;--nav-a-border-hover: #4169e1;--nav-svg-fill-hover: #fff;--nav-text-color-hover: #fff;--action-button-fill-color: #90a8ee;--action-button-fill-color-hover: #a2b6f0;--action-button-fill-color-active: #577ae4;--action-button-fill-color-pressed: #2351dc;--action-button-fill-color-pressed-hover: #3862e0;--action-button-fill-color-pressed-active: #1d44b8;--action-button-deemphasized-fill-color: #666;--action-button-deemphasized-fill-color-hover: #9e9e9e;--action-button-deemphasized-fill-color-active: #737373;--action-button-deemphasized-fill-color-pressed: #545454;--action-button-deemphasized-fill-color-pressed-hover: #616161;--action-button-deemphasized-fill-color-pressed-active: #404040;--settings-list-item-bg: #fff;--settings-list-item-text: #4169e1;--settings-list-item-text-hover: #4169e1;--settings-list-item-border: #dadada;--settings-list-item-bg-active: #e6e6e6;--settings-list-item-bg-hover: #fafafa;--toast-bg: #333;--toast-border: #fafafa;--toast-text: #fff;--mask-bg: #333;--mask-svg-fill: #fff;--mask-opaque-bg: rgba(51,51,51,0.8);--loading-bg: #ededed;--account-profile-bg-backdrop-filter: rgba(255,255,255,0.7);--account-profile-bg: rgba(255,255,255,0.9);--deemphasized-text-color: #666;--focus-outline: #c5d1f6;--very-deemphasized-link-color: rgba(65,105,225,0.6);--very-deemphasized-text-color: rgba(102,102,102,0.6);--status-direct-background: #d2dcf8;--main-theme-color: #4169e1;--warning-color: #e01f19;--alt-input-bg: rgba(255,255,255,0.7);--muted-modal-bg: transparent;--muted-modal-focus: #999;--muted-modal-hover: rgba(255,255,255,0.2);--compose-autosuggest-item-hover: #ced8f7;--compose-autosuggest-item-active: #b8c7f4;--compose-autosuggest-outline: #dbe3f9;--compose-button-halo: rgba(255,255,255,0.1)}
<!-- begin inline CSS -->
<style>
:root{--button-primary-bg: #6081e6;--button-primary-text: #fff;--button-primary-border: #132c76;--button-primary-bg-active: #456ce2;--button-primary-bg-hover: #6988e7;--button-bg: #e6e6e6;--button-text: #333;--button-border: #a7a7a7;--button-bg-active: #bfbfbf;--button-bg-hover: #f2f2f2;--input-border: #dadada;--anchor-text: #4169e1;--main-bg: #fff;--body-bg: #e8edfb;--body-text-color: #333;--main-border: #dadada;--svg-fill: #4169e1;--form-bg: #f7f7f7;--form-border: #c1c1c1;--nav-bg: #4169e1;--nav-border: #214cce;--nav-a-border: #4169e1;--nav-a-selected-border: #fff;--nav-a-selected-bg: #6d8ce8;--nav-svg-fill: #fff;--nav-text-color: #fff;--nav-a-selected-border-hover: #fff;--nav-a-selected-bg-hover: #839deb;--nav-a-bg-hover: #577ae4;--nav-a-border-hover: #4169e1;--nav-svg-fill-hover: #fff;--nav-text-color-hover: #fff;--action-button-fill-color: #90a8ee;--action-button-fill-color-hover: #a2b6f0;--action-button-fill-color-active: #577ae4;--action-button-fill-color-pressed: #2351dc;--action-button-fill-color-pressed-hover: #3862e0;--action-button-fill-color-pressed-active: #1d44b8;--action-button-deemphasized-fill-color: #666;--action-button-deemphasized-fill-color-hover: #9e9e9e;--action-button-deemphasized-fill-color-active: #737373;--action-button-deemphasized-fill-color-pressed: #545454;--action-button-deemphasized-fill-color-pressed-hover: #616161;--action-button-deemphasized-fill-color-pressed-active: #404040;--settings-list-item-bg: #fff;--settings-list-item-text: #4169e1;--settings-list-item-text-hover: #4169e1;--settings-list-item-border: #dadada;--settings-list-item-bg-active: #e6e6e6;--settings-list-item-bg-hover: #fafafa;--toast-bg: #333;--toast-border: #fafafa;--toast-text: #fff;--mask-bg: #333;--mask-svg-fill: #fff;--mask-opaque-bg: rgba(51,51,51,0.8);--loading-bg: #ededed;--account-profile-bg-backdrop-filter: rgba(255,255,255,0.7);--account-profile-bg: rgba(255,255,255,0.9);--deemphasized-text-color: #666;--focus-outline: #c5d1f6;--very-deemphasized-link-color: rgba(65,105,225,0.6);--very-deemphasized-text-color: rgba(102,102,102,0.6);--status-direct-background: #d2dcf8;--main-theme-color: #4169e1;--warning-color: #e01f19;--alt-input-bg: rgba(255,255,255,0.7);--muted-modal-bg: transparent;--muted-modal-focus: #999;--muted-modal-hover: rgba(255,255,255,0.2);--compose-autosuggest-item-hover: #ced8f7;--compose-autosuggest-item-active: #b8c7f4;--compose-autosuggest-outline: #dbe3f9;--compose-button-halo: rgba(255,255,255,0.1)}
@font-face{font-family:PinaforeRegular;src:local("BlinkMacSystemFont"),local("Segoe UI"),local("Roboto"),local("Oxygen-Sans"),local("Ubuntu"),local("Cantarell"),local("Fira Sans"),local("Droid Sans"),local("Helvetica Neue")}@font-face{font-family:PinaforeEmoji;src:local("Apple Color Emoji"),local("Segoe UI Emoji"),local("Segoe UI Symbol"),local("Twemoji Mozilla"),local("Noto Color Emoji"),local("EmojiOne Color"),local("Android Emoji")}body{margin:0;font-family:system-ui, -apple-system, PinaforeRegular, sans-serif;font-size:14px;line-height:1.4;color:var(--body-text-color);background:var(--body-bg);-webkit-tap-highlight-color:transparent;overflow-x:hidden}.main-content{contain:content;padding-top:42px}@media (max-width: 991px){.main-content{padding-top:52px}}@media (max-width: 767px){.main-content{padding-top:62px}}main{position:relative;width:602px;max-width:100vw;padding:0;box-sizing:border-box;margin:30px auto 15px;background:var(--main-bg);border:1px solid var(--main-border);border-radius:1px;min-height:70vh}@media (max-width: 767px){main{margin:5px auto 15px}}footer{width:602px;max-width:100vw;box-sizing:border-box;margin:15px auto;border-radius:1px;background:var(--main-bg);font-size:0.9em;padding:20px;border:1px solid var(--main-border)}h1,h2,h3,h4,h5,h6{margin:0 0 0.5em 0;font-weight:400;line-height:1.2}h1{font-size:2em}a{color:var(--anchor-text);text-decoration:none}a:visited{color:var(--anchor-text)}a:hover{text-decoration:underline}input{border:1px solid var(--input-border);padding:5px;box-sizing:border-box}input[type=search]{-webkit-appearance:none}input,textarea{background:inherit;color:inherit}textarea{font-family:system-ui, -apple-system, PinaforeRegular, sans-serif, PinaforeEmoji}button,.button{font-size:1.2em;background:var(--button-bg);border-radius:2px;padding:10px 15px;border:1px solid var(--button-border);cursor:pointer;color:var(--button-text)}button:hover,.button:hover{background:var(--button-bg-hover);text-decoration:none}button:active,.button:active{background:var(--button-bg-active)}button[disabled],.button[disabled]{opacity:0.35;pointer-events:none;cursor:not-allowed}button.primary,.button.primary{border:1px solid var(--button-primary-border);background:var(--button-primary-bg);color:var(--button-primary-text)}button.primary:hover,.button.primary:hover{background:var(--button-primary-bg-hover)}button.primary:active,.button.primary:active{background:var(--button-primary-bg-active)}p,label,input{font-size:1.3em}ul,li,p{padding:0;margin:0}.hidden{opacity:0}*:focus{outline:2px solid var(--focus-outline)}.container:focus{outline:none}button::-moz-focus-inner{border:0}input:required,input:invalid{box-shadow:none}textarea{font-family:inherit;font-size:inherit;box-sizing:border-box}@keyframes spin{0%{transform:rotate(0deg)}25%{transform:rotate(90deg)}50%{transform:rotate(180deg)}75%{transform:rotate(270deg)}100%{transform:rotate(360deg)}}.spin{animation:spin 1.5s infinite linear}.ellipsis::after{content:"\2026"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.inline-custom-emoji{width:1.4em;height:1.4em;margin:-0.1em 0;object-fit:contain;vertical-align:middle}.inline-emoji{font-family:PinaforeEmoji, sans-serif}
body.the-body.offline{--button-primary-bg: #ababab;--button-primary-text: #fff;--button-primary-border: #4d4d4d;--button-primary-bg-active: #9c9c9c;--button-primary-bg-hover: #b0b0b0;--button-bg: #e6e6e6;--button-text: #333;--button-border: #a7a7a7;--button-bg-active: #bfbfbf;--button-bg-hover: #f2f2f2;--input-border: #dadada;--anchor-text: #999;--main-bg: #fff;--body-bg: #fafafa;--body-text-color: #333;--main-border: #dadada;--svg-fill: #999;--form-bg: #f7f7f7;--form-border: #c1c1c1;--nav-bg: #999;--nav-border: gray;--nav-a-border: #999;--nav-a-selected-border: #fff;--nav-a-selected-bg: #b3b3b3;--nav-svg-fill: #fff;--nav-text-color: #fff;--nav-a-selected-border-hover: #fff;--nav-a-selected-bg-hover: #bfbfbf;--nav-a-bg-hover: #a6a6a6;--nav-a-border-hover: #999;--nav-svg-fill-hover: #fff;--nav-text-color-hover: #fff;--action-button-fill-color: #c7c7c7;--action-button-fill-color-hover: #d1d1d1;--action-button-fill-color-active: #a6a6a6;--action-button-fill-color-pressed: #878787;--action-button-fill-color-pressed-hover: #949494;--action-button-fill-color-pressed-active: #737373;--action-button-deemphasized-fill-color: #666;--action-button-deemphasized-fill-color-hover: #9e9e9e;--action-button-deemphasized-fill-color-active: #737373;--action-button-deemphasized-fill-color-pressed: #545454;--action-button-deemphasized-fill-color-pressed-hover: #616161;--action-button-deemphasized-fill-color-pressed-active: #404040;--settings-list-item-bg: #fff;--settings-list-item-text: #999;--settings-list-item-text-hover: #999;--settings-list-item-border: #dadada;--settings-list-item-bg-active: #e6e6e6;--settings-list-item-bg-hover: #fafafa;--toast-bg: #333;--toast-border: #fafafa;--toast-text: #fff;--mask-bg: #333;--mask-svg-fill: #fff;--mask-opaque-bg: rgba(51,51,51,0.8);--loading-bg: #ededed;--account-profile-bg-backdrop-filter: rgba(255,255,255,0.7);--account-profile-bg: rgba(255,255,255,0.9);--deemphasized-text-color: #666;--focus-outline: #bfbfbf;--very-deemphasized-link-color: rgba(153,153,153,0.6);--very-deemphasized-text-color: rgba(102,102,102,0.6);--status-direct-background: #ededed;--main-theme-color: #999;--warning-color: #e01f19;--alt-input-bg: rgba(255,255,255,0.7);--muted-modal-bg: transparent;--muted-modal-focus: #999;--muted-modal-hover: rgba(255,255,255,0.2);--compose-autosuggest-item-hover: #c4c4c4;--compose-autosuggest-item-active: #b8b8b8;--compose-autosuggest-outline: #ccc;--compose-button-halo: rgba(255,255,255,0.1)}
</style>
<style media="only x" id="theOfflineStyle">
:root{--button-primary-bg: #ababab;--button-primary-text: #fff;--button-primary-border: #4d4d4d;--button-primary-bg-active: #9c9c9c;--button-primary-bg-hover: #b0b0b0;--button-bg: #e6e6e6;--button-text: #333;--button-border: #a7a7a7;--button-bg-active: #bfbfbf;--button-bg-hover: #f2f2f2;--input-border: #dadada;--anchor-text: #999;--main-bg: #fff;--body-bg: #fafafa;--body-text-color: #333;--main-border: #dadada;--svg-fill: #999;--form-bg: #f7f7f7;--form-border: #c1c1c1;--nav-bg: #999;--nav-border: gray;--nav-a-border: #999;--nav-a-selected-border: #fff;--nav-a-selected-bg: #b3b3b3;--nav-svg-fill: #fff;--nav-text-color: #fff;--nav-a-selected-border-hover: #fff;--nav-a-selected-bg-hover: #bfbfbf;--nav-a-bg-hover: #a6a6a6;--nav-a-border-hover: #999;--nav-svg-fill-hover: #fff;--nav-text-color-hover: #fff;--action-button-fill-color: #c7c7c7;--action-button-fill-color-hover: #d1d1d1;--action-button-fill-color-active: #a6a6a6;--action-button-fill-color-pressed: #878787;--action-button-fill-color-pressed-hover: #949494;--action-button-fill-color-pressed-active: #737373;--action-button-deemphasized-fill-color: #666;--action-button-deemphasized-fill-color-hover: #9e9e9e;--action-button-deemphasized-fill-color-active: #737373;--action-button-deemphasized-fill-color-pressed: #545454;--action-button-deemphasized-fill-color-pressed-hover: #616161;--action-button-deemphasized-fill-color-pressed-active: #404040;--settings-list-item-bg: #fff;--settings-list-item-text: #999;--settings-list-item-text-hover: #999;--settings-list-item-border: #dadada;--settings-list-item-bg-active: #e6e6e6;--settings-list-item-bg-hover: #fafafa;--toast-bg: #333;--toast-border: #fafafa;--toast-text: #fff;--mask-bg: #333;--mask-svg-fill: #fff;--mask-opaque-bg: rgba(51,51,51,0.8);--loading-bg: #ededed;--account-profile-bg-backdrop-filter: rgba(255,255,255,0.7);--account-profile-bg: rgba(255,255,255,0.9);--deemphasized-text-color: #666;--focus-outline: #bfbfbf;--very-deemphasized-link-color: rgba(153,153,153,0.6);--very-deemphasized-text-color: rgba(102,102,102,0.6);--status-direct-background: #ededed;--main-theme-color: #999;--warning-color: #e01f19;--alt-input-bg: rgba(255,255,255,0.7);--muted-modal-bg: transparent;--muted-modal-focus: #999;--muted-modal-hover: rgba(255,255,255,0.2);--compose-autosuggest-item-hover: #c4c4c4;--compose-autosuggest-item-active: #b8b8b8;--compose-autosuggest-outline: #ccc;--compose-button-halo: rgba(255,255,255,0.1)}
</style>
<!-- end inline CSS -->
<noscript>
<style>
@ -39,7 +41,7 @@ body.the-body.offline{--button-primary-bg: #ababab;--button-primary-text: #fff;-
the current page has one -->
%sapper.head%
</head>
<body class="the-body">
<body>
<!-- auto-generated w/ build-inline-script.js -->
<!-- insert inline script here --><script>(function () {'use strict'
// For perf reasons, this script is run inline to quickly set certain styles.
@ -51,11 +53,11 @@ if (localStorage.store_currentInstance && localStorage.store_instanceThemes) {
let safeParse = (str) => str === 'undefined' ? undefined : JSON.parse(str)
let theme = safeParse(localStorage.store_instanceThemes)[safeParse(localStorage.store_currentInstance)]
if (theme && theme !== 'default') {
document.body.classList.add(`theme-${theme}`)
let link = document.createElement('link')
link.rel = 'stylesheet'
link.href = `/theme-${theme}.css`
document.head.appendChild(link)
// inserting before the offline <style> ensures that the offline style wins when offline
document.head.insertBefore(link, document.getElementById('theOfflineStyle'))
if (window.__themeColors[theme]) {
document.getElementById('theThemeColor').content = window.__themeColors[theme]
}

View File

@ -1,7 +1,7 @@
import {
getNthStatus, scrollToStatus, closeDialogButton, modalDialogContents, getActiveElementClass, goBack, getUrl,
goBackButton, getActiveElementInnerText, getNthReplyButton, getActiveElementInsideNthStatus, focus,
getNthStatusSelector
getNthStatusSelector, getActiveElementTagName
} from '../utils'
import { loginAsFoobar } from '../roles'
import { Selector as $ } from 'testcafe'
@ -103,6 +103,6 @@ test('reply preserves focus and moves focus to the text input', async t => {
.expect(getActiveElementClass()).contains('compose-box-input')
})
test('focus .main-content div on index page load', async t => {
await t.expect(getActiveElementClass()).contains('the-body')
test('focus main content element on index page load', async t => {
await t.expect(getActiveElementTagName()).match(/body/i)
})

View File

@ -1,5 +1,5 @@
import {
getBodyClassList,
getCurrentTheme,
settingsNavButton
} from '../utils'
import { loginAsFoobar } from '../roles'
@ -14,9 +14,9 @@ test('can set a theme', async t => {
.click(settingsNavButton)
.click($('a[href="/settings/instances"]'))
.click($('a[href="/settings/instances/localhost:3000"]'))
.expect(getBodyClassList()).eql([])
.expect(getCurrentTheme()).eql('default')
.click($('input[value="scarlet"]'))
.expect(getBodyClassList()).eql(['theme-scarlet'])
.expect(getCurrentTheme()).eql('scarlet')
.click($('input[value="default"]'))
.expect(getBodyClassList()).eql([])
.expect(getCurrentTheme()).eql('default')
})

View File

@ -60,6 +60,10 @@ export const getActiveElementClass = exec(() =>
(document.activeElement && document.activeElement.getAttribute('class')) || ''
)
export const getActiveElementTagName = exec(() =>
(document.activeElement && document.activeElement.tagName) || ''
)
export const getActiveElementInnerText = exec(() =>
(document.activeElement && document.activeElement.innerText) || ''
)
@ -87,9 +91,13 @@ export const getComposeSelectionStart = exec(() => composeInput().selectionStart
dependencies: { composeInput }
})
export const getBodyClassList = exec(() => (
Array.prototype.slice.apply(document.body.classList).filter(_ => _ !== 'the-body'))
)
export const getCurrentTheme = exec(() => {
let themeLink = document.head.querySelector('link[rel=stylesheet][href^="/theme-"]')
if (themeLink) {
return themeLink.getAttribute('href').match(/^\/theme-(.*)\.css$/, '')[1]
}
return 'default'
})
export const uploadKittenImage = i => (exec(() => {
let image = images[`kitten${i}`]