From 81ef21a0c802f1d905f37a2a818544a8b400793c Mon Sep 17 00:00:00 2001 From: Renaud Chaput Date: Sat, 25 Feb 2023 14:34:32 +0100 Subject: [Glitch] Rename JSX files with proper `.jsx` extension Port 44a7d87cb1f5df953b6c14c16c59e2e4ead1bcb9 to glitch-soc Signed-off-by: Claire --- .../getting_started/components/announcements.js | 449 --------------------- .../getting_started/components/announcements.jsx | 449 +++++++++++++++++++++ .../features/getting_started/components/trends.js | 51 --- .../features/getting_started/components/trends.jsx | 51 +++ .../glitch/features/getting_started/index.js | 204 ---------- .../glitch/features/getting_started/index.jsx | 204 ++++++++++ 6 files changed, 704 insertions(+), 704 deletions(-) delete mode 100644 app/javascript/flavours/glitch/features/getting_started/components/announcements.js create mode 100644 app/javascript/flavours/glitch/features/getting_started/components/announcements.jsx delete mode 100644 app/javascript/flavours/glitch/features/getting_started/components/trends.js create mode 100644 app/javascript/flavours/glitch/features/getting_started/components/trends.jsx delete mode 100644 app/javascript/flavours/glitch/features/getting_started/index.js create mode 100644 app/javascript/flavours/glitch/features/getting_started/index.jsx (limited to 'app/javascript/flavours/glitch/features/getting_started') diff --git a/app/javascript/flavours/glitch/features/getting_started/components/announcements.js b/app/javascript/flavours/glitch/features/getting_started/components/announcements.js deleted file mode 100644 index fb9024447..000000000 --- a/app/javascript/flavours/glitch/features/getting_started/components/announcements.js +++ /dev/null @@ -1,449 +0,0 @@ -import React from 'react'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import ReactSwipeableViews from 'react-swipeable-views'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import IconButton from 'flavours/glitch/components/icon_button'; -import Icon from 'flavours/glitch/components/icon'; -import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl'; -import { autoPlayGif, reduceMotion, disableSwiping, mascot } from 'flavours/glitch/initial_state'; -import elephantUIPlane from 'mastodon/../images/elephant_ui_plane.svg'; -import unicodeMapping from 'flavours/glitch/features/emoji/emoji_unicode_mapping_light'; -import classNames from 'classnames'; -import EmojiPickerDropdown from 'flavours/glitch/features/compose/containers/emoji_picker_dropdown_container'; -import AnimatedNumber from 'flavours/glitch/components/animated_number'; -import TransitionMotion from 'react-motion/lib/TransitionMotion'; -import spring from 'react-motion/lib/spring'; -import { assetHost } from 'flavours/glitch/utils/config'; - -const messages = defineMessages({ - close: { id: 'lightbox.close', defaultMessage: 'Close' }, - previous: { id: 'lightbox.previous', defaultMessage: 'Previous' }, - next: { id: 'lightbox.next', defaultMessage: 'Next' }, -}); - -class Content extends ImmutablePureComponent { - - static contextTypes = { - router: PropTypes.object, - }; - - static propTypes = { - announcement: ImmutablePropTypes.map.isRequired, - }; - - setRef = c => { - this.node = c; - }; - - componentDidMount () { - this._updateLinks(); - } - - componentDidUpdate () { - this._updateLinks(); - } - - _updateLinks () { - const node = this.node; - - if (!node) { - return; - } - - const links = node.querySelectorAll('a'); - - for (var i = 0; i < links.length; ++i) { - let link = links[i]; - - if (link.classList.contains('status-link')) { - continue; - } - - link.classList.add('status-link'); - - let mention = this.props.announcement.get('mentions').find(item => link.href === item.get('url')); - - if (mention) { - link.addEventListener('click', this.onMentionClick.bind(this, mention), false); - link.setAttribute('title', mention.get('acct')); - } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) { - link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false); - } else { - let status = this.props.announcement.get('statuses').find(item => link.href === item.get('url')); - if (status) { - link.addEventListener('click', this.onStatusClick.bind(this, status), false); - } - link.setAttribute('title', link.href); - link.classList.add('unhandled-link'); - } - - link.setAttribute('target', '_blank'); - link.setAttribute('rel', 'noopener noreferrer'); - } - } - - onMentionClick = (mention, e) => { - if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { - e.preventDefault(); - this.context.router.history.push(`/@${mention.get('acct')}`); - } - }; - - onHashtagClick = (hashtag, e) => { - hashtag = hashtag.replace(/^#/, ''); - - if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { - e.preventDefault(); - this.context.router.history.push(`/tags/${hashtag}`); - } - }; - - onStatusClick = (status, e) => { - if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { - e.preventDefault(); - this.context.router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`); - } - }; - - handleMouseEnter = ({ currentTarget }) => { - if (autoPlayGif) { - return; - } - - const emojis = currentTarget.querySelectorAll('.custom-emoji'); - - for (var i = 0; i < emojis.length; i++) { - let emoji = emojis[i]; - emoji.src = emoji.getAttribute('data-original'); - } - }; - - handleMouseLeave = ({ currentTarget }) => { - if (autoPlayGif) { - return; - } - - const emojis = currentTarget.querySelectorAll('.custom-emoji'); - - for (var i = 0; i < emojis.length; i++) { - let emoji = emojis[i]; - emoji.src = emoji.getAttribute('data-static'); - } - }; - - render () { - const { announcement } = this.props; - - return ( -
- ); - } - -} - -class Emoji extends React.PureComponent { - - static propTypes = { - emoji: PropTypes.string.isRequired, - emojiMap: ImmutablePropTypes.map.isRequired, - hovered: PropTypes.bool.isRequired, - }; - - render () { - const { emoji, emojiMap, hovered } = this.props; - - if (unicodeMapping[emoji]) { - const { filename, shortCode } = unicodeMapping[this.props.emoji]; - const title = shortCode ? `:${shortCode}:` : ''; - - return ( - {emoji} - ); - } else if (emojiMap.get(emoji)) { - const filename = (autoPlayGif || hovered) ? emojiMap.getIn([emoji, 'url']) : emojiMap.getIn([emoji, 'static_url']); - const shortCode = `:${emoji}:`; - - return ( - {shortCode} - ); - } else { - return null; - } - } - -} - -class Reaction extends ImmutablePureComponent { - - static propTypes = { - announcementId: PropTypes.string.isRequired, - reaction: ImmutablePropTypes.map.isRequired, - addReaction: PropTypes.func.isRequired, - removeReaction: PropTypes.func.isRequired, - emojiMap: ImmutablePropTypes.map.isRequired, - style: PropTypes.object, - }; - - state = { - hovered: false, - }; - - handleClick = () => { - const { reaction, announcementId, addReaction, removeReaction } = this.props; - - if (reaction.get('me')) { - removeReaction(announcementId, reaction.get('name')); - } else { - addReaction(announcementId, reaction.get('name')); - } - }; - - handleMouseEnter = () => this.setState({ hovered: true }); - - handleMouseLeave = () => this.setState({ hovered: false }); - - render () { - const { reaction } = this.props; - - let shortCode = reaction.get('name'); - - if (unicodeMapping[shortCode]) { - shortCode = unicodeMapping[shortCode].shortCode; - } - - return ( - - ); - } - -} - -class ReactionsBar extends ImmutablePureComponent { - - static propTypes = { - announcementId: PropTypes.string.isRequired, - reactions: ImmutablePropTypes.list.isRequired, - addReaction: PropTypes.func.isRequired, - removeReaction: PropTypes.func.isRequired, - emojiMap: ImmutablePropTypes.map.isRequired, - }; - - handleEmojiPick = data => { - const { addReaction, announcementId } = this.props; - addReaction(announcementId, data.native.replace(/:/g, '')); - }; - - willEnter () { - return { scale: reduceMotion ? 1 : 0 }; - } - - willLeave () { - return { scale: reduceMotion ? 0 : spring(0, { stiffness: 170, damping: 26 }) }; - } - - render () { - const { reactions } = this.props; - const visibleReactions = reactions.filter(x => x.get('count') > 0); - - const styles = visibleReactions.map(reaction => ({ - key: reaction.get('name'), - data: reaction, - style: { scale: reduceMotion ? 1 : spring(1, { stiffness: 150, damping: 13 }) }, - })).toArray(); - - return ( - - {items => ( -
- {items.map(({ key, data, style }) => ( - - ))} - - {visibleReactions.size < 8 && } />} -
- )} -
- ); - } - -} - -class Announcement extends ImmutablePureComponent { - - static propTypes = { - announcement: ImmutablePropTypes.map.isRequired, - emojiMap: ImmutablePropTypes.map.isRequired, - addReaction: PropTypes.func.isRequired, - removeReaction: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - selected: PropTypes.bool, - }; - - state = { - unread: !this.props.announcement.get('read'), - }; - - componentDidUpdate () { - const { selected, announcement } = this.props; - if (!selected && this.state.unread !== !announcement.get('read')) { - this.setState({ unread: !announcement.get('read') }); - } - } - - render () { - const { announcement } = this.props; - const { unread } = this.state; - const startsAt = announcement.get('starts_at') && new Date(announcement.get('starts_at')); - const endsAt = announcement.get('ends_at') && new Date(announcement.get('ends_at')); - const now = new Date(); - const hasTimeRange = startsAt && endsAt; - const skipYear = hasTimeRange && startsAt.getFullYear() === endsAt.getFullYear() && endsAt.getFullYear() === now.getFullYear(); - const skipEndDate = hasTimeRange && startsAt.getDate() === endsAt.getDate() && startsAt.getMonth() === endsAt.getMonth() && startsAt.getFullYear() === endsAt.getFullYear(); - const skipTime = announcement.get('all_day'); - - return ( -
- - - {hasTimeRange && · - } - - - - - - - {unread && } -
- ); - } - -} - -export default @injectIntl -class Announcements extends ImmutablePureComponent { - - static propTypes = { - announcements: ImmutablePropTypes.list, - emojiMap: ImmutablePropTypes.map.isRequired, - dismissAnnouncement: PropTypes.func.isRequired, - addReaction: PropTypes.func.isRequired, - removeReaction: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - }; - - state = { - index: 0, - }; - - static getDerivedStateFromProps(props, state) { - if (props.announcements.size > 0 && state.index >= props.announcements.size) { - return { index: props.announcements.size - 1 }; - } else { - return null; - } - } - - componentDidMount () { - this._markAnnouncementAsRead(); - } - - componentDidUpdate () { - this._markAnnouncementAsRead(); - } - - _markAnnouncementAsRead () { - const { dismissAnnouncement, announcements } = this.props; - const { index } = this.state; - const announcement = announcements.get(index); - if (!announcement.get('read')) dismissAnnouncement(announcement.get('id')); - } - - handleChangeIndex = index => { - this.setState({ index: index % this.props.announcements.size }); - }; - - handleNextClick = () => { - this.setState({ index: (this.state.index + 1) % this.props.announcements.size }); - }; - - handlePrevClick = () => { - this.setState({ index: (this.props.announcements.size + this.state.index - 1) % this.props.announcements.size }); - }; - - render () { - const { announcements, intl } = this.props; - const { index } = this.state; - - if (announcements.isEmpty()) { - return null; - } - - return ( -
- - -
- - {announcements.map((announcement, idx) => ( - - ))} - - - {announcements.size > 1 && ( -
- - {index + 1} / {announcements.size} - -
- )} -
-
- ); - } - -} diff --git a/app/javascript/flavours/glitch/features/getting_started/components/announcements.jsx b/app/javascript/flavours/glitch/features/getting_started/components/announcements.jsx new file mode 100644 index 000000000..fb9024447 --- /dev/null +++ b/app/javascript/flavours/glitch/features/getting_started/components/announcements.jsx @@ -0,0 +1,449 @@ +import React from 'react'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import ReactSwipeableViews from 'react-swipeable-views'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import IconButton from 'flavours/glitch/components/icon_button'; +import Icon from 'flavours/glitch/components/icon'; +import { defineMessages, injectIntl, FormattedMessage, FormattedDate } from 'react-intl'; +import { autoPlayGif, reduceMotion, disableSwiping, mascot } from 'flavours/glitch/initial_state'; +import elephantUIPlane from 'mastodon/../images/elephant_ui_plane.svg'; +import unicodeMapping from 'flavours/glitch/features/emoji/emoji_unicode_mapping_light'; +import classNames from 'classnames'; +import EmojiPickerDropdown from 'flavours/glitch/features/compose/containers/emoji_picker_dropdown_container'; +import AnimatedNumber from 'flavours/glitch/components/animated_number'; +import TransitionMotion from 'react-motion/lib/TransitionMotion'; +import spring from 'react-motion/lib/spring'; +import { assetHost } from 'flavours/glitch/utils/config'; + +const messages = defineMessages({ + close: { id: 'lightbox.close', defaultMessage: 'Close' }, + previous: { id: 'lightbox.previous', defaultMessage: 'Previous' }, + next: { id: 'lightbox.next', defaultMessage: 'Next' }, +}); + +class Content extends ImmutablePureComponent { + + static contextTypes = { + router: PropTypes.object, + }; + + static propTypes = { + announcement: ImmutablePropTypes.map.isRequired, + }; + + setRef = c => { + this.node = c; + }; + + componentDidMount () { + this._updateLinks(); + } + + componentDidUpdate () { + this._updateLinks(); + } + + _updateLinks () { + const node = this.node; + + if (!node) { + return; + } + + const links = node.querySelectorAll('a'); + + for (var i = 0; i < links.length; ++i) { + let link = links[i]; + + if (link.classList.contains('status-link')) { + continue; + } + + link.classList.add('status-link'); + + let mention = this.props.announcement.get('mentions').find(item => link.href === item.get('url')); + + if (mention) { + link.addEventListener('click', this.onMentionClick.bind(this, mention), false); + link.setAttribute('title', mention.get('acct')); + } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) { + link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false); + } else { + let status = this.props.announcement.get('statuses').find(item => link.href === item.get('url')); + if (status) { + link.addEventListener('click', this.onStatusClick.bind(this, status), false); + } + link.setAttribute('title', link.href); + link.classList.add('unhandled-link'); + } + + link.setAttribute('target', '_blank'); + link.setAttribute('rel', 'noopener noreferrer'); + } + } + + onMentionClick = (mention, e) => { + if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { + e.preventDefault(); + this.context.router.history.push(`/@${mention.get('acct')}`); + } + }; + + onHashtagClick = (hashtag, e) => { + hashtag = hashtag.replace(/^#/, ''); + + if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { + e.preventDefault(); + this.context.router.history.push(`/tags/${hashtag}`); + } + }; + + onStatusClick = (status, e) => { + if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { + e.preventDefault(); + this.context.router.history.push(`/@${status.getIn(['account', 'acct'])}/${status.get('id')}`); + } + }; + + handleMouseEnter = ({ currentTarget }) => { + if (autoPlayGif) { + return; + } + + const emojis = currentTarget.querySelectorAll('.custom-emoji'); + + for (var i = 0; i < emojis.length; i++) { + let emoji = emojis[i]; + emoji.src = emoji.getAttribute('data-original'); + } + }; + + handleMouseLeave = ({ currentTarget }) => { + if (autoPlayGif) { + return; + } + + const emojis = currentTarget.querySelectorAll('.custom-emoji'); + + for (var i = 0; i < emojis.length; i++) { + let emoji = emojis[i]; + emoji.src = emoji.getAttribute('data-static'); + } + }; + + render () { + const { announcement } = this.props; + + return ( +
+ ); + } + +} + +class Emoji extends React.PureComponent { + + static propTypes = { + emoji: PropTypes.string.isRequired, + emojiMap: ImmutablePropTypes.map.isRequired, + hovered: PropTypes.bool.isRequired, + }; + + render () { + const { emoji, emojiMap, hovered } = this.props; + + if (unicodeMapping[emoji]) { + const { filename, shortCode } = unicodeMapping[this.props.emoji]; + const title = shortCode ? `:${shortCode}:` : ''; + + return ( + {emoji} + ); + } else if (emojiMap.get(emoji)) { + const filename = (autoPlayGif || hovered) ? emojiMap.getIn([emoji, 'url']) : emojiMap.getIn([emoji, 'static_url']); + const shortCode = `:${emoji}:`; + + return ( + {shortCode} + ); + } else { + return null; + } + } + +} + +class Reaction extends ImmutablePureComponent { + + static propTypes = { + announcementId: PropTypes.string.isRequired, + reaction: ImmutablePropTypes.map.isRequired, + addReaction: PropTypes.func.isRequired, + removeReaction: PropTypes.func.isRequired, + emojiMap: ImmutablePropTypes.map.isRequired, + style: PropTypes.object, + }; + + state = { + hovered: false, + }; + + handleClick = () => { + const { reaction, announcementId, addReaction, removeReaction } = this.props; + + if (reaction.get('me')) { + removeReaction(announcementId, reaction.get('name')); + } else { + addReaction(announcementId, reaction.get('name')); + } + }; + + handleMouseEnter = () => this.setState({ hovered: true }); + + handleMouseLeave = () => this.setState({ hovered: false }); + + render () { + const { reaction } = this.props; + + let shortCode = reaction.get('name'); + + if (unicodeMapping[shortCode]) { + shortCode = unicodeMapping[shortCode].shortCode; + } + + return ( + + ); + } + +} + +class ReactionsBar extends ImmutablePureComponent { + + static propTypes = { + announcementId: PropTypes.string.isRequired, + reactions: ImmutablePropTypes.list.isRequired, + addReaction: PropTypes.func.isRequired, + removeReaction: PropTypes.func.isRequired, + emojiMap: ImmutablePropTypes.map.isRequired, + }; + + handleEmojiPick = data => { + const { addReaction, announcementId } = this.props; + addReaction(announcementId, data.native.replace(/:/g, '')); + }; + + willEnter () { + return { scale: reduceMotion ? 1 : 0 }; + } + + willLeave () { + return { scale: reduceMotion ? 0 : spring(0, { stiffness: 170, damping: 26 }) }; + } + + render () { + const { reactions } = this.props; + const visibleReactions = reactions.filter(x => x.get('count') > 0); + + const styles = visibleReactions.map(reaction => ({ + key: reaction.get('name'), + data: reaction, + style: { scale: reduceMotion ? 1 : spring(1, { stiffness: 150, damping: 13 }) }, + })).toArray(); + + return ( + + {items => ( +
+ {items.map(({ key, data, style }) => ( + + ))} + + {visibleReactions.size < 8 && } />} +
+ )} +
+ ); + } + +} + +class Announcement extends ImmutablePureComponent { + + static propTypes = { + announcement: ImmutablePropTypes.map.isRequired, + emojiMap: ImmutablePropTypes.map.isRequired, + addReaction: PropTypes.func.isRequired, + removeReaction: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + selected: PropTypes.bool, + }; + + state = { + unread: !this.props.announcement.get('read'), + }; + + componentDidUpdate () { + const { selected, announcement } = this.props; + if (!selected && this.state.unread !== !announcement.get('read')) { + this.setState({ unread: !announcement.get('read') }); + } + } + + render () { + const { announcement } = this.props; + const { unread } = this.state; + const startsAt = announcement.get('starts_at') && new Date(announcement.get('starts_at')); + const endsAt = announcement.get('ends_at') && new Date(announcement.get('ends_at')); + const now = new Date(); + const hasTimeRange = startsAt && endsAt; + const skipYear = hasTimeRange && startsAt.getFullYear() === endsAt.getFullYear() && endsAt.getFullYear() === now.getFullYear(); + const skipEndDate = hasTimeRange && startsAt.getDate() === endsAt.getDate() && startsAt.getMonth() === endsAt.getMonth() && startsAt.getFullYear() === endsAt.getFullYear(); + const skipTime = announcement.get('all_day'); + + return ( +
+ + + {hasTimeRange && · - } + + + + + + + {unread && } +
+ ); + } + +} + +export default @injectIntl +class Announcements extends ImmutablePureComponent { + + static propTypes = { + announcements: ImmutablePropTypes.list, + emojiMap: ImmutablePropTypes.map.isRequired, + dismissAnnouncement: PropTypes.func.isRequired, + addReaction: PropTypes.func.isRequired, + removeReaction: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + state = { + index: 0, + }; + + static getDerivedStateFromProps(props, state) { + if (props.announcements.size > 0 && state.index >= props.announcements.size) { + return { index: props.announcements.size - 1 }; + } else { + return null; + } + } + + componentDidMount () { + this._markAnnouncementAsRead(); + } + + componentDidUpdate () { + this._markAnnouncementAsRead(); + } + + _markAnnouncementAsRead () { + const { dismissAnnouncement, announcements } = this.props; + const { index } = this.state; + const announcement = announcements.get(index); + if (!announcement.get('read')) dismissAnnouncement(announcement.get('id')); + } + + handleChangeIndex = index => { + this.setState({ index: index % this.props.announcements.size }); + }; + + handleNextClick = () => { + this.setState({ index: (this.state.index + 1) % this.props.announcements.size }); + }; + + handlePrevClick = () => { + this.setState({ index: (this.props.announcements.size + this.state.index - 1) % this.props.announcements.size }); + }; + + render () { + const { announcements, intl } = this.props; + const { index } = this.state; + + if (announcements.isEmpty()) { + return null; + } + + return ( +
+ + +
+ + {announcements.map((announcement, idx) => ( + + ))} + + + {announcements.size > 1 && ( +
+ + {index + 1} / {announcements.size} + +
+ )} +
+
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/features/getting_started/components/trends.js b/app/javascript/flavours/glitch/features/getting_started/components/trends.js deleted file mode 100644 index d7e222d71..000000000 --- a/app/javascript/flavours/glitch/features/getting_started/components/trends.js +++ /dev/null @@ -1,51 +0,0 @@ -import React from 'react'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag'; -import { FormattedMessage } from 'react-intl'; -import { Link } from 'react-router-dom'; - -export default class Trends extends ImmutablePureComponent { - - static defaultProps = { - loading: false, - }; - - static propTypes = { - trends: ImmutablePropTypes.list, - fetchTrends: PropTypes.func.isRequired, - }; - - componentDidMount () { - this.props.fetchTrends(); - this.refreshInterval = setInterval(() => this.props.fetchTrends(), 900 * 1000); - } - - componentWillUnmount () { - if (this.refreshInterval) { - clearInterval(this.refreshInterval); - } - } - - render () { - const { trends } = this.props; - - if (!trends || trends.isEmpty()) { - return null; - } - - return ( -
-

- - - -

- - {trends.take(3).map(hashtag => )} -
- ); - } - -} diff --git a/app/javascript/flavours/glitch/features/getting_started/components/trends.jsx b/app/javascript/flavours/glitch/features/getting_started/components/trends.jsx new file mode 100644 index 000000000..d7e222d71 --- /dev/null +++ b/app/javascript/flavours/glitch/features/getting_started/components/trends.jsx @@ -0,0 +1,51 @@ +import React from 'react'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import { ImmutableHashtag as Hashtag } from 'flavours/glitch/components/hashtag'; +import { FormattedMessage } from 'react-intl'; +import { Link } from 'react-router-dom'; + +export default class Trends extends ImmutablePureComponent { + + static defaultProps = { + loading: false, + }; + + static propTypes = { + trends: ImmutablePropTypes.list, + fetchTrends: PropTypes.func.isRequired, + }; + + componentDidMount () { + this.props.fetchTrends(); + this.refreshInterval = setInterval(() => this.props.fetchTrends(), 900 * 1000); + } + + componentWillUnmount () { + if (this.refreshInterval) { + clearInterval(this.refreshInterval); + } + } + + render () { + const { trends } = this.props; + + if (!trends || trends.isEmpty()) { + return null; + } + + return ( +
+

+ + + +

+ + {trends.take(3).map(hashtag => )} +
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/features/getting_started/index.js b/app/javascript/flavours/glitch/features/getting_started/index.js deleted file mode 100644 index 91b33c1dd..000000000 --- a/app/javascript/flavours/glitch/features/getting_started/index.js +++ /dev/null @@ -1,204 +0,0 @@ -import React from 'react'; -import Column from 'flavours/glitch/features/ui/components/column'; -import ColumnLink from 'flavours/glitch/features/ui/components/column_link'; -import ColumnSubheading from 'flavours/glitch/features/ui/components/column_subheading'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import { connect } from 'react-redux'; -import { openModal } from 'flavours/glitch/actions/modal'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { me, showTrends } from 'flavours/glitch/initial_state'; -import { fetchFollowRequests } from 'flavours/glitch/actions/accounts'; -import { List as ImmutableList } from 'immutable'; -import { createSelector } from 'reselect'; -import { fetchLists } from 'flavours/glitch/actions/lists'; -import { preferencesLink } from 'flavours/glitch/utils/backend_links'; -import NavigationBar from '../compose/components/navigation_bar'; -import LinkFooter from 'flavours/glitch/features/ui/components/link_footer'; -import TrendsContainer from './containers/trends_container'; -import { Helmet } from 'react-helmet'; - -const messages = defineMessages({ - heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, - home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' }, - notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' }, - public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' }, - navigation_subheading: { id: 'column_subheading.navigation', defaultMessage: 'Navigation' }, - settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' }, - community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' }, - explore: { id: 'navigation_bar.explore', defaultMessage: 'Explore' }, - direct: { id: 'navigation_bar.direct', defaultMessage: 'Direct messages' }, - bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' }, - preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, - settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' }, - follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, - lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' }, - keyboard_shortcuts: { id: 'navigation_bar.keyboard_shortcuts', defaultMessage: 'Keyboard shortcuts' }, - lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' }, - lists_subheading: { id: 'column_subheading.lists', defaultMessage: 'Lists' }, - misc: { id: 'navigation_bar.misc', defaultMessage: 'Misc' }, - menu: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, -}); - -const makeMapStateToProps = () => { - const getOrderedLists = createSelector([state => state.get('lists')], lists => { - if (!lists) { - return lists; - } - - return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title'))); - }); - - const mapStateToProps = state => ({ - lists: getOrderedLists(state), - myAccount: state.getIn(['accounts', me]), - columns: state.getIn(['settings', 'columns']), - unreadFollowRequests: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size, - unreadNotifications: state.getIn(['notifications', 'unread']), - }); - - return mapStateToProps; -}; - -const mapDispatchToProps = dispatch => ({ - fetchFollowRequests: () => dispatch(fetchFollowRequests()), - fetchLists: () => dispatch(fetchLists()), - openSettings: () => dispatch(openModal('SETTINGS', {})), -}); - -const badgeDisplay = (number, limit) => { - if (number === 0) { - return undefined; - } else if (limit && number >= limit) { - return `${limit}+`; - } else { - return number; - } -}; - -const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2); - -export default @connect(makeMapStateToProps, mapDispatchToProps) - @injectIntl -class GettingStarted extends ImmutablePureComponent { - - static contextTypes = { - router: PropTypes.object.isRequired, - identity: PropTypes.object, - }; - - static propTypes = { - intl: PropTypes.object.isRequired, - myAccount: ImmutablePropTypes.map, - columns: ImmutablePropTypes.list, - multiColumn: PropTypes.bool, - fetchFollowRequests: PropTypes.func.isRequired, - unreadFollowRequests: PropTypes.number, - unreadNotifications: PropTypes.number, - lists: ImmutablePropTypes.list, - fetchLists: PropTypes.func.isRequired, - openSettings: PropTypes.func.isRequired, - }; - - componentWillMount () { - this.props.fetchLists(); - } - - componentDidMount () { - const { fetchFollowRequests } = this.props; - const { signedIn } = this.context.identity; - - if (!signedIn) { - return; - } - - fetchFollowRequests(); - } - - render () { - const { intl, myAccount, columns, multiColumn, unreadFollowRequests, unreadNotifications, lists, openSettings } = this.props; - const { signedIn } = this.context.identity; - - const navItems = []; - let listItems = []; - - if (multiColumn) { - if (signedIn && !columns.find(item => item.get('id') === 'HOME')) { - navItems.push(); - } - - if (!columns.find(item => item.get('id') === 'NOTIFICATIONS')) { - navItems.push(); - } - - if (!columns.find(item => item.get('id') === 'COMMUNITY')) { - navItems.push(); - } - - if (!columns.find(item => item.get('id') === 'PUBLIC')) { - navItems.push(); - } - } - - if (showTrends) { - navItems.push(); - } - - if (signedIn) { - if (!multiColumn || !columns.find(item => item.get('id') === 'DIRECT')) { - navItems.push(); - } - - if (!multiColumn || !columns.find(item => item.get('id') === 'BOOKMARKS')) { - navItems.push(); - } - - if (myAccount.get('locked') || unreadFollowRequests > 0) { - navItems.push(); - } - - navItems.push(); - - listItems = listItems.concat([ -
- - {lists.filter(list => !columns.find(item => item.get('id') === 'LIST' && item.getIn(['params', 'id']) === list.get('id'))).map(list => - , - )} -
, - ]); - } - - return ( - -
-
- {!multiColumn && signedIn && } - {multiColumn && } - {navItems} - {signedIn && ( - - - {listItems} - - { preferencesLink !== undefined && } - - - )} -
- - -
- - {multiColumn && showTrends && } - - - {intl.formatMessage(messages.menu)} - - -
- ); - } - -} diff --git a/app/javascript/flavours/glitch/features/getting_started/index.jsx b/app/javascript/flavours/glitch/features/getting_started/index.jsx new file mode 100644 index 000000000..91b33c1dd --- /dev/null +++ b/app/javascript/flavours/glitch/features/getting_started/index.jsx @@ -0,0 +1,204 @@ +import React from 'react'; +import Column from 'flavours/glitch/features/ui/components/column'; +import ColumnLink from 'flavours/glitch/features/ui/components/column_link'; +import ColumnSubheading from 'flavours/glitch/features/ui/components/column_subheading'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { connect } from 'react-redux'; +import { openModal } from 'flavours/glitch/actions/modal'; +import PropTypes from 'prop-types'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { me, showTrends } from 'flavours/glitch/initial_state'; +import { fetchFollowRequests } from 'flavours/glitch/actions/accounts'; +import { List as ImmutableList } from 'immutable'; +import { createSelector } from 'reselect'; +import { fetchLists } from 'flavours/glitch/actions/lists'; +import { preferencesLink } from 'flavours/glitch/utils/backend_links'; +import NavigationBar from '../compose/components/navigation_bar'; +import LinkFooter from 'flavours/glitch/features/ui/components/link_footer'; +import TrendsContainer from './containers/trends_container'; +import { Helmet } from 'react-helmet'; + +const messages = defineMessages({ + heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, + home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' }, + notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' }, + public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' }, + navigation_subheading: { id: 'column_subheading.navigation', defaultMessage: 'Navigation' }, + settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' }, + community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' }, + explore: { id: 'navigation_bar.explore', defaultMessage: 'Explore' }, + direct: { id: 'navigation_bar.direct', defaultMessage: 'Direct messages' }, + bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' }, + preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, + settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' }, + follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' }, + lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' }, + keyboard_shortcuts: { id: 'navigation_bar.keyboard_shortcuts', defaultMessage: 'Keyboard shortcuts' }, + lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' }, + lists_subheading: { id: 'column_subheading.lists', defaultMessage: 'Lists' }, + misc: { id: 'navigation_bar.misc', defaultMessage: 'Misc' }, + menu: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, +}); + +const makeMapStateToProps = () => { + const getOrderedLists = createSelector([state => state.get('lists')], lists => { + if (!lists) { + return lists; + } + + return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title'))); + }); + + const mapStateToProps = state => ({ + lists: getOrderedLists(state), + myAccount: state.getIn(['accounts', me]), + columns: state.getIn(['settings', 'columns']), + unreadFollowRequests: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size, + unreadNotifications: state.getIn(['notifications', 'unread']), + }); + + return mapStateToProps; +}; + +const mapDispatchToProps = dispatch => ({ + fetchFollowRequests: () => dispatch(fetchFollowRequests()), + fetchLists: () => dispatch(fetchLists()), + openSettings: () => dispatch(openModal('SETTINGS', {})), +}); + +const badgeDisplay = (number, limit) => { + if (number === 0) { + return undefined; + } else if (limit && number >= limit) { + return `${limit}+`; + } else { + return number; + } +}; + +const NAVIGATION_PANEL_BREAKPOINT = 600 + (285 * 2) + (10 * 2); + +export default @connect(makeMapStateToProps, mapDispatchToProps) + @injectIntl +class GettingStarted extends ImmutablePureComponent { + + static contextTypes = { + router: PropTypes.object.isRequired, + identity: PropTypes.object, + }; + + static propTypes = { + intl: PropTypes.object.isRequired, + myAccount: ImmutablePropTypes.map, + columns: ImmutablePropTypes.list, + multiColumn: PropTypes.bool, + fetchFollowRequests: PropTypes.func.isRequired, + unreadFollowRequests: PropTypes.number, + unreadNotifications: PropTypes.number, + lists: ImmutablePropTypes.list, + fetchLists: PropTypes.func.isRequired, + openSettings: PropTypes.func.isRequired, + }; + + componentWillMount () { + this.props.fetchLists(); + } + + componentDidMount () { + const { fetchFollowRequests } = this.props; + const { signedIn } = this.context.identity; + + if (!signedIn) { + return; + } + + fetchFollowRequests(); + } + + render () { + const { intl, myAccount, columns, multiColumn, unreadFollowRequests, unreadNotifications, lists, openSettings } = this.props; + const { signedIn } = this.context.identity; + + const navItems = []; + let listItems = []; + + if (multiColumn) { + if (signedIn && !columns.find(item => item.get('id') === 'HOME')) { + navItems.push(); + } + + if (!columns.find(item => item.get('id') === 'NOTIFICATIONS')) { + navItems.push(); + } + + if (!columns.find(item => item.get('id') === 'COMMUNITY')) { + navItems.push(); + } + + if (!columns.find(item => item.get('id') === 'PUBLIC')) { + navItems.push(); + } + } + + if (showTrends) { + navItems.push(); + } + + if (signedIn) { + if (!multiColumn || !columns.find(item => item.get('id') === 'DIRECT')) { + navItems.push(); + } + + if (!multiColumn || !columns.find(item => item.get('id') === 'BOOKMARKS')) { + navItems.push(); + } + + if (myAccount.get('locked') || unreadFollowRequests > 0) { + navItems.push(); + } + + navItems.push(); + + listItems = listItems.concat([ +
+ + {lists.filter(list => !columns.find(item => item.get('id') === 'LIST' && item.getIn(['params', 'id']) === list.get('id'))).map(list => + , + )} +
, + ]); + } + + return ( + +
+
+ {!multiColumn && signedIn && } + {multiColumn && } + {navItems} + {signedIn && ( + + + {listItems} + + { preferencesLink !== undefined && } + + + )} +
+ + +
+ + {multiColumn && showTrends && } + + + {intl.formatMessage(messages.menu)} + + +
+ ); + } + +} -- cgit