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 globalScss = path.join(__dirname, '../src/scss/global.scss')
|
||||||
const defaultThemeScss = path.join(__dirname, '../src/scss/themes/_default.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 customScrollbarScss = path.join(__dirname, '../src/scss/custom-scrollbars.scss')
|
||||||
const themesScssDir = path.join(__dirname, '../src/scss/themes')
|
const themesScssDir = path.join(__dirname, '../src/scss/themes')
|
||||||
const assetsDir = path.join(__dirname, '../static')
|
const assetsDir = path.join(__dirname, '../static')
|
||||||
|
@ -22,11 +21,9 @@ async function renderCss (file) {
|
||||||
|
|
||||||
async function compileGlobalSass () {
|
async function compileGlobalSass () {
|
||||||
let mainStyle = (await Promise.all([defaultThemeScss, globalScss].map(renderCss))).join('')
|
let mainStyle = (await Promise.all([defaultThemeScss, globalScss].map(renderCss))).join('')
|
||||||
let offlineStyle = (await renderCss(offlineThemeScss))
|
|
||||||
let scrollbarStyle = (await renderCss(customScrollbarScss))
|
let scrollbarStyle = (await renderCss(customScrollbarScss))
|
||||||
|
|
||||||
return `<style>\n${mainStyle}</style>\n` +
|
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`
|
`<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-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-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-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
|
// To allow CSP to work correctly, we also calculate a sha256 hash during
|
||||||
// the build process and write it to checksum.js.
|
// 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 { INLINE_THEME, DEFAULT_THEME, switchToTheme } from '../routes/_utils/themeEngine'
|
||||||
import { basename } from '../routes/_api/utils'
|
import { basename } from '../routes/_api/utils'
|
||||||
import { onUserIsLoggedOut } from '../routes/_actions/onUserIsLoggedOut'
|
import { onUserIsLoggedOut } from '../routes/_actions/onUserIsLoggedOut'
|
||||||
|
import { storeLite } from '../routes/_store/storeLite'
|
||||||
|
|
||||||
window.__themeColors = process.env.THEME_COLORS
|
window.__themeColors = process.env.THEME_COLORS
|
||||||
|
|
||||||
const safeParse = str => (typeof str === 'undefined' || str === 'undefined') ? undefined : JSON.parse(str)
|
const {
|
||||||
const hasLocalStorage = testHasLocalStorageOnce()
|
currentInstance,
|
||||||
const currentInstance = hasLocalStorage && safeParse(localStorage.store_currentInstance)
|
instanceThemes,
|
||||||
|
disableCustomScrollbars,
|
||||||
|
enableGrayscale
|
||||||
|
} = storeLite.get()
|
||||||
|
|
||||||
|
const theme = (instanceThemes && instanceThemes[currentInstance]) || DEFAULT_THEME
|
||||||
|
|
||||||
if (currentInstance) {
|
if (currentInstance) {
|
||||||
// Do prefetch if we're logged in, so we can connect faster to the other origin.
|
// 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)
|
document.head.appendChild(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
let theme = (currentInstance &&
|
|
||||||
localStorage.store_instanceThemes &&
|
|
||||||
safeParse(localStorage.store_instanceThemes)[safeParse(localStorage.store_currentInstance)]) ||
|
|
||||||
DEFAULT_THEME
|
|
||||||
if (theme !== INLINE_THEME) {
|
if (theme !== INLINE_THEME) {
|
||||||
// switch theme ASAP to minimize flash of default 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
|
// if not logged in, show all these 'hidden-from-ssr' elements
|
||||||
onUserIsLoggedOut()
|
onUserIsLoggedOut()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasLocalStorage && localStorage.store_disableCustomScrollbars === 'true') {
|
if (disableCustomScrollbars) {
|
||||||
// if user has disabled custom scrollbars, remove this style
|
document.getElementById('theScrollbarStyle')
|
||||||
let theScrollbarStyle = document.getElementById('theScrollbarStyle')
|
.setAttribute('media', 'only x') // disables the style
|
||||||
theScrollbarStyle.setAttribute('media', 'only x') // disables the style
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// hack to make the scrollbars rounded only on macOS
|
// hack to make the scrollbars rounded only on macOS
|
||||||
|
|
|
@ -84,7 +84,8 @@ async function registerNewInstance (code) {
|
||||||
instanceThemes: instanceThemes
|
instanceThemes: instanceThemes
|
||||||
})
|
})
|
||||||
store.save()
|
store.save()
|
||||||
switchToTheme(DEFAULT_THEME)
|
let { enableGrayscale } = store.get()
|
||||||
|
switchToTheme(DEFAULT_THEME, enableGrayscale)
|
||||||
// fire off these requests so they're cached
|
// fire off these requests so they're cached
|
||||||
/* no await */ updateVerifyCredentialsForInstance(currentRegisteredInstanceName)
|
/* no await */ updateVerifyCredentialsForInstance(currentRegisteredInstanceName)
|
||||||
/* no await */ updateCustomEmojiForInstance(currentRegisteredInstanceName)
|
/* no await */ updateCustomEmojiForInstance(currentRegisteredInstanceName)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { getVerifyCredentials } from '../_api/user'
|
import { getVerifyCredentials } from '../_api/user'
|
||||||
import { store } from '../_store/store'
|
import { store } from '../_store/store'
|
||||||
import { DEFAULT_THEME, switchToTheme } from '../_utils/themeEngine'
|
import { switchToTheme } from '../_utils/themeEngine'
|
||||||
import { toast } from '../_components/toast/toast'
|
import { toast } from '../_components/toast/toast'
|
||||||
import { goto } from '../../../__sapper__/client'
|
import { goto } from '../../../__sapper__/client'
|
||||||
import { cacheFirstUpdateAfter } from '../_utils/sync'
|
import { cacheFirstUpdateAfter } from '../_utils/sync'
|
||||||
|
@ -14,7 +14,8 @@ export function changeTheme (instanceName, newTheme) {
|
||||||
store.save()
|
store.save()
|
||||||
let { currentInstance } = store.get()
|
let { currentInstance } = store.get()
|
||||||
if (instanceName === currentInstance) {
|
if (instanceName === currentInstance) {
|
||||||
switchToTheme(newTheme)
|
let { enableGrayscale } = store.get()
|
||||||
|
switchToTheme(newTheme, enableGrayscale)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +27,8 @@ export function switchToInstance (instanceName) {
|
||||||
queryInSearch: ''
|
queryInSearch: ''
|
||||||
})
|
})
|
||||||
store.save()
|
store.save()
|
||||||
switchToTheme(instanceThemes[instanceName])
|
let { enableGrayscale } = store.get()
|
||||||
|
switchToTheme(instanceThemes[instanceName], enableGrayscale)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function logOutOfInstance (instanceName) {
|
export async function logOutOfInstance (instanceName) {
|
||||||
|
@ -55,7 +57,8 @@ export async function logOutOfInstance (instanceName) {
|
||||||
})
|
})
|
||||||
store.save()
|
store.save()
|
||||||
toast.say(`Logged out of ${instanceName}`)
|
toast.say(`Logged out of ${instanceName}`)
|
||||||
switchToTheme(instanceThemes[newInstance] || DEFAULT_THEME)
|
let { enableGrayscale } = store.get()
|
||||||
|
switchToTheme(instanceThemes[newInstance], enableGrayscale)
|
||||||
/* no await */ database.clearDatabaseForInstance(instanceName)
|
/* no await */ database.clearDatabaseForInstance(instanceName)
|
||||||
goto('/settings/instances')
|
goto('/settings/instances')
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,7 +127,12 @@
|
||||||
numFollowers: ({ account }) => account.followers_count || 0,
|
numFollowers: ({ account }) => account.followers_count || 0,
|
||||||
numStatusesDisplay: ({ numStatuses }) => numberFormat.format(numStatuses),
|
numStatusesDisplay: ({ numStatuses }) => numberFormat.format(numStatuses),
|
||||||
numFollowingDisplay: ({ numFollowing }) => numberFormat.format(numFollowing),
|
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}`,
|
followersLabel: ({ numFollowers }) => `Followed by ${numFollowers}`,
|
||||||
followingLabel: ({ numFollowing }) => `Follows ${numFollowing}`
|
followingLabel: ({ numFollowing }) => `Follows ${numFollowing}`
|
||||||
},
|
},
|
||||||
|
|
|
@ -158,13 +158,19 @@
|
||||||
application: ({ originalStatus }) => originalStatus.application,
|
application: ({ originalStatus }) => originalStatus.application,
|
||||||
applicationName: ({ application }) => (application && application.name),
|
applicationName: ({ application }) => (application && application.name),
|
||||||
applicationWebsite: ({ application }) => (application && application.website),
|
applicationWebsite: ({ application }) => (application && application.website),
|
||||||
numReblogs: ({ overrideNumReblogs, originalStatus }) => {
|
numReblogs: ({ $disableReblogCounts, overrideNumReblogs, originalStatus }) => {
|
||||||
|
if ($disableReblogCounts) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
if (typeof overrideNumReblogs === 'number') {
|
if (typeof overrideNumReblogs === 'number') {
|
||||||
return overrideNumReblogs
|
return overrideNumReblogs
|
||||||
}
|
}
|
||||||
return originalStatus.reblogs_count || 0
|
return originalStatus.reblogs_count || 0
|
||||||
},
|
},
|
||||||
numFavs: ({ overrideNumFavs, originalStatus }) => {
|
numFavs: ({ $disableFavCounts, overrideNumFavs, originalStatus }) => {
|
||||||
|
if ($disableFavCounts) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
if (typeof overrideNumFavs === 'number') {
|
if (typeof overrideNumFavs === 'number') {
|
||||||
return overrideNumFavs
|
return overrideNumFavs
|
||||||
}
|
}
|
||||||
|
@ -173,13 +179,19 @@
|
||||||
displayAbsoluteFormattedDate: ({ createdAtDateTS, $isMobileSize }) => (
|
displayAbsoluteFormattedDate: ({ createdAtDateTS, $isMobileSize }) => (
|
||||||
($isMobileSize ? shortAbsoluteDateFormatter : absoluteDateFormatter).format(createdAtDateTS)
|
($isMobileSize ? shortAbsoluteDateFormatter : absoluteDateFormatter).format(createdAtDateTS)
|
||||||
),
|
),
|
||||||
reblogsLabel: ({ numReblogs }) => {
|
reblogsLabel: ({ $disableReblogCounts, numReblogs }) => {
|
||||||
|
if ($disableReblogCounts) {
|
||||||
|
return 'Boost counts hidden'
|
||||||
|
}
|
||||||
// TODO: intl
|
// TODO: intl
|
||||||
return numReblogs === 1
|
return numReblogs === 1
|
||||||
? `Boosted ${numReblogs} time`
|
? `Boosted ${numReblogs} time`
|
||||||
: `Boosted ${numReblogs} times`
|
: `Boosted ${numReblogs} times`
|
||||||
},
|
},
|
||||||
favoritesLabel: ({ numFavs }) => {
|
favoritesLabel: ({ $disableFavCounts, numFavs }) => {
|
||||||
|
if ($disableFavCounts) {
|
||||||
|
return 'Favorite counts hidden'
|
||||||
|
}
|
||||||
// TODO: intl
|
// TODO: intl
|
||||||
return numFavs === 1
|
return numFavs === 1
|
||||||
? `Favorited ${numFavs} time`
|
? `Favorited ${numFavs} time`
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
<SettingsListRow>
|
<SettingsListRow>
|
||||||
<SettingsListButton href="/settings/instances" label="Instances"/>
|
<SettingsListButton href="/settings/instances" label="Instances"/>
|
||||||
</SettingsListRow>
|
</SettingsListRow>
|
||||||
|
<SettingsListRow>
|
||||||
|
<SettingsListButton href="/settings/wellness" label="Wellness"/>
|
||||||
|
</SettingsListRow>
|
||||||
<SettingsListRow>
|
<SettingsListRow>
|
||||||
<SettingsListButton href="/settings/hotkeys" label="Hotkeys"/>
|
<SettingsListButton href="/settings/hotkeys" label="Hotkeys"/>
|
||||||
</SettingsListRow>
|
</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,
|
dark: false,
|
||||||
color: '#4ab92f'
|
color: '#4ab92f'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'grayscale',
|
||||||
|
label: 'Grayscale',
|
||||||
|
dark: false,
|
||||||
|
color: '#999999'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'ozark',
|
name: 'ozark',
|
||||||
label: 'Ozark',
|
label: 'Ozark',
|
||||||
|
@ -88,6 +94,12 @@ const themes = [
|
||||||
label: 'Pitch Black',
|
label: 'Pitch Black',
|
||||||
dark: true,
|
dark: true,
|
||||||
color: '#000'
|
color: '#000'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'dark-grayscale',
|
||||||
|
label: 'Dark Grayscale',
|
||||||
|
dark: true,
|
||||||
|
color: '#666'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
import { Store } from 'svelte/store'
|
import { Store } from 'svelte/store'
|
||||||
import { safeLocalStorage as LS } from '../_utils/safeLocalStorage'
|
import { safeLocalStorage as LS } from '../_utils/safeLocalStorage'
|
||||||
import lifecycle from 'page-lifecycle/dist/lifecycle.mjs'
|
import lifecycle from 'page-lifecycle/dist/lifecycle.mjs'
|
||||||
|
import { safeParse } from './safeParse'
|
||||||
function safeParse (str) {
|
|
||||||
return !str ? undefined : (str === 'undefined' ? undefined : JSON.parse(str))
|
|
||||||
}
|
|
||||||
|
|
||||||
export class LocalStorageStore extends Store {
|
export class LocalStorageStore extends Store {
|
||||||
constructor (state, keysToWatch) {
|
constructor (state, keysToWatch) {
|
||||||
|
|
|
@ -174,7 +174,9 @@ export function timelineComputations (store) {
|
||||||
)
|
)
|
||||||
|
|
||||||
store.compute('hasNotifications',
|
store.compute('hasNotifications',
|
||||||
['numberOfNotifications', 'currentPage'],
|
['numberOfNotifications', 'currentPage', 'disableNotificationBadge'],
|
||||||
(numberOfNotifications, currentPage) => currentPage !== 'notifications' && !!numberOfNotifications
|
(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 { setupLoggedInObservers } from './setupLoggedInObservers'
|
||||||
import { logOutObservers } from './logOutObservers'
|
import { logOutObservers } from './logOutObservers'
|
||||||
import { touchObservers } from './touchObservers'
|
import { touchObservers } from './touchObservers'
|
||||||
|
import { grayscaleObservers } from './grayscaleObservers'
|
||||||
|
|
||||||
export function observers (store) {
|
export function observers (store) {
|
||||||
onlineObservers(store)
|
onlineObservers(store)
|
||||||
|
@ -15,5 +16,6 @@ export function observers (store) {
|
||||||
resizeObservers(store)
|
resizeObservers(store)
|
||||||
touchObservers(store)
|
touchObservers(store)
|
||||||
logOutObservers(store)
|
logOutObservers(store)
|
||||||
|
grayscaleObservers(store)
|
||||||
setupLoggedInObservers(store)
|
setupLoggedInObservers(store)
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,6 @@ const NOTIFY_OFFLINE_LIMIT = 1
|
||||||
|
|
||||||
let notifyCount = 0
|
let notifyCount = 0
|
||||||
|
|
||||||
let offlineStyle = process.browser && document.getElementById('theOfflineStyle')
|
|
||||||
|
|
||||||
// debounce to avoid notifying for a short connection issue
|
// debounce to avoid notifying for a short connection issue
|
||||||
const notifyOffline = debounce(() => {
|
const notifyOffline = debounce(() => {
|
||||||
if (process.browser && !navigator.onLine && ++notifyCount <= NOTIFY_OFFLINE_LIMIT) {
|
if (process.browser && !navigator.onLine && ++notifyCount <= NOTIFY_OFFLINE_LIMIT) {
|
||||||
|
@ -19,20 +17,9 @@ export function onlineObservers (store) {
|
||||||
if (!process.browser) {
|
if (!process.browser) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let meta = document.getElementById('theThemeColor')
|
|
||||||
let oldTheme = meta.content
|
|
||||||
|
|
||||||
store.observe('online', online => {
|
store.observe('online', online => {
|
||||||
// "only x" ensures the <style> tag does not have any effect
|
if (!online) {
|
||||||
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
|
|
||||||
notifyOffline()
|
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,
|
currentRegisteredInstance: undefined,
|
||||||
// we disable scrollbars by default on iOS
|
// we disable scrollbars by default on iOS
|
||||||
disableCustomScrollbars: process.browser && /iP(?:hone|ad|od)/.test(navigator.userAgent),
|
disableCustomScrollbars: process.browser && /iP(?:hone|ad|od)/.test(navigator.userAgent),
|
||||||
|
disableFavCounts: false,
|
||||||
|
disableFollowerCounts: false,
|
||||||
disableHotkeys: false,
|
disableHotkeys: false,
|
||||||
disableInfiniteScroll: false,
|
disableInfiniteScroll: false,
|
||||||
disableLongAriaLabels: false,
|
disableLongAriaLabels: false,
|
||||||
|
disableNotificationBadge: false,
|
||||||
|
disableReblogCounts: false,
|
||||||
disableTapOnStatus: false,
|
disableTapOnStatus: false,
|
||||||
|
enableGrayscale: false,
|
||||||
hideCards: false,
|
hideCards: false,
|
||||||
largeInlineMedia: false,
|
largeInlineMedia: false,
|
||||||
instanceNameInSearch: '',
|
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')
|
const prefersDarkTheme = process.browser && window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
let offlineStyle = process.browser && document.getElementById('theOfflineStyle')
|
const meta = process.browser && document.getElementById('theThemeColor')
|
||||||
let prefersDarkTheme = process.browser && window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
||||||
|
|
||||||
export const INLINE_THEME = 'default' // theme that does not require external CSS
|
export const INLINE_THEME = 'default' // theme that does not require external CSS
|
||||||
export const DEFAULT_LIGHT_THEME = 'default' // theme that is shown by default
|
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.appendChild(link)
|
||||||
document.head.insertBefore(link, offlineStyle)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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]
|
let themeColor = window.__themeColors[themeName]
|
||||||
meta.content = themeColor || window.__themeColors[DEFAULT_THEME]
|
meta.content = themeColor || window.__themeColors[DEFAULT_THEME]
|
||||||
if (themeName !== INLINE_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);
|
background: var(--body-bg);
|
||||||
-webkit-tap-highlight-color: transparent; // fix for blue background on spoiler tap on Chrome for Android
|
-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
|
overflow-x: hidden; // Prevent horizontal scrolling on mobile Firefox on small screens
|
||||||
|
|
||||||
|
&.grayscale {
|
||||||
|
filter: grayscale(100%);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content {
|
.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%);
|
$body-bg-color: lighten($main-theme-color, 38%);
|
||||||
$anchor-color: $main-theme-color;
|
$anchor-color: lighten($main-theme-color, 5%);
|
||||||
$main-text-color: #333;
|
$main-text-color: #333;
|
||||||
$border-color: #dadada;
|
$border-color: #dadada;
|
||||||
$main-bg-color: white;
|
$main-bg-color: white;
|
Loading…
Reference in a new issue