diff --git a/bin/build-sass.js b/bin/build-sass.js index be8dd3ef..a61a0095 100755 --- a/bin/build-sass.js +++ b/bin/build-sass.js @@ -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 `\n` + - `\n` + `\n` } diff --git a/bin/svgs.js b/bin/svgs.js index 04a78d7c..933d58fb 100644 --- a/bin/svgs.js +++ b/bin/svgs.js @@ -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' } ] diff --git a/src/inline-script/inline-script.js b/src/inline-script/inline-script.js index 1d084a2b..2621daf3 100644 --- a/src/inline-script/inline-script.js +++ b/src/inline-script/inline-script.js @@ -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 diff --git a/src/routes/_actions/addInstance.js b/src/routes/_actions/addInstance.js index df808824..8a2a2a4e 100644 --- a/src/routes/_actions/addInstance.js +++ b/src/routes/_actions/addInstance.js @@ -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) diff --git a/src/routes/_actions/instances.js b/src/routes/_actions/instances.js index 21d6816d..92807f5f 100644 --- a/src/routes/_actions/instances.js +++ b/src/routes/_actions/instances.js @@ -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') } diff --git a/src/routes/_components/profile/AccountProfileDetails.html b/src/routes/_components/profile/AccountProfileDetails.html index 51dbe488..a95b4518 100644 --- a/src/routes/_components/profile/AccountProfileDetails.html +++ b/src/routes/_components/profile/AccountProfileDetails.html @@ -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}` }, diff --git a/src/routes/_components/status/StatusDetails.html b/src/routes/_components/status/StatusDetails.html index d3c4b2a2..8c537af7 100644 --- a/src/routes/_components/status/StatusDetails.html +++ b/src/routes/_components/status/StatusDetails.html @@ -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` diff --git a/src/routes/_pages/settings/index.html b/src/routes/_pages/settings/index.html index 45503ed0..223abffc 100644 --- a/src/routes/_pages/settings/index.html +++ b/src/routes/_pages/settings/index.html @@ -8,6 +8,9 @@ + + + diff --git a/src/routes/_pages/settings/wellness.html b/src/routes/_pages/settings/wellness.html new file mode 100644 index 00000000..077a9655 --- /dev/null +++ b/src/routes/_pages/settings/wellness.html @@ -0,0 +1,154 @@ + +

Wellness Settings

+ +

+ Wellness settings are designed to reduce the addictive or anxiety-inducing aspects of social media. + Choose any options that work well for you. +

+ +
+
+ + +
+
+ +

Metrics

+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +

Notifications

+ +
+
+ + +
+
+ + + +

UI

+ +
+
+ + +
+
+

+ These settings are partly based on guidelines from the + Center for Humane Technology. +

+
+ + diff --git a/src/routes/_static/themes.js b/src/routes/_static/themes.js index f52dd9cc..7f0b5754 100644 --- a/src/routes/_static/themes.js +++ b/src/routes/_static/themes.js @@ -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' } ] diff --git a/src/routes/_store/LocalStorageStore.js b/src/routes/_store/LocalStorageStore.js index 4c1bb8db..edfa019c 100644 --- a/src/routes/_store/LocalStorageStore.js +++ b/src/routes/_store/LocalStorageStore.js @@ -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) { diff --git a/src/routes/_store/computations/timelineComputations.js b/src/routes/_store/computations/timelineComputations.js index 5280151d..5716929d 100644 --- a/src/routes/_store/computations/timelineComputations.js +++ b/src/routes/_store/computations/timelineComputations.js @@ -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 + ) ) } diff --git a/src/routes/_store/observers/grayscaleObservers.js b/src/routes/_store/observers/grayscaleObservers.js new file mode 100644 index 00000000..04d19772 --- /dev/null +++ b/src/routes/_store/observers/grayscaleObservers.js @@ -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) + }) +} diff --git a/src/routes/_store/observers/observers.js b/src/routes/_store/observers/observers.js index ff8f6b1f..527f6441 100644 --- a/src/routes/_store/observers/observers.js +++ b/src/routes/_store/observers/observers.js @@ -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) } diff --git a/src/routes/_store/observers/onlineObservers.js b/src/routes/_store/observers/onlineObservers.js index ebe9a156..a0c73087 100644 --- a/src/routes/_store/observers/onlineObservers.js +++ b/src/routes/_store/observers/onlineObservers.js @@ -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