From bc4fa6b198557a7f3989eb0865e2c77ac7451d29 Mon Sep 17 00:00:00 2001 From: kibigo! Date: Sun, 3 Dec 2017 23:26:40 -0800 Subject: Rename themes -> flavours ? ? --- .../flavours/glitch/components/account.js | 116 ++++++ .../flavours/glitch/components/attachment_list.js | 33 ++ .../glitch/components/autosuggest_emoji.js | 42 ++ .../glitch/components/autosuggest_textarea.js | 222 +++++++++++ .../flavours/glitch/components/avatar.js | 72 ++++ .../flavours/glitch/components/avatar_overlay.js | 30 ++ .../flavours/glitch/components/button.js | 64 +++ .../flavours/glitch/components/collapsable.js | 22 + .../flavours/glitch/components/column.js | 54 +++ .../glitch/components/column_back_button.js | 29 ++ .../glitch/components/column_back_button_slim.js | 31 ++ .../flavours/glitch/components/column_header.js | 213 ++++++++++ .../flavours/glitch/components/display_name.js | 20 + .../flavours/glitch/components/dropdown_menu.js | 211 ++++++++++ .../glitch/components/extended_video_player.js | 54 +++ .../flavours/glitch/components/icon_button.js | 137 +++++++ .../components/intersection_observer_article.js | 130 ++++++ .../flavours/glitch/components/load_more.js | 26 ++ .../glitch/components/loading_indicator.js | 11 + .../flavours/glitch/components/media_gallery.js | 255 ++++++++++++ .../glitch/components/missing_indicator.js | 12 + .../components/notification_purge_buttons.js | 58 +++ .../flavours/glitch/components/permalink.js | 34 ++ .../glitch/components/relative_timestamp.js | 147 +++++++ .../flavours/glitch/components/scrollable_list.js | 198 +++++++++ .../flavours/glitch/components/setting_text.js | 34 ++ .../flavours/glitch/components/status.js | 442 +++++++++++++++++++++ .../glitch/components/status_action_bar.js | 185 +++++++++ .../flavours/glitch/components/status_content.js | 245 ++++++++++++ .../flavours/glitch/components/status_header.js | 120 ++++++ .../flavours/glitch/components/status_list.js | 72 ++++ .../flavours/glitch/components/status_prepend.js | 83 ++++ .../glitch/components/status_visibility_icon.js | 48 +++ 33 files changed, 3450 insertions(+) create mode 100644 app/javascript/flavours/glitch/components/account.js create mode 100644 app/javascript/flavours/glitch/components/attachment_list.js create mode 100644 app/javascript/flavours/glitch/components/autosuggest_emoji.js create mode 100644 app/javascript/flavours/glitch/components/autosuggest_textarea.js create mode 100644 app/javascript/flavours/glitch/components/avatar.js create mode 100644 app/javascript/flavours/glitch/components/avatar_overlay.js create mode 100644 app/javascript/flavours/glitch/components/button.js create mode 100644 app/javascript/flavours/glitch/components/collapsable.js create mode 100644 app/javascript/flavours/glitch/components/column.js create mode 100644 app/javascript/flavours/glitch/components/column_back_button.js create mode 100644 app/javascript/flavours/glitch/components/column_back_button_slim.js create mode 100644 app/javascript/flavours/glitch/components/column_header.js create mode 100644 app/javascript/flavours/glitch/components/display_name.js create mode 100644 app/javascript/flavours/glitch/components/dropdown_menu.js create mode 100644 app/javascript/flavours/glitch/components/extended_video_player.js create mode 100644 app/javascript/flavours/glitch/components/icon_button.js create mode 100644 app/javascript/flavours/glitch/components/intersection_observer_article.js create mode 100644 app/javascript/flavours/glitch/components/load_more.js create mode 100644 app/javascript/flavours/glitch/components/loading_indicator.js create mode 100644 app/javascript/flavours/glitch/components/media_gallery.js create mode 100644 app/javascript/flavours/glitch/components/missing_indicator.js create mode 100644 app/javascript/flavours/glitch/components/notification_purge_buttons.js create mode 100644 app/javascript/flavours/glitch/components/permalink.js create mode 100644 app/javascript/flavours/glitch/components/relative_timestamp.js create mode 100644 app/javascript/flavours/glitch/components/scrollable_list.js create mode 100644 app/javascript/flavours/glitch/components/setting_text.js create mode 100644 app/javascript/flavours/glitch/components/status.js create mode 100644 app/javascript/flavours/glitch/components/status_action_bar.js create mode 100644 app/javascript/flavours/glitch/components/status_content.js create mode 100644 app/javascript/flavours/glitch/components/status_header.js create mode 100644 app/javascript/flavours/glitch/components/status_list.js create mode 100644 app/javascript/flavours/glitch/components/status_prepend.js create mode 100644 app/javascript/flavours/glitch/components/status_visibility_icon.js (limited to 'app/javascript/flavours/glitch/components') diff --git a/app/javascript/flavours/glitch/components/account.js b/app/javascript/flavours/glitch/components/account.js new file mode 100644 index 000000000..c8dacb0ab --- /dev/null +++ b/app/javascript/flavours/glitch/components/account.js @@ -0,0 +1,116 @@ +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import Avatar from './avatar'; +import DisplayName from './display_name'; +import Permalink from './permalink'; +import IconButton from './icon_button'; +import { defineMessages, injectIntl } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { me } from 'flavours/glitch/util/initial_state'; + +const messages = defineMessages({ + follow: { id: 'account.follow', defaultMessage: 'Follow' }, + unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, + requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }, + unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, + unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, + mute_notifications: { id: 'account.mute_notifications', defaultMessage: 'You are not currently muting notifications from @{name}. Click to mute notifications' }, + unmute_notifications: { id: 'account.unmute_notifications', defaultMessage: 'You are currently muting notifications from @{name}. Click to unmute notifications' }, +}); + +@injectIntl +export default class Account extends ImmutablePureComponent { + + static propTypes = { + account: ImmutablePropTypes.map.isRequired, + onFollow: PropTypes.func.isRequired, + onBlock: PropTypes.func.isRequired, + onMute: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + hidden: PropTypes.bool, + }; + + handleFollow = () => { + this.props.onFollow(this.props.account); + } + + handleBlock = () => { + this.props.onBlock(this.props.account); + } + + handleMute = () => { + this.props.onMute(this.props.account); + } + + handleMuteNotifications = () => { + this.props.onMuteNotifications(this.props.account, true); + } + + handleUnmuteNotifications = () => { + this.props.onMuteNotifications(this.props.account, false); + } + + render () { + const { account, intl, hidden } = this.props; + + if (!account) { + return
; + } + + if (hidden) { + return ( +
+ {account.get('display_name')} + {account.get('username')} +
+ ); + } + + let buttons; + + if (account.get('id') !== me && account.get('relationship', null) !== null) { + const following = account.getIn(['relationship', 'following']); + const requested = account.getIn(['relationship', 'requested']); + const blocking = account.getIn(['relationship', 'blocking']); + const muting = account.getIn(['relationship', 'muting']); + + if (requested) { + buttons = ; + } else if (blocking) { + buttons = ; + } else if (muting) { + let hidingNotificationsButton; + if (muting.get('notifications')) { + hidingNotificationsButton = ; + } else { + hidingNotificationsButton = ; + } + buttons = ( +
+ + {hidingNotificationsButton} +
+ ); + } else { + buttons = ; + } + } + + return ( +
+
+ +
+ +
+ +
+ {buttons} +
+
+
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/components/attachment_list.js b/app/javascript/flavours/glitch/components/attachment_list.js new file mode 100644 index 000000000..b3d00b335 --- /dev/null +++ b/app/javascript/flavours/glitch/components/attachment_list.js @@ -0,0 +1,33 @@ +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import ImmutablePureComponent from 'react-immutable-pure-component'; + +const filename = url => url.split('/').pop().split('#')[0].split('?')[0]; + +export default class AttachmentList extends ImmutablePureComponent { + + static propTypes = { + media: ImmutablePropTypes.list.isRequired, + }; + + render () { + const { media } = this.props; + + return ( +
+
+ +
+ + +
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/components/autosuggest_emoji.js b/app/javascript/flavours/glitch/components/autosuggest_emoji.js new file mode 100644 index 000000000..79e113d9c --- /dev/null +++ b/app/javascript/flavours/glitch/components/autosuggest_emoji.js @@ -0,0 +1,42 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import unicodeMapping from 'flavours/glitch/util/emoji/emoji_unicode_mapping_light'; + +const assetHost = process.env.CDN_HOST || ''; + +export default class AutosuggestEmoji extends React.PureComponent { + + static propTypes = { + emoji: PropTypes.object.isRequired, + }; + + render () { + const { emoji } = this.props; + let url; + + if (emoji.custom) { + url = emoji.imageUrl; + } else { + const mapping = unicodeMapping[emoji.native] || unicodeMapping[emoji.native.replace(/\uFE0F$/, '')]; + + if (!mapping) { + return null; + } + + url = `${assetHost}/emoji/${mapping.filename}.svg`; + } + + return ( +
+ {emoji.native + + {emoji.colons} +
+ ); + } + +} diff --git a/app/javascript/flavours/glitch/components/autosuggest_textarea.js b/app/javascript/flavours/glitch/components/autosuggest_textarea.js new file mode 100644 index 000000000..551528e5a --- /dev/null +++ b/app/javascript/flavours/glitch/components/autosuggest_textarea.js @@ -0,0 +1,222 @@ +import React from 'react'; +import AutosuggestAccountContainer from 'flavours/glitch/features/compose/containers/autosuggest_account_container'; +import AutosuggestEmoji from './autosuggest_emoji'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import PropTypes from 'prop-types'; +import { isRtl } from 'flavours/glitch/util/rtl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import Textarea from 'react-textarea-autosize'; +import classNames from 'classnames'; + +const textAtCursorMatchesToken = (str, caretPosition) => { + let word; + + let left = str.slice(0, caretPosition).search(/[^\s\u200B]+$/); + let right = str.slice(caretPosition).search(/[\s\u200B]/); + + if (right < 0) { + word = str.slice(left); + } else { + word = str.slice(left, right + caretPosition); + } + + if (!word || word.trim().length < 3 || ['@', ':'].indexOf(word[0]) === -1) { + return [null, null]; + } + + word = word.trim().toLowerCase(); + + if (word.length > 0) { + return [left + 1, word]; + } else { + return [null, null]; + } +}; + +export default class AutosuggestTextarea extends ImmutablePureComponent { + + static propTypes = { + value: PropTypes.string, + suggestions: ImmutablePropTypes.list, + disabled: PropTypes.bool, + placeholder: PropTypes.string, + onSuggestionSelected: PropTypes.func.isRequired, + onSuggestionsClearRequested: PropTypes.func.isRequired, + onSuggestionsFetchRequested: PropTypes.func.isRequired, + onChange: PropTypes.func.isRequired, + onKeyUp: PropTypes.func, + onKeyDown: PropTypes.func, + onPaste: PropTypes.func.isRequired, + autoFocus: PropTypes.bool, + }; + + static defaultProps = { + autoFocus: true, + }; + + state = { + suggestionsHidden: false, + selectedSuggestion: 0, + lastToken: null, + tokenStart: 0, + }; + + onChange = (e) => { + const [ tokenStart, token ] = textAtCursorMatchesToken(e.target.value, e.target.selectionStart); + + if (token !== null && this.state.lastToken !== token) { + this.setState({ lastToken: token, selectedSuggestion: 0, tokenStart }); + this.props.onSuggestionsFetchRequested(token); + } else if (token === null) { + this.setState({ lastToken: null }); + this.props.onSuggestionsClearRequested(); + } + + this.props.onChange(e); + } + + onKeyDown = (e) => { + const { suggestions, disabled } = this.props; + const { selectedSuggestion, suggestionsHidden } = this.state; + + if (disabled) { + e.preventDefault(); + return; + } + + switch(e.key) { + case 'Escape': + if (!suggestionsHidden) { + e.preventDefault(); + this.setState({ suggestionsHidden: true }); + } + + break; + case 'ArrowDown': + if (suggestions.size > 0 && !suggestionsHidden) { + e.preventDefault(); + this.setState({ selectedSuggestion: Math.min(selectedSuggestion + 1, suggestions.size - 1) }); + } + + break; + case 'ArrowUp': + if (suggestions.size > 0 && !suggestionsHidden) { + e.preventDefault(); + this.setState({ selectedSuggestion: Math.max(selectedSuggestion - 1, 0) }); + } + + break; + case 'Enter': + case 'Tab': + // Select suggestion + if (this.state.lastToken !== null && suggestions.size > 0 && !suggestionsHidden) { + e.preventDefault(); + e.stopPropagation(); + this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestions.get(selectedSuggestion)); + } + + break; + } + + if (e.defaultPrevented || !this.props.onKeyDown) { + return; + } + + this.props.onKeyDown(e); + } + + onKeyUp = e => { + if (e.key === 'Escape' && this.state.suggestionsHidden) { + document.querySelector('.ui').parentElement.focus(); + } + + if (this.props.onKeyUp) { + this.props.onKeyUp(e); + } + } + + onBlur = () => { + this.setState({ suggestionsHidden: true }); + } + + onSuggestionClick = (e) => { + const suggestion = this.props.suggestions.get(e.currentTarget.getAttribute('data-index')); + e.preventDefault(); + this.props.onSuggestionSelected(this.state.tokenStart, this.state.lastToken, suggestion); + this.textarea.focus(); + } + + componentWillReceiveProps (nextProps) { + if (nextProps.suggestions !== this.props.suggestions && nextProps.suggestions.size > 0 && this.state.suggestionsHidden) { + this.setState({ suggestionsHidden: false }); + } + } + + setTextarea = (c) => { + this.textarea = c; + } + + onPaste = (e) => { + if (e.clipboardData && e.clipboardData.files.length === 1) { + this.props.onPaste(e.clipboardData.files); + e.preventDefault(); + } + } + + renderSuggestion = (suggestion, i) => { + const { selectedSuggestion } = this.state; + let inner, key; + + if (typeof suggestion === 'object') { + inner = ; + key = suggestion.id; + } else { + inner = ; + key = suggestion; + } + + return ( +
+ {inner} +
+ ); + } + + render () { + const { value, suggestions, disabled, placeholder, autoFocus } = this.props; + const { suggestionsHidden } = this.state; + const style = { direction: 'ltr' }; + + if (isRtl(value)) { + style.direction = 'rtl'; + } + + return ( +
+