Compare commits
199 Commits
fix-async
...
feature/19
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b3ff6a92f | ||
|
|
2ce4af84e9 | ||
|
|
c17c07158a | ||
|
|
8f476ee39e | ||
|
|
041168df31 | ||
|
|
5e6a09498a | ||
|
|
1704e0066a | ||
|
|
27f8d2c739 | ||
|
|
47ec2eff65 | ||
|
|
a37753179d | ||
|
|
00ce2be148 | ||
|
|
7e07e61a30 | ||
|
|
4a974c6db1 | ||
|
|
0e10667fbe | ||
|
|
1b4d79acf8 | ||
|
|
5a4de1cf0f | ||
|
|
a3c0a20373 | ||
|
|
9abb5becf4 | ||
|
|
8cd2828e91 | ||
|
|
3d881eed27 | ||
|
|
7650506b39 | ||
|
|
e6db3427b7 | ||
|
|
daefbd66a6 | ||
|
|
b1daa71da5 | ||
|
|
1cc44cba81 | ||
|
|
c53d7196e6 | ||
|
|
4ec9d8b4d9 | ||
|
|
d966878e87 | ||
|
|
2fc2725076 | ||
|
|
2107891edb | ||
|
|
16d5f9e162 | ||
|
|
69f13e7bca | ||
|
|
613e7c7521 | ||
|
|
17cecd75ca | ||
|
|
8cc65cde27 | ||
|
|
b7f6ddeaf1 | ||
|
|
a90d0419d9 | ||
|
|
143fb54ab9 | ||
|
|
48d79007f6 | ||
|
|
81b78fac54 | ||
|
|
48cd6dc6ca | ||
|
|
1253279feb | ||
|
|
cfd2b06821 | ||
|
|
d613dda91d | ||
|
|
112b1fa265 | ||
|
|
31d1485887 | ||
|
|
1287de1b83 | ||
|
|
2c578a01d8 | ||
|
|
e7a0840f8c | ||
|
|
24b6e4121f | ||
|
|
72f9eab3d6 | ||
|
|
3cae362214 | ||
|
|
38f6d8130d | ||
|
|
071c2c9c85 | ||
|
|
0b7a0d15c7 | ||
|
|
80b3ca0f6f | ||
|
|
45afdf1781 | ||
|
|
79b34a0fa2 | ||
|
|
872a0d5bd8 | ||
|
|
01421999ae | ||
|
|
2ca965c704 | ||
|
|
b8c6656ce9 | ||
|
|
1619efd735 | ||
|
|
0b888acfd4 | ||
|
|
238de58e65 | ||
|
|
7233ac07d2 | ||
|
|
b1e03197fa | ||
|
|
7be53a10b0 | ||
|
|
a0de3222dd | ||
|
|
540b3f37ae | ||
|
|
852b48295f | ||
|
|
1287b2782b | ||
|
|
2b2de41bc7 | ||
|
|
26ecee79bf | ||
|
|
ae369bceb3 | ||
|
|
9b3b40df66 | ||
|
|
d799921c75 | ||
|
|
766d6aac44 | ||
|
|
d3f64812a6 | ||
|
|
3896cd5d79 | ||
|
|
bcd86404da | ||
|
|
1964a0f941 | ||
|
|
e27eedbd08 | ||
|
|
708ec07e27 | ||
|
|
e56404be41 | ||
|
|
201e82686f | ||
|
|
7badad7797 | ||
|
|
fe5c1f0803 | ||
|
|
59797ee233 | ||
|
|
aa2bf07281 | ||
|
|
fbe7756da6 | ||
|
|
0a103c7749 | ||
|
|
fb16c37d2a | ||
|
|
6f244ba82c | ||
|
|
ea75ae2d1f | ||
|
|
acb982fc66 | ||
|
|
eed7484cd6 | ||
|
|
02194838dd | ||
|
|
3323b4173e | ||
|
|
9a28052e92 | ||
|
|
60ff086960 | ||
|
|
cda845ae0e | ||
|
|
e6fd4bea35 | ||
|
|
89a9d629f7 | ||
|
|
5276c0a090 | ||
|
|
7861c5f108 | ||
|
|
3987bd18a4 | ||
|
|
0c7dc6c781 | ||
|
|
74c1c9ec01 | ||
|
|
537d2939b1 | ||
|
|
2091ae92be | ||
|
|
dcc614f869 | ||
|
|
ed867eca9d | ||
|
|
08e4c78e78 | ||
|
|
8ee6ed358f | ||
|
|
1ee0e91fb6 | ||
|
|
b9f4896830 | ||
|
|
cd6674606f | ||
|
|
39f231f3da | ||
|
|
0fb3bd09e9 | ||
|
|
1fa03e026a | ||
|
|
a5931e1f48 | ||
|
|
c2669f93d0 | ||
|
|
756a9cd139 | ||
|
|
e64cc311dd | ||
|
|
53cbc9933e | ||
|
|
cd1377de7f | ||
|
|
1dbb6b5e08 | ||
|
|
1e0b707018 | ||
|
|
835eec8a4c | ||
|
|
f9b08e2142 | ||
|
|
1ebe66b78c | ||
|
|
be2bac05aa | ||
|
|
b5476f6c75 | ||
|
|
bc0524834e | ||
|
|
6ecf81cacf | ||
|
|
64425dbb77 | ||
|
|
a16d885ac9 | ||
|
|
5f35b39f6f | ||
|
|
bb2ca23839 | ||
|
|
a9be680807 | ||
|
|
6a73c8c8a2 | ||
|
|
9526aababc | ||
|
|
3a0b47982d | ||
|
|
9b9b7fa005 | ||
|
|
0210e59759 | ||
|
|
4773481a90 | ||
|
|
ee7217bc94 | ||
|
|
e2ce628724 | ||
|
|
cf5789146b | ||
|
|
9fa79bc317 | ||
|
|
704053d221 | ||
|
|
8c08c852bc | ||
|
|
f13ebd02c9 | ||
|
|
26f054253c | ||
|
|
395e64e858 | ||
|
|
514db316f7 | ||
|
|
6fcb870d96 | ||
|
|
3ce1385b25 | ||
|
|
095a00ef3d | ||
|
|
622c8fdb75 | ||
|
|
991371af5f | ||
|
|
35b84985a8 | ||
|
|
d41f0b66cc | ||
|
|
aef4b1af66 | ||
|
|
f43c2f12f3 | ||
|
|
9bdbe66316 | ||
|
|
b535f24fe5 | ||
|
|
921b781909 | ||
|
|
0d4dcb5fb2 | ||
|
|
6d1c325167 | ||
|
|
7f4374d97d | ||
|
|
8a0e4bb9a4 | ||
|
|
5963630c63 | ||
|
|
6f5c0afe93 | ||
|
|
eec6095e02 | ||
|
|
e780d5951b | ||
|
|
9f04b0d4b1 | ||
|
|
ce7f4aef16 | ||
|
|
ec0bdd6c1a | ||
|
|
df04da098a | ||
|
|
7c719c567c | ||
|
|
488381ae2f | ||
|
|
60433d03f5 | ||
|
|
5d2ef7a616 | ||
|
|
44792de49a | ||
|
|
824a790e63 | ||
|
|
628358aeea | ||
|
|
fe6663ef50 | ||
|
|
c235711ffe | ||
|
|
ff6ca8bdc6 | ||
|
|
56312df73b | ||
|
|
7a93acd803 | ||
|
|
077f2e600c | ||
|
|
39c0b6ceb3 | ||
|
|
ed574fbc09 | ||
|
|
ac686d5a5d | ||
|
|
ec620ae486 | ||
|
|
3b016342c6 |
@@ -27,6 +27,7 @@ plugins:
|
||||
enabled: true
|
||||
eslint:
|
||||
enabled: true
|
||||
channel: eslint-4
|
||||
rubocop:
|
||||
enabled: true
|
||||
scss-lint:
|
||||
|
||||
@@ -17,11 +17,9 @@ plugins:
|
||||
parserOptions:
|
||||
sourceType: module
|
||||
ecmaFeatures:
|
||||
arrowFunctions: true
|
||||
experimentalObjectRestSpread: true
|
||||
jsx: true
|
||||
destructuring: true
|
||||
modules: true
|
||||
spread: true
|
||||
ecmaVersion: 2018
|
||||
|
||||
settings:
|
||||
import/extensions:
|
||||
@@ -114,6 +112,7 @@ rules:
|
||||
react/self-closing-comp: error
|
||||
|
||||
jsx-a11y/accessible-emoji: warn
|
||||
jsx-a11y/alt-text: warn
|
||||
jsx-a11y/anchor-has-content: warn
|
||||
jsx-a11y/aria-activedescendant-has-tabindex: warn
|
||||
jsx-a11y/aria-props: warn
|
||||
@@ -124,16 +123,22 @@ rules:
|
||||
jsx-a11y/href-no-hash: warn
|
||||
jsx-a11y/html-has-lang: warn
|
||||
jsx-a11y/iframe-has-title: warn
|
||||
jsx-a11y/img-has-alt: warn
|
||||
jsx-a11y/img-redundant-alt: warn
|
||||
jsx-a11y/interactive-supports-focus: warn
|
||||
jsx-a11y/label-has-for: off
|
||||
jsx-a11y/mouse-events-have-key-events: warn
|
||||
jsx-a11y/no-access-key: warn
|
||||
jsx-a11y/no-distracting-elements: warn
|
||||
jsx-a11y/no-noninteractive-element-interactions:
|
||||
- warn
|
||||
- handlers:
|
||||
- onClick
|
||||
jsx-a11y/no-onchange: warn
|
||||
jsx-a11y/no-redundant-roles: warn
|
||||
jsx-a11y/onclick-has-focus: warn
|
||||
jsx-a11y/onclick-has-role: warn
|
||||
jsx-a11y/no-static-element-interactions:
|
||||
- warn
|
||||
- handlers:
|
||||
- onClick
|
||||
jsx-a11y/role-has-required-aria-props: warn
|
||||
jsx-a11y/role-supports-aria-props: off
|
||||
jsx-a11y/scope: warn
|
||||
|
||||
@@ -1 +1 @@
|
||||
2.4.2
|
||||
2.5.0
|
||||
|
||||
@@ -8,7 +8,7 @@ cache:
|
||||
- public/packs-test
|
||||
- tmp/cache/babel-loader
|
||||
dist: trusty
|
||||
sudo: required
|
||||
sudo: false
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
@@ -37,8 +37,8 @@ addons:
|
||||
- yarn
|
||||
|
||||
rvm:
|
||||
- 2.3.4
|
||||
- 2.4.2
|
||||
- 2.5.0
|
||||
|
||||
services:
|
||||
- redis-server
|
||||
@@ -49,8 +49,7 @@ install:
|
||||
- yarn install
|
||||
|
||||
before_script:
|
||||
- bundle exec rake parallel:create parallel:load_schema parallel:prepare
|
||||
- bundle exec rails assets:precompile
|
||||
- ./bin/rails parallel:create parallel:load_schema parallel:prepare assets:precompile
|
||||
- ln -s /usr/bin/x86_64-linux-gnu-g++-6 "$HOME/g++"
|
||||
|
||||
script:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM ruby:2.4.2-alpine3.6
|
||||
FROM ruby:2.5.0-alpine3.7
|
||||
|
||||
LABEL maintainer="https://github.com/tootsuite/mastodon" \
|
||||
description="A GNU Social-compatible microblogging server"
|
||||
@@ -40,6 +40,7 @@ RUN apk -U upgrade \
|
||||
protobuf \
|
||||
su-exec \
|
||||
tini \
|
||||
tzdata \
|
||||
&& update-ca-certificates \
|
||||
&& mkdir -p /tmp/src /opt \
|
||||
&& wget -O yarn.tar.gz "https://github.com/yarnpkg/yarn/releases/download/v$YARN_VERSION/yarn-v$YARN_VERSION.tar.gz" \
|
||||
@@ -61,6 +62,9 @@ RUN apk -U upgrade \
|
||||
&& rm -rf /tmp/* /var/cache/apk/*
|
||||
|
||||
COPY Gemfile Gemfile.lock package.json yarn.lock .yarnclean /mastodon/
|
||||
COPY stack-fix.c /lib
|
||||
RUN gcc -shared -fPIC /lib/stack-fix.c -o /lib/stack-fix.so
|
||||
RUN rm /lib/stack-fix.c
|
||||
|
||||
RUN bundle config build.nokogiri --with-iconv-lib=/usr/local/lib --with-iconv-include=/usr/local/include \
|
||||
&& bundle install -j$(getconf _NPROCESSORS_ONLN) --deployment --without test development \
|
||||
|
||||
9
Gemfile
@@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
source 'https://rubygems.org'
|
||||
ruby '>= 2.3.0', '< 2.5.0'
|
||||
ruby '>= 2.3.0', '< 2.6.0'
|
||||
|
||||
gem 'pkg-config', '~> 1.2'
|
||||
|
||||
@@ -29,15 +29,15 @@ gem 'browser'
|
||||
gem 'charlock_holmes', '~> 0.7.5'
|
||||
gem 'iso-639'
|
||||
gem 'cld3', '~> 3.2.0'
|
||||
gem 'devise', '~> 4.3'
|
||||
gem 'devise', '~> 4.4'
|
||||
gem 'devise-two-factor', '~> 3.0'
|
||||
gem 'doorkeeper', '~> 4.2'
|
||||
gem 'fast_blank', '~> 1.0'
|
||||
gem 'goldfinger', '~> 2.0'
|
||||
gem 'goldfinger', '~> 2.1'
|
||||
gem 'hiredis', '~> 0.6'
|
||||
gem 'redis-namespace', '~> 1.5'
|
||||
gem 'htmlentities', '~> 4.3'
|
||||
gem 'http', '~> 2.2'
|
||||
gem 'http', '~> 3.0'
|
||||
gem 'http_accept_language', '~> 2.1'
|
||||
gem 'httplog', '~> 0.99'
|
||||
gem 'idn-ruby', require: 'idn'
|
||||
@@ -50,6 +50,7 @@ gem 'oj', '~> 3.3'
|
||||
gem 'ostatus2', '~> 2.0'
|
||||
gem 'ox', '~> 2.8'
|
||||
gem 'pundit', '~> 1.1'
|
||||
gem 'premailer-rails'
|
||||
gem 'rack-attack', '~> 5.0'
|
||||
gem 'rack-cors', '~> 0.4', require: 'rack/cors'
|
||||
gem 'rack-timeout', '~> 0.4'
|
||||
|
||||
44
Gemfile.lock
@@ -70,7 +70,7 @@ GEM
|
||||
coderay (>= 1.0.0)
|
||||
erubi (>= 1.0.0)
|
||||
rack (>= 0.9.0)
|
||||
binding_of_caller (0.7.3)
|
||||
binding_of_caller (0.8.0)
|
||||
debug_inspector (>= 0.0.1)
|
||||
bootsnap (1.1.5)
|
||||
msgpack (~> 1.0)
|
||||
@@ -110,7 +110,7 @@ GEM
|
||||
activesupport
|
||||
charlock_holmes (0.7.5)
|
||||
chunky_png (1.3.8)
|
||||
cld3 (3.2.1)
|
||||
cld3 (3.2.2)
|
||||
ffi (>= 1.1.0, < 1.10.0)
|
||||
climate_control (0.2.0)
|
||||
cocaine (0.5.8)
|
||||
@@ -122,8 +122,10 @@ GEM
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
crass (1.0.3)
|
||||
css_parser (1.6.0)
|
||||
addressable
|
||||
debug_inspector (0.0.3)
|
||||
devise (4.3.0)
|
||||
devise (4.4.0)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0, < 5.2)
|
||||
@@ -179,9 +181,9 @@ GEM
|
||||
ruby-progressbar (~> 1.4)
|
||||
globalid (0.4.1)
|
||||
activesupport (>= 4.2.0)
|
||||
goldfinger (2.0.1)
|
||||
goldfinger (2.1.0)
|
||||
addressable (~> 2.5)
|
||||
http (~> 2.2)
|
||||
http (~> 3.0)
|
||||
nokogiri (~> 1.8)
|
||||
oj (~> 3.0)
|
||||
hamlit (2.8.5)
|
||||
@@ -200,14 +202,14 @@ GEM
|
||||
hiredis (0.6.1)
|
||||
hkdf (0.3.0)
|
||||
htmlentities (4.3.4)
|
||||
http (2.2.2)
|
||||
http (3.0.0)
|
||||
addressable (~> 2.3)
|
||||
http-cookie (~> 1.0)
|
||||
http-form_data (~> 1.0.1)
|
||||
http-form_data (>= 2.0.0.pre.pre2, < 3)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
http-cookie (1.0.3)
|
||||
domain_name (~> 0.5)
|
||||
http-form_data (1.0.3)
|
||||
http-form_data (2.0.0)
|
||||
http_accept_language (2.1.1)
|
||||
http_parser.rb (0.6.0)
|
||||
httplog (0.99.7)
|
||||
@@ -298,12 +300,12 @@ GEM
|
||||
concurrent-ruby (~> 1.0.0)
|
||||
sidekiq (>= 3.5.0)
|
||||
statsd-ruby (~> 1.2.0)
|
||||
oj (3.3.9)
|
||||
oj (3.3.10)
|
||||
orm_adapter (0.5.0)
|
||||
ostatus2 (2.0.2)
|
||||
addressable (~> 2.4)
|
||||
http (~> 2.0)
|
||||
nokogiri (~> 1.6)
|
||||
ostatus2 (2.0.3)
|
||||
addressable (~> 2.5)
|
||||
http (~> 3.0)
|
||||
nokogiri (~> 1.8)
|
||||
ox (2.8.2)
|
||||
paperclip (5.1.0)
|
||||
activemodel (>= 4.2.0)
|
||||
@@ -325,6 +327,13 @@ GEM
|
||||
pkg-config (1.2.8)
|
||||
posix-spawn (0.3.13)
|
||||
powerpack (0.1.1)
|
||||
premailer (1.11.1)
|
||||
addressable
|
||||
css_parser (>= 1.6.0)
|
||||
htmlentities (>= 4.0.0)
|
||||
premailer-rails (1.10.1)
|
||||
actionmailer (>= 3, < 6)
|
||||
premailer (~> 1.7, >= 1.7.9)
|
||||
pry (0.11.3)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.9.0)
|
||||
@@ -560,7 +569,7 @@ DEPENDENCIES
|
||||
charlock_holmes (~> 0.7.5)
|
||||
cld3 (~> 3.2.0)
|
||||
climate_control (~> 0.2)
|
||||
devise (~> 4.3)
|
||||
devise (~> 4.4)
|
||||
devise-two-factor (~> 3.0)
|
||||
doorkeeper (~> 4.2)
|
||||
dotenv-rails (~> 2.2)
|
||||
@@ -571,11 +580,11 @@ DEPENDENCIES
|
||||
fog-local (~> 0.4)
|
||||
fog-openstack (~> 0.1)
|
||||
fuubar (~> 2.2)
|
||||
goldfinger (~> 2.0)
|
||||
goldfinger (~> 2.1)
|
||||
hamlit-rails (~> 0.2)
|
||||
hiredis (~> 0.6)
|
||||
htmlentities (~> 4.3)
|
||||
http (~> 2.2)
|
||||
http (~> 3.0)
|
||||
http_accept_language (~> 2.1)
|
||||
httplog (~> 0.99)
|
||||
i18n-tasks (~> 0.9)
|
||||
@@ -602,6 +611,7 @@ DEPENDENCIES
|
||||
pghero (~> 1.7)
|
||||
pkg-config (~> 1.2)
|
||||
posix-spawn
|
||||
premailer-rails
|
||||
pry-rails (~> 0.3)
|
||||
puma (~> 3.10)
|
||||
pundit (~> 1.1)
|
||||
@@ -641,7 +651,7 @@ DEPENDENCIES
|
||||
webpush
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.4.2p198
|
||||
ruby 2.5.0p0
|
||||
|
||||
BUNDLED WITH
|
||||
1.16.1
|
||||
|
||||
2
Vagrantfile
vendored
@@ -79,7 +79,7 @@ VAGRANTFILE_API_VERSION = "2"
|
||||
|
||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
|
||||
config.vm.box = "ubuntu/trusty64"
|
||||
config.vm.box = "ubuntu/xenial64"
|
||||
|
||||
config.vm.provider :virtualbox do |vb|
|
||||
vb.name = "mastodon"
|
||||
|
||||
@@ -28,7 +28,7 @@ class ActivityPub::InboxesController < Api::BaseController
|
||||
def upgrade_account
|
||||
if signed_request_account.ostatus?
|
||||
signed_request_account.update(last_webfingered_at: nil)
|
||||
ResolveRemoteAccountWorker.perform_async(signed_request_account.acct)
|
||||
ResolveAccountWorker.perform_async(signed_request_account.acct)
|
||||
end
|
||||
|
||||
Pubsubhubbub::UnsubscribeWorker.perform_async(signed_request_account.id) if signed_request_account.subscribed?
|
||||
|
||||
@@ -6,8 +6,8 @@ class Api::BaseController < ApplicationController
|
||||
|
||||
include RateLimitHeaders
|
||||
|
||||
skip_before_action :verify_authenticity_token
|
||||
skip_before_action :store_current_location
|
||||
protect_from_forgery with: :null_session
|
||||
|
||||
rescue_from ActiveRecord::RecordInvalid, Mastodon::ValidationError do |e|
|
||||
render json: { error: e.to_s }, status: 422
|
||||
|
||||
@@ -21,12 +21,12 @@ class Api::V1::AccountsController < Api::BaseController
|
||||
end
|
||||
|
||||
def block
|
||||
BlockService.new.call(current_user.account, @account)
|
||||
BlockService.new.call(current_user.account, @account, note: params[:note])
|
||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
|
||||
end
|
||||
|
||||
def mute
|
||||
MuteService.new.call(current_user.account, @account, notifications: params[:notifications])
|
||||
MuteService.new.call(current_user.account, @account, notifications: params[:notifications], note: params[:note])
|
||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
|
||||
end
|
||||
|
||||
|
||||
@@ -9,7 +9,11 @@ class Api::V1::Timelines::HomeController < Api::BaseController
|
||||
|
||||
def show
|
||||
@statuses = load_statuses
|
||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||
|
||||
render json: @statuses,
|
||||
each_serializer: REST::StatusSerializer,
|
||||
relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
|
||||
status: regeneration_in_progress? ? 206 : 200
|
||||
end
|
||||
|
||||
private
|
||||
@@ -57,4 +61,8 @@ class Api::V1::Timelines::HomeController < Api::BaseController
|
||||
def pagination_since_id
|
||||
@statuses.first.id
|
||||
end
|
||||
|
||||
def regeneration_in_progress?
|
||||
Redis.current.exists("account:#{current_account.id}:regeneration")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,6 +4,7 @@ class Api::Web::PushSubscriptionsController < Api::BaseController
|
||||
respond_to :json
|
||||
|
||||
before_action :require_user!
|
||||
protect_from_forgery with: :exception
|
||||
|
||||
def create
|
||||
params.require(:subscription).require(:endpoint)
|
||||
|
||||
@@ -46,7 +46,7 @@ class AuthorizeFollowsController < ApplicationController
|
||||
end
|
||||
|
||||
def account_from_remote_follow
|
||||
ResolveRemoteAccountService.new.call(acct_without_prefix)
|
||||
ResolveAccountService.new.call(acct_without_prefix)
|
||||
end
|
||||
|
||||
def acct_param_is_url?
|
||||
|
||||
@@ -114,7 +114,7 @@ module SignatureVerification
|
||||
|
||||
def account_from_key_id(key_id)
|
||||
if key_id.start_with?('acct:')
|
||||
ResolveRemoteAccountService.new.call(key_id.gsub(/\Aacct:/, ''))
|
||||
ResolveAccountService.new.call(key_id.gsub(/\Aacct:/, ''))
|
||||
elsif !ActivityPub::TagManager.instance.local_uri?(key_id)
|
||||
account = ActivityPub::TagManager.instance.uri_to_resource(key_id, Account)
|
||||
account ||= ActivityPub::FetchRemoteKeyService.new.call(key_id, id: false)
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
module UserTrackingConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
REGENERATE_FEED_DAYS = 14
|
||||
UPDATE_SIGN_IN_HOURS = 24
|
||||
|
||||
included do
|
||||
@@ -14,25 +13,10 @@ module UserTrackingConcern
|
||||
|
||||
def set_user_activity
|
||||
return unless user_needs_sign_in_update?
|
||||
|
||||
# Mark as signed-in today
|
||||
current_user.update_tracked_fields!(request)
|
||||
ActivityTracker.record('activity:logins', current_user.id)
|
||||
|
||||
# Regenerate feed if needed
|
||||
regenerate_feed! if user_needs_feed_update?
|
||||
end
|
||||
|
||||
def user_needs_sign_in_update?
|
||||
user_signed_in? && (current_user.current_sign_in_at.nil? || current_user.current_sign_in_at < UPDATE_SIGN_IN_HOURS.hours.ago)
|
||||
end
|
||||
|
||||
def user_needs_feed_update?
|
||||
current_user.last_sign_in_at < REGENERATE_FEED_DAYS.days.ago
|
||||
end
|
||||
|
||||
def regenerate_feed!
|
||||
Redis.current.setnx("account:#{current_user.account_id}:regeneration", true) == 1 && Redis.current.expire("account:#{current_user.account_id}:regeneration", 3_600 * 24)
|
||||
RegenerationWorker.perform_async(current_user.account_id)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Settings::FlavoursController < Settings::BaseController
|
||||
|
||||
def index
|
||||
redirect_to action: 'show', flavour: current_flavour
|
||||
end
|
||||
|
||||
def show
|
||||
unless Themes.instance.flavours.include?(params[:flavour]) or params[:flavour] == current_flavour
|
||||
unless Themes.instance.flavours.include?(params[:flavour]) || (params[:flavour] == current_flavour)
|
||||
redirect_to action: 'show', flavour: current_flavour
|
||||
end
|
||||
|
||||
@@ -16,7 +15,7 @@ class Settings::FlavoursController < Settings::BaseController
|
||||
end
|
||||
|
||||
def update
|
||||
user_settings.update(user_settings_params(params[:flavour]).to_h)
|
||||
user_settings.update(user_settings_params)
|
||||
redirect_to action: 'show', flavour: params[:flavour]
|
||||
end
|
||||
|
||||
@@ -26,10 +25,8 @@ class Settings::FlavoursController < Settings::BaseController
|
||||
UserSettingsDecorator.new(current_user)
|
||||
end
|
||||
|
||||
def user_settings_params(flavour)
|
||||
params.require(:user).merge({ setting_flavour: flavour }).permit(
|
||||
:setting_flavour,
|
||||
:setting_skin
|
||||
)
|
||||
def user_settings_params
|
||||
{ setting_flavour: params.require(:flavour),
|
||||
setting_skin: params.dig(:user, :setting_skin) }.with_indifferent_access
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
module Settings
|
||||
module TwoFactorAuthentication
|
||||
class ConfirmationsController < BaseController
|
||||
before_action :ensure_otp_secret
|
||||
|
||||
def new
|
||||
prepare_two_factor_form
|
||||
end
|
||||
@@ -34,6 +36,10 @@ module Settings
|
||||
@provision_url = current_user.otp_provisioning_uri(current_user.email, issuer: Rails.configuration.x.local_domain)
|
||||
@qrcode = RQRCode::QRCode.new(@provision_url)
|
||||
end
|
||||
|
||||
def ensure_otp_secret
|
||||
redirect_to settings_two_factor_authentication_path unless current_user.otp_secret
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,13 +15,14 @@ class SharesController < ApplicationController
|
||||
private
|
||||
|
||||
def initial_state_params
|
||||
text = [params[:title], params[:text], params[:url]].compact.join(' ')
|
||||
{
|
||||
settings: Web::Setting.find_by(user: current_user)&.data || {},
|
||||
push_subscription: current_account.user.web_push_subscription(current_session),
|
||||
current_account: current_account,
|
||||
token: current_session.token,
|
||||
admin: Account.find_local(Setting.site_contact_username),
|
||||
text: params[:text],
|
||||
text: text,
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -6,6 +6,6 @@ module InstanceHelper
|
||||
end
|
||||
|
||||
def site_hostname
|
||||
Rails.configuration.x.local_domain
|
||||
@site_hostname ||= Addressable::URI.parse("//#{Rails.configuration.x.local_domain}").display_uri.host
|
||||
end
|
||||
end
|
||||
|
||||
4
app/helpers/mailer_helper.rb
Normal file
@@ -0,0 +1,4 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module MailerHelper
|
||||
end
|
||||
@@ -16,6 +16,7 @@ module SettingsHelper
|
||||
he: 'עברית',
|
||||
hr: 'Hrvatski',
|
||||
hu: 'Magyar',
|
||||
hy: 'Հայերեն',
|
||||
id: 'Bahasa Indonesia',
|
||||
io: 'Ido',
|
||||
it: 'Italiano',
|
||||
|
||||
@@ -30,13 +30,13 @@ delegate(document, batchCheckboxClassName, 'change', () => {
|
||||
});
|
||||
|
||||
delegate(document, '.media-spoiler-show-button', 'click', () => {
|
||||
[].forEach.call(document.querySelectorAll('.activity-stream .media-spoiler-wrapper'), (content) => {
|
||||
content.classList.add('media-spoiler-wrapper__visible');
|
||||
[].forEach.call(document.querySelectorAll('button.media-spoiler'), (element) => {
|
||||
element.click();
|
||||
});
|
||||
});
|
||||
|
||||
delegate(document, '.media-spoiler-hide-button', 'click', () => {
|
||||
[].forEach.call(document.querySelectorAll('.activity-stream .media-spoiler-wrapper'), (content) => {
|
||||
content.classList.remove('media-spoiler-wrapper__visible');
|
||||
[].forEach.call(document.querySelectorAll('.spoiler-button.spoiler-button--visible button'), (element) => {
|
||||
element.click();
|
||||
});
|
||||
});
|
||||
|
||||
1
app/javascript/core/mailer.js
Normal file
@@ -0,0 +1 @@
|
||||
import 'styles/mailer.scss';
|
||||
@@ -10,6 +10,9 @@ pack:
|
||||
embed: embed.js
|
||||
error:
|
||||
home:
|
||||
mailer:
|
||||
filename: mailer.js
|
||||
stylesheet: true
|
||||
modal:
|
||||
public: public.js
|
||||
settings: settings.js
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from './timelines';
|
||||
|
||||
export const COMPOSE_CHANGE = 'COMPOSE_CHANGE';
|
||||
export const COMPOSE_CYCLE_ELEFRIEND = 'COMPOSE_CYCLE_ELEFRIEND';
|
||||
export const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST';
|
||||
export const COMPOSE_SUBMIT_SUCCESS = 'COMPOSE_SUBMIT_SUCCESS';
|
||||
export const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL';
|
||||
@@ -54,6 +55,12 @@ export function changeCompose(text) {
|
||||
};
|
||||
};
|
||||
|
||||
export function cycleElefriendCompose() {
|
||||
return {
|
||||
type: COMPOSE_CYCLE_ELEFRIEND,
|
||||
};
|
||||
};
|
||||
|
||||
export function replyCompose(status, router) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
|
||||
@@ -10,6 +10,10 @@ export const FAVOURITED_STATUSES_EXPAND_FAIL = 'FAVOURITED_STATUSES_EXPAND_FA
|
||||
|
||||
export function fetchFavouritedStatuses() {
|
||||
return (dispatch, getState) => {
|
||||
if (getState().getIn(['status_lists', 'favourites', 'isLoading'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(fetchFavouritedStatusesRequest());
|
||||
|
||||
api(getState).get('/api/v1/favourites').then(response => {
|
||||
@@ -46,7 +50,7 @@ export function expandFavouritedStatuses() {
|
||||
return (dispatch, getState) => {
|
||||
const url = getState().getIn(['status_lists', 'favourites', 'next'], null);
|
||||
|
||||
if (url === null) {
|
||||
if (url === null || getState().getIn(['status_lists', 'favourites', 'isLoading'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ const fetchRelatedRelationships = (dispatch, notifications) => {
|
||||
|
||||
const unescapeHTML = (html) => {
|
||||
const wrapper = document.createElement('div');
|
||||
html = html.replace(/<br \/>|<br>|\n/, ' ');
|
||||
html = html.replace(/<br \/>|<br>|\n/g, ' ');
|
||||
wrapper.innerHTML = html;
|
||||
return wrapper.textContent;
|
||||
};
|
||||
|
||||
@@ -15,9 +15,9 @@ export {
|
||||
register,
|
||||
};
|
||||
|
||||
export function changeAlerts(key, value) {
|
||||
export function changeAlerts(path, value) {
|
||||
return dispatch => {
|
||||
dispatch(setAlerts(key, value));
|
||||
dispatch(setAlerts(path, value));
|
||||
dispatch(saveSettings());
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import axios from 'axios';
|
||||
import api from 'flavours/glitch/util/api';
|
||||
import { pushNotificationsSetting } from 'flavours/glitch/util/settings';
|
||||
import { setBrowserSupport, setSubscription, clearSubscription } from './setter';
|
||||
|
||||
@@ -35,7 +35,7 @@ const subscribe = (registration) =>
|
||||
const unsubscribe = ({ registration, subscription }) =>
|
||||
subscription ? subscription.unsubscribe().then(() => registration) : registration;
|
||||
|
||||
const sendSubscriptionToBackend = (subscription, me) => {
|
||||
const sendSubscriptionToBackend = (getState, subscription, me) => {
|
||||
const params = { subscription };
|
||||
|
||||
if (me) {
|
||||
@@ -45,7 +45,7 @@ const sendSubscriptionToBackend = (subscription, me) => {
|
||||
}
|
||||
}
|
||||
|
||||
return axios.post('/api/web/push_subscriptions', params).then(response => response.data);
|
||||
return api(getState).post('/api/web/push_subscriptions', params).then(response => response.data);
|
||||
};
|
||||
|
||||
// Last one checks for payload support: https://web-push-book.gauntface.com/chapter-06/01-non-standards-browsers/#no-payload
|
||||
@@ -85,13 +85,13 @@ export function register () {
|
||||
} else {
|
||||
// Something went wrong, try to subscribe again
|
||||
return unsubscribe({ registration, subscription }).then(subscribe).then(
|
||||
subscription => sendSubscriptionToBackend(subscription, me));
|
||||
subscription => sendSubscriptionToBackend(getState, subscription, me));
|
||||
}
|
||||
}
|
||||
|
||||
// No subscription, try to subscribe
|
||||
return subscribe(registration).then(
|
||||
subscription => sendSubscriptionToBackend(subscription, me));
|
||||
subscription => sendSubscriptionToBackend(getState, subscription, me));
|
||||
})
|
||||
.then(subscription => {
|
||||
// If we got a PushSubscription (and not a subscription object from the backend)
|
||||
@@ -137,7 +137,7 @@ export function saveSettings() {
|
||||
const alerts = state.get('alerts');
|
||||
const data = { alerts };
|
||||
|
||||
axios.put(`/api/web/push_subscriptions/${subscription.get('id')}`, {
|
||||
api(getState).put(`/api/web/push_subscriptions/${subscription.get('id')}`, {
|
||||
data,
|
||||
}).then(() => {
|
||||
const me = getState().getIn(['meta', 'me']);
|
||||
|
||||
@@ -23,11 +23,11 @@ export function clearSubscription () {
|
||||
};
|
||||
}
|
||||
|
||||
export function setAlerts (key, value) {
|
||||
export function setAlerts (path, value) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: SET_ALERTS,
|
||||
key,
|
||||
path,
|
||||
value,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import axios from 'axios';
|
||||
import api from 'flavours/glitch/util/api';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
export const SETTING_CHANGE = 'SETTING_CHANGE';
|
||||
export const SETTING_SAVE = 'SETTING_SAVE';
|
||||
|
||||
export function changeSetting(key, value) {
|
||||
export function changeSetting(path, value) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: SETTING_CHANGE,
|
||||
key,
|
||||
path,
|
||||
value,
|
||||
});
|
||||
|
||||
@@ -21,9 +21,9 @@ const debouncedSave = debounce((dispatch, getState) => {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = getState().get('settings').filter((_, key) => key !== 'saved').toJS();
|
||||
const data = getState().get('settings').filter((_, path) => path !== 'saved').toJS();
|
||||
|
||||
axios.put('/api/web/settings', { data }).then(() => dispatch({ type: SETTING_SAVE }));
|
||||
api(getState).put('/api/web/settings', { data }).then(() => dispatch({ type: SETTING_SAVE }));
|
||||
}, 5000, { trailing: true });
|
||||
|
||||
export function saveSettings() {
|
||||
|
||||
@@ -19,13 +19,14 @@ export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
|
||||
|
||||
export const TIMELINE_CONTEXT_UPDATE = 'CONTEXT_UPDATE';
|
||||
|
||||
export function refreshTimelineSuccess(timeline, statuses, skipLoading, next) {
|
||||
export function refreshTimelineSuccess(timeline, statuses, skipLoading, next, partial) {
|
||||
return {
|
||||
type: TIMELINE_REFRESH_SUCCESS,
|
||||
timeline,
|
||||
statuses,
|
||||
skipLoading,
|
||||
next,
|
||||
partial,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -88,7 +89,7 @@ export function refreshTimeline(timelineId, path, params = {}) {
|
||||
return function (dispatch, getState) {
|
||||
const timeline = getState().getIn(['timelines', timelineId], ImmutableMap());
|
||||
|
||||
if (timeline.get('isLoading') || timeline.get('online')) {
|
||||
if (timeline.get('isLoading') || (timeline.get('online') && !timeline.get('isPartial'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -104,8 +105,12 @@ export function refreshTimeline(timelineId, path, params = {}) {
|
||||
dispatch(refreshTimelineRequest(timelineId, skipLoading));
|
||||
|
||||
api(getState).get(path, { params }).then(response => {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null));
|
||||
if (response.status === 206) {
|
||||
dispatch(refreshTimelineSuccess(timelineId, [], skipLoading, null, true));
|
||||
} else {
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null, false));
|
||||
}
|
||||
}).catch(error => {
|
||||
dispatch(refreshTimelineFail(timelineId, error, skipLoading));
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import Avatar from './avatar';
|
||||
@@ -94,12 +94,12 @@ export default class Account extends ImmutablePureComponent {
|
||||
hidingNotificationsButton = <IconButton active icon='bell-slash' title={intl.formatMessage(messages.mute_notifications, { name: account.get('username') })} onClick={this.handleMuteNotifications} />;
|
||||
}
|
||||
buttons = (
|
||||
<div>
|
||||
<Fragment>
|
||||
<IconButton active icon='volume-up' title={intl.formatMessage(messages.unmute, { name: account.get('username') })} onClick={this.handleMute} />
|
||||
{hidingNotificationsButton}
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
} else {
|
||||
} else if (!account.get('moved')) {
|
||||
buttons = <IconButton icon={following ? 'user-times' : 'user-plus'} title={intl.formatMessage(following ? messages.unfollow : messages.follow)} onClick={this.handleFollow} active={following} />;
|
||||
}
|
||||
}
|
||||
@@ -132,7 +132,7 @@ export default class Account extends ImmutablePureComponent {
|
||||
<div className='account__relationship'>
|
||||
{buttons}
|
||||
</div>
|
||||
: null}
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -21,9 +21,9 @@ export default class AttachmentList extends ImmutablePureComponent {
|
||||
|
||||
<ul className='attachment-list__list'>
|
||||
{media.map(attachment =>
|
||||
<li key={attachment.get('id')}>
|
||||
(<li key={attachment.get('id')}>
|
||||
<a href={attachment.get('remote_url')} target='_blank' rel='noopener'>{filename(attachment.get('remote_url'))}</a>
|
||||
</li>
|
||||
</li>)
|
||||
)}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -6,9 +6,9 @@ import PropTypes from 'prop-types';
|
||||
const Collapsable = ({ fullHeight, isVisible, children }) => (
|
||||
<Motion defaultStyle={{ opacity: !isVisible ? 0 : 100, height: isVisible ? fullHeight : 0 }} style={{ opacity: spring(!isVisible ? 0 : 100), height: spring(!isVisible ? 0 : fullHeight) }}>
|
||||
{({ opacity, height }) =>
|
||||
<div style={{ height: `${height}px`, overflow: 'hidden', opacity: opacity / 100, display: Math.floor(opacity) === 0 ? 'none' : 'block' }}>
|
||||
(<div style={{ height: `${height}px`, overflow: 'hidden', opacity: opacity / 100, display: Math.floor(opacity) === 0 ? 'none' : 'block' }}>
|
||||
{children}
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
</Motion>
|
||||
);
|
||||
|
||||
@@ -116,7 +116,7 @@ export default class IconButton extends React.PureComponent {
|
||||
return (
|
||||
<Motion defaultStyle={motionDefaultStyle} style={motionStyle}>
|
||||
{({ rotate }) =>
|
||||
<button
|
||||
(<button
|
||||
aria-label={title}
|
||||
aria-pressed={pressed}
|
||||
aria-expanded={expanded}
|
||||
@@ -128,7 +128,7 @@ export default class IconButton extends React.PureComponent {
|
||||
>
|
||||
<i style={{ transform: `rotate(${rotate}deg)` }} className={`fa fa-fw fa-${icon}`} aria-hidden='true' />
|
||||
{this.props.label}
|
||||
</button>
|
||||
</button>)
|
||||
}
|
||||
</Motion>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,26 @@ import classNames from 'classnames';
|
||||
import { autoPlayGif } from 'flavours/glitch/util/initial_state';
|
||||
|
||||
const messages = defineMessages({
|
||||
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
|
||||
hidden: {
|
||||
defaultMessage: 'Media hidden',
|
||||
id: 'status.media_hidden',
|
||||
},
|
||||
sensitive: {
|
||||
defaultMessage: 'Sensitive',
|
||||
id: 'media_gallery.sensitive',
|
||||
},
|
||||
toggle: {
|
||||
defaultMessage: 'Click to view',
|
||||
id: 'status.sensitive_toggle',
|
||||
},
|
||||
toggle_visible: {
|
||||
defaultMessage: 'Toggle visibility',
|
||||
id: 'media_gallery.toggle_visible',
|
||||
},
|
||||
warning: {
|
||||
defaultMessage: 'Sensitive content',
|
||||
id: 'status.sensitive_warning',
|
||||
},
|
||||
});
|
||||
|
||||
class Item extends React.PureComponent {
|
||||
@@ -206,48 +225,79 @@ export default class MediaGallery extends React.PureComponent {
|
||||
this.props.onOpenMedia(this.props.media, index);
|
||||
}
|
||||
|
||||
isStandaloneEligible() {
|
||||
const { media, standalone } = this.props;
|
||||
return standalone && media.size === 1 && media.getIn([0, 'meta', 'small', 'aspect']);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { media, intl, sensitive, letterbox, fullwidth } = this.props;
|
||||
const {
|
||||
handleClick,
|
||||
handleOpen,
|
||||
} = this;
|
||||
const {
|
||||
fullwidth,
|
||||
intl,
|
||||
letterbox,
|
||||
media,
|
||||
sensitive,
|
||||
standalone,
|
||||
} = this.props;
|
||||
const { visible } = this.state;
|
||||
const size = media.take(4).size;
|
||||
|
||||
let children;
|
||||
|
||||
if (!visible) {
|
||||
let warning;
|
||||
|
||||
if (sensitive) {
|
||||
warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
|
||||
} else {
|
||||
warning = <FormattedMessage id='status.media_hidden' defaultMessage='Media hidden' />;
|
||||
}
|
||||
|
||||
children = (
|
||||
<button className='media-spoiler' onClick={this.handleOpen}>
|
||||
<span className='media-spoiler__warning'>{warning}</span>
|
||||
<span className='media-spoiler__trigger'><FormattedMessage id='status.sensitive_toggle' defaultMessage='Click to view' /></span>
|
||||
</button>
|
||||
);
|
||||
} else {
|
||||
if (this.isStandaloneEligible()) {
|
||||
children = <Item standalone onClick={this.handleClick} attachment={media.get(0)} />;
|
||||
} else {
|
||||
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} onClick={this.handleClick} attachment={attachment} index={i} size={size} letterbox={letterbox} />);
|
||||
}
|
||||
}
|
||||
const computedClass = classNames('media-gallery', `size-${size}`, { 'full-width': fullwidth });
|
||||
|
||||
return (
|
||||
<div className={`media-gallery size-${size} ${fullwidth ? 'full-width' : ''}`}>
|
||||
<div className={classNames('spoiler-button', { 'spoiler-button--visible': visible })}>
|
||||
<IconButton title={intl.formatMessage(messages.toggle_visible)} icon={visible ? 'eye' : 'eye-slash'} overlay onClick={this.handleOpen} />
|
||||
</div>
|
||||
|
||||
{children}
|
||||
<div className={computedClass}>
|
||||
{visible ? (
|
||||
<div className='sensitive-info'>
|
||||
<IconButton
|
||||
icon='eye'
|
||||
onClick={handleOpen}
|
||||
overlay
|
||||
title={intl.formatMessage(messages.toggle_visible)}
|
||||
/>
|
||||
{sensitive ? (
|
||||
<span className='sensitive-marker'>
|
||||
<FormattedMessage {...messages.sensitive} />
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
{function () {
|
||||
switch (true) {
|
||||
case !visible:
|
||||
return (
|
||||
<button
|
||||
className='media-spoiler'
|
||||
onClick={handleOpen}
|
||||
>
|
||||
<span className='media-spoiler__warning'>
|
||||
<FormattedMessage {...(sensitive ? messages.warning : messages.hidden)} />
|
||||
</span>
|
||||
<span className='media-spoiler__trigger'>
|
||||
<FormattedMessage {...messages.toggle} />
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
case standalone && media.size === 1 && !!media.getIn([0, 'meta', 'small', 'aspect']):
|
||||
return (
|
||||
<Item
|
||||
attachment={media.get(0)}
|
||||
onClick={handleClick}
|
||||
standalone
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return media.take(4).map(
|
||||
(attachment, i) => (
|
||||
<Item
|
||||
attachment={attachment}
|
||||
index={i}
|
||||
key={attachment.get('id')}
|
||||
letterbox={letterbox}
|
||||
onClick={handleClick}
|
||||
size={size}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
}()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,9 +2,14 @@ import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
const MissingIndicator = () => (
|
||||
<div className='missing-indicator'>
|
||||
<div className='regeneration-indicator missing-indicator'>
|
||||
<div>
|
||||
<FormattedMessage id='missing_indicator.label' defaultMessage='Not found' />
|
||||
<div className='regeneration-indicator__figure' />
|
||||
|
||||
<div className='regeneration-indicator__label'>
|
||||
<FormattedMessage id='missing_indicator.label' tagName='strong' defaultMessage='Not found' />
|
||||
<FormattedMessage id='missing_indicator.sublabel' defaultMessage='This resource could not be found' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -47,7 +47,6 @@ export default class Status extends ImmutablePureComponent {
|
||||
|
||||
state = {
|
||||
isExpanded: null,
|
||||
markedForDelete: false,
|
||||
}
|
||||
|
||||
// Avoid checking props that are functions (and whose equality will always
|
||||
@@ -67,7 +66,6 @@ export default class Status extends ImmutablePureComponent {
|
||||
|
||||
updateOnStates = [
|
||||
'isExpanded',
|
||||
'markedForDelete',
|
||||
]
|
||||
|
||||
// If our settings have changed to disable collapsed statuses, then we
|
||||
@@ -121,15 +119,15 @@ export default class Status extends ImmutablePureComponent {
|
||||
|
||||
if (function () {
|
||||
switch (true) {
|
||||
case collapse:
|
||||
case autoCollapseSettings.get('all'):
|
||||
case autoCollapseSettings.get('notifications') && muted:
|
||||
case !!collapse:
|
||||
case !!autoCollapseSettings.get('all'):
|
||||
case autoCollapseSettings.get('notifications') && !!muted:
|
||||
case autoCollapseSettings.get('lengthy') && node.clientHeight > (
|
||||
status.get('media_attachments').size && !muted ? 650 : 400
|
||||
):
|
||||
case autoCollapseSettings.get('reblogs') && prepend === 'reblogged_by':
|
||||
case autoCollapseSettings.get('replies') && status.get('in_reply_to_id', null) !== null:
|
||||
case autoCollapseSettings.get('media') && !(status.get('spoiler_text').length) && status.get('media_attachments').size:
|
||||
case autoCollapseSettings.get('media') && !(status.get('spoiler_text').length) && !!status.get('media_attachments').size:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
@@ -318,14 +316,14 @@ export default class Status extends ImmutablePureComponent {
|
||||
|
||||
media = (
|
||||
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
|
||||
{Component => <Component
|
||||
{Component => (<Component
|
||||
preview={video.get('preview_url')}
|
||||
src={video.get('url')}
|
||||
sensitive={status.get('sensitive')}
|
||||
letterbox={settings.getIn(['media', 'letterbox'])}
|
||||
fullwidth={settings.getIn(['media', 'fullwidth'])}
|
||||
onOpenVideo={this.handleOpenVideo}
|
||||
/>}
|
||||
/>)}
|
||||
</Bundle>
|
||||
);
|
||||
mediaIcon = 'video-camera';
|
||||
@@ -382,7 +380,6 @@ export default class Status extends ImmutablePureComponent {
|
||||
const computedClass = classNames('status', `status-${status.get('visibility')}`, {
|
||||
collapsed: isExpanded === false,
|
||||
'has-background': isExpanded === false && background,
|
||||
'marked-for-delete': this.state.markedForDelete,
|
||||
muted,
|
||||
}, 'focusable');
|
||||
|
||||
|
||||
@@ -104,8 +104,8 @@ export default class StatusHeader extends React.PureComponent {
|
||||
active={collapsed}
|
||||
title={
|
||||
collapsed ?
|
||||
intl.formatMessage(messages.uncollapse) :
|
||||
intl.formatMessage(messages.collapse)
|
||||
intl.formatMessage(messages.uncollapse) :
|
||||
intl.formatMessage(messages.collapse)
|
||||
}
|
||||
icon='angle-double-up'
|
||||
onClick={this.handleCollapsedClick}
|
||||
|
||||
@@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
|
||||
import StatusContainer from 'flavours/glitch/containers/status_container';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ScrollableList from './scrollable_list';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export default class StatusList extends ImmutablePureComponent {
|
||||
|
||||
@@ -16,6 +17,7 @@ export default class StatusList extends ImmutablePureComponent {
|
||||
trackScroll: PropTypes.bool,
|
||||
shouldUpdateScroll: PropTypes.func,
|
||||
isLoading: PropTypes.bool,
|
||||
isPartial: PropTypes.bool,
|
||||
hasMore: PropTypes.bool,
|
||||
prepend: PropTypes.node,
|
||||
emptyMessage: PropTypes.node,
|
||||
@@ -48,8 +50,23 @@ export default class StatusList extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { statusIds, ...other } = this.props;
|
||||
const { isLoading } = other;
|
||||
const { statusIds, ...other } = this.props;
|
||||
const { isLoading, isPartial } = other;
|
||||
|
||||
if (isPartial) {
|
||||
return (
|
||||
<div className='regeneration-indicator'>
|
||||
<div>
|
||||
<div className='regeneration-indicator__figure' />
|
||||
|
||||
<div className='regeneration-indicator__label'>
|
||||
<FormattedMessage id='regeneration_indicator.label' tagName='strong' defaultMessage='Loading…' />
|
||||
<FormattedMessage id='regeneration_indicator.sublabel' defaultMessage='Your home feed is being prepared!' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const scrollableContent = (isLoading || statusIds.size > 0) ? (
|
||||
statusIds.map((statusId) => (
|
||||
|
||||
@@ -64,11 +64,11 @@ export default class Header extends ImmutablePureComponent {
|
||||
<div className='account__header' style={{ backgroundImage: `url(${account.get('header')})` }}>
|
||||
<div>
|
||||
<a
|
||||
href={account.get('url')}
|
||||
className='account__header__avatar'
|
||||
role='presentation'
|
||||
target='_blank'
|
||||
rel='noopener'
|
||||
href={account.get('url')}
|
||||
className='account__header__avatar'
|
||||
role='presentation'
|
||||
target='_blank'
|
||||
rel='noopener'
|
||||
>
|
||||
<Avatar account={account} size={90} />
|
||||
</a>
|
||||
|
||||
@@ -95,10 +95,10 @@ export default class AccountGallery extends ImmutablePureComponent {
|
||||
|
||||
<div className='account-gallery__container'>
|
||||
{medias.map(media =>
|
||||
<MediaItem
|
||||
(<MediaItem
|
||||
key={media.get('id')}
|
||||
media={media}
|
||||
/>
|
||||
/>)
|
||||
)}
|
||||
{loadMore}
|
||||
</div>
|
||||
|
||||
@@ -8,8 +8,8 @@ const mapStateToProps = state => ({
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
onChange (key, checked) {
|
||||
dispatch(changeSetting(['community', ...key], checked));
|
||||
onChange (path, checked) {
|
||||
dispatch(changeSetting(['community', ...path], checked));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import Motion from 'flavours/glitch/util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import { defineMessages, FormattedMessage } from 'react-intl';
|
||||
|
||||
// This is the spring used with our motion.
|
||||
const motionSpring = spring(1, { damping: 35, stiffness: 400 });
|
||||
|
||||
// Messages.
|
||||
const messages = defineMessages({
|
||||
disclaimer: {
|
||||
defaultMessage: 'This toot won\'t be listed under any hashtag as it is unlisted. Only public toots can be searched by hashtag.',
|
||||
id: 'compose_form.hashtag_warning',
|
||||
},
|
||||
});
|
||||
|
||||
// The component.
|
||||
export default function ComposerHashtagWarning () {
|
||||
return (
|
||||
<Motion
|
||||
defaultStyle={{
|
||||
opacity: 0,
|
||||
scaleX: 0.85,
|
||||
scaleY: 0.75,
|
||||
}}
|
||||
style={{
|
||||
opacity: motionSpring,
|
||||
scaleX: motionSpring,
|
||||
scaleY: motionSpring,
|
||||
}}
|
||||
>
|
||||
{({ opacity, scaleX, scaleY }) => (
|
||||
<div
|
||||
className='composer--warning'
|
||||
style={{
|
||||
opacity: opacity,
|
||||
transform: `scale(${scaleX}, ${scaleY})`,
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
{...messages.disclaimer}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Motion>
|
||||
);
|
||||
}
|
||||
|
||||
ComposerHashtagWarning.propTypes = {};
|
||||
@@ -3,6 +3,8 @@ import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
|
||||
const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\S+)/i;
|
||||
|
||||
// Actions.
|
||||
import {
|
||||
cancelReplyCompose,
|
||||
@@ -36,6 +38,7 @@ import ComposerSpoiler from './spoiler';
|
||||
import ComposerTextarea from './textarea';
|
||||
import ComposerUploadForm from './upload_form';
|
||||
import ComposerWarning from './warning';
|
||||
import ComposerHashtagWarning from './hashtag_warning';
|
||||
|
||||
// Utils.
|
||||
import { countableText } from 'flavours/glitch/util/counter';
|
||||
@@ -312,6 +315,7 @@ class Composer extends React.Component {
|
||||
text={spoilerText}
|
||||
/>
|
||||
{privacy === 'private' && amUnlocked ? <ComposerWarning /> : null}
|
||||
{privacy !== 'public' && APPROX_HASHTAG_RE.test(text) ? <ComposerHashtagWarning /> : null}
|
||||
{replyContent ? (
|
||||
<ComposerReply
|
||||
account={replyAccount}
|
||||
@@ -350,10 +354,10 @@ class Composer extends React.Component {
|
||||
acceptContentTypes={acceptContentTypes}
|
||||
advancedOptions={advancedOptions}
|
||||
disabled={isSubmitting}
|
||||
full={media.size >= 4 || media.some(
|
||||
full={media ? media.size >= 4 || media.some(
|
||||
item => item.get('type') === 'video'
|
||||
)}
|
||||
hasMedia={!!media.size}
|
||||
) : false}
|
||||
hasMedia={media && !!media.size}
|
||||
intl={intl}
|
||||
onChangeAdvancedOption={onChangeAdvancedOption}
|
||||
onChangeSensitivity={onChangeSensitivity}
|
||||
@@ -369,7 +373,7 @@ class Composer extends React.Component {
|
||||
spoiler={spoiler}
|
||||
/>
|
||||
<ComposerPublisher
|
||||
countText={`${spoilerText}${countableText(text)}${advancedOptions.get('do_not_federate') ? ' 👁️' : ''}`}
|
||||
countText={`${spoilerText}${countableText(text)}${advancedOptions && advancedOptions.get('do_not_federate') ? ' 👁️' : ''}`}
|
||||
disabled={isSubmitting || isUploading || !!text.length && !text.trim().length}
|
||||
intl={intl}
|
||||
onSecondarySubmit={handleSecondarySubmit}
|
||||
|
||||
@@ -96,7 +96,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
|
||||
transform: `scale(${scaleX}, ${scaleY})`,
|
||||
}}
|
||||
>
|
||||
{items.map(
|
||||
{items ? items.map(
|
||||
({
|
||||
name,
|
||||
...rest
|
||||
@@ -110,7 +110,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent
|
||||
options={rest}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
)}
|
||||
</Motion>
|
||||
@@ -127,7 +127,7 @@ ComposerOptionsDropdownContent.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
on: PropTypes.bool,
|
||||
text: PropTypes.node,
|
||||
})).isRequired,
|
||||
})),
|
||||
onChange: PropTypes.func,
|
||||
onClose: PropTypes.func,
|
||||
style: PropTypes.object,
|
||||
|
||||
@@ -104,7 +104,10 @@ export default class ComposerOptionsDropdownContentItem extends React.PureCompon
|
||||
<strong>{text}</strong>
|
||||
{meta}
|
||||
</div>
|
||||
) : <div className='content'>{text}</div>}
|
||||
) :
|
||||
<div className='content'>
|
||||
<strong>{text}</strong>
|
||||
</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -292,31 +292,29 @@ export default class ComposerOptions extends React.PureComponent {
|
||||
onClick={onToggleSpoiler}
|
||||
title={intl.formatMessage(messages.spoiler)}
|
||||
/>
|
||||
{advancedOptions ? (
|
||||
<Dropdown
|
||||
active={advancedOptions.some(value => !!value)}
|
||||
disabled={disabled}
|
||||
icon='ellipsis-h'
|
||||
items={[
|
||||
{
|
||||
meta: <FormattedMessage {...messages.local_only_long} />,
|
||||
name: 'do_not_federate',
|
||||
on: advancedOptions.get('do_not_federate'),
|
||||
text: <FormattedMessage {...messages.local_only_short} />,
|
||||
},
|
||||
{
|
||||
meta: <FormattedMessage {...messages.threaded_mode_long} />,
|
||||
name: 'threaded_mode',
|
||||
on: advancedOptions.get('threaded_mode'),
|
||||
text: <FormattedMessage {...messages.threaded_mode_short} />,
|
||||
},
|
||||
]}
|
||||
onChange={onChangeAdvancedOption}
|
||||
onModalClose={onModalClose}
|
||||
onModalOpen={onModalOpen}
|
||||
title={intl.formatMessage(messages.advanced_options_icon_title)}
|
||||
/>
|
||||
) : null}
|
||||
<Dropdown
|
||||
active={advancedOptions && advancedOptions.some(value => !!value)}
|
||||
disabled={disabled}
|
||||
icon='ellipsis-h'
|
||||
items={advancedOptions ? [
|
||||
{
|
||||
meta: <FormattedMessage {...messages.local_only_long} />,
|
||||
name: 'do_not_federate',
|
||||
on: advancedOptions.get('do_not_federate'),
|
||||
text: <FormattedMessage {...messages.local_only_short} />,
|
||||
},
|
||||
{
|
||||
meta: <FormattedMessage {...messages.threaded_mode_long} />,
|
||||
name: 'threaded_mode',
|
||||
on: advancedOptions.get('threaded_mode'),
|
||||
text: <FormattedMessage {...messages.threaded_mode_short} />,
|
||||
},
|
||||
] : null}
|
||||
onChange={onChangeAdvancedOption}
|
||||
onModalClose={onModalClose}
|
||||
onModalOpen={onModalOpen}
|
||||
title={intl.formatMessage(messages.advanced_options_icon_title)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,10 +36,10 @@ export default function ComposerUploadFormProgress ({ progress }) {
|
||||
style={{ width: spring(progress) }}
|
||||
>
|
||||
{({ width }) =>
|
||||
<div
|
||||
(<div
|
||||
className='tracker'
|
||||
style={{ width: `${width}%` }}
|
||||
/>
|
||||
/>)
|
||||
}
|
||||
</Motion>
|
||||
</div>
|
||||
|
||||
@@ -8,8 +8,8 @@ const mapStateToProps = state => ({
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
onChange (key, checked) {
|
||||
dispatch(changeSetting(['direct', ...key], checked));
|
||||
onChange (path, checked) {
|
||||
dispatch(changeSetting(['direct', ...path], checked));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import classNames from 'classnames';
|
||||
|
||||
// Actions.
|
||||
import { openModal } from 'flavours/glitch/actions/modal';
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
showSearch,
|
||||
submitSearch,
|
||||
} from 'flavours/glitch/actions/search';
|
||||
import { cycleElefriendCompose } from 'flavours/glitch/actions/compose';
|
||||
|
||||
// Components.
|
||||
import Composer from 'flavours/glitch/features/composer';
|
||||
@@ -27,6 +29,7 @@ import { wrap } from 'flavours/glitch/util/redux_helpers';
|
||||
const mapStateToProps = state => ({
|
||||
account: state.getIn(['accounts', me]),
|
||||
columns: state.getIn(['settings', 'columns']),
|
||||
elefriend: state.getIn(['compose', 'elefriend']),
|
||||
results: state.getIn(['search', 'results']),
|
||||
searchHidden: state.getIn(['search', 'hidden']),
|
||||
searchValue: state.getIn(['search', 'value']),
|
||||
@@ -37,6 +40,7 @@ const mapStateToProps = state => ({
|
||||
const mapDispatchToProps = {
|
||||
onChange: changeSearch,
|
||||
onClear: clearSearch,
|
||||
onClickElefriend: cycleElefriendCompose,
|
||||
onShow: showSearch,
|
||||
onSubmit: submitSearch,
|
||||
onOpenSettings: openModal.bind(null, 'SETTINGS', {}),
|
||||
@@ -55,10 +59,12 @@ class Drawer extends React.Component {
|
||||
const {
|
||||
account,
|
||||
columns,
|
||||
elefriend,
|
||||
intl,
|
||||
multiColumn,
|
||||
onChange,
|
||||
onClear,
|
||||
onClickElefriend,
|
||||
onOpenSettings,
|
||||
onShow,
|
||||
onSubmit,
|
||||
@@ -67,10 +73,11 @@ class Drawer extends React.Component {
|
||||
searchValue,
|
||||
submitted,
|
||||
} = this.props;
|
||||
const computedClass = classNames('drawer', `mbstobon-${elefriend}`);
|
||||
|
||||
// The result.
|
||||
return (
|
||||
<div className='drawer'>
|
||||
<div className={computedClass}>
|
||||
{multiColumn ? (
|
||||
<DrawerHeader
|
||||
columns={columns}
|
||||
@@ -90,6 +97,7 @@ class Drawer extends React.Component {
|
||||
<div className='contents'>
|
||||
<DrawerAccount account={account} />
|
||||
<Composer />
|
||||
{multiColumn && <button className='mastodon' onClick={onClickElefriend} />}
|
||||
<DrawerResults
|
||||
results={results}
|
||||
visible={submitted && !searchHidden}
|
||||
@@ -110,6 +118,7 @@ Drawer.propTypes = {
|
||||
account: ImmutablePropTypes.map,
|
||||
columns: ImmutablePropTypes.list,
|
||||
results: ImmutablePropTypes.map,
|
||||
elefriend: PropTypes.number,
|
||||
searchHidden: PropTypes.bool,
|
||||
searchValue: PropTypes.string,
|
||||
submitted: PropTypes.bool,
|
||||
@@ -117,6 +126,7 @@ Drawer.propTypes = {
|
||||
// Dispatch props.
|
||||
onChange: PropTypes.func,
|
||||
onClear: PropTypes.func,
|
||||
onClickElefriend: PropTypes.func,
|
||||
onShow: PropTypes.func,
|
||||
onSubmit: PropTypes.func,
|
||||
onOpenSettings: PropTypes.func,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/col
|
||||
import StatusList from 'flavours/glitch/components/status_list';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.favourites', defaultMessage: 'Favourites' },
|
||||
@@ -16,6 +17,7 @@ const messages = defineMessages({
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
statusIds: state.getIn(['status_lists', 'favourites', 'items']),
|
||||
isLoading: state.getIn(['status_lists', 'favourites', 'isLoading'], true),
|
||||
hasMore: !!state.getIn(['status_lists', 'favourites', 'next']),
|
||||
});
|
||||
|
||||
@@ -30,6 +32,7 @@ export default class Favourites extends ImmutablePureComponent {
|
||||
columnId: PropTypes.string,
|
||||
multiColumn: PropTypes.bool,
|
||||
hasMore: PropTypes.bool,
|
||||
isLoading: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
@@ -59,12 +62,12 @@ export default class Favourites extends ImmutablePureComponent {
|
||||
this.column = c;
|
||||
}
|
||||
|
||||
handleScrollToBottom = () => {
|
||||
handleScrollToBottom = debounce(() => {
|
||||
this.props.dispatch(expandFavouritedStatuses());
|
||||
}
|
||||
}, 300, { leading: true })
|
||||
|
||||
render () {
|
||||
const { intl, statusIds, columnId, multiColumn, hasMore } = this.props;
|
||||
const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
|
||||
const pinned = !!columnId;
|
||||
|
||||
return (
|
||||
@@ -85,6 +88,7 @@ export default class Favourites extends ImmutablePureComponent {
|
||||
statusIds={statusIds}
|
||||
scrollKey={`favourited_statuses-${columnId}`}
|
||||
hasMore={hasMore}
|
||||
isLoading={isLoading}
|
||||
onScrollToBottom={this.handleScrollToBottom}
|
||||
/>
|
||||
</Column>
|
||||
|
||||
@@ -79,7 +79,7 @@ export default class GettingStarted extends ImmutablePureComponent {
|
||||
render () {
|
||||
const { intl, myAccount, columns, multiColumn, lists } = this.props;
|
||||
|
||||
let navItems = [];
|
||||
const navItems = [];
|
||||
let listItems = [];
|
||||
|
||||
if (multiColumn) {
|
||||
|
||||
@@ -8,8 +8,8 @@ const mapStateToProps = state => ({
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
onChange (key, checked) {
|
||||
dispatch(changeSetting(['home', ...key], checked));
|
||||
onChange (path, checked) {
|
||||
dispatch(changeSetting(['home', ...path], checked));
|
||||
},
|
||||
|
||||
onSave () {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { expandHomeTimeline } from 'flavours/glitch/actions/timelines';
|
||||
import { expandHomeTimeline, refreshHomeTimeline } from 'flavours/glitch/actions/timelines';
|
||||
import PropTypes from 'prop-types';
|
||||
import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
|
||||
import Column from 'flavours/glitch/components/column';
|
||||
@@ -16,6 +16,7 @@ const messages = defineMessages({
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0,
|
||||
isPartial: state.getIn(['timelines', 'home', 'isPartial'], false),
|
||||
});
|
||||
|
||||
@connect(mapStateToProps)
|
||||
@@ -26,6 +27,7 @@ export default class HomeTimeline extends React.PureComponent {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
hasUnread: PropTypes.bool,
|
||||
isPartial: PropTypes.bool,
|
||||
columnId: PropTypes.string,
|
||||
multiColumn: PropTypes.bool,
|
||||
};
|
||||
@@ -57,6 +59,39 @@ export default class HomeTimeline extends React.PureComponent {
|
||||
this.props.dispatch(expandHomeTimeline());
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this._checkIfReloadNeeded(false, this.props.isPartial);
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
this._checkIfReloadNeeded(prevProps.isPartial, this.props.isPartial);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this._stopPolling();
|
||||
}
|
||||
|
||||
_checkIfReloadNeeded (wasPartial, isPartial) {
|
||||
const { dispatch } = this.props;
|
||||
|
||||
if (wasPartial === isPartial) {
|
||||
return;
|
||||
} else if (!wasPartial && isPartial) {
|
||||
this.polling = setInterval(() => {
|
||||
dispatch(refreshHomeTimeline());
|
||||
}, 3000);
|
||||
} else if (wasPartial && !isPartial) {
|
||||
this._stopPolling();
|
||||
}
|
||||
}
|
||||
|
||||
_stopPolling () {
|
||||
if (this.polling) {
|
||||
clearInterval(this.polling);
|
||||
this.polling = null;
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl, hasUnread, columnId, multiColumn } = this.props;
|
||||
const pinned = !!columnId;
|
||||
|
||||
@@ -67,9 +67,9 @@ export default class ListEditor extends ImmutablePureComponent {
|
||||
|
||||
<Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
|
||||
{({ x }) =>
|
||||
<div className='drawer__inner backdrop' style={{ transform: x === 0 ? null : `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
|
||||
(<div className='drawer__inner backdrop' style={{ transform: x === 0 ? null : `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
|
||||
{searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} />)}
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
</Motion>
|
||||
</div>
|
||||
|
||||
@@ -120,13 +120,17 @@ export default class ListTimeline extends React.PureComponent {
|
||||
if (typeof list === 'undefined') {
|
||||
return (
|
||||
<Column>
|
||||
<LoadingIndicator />
|
||||
<div className='scrollable'>
|
||||
<LoadingIndicator />
|
||||
</div>
|
||||
</Column>
|
||||
);
|
||||
} else if (list === false) {
|
||||
return (
|
||||
<Column>
|
||||
<MissingIndicator />
|
||||
<div className='scrollable'>
|
||||
<MissingIndicator />
|
||||
</div>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ export default class ColumnSettings extends React.PureComponent {
|
||||
onClear: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
onPushChange = (key, checked) => {
|
||||
this.props.onChange(['push', ...key], checked);
|
||||
onPushChange = (path, checked) => {
|
||||
this.props.onChange(['push', ...path], checked);
|
||||
}
|
||||
|
||||
render () {
|
||||
|
||||
@@ -18,11 +18,11 @@ const mapStateToProps = state => ({
|
||||
|
||||
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
|
||||
onChange (key, checked) {
|
||||
if (key[0] === 'push') {
|
||||
dispatch(changePushNotifications(key.slice(1), checked));
|
||||
onChange (path, checked) {
|
||||
if (path[0] === 'push') {
|
||||
dispatch(changePushNotifications(path.slice(1), checked));
|
||||
} else {
|
||||
dispatch(changeSetting(['notifications', ...key], checked));
|
||||
dispatch(changeSetting(['notifications', ...path], checked));
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ const mapStateToProps = state => ({
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
|
||||
onChange (key, checked) {
|
||||
dispatch(changeSetting(['public', ...key], checked));
|
||||
onChange (path, checked) {
|
||||
dispatch(changeSetting(['public', ...path], checked));
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
import axios from 'axios';
|
||||
import api from 'flavours/glitch/util/api';
|
||||
|
||||
@injectIntl
|
||||
export default class EmbedModal extends ImmutablePureComponent {
|
||||
@@ -23,7 +23,7 @@ export default class EmbedModal extends ImmutablePureComponent {
|
||||
|
||||
this.setState({ loading: true });
|
||||
|
||||
axios.post('/api/web/embed', { url }).then(res => {
|
||||
api().post('/api/web/embed', { url }).then(res => {
|
||||
this.setState({ loading: false, oembed: res.data });
|
||||
|
||||
const iframeDocument = this.iframe.contentWindow.document;
|
||||
|
||||
@@ -124,7 +124,7 @@ export default class ModalRoot extends React.PureComponent {
|
||||
(<BundleContainer fetchComponent={MODAL_COMPONENTS[type]} loading={this.renderLoading(type)} error={this.renderError} renderDelay={200}>
|
||||
{(SpecificComponent) => <SpecificComponent {...props} onClose={onClose} />}
|
||||
</BundleContainer>) :
|
||||
null
|
||||
null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -38,11 +38,6 @@ PageOne.propTypes = {
|
||||
domain: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
const composerState = {
|
||||
showSearch: true,
|
||||
text: 'Awoo! #introductions',
|
||||
};
|
||||
|
||||
const PageTwo = ({ intl, myAccount }) => (
|
||||
<div className='onboarding-modal__page onboarding-modal__page-two'>
|
||||
<div className='figure non-interactive'>
|
||||
@@ -50,7 +45,8 @@ const PageTwo = ({ intl, myAccount }) => (
|
||||
<DrawerAccount account={myAccount} />
|
||||
<RawComposer
|
||||
intl={intl}
|
||||
state={composerState}
|
||||
privacy='public'
|
||||
text='Awoo! #introductions'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -38,12 +38,12 @@ export default class UploadArea extends React.PureComponent {
|
||||
return (
|
||||
<Motion defaultStyle={{ backgroundOpacity: 0, backgroundScale: 0.95 }} style={{ backgroundOpacity: spring(active ? 1 : 0, { stiffness: 150, damping: 15 }), backgroundScale: spring(active ? 1 : 0.95, { stiffness: 200, damping: 3 }) }}>
|
||||
{({ backgroundOpacity, backgroundScale }) =>
|
||||
<div className='upload-area' style={{ visibility: active ? 'visible' : 'hidden', opacity: backgroundOpacity }}>
|
||||
(<div className='upload-area' style={{ visibility: active ? 'visible' : 'hidden', opacity: backgroundOpacity }}>
|
||||
<div className='upload-area__drop'>
|
||||
<div className='upload-area__background' style={{ transform: `scale(${backgroundScale})` }} />
|
||||
<div className='upload-area__content'><FormattedMessage id='upload_area.title' defaultMessage='Drag & drop to upload' /></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
</Motion>
|
||||
);
|
||||
|
||||
@@ -23,6 +23,7 @@ export default class VideoModal extends ImmutablePureComponent {
|
||||
src={media.get('url')}
|
||||
startTime={time}
|
||||
onCloseVideo={onClose}
|
||||
detailed
|
||||
description={media.get('description')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -51,6 +51,7 @@ const makeMapStateToProps = () => {
|
||||
const mapStateToProps = (state, { timelineId }) => ({
|
||||
statusIds: getStatusIds(state, { type: timelineId }),
|
||||
isLoading: state.getIn(['timelines', timelineId, 'isLoading'], true),
|
||||
isPartial: state.getIn(['timelines', timelineId, 'isPartial'], false),
|
||||
hasMore: !!state.getIn(['timelines', timelineId, 'next']),
|
||||
});
|
||||
|
||||
|
||||
@@ -17,6 +17,17 @@ const messages = defineMessages({
|
||||
exit_fullscreen: { id: 'video.exit_fullscreen', defaultMessage: 'Exit full screen' },
|
||||
});
|
||||
|
||||
const formatTime = secondsNum => {
|
||||
let hours = Math.floor(secondsNum / 3600);
|
||||
let minutes = Math.floor((secondsNum - (hours * 3600)) / 60);
|
||||
let seconds = secondsNum - (hours * 3600) - (minutes * 60);
|
||||
|
||||
if (hours < 10) hours = '0' + hours;
|
||||
if (minutes < 10) minutes = '0' + minutes;
|
||||
if (seconds < 10) seconds = '0' + seconds;
|
||||
return (hours === '00' ? '' : `${hours}:`) + `${minutes}:${seconds}`;
|
||||
};
|
||||
|
||||
const findElementPosition = el => {
|
||||
let box;
|
||||
|
||||
@@ -85,11 +96,13 @@ export default class Video extends React.PureComponent {
|
||||
onCloseVideo: PropTypes.func,
|
||||
letterbox: PropTypes.bool,
|
||||
fullwidth: PropTypes.bool,
|
||||
detailed: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
progress: 0,
|
||||
currentTime: 0,
|
||||
duration: 0,
|
||||
paused: true,
|
||||
dragging: false,
|
||||
fullscreen: false,
|
||||
@@ -119,7 +132,10 @@ export default class Video extends React.PureComponent {
|
||||
}
|
||||
|
||||
handleTimeUpdate = () => {
|
||||
this.setState({ progress: 100 * (this.video.currentTime / this.video.duration) });
|
||||
this.setState({
|
||||
currentTime: Math.floor(this.video.currentTime),
|
||||
duration: Math.floor(this.video.duration),
|
||||
});
|
||||
}
|
||||
|
||||
handleMouseDown = e => {
|
||||
@@ -145,8 +161,10 @@ export default class Video extends React.PureComponent {
|
||||
|
||||
handleMouseMove = throttle(e => {
|
||||
const { x } = getPointerPosition(this.seek, e);
|
||||
this.video.currentTime = this.video.duration * x;
|
||||
this.setState({ progress: x * 100 });
|
||||
const currentTime = Math.floor(this.video.duration * x);
|
||||
|
||||
this.video.currentTime = currentTime;
|
||||
this.setState({ currentTime });
|
||||
}, 60);
|
||||
|
||||
togglePlay = () => {
|
||||
@@ -228,11 +246,12 @@ export default class Video extends React.PureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { preview, src, width, height, startTime, onOpenVideo, onCloseVideo, intl, alt, letterbox, fullwidth } = this.props;
|
||||
const { progress, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
|
||||
const { preview, src, width, height, startTime, onOpenVideo, onCloseVideo, intl, alt, letterbox, fullwidth, detailed } = this.props;
|
||||
const { currentTime, duration, buffer, dragging, paused, fullscreen, hovered, muted, revealed } = this.state;
|
||||
const progress = (currentTime / duration) * 100;
|
||||
|
||||
return (
|
||||
<div className={classNames('video-player', { inactive: !revealed, inline: !fullscreen, fullscreen, letterbox, 'full-width': fullwidth })} ref={this.setPlayerRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<div className={classNames('video-player', { inactive: !revealed, detailed, inline: width && height && !fullscreen, fullscreen, letterbox, 'full-width': fullwidth })} style={{ width, height }} ref={this.setPlayerRef} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<video
|
||||
ref={this.setVideoRef}
|
||||
src={src}
|
||||
@@ -269,16 +288,27 @@ export default class Video extends React.PureComponent {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='video-player__buttons left'>
|
||||
<button aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button>
|
||||
<button aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button>
|
||||
{!onCloseVideo && <button aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><i className='fa fa-fw fa-eye' /></button>}
|
||||
</div>
|
||||
<div className='video-player__buttons-bar'>
|
||||
<div className='video-player__buttons left'>
|
||||
<button aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><i className={classNames('fa fa-fw', { 'fa-play': paused, 'fa-pause': !paused })} /></button>
|
||||
<button aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><i className={classNames('fa fa-fw', { 'fa-volume-off': muted, 'fa-volume-up': !muted })} /></button>
|
||||
|
||||
<div className='video-player__buttons right'>
|
||||
{(!fullscreen && onOpenVideo) && <button aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><i className='fa fa-fw fa-expand' /></button>}
|
||||
{onCloseVideo && <button aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><i className='fa fa-fw fa-times' /></button>}
|
||||
<button aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><i className={classNames('fa fa-fw', { 'fa-arrows-alt': !fullscreen, 'fa-compress': fullscreen })} /></button>
|
||||
{!onCloseVideo && <button aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><i className='fa fa-fw fa-eye' /></button>}
|
||||
|
||||
{(detailed || fullscreen) &&
|
||||
<span>
|
||||
<span className='video-player__time-current'>{formatTime(currentTime)}</span>
|
||||
<span className='video-player__time-sep'>/</span>
|
||||
<span className='video-player__time-total'>{formatTime(duration)}</span>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className='video-player__buttons right'>
|
||||
{(!fullscreen && onOpenVideo) && <button aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><i className='fa fa-fw fa-expand' /></button>}
|
||||
{onCloseVideo && <button aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><i className='fa fa-fw fa-compress' /></button>}
|
||||
<button aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><i className={classNames('fa fa-fw', { 'fa-arrows-alt': !fullscreen, 'fa-compress': fullscreen })} /></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 8.3 KiB |
BIN
app/javascript/flavours/glitch/images/mbstobon-ui-0.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
app/javascript/flavours/glitch/images/mbstobon-ui-1.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
app/javascript/flavours/glitch/images/mbstobon-ui-2.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
BIN
app/javascript/flavours/glitch/images/wave-drawer-glitched.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
@@ -34,6 +34,8 @@ const messages = {
|
||||
'status.collapse': 'Collapse',
|
||||
'status.uncollapse': 'Uncollapse',
|
||||
|
||||
'media_gallery.sensitive': 'Sensitive',
|
||||
|
||||
'favourite_modal.combo': 'You can press {combo} to skip this next time',
|
||||
|
||||
'home.column_settings.show_direct': 'Show DMs',
|
||||
|
||||
@@ -34,6 +34,8 @@ const messages = {
|
||||
'status.collapse': 'Zwiń',
|
||||
'status.uncollapse': 'Rozwiń',
|
||||
|
||||
'media_gallery.sensitive': 'Zawartość wrażliwa',
|
||||
|
||||
'favourite_modal.combo': 'Możesz nacisnąć {combo}, aby pominąć to następnym razem',
|
||||
|
||||
'home.column_settings.show_direct': 'Pokaż wiadomości bezpośrednie',
|
||||
@@ -52,9 +54,13 @@ const messages = {
|
||||
'compose.attach.doodle': 'Narysuj coś',
|
||||
'compose.attach': 'Załącz coś',
|
||||
|
||||
'advanced-options.local-only.short': 'Tylko lokalnie',
|
||||
'advanced-options.local-only.long': 'Nie wysyłaj na inne instancje',
|
||||
'advanced_options.local-only.short': 'Tylko lokalnie',
|
||||
'advanced_options.local-only.long': 'Nie wysyłaj na inne instancje',
|
||||
'advanced_options.local-only.tooltip': 'Ten wpis jest widoczny tylko lokalnie',
|
||||
'advanced_options.icon_title': 'Ustawienia zaawansowane',
|
||||
'advanced_options.threaded_mode.short': 'Tryb wątków',
|
||||
'advanced_options.threaded_mode.long': 'Przechodzi do tworzenia odpowiedzi po publikacji wpisu',
|
||||
'advanced_options.threaded_mode.tooltip': 'Włączono tryb wątków',
|
||||
};
|
||||
|
||||
export default Object.assign({}, inherited, messages);
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
COMPOSE_MOUNT,
|
||||
COMPOSE_UNMOUNT,
|
||||
COMPOSE_CHANGE,
|
||||
COMPOSE_CYCLE_ELEFRIEND,
|
||||
COMPOSE_REPLY,
|
||||
COMPOSE_REPLY_CANCEL,
|
||||
COMPOSE_MENTION,
|
||||
@@ -35,6 +36,12 @@ import uuid from 'flavours/glitch/util/uuid';
|
||||
import { me } from 'flavours/glitch/util/initial_state';
|
||||
import { overwrite } from 'flavours/glitch/util/js_helpers';
|
||||
|
||||
const totalElefriends = 3;
|
||||
|
||||
// ~4% chance you'll end up with an unexpected friend
|
||||
// glitch-soc/mastodon repo created_at date: 2017-04-20T21:55:28Z
|
||||
const glitchProbability = 1 - 0.0420215528;
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
mounted: false,
|
||||
advanced_options: ImmutableMap({
|
||||
@@ -42,6 +49,7 @@ const initialState = ImmutableMap({
|
||||
threaded_mode: false,
|
||||
}),
|
||||
sensitive: false,
|
||||
elefriend: Math.random() < glitchProbability ? Math.floor(Math.random() * totalElefriends) : totalElefriends,
|
||||
spoiler: false,
|
||||
spoiler_text: '',
|
||||
privacy: null,
|
||||
@@ -134,7 +142,7 @@ function continueThread (state, status) {
|
||||
'advanced_options',
|
||||
map => map.merge(new ImmutableMap({ do_not_federate: /👁\ufe0f?\u200b?(?:<\/p>)?$/.test(status.content) }))
|
||||
);
|
||||
map.set('privacy', privacyPreference(status.visibility, state.get('default_privacy')));
|
||||
map.set('privacy', status.visibility);
|
||||
map.set('sensitive', false);
|
||||
map.update('media_attachments', list => list.clear());
|
||||
map.set('idempotencyKey', uuid());
|
||||
@@ -259,6 +267,9 @@ export default function compose(state = initialState, action) {
|
||||
return state
|
||||
.set('text', action.text)
|
||||
.set('idempotencyKey', uuid());
|
||||
case COMPOSE_CYCLE_ELEFRIEND:
|
||||
return state
|
||||
.set('elefriend', (state.get('elefriend') + 1) % totalElefriends);
|
||||
case COMPOSE_REPLY:
|
||||
return state.withMutations(map => {
|
||||
map.set('in_reply_to', action.status.get('id'));
|
||||
|
||||
@@ -44,7 +44,7 @@ export default function push_subscriptions(state = initialState, action) {
|
||||
case CLEAR_SUBSCRIPTION:
|
||||
return initialState;
|
||||
case SET_ALERTS:
|
||||
return state.setIn(action.key, action.value);
|
||||
return state.setIn(action.path, action.value);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ export default function settings(state = initialState, action) {
|
||||
return hydrate(state, action.state.get('settings'));
|
||||
case SETTING_CHANGE:
|
||||
return state
|
||||
.setIn(action.key, action.value)
|
||||
.setIn(action.path, action.value)
|
||||
.set('saved', false);
|
||||
case COLUMN_ADD:
|
||||
return state
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import {
|
||||
FAVOURITED_STATUSES_FETCH_REQUEST,
|
||||
FAVOURITED_STATUSES_FETCH_SUCCESS,
|
||||
FAVOURITED_STATUSES_FETCH_FAIL,
|
||||
FAVOURITED_STATUSES_EXPAND_REQUEST,
|
||||
FAVOURITED_STATUSES_EXPAND_SUCCESS,
|
||||
FAVOURITED_STATUSES_EXPAND_FAIL,
|
||||
} from 'flavours/glitch/actions/favourites';
|
||||
import {
|
||||
PINNED_STATUSES_FETCH_SUCCESS,
|
||||
@@ -30,6 +34,7 @@ const normalizeList = (state, listType, statuses, next) => {
|
||||
return state.update(listType, listMap => listMap.withMutations(map => {
|
||||
map.set('next', next);
|
||||
map.set('loaded', true);
|
||||
map.set('isLoading', false);
|
||||
map.set('items', ImmutableList(statuses.map(item => item.id)));
|
||||
}));
|
||||
};
|
||||
@@ -37,6 +42,7 @@ const normalizeList = (state, listType, statuses, next) => {
|
||||
const appendToList = (state, listType, statuses, next) => {
|
||||
return state.update(listType, listMap => listMap.withMutations(map => {
|
||||
map.set('next', next);
|
||||
map.set('isLoading', false);
|
||||
map.set('items', map.get('items').concat(statuses.map(item => item.id)));
|
||||
}));
|
||||
};
|
||||
@@ -55,6 +61,12 @@ const removeOneFromList = (state, listType, status) => {
|
||||
|
||||
export default function statusLists(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case FAVOURITED_STATUSES_FETCH_REQUEST:
|
||||
case FAVOURITED_STATUSES_EXPAND_REQUEST:
|
||||
return state.setIn(['favourites', 'isLoading'], true);
|
||||
case FAVOURITED_STATUSES_FETCH_FAIL:
|
||||
case FAVOURITED_STATUSES_EXPAND_FAIL:
|
||||
return state.setIn(['favourites', 'isLoading'], false);
|
||||
case FAVOURITED_STATUSES_FETCH_SUCCESS:
|
||||
return normalizeList(state, 'favourites', action.statuses, action.next);
|
||||
case FAVOURITED_STATUSES_EXPAND_SUCCESS:
|
||||
|
||||
@@ -30,7 +30,7 @@ const initialTimeline = ImmutableMap({
|
||||
items: ImmutableList(),
|
||||
});
|
||||
|
||||
const normalizeTimeline = (state, timeline, statuses, next) => {
|
||||
const normalizeTimeline = (state, timeline, statuses, next, isPartial) => {
|
||||
const oldIds = state.getIn([timeline, 'items'], ImmutableList());
|
||||
const ids = ImmutableList(statuses.map(status => status.get('id'))).filter(newId => !oldIds.includes(newId));
|
||||
const wasLoaded = state.getIn([timeline, 'loaded']);
|
||||
@@ -40,7 +40,8 @@ const normalizeTimeline = (state, timeline, statuses, next) => {
|
||||
mMap.set('loaded', true);
|
||||
mMap.set('isLoading', false);
|
||||
if (!hadNext) mMap.set('next', next);
|
||||
mMap.set('items', wasLoaded ? ids.concat(oldIds) : ids);
|
||||
mMap.set('items', wasLoaded ? ids.concat(oldIds) : oldIds.concat(ids));
|
||||
mMap.set('isPartial', isPartial);
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -125,7 +126,7 @@ export default function timelines(state = initialState, action) {
|
||||
case TIMELINE_EXPAND_FAIL:
|
||||
return state.update(action.timeline, initialTimeline, map => map.set('isLoading', false));
|
||||
case TIMELINE_REFRESH_SUCCESS:
|
||||
return normalizeTimeline(state, action.timeline, fromJS(action.statuses), action.next);
|
||||
return normalizeTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial);
|
||||
case TIMELINE_EXPAND_SUCCESS:
|
||||
return appendNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next);
|
||||
case TIMELINE_UPDATE:
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
display: inline;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: 500;
|
||||
font-weight: 700;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
@@ -424,14 +424,19 @@
|
||||
text-align: center;
|
||||
|
||||
.avatar {
|
||||
@include avatar-size(80px);
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 15px;
|
||||
@include avatar-size(80px);
|
||||
|
||||
img {
|
||||
display: block;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 48px;
|
||||
@include avatar-radius();
|
||||
@include avatar-size(80px);
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
margin-bottom: 30px;
|
||||
margin-bottom: 15px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@@ -83,16 +83,20 @@
|
||||
}
|
||||
|
||||
.avatar {
|
||||
@include avatar-size(120px);
|
||||
width: 120px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
@include avatar-size(120px);
|
||||
|
||||
img {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
display: block;
|
||||
border-radius: 120px;
|
||||
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
|
||||
@include avatar-radius();
|
||||
@include avatar-size(120px);
|
||||
display: block;
|
||||
box-shadow: 0 0 15px rgba($base-shadow-color, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +127,7 @@
|
||||
}
|
||||
|
||||
.roles {
|
||||
margin-bottom: 30px;
|
||||
margin-bottom: 15px;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
@@ -203,53 +207,10 @@
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
padding: 0 15px;
|
||||
text-align: center;
|
||||
color: $ui-secondary-color;
|
||||
}
|
||||
|
||||
.metadata {
|
||||
$meta-table-border: darken($classic-highlight-color, 20%);//#174f77;
|
||||
|
||||
border-collapse: collapse;
|
||||
padding: 0;
|
||||
margin: 15px -15px -10px -15px;
|
||||
border: 0 none;
|
||||
border-top: 1px solid $meta-table-border;
|
||||
border-bottom: 1px solid $meta-table-border;
|
||||
|
||||
td, th {
|
||||
padding: 10px;
|
||||
border: 0 none;
|
||||
border-bottom: 1px solid $meta-table-border;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
tr:last-child {
|
||||
td, th {
|
||||
border-bottom: 0 none;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
color: $ui-primary-color;
|
||||
width:100%; // makes it stretch
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
th {
|
||||
padding-left: 15px;
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
width: 94px;
|
||||
color: $ui-secondary-color;
|
||||
background: darken($ui-base-color, 8%);
|
||||
//background: #131415;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $classic-highlight-color;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
display: block;
|
||||
|
||||
@@ -260,7 +221,7 @@
|
||||
.name,
|
||||
.roles {
|
||||
text-align: center;
|
||||
margin-bottom: 15px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.bio {
|
||||
@@ -407,14 +368,19 @@
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
@include avatar-size(80px);
|
||||
|
||||
img {
|
||||
display: block;
|
||||
@include avatar-radius();
|
||||
@include avatar-size(80px);
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 80px;
|
||||
border: 2px solid $simple-background-color;
|
||||
background: $simple-background-color;
|
||||
@include avatar-radius();
|
||||
@include avatar-size(80px);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -492,14 +458,17 @@
|
||||
}
|
||||
|
||||
& > div {
|
||||
@include avatar-size(48px);
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
@include avatar-size(48px);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
@include avatar-radius();
|
||||
display: block;
|
||||
border-radius: 4px;
|
||||
@include avatar-radius();
|
||||
}
|
||||
|
||||
.display-name {
|
||||
@@ -513,6 +482,12 @@
|
||||
strong {
|
||||
font-weight: 500;
|
||||
color: $ui-base-color;
|
||||
|
||||
@each $lang in $cjk-langs {
|
||||
&:lang(#{$lang}) {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
@@ -587,3 +562,5 @@
|
||||
border-color: rgba(lighten($error-red, 12%), 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
@import 'metadata';
|
||||
|
||||
@@ -121,6 +121,12 @@
|
||||
strong {
|
||||
color: $primary-text-color;
|
||||
font-weight: 500;
|
||||
|
||||
@each $lang in $cjk-langs {
|
||||
&:lang(#{$lang}) {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,6 +228,12 @@
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
|
||||
@each $lang in $cjk-langs {
|
||||
&:lang(#{$lang}) {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -281,6 +293,12 @@
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
color: $ui-secondary-color;
|
||||
|
||||
@each $lang in $cjk-langs {
|
||||
&:lang(#{$lang}) {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.account-card {
|
||||
@@ -396,10 +414,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
max-width: calc(100% - 90px);
|
||||
}
|
||||
|
||||
&__title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
&__timestamp {
|
||||
@@ -413,7 +433,7 @@
|
||||
color: $ui-primary-color;
|
||||
font-family: 'mastodon-font-monospace', monospace;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
word-wrap: break-word;
|
||||
min-height: 20px;
|
||||
}
|
||||
|
||||
|
||||
463
app/javascript/flavours/glitch/styles/components/accounts.scss
Normal file
@@ -0,0 +1,463 @@
|
||||
.account {
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
|
||||
.account__display-name {
|
||||
flex: 1 1 auto;
|
||||
display: block;
|
||||
color: $ui-primary-color;
|
||||
overflow: hidden;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&.small {
|
||||
border: none;
|
||||
padding: 0;
|
||||
|
||||
& > .account__avatar-wrapper { margin: 0 8px 0 0 }
|
||||
|
||||
& > .display-name {
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.account__wrapper {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.account__avatar-wrapper {
|
||||
float: left;
|
||||
margin: 6px 16px 6px 6px;
|
||||
}
|
||||
|
||||
.account__avatar {
|
||||
@include avatar-radius();
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
|
||||
&-inline {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.account__avatar-overlay {
|
||||
position: relative;
|
||||
@include avatar-size(48px);
|
||||
|
||||
&-base {
|
||||
@include avatar-radius();
|
||||
@include avatar-size(36px);
|
||||
}
|
||||
|
||||
&-overlay {
|
||||
@include avatar-radius();
|
||||
@include avatar-size(24px);
|
||||
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.account__relationship {
|
||||
height: 18px;
|
||||
padding: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.account__header__wrapper {
|
||||
flex: 0 0 auto;
|
||||
background: lighten($ui-base-color, 4%);
|
||||
}
|
||||
|
||||
.account__header {
|
||||
flex: 0 0 auto;
|
||||
background: lighten($ui-base-color, 4%);
|
||||
text-align: center;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
position: relative;
|
||||
|
||||
.account__avatar {
|
||||
@include avatar-radius();
|
||||
@include avatar-size(90px);
|
||||
display: block;
|
||||
margin: 0 auto 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&.inactive {
|
||||
opacity: 0.5;
|
||||
|
||||
.account__header__avatar {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
.account__header__username {
|
||||
color: $ui-primary-color;
|
||||
}
|
||||
}
|
||||
|
||||
& > div {
|
||||
background: rgba(lighten($ui-base-color, 4%), 0.9);
|
||||
padding: 20px 10px;
|
||||
}
|
||||
|
||||
.account__header__content {
|
||||
color: $ui-secondary-color;
|
||||
}
|
||||
|
||||
.account__header__display-name {
|
||||
color: $primary-text-color;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
font-size: 20px;
|
||||
line-height: 27px;
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.account__header__username {
|
||||
color: $ui-highlight-color;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.account__disclaimer {
|
||||
padding: 10px;
|
||||
border-top: 1px solid lighten($ui-base-color, 8%);
|
||||
color: $ui-base-lighter-color;
|
||||
|
||||
strong {
|
||||
font-weight: 500;
|
||||
|
||||
@each $lang in $cjk-langs {
|
||||
&:lang(#{$lang}) {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.account__header__content {
|
||||
color: $ui-primary-color;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
overflow: hidden;
|
||||
word-break: normal;
|
||||
word-wrap: break-word;
|
||||
|
||||
p {
|
||||
margin-bottom: 20px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.account__header__display-name {
|
||||
.emojione {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.account__action-bar {
|
||||
border-top: 1px solid lighten($ui-base-color, 8%);
|
||||
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
||||
line-height: 36px;
|
||||
overflow: hidden;
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.account__action-bar-dropdown {
|
||||
flex: 0 1 calc(50% - 140px);
|
||||
padding: 10px;
|
||||
|
||||
.dropdown--active {
|
||||
.dropdown__content.dropdown__right {
|
||||
left: 6px;
|
||||
right: initial;
|
||||
}
|
||||
|
||||
&::after {
|
||||
bottom: initial;
|
||||
margin-left: 11px;
|
||||
margin-top: -7px;
|
||||
right: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.account__action-bar-links {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.account__action-bar__tab {
|
||||
text-decoration: none;
|
||||
overflow: hidden;
|
||||
flex: 0 1 80px;
|
||||
border-left: 1px solid lighten($ui-base-color, 8%);
|
||||
padding: 10px 5px;
|
||||
|
||||
& > span {
|
||||
display: block;
|
||||
text-transform: uppercase;
|
||||
font-size: 11px;
|
||||
color: $ui-primary-color;
|
||||
}
|
||||
|
||||
strong {
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: $primary-text-color;
|
||||
|
||||
@each $lang in $cjk-langs {
|
||||
&:lang(#{$lang}) {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abbr {
|
||||
color: $ui-base-lighter-color;
|
||||
}
|
||||
}
|
||||
|
||||
.account__header__avatar {
|
||||
background-size: 90px 90px;
|
||||
display: block;
|
||||
height: 90px;
|
||||
margin: 0 auto 10px;
|
||||
overflow: hidden;
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
.account-authorize {
|
||||
padding: 14px 10px;
|
||||
|
||||
.detailed-status__display-name {
|
||||
display: block;
|
||||
margin-bottom: 15px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
.account-authorize__avatar {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.notification__message {
|
||||
margin-left: 42px;
|
||||
padding: 8px 0 0 26px;
|
||||
cursor: default;
|
||||
color: $ui-primary-color;
|
||||
font-size: 15px;
|
||||
position: relative;
|
||||
|
||||
.fa {
|
||||
color: $ui-highlight-color;
|
||||
}
|
||||
|
||||
> span {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.account--panel {
|
||||
background: lighten($ui-base-color, 4%);
|
||||
border-top: 1px solid lighten($ui-base-color, 8%);
|
||||
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.account--panel__button,
|
||||
.detailed-status__button {
|
||||
flex: 1 1 auto;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.column-settings__outer {
|
||||
background: lighten($ui-base-color, 8%);
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.column-settings__section {
|
||||
color: $ui-primary-color;
|
||||
cursor: default;
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.column-settings__row {
|
||||
.text-btn {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.account--follows-info {
|
||||
color: $primary-text-color;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
opacity: 0.7;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
background-color: rgba($base-overlay-background, 0.4);
|
||||
text-transform: uppercase;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.account--action-button {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 20px;
|
||||
}
|
||||
|
||||
.account-gallery__container {
|
||||
margin: -2px;
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.account-gallery__item {
|
||||
flex: 1 1 auto;
|
||||
width: calc(100% / 3 - 4px);
|
||||
height: 95px;
|
||||
margin: 2px;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: $base-overlay-background;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
position: relative;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.account-section-headline {
|
||||
color: $ui-base-lighter-color;
|
||||
background: lighten($ui-base-color, 2%);
|
||||
border-bottom: 1px solid lighten($ui-base-color, 4%);
|
||||
padding: 15px 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
position: relative;
|
||||
cursor: default;
|
||||
|
||||
&::before,
|
||||
&::after {
|
||||
display: block;
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 18px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 0 10px 10px;
|
||||
border-color: transparent transparent lighten($ui-base-color, 4%);
|
||||
}
|
||||
|
||||
&::after {
|
||||
bottom: -1px;
|
||||
border-color: transparent transparent $ui-base-color;
|
||||
}
|
||||
}
|
||||
|
||||
.account__moved-note {
|
||||
padding: 14px 10px;
|
||||
padding-bottom: 16px;
|
||||
background: lighten($ui-base-color, 4%);
|
||||
border-top: 1px solid lighten($ui-base-color, 8%);
|
||||
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
||||
|
||||
&__message {
|
||||
position: relative;
|
||||
margin-left: 58px;
|
||||
color: $ui-base-lighter-color;
|
||||
padding: 8px 0;
|
||||
padding-top: 0;
|
||||
padding-bottom: 4px;
|
||||
font-size: 14px;
|
||||
|
||||
> span {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
&__icon-wrapper {
|
||||
left: -26px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.detailed-status__display-avatar {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.detailed-status__display-name {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
503
app/javascript/flavours/glitch/styles/components/columns.scss
Normal file
@@ -0,0 +1,503 @@
|
||||
.column__wrapper {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.column-icon {
|
||||
background: lighten($ui-base-color, 4%);
|
||||
color: $ui-primary-color;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
padding: 15px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -48px;
|
||||
z-index: 3;
|
||||
|
||||
&:hover {
|
||||
color: lighten($ui-primary-color, 7%);
|
||||
}
|
||||
}
|
||||
|
||||
.columns-area {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
overflow-x: auto;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@include limited-single-column('screen and (max-width: 360px)', $parent: null) {
|
||||
.columns-area {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.react-swipeable-view-container .columns-area {
|
||||
height: calc(100% - 20px) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.react-swipeable-view-container {
|
||||
&,
|
||||
.columns-area,
|
||||
.column {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.react-swipeable-view-container > * {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.column {
|
||||
width: 330px;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> .scrollable {
|
||||
background: $ui-base-color;
|
||||
}
|
||||
}
|
||||
|
||||
.ui {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: darken($ui-base-color, 7%);
|
||||
}
|
||||
|
||||
.column {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@include limited-single-column('screen and (max-width: 360px)', $parent: null) {
|
||||
.tabs-bar {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
:root { // Overrides .wide stylings for mobile view
|
||||
@include single-column('screen and (max-width: 630px)', $parent: null) {
|
||||
.column {
|
||||
flex: auto;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
max-width: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.columns-area {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.search__input,
|
||||
.autosuggest-textarea__textarea {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include multi-columns('screen and (min-width: 631px)', $parent: null) {
|
||||
.columns-area {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.column {
|
||||
flex: 0 0 auto;
|
||||
padding: 10px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
|
||||
&:first-child {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.columns-area > div {
|
||||
.column {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.column-back-button {
|
||||
background: lighten($ui-base-color, 4%);
|
||||
color: $ui-highlight-color;
|
||||
cursor: pointer;
|
||||
flex: 0 0 auto;
|
||||
font-size: 16px;
|
||||
border: 0;
|
||||
text-align: unset;
|
||||
padding: 15px;
|
||||
margin: 0;
|
||||
z-index: 3;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.column-header__back-button {
|
||||
background: lighten($ui-base-color, 4%);
|
||||
border: 0;
|
||||
font-family: inherit;
|
||||
color: $ui-highlight-color;
|
||||
cursor: pointer;
|
||||
flex: 0 0 auto;
|
||||
font-size: 16px;
|
||||
padding: 0 5px 0 0;
|
||||
z-index: 3;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding: 0 15px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.column-back-button__icon {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.column-back-button--slim {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.column-back-button--slim-button {
|
||||
cursor: pointer;
|
||||
flex: 0 0 auto;
|
||||
font-size: 16px;
|
||||
padding: 15px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: -48px;
|
||||
}
|
||||
|
||||
.column-link {
|
||||
background: lighten($ui-base-color, 8%);
|
||||
color: $primary-text-color;
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
padding: 15px;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
background: lighten($ui-base-color, 11%);
|
||||
}
|
||||
}
|
||||
|
||||
.column-link__icon {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.column-subheading {
|
||||
background: $ui-base-color;
|
||||
color: $ui-base-lighter-color;
|
||||
padding: 8px 20px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.column-header__wrapper {
|
||||
position: relative;
|
||||
flex: 0 0 auto;
|
||||
|
||||
&.active {
|
||||
&::before {
|
||||
display: block;
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 35px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
width: 60%;
|
||||
pointer-events: none;
|
||||
height: 28px;
|
||||
z-index: 1;
|
||||
background: radial-gradient(ellipse, rgba($ui-highlight-color, 0.23) 0%, rgba($ui-highlight-color, 0) 60%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.column-header {
|
||||
display: flex;
|
||||
padding: 15px;
|
||||
font-size: 16px;
|
||||
background: lighten($ui-base-color, 4%);
|
||||
flex: 0 0 auto;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
outline: 0;
|
||||
|
||||
&.active {
|
||||
box-shadow: 0 1px 0 rgba($ui-highlight-color, 0.3);
|
||||
|
||||
.column-header__icon {
|
||||
color: $ui-highlight-color;
|
||||
text-shadow: 0 0 10px rgba($ui-highlight-color, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.column {
|
||||
width: 330px;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
|
||||
.wide & {
|
||||
flex: auto;
|
||||
min-width: 330px;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
> .scrollable {
|
||||
background: $ui-base-color;
|
||||
}
|
||||
}
|
||||
|
||||
.column-header__buttons {
|
||||
height: 48px;
|
||||
display: flex;
|
||||
margin: -15px;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.column-header__links .text-btn {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.column-header__button {
|
||||
background: lighten($ui-base-color, 4%);
|
||||
border: 0;
|
||||
color: $ui-primary-color;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
padding: 0 15px;
|
||||
|
||||
&:hover {
|
||||
color: lighten($ui-primary-color, 7%);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $primary-text-color;
|
||||
background: lighten($ui-base-color, 8%);
|
||||
|
||||
&:hover {
|
||||
color: $primary-text-color;
|
||||
background: lighten($ui-base-color, 8%);
|
||||
}
|
||||
}
|
||||
|
||||
// glitch - added focus ring for keyboard navigation
|
||||
&:focus {
|
||||
text-shadow: 0 0 4px darken($ui-highlight-color, 5%);
|
||||
}
|
||||
}
|
||||
|
||||
.column-header__notif-cleaning-buttons {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
justify-content: space-around;
|
||||
|
||||
button {
|
||||
@extend .column-header__button;
|
||||
background: transparent;
|
||||
text-align: center;
|
||||
padding: 10px 0;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
b {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
// The notifs drawer with no padding to have more space for the buttons
|
||||
.column-header__collapsible-inner.nopad-drawer {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.column-header__collapsible {
|
||||
max-height: 70vh;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
color: $ui-primary-color;
|
||||
transition: max-height 150ms ease-in-out, opacity 300ms linear;
|
||||
opacity: 1;
|
||||
|
||||
&.collapsed {
|
||||
max-height: 0;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&.animating {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
hr {
|
||||
height: 0;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-top: 1px solid lighten($ui-base-color, 12%);
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
// notif cleaning drawer
|
||||
&.ncd {
|
||||
transition: none;
|
||||
&.collapsed {
|
||||
max-height: 0;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.column-header__collapsible-inner {
|
||||
background: lighten($ui-base-color, 8%);
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.column-header__setting-btn {
|
||||
&:hover {
|
||||
color: lighten($ui-primary-color, 4%);
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.column-header__setting-arrows {
|
||||
float: right;
|
||||
|
||||
.column-header__setting-btn {
|
||||
padding: 0 10px;
|
||||
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.column-header__title {
|
||||
display: inline-block;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.column-header__icon {
|
||||
display: inline-block;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.empty-column-indicator,
|
||||
.error-column {
|
||||
color: lighten($ui-base-color, 20%);
|
||||
background: $ui-base-color;
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
cursor: default;
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@supports(display: grid) { // hack to fix Chrome <57
|
||||
contain: strict;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $ui-highlight-color;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error-column {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
// more fixes for the navbar-under mode
|
||||
@mixin fix-margins-for-navbar-under {
|
||||
.tabs-bar {
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: -6px !important;
|
||||
}
|
||||
}
|
||||
|
||||
.single-column.navbar-under {
|
||||
@include fix-margins-for-navbar-under;
|
||||
}
|
||||
|
||||
.auto-columns.navbar-under {
|
||||
@media screen and (max-width: 360px) {
|
||||
@include fix-margins-for-navbar-under;
|
||||
}
|
||||
}
|
||||
|
||||
.auto-columns.navbar-under .react-swipeable-view-container .columns-area,
|
||||
.single-column.navbar-under .react-swipeable-view-container .columns-area {
|
||||
@media screen and (max-width: 360px) {
|
||||
height: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
.column-inline-form {
|
||||
padding: 7px 15px;
|
||||
padding-right: 5px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
background: lighten($ui-base-color, 4%);
|
||||
|
||||
label {
|
||||
flex: 1 1 auto;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
margin-bottom: 6px;
|
||||
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
flex: 0 0 auto;
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
.composer { padding: 10px }
|
||||
.composer {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.composer--spoiler {
|
||||
input {
|
||||
@@ -102,6 +104,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-picker-dropdown {
|
||||
position: absolute;
|
||||
right: 5px;
|
||||
top: 5px;
|
||||
|
||||
::-webkit-scrollbar-track:hover,
|
||||
::-webkit-scrollbar-track:active {
|
||||
background-color: rgba($base-overlay-background, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.composer--textarea {
|
||||
position: relative;
|
||||
|
||||
|
||||
@@ -40,7 +40,9 @@
|
||||
.react-swipeable-view-container & { height: 100% }
|
||||
|
||||
& > .contents {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -48,6 +50,30 @@
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
contain: strict;
|
||||
|
||||
& > .mastodon {
|
||||
flex: 1;
|
||||
border: none;
|
||||
cursor: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
@for $i from 0 through 3 {
|
||||
&.mbstobon-#{$i} > .contents {
|
||||
@if $i == 3 {
|
||||
background: url('~flavours/glitch/images/wave-drawer.png') no-repeat bottom / 100% auto, lighten($ui-base-color, 13%);
|
||||
} @else {
|
||||
background: url('~flavours/glitch/images/wave-drawer-glitched.png') no-repeat bottom / 100% auto, lighten($ui-base-color, 13%);
|
||||
}
|
||||
|
||||
& > .mastodon {
|
||||
background: url("~flavours/glitch/images/mbstobon-ui-#{$i}.png") no-repeat left bottom / contain;
|
||||
|
||||
@if $i != 3 {
|
||||
filter: contrast(50%) brightness(50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,3 +282,52 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.drawer__pager {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.drawer__inner {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: lighten($ui-base-color, 13%) url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="#{hex-color($ui-base-color)}"/></svg>') no-repeat bottom / 100% auto;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&.darker {
|
||||
background: $ui-base-color;
|
||||
}
|
||||
|
||||
> .mastodon {
|
||||
background: url('~images/elephant_ui_plane.svg') no-repeat left bottom / contain;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.pseudo-drawer {
|
||||
background: lighten($ui-base-color, 13%);
|
||||
font-size: 13px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.drawer__backdrop {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba($base-overlay-background, 0.5);
|
||||
}
|
||||
|
||||
105
app/javascript/flavours/glitch/styles/components/emoji.scss
Normal file
@@ -0,0 +1,105 @@
|
||||
.emojione {
|
||||
display: inline-block;
|
||||
font-size: inherit;
|
||||
vertical-align: middle;
|
||||
object-fit: contain;
|
||||
margin: -.2ex .15em .2ex;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
|
||||
img {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-picker-dropdown__menu {
|
||||
background: $simple-background-color;
|
||||
position: absolute;
|
||||
box-shadow: 4px 4px 6px rgba($base-shadow-color, 0.4);
|
||||
border-radius: 4px;
|
||||
margin-top: 5px;
|
||||
|
||||
.emoji-mart-scroll {
|
||||
transition: opacity 200ms ease;
|
||||
}
|
||||
|
||||
&.selecting .emoji-mart-scroll {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-picker-dropdown__modifiers {
|
||||
position: absolute;
|
||||
top: 60px;
|
||||
right: 11px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.emoji-picker-dropdown__modifiers__menu {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
top: -4px;
|
||||
left: -8px;
|
||||
background: $simple-background-color;
|
||||
border-radius: 4px;
|
||||
box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
|
||||
overflow: hidden;
|
||||
|
||||
button {
|
||||
display: block;
|
||||
cursor: pointer;
|
||||
border: 0;
|
||||
padding: 4px 8px;
|
||||
background: transparent;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
background: rgba($ui-secondary-color, 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-mart-emoji {
|
||||
height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-mart-emoji {
|
||||
span {
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-button {
|
||||
display: block;
|
||||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
margin-left: 2px;
|
||||
width: 24px;
|
||||
outline: 0;
|
||||
cursor: pointer;
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
img {
|
||||
filter: grayscale(100%);
|
||||
opacity: 0.8;
|
||||
display: block;
|
||||
margin: 0;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
img {
|
||||
opacity: 1;
|
||||
filter: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,6 +95,11 @@
|
||||
padding: 0 6px 6px;
|
||||
background: $simple-background-color;
|
||||
will-change: transform;
|
||||
|
||||
&::-webkit-scrollbar-track:hover,
|
||||
&::-webkit-scrollbar-track:active {
|
||||
background-color: rgba($base-overlay-background, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-mart-search {
|
||||
|
||||
103
app/javascript/flavours/glitch/styles/components/lists.scss
Normal file
@@ -0,0 +1,103 @@
|
||||
.attachment-list {
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
border: 1px solid lighten($ui-base-color, 8%);
|
||||
border-radius: 4px;
|
||||
margin-top: 14px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.attachment-list__icon {
|
||||
flex: 0 0 auto;
|
||||
color: $ui-base-lighter-color;
|
||||
padding: 8px 18px;
|
||||
cursor: default;
|
||||
border-right: 1px solid lighten($ui-base-color, 8%);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 26px;
|
||||
|
||||
.fa {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-list__list {
|
||||
list-style: none;
|
||||
padding: 4px 0;
|
||||
padding-left: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
li {
|
||||
display: block;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: $ui-base-lighter-color;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list-editor {
|
||||
background: $ui-base-color;
|
||||
flex-direction: column;
|
||||
border-radius: 8px;
|
||||
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
||||
width: 380px;
|
||||
overflow: hidden;
|
||||
|
||||
@media screen and (max-width: 420px) {
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
h4 {
|
||||
padding: 15px 0;
|
||||
background: lighten($ui-base-color, 13%);
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
border-radius: 8px 8px 0 0;
|
||||
}
|
||||
|
||||
.drawer__pager {
|
||||
height: 50vh;
|
||||
}
|
||||
|
||||
.drawer__inner {
|
||||
border-radius: 0 0 8px 8px;
|
||||
|
||||
&.backdrop {
|
||||
width: calc(100% - 60px);
|
||||
box-shadow: 2px 4px 15px rgba($base-shadow-color, 0.4);
|
||||
border-radius: 0 0 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&__accounts {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.account__display-name {
|
||||
&:hover strong {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.account__avatar {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.search {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
485
app/javascript/flavours/glitch/styles/components/media.scss
Normal file
@@ -0,0 +1,485 @@
|
||||
.video-error-cover {
|
||||
align-items: center;
|
||||
background: $base-overlay-background;
|
||||
color: $primary-text-color;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
margin-top: 8px;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.media-spoiler {
|
||||
background: $base-overlay-background;
|
||||
color: $ui-primary-color;
|
||||
border: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color: lighten($ui-primary-color, 8%);
|
||||
}
|
||||
|
||||
.status__content > & {
|
||||
margin-top: 15px; // Add margin when used bare for NSFW video player
|
||||
}
|
||||
@include fullwidth-gallery;
|
||||
}
|
||||
|
||||
.media-spoiler__warning {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.media-spoiler__trigger {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.media-gallery__gifv__label {
|
||||
display: block;
|
||||
position: absolute;
|
||||
color: $primary-text-color;
|
||||
background: rgba($base-overlay-background, 0.5);
|
||||
bottom: 6px;
|
||||
left: 6px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 2px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
opacity: 0.9;
|
||||
transition: opacity 0.1s ease;
|
||||
}
|
||||
|
||||
.media-gallery__gifv {
|
||||
&.autoplay {
|
||||
.media-gallery__gifv__label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.media-gallery__gifv__label {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-gallery {
|
||||
box-sizing: border-box;
|
||||
margin-top: 8px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: $base-shadow-color;
|
||||
width: 100%;
|
||||
height: 110px;
|
||||
|
||||
.detailed-status & {
|
||||
margin-left: -22px;
|
||||
width: calc(100% + 44px);
|
||||
height: 250px;
|
||||
}
|
||||
|
||||
@include fullwidth-gallery;
|
||||
}
|
||||
|
||||
.media-gallery__item {
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
float: left;
|
||||
position: relative;
|
||||
|
||||
&.standalone {
|
||||
.media-gallery__item-gifv-thumbnail {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-gallery__item-thumbnail {
|
||||
cursor: zoom-in;
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
height: 100%;
|
||||
line-height: 0;
|
||||
|
||||
&,
|
||||
img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: contain;
|
||||
|
||||
&:not(.letterbox) {
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-gallery__gifv {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.media-gallery__item-gifv-thumbnail {
|
||||
cursor: zoom-in;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
object-fit: contain;
|
||||
|
||||
&:not(.letterbox) {
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.media-gallery__item-thumbnail-label {
|
||||
clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.media-modal {
|
||||
max-width: 80vw;
|
||||
max-height: 80vh;
|
||||
position: relative;
|
||||
|
||||
.extended-video-player,
|
||||
img,
|
||||
canvas,
|
||||
video {
|
||||
max-width: 80vw;
|
||||
max-height: 80vh;
|
||||
width: auto;
|
||||
height: auto;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.extended-video-player,
|
||||
video {
|
||||
display: flex;
|
||||
width: 80vw;
|
||||
height: 80vh;
|
||||
}
|
||||
|
||||
img,
|
||||
canvas {
|
||||
display: block;
|
||||
background: url('~images/void.png') repeat;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.react-swipeable-view-container {
|
||||
max-width: 80vw;
|
||||
}
|
||||
}
|
||||
|
||||
.media-modal__content {
|
||||
background: $base-overlay-background;
|
||||
}
|
||||
|
||||
.media-modal__pagination {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -40px;
|
||||
}
|
||||
|
||||
.media-modal__page-dot {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.media-modal__button {
|
||||
background-color: $white;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
border-radius: 6px;
|
||||
margin: 10px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.media-modal__button--active {
|
||||
background-color: $ui-highlight-color;
|
||||
}
|
||||
|
||||
.media-modal__close {
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 4px;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.video-player {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background: $base-shadow-color;
|
||||
max-width: 100%;
|
||||
|
||||
.detailed-status & {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@include fullwidth-gallery;
|
||||
|
||||
video {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
object-fit: cover;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&.fullscreen {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
margin: 0;
|
||||
|
||||
video {
|
||||
max-width: 100% !important;
|
||||
max-height: 100% !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.inline {
|
||||
video {
|
||||
object-fit: cover;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
&__controls {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
box-sizing: border-box;
|
||||
background: linear-gradient(0deg, rgba($base-shadow-color, 0.85) 0, rgba($base-shadow-color, 0.45) 60%, transparent);
|
||||
padding: 0 15px;
|
||||
opacity: 0;
|
||||
transition: opacity .1s ease;
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&.inactive {
|
||||
video,
|
||||
.video-player__controls {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
&__spoiler {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 4;
|
||||
border: 0;
|
||||
background: $base-shadow-color;
|
||||
color: $ui-primary-color;
|
||||
transition: none;
|
||||
pointer-events: none;
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
pointer-events: auto;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color: lighten($ui-primary-color, 8%);
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&__subtitle {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
&__buttons-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
&__buttons {
|
||||
font-size: 16px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&.left {
|
||||
button {
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.right {
|
||||
button {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
background: transparent;
|
||||
padding: 2px 10px;
|
||||
font-size: 16px;
|
||||
border: 0;
|
||||
color: rgba($white, 0.75);
|
||||
|
||||
&:active,
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__time-sep,
|
||||
&__time-total,
|
||||
&__time-current {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&__time-current {
|
||||
color: $white;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
&__time-sep {
|
||||
display: inline-block;
|
||||
margin: 0 6px;
|
||||
}
|
||||
|
||||
&__time-sep,
|
||||
&__time-total {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
&__seek {
|
||||
cursor: pointer;
|
||||
height: 24px;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
width: 100%;
|
||||
background: rgba($white, 0.35);
|
||||
border-radius: 4px;
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 4px;
|
||||
top: 10px;
|
||||
}
|
||||
|
||||
&__progress,
|
||||
&__buffer {
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 4px;
|
||||
border-radius: 4px;
|
||||
top: 10px;
|
||||
background: lighten($ui-highlight-color, 8%);
|
||||
}
|
||||
|
||||
&__buffer {
|
||||
background: rgba($white, 0.2);
|
||||
}
|
||||
|
||||
&__handle {
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
opacity: 0;
|
||||
border-radius: 50%;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
top: 6px;
|
||||
margin-left: -6px;
|
||||
transition: opacity .1s ease;
|
||||
background: lighten($ui-highlight-color, 8%);
|
||||
box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.2);
|
||||
pointer-events: none;
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.video-player__seek__handle {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.detailed,
|
||||
&.fullscreen {
|
||||
.video-player__buttons {
|
||||
button {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-spoiler-video {
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
cursor: pointer;
|
||||
margin-top: 8px;
|
||||
position: relative;
|
||||
|
||||
@include fullwidth-gallery;
|
||||
|
||||
border: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.media-spoiler-video-play-icon {
|
||||
border-radius: 100px;
|
||||
color: rgba($primary-text-color, 0.8);
|
||||
font-size: 36px;
|
||||
left: 50%;
|
||||
padding: 5px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
.account__metadata {
|
||||
width: 100%;
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
overflow: hidden;
|
||||
border-collapse: collapse;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
|
||||
&:hover{
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
tr {
|
||||
border-top: 1px solid lighten($ui-base-color, 8%);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 14px 20px;
|
||||
vertical-align: middle;
|
||||
|
||||
& > div {
|
||||
max-height: 40px;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
color: $ui-primary-color;
|
||||
background: lighten($ui-base-color, 13%);
|
||||
max-width: 120px;
|
||||
|
||||
a {
|
||||
color: $primary-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
flex: auto;
|
||||
color: $primary-text-color;
|
||||
background: $ui-base-color;
|
||||
|
||||
a {
|
||||
color: $ui-highlight-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
699
app/javascript/flavours/glitch/styles/components/modal.scss
Normal file
@@ -0,0 +1,699 @@
|
||||
.modal-container--preloader {
|
||||
background: lighten($ui-base-color, 8%);
|
||||
}
|
||||
|
||||
.modal-container__nav {
|
||||
align-items: center;
|
||||
background: rgba($base-overlay-background, 0.5);
|
||||
box-sizing: border-box;
|
||||
border: 0;
|
||||
color: $primary-text-color;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: 24px;
|
||||
height: 100%;
|
||||
padding: 30px 15px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.modal-container__nav--left {
|
||||
left: -61px;
|
||||
}
|
||||
|
||||
.modal-container__nav--right {
|
||||
right: -61px;
|
||||
}
|
||||
|
||||
.modal-root {
|
||||
transition: opacity 0.3s linear;
|
||||
will-change: opacity;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.modal-root__overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba($base-overlay-background, 0.7);
|
||||
}
|
||||
|
||||
.modal-root__container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
align-content: space-around;
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.modal-root__modal {
|
||||
pointer-events: auto;
|
||||
display: flex;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.onboarding-modal,
|
||||
.error-modal,
|
||||
.embed-modal {
|
||||
background: $ui-secondary-color;
|
||||
color: $ui-base-color;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.onboarding-modal__pager {
|
||||
height: 80vh;
|
||||
width: 80vw;
|
||||
max-width: 520px;
|
||||
max-height: 470px;
|
||||
|
||||
.react-swipeable-view-container > div {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
user-select: text;
|
||||
}
|
||||
}
|
||||
|
||||
.error-modal__body {
|
||||
height: 80vh;
|
||||
width: 80vw;
|
||||
max-width: 520px;
|
||||
max-height: 420px;
|
||||
position: relative;
|
||||
|
||||
& > div {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 25px;
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
display: flex;
|
||||
opacity: 0;
|
||||
user-select: text;
|
||||
}
|
||||
}
|
||||
|
||||
.error-modal__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 550px) {
|
||||
.onboarding-modal {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.onboarding-modal__pager {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-width: none;
|
||||
max-height: none;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.onboarding-modal__paginator,
|
||||
.error-modal__footer {
|
||||
flex: 0 0 auto;
|
||||
background: darken($ui-secondary-color, 8%);
|
||||
display: flex;
|
||||
padding: 25px;
|
||||
|
||||
& > div {
|
||||
min-width: 33px;
|
||||
}
|
||||
|
||||
.onboarding-modal__nav,
|
||||
.error-modal__nav {
|
||||
color: darken($ui-secondary-color, 34%);
|
||||
border: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
padding: 10px 25px;
|
||||
line-height: inherit;
|
||||
height: auto;
|
||||
margin: -10px;
|
||||
border-radius: 4px;
|
||||
background-color: transparent;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
color: darken($ui-secondary-color, 38%);
|
||||
background-color: darken($ui-secondary-color, 16%);
|
||||
}
|
||||
|
||||
&.onboarding-modal__done,
|
||||
&.onboarding-modal__next {
|
||||
color: $ui-base-color;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
color: darken($ui-base-color, 4%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error-modal__footer {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.onboarding-modal__dots {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.onboarding-modal__dot {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 14px;
|
||||
background: darken($ui-secondary-color, 16%);
|
||||
margin: 0 3px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: darken($ui-secondary-color, 18%);
|
||||
}
|
||||
|
||||
&.active {
|
||||
cursor: default;
|
||||
background: darken($ui-secondary-color, 24%);
|
||||
}
|
||||
}
|
||||
|
||||
.onboarding-modal__page__wrapper {
|
||||
pointer-events: none;
|
||||
padding: 25px;
|
||||
padding-bottom: 0;
|
||||
|
||||
&.onboarding-modal__page__wrapper--active {
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.onboarding-modal__page {
|
||||
cursor: default;
|
||||
line-height: 21px;
|
||||
|
||||
h1 {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: $ui-base-color;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $ui-highlight-color;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
color: lighten($ui-highlight-color, 4%);
|
||||
}
|
||||
}
|
||||
|
||||
.navigation-bar a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
color: lighten($ui-base-color, 8%);
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 500;
|
||||
background: $ui-base-color;
|
||||
color: $ui-secondary-color;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
padding: 3px 6px;
|
||||
|
||||
@each $lang in $cjk-langs {
|
||||
&:lang(#{$lang}) {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.onboarding-modal__page__wrapper-0 {
|
||||
background: url('~images/elephant_ui_greeting.svg') no-repeat left bottom / auto 250px;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.onboarding-modal__page-one {
|
||||
&__lead {
|
||||
padding: 65px;
|
||||
padding-top: 45px;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 10px;
|
||||
|
||||
h1 {
|
||||
font-size: 26px;
|
||||
line-height: 36px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__extra {
|
||||
padding-right: 65px;
|
||||
padding-left: 185px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.display-case {
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
margin-bottom: 15px;
|
||||
|
||||
&__label {
|
||||
font-weight: 500;
|
||||
color: $ui-base-color;
|
||||
margin-bottom: 5px;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&__case {
|
||||
background: $ui-base-color;
|
||||
color: $ui-secondary-color;
|
||||
font-weight: 500;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.onboarding-modal__page-two,
|
||||
.onboarding-modal__page-three,
|
||||
.onboarding-modal__page-four,
|
||||
.onboarding-modal__page-five {
|
||||
p {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.figure {
|
||||
background: darken($ui-base-color, 8%);
|
||||
color: $ui-secondary-color;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 4px;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
box-shadow: 1px 2px 6px rgba($base-shadow-color, 0.3);
|
||||
|
||||
.onboarding-modal__image {
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
&.non-interactive {
|
||||
pointer-events: none;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.onboarding-modal__page-four__columns {
|
||||
.row {
|
||||
display: flex;
|
||||
margin-bottom: 20px;
|
||||
|
||||
& > div {
|
||||
flex: 1 1 0;
|
||||
margin: 0 10px;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.column-header {
|
||||
color: $primary-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 320px) and (max-height: 600px) {
|
||||
.onboarding-modal__page p {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.onboarding-modal__page-two .figure,
|
||||
.onboarding-modal__page-three .figure,
|
||||
.onboarding-modal__page-four .figure,
|
||||
.onboarding-modal__page-five .figure {
|
||||
font-size: 12px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.onboarding-modal__page-four__columns .row {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.onboarding-modal__page-four__columns .column-header {
|
||||
padding: 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.onboard-sliders {
|
||||
display: inline-block;
|
||||
max-width: 30px;
|
||||
max-height: auto;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.boost-modal,
|
||||
.favourite-modal,
|
||||
.confirmation-modal,
|
||||
.report-modal,
|
||||
.actions-modal,
|
||||
.mute-modal {
|
||||
background: lighten($ui-secondary-color, 8%);
|
||||
color: $ui-base-color;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
max-width: 90vw;
|
||||
width: 480px;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
|
||||
.status__display-name {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.status__avatar {
|
||||
height: 28px;
|
||||
left: 10px;
|
||||
top: 10px;
|
||||
width: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.actions-modal {
|
||||
.status {
|
||||
background: $white;
|
||||
border-bottom-color: $ui-secondary-color;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.dropdown-menu__separator {
|
||||
border-bottom-color: $ui-secondary-color;
|
||||
}
|
||||
}
|
||||
|
||||
.boost-modal__container,
|
||||
.favourite-modal__container {
|
||||
overflow-x: scroll;
|
||||
padding: 10px;
|
||||
|
||||
.status {
|
||||
user-select: text;
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.boost-modal__action-bar,
|
||||
.favourite-modal__action-bar,
|
||||
.confirmation-modal__action-bar,
|
||||
.mute-modal__action-bar,
|
||||
.report-modal__action-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background: $ui-secondary-color;
|
||||
padding: 10px;
|
||||
line-height: 36px;
|
||||
|
||||
& > div {
|
||||
flex: 1 1 auto;
|
||||
text-align: right;
|
||||
color: lighten($ui-base-color, 33%);
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.button {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.boost-modal__status-header,
|
||||
.favourite-modal__status-header {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.boost-modal__status-time,
|
||||
.favourite-modal__status-time {
|
||||
float: right;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.confirmation-modal {
|
||||
max-width: 85vw;
|
||||
|
||||
@media screen and (min-width: 480px) {
|
||||
max-width: 380px;
|
||||
}
|
||||
}
|
||||
|
||||
.mute-modal {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.mute-modal .react-toggle {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.report-modal__statuses,
|
||||
.report-modal__comment {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.report-modal__statuses {
|
||||
min-height: 20vh;
|
||||
max-height: 40vh;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.report-modal__comment {
|
||||
.setting-text {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.actions-modal {
|
||||
.status {
|
||||
overflow-y: auto;
|
||||
max-height: 300px;
|
||||
}
|
||||
|
||||
strong {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
max-height: 80vh;
|
||||
max-width: 80vw;
|
||||
|
||||
.actions-modal__item-label {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
ul {
|
||||
overflow-y: auto;
|
||||
flex-shrink: 0;
|
||||
|
||||
li:empty {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
li:not(:empty) {
|
||||
a {
|
||||
color: $ui-base-color;
|
||||
display: flex;
|
||||
padding: 12px 16px;
|
||||
font-size: 15px;
|
||||
align-items: center;
|
||||
text-decoration: none;
|
||||
|
||||
&,
|
||||
button {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
&.active,
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
&,
|
||||
button {
|
||||
background: $ui-highlight-color;
|
||||
color: $primary-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
& > .react-toggle,
|
||||
& > .icon,
|
||||
button:first-child {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.confirmation-modal__action-bar,
|
||||
.mute-modal__action-bar {
|
||||
.confirmation-modal__cancel-button,
|
||||
.mute-modal__cancel-button {
|
||||
background-color: transparent;
|
||||
color: darken($ui-secondary-color, 34%);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
color: darken($ui-secondary-color, 38%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.confirmation-modal__container,
|
||||
.mute-modal__container,
|
||||
.report-modal__target {
|
||||
padding: 30px;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
|
||||
strong {
|
||||
font-weight: 500;
|
||||
|
||||
@each $lang in $cjk-langs {
|
||||
&:lang(#{$lang}) {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.embed-modal {
|
||||
max-width: 80vw;
|
||||
max-height: 80vh;
|
||||
|
||||
h4 {
|
||||
padding: 30px;
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.embed-modal__container {
|
||||
padding: 10px;
|
||||
|
||||
.hint {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.embed-modal__html {
|
||||
color: $ui-secondary-color;
|
||||
outline: 0;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: none;
|
||||
padding: 10px;
|
||||
font-family: 'mastodon-font-monospace', monospace;
|
||||
background: $ui-base-color;
|
||||
color: $ui-primary-color;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
margin-bottom: 15px;
|
||||
|
||||
&::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&::-moz-focus-inner,
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background: lighten($ui-base-color, 4%);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.embed-modal__iframe {
|
||||
width: 400px;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
.regeneration-indicator {
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: lighten($ui-base-color, 16%);
|
||||
background: $ui-base-color;
|
||||
cursor: default;
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
|
||||
& > div {
|
||||
width: 100%;
|
||||
background: transparent;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
&__figure {
|
||||
background: url('~flavours/glitch/images/elephant_ui_working.svg') no-repeat center 0;
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
background-size: contain;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
&.missing-indicator {
|
||||
padding-top: 20px + 48px;
|
||||
|
||||
.regeneration-indicator__figure {
|
||||
background-image: url('~flavours/glitch/images/elephant_ui_disappointed.svg');
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
margin-top: 200px;
|
||||
|
||||
strong {
|
||||
display: block;
|
||||
margin-bottom: 10px;
|
||||
color: lighten($ui-base-color, 34%);
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 15px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
105
app/javascript/flavours/glitch/styles/components/search.scss
Normal file
@@ -0,0 +1,105 @@
|
||||
.search {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search__input {
|
||||
outline: 0;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: none;
|
||||
padding: 10px;
|
||||
padding-right: 30px;
|
||||
font-family: inherit;
|
||||
background: $ui-base-color;
|
||||
color: $ui-primary-color;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
|
||||
&::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&::-moz-focus-inner,
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background: lighten($ui-base-color, 4%);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.search__icon {
|
||||
.fa {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
z-index: 2;
|
||||
display: inline-block;
|
||||
opacity: 0;
|
||||
transition: all 100ms linear;
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: $ui-secondary-color;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
|
||||
&.active {
|
||||
pointer-events: auto;
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
.fa-search {
|
||||
transform: rotate(90deg);
|
||||
|
||||
&.active {
|
||||
pointer-events: none;
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
.fa-times-circle {
|
||||
top: 11px;
|
||||
transform: rotate(0deg);
|
||||
cursor: pointer;
|
||||
|
||||
&.active {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $primary-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-results__header {
|
||||
color: $ui-base-lighter-color;
|
||||
background: lighten($ui-base-color, 2%);
|
||||
border-bottom: 1px solid darken($ui-base-color, 4%);
|
||||
padding: 15px 10px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.search-results__hashtag {
|
||||
display: block;
|
||||
padding: 10px;
|
||||
color: $ui-secondary-color;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
color: lighten($ui-secondary-color, 4%);
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||