Merge branch 'main' into 3.4.1

This commit is contained in:
YorimiMochida 2021-06-03 14:14:41 +09:00 committed by GitHub
commit d1a003e94a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
212 changed files with 9061 additions and 243 deletions

View File

@ -1,22 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 99
allow:
- dependency-type: direct
- package-ecosystem: bundler
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 99
allow:
- dependency-type: direct

View File

@ -99,6 +99,8 @@ gem 'json-ld'
gem 'json-ld-preloaded', '~> 3.1'
gem 'rdf-normalize', '~> 0.4'
gem 'redcarpet', "~> 3.4.0"
group :development, :test do
gem 'fabrication', '~> 2.22'
gem 'fuubar', '~> 2.5'

View File

@ -491,6 +491,14 @@ GEM
rdf-normalize (0.4.0)
rdf (~> 3.1)
redis (4.2.5)
redcarpet (3.4.0)
redis-actionpack (5.2.0)
actionpack (>= 5, < 7)
redis-rack (>= 2.1.0, < 3)
redis-store (>= 1.1.0, < 2)
redis-activesupport (5.2.0)
activesupport (>= 3, < 7)
redis-store (>= 1.3, < 2)
redis-namespace (1.8.1)
redis (>= 3.0.4)
regexp_parser (2.1.1)
@ -768,6 +776,7 @@ DEPENDENCIES
rails-i18n (~> 6.0)
rails-settings-cached (~> 0.6)
rdf-normalize (~> 0.4)
redcarpet (~> 3.4.0)
redis (~> 4.2)
redis-namespace (~> 1.8)
resolv (~> 0.1.0)

View File

@ -7,8 +7,9 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
after_action :insert_pagination_headers, unless: -> { truthy_param?(:pinned) }
def index
@statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
@statuses = load_statuses
accountIds = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), account_relationships: AccountRelationshipsPresenter.new(accountIds, current_user&.account_id)
end
private

View File

@ -6,8 +6,9 @@ class Api::V1::BookmarksController < Api::BaseController
after_action :insert_pagination_headers
def index
@statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
@statuses = load_statuses
accountIds = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), account_relationships: AccountRelationshipsPresenter.new(accountIds, current_user&.account_id)
end
private

View File

@ -6,8 +6,9 @@ class Api::V1::FavouritesController < Api::BaseController
after_action :insert_pagination_headers
def index
@statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
@statuses = load_statuses
accountIds = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), account_relationships: AccountRelationshipsPresenter.new(accountIds, current_user&.account_id)
end
private

View File

@ -30,8 +30,9 @@ class Api::V1::StatusesController < Api::BaseController
@context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants)
statuses = [@status] + @context.ancestors + @context.descendants
accountIds = statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq
render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id)
render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id), account_relationships: AccountRelationshipsPresenter.new(accountIds, current_user&.account_id)
end
def create
@ -46,7 +47,8 @@ class Api::V1::StatusesController < Api::BaseController
application: doorkeeper_token.application,
poll: status_params[:poll],
idempotency: request.headers['Idempotency-Key'],
with_rate_limit: true)
with_rate_limit: true,
quote_id: status_params[:quote_id].presence)
render json: @status, serializer: @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer
end
@ -85,6 +87,7 @@ class Api::V1::StatusesController < Api::BaseController
:spoiler_text,
:visibility,
:scheduled_at,
:quote_id,
media_ids: [],
poll: [
:multiple,

View File

@ -6,11 +6,13 @@ class Api::V1::Timelines::HomeController < Api::BaseController
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
def show
@statuses = load_statuses
@statuses = load_statuses
accountIds = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq
render json: @statuses,
each_serializer: REST::StatusSerializer,
relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
account_relationships: AccountRelationshipsPresenter.new(accountIds, current_user&.account_id),
status: account_home_feed.regenerating? ? 206 : 200
end

View File

@ -9,9 +9,12 @@ class Api::V1::Timelines::ListController < Api::BaseController
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
def show
accountIds = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq
render json: @statuses,
each_serializer: REST::StatusSerializer,
relationships: StatusRelationshipsPresenter.new(@statuses, current_user.account_id)
relationships: StatusRelationshipsPresenter.new(@statuses, current_user.account_id),
account_relationships: AccountRelationshipsPresenter.new(accountIds, current_user&.account_id)
end
private

View File

@ -5,8 +5,10 @@ class Api::V1::Timelines::PublicController < Api::BaseController
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
def show
@statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
@statuses = load_statuses
accountIds = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), account_relationships: AccountRelationshipsPresenter.new(accountIds, current_user&.account_id)
end
private

View File

@ -5,8 +5,9 @@ class Api::V1::Timelines::TagController < Api::BaseController
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
def show
@statuses = load_statuses
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
@statuses = load_statuses
accountIds = @statuses.filter(&:quote?).map { |status| status.quote.account_id }.uniq
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), account_relationships: AccountRelationshipsPresenter.new(accountIds, current_user&.account_id)
end
private

View File

@ -1,5 +1,8 @@
# frozen_string_literal: true
class Api::V2::MediaController < Api::V1::MediaController
end
__END__
class Api::V2::MediaController < Api::V1::MediaController
def create
@media_attachment = current_account.media_attachments.create!({ delay_processing: true }.merge(media_attachment_params))

View File

@ -55,6 +55,8 @@ class Settings::PreferencesController < Settings::BaseController
:setting_use_pending_items,
:setting_trends,
:setting_crop_images,
:setting_place_tab_bar_at_bottom,
:setting_show_tab_bar_label,
notification_emails: %i(follow follow_request reblog favourite mention digest report pending_account trending_tag),
interactions: %i(must_be_follower must_be_following must_be_following_dm)
)

Binary file not shown.

View File

@ -1 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216.4144 232.00976"><path d="M211.80734 139.0875c-3.18125 16.36625-28.4925 34.2775-57.5625 37.74875-15.15875 1.80875-30.08375 3.47125-45.99875 2.74125-26.0275-1.1925-46.565-6.2125-46.565-6.2125 0 2.53375.15625 4.94625.46875 7.2025 3.38375 25.68625 25.47 27.225 46.39125 27.9425 21.11625.7225 39.91875-5.20625 39.91875-5.20625l.8675 19.09s-14.77 7.93125-41.08125 9.39c-14.50875.7975-32.52375-.365-53.50625-5.91875C9.23234 213.82 1.40609 165.31125.20859 116.09125c-.365-14.61375-.14-28.39375-.14-39.91875 0-50.33 32.97625-65.0825 32.97625-65.0825C49.67234 3.45375 78.20359.2425 107.86484 0h.72875c29.66125.2425 58.21125 3.45375 74.8375 11.09 0 0 32.975 14.7525 32.975 65.0825 0 0 .41375 37.13375-4.59875 62.915" fill="#3088d4"/><path d="M177.50984 80.077v60.94125h-24.14375v-59.15c0-12.46875-5.24625-18.7975-15.74-18.7975-11.6025 0-17.4175 7.5075-17.4175 22.3525v32.37625H96.20734V85.42325c0-14.845-5.81625-22.3525-17.41875-22.3525-10.49375 0-15.74 6.32875-15.74 18.7975v59.15H38.90484V80.077c0-12.455 3.17125-22.3525 9.54125-29.675 6.56875-7.3225 15.17125-11.07625 25.85-11.07625 12.355 0 21.71125 4.74875 27.8975 14.2475l6.01375 10.08125 6.015-10.08125c6.185-9.49875 15.54125-14.2475 27.8975-14.2475 10.6775 0 19.28 3.75375 25.85 11.07625 6.36875 7.3225 9.54 17.22 9.54 29.675" fill="#fff"/></svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="レイヤー_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
y="0px" viewBox="0 0 216.4 232" style="enable-background:new 0 0 216.4 232;" xml:space="preserve">
<style type="text/css">
.st0{fill:#92787D;}
.st1{fill:#FFFFFF;}
</style>
<path class="st0" d="M211.8,139.1c-3.2,16.4-28.5,34.3-57.6,37.7c-15.2,1.8-30.1,3.5-46,2.7c-26-1.2-46.6-6.2-46.6-6.2
c0,2.5,0.2,4.9,0.5,7.2c3.4,25.7,25.5,27.2,46.4,27.9c21.1,0.7,39.9-5.2,39.9-5.2l0.9,19.1c0,0-14.8,7.9-41.1,9.4
c-14.5,0.8-32.5-0.4-53.5-5.9c-45.5-12-53.3-60.6-54.5-109.8c-0.4-14.6-0.1-28.4-0.1-39.9c0-50.3,33-65.1,33-65.1
C49.7,3.5,78.2,0.2,107.9,0h0.7c29.7,0.2,58.2,3.5,74.8,11.1c0,0,33,14.8,33,65.1C216.4,76.2,216.8,113.3,211.8,139.1"/>
<path class="st1" d="M177.5,80.1V141h-24.1V81.9c0-12.5-5.2-18.8-15.7-18.8c-11.6,0-17.4,7.5-17.4,22.4v32.4h-24V85.4
c0-14.8-5.8-22.4-17.4-22.4C68.3,63.1,63,69.4,63,81.9V141H38.9V80.1c0-12.5,3.2-22.4,9.5-29.7c6.6-7.3,15.2-11.1,25.8-11.1
c12.4,0,21.7,4.7,27.9,14.2l6,10.1l6-10.1c6.2-9.5,15.5-14.2,27.9-14.2c10.7,0,19.3,3.8,25.9,11.1C174.3,57.7,177.5,67.6,177.5,80.1
"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1 +1,16 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 216.41507 232.00976"><path d="M211.80683 139.0875c-3.1825 16.36625-28.4925 34.2775-57.5625 37.74875-15.16 1.80875-30.0825 3.47125-45.99875 2.74125-26.0275-1.1925-46.565-6.2125-46.565-6.2125 0 2.53375.15625 4.94625.46875 7.2025 3.38375 25.68625 25.47 27.225 46.3925 27.9425 21.115.7225 39.91625-5.20625 39.91625-5.20625l.86875 19.09s-14.77 7.93125-41.08125 9.39c-14.50875.7975-32.52375-.365-53.50625-5.91875C9.23183 213.82 1.40558 165.31125.20808 116.09125c-.36375-14.61375-.14-28.39375-.14-39.91875 0-50.33 32.97625-65.0825 32.97625-65.0825C49.67058 3.45375 78.20308.2425 107.86433 0h.72875c29.66125.2425 58.21125 3.45375 74.8375 11.09 0 0 32.97625 14.7525 32.97625 65.0825 0 0 .4125 37.13375-4.6 62.915" fill="#3088d4"/><path d="M65.68743 96.45938c0 9.01375-7.3075 16.32125-16.3225 16.32125-9.01375 0-16.32-7.3075-16.32-16.32125 0-9.01375 7.30625-16.3225 16.32-16.3225 9.015 0 16.3225 7.30875 16.3225 16.3225M124.52893 96.45938c0 9.01375-7.30875 16.32125-16.3225 16.32125-9.01375 0-16.32125-7.3075-16.32125-16.32125 0-9.01375 7.3075-16.3225 16.32125-16.3225 9.01375 0 16.3225 7.30875 16.3225 16.3225M183.36933 96.45938c0 9.01375-7.3075 16.32125-16.32125 16.32125-9.01375 0-16.32125-7.3075-16.32125-16.32125 0-9.01375 7.3075-16.3225 16.32125-16.3225 9.01375 0 16.32125 7.30875 16.32125 16.3225" fill="#fff"/></svg>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="レイヤー_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
y="0px" viewBox="0 0 216.4 232" style="enable-background:new 0 0 216.4 232;" xml:space="preserve">
<style type="text/css">
.st0{fill:#92787D;}
.st1{fill:#FFFFFF;}
</style>
<path class="st0" d="M211.8,139.1c-3.2,16.4-28.5,34.3-57.6,37.7c-15.2,1.8-30.1,3.5-46,2.7c-26-1.2-46.6-6.2-46.6-6.2
c0,2.5,0.2,4.9,0.5,7.2c3.4,25.7,25.5,27.2,46.4,27.9c21.1,0.7,39.9-5.2,39.9-5.2l0.9,19.1c0,0-14.8,7.9-41.1,9.4
c-14.5,0.8-32.5-0.4-53.5-5.9c-45.5-12-53.3-60.6-54.5-109.8c-0.4-14.6-0.1-28.4-0.1-39.9c0-50.3,33-65.1,33-65.1
C49.7,3.5,78.2,0.2,107.9,0h0.7c29.7,0.2,58.2,3.5,74.8,11.1c0,0,33,14.8,33,65.1C216.4,76.2,216.8,113.3,211.8,139.1"/>
<path class="st1" d="M65.7,96.5c0,9-7.3,16.3-16.3,16.3c-9,0-16.3-7.3-16.3-16.3s7.3-16.3,16.3-16.3C58.4,80.1,65.7,87.4,65.7,96.5
M124.5,96.5c0,9-7.3,16.3-16.3,16.3s-16.3-7.3-16.3-16.3s7.3-16.3,16.3-16.3S124.5,87.4,124.5,96.5 M183.4,96.5
c0,9-7.3,16.3-16.3,16.3c-9,0-16.3-7.3-16.3-16.3S158,80.1,167,80.1C176.1,80.1,183.4,87.4,183.4,96.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,71 @@
import { changeCompose } from '../actions/compose';
export const UTILBTNS_GOJI = 'UTILBTNS_GOJI';
export const UTILBTNS_HARUKIN = 'UTILBTNS_HARUKIN';
export function submitGoji (textarea) {
return function (dispatch, getState) {
if (!textarea.value) {
let text = [
"#ゴジモリィィィィイイ",
":goji:"
].join("\r\n");
dispatch(submitGojiRequest());
dispatch(changeCompose(text));
textarea.focus();
}
}
}
export function submitGojiRequest () {
return {
type: UTILBTNS_GOJI
}
}
export function submitHarukin (textarea) {
return function (dispatch, getState) {
const HARUKINS = [":harukin: ", ":harukin_old: ", ":harukin_ika: ", ":harukin_tako: "];
const MAX = 6;
if (!textarea.value) {
let text = "";
let quantity = Math.round(Math.random() * MAX + 1);
let type = Math.round(Math.random() * (HARUKINS.length - 1));
let harukin = HARUKINS[type];
switch (quantity) {
default:
text = [
harukin.repeat(quantity),
"🔥 ".repeat(quantity)
].join("\r\n");
break;
case MAX + 1:
text = `${harukin}💕\r\n`.repeat(6);
break;
}
dispatch(submitHarukinRequest());
dispatch(changeCompose(text));
textarea.focus();
}
}
}
export function submitHarukinRequest () {
return {
type: UTILBTNS_HARUKIN
}
}

View File

@ -1,5 +1,7 @@
import { fetchRelationships } from './accounts';
import api, { getLinks } from '../api';
import { importFetchedStatuses } from './importer';
import { uniq } from '../utils/uniq';
export const BOOKMARKED_STATUSES_FETCH_REQUEST = 'BOOKMARKED_STATUSES_FETCH_REQUEST';
export const BOOKMARKED_STATUSES_FETCH_SUCCESS = 'BOOKMARKED_STATUSES_FETCH_SUCCESS';
@ -20,6 +22,7 @@ export function fetchBookmarkedStatuses() {
api(getState).get('/api/v1/bookmarks').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data));
dispatch(fetchRelationships(uniq(response.data.map(item => item.reblog ? item.reblog.account.id : item.account.id))));
dispatch(fetchBookmarkedStatusesSuccess(response.data, next ? next.uri : null));
}).catch(error => {
dispatch(fetchBookmarkedStatusesFail(error));
@ -61,6 +64,7 @@ export function expandBookmarkedStatuses() {
api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data));
dispatch(fetchRelationships(uniq(response.data.map(item => item.reblog ? item.reblog.account.id : item.account.id))));
dispatch(expandBookmarkedStatusesSuccess(response.data, next ? next.uri : null));
}).catch(error => {
dispatch(expandBookmarkedStatusesFail(error));

View File

@ -20,6 +20,8 @@ export const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL';
export const COMPOSE_REPLY = 'COMPOSE_REPLY';
export const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL';
export const COMPOSE_DIRECT = 'COMPOSE_DIRECT';
export const COMPOSE_QUOTE = 'COMPOSE_QUOTE';
export const COMPOSE_QUOTE_CANCEL = 'COMPOSE_QUOTE_CANCEL';
export const COMPOSE_MENTION = 'COMPOSE_MENTION';
export const COMPOSE_RESET = 'COMPOSE_RESET';
export const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST';
@ -100,6 +102,23 @@ export function cancelReplyCompose() {
};
};
export function quoteCompose(status, routerHistory) {
return (dispatch, getState) => {
dispatch({
type: COMPOSE_QUOTE,
status: status,
});
ensureComposeIsVisible(getState, routerHistory);
};
};
export function cancelQuoteCompose() {
return {
type: COMPOSE_QUOTE_CANCEL,
};
};
export function resetCompose() {
return {
type: COMPOSE_RESET,
@ -147,6 +166,7 @@ export function submitCompose(routerHistory) {
spoiler_text: getState().getIn(['compose', 'spoiler']) ? getState().getIn(['compose', 'spoiler_text'], '') : '',
visibility: getState().getIn(['compose', 'privacy']),
poll: getState().getIn(['compose', 'poll'], null),
quote_id: getState().getIn(['compose', 'quote_from'], null),
}, {
headers: {
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),

View File

@ -1,5 +1,7 @@
import { fetchRelationships } from './accounts';
import api, { getLinks } from '../api';
import { importFetchedStatuses } from './importer';
import { uniq } from '../utils/uniq';
export const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST';
export const FAVOURITED_STATUSES_FETCH_SUCCESS = 'FAVOURITED_STATUSES_FETCH_SUCCESS';
@ -20,6 +22,7 @@ export function fetchFavouritedStatuses() {
api(getState).get('/api/v1/favourites').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data));
dispatch(fetchRelationships(uniq(response.data.map(item => item.reblog ? item.reblog.account.id : item.account.id))));
dispatch(fetchFavouritedStatusesSuccess(response.data, next ? next.uri : null));
}).catch(error => {
dispatch(fetchFavouritedStatusesFail(error));
@ -64,6 +67,7 @@ export function expandFavouritedStatuses() {
api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data));
dispatch(fetchRelationships(uniq(response.data.map(item => item.reblog ? item.reblog.account.id : item.account.id))));
dispatch(expandFavouritedStatusesSuccess(response.data, next ? next.uri : null));
}).catch(error => {
dispatch(expandFavouritedStatusesFail(error));

View File

@ -70,6 +70,10 @@ export function importFetchedStatuses(statuses) {
processStatus(status.reblog);
}
if (status.quote && status.quote.id) {
processStatus(status.quote);
}
if (status.poll && status.poll.id) {
pushUnique(polls, normalizePoll(status.poll));
}

View File

@ -62,6 +62,8 @@ export function normalizeStatus(status, normalOldStatus) {
normalStatus.spoilerHtml = normalOldStatus.get('spoilerHtml');
normalStatus.spoiler_text = normalOldStatus.get('spoiler_text');
normalStatus.hidden = normalOldStatus.get('hidden');
normalStatus.quote = normalOldStatus.get('quote');
normalStatus.quote_hidden = normalOldStatus.get('quote_hidden');
} else {
// If the status has a CW but no contents, treat the CW as if it were the
// status' contents, to avoid having a CW toggle with seemingly no effect.
@ -78,6 +80,29 @@ export function normalizeStatus(status, normalOldStatus) {
normalStatus.contentHtml = emojify(normalStatus.content, emojiMap);
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap);
normalStatus.hidden = expandSpoilers ? false : spoilerText.length > 0 || normalStatus.sensitive;
if (status.quote && status.quote.id) {
const quote_spoilerText = status.quote.spoiler_text || '';
const quote_searchContent = [quote_spoilerText, status.quote.content].join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n');
const quote_emojiMap = makeEmojiMap(normalStatus.quote);
const quote_account_emojiMap = makeEmojiMap(status.quote.account);
const displayName = normalStatus.quote.account.display_name.length === 0 ? normalStatus.quote.account.username : normalStatus.quote.account.display_name;
normalStatus.quote.account.display_name_html = emojify(escapeTextContentForBrowser(displayName), quote_account_emojiMap);
normalStatus.quote.search_index = domParser.parseFromString(quote_searchContent, 'text/html').documentElement.textContent;
let docElem = domParser.parseFromString(normalStatus.quote.content, 'text/html').documentElement;
Array.from(docElem.querySelectorAll('p,br'), line => {
let parentNode = line.parentNode;
if (line.nextSibling) {
parentNode.insertBefore(document.createTextNode(' '), line.nextSibling);
}
});
let _contentHtml = docElem.textContent;
normalStatus.quote.contentHtml = '<p>'+emojify(_contentHtml.substr(0, 150), quote_emojiMap) + (_contentHtml.substr(150) ? '...' : '')+'</p>';
normalStatus.quote.spoilerHtml = emojify(escapeTextContentForBrowser(quote_spoilerText), quote_emojiMap);
normalStatus.quote_hidden = expandSpoilers ? false : quote_spoilerText.length > 0 || normalStatus.quote.sensitive;
}
}
return normalStatus;

View File

@ -46,7 +46,7 @@ defineMessages({
});
const fetchRelatedRelationships = (dispatch, notifications) => {
const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id);
const accountIds = notifications.map(item => item.account.id);
if (accountIds.length > 0) {
dispatch(fetchRelationships(accountIds));

View File

@ -30,6 +30,9 @@ export const STATUS_COLLAPSE = 'STATUS_COLLAPSE';
export const REDRAFT = 'REDRAFT';
export const QUOTE_REVEAL = 'QUOTE_REVEAL';
export const QUOTE_HIDE = 'QUOTE_HIDE';
export function fetchStatusRequest(id, skipLoading) {
return {
type: STATUS_FETCH_REQUEST,
@ -272,3 +275,25 @@ export function toggleStatusCollapse(id, isCollapsed) {
isCollapsed,
};
}
export function hideQuote(ids) {
if (!Array.isArray(ids)) {
ids = [ids];
}
return {
type: QUOTE_HIDE,
ids,
};
};
export function revealQuote(ids) {
if (!Array.isArray(ids)) {
ids = [ids];
}
return {
type: QUOTE_REVEAL,
ids,
};
};

View File

@ -1,9 +1,11 @@
import { fetchRelationships } from './accounts';
import { importFetchedStatus, importFetchedStatuses } from './importer';
import { submitMarkers } from './markers';
import api, { getLinks } from 'mastodon/api';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import compareId from 'mastodon/compare_id';
import { usePendingItems as preferPendingItems } from 'mastodon/initial_state';
import { uniq } from '../utils/uniq';
export const TIMELINE_UPDATE = 'TIMELINE_UPDATE';
export const TIMELINE_DELETE = 'TIMELINE_DELETE';
@ -39,6 +41,7 @@ export function updateTimeline(timeline, status, accept) {
}
dispatch(importFetchedStatus(status));
dispatch(fetchRelationships([status.reblog ? status.reblog.account.id : status.account.id, status.quote ? status.quote.account.id : null].filter(function(e){return e})));
dispatch({
type: TIMELINE_UPDATE,
@ -111,6 +114,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
api(getState).get(path, { params }).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedStatuses(response.data));
dispatch(fetchRelationships(uniq(response.data.map(item => item.reblog ? item.reblog.account.id : item.account.id).concat(response.data.map(item => item.quote ? item.quote.account.id : null)).filter(function(e){return e}))));
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.status === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems));
if (timelineId === 'home') {

View File

@ -0,0 +1,90 @@
import React from 'react';
import Motion from 'react-motion/lib/Motion';
import spring from 'react-motion/lib/spring';
import PropTypes from 'prop-types';
class IconButton extends React.PureComponent {
static propTypes = {
className: PropTypes.string,
title: PropTypes.string.isRequired,
icon: PropTypes.string.isRequired,
onClick: PropTypes.func,
size: PropTypes.number,
active: PropTypes.bool,
style: PropTypes.object,
activeStyle: PropTypes.object,
disabled: PropTypes.bool,
inverted: PropTypes.bool,
animate: PropTypes.bool,
overlay: PropTypes.bool,
};
static defaultProps = {
size: 18,
active: false,
disabled: false,
animate: false,
overlay: false,
};
handleClick = (e) => {
e.preventDefault();
if (!this.props.disabled) {
this.props.onClick(e);
}
}
render () {
const style = {
fontSize: `${this.props.size}px`,
width: `${this.props.size * 1.28571429}px`,
height: `${this.props.size * 1.28571429}px`,
lineHeight: `${this.props.size}px`,
...this.props.style,
...(this.props.active ? this.props.activeStyle : {}),
};
const classes = ['icon-button'];
if (this.props.active) {
classes.push('active');
}
if (this.props.disabled) {
classes.push('disabled');
}
if (this.props.inverted) {
classes.push('inverted');
}
if (this.props.overlay) {
classes.push('overlayed');
}
if (this.props.className) {
classes.push(this.props.className);
}
return (
<Motion defaultStyle={{ rotate: this.props.active ? 180 : 0 }} style={{ rotate: this.props.animate ? spring(this.props.active ? 0 : 180) : 0 }}>
{({ rotate }) =>
<button
aria-label={this.props.title}
title={this.props.title}
className={classes.join(' ')}
onClick={this.handleClick}
style={style}
>
<i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${this.props.icon}`} aria-hidden='true' />
</button>
}
</Motion>
);
}
}
export default IconButton;

View File

@ -236,10 +236,12 @@ class MediaGallery extends React.PureComponent {
visible: PropTypes.bool,
autoplay: PropTypes.bool,
onToggleVisibility: PropTypes.func,
quote: PropTypes.bool,
};
static defaultProps = {
standalone: false,
quote: false,
};
state = {
@ -310,7 +312,7 @@ class MediaGallery extends React.PureComponent {
}
render () {
const { media, intl, sensitive, height, defaultWidth, standalone, autoplay } = this.props;
const { media, intl, sensitive, height, defaultWidth, standalone, autoplay, quote } = this.props;
const { visible } = this.state;
const width = this.state.width || defaultWidth;
@ -332,6 +334,10 @@ class MediaGallery extends React.PureComponent {
const size = media.take(4).size;
const uncached = media.every(attachment => attachment.get('type') === 'unknown');
if (quote && style.height) {
style.height /= 2;
}
if (standalone && this.isFullSizeEligible()) {
children = <Item standalone autoplay={autoplay} onClick={this.handleClick} attachment={media.get(0)} displayWidth={width} visible={visible} />;
} else {

View File

@ -39,6 +39,9 @@ class Poll extends ImmutablePureComponent {
static getDerivedStateFromProps (props, state) {
const { poll, intl } = props;
if (!poll) {
return null;
}
const expires_at = poll.get('expires_at');
const expired = poll.get('expired') || expires_at !== null && (new Date(expires_at)).getTime() < intl.now();
return (expired === state.expired) ? null : { expired };
@ -59,7 +62,7 @@ class Poll extends ImmutablePureComponent {
_setupTimer () {
const { poll, intl } = this.props;
clearTimeout(this._timer);
if (!this.state.expired) {
if (!this.state.expired && !!poll) {
const delay = (new Date(poll.get('expires_at'))).getTime() - intl.now();
this._timer = setTimeout(() => {
this.setState({ expired: true });

View File

@ -1,4 +1,5 @@
import React from 'react';
import { connect } from 'react-redux';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import Avatar from './avatar';
@ -23,6 +24,29 @@ import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_
// to use the progress bar to show download progress
import Bundle from '../features/ui/components/bundle';
const mapStateToProps = (state, props) => {
let status = props.status;
if (status === null) {
return null;
}
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
status = status.get('reblog');
}
if (status.get('quote', null) === null) {
return {
quote_muted: status.get('quote_id', null) ? true : false,
};
}
const id = status.getIn(['quote', 'account', 'id'], null);
return {
quote_muted: id !== null && (state.getIn(['relationships', id, 'muting']) || state.getIn(['relationships', id, 'blocking']) || state.getIn(['relationships', id, 'blocked_by']) || state.getIn(['relationships', id, 'domain_blocking'])) || status.getIn(['quote', 'quote_muted']),
};
};
export const textForScreenReader = (intl, status, rebloggedByText = false) => {
const displayName = status.getIn(['account', 'display_name']);
@ -59,7 +83,8 @@ const messages = defineMessages({
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
});
export default @injectIntl
export default @connect(mapStateToProps)
@injectIntl
class Status extends ImmutablePureComponent {
static contextTypes = {
@ -70,6 +95,7 @@ class Status extends ImmutablePureComponent {
status: ImmutablePropTypes.map,
account: ImmutablePropTypes.map,
otherAccounts: ImmutablePropTypes.list,
quote_muted: PropTypes.bool,
onClick: PropTypes.func,
onReply: PropTypes.func,
onFavourite: PropTypes.func,
@ -85,6 +111,7 @@ class Status extends ImmutablePureComponent {
onHeightChange: PropTypes.func,
onToggleHidden: PropTypes.func,
onToggleCollapsed: PropTypes.func,
onQuoteToggleHidden: PropTypes.func,
muted: PropTypes.bool,
hidden: PropTypes.bool,
unread: PropTypes.bool,
@ -101,6 +128,7 @@ class Status extends ImmutablePureComponent {
inUse: PropTypes.bool,
available: PropTypes.bool,
}),
contextType: PropTypes.string,
};
// Avoid checking props that are functions (and whose equality will always
@ -112,10 +140,12 @@ class Status extends ImmutablePureComponent {
'hidden',
'unread',
'pictureInPicture',
'quote_muted',
];
state = {
showMedia: defaultMediaVisibility(this.props.status),
showQuoteMedia: defaultMediaVisibility(this.props.status ? this.props.status.get('quote', null) : null),
statusId: undefined,
};
@ -123,6 +153,7 @@ class Status extends ImmutablePureComponent {
if (nextProps.status && nextProps.status.get('id') !== prevState.statusId) {
return {
showMedia: defaultMediaVisibility(nextProps.status),
showQuoteMedia: defaultMediaVisibility(nextProps.status.get('quote', null)),
statusId: nextProps.status.get('id'),
};
} else {
@ -134,6 +165,10 @@ class Status extends ImmutablePureComponent {
this.setState({ showMedia: !this.state.showMedia });
}
handleToggleQuoteMediaVisibility = () => {
this.setState({ showQuoteMedia: !this.state.showQuoteMedia });
}
handleClick = () => {
if (this.props.onClick) {
this.props.onClick();
@ -164,6 +199,15 @@ class Status extends ImmutablePureComponent {
}
}
handleQuoteClick = () => {
if (!this.context.router) {
return;
}
const { status } = this.props;
this.context.router.history.push(`/statuses/${status.getIn(['reblog', 'quote', 'id'], status.getIn(['quote', 'id']))}`);
}
handleAccountClick = (e) => {
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
const id = e.currentTarget.getAttribute('data-id');
@ -180,6 +224,10 @@ class Status extends ImmutablePureComponent {
this.props.onToggleCollapsed(this._properStatus(), isCollapsed);
}
handleExpandedQuoteToggle = () => {
this.props.onQuoteToggleHidden(this._properStatus());
};
renderLoadingMediaGallery () {
return <div className='media-gallery' style={{ height: '110px' }} />;
}
@ -197,10 +245,19 @@ class Status extends ImmutablePureComponent {
this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), options);
}
handleOpenVideoQuote = (options) => {
const status = this._properQuoteStatus();
this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), options);
}
handleOpenMedia = (media, index) => {
this.props.onOpenMedia(this._properStatus().get('id'), media, index);
}
handleOpenMediaQuote = (media, index) => {
this.props.onOpenMedia(this._properQuoteStatus().get('id'), media, index);
}
handleHotkeyOpenMedia = e => {
const { onOpenMedia, onOpenVideo } = this.props;
const status = this._properStatus();
@ -275,6 +332,16 @@ class Status extends ImmutablePureComponent {
}
}
_properQuoteStatus () {
const status = this._properStatus();
if (status.get('quote', null) !== null && typeof status.get('quote') === 'object') {
return status.get('quote');
} else {
return status;
}
}
handleRef = c => {
this.node = c;
}
@ -283,7 +350,7 @@ class Status extends ImmutablePureComponent {
let media = null;
let statusAvatar, prepend, rebloggedByText;
const { intl, hidden, featured, otherAccounts, unread, showThread, scrollKey, pictureInPicture } = this.props;
const { intl, hidden, featured, otherAccounts, unread, showThread, scrollKey, pictureInPicture, contextType, quote_muted } = this.props;
let { status, account, ...other } = this.props;
@ -354,10 +421,10 @@ class Status extends ImmutablePureComponent {
status = status.get('reblog');
}
if (pictureInPicture.get('inUse')) {
media = <PictureInPicturePlaceholder width={this.props.cachedMediaWidth} />;
} else if (status.get('media_attachments').size > 0) {
if (this.props.muted) {
if (status.get('media_attachments').size > 0) {
if (pictureInPicture.get('inUse')) {
media = <PictureInPicturePlaceholder width={this.props.cachedMediaWidth} />;
} else if (this.props.muted) {
media = (
<AttachmentList
compact
@ -459,6 +526,132 @@ class Status extends ImmutablePureComponent {
const visibilityIcon = visibilityIconInfo[status.get('visibility')];
let quote = null;
if (status.get('quote', null) !== null && typeof status.get('quote') === 'object') {
let quote_status = status.get('quote');
let quote_media = null;
if (quote_status.get('media_attachments').size > 0) {
if (pictureInPicture.get('inUse')) {
quote_media = <PictureInPicturePlaceholder width={this.props.cachedMediaWidth} />;
} else if (this.props.muted) {
quote_media = (
<AttachmentList
compact
media={quote_status.get('media_attachments')}
/>
);
} else if (quote_status.getIn(['media_attachments', 0, 'type']) === 'audio') {
const attachment = quote_status.getIn(['media_attachments', 0]);
quote_media = (
<Bundle fetchComponent={Audio} loading={this.renderLoadingAudioPlayer} >
{Component => (
<Component
src={attachment.get('url')}
alt={attachment.get('description')}
poster={attachment.get('preview_url') || quote_status.getIn(['account', 'avatar_static'])}
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
accentColor={attachment.getIn(['meta', 'colors', 'accent'])}
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
width={this.props.cachedMediaWidth}
height={70}
cacheWidth={this.props.cacheMediaWidth}
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
/>
)}
</Bundle>
);
} else if (quote_status.getIn(['media_attachments', 0, 'type']) === 'video') {
const attachment = quote_status.getIn(['media_attachments', 0]);
quote_media = (
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
{Component => (
<Component
preview={attachment.get('preview_url')}
frameRate={attachment.getIn(['meta', 'original', 'frame_rate'])}
blurhash={attachment.get('blurhash')}
src={attachment.get('url')}
alt={attachment.get('description')}
width={this.props.cachedMediaWidth}
height={110}
inline
sensitive={quote_status.get('sensitive')}
onOpenVideo={this.handleOpenVideoQuote}
cacheWidth={this.props.cacheMediaWidth}
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
visible={this.state.showQuoteMedia}
onToggleVisibility={this.handleToggleQuoteMediaVisibility}
quote
/>
)}
</Bundle>
);
} else {
quote_media = (
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>
{Component => (
<Component
media={quote_status.get('media_attachments')}
sensitive={quote_status.get('sensitive')}
height={110}
onOpenMedia={this.handleOpenMediaQuote}
cacheWidth={this.props.cacheMediaWidth}
defaultWidth={this.props.cachedMediaWidth}
visible={this.state.showQuoteMedia}
onToggleVisibility={this.handleToggleQuoteMediaVisibility}
quote
/>
)}
</Bundle>
);
}
}
if (quote_muted) {
quote = (
<div className={classNames('quote-status', `status-${quote_status.get('visibility')}`, { muted: this.props.muted })} data-id={quote_status.get('id')}>
<div className={classNames('status__content muted-quote', { 'status__content--with-action': this.context.router })}>
<FormattedMessage id='status.muted_quote' defaultMessage='Muted quote' />
</div>
</div>
);
} else if (quote_status.get('visibility') === 'unlisted' && !!contextType && ['public', 'community', 'hashtag'].includes(contextType.split(':', 2)[0])) {
quote = (
<div className={classNames('quote-status', `status-${quote_status.get('visibility')}`, { muted: this.props.muted })} data-id={quote_status.get('id')}>
<div className={classNames('status__content unlisted-quote', { 'status__content--with-action': this.context.router })}>
<button onClick={this.handleQuoteClick}>
<FormattedMessage id='status.unlisted_quote' defaultMessage='Unlisted quote' />
</button>
</div>
</div>
);
} else {
quote = (
<div className={classNames('quote-status', `status-${quote_status.get('visibility')}`, { muted: this.props.muted })} data-id={quote_status.get('id')}>
<div className='status__info'>
<a onClick={this.handleAccountClick} target='_blank' data-id={quote_status.getIn(['account', 'id'])} href={quote_status.getIn(['account', 'url'])} title={quote_status.getIn(['account', 'acct'])} className='status__display-name'>
<div className='status__avatar'><Avatar account={quote_status.get('account')} size={18} /></div>
<DisplayName account={quote_status.get('account')} />
</a>
</div>
<StatusContent status={quote_status} onClick={this.handleQuoteClick} expanded={!status.get('quote_hidden')} onExpandedToggle={this.handleExpandedQuoteToggle} quote />
{quote_media}
</div>
);
}
} else if (quote_muted) {
quote = (
<div className={classNames('quote-status', { muted: this.props.muted })}>
<div className={classNames('status__content muted-quote', { 'status__content--with-action': this.context.router })}>
<FormattedMessage id='status.muted_quote' defaultMessage='Muted quote' />
</div>
</div>
);
}
return (
<HotKeys handlers={handlers}>
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}>
@ -483,6 +676,7 @@ class Status extends ImmutablePureComponent {
<StatusContent status={status} onClick={this.handleClick} expanded={!status.get('hidden')} showThread={showThread} onExpandedToggle={this.handleExpandedToggle} collapsable onCollapsedToggle={this.handleCollapsedToggle} />
{quote}
{media}
<StatusActionBar scrollKey={scrollKey} status={status} account={account} {...other} />

View File

@ -23,7 +23,9 @@ const messages = defineMessages({
reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' },
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
cannot_quote: { id: 'status.cannot_quote', defaultMessage: 'This post cannot be quoted' },
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
quote: { id: 'status.quote', defaultMessage: 'Quote' },
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
removeBookmark: { id: 'status.remove_bookmark', defaultMessage: 'Remove bookmark' },
@ -61,6 +63,7 @@ class StatusActionBar extends ImmutablePureComponent {
onReply: PropTypes.func,
onFavourite: PropTypes.func,
onReblog: PropTypes.func,
onQuote: PropTypes.func,
onDelete: PropTypes.func,
onDirect: PropTypes.func,
onMention: PropTypes.func,
@ -121,6 +124,14 @@ class StatusActionBar extends ImmutablePureComponent {
}
}
handleBookmarkClick = () => {
this.props.onBookmark(this.props.status);
}
handleReblogClick = (e) => {
this.props.onReblog(this.props.status, e);
}
_openInteractionDialog = type => {
window.open(`/interact/${this.props.status.get('id')}?type=${type}`, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
}
@ -129,6 +140,10 @@ class StatusActionBar extends ImmutablePureComponent {
this.props.onBookmark(this.props.status);
}
handleQuoteClick = () => {
this.props.onQuote(this.props.status, this.context.router.history);
}
handleDeleteClick = () => {
this.props.onDelete(this.props.status, this.context.router.history);
}
@ -326,7 +341,7 @@ class StatusActionBar extends ImmutablePureComponent {
<IconButton className='status__action-bar-button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} counter={status.get('replies_count')} obfuscateCount />
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} />
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
<IconButton className='status__action-bar-button' disabled={anonymousAccess || !publicStatus} title={!publicStatus ? intl.formatMessage(messages.cannot_quote) : intl.formatMessage(messages.quote)} icon='quote-right' onClick={this.handleQuoteClick} />
{shareButton}
<div className='status__action-bar-dropdown'>

View File

@ -24,6 +24,7 @@ export default class StatusContent extends React.PureComponent {
onClick: PropTypes.func,
collapsable: PropTypes.bool,
onCollapsedToggle: PropTypes.func,
quote: PropTypes.bool,
};
state = {
@ -125,6 +126,15 @@ export default class StatusContent extends React.PureComponent {
}
}
onQuoteClick = (statusId, e) => {
let statusUrl = `/statuses/${statusId}`;
if (this.context.router && e.button === 0) {
e.preventDefault();
this.context.router.history.push(statusUrl);
}
}
handleMouseDown = (e) => {
this.startXY = [e.clientX, e.clientY];
}
@ -168,11 +178,12 @@ export default class StatusContent extends React.PureComponent {
}
render () {
const { status } = this.props;
const { status, quote } = this.props;
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
const renderReadMore = this.props.onClick && status.get('collapsed');
const renderViewThread = this.props.showThread && status.get('in_reply_to_id') && status.get('in_reply_to_account_id') === status.getIn(['account', 'id']);
const renderShowPoll = !!status.get('poll');
const content = { __html: status.get('contentHtml') };
const spoilerContent = { __html: status.get('spoilerHtml') };
@ -194,6 +205,16 @@ export default class StatusContent extends React.PureComponent {
</button>
);
const showPollButton = (
<button className='status__content__read-more-button' onClick={this.props.onClick} key='show-poll'>
<FormattedMessage id='status.show_poll' defaultMessage='Show poll' /><Icon id='angle-right' fixedWidth />
</button>
);
const pollContainer = (
<PollContainer pollId={status.get('poll')} />
);
if (status.get('spoiler_text').length > 0) {
let mentionsPlaceholder = '';
@ -221,7 +242,7 @@ export default class StatusContent extends React.PureComponent {
<div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''} translate`} dangerouslySetInnerHTML={content} />
{!hidden && !!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
{!hidden && renderShowPoll && quote ? showPollButton : pollContainer}
{renderViewThread && showThreadButton}
</div>
@ -231,7 +252,7 @@ export default class StatusContent extends React.PureComponent {
<div className={classNames} ref={this.setRef} tabIndex='0' onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} key='status-content' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} />
{!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
{renderShowPoll && quote ? showPollButton : pollContainer}
{renderViewThread && showThreadButton}
</div>,
@ -247,7 +268,7 @@ export default class StatusContent extends React.PureComponent {
<div className={classNames} ref={this.setRef} tabIndex='0' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
<div className='status__content__text status__content__text--visible translate' dangerouslySetInnerHTML={content} />
{!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
{renderShowPoll && quote ? showPollButton : pollContainer}
{renderViewThread && showThreadButton}
</div>

View File

@ -4,6 +4,7 @@ import Status from '../components/status';
import { makeGetStatus, makeGetPictureInPicture } from '../selectors';
import {
replyCompose,
quoteCompose,
mentionCompose,
directCompose,
} from '../actions/compose';
@ -24,6 +25,8 @@ import {
hideStatus,
revealStatus,
toggleStatusCollapse,
hideQuote,
revealQuote,
} from '../actions/statuses';
import {
unmuteAccount,
@ -50,6 +53,8 @@ const messages = defineMessages({
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
quoteConfirm: { id: 'confirmations.quote.confirm', defaultMessage: 'Quote' },
quoteMessage: { id: 'confirmations.quote.message', defaultMessage: 'Quoting now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
});
@ -99,6 +104,22 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}
},
onQuote (status, router) {
dispatch((_, getState) => {
let state = getState();
if (state.getIn(['compose', 'text']).trim().length !== 0) {
dispatch(openModal('CONFIRM', {
message: intl.formatMessage(messages.quoteMessage),
confirm: intl.formatMessage(messages.quoteConfirm),
onConfirm: () => dispatch(quoteCompose(status, router)),
}));
} else {
dispatch(quoteCompose(status, router));
}
});
},
onFavourite (status) {
if (status.get('favourited')) {
dispatch(unfavourite(status));
@ -215,6 +236,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
dispatch(deployPictureInPicture(status.get('id'), status.getIn(['account', 'id']), type, mediaProps));
},
onQuoteToggleHidden (status) {
if (status.get('quote_hidden')) {
dispatch(revealQuote(status.get('id')));
} else {
dispatch(hideQuote(status.get('id')));
}
},
});
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status));

View File

@ -47,7 +47,9 @@ export default class TimelineContainer extends React.PureComponent {
<IntlProvider locale={locale} messages={messages}>
<Provider store={store}>
<Fragment>
{timeline}
<div className='standalone-timeline'>
{timeline}
</div>
{ReactDOM.createPortal(
<ModalContainer />,

View File

@ -0,0 +1,127 @@
import React from 'react';
import Immutable from 'immutable';
import PropTypes from 'prop-types';
import Link from 'react-router-dom/Link';
import { defineMessages, injectIntl } from 'react-intl';
import IconButton from '../../../components/announcement_icon_button';
import Motion from 'react-motion/lib/Motion';
import spring from 'react-motion/lib/spring';
const Collapsable = ({ fullHeight, minHeight, isVisible, children }) => (
<Motion defaultStyle={{ height: isVisible ? fullHeight : minHeight }} style={{ height: spring(!isVisible ? minHeight : fullHeight) }}>
{({ height }) =>
<div style={{ height: `${height}px`, overflow: 'hidden' }}>
{children}
</div>
}
</Motion>
);
Collapsable.propTypes = {
fullHeight: PropTypes.number.isRequired,
minHeight: PropTypes.number.isRequired,
isVisible: PropTypes.bool.isRequired,
children: PropTypes.node.isRequired,
};
const messages = defineMessages({
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
welcome: { id: 'welcome.message', defaultMessage: '{domain}へようこそ!' },
markdown: { id: 'markdown.list', defaultMessage: 'markdown一覧' },
});
const hashtags = Immutable.fromJS([
'神崎ドン自己紹介',
]);
class Announcements extends React.PureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
homeSize: PropTypes.number,
isLoading: PropTypes.bool,
};
state = {
showId: null,
isLoaded: false,
};
onClick = (announcementId, currentState) => {
this.setState({ showId: currentState.showId === announcementId ? null : announcementId });
}
nl2br (text) {
return text.split(/(\n)/g).map((line, i) => {
if (line.match(/(\n)/g)) {
return React.createElement('br', { key: i });
}
return line;
});
}
render () {
const { intl } = this.props;
return (
<ul className='announcements'>
<li>
<Collapsable isVisible={this.state.showId === 'markdown'} fullHeight={1240} minHeight={20} >
<div className='announcements__body'>
<p>{ this.nl2br(intl.formatMessage(messages.markdown, { domain: document.title }))}<br />
<br />
(半角)は半角スペースを入力する必要がある場所です(半角)だけの列は半角スペースのみが入力された列が必要であるを指します<br /><br />
見出し<br /><br />
#(半角)見出しテキスト<br /><br />
#は16個重ねることができます<br /><br />
コードブロック<br /><br />
`コード`<br /><br />
引用<br /><br />
>引用文<br />
(半角)<br />
ここから先は引用が切れます<br />
引用は複数回重ねることが可能です<br /><br />
リスト<br /><br />
(半角)<br />
+(半角)内容1<br />
+(半角)内容2<br />
(半角)<br /><br />
内容の数に制限はありません<br />
投稿トップにリストを持ってくる場合に限り1行目の(半角)は必要ありません<br />
+(半角)を1.(半角)に置き換えることで数字付きリストになります<br /><br />
上付き文字<br /><br />
_上付き文字_<br /><br />
下付き文字<br /><br />
__下付き文字__<br /><br />
小さい文字<br /><br />
___小さい文字___<br /><br />
取り消し線<br /><br />
~~取り消したい文字列~~<br /><br />
横罫線<br /><br />
___<br /><br />
リンク<br /><br />
[リンク文章](https://・・・)<br /><br />
画像<br /><br />
![画像説明](https://・・・)<br /><br />
リンク画像ともにURLにはhttps://から始まる物のみご利用可能です。
</p>
</div>
</Collapsable>
<div className='announcements__icon'>
<IconButton title={intl.formatMessage(messages.toggle_visible)} icon='caret-up' onClick={() => this.onClick('markdown', this.state)} size={20} animate active={this.state.showId === 'markdown'} />
</div>
</li>
</ul>
);
}
componentWillReceiveProps (nextProps) {
if (!this.state.isLoaded) {
if (!nextProps.isLoading && (nextProps.homeSize === 0 || this.props.homeSize !== nextProps.homeSize)) {
this.setState({ isLoaded: true });
}
}
}
}
export default injectIntl(Announcements);

View File

@ -4,6 +4,7 @@ import Button from '../../../components/button';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import ReplyIndicatorContainer from '../containers/reply_indicator_container';
import QuoteIndicatorContainer from '../containers/quote_indicator_container';
import AutosuggestTextarea from '../../../components/autosuggest_textarea';
import AutosuggestInput from '../../../components/autosuggest_input';
import PollButtonContainer from '../containers/poll_button_container';
@ -20,6 +21,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import { length } from 'stringz';
import { countableText } from '../util/counter';
import Icon from 'mastodon/components/icon';
import { UserCounter } from './user_counter';
const allowedAroundShortCode = '><\u0085\u0020\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029\u0009\u000a\u000b\u000c\u000d';
@ -28,6 +30,10 @@ const messages = defineMessages({
spoiler_placeholder: { id: 'compose_form.spoiler_placeholder', defaultMessage: 'Write your warning here' },
publish: { id: 'compose_form.publish', defaultMessage: 'Toot' },
publishLoud: { id: 'compose_form.publish_loud', defaultMessage: '{publish}!' },
utilBtns_goji: { id: 'compose_form.utilBtns_goji', defaultMessage: 'Typo!!!' },
utilBtns_harukin: { id: 'compose_form.utilBtns_harukin', defaultMessage: 'Burn Harukin' }
});
export default @injectIntl
@ -60,7 +66,9 @@ class ComposeForm extends ImmutablePureComponent {
onPickEmoji: PropTypes.func.isRequired,
showSearch: PropTypes.bool,
anyMedia: PropTypes.bool,
singleColumn: PropTypes.bool,
singleColumn: PropTypes.bool,
onGojiSubmit: PropTypes.func.isRequired,
onHarukinSubmit: PropTypes.func.isRequired
};
static defaultProps = {
@ -86,7 +94,7 @@ class ComposeForm extends ImmutablePureComponent {
const fulltext = this.getFulltextForCharacterCounting();
const isOnlyWhitespace = fulltext.length !== 0 && fulltext.trim().length === 0;
return !(isSubmitting || isUploading || isChangingUpload || length(fulltext) > 500 || (isOnlyWhitespace && !anyMedia));
return !(isSubmitting || isUploading || isChangingUpload || length(fulltext) > 2048 || (isOnlyWhitespace && !anyMedia));
}
handleSubmit = () => {
@ -193,6 +201,10 @@ class ComposeForm extends ImmutablePureComponent {
this.props.onPickEmoji(position, data, needsSpace);
}
handleOnGojiSubmit = () => this.props.onGojiSubmit(this.autosuggestTextarea.textarea);
handleOnHarukinSubmit = () => this.props.onHarukinSubmit(this.autosuggestTextarea.textarea);
render () {
const { intl, onPaste, showSearch } = this.props;
const disabled = this.props.isSubmitting;
@ -209,6 +221,7 @@ class ComposeForm extends ImmutablePureComponent {
<WarningContainer />
<ReplyIndicatorContainer />
<QuoteIndicatorContainer />
<div className={`spoiler-input ${this.props.spoiler ? 'spoiler-input--visible' : ''}`} ref={this.setRef}>
<AutosuggestInput
@ -257,11 +270,22 @@ class ComposeForm extends ImmutablePureComponent {
<PrivacyDropdownContainer />
<SpoilerButtonContainer />
</div>
<div className='character-counter__wrapper'><CharacterCounter max={500} text={this.getFulltextForCharacterCounting()} /></div>
<div className='character-counter__wrapper'><CharacterCounter max={2048} text={this.getFulltextForCharacterCounting()} /></div>
</div>
<div className='compose-form__publish'>
<div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={!this.canSubmit()} block /></div>
<div className='compose-form__publish-button-wrapper'>
<Button text={publishText} onClick={this.handleSubmit} disabled={!this.canSubmit()} block>
<span className="fa fa-send">{publishText}</span>
</Button>
</div>
</div>
<div className="compose-form__utilBtns">
<Button className="compose-form__utilBtns-goji" text={intl.formatMessage(messages.utilBtns_goji)} onClick={this.handleOnGojiSubmit} block />
<Button className="compose-form__utilBtns-harukin" text={intl.formatMessage(messages.utilBtns_harukin)} onClick={this.handleOnHarukinSubmit} block />
</div>
</div>
);

View File

@ -0,0 +1,71 @@
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import Avatar from '../../../components/avatar';
import IconButton from '../../../components/icon_button';
import DisplayName from '../../../components/display_name';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import AttachmentList from 'mastodon/components/attachment_list';
const messages = defineMessages({
cancel: { id: 'quote_indicator.cancel', defaultMessage: 'Cancel' },
});
export default @injectIntl
class QuoteIndicator extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = {
status: ImmutablePropTypes.map,
onCancel: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
handleClick = () => {
this.props.onCancel();
}
handleAccountClick = (e) => {
if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault();
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
}
}
render () {
const { status, intl } = this.props;
if (!status) {
return null;
}
const content = { __html: status.get('contentHtml') };
return (
<div className='quote-indicator'>
<div className='quote-indicator__header'>
<div className='quote-indicator__cancel'><IconButton title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} /></div>
<a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='quote-indicator__display-name'>
<div className='quote-indicator__display-avatar'><Avatar account={status.get('account')} size={24} /></div>
<DisplayName account={status.get('account')} />
</a>
</div>
<div className='quote-indicator__content' dir='auto' dangerouslySetInnerHTML={content} />
{status.get('media_attachments').size > 0 && (
<AttachmentList
compact
media={status.get('media_attachments')}
/>
)}
</div>
);
}
}

View File

@ -0,0 +1,10 @@
import React from 'react';
import PropTypes from 'prop-types';
export default class UserCounter extends React.PureComponent {
render () {
return (
<span>10</span>
);
}
}

View File

@ -0,0 +1,11 @@
import { connect } from 'react-redux';
import Announcements from '../components/announcements';
const mapStateToProps = state => {
return {
homeSize: state.getIn(['timelines', 'home', 'items']).size,
isLoading: state.getIn(['timelines', 'home', 'isLoading']),
};
};
export default connect(mapStateToProps)(Announcements);

View File

@ -11,6 +11,11 @@ import {
uploadCompose,
} from '../../../actions/compose';
import {
submitGoji,
submitHarukin
} from '../../../actions/UtilBtns';
const mapStateToProps = state => ({
text: state.getIn(['compose', 'text']),
suggestions: state.getIn(['compose', 'suggestions']),
@ -60,6 +65,14 @@ const mapDispatchToProps = (dispatch) => ({
onPickEmoji (position, data, needsSpace) {
dispatch(insertEmojiCompose(position, data, needsSpace));
},
onGojiSubmit (textarea) {
dispatch(submitGoji(textarea));
},
onHarukinSubmit (textarea) {
dispatch(submitHarukin(textarea));
},
});

View File

@ -0,0 +1,24 @@
import { connect } from 'react-redux';
import { cancelQuoteCompose } from '../../../actions/compose';
import { makeGetStatus } from '../../../selectors';
import QuoteIndicator from '../components/quote_indicator';
const makeMapStateToProps = () => {
const getStatus = makeGetStatus();
const mapStateToProps = state => ({
status: getStatus(state, { id: state.getIn(['compose', 'quote_from']) }),
});
return mapStateToProps;
};
const mapDispatchToProps = dispatch => ({
onCancel () {
dispatch(cancelQuoteCompose());
},
});
export default connect(makeMapStateToProps, mapDispatchToProps)(QuoteIndicator);

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { Fragment } from 'react';
import ComposeFormContainer from './containers/compose_form_container';
import NavigationContainer from './containers/navigation_container';
import PropTypes from 'prop-types';
@ -14,16 +14,28 @@ import SearchResultsContainer from './containers/search_results_container';
import { changeComposing } from '../../actions/compose';
import { openModal } from 'mastodon/actions/modal';
import elephantUIPlane from '../../../images/elephant_ui_plane.svg';
import { mascot } from '../../initial_state';
import { mascot, show_tab_bar_label } from '../../initial_state';
import Icon from 'mastodon/components/icon';
import { logOut } from 'mastodon/utils/log_out';
import AnnouncementsContainer from './containers/announcements_container';
import NotificationsCounterIcon from '../ui/components/notifications_counter_icon';
import classNames from 'classnames';
const messages = defineMessages({
short_start: { id: 'navigation_bar.short.getting_started', defaultMessage: 'Started' },
short_home_timeline: { id: 'navigation_bar.short.home', defaultMessage: 'Home' },
short_notifications: { id: 'navigation_bar.short.notifications', defaultMessage: 'Notif.' },
short_public: { id: 'navigation_bar.short.public_timeline', defaultMessage: 'FTL' },
short_community: { id: 'navigation_bar.short.community_timeline', defaultMessage: 'LTL' },
short_lists: { id: 'navigation_bar.short.lists', defaultMessage: 'Lists' },
short_preferences: { id: 'navigation_bar.short.preferences', defaultMessage: 'Pref.' },
short_logout: { id: 'navigation_bar.short.logout', defaultMessage: 'Logout' },
start: { id: 'getting_started.heading', defaultMessage: 'Getting started' },
home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' },
public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
compose: { id: 'navigation_bar.compose', defaultMessage: 'Compose new toot' },
@ -88,30 +100,54 @@ class Compose extends React.PureComponent {
this.props.dispatch(changeComposing(false));
}
tab (id) {
const { columns, intl: { formatMessage } } = this.props;
if (!columns.some(column => column.get('id') === id)) {
const tabParams = {
'START': { to: '/getting-started', title: formatMessage(messages.start), label: formatMessage(messages.short_start), icon_id: 'bars' },
'HOME': { to: '/timelines/home', title: formatMessage(messages.home_timeline), label: formatMessage(messages.short_home_timeline), icon_id: 'home' },
'NOTIFICATIONS': { to: '/notifications', title: formatMessage(messages.notifications), label: formatMessage(messages.short_notifications), icon_id: 'bell' },
'COMMUNITY': { to: '/timelines/public/local', title: formatMessage(messages.community), label: formatMessage(messages.short_community), icon_id: 'users' },
'PUBLIC': { to: '/timelines/public', title: formatMessage(messages.public), label: formatMessage(messages.short_public), icon_id: 'globe' },
'LIST': { to: '/lists', title: formatMessage(messages.lists), label: formatMessage(messages.short_lists), icon_id: 'list-ul' },
'PREFERENCES': { href: '/settings/preferences', title: formatMessage(messages.preferences), label: formatMessage(messages.short_preferences), icon_id: 'cog' },
'SIGN_OUT': { href: '/auth/sign_out', title: formatMessage(messages.logout), label: formatMessage(messages.short_logout), icon_id: 'sign-out', method: 'delete' },
};
const { href, to, title, label, icon_id, method } = tabParams[id];
const icon = (id === 'NOTIFICATIONS') ? <NotificationsCounterIcon /> : <Icon id={icon_id} fixedWidth />;
if (href) {
return (
<a href={href} className={classNames('drawer__tab', { 'short-label': show_tab_bar_label })} title={title} aria-label={title} data-method={method}>{icon}<span className='drawer__tab__short-label'>{label}</span></a>
);
} else {
return (
<Link to={to} className={classNames('drawer__tab', { 'short-label': show_tab_bar_label })} title={title} aria-label={title}>{icon}<span className='drawer__tab__short-label'>{label}</span></Link>
);
}
}
return null;
}
render () {
const { multiColumn, showSearch, isSearchPage, intl } = this.props;
let header = '';
if (multiColumn) {
const { columns } = this.props;
const defaultTabIds = ['START', 'HOME', 'NOTIFICATIONS', 'COMMUNITY', 'PUBLIC', 'LIST', 'PREFERENCES', 'SIGN_OUT'];
// const defaultTabIds = ['START', 'HOME', 'NOTIFICATIONS', 'PUBLIC', 'LIST', 'PREFERENCES', 'SIGN_OUT'];
let tabs = defaultTabIds;
header = (
<nav className='drawer__header'>
<Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)} aria-label={intl.formatMessage(messages.start)}><Icon id='bars' fixedWidth /></Link>
{!columns.some(column => column.get('id') === 'HOME') && (
<Link to='/timelines/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><Icon id='home' fixedWidth /></Link>
)}
{!columns.some(column => column.get('id') === 'NOTIFICATIONS') && (
<Link to='/notifications' className='drawer__tab' title={intl.formatMessage(messages.notifications)} aria-label={intl.formatMessage(messages.notifications)}><Icon id='bell' fixedWidth /></Link>
)}
{!columns.some(column => column.get('id') === 'COMMUNITY') && (
<Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><Icon id='users' fixedWidth /></Link>
)}
{!columns.some(column => column.get('id') === 'PUBLIC') && (
<Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe' fixedWidth /></Link>
)}
<a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)} aria-label={intl.formatMessage(messages.preferences)}><Icon id='cog' fixedWidth /></a>
<a href='/auth/sign_out' className='drawer__tab' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)} onClick={this.handleLogoutClick}><Icon id='sign-out' fixedWidth /></a>
{tabs.map(tabId => (
<Fragment key={tabId}>{this.tab(tabId)}</Fragment>
))}
</nav>
);
}
@ -127,6 +163,7 @@ class Compose extends React.PureComponent {
<NavigationContainer onClose={this.onBlur} />
<ComposeFormContainer />
<AnnouncementsContainer />
<div className='drawer__inner__mastodon'>
<img alt='' draggable='false' src={mascot || elephantUIPlane} />

View File

@ -18,6 +18,7 @@ import TrendsContainer from './containers/trends_container';
const messages = defineMessages({
home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' },
notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' },
admin_notifications: { id: 'tabs_bar.admin_notifications', defaultMessage: 'Admin Notifications' },
public_timeline: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' },
settings_subheading: { id: 'column_subheading.settings', defaultMessage: 'Settings' },
community_timeline: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' },
@ -107,9 +108,10 @@ class GettingStarted extends ImmutablePureComponent {
if (profile_directory) {
navItems.push(
<ColumnLink key='directory' icon='address-book' text={intl.formatMessage(messages.profile_directory)} to='/directory' />,
<ColumnLink key='infomation' icon='bullhorn' text={intl.formatMessage(messages.admin_notifications)} to='/timelines/tag/Yづinfo' />,
);
height += 48;
height += 48*2;
}
navItems.push(
@ -120,9 +122,10 @@ class GettingStarted extends ImmutablePureComponent {
} else if (profile_directory) {
navItems.push(
<ColumnLink key='directory' icon='address-book' text={intl.formatMessage(messages.profile_directory)} to='/directory' />,
<ColumnLink key='infomation' icon='bullhorn' text={intl.formatMessage(messages.admin_notifications)} to='/timelines/tag/Yづinfo' />,
);
height += 48;
height += 48*2;
}
if (multiColumn && !columns.find(item => item.get('id') === 'HOME')) {

View File

@ -7,7 +7,7 @@ import IconButton from 'mastodon/components/icon_button';
import classNames from 'classnames';
import { me, boostModal } from 'mastodon/initial_state';
import { defineMessages, injectIntl } from 'react-intl';
import { replyCompose } from 'mastodon/actions/compose';
import { replyCompose, quoteCompose } from 'mastodon/actions/compose';
import { reblog, favourite, unreblog, unfavourite } from 'mastodon/actions/interactions';
import { makeGetStatus } from 'mastodon/selectors';
import { initBoostModal } from 'mastodon/actions/boosts';
@ -20,9 +20,13 @@ const messages = defineMessages({
reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' },
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
cannot_quote: { id: 'status.cannot_quote', defaultMessage: 'This post cannot be quoted' },
quote: { id: 'status.quote', defaultMessage: 'Quote' },
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
quoteConfirm: { id: 'confirmations.quote.confirm', defaultMessage: 'Quote' },
quoteMessage: { id: 'confirmations.quote.message', defaultMessage: 'Quoting now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
open: { id: 'status.open', defaultMessage: 'Expand this status' },
});
@ -119,6 +123,31 @@ class Footer extends ImmutablePureComponent {
router.history.push(`/statuses/${status.get('id')}`);
}
_performQuote = () => {
const { dispatch, status, onClose } = this.props;
const { router } = this.context;
if (onClose) {
onClose();
}
dispatch(quoteCompose(status, router.history));
};
handleQuoteClick = () => {
const { dispatch, askReplyConfirmation, intl } = this.props;
if (askReplyConfirmation) {
dispatch(openModal('CONFIRM', {
message: intl.formatMessage(messages.quoteMessage),
confirm: intl.formatMessage(messages.quoteConfirm),
onConfirm: this._performQuote,
}));
} else {
this._performQuote();
}
}
render () {
const { status, intl, withOpenButton } = this.props;
@ -152,6 +181,7 @@ class Footer extends ImmutablePureComponent {
<IconButton className='status__action-bar-button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} counter={status.get('replies_count')} obfuscateCount />
<IconButton className={classNames('status__action-bar-button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} counter={status.get('reblogs_count')} />
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={status.get('favourites_count')} />
<IconButton className='status__action-bar-button' disabled={!publicStatus} title={!publicStatus ? intl.formatMessage(messages.cannot_quote) : intl.formatMessage(messages.quote)} icon='quote-right' onClick={this.handleQuoteClick} />
{withOpenButton && <IconButton className='status__action-bar-button' title={intl.formatMessage(messages.open)} icon='external-link' onClick={this.handleOpenClick} />}
</div>
);

View File

@ -17,7 +17,9 @@ const messages = defineMessages({
reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' },
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' },
cannot_quote: { id: 'status.cannot_quote', defaultMessage: 'This post cannot be quoted' },
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
quote: { id: 'status.quote', defaultMessage: 'Quote' },
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
more: { id: 'status.more', defaultMessage: 'More' },
@ -56,6 +58,7 @@ class ActionBar extends React.PureComponent {
relationship: ImmutablePropTypes.map,
onReply: PropTypes.func.isRequired,
onReblog: PropTypes.func.isRequired,
onQuote: PropTypes.func.isRequired,
onFavourite: PropTypes.func.isRequired,
onBookmark: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired,
@ -82,6 +85,10 @@ class ActionBar extends React.PureComponent {
this.props.onReblog(this.props.status, e);
}
handleQuoteClick = () => {
this.props.onQuote(this.props.status, this.context.router.history);
}
handleFavouriteClick = () => {
this.props.onFavourite(this.props.status);
}
@ -277,6 +284,7 @@ class ActionBar extends React.PureComponent {
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /></div>
<div className='detailed-status__button' ><IconButton className={classNames({ reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} /></div>
<div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /></div>
<div className='detailed-status__button'><IconButton disabled={!publicStatus} title={!publicStatus ? intl.formatMessage(messages.cannot_quote) : intl.formatMessage(messages.quote)} icon='quote-right' onClick={this.handleQuoteClick} /></div>
{shareButton}
<div className='detailed-status__button'><IconButton className='bookmark-icon' active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /></div>

View File

@ -60,6 +60,10 @@ const addAutoPlay = html => {
export default class Card extends React.PureComponent {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = {
card: ImmutablePropTypes.map,
maxDescription: PropTypes.number,
@ -68,6 +72,7 @@ export default class Card extends React.PureComponent {
defaultWidth: PropTypes.number,
cacheWidth: PropTypes.func,
sensitive: PropTypes.bool,
quote: PropTypes.bool,
};
static defaultProps = {
@ -184,7 +189,7 @@ export default class Card extends React.PureComponent {
}
render () {
const { card, maxDescription, compact } = this.props;
const { card, maxDescription, compact, quote } = this.props;
const { width, embedded, revealed } = this.state;
if (card === null) {
@ -197,7 +202,11 @@ export default class Card extends React.PureComponent {
const className = classnames('status-card', { horizontal, compact, interactive });
const title = interactive ? <a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener noreferrer' target='_blank'><strong>{card.get('title')}</strong></a> : <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>;
const ratio = card.get('width') / card.get('height');
const height = (compact && !embedded) ? (width / (16 / 9)) : (width / ratio);
let height = (compact && !embedded) ? (width / (16 / 9)) : (width / ratio);
if (quote && height) {
height /= 2;
}
const description = (
<div className='status-card__content'>

View File

@ -1,4 +1,5 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import Avatar from '../../../components/avatar';
@ -6,7 +7,7 @@ import DisplayName from '../../../components/display_name';
import StatusContent from '../../../components/status_content';
import MediaGallery from '../../../components/media_gallery';
import { Link } from 'react-router-dom';
import { injectIntl, defineMessages, FormattedDate } from 'react-intl';
import { injectIntl, defineMessages, FormattedDate, FormattedMessage } from 'react-intl';
import Card from './card';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Video from '../../video';
@ -24,7 +25,31 @@ const messages = defineMessages({
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
});
export default @injectIntl
const mapStateToProps = (state, props) => {
let status = props.status;
if (status === null) {
return null;
}
if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
status = status.get('reblog');
}
if (status.get('quote', null) === null) {
return {
quote_muted: status.get('quote_id', null) ? true : false,
};
}
const id = status.getIn(['quote', 'account', 'id'], null);
return {
quote_muted: id !== null && (state.getIn(['relationships', id, 'muting']) || state.getIn(['relationships', id, 'blocking']) || state.getIn(['relationships', id, 'blocked_by']) || state.getIn(['relationships', id, 'domain_blocking'])) || status.getIn(['quote', 'quote_muted']),
};
};
export default @connect(mapStateToProps)
@injectIntl
class DetailedStatus extends ImmutablePureComponent {
static contextTypes = {
@ -33,8 +58,11 @@ class DetailedStatus extends ImmutablePureComponent {
static propTypes = {
status: ImmutablePropTypes.map,
quote_muted: PropTypes.bool,
onOpenMedia: PropTypes.func.isRequired,
onOpenVideo: PropTypes.func.isRequired,
onOpenMediaQuote: PropTypes.func.isRequired,
onOpenVideoQuote: PropTypes.func.isRequired,
onToggleHidden: PropTypes.func.isRequired,
measureHeight: PropTypes.bool,
onHeightChange: PropTypes.func,
@ -46,6 +74,9 @@ class DetailedStatus extends ImmutablePureComponent {
available: PropTypes.bool,
}),
onToggleMediaVisibility: PropTypes.func,
onQuoteToggleHidden: PropTypes.func.isRequired,
showQuoteMedia: PropTypes.bool,
onToggleQuoteMediaVisibility: PropTypes.func,
};
state = {
@ -54,6 +85,7 @@ class DetailedStatus extends ImmutablePureComponent {
handleAccountClick = (e) => {
if (e.button === 0 && !(e.ctrlKey || e.metaKey) && this.context.router) {
const id = e.currentTarget.getAttribute('data-id');
e.preventDefault();
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
}
@ -65,6 +97,10 @@ class DetailedStatus extends ImmutablePureComponent {
this.props.onOpenVideo(this.props.status.getIn(['media_attachments', 0]), options);
}
handleOpenVideoQuote = (options) => {
this.props.onOpenVideoQuote(this.props.status.getIn(['quote', 'media_attachments', 0]), options);
}
handleExpandedToggle = () => {
this.props.onToggleHidden(this.props.status);
}
@ -102,8 +138,22 @@ class DetailedStatus extends ImmutablePureComponent {
window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
}
handleExpandedQuoteToggle = () => {
this.props.onQuoteToggleHidden(this.props.status);
}
handleQuoteClick = () => {
if (!this.context.router) {
return;
}
const { status } = this.props;
this.context.router.history.push(`/statuses/${status.getIn(['quote', 'id'])}`);
}
render () {
const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status;
const quote_muted = this.props.quote_muted
const outerStyle = { boxSizing: 'border-box' };
const { intl, compact, pictureInPicture } = this.props;
@ -121,6 +171,95 @@ class DetailedStatus extends ImmutablePureComponent {
outerStyle.height = `${this.state.height}px`;
}
let quote = null;
if (status.get('quote', null) !== null) {
let quote_status = status.get('quote');
let quote_media = null;
if (quote_status.get('media_attachments').size > 0) {
if (quote_status.getIn(['media_attachments', 0, 'type']) === 'audio') {
const attachment = quote_status.getIn(['media_attachments', 0]);
quote_media = (
<Audio
src={attachment.get('url')}
alt={attachment.get('description')}
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
poster={attachment.get('preview_url') || quote_status.getIn(['account', 'avatar_static'])}
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
accentColor={attachment.getIn(['meta', 'colors', 'accent'])}
height={60}
/>
);
} else if (quote_status.getIn(['media_attachments', 0, 'type']) === 'video') {
const attachment = quote_status.getIn(['media_attachments', 0]);
quote_media = (
<Video
preview={attachment.get('preview_url')}
frameRate={attachment.getIn(['meta', 'original', 'frame_rate'])}
blurhash={attachment.get('blurhash')}
src={attachment.get('url')}
alt={attachment.get('description')}
width={300}
height={150}
inline
onOpenVideo={this.handleOpenVideoQuote}
sensitive={quote_status.get('sensitive')}
visible={this.props.showQuoteMedia}
onToggleVisibility={this.props.onToggleQuoteMediaVisibility}
quote
/>
);
} else {
quote_media = (
<MediaGallery
standalone
sensitive={quote_status.get('sensitive')}
media={quote_status.get('media_attachments')}
height={300}
onOpenMedia={this.props.onOpenMediaQuote}
visible={this.props.showQuoteMedia}
onToggleVisibility={this.props.onToggleQuoteMediaVisibility}
quote
/>
);
}
}
if (quote_muted) {
quote = (
<div className='quote-status' data-id={quote_status.get('id')} dataurl={quote_status.get('url')}>
<div className='status__content muted-quote'>
<FormattedMessage id='status.muted_quote' defaultMessage='Muted quote' />
</div>
</div>
);
} else {
quote = (
<div className='quote-status' data-id={quote_status.get('id')} dataurl={quote_status.get('url')}>
<a href={quote_status.getIn(['account', 'url'])} onClick={this.handleAccountClick} data-id={quote_status.getIn(['account', 'id'])} className='detailed-status__display-name'>
<div className='detailed-status__display-avatar'><Avatar account={quote_status.get('account')} size={18} /></div>
<DisplayName account={quote_status.get('account')} localDomain={this.props.domain} />
</a>
<StatusContent status={quote_status} onClick={this.handleQuoteClick} expanded={!status.get('quote_hidden')} onExpandedToggle={this.handleExpandedQuoteToggle} quote />
{quote_media}
</div>
);
}
} else if (quote_muted) {
quote = (
<div className={classNames('quote-status', { muted: this.props.muted })} data-id={quote_status.get('id')} dataurl={quote_status.get('url')}>
<div className={classNames('status__content muted-quote', { 'status__content--with-action': this.context.router })}>
<FormattedMessage id='status.muted_quote' defaultMessage='Muted quote' />
</div>
</div>
);
}
if (pictureInPicture.get('inUse')) {
media = <PictureInPicturePlaceholder />;
} else if (status.get('media_attachments').size > 0) {
@ -247,6 +386,7 @@ class DetailedStatus extends ImmutablePureComponent {
<StatusContent status={status} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} />
{quote}
{media}
<div className='detailed-status__meta'>

View File

@ -20,6 +20,8 @@ import {
deleteStatus,
hideStatus,
revealStatus,
hideQuote,
revealQuote,
} from '../../../actions/statuses';
import { initMuteModal } from '../../../actions/mutes';
import { initBlockModal } from '../../../actions/blocks';
@ -136,6 +138,14 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
dispatch(openModal('VIDEO', { media, options }));
},
onOpenMediaQuote (media, index) {
dispatch(openModal('MEDIA', { media, index }));
},
onOpenVideoQuote (media, options) {
dispatch(openModal('VIDEO', { media, options }));
},
onBlock (status) {
const account = status.get('account');
dispatch(initBlockModal(account));
@ -165,6 +175,13 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
}
},
onQuoteToggleHidden (status) {
if (status.get('quote_hidden')) {
dispatch(revealQuote(status.get('id')));
} else {
dispatch(hideQuote(status.get('id')));
}
},
});
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(DetailedStatus));

View File

@ -22,6 +22,7 @@ import {
} from '../../actions/interactions';
import {
replyCompose,
quoteCompose,
mentionCompose,
directCompose,
} from '../../actions/compose';
@ -31,6 +32,8 @@ import {
deleteStatus,
hideStatus,
revealStatus,
hideQuote,
revealQuote,
} from '../../actions/statuses';
import {
unblockAccount,
@ -68,6 +71,8 @@ const messages = defineMessages({
detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' },
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
quoteConfirm: { id: 'confirmations.quote.confirm', defaultMessage: 'Quote' },
quoteMessage: { id: 'confirmations.quote.message', defaultMessage: 'Quoting now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
});
@ -180,6 +185,7 @@ class Status extends ImmutablePureComponent {
state = {
fullscreen: false,
showMedia: defaultMediaVisibility(this.props.status),
showQuoteMedia: defaultMediaVisibility(this.props.status ? this.props.status.get('quote', null) : null),
loadedStatusId: undefined,
};
@ -198,7 +204,8 @@ class Status extends ImmutablePureComponent {
}
if (nextProps.status && nextProps.status.get('id') !== this.state.loadedStatusId) {
this.setState({ showMedia: defaultMediaVisibility(nextProps.status), loadedStatusId: nextProps.status.get('id') });
this.setState({ showMedia: defaultMediaVisibility(nextProps.status), loadedStatusId: nextProps.status.get('id'),
showQuoteMedia: defaultMediaVisibility(nextProps.status.get('quote', null)) });
}
}
@ -206,6 +213,10 @@ class Status extends ImmutablePureComponent {
this.setState({ showMedia: !this.state.showMedia });
}
handleToggleQuoteMediaVisibility = () => {
this.setState({ showQuoteMedia: !this.state.showQuoteMedia });
}
handleFavouriteClick = (status) => {
if (status.get('favourited')) {
this.props.dispatch(unfavourite(status));
@ -259,6 +270,19 @@ class Status extends ImmutablePureComponent {
}
}
handleQuoteClick = (status) => {
let { askReplyConfirmation, dispatch, intl } = this.props;
if (askReplyConfirmation) {
dispatch(openModal('CONFIRM', {
message: intl.formatMessage(messages.quoteMessage),
confirm: intl.formatMessage(messages.quoteConfirm),
onConfirm: () => dispatch(quoteCompose(status, this.context.router.history)),
}));
} else {
dispatch(quoteCompose(status, this.context.router.history));
}
}
handleDeleteClick = (status, history, withRedraft = false) => {
const { dispatch, intl } = this.props;
@ -289,6 +313,14 @@ class Status extends ImmutablePureComponent {
this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.get('id'), media, options }));
}
handleOpenMediaQuote = (media, index) => {
this.props.dispatch(openModal('MEDIA', { statusId: this.props.status.getIn(['quote', 'id']), media, index }));
}
handleOpenVideoQuote = (media, options) => {
this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.getIn(['quote', 'id']), media, options }));
}
handleHotkeyOpenMedia = e => {
const { status } = this.props;
@ -323,6 +355,14 @@ class Status extends ImmutablePureComponent {
}
}
handleQuoteToggleHidden = (status) => {
if (status.get('quote_hidden')) {
this.props.dispatch(revealQuote(status.get('id')));
} else {
this.props.dispatch(hideQuote(status.get('id')));
}
}
handleToggleAll = () => {
const { status, ancestorsIds, descendantsIds } = this.props;
const statusIds = [status.get('id')].concat(ancestorsIds.toJS(), descendantsIds.toJS());
@ -552,11 +592,16 @@ class Status extends ImmutablePureComponent {
status={status}
onOpenVideo={this.handleOpenVideo}
onOpenMedia={this.handleOpenMedia}
onOpenVideoQuote={this.handleOpenVideoQuote}
onOpenMediaQuote={this.handleOpenMediaQuote}
onToggleHidden={this.handleToggleHidden}
domain={domain}
showMedia={this.state.showMedia}
onToggleMediaVisibility={this.handleToggleMediaVisibility}
pictureInPicture={pictureInPicture}
onQuoteToggleHidden={this.handleQuoteToggleHidden}
showQuoteMedia={this.state.showQuoteMedia}
onToggleQuoteMediaVisibility={this.handleToggleQuoteMediaVisibility}
/>
<ActionBar
@ -566,6 +611,7 @@ class Status extends ImmutablePureComponent {
onFavourite={this.handleFavouriteClick}
onReblog={this.handleReblogClick}
onBookmark={this.handleBookmarkClick}
onQuote={this.handleQuoteClick}
onDelete={this.handleDeleteClick}
onDirect={this.handleDirectClick}
onMention={this.handleMentionClick}

View File

@ -8,7 +8,7 @@ import ReactSwipeableViews from 'react-swipeable-views';
import TabsBar, { links, getIndex, getLink } from './tabs_bar';
import { Link } from 'react-router-dom';
import { disableSwiping } from 'mastodon/initial_state';
import { disableSwiping, place_tab_bar_at_bottom } from 'mastodon/initial_state';
import BundleContainer from '../containers/bundle_container';
import ColumnLoading from './column_loading';
@ -34,6 +34,8 @@ import NavigationPanel from './navigation_panel';
import { supportsPassiveEvents } from 'detect-passive-events';
import { scrollRight } from '../../../scroll';
import classNames from 'classnames';
const componentMap = {
'COMPOSE': Compose,
'HOME': HomeTimeline,
@ -216,14 +218,14 @@ class ColumnsArea extends ImmutablePureComponent {
const columnIndex = getIndex(this.context.router.history.location.pathname);
if (singleColumn) {
const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/statuses/new' className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}><Icon id='pencil' /></Link>;
const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <Link key='floating-action-button' to='/statuses/new' className={classNames('floating-action-button', { 'bottom-bar': place_tab_bar_at_bottom })} aria-label={intl.formatMessage(messages.publish)}><Icon id='pencil' /></Link>;
const content = columnIndex !== -1 ? (
<ReactSwipeableViews key='content' hysteresis={0.2} threshold={15} index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }} disabled={disableSwiping}>
<ReactSwipeableViews key='content' className={classNames('swipeable-view__wrapper', { 'bottom-bar': place_tab_bar_at_bottom })} hysteresis={0.2} threshold={15} index={columnIndex} onChangeIndex={this.handleSwipe} onTransitionEnd={this.handleAnimationEnd} animateTransitions={shouldAnimate} springConfig={{ duration: '400ms', delay: '0s', easeFunction: 'ease' }} style={{ height: '100%' }} disabled={disableSwiping}>
{links.map(this.renderView)}
</ReactSwipeableViews>
) : (
<div key='content' className='columns-area columns-area--mobile'>{children}</div>
<div key='content' className={classNames('columns-area columns-area--mobile', { 'bottom-bar': place_tab_bar_at_bottom })}>{children}</div>
);
return (

View File

@ -53,6 +53,7 @@ class LinkFooter extends React.PureComponent {
<li><a href='/about/more' target='_blank'><FormattedMessage id='navigation_bar.info' defaultMessage='About this server' /></a> · </li>
<li><a href='https://joinmastodon.org/apps' target='_blank'><FormattedMessage id='navigation_bar.apps' defaultMessage='Mobile apps' /></a> · </li>
<li><a href='/terms' target='_blank'><FormattedMessage id='getting_started.terms' defaultMessage='Terms of service' /></a> · </li>
<li><a href='https://mattermost.pcgf.io/signup_user_complete/?id=i8xgw5ytb78b7ef5qz7zf7ieae' target='_blank'><FormattedMessage id='getting_started.chatroom' defaultMessage='Chatroom' /></a> · </li>
<li><a href='/settings/applications' target='_blank'><FormattedMessage id='getting_started.developers' defaultMessage='Developers' /></a> · </li>
<li><a href='https://docs.joinmastodon.org' target='_blank'><FormattedMessage id='getting_started.documentation' defaultMessage='Documentation' /></a> · </li>
<li><a href='/auth/sign_out' onClick={this.handleLogoutClick}><FormattedMessage id='navigation_bar.logout' defaultMessage='Logout' /></a></li>

View File

@ -6,14 +6,16 @@ import { debounce } from 'lodash';
import { isUserTouching } from '../../../is_mobile';
import Icon from 'mastodon/components/icon';
import NotificationsCounterIcon from './notifications_counter_icon';
import { place_tab_bar_at_bottom, show_tab_bar_label } from 'mastodon/initial_state';
import classNames from 'classnames';
export const links = [
<NavLink className='tabs-bar__link' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon id='home' fixedWidth /><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></NavLink>,
<NavLink className='tabs-bar__link' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon /><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></NavLink>,
<NavLink className='tabs-bar__link' to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon id='users' fixedWidth /><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></NavLink>,
<NavLink className='tabs-bar__link' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon id='globe' fixedWidth /><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></NavLink>,
<NavLink className='tabs-bar__link optional' to='/search' data-preview-title-id='tabs_bar.search' data-preview-icon='bell' ><Icon id='search' fixedWidth /><FormattedMessage id='tabs_bar.search' defaultMessage='Search' /></NavLink>,
<NavLink className='tabs-bar__link' style={{ flexGrow: '0', flexBasis: '30px' }} to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='bars' ><Icon id='bars' fixedWidth /></NavLink>,
<NavLink className='tabs-bar__link' to='/timelines/home' data-preview-title-id='column.home' data-preview-icon='home' ><Icon id='home' fixedWidth /><span className='tabs-bar__link__full-label'><FormattedMessage id='tabs_bar.home' defaultMessage='Home' /></span><span className='tabs-bar__link__short-label'><FormattedMessage id='navigation_bar.short.home' defaultMessage='Home' /></span></NavLink>,
<NavLink className='tabs-bar__link' to='/notifications' data-preview-title-id='column.notifications' data-preview-icon='bell' ><NotificationsCounterIcon /><span className='tabs-bar__link__full-label'><FormattedMessage id='tabs_bar.notifications' defaultMessage='Notifications' /></span><span className='tabs-bar__link__short-label'><FormattedMessage id='navigation_bar.short.notifications' defaultMessage='Notif.' /></span></NavLink>,
<NavLink className='tabs-bar__link' exact to='/timelines/public/local' data-preview-title-id='column.community' data-preview-icon='users' ><Icon id='users' fixedWidth /><span className='tabs-bar__link__full-label'><FormattedMessage id='tabs_bar.local_timeline' defaultMessage='Local' /></span><span className='tabs-bar__link__short-label'><FormattedMessage id='navigation_bar.short.community_timeline' defaultMessage='LTL' /></span></NavLink>,
<NavLink className='tabs-bar__link' exact to='/timelines/public' data-preview-title-id='column.public' data-preview-icon='globe' ><Icon id='globe' fixedWidth /><span className='tabs-bar__link__full-label'><FormattedMessage id='tabs_bar.federated_timeline' defaultMessage='Federated' /></span><span className='tabs-bar__link__short-label'><FormattedMessage id='navigation_bar.short.public_timeline' defaultMessage='FTL' /></span></NavLink>,
<NavLink className='tabs-bar__link optional' to='/search' data-preview-title-id='tabs_bar.search' data-preview-icon='bell' ><Icon id='search' fixedWidth /><span className='tabs-bar__link__full-label'><FormattedMessage id='tabs_bar.search' defaultMessage='Search' /></span><span className='tabs-bar__link__short-label'><FormattedMessage id='navigation_bar.short.search' defaultMessage='Search' /></span></NavLink>,
<NavLink className='tabs-bar__link hamburger' to='/getting-started' data-preview-title-id='getting_started.heading' data-preview-icon='bars' ><Icon id='bars' fixedWidth /></NavLink>,
];
export function getIndex (path) {
@ -31,7 +33,7 @@ class TabsBar extends React.PureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
history: PropTypes.object.isRequired,
}
};
setRef = ref => {
this.node = ref;
@ -74,8 +76,8 @@ class TabsBar extends React.PureComponent {
return (
<div className='tabs-bar__wrapper'>
<nav className='tabs-bar' ref={this.setRef}>
{links.map(link => React.cloneElement(link, { key: link.props.to, onClick: this.handleClick, 'aria-label': formatMessage({ id: link.props['data-preview-title-id'] }) }))}
<nav className={classNames('tabs-bar', { 'bottom-bar': place_tab_bar_at_bottom })} ref={this.setRef}>
{links.map(link => React.cloneElement(link, { key: link.props.to, className: classNames(link.props.className, { 'short-label': show_tab_bar_label }), onClick: this.handleClick, 'aria-label': formatMessage({ id: link.props['data-preview-title-id'] }) }))}
</nav>
<div id='tabs-bar__portal' />

View File

@ -122,6 +122,7 @@ class Video extends React.PureComponent {
volume: PropTypes.number,
muted: PropTypes.bool,
componetIndex: PropTypes.number,
quote: PropTypes.bool,
};
static defaultProps = {
@ -523,7 +524,7 @@ class Video extends React.PureComponent {
}
render () {
const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, detailed, sensitive, editable, blurhash } = this.props;
const { preview, src, inline, onOpenVideo, onCloseVideo, intl, alt, detailed, sensitive, editable, blurhash, quote } = this.props;
const { containerWidth, currentTime, duration, volume, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
const progress = Math.min((currentTime / duration) * 100, 100);
const playerStyle = {};
@ -537,6 +538,11 @@ class Video extends React.PureComponent {
playerStyle.height = height;
}
if (quote && height) {
height /= 2;
playerStyle.height = height;
}
let preload;
if (this.props.currentTime || fullscreen || dragging) {

View File

@ -26,5 +26,7 @@ export const showTrends = getMeta('trends');
export const title = getMeta('title');
export const cropImages = getMeta('crop_images');
export const disableSwiping = getMeta('disable_swiping');
export const place_tab_bar_at_bottom = getMeta('place_tab_bar_at_bottom');
export const show_tab_bar_label = getMeta('show_tab_bar_label');
export default initialState;

View File

@ -31,7 +31,7 @@
"account.moved_to": "Uživatel {name} se přesunul na:",
"account.mute": "Skrýt @{name}",
"account.mute_notifications": "Skrýt oznámení od @{name}",
"account.muted": "Skryt",
"account.muted": "Účet skryt",
"account.never_active": "Nikdy",
"account.posts": "Příspěvky",
"account.posts_with_replies": "Příspěvky a odpovědi",

View File

@ -449,6 +449,18 @@
"defaultMessage": "This post cannot be boosted",
"id": "status.cannot_reblog"
},
{
"defaultMessage": "Quote",
"id": "status.quote"
},
{
"defaultMessage": "Unlisted quote",
"id": "status.unlisted_quote"
},
{
"defaultMessage": "Muted quote",
"id": "status.muted_quote"
},
{
"defaultMessage": "Favourite",
"id": "status.favourite"
@ -1229,6 +1241,15 @@
],
"path": "app/javascript/mastodon/features/compose/components/privacy_dropdown.json"
},
{
"descriptors": [
{
"defaultMessage": "Cancel",
"id": "quote_indicator.cancel"
}
],
"path": "app/javascript/mastodon/features/compose/components/quote_indicator.json"
},
{
"descriptors": [
{

View File

@ -120,6 +120,8 @@
"confirmations.mute.confirm": "Mute",
"confirmations.mute.explanation": "This will hide posts from them and posts mentioning them, but it will still allow them to see your posts and follow you.",
"confirmations.mute.message": "Are you sure you want to mute {name}?",
"confirmations.quote.confirm": "Quote",
"confirmations.quote.message": "Quoting now will overwrite the message you are currently composing. Are you sure you want to proceed?",
"confirmations.redraft.confirm": "Delete & redraft",
"confirmations.redraft.message": "Are you sure you want to delete this post and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.",
"confirmations.reply.confirm": "Reply",
@ -291,7 +293,16 @@
"navigation_bar.preferences": "Preferences",
"navigation_bar.public_timeline": "Federated timeline",
"navigation_bar.security": "Security",
"notification.favourite": "{name} favourited your post",
"navigation_bar.short.community_timeline": "LTL",
"navigation_bar.short.getting_started": "Started",
"navigation_bar.short.home": "Home",
"navigation_bar.short.lists": "Lists",
"navigation_bar.short.logout": "Logout",
"navigation_bar.short.notifications": "Notif.",
"navigation_bar.short.preferences": "Pref.",
"navigation_bar.short.public_timeline": "FTL",
"navigation_bar.short.search": "Search",
"notification.favourite": "{name} favourited your toot",
"notification.follow": "{name} followed you",
"notification.follow_request": "{name} has requested to follow you",
"notification.mention": "{name} mentioned you",
@ -350,6 +361,7 @@
"privacy.public.short": "Public",
"privacy.unlisted.long": "Visible for all, but not in public timelines",
"privacy.unlisted.short": "Unlisted",
"quote_indicator.cancel": "Cancel",
"refresh": "Refresh",
"regeneration_indicator.label": "Loading…",
"regeneration_indicator.sublabel": "Your home feed is being prepared!",
@ -383,6 +395,7 @@
"status.block": "Block @{name}",
"status.bookmark": "Bookmark",
"status.cancel_reblog_private": "Unboost",
"status.cannot_quote": "This post cannot be quoted",
"status.cannot_reblog": "This post cannot be boosted",
"status.copy": "Copy link to post",
"status.delete": "Delete",
@ -397,9 +410,11 @@
"status.more": "More",
"status.mute": "Mute @{name}",
"status.mute_conversation": "Mute conversation",
"status.muted_quote": "Muted quote",
"status.open": "Expand this post",
"status.pin": "Pin on profile",
"status.pinned": "Pinned post",
"status.quote": "Quote",
"status.read_more": "Read more",
"status.reblog": "Boost",
"status.reblog_private": "Boost with original visibility",
@ -416,8 +431,10 @@
"status.show_less_all": "Show less for all",
"status.show_more": "Show more",
"status.show_more_all": "Show more for all",
"status.show_poll": "Show poll",
"status.show_thread": "Show thread",
"status.uncached_media_warning": "Not available",
"status.unlisted_quote": "Unlisted quote",
"status.unmute_conversation": "Unmute conversation",
"status.unpin": "Unpin from profile",
"suggestions.dismiss": "Dismiss suggestion",

View File

@ -51,7 +51,7 @@
"alert.rate_limited.title": "Forgalomkorlátozás",
"alert.unexpected.message": "Váratlan hiba történt.",
"alert.unexpected.title": "Hoppá!",
"announcement.announcement": "Közlemény",
"announcement.announcement": "Bejelentés",
"autosuggest_hashtag.per_week": "{count} hetente",
"boost_modal.combo": "Hogy átugord ezt következő alkalommal, használd {combo}",
"bundle_column_error.body": "Valami hiba történt a komponens betöltése közben.",

View File

@ -90,7 +90,7 @@
"compose_form.hashtag_warning": "この投稿は公開設定ではないのでハッシュタグの一覧に表示されません。公開投稿だけがハッシュタグで検索できます。",
"compose_form.lock_disclaimer": "あなたのアカウントは{locked}になっていません。誰でもあなたをフォローすることができ、フォロワー限定の投稿を見ることができます。",
"compose_form.lock_disclaimer.lock": "承認制",
"compose_form.placeholder": "今なにしてる?",
"compose_form.placeholder": "ちょうどL.A.に到着しました。",
"compose_form.poll.add_option": "追加",
"compose_form.poll.duration": "アンケート期間",
"compose_form.poll.option_placeholder": "項目 {number}",
@ -105,6 +105,8 @@
"compose_form.spoiler.marked": "本文は警告の後ろに隠されます",
"compose_form.spoiler.unmarked": "本文は隠されていません",
"compose_form.spoiler_placeholder": "ここに警告を書いてください",
"compose_form.utilBtns_goji": "誤字盛!",
"compose_form.utilBtns_harukin": "はるきん焼却",
"confirmation_modal.cancel": "キャンセル",
"confirmations.block.block_and_report": "ブロックし通報",
"confirmations.block.confirm": "ブロック",
@ -120,6 +122,8 @@
"confirmations.mute.confirm": "ミュート",
"confirmations.mute.explanation": "これにより相手の投稿と返信は見えなくなりますが、相手はあなたをフォローし続け投稿を見ることができます。",
"confirmations.mute.message": "本当に{name}さんをミュートしますか?",
"confirmations.quote.confirm": "引用",
"confirmations.quote.message": "今引用すると現在作成中のメッセージが上書きされます。本当に実行しますか?",
"confirmations.redraft.confirm": "削除して下書きに戻す",
"confirmations.redraft.message": "本当にこの投稿を削除して下書きに戻しますか? この投稿へのお気に入り登録やブーストは失われ、返信は孤立することになります。",
"confirmations.reply.confirm": "返信",
@ -183,6 +187,7 @@
"follow_request.reject": "拒否",
"follow_requests.unlocked_explanation": "あなたのアカウントは承認制ではありませんが、{domain} のスタッフはこれらのアカウントからのフォローリクエストの確認が必要であると判断しました。",
"generic.saved": "保存しました",
"getting_started.chatroom": "チャットルーム",
"getting_started.developers": "開発",
"getting_started.directory": "ディレクトリ",
"getting_started.documentation": "ドキュメント",
@ -190,7 +195,8 @@
"getting_started.invite": "招待",
"getting_started.open_source_notice": "Mastodonはオープンソースソフトウェアです。誰でもGitHub ( {github} ) から開発に参加したり、問題を報告したりできます。",
"getting_started.security": "アカウント設定",
"getting_started.terms": "プライバシーポリシー",
"getting_started.security": "セキュリティ",
"getting_started.terms": "利用規約とプライバシーポリシー",
"hashtag.column_header.tag_mode.all": "と {additional}",
"hashtag.column_header.tag_mode.any": "か {additional}",
"hashtag.column_header.tag_mode.none": "({additional} を除く)",
@ -200,6 +206,7 @@
"hashtag.column_settings.tag_mode.any": "いずれかを含む",
"hashtag.column_settings.tag_mode.none": "これらを除く",
"hashtag.column_settings.tag_toggle": "このカラムに追加のタグを含める",
"home.column_settings.advanced": "高度な設定",
"home.column_settings.basic": "基本設定",
"home.column_settings.show_reblogs": "ブースト表示",
"home.column_settings.show_replies": "返信表示",
@ -281,7 +288,8 @@
"navigation_bar.filters": "フィルター設定",
"navigation_bar.follow_requests": "フォローリクエスト",
"navigation_bar.follows_and_followers": "フォロー・フォロワー",
"navigation_bar.info": "このサーバーについて",
"navigation_bar.generate_qrcode": "プロフィールのQRコードを生成",
"navigation_bar.info": "Yづドンについて",
"navigation_bar.keyboard_shortcuts": "ホットキー",
"navigation_bar.lists": "リスト",
"navigation_bar.logout": "ログアウト",
@ -291,7 +299,21 @@
"navigation_bar.preferences": "ユーザー設定",
"navigation_bar.public_timeline": "連合タイムライン",
"navigation_bar.security": "セキュリティ",
"notification.favourite": "{name}さんがあなたの投稿をお気に入りに登録しました",
"notification.and_n_others": "and {count, plural, one {# other} other {# others}}",
"notification.and_n_others": "と、他 {count} 件",
"navigation_bar.announcements": "運営からのお知らせ",
"navigation_bar.trends": "トレンド",
"navigation_bar.bookmarks": "ブックマーク",
"notification.favourite": "{name}さんがあなたの投稿に╰( ^o^)╮-=ニ=一=三★しました",
"navigation_bar.short.community_timeline": "ローカル",
"navigation_bar.short.getting_started": "スタート",
"navigation_bar.short.home": "ホーム",
"navigation_bar.short.lists": "リスト",
"navigation_bar.short.logout": "ログアウト",
"navigation_bar.short.notifications": "通知",
"navigation_bar.short.preferences": "設定",
"navigation_bar.short.public_timeline": "連合",
"navigation_bar.short.search": "検索",
"notification.follow": "{name}さんにフォローされました",
"notification.follow_request": "{name} さんがあなたにフォローリクエストしました",
"notification.mention": "{name}さんがあなたに返信しました",
@ -350,7 +372,9 @@
"privacy.public.short": "公開",
"privacy.unlisted.long": "誰でも閲覧可、公開TLに非表示",
"privacy.unlisted.short": "未収載",
"quote_indicator.cancel": "キャンセル",
"refresh": "更新",
"qr_modal.description": "QRコードを読み取って簡単にプロフィールにアクセスしましょう。",
"regeneration_indicator.label": "読み込み中…",
"regeneration_indicator.sublabel": "ホームタイムラインは準備中です!",
"relative_time.days": "{number}日前",
@ -383,6 +407,7 @@
"status.block": "@{name}さんをブロック",
"status.bookmark": "ブックマーク",
"status.cancel_reblog_private": "ブースト解除",
"status.cannot_quote": "この投稿は引用できません",
"status.cannot_reblog": "この投稿はブーストできません",
"status.copy": "投稿へのリンクをコピー",
"status.delete": "削除",
@ -397,9 +422,11 @@
"status.more": "もっと見る",
"status.mute": "@{name}さんをミュート",
"status.mute_conversation": "会話をミュート",
"status.muted_quote": "ミュートされた引用",
"status.open": "詳細を表示",
"status.pin": "プロフィールに固定表示",
"status.pinned": "固定された投稿",
"status.quote": "引用",
"status.read_more": "もっと見る",
"status.reblog": "ブースト",
"status.reblog_private": "ブースト",
@ -416,12 +443,15 @@
"status.show_less_all": "全て隠す",
"status.show_more": "もっと見る",
"status.show_more_all": "全て見る",
"status.show_poll": "アンケートを表示",
"status.show_thread": "スレッドを表示",
"status.uncached_media_warning": "利用できません",
"status.unlisted_quote": "未収載の引用",
"status.unmute_conversation": "会話のミュートを解除",
"status.unpin": "プロフィールへの固定を解除",
"suggestions.dismiss": "隠す",
"suggestions.header": "興味あるかもしれません…",
"tabs_bar.admin_notifications": "Adminからのお知らせ",
"tabs_bar.federated_timeline": "連合",
"tabs_bar.home": "ホーム",
"tabs_bar.local_timeline": "ローカル",

View File

@ -431,7 +431,7 @@
"time_remaining.hours": "{number, plural, other {# giờ}}",
"time_remaining.minutes": "{number, plural, other {# phút}}",
"time_remaining.moments": "Còn lại",
"time_remaining.seconds": "{number, plural, other {# giây}}",
"time_remaining.seconds": "còn {number, plural, other {# giây}}",
"timeline_hint.remote_resource_not_displayed": "{resource} từ máy chủ khác sẽ không hiển thị.",
"timeline_hint.resources.followers": "Người theo dõi",
"timeline_hint.resources.follows": "Đang theo dõi",

View File

@ -5,6 +5,8 @@ import {
COMPOSE_REPLY,
COMPOSE_REPLY_CANCEL,
COMPOSE_DIRECT,
COMPOSE_QUOTE,
COMPOSE_QUOTE_CANCEL,
COMPOSE_MENTION,
COMPOSE_SUBMIT_REQUEST,
COMPOSE_SUBMIT_SUCCESS,
@ -59,6 +61,8 @@ const initialState = ImmutableMap({
caretPosition: null,
preselectDate: null,
in_reply_to: null,
quote_from: null,
quote_from_url: null,
is_composing: false,
is_submitting: false,
is_changing_upload: false,
@ -102,6 +106,7 @@ function clearAll(state) {
map.set('is_submitting', false);
map.set('is_changing_upload', false);
map.set('in_reply_to', null);
map.set('quote_from', null);
map.set('privacy', state.get('default_privacy'));
map.set('sensitive', false);
map.update('media_attachments', list => list.clear());
@ -247,6 +252,17 @@ const updateSuggestionTags = (state, token) => {
});
};
const rejectQuoteAltText = html => {
const fragment = domParser.parseFromString(html, 'text/html').documentElement;
const quote_inline = fragment.querySelector('span.quote-inline');
if (quote_inline) {
quote_inline.remove();
}
return fragment.innerHTML;
};
export default function compose(state = initialState, action) {
switch(action.type) {
case STORE_HYDRATE:
@ -292,6 +308,8 @@ export default function compose(state = initialState, action) {
case COMPOSE_REPLY:
return state.withMutations(map => {
map.set('in_reply_to', action.status.get('id'));
map.set('quote_from', null);
map.set('quote_from_url', null);
map.set('text', statusToTextMentions(state, action.status));
map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
map.set('focusDate', new Date());
@ -299,6 +317,25 @@ export default function compose(state = initialState, action) {
map.set('preselectDate', new Date());
map.set('idempotencyKey', uuid());
if (action.status.get('spoiler_text').length > 0) {
map.set('spoiler', true);
map.set('spoiler_text', action.status.get('spoiler_text'));
} else {
map.set('spoiler', false);
map.set('spoiler_text', '');
}
});
case COMPOSE_QUOTE:
return state.withMutations(map => {
map.set('in_reply_to', null);
map.set('quote_from', action.status.get('id'));
map.set('quote_from_url', action.status.get('url'));
map.set('text', '');
map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
map.set('focusDate', new Date());
map.set('preselectDate', new Date());
map.set('idempotencyKey', uuid());
if (action.status.get('spoiler_text').length > 0) {
map.set('spoiler', true);
map.set('spoiler_text', action.status.get('spoiler_text'));
@ -308,9 +345,12 @@ export default function compose(state = initialState, action) {
}
});
case COMPOSE_REPLY_CANCEL:
case COMPOSE_QUOTE_CANCEL:
case COMPOSE_RESET:
return state.withMutations(map => {
map.set('in_reply_to', null);
map.set('quote_from', null);
map.set('quote_from_url', null);
map.set('text', '');
map.set('spoiler', false);
map.set('spoiler_text', '');
@ -399,8 +439,10 @@ export default function compose(state = initialState, action) {
}));
case REDRAFT:
return state.withMutations(map => {
map.set('text', action.raw_text || unescapeHTML(expandMentions(action.status)));
map.set('text', action.raw_text || unescapeHTML(rejectQuoteAltText(expandMentions(action.status))));
map.set('in_reply_to', action.status.get('in_reply_to_id'));
map.set('quote_from', action.status.getIn(['quote', 'id']));
map.set('quote_from_url', action.status.getIn(['quote', 'url']));
map.set('privacy', action.status.get('visibility'));
map.set('media_attachments', action.status.get('media_attachments'));
map.set('focusDate', new Date());

View File

@ -9,6 +9,7 @@ import {
COMPOSE_MENTION,
COMPOSE_REPLY,
COMPOSE_DIRECT,
COMPOSE_QUOTE,
} from '../actions/compose';
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
@ -36,6 +37,7 @@ export default function search(state = initialState, action) {
case COMPOSE_REPLY:
case COMPOSE_MENTION:
case COMPOSE_DIRECT:
case COMPOSE_QUOTE:
return state.set('hidden', true);
case SEARCH_FETCH_SUCCESS:
return state.set('results', ImmutableMap({

View File

@ -13,6 +13,8 @@ import {
STATUS_REVEAL,
STATUS_HIDE,
STATUS_COLLAPSE,
QUOTE_REVEAL,
QUOTE_HIDE,
} from '../actions/statuses';
import { TIMELINE_DELETE } from '../actions/timelines';
import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer';
@ -75,6 +77,14 @@ export default function statuses(state = initialState, action) {
});
case STATUS_COLLAPSE:
return state.setIn([action.id, 'collapsed'], action.isCollapsed);
case QUOTE_REVEAL:
return state.withMutations(map => {
action.ids.forEach(id => map.setIn([id, 'quote_hidden'], false));
});
case QUOTE_HIDE:
return state.withMutations(map => {
action.ids.forEach(id => map.setIn([id, 'quote_hidden'], true));
});
case TIMELINE_DELETE:
return deleteStatus(state, action.id, action.references);
default:

View File

@ -88,22 +88,60 @@ export const makeGetStatus = () => {
[
(state, { id }) => state.getIn(['statuses', id]),
(state, { id }) => state.getIn(['statuses', state.getIn(['statuses', id, 'reblog'])]),
(state, { id }) => state.getIn(['statuses', state.getIn(['statuses', id, 'quote_id'])]),
(state, { id }) => state.getIn(['accounts', state.getIn(['statuses', id, 'account'])]),
(state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account'])]),
(state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'quote_id']), 'account'])]),
(state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'quote', 'account'])]),
(state, { id }) => state.getIn(['relationships', state.getIn(['statuses', id, 'account'])]),
(state, { id }) => state.getIn(['relationships', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account'])]),
(state, { id }) => state.getIn(['relationships', state.getIn(['statuses', state.getIn(['statuses', id, 'quote_id']), 'account'])]),
(state, { id }) => state.getIn(['relationships', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'quote', 'account'])]),
(state, { id }) => state.getIn(['accounts', state.getIn(['accounts', state.getIn(['statuses', id, 'account']), 'moved'])]),
(state, { id }) => state.getIn(['accounts', state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account']), 'moved'])]),
(state, { id }) => state.getIn(['accounts', state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'quote_id']), 'account']), 'moved'])]),
(state, { id }) => state.getIn(['accounts', state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'quote', 'account']), 'moved'])]),
getFiltersRegex,
],
(statusBase, statusReblog, accountBase, accountReblog, filtersRegex) => {
(statusBase, statusReblog, statusQuote, accountBase, accountReblog, accountQuote, accountReblogQuote, relationship, reblogRelationship, quoteRelationship, reblogQuoteRelationship, moved, reblogMoved, quoteMoved, reblogQuoteMoved, filtersRegex) => {
if (!statusBase) {
return null;
}
accountBase = accountBase.withMutations(map => {
map.set('relationship', relationship);
map.set('moved', moved);
});
if (statusReblog) {
accountReblog = accountReblog.withMutations(map => {
map.set('relationship', reblogRelationship);
map.set('moved', reblogMoved);
});
statusReblog = statusReblog.set('account', accountReblog);
} else {
statusReblog = null;
}
if (statusQuote) {
accountQuote = accountQuote.withMutations(map => {
map.set('relationship', quoteRelationship);
map.set('moved', quoteMoved);
});
statusQuote = statusQuote.set('account', accountQuote);
} else {
statusQuote = null;
}
if (statusReblog && accountReblogQuote) {
accountReblogQuote = accountReblog.withMutations(map => {
map.set('relationship', reblogQuoteRelationship);
map.set('moved', reblogQuoteMoved);
});
statusReblog = statusReblog.setIn(['quote', 'account'], accountReblogQuote);
}
const dropRegex = (accountReblog || accountBase).get('id') !== me && filtersRegex[0];
if (dropRegex && dropRegex.test(statusBase.get('reblog') ? statusReblog.get('search_index') : statusBase.get('search_index'))) {
return null;
@ -114,6 +152,7 @@ export const makeGetStatus = () => {
return statusBase.withMutations(map => {
map.set('reblog', statusReblog);
map.set('quote', statusQuote);
map.set('account', accountBase);
map.set('filtered', filtered);
});

View File

@ -0,0 +1,3 @@
export const uniq = array => {
return array.filter((x, i, self) => self.indexOf(x) === i)
};

View File

@ -291,6 +291,31 @@ function main() {
}
});
});
delegate(document, '.quote-status', 'click', ({ target }) => {
if (target.closest('.status__content__spoiler-link') ||
target.closest('.media-gallery') ||
target.closest('.video-player') ||
target.closest('.audio-player')) {
return false;
}
let url = target.closest('.quote-status').getAttribute('dataurl');
if (target.closest('.status__display-name')) {
url = target.closest('.status__display-name').getAttribute('href');
} else if (target.closest('.detailed-status__display-name')) {
url = target.closest('.detailed-status__display-name').getAttribute('href');
} else if (target.closest('.status-card')) {
url = target.closest('.status-card').getAttribute('href');
}
if (window.location.hostname === url.split('/')[2].split(':')[0]) {
window.location.href = url;
} else {
window.open(url, 'blank');
}
return false;
});
}
loadPolyfills()

View File

@ -26,3 +26,6 @@
@import 'mastodon/dashboard';
@import 'mastodon/rtl';
@import 'mastodon/accessibility';
@import 'markdown';
@import 'astarte';

View File

@ -0,0 +1,59 @@
.announcements {
padding: 0 10px;
li {
display: flex;
padding: 10px;
color: #282c37;
background: darken($white, 10%);
border-radius: 4px;
& + li {
margin-top: 10px;
}
}
}
.announcements__admin {
width: 100%;
position: relative;
p {
padding: 0 5px;
font-size: 14px;
}
}
.announcements__icon {
display: inline-block;
position: absolute;
margin: -5px 5px;
right: 10px;
}
.announcements__body {
width: 100%;
position: relative;
p {
padding: 0 5px;
font-size: 14px;
}
a {
display: inline-block;
float: right;
clear: both;
color: #282c37;
background: darken($white, 5%);
text-decoration: none;
padding: 1px 10px 0;
border: solid 1px #282c37;
font-size: 10px;
font-weight: 600;
border-radius: 4px;
margin-top: 5px;
}
}

View File

@ -1,3 +1,5 @@
@import 'contrast/variables';
@import 'application';
@import 'contrast/diff';
@import 'markdown';

View File

@ -0,0 +1,266 @@
.status__content {
font-family: inherit;
h1{
color: #ec840d;
font-weight: bold;
font-size: 1.6em;
padding: 0.5em;
display: inline-block;
line-height: 1.3;
background: #dbebf8;
vertical-align: middle;
border-radius: 25px 25px 25px 25px;
text-align: center;
border-bottom: solid 3px #ff0000;
}
h2{
color: #ec840d;
font-weight: bold;
font-size: 1.5em;
padding: 0.5em;
display: inline-block;
line-height: 1.3;
background: #dbebf8;
vertical-align: middle;
border-radius: 25px 25px 25px 25px;
text-align: center;
border-bottom: solid 3px #fffb00;
}
h3{
color: #ec840d;
font-weight: bold;
font-size: 1.4em;
padding: 0.5em;
display: inline-block;
line-height: 1.3;
background: #dbebf8;
vertical-align: middle;
border-radius: 25px 25px 25px 25px;
text-align: center;
border-bottom: solid 3px #2bff00;
}
h4{
color: #ec840d;
font-weight: bold;
font-size: 1.3em;
padding: 0.5em;
display: inline-block;
line-height: 1.3;
background: #dbebf8;
vertical-align: middle;
border-radius: 25px 25px 25px 25px;
text-align: center;
border-bottom: solid 3px #00ffea;
}
h5{
color: #ec840d;
font-weight: bold;
font-size: 1.2em;
padding: 0.5em;
display: inline-block;
line-height: 1.3;
background: #dbebf8;
vertical-align: middle;
border-radius: 25px 25px 25px 25px;
text-align: center;
border-bottom: solid 3px #0004ff;
}
h6{
color: #ec840d;
font-weight: bold;
font-size: 1.1em;
padding: 0.5em;
display: inline-block;
line-height: 1.3;
background: #dbebf8;
vertical-align: middle;
border-radius: 25px 25px 25px 25px;
text-align: center;
border-bottom: solid 3px #7700ff;
}
em{
font-style: italic;
}
strong{
font-weight: bold;
}
code{
display: block;
border-left: 2px solid;
border-color: #079903;
color: $white;
padding-left: 10px;
margin-top: 5px;
margin-bottom: 5px;
margin-left: 5px;
background-color: #000000;
.positive{
color: #5bda57;
}
.negative{
color: #ff4949;
}
.rust-fanc{
color: #ba7eff;
}
.ruby-func{
color: #24a8e6;
}
.rust-macro{
color: #d2ff6a;
}
.contents{
color: #ff9925;
}
}
pre{
display: inline-block;
font-family: 'Noto Sans Japanese', sans-serif;
font-weight: 400;
}
p ~ blockquote {
margin-top: -8px;
}
blockquote{
padding-left: 8px;
margin-top: 0.5em;
margin-bottom: 0.5em;
color: $primary-text-color;
background-color: $ui-base-color;
display: block;
border-left: 4px solid $classic-highlight-color;
}
ul.md-contents {
border: double 4px #21b384;
padding: 0.5em 1em 0.5em 2.3em;
position: relative;
}
ul li.md-contents {
line-height: 1.5;
padding: 0.2em 0;
list-style-type: none!important;
}
ul li.md-contents:before {
font-family: FontAwesome;
content: "\f0a4";
position: absolute;
left : 1em;
color: #21b384;
}
ol.md-contents {
border: double 4px #ff954f;
padding: 0.5em 1em 0.5em 1em;
position: relative;
list-style: inside decimal;
}
ol li.md-contents {
line-height: 1.5;
padding: 0.2em 0;
}
hr {
border-width: 2px 0px 0px 0px;
border-style: dashed;
border-color: #ff7676;
height: 1px;
}
p>a>img{
width: 100%;
height: 100%;
object-fit: contain;
}
a>img{
width: 100%;
height: 100%;
object-fit: contain;
}
p>a{
color: #1FBFF9;
}
a{
color: #1FBFF9;
}
sup{
font-size: 75.5%;
vertical-align: top;
position: relative;
top: -0.5em;
}
sub{
font-size: 75.5%;
vertical-align: bottom;
position: relative;
top: 0.5em;
}
small{
font-size: 50.0%;
vertical-align: bottom;
position: relative;
}
table {
margin-top: 0.5em;
margin-bottom: 0.5em;
color: $classic-highlight-color;
display: block;
width: 100%;
overflow: auto;
border-spacing: 0;
border-collapse: collapse;
}
table tr{
background-color: #000000;
}
table th, table td{
padding: 6px 13px;
border: 1px solid $classic-highlight-color;
}
table th{
font-weight: 600;
}
table thead tr{
background-color: $black;
}
td, th{
padding: 0;
}
span.img_FTL {
display: none;
}
}

View File

@ -0,0 +1,2 @@
@use 'application';
@media (prefers-color-scheme: light) { @import 'mastodon-light'; }

View File

@ -1,3 +1,5 @@
@import 'mastodon-light/variables';
@import 'application';
@import 'mastodon-light/diff';
@import 'markdown';

View File

@ -0,0 +1,2 @@
@use 'mastodon-material-dark';
@media (prefers-color-scheme: light) { @import 'mastodon-material-light'; }

View File

@ -0,0 +1,2 @@
@import 'application';
@import 'mastodon-material/profiles/mastodon-material-dark/loader';

View File

@ -0,0 +1,2 @@
@import 'application';
@import 'mastodon-material/profiles/mastodon-material-light/loader';

View File

@ -0,0 +1,148 @@
@charset "UTF-8";
$color-scheme: dark;
// Base color
$primary-color: #4285f4;
$secondary-color: #db4437;
$error-color: #B00020;
$verified-color: #4caf50;
// Text color
$primary-text-color: #ffffff;
$secondary-text-color: #9aa0a6;
$ui-text-color: #e8eaed;
$inverted-text-color: #000000;
$section-text-color: $primary-color;
$info-text-color: #9aa0a6;
$tips-text-color: #c0c0c0;
$disabled-text-color: rgba(0,0,0,.54);
$link-text-color: #4285f4;
$menu-text-color: $ui-text-color;
$top-bar-text-color: $ui-text-color;
$search-bar-text-color: $inverted-text-color;
$contained-button-text-color: #ffffff;
$primary-text-on-shadow-color: #ffffff;
$text-field-color: $ui-text-color;
// Background-color
$bg-color: #000000;
$menu-bg-color: #121212;
$menu-bg-hover-color: lighten($menu-bg-color, 6%);
$menu-bg-active-color: lighten($menu-bg-color, 10%);
$menu-bg-active-hover-color: lighten($menu-bg-color, 16%);
$card-bg-color: #121212;
$card-bg-hover-color: lighten($card-bg-color, 6%);
$succeeded-card-bg-color: lighten($verified-color, 36%);
$list-bg-color: #000000;
$list-bg-hover-color: lighten($list-bg-color, 6%);
$list-bg-active-color: lighten($list-bg-color, 10%);
$list-bg-active-hover-color: lighten($list-bg-color, 16%);
$list-bg-inactive-color: lighten($list-bg-color, 10%);
$text-field-bg-color: $card-bg-color;
$dropdown-field-bg-color: lighten($bg-color, 6%);
$dropdown-field-bg-hover-color: lighten($dropdown-field-bg-color, 6%);
$dropdown-field-bg-active-color: lighten($dropdown-field-bg-color, 10%);
$verified-bg-color: darken($verified-color, 20%);
$unread-bg-color: transparentize($primary-color, 0.8);
// Chip color
$contained-chip-color: #1e1e1e;
$chip-selected-text-color: $primary-color;
$contained-chip-hover-color: lighten($contained-chip-color, 6%);
$contained-chip-selected-color: transparentize($chip-selected-text-color, 0.7);
$outlined-chip-color: #121212;
$outlined-chip-hover-color: lighten($outlined-chip-color, 6%);
$outlined-chip-selected-color: transparentize($chip-selected-text-color, 0.8);
$outlined-chip-selected-border-color: transparentize($chip-selected-text-color, 0.7);
// Relationship tag color
$relationship-tag-color: transparentize(#2e2e2e, 0.2);
// Badge color
$badge-color: $primary-color;
//$badge-color: $secondary-color;
// Icon color
$icon-color: #e2e2e3;
$icon-hover-color: #ffffff;
$icon-bg-hover-color: transparentize(#ffffff, 0.8);
$icon-bg-active-color: transparentize(#ffffff, 0.7);
$disabled-icon-color: darken($icon-color, 16%);
$top-bar-icon-color: $icon-color;
$top-bar-icon-hover-color: $icon-hover-color;
$top-bar-icon-active-color: $primary-color;
$top-bar-unread-icon-color: $secondary-color;
$media-icon-color: transparentize(#ffffff, 0.4);
$media-icon-hover-color: transparentize(#ffffff, 0.2);
$media-icon-bg-color: transparentize(#000000, 0.5);
$media-icon-bg-hover-color: transparentize(#000000, 0.8);
// Control color
$control-border-color: $icon-color;
$control-border-active-color: $primary-color;
$control-border-hover-color: transparentize($control-border-active-color, 0.8);
// Button color
$icon-button-color: $icon-color;
$icon-button-hover-color: $icon-hover-color;
$icon-button-active-color: $primary-color;
$icon-button-active-hover-color: lighten($icon-button-active-color, 10%);
$contained-button-color: $primary-color;
$contained-button-hover-color: lighten($contained-button-color, 10%);
$outlined-button-color: $primary-color;
$outlined-button-hover-color: transparentize($outlined-button-color, 0.8);
$outlined-button-active-color: transparentize($outlined-button-color, 0.7);
$text-button-color: $primary-color;
$text-button-hover-color: transparentize($text-button-color, 0.8);
$text-button-focus-color: transparentize($text-button-color, 0.7);
$disabled-button-color: #cccccc;
$floating-action-button-color: $primary-color;
$floating-action-button-hover-color: lighten($floating-action-button-color, 6%);
$floating-action-button-active-color: lighten($floating-action-button-color, 10%);
$floating-action-button-icon-color: #ffffff;
// Toggle color
$toggle-thumb-color: #ffffff;
$toggle-track-color: darken($toggle-thumb-color, 18%);
$toggle-thumb-active-color: #1a73e8;
$toggle-track-active-color: lighten($toggle-thumb-active-color, 18%);
// Border color
$border-color: #1e1e1e;
$border-hover-color: lighten($border-color, 30%);
$border-active-color: $primary-color;
// Scroll bar color
$scroll-bar-thumb-color: lighten($bg-color, 20%);
$scroll-bar-thumb-hover-color: lighten($scroll-bar-thumb-color, 10%);
$scroll-bar-thumb-active-color: lighten($scroll-bar-thumb-color, 18%);
// App bar color
$top-bar-color: #1e1e1e;
$search-bar-color: lighten($top-bar-color, 6%);
$search-bar-focus-color: #ffffff;
// Tab color
$tab-item-color: transparentize(#ffffff, 0.5);
$tab-item-active-color: $primary-color;
$tab-bg-color: $top-bar-color;
$tab-bg-hover-color: transparentize($tab-item-active-color, 0.9);
$tab-bg-focus-color: transparentize($tab-item-active-color, 0.8);
// Media indicator color
$media-page-indicator-color: #9e9e9e;
$media-page-indicator-active-color: #e6e6e6;
// Progress indicator color
$progress-indicator-color: $primary-color;
$progress-indicator-track-color: lighten($progress-indicator-color, 30%);
$loading-indicator-color: $primary-color;
// Contents color in read status
$read-primary-text-color: transparentize($primary-text-color, 0.3);
$read-secondary-text-color: transparentize($secondary-text-color, 0.3);
$read-ui-text-color: transparentize($ui-text-color, 0.3);
$read-poll-bar-leading-color: transparentize($progress-indicator-color, 0.3);
$read-poll-bar-color: transparentize($progress-indicator-track-color, 0.3);

View File

@ -0,0 +1,148 @@
@charset "UTF-8";
$color-scheme: dark;
// Base color
$primary-color: #2b90d9;
$secondary-color: #2b90d9;
$error-color: #B00020;
$verified-color: #4caf50;
// Text color
$primary-text-color: #ffffff;
$secondary-text-color: #9baec8;
$ui-text-color: #fff;
$inverted-text-color: #000000;
$section-text-color: $primary-color;
$info-text-color: #606984;
$tips-text-color: #6d7889;
$disabled-text-color: rgba(0,0,0,.54);
$link-text-color: #4ea2df;
$menu-text-color: #282c37;
$top-bar-text-color: $ui-text-color;
$search-bar-text-color: $inverted-text-color;
$contained-button-text-color: #ffffff;
$primary-text-on-shadow-color: #ffffff;
$text-field-color: $ui-text-color;
// Background-color
$bg-color: #191b22;
$menu-bg-color: #d9e1e8;
$menu-bg-hover-color: lighten($menu-bg-color, 6%);
$menu-bg-active-color: lighten($menu-bg-color, 10%);
$menu-bg-active-hover-color: lighten($menu-bg-color, 16%);
$card-bg-color: #313543;
$card-bg-hover-color: lighten($card-bg-color, 6%);
$succeeded-card-bg-color: lighten($verified-color, 36%);
$list-bg-color: #282c37;
$list-bg-hover-color: lighten($list-bg-color, 6%);
$list-bg-active-color: lighten($list-bg-color, 10%);
$list-bg-active-hover-color: lighten($list-bg-color, 16%);
$list-bg-inactive-color: lighten($list-bg-color, 10%);
$text-field-bg-color: $card-bg-color;
$dropdown-field-bg-color: lighten($bg-color, 6%);
$dropdown-field-bg-hover-color: lighten($dropdown-field-bg-color, 6%);
$dropdown-field-bg-active-color: lighten($dropdown-field-bg-color, 10%);
$verified-bg-color: darken($verified-color, 20%);
$unread-bg-color: transparentize($primary-color, 0.8);
// Chip color
$contained-chip-color: #42485a;
$chip-selected-text-color: $primary-color;
$contained-chip-hover-color: lighten($contained-chip-color, 6%);
$contained-chip-selected-color: transparentize($chip-selected-text-color, 0.7);
$outlined-chip-color: #393f4f;
$outlined-chip-hover-color: lighten($outlined-chip-color, 6%);
$outlined-chip-selected-color: transparentize($chip-selected-text-color, 0.8);
$outlined-chip-selected-border-color: transparentize($chip-selected-text-color, 0.7);
// Relationship tag color
$relationship-tag-color: transparentize(#42485a, 0.2);
// Badge color
$badge-color: $primary-color;
//$badge-color: $secondary-color;
// Icon color
$icon-color: #9baec8;
$icon-hover-color: lighten($icon-color, 30%);
$icon-bg-hover-color: lighten($bg-color, 14%);
$icon-bg-active-color: lighten($bg-color, 18%);
$disabled-icon-color: lighten($icon-color, 16%);
$top-bar-icon-color: #fff;
$top-bar-icon-hover-color: $icon-hover-color;
$top-bar-icon-active-color: $icon-hover-color;
$top-bar-unread-icon-color: $secondary-color;
$media-icon-color: transparentize(#ffffff, 0.4);
$media-icon-hover-color: transparentize(#ffffff, 0.2);
$media-icon-bg-color: transparentize(#000000, 0.5);
$media-icon-bg-hover-color: transparentize(#000000, 0.8);
// Control color
$control-border-color: $icon-color;
$control-border-active-color: $primary-color;
$control-border-hover-color: transparentize($control-border-active-color, 0.8);
// Button color
$icon-button-color: $icon-color;
$icon-button-hover-color: $icon-hover-color;
$icon-button-active-color: $primary-color;
$icon-button-active-hover-color: lighten($icon-button-active-color, 10%);
$contained-button-color: $primary-color;
$contained-button-hover-color: lighten($contained-button-color, 10%);
$outlined-button-color: $primary-color;
$outlined-button-hover-color: transparentize($outlined-button-color, 0.8);
$outlined-button-active-color: transparentize($outlined-button-color, 0.7);
$text-button-color: $primary-color;
$text-button-hover-color: transparentize($text-button-color, 0.7);
$text-button-focus-color: transparentize($text-button-color, 0.6);
$disabled-button-color: #cccccc;
$floating-action-button-color: $primary-color;
$floating-action-button-hover-color: lighten($floating-action-button-color, 6%);
$floating-action-button-active-color: lighten($floating-action-button-color, 10%);
$floating-action-button-icon-color: #ffffff;
// Toggle color
$toggle-thumb-color: #ffffff;
$toggle-track-color: darken($toggle-thumb-color, 18%);
$toggle-thumb-active-color: #1a73e8;
$toggle-track-active-color: lighten($primary-color, 18%);
// Border color
$border-color: #393f4f;
$border-hover-color: lighten($border-color, 30%);
$border-active-color: $primary-color;
// Scroll bar color
$scroll-bar-thumb-color: lighten($bg-color, 16%);
$scroll-bar-thumb-hover-color: lighten($bg-color, 26%);
$scroll-bar-thumb-active-color: lighten($bg-color, 32%);
// App bar color
$top-bar-color: #313543;
$search-bar-color: lighten($top-bar-color, 6%);
$search-bar-focus-color: #ffffff;
// Tab color
$tab-item-color: #d9e1e8;
$tab-item-active-color: $primary-color;
$tab-bg-color: #1f232b;
$tab-bg-hover-color: transparentize($tab-item-active-color, 0.9);
$tab-bg-focus-color: transparentize($tab-item-active-color, 0.8);
// Media indicator color
$media-page-indicator-color: #9e9e9e;
$media-page-indicator-active-color: #e6e6e6;
// Progress indicator color
$progress-indicator-color: $primary-color;
$progress-indicator-track-color: lighten($progress-indicator-color, 30%);
$loading-indicator-color: $primary-color;
// Contents color in read status
$read-primary-text-color: transparentize($primary-text-color, 0.3);
$read-secondary-text-color: transparentize($secondary-text-color, 0.3);
$read-ui-text-color: transparentize($ui-text-color, 0.3);
$read-poll-bar-leading-color: transparentize($progress-indicator-color, 0.3);
$read-poll-bar-color: transparentize($progress-indicator-track-color, 0.3);

View File

@ -0,0 +1,147 @@
@charset "UTF-8";
$color-scheme: light;
// Base color
$primary-color: #2b90d9;
$secondary-color: #2b90d9;
$error-color: #B00020;
$verified-color: #4caf50;
// Text color
$primary-text-color: #000000;
$secondary-text-color: #5f6368;
$ui-text-color: #202124;
$section-text-color: $primary-color;
$info-text-color: #5e5e5e;
$tips-text-color: #c0c0c0;
$disabled-text-color: rgba(0,0,0,.54);
$link-text-color: #217aba;
$menu-text-color: $ui-text-color;
$top-bar-text-color: $ui-text-color;
$search-bar-text-color: $primary-text-color;
$contained-button-text-color: #ffffff;
$primary-text-on-shadow-color: #ffffff;
$text-field-color: $ui-text-color;
// Background-color
$bg-color: #eff3f5;
$menu-bg-color: $bg-color;
$menu-bg-hover-color: darken($bg-color, 6%);
$menu-bg-active-color: darken($bg-color, 10%);
$menu-bg-active-hover-color: darken($bg-color, 16%);
$card-bg-color: #ffffff;
$card-bg-hover-color: darken($card-bg-color, 6%);
$succeeded-card-bg-color: lighten($verified-color, 36%);
$list-bg-color: #ffffff;
$list-bg-hover-color: darken($list-bg-color, 6%);
$list-bg-active-color: darken($list-bg-color, 10%);
$list-bg-active-hover-color: darken($list-bg-color, 16%);
$list-bg-inactive-color: darken($list-bg-color, 10%);
$text-field-bg-color: $card-bg-color;
$dropdown-field-bg-color: darken($bg-color, 6%);
$dropdown-field-bg-hover-color: darken($dropdown-field-bg-color, 6%);
$dropdown-field-bg-active-color: darken($dropdown-field-bg-color, 10%);
$verified-bg-color: lighten($verified-color, 20%);
$unread-bg-color: transparentize($primary-color, 0.8);
// Chip color
$contained-chip-color: #e0e0e0;
$chip-selected-text-color: $primary-color;
$contained-chip-hover-color: darken($contained-chip-color, 6%);
$contained-chip-selected-color: transparentize($chip-selected-text-color, 0.7);
$outlined-chip-color: #ffffff;
$outlined-chip-hover-color: darken($outlined-chip-color, 6%);
$outlined-chip-selected-color: transparentize($chip-selected-text-color, 0.8);
$outlined-chip-selected-border-color: transparentize($chip-selected-text-color, 0.7);
// Relationship tag color
$relationship-tag-color: transparentize(#e0e0e0, 0.2);
// Badge color
$badge-color: $primary-color;
//$badge-color: $secondary-color;
// Icon color
$icon-color: #282c37;
$icon-hover-color: darken($icon-color, 30%);
$icon-bg-hover-color: darken($bg-color, 4%);
$icon-bg-active-color: darken($bg-color, 8%);
$disabled-icon-color: lighten($icon-color, 16%);
$top-bar-icon-color: $icon-color;
$top-bar-icon-hover-color: $icon-hover-color;
$top-bar-icon-active-color: $icon-hover-color;
$top-bar-unread-icon-color: $secondary-color;
$media-icon-color: transparentize(#ffffff, 0.4);
$media-icon-hover-color: transparentize(#ffffff, 0.2);
$media-icon-bg-color: transparentize(#000000, 0.5);
$media-icon-bg-hover-color: transparentize(#000000, 0.8);
// Control color
$control-border-color: $icon-color;
$control-border-active-color: $primary-color;
$control-border-hover-color: transparentize($control-border-active-color, 0.8);
// Button color
$icon-button-color: $icon-color;
$icon-button-hover-color: $icon-hover-color;
$icon-button-active-color: $primary-color;
$icon-button-active-hover-color: lighten($icon-button-active-color, 10%);
$contained-button-color: $primary-color;
$contained-button-hover-color: lighten($contained-button-color, 10%);
$outlined-button-color: $primary-color;
$outlined-button-hover-color: transparentize($outlined-button-color, 0.8);
$outlined-button-active-color: transparentize($outlined-button-color, 0.7);
$text-button-color: $primary-color;
$text-button-hover-color: lighten($text-button-color, 36%);
$text-button-focus-color: lighten($text-button-color, 30%);
$disabled-button-color: #cccccc;
$floating-action-button-color: $primary-color;
$floating-action-button-hover-color: lighten($floating-action-button-color, 6%);
$floating-action-button-active-color: lighten($floating-action-button-color, 10%);
$floating-action-button-icon-color: #ffffff;
// Toggle color
$toggle-thumb-color: #ffffff;
$toggle-track-color: darken($toggle-thumb-color, 18%);
$toggle-thumb-active-color: $primary-color;
$toggle-track-active-color: lighten($primary-color, 26%);
// Border color
$border-color: #dadce0;
$border-hover-color: darken($border-color, 30%);
$border-active-color: $primary-color;
// Scroll bar color
$scroll-bar-thumb-color: #ccd7e0;
$scroll-bar-thumb-hover-color: darken($scroll-bar-thumb-color, 10%);
$scroll-bar-thumb-active-color: darken($scroll-bar-thumb-color, 18%);
// App bar color
$top-bar-color: #ffffff;
$search-bar-color: #d9e1e8;
$search-bar-focus-color: $bg-color;
// Tab color
$tab-item-color: #282c37;
$tab-item-active-color: $primary-color;
$tab-bg-color: #e6ebf0;
$tab-bg-hover-color: transparentize($tab-item-active-color, 0.9);
$tab-bg-focus-color: transparentize($tab-item-active-color, 0.8);
// Media indicator color
$media-page-indicator-color: #9e9e9e;
$media-page-indicator-active-color: #e6e6e6;
// Progress indicator color
$progress-indicator-color: $primary-color;
$progress-indicator-track-color: lighten($progress-indicator-color, 30%);
$loading-indicator-color: $primary-color;
// Contents color in read status
$read-primary-text-color: transparentize($primary-text-color, 0.3);
$read-secondary-text-color: transparentize($secondary-text-color, 0.3);
$read-ui-text-color: transparentize($ui-text-color, 0.3);
$read-poll-bar-leading-color: transparentize($progress-indicator-color, 0.3);
$read-poll-bar-color: transparentize($progress-indicator-track-color, 0.3);

View File

@ -0,0 +1,147 @@
@charset "UTF-8";
$color-scheme: light;
// Base color
$primary-color: #4285f4;
$secondary-color: #db4437;
$error-color: #B00020;
$verified-color: #4caf50;
// Text color
$primary-text-color: #000000;
$secondary-text-color: #5f6368;
$ui-text-color: #202124;
$inverted-text-color: #ffffff;
$section-text-color: $primary-color;
$info-text-color: #5e5e5e;
$tips-text-color: #c0c0c0;
$disabled-text-color: rgba(0,0,0,.54);
$link-text-color: #4285f4;
$menu-text-color: $ui-text-color;
$top-bar-text-color: $inverted-text-color;
$search-bar-text-color: $primary-text-color;
$contained-button-text-color: #ffffff;
$primary-text-on-shadow-color: #ffffff;
$text-field-color: $ui-text-color;
// Background-color
$bg-color: #fafafa;
$menu-bg-color: #ffffff;
$menu-bg-hover-color: darken($menu-bg-color, 6%);
$menu-bg-active-color: darken($menu-bg-color, 10%);
$menu-bg-active-hover-color: darken($menu-bg-color, 16%);
$card-bg-color: #ffffff;
$card-bg-hover-color: darken($card-bg-color, 6%);
$succeeded-card-bg-color: lighten($verified-color, 36%);
$list-bg-color: #ffffff;
$list-bg-hover-color: darken($list-bg-color, 6%);
$list-bg-active-color: darken($list-bg-color, 10%);
$list-bg-active-hover-color: darken($list-bg-color, 16%);
$list-bg-inactive-color: darken($list-bg-color, 10%);
$text-field-bg-color: $card-bg-color;
$dropdown-field-bg-color: darken($bg-color, 6%);
$dropdown-field-bg-hover-color: darken($dropdown-field-bg-color, 6%);
$dropdown-field-bg-active-color: darken($dropdown-field-bg-color, 10%);
$verified-bg-color: lighten($verified-color, 20%);
$unread-bg-color: transparentize($primary-color, 0.8);
// Chip color
$contained-chip-color: #e0e0e0;
$chip-selected-text-color: $primary-color;
$contained-chip-hover-color: darken($contained-chip-color, 6%);
$contained-chip-selected-color: transparentize($chip-selected-text-color, 0.7);
$outlined-chip-color: #ffffff;
$outlined-chip-hover-color: darken($outlined-chip-color, 6%);
$outlined-chip-selected-color: transparentize($chip-selected-text-color, 0.8);
$outlined-chip-selected-border-color: transparentize($chip-selected-text-color, 0.7);
// Relationship tag color
$relationship-tag-color: transparentize(#e0e0e0, 0.2);
// Badge color
$badge-color: $primary-color;
//$badge-color: $secondary-color;
// Icon color
$icon-color: #757575;
$icon-hover-color: darken($icon-color, 30%);
$icon-bg-hover-color: transparentize(#000000, 0.9);
$icon-bg-active-color: transparentize(#000000, 0.8);
$disabled-icon-color: lighten($icon-color, 16%);
$top-bar-icon-color: #ffffff;
$top-bar-icon-hover-color: darken($top-bar-icon-color, 10%);
$top-bar-icon-active-color: darken($top-bar-icon-color, 18%);
$media-icon-color: transparentize(#ffffff, 0.4);
$media-icon-hover-color: transparentize(#ffffff, 0.2);
$media-icon-bg-color: transparentize(#000000, 0.5);
$media-icon-bg-hover-color: transparentize(#000000, 0.8);
// Control color
$control-border-color: $icon-color;
$control-border-active-color: $primary-color;
$control-border-hover-color: transparentize($control-border-active-color, 0.8);
// Button color
$icon-button-color: $icon-color;
$icon-button-hover-color: $icon-hover-color;
$icon-button-active-color: $primary-color;
$icon-button-active-hover-color: lighten($icon-button-active-color, 10%);
$contained-button-color: $primary-color;
$contained-button-hover-color: lighten($contained-button-color, 10%);
$outlined-button-color: $primary-color;
$outlined-button-hover-color: transparentize($outlined-button-color, 0.8);
$outlined-button-active-color: transparentize($outlined-button-color, 0.7);
$text-button-color: $primary-color;
$text-button-hover-color: transparentize($text-button-color, 0.8);
$text-button-focus-color: transparentize($text-button-color, 0.7);
$disabled-button-color: #cccccc;
$floating-action-button-color: $secondary-color;
$floating-action-button-hover-color: lighten($floating-action-button-color, 6%);
$floating-action-button-active-color: lighten($floating-action-button-color, 10%);
$floating-action-button-icon-color: #ffffff;
// Toggle color
$toggle-thumb-color: #ffffff;
$toggle-track-color: darken($toggle-thumb-color, 18%);
$toggle-thumb-active-color: #1a73e8;
$toggle-track-active-color: lighten($toggle-thumb-active-color, 18%);
// Border color
$border-color: #dadce0;
$border-hover-color: darken($border-color, 30%);
$border-active-color: $primary-color;
// Scroll bar color
$scroll-bar-thumb-color: darken($bg-color, 20%);
$scroll-bar-thumb-hover-color: darken($scroll-bar-thumb-color, 10%);
$scroll-bar-thumb-active-color: darken($scroll-bar-thumb-color, 18%);
// App bar color
$top-bar-color: #db4437;
$search-bar-color: darken($bg-color, 6%);
$search-bar-focus-color: $bg-color;
// Tab color
$tab-item-color: transparentize(#ffffff, 0.5);
$tab-item-active-color: #ffffff;
$tab-bg-color: $top-bar-color;
$tab-bg-hover-color: transparentize($tab-item-active-color, 0.9);
$tab-bg-focus-color: transparentize($tab-item-active-color, 0.8);
// Media indicator color
$media-page-indicator-color: #9e9e9e;
$media-page-indicator-active-color: #e6e6e6;
// Progress indicator color
$progress-indicator-color: $primary-color;
$progress-indicator-track-color: lighten($progress-indicator-color, 30%);
$loading-indicator-color: $primary-color;
// Contents color in read status
$read-primary-text-color: transparentize($primary-text-color, 0.3);
$read-secondary-text-color: transparentize($secondary-text-color, 0.3);
$read-ui-text-color: transparentize($ui-text-color, 0.3);
$read-poll-bar-leading-color: transparentize($progress-indicator-color, 0.3);
$read-poll-bar-color: transparentize($progress-indicator-track-color, 0.3);

View File

@ -0,0 +1,147 @@
@charset "UTF-8";
$color-scheme: dark;
// Base color
$primary-color: #4285f4;
$secondary-color: #db4437;
$error-color: #B00020;
$verified-color: #4caf50;
// Text color
$primary-text-color: #ffffff;
$secondary-text-color: #9aa0a6;
$ui-text-color: #e8eaed;
$inverted-text-color: #000000;
$section-text-color: $primary-color;
$info-text-color: #9aa0a6;
$tips-text-color: #c0c0c0;
$disabled-text-color: rgba(0,0,0,.54);
$link-text-color: #4285f4;
$menu-text-color: $ui-text-color;
$top-bar-text-color: $ui-text-color;
$search-bar-text-color: $inverted-text-color;
$contained-button-text-color: #ffffff;
$primary-text-on-shadow-color: #ffffff;
$text-field-color: $ui-text-color;
// Background-color
$bg-color: #303030;
$menu-bg-color: #424242;
$menu-bg-hover-color: lighten($menu-bg-color, 6%);
$menu-bg-active-color: lighten($menu-bg-color, 10%);
$menu-bg-active-hover-color: lighten($menu-bg-color, 16%);
$card-bg-color: #424242;
$card-bg-hover-color: lighten($card-bg-color, 6%);
$succeeded-card-bg-color: lighten($verified-color, 36%);
$list-bg-color: #424242;
$list-bg-hover-color: lighten($list-bg-color, 6%);
$list-bg-active-color: lighten($list-bg-color, 10%);
$list-bg-active-hover-color: lighten($list-bg-color, 16%);
$list-bg-inactive-color: lighten($list-bg-color, 10%);
$text-field-bg-color: $card-bg-color;
$dropdown-field-bg-color: lighten($bg-color, 6%);
$dropdown-field-bg-hover-color: lighten($dropdown-field-bg-color, 6%);
$dropdown-field-bg-active-color: lighten($dropdown-field-bg-color, 10%);
$verified-bg-color: darken($verified-color, 20%);
$unread-bg-color: transparentize($primary-color, 0.8);
// Chip color
$contained-chip-color: #2e2e2e;
$chip-selected-text-color: $primary-color;
$contained-chip-hover-color: lighten($contained-chip-color, 6%);
$contained-chip-selected-color: transparentize($chip-selected-text-color, 0.7);
$outlined-chip-color: #1e1e1e;
$outlined-chip-hover-color: lighten($outlined-chip-color, 6%);
$outlined-chip-selected-color: transparentize($chip-selected-text-color, 0.8);
$outlined-chip-selected-border-color: transparentize($chip-selected-text-color, 0.7);
// Relationship tag color
$relationship-tag-color: transparentize(#2e2e2e, 0.2);
// Badge color
$badge-color: $primary-color;
//$badge-color: $secondary-color;
// Icon color
$icon-color: #e2e2e3;
$icon-hover-color: #ffffff;
$icon-bg-hover-color: transparentize(#ffffff, 0.8);
$icon-bg-active-color: transparentize(#ffffff, 0.7);
$disabled-icon-color: darken($icon-color, 16%);
$top-bar-icon-color: #ffffff;
$top-bar-icon-hover-color: lighten($top-bar-icon-color, 10%);
$top-bar-icon-active-color: lighten($top-bar-icon-color, 18%);
$media-icon-color: transparentize(#ffffff, 0.4);
$media-icon-hover-color: transparentize(#ffffff, 0.2);
$media-icon-bg-color: transparentize(#000000, 0.5);
$media-icon-bg-hover-color: transparentize(#000000, 0.8);
// Control color
$control-border-color: $icon-color;
$control-border-active-color: $primary-color;
$control-border-hover-color: transparentize($control-border-active-color, 0.8);
// Button color
$icon-button-color: $icon-color;
$icon-button-hover-color: $icon-hover-color;
$icon-button-active-color: $primary-color;
$icon-button-active-hover-color: lighten($icon-button-active-color, 10%);
$contained-button-color: $primary-color;
$contained-button-hover-color: lighten($contained-button-color, 10%);
$outlined-button-color: $primary-color;
$outlined-button-hover-color: transparentize($outlined-button-color, 0.8);
$outlined-button-active-color: transparentize($outlined-button-color, 0.7);
$text-button-color: $primary-color;
$text-button-hover-color: transparentize($text-button-color, 0.8);
$text-button-focus-color: transparentize($text-button-color, 0.7);
$disabled-button-color: #cccccc;
$floating-action-button-color: $primary-color;
$floating-action-button-hover-color: lighten($floating-action-button-color, 6%);
$floating-action-button-active-color: lighten($floating-action-button-color, 10%);
$floating-action-button-icon-color: #ffffff;
// Toggle color
$toggle-thumb-color: #ffffff;
$toggle-track-color: darken($toggle-thumb-color, 18%);
$toggle-thumb-active-color: #1a73e8;
$toggle-track-active-color: lighten($toggle-thumb-active-color, 18%);
// Border color
$border-color: #2e2e2e;
$border-hover-color: lighten($border-color, 30%);
$border-active-color: $primary-color;
// Scroll bar color
$scroll-bar-thumb-color: lighten($bg-color, 20%);
$scroll-bar-thumb-hover-color: lighten($bg-color, 30%);
$scroll-bar-thumb-active-color: lighten($bg-color, 38%);
// App bar color
$top-bar-color: #1565C0;
$search-bar-color: lighten($bg-color, 6%);
$search-bar-focus-color: #ffffff;
// Tab color
$tab-item-color: transparentize(#ffffff, 0.5);
$tab-item-active-color: $primary-color;
$tab-bg-color: $top-bar-color;
$tab-bg-hover-color: transparentize($tab-item-active-color, 0.9);
$tab-bg-focus-color: transparentize($tab-item-active-color, 0.8);
// Media indicator color
$media-page-indicator-color: #9e9e9e;
$media-page-indicator-active-color: #e6e6e6;
// Progress indicator color
$progress-indicator-color: $primary-color;
$progress-indicator-track-color: lighten($progress-indicator-color, 30%);
$loading-indicator-color: $primary-color;
// Contents color in read status
$read-primary-text-color: transparentize($primary-text-color, 0.3);
$read-secondary-text-color: transparentize($secondary-text-color, 0.3);
$read-ui-text-color: transparentize($ui-text-color, 0.3);
$read-poll-bar-leading-color: transparentize($progress-indicator-color, 0.3);
$read-poll-bar-color: transparentize($progress-indicator-track-color, 0.3);

View File

@ -0,0 +1,148 @@
@charset "UTF-8";
$color-scheme: light;
// Base color
$primary-color: #4285f4;
$secondary-color: #db4437;
$error-color: #B00020;
$verified-color: #4caf50;
// Text color
$primary-text-color: #000000;
$secondary-text-color: #5f6368;
$ui-text-color: #202124;
$inverted-text-color: #ffffff;
$section-text-color: $primary-color;
$info-text-color: #5e5e5e;
$tips-text-color: #c0c0c0;
$disabled-text-color: rgba(0,0,0,.54);
$link-text-color: #4285f4;
$menu-text-color: $ui-text-color;
$top-bar-text-color: $inverted-text-color;
$search-bar-text-color: $primary-text-color;
$contained-button-text-color: #ffffff;
$primary-text-on-shadow-color: #ffffff;
$text-field-color: $ui-text-color;
// Background-color
$bg-color: #fafafa;
$menu-bg-color: #ffffff;
$menu-bg-hover-color: darken($menu-bg-color, 6%);
$menu-bg-active-color: darken($menu-bg-color, 10%);
$menu-bg-active-hover-color: darken($menu-bg-color, 16%);
$card-bg-color: #ffffff;
$card-bg-hover-color: darken($card-bg-color, 6%);
$succeeded-card-bg-color: lighten($verified-color, 36%);
$list-bg-color: #ffffff;
$list-bg-hover-color: darken($list-bg-color, 6%);
$list-bg-active-color: darken($list-bg-color, 10%);
$list-bg-active-hover-color: darken($list-bg-color, 16%);
$list-bg-inactive-color: darken($list-bg-color, 10%);
$text-field-bg-color: $card-bg-color;
$dropdown-field-bg-color: darken($bg-color, 6%);
$dropdown-field-bg-hover-color: darken($dropdown-field-bg-color, 6%);
$dropdown-field-bg-active-color: darken($dropdown-field-bg-color, 10%);
$verified-bg-color: lighten($verified-color, 20%);
$unread-bg-color: transparentize($primary-color, 0.8);
// Chip color
$contained-chip-color: #e0e0e0;
$chip-selected-text-color: $primary-color;
$contained-chip-hover-color: darken($contained-chip-color, 6%);
$contained-chip-selected-color: transparentize($chip-selected-text-color, 0.7);
$outlined-chip-color: #ffffff;
$outlined-chip-hover-color: darken($outlined-chip-color, 6%);
$outlined-chip-selected-color: transparentize($chip-selected-text-color, 0.8);
$outlined-chip-selected-border-color: transparentize($chip-selected-text-color, 0.7);
// Relationship tag color
$relationship-tag-color: transparentize(#e0e0e0, 0.2);
// Badge color
$badge-color: $primary-color;
//$badge-color: $secondary-color;
// Icon color
$icon-color: #757575;
$icon-hover-color: darken($icon-color, 30%);
$icon-bg-hover-color: transparentize(#000000, 0.9);
$icon-bg-active-color: transparentize(#000000, 0.8);
$disabled-icon-color: lighten($icon-color, 16%);
$top-bar-icon-color: #ffffff;
$top-bar-icon-hover-color: darken($top-bar-icon-color, 10%);
$top-bar-icon-active-color: darken($top-bar-icon-color, 18%);
$top-bar-unread-icon-color: darken($primary-color, 30%);
$media-icon-color: transparentize(#ffffff, 0.4);
$media-icon-hover-color: transparentize(#ffffff, 0.2);
$media-icon-bg-color: transparentize(#000000, 0.5);
$media-icon-bg-hover-color: transparentize(#000000, 0.8);
// Control color
$control-border-color: $icon-color;
$control-border-active-color: $primary-color;
$control-border-hover-color: transparentize($control-border-active-color, 0.8);
// Button color
$icon-button-color: $icon-color;
$icon-button-hover-color: $icon-hover-color;
$icon-button-active-color: $primary-color;
$icon-button-active-hover-color: lighten($icon-button-active-color, 10%);
$contained-button-color: $primary-color;
$contained-button-hover-color: lighten($contained-button-color, 10%);
$outlined-button-color: $primary-color;
$outlined-button-hover-color: transparentize($outlined-button-color, 0.8);
$outlined-button-active-color: transparentize($outlined-button-color, 0.7);
$text-button-color: $primary-color;
$text-button-hover-color: transparentize($text-button-color, 0.8);
$text-button-focus-color: transparentize($text-button-color, 0.7);
$disabled-button-color: #cccccc;
$floating-action-button-color: $primary-color;
$floating-action-button-hover-color: lighten($floating-action-button-color, 6%);
$floating-action-button-active-color: lighten($floating-action-button-color, 10%);
$floating-action-button-icon-color: #ffffff;
// Toggle color
$toggle-thumb-color: #ffffff;
$toggle-track-color: darken($toggle-thumb-color, 18%);
$toggle-thumb-active-color: #1a73e8;
$toggle-track-active-color: lighten($toggle-thumb-active-color, 18%);
// Border color
$border-color: #dadce0;
$border-hover-color: darken($border-color, 30%);
$border-active-color: $primary-color;
// Scroll bar color
$scroll-bar-thumb-color: darken($bg-color, 20%);
$scroll-bar-thumb-hover-color: darken($scroll-bar-thumb-color, 10%);
$scroll-bar-thumb-active-color: darken($scroll-bar-thumb-color, 18%);
// App bar color
$top-bar-color: #2196f3;
$search-bar-color: darken($bg-color, 6%);
$search-bar-focus-color: $bg-color;
// Tab color
$tab-item-color: transparentize(#ffffff, 0.5);
$tab-item-active-color: #ffffff;
$tab-bg-color: $top-bar-color;
$tab-bg-hover-color: transparentize($tab-item-active-color, 0.9);
$tab-bg-focus-color: transparentize($tab-item-active-color, 0.8);
// Media indicator color
$media-page-indicator-color: #9e9e9e;
$media-page-indicator-active-color: #e6e6e6;
// Progress indicator color
$progress-indicator-color: $primary-color;
$progress-indicator-track-color: lighten($progress-indicator-color, 30%);
$loading-indicator-color: $primary-color;
// Contents color in read status
$read-primary-text-color: transparentize($primary-text-color, 0.3);
$read-secondary-text-color: transparentize($secondary-text-color, 0.3);
$read-ui-text-color: transparentize($ui-text-color, 0.3);
$read-poll-bar-leading-color: transparentize($progress-indicator-color, 0.3);
$read-poll-bar-color: transparentize($progress-indicator-track-color, 0.3);

View File

@ -0,0 +1,148 @@
@charset "UTF-8";
$color-scheme: dark;
// Base color
$primary-color: #4285f4;
$secondary-color: #db4437;
$error-color: #B00020;
$verified-color: #4caf50;
// Text color
$primary-text-color: #ffffff;
$secondary-text-color: #9aa0a6;
$ui-text-color: #e8eaed;
$inverted-text-color: #000000;
$section-text-color: $primary-color;
$info-text-color: #9aa0a6;
$tips-text-color: #c0c0c0;
$disabled-text-color: rgba(0,0,0,.54);
$link-text-color: #4285f4;
$menu-text-color: $ui-text-color;
$top-bar-text-color: $ui-text-color;
$search-bar-text-color: $inverted-text-color;
$contained-button-text-color: #ffffff;
$primary-text-on-shadow-color: #ffffff;
$text-field-color: $ui-text-color;
// Background-color
$bg-color: #121212;
$menu-bg-color: #1e1e1e;
$menu-bg-hover-color: lighten($menu-bg-color, 6%);
$menu-bg-active-color: lighten($menu-bg-color, 10%);
$menu-bg-active-hover-color: lighten($menu-bg-color, 16%);
$card-bg-color: #1e1e1e;
$card-bg-hover-color: lighten($card-bg-color, 6%);
$succeeded-card-bg-color: lighten($verified-color, 36%);
$list-bg-color: #1e1e1e;
$list-bg-hover-color: lighten($list-bg-color, 6%);
$list-bg-active-color: lighten($list-bg-color, 10%);
$list-bg-active-hover-color: lighten($list-bg-color, 16%);
$list-bg-inactive-color: lighten($list-bg-color, 10%);
$text-field-bg-color: $card-bg-color;
$dropdown-field-bg-color: lighten($bg-color, 6%);
$dropdown-field-bg-hover-color: lighten($dropdown-field-bg-color, 6%);
$dropdown-field-bg-active-color: lighten($dropdown-field-bg-color, 10%);
$verified-bg-color: darken($verified-color, 20%);
$unread-bg-color: transparentize($primary-color, 0.8);
// Chip color
$contained-chip-color: #2e2e2e;
$chip-selected-text-color: $primary-color;
$contained-chip-hover-color: lighten($contained-chip-color, 6%);
$contained-chip-selected-color: transparentize($chip-selected-text-color, 0.7);
$outlined-chip-color: #1e1e1e;
$outlined-chip-hover-color: lighten($outlined-chip-color, 6%);
$outlined-chip-selected-color: transparentize($chip-selected-text-color, 0.8);
$outlined-chip-selected-border-color: transparentize($chip-selected-text-color, 0.7);
// Relationship tag color
$relationship-tag-color: transparentize(#2e2e2e, 0.2);
// Badge color
$badge-color: $primary-color;
//$badge-color: $secondary-color;
// Icon color
$icon-color: #e2e2e3;
$icon-hover-color: #ffffff;
$icon-bg-hover-color: transparentize(#ffffff, 0.8);
$icon-bg-active-color: transparentize(#ffffff, 0.7);
$disabled-icon-color: darken($icon-color, 16%);
$top-bar-icon-color: $icon-color;
$top-bar-icon-hover-color: $icon-hover-color;
$top-bar-icon-active-color: $primary-color;
$top-bar-unread-icon-color: $secondary-color;
$media-icon-color: transparentize(#ffffff, 0.4);
$media-icon-hover-color: transparentize(#ffffff, 0.2);
$media-icon-bg-color: transparentize(#000000, 0.5);
$media-icon-bg-hover-color: transparentize(#000000, 0.8);
// Control color
$control-border-color: $icon-color;
$control-border-active-color: $primary-color;
$control-border-hover-color: transparentize($control-border-active-color, 0.8);
// Button color
$icon-button-color: $icon-color;
$icon-button-hover-color: $icon-hover-color;
$icon-button-active-color: $primary-color;
$icon-button-active-hover-color: lighten($icon-button-active-color, 10%);
$contained-button-color: $primary-color;
$contained-button-hover-color: lighten($contained-button-color, 10%);
$outlined-button-color: $primary-color;
$outlined-button-hover-color: transparentize($outlined-button-color, 0.8);
$outlined-button-active-color: transparentize($outlined-button-color, 0.7);
$text-button-color: $primary-color;
$text-button-hover-color: transparentize($text-button-color, 0.8);
$text-button-focus-color: transparentize($text-button-color, 0.7);
$disabled-button-color: #cccccc;
$floating-action-button-color: #272727;
$floating-action-button-hover-color: lighten($floating-action-button-color, 6%);
$floating-action-button-active-color: lighten($floating-action-button-color, 10%);
$floating-action-button-icon-color: $primary-color;
// Toggle color
$toggle-thumb-color: #ffffff;
$toggle-track-color: darken($toggle-thumb-color, 18%);
$toggle-thumb-active-color: #1a73e8;
$toggle-track-active-color: lighten($toggle-thumb-active-color, 18%);
// Border color
$border-color: #5f6368;
$border-hover-color: lighten($border-color, 30%);
$border-active-color: $primary-color;
// Scroll bar color
$scroll-bar-thumb-color: lighten($bg-color, 20%);
$scroll-bar-thumb-hover-color: lighten($scroll-bar-thumb-color, 10%);
$scroll-bar-thumb-active-color: lighten($scroll-bar-thumb-color, 18%);
// App bar color
$top-bar-color: #272727;
$search-bar-color: lighten($top-bar-color, 18%);
$search-bar-focus-color: #ffffff;
// Tab color
$tab-item-color: transparentize(#ffffff, 0.5);
$tab-item-active-color: $primary-color;
$tab-bg-color: $top-bar-color;
$tab-bg-hover-color: transparentize($tab-item-active-color, 0.9);
$tab-bg-focus-color: transparentize($tab-item-active-color, 0.8);
// Media indicator color
$media-page-indicator-color: #9e9e9e;
$media-page-indicator-active-color: #e6e6e6;
// Progress indicator color
$progress-indicator-color: $primary-color;
$progress-indicator-track-color: lighten($progress-indicator-color, 30%);
$loading-indicator-color: $primary-color;
// Contents color in read status
$read-primary-text-color: transparentize($primary-text-color, 0.3);
$read-secondary-text-color: transparentize($secondary-text-color, 0.3);
$read-ui-text-color: transparentize($ui-text-color, 0.3);
$read-poll-bar-leading-color: transparentize($progress-indicator-color, 0.3);
$read-poll-bar-color: transparentize($progress-indicator-track-color, 0.3);

View File

@ -0,0 +1,148 @@
@charset "UTF-8";
$color-scheme: light;
// Base color
$primary-color: #4285f4;
$secondary-color: #db4437;
$error-color: #B00020;
$verified-color: #4caf50;
// Text color
$primary-text-color: #000000;
$secondary-text-color: #5f6368;
$ui-text-color: #202124;
$inverted-text-color: #ffffff;
$section-text-color: $primary-color;
$info-text-color: #5e5e5e;
$tips-text-color: #c0c0c0;
$disabled-text-color: rgba(0,0,0,.54);
$link-text-color: #4285f4;
$menu-text-color: $ui-text-color;
$top-bar-text-color: $ui-text-color;
$search-bar-text-color: $primary-text-color;
$contained-button-text-color: #ffffff;
$primary-text-on-shadow-color: #ffffff;
$text-field-color: $ui-text-color;
// Background-color
$bg-color: #ffffff;
$menu-bg-color: $bg-color;
$menu-bg-hover-color: darken($menu-bg-color, 6%);
$menu-bg-active-color: darken($menu-bg-color, 10%);
$menu-bg-active-hover-color: darken($menu-bg-color, 16%);
$card-bg-color: $bg-color;
$card-bg-hover-color: darken($card-bg-color, 6%);
$succeeded-card-bg-color: lighten($verified-color, 36%);
$list-bg-color: $bg-color;
$list-bg-hover-color: darken($list-bg-color, 6%);
$list-bg-active-color: darken($list-bg-color, 10%);
$list-bg-active-hover-color: darken($list-bg-color, 16%);
$list-bg-inactive-color: darken($list-bg-color, 10%);
$text-field-bg-color: $card-bg-color;
$dropdown-field-bg-color: darken($bg-color, 6%);
$dropdown-field-bg-hover-color: darken($dropdown-field-bg-color, 6%);
$dropdown-field-bg-active-color: darken($dropdown-field-bg-color, 10%);
$verified-bg-color: lighten($verified-color, 20%);
$unread-bg-color: transparentize($primary-color, 0.8);
// Chip color
$contained-chip-color: #e0e0e0;
$chip-selected-text-color: $primary-color;
$contained-chip-hover-color: darken($contained-chip-color, 6%);
$contained-chip-selected-color: transparentize($chip-selected-text-color, 0.7);
$outlined-chip-color: #ffffff;
$outlined-chip-hover-color: darken($outlined-chip-color, 6%);
$outlined-chip-selected-color: transparentize($chip-selected-text-color, 0.8);
$outlined-chip-selected-border-color: transparentize($chip-selected-text-color, 0.7);
// Relationship tag color
$relationship-tag-color: transparentize(#e0e0e0, 0.2);
// Badge color
$badge-color: $primary-color;
//$badge-color: $secondary-color;
// Icon color
$icon-color: #757575;
$icon-hover-color: darken($icon-color, 30%);
$icon-bg-hover-color: transparentize(#000000, 0.9);
$icon-bg-active-color: transparentize(#000000, 0.8);
$disabled-icon-color: lighten($icon-color, 16%);
$top-bar-icon-color: $icon-color;
$top-bar-icon-hover-color: $icon-hover-color;
$top-bar-icon-active-color: $primary-color;
$top-bar-unread-icon-color: $secondary-color;
$media-icon-color: transparentize(#ffffff, 0.4);
$media-icon-hover-color: transparentize(#ffffff, 0.2);
$media-icon-bg-color: transparentize(#000000, 0.5);
$media-icon-bg-hover-color: transparentize(#000000, 0.8);
// Control color
$control-border-color: $icon-color;
$control-border-active-color: $primary-color;
$control-border-hover-color: transparentize($control-border-active-color, 0.8);
// Button color
$icon-button-color: $icon-color;
$icon-button-hover-color: $icon-hover-color;
$icon-button-active-color: $primary-color;
$icon-button-active-hover-color: lighten($icon-button-active-color, 10%);
$contained-button-color: $primary-color;
$contained-button-hover-color: lighten($contained-button-color, 10%);
$outlined-button-color: $primary-color;
$outlined-button-hover-color: transparentize($outlined-button-color, 0.8);
$outlined-button-active-color: transparentize($outlined-button-color, 0.7);
$text-button-color: $primary-color;
$text-button-hover-color: transparentize($text-button-color, 0.8);
$text-button-focus-color: transparentize($text-button-color, 0.7);
$disabled-button-color: #cccccc;
$floating-action-button-color: #ffffff;
$floating-action-button-hover-color: darken($floating-action-button-color, 6%);
$floating-action-button-active-color: darken($floating-action-button-color, 10%);
$floating-action-button-icon-color: $primary-color;
// Toggle color
$toggle-thumb-color: #ffffff;
$toggle-track-color: darken($toggle-thumb-color, 18%);
$toggle-thumb-active-color: #1a73e8;
$toggle-track-active-color: lighten($toggle-thumb-active-color, 18%);
// Border color
$border-color: #dadce0;
$border-hover-color: darken($border-color, 30%);
$border-active-color: $primary-color;
// Scroll bar color
$scroll-bar-thumb-color: darken($bg-color, 20%);
$scroll-bar-thumb-hover-color: darken($bg-color, 30%);
$scroll-bar-thumb-active-color: darken($bg-color, 38%);
// App bar color
$top-bar-color: $bg-color;
$search-bar-color: darken($bg-color, 6%);
$search-bar-focus-color: $bg-color;
// Tab color
$tab-item-color: transparentize(#000000, 0.5);
$tab-item-active-color: $primary-color;
$tab-bg-color: $top-bar-color;
$tab-bg-hover-color: transparentize($tab-item-active-color, 0.9);
$tab-bg-focus-color: transparentize($tab-item-active-color, 0.8);
// Media indicator color
$media-page-indicator-color: #9e9e9e;
$media-page-indicator-active-color: #e6e6e6;
// Progress indicator color
$progress-indicator-color: $primary-color;
$progress-indicator-track-color: lighten($progress-indicator-color, 30%);
$loading-indicator-color: $primary-color;
// Contents color in read status
$read-primary-text-color: transparentize($primary-text-color, 0.3);
$read-secondary-text-color: transparentize($secondary-text-color, 0.3);
$read-ui-text-color: transparentize($ui-text-color, 0.3);
$read-poll-bar-leading-color: transparentize($progress-indicator-color, 0.3);
$read-poll-bar-color: transparentize($progress-indicator-track-color, 0.3);

View File

@ -0,0 +1,78 @@
@charset "UTF-8";
@import '../theme/mixins';
// Navigation bar radius
$nav-bar-radius: 2px;
// Search bar radius
$search-bar-radius: 2px;
// Bar radius settings
$bar-radius: 0;
// Button radius settings
$button-radius: 2px;
// Card radius settings
$card-radius: 2px;
// Dialog radius settings
$dialog-radius: 2px;
// Menu radius settings
$menu-radius: 2px;
// Table radius settings
$table-radius: 2px;
// Media radius settings
$media-radius: 0;
// Navigation drawer item settings
$nav-drawer-item-radius: 0;
// Avatar cropping settings
$avatar-radius: 50%; // Rounded cropping
//$avatar-radius: 2px // Material v1 square
//$avatar-radius: $card-radius // Fit to card radius
// Chip settings
$chip-type: contained; // Material v1 styled contained chip
// Button on statuses
$button-on-status: text; // Material v1 styled text button
//$button-on-status: outlined; // Material v2 styled outlined button
// Button shadow
$button-shadow: true; // Material v1 styled colored button with shadow
// Floating Action Button size
$fab-size: 56px;
// CW text field border
$cw-area-border: underlined;
//$cw-area-border: none;
// Compose text field border
$compose-area-border: none;
//$compose-area-border: underlined;
// Other text field border
$other-text-area-border: underlined;
//$other-text-area-border: none;

View File

@ -0,0 +1,92 @@
@charset "UTF-8";
@import '../theme/mixins';
// Navigation bar radius
$nav-bar-radius: 8px;
//$nav-bar-radius: 28px; // full radius
// Search bar radius
$search-bar-radius: 8px;
//$search-bar-radius: 21px; // full rounded
// Bar radius settings
$bar-radius: 0;
//$bar-radius: 8px;
// Button radius settings
$button-radius: 8px;
// Card radius settings
$card-radius: 8px;
// Dialog radius settings
$dialog-radius: 8px;
// Menu radius settings
$menu-radius: 8px;
// Table radius settings
$table-radius: 8px;
//$table-radius: 2px;
// Media radius settings
$media-radius: 0;
//$media-radius: 2px;
// Navigation drawer item settings
$nav-drawer-item-radius: 8px; // corner rounded
//$nav-drawer-item-radius: 32px; // full rounded
//$nav-drawer-item-radius: 32px 0 0 32px; // left rounded
// Avatar cropping settings
$avatar-radius: 50%; // Rounded cropping
//$avatar-radius: $card-radius // Fit to card radius
//$avatar-radius: 2px // Material v1 square
//$avatar-radius: 8px // Material v2 square
// Chip settings
$chip-type: outlined; // Material v2 styled outlined chip
//$outlined-chip: contained; // Material v1 styled contained chip
// Button on statuses
$button-on-status: text; // Material v1 styled text button
//$button-on-status: outlined; // Material v2 styled outlined button
// Button shadow
$button-shadow: true; // Material v1 styled colored button with shadow
//$button-shadow: false; // Material v2 styled colored button without shadow
// Floating Action Button size
$fab-size: 56px; // Regular
//$fab-size: 40px; // Mini
// CW text field border
$cw-area-border: none;
//$cw-area-border: underlined;
//$cw-area-border: outlined;
// Compose text field border
$compose-area-border: none;
//$compose-area-border: underlined;
//$compose-area-border: outlined;
// Other text field border
$other-text-area-border: underlined;
//$other-text-area-border: outlined;
//$other-text-area-border: none;

View File

@ -0,0 +1,21 @@
.announcements {
li {
padding: 8px 16px;
background: $card-bg-color;
color: $primary-text-color;
border-radius: $card-radius;
border: 1px solid $border-color;
}
&__icon {
margin: 0;
right: 16px;
height: 20px;
.icon-button {
@include icon-button;
&:hover { background: transparent }
}
}
}

View File

@ -0,0 +1,43 @@
.column {
box-shadow: none !important;
margin: 2px 0 !important;
padding: 8px !important;
> .scrollable { background: $bg-color }
}
@media screen and (min-width: 415px) {
.column { padding: 0 !important }
.notification.notification,
.status {
border-radius: 0;
&__wrapper.status__wrapper {
margin: 8px 0;
border-radius: 0;
}
}
}
.status {
border-bottom: 0;
border-radius: $card-radius;
background: $card-bg-color;
&__prepend { padding: 8px 8px 2px 0 }
}
.account { border-bottom: 0 !important }
.status__wrapper.status__wrapper,
.notification.notification {
@include shadow-1dp;
margin: 8px 2px;
border-radius: $card-radius;
background: $card-bg-color;
}
.notification .status__wrapper.status__wrapper { box-shadow: none !important }
.notification__filter-bar { @include non-overflow-shadow-4dp }

View File

@ -0,0 +1,97 @@
.drawer {
&__header { height: 48px }
&__tab { margin: 6px auto 6px }
}
.column { margin: 10px 5px }
.column-header {
height: 48px;
> button { padding: 12px 16px }
&__buttons { height: 48px }
&__button { margin: 6px }
&__back-button { margin: 6px }
}
.column-subheading { padding: 8px 16px }
.column-link { padding: 12px 16px }
.tabs-bar__link { padding: 12px 16px 8px 16px }
.notification__filter-bar button { padding: 12px 0 }
.getting-started__footer {
padding: 12px 16px;
p { margin-bottom: 12px }
}
.compose-form {
.compose-form {
&__publish { padding-top: 8px }
&__buttons-wrapper {
.icon-button,
.text-icon-button { padding: 4px }
}
}
}
.status {
padding: 8px 8px 4px 66px;
&__expand { width: 66px }
&__info { padding-right: 0 }
&__avatar {
left: 8px;
top: 10px;
}
&__content {
padding-top: 2px;
&__spoiler-link,
&__read-more-button {
height: auto;
padding: 4px 8px;
}
}
&__action-bar-button {
margin: 0 4px;
&.icon-button { padding: 4px }
}
&__action-bar-dropdown {
width: 28px !important;
height: 28px !important;
margin: 0 4px;
.icon-button { padding: 4px }
}
}
@media screen and (min-width: 630px) {
.columns-area--mobile .status { padding: 8px 8px 8px 78px }
}
.detailed-status {
padding: 14px;
&__meta { margin-top: 14px }
&__action-bar { padding: 10px 0 }
}
.reply-indicator { padding: 8px }
.button.button--block { margin: 6px 0 }

View File

@ -0,0 +1,27 @@
.drawer__tab.short-label {
padding: 8px;
.drawer__tab__short-label {
overflow-x: visible;
display: none;
}
i { margin: 0 }
}
.tabs-bar__link span { margin-left: 0 }
.tabs-bar.bottom-bar {
@include shadow-4dp;
.tabs-bar {
&__link,
&__link.active { border-bottom: 4px solid transparent }
}
&__link span { margin-left: 0 }
}
@media screen and (max-width: 1190px) {
.tabs-bar__wrapper .tabs-bar.bottom-bar { width: 100% }
}

View File

@ -0,0 +1,52 @@
.quote-indicator,
.reply-indicator {
@include card-elevation(false);
margin-bottom: 16px;
padding: 16px;
&__header { margin-bottom: 4px }
&__display-avatar {
margin-right: 8px;
.account__avatar {
width: 32px !important;
height: 32px !important;
background-size: 32px !important;
}
}
&__content {
padding-top: 0;
padding-left: 32px;
color: $primary-text-color;
}
}
.quote-status {
@include card-elevation(false);
padding: 16px;
.detailed-status__display-avatar,
.status__avatar {
height: 32px;
width: 232px;
top: 16px !important;
left: 16px !important;
&>div {
width: 32px !important;
height: 32px !important;
background-size: 32px !important;
}
}
.detailed-status__display-name {
span,
strong { @include separate-address-line }
}
.display-name { padding-left: 40px }
&>.unlisted-quote>button { color: $ui-text-color }
}

View File

@ -0,0 +1,56 @@
.status__action-bar-button {
background: #eeeeee;
width: 28px !important;
height: 28px !important;
.fa { vertical-align: bottom }
}
// exception
.media-modal__overlay .picture-in-picture__footer .icon-button,
.status__action-bar-button.icon-button--with-counter { background: transparent }
// favorite icon
.star-icon.active,
.star-icon.icon-button.active.activate,
.notification__favourite-icon-wrapper .star-icon { background: #db4437 }
.notification__favourite-icon-wrapper {
left: -34px;
.star-icon {
border-radius: 50%;
width: 28px;
height: 28px;
vertical-align: baseline;
font-size: 18px;
&.fa-fw::before { vertical-align: middle }
}
}
// Fedibird bottom bar in mobile
.tabs-bar.bottom-bar {
background: #212121;
.tabs-bar__link {
color: transparentize(#ffffff, 0.5);
&.active { color: #ffffff }
}
}
// Itabashi quote
.quote-status {
border-radius: 0;
border: none;
border-top: 1px solid $border-color;
padding: 16px 0;
.detailed-status__display-avatar,
.status__avatar { left: 0 !important }
}
@media screen and (min-width: 630px) {
.quote-indicator__content, .reply-indicator__content, .status__content { padding-top: 32px }
}

View File

@ -0,0 +1 @@
// Add your customization below

View File

@ -0,0 +1,3 @@
// Add your settings below
@import '../../color/mastodon-dark.scss';

View File

@ -0,0 +1,3 @@
// Add your icon customization below
$icon-font-source: self;

View File

@ -0,0 +1 @@
// Add your customization below

View File

@ -0,0 +1,11 @@
@charset "UTF-8";
@import '../../theme/base_config', '../../theme/base_icon_config';
@import 'config', 'icon_config';
@import 'color', 'layout';
@import '../../theme/functions', '../../theme/mixins';
@import '../../theme/theme';
@import 'plugins';
@import '../../theme/material-icons';

View File

@ -0,0 +1,6 @@
@import '../../plugins/astarte';
//@import '../../plugins/cards';
//@import '../../plugins/dense';
@import '../../plugins/fedibird';
@import '../../plugins/itabashi';
//@import '../../plugins/plus';

View File

@ -0,0 +1 @@
// Add your customization below

View File

@ -0,0 +1,3 @@
// Add your settings below
@import '../../color/mastodon-light.scss';

View File

@ -0,0 +1,3 @@
// Add your icon customization below
$icon-font-source: self;

View File

@ -0,0 +1 @@
// Add your customization below

Some files were not shown because too many files have changed in this diff Show More