summaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2017-07-11 15:27:59 +0200
committerGitHub <noreply@github.com>2017-07-11 15:27:59 +0200
commite19eefe219c46ea9f763d0279029f03c5cf4554f (patch)
treedb31dd31a9bcabe22f18155872e0498fc0d906f6 /app
parent8784bd79d0053cb15775eb078f45e6aca7775d77 (diff)
Redesign the landing page, mount public timeline on it (#4122)
* Redesign the landing page, mount public timeline on it * Adjust the standalone mounted component to the lacking of router * Adjust auth layout pages to new design * Fix tests * Standalone public timeline polling every 5 seconds * Remove now obsolete translations * Add responsive design for new landing page * Address reviews * Add floating clouds behind frontpage form * Use access token from public page when available * Fix mentions and hashtags links, cursor on status content in standalone mode * Add footer link to source code * Fix errors on pages that don't embed the component, use classnames * Fix tests * Change anonymous autoPlayGif default to false * When gif autoplay is disabled, hover to play * Add option to hide the timeline preview * Slightly improve alt layout * Add elephant friend to new frontpage * Display "back to mastodon" in place of "login" when logged in on frontpage * Change polling time to 3s
Diffstat (limited to 'app')
-rw-r--r--app/controllers/about_controller.rb13
-rw-r--r--app/controllers/admin/settings_controller.rb9
-rw-r--r--app/controllers/home_controller.rb16
-rw-r--r--app/javascript/fonts/montserrat/Montserrat-Medium.ttfbin0 -> 192488 bytes
-rw-r--r--app/javascript/images/cloud2.pngbin0 -> 4973 bytes
-rw-r--r--app/javascript/images/cloud3.pngbin0 -> 5860 bytes
-rw-r--r--app/javascript/images/cloud4.pngbin0 -> 5273 bytes
-rw-r--r--app/javascript/images/elephant-fren.pngbin0 -> 40859 bytes
-rw-r--r--app/javascript/images/logo.svg2
-rw-r--r--app/javascript/mastodon/components/dropdown_menu.js19
-rw-r--r--app/javascript/mastodon/components/media_gallery.js38
-rw-r--r--app/javascript/mastodon/components/permalink.js4
-rw-r--r--app/javascript/mastodon/components/status.js8
-rw-r--r--app/javascript/mastodon/components/status_action_bar.js11
-rw-r--r--app/javascript/mastodon/components/status_content.js17
-rw-r--r--app/javascript/mastodon/components/video_player.js22
-rw-r--r--app/javascript/mastodon/containers/timeline_container.js39
-rw-r--r--app/javascript/mastodon/features/standalone/public_timeline/index.js76
-rw-r--r--app/javascript/packs/public.js10
-rw-r--r--app/javascript/styles/about.scss448
-rw-r--r--app/javascript/styles/basics.scss7
-rw-r--r--app/javascript/styles/boost.scss4
-rw-r--r--app/javascript/styles/components.scss32
-rw-r--r--app/javascript/styles/containers.scss48
-rw-r--r--app/javascript/styles/fonts/montserrat.scss8
-rw-r--r--app/javascript/styles/forms.scss39
-rw-r--r--app/presenters/instance_presenter.rb1
-rw-r--r--app/serializers/initial_state_serializer.rb35
-rw-r--r--app/views/about/_features.html.haml25
-rw-r--r--app/views/about/_registration.html.haml20
-rw-r--r--app/views/about/show.html.haml120
-rw-r--r--app/views/admin/settings/edit.html.haml43
-rw-r--r--app/views/auth/registrations/new.html.haml6
-rw-r--r--app/views/layouts/auth.html.haml3
34 files changed, 890 insertions, 233 deletions
diff --git a/app/controllers/about_controller.rb b/app/controllers/about_controller.rb
index c0addbeccc6..47690e81eb9 100644
--- a/app/controllers/about_controller.rb
+++ b/app/controllers/about_controller.rb
@@ -4,7 +4,10 @@ class AboutController < ApplicationController
before_action :set_body_classes
before_action :set_instance_presenter, only: [:show, :more, :terms]
- def show; end
+ def show
+ serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer)
+ @initial_state_json = serializable_resource.to_json
+ end
def more; end
@@ -15,6 +18,7 @@ class AboutController < ApplicationController
def new_user
User.new.tap(&:build_account)
end
+
helper_method :new_user
def set_instance_presenter
@@ -24,4 +28,11 @@ class AboutController < ApplicationController
def set_body_classes
@body_classes = 'about-body'
end
+
+ def initial_state_params
+ {
+ settings: {},
+ token: current_session&.token,
+ }
+ end
end
diff --git a/app/controllers/admin/settings_controller.rb b/app/controllers/admin/settings_controller.rb
index f27a1f4d450..29b590d7a3d 100644
--- a/app/controllers/admin/settings_controller.rb
+++ b/app/controllers/admin/settings_controller.rb
@@ -11,8 +11,15 @@ module Admin
site_terms
open_registrations
closed_registrations_message
+ open_deletion
+ timeline_preview
+ ).freeze
+
+ BOOLEAN_SETTINGS = %w(
+ open_registrations
+ open_deletion
+ timeline_preview
).freeze
- BOOLEAN_SETTINGS = %w(open_registrations).freeze
def edit
@settings = Setting.all_as_records
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
index 218da69066f..8a8b9ec76ba 100644
--- a/app/controllers/home_controller.rb
+++ b/app/controllers/home_controller.rb
@@ -15,12 +15,16 @@ class HomeController < ApplicationController
end
def set_initial_state_json
- state = InitialStatePresenter.new(settings: Web::Setting.find_by(user: current_user)&.data || {},
- current_account: current_account,
- token: current_session.token,
- admin: Account.find_local(Setting.site_contact_username))
-
- serializable_resource = ActiveModelSerializers::SerializableResource.new(state, serializer: InitialStateSerializer)
+ serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer)
@initial_state_json = serializable_resource.to_json
end
+
+ def initial_state_params
+ {
+ settings: Web::Setting.find_by(user: current_user)&.data || {},
+ current_account: current_account,
+ token: current_session.token,
+ admin: Account.find_local(Setting.site_contact_username),
+ }
+ end
end
diff --git a/app/javascript/fonts/montserrat/Montserrat-Medium.ttf b/app/javascript/fonts/montserrat/Montserrat-Medium.ttf
new file mode 100644
index 00000000000..88d70b89c3f
--- /dev/null
+++ b/app/javascript/fonts/montserrat/Montserrat-Medium.ttf
Binary files differ
diff --git a/app/javascript/images/cloud2.png b/app/javascript/images/cloud2.png
new file mode 100644
index 00000000000..f325ca6de09
--- /dev/null
+++ b/app/javascript/images/cloud2.png
Binary files differ
diff --git a/app/javascript/images/cloud3.png b/app/javascript/images/cloud3.png
new file mode 100644
index 00000000000..ab194d0b857
--- /dev/null
+++ b/app/javascript/images/cloud3.png
Binary files differ
diff --git a/app/javascript/images/cloud4.png b/app/javascript/images/cloud4.png
new file mode 100644
index 00000000000..98323f5a271
--- /dev/null
+++ b/app/javascript/images/cloud4.png
Binary files differ
diff --git a/app/javascript/images/elephant-fren.png b/app/javascript/images/elephant-fren.png
new file mode 100644
index 00000000000..3b64edf084c
--- /dev/null
+++ b/app/javascript/images/elephant-fren.png
Binary files differ
diff --git a/app/javascript/images/logo.svg b/app/javascript/images/logo.svg
index c233db84281..16cb3a94407 100644
--- a/app/javascript/images/logo.svg
+++ b/app/javascript/images/logo.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" height="1000" width="1000"><path d="M500 0a500 500 0 0 0-353.553 146.447 500 500 0 1 0 707.106 707.106A500 500 0 0 0 500 0zm-.059 280.05h107.12c-19.071 13.424-26.187 51.016-27.12 73.843V562.05c0 44.32-35.68 80-80 80s-80-35.68-80-80v-202c0-44.32 35.68-80 80-80zm-.441 52c-15.464 0-28 12.537-28 28 0 15.465 12.536 28 28 28s28-12.535 28-28c0-15.463-12.536-28-28-28zm-279.059 7.9c44.32 0 80 35.68 80 80v206.157c.933 22.827 8.049 60.42 27.12 73.842H220.44c-44.32 0-80-35.68-80-80v-200c0-44.32 35.68-80 80-80zm559.12 0c44.32 0 80 35.68 80 80v200c0 44.32-35.68 80-80 80H672.44c19.071-13.424 26.187-51.016 27.12-73.843V419.95c0-44.32 35.68-80 80-80zM220 392c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28zm560 0c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28zm-280.5 40.05c-15.464 0-28 12.537-28 28 0 15.465 12.536 28 28 28s28-12.535 28-28c0-15.463-12.536-28-28-28zM220 491.95c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28zm560 0c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28zM499.5 532c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28zM220 591.95c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28zm560 0c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28z" fill="#189efc"/></svg> \ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1000 1000" height="1000" width="1000"><path d="M500 0a500 500 0 0 0-353.553 146.447 500 500 0 1 0 707.106 707.106A500 500 0 0 0 500 0zm-.059 280.05h107.12c-19.071 13.424-26.187 51.016-27.12 73.843V562.05c0 44.32-35.68 80-80 80s-80-35.68-80-80v-202c0-44.32 35.68-80 80-80zm-.441 52c-15.464 0-28 12.537-28 28 0 15.465 12.536 28 28 28s28-12.535 28-28c0-15.463-12.536-28-28-28zm-279.059 7.9c44.32 0 80 35.68 80 80v206.157c.933 22.827 8.049 60.42 27.12 73.842H220.44c-44.32 0-80-35.68-80-80v-200c0-44.32 35.68-80 80-80zm559.12 0c44.32 0 80 35.68 80 80v200c0 44.32-35.68 80-80 80H672.44c19.071-13.424 26.187-51.016 27.12-73.843V419.95c0-44.32 35.68-80 80-80zM220 392c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28zm560 0c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28zm-280.5 40.05c-15.464 0-28 12.537-28 28 0 15.465 12.536 28 28 28s28-12.535 28-28c0-15.463-12.536-28-28-28zM220 491.95c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28zm560 0c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28zM499.5 532c-15.464 0-28 12.536-28 28s12.536 28 28 28 28-12.536 28-28-12.536-28-28-28zM220 591.95c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28zm560 0c-15.464 0-28 12.535-28 28 0 15.463 12.536 28 28 28s28-12.537 28-28c0-15.465-12.536-28-28-28z" fill="#fff"/></svg>
diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js
index 12e1b44fa22..98323b0691d 100644
--- a/app/javascript/mastodon/components/dropdown_menu.js
+++ b/app/javascript/mastodon/components/dropdown_menu.js
@@ -14,6 +14,7 @@ export default class DropdownMenu extends React.PureComponent {
size: PropTypes.number.isRequired,
direction: PropTypes.string,
ariaLabel: PropTypes.string,
+ disabled: PropTypes.bool,
};
static defaultProps = {
@@ -68,9 +69,19 @@ export default class DropdownMenu extends React.PureComponent {
}
render () {
- const { icon, items, size, direction, ariaLabel } = this.props;
- const { expanded } = this.state;
+ const { icon, items, size, direction, ariaLabel, disabled } = this.props;
+ const { expanded } = this.state;
const directionClass = (direction === 'left') ? 'dropdown__left' : 'dropdown__right';
+ const iconStyle = { fontSize: `${size}px`, width: `${size}px`, lineHeight: `${size}px` };
+ const iconClassname = `fa fa-fw fa-${icon} dropdown__icon`;
+
+ if (disabled) {
+ return (
+ <div className='icon-button disabled' style={iconStyle} aria-label={ariaLabel}>
+ <i className={iconClassname} aria-hidden />
+ </div>
+ );
+ }
const dropdownItems = expanded && (
<ul className='dropdown__content-list'>
@@ -80,8 +91,8 @@ export default class DropdownMenu extends React.PureComponent {
return (
<Dropdown ref={this.setRef} onShow={this.handleShow} onHide={this.handleHide}>
- <DropdownTrigger className='icon-button' style={{ fontSize: `${size}px`, width: `${size}px`, lineHeight: `${size}px` }} aria-label={ariaLabel}>
- <i className={`fa fa-fw fa-${icon} dropdown__icon`} aria-hidden />
+ <DropdownTrigger className='icon-button' style={iconStyle} aria-label={ariaLabel}>
+ <i className={iconClassname} aria-hidden />
</DropdownTrigger>
<DropdownContent className={directionClass}>
diff --git a/app/javascript/mastodon/components/media_gallery.js b/app/javascript/mastodon/components/media_gallery.js
index 75222e9650a..89a358e384c 100644
--- a/app/javascript/mastodon/components/media_gallery.js
+++ b/app/javascript/mastodon/components/media_gallery.js
@@ -11,18 +11,44 @@ const messages = defineMessages({
class Item extends React.PureComponent {
+ static contextTypes = {
+ router: PropTypes.object,
+ };
+
static propTypes = {
attachment: ImmutablePropTypes.map.isRequired,
index: PropTypes.number.isRequired,
size: PropTypes.number.isRequired,
onClick: PropTypes.func.isRequired,
- autoPlayGif: PropTypes.bool.isRequired,
+ autoPlayGif: PropTypes.bool,
+ };
+
+ static defaultProps = {
+ autoPlayGif: false,
};
+ handleMouseEnter = (e) => {
+ if (this.hoverToPlay()) {
+ e.target.play();
+ }
+ }
+
+ handleMouseLeave = (e) => {
+ if (this.hoverToPlay()) {
+ e.target.pause();
+ e.target.currentTime = 0;
+ }
+ }
+
+ hoverToPlay () {
+ const { attachment, autoPlayGif } = this.props;
+ return !autoPlayGif && attachment.get('type') === 'gifv';
+ }
+
handleClick = (e) => {
const { index, onClick } = this.props;
- if (e.button === 0) {
+ if (this.context.router && e.button === 0) {
e.preventDefault();
onClick(index);
}
@@ -116,6 +142,8 @@ class Item extends React.PureComponent {
role='application'
src={attachment.get('url')}
onClick={this.handleClick}
+ onMouseEnter={this.handleMouseEnter}
+ onMouseLeave={this.handleMouseLeave}
autoPlay={autoPlay}
loop
muted
@@ -144,7 +172,11 @@ export default class MediaGallery extends React.PureComponent {
height: PropTypes.number.isRequired,
onOpenMedia: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
- autoPlayGif: PropTypes.bool.isRequired,
+ autoPlayGif: PropTypes.bool,
+ };
+
+ static defaultProps = {
+ autoPlayGif: false,
};
state = {
diff --git a/app/javascript/mastodon/components/permalink.js b/app/javascript/mastodon/components/permalink.js
index 0b7d0a65ad9..d726d37a20a 100644
--- a/app/javascript/mastodon/components/permalink.js
+++ b/app/javascript/mastodon/components/permalink.js
@@ -15,7 +15,7 @@ export default class Permalink extends React.PureComponent {
};
handleClick = (e) => {
- if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
+ if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(this.props.to);
}
@@ -25,7 +25,7 @@ export default class Permalink extends React.PureComponent {
const { href, children, className, ...other } = this.props;
return (
- <a href={href} onClick={this.handleClick} {...other} className={`permalink${className ? ' ' + className : ''}`}>
+ <a target='_blank' href={href} onClick={this.handleClick} {...other} className={`permalink${className ? ' ' + className : ''}`}>
{children}
</a>
);
diff --git a/app/javascript/mastodon/components/status.js b/app/javascript/mastodon/components/status.js
index df771f5a8f6..6b9fdd2afe7 100644
--- a/app/javascript/mastodon/components/status.js
+++ b/app/javascript/mastodon/components/status.js
@@ -140,12 +140,16 @@ export default class Status extends ImmutablePureComponent {
}
handleClick = () => {
+ if (!this.context.router) {
+ return;
+ }
+
const { status } = this.props;
this.context.router.history.push(`/statuses/${status.getIn(['reblog', 'id'], status.get('id'))}`);
}
handleAccountClick = (e) => {
- if (e.button === 0) {
+ if (this.context.router && e.button === 0) {
const id = Number(e.currentTarget.getAttribute('data-id'));
e.preventDefault();
this.context.router.history.push(`/accounts/${id}`);
@@ -236,7 +240,7 @@ export default class Status extends ImmutablePureComponent {
<div className='status__info'>
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
- <a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name'>
+ <a onClick={this.handleAccountClick} target='_blank' data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} className='status__display-name'>
<div className='status__avatar'>
{statusAvatar}
</div>
diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js
index fd7c990544e..7bb394e71bd 100644
--- a/app/javascript/mastodon/components/status_action_bar.js
+++ b/app/javascript/mastodon/components/status_action_bar.js
@@ -40,7 +40,7 @@ export default class StatusActionBar extends ImmutablePureComponent {
onBlock: PropTypes.func,
onReport: PropTypes.func,
onMuteConversation: PropTypes.func,
- me: PropTypes.number.isRequired,
+ me: PropTypes.number,
withDismiss: PropTypes.bool,
intl: PropTypes.object.isRequired,
};
@@ -97,6 +97,7 @@ export default class StatusActionBar extends ImmutablePureComponent {
const { status, me, intl, withDismiss } = this.props;
const reblogDisabled = status.get('visibility') === 'private' || status.get('visibility') === 'direct';
const mutingConversation = status.get('muted');
+ const anonymousAccess = !me;
let menu = [];
let reblogIcon = 'retweet';
@@ -137,12 +138,12 @@ export default class StatusActionBar extends ImmutablePureComponent {
return (
<div className='status__action-bar'>
- <IconButton className='status__action-bar-button' title={replyTitle} icon={replyIcon} onClick={this.handleReplyClick} />
- <IconButton className='status__action-bar-button' disabled={reblogDisabled} active={status.get('reblogged')} title={reblogDisabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} />
- <IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
+ <IconButton className='status__action-bar-button' disabled={anonymousAccess} title={replyTitle} icon={replyIcon} onClick={this.handleReplyClick} />
+ <IconButton className='status__action-bar-button' disabled={anonymousAccess || reblogDisabled} active={status.get('reblogged')} title={reblogDisabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} />
+ <IconButton className='status__action-bar-button star-icon' disabled={anonymousAccess} animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
<div className='status__action-bar-dropdown'>
- <DropdownMenu items={menu} icon='ellipsis-h' size={18} direction='right' ariaLabel='More' />
+ <DropdownMenu disabled={anonymousAccess} items={menu} icon='ellipsis-h' size={18} direction='right' ariaLabel='More' />
</div>
</div>
);
diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js
index 02b4c84020c..1b803a22ec0 100644
--- a/app/javascript/mastodon/components/status_content.js
+++ b/app/javascript/mastodon/components/status_content.js
@@ -6,6 +6,7 @@ import emojify from '../emoji';
import { isRtl } from '../rtl';
import { FormattedMessage } from 'react-intl';
import Permalink from './permalink';
+import classnames from 'classnames';
export default class StatusContent extends React.PureComponent {
@@ -43,10 +44,11 @@ export default class StatusContent extends React.PureComponent {
} 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 {
- link.set