Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redesign public hashtag pages #5237

Merged
merged 2 commits into from
Oct 7, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 25 additions & 5 deletions app/controllers/tags_controller.rb
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
# frozen_string_literal: true

class TagsController < ApplicationController
layout 'public'
before_action :set_body_classes
before_action :set_instance_presenter

def show
@tag = Tag.find_by!(name: params[:id].downcase)
@statuses = Status.as_tag_timeline(@tag, current_account, params[:local]).paginate_by_max_id(20, params[:max_id])
@statuses = cache_collection(@statuses, Status)
@tag = Tag.find_by!(name: params[:id].downcase)

respond_to do |format|
format.html
format.html do
serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer)
@initial_state_json = serializable_resource.to_json
end

format.json do
@statuses = Status.as_tag_timeline(@tag, current_account, params[:local]).paginate_by_max_id(20, params[:max_id])
@statuses = cache_collection(@statuses, Status)

render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
end
end
end

private

def set_body_classes
@body_classes = 'tag-body'
end

def set_instance_presenter
@instance_presenter = InstancePresenter.new
end

def collection_presenter
ActivityPub::CollectionPresenter.new(
id: tag_url(@tag),
Expand All @@ -27,4 +40,11 @@ def collection_presenter
items: @statuses.map { |s| ActivityPub::TagManager.instance.uri_for(s) }
)
end

def initial_state_params
{
settings: {},
token: current_session&.token,
}
end
end
14 changes: 12 additions & 2 deletions app/javascript/mastodon/containers/timeline_container.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { hydrateStore } from '../actions/store';
import { IntlProvider, addLocaleData } from 'react-intl';
import { getLocale } from '../locales';
import PublicTimeline from '../features/standalone/public_timeline';
import HashtagTimeline from '../features/standalone/hashtag_timeline';

const { localeData, messages } = getLocale();
addLocaleData(localeData);
Expand All @@ -22,15 +23,24 @@ export default class TimelineContainer extends React.PureComponent {

static propTypes = {
locale: PropTypes.string.isRequired,
hashtag: PropTypes.string,
};

render () {
const { locale } = this.props;
const { locale, hashtag } = this.props;

let timeline;

if (hashtag) {
timeline = <HashtagTimeline hashtag={hashtag} />;
} else {
timeline = <PublicTimeline />;
}

return (
<IntlProvider locale={locale} messages={messages}>
<Provider store={store}>
<PublicTimeline />
{timeline}
</Provider>
</IntlProvider>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import StatusListContainer from '../../ui/containers/status_list_container';
import {
refreshHashtagTimeline,
expandHashtagTimeline,
} from '../../../actions/timelines';
import Column from '../../../components/column';
import ColumnHeader from '../../../components/column_header';

@connect()
export default class HashtagTimeline extends React.PureComponent {

static propTypes = {
dispatch: PropTypes.func.isRequired,
hashtag: PropTypes.string.isRequired,
};

handleHeaderClick = () => {
this.column.scrollTop();
}

setRef = c => {
this.column = c;
}

componentDidMount () {
const { dispatch, hashtag } = this.props;

dispatch(refreshHashtagTimeline(hashtag));

this.polling = setInterval(() => {
dispatch(refreshHashtagTimeline(hashtag));
}, 10000);
}

componentWillUnmount () {
if (typeof this.polling !== 'undefined') {
clearInterval(this.polling);
this.polling = null;
}
}

handleLoadMore = () => {
this.props.dispatch(expandHashtagTimeline(this.props.hashtag));
}

render () {
const { hashtag } = this.props;

return (
<Column ref={this.setRef}>
<ColumnHeader
icon='hashtag'
title={hashtag}
onClick={this.handleHeaderClick}
/>

<StatusListContainer
trackScroll={false}
scrollKey='standalone_hashtag_timeline'
timelineId={`hashtag:${hashtag}`}
loadMore={this.handleLoadMore}
/>
</Column>
);
}

}
6 changes: 3 additions & 3 deletions app/javascript/packs/about.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ require.context('../images/', true);

function loaded() {
const TimelineContainer = require('../mastodon/containers/timeline_container').default;
const React = require('react');
const ReactDOM = require('react-dom');
const mountNode = document.getElementById('mastodon-timeline');
const React = require('react');
const ReactDOM = require('react-dom');
const mountNode = document.getElementById('mastodon-timeline');

if (mountNode !== null) {
const props = JSON.parse(mountNode.getAttribute('data-props'));
Expand Down
91 changes: 91 additions & 0 deletions app/javascript/styles/about.scss
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,7 @@
flex: 0 0 auto;
background: $ui-base-color;
overflow: hidden;
border-radius: 4px;
box-shadow: 0 0 6px rgba($black, 0.1);

.column-header {
Expand Down Expand Up @@ -703,8 +704,98 @@
.features #mastodon-timeline {
height: 70vh;
width: 100%;
min-width: 330px;
margin-bottom: 50px;

.column {
width: 100%;
}
}
}

.cta {
margin: 20px;
}

&.tag-page {
.brand {
padding-top: 20px;
margin-bottom: 20px;

img {
height: 48px;
width: auto;
}
}

.container {
max-width: 690px;
}

.cta {
margin: 40px 0;
margin-bottom: 80px;

.button {
margin-right: 4px;
}
}

.about-mastodon {
max-width: 330px;

p {
strong {
color: $ui-secondary-color;
font-weight: 700;
}
}
}

@media screen and (max-width: 675px) {
.container {
display: flex;
flex-direction: column;
}

.features {
padding: 20px 0;
}

.about-mastodon {
order: 1;
flex: 0 0 auto;
max-width: 100%;
}

#mastodon-timeline {
order: 2;
flex: 0 0 auto;
height: 60vh;
}

.cta {
margin: 20px 0;
margin-bottom: 30px;
}

.features-list {
display: none;
}

.stripe {
display: none;
}
}
}

.stripe {
width: 100%;
height: 360px;
overflow: hidden;
background: darken($ui-base-color, 4%);
position: absolute;
z-index: -1;
}
}

Expand Down
5 changes: 5 additions & 0 deletions app/javascript/styles/basics.scss
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ body {
padding-bottom: 0;
}

&.tag-body {
background: darken($ui-base-color, 8%);
padding-bottom: 0;
}

&.embed {
background: transparent;
margin: 0;
Expand Down
1 change: 1 addition & 0 deletions app/javascript/styles/components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
text-transform: none;
background: transparent;
padding: 3px 15px;
border-radius: 4px;
border: 1px solid $ui-primary-color;

&:active,
Expand Down
2 changes: 1 addition & 1 deletion app/views/about/show.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
.about-mastodon
%h3= t 'about.what_is_mastodon'
%p= t 'about.about_mastodon_html'
%a.button.button-secondary{ href: 'https://joinmastodon.org/' }= t 'about.learn_more'
= link_to t('about.learn_more'), 'https://joinmastodon.org/', class: 'button button-secondary'
= render 'features'
.footer-links
.container
Expand Down
6 changes: 6 additions & 0 deletions app/views/tags/_og.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
= opengraph 'og:site_name', t('about.hosted_on', domain: site_hostname)
= opengraph 'og:url', tag_url(@tag)
= opengraph 'og:type', 'website'
= opengraph 'og:title', "##{@tag.name}"
= opengraph 'og:description', t('about.about_hashtag_html', hashtag: @tag.name)
= opengraph 'twitter:card', 'summary'
47 changes: 33 additions & 14 deletions app/views/tags/show.html.haml
Original file line number Diff line number Diff line change
@@ -1,19 +1,38 @@
- content_for :page_title do
= "##{@tag.name}"

.compact-header
%h1<
= link_to site_title, root_path
%br
%small ##{@tag.name}
- content_for :header_tags do
%script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
= javascript_pack_tag 'about', integrity: true, crossorigin: 'anonymous'
= render 'og'

- if @statuses.empty?
.accounts-grid
= render partial: 'accounts/nothing_here'
- else
.activity-stream.h-feed
= render partial: 'stream_entries/status', collection: @statuses, as: :status
.landing-page.tag-page
.stripe
.features
.container
#mastodon-timeline{ data: { props: Oj.dump(default_props.merge(hashtag: @tag.name)) } }

- if @statuses.size == 20
.pagination
= link_to safe_join([t('pagination.next'), fa_icon('chevron-right')], ' '), tag_url(@tag, max_id: @statuses.last.id), class: 'next', rel: 'next'
.about-mastodon
.brand
= link_to root_url do
= image_tag asset_pack_path('logo_full.svg'), alt: 'Mastodon'

%p= t 'about.about_hashtag_html', hashtag: @tag.name

.cta
= link_to t('auth.login'), new_user_session_path, class: 'button button-secondary'
= link_to t('about.learn_more'), root_url, class: 'button button-alternative'

.features-list
.features-list__row
.text
%h6= t 'about.features.not_a_product_title'
= t 'about.features.not_a_product_body'
.visual
= fa_icon 'fw users'
.features-list__row
.text
%h6= t 'about.features.humane_approach_title'
= t 'about.features.humane_approach_body'
.visual
= fa_icon 'fw leaf'
1 change: 1 addition & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
en:
about:
about_mastodon_html: Mastodon is a social network based on open web protocols and free, open-source software. It is decentralized like e-mail.
about_hashtag_html: These are public toots tagged with <strong>#%{hashtag}</strong>. You can interact with them if you have an account anywhere in the fediverse.
about_this: About
closed_registrations: Registrations are currently closed on this instance. However! You can find a different instance to make an account on and get access to the very same network from there.
contact: Contact
Expand Down