feat: implement wellness settings (#1256)
* implement wellness settings fixes #1192 Adds - grayscale mode (as well as separate grayscale/dark grayscale themes) - disable follower/boost/fav counts (follower counts capped at 10) - disable unread notification count (red dot) * fix lint * fix crawler
This commit is contained in:
parent
27864fc47f
commit
a35f5ee2d9
|
@ -11,7 +11,6 @@ const render = promisify(sass.render.bind(sass))
|
|||
|
||||
const globalScss = path.join(__dirname, '../src/scss/global.scss')
|
||||
const defaultThemeScss = path.join(__dirname, '../src/scss/themes/_default.scss')
|
||||
const offlineThemeScss = path.join(__dirname, '../src/scss/themes/_offline.scss')
|
||||
const customScrollbarScss = path.join(__dirname, '../src/scss/custom-scrollbars.scss')
|
||||
const themesScssDir = path.join(__dirname, '../src/scss/themes')
|
||||
const assetsDir = path.join(__dirname, '../static')
|
||||
|
@ -22,11 +21,9 @@ async function renderCss (file) {
|
|||
|
||||
async function compileGlobalSass () {
|
||||
let mainStyle = (await Promise.all([defaultThemeScss, globalScss].map(renderCss))).join('')
|
||||
let offlineStyle = (await renderCss(offlineThemeScss))
|
||||
let scrollbarStyle = (await renderCss(customScrollbarScss))
|
||||
|
||||
return `<style>\n${mainStyle}</style>\n` +
|
||||
`<style media="only x" id="theOfflineStyle">\n${offlineStyle}</style>\n` +
|
||||
`<style media="all" id="theScrollbarStyle">\n${scrollbarStyle}</style>\n`
|
||||
}
|
||||
|
||||
|
|
|
@ -51,5 +51,6 @@ module.exports = [
|
|||
{ id: 'fa-bar-chart', src: 'src/thirdparty/font-awesome-svg-png/white/svg/bar-chart.svg' },
|
||||
{ id: 'fa-clock', src: 'src/thirdparty/font-awesome-svg-png/white/svg/clock-o.svg' },
|
||||
{ id: 'fa-refresh', src: 'src/thirdparty/font-awesome-svg-png/white/svg/refresh.svg' },
|
||||
{ id: 'fa-plus', src: 'src/thirdparty/font-awesome-svg-png/white/svg/plus.svg' }
|
||||
{ id: 'fa-plus', src: 'src/thirdparty/font-awesome-svg-png/white/svg/plus.svg' },
|
||||
{ id: 'fa-info-circle', src: 'src/thirdparty/font-awesome-svg-png/white/svg/info-circle.svg' }
|
||||
]
|
||||
|
|
|
@ -3,16 +3,21 @@
|
|||
// To allow CSP to work correctly, we also calculate a sha256 hash during
|
||||
// the build process and write it to checksum.js.
|
||||
|
||||
import { testHasLocalStorageOnce } from '../routes/_utils/testStorage'
|
||||
import { INLINE_THEME, DEFAULT_THEME, switchToTheme } from '../routes/_utils/themeEngine'
|
||||
import { basename } from '../routes/_api/utils'
|
||||
import { onUserIsLoggedOut } from '../routes/_actions/onUserIsLoggedOut'
|
||||
import { storeLite } from '../routes/_store/storeLite'
|
||||
|
||||
window.__themeColors = process.env.THEME_COLORS
|
||||
|
||||
const safeParse = str => (typeof str === 'undefined' || str === 'undefined') ? undefined : JSON.parse(str)
|
||||
const hasLocalStorage = testHasLocalStorageOnce()
|
||||
const currentInstance = hasLocalStorage && safeParse(localStorage.store_currentInstance)
|
||||
const {
|
||||
currentInstance,
|
||||
instanceThemes,
|
||||
disableCustomScrollbars,
|
||||
enableGrayscale
|
||||
} = storeLite.get()
|
||||
|
||||
const theme = (instanceThemes && instanceThemes[currentInstance]) || DEFAULT_THEME
|
||||
|
||||
if (currentInstance) {
|
||||
// Do prefetch if we're logged in, so we can connect faster to the other origin.
|
||||
|
@ -26,24 +31,23 @@ if (currentInstance) {
|
|||
document.head.appendChild(link)
|
||||
}
|
||||
|
||||
let theme = (currentInstance &&
|
||||
localStorage.store_instanceThemes &&
|
||||
safeParse(localStorage.store_instanceThemes)[safeParse(localStorage.store_currentInstance)]) ||
|
||||
DEFAULT_THEME
|
||||
if (theme !== INLINE_THEME) {
|
||||
// switch theme ASAP to minimize flash of default theme
|
||||
switchToTheme(theme)
|
||||
switchToTheme(theme, enableGrayscale)
|
||||
}
|
||||
|
||||
if (!hasLocalStorage || !currentInstance) {
|
||||
if (enableGrayscale) {
|
||||
document.body.classList.add('grayscale')
|
||||
}
|
||||
|
||||
if (!currentInstance) {
|
||||
// if not logged in, show all these 'hidden-from-ssr' elements
|
||||
onUserIsLoggedOut()
|
||||
}
|
||||
|
||||
if (hasLocalStorage && localStorage.store_disableCustomScrollbars === 'true') {
|
||||
// if user has disabled custom scrollbars, remove this style
|
||||
let theScrollbarStyle = document.getElementById('theScrollbarStyle')
|
||||
theScrollbarStyle.setAttribute('media', 'only x') // disables the style
|
||||
if (disableCustomScrollbars) {
|
||||
document.getElementById('theScrollbarStyle')
|
||||
.setAttribute('media', 'only x') // disables the style
|
||||
}
|
||||
|
||||
// hack to make the scrollbars rounded only on macOS
|
||||
|
|
|
@ -84,7 +84,8 @@ async function registerNewInstance (code) {
|
|||
instanceThemes: instanceThemes
|
||||
})
|
||||
store.save()
|
||||
switchToTheme(DEFAULT_THEME)
|
||||
let { enableGrayscale } = store.get()
|
||||
switchToTheme(DEFAULT_THEME, enableGrayscale)
|
||||
// fire off these requests so they're cached
|
||||
/* no await */ updateVerifyCredentialsForInstance(currentRegisteredInstanceName)
|
||||
/* no await */ updateCustomEmojiForInstance(currentRegisteredInstanceName)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { getVerifyCredentials } from '../_api/user'
|
||||
import { store } from '../_store/store'
|
||||
import { DEFAULT_THEME, switchToTheme } from '../_utils/themeEngine'
|
||||
import { switchToTheme } from '../_utils/themeEngine'
|
||||
import { toast } from '../_components/toast/toast'
|
||||
import { goto } from '../../../__sapper__/client'
|
||||
import { cacheFirstUpdateAfter } from '../_utils/sync'
|
||||
|
@ -14,7 +14,8 @@ export function changeTheme (instanceName, newTheme) {
|
|||
store.save()
|
||||
let { currentInstance } = store.get()
|
||||
if (instanceName === currentInstance) {
|
||||
switchToTheme(newTheme)
|
||||
let { enableGrayscale } = store.get()
|
||||
switchToTheme(newTheme, enableGrayscale)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,7 +27,8 @@ export function switchToInstance (instanceName) {
|
|||
queryInSearch: ''
|
||||
})
|
||||
store.save()
|
||||
switchToTheme(instanceThemes[instanceName])
|
||||
let { enableGrayscale } = store.get()
|
||||
switchToTheme(instanceThemes[instanceName], enableGrayscale)
|
||||
}
|
||||
|
||||
export async function logOutOfInstance (instanceName) {
|
||||
|
@ -55,7 +57,8 @@ export async function logOutOfInstance (instanceName) {
|
|||
})
|
||||
store.save()
|
||||
toast.say(`Logged out of ${instanceName}`)
|
||||
switchToTheme(instanceThemes[newInstance] || DEFAULT_THEME)
|
||||
let { enableGrayscale } = store.get()
|
||||
switchToTheme(instanceThemes[newInstance], enableGrayscale)
|
||||
/* no await */ database.clearDatabaseForInstance(instanceName)
|
||||
goto('/settings/instances')
|
||||
}
|
||||
|
|
|
@ -127,7 +127,12 @@
|
|||
numFollowers: ({ account }) => account.followers_count || 0,
|
||||
numStatusesDisplay: ({ numStatuses }) => numberFormat.format(numStatuses),
|
||||
numFollowingDisplay: ({ numFollowing }) => numberFormat.format(numFollowing),
|
||||
numFollowersDisplay: ({ numFollowers }) => numberFormat.format(numFollowers),
|
||||
numFollowersDisplay: ({ numFollowers, $disableFollowerCounts }) => {
|
||||
if ($disableFollowerCounts && numFollowers >= 10) {
|
||||
return '10+'
|
||||
}
|
||||
return numberFormat.format(numFollowers)
|
||||
},
|
||||
followersLabel: ({ numFollowers }) => `Followed by ${numFollowers}`,
|
||||
followingLabel: ({ numFollowing }) => `Follows ${numFollowing}`
|
||||
},
|
||||
|
|
|
@ -158,13 +158,19 @@
|
|||
application: ({ originalStatus }) => originalStatus.application,
|
||||
applicationName: ({ application }) => (application && application.name),
|
||||
applicationWebsite: ({ application }) => (application && application.website),
|
||||
numReblogs: ({ overrideNumReblogs, originalStatus }) => {
|
||||
numReblogs: ({ $disableReblogCounts, overrideNumReblogs, originalStatus }) => {
|
||||
if ($disableReblogCounts) {
|
||||
return 0
|
||||
}
|
||||
if (typeof overrideNumReblogs === 'number') {
|
||||
return overrideNumReblogs
|
||||
}
|
||||
return originalStatus.reblogs_count || 0
|
||||
},
|
||||
numFavs: ({ overrideNumFavs, originalStatus }) => {
|
||||
numFavs: ({ $disableFavCounts, overrideNumFavs, originalStatus }) => {
|
||||
if ($disableFavCounts) {
|
||||
return 0
|
||||
}
|
||||
if (typeof overrideNumFavs === 'number') {
|
||||
return overrideNumFavs
|
||||
}
|
||||
|
@ -173,13 +179,19 @@
|
|||
displayAbsoluteFormattedDate: ({ createdAtDateTS, $isMobileSize }) => (
|
||||
($isMobileSize ? shortAbsoluteDateFormatter : absoluteDateFormatter).format(createdAtDateTS)
|
||||
),
|
||||
reblogsLabel: ({ numReblogs }) => {
|
||||
reblogsLabel: ({ $disableReblogCounts, numReblogs }) => {
|
||||
if ($disableReblogCounts) {
|
||||
return 'Boost counts hidden'
|
||||
}
|
||||
// TODO: intl
|
||||
return numReblogs === 1
|
||||
? `Boosted ${numReblogs} time`
|
||||
: `Boosted ${numReblogs} times`
|
||||
},
|
||||
favoritesLabel: ({ numFavs }) => {
|
||||
favoritesLabel: ({ $disableFavCounts, numFavs }) => {
|
||||
if ($disableFavCounts) {
|
||||
return 'Favorite counts hidden'
|
||||
}
|
||||
// TODO: intl
|
||||
return numFavs === 1
|
||||
? `Favorited ${numFavs} time`
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
<SettingsListRow>
|
||||
<SettingsListButton href="/settings/instances" label="Instances"/>
|
||||
</SettingsListRow>
|
||||
<SettingsListRow>
|
||||
<SettingsListButton href="/settings/wellness" label="Wellness"/>
|
||||
</SettingsListRow>
|
||||
<SettingsListRow>
|
||||
<SettingsListButton href="/settings/hotkeys" label="Hotkeys"/>
|
||||
</SettingsListRow>
|
||||
|
|
154
src/routes/_pages/settings/wellness.html
Normal file
154
src/routes/_pages/settings/wellness.html
Normal file
|
@ -0,0 +1,154 @@
|
|||
<SettingsLayout page='settings/general' label="General">
|
||||
<h1>Wellness Settings</h1>
|
||||
|
||||
<p>
|
||||
Wellness settings are designed to reduce the addictive or anxiety-inducing aspects of social media.
|
||||
Choose any options that work well for you.
|
||||
</p>
|
||||
|
||||
<form class="ui-settings">
|
||||
<div class="setting-group">
|
||||
<input type="checkbox" id="choice-check-all"
|
||||
on:change="onCheckAllChange(event)">
|
||||
<label for="choice-check-all">Enable all</label>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<h2>Metrics</h2>
|
||||
|
||||
<form class="ui-settings">
|
||||
<div class="setting-group">
|
||||
<input type="checkbox" id="choice-disable-follower-counts"
|
||||
bind:checked="$disableFollowerCounts" on:change="onChange(event)">
|
||||
<label for="choice-disable-follower-counts">
|
||||
Hide follower counts (capped at 10)
|
||||
</label>
|
||||
</div>
|
||||
<div class="setting-group">
|
||||
<input type="checkbox" id="choice-disable-reblog-counts"
|
||||
bind:checked="$disableReblogCounts" on:change="onChange(event)">
|
||||
<label for="choice-disable-reblog-counts">Hide boost counts</label>
|
||||
</div>
|
||||
<div class="setting-group">
|
||||
<input type="checkbox" id="choice-disable-fav-counts"
|
||||
bind:checked="$disableFavCounts" on:change="onChange(event)">
|
||||
<label for="choice-disable-fav-counts">Hide favorite counts</label>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<h2>Notifications</h2>
|
||||
|
||||
<form class="ui-settings">
|
||||
<div class="setting-group">
|
||||
<input type="checkbox" id="choice-disable-unread-notification-counts"
|
||||
bind:checked="$disableNotificationBadge" on:change="onChange(event)">
|
||||
<label for="choice-disable-unread-notification-counts">
|
||||
Hide unread notifications count (i.e. the red dot)
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<aside>
|
||||
<SvgIcon href="#fa-info-circle" className="aside-icon" />
|
||||
<span>
|
||||
You can filter or disable notifications in the
|
||||
<a rel="prefetch" href="/settings/instances{$currentInstance ? '/' + $currentInstance : ''}">instance settings</a>.
|
||||
</span>
|
||||
</aside>
|
||||
|
||||
<h2>UI</h2>
|
||||
|
||||
<form class="ui-settings">
|
||||
<div class="setting-group">
|
||||
<input type="checkbox" id="choice-grayscale"
|
||||
bind:checked="$enableGrayscale" on:change="onChange(event)">
|
||||
<label for="choice-grayscale">Grayscale mode</label>
|
||||
</div>
|
||||
</form>
|
||||
<p>
|
||||
These settings are partly based on guidelines from the
|
||||
<ExternalLink href="https://humanetech.com">Center for Humane Technology</ExternalLink>.
|
||||
</p>
|
||||
</SettingsLayout>
|
||||
<style>
|
||||
.ui-settings {
|
||||
background: var(--form-bg);
|
||||
border: 1px solid var(--main-border);
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
line-height: 2em;
|
||||
}
|
||||
.setting-group {
|
||||
padding: 5px 0;
|
||||
}
|
||||
aside {
|
||||
font-size: 1.2em;
|
||||
margin: 20px 10px 0px 10px;
|
||||
color: var(--deemphasized-text-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
aside a {
|
||||
text-decoration: underline;
|
||||
color: var(--deemphasized-text-color);
|
||||
}
|
||||
aside span {
|
||||
flex: 1;
|
||||
}
|
||||
:global(.aside-icon) {
|
||||
fill: var(--deemphasized-text-color);
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
margin: 0 10px 0 5px;
|
||||
min-width: 18px;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import SettingsLayout from '../../_components/settings/SettingsLayout.html'
|
||||
import { store } from '../../_store/store'
|
||||
import ExternalLink from '../../_components/ExternalLink.html'
|
||||
import SvgIcon from '../../_components/SvgIcon.html'
|
||||
|
||||
export default {
|
||||
oncreate () {
|
||||
this.flushChangesToCheckAll()
|
||||
},
|
||||
components: {
|
||||
SettingsLayout,
|
||||
ExternalLink,
|
||||
SvgIcon
|
||||
},
|
||||
methods: {
|
||||
flushChangesToCheckAll () {
|
||||
const {
|
||||
disableFollowerCounts,
|
||||
disableReblogCounts,
|
||||
disableFavCounts,
|
||||
disableNotificationBadge,
|
||||
enableGrayscale
|
||||
} = this.store.get()
|
||||
document.querySelector('#choice-check-all').checked = disableFollowerCounts &&
|
||||
disableReblogCounts &&
|
||||
disableFavCounts &&
|
||||
disableNotificationBadge &&
|
||||
enableGrayscale
|
||||
},
|
||||
onCheckAllChange (e) {
|
||||
let { checked } = e.target
|
||||
this.store.set({
|
||||
disableFollowerCounts: checked,
|
||||
disableReblogCounts: checked,
|
||||
disableFavCounts: checked,
|
||||
disableNotificationBadge: checked,
|
||||
enableGrayscale: checked
|
||||
})
|
||||
this.store.save()
|
||||
},
|
||||
onChange () {
|
||||
this.flushChangesToCheckAll()
|
||||
this.store.save()
|
||||
}
|
||||
},
|
||||
store: () => store
|
||||
}
|
||||
</script>
|
|
@ -41,6 +41,12 @@ const themes = [
|
|||
dark: false,
|
||||
color: '#4ab92f'
|
||||
},
|
||||
{
|
||||
name: 'grayscale',
|
||||
label: 'Grayscale',
|
||||
dark: false,
|
||||
color: '#999999'
|
||||
},
|
||||
{
|
||||
name: 'ozark',
|
||||
label: 'Ozark',
|
||||
|
@ -88,6 +94,12 @@ const themes = [
|
|||
label: 'Pitch Black',
|
||||
dark: true,
|
||||
color: '#000'
|
||||
},
|
||||
{
|
||||
name: 'dark-grayscale',
|
||||
label: 'Dark Grayscale',
|
||||
dark: true,
|
||||
color: '#666'
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import { Store } from 'svelte/store'
|
||||
import { safeLocalStorage as LS } from '../_utils/safeLocalStorage'
|
||||
import lifecycle from 'page-lifecycle/dist/lifecycle.mjs'
|
||||
|
||||
function safeParse (str) {
|
||||
return !str ? undefined : (str === 'undefined' ? undefined : JSON.parse(str))
|
||||
}
|
||||
import { safeParse } from './safeParse'
|
||||
|
||||
export class LocalStorageStore extends Store {
|
||||
constructor (state, keysToWatch) {
|
||||
|
|
|
@ -174,7 +174,9 @@ export function timelineComputations (store) {
|
|||
)
|
||||
|
||||
store.compute('hasNotifications',
|
||||
['numberOfNotifications', 'currentPage'],
|
||||
(numberOfNotifications, currentPage) => currentPage !== 'notifications' && !!numberOfNotifications
|
||||
['numberOfNotifications', 'currentPage', 'disableNotificationBadge'],
|
||||
(numberOfNotifications, currentPage, $disableNotificationBadge) => (
|
||||
!$disableNotificationBadge && currentPage !== 'notifications' && !!numberOfNotifications
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
14
src/routes/_store/observers/grayscaleObservers.js
Normal file
14
src/routes/_store/observers/grayscaleObservers.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { switchToTheme } from '../../_utils/themeEngine'
|
||||
|
||||
export function grayscaleObservers (store) {
|
||||
if (!process.browser) {
|
||||
return
|
||||
}
|
||||
|
||||
store.observe('enableGrayscale', enableGrayscale => {
|
||||
const { instanceThemes, currentInstance } = store.get()
|
||||
const theme = instanceThemes && instanceThemes[currentInstance]
|
||||
document.body.classList.toggle('grayscale', enableGrayscale)
|
||||
switchToTheme(theme, enableGrayscale)
|
||||
})
|
||||
}
|
|
@ -6,6 +6,7 @@ import { resizeObservers } from './resizeObservers'
|
|||
import { setupLoggedInObservers } from './setupLoggedInObservers'
|
||||
import { logOutObservers } from './logOutObservers'
|
||||
import { touchObservers } from './touchObservers'
|
||||
import { grayscaleObservers } from './grayscaleObservers'
|
||||
|
||||
export function observers (store) {
|
||||
onlineObservers(store)
|
||||
|
@ -15,5 +16,6 @@ export function observers (store) {
|
|||
resizeObservers(store)
|
||||
touchObservers(store)
|
||||
logOutObservers(store)
|
||||
grayscaleObservers(store)
|
||||
setupLoggedInObservers(store)
|
||||
}
|
||||
|
|
|
@ -6,8 +6,6 @@ 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) {
|
||||
|
@ -19,20 +17,9 @@ export function onlineObservers (store) {
|
|||
if (!process.browser) {
|
||||
return
|
||||
}
|
||||
let meta = document.getElementById('theThemeColor')
|
||||
let oldTheme = meta.content
|
||||
|
||||
store.observe('online', 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 {
|
||||
let offlineThemeColor = window.__themeColors.offline
|
||||
if (meta.content !== offlineThemeColor) {
|
||||
oldTheme = meta.content
|
||||
}
|
||||
meta.content = offlineThemeColor
|
||||
if (!online) {
|
||||
notifyOffline()
|
||||
}
|
||||
})
|
||||
|
|
3
src/routes/_store/safeParse.js
Normal file
3
src/routes/_store/safeParse.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export function safeParse (str) {
|
||||
return !str ? undefined : (str === 'undefined' ? undefined : JSON.parse(str))
|
||||
}
|
|
@ -12,10 +12,15 @@ const persistedState = {
|
|||
currentRegisteredInstance: undefined,
|
||||
// we disable scrollbars by default on iOS
|
||||
disableCustomScrollbars: process.browser && /iP(?:hone|ad|od)/.test(navigator.userAgent),
|
||||
disableFavCounts: false,
|
||||
disableFollowerCounts: false,
|
||||
disableHotkeys: false,
|
||||
disableInfiniteScroll: false,
|
||||
disableLongAriaLabels: false,
|
||||
disableNotificationBadge: false,
|
||||
disableReblogCounts: false,
|
||||
disableTapOnStatus: false,
|
||||
enableGrayscale: false,
|
||||
hideCards: false,
|
||||
largeInlineMedia: false,
|
||||
instanceNameInSearch: '',
|
||||
|
|
26
src/routes/_store/storeLite.js
Normal file
26
src/routes/_store/storeLite.js
Normal file
|
@ -0,0 +1,26 @@
|
|||
// "lite" version of the store used in the inline script. Purely read-only,
|
||||
// does not implement non-LocalStorage store features.
|
||||
|
||||
import { safeParse } from './safeParse'
|
||||
import { testHasLocalStorageOnce } from '../_utils/testStorage'
|
||||
|
||||
const hasLocalStorage = testHasLocalStorageOnce()
|
||||
|
||||
export const storeLite = {
|
||||
get () {
|
||||
if (!hasLocalStorage) {
|
||||
return {}
|
||||
}
|
||||
const res = {}
|
||||
const LS = localStorage
|
||||
for (let i = 0, len = LS.length; i < len; i++) {
|
||||
let key = LS.key(i)
|
||||
if (key.startsWith('store_')) {
|
||||
let item = LS.getItem(key)
|
||||
let value = safeParse(item)
|
||||
res[key] = value
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
let meta = process.browser && document.getElementById('theThemeColor')
|
||||
let offlineStyle = process.browser && document.getElementById('theOfflineStyle')
|
||||
let prefersDarkTheme = process.browser && window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
const prefersDarkTheme = process.browser && window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
const meta = process.browser && document.getElementById('theThemeColor')
|
||||
|
||||
export const INLINE_THEME = 'default' // theme that does not require external CSS
|
||||
export const DEFAULT_LIGHT_THEME = 'default' // theme that is shown by default
|
||||
|
@ -32,11 +31,13 @@ function loadCSS (href) {
|
|||
}
|
||||
})
|
||||
|
||||
// inserting before the offline <style> ensures that the offline style wins when offline
|
||||
document.head.insertBefore(link, offlineStyle)
|
||||
document.head.appendChild(link)
|
||||
}
|
||||
|
||||
export function switchToTheme (themeName = DEFAULT_THEME) {
|
||||
export function switchToTheme (themeName = DEFAULT_THEME, enableGrayscale) {
|
||||
if (enableGrayscale) {
|
||||
themeName = prefersDarkTheme ? 'grayscale-dark' : 'grayscale'
|
||||
}
|
||||
let themeColor = window.__themeColors[themeName]
|
||||
meta.content = themeColor || window.__themeColors[DEFAULT_THEME]
|
||||
if (themeName !== INLINE_THEME) {
|
||||
|
|
20
src/routes/settings/wellness.html
Normal file
20
src/routes/settings/wellness.html
Normal file
|
@ -0,0 +1,20 @@
|
|||
<Title name="Wellness Settings" settingsPage={true} />
|
||||
|
||||
<LazyPage {pageComponent} {params} />
|
||||
|
||||
<script>
|
||||
import Title from '../_components/Title.html'
|
||||
import LazyPage from '../_components/LazyPage.html'
|
||||
import pageComponent from '../_pages/settings/wellness.html'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
Title,
|
||||
LazyPage
|
||||
},
|
||||
data: () => ({
|
||||
pageComponent
|
||||
})
|
||||
}
|
||||
</script>
|
|
@ -31,6 +31,11 @@ body {
|
|||
background: var(--body-bg);
|
||||
-webkit-tap-highlight-color: transparent; // fix for blue background on spoiler tap on Chrome for Android
|
||||
overflow-x: hidden; // Prevent horizontal scrolling on mobile Firefox on small screens
|
||||
|
||||
&.grayscale {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.main-content {
|
||||
|
|
16
src/scss/themes/dark-grayscale.scss
Normal file
16
src/scss/themes/dark-grayscale.scss
Normal file
|
@ -0,0 +1,16 @@
|
|||
$main-theme-color: #444;
|
||||
$main-bg-color: #202020;
|
||||
$body-bg-color: darken($main-bg-color, 5%);
|
||||
$anchor-color: #999;
|
||||
$main-text-color: #FFF;
|
||||
$border-color: lighten($body-bg-color, 10%);
|
||||
$secondary-text-color: white;
|
||||
$toast-border: #fafafa;
|
||||
$toast-bg: #333;
|
||||
$focus-outline: lighten($main-theme-color, 50%);
|
||||
$compose-background: lighten($main-theme-color, 52%);
|
||||
|
||||
@import "_base.scss";
|
||||
@import "_dark.scss";
|
||||
@import "_dark_navbar.scss";
|
||||
@import "_dark_scrollbars.scss";
|
|
@ -1,6 +1,6 @@
|
|||
$main-theme-color: #999999;
|
||||
$main-theme-color: #666;
|
||||
$body-bg-color: lighten($main-theme-color, 38%);
|
||||
$anchor-color: $main-theme-color;
|
||||
$anchor-color: lighten($main-theme-color, 5%);
|
||||
$main-text-color: #333;
|
||||
$border-color: #dadada;
|
||||
$main-bg-color: white;
|
Loading…
Reference in a new issue