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
					
				
					 23 changed files with 327 additions and 57 deletions
				
			
		| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			@ -11,4 +11,4 @@ $focus-outline: lighten($main-theme-color, 15%);
 | 
			
		|||
$compose-background: lighten($main-theme-color, 17%);
 | 
			
		||||
 | 
			
		||||
@import "_base.scss";
 | 
			
		||||
@import "_light_scrollbars.scss";
 | 
			
		||||
@import "_light_scrollbars.scss";
 | 
			
		||||
		Loading…
	
	Add table
		
		Reference in a new issue