diff --git a/package.json b/package.json index 757b285157..80d27645a2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "busy", - "version": "2.5.3", + "version": "2.5.4", "engines": { "node": ">=7.10.1", "npm": "=5.3.0" diff --git a/src/client/Wrapper.js b/src/client/Wrapper.js index 33686c91c1..c424a40f64 100644 --- a/src/client/Wrapper.js +++ b/src/client/Wrapper.js @@ -17,6 +17,7 @@ import { getUsedLocale, getTranslations, getUseBeta, + getNightmode, } from './reducers'; import { login, logout, busyLogin } from './auth/authActions'; import { getFollowing, getNotifications } from './user/userActions'; @@ -44,6 +45,7 @@ import BBackTop from './components/BBackTop'; usedLocale: getUsedLocale(state), translations: getTranslations(state), locale: getLocale(state), + nightmode: getNightmode(state), }), { login, @@ -77,6 +79,7 @@ export default class Wrapper extends React.PureComponent { getNotifications: PropTypes.func, setUsedLocale: PropTypes.func, busyLogin: PropTypes.func, + nightmode: PropTypes.bool, }; static defaultProps = { @@ -93,6 +96,7 @@ export default class Wrapper extends React.PureComponent { getNotifications: () => {}, setUsedLocale: () => {}, busyLogin: () => {}, + nightmode: false, }; static async fetchData({ store, req, res }) { @@ -201,14 +205,17 @@ export default class Wrapper extends React.PureComponent { } render() { - const { user, usedLocale, translations } = this.props; + const { user, usedLocale, translations, nightmode } = this.props; const language = findLanguage(usedLocale); return ( - + diff --git a/src/client/components/Navigation/Topnav-dark.less b/src/client/components/Navigation/Topnav-dark.less new file mode 100644 index 0000000000..10e68f5ba3 --- /dev/null +++ b/src/client/components/Navigation/Topnav-dark.less @@ -0,0 +1,37 @@ +@import (reference) '../../styles/modules/variables.less'; +@import (reference) '../../styles/custom.less'; + +@base-spacing: 0; + +.dark { + .Topnav { + background-color: @grey-nero; + border-bottom: @normal-grey; + + &__input-container { + input { + color: @white-smoke; + border: @border-width-base @border-style-base @normal-grey; + + &:hover, + &:focus { + border: @border-width-base @border-style-base @normal-grey !important; + } + } + } + + &__menu-container { + &__menu { + & > li.ant-menu-item { + & > a { + color: @tertiary-color !important; + + &:hover { + color: @white-smoke !important; + } + } + } + } + } + } +} diff --git a/src/client/components/Navigation/Topnav.less b/src/client/components/Navigation/Topnav.less index 6af210c59a..817fd36d07 100644 --- a/src/client/components/Navigation/Topnav.less +++ b/src/client/components/Navigation/Topnav.less @@ -286,3 +286,5 @@ div[data-dir='rtl'] .Topnav { width: 16px !important; justify-content: flex-end !important; } + +@import 'Topnav-dark'; diff --git a/src/client/helpers/regexHelpers.js b/src/client/helpers/regexHelpers.js index ae42f8df59..0d89e13fe9 100644 --- a/src/client/helpers/regexHelpers.js +++ b/src/client/helpers/regexHelpers.js @@ -6,7 +6,7 @@ export const usernameURLRegex = /@([^/]+)/; export const categoryRegex = /\/([^/]+)/; -export const rewriteRegex = /"https?:\/\/(?:www)?steemit\.com((\/)(((\w+\/)?@\w+\/\w+)|(@\w+(\/(comments|followers|followed|reblogs|transfers|activity))?)|((trending|created|active|hot|promoted)(\/\w+)?))?)?"/g; +export const rewriteRegex = /"https?:\/\/(?:www)?steemit\.com(\/((([\w-]+\/)?@[\w.-]+\/[\w-]+)|(@[\w.-]+(\/(comments|followers|followed|reblogs|transfers|activity))?)|((trending|created|active|hot|promoted)(\/[\w-]+)?))?)?"/g; export const ownUrl = /^(localhost|busy\.org|staging\.busy\.org|busy-master-pr-\d+\.herokuapp.com)$/; diff --git a/src/client/locales/de-DE.json b/src/client/locales/de-DE.json index 5a2f3ce0b6..f450e26e99 100644 --- a/src/client/locales/de-DE.json +++ b/src/client/locales/de-DE.json @@ -314,6 +314,9 @@ "nsfw_posts": "NSFW Beiträge", "display_nsfw_posts_details": "Sie können alle Beiträge mit dem Stichwort NSFW standardmäßig anzeigen lassen.", "display_nsfw_posts": "NSFW Beiträge anzeigen", + "nightmode": "Nachtmodus", + "nightmode_details": "Aktiviere diese Option, für eine augenschonendere Oberfläche bei Nacht.", + "use_nightmode": "Nachtmodus aktivieren", "rewrite_links": "Erneuern sie ihre Links", "rewrite_links_details": "Sie können diese Option aktivieren, um Steemit.com Links mit Busy.org Links zu ersetzen.", "use_beta": "Verwenden sie die Busy-Betaversion", diff --git a/src/client/locales/default.json b/src/client/locales/default.json index d101e7f5e6..6a27b126f6 100644 --- a/src/client/locales/default.json +++ b/src/client/locales/default.json @@ -316,6 +316,9 @@ "nsfw_posts": "NSFW Posts", "display_nsfw_posts_details": "You can enable all posts tagged with NSFW to be shown as default.", "display_nsfw_posts": "Display NSFW Posts", + "nightmode": "Nightmode", + "nightmode_details": "You can enable this option for a more eye-friendly experience at night.", + "use_nightmode": "Use Nightmode", "rewrite_links": "Rewrite links", "rewrite_links_details": "You can enable this option to replace Steemit.com links with Busy.org links.", "use_beta": "Use Busy beta", diff --git a/src/client/reducers.js b/src/client/reducers.js index 97d16ac8da..8df34b7b91 100644 --- a/src/client/reducers.js +++ b/src/client/reducers.js @@ -122,6 +122,7 @@ export const getLocale = state => fromSettings.getLocale(state.settings); export const getVotingPower = state => fromSettings.getVotingPower(state.settings); export const getVotePercent = state => fromSettings.getVotePercent(state.settings); export const getShowNSFWPosts = state => fromSettings.getShowNSFWPosts(state.settings); +export const getNightmode = state => fromSettings.getNightmode(state.settings); export const getRewriteLinks = state => fromSettings.getRewriteLinks(state.settings); export const getUpvoteSetting = state => fromSettings.getUpvoteSetting(state.settings); export const getExitPageSetting = state => fromSettings.getExitPageSetting(state.settings); diff --git a/src/client/settings/Settings.js b/src/client/settings/Settings.js index 8cf31c6ef6..8a11cabf71 100644 --- a/src/client/settings/Settings.js +++ b/src/client/settings/Settings.js @@ -11,6 +11,7 @@ import { getIsSettingsLoading, getVotePercent, getShowNSFWPosts, + getNightmode, getRewriteLinks, getUseBeta, getUpvoteSetting, @@ -39,6 +40,7 @@ import packageJson from '../../../package.json'; votingPower: getVotingPower(state), votePercent: getVotePercent(state), showNSFWPosts: getShowNSFWPosts(state), + nightmode: getNightmode(state), rewriteLinks: getRewriteLinks(state), useBeta: getUseBeta(state), loading: getIsSettingsLoading(state), @@ -56,6 +58,7 @@ export default class Settings extends React.Component { votePercent: PropTypes.number, loading: PropTypes.bool, showNSFWPosts: PropTypes.bool, + nightmode: PropTypes.bool, rewriteLinks: PropTypes.bool, useBeta: PropTypes.bool, reload: PropTypes.func, @@ -72,6 +75,7 @@ export default class Settings extends React.Component { votePercent: 10000, loading: false, showNSFWPosts: false, + nightmode: false, rewriteLinks: false, useBeta: false, upvoteSetting: true, @@ -91,6 +95,7 @@ export default class Settings extends React.Component { votingPower: 'auto', votePercent: 10000, showNSFWPosts: false, + nightmode: false, rewriteLinks: false, exitPageSetting: true, }; @@ -101,6 +106,7 @@ export default class Settings extends React.Component { votingPower: this.props.votingPower, votePercent: this.props.votePercent / 100, showNSFWPosts: this.props.showNSFWPosts, + nightmode: this.props.nightmode, rewriteLinks: this.props.rewriteLinks, useBeta: this.props.useBeta, upvoteSetting: this.props.upvoteSetting, @@ -129,6 +135,10 @@ export default class Settings extends React.Component { this.setState({ showNSFWPosts: nextProps.showNSFWPosts }); } + if (nextProps.nightmode !== this.props.nightmode) { + this.setState({ nightmode: nextProps.nightmode }); + } + if (nextProps.rewriteLinks !== this.props.rewriteLinks) { this.setState({ rewriteLinks: nextProps.rewriteLinks }); } @@ -153,6 +163,7 @@ export default class Settings extends React.Component { votingPower: this.state.votingPower, votePercent: this.state.votePercent * 100, showNSFWPosts: this.state.showNSFWPosts, + nightmode: this.state.nightmode, rewriteLinks: this.state.rewriteLinks, useBeta: this.state.useBeta, upvoteSetting: this.state.upvoteSetting, @@ -170,6 +181,7 @@ export default class Settings extends React.Component { handleVotingPowerChange = event => this.setState({ votingPower: event.target.value }); handleVotePercentChange = value => this.setState({ votePercent: value }); handleShowNSFWPosts = event => this.setState({ showNSFWPosts: event.target.checked }); + handleNightmode = event => this.setState({ nightmode: event.target.checked }); handleRewriteLinksChange = event => this.setState({ rewriteLinks: event.target.checked }); handleUseBetaChange = event => this.setState({ useBeta: event.target.checked }); handleExitPageSettingChange = event => this.setState({ exitPageSetting: event.target.checked }); @@ -185,12 +197,14 @@ export default class Settings extends React.Component { locale: initialLocale, votingPower: initialVotingPower, showNSFWPosts: initialShowNSFWPosts, + nightmode: initialNightmode, loading, } = this.props; const { votingPower, locale, showNSFWPosts, + nightmode, rewriteLinks, useBeta, upvoteSetting, @@ -317,6 +331,27 @@ export default class Settings extends React.Component { +
+

+ +

+

+ +

+
+ + + +
+

diff --git a/src/client/settings/__tests__/settingsReducer.js b/src/client/settings/__tests__/settingsReducer.js index 20d158e039..81bdc42eb2 100644 --- a/src/client/settings/__tests__/settingsReducer.js +++ b/src/client/settings/__tests__/settingsReducer.js @@ -11,6 +11,7 @@ describe('settingsReducer', () => { votePercent: 10000, loading: false, showNSFWPosts: false, + nightmode: false, rewriteLinks: false, upvoteSetting: true, exitPageSetting: true, @@ -63,7 +64,7 @@ describe('settingsReducer', () => { expect(settingsReducer(stateBefore, action)).to.eql(stateAfter); }); - it('should set locale, voting power, vote percent, loading, showNSFWPosts, and rewriteLinks after saving succeeded', () => { + it('should set locale, voting power, vote percent, loading, showNSFWPosts, nightmode and rewriteLinks after saving succeeded', () => { const stateBefore = { ...initialState, loading: true, @@ -75,6 +76,7 @@ describe('settingsReducer', () => { votePercent: 10000, votingPower: 'on', showNSFWPosts: true, + nightmode: true, rewriteLinks: true, }; const action = { @@ -84,6 +86,7 @@ describe('settingsReducer', () => { votingPower: 'on', votePercent: 10000, showNSFWPosts: true, + nightmode: true, rewriteLinks: true, upvoteSetting: true, exitPageSetting: true, diff --git a/src/client/settings/settingsReducer.js b/src/client/settings/settingsReducer.js index 6c2b68a70e..9c355a0c39 100644 --- a/src/client/settings/settingsReducer.js +++ b/src/client/settings/settingsReducer.js @@ -7,6 +7,7 @@ const initialState = { votingPower: 'auto', votePercent: 10000, showNSFWPosts: false, + nightmode: false, rewriteLinks: false, loading: false, upvoteSetting: true, @@ -26,6 +27,7 @@ const settings = (state = initialState, action) => { votingPower, votePercent, showNSFWPosts, + nightmode, rewriteLinks, upvoteSetting, exitPageSetting, @@ -38,6 +40,7 @@ const settings = (state = initialState, action) => { votingPower: votingPower || initialState.votingPower, votePercent: votePercent || initialState.votePercent, showNSFWPosts: showNSFWPosts || initialState.showNSFWPosts, + nightmode: nightmode || initialState.nightmode, rewriteLinks: typeof rewriteLinks === 'boolean' ? rewriteLinks : initialState.rewriteLinks, upvoteSetting: @@ -62,6 +65,7 @@ const settings = (state = initialState, action) => { votingPower: action.payload.votingPower, votePercent: action.payload.votePercent, showNSFWPosts: action.payload.showNSFWPosts, + nightmode: action.payload.nightmode, rewriteLinks: action.payload.rewriteLinks, upvoteSetting: action.payload.upvoteSetting, exitPageSetting: action.payload.exitPageSetting, @@ -90,6 +94,7 @@ export const getLocale = state => state.locale; export const getVotingPower = state => state.votingPower; export const getVotePercent = state => state.votePercent; export const getShowNSFWPosts = state => state.showNSFWPosts; +export const getNightmode = state => state.nightmode; export const getRewriteLinks = state => !!state.rewriteLinks; export const getUpvoteSetting = state => state.upvoteSetting; export const getExitPageSetting = state => state.exitPageSetting; diff --git a/src/client/vendor/SanitizeConfig.js b/src/client/vendor/SanitizeConfig.js index 16d822437f..6a5d10758c 100644 --- a/src/client/vendor/SanitizeConfig.js +++ b/src/client/vendor/SanitizeConfig.js @@ -1,3 +1,4 @@ +import sanitizeHtml from 'sanitize-html'; import URL from 'url-parse'; import { ownUrl } from '../helpers/regexHelpers'; import { knownDomains } from '../helpers/constants'; @@ -87,6 +88,7 @@ export default ({ large = true, noImage = false, sanitizeErrors = [], secureLink img: ['src', 'alt'], a: ['href', 'rel', 'target'], }, + allowedSchemes: sanitizeHtml.defaults.allowedSchemes.concat(['byteball', 'bitcoin']), transformTags: { iframe: (tagName, attribs) => { const srcAtty = decodeURIComponent(attribs.src); @@ -168,7 +170,7 @@ export default ({ large = true, noImage = false, sanitizeErrors = [], secureLink const url = new URL(href); const hostname = url.hostname || 'localhost'; - if (!hostname.match(ownUrl)) { + if (['https', 'http'].indexOf(url.protocol) || !hostname.match(ownUrl)) { attys.target = '_blank'; } diff --git a/src/client/vendor/steemitHtmlReady.js b/src/client/vendor/steemitHtmlReady.js index 43bf386766..f4c7ac3850 100644 --- a/src/client/vendor/steemitHtmlReady.js +++ b/src/client/vendor/steemitHtmlReady.js @@ -122,7 +122,7 @@ function link(state, child) { state.links.add(url); if (state.mutate) { // If this link is not relative, http, or https -- add https. - if (!/^\/(?!\/)|(https?:)?\/\//.test(url)) { + if (!/^[\w.-]+:(\/\/)?/.test(url)) { child.setAttribute('href', `https://${url}`); } }