diff options
Diffstat (limited to 'app/assets/javascripts')
6 files changed, 133 insertions, 23 deletions
diff --git a/app/assets/javascripts/components/emoji.jsx b/app/assets/javascripts/components/emoji.jsx index c93c07c74..eee657b86 100644 --- a/app/assets/javascripts/components/emoji.jsx +++ b/app/assets/javascripts/components/emoji.jsx @@ -1,9 +1,35 @@ import emojione from 'emojione'; -emojione.imageType = 'png'; -emojione.sprites = false; -emojione.imagePathPNG = '/emoji/'; +const toImage = str => shortnameToImage(unicodeToImage(str)); + +const unicodeToImage = str => { + const mappedUnicode = emojione.mapUnicodeToShort(); + + return str.replace(emojione.regUnicode, unicodeChar => { + if (typeof unicodeChar === 'undefined' || unicodeChar === '' || !(unicodeChar in emojione.jsEscapeMap)) { + return unicodeChar; + } + + const unicode = emojione.jsEscapeMap[unicodeChar]; + const short = mappedUnicode[unicode]; + const filename = emojione.emojioneList[short].fname; + const alt = emojione.convert(unicode.toUpperCase()); + + return `<img draggable="false" class="emojione" alt="${alt}" src="/emoji/${filename}.svg" />`; + }); +}; + +const shortnameToImage = str => str.replace(emojione.regShortNames, shortname => { + if (typeof shortname === 'undefined' || shortname === '' || !(shortname in emojione.emojioneList)) { + return shortname; + } + + const unicode = emojione.emojioneList[shortname].unicode[emojione.emojioneList[shortname].unicode.length - 1]; + const alt = emojione.convert(unicode.toUpperCase()); + + return `<img draggable="false" class="emojione" alt="${alt}" src="/emoji/${unicode}.svg" />`; +}); export default function emojify(text) { - return emojione.toImage(text); + return toImage(text); }; diff --git a/app/assets/javascripts/components/features/account/components/header.jsx b/app/assets/javascripts/components/features/account/components/header.jsx index e1aae3c77..a359963c4 100644 --- a/app/assets/javascripts/components/features/account/components/header.jsx +++ b/app/assets/javascripts/components/features/account/components/header.jsx @@ -4,6 +4,7 @@ import emojify from '../../../emoji'; import escapeTextContentForBrowser from 'escape-html'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import IconButton from '../../../components/icon_button'; +import { Motion, spring } from 'react-motion'; const messages = defineMessages({ unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, @@ -11,6 +12,47 @@ const messages = defineMessages({ requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' } }); +const Avatar = React.createClass({ + + propTypes: { + account: ImmutablePropTypes.map.isRequired + }, + + getInitialState () { + return { + isHovered: false + }; + }, + + mixins: [PureRenderMixin], + + handleMouseOver () { + if (this.state.isHovered) return; + this.setState({ isHovered: true }); + }, + + handleMouseOut () { + if (!this.state.isHovered) return; + this.setState({ isHovered: false }); + }, + + render () { + const { account } = this.props; + const { isHovered } = this.state; + + return ( + <Motion defaultStyle={{ radius: 90 }} style={{ radius: spring(isHovered ? 30 : 90, { stiffness: 180, damping: 12 }) }}> + {({ radius }) => + <a href={account.get('url')} className='account__header__avatar' target='_blank' rel='noopener' style={{ display: 'block', width: '90px', height: '90px', margin: '0 auto', marginBottom: '10px', borderRadius: `${radius}px`, overflow: 'hidden' }} onMouseOver={this.handleMouseOver} onMouseOut={this.handleMouseOut}> + <img src={account.get('avatar')} alt={account.get('acct')} style={{ display: 'block', width: '90px', height: '90px' }} /> + </a> + } + </Motion> + ); + } + +}); + const Header = React.createClass({ propTypes: { @@ -68,14 +110,9 @@ const Header = React.createClass({ return ( <div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}> <div style={{ padding: '20px 10px' }}> - <a href={account.get('url')} target='_blank' rel='noopener' style={{ display: 'block', color: 'inherit', textDecoration: 'none' }}> - <div className='account__header__avatar' style={{ width: '90px', margin: '0 auto', marginBottom: '10px' }}> - <img src={account.get('avatar')} alt='' style={{ display: 'block', width: '90px', height: '90px', borderRadius: '90px' }} /> - </div> - - <span style={{ display: 'inline-block', fontSize: '20px', lineHeight: '27px', fontWeight: '500' }} className='account__header__display-name' dangerouslySetInnerHTML={displayNameHTML} /> - </a> + <Avatar account={account} /> + <span style={{ display: 'inline-block', fontSize: '20px', lineHeight: '27px', fontWeight: '500' }} className='account__header__display-name' dangerouslySetInnerHTML={displayNameHTML} /> <span className='account__header__username' style={{ fontSize: '14px', fontWeight: '400', display: 'block', marginBottom: '10px' }}>@{account.get('acct')} {lockedIcon}</span> <div style={{ fontSize: '14px' }} className='account__header__content' dangerouslySetInnerHTML={content} /> diff --git a/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx b/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx index 471f0e1bb..1920b29bf 100644 --- a/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx +++ b/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx @@ -43,7 +43,7 @@ const EmojiPickerDropdown = React.createClass({ return ( <Dropdown ref={this.setRef} style={style}> <DropdownTrigger className='emoji-button' title={intl.formatMessage(messages.emoji)} style={{ fontSize: `24px`, width: `24px`, lineHeight: `24px`, display: 'block', marginLeft: '2px' }}> - <img className="emojione" alt="🙂" src="/emoji/1f602.png" /> + <img draggable="false" className="emojione" alt="🙂" src="/emoji/1f602.svg" /> </DropdownTrigger> <DropdownContent className='dropdown__left'> diff --git a/app/assets/javascripts/components/features/getting_started/index.jsx b/app/assets/javascripts/components/features/getting_started/index.jsx index 846e4f53b..6f9e988ba 100644 --- a/app/assets/javascripts/components/features/getting_started/index.jsx +++ b/app/assets/javascripts/components/features/getting_started/index.jsx @@ -41,7 +41,7 @@ const GettingStarted = ({ intl, me }) => { <ColumnLink icon='sign-out' text={intl.formatMessage(messages.sign_out)} href='/auth/sign_out' method='delete' /> </div> - <div className='scrollable optionally-scrollable'> + <div className='scrollable optionally-scrollable' style={{ display: 'flex', flexDirection: 'column' }}> <div className='static-content getting-started'> <p><FormattedMessage id='getting_started.about_addressing' defaultMessage='You can follow people if you know their username and the domain they are on by entering an e-mail-esque address into the form at the top of the sidebar.' /></p> <p><FormattedMessage id='getting_started.about_shortcuts' defaultMessage='If the target user is on the same domain as you, just the username will work. The same rule applies to mentioning people in statuses.' /></p> diff --git a/app/assets/javascripts/components/features/ui/index.jsx b/app/assets/javascripts/components/features/ui/index.jsx index 10e989b2a..4b7e4bb46 100644 --- a/app/assets/javascripts/components/features/ui/index.jsx +++ b/app/assets/javascripts/components/features/ui/index.jsx @@ -36,34 +36,62 @@ const UI = React.createClass({ this.setState({ width: window.innerWidth }); }, + handleDragEnter (e) { + e.preventDefault(); + + if (!this.dragTargets) { + this.dragTargets = []; + } + + if (this.dragTargets.indexOf(e.target) === -1) { + this.dragTargets.push(e.target); + } + + this.setState({ draggingOver: true }); + }, + handleDragOver (e) { e.preventDefault(); e.stopPropagation(); - e.dataTransfer.dropEffect = 'copy'; + try { + e.dataTransfer.dropEffect = 'copy'; + } catch (err) { - if (e.dataTransfer.effectAllowed === 'all' || e.dataTransfer.effectAllowed === 'uninitialized') { - this.setState({ draggingOver: true }); } + + return false; }, handleDrop (e) { e.preventDefault(); + this.setState({ draggingOver: false }); + if (e.dataTransfer && e.dataTransfer.files.length === 1) { - this.setState({ draggingOver: false }); this.props.dispatch(uploadCompose(e.dataTransfer.files)); } }, - handleDragLeave () { + handleDragLeave (e) { + e.preventDefault(); + e.stopPropagation(); + + this.dragTargets = this.dragTargets.filter(el => el !== e.target && this.node.contains(el)); + + if (this.dragTargets.length > 0) { + return; + } + this.setState({ draggingOver: false }); }, componentWillMount () { window.addEventListener('resize', this.handleResize, { passive: true }); - window.addEventListener('dragover', this.handleDragOver); - window.addEventListener('drop', this.handleDrop); + document.addEventListener('dragenter', this.handleDragEnter, false); + document.addEventListener('dragover', this.handleDragOver, false); + document.addEventListener('drop', this.handleDrop, false); + document.addEventListener('dragleave', this.handleDragLeave, false); this.props.dispatch(refreshTimeline('home')); this.props.dispatch(refreshNotifications()); @@ -71,8 +99,14 @@ const UI = React.createClass({ componentWillUnmount () { window.removeEventListener('resize', this.handleResize); - window.removeEventListener('dragover', this.handleDragOver); - window.removeEventListener('drop', this.handleDrop); + document.removeEventListener('dragenter', this.handleDragEnter); + document.removeEventListener('dragover', this.handleDragOver); + document.removeEventListener('drop', this.handleDrop); + document.removeEventListener('dragleave', this.handleDragLeave); + }, + + setRef (c) { + this.node = c; }, render () { @@ -99,7 +133,7 @@ const UI = React.createClass({ } return ( - <div className='ui' onDragLeave={this.handleDragLeave}> + <div className='ui' ref={this.setRef}> <TabsBar /> {mountedColumns} diff --git a/app/assets/javascripts/extras.jsx b/app/assets/javascripts/extras.jsx index 5738863dd..c13feceff 100644 --- a/app/assets/javascripts/extras.jsx +++ b/app/assets/javascripts/extras.jsx @@ -24,4 +24,17 @@ $(() => { window.location.href = $(e.target).attr('href'); } }); + + $('.status__content__spoiler-link').on('click', e => { + e.preventDefault(); + const contentEl = $(e.target).parent().parent().find('div'); + + if (contentEl.is(':visible')) { + contentEl.hide(); + $(e.target).parent().attr('style', 'margin-bottom: 0'); + } else { + contentEl.show(); + $(e.target).parent().attr('style', null); + } + }); }); |