diff options
Diffstat (limited to 'app/javascript/glitch')
11 files changed, 705 insertions, 189 deletions
diff --git a/app/javascript/glitch/actions/local_settings.js b/app/javascript/glitch/actions/local_settings.js index 479b5841d..93c5a9a17 100644 --- a/app/javascript/glitch/actions/local_settings.js +++ b/app/javascript/glitch/actions/local_settings.js @@ -21,12 +21,12 @@ consists of the following: */ - /* * * * */ +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* -Constants ---------- +Constants: +---------- We provide the following constants: @@ -39,12 +39,12 @@ We provide the following constants: export const LOCAL_SETTING_CHANGE = 'LOCAL_SETTING_CHANGE'; - /* * * * */ +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* -`changeLocalSetting(key, value)` --------------------------------- +`changeLocalSetting(key, value)`: +--------------------------------- Changes the local setting with the given `key` to the given `value`. `key` **MUST** be an array of strings, as required by @@ -67,12 +67,12 @@ export function changeLocalSetting(key, value) { }; }; - /* * * * */ +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* -`saveLocalSettings()` ---------------------- +`saveLocalSettings()`: +---------------------- Saves the local settings to `localStorage` as a JSON object. `changeLocalSetting()` calls this whenever it changes a setting. We diff --git a/app/javascript/glitch/components/account/header.js b/app/javascript/glitch/components/account/header.js index e2d961240..b79140c02 100644 --- a/app/javascript/glitch/components/account/header.js +++ b/app/javascript/glitch/components/account/header.js @@ -1,3 +1,45 @@ +/* + +`<AccountHeader>` +================= + +> For more information on the contents of this file, please contact: +> +> - kibigo! [@kibi@glitch.social] + +Original file by @gargron@mastodon.social et al as part of +tootsuite/mastodon. We've expanded it in order to handle user bio +frontmatter. + +The `<AccountHeader>` component provides the header for account +timelines. It is a fairly simple component which mostly just consists +of a `render()` method. + +__Props:__ + + - __`account` (`ImmutablePropTypes.map`) :__ + The account to render a header for. + + - __`me` (`PropTypes.number.isRequired`) :__ + The id of the currently-signed-in account. + + - __`onFollow` (`PropTypes.func.isRequired`) :__ + The function to call when the user clicks the "follow" button. + + - __`intl` (`PropTypes.object.isRequired`) :__ + Our internationalization object, inserted by `@injectIntl`. + +*/ + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Imports: +-------- + +*/ + // Package imports // import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; @@ -14,25 +56,63 @@ import Avatar from '../../../mastodon/components/avatar'; // Our imports // import { processBio } from '../../util/bio_metadata'; +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Inital setup: +------------- + +The `messages` constant is used to define any messages that we need +from inside props. In our case, these are the `unfollow`, `follow`, and +`requested` messages used in the `title` of our buttons. + +*/ + const messages = defineMessages({ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, follow: { id: 'account.follow', defaultMessage: 'Follow' }, requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }, }); +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Implementation: +--------------- + +*/ + @injectIntl -export default class Header extends ImmutablePureComponent { +export default class AccountHeader extends ImmutablePureComponent { static propTypes = { - account: ImmutablePropTypes.map, - me: PropTypes.number.isRequired, - onFollow: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, + account : ImmutablePropTypes.map, + me : PropTypes.number.isRequired, + onFollow : PropTypes.func.isRequired, + intl : PropTypes.object.isRequired, }; +/* + +### `render()` + +The `render()` function is used to render our component. + +*/ + render () { const { account, me, intl } = this.props; +/* + +If no `account` is provided, then we can't render a header. Otherwise, +we get the `displayName` for the account, if available. If it's blank, +then we set the `displayName` to just be the `username` of the account. + +*/ + if (!account) { return null; } @@ -40,17 +120,30 @@ export default class Header extends ImmutablePureComponent { let displayName = account.get('display_name'); let info = ''; let actionBtn = ''; - let lockedIcon = ''; + let following = false; if (displayName.length === 0) { displayName = account.get('username'); } - if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) { - info = <span className='account--follows-info'><FormattedMessage id='account.follows_you' defaultMessage='Follows you' /></span>; - } +/* + +Next, we handle the account relationships. If the account follows the +user, then we add an `info` message. If the user has requested a +follow, then we disable the `actionBtn` and display an hourglass. +Otherwise, if the account isn't blocked, we set the `actionBtn` to the +appropriate icon. + +*/ if (me !== account.get('id')) { + if (account.getIn(['relationship', 'followed_by'])) { + info = ( + <span className='account--follows-info'> + <FormattedMessage id='account.follows_you' defaultMessage='Follows you' /> + </span> + ); + } if (account.getIn(['relationship', 'requested'])) { actionBtn = ( <div className='account--action-button'> @@ -58,30 +151,64 @@ export default class Header extends ImmutablePureComponent { </div> ); } else if (!account.getIn(['relationship', 'blocking'])) { + following = account.getIn(['relationship', 'following']); actionBtn = ( <div className='account--action-button'> - <IconButton size={26} icon={account.getIn(['relationship', 'following']) ? 'user-times' : 'user-plus'} active={account.getIn(['relationship', 'following'])} title={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} /> + <IconButton + size={26} + icon={following ? 'user-times' : 'user-plus'} + active={following} + title={intl.formatMessage(following ? messages.unfollow : messages.follow)} + onClick={this.props.onFollow} + /> </div> ); } } - if (account.get('locked')) { - lockedIcon = <i className='fa fa-lock' />; - } +/* - const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) }; +`displayNameHTML` processes the `displayName` and prepares it for +insertion into the document. Meanwhile, we extract the `text` and +`metadata` from our account's `note` using `processBio()`. + +*/ + + const displayNameHTML = { + __html : emojify(escapeTextContentForBrowser(displayName)), + }; const { text, metadata } = processBio(account.get('note')); +/* + +Here, we render our component using all the things we've defined above. + +*/ + return ( <div className='account__header__wrapper'> - <div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}> + <div + className='account__header' + style={{ backgroundImage: `url(${account.get('header')})` }} + > <div> <a href={account.get('url')} target='_blank' rel='noopener'> - <span className='account__header__avatar'><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={90} /></span> - <span className='account__header__display-name' dangerouslySetInnerHTML={displayNameHTML} /> + <span className='account__header__avatar'> + <Avatar + src={account.get('avatar')} + staticSrc={account.get('avatar_static')} + size={90} + /> + </span> + <span + className='account__header__display-name' + dangerouslySetInnerHTML={displayNameHTML} + /> </a> - <span className='account__header__username'>@{account.get('acct')} {lockedIcon}</span> + <span className='account__header__username'> + @{account.get('acct')} + {account.get('locked') ? <i className='fa fa-lock' /> : null} + </span> <div className='account__header__content' dangerouslySetInnerHTML={{ __html: emojify(text) }} /> {info} diff --git a/app/javascript/glitch/components/compose/advanced_options/container.js b/app/javascript/glitch/components/compose/advanced_options/container.js index 10804454a..160f22737 100644 --- a/app/javascript/glitch/components/compose/advanced_options/container.js +++ b/app/javascript/glitch/components/compose/advanced_options/container.js @@ -1,3 +1,21 @@ +/* + +`<ComposeAdvancedOptionsContainer>` +=================================== + +This container connects `<ComposeAdvancedOptions>` to the Redux store. + +*/ + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Imports: +-------- + +*/ + // Package imports // import { connect } from 'react-redux'; @@ -7,10 +25,36 @@ import { toggleComposeAdvancedOption } from '../../../../mastodon/actions/compos // Our imports // import ComposeAdvancedOptions from '.'; +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +State mapping: +-------------- + +The `mapStateToProps()` function maps various state properties to the +props of our component. The only property we care about is +`compose.advanced_options`. + +*/ + const mapStateToProps = state => ({ values: state.getIn(['compose', 'advanced_options']), }); +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Dispatch mapping: +----------------- + +The `mapDispatchToProps()` function maps dispatches to our store to the +various props of our component. We just need to provide a dispatch for +when an advanced option toggle changes. + +*/ + const mapDispatchToProps = dispatch => ({ onChange (option) { diff --git a/app/javascript/glitch/components/compose/advanced_options/index.js b/app/javascript/glitch/components/compose/advanced_options/index.js index dabf66095..b745d1cdf 100644 --- a/app/javascript/glitch/components/compose/advanced_options/index.js +++ b/app/javascript/glitch/components/compose/advanced_options/index.js @@ -1,137 +1,241 @@ +/* + +`<ComposeAdvancedOptions>` +========================== + +> For more information on the contents of this file, please contact: +> +> - surinna [@srn@dev.glitch.social] + +This adds an advanced options dropdown to the toot compose box, for +toggles that don't necessarily fit elsewhere. + +__Props:__ + + - __`values` (`ImmutablePropTypes.contains(…).isRequired`) :__ + An Immutable map with the following values: + + - __`do_not_federate` (`PropTypes.bool.isRequired`) :__ + Specifies whether or not to federate the status. + + - __`onChange` (`PropTypes.func.isRequired`) :__ + The function to call when a toggle is changed. We pass this from + our container to the toggle. + + - __`intl` (`PropTypes.object.isRequired`) :__ + Our internationalization object, inserted by `@injectIntl`. + +__State:__ + + - __`open` :__ + This tells whether the dropdown is currently open or closed. + +*/ + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Imports: +-------- + +*/ + // Package imports // import React from 'react'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import Toggle from 'react-toggle'; import { injectIntl, defineMessages } from 'react-intl'; // Mastodon imports // import IconButton from '../../../../mastodon/components/icon_button'; +// Our imports // +import ComposeAdvancedOptionsToggle from './toggle'; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Inital setup: +------------- + +The `messages` constant is used to define any messages that we need +from inside props. These are the various titles and labels on our +toggles. + +`iconStyle` styles the icon used for the dropdown button. + +*/ + const messages = defineMessages({ - local_only_short: { id: 'advanced-options.local-only.short', defaultMessage: 'Local-only' }, - local_only_long: { id: 'advanced-options.local-only.long', defaultMessage: 'Do not post to other instances' }, - advanced_options_icon_title: { id: 'advanced_options.icon_title', defaultMessage: 'Advanced options' }, + local_only_short : + { id: 'advanced-options.local-only.short', defaultMessage: 'Local-only' }, + local_only_long : + { id: 'advanced-options.local-only.long', defaultMessage: 'Do not post to other instances' }, + advanced_options_icon_title : + { id: 'advanced_options.icon_title', defaultMessage: 'Advanced options' }, }); const iconStyle = { - height: null, - lineHeight: '27px', + height : null, + lineHeight : '27px', }; -class AdvancedOptionToggle extends React.PureComponent { - - static propTypes = { - onChange: PropTypes.func.isRequired, - active: PropTypes.bool.isRequired, - name: PropTypes.string.isRequired, - shortText: PropTypes.string.isRequired, - longText: PropTypes.string.isRequired, - } - - onToggle = () => { - this.props.onChange(this.props.name); - } +/* - render() { - const { active, shortText, longText } = this.props; - return ( - <div role='button' tabIndex='0' className='advanced-options-dropdown__option' onClick={this.onToggle}> - <div className='advanced-options-dropdown__option__toggle'> - <Toggle checked={active} onChange={this.onToggle} /> - </div> - <div className='advanced-options-dropdown__option__content'> - <strong>{shortText}</strong> - {longText} - </div> - </div> - ); - } +Implementation: +--------------- -} +*/ @injectIntl export default class ComposeAdvancedOptions extends React.PureComponent { static propTypes = { - values: ImmutablePropTypes.contains({ - do_not_federate: PropTypes.bool.isRequired, + values : ImmutablePropTypes.contains({ + do_not_federate : PropTypes.bool.isRequired, }).isRequired, - onChange: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, + onChange : PropTypes.func.isRequired, + intl : PropTypes.object.isRequired, }; + state = { + open: false, + }; + +/* + +### `onToggleDropdown()` + +This function toggles the opening and closing of the advanced options +dropdown. + +*/ + onToggleDropdown = () => { this.setState({ open: !this.state.open }); }; +/* + +### `onGlobalClick(e)` + +This function closes the advanced options dropdown if you click +anywhere else on the screen. + +*/ + onGlobalClick = (e) => { if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) { this.setState({ open: false }); } } +/* + +### `componentDidMount()`, `componentWillUnmount()` + +This function closes the advanced options dropdown if you click +anywhere else on the screen. + +*/ + componentDidMount () { window.addEventListener('click', this.onGlobalClick); window.addEventListener('touchstart', this.onGlobalClick); } - componentWillUnmount () { window.removeEventListener('click', this.onGlobalClick); window.removeEventListener('touchstart', this.onGlobalClick); } - state = { - open: false, - }; +/* - handleClick = (e) => { - const option = e.currentTarget.getAttribute('data-index'); - e.preventDefault(); - this.props.onChange(option); - } +### `setRef(c)` + +`setRef()` stores a reference to the dropdown's `<div> in `this.node`. + +*/ setRef = (c) => { this.node = c; } +/* + +### `render()` + +`render()` actually puts our component on the screen. + +*/ + render () { const { open } = this.state; const { intl, values } = this.props; +/* + +The `options` array provides all of the available advanced options +alongside their icon, text, and name. + +*/ const options = [ - { icon: 'wifi', shortText: messages.local_only_short, longText: messages.local_only_long, key: 'do_not_federate' }, + { icon: 'wifi', shortText: messages.local_only_short, longText: messages.local_only_long, name: 'do_not_federate' }, ]; +/* + +`anyEnabled` tells us if any of our advanced options have been enabled. + +*/ + const anyEnabled = values.some((enabled) => enabled); + +/* + +`optionElems` takes our `options` and creates +`<ComposeAdvancedOptionsToggle>`s out of them. We use the `name` of the +toggle as its `key` so that React can keep track of it. + +*/ + const optionElems = options.map((option) => { return ( - <AdvancedOptionToggle + <ComposeAdvancedOptionsToggle onChange={this.props.onChange} - active={values.get(option.key)} - key={option.key} - name={option.key} + active={values.get(option.name)} + key={option.name} + name={option.name} shortText={intl.formatMessage(option.shortText)} longText={intl.formatMessage(option.longText)} /> ); }); - return (<div ref={this.setRef} className={`advanced-options-dropdown ${open ? 'open' : ''} ${anyEnabled ? 'active' : ''} `}> - <div className='advanced-options-dropdown__value'> - <IconButton - className='advanced-options-dropdown__value' - title={intl.formatMessage(messages.advanced_options_icon_title)} - icon='ellipsis-h' active={open || anyEnabled} - size={18} - style={iconStyle} - onClick={this.onToggleDropdown} - /> - </div> - <div className='advanced-options-dropdown__dropdown'> - {optionElems} +/* + +Finally, we can render our component. + +*/ + + return ( + <div ref={this.setRef} className={`advanced-options-dropdown ${open ? 'open' : ''} ${anyEnabled ? 'active' : ''} `}> + <div className='advanced-options-dropdown__value'> + <IconButton + className='advanced-options-dropdown__value' + title={intl.formatMessage(messages.advanced_options_icon_title)} + icon='ellipsis-h' active={open || anyEnabled} + size={18} + style={iconStyle} + onClick={this.onToggleDropdown} + /> + </div> + <div className='advanced-options-dropdown__dropdown'> + {optionElems} + </div> </div> - </div>); + ); } } diff --git a/app/javascript/glitch/components/compose/advanced_options/toggle.js b/app/javascript/glitch/components/compose/advanced_options/toggle.js new file mode 100644 index 000000000..d6907472a --- /dev/null +++ b/app/javascript/glitch/components/compose/advanced_options/toggle.js @@ -0,0 +1,103 @@ +/* + +`<ComposeAdvancedOptionsToggle>` +================================ + +> For more information on the contents of this file, please contact: +> +> - surinna [@srn@dev.glitch.social] + +This creates the toggle used by `<ComposeAdvancedOptions>`. + +__Props:__ + + - __`onChange` (`PropTypes.func`) :__ + This provides the function to call when the toggle is + (de-?)activated. + + - __`active` (`PropTypes.bool`) :__ + This prop controls whether the toggle is currently active or not. + + - __`name` (`PropTypes.string`) :__ + This identifies the toggle, and is sent to `onChange()` when it is + called. + + - __`shortText` (`PropTypes.string`) :__ + This is a short string used as the title of the toggle. + + - __`longText` (`PropTypes.string`) :__ + This is a longer string used as a subtitle for the toggle. + +*/ + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Imports: +-------- + +*/ + +// Package imports // +import React from 'react'; +import PropTypes from 'prop-types'; +import Toggle from 'react-toggle'; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Implementation: +--------------- + +*/ + +export default class ComposeAdvancedOptionsToggle extends React.PureComponent { + + static propTypes = { + onChange: PropTypes.func.isRequired, + active: PropTypes.bool.isRequired, + name: PropTypes.string.isRequired, + shortText: PropTypes.string.isRequired, + longText: PropTypes.string.isRequired, + } + +/* + +### `onToggle()` + +The `onToggle()` function simply calls the `onChange()` prop with the +toggle's `name`. + +*/ + + onToggle = () => { + this.props.onChange(this.props.name); + } + +/* + +### `render()` + +The `render()` function is used to render our component. We just render +a `<Toggle>` and place next to it our text. + +*/ + + render() { + const { active, shortText, longText } = this.props; + return ( + <div role='button' tabIndex='0' className='advanced-options-dropdown__option' onClick={this.onToggle}> + <div className='advanced-options-dropdown__option__toggle'> + <Toggle checked={active} onChange={this.onToggle} /> + </div> + <div className='advanced-options-dropdown__option__content'> + <strong>{shortText}</strong> + {longText} + </div> + </div> + ); + } + +} diff --git a/app/javascript/glitch/components/notification/container.js b/app/javascript/glitch/components/notification/container.js index 60303537d..bed086172 100644 --- a/app/javascript/glitch/components/notification/container.js +++ b/app/javascript/glitch/components/notification/container.js @@ -1,3 +1,21 @@ +/* + +`<NotificationContainer>` +========================= + +This container connects `<Notification>`s to the Redux store. + +*/ + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Imports: +-------- + +*/ + // Package imports // import { connect } from 'react-redux'; @@ -8,6 +26,20 @@ import { makeGetNotification } from '../../../mastodon/selectors'; import Notification from '.'; import { deleteNotification } from '../../../mastodon/actions/notifications'; +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +State mapping: +-------------- + +The `mapStateToProps()` function maps various state properties to the +props of our component. We wrap this in `makeMapStateToProps()` so that +we only have to call `makeGetNotification()` once instead of every +time. + +*/ + const makeMapStateToProps = () => { const getNotification = makeGetNotification(); @@ -19,7 +51,20 @@ const makeMapStateToProps = () => { return mapStateToProps; }; -const mapDispatchToProps = (dispatch) => ({ +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Dispatch mapping: +----------------- + +The `mapDispatchToProps()` function maps dispatches to our store to the +various props of our component. We only need to provide a dispatch for +deleting notifications. + +*/ + +const mapDispatchToProps = dispatch => ({ onDeleteNotification (id) { dispatch(deleteNotification(id)); }, diff --git a/app/javascript/glitch/components/notification/follow.js b/app/javascript/glitch/components/notification/follow.js new file mode 100644 index 000000000..26396478b --- /dev/null +++ b/app/javascript/glitch/components/notification/follow.js @@ -0,0 +1,171 @@ +/* + +`<NotificationFollow>` +====================== + +This component renders a follow notification. + +__Props:__ + + - __`id` (`PropTypes.number.isRequired`) :__ + This is the id of the notification. + + - __`onDeleteNotification` (`PropTypes.func.isRequired`) :__ + The function to call when a notification should be + dismissed/deleted. + + - __`account` (`PropTypes.object.isRequired`) :__ + The account associated with the follow notification, ie the account + which followed the user. + + - __`intl` (`PropTypes.object.isRequired`) :__ + Our internationalization object, inserted by `@injectIntl`. + +*/ + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Imports: +-------- + +*/ + +// Package imports // +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; +import escapeTextContentForBrowser from 'escape-html'; +import ImmutablePureComponent from 'react-immutable-pure-component'; + +// Mastodon imports // +import emojify from '../../../mastodon/emoji'; +import Permalink from '../../../mastodon/components/permalink'; +import AccountContainer from '../../../mastodon/containers/account_container'; + +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + +/* + +Inital setup: +------------- + +The `messages` constant is used to define any messages that we need +from inside props. + +*/ + +const messages = defineMessages({ + deleteNotification : + { id: 'status.dismiss_notification', defaultMessage: 'Dismiss notification' }, +}); + +/* + +Implementation: +--------------- + +*/ + +@injectIntl +export default class NotificationFollow extends ImmutablePureComponent { + + static propTypes = { + id : PropTypes.number.isRequired, + onDeleteNotification : PropTypes.func.isRequired, + account : ImmutablePropTypes.map.isRequired, + intl : PropTypes.object.isRequired, + }; + +/* + +### `handleNotificationDeleteClick()` + +This function just calls our `onDeleteNotification()` prop with the +notification's `id`. + +*/ + + handleNotificationDeleteClick = () => { + this.props.onDeleteNotification(this.props.id); + } + +/* + +### `render()` + +This actually renders the component. + +*/ + + render () { + const { account, intl } = this.props; + +/* + +`dismiss` creates the notification dismissal button. Its title is given +by `dismissTitle`. + +*/ + + const dismissTitle = intl.formatMessage(messages.deleteNotification); + const dismiss = ( + <button + aria-label={dismissTitle} + title={dismissTitle} + onClick={this.handleNotificationDeleteClick} + className='status__prepend-dismiss-button' + > + <i className='fa fa-eraser' /> + </button> + ); + +/* + +`link` is a container for the account's `displayName`, which links to +the account timeline using a `<Permalink>`. + +*/ + + const displayName = account.get('display_name') || account.get('username'); + const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) }; + const link = ( + <Permalink + className='notification__display-name' + href={account.get('url')} + title={account.get('acct')} + to={`/accounts/${account.get('id')}`} + dangerouslySetInnerHTML={displayNameHTML} + /> + ); + +/* + +We can now render our component. + +*/ + + return ( + <div className='notification notification-follow'> + <div className='notification__message'> + <div className='notification__favourite-icon-wrapper'> + <i className='fa fa-fw fa-user-plus' /> + </div> + + <FormattedMessage + id='notification.follow' + defaultMessage='{name} followed you' + values={{ name: link }} + /> + + {dismiss} + </div> + + <AccountContainer id={account.get('id')} withNote={false} /> + </div> + ); + } + +} diff --git a/app/javascript/glitch/components/notification/follow_notification.js b/app/javascript/glitch/components/notification/follow_notification.js deleted file mode 100644 index 7cabd91f6..000000000 --- a/app/javascript/glitch/components/notification/follow_notification.js +++ /dev/null @@ -1,78 +0,0 @@ -// Package imports // -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import { defineMessages, FormattedMessage, injectIntl } from 'react-intl'; -import escapeTextContentForBrowser from 'escape-html'; -import ImmutablePureComponent from 'react-immutable-pure-component'; - -// Mastodon imports // -import emojify from '../../../mastodon/emoji'; -import Permalink from '../../../mastodon/components/permalink'; -import AccountContainer from '../../../mastodon/containers/account_container'; - -const messages = defineMessages({ - deleteNotification: { id: 'status.dismiss_notification', defaultMessage: 'Dismiss notification' }, -}); - - -@injectIntl -export default class FollowNotification extends ImmutablePureComponent { - - static contextTypes = { - router: PropTypes.object, - }; - - static propTypes = { - notificationId: PropTypes.number.isRequired, - onDeleteNotification: PropTypes.func.isRequired, - account: ImmutablePropTypes.map.isRequired, - intl: PropTypes.object.isRequired, - }; - - // Avoid checking props that are functions (and whose equality will always - // evaluate to false. See react-immutable-pure-component for usage. - updateOnProps = [ - 'account', - ] - - handleNotificationDeleteClick = () => { - this.props.onDeleteNotification(this.props.notificationId); - } - - render () { - const { account, intl } = this.props; - - const dismissTitle = intl.formatMessage(messages.deleteNotification); - const dismiss = ( - <button - aria-label={dismissTitle} - title={dismissTitle} - onClick={this.handleNotificationDeleteClick} - className='status__prepend-dismiss-button' - > - <i className='fa fa-eraser' /> - </button> - ); - - const displayName = account.get('display_name').length > 0 ? account.get('display_name') : account.get('username'); - const displayNameHTML = { __html: emojify(escapeTextContentForBrowser(displayName)) }; - const link = <Permalink className='notification__display-name' href={account.get('url')} title={account.get('acct')} to={`/accounts/${account.get('id')}`} dangerouslySetInnerHTML={displayNameHTML} />; - return ( - <div className='notification notification-follow'> - <div className='notification__message'> - <div className='notification__favourite-icon-wrapper'> - <i className='fa fa-fw fa-user-plus' /> - </div> - - <FormattedMessage id='notification.follow' defaultMessage='{name} followed you' values={{ name: link }} /> - - {dismiss} - </div> - - <AccountContainer id={account.get('id')} withNote={false} /> - </div> - ); - } - -} diff --git a/app/javascript/glitch/components/notification/index.js b/app/javascript/glitch/components/notification/index.js index 0cdc03cbe..556d5aea8 100644 --- a/app/javascript/glitch/components/notification/index.js +++ b/app/javascript/glitch/components/notification/index.js @@ -8,7 +8,7 @@ import PropTypes from 'prop-types'; // Our imports // import StatusContainer from '../status/container'; -import FollowNotification from './follow_notification'; +import NotificationFollow from './follow'; export default class Notification extends ImmutablePureComponent { @@ -20,8 +20,8 @@ export default class Notification extends ImmutablePureComponent { renderFollow (notification) { return ( - <FollowNotification - notificationId={notification.get('id')} + <NotificationFollow + id={notification.get('id')} account={notification.get('account')} onDeleteNotification={this.props.onDeleteNotification} /> diff --git a/app/javascript/glitch/reducers/local_settings.js b/app/javascript/glitch/reducers/local_settings.js index 776dcead7..35a8e065b 100644 --- a/app/javascript/glitch/reducers/local_settings.js +++ b/app/javascript/glitch/reducers/local_settings.js @@ -18,12 +18,12 @@ associated actions are: */ - /* * * * */ +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* -Imports -------- +Imports: +-------- */ @@ -36,12 +36,12 @@ import { STORE_HYDRATE } from '../../mastodon/actions/store'; // Our imports // import { LOCAL_SETTING_CHANGE } from '../actions/local_settings'; - /* * * * */ +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* -initialState ------------- +initialState: +------------- You can see the default values for all of our local settings here. These are only used if no previously-saved values exist. @@ -71,12 +71,12 @@ const initialState = ImmutableMap({ }), }); - /* * * * */ +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* -Helper functions ----------------- +Helper functions: +----------------- ### `hydrate(state, localSettings)` @@ -89,12 +89,12 @@ from `localStorage`. const hydrate = (state, localSettings) => state.mergeDeep(localSettings); - /* * * * */ +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /* -`localSettings(state = initialState, action)` ---------------------------------------------- +`localSettings(state = initialState, action)`: +---------------------------------------------- This function holds our actual reducer. diff --git a/app/javascript/glitch/util/bio_metadata.js b/app/javascript/glitch/util/bio_metadata.js index c5e87f356..0c8195e9d 100644 --- a/app/javascript/glitch/util/bio_metadata.js +++ b/app/javascript/glitch/util/bio_metadata.js @@ -1,7 +1,7 @@ /* `util/bio_metadata` -======================== +=================== > For more information on the contents of this file, please contact: > @@ -26,7 +26,7 @@ functions are: */ - /* * * * */ +// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /*********************************************************************\ |