-
-
- • •
-
-
- glitch-soc/mastodon, Mastodon: Mastodon }}
- />
-
+
+
+
+ • •
+
+
+ glitch-soc/mastodon, Mastodon: Mastodon }}
+ />
+
+
diff --git a/app/javascript/mastodon/features/notifications/components/notification.js b/app/javascript/mastodon/features/notifications/components/notification.js
index bf580794dd..6c1985174a 100644
--- a/app/javascript/mastodon/features/notifications/components/notification.js
+++ b/app/javascript/mastodon/features/notifications/components/notification.js
@@ -12,6 +12,7 @@ export default class Notification extends ImmutablePureComponent {
static propTypes = {
notification: ImmutablePropTypes.map.isRequired,
+ settings: ImmutablePropTypes.map.isRequired,
};
renderFollow (account, link) {
@@ -34,7 +35,7 @@ export default class Notification extends ImmutablePureComponent {
return
;
}
- renderFavourite (notification, link) {
+ renderFavourite (notification, settings, link) {
return (
@@ -44,12 +45,12 @@ export default class Notification extends ImmutablePureComponent {
-
+
);
}
- renderReblog (notification, link) {
+ renderReblog (notification, settings, link) {
return (
@@ -59,13 +60,13 @@ export default class Notification extends ImmutablePureComponent {
-
+
);
}
render () {
- const { notification } = this.props;
+ const { notification, settings } = this.props;
const account = notification.get('account');
const displayName = account.get('display_name').length > 0 ? account.get('display_name') : account.get('username');
const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) };
@@ -77,9 +78,9 @@ export default class Notification extends ImmutablePureComponent {
case 'mention':
return this.renderMention(notification);
case 'favourite':
- return this.renderFavourite(notification, link);
+ return this.renderFavourite(notification, settings, link);
case 'reblog':
- return this.renderReblog(notification, link);
+ return this.renderReblog(notification, settings, link);
}
return null;
diff --git a/app/javascript/mastodon/features/notifications/containers/notification_container.js b/app/javascript/mastodon/features/notifications/containers/notification_container.js
index 7862229676..66baf98a16 100644
--- a/app/javascript/mastodon/features/notifications/containers/notification_container.js
+++ b/app/javascript/mastodon/features/notifications/containers/notification_container.js
@@ -7,6 +7,7 @@ const makeMapStateToProps = () => {
const mapStateToProps = (state, props) => ({
notification: getNotification(state, props.notification, props.accountId),
+ settings: state.get('local_settings'),
});
return mapStateToProps;
diff --git a/app/javascript/mastodon/features/ui/components/column_link.js b/app/javascript/mastodon/features/ui/components/column_link.js
index cbdb6534fd..cbc9265814 100644
--- a/app/javascript/mastodon/features/ui/components/column_link.js
+++ b/app/javascript/mastodon/features/ui/components/column_link.js
@@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import Link from 'react-router-dom/Link';
-const ColumnLink = ({ icon, text, to, href, method, hideOnMobile }) => {
+const ColumnLink = ({ icon, text, to, onClick, href, method, hideOnMobile }) => {
if (href) {
return (
@@ -10,13 +10,20 @@ const ColumnLink = ({ icon, text, to, href, method, hideOnMobile }) => {
{text}
);
- } else {
+ } else if (to) {
return (
{text}
);
+ } else {
+ return (
+
+
+ {text}
+
+ );
}
};
@@ -24,6 +31,7 @@ ColumnLink.propTypes = {
icon: PropTypes.string.isRequired,
text: PropTypes.string.isRequired,
to: PropTypes.string,
+ onClick: PropTypes.func,
href: PropTypes.string,
method: PropTypes.string,
hideOnMobile: PropTypes.bool,
diff --git a/app/javascript/mastodon/features/ui/components/modal_root.js b/app/javascript/mastodon/features/ui/components/modal_root.js
index 48b048eb7e..3e38944417 100644
--- a/app/javascript/mastodon/features/ui/components/modal_root.js
+++ b/app/javascript/mastodon/features/ui/components/modal_root.js
@@ -6,6 +6,7 @@ import VideoModal from './video_modal';
import BoostModal from './boost_modal';
import ConfirmationModal from './confirmation_modal';
import ReportModal from './report_modal';
+import SettingsModal from '../containers/settings_modal_container';
import TransitionMotion from 'react-motion/lib/TransitionMotion';
import spring from 'react-motion/lib/spring';
@@ -16,6 +17,7 @@ const MODAL_COMPONENTS = {
'BOOST': BoostModal,
'CONFIRM': ConfirmationModal,
'REPORT': ReportModal,
+ 'SETTINGS': SettingsModal,
};
export default class ModalRoot extends React.PureComponent {
diff --git a/app/javascript/mastodon/features/ui/components/settings_modal.js b/app/javascript/mastodon/features/ui/components/settings_modal.js
new file mode 100644
index 0000000000..4fc059ac23
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/components/settings_modal.js
@@ -0,0 +1,212 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import ImmutablePropTypes from 'react-immutable-proptypes';
+import { FormattedMessage } from 'react-intl';
+
+class SettingsItem extends React.PureComponent {
+
+ static propTypes = {
+ settings: ImmutablePropTypes.map.isRequired,
+ item: PropTypes.array.isRequired,
+ id: PropTypes.string.isRequired,
+ dependsOn: PropTypes.array,
+ dependsOnNot: PropTypes.array,
+ children: PropTypes.element.isRequired,
+ onChange: PropTypes.func.isRequired,
+ };
+
+ handleChange = (e) => {
+ const { item, onChange } = this.props;
+ onChange(item, e);
+ }
+
+ render () {
+ const { settings, item, id, children, dependsOn, dependsOnNot } = this.props;
+ let enabled = true;
+
+ if (dependsOn) {
+ for (let i = 0; i < dependsOn.length; i++) {
+ enabled = enabled && settings.getIn(dependsOn[i]);
+ }
+ }
+ if (dependsOnNot) {
+ for (let i = 0; i < dependsOnNot.length; i++) {
+ enabled = enabled && !settings.getIn(dependsOnNot[i]);
+ }
+ }
+
+ return (
+
+ );
+ }
+
+}
+
+export default class SettingsModal extends React.PureComponent {
+
+ static propTypes = {
+ settings: ImmutablePropTypes.map.isRequired,
+ toggleSetting: PropTypes.func.isRequired,
+ onClose: PropTypes.func.isRequired,
+ };
+
+ state = {
+ currentIndex: 0,
+ };
+
+ General = () => {
+ return (
+
+
+
+
+
+
+ );
+ }
+
+ CollapsedStatuses = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+ navigateTo = (e) =>
+ this.setState({ currentIndex: +e.currentTarget.getAttribute('data-mastodon-navigation_index') });
+
+ render () {
+
+ const { General, CollapsedStatuses, navigateTo } = this;
+ const { onClose } = this.props;
+ const { currentIndex } = this.state;
+
+ return (
+
+
+
+
+
+ {
+ [
+ ,
+ ,
+ ][currentIndex] ||
+ }
+
+
+
+ );
+ }
+
+}
diff --git a/app/javascript/mastodon/features/ui/containers/settings_modal_container.js b/app/javascript/mastodon/features/ui/containers/settings_modal_container.js
new file mode 100644
index 0000000000..83565c4b9f
--- /dev/null
+++ b/app/javascript/mastodon/features/ui/containers/settings_modal_container.js
@@ -0,0 +1,19 @@
+import { connect } from 'react-redux';
+import { changeLocalSetting } from '../../../actions/local_settings';
+import { closeModal } from '../../../actions/modal';
+import SettingsModal from '../components/settings_modal';
+
+const mapStateToProps = state => ({
+ settings: state.get('local_settings'),
+});
+
+const mapDispatchToProps = dispatch => ({
+ toggleSetting (setting, e) {
+ dispatch(changeLocalSetting(setting, e.target.checked));
+ },
+ onClose () {
+ dispatch(closeModal());
+ },
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(SettingsModal);
diff --git a/app/javascript/mastodon/features/ui/index.js b/app/javascript/mastodon/features/ui/index.js
index 4d38c26770..824963a537 100644
--- a/app/javascript/mastodon/features/ui/index.js
+++ b/app/javascript/mastodon/features/ui/index.js
@@ -73,7 +73,8 @@ class WrappedRoute extends React.Component {
}
const mapStateToProps = state => ({
- layout: state.getIn(['localSettings', 'layout']),
+ layout: state.getIn(['local_settings', 'layout']),
+ isWide: state.getIn(['local_settings', 'stretch']),
});
@connect(mapStateToProps)
@@ -83,6 +84,7 @@ export default class UI extends React.PureComponent {
dispatch: PropTypes.func.isRequired,
children: PropTypes.node,
layout: PropTypes.string,
+ isWide: PropTypes.bool,
};
state = {
@@ -179,7 +181,7 @@ export default class UI extends React.PureComponent {
render () {
const { width, draggingOver } = this.state;
- const { children, layout } = this.props;
+ const { children, layout, isWide } = this.props;
const columnsClass = layout => {
switch (layout) {
@@ -193,7 +195,7 @@ export default class UI extends React.PureComponent {
};
return (
-
+
diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json
index 5ab9144776..147f6a9718 100644
--- a/app/javascript/mastodon/locales/defaultMessages.json
+++ b/app/javascript/mastodon/locales/defaultMessages.json
@@ -188,10 +188,6 @@
},
{
"descriptors": [
- {
- "defaultMessage": "{name} boosted",
- "id": "status.reblogged_by"
- },
{
"defaultMessage": "Collapse",
"id": "status.collapse"
@@ -199,6 +195,10 @@
{
"defaultMessage": "Uncollapse",
"id": "status.uncollapse"
+ },
+ {
+ "defaultMessage": "{name} boosted",
+ "id": "status.reblogged_by"
}
],
"path": "app/javascript/mastodon/components/status.json"
@@ -652,8 +652,8 @@
"id": "navigation_bar.community_timeline"
},
{
- "defaultMessage": "Preferences",
- "id": "navigation_bar.preferences"
+ "defaultMessage": "App settings",
+ "id": "navigation_bar.app_settings"
},
{
"defaultMessage": "Logout",
@@ -667,13 +667,13 @@
"defaultMessage": "Mobile",
"id": "layout.mobile"
},
- {
- "defaultMessage": "Desktop",
- "id": "layout.desktop"
- },
{
"defaultMessage": "Auto",
"id": "layout.auto"
+ },
+ {
+ "defaultMessage": "Desktop",
+ "id": "layout.desktop"
}
],
"path": "app/javascript/mastodon/features/compose/index.json"
@@ -743,6 +743,10 @@
"defaultMessage": "Preferences",
"id": "navigation_bar.preferences"
},
+ {
+ "defaultMessage": "App settings",
+ "id": "navigation_bar.app_settings"
+ },
{
"defaultMessage": "Follow requests",
"id": "navigation_bar.follow_requests"
@@ -1073,7 +1077,7 @@
"id": "onboarding.page_one.welcome"
},
{
- "defaultMessage": "{domain} is an 'instance' of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
+ "defaultMessage": "{domain} is an \"instance\" of Mastodon. Mastodon is a network of independent servers joining up to make one larger social network. We call these servers instances.",
"id": "onboarding.page_one.federation"
},
{
@@ -1121,7 +1125,7 @@
"id": "onboarding.page_six.almost_done"
},
{
- "defaultMessage": "{domain} runs on Glitchsoc, a friendly fork of {Mastodon}. Glitchsoc is fully compatible with any Mastodon instance or app. You can report bugs, request features, or contribute to the code on {github}.",
+ "defaultMessage": "{domain} runs on Glitchsoc. Glitchsoc is a friendly {fork} of {Mastodon}. Glitchsoc is fully compatible with all Mastodon apps and instances. Glitchsoc is free open-source software. You can report bugs, request features, or contribute to the code on {github}.",
"id": "onboarding.page_six.github"
},
{
@@ -1168,6 +1172,71 @@
],
"path": "app/javascript/mastodon/features/ui/components/report_modal.json"
},
+ {
+ "descriptors": [
+ {
+ "defaultMessage": "General",
+ "id": "settings.general"
+ },
+ {
+ "defaultMessage": "Wide view (Desktop mode only)",
+ "id": "settings.wide_view"
+ },
+ {
+ "defaultMessage": "Collapsed toots",
+ "id": "settings.collapsed_statuses"
+ },
+ {
+ "defaultMessage": "Enable collapsed toots",
+ "id": "settings.enable_collapsed"
+ },
+ {
+ "defaultMessage": "Automatic collapsing",
+ "id": "settings.auto_collapse"
+ },
+ {
+ "defaultMessage": "Everything",
+ "id": "settings.auto_collapse_all"
+ },
+ {
+ "defaultMessage": "Notifications",
+ "id": "settings.auto_collapse_notifications"
+ },
+ {
+ "defaultMessage": "Lengthy toots",
+ "id": "settings.auto_collapse_lengthy"
+ },
+ {
+ "defaultMessage": "Replies",
+ "id": "settings.auto_collapse_replies"
+ },
+ {
+ "defaultMessage": "Toots with media",
+ "id": "settings.auto_collapse_media"
+ },
+ {
+ "defaultMessage": "Image backgrounds",
+ "id": "settings.image_backgrounds"
+ },
+ {
+ "defaultMessage": "Give collapsed toots an image background",
+ "id": "settings.image_backgrounds_users"
+ },
+ {
+ "defaultMessage": "Preview collapsed toot media",
+ "id": "settings.image_backgrounds_media"
+ },
+ {
+ "defaultMessage": "User preferences",
+ "id": "settings.global_settings"
+ },
+ {
+ "defaultMessage": "Close",
+ "id": "settings.close"
+ }
+ ],
+ "path": "app/javascript/mastodon/features/ui/components/settings_modal.json"
+ },
{
"descriptors": [
{
@@ -1211,4 +1280,4 @@
],
"path": "app/javascript/mastodon/features/ui/components/video_modal.json"
}
-]
+]
\ No newline at end of file
diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index d0c0ca1372..fb81aba4ac 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -87,6 +87,7 @@
"loading_indicator.label": "Loading...",
"media_gallery.toggle_visible": "Toggle visibility",
"missing_indicator.label": "Not found",
+ "navigation_bar.app_settings": "App settings",
"navigation_bar.blocks": "Blocked users",
"navigation_bar.community_timeline": "Local timeline",
"navigation_bar.edit_profile": "Edit profile",
@@ -146,6 +147,21 @@
"report.target": "Reporting {target}",
"search.placeholder": "Search",
"search_results.total": "{count, number} {count, plural, one {result} other {results}}",
+ "settings.auto_collapse": "Automatic collapsing",
+ "settings.auto_collapse_all": "Everything",
+ "settings.auto_collapse_lengthy": "Lengthy toots",
+ "settings.auto_collapse_media": "Toots with media",
+ "settings.auto_collapse_notifications": "Notifications",
+ "settings.auto_collapse_replies": "Replies",
+ "settings.close": "Close",
+ "settings.collapsed_statuses": "Collapsed toots",
+ "settings.enable_collapsed": "Enable collapsed toots",
+ "settings.general": "General",
+ "settings.global_settings": "User preferences",
+ "settings.image_backgrounds": "Image backgrounds",
+ "settings.image_backgrounds_media": "Preview collapsed toot media",
+ "settings.image_backgrounds_users": "Give collapsed toots an image background",
+ "settings.wide_view": "Wide view (Desktop mode only)",
"status.cannot_reblog": "This post cannot be boosted",
"status.collapse": "Collapse",
"status.delete": "Delete",
diff --git a/app/javascript/mastodon/reducers/index.js b/app/javascript/mastodon/reducers/index.js
index 24f7f94a6d..0749a4e4a8 100644
--- a/app/javascript/mastodon/reducers/index.js
+++ b/app/javascript/mastodon/reducers/index.js
@@ -14,7 +14,7 @@ import relationships from './relationships';
import search from './search';
import notifications from './notifications';
import settings from './settings';
-import localSettings from './local_settings';
+import local_settings from './local_settings';
import status_lists from './status_lists';
import cards from './cards';
import reports from './reports';
@@ -37,7 +37,7 @@ export default combineReducers({
search,
notifications,
settings,
- localSettings,
+ local_settings,
cards,
reports,
contexts,
diff --git a/app/javascript/mastodon/reducers/local_settings.js b/app/javascript/mastodon/reducers/local_settings.js
index 529d31ebba..c6581fe2e8 100644
--- a/app/javascript/mastodon/reducers/local_settings.js
+++ b/app/javascript/mastodon/reducers/local_settings.js
@@ -3,7 +3,22 @@ import { STORE_HYDRATE } from '../actions/store';
import Immutable from 'immutable';
const initialState = Immutable.Map({
- layout: 'auto',
+ layout : 'auto',
+ stretch : true,
+ collapsed : {
+ enabled : true,
+ auto : {
+ all : false,
+ notifications : true,
+ lengthy : true,
+ replies : false,
+ media : false,
+ },
+ backgrounds : {
+ user_backgrounds : false,
+ preview_images : false,
+ },
+ },
});
const hydrate = (state, localSettings) => state.mergeDeep(localSettings);
@@ -11,7 +26,7 @@ const hydrate = (state, localSettings) => state.mergeDeep(localSettings);
export default function localSettings(state = initialState, action) {
switch(action.type) {
case STORE_HYDRATE:
- return hydrate(state, action.state.get('localSettings'));
+ return hydrate(state, action.state.get('local_settings'));
case LOCAL_SETTING_CHANGE:
return state.setIn(action.key, action.value);
default:
diff --git a/app/javascript/styles/components.scss b/app/javascript/styles/components.scss
index 9bf386c0c5..2ed6d14a30 100644
--- a/app/javascript/styles/components.scss
+++ b/app/javascript/styles/components.scss
@@ -1351,6 +1351,10 @@
overflow-x: auto;
position: relative;
padding: 10px;
+
+ .wide & {
+ justify-content: center;
+ }
}
@include limited-single-column('screen and (max-width: 360px)', $parent: null) {
@@ -1367,6 +1371,12 @@
flex-direction: column;
overflow: hidden;
+ .wide & {
+ flex: auto;
+ min-width: 330px;
+ max-width: 400px;
+ }
+
> .scrollable {
background: $ui-base-color;
}
@@ -1387,6 +1397,12 @@
display: flex;
flex-direction: column;
overflow-y: auto;
+
+ .wide & {
+ flex: 1 1 200px;
+ min-width: 300px;
+ max-width: 400px;
+ }
}
.drawer__tab {
@@ -1399,11 +1415,12 @@
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
+ outline: none;
+ cursor: pointer;
}
.column,
.drawer {
- flex: 1 1 100%;
@supports(display: grid) { // hack to fix Chrome <57
contain: strict;
}
@@ -1419,20 +1436,25 @@
}
}
-@include single-column('screen and (max-width: 1024px)', $parent: null) {
- .column,
- .drawer {
- width: 100%;
- padding: 0;
- }
+:root { // Overrides .wide stylings for mobile view
+ @include single-column('screen and (max-width: 1024px)', $parent: null) {
+ .column,
+ .drawer {
+ flex: auto;
+ width: 100%;
+ min-width: 0;
+ max-width: none;
+ padding: 0;
+ }
- .columns-area {
- flex-direction: column;
- }
+ .columns-area {
+ flex-direction: column;
+ }
- .search__input,
- .autosuggest-textarea__textarea {
- font-size: 16px;
+ .search__input,
+ .autosuggest-textarea__textarea {
+ font-size: 16px;
+ }
}
}
@@ -1443,7 +1465,6 @@
.column,
.drawer {
- flex: 0 0 auto;
padding: 10px;
padding-left: 5px;
padding-right: 5px;
@@ -1771,6 +1792,8 @@
font-size: 16px;
padding: 15px;
text-decoration: none;
+ cursor: pointer;
+ outline: none;
&:hover {
background: lighten($ui-base-color, 11%);
@@ -3312,6 +3335,85 @@ button.icon-button.active i.fa-retweet {
margin-bottom: 20px;
}
+.settings-modal {
+ position: relative;
+ display: flex;
+ flex-direction: row;
+ background: $ui-secondary-color;
+ color: $ui-base-color;
+ border-radius: 8px;
+ height: 80vh;
+ width: 80vw;
+ max-width: 740px;
+ max-height: 450px;
+ overflow: hidden;
+
+ label {
+ display: block;
+ }
+
+ h1 {
+ font-size: 18px;
+ font-weight: 500;
+ line-height: 24px;
+ margin-bottom: 20px;
+ }
+
+ h2 {
+ font-size: 15px;
+ font-weight: 500;
+ line-height: 20px;
+ margin-top: 20px;
+ margin-bottom: 10px;
+ }
+}
+
+.settings-modal__navigation {
+ background: $primary-text-color;
+ color: $ui-base-color;
+ width: 200px;
+ font-size: 15px;
+ line-height: 20px;
+ overflow-y: auto;
+
+ .settings-modal__navigation-item, .settings-modal__navigation-close {
+ display: block;
+ padding: 15px 20px;
+ cursor: pointer;
+ outline: none;
+ text-decoration: none;
+ }
+
+ .settings-modal__navigation-item {
+ background: $primary-text-color;
+ color: inherit;
+ border-bottom: 1px $ui-primary-color solid;
+ transition: background .3s;
+
+ &:hover {
+ background: $ui-secondary-color;
+ }
+
+ &.active {
+ background: $ui-highlight-color;
+ color: $primary-text-color;
+ }
+ }
+
+ .settings-modal__navigation-close {
+ background: $error-value-color;
+ color: $primary-text-color;
+ }
+}
+
+.settings-modal__content {
+ display: block;
+ flex: auto;
+ padding: 15px 20px 15px 20px;
+ width: 360px;
+ overflow-y: auto;
+}
+
.onboard-sliders {
display: inline-block;
max-width: 30px;
diff --git a/app/javascript/styles/custom.scss b/app/javascript/styles/custom.scss
index 5144e4fb6f..6a18fd6289 100644
--- a/app/javascript/styles/custom.scss
+++ b/app/javascript/styles/custom.scss
@@ -1,19 +1,5 @@
@import 'application';
-@include multi-columns('screen and (min-width: 1300px)', $parent: null) {
- .column {
- flex-grow: 1 !important;
- max-width: 400px;
- }
-
- .drawer {
- flex-grow: 1 !important;
- flex-basis: 200px !important;
- min-width: 268px;
- max-width: 400px;
- }
-}
-
.muted {
.status__content p, .status__content a {
color: lighten($ui-base-color, 35%);