mirror of
https://github.com/glitch-soc/mastodon.git
synced 2025-12-14 16:28:59 +00:00
Compare commits
2 Commits
status-sty
...
images-in-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6839ee390f | ||
|
|
54c1f56c9a |
4
.babelrc
4
.babelrc
@@ -22,8 +22,7 @@
|
|||||||
{
|
{
|
||||||
"messagesDir": "./build/messages"
|
"messagesDir": "./build/messages"
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
"preval"
|
|
||||||
],
|
],
|
||||||
"env": {
|
"env": {
|
||||||
"development": {
|
"development": {
|
||||||
@@ -45,7 +44,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"transform-react-inline-elements",
|
|
||||||
[
|
[
|
||||||
"transform-runtime",
|
"transform-runtime",
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ public/system
|
|||||||
public/assets
|
public/assets
|
||||||
public/packs
|
public/packs
|
||||||
node_modules
|
node_modules
|
||||||
|
storybook
|
||||||
neo4j
|
neo4j
|
||||||
vendor/bundle
|
vendor/bundle
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ SMTP_FROM_ADDRESS=notifications@${APP_NAME}.nanoapp.io
|
|||||||
# PAPERCLIP_ROOT_URL=/system
|
# PAPERCLIP_ROOT_URL=/system
|
||||||
|
|
||||||
# Optional asset host for multi-server setups
|
# Optional asset host for multi-server setups
|
||||||
# CDN_HOST=https://assets.example.com
|
# CDN_HOST=assets.example.com
|
||||||
|
|
||||||
# S3 (optional)
|
# S3 (optional)
|
||||||
# S3_ENABLED=true
|
# S3_ENABLED=true
|
||||||
|
|||||||
@@ -31,17 +31,6 @@ PAPERCLIP_SECRET=
|
|||||||
SECRET_KEY_BASE=
|
SECRET_KEY_BASE=
|
||||||
OTP_SECRET=
|
OTP_SECRET=
|
||||||
|
|
||||||
# VAPID keys (used for push notifications
|
|
||||||
# You can generate the keys using the following command (first is the private key, second is the public one)
|
|
||||||
# You should only generate this once per instance. If you later decide to change it, all push subscription will
|
|
||||||
# be invalidated, requiring the users to access the website again to resubscribe.
|
|
||||||
#
|
|
||||||
# Generate with `rake mastodon:webpush:generate_vapid_key` task (`docker-compose run --rm web rake mastodon:webpush:generate_vapid_key` if you use docker compose)
|
|
||||||
#
|
|
||||||
# For more information visit https://rossta.net/blog/using-the-web-push-api-with-vapid.html
|
|
||||||
VAPID_PRIVATE_KEY=
|
|
||||||
VAPID_PUBLIC_KEY=
|
|
||||||
|
|
||||||
# Registrations
|
# Registrations
|
||||||
# Single user mode will disable registrations and redirect frontpage to the first profile
|
# Single user mode will disable registrations and redirect frontpage to the first profile
|
||||||
# SINGLE_USER_MODE=true
|
# SINGLE_USER_MODE=true
|
||||||
@@ -69,7 +58,7 @@ SMTP_FROM_ADDRESS=notifications@example.com
|
|||||||
#SMTP_CA_FILE=/etc/ssl/certs/ca-certificates.crt
|
#SMTP_CA_FILE=/etc/ssl/certs/ca-certificates.crt
|
||||||
#SMTP_OPENSSL_VERIFY_MODE=peer
|
#SMTP_OPENSSL_VERIFY_MODE=peer
|
||||||
#SMTP_ENABLE_STARTTLS_AUTO=true
|
#SMTP_ENABLE_STARTTLS_AUTO=true
|
||||||
#SMTP_TLS=true
|
|
||||||
|
|
||||||
# Optional user upload path and URL (images, avatars). Default is :rails_root/public/system. If you set this variable, you are responsible for making your HTTP server (eg. nginx) serve these files.
|
# Optional user upload path and URL (images, avatars). Default is :rails_root/public/system. If you set this variable, you are responsible for making your HTTP server (eg. nginx) serve these files.
|
||||||
# PAPERCLIP_ROOT_PATH=/var/lib/mastodon/public-system
|
# PAPERCLIP_ROOT_PATH=/var/lib/mastodon/public-system
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ rules:
|
|||||||
jsx-a11y/iframe-has-title: warn
|
jsx-a11y/iframe-has-title: warn
|
||||||
jsx-a11y/img-has-alt: warn
|
jsx-a11y/img-has-alt: warn
|
||||||
jsx-a11y/img-redundant-alt: warn
|
jsx-a11y/img-redundant-alt: warn
|
||||||
jsx-a11y/label-has-for: off
|
jsx-a11y/label-has-for: warn
|
||||||
jsx-a11y/mouse-events-have-key-events: warn
|
jsx-a11y/mouse-events-have-key-events: warn
|
||||||
jsx-a11y/no-access-key: warn
|
jsx-a11y/no-access-key: warn
|
||||||
jsx-a11y/no-distracting-elements: warn
|
jsx-a11y/no-distracting-elements: warn
|
||||||
@@ -121,6 +121,6 @@ rules:
|
|||||||
jsx-a11y/onclick-has-focus: warn
|
jsx-a11y/onclick-has-focus: warn
|
||||||
jsx-a11y/onclick-has-role: warn
|
jsx-a11y/onclick-has-role: warn
|
||||||
jsx-a11y/role-has-required-aria-props: warn
|
jsx-a11y/role-has-required-aria-props: warn
|
||||||
jsx-a11y/role-supports-aria-props: off
|
jsx-a11y/role-supports-aria-props: warn
|
||||||
jsx-a11y/scope: warn
|
jsx-a11y/scope: warn
|
||||||
jsx-a11y/tabindex-no-positive: warn
|
jsx-a11y/tabindex-no-positive: warn
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ node_modules/
|
|||||||
public/assets/
|
public/assets/
|
||||||
public/system/
|
public/system/
|
||||||
spec/
|
spec/
|
||||||
|
storybook/
|
||||||
tmp/
|
tmp/
|
||||||
.vagrant/
|
.vagrant/
|
||||||
vendor/bundle/
|
vendor/bundle/
|
||||||
|
|||||||
@@ -6,4 +6,3 @@ plugins:
|
|||||||
- last 2 versions
|
- last 2 versions
|
||||||
- IE >= 11
|
- IE >= 11
|
||||||
- iOS >= 9
|
- iOS >= 9
|
||||||
postcss-object-fit-images: {}
|
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ Metrics/AbcSize:
|
|||||||
Max: 100
|
Max: 100
|
||||||
|
|
||||||
Metrics/BlockLength:
|
Metrics/BlockLength:
|
||||||
Max: 35
|
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'lib/tasks/**/*'
|
- 'lib/tasks/**/*'
|
||||||
|
|
||||||
@@ -36,10 +35,10 @@ Metrics/BlockNesting:
|
|||||||
|
|
||||||
Metrics/ClassLength:
|
Metrics/ClassLength:
|
||||||
CountComments: false
|
CountComments: false
|
||||||
Max: 300
|
Max: 200
|
||||||
|
|
||||||
Metrics/CyclomaticComplexity:
|
Metrics/CyclomaticComplexity:
|
||||||
Max: 25
|
Max: 15
|
||||||
|
|
||||||
Metrics/LineLength:
|
Metrics/LineLength:
|
||||||
AllowURI: true
|
AllowURI: true
|
||||||
@@ -54,11 +53,11 @@ Metrics/ModuleLength:
|
|||||||
Max: 200
|
Max: 200
|
||||||
|
|
||||||
Metrics/ParameterLists:
|
Metrics/ParameterLists:
|
||||||
Max: 5
|
Max: 4
|
||||||
CountKeywordArgs: true
|
CountKeywordArgs: true
|
||||||
|
|
||||||
Metrics/PerceivedComplexity:
|
Metrics/PerceivedComplexity:
|
||||||
Max: 20
|
Max: 10
|
||||||
|
|
||||||
Rails:
|
Rails:
|
||||||
Enabled: true
|
Enabled: true
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ node_modules/
|
|||||||
.cache/
|
.cache/
|
||||||
docs/
|
docs/
|
||||||
spec/
|
spec/
|
||||||
|
storybook/
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ cache:
|
|||||||
- node_modules
|
- node_modules
|
||||||
- public/assets
|
- public/assets
|
||||||
- public/packs-test
|
- public/packs-test
|
||||||
- tmp/cache/babel-loader
|
|
||||||
dist: trusty
|
dist: trusty
|
||||||
sudo: required
|
sudo: required
|
||||||
|
|
||||||
@@ -33,7 +32,6 @@ addons:
|
|||||||
- g++-6
|
- g++-6
|
||||||
- libprotobuf-dev
|
- libprotobuf-dev
|
||||||
- protobuf-compiler
|
- protobuf-compiler
|
||||||
- libicu-dev
|
|
||||||
|
|
||||||
rvm:
|
rvm:
|
||||||
- 2.3.4
|
- 2.3.4
|
||||||
|
|||||||
8
Aptfile
8
Aptfile
@@ -1,9 +1,5 @@
|
|||||||
ffmpeg
|
protobuf-compiler
|
||||||
libicu-dev
|
|
||||||
libidn11
|
|
||||||
libidn11-dev
|
|
||||||
libpq-dev
|
|
||||||
libprotobuf-dev
|
libprotobuf-dev
|
||||||
|
ffmpeg
|
||||||
libxdamage1
|
libxdamage1
|
||||||
libxfixes3
|
libxfixes3
|
||||||
protobuf-compiler
|
|
||||||
|
|||||||
@@ -1,36 +1,3 @@
|
|||||||
# Contributing to Mastodon Glitch Edition #
|
|
||||||
|
|
||||||
Thank you for your interest in contributing to the `glitch-soc` project!
|
|
||||||
Here are some guidelines, and ways you can help.
|
|
||||||
|
|
||||||
> (This document is a bit of a work-in-progress, so please bear with us.
|
|
||||||
> If you don't see what you're looking for here, please don't hesitate to reach out!)
|
|
||||||
|
|
||||||
## Planning ##
|
|
||||||
|
|
||||||
Right now a lot of the planning for this project takes place in our development Discord, or through GitHub Issues and Projects.
|
|
||||||
We're working on ways to improve the planning structure and better solicit feedback, and if you feel like you can help in this respect, feel free to give us a holler.
|
|
||||||
|
|
||||||
## Documentation ##
|
|
||||||
|
|
||||||
The documentation for this repository is available at [`glitch-soc/docs`](https://github.com/glitch-soc/docs) (online at [glitch-soc.github.io/docs/](https://glitch-soc.github.io/docs/)).
|
|
||||||
Right now, we've mostly focused on the features that make this fork different from upstream in some manner.
|
|
||||||
Adding screenshots, improving descriptions, and so forth are all ways to help contribute to the project even if you don't know any code.
|
|
||||||
|
|
||||||
## Frontend Development ##
|
|
||||||
|
|
||||||
Check out [the documentation here](https://glitch-soc.github.io/docs/contributing/frontend/) for more information.
|
|
||||||
|
|
||||||
## Backend Development ##
|
|
||||||
|
|
||||||
See the guidelines below.
|
|
||||||
|
|
||||||
- - -
|
|
||||||
|
|
||||||
You should also try to follow the guidelines set out in the original `CONTRIBUTING.md` from `tootsuite/mastodon`, reproduced below.
|
|
||||||
|
|
||||||
<blockquote>
|
|
||||||
|
|
||||||
CONTRIBUTING
|
CONTRIBUTING
|
||||||
============
|
============
|
||||||
|
|
||||||
@@ -82,5 +49,3 @@ It is expected that you have a working development environment set up (see back-
|
|||||||
* If you are introducing new strings, they must be using localization methods
|
* If you are introducing new strings, they must be using localization methods
|
||||||
|
|
||||||
If the JavaScript or CSS assets won't compile due to a syntax error, it's a good sign that the pull request isn't ready for submission yet.
|
If the JavaScript or CSS assets won't compile due to a syntax error, it's a good sign that the pull request isn't ready for submission yet.
|
||||||
|
|
||||||
</blockquote>
|
|
||||||
|
|||||||
29
Dockerfile
29
Dockerfile
@@ -7,21 +7,16 @@ ENV UID=991 GID=991 \
|
|||||||
RAILS_SERVE_STATIC_FILES=true \
|
RAILS_SERVE_STATIC_FILES=true \
|
||||||
RAILS_ENV=production NODE_ENV=production
|
RAILS_ENV=production NODE_ENV=production
|
||||||
|
|
||||||
ARG LIBICONV_VERSION=1.15
|
|
||||||
ARG LIBICONV_DOWNLOAD_SHA256=ccf536620a45458d26ba83887a983b96827001e92a13847b45e4925cc8913178
|
|
||||||
|
|
||||||
EXPOSE 3000 4000
|
EXPOSE 3000 4000
|
||||||
|
|
||||||
WORKDIR /mastodon
|
WORKDIR /mastodon
|
||||||
|
|
||||||
RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories \
|
RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories \
|
||||||
&& echo "@edge https://nl.alpinelinux.org/alpine/edge/community" >> /etc/apk/repositories \
|
|
||||||
&& apk -U upgrade \
|
&& apk -U upgrade \
|
||||||
&& apk add -t build-dependencies \
|
&& apk add -t build-dependencies \
|
||||||
build-base \
|
build-base \
|
||||||
icu-dev \
|
libxml2-dev \
|
||||||
libidn-dev \
|
libxslt-dev \
|
||||||
libtool \
|
|
||||||
postgresql-dev \
|
postgresql-dev \
|
||||||
protobuf-dev \
|
protobuf-dev \
|
||||||
python \
|
python \
|
||||||
@@ -30,34 +25,22 @@ RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/reposit
|
|||||||
ffmpeg \
|
ffmpeg \
|
||||||
file \
|
file \
|
||||||
git \
|
git \
|
||||||
icu-libs \
|
|
||||||
imagemagick@edge \
|
imagemagick@edge \
|
||||||
libidn \
|
|
||||||
libpq \
|
libpq \
|
||||||
|
libxml2 \
|
||||||
|
libxslt \
|
||||||
nodejs-npm@edge \
|
nodejs-npm@edge \
|
||||||
nodejs@edge \
|
nodejs@edge \
|
||||||
protobuf \
|
protobuf \
|
||||||
su-exec \
|
su-exec \
|
||||||
tini \
|
tini \
|
||||||
yarn@edge \
|
&& npm install -g npm@3 && npm install -g yarn \
|
||||||
&& update-ca-certificates \
|
&& update-ca-certificates \
|
||||||
&& wget -O libiconv.tar.gz "http://ftp.gnu.org/pub/gnu/libiconv/libiconv-$LIBICONV_VERSION.tar.gz" \
|
|
||||||
&& echo "$LIBICONV_DOWNLOAD_SHA256 *libiconv.tar.gz" | sha256sum -c - \
|
|
||||||
&& mkdir -p /tmp/src \
|
|
||||||
&& tar -xzf libiconv.tar.gz -C /tmp/src \
|
|
||||||
&& rm libiconv.tar.gz \
|
|
||||||
&& cd /tmp/src/libiconv-$LIBICONV_VERSION \
|
|
||||||
&& ./configure --prefix=/usr/local \
|
|
||||||
&& make -j$(getconf _NPROCESSORS_ONLN)\
|
|
||||||
&& make install \
|
|
||||||
&& libtool --finish /usr/local/lib \
|
|
||||||
&& cd /mastodon \
|
|
||||||
&& rm -rf /tmp/* /var/cache/apk/*
|
&& rm -rf /tmp/* /var/cache/apk/*
|
||||||
|
|
||||||
COPY Gemfile Gemfile.lock package.json yarn.lock /mastodon/
|
COPY Gemfile Gemfile.lock package.json yarn.lock /mastodon/
|
||||||
|
|
||||||
RUN bundle config build.nokogiri --with-iconv-lib=/usr/local/lib --with-iconv-include=/usr/local/include \
|
RUN bundle install --deployment --without test development \
|
||||||
&& bundle install -j$(getconf _NPROCESSORS_ONLN) --deployment --without test development \
|
|
||||||
&& yarn --ignore-optional --pure-lockfile
|
&& yarn --ignore-optional --pure-lockfile
|
||||||
|
|
||||||
COPY . /mastodon
|
COPY . /mastodon
|
||||||
|
|||||||
10
Gemfile
10
Gemfile
@@ -18,27 +18,23 @@ gem 'aws-sdk', '~> 2.9'
|
|||||||
gem 'paperclip', '~> 5.1'
|
gem 'paperclip', '~> 5.1'
|
||||||
gem 'paperclip-av-transcoder', '~> 0.6'
|
gem 'paperclip-av-transcoder', '~> 0.6'
|
||||||
|
|
||||||
gem 'active_model_serializers', '~> 0.10'
|
|
||||||
gem 'addressable', '~> 2.5'
|
gem 'addressable', '~> 2.5'
|
||||||
gem 'bootsnap'
|
gem 'bootsnap'
|
||||||
gem 'browser'
|
gem 'browser'
|
||||||
gem 'charlock_holmes', '~> 0.7.3'
|
|
||||||
gem 'cld3', '~> 3.1'
|
gem 'cld3', '~> 3.1'
|
||||||
gem 'devise', '~> 4.2'
|
gem 'devise', '~> 4.2'
|
||||||
gem 'devise-two-factor', '~> 3.0'
|
gem 'devise-two-factor', '~> 3.0'
|
||||||
gem 'doorkeeper', '~> 4.2'
|
gem 'doorkeeper', '~> 4.2'
|
||||||
gem 'fast_blank', '~> 1.0'
|
gem 'fast_blank', '~> 1.0'
|
||||||
gem 'goldfinger', '~> 2.0'
|
gem 'goldfinger', '~> 1.2'
|
||||||
gem 'hiredis', '~> 0.6'
|
gem 'hiredis', '~> 0.6'
|
||||||
gem 'redis-namespace', '~> 1.5'
|
gem 'redis-namespace', '~> 1.5'
|
||||||
gem 'htmlentities', '~> 4.3'
|
gem 'htmlentities', '~> 4.3'
|
||||||
gem 'http', '~> 2.2'
|
gem 'http', '~> 2.2'
|
||||||
gem 'http_accept_language', '~> 2.1'
|
gem 'http_accept_language', '~> 2.1'
|
||||||
gem 'httplog', '~> 0.99'
|
gem 'httplog', '~> 0.99'
|
||||||
gem 'idn-ruby', require: 'idn'
|
|
||||||
gem 'kaminari', '~> 1.0'
|
gem 'kaminari', '~> 1.0'
|
||||||
gem 'link_header', '~> 0.0'
|
gem 'link_header', '~> 0.0'
|
||||||
gem 'mime-types', '~> 3.1'
|
|
||||||
gem 'nokogiri', '~> 1.7'
|
gem 'nokogiri', '~> 1.7'
|
||||||
gem 'oj', '~> 3.0'
|
gem 'oj', '~> 3.0'
|
||||||
gem 'ostatus2', '~> 2.0'
|
gem 'ostatus2', '~> 2.0'
|
||||||
@@ -51,7 +47,6 @@ gem 'rack-timeout', '~> 0.4'
|
|||||||
gem 'rails-i18n', '~> 5.0'
|
gem 'rails-i18n', '~> 5.0'
|
||||||
gem 'rails-settings-cached', '~> 0.6'
|
gem 'rails-settings-cached', '~> 0.6'
|
||||||
gem 'redis', '~> 3.3', require: ['redis', 'redis/connection/hiredis']
|
gem 'redis', '~> 3.3', require: ['redis', 'redis/connection/hiredis']
|
||||||
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
|
|
||||||
gem 'rqrcode', '~> 0.10'
|
gem 'rqrcode', '~> 0.10'
|
||||||
gem 'ruby-oembed', '~> 0.12', require: 'oembed'
|
gem 'ruby-oembed', '~> 0.12', require: 'oembed'
|
||||||
gem 'sanitize', '~> 4.4'
|
gem 'sanitize', '~> 4.4'
|
||||||
@@ -66,7 +61,6 @@ gem 'statsd-instrument', '~> 2.1'
|
|||||||
gem 'twitter-text', '~> 1.14'
|
gem 'twitter-text', '~> 1.14'
|
||||||
gem 'tzinfo-data', '~> 1.2017'
|
gem 'tzinfo-data', '~> 1.2017'
|
||||||
gem 'webpacker', '~> 2.0'
|
gem 'webpacker', '~> 2.0'
|
||||||
gem 'webpush'
|
|
||||||
|
|
||||||
group :development, :test do
|
group :development, :test do
|
||||||
gem 'fabrication', '~> 2.16'
|
gem 'fabrication', '~> 2.16'
|
||||||
@@ -80,7 +74,7 @@ group :test do
|
|||||||
gem 'capybara', '~> 2.14'
|
gem 'capybara', '~> 2.14'
|
||||||
gem 'climate_control', '~> 0.2'
|
gem 'climate_control', '~> 0.2'
|
||||||
gem 'faker', '~> 1.7'
|
gem 'faker', '~> 1.7'
|
||||||
gem 'microformats', '~> 4.0'
|
gem 'microformats2', '~> 3.0'
|
||||||
gem 'rails-controller-testing', '~> 1.0'
|
gem 'rails-controller-testing', '~> 1.0'
|
||||||
gem 'rspec-sidekiq', '~> 3.0'
|
gem 'rspec-sidekiq', '~> 3.0'
|
||||||
gem 'simplecov', '~> 0.14', require: false
|
gem 'simplecov', '~> 0.14', require: false
|
||||||
|
|||||||
180
Gemfile.lock
180
Gemfile.lock
@@ -1,52 +1,47 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actioncable (5.1.3)
|
actioncable (5.1.2)
|
||||||
actionpack (= 5.1.3)
|
actionpack (= 5.1.2)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
websocket-driver (~> 0.6.1)
|
websocket-driver (~> 0.6.1)
|
||||||
actionmailer (5.1.3)
|
actionmailer (5.1.2)
|
||||||
actionpack (= 5.1.3)
|
actionpack (= 5.1.2)
|
||||||
actionview (= 5.1.3)
|
actionview (= 5.1.2)
|
||||||
activejob (= 5.1.3)
|
activejob (= 5.1.2)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
actionpack (5.1.3)
|
actionpack (5.1.2)
|
||||||
actionview (= 5.1.3)
|
actionview (= 5.1.2)
|
||||||
activesupport (= 5.1.3)
|
activesupport (= 5.1.2)
|
||||||
rack (~> 2.0)
|
rack (~> 2.0)
|
||||||
rack-test (~> 0.6.3)
|
rack-test (~> 0.6.3)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||||
actionview (5.1.3)
|
actionview (5.1.2)
|
||||||
activesupport (= 5.1.3)
|
activesupport (= 5.1.2)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubi (~> 1.4)
|
erubi (~> 1.4)
|
||||||
rails-dom-testing (~> 2.0)
|
rails-dom-testing (~> 2.0)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||||
active_model_serializers (0.10.6)
|
|
||||||
actionpack (>= 4.1, < 6)
|
|
||||||
activemodel (>= 4.1, < 6)
|
|
||||||
case_transform (>= 0.2)
|
|
||||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.2)
|
|
||||||
active_record_query_trace (1.5.4)
|
active_record_query_trace (1.5.4)
|
||||||
activejob (5.1.3)
|
activejob (5.1.2)
|
||||||
activesupport (= 5.1.3)
|
activesupport (= 5.1.2)
|
||||||
globalid (>= 0.3.6)
|
globalid (>= 0.3.6)
|
||||||
activemodel (5.1.3)
|
activemodel (5.1.2)
|
||||||
activesupport (= 5.1.3)
|
activesupport (= 5.1.2)
|
||||||
activerecord (5.1.3)
|
activerecord (5.1.2)
|
||||||
activemodel (= 5.1.3)
|
activemodel (= 5.1.2)
|
||||||
activesupport (= 5.1.3)
|
activesupport (= 5.1.2)
|
||||||
arel (~> 8.0)
|
arel (~> 8.0)
|
||||||
activesupport (5.1.3)
|
activesupport (5.1.2)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (~> 0.7)
|
i18n (~> 0.7)
|
||||||
minitest (~> 5.1)
|
minitest (~> 5.1)
|
||||||
tzinfo (~> 1.1)
|
tzinfo (~> 1.1)
|
||||||
addressable (2.5.1)
|
addressable (2.5.1)
|
||||||
public_suffix (~> 2.0, >= 2.0.2)
|
public_suffix (~> 2.0, >= 2.0.2)
|
||||||
airbrussh (1.3.0)
|
airbrussh (1.2.0)
|
||||||
sshkit (>= 1.6.1, != 1.7.0)
|
sshkit (>= 1.6.1, != 1.7.0)
|
||||||
annotate (2.7.2)
|
annotate (2.7.2)
|
||||||
activerecord (>= 3.2, < 6.0)
|
activerecord (>= 3.2, < 6.0)
|
||||||
@@ -57,14 +52,14 @@ GEM
|
|||||||
encryptor (~> 3.0.0)
|
encryptor (~> 3.0.0)
|
||||||
av (0.9.0)
|
av (0.9.0)
|
||||||
cocaine (~> 0.5.3)
|
cocaine (~> 0.5.3)
|
||||||
aws-sdk (2.10.21)
|
aws-sdk (2.9.37)
|
||||||
aws-sdk-resources (= 2.10.21)
|
aws-sdk-resources (= 2.9.37)
|
||||||
aws-sdk-core (2.10.21)
|
aws-sdk-core (2.9.37)
|
||||||
aws-sigv4 (~> 1.0)
|
aws-sigv4 (~> 1.0)
|
||||||
jmespath (~> 1.0)
|
jmespath (~> 1.0)
|
||||||
aws-sdk-resources (2.10.21)
|
aws-sdk-resources (2.9.37)
|
||||||
aws-sdk-core (= 2.10.21)
|
aws-sdk-core (= 2.9.37)
|
||||||
aws-sigv4 (1.0.1)
|
aws-sigv4 (1.0.0)
|
||||||
bcrypt (3.1.11)
|
bcrypt (3.1.11)
|
||||||
better_errors (2.1.1)
|
better_errors (2.1.1)
|
||||||
coderay (>= 1.0.0)
|
coderay (>= 1.0.0)
|
||||||
@@ -72,7 +67,7 @@ GEM
|
|||||||
rack (>= 0.9.0)
|
rack (>= 0.9.0)
|
||||||
binding_of_caller (0.7.2)
|
binding_of_caller (0.7.2)
|
||||||
debug_inspector (>= 0.0.1)
|
debug_inspector (>= 0.0.1)
|
||||||
bootsnap (1.1.2)
|
bootsnap (1.0.0)
|
||||||
msgpack (~> 1.0)
|
msgpack (~> 1.0)
|
||||||
brakeman (3.6.2)
|
brakeman (3.6.2)
|
||||||
browser (2.4.0)
|
browser (2.4.0)
|
||||||
@@ -83,7 +78,7 @@ GEM
|
|||||||
bundler-audit (0.5.0)
|
bundler-audit (0.5.0)
|
||||||
bundler (~> 1.2)
|
bundler (~> 1.2)
|
||||||
thor (~> 0.18)
|
thor (~> 0.18)
|
||||||
capistrano (3.8.2)
|
capistrano (3.8.1)
|
||||||
airbrussh (>= 1.0.0)
|
airbrussh (>= 1.0.0)
|
||||||
i18n
|
i18n
|
||||||
rake (>= 10.0.0)
|
rake (>= 10.0.0)
|
||||||
@@ -99,18 +94,15 @@ GEM
|
|||||||
sshkit (~> 1.3)
|
sshkit (~> 1.3)
|
||||||
capistrano-yarn (2.0.2)
|
capistrano-yarn (2.0.2)
|
||||||
capistrano (~> 3.0)
|
capistrano (~> 3.0)
|
||||||
capybara (2.14.4)
|
capybara (2.14.2)
|
||||||
addressable
|
addressable
|
||||||
mime-types (>= 1.16)
|
mime-types (>= 1.16)
|
||||||
nokogiri (>= 1.3.3)
|
nokogiri (>= 1.3.3)
|
||||||
rack (>= 1.0.0)
|
rack (>= 1.0.0)
|
||||||
rack-test (>= 0.5.4)
|
rack-test (>= 0.5.4)
|
||||||
xpath (~> 2.0)
|
xpath (~> 2.0)
|
||||||
case_transform (0.2)
|
|
||||||
activesupport
|
|
||||||
charlock_holmes (0.7.3)
|
|
||||||
chunky_png (1.3.8)
|
chunky_png (1.3.8)
|
||||||
cld3 (3.1.3)
|
cld3 (3.1.2)
|
||||||
ffi (>= 1.1.0, < 1.10.0)
|
ffi (>= 1.1.0, < 1.10.0)
|
||||||
climate_control (0.2.0)
|
climate_control (0.2.0)
|
||||||
cocaine (0.5.8)
|
cocaine (0.5.8)
|
||||||
@@ -150,12 +142,12 @@ GEM
|
|||||||
thread
|
thread
|
||||||
thread_safe
|
thread_safe
|
||||||
encryptor (3.0.0)
|
encryptor (3.0.0)
|
||||||
erubi (1.6.1)
|
erubi (1.6.0)
|
||||||
erubis (2.7.0)
|
erubis (2.7.0)
|
||||||
et-orbi (1.0.5)
|
et-orbi (1.0.4)
|
||||||
tzinfo
|
tzinfo
|
||||||
execjs (2.7.0)
|
execjs (2.7.0)
|
||||||
fabrication (2.16.2)
|
fabrication (2.16.1)
|
||||||
faker (1.7.3)
|
faker (1.7.3)
|
||||||
i18n (~> 0.5)
|
i18n (~> 0.5)
|
||||||
fast_blank (1.0.0)
|
fast_blank (1.0.0)
|
||||||
@@ -165,12 +157,11 @@ GEM
|
|||||||
ruby-progressbar (~> 1.4)
|
ruby-progressbar (~> 1.4)
|
||||||
globalid (0.4.0)
|
globalid (0.4.0)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
goldfinger (2.0.1)
|
goldfinger (1.2.0)
|
||||||
addressable (~> 2.5)
|
addressable (~> 2.4)
|
||||||
http (~> 2.2)
|
http (~> 2.0)
|
||||||
nokogiri (~> 1.8)
|
nokogiri (~> 1.6)
|
||||||
oj (~> 3.0)
|
hamlit (2.8.1)
|
||||||
hamlit (2.8.4)
|
|
||||||
temple (>= 0.8.0)
|
temple (>= 0.8.0)
|
||||||
thor
|
thor
|
||||||
tilt
|
tilt
|
||||||
@@ -179,10 +170,9 @@ GEM
|
|||||||
activesupport (>= 4.0.1)
|
activesupport (>= 4.0.1)
|
||||||
hamlit (>= 1.2.0)
|
hamlit (>= 1.2.0)
|
||||||
railties (>= 4.0.1)
|
railties (>= 4.0.1)
|
||||||
hashdiff (0.3.5)
|
hashdiff (0.3.4)
|
||||||
highline (1.7.8)
|
highline (1.7.8)
|
||||||
hiredis (0.6.1)
|
hiredis (0.6.1)
|
||||||
hkdf (0.3.0)
|
|
||||||
htmlentities (4.3.4)
|
htmlentities (4.3.4)
|
||||||
http (2.2.2)
|
http (2.2.2)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
@@ -192,13 +182,13 @@ GEM
|
|||||||
http-cookie (1.0.3)
|
http-cookie (1.0.3)
|
||||||
domain_name (~> 0.5)
|
domain_name (~> 0.5)
|
||||||
http-form_data (1.0.3)
|
http-form_data (1.0.3)
|
||||||
http_accept_language (2.1.1)
|
http_accept_language (2.1.0)
|
||||||
http_parser.rb (0.6.0)
|
http_parser.rb (0.6.0)
|
||||||
httplog (0.99.7)
|
httplog (0.99.3)
|
||||||
colorize
|
colorize
|
||||||
rack
|
rack
|
||||||
i18n (0.8.6)
|
i18n (0.8.4)
|
||||||
i18n-tasks (0.9.16)
|
i18n-tasks (0.9.15)
|
||||||
activesupport (>= 4.0.2)
|
activesupport (>= 4.0.2)
|
||||||
ast (>= 2.1.0)
|
ast (>= 2.1.0)
|
||||||
easy_translate (>= 0.5.0)
|
easy_translate (>= 0.5.0)
|
||||||
@@ -208,11 +198,8 @@ GEM
|
|||||||
parser (>= 2.2.3.0)
|
parser (>= 2.2.3.0)
|
||||||
rainbow (~> 2.2)
|
rainbow (~> 2.2)
|
||||||
terminal-table (>= 1.5.1)
|
terminal-table (>= 1.5.1)
|
||||||
idn-ruby (0.1.0)
|
|
||||||
jmespath (1.3.1)
|
jmespath (1.3.1)
|
||||||
json (2.1.0)
|
json (2.1.0)
|
||||||
jsonapi-renderer (0.1.3)
|
|
||||||
jwt (1.5.6)
|
|
||||||
kaminari (1.0.1)
|
kaminari (1.0.1)
|
||||||
activesupport (>= 4.1.0)
|
activesupport (>= 4.1.0)
|
||||||
kaminari-actionview (= 1.0.1)
|
kaminari-actionview (= 1.0.1)
|
||||||
@@ -242,10 +229,8 @@ GEM
|
|||||||
nokogiri (>= 1.5.9)
|
nokogiri (>= 1.5.9)
|
||||||
mail (2.6.6)
|
mail (2.6.6)
|
||||||
mime-types (>= 1.16, < 4)
|
mime-types (>= 1.16, < 4)
|
||||||
mario-redis-lock (1.2.0)
|
|
||||||
redis (~> 3, >= 3.0.5)
|
|
||||||
method_source (0.8.2)
|
method_source (0.8.2)
|
||||||
microformats (4.0.7)
|
microformats2 (3.1.0)
|
||||||
json
|
json
|
||||||
nokogiri
|
nokogiri
|
||||||
mime-types (3.1)
|
mime-types (3.1)
|
||||||
@@ -253,7 +238,7 @@ GEM
|
|||||||
mime-types-data (3.2016.0521)
|
mime-types-data (3.2016.0521)
|
||||||
mimemagic (0.3.2)
|
mimemagic (0.3.2)
|
||||||
mini_portile2 (2.2.0)
|
mini_portile2 (2.2.0)
|
||||||
minitest (5.10.3)
|
minitest (5.10.2)
|
||||||
msgpack (1.1.0)
|
msgpack (1.1.0)
|
||||||
multi_json (1.12.1)
|
multi_json (1.12.1)
|
||||||
net-scp (1.2.1)
|
net-scp (1.2.1)
|
||||||
@@ -264,8 +249,8 @@ GEM
|
|||||||
mini_portile2 (~> 2.2.0)
|
mini_portile2 (~> 2.2.0)
|
||||||
nokogumbo (1.4.13)
|
nokogumbo (1.4.13)
|
||||||
nokogiri
|
nokogiri
|
||||||
oj (3.3.4)
|
oj (3.1.0)
|
||||||
openssl (2.0.4)
|
openssl (2.0.3)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
ostatus2 (2.0.1)
|
ostatus2 (2.0.1)
|
||||||
addressable (~> 2.4)
|
addressable (~> 2.4)
|
||||||
@@ -283,14 +268,14 @@ GEM
|
|||||||
av (~> 0.9.0)
|
av (~> 0.9.0)
|
||||||
paperclip (>= 2.5.2)
|
paperclip (>= 2.5.2)
|
||||||
parallel (1.11.2)
|
parallel (1.11.2)
|
||||||
parallel_tests (2.14.2)
|
parallel_tests (2.14.1)
|
||||||
parallel
|
parallel
|
||||||
parser (2.4.0.0)
|
parser (2.4.0.0)
|
||||||
ast (~> 2.2)
|
ast (~> 2.2)
|
||||||
pg (0.21.0)
|
pg (0.20.0)
|
||||||
pghero (1.7.0)
|
pghero (1.7.0)
|
||||||
activerecord
|
activerecord
|
||||||
pkg-config (1.2.4)
|
pkg-config (1.2.3)
|
||||||
powerpack (0.1.1)
|
powerpack (0.1.1)
|
||||||
pry (0.10.4)
|
pry (0.10.4)
|
||||||
coderay (~> 1.1.0)
|
coderay (~> 1.1.0)
|
||||||
@@ -313,17 +298,17 @@ GEM
|
|||||||
rack-test (0.6.3)
|
rack-test (0.6.3)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
rack-timeout (0.4.2)
|
rack-timeout (0.4.2)
|
||||||
rails (5.1.3)
|
rails (5.1.2)
|
||||||
actioncable (= 5.1.3)
|
actioncable (= 5.1.2)
|
||||||
actionmailer (= 5.1.3)
|
actionmailer (= 5.1.2)
|
||||||
actionpack (= 5.1.3)
|
actionpack (= 5.1.2)
|
||||||
actionview (= 5.1.3)
|
actionview (= 5.1.2)
|
||||||
activejob (= 5.1.3)
|
activejob (= 5.1.2)
|
||||||
activemodel (= 5.1.3)
|
activemodel (= 5.1.2)
|
||||||
activerecord (= 5.1.3)
|
activerecord (= 5.1.2)
|
||||||
activesupport (= 5.1.3)
|
activesupport (= 5.1.2)
|
||||||
bundler (>= 1.3.0)
|
bundler (>= 1.3.0, < 2.0)
|
||||||
railties (= 5.1.3)
|
railties (= 5.1.2)
|
||||||
sprockets-rails (>= 2.0.0)
|
sprockets-rails (>= 2.0.0)
|
||||||
rails-controller-testing (1.0.2)
|
rails-controller-testing (1.0.2)
|
||||||
actionpack (~> 5.x, >= 5.0.1)
|
actionpack (~> 5.x, >= 5.0.1)
|
||||||
@@ -337,11 +322,11 @@ GEM
|
|||||||
rails-i18n (5.0.4)
|
rails-i18n (5.0.4)
|
||||||
i18n (~> 0.7)
|
i18n (~> 0.7)
|
||||||
railties (~> 5.0)
|
railties (~> 5.0)
|
||||||
rails-settings-cached (0.6.6)
|
rails-settings-cached (0.6.5)
|
||||||
rails (>= 4.2.0)
|
rails (>= 4.2.0)
|
||||||
railties (5.1.3)
|
railties (5.1.2)
|
||||||
actionpack (= 5.1.3)
|
actionpack (= 5.1.2)
|
||||||
activesupport (= 5.1.3)
|
activesupport (= 5.1.2)
|
||||||
method_source
|
method_source
|
||||||
rake (>= 0.8.7)
|
rake (>= 0.8.7)
|
||||||
thor (>= 0.18.1, < 2.0)
|
thor (>= 0.18.1, < 2.0)
|
||||||
@@ -353,7 +338,7 @@ GEM
|
|||||||
actionpack (>= 4.0, < 6)
|
actionpack (>= 4.0, < 6)
|
||||||
redis-rack (>= 1, < 3)
|
redis-rack (>= 1, < 3)
|
||||||
redis-store (>= 1.1.0, < 1.4.0)
|
redis-store (>= 1.1.0, < 1.4.0)
|
||||||
redis-activesupport (5.0.3)
|
redis-activesupport (5.0.2)
|
||||||
activesupport (>= 3, < 6)
|
activesupport (>= 3, < 6)
|
||||||
redis-store (~> 1.3.0)
|
redis-store (~> 1.3.0)
|
||||||
redis-namespace (1.5.3)
|
redis-namespace (1.5.3)
|
||||||
@@ -389,7 +374,7 @@ GEM
|
|||||||
rspec-expectations (~> 3.6.0)
|
rspec-expectations (~> 3.6.0)
|
||||||
rspec-mocks (~> 3.6.0)
|
rspec-mocks (~> 3.6.0)
|
||||||
rspec-support (~> 3.6.0)
|
rspec-support (~> 3.6.0)
|
||||||
rspec-sidekiq (3.0.3)
|
rspec-sidekiq (3.0.1)
|
||||||
rspec-core (~> 3.0, >= 3.0.0)
|
rspec-core (~> 3.0, >= 3.0.0)
|
||||||
sidekiq (>= 2.4.0)
|
sidekiq (>= 2.4.0)
|
||||||
rspec-support (3.6.0)
|
rspec-support (3.6.0)
|
||||||
@@ -410,10 +395,10 @@ GEM
|
|||||||
nokogiri (>= 1.4.4)
|
nokogiri (>= 1.4.4)
|
||||||
nokogumbo (~> 1.4.1)
|
nokogumbo (~> 1.4.1)
|
||||||
sass (3.4.24)
|
sass (3.4.24)
|
||||||
scss_lint (0.54.0)
|
scss_lint (0.53.0)
|
||||||
rake (>= 0.9, < 13)
|
rake (>= 0.9, < 13)
|
||||||
sass (~> 3.4.20)
|
sass (~> 3.4.20)
|
||||||
sidekiq (5.0.4)
|
sidekiq (5.0.2)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
connection_pool (~> 2.2, >= 2.2.0)
|
connection_pool (~> 2.2, >= 2.2.0)
|
||||||
rack-protection (>= 1.5.0)
|
rack-protection (>= 1.5.0)
|
||||||
@@ -421,12 +406,12 @@ GEM
|
|||||||
sidekiq-bulk (0.1.1)
|
sidekiq-bulk (0.1.1)
|
||||||
activesupport
|
activesupport
|
||||||
sidekiq
|
sidekiq
|
||||||
sidekiq-scheduler (2.1.8)
|
sidekiq-scheduler (2.1.5)
|
||||||
redis (~> 3)
|
redis (~> 3)
|
||||||
rufus-scheduler (~> 3.2)
|
rufus-scheduler (~> 3.2)
|
||||||
sidekiq (>= 3)
|
sidekiq (>= 3)
|
||||||
tilt (>= 1.4.0)
|
tilt (>= 1.4.0)
|
||||||
sidekiq-unique-jobs (5.0.9)
|
sidekiq-unique-jobs (5.0.8)
|
||||||
sidekiq (>= 4.0, <= 6.0)
|
sidekiq (>= 4.0, <= 6.0)
|
||||||
thor (~> 0)
|
thor (~> 0)
|
||||||
simple-navigation (4.0.5)
|
simple-navigation (4.0.5)
|
||||||
@@ -450,15 +435,15 @@ GEM
|
|||||||
sshkit (1.13.1)
|
sshkit (1.13.1)
|
||||||
net-scp (>= 1.1.2)
|
net-scp (>= 1.1.2)
|
||||||
net-ssh (>= 2.8.0)
|
net-ssh (>= 2.8.0)
|
||||||
statsd-instrument (2.1.4)
|
statsd-instrument (2.1.2)
|
||||||
temple (0.8.0)
|
temple (0.8.0)
|
||||||
terminal-table (1.8.0)
|
terminal-table (1.8.0)
|
||||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||||
thor (0.19.4)
|
thor (0.19.4)
|
||||||
thread (0.2.2)
|
thread (0.2.2)
|
||||||
thread_safe (0.3.6)
|
thread_safe (0.3.6)
|
||||||
tilt (2.0.8)
|
tilt (2.0.7)
|
||||||
twitter-text (1.14.7)
|
twitter-text (1.14.5)
|
||||||
unf (~> 0.1.0)
|
unf (~> 0.1.0)
|
||||||
tzinfo (1.2.3)
|
tzinfo (1.2.3)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
@@ -469,7 +454,7 @@ GEM
|
|||||||
unf (0.1.4)
|
unf (0.1.4)
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.7.4)
|
unf_ext (0.0.7.4)
|
||||||
unicode-display_width (1.3.0)
|
unicode-display_width (1.2.1)
|
||||||
uniform_notifier (1.10.0)
|
uniform_notifier (1.10.0)
|
||||||
warden (1.2.7)
|
warden (1.2.7)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
@@ -481,9 +466,6 @@ GEM
|
|||||||
activesupport (>= 4.2)
|
activesupport (>= 4.2)
|
||||||
multi_json (~> 1.2)
|
multi_json (~> 1.2)
|
||||||
railties (>= 4.2)
|
railties (>= 4.2)
|
||||||
webpush (0.3.2)
|
|
||||||
hkdf (~> 0.2)
|
|
||||||
jwt
|
|
||||||
websocket-driver (0.6.5)
|
websocket-driver (0.6.5)
|
||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.2)
|
websocket-extensions (0.1.2)
|
||||||
@@ -494,7 +476,6 @@ PLATFORMS
|
|||||||
ruby
|
ruby
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
active_model_serializers (~> 0.10)
|
|
||||||
active_record_query_trace (~> 1.5)
|
active_record_query_trace (~> 1.5)
|
||||||
addressable (~> 2.5)
|
addressable (~> 2.5)
|
||||||
annotate (~> 2.7)
|
annotate (~> 2.7)
|
||||||
@@ -511,7 +492,6 @@ DEPENDENCIES
|
|||||||
capistrano-rbenv (~> 2.1)
|
capistrano-rbenv (~> 2.1)
|
||||||
capistrano-yarn (~> 2.0)
|
capistrano-yarn (~> 2.0)
|
||||||
capybara (~> 2.14)
|
capybara (~> 2.14)
|
||||||
charlock_holmes (~> 0.7.3)
|
|
||||||
cld3 (~> 3.1)
|
cld3 (~> 3.1)
|
||||||
climate_control (~> 0.2)
|
climate_control (~> 0.2)
|
||||||
devise (~> 4.2)
|
devise (~> 4.2)
|
||||||
@@ -522,7 +502,7 @@ DEPENDENCIES
|
|||||||
faker (~> 1.7)
|
faker (~> 1.7)
|
||||||
fast_blank (~> 1.0)
|
fast_blank (~> 1.0)
|
||||||
fuubar (~> 2.2)
|
fuubar (~> 2.2)
|
||||||
goldfinger (~> 2.0)
|
goldfinger (~> 1.2)
|
||||||
hamlit-rails (~> 0.2)
|
hamlit-rails (~> 0.2)
|
||||||
hiredis (~> 0.6)
|
hiredis (~> 0.6)
|
||||||
htmlentities (~> 4.3)
|
htmlentities (~> 4.3)
|
||||||
@@ -530,15 +510,12 @@ DEPENDENCIES
|
|||||||
http_accept_language (~> 2.1)
|
http_accept_language (~> 2.1)
|
||||||
httplog (~> 0.99)
|
httplog (~> 0.99)
|
||||||
i18n-tasks (~> 0.9)
|
i18n-tasks (~> 0.9)
|
||||||
idn-ruby
|
|
||||||
kaminari (~> 1.0)
|
kaminari (~> 1.0)
|
||||||
letter_opener (~> 1.4)
|
letter_opener (~> 1.4)
|
||||||
letter_opener_web (~> 1.3)
|
letter_opener_web (~> 1.3)
|
||||||
link_header (~> 0.0)
|
link_header (~> 0.0)
|
||||||
lograge (~> 0.5)
|
lograge (~> 0.5)
|
||||||
mario-redis-lock (~> 1.2)
|
microformats2 (~> 3.0)
|
||||||
microformats (~> 4.0)
|
|
||||||
mime-types (~> 3.1)
|
|
||||||
nokogiri (~> 1.7)
|
nokogiri (~> 1.7)
|
||||||
oj (~> 3.0)
|
oj (~> 3.0)
|
||||||
ostatus2 (~> 2.0)
|
ostatus2 (~> 2.0)
|
||||||
@@ -584,10 +561,9 @@ DEPENDENCIES
|
|||||||
uglifier (~> 3.2)
|
uglifier (~> 3.2)
|
||||||
webmock (~> 3.0)
|
webmock (~> 3.0)
|
||||||
webpacker (~> 2.0)
|
webpacker (~> 2.0)
|
||||||
webpush
|
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
ruby 2.4.1p111
|
ruby 2.4.1p111
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
1.15.3
|
1.15.1
|
||||||
|
|||||||
72
README.md
72
README.md
@@ -1,10 +1,70 @@
|
|||||||
# Mastodon Glitch Edition #
|
Mastodon
|
||||||
|
========
|
||||||
|
|
||||||
> Now with automated deploys!
|
[][travis]
|
||||||
|
[][code_climate]
|
||||||
|
|
||||||
[](https://travis-ci.org/glitch-soc/mastodon)
|
[travis]: https://travis-ci.org/tootsuite/mastodon
|
||||||
|
[code_climate]: https://codeclimate.com/github/tootsuite/mastodon
|
||||||
|
|
||||||
So here's the deal: we all work on this code, and then it runs on dev.glitch.social and anyone who uses that does so absolutely at their own risk. can you dig it?
|
Mastodon is a free, open-source social network server. A decentralized solution to commercial platforms, it avoids the risks of a single company monopolizing your communication. Anyone can run Mastodon and participate in the social network seamlessly.
|
||||||
|
|
||||||
- You can view documentation for this project at [glitch-soc.github.io/docs/](https://glitch-soc.github.io/docs/).
|
An alternative implementation of the GNU social project. Based on [ActivityStreams](https://en.wikipedia.org/wiki/Activity_Streams_(format)), [Webfinger](https://en.wikipedia.org/wiki/WebFinger), [PubsubHubbub](https://en.wikipedia.org/wiki/PubSubHubbub) and [Salmon](https://en.wikipedia.org/wiki/Salmon_(protocol)).
|
||||||
- And contributing guidelines are available [here](CONTRIBUTING.md) and [here](https://glitch-soc.github.io/docs/contributing/).
|
|
||||||
|
Click on the screenshot to watch a demo of the UI:
|
||||||
|
|
||||||
|
[][youtube_demo]
|
||||||
|
|
||||||
|
[youtube_demo]: https://www.youtube.com/watch?v=YO1jQ8_rAMU
|
||||||
|
|
||||||
|
The project focus is a clean REST API and a good user interface. Ruby on Rails is used for the back-end, while React.js and Redux are used for the dynamic front-end. A static front-end for public resources (profiles and statuses) is also provided.
|
||||||
|
|
||||||
|
If you would like, you can [support the development of this project on Patreon][patreon]. Alternatively, you can donate to this BTC address: `17j2g7vpgHhLuXhN4bueZFCvdxxieyRVWd`
|
||||||
|
|
||||||
|
[patreon]: https://www.patreon.com/user?u=619786
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [List of Mastodon instances](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/List-of-Mastodon-instances.md)
|
||||||
|
- [Use this tool to find Twitter friends on Mastodon](https://mastodon-bridge.herokuapp.com)
|
||||||
|
- [API overview](https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md)
|
||||||
|
- [Frequently Asked Questions](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/FAQ.md)
|
||||||
|
- [List of apps](https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/Apps.md)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Fully interoperable with GNU social and any OStatus platform**
|
||||||
|
Whatever implements Atom feeds, ActivityStreams, Salmon, PubSubHubbub and Webfinger is part of the network
|
||||||
|
- **Real-time timeline updates**
|
||||||
|
See the updates of people you're following appear in real-time in the UI via WebSockets
|
||||||
|
- **Federated thread resolving**
|
||||||
|
If someone you follow replies to a user unknown to the server, the server fetches the full thread so you can view it without leaving the UI
|
||||||
|
- **Media attachments like images and WebM**
|
||||||
|
Upload and view images and WebM videos attached to the updates
|
||||||
|
- **OAuth2 and a straightforward REST API**
|
||||||
|
Mastodon acts as an OAuth2 provider so 3rd party apps can use the API, which is RESTful and simple
|
||||||
|
- **Background processing for long-running tasks**
|
||||||
|
Mastodon tries to be as fast and responsive as possible, so all long-running tasks that can be delegated to background processing, are
|
||||||
|
- **Deployable via Docker**
|
||||||
|
You don't need to mess with dependencies and configuration if you want to try Mastodon, if you have Docker and Docker Compose the deployment is extremely easy
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
Please follow the [development guide](https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Development-guide.md) from the documentation repository.
|
||||||
|
|
||||||
|
## Deployment
|
||||||
|
|
||||||
|
There are guides in the documentation repository for [deploying on various platforms](https://github.com/tootsuite/documentation#running-mastodon).
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
You can open issues for bugs you've found or features you think are missing. You can also submit pull requests to this repository. [Here are the guidelines for code contributions](CONTRIBUTING.md)
|
||||||
|
|
||||||
|
**IRC channel**: #mastodon on irc.freenode.net
|
||||||
|
|
||||||
|
## Extra credits
|
||||||
|
|
||||||
|
- The [Emoji One](https://github.com/Ranks/emojione) pack has been used for the emojis
|
||||||
|
- The error page image courtesy of [Dopatwo](https://www.youtube.com/user/dopatwo)
|
||||||
|
|
||||||
|

|
||||||
|
|||||||
2
Vagrantfile
vendored
2
Vagrantfile
vendored
@@ -35,8 +35,6 @@ sudo apt-get install \
|
|||||||
postgresql-contrib \
|
postgresql-contrib \
|
||||||
protobuf-compiler \
|
protobuf-compiler \
|
||||||
yarn \
|
yarn \
|
||||||
libicu-dev \
|
|
||||||
libidn11-dev \
|
|
||||||
libprotobuf-dev \
|
libprotobuf-dev \
|
||||||
libreadline-dev \
|
libreadline-dev \
|
||||||
-y
|
-y
|
||||||
|
|||||||
2
app.json
2
app.json
@@ -2,7 +2,7 @@
|
|||||||
"name": "Mastodon",
|
"name": "Mastodon",
|
||||||
"description": "A GNU Social-compatible microblogging server",
|
"description": "A GNU Social-compatible microblogging server",
|
||||||
"repository": "https://github.com/tootsuite/mastodon",
|
"repository": "https://github.com/tootsuite/mastodon",
|
||||||
"logo": "https://github.com/tootsuite.png",
|
"logo": "https://github.com/tootsuite/mastodon/raw/master/app/assets/images/logo.png",
|
||||||
"env": {
|
"env": {
|
||||||
"HEROKU": {
|
"HEROKU": {
|
||||||
"description": "Leave this as true",
|
"description": "Leave this as true",
|
||||||
|
|||||||
@@ -2,12 +2,9 @@
|
|||||||
|
|
||||||
class AboutController < ApplicationController
|
class AboutController < ApplicationController
|
||||||
before_action :set_body_classes
|
before_action :set_body_classes
|
||||||
before_action :set_instance_presenter, only: [:show, :more, :terms]
|
before_action :set_instance_presenter, only: [:show, :more]
|
||||||
|
|
||||||
def show
|
def show; end
|
||||||
serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer)
|
|
||||||
@initial_state_json = serializable_resource.to_json
|
|
||||||
end
|
|
||||||
|
|
||||||
def more; end
|
def more; end
|
||||||
|
|
||||||
@@ -18,7 +15,6 @@ class AboutController < ApplicationController
|
|||||||
def new_user
|
def new_user
|
||||||
User.new.tap(&:build_account)
|
User.new.tap(&:build_account)
|
||||||
end
|
end
|
||||||
|
|
||||||
helper_method :new_user
|
helper_method :new_user
|
||||||
|
|
||||||
def set_instance_presenter
|
def set_instance_presenter
|
||||||
@@ -28,11 +24,4 @@ class AboutController < ApplicationController
|
|||||||
def set_body_classes
|
def set_body_classes
|
||||||
@body_classes = 'about-body'
|
@body_classes = 'about-body'
|
||||||
end
|
end
|
||||||
|
|
||||||
def initial_state_params
|
|
||||||
{
|
|
||||||
settings: {},
|
|
||||||
token: current_session&.token,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
class AccountsController < ApplicationController
|
class AccountsController < ApplicationController
|
||||||
include AccountControllerConcern
|
include AccountControllerConcern
|
||||||
include SignatureVerification
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
@@ -13,12 +12,10 @@ class AccountsController < ApplicationController
|
|||||||
|
|
||||||
format.atom do
|
format.atom do
|
||||||
@entries = @account.stream_entries.where(hidden: false).with_includes.paginate_by_max_id(20, params[:max_id], params[:since_id])
|
@entries = @account.stream_entries.where(hidden: false).with_includes.paginate_by_max_id(20, params[:max_id], params[:since_id])
|
||||||
render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.feed(@account, @entries.to_a))
|
render xml: AtomSerializer.render(AtomSerializer.new.feed(@account, @entries.to_a))
|
||||||
end
|
end
|
||||||
|
|
||||||
format.json do
|
format.activitystreams2
|
||||||
render json: @account, serializer: ActivityPub::ActorSerializer, adapter: ActivityPub::Adapter
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class ActivityPub::OutboxesController < Api::BaseController
|
|
||||||
before_action :set_account
|
|
||||||
|
|
||||||
def show
|
|
||||||
@statuses = @account.statuses.permitted_for(@account, current_account).paginate_by_max_id(20, params[:max_id], params[:since_id])
|
|
||||||
@statuses = cache_collection(@statuses, Status)
|
|
||||||
|
|
||||||
render json: outbox_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_account
|
|
||||||
@account = Account.find_local!(params[:account_username])
|
|
||||||
end
|
|
||||||
|
|
||||||
def outbox_presenter
|
|
||||||
ActivityPub::CollectionPresenter.new(
|
|
||||||
id: account_outbox_url(@account),
|
|
||||||
type: :ordered,
|
|
||||||
size: @account.statuses_count,
|
|
||||||
items: @statuses
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -22,8 +22,8 @@ module Admin
|
|||||||
end
|
end
|
||||||
|
|
||||||
def redownload
|
def redownload
|
||||||
@account.reset_avatar!
|
@account.avatar = @account.avatar_remote_url
|
||||||
@account.reset_header!
|
@account.header = @account.header_remote_url
|
||||||
@account.save!
|
@account.save!
|
||||||
|
|
||||||
redirect_to admin_account_path(@account.id)
|
redirect_to admin_account_path(@account.id)
|
||||||
|
|||||||
@@ -6,26 +6,15 @@ module Admin
|
|||||||
@instances = ordered_instances
|
@instances = ordered_instances
|
||||||
end
|
end
|
||||||
|
|
||||||
def resubscribe
|
|
||||||
params.require(:by_domain)
|
|
||||||
Pubsubhubbub::SubscribeWorker.push_bulk(subscribeable_accounts.pluck(:id))
|
|
||||||
redirect_to admin_instances_path
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def paginated_instances
|
def paginated_instances
|
||||||
Account.remote.by_domain_accounts.page(params[:page])
|
Account.remote.by_domain_accounts.page(params[:page])
|
||||||
end
|
end
|
||||||
|
|
||||||
helper_method :paginated_instances
|
helper_method :paginated_instances
|
||||||
|
|
||||||
def ordered_instances
|
def ordered_instances
|
||||||
paginated_instances.map { |account| Instance.new(account) }
|
paginated_instances.map { |account| Instance.new(account) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def subscribeable_accounts
|
|
||||||
Account.with_followers.remote.where(domain: params[:by_domain])
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,14 +5,7 @@ module Admin
|
|||||||
include Authorization
|
include Authorization
|
||||||
|
|
||||||
before_action :set_report
|
before_action :set_report
|
||||||
before_action :set_status, only: [:update, :destroy]
|
before_action :set_status
|
||||||
|
|
||||||
def create
|
|
||||||
@form = Form::StatusBatch.new(form_status_batch_params)
|
|
||||||
flash[:alert] = t('admin.statuses.failed_to_execute') unless @form.save
|
|
||||||
|
|
||||||
redirect_to admin_report_path(@report)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@status.update(status_params)
|
@status.update(status_params)
|
||||||
@@ -22,7 +15,7 @@ module Admin
|
|||||||
def destroy
|
def destroy
|
||||||
authorize @status, :destroy?
|
authorize @status, :destroy?
|
||||||
RemovalWorker.perform_async(@status.id)
|
RemovalWorker.perform_async(@status.id)
|
||||||
render json: @status
|
redirect_to admin_report_path(@report)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -31,10 +24,6 @@ module Admin
|
|||||||
params.require(:status).permit(:sensitive)
|
params.require(:status).permit(:sensitive)
|
||||||
end
|
end
|
||||||
|
|
||||||
def form_status_batch_params
|
|
||||||
params.require(:form_status_batch).permit(:action, status_ids: [])
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_report
|
def set_report
|
||||||
@report = Report.find(params[:report_id])
|
@report = Report.find(params[:report_id])
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,9 +8,7 @@ module Admin
|
|||||||
@reports = filtered_reports.page(params[:page])
|
@reports = filtered_reports.page(params[:page])
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show; end
|
||||||
@form = Form::StatusBatch.new
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
def update
|
||||||
process_report
|
process_report
|
||||||
|
|||||||
@@ -8,21 +8,13 @@ module Admin
|
|||||||
site_title
|
site_title
|
||||||
site_description
|
site_description
|
||||||
site_extended_description
|
site_extended_description
|
||||||
site_terms
|
|
||||||
open_registrations
|
open_registrations
|
||||||
closed_registrations_message
|
closed_registrations_message
|
||||||
open_deletion
|
|
||||||
timeline_preview
|
|
||||||
).freeze
|
|
||||||
|
|
||||||
BOOLEAN_SETTINGS = %w(
|
|
||||||
open_registrations
|
|
||||||
open_deletion
|
|
||||||
timeline_preview
|
|
||||||
).freeze
|
).freeze
|
||||||
|
BOOLEAN_SETTINGS = %w(open_registrations).freeze
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
@admin_settings = Form::AdminSettings.new
|
@settings = Setting.all_as_records
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
@@ -31,19 +23,19 @@ module Admin
|
|||||||
setting.update(value: value_for_update(key, value))
|
setting.update(value: value_for_update(key, value))
|
||||||
end
|
end
|
||||||
|
|
||||||
flash[:notice] = I18n.t('generic.changes_saved_msg')
|
flash[:notice] = 'Success!'
|
||||||
redirect_to edit_admin_settings_path
|
redirect_to edit_admin_settings_path
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def settings_params
|
def settings_params
|
||||||
params.require(:form_admin_settings).permit(ADMIN_SETTINGS)
|
params.permit(ADMIN_SETTINGS)
|
||||||
end
|
end
|
||||||
|
|
||||||
def value_for_update(key, value)
|
def value_for_update(key, value)
|
||||||
if BOOLEAN_SETTINGS.include?(key)
|
if BOOLEAN_SETTINGS.include?(key)
|
||||||
value == '1'
|
value == 'true'
|
||||||
else
|
else
|
||||||
value
|
value
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Admin
|
|
||||||
class StatusesController < BaseController
|
|
||||||
include Authorization
|
|
||||||
|
|
||||||
helper_method :current_params
|
|
||||||
|
|
||||||
before_action :set_account
|
|
||||||
before_action :set_status, only: [:update, :destroy]
|
|
||||||
|
|
||||||
PAR_PAGE = 20
|
|
||||||
|
|
||||||
def index
|
|
||||||
@statuses = @account.statuses
|
|
||||||
if params[:media]
|
|
||||||
account_media_status_ids = @account.media_attachments.attached.reorder(nil).select(:status_id).distinct
|
|
||||||
@statuses.merge!(Status.where(id: account_media_status_ids))
|
|
||||||
end
|
|
||||||
@statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PAR_PAGE)
|
|
||||||
|
|
||||||
@form = Form::StatusBatch.new
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
@form = Form::StatusBatch.new(form_status_batch_params)
|
|
||||||
flash[:alert] = t('admin.statuses.failed_to_execute') unless @form.save
|
|
||||||
|
|
||||||
redirect_to admin_account_statuses_path(@account.id, current_params)
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
@status.update(status_params)
|
|
||||||
redirect_to admin_account_statuses_path(@account.id, current_params)
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
authorize @status, :destroy?
|
|
||||||
RemovalWorker.perform_async(@status.id)
|
|
||||||
render json: @status
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def status_params
|
|
||||||
params.require(:status).permit(:sensitive)
|
|
||||||
end
|
|
||||||
|
|
||||||
def form_status_batch_params
|
|
||||||
params.require(:form_status_batch).permit(:action, status_ids: [])
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_status
|
|
||||||
@status = @account.statuses.find(params[:id])
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_account
|
|
||||||
@account = Account.find(params[:account_id])
|
|
||||||
end
|
|
||||||
|
|
||||||
def current_params
|
|
||||||
page = (params[:page] || 1).to_i
|
|
||||||
{
|
|
||||||
media: params[:media],
|
|
||||||
page: page > 1 && page,
|
|
||||||
}.select { |_, value| value.present? }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
27
app/controllers/api/activitypub/activities_controller.rb
Normal file
27
app/controllers/api/activitypub/activities_controller.rb
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::ActivityPub::ActivitiesController < Api::BaseController
|
||||||
|
include Authorization
|
||||||
|
|
||||||
|
# before_action :set_follow, only: [:show_follow]
|
||||||
|
before_action :set_status, only: [:show_status]
|
||||||
|
|
||||||
|
respond_to :activitystreams2
|
||||||
|
|
||||||
|
# Show a status in AS2 format, as either an Announce (reblog) or a Create (post) activity.
|
||||||
|
def show_status
|
||||||
|
authorize @status, :show?
|
||||||
|
|
||||||
|
if @status.reblog?
|
||||||
|
render :show_status_announce
|
||||||
|
else
|
||||||
|
render :show_status_create
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_status
|
||||||
|
@status = Status.find(params[:id])
|
||||||
|
end
|
||||||
|
end
|
||||||
19
app/controllers/api/activitypub/notes_controller.rb
Normal file
19
app/controllers/api/activitypub/notes_controller.rb
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::ActivityPub::NotesController < Api::BaseController
|
||||||
|
include Authorization
|
||||||
|
|
||||||
|
before_action :set_status
|
||||||
|
|
||||||
|
respond_to :activitystreams2
|
||||||
|
|
||||||
|
def show
|
||||||
|
authorize @status, :show?
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_status
|
||||||
|
@status = Status.find(params[:id])
|
||||||
|
end
|
||||||
|
end
|
||||||
69
app/controllers/api/activitypub/outbox_controller.rb
Normal file
69
app/controllers/api/activitypub/outbox_controller.rb
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::ActivityPub::OutboxController < Api::BaseController
|
||||||
|
before_action :set_account
|
||||||
|
|
||||||
|
respond_to :activitystreams2
|
||||||
|
|
||||||
|
def show
|
||||||
|
if params[:max_id] || params[:since_id]
|
||||||
|
show_outbox_page
|
||||||
|
else
|
||||||
|
show_base_outbox
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def show_base_outbox
|
||||||
|
@statuses = Status.as_outbox_timeline(@account)
|
||||||
|
@statuses = cache_collection(@statuses)
|
||||||
|
|
||||||
|
set_maps(@statuses)
|
||||||
|
|
||||||
|
set_first_last_page(@statuses)
|
||||||
|
|
||||||
|
render :show
|
||||||
|
end
|
||||||
|
|
||||||
|
def show_outbox_page
|
||||||
|
all_statuses = Status.as_outbox_timeline(@account)
|
||||||
|
@statuses = all_statuses.paginate_by_max_id(limit_param(DEFAULT_STATUSES_LIMIT), params[:max_id], params[:since_id])
|
||||||
|
|
||||||
|
all_statuses = cache_collection(all_statuses)
|
||||||
|
@statuses = cache_collection(@statuses)
|
||||||
|
|
||||||
|
set_maps(@statuses)
|
||||||
|
|
||||||
|
set_first_last_page(all_statuses)
|
||||||
|
|
||||||
|
@next_page_url = api_activitypub_outbox_url(pagination_params(max_id: @statuses.last.id)) unless @statuses.empty?
|
||||||
|
@prev_page_url = api_activitypub_outbox_url(pagination_params(since_id: @statuses.first.id)) unless @statuses.empty?
|
||||||
|
|
||||||
|
@paginated = @next_page_url || @prev_page_url
|
||||||
|
@part_of_url = api_activitypub_outbox_url
|
||||||
|
|
||||||
|
set_pagination_headers(@next_page_url, @prev_page_url)
|
||||||
|
|
||||||
|
render :show_page
|
||||||
|
end
|
||||||
|
|
||||||
|
def cache_collection(raw)
|
||||||
|
super(raw, Status)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_account
|
||||||
|
@account = Account.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_first_last_page(statuses) # rubocop:disable Style/AccessorMethodName
|
||||||
|
return if statuses.empty?
|
||||||
|
|
||||||
|
@first_page_url = api_activitypub_outbox_url(max_id: statuses.first.id + 1)
|
||||||
|
@last_page_url = api_activitypub_outbox_url(since_id: statuses.last.id - 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
def pagination_params(core_params)
|
||||||
|
params.permit(:local, :limit).merge(core_params)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -17,7 +17,11 @@ class Api::BaseController < ApplicationController
|
|||||||
render json: { error: 'Record not found' }, status: 404
|
render json: { error: 'Record not found' }, status: 404
|
||||||
end
|
end
|
||||||
|
|
||||||
rescue_from HTTP::Error, Mastodon::UnexpectedResponseError do
|
rescue_from Goldfinger::Error do
|
||||||
|
render json: { error: 'Remote account could not be resolved' }, status: 422
|
||||||
|
end
|
||||||
|
|
||||||
|
rescue_from HTTP::Error do
|
||||||
render json: { error: 'Remote data could not be fetched' }, status: 503
|
render json: { error: 'Remote data could not be fetched' }, status: 503
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ class Api::OEmbedController < Api::BaseController
|
|||||||
|
|
||||||
def show
|
def show
|
||||||
@stream_entry = find_stream_entry.stream_entry
|
@stream_entry = find_stream_entry.stream_entry
|
||||||
render json: @stream_entry, serializer: OEmbedSerializer, width: maxwidth_or_default, height: maxheight_or_default
|
@width = maxwidth_or_default
|
||||||
|
@height = maxheight_or_default
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::PushController < Api::BaseController
|
class Api::PushController < Api::BaseController
|
||||||
include SignatureVerification
|
|
||||||
|
|
||||||
def update
|
def update
|
||||||
response, status = process_push_request
|
response, status = process_push_request
|
||||||
render plain: response, status: status
|
render plain: response, status: status
|
||||||
@@ -13,7 +11,7 @@ class Api::PushController < Api::BaseController
|
|||||||
def process_push_request
|
def process_push_request
|
||||||
case hub_mode
|
case hub_mode
|
||||||
when 'subscribe'
|
when 'subscribe'
|
||||||
Pubsubhubbub::SubscribeService.new.call(account_from_topic, hub_callback, hub_secret, hub_lease_seconds, verified_domain)
|
Pubsubhubbub::SubscribeService.new.call(account_from_topic, hub_callback, hub_secret, hub_lease_seconds)
|
||||||
when 'unsubscribe'
|
when 'unsubscribe'
|
||||||
Pubsubhubbub::UnsubscribeService.new.call(account_from_topic, hub_callback)
|
Pubsubhubbub::UnsubscribeService.new.call(account_from_topic, hub_callback)
|
||||||
else
|
else
|
||||||
@@ -59,10 +57,6 @@ class Api::PushController < Api::BaseController
|
|||||||
TagManager.instance.web_domain?(hub_topic_domain)
|
TagManager.instance.web_domain?(hub_topic_domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
def verified_domain
|
|
||||||
return signed_request_account.domain if signed_request_account
|
|
||||||
end
|
|
||||||
|
|
||||||
def hub_topic_domain
|
def hub_topic_domain
|
||||||
hub_topic_uri.host + (hub_topic_uri.port ? ":#{hub_topic_uri.port}" : '')
|
hub_topic_uri.host + (hub_topic_uri.port ? ":#{hub_topic_uri.port}" : '')
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class Api::SubscriptionsController < Api::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def lease_seconds_or_default
|
def lease_seconds_or_default
|
||||||
(params['hub.lease_seconds'] || 1.day).to_i.seconds
|
(params['hub.lease_seconds'] || 86_400).to_i.seconds
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_account
|
def set_account
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
|
|||||||
|
|
||||||
def show
|
def show
|
||||||
@account = current_account
|
@account = current_account
|
||||||
render json: @account, serializer: REST::CredentialAccountSerializer
|
render 'api/v1/accounts/show'
|
||||||
end
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
current_account.update!(account_params)
|
current_account.update!(account_params)
|
||||||
@account = current_account
|
@account = current_account
|
||||||
render json: @account, serializer: REST::CredentialAccountSerializer
|
render 'api/v1/accounts/show'
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
@accounts = load_accounts
|
@accounts = load_accounts
|
||||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
render 'api/v1/accounts/index'
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
@accounts = load_accounts
|
@accounts = load_accounts
|
||||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
render 'api/v1/accounts/index'
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -8,15 +8,16 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
@accounts = Account.where(id: account_ids).select('id')
|
@accounts = Account.where(id: account_ids).select('id')
|
||||||
render json: @accounts, each_serializer: REST::RelationshipSerializer, relationships: relationships
|
@following = Account.following_map(account_ids, current_user.account_id)
|
||||||
|
@followed_by = Account.followed_by_map(account_ids, current_user.account_id)
|
||||||
|
@blocking = Account.blocking_map(account_ids, current_user.account_id)
|
||||||
|
@muting = Account.muting_map(account_ids, current_user.account_id)
|
||||||
|
@requested = Account.requested_map(account_ids, current_user.account_id)
|
||||||
|
@domain_blocking = Account.domain_blocking_map(account_ids, current_user.account_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def relationships
|
|
||||||
AccountRelationshipsPresenter.new(@accounts, current_user.account_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def account_ids
|
def account_ids
|
||||||
@_account_ids ||= Array(params[:id]).map(&:to_i)
|
@_account_ids ||= Array(params[:id]).map(&:to_i)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ class Api::V1::Accounts::SearchController < Api::BaseController
|
|||||||
|
|
||||||
def show
|
def show
|
||||||
@accounts = account_search
|
@accounts = account_search
|
||||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
|
||||||
|
render 'api/v1/accounts/index'
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
@statuses = load_statuses
|
@statuses = load_statuses
|
||||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -19,7 +18,9 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def load_statuses
|
def load_statuses
|
||||||
cached_account_statuses
|
cached_account_statuses.tap do |statuses|
|
||||||
|
set_maps(statuses)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def cached_account_statuses
|
def cached_account_statuses
|
||||||
|
|||||||
@@ -8,38 +8,49 @@ class Api::V1::AccountsController < Api::BaseController
|
|||||||
|
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def show
|
def show; end
|
||||||
render json: @account, serializer: REST::AccountSerializer
|
|
||||||
end
|
|
||||||
|
|
||||||
def follow
|
def follow
|
||||||
FollowService.new.call(current_user.account, @account.acct)
|
FollowService.new.call(current_user.account, @account.acct)
|
||||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
|
set_relationship
|
||||||
|
render :relationship
|
||||||
end
|
end
|
||||||
|
|
||||||
def block
|
def block
|
||||||
BlockService.new.call(current_user.account, @account)
|
BlockService.new.call(current_user.account, @account)
|
||||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
|
|
||||||
|
@following = { @account.id => false }
|
||||||
|
@followed_by = { @account.id => false }
|
||||||
|
@blocking = { @account.id => true }
|
||||||
|
@requested = { @account.id => false }
|
||||||
|
@muting = { @account.id => current_account.muting?(@account.id) }
|
||||||
|
@domain_blocking = { @account.id => current_account.domain_blocking?(@account.domain) }
|
||||||
|
|
||||||
|
render :relationship
|
||||||
end
|
end
|
||||||
|
|
||||||
def mute
|
def mute
|
||||||
MuteService.new.call(current_user.account, @account)
|
MuteService.new.call(current_user.account, @account)
|
||||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
|
set_relationship
|
||||||
|
render :relationship
|
||||||
end
|
end
|
||||||
|
|
||||||
def unfollow
|
def unfollow
|
||||||
UnfollowService.new.call(current_user.account, @account)
|
UnfollowService.new.call(current_user.account, @account)
|
||||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
|
set_relationship
|
||||||
|
render :relationship
|
||||||
end
|
end
|
||||||
|
|
||||||
def unblock
|
def unblock
|
||||||
UnblockService.new.call(current_user.account, @account)
|
UnblockService.new.call(current_user.account, @account)
|
||||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
|
set_relationship
|
||||||
|
render :relationship
|
||||||
end
|
end
|
||||||
|
|
||||||
def unmute
|
def unmute
|
||||||
UnmuteService.new.call(current_user.account, @account)
|
UnmuteService.new.call(current_user.account, @account)
|
||||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships
|
set_relationship
|
||||||
|
render :relationship
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -48,7 +59,12 @@ class Api::V1::AccountsController < Api::BaseController
|
|||||||
@account = Account.find(params[:id])
|
@account = Account.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def relationships
|
def set_relationship
|
||||||
AccountRelationshipsPresenter.new([@account.id], current_user.account_id)
|
@following = Account.following_map([@account.id], current_user.account_id)
|
||||||
|
@followed_by = Account.followed_by_map([@account.id], current_user.account_id)
|
||||||
|
@blocking = Account.blocking_map([@account.id], current_user.account_id)
|
||||||
|
@muting = Account.muting_map([@account.id], current_user.account_id)
|
||||||
|
@requested = Account.requested_map([@account.id], current_user.account_id)
|
||||||
|
@domain_blocking = Account.domain_blocking_map([@account.id], current_user.account_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ class Api::V1::AppsController < Api::BaseController
|
|||||||
|
|
||||||
def create
|
def create
|
||||||
@app = Doorkeeper::Application.create!(application_options)
|
@app = Doorkeeper::Application.create!(application_options)
|
||||||
render json: @app, serializer: REST::ApplicationSerializer
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ class Api::V1::BlocksController < Api::BaseController
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
@accounts = load_accounts
|
@accounts = load_accounts
|
||||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -9,18 +9,21 @@ class Api::V1::FavouritesController < Api::BaseController
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
@statuses = load_statuses
|
@statuses = load_statuses
|
||||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def load_statuses
|
def load_statuses
|
||||||
cached_favourites
|
cached_favourites.tap do |statuses|
|
||||||
|
set_maps(statuses)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def cached_favourites
|
def cached_favourites
|
||||||
cache_collection(
|
cache_collection(
|
||||||
Status.reorder(nil).joins(:favourites).merge(results),
|
Status.where(
|
||||||
|
id: results.map(&:status_id)
|
||||||
|
),
|
||||||
Status
|
Status
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ class Api::V1::FollowRequestsController < Api::BaseController
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
@accounts = load_accounts
|
@accounts = load_accounts
|
||||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def authorize
|
def authorize
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class Api::V1::FollowsController < Api::BaseController
|
|||||||
raise ActiveRecord::RecordNotFound if follow_params[:uri].blank?
|
raise ActiveRecord::RecordNotFound if follow_params[:uri].blank?
|
||||||
|
|
||||||
@account = FollowService.new.call(current_user.account, target_uri).try(:target_account)
|
@account = FollowService.new.call(current_user.account, target_uri).try(:target_account)
|
||||||
render json: @account, serializer: REST::AccountSerializer
|
render :show
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -3,7 +3,5 @@
|
|||||||
class Api::V1::InstancesController < Api::BaseController
|
class Api::V1::InstancesController < Api::BaseController
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def show
|
def show; end
|
||||||
render json: {}, serializer: REST::InstanceSerializer
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ class Api::V1::MediaController < Api::BaseController
|
|||||||
|
|
||||||
def create
|
def create
|
||||||
@media = current_account.media_attachments.create!(file: media_params[:file])
|
@media = current_account.media_attachments.create!(file: media_params[:file])
|
||||||
render json: @media, serializer: REST::MediaAttachmentSerializer
|
|
||||||
rescue Paperclip::Errors::NotIdentifiedByImageMagickError
|
rescue Paperclip::Errors::NotIdentifiedByImageMagickError
|
||||||
render json: file_type_error, status: 422
|
render json: file_type_error, status: 422
|
||||||
rescue Paperclip::Error
|
rescue Paperclip::Error
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ class Api::V1::MutesController < Api::BaseController
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
@accounts = load_accounts
|
@accounts = load_accounts
|
||||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -11,12 +11,11 @@ class Api::V1::NotificationsController < Api::BaseController
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
@notifications = load_notifications
|
@notifications = load_notifications
|
||||||
render json: @notifications, each_serializer: REST::NotificationSerializer, relationships: StatusRelationshipsPresenter.new(target_statuses_from_notifications, current_user&.account_id)
|
set_maps_for_notification_target_statuses
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@notification = current_account.notifications.find(params[:id])
|
@notification = current_account.notifications.find(params[:id])
|
||||||
render json: @notification, serializer: REST::NotificationSerializer
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def clear
|
def clear
|
||||||
@@ -24,20 +23,11 @@ class Api::V1::NotificationsController < Api::BaseController
|
|||||||
render_empty
|
render_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
|
||||||
dismiss
|
|
||||||
end
|
|
||||||
|
|
||||||
def dismiss
|
def dismiss
|
||||||
current_account.notifications.find_by!(id: params[:id]).destroy!
|
current_account.notifications.find_by!(id: params[:id]).destroy!
|
||||||
render_empty
|
render_empty
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy_multiple
|
|
||||||
current_account.notifications.where(id: params[:ids]).destroy_all
|
|
||||||
render_empty
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def load_notifications
|
def load_notifications
|
||||||
@@ -56,6 +46,10 @@ class Api::V1::NotificationsController < Api::BaseController
|
|||||||
current_account.notifications.browserable(exclude_types)
|
current_account.notifications.browserable(exclude_types)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_maps_for_notification_target_statuses
|
||||||
|
set_maps target_statuses_from_notifications
|
||||||
|
end
|
||||||
|
|
||||||
def target_statuses_from_notifications
|
def target_statuses_from_notifications
|
||||||
@notifications.reject { |notification| notification.target_status.nil? }.map(&:target_status)
|
@notifications.reject { |notification| notification.target_status.nil? }.map(&:target_status)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ class Api::V1::ReportsController < Api::BaseController
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
@reports = current_account.reports
|
@reports = current_account.reports
|
||||||
render json: @reports, each_serializer: REST::ReportSerializer
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@@ -21,7 +20,7 @@ class Api::V1::ReportsController < Api::BaseController
|
|||||||
|
|
||||||
User.admins.includes(:account).each { |u| AdminMailer.new_report(u.account, @report).deliver_later }
|
User.admins.includes(:account).each { |u| AdminMailer.new_report(u.account, @report).deliver_later }
|
||||||
|
|
||||||
render json: @report, serializer: REST::ReportSerializer
|
render :show
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::SearchController < Api::BaseController
|
class Api::V1::SearchController < Api::BaseController
|
||||||
RESULTS_LIMIT = 10
|
RESULTS_LIMIT = 5
|
||||||
|
|
||||||
before_action -> { doorkeeper_authorize! :read }
|
|
||||||
before_action :require_user!
|
|
||||||
|
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@search = Search.new(search_results)
|
@search = OpenStruct.new(search_results)
|
||||||
render json: @search, serializer: REST::SearchSerializer
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
@accounts = load_accounts
|
@accounts = load_accounts
|
||||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
render 'api/v1/statuses/accounts'
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController
|
|||||||
|
|
||||||
def create
|
def create
|
||||||
@status = favourited_status
|
@status = favourited_status
|
||||||
render json: @status, serializer: REST::StatusSerializer
|
render 'api/v1/statuses/show'
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@@ -19,7 +19,7 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController
|
|||||||
|
|
||||||
UnfavouriteWorker.perform_async(current_user.account_id, @status.id)
|
UnfavouriteWorker.perform_async(current_user.account_id, @status.id)
|
||||||
|
|
||||||
render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_user&.account_id, favourites_map: @favourites_map)
|
render 'api/v1/statuses/show'
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -14,14 +14,14 @@ class Api::V1::Statuses::MutesController < Api::BaseController
|
|||||||
current_account.mute_conversation!(@conversation)
|
current_account.mute_conversation!(@conversation)
|
||||||
@mutes_map = { @conversation.id => true }
|
@mutes_map = { @conversation.id => true }
|
||||||
|
|
||||||
render json: @status, serializer: REST::StatusSerializer
|
render 'api/v1/statuses/show'
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
current_account.unmute_conversation!(@conversation)
|
current_account.unmute_conversation!(@conversation)
|
||||||
@mutes_map = { @conversation.id => false }
|
@mutes_map = { @conversation.id => false }
|
||||||
|
|
||||||
render json: @status, serializer: REST::StatusSerializer
|
render 'api/v1/statuses/show'
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
@accounts = load_accounts
|
@accounts = load_accounts
|
||||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
render 'api/v1/statuses/accounts'
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
|
|||||||
|
|
||||||
def create
|
def create
|
||||||
@status = ReblogService.new.call(current_user.account, status_for_reblog)
|
@status = ReblogService.new.call(current_user.account, status_for_reblog)
|
||||||
render json: @status, serializer: REST::StatusSerializer
|
render 'api/v1/statuses/show'
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@@ -20,7 +20,7 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
|
|||||||
authorize status_for_destroy, :unreblog?
|
authorize status_for_destroy, :unreblog?
|
||||||
RemovalWorker.perform_async(status_for_destroy.id)
|
RemovalWorker.perform_async(status_for_destroy.id)
|
||||||
|
|
||||||
render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_user&.account_id, reblogs_map: @reblogs_map)
|
render 'api/v1/statuses/show'
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ class Api::V1::StatusesController < Api::BaseController
|
|||||||
def show
|
def show
|
||||||
cached = Rails.cache.read(@status.cache_key)
|
cached = Rails.cache.read(@status.cache_key)
|
||||||
@status = cached unless cached.nil?
|
@status = cached unless cached.nil?
|
||||||
render json: @status, serializer: REST::StatusSerializer
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def context
|
def context
|
||||||
@@ -22,20 +21,15 @@ class Api::V1::StatusesController < Api::BaseController
|
|||||||
loaded_ancestors = cache_collection(ancestors_results, Status)
|
loaded_ancestors = cache_collection(ancestors_results, Status)
|
||||||
loaded_descendants = cache_collection(descendants_results, Status)
|
loaded_descendants = cache_collection(descendants_results, Status)
|
||||||
|
|
||||||
@context = Context.new(ancestors: loaded_ancestors, descendants: loaded_descendants)
|
@context = OpenStruct.new(ancestors: loaded_ancestors, descendants: loaded_descendants)
|
||||||
statuses = [@status] + @context.ancestors + @context.descendants
|
statuses = [@status] + @context[:ancestors] + @context[:descendants]
|
||||||
|
|
||||||
render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id)
|
set_maps(statuses)
|
||||||
end
|
end
|
||||||
|
|
||||||
def card
|
def card
|
||||||
@card = PreviewCard.find_by(status: @status)
|
@card = PreviewCard.find_by(status: @status)
|
||||||
|
render_empty if @card.nil?
|
||||||
if @card.nil?
|
|
||||||
render_empty
|
|
||||||
else
|
|
||||||
render json: @card, serializer: REST::PreviewCardSerializer
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@@ -49,7 +43,7 @@ class Api::V1::StatusesController < Api::BaseController
|
|||||||
application: doorkeeper_token.application,
|
application: doorkeeper_token.application,
|
||||||
idempotency: request.headers['Idempotency-Key'])
|
idempotency: request.headers['Idempotency-Key'])
|
||||||
|
|
||||||
render json: @status, serializer: REST::StatusSerializer
|
render :show
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
|
|||||||
@@ -9,13 +9,15 @@ class Api::V1::Timelines::HomeController < Api::BaseController
|
|||||||
|
|
||||||
def show
|
def show
|
||||||
@statuses = load_statuses
|
@statuses = load_statuses
|
||||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
render 'api/v1/timelines/show'
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def load_statuses
|
def load_statuses
|
||||||
cached_home_statuses
|
cached_home_statuses.tap do |statuses|
|
||||||
|
set_maps(statuses)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def cached_home_statuses
|
def cached_home_statuses
|
||||||
|
|||||||
@@ -7,13 +7,15 @@ class Api::V1::Timelines::PublicController < Api::BaseController
|
|||||||
|
|
||||||
def show
|
def show
|
||||||
@statuses = load_statuses
|
@statuses = load_statuses
|
||||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
render 'api/v1/timelines/show'
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def load_statuses
|
def load_statuses
|
||||||
cached_public_statuses
|
cached_public_statuses.tap do |statuses|
|
||||||
|
set_maps(statuses)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def cached_public_statuses
|
def cached_public_statuses
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class Api::V1::Timelines::TagController < Api::BaseController
|
|||||||
|
|
||||||
def show
|
def show
|
||||||
@statuses = load_statuses
|
@statuses = load_statuses
|
||||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
render 'api/v1/timelines/show'
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -18,7 +18,9 @@ class Api::V1::Timelines::TagController < Api::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def load_statuses
|
def load_statuses
|
||||||
cached_tagged_statuses
|
cached_tagged_statuses.tap do |statuses|
|
||||||
|
set_maps(statuses)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def cached_tagged_statuses
|
def cached_tagged_statuses
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Api::Web::PushSubscriptionsController < Api::BaseController
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
before_action :require_user!
|
|
||||||
|
|
||||||
def create
|
|
||||||
params.require(:subscription).require(:endpoint)
|
|
||||||
params.require(:subscription).require(:keys).require([:auth, :p256dh])
|
|
||||||
|
|
||||||
active_session = current_session
|
|
||||||
|
|
||||||
unless active_session.web_push_subscription.nil?
|
|
||||||
active_session.web_push_subscription.destroy!
|
|
||||||
active_session.update!(web_push_subscription: nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Mobile devices do not support regular notifications, so we enable push notifications by default
|
|
||||||
alerts_enabled = active_session.detection.device.mobile? || active_session.detection.device.tablet?
|
|
||||||
|
|
||||||
data = {
|
|
||||||
alerts: {
|
|
||||||
follow: alerts_enabled,
|
|
||||||
favourite: alerts_enabled,
|
|
||||||
reblog: alerts_enabled,
|
|
||||||
mention: alerts_enabled,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
web_subscription = ::Web::PushSubscription.create!(
|
|
||||||
endpoint: params[:subscription][:endpoint],
|
|
||||||
key_p256dh: params[:subscription][:keys][:p256dh],
|
|
||||||
key_auth: params[:subscription][:keys][:auth],
|
|
||||||
data: data
|
|
||||||
)
|
|
||||||
|
|
||||||
active_session.update!(web_push_subscription: web_subscription)
|
|
||||||
|
|
||||||
render json: web_subscription.as_payload
|
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
|
||||||
params.require([:id, :data])
|
|
||||||
|
|
||||||
web_subscription = ::Web::PushSubscription.find(params[:id])
|
|
||||||
|
|
||||||
web_subscription.update!(data: params[:data])
|
|
||||||
|
|
||||||
render json: web_subscription.as_payload
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -43,10 +43,6 @@ class ApplicationController < ActionController::Base
|
|||||||
forbidden if current_user.account.suspended?
|
forbidden if current_user.account.suspended?
|
||||||
end
|
end
|
||||||
|
|
||||||
def after_sign_out_path_for(_resource_or_scope)
|
|
||||||
new_user_session_path
|
|
||||||
end
|
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def forbidden
|
def forbidden
|
||||||
@@ -74,7 +70,7 @@ class ApplicationController < ActionController::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def current_session
|
def current_session
|
||||||
@current_session ||= SessionActivation.find_by(session_id: cookies.signed['_session_id'])
|
@current_session ||= SessionActivation.find_by(session_id: session['auth_id'])
|
||||||
end
|
end
|
||||||
|
|
||||||
def cache_collection(raw, klass)
|
def cache_collection(raw, klass)
|
||||||
|
|||||||
@@ -1,20 +1,5 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Auth::PasswordsController < Devise::PasswordsController
|
class Auth::PasswordsController < Devise::PasswordsController
|
||||||
before_action :check_validity_of_reset_password_token, only: :edit
|
|
||||||
|
|
||||||
layout 'auth'
|
layout 'auth'
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def check_validity_of_reset_password_token
|
|
||||||
unless reset_password_token_is_valid?
|
|
||||||
flash[:error] = I18n.t('auth.invalid_reset_password_token')
|
|
||||||
redirect_to new_password_path(resource_name)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def reset_password_token_is_valid?
|
|
||||||
resource_class.with_reset_password_token(params[:reset_password_token]).present?
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class AuthorizeFollowsController < ApplicationController
|
class AuthorizeFollowsController < ApplicationController
|
||||||
layout 'modal'
|
layout 'public'
|
||||||
|
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ class AuthorizeFollowsController < ApplicationController
|
|||||||
if @account.nil?
|
if @account.nil?
|
||||||
render :error
|
render :error
|
||||||
else
|
else
|
||||||
render :success
|
redirect_to web_url("accounts/#{@account.id}")
|
||||||
end
|
end
|
||||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||||
render :error
|
render :error
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
# Implemented according to HTTP signatures (Draft 6)
|
|
||||||
# <https://tools.ietf.org/html/draft-cavage-http-signatures-06>
|
|
||||||
module SignatureVerification
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
def signed_request?
|
|
||||||
request.headers['Signature'].present?
|
|
||||||
end
|
|
||||||
|
|
||||||
def signed_request_account
|
|
||||||
return @signed_request_account if defined?(@signed_request_account)
|
|
||||||
|
|
||||||
unless signed_request?
|
|
||||||
@signed_request_account = nil
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
raw_signature = request.headers['Signature']
|
|
||||||
signature_params = {}
|
|
||||||
|
|
||||||
raw_signature.split(',').each do |part|
|
|
||||||
parsed_parts = part.match(/([a-z]+)="([^"]+)"/i)
|
|
||||||
next if parsed_parts.nil? || parsed_parts.size != 3
|
|
||||||
signature_params[parsed_parts[1]] = parsed_parts[2]
|
|
||||||
end
|
|
||||||
|
|
||||||
if incompatible_signature?(signature_params)
|
|
||||||
@signed_request_account = nil
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
account = ResolveRemoteAccountService.new.call(signature_params['keyId'].gsub(/\Aacct:/, ''))
|
|
||||||
|
|
||||||
if account.nil?
|
|
||||||
@signed_request_account = nil
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
signature = Base64.decode64(signature_params['signature'])
|
|
||||||
compare_signed_string = build_signed_string(signature_params['headers'])
|
|
||||||
|
|
||||||
if account.keypair.public_key.verify(OpenSSL::Digest::SHA256.new, signature, compare_signed_string)
|
|
||||||
@signed_request_account = account
|
|
||||||
@signed_request_account
|
|
||||||
else
|
|
||||||
@signed_request_account = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def build_signed_string(signed_headers)
|
|
||||||
signed_headers = 'date' if signed_headers.blank?
|
|
||||||
|
|
||||||
signed_headers.split(' ').map do |signed_header|
|
|
||||||
if signed_header == Request::REQUEST_TARGET
|
|
||||||
"#{Request::REQUEST_TARGET}: #{request.method.downcase} #{request.path}"
|
|
||||||
else
|
|
||||||
"#{signed_header}: #{request.headers[to_header_name(signed_header)]}"
|
|
||||||
end
|
|
||||||
end.join("\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
def matches_time_window?
|
|
||||||
begin
|
|
||||||
time_sent = DateTime.httpdate(request.headers['Date'])
|
|
||||||
rescue ArgumentError
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
(Time.now.utc - time_sent).abs <= 30
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_header_name(name)
|
|
||||||
name.split(/-/).map(&:capitalize).join('-')
|
|
||||||
end
|
|
||||||
|
|
||||||
def incompatible_signature?(signature_params)
|
|
||||||
signature_params['keyId'].blank? ||
|
|
||||||
signature_params['signature'].blank? ||
|
|
||||||
signature_params['algorithm'].blank? ||
|
|
||||||
signature_params['algorithm'] != 'rsa-sha256' ||
|
|
||||||
!signature_params['keyId'].start_with?('acct:')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -5,24 +5,5 @@ class FollowerAccountsController < ApplicationController
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
@follows = Follow.where(target_account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account)
|
@follows = Follow.where(target_account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account)
|
||||||
|
|
||||||
respond_to do |format|
|
|
||||||
format.html
|
|
||||||
|
|
||||||
format.json do
|
|
||||||
render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def collection_presenter
|
|
||||||
ActivityPub::CollectionPresenter.new(
|
|
||||||
id: account_followers_url(@account),
|
|
||||||
type: :ordered,
|
|
||||||
size: @account.followers_count,
|
|
||||||
items: @follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.account) }
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,24 +5,5 @@ class FollowingAccountsController < ApplicationController
|
|||||||
|
|
||||||
def index
|
def index
|
||||||
@follows = Follow.where(account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account)
|
@follows = Follow.where(account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account)
|
||||||
|
|
||||||
respond_to do |format|
|
|
||||||
format.html
|
|
||||||
|
|
||||||
format.json do
|
|
||||||
render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def collection_presenter
|
|
||||||
ActivityPub::CollectionPresenter.new(
|
|
||||||
id: account_following_index_url(@account),
|
|
||||||
type: :ordered,
|
|
||||||
size: @account.following_count,
|
|
||||||
items: @follows.map { |f| ActivityPub::TagManager.instance.uri_for(f.target_account) }
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,11 +2,13 @@
|
|||||||
|
|
||||||
class HomeController < ApplicationController
|
class HomeController < ApplicationController
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
before_action :set_initial_state_json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@body_classes = 'app-body'
|
@body_classes = 'app-body'
|
||||||
@frontend = (params[:frontend] and Rails.configuration.x.available_frontends.include? params[:frontend] + '.js') ? params[:frontend] : 'mastodon'
|
@token = current_session.token
|
||||||
|
@web_settings = Web::Setting.find_by(user: current_user)&.data || {}
|
||||||
|
@admin = Account.find_local(Setting.site_contact_username)
|
||||||
|
@streaming_api_base_url = Rails.configuration.x.streaming_api_base_url
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -14,19 +16,4 @@ class HomeController < ApplicationController
|
|||||||
def authenticate_user!
|
def authenticate_user!
|
||||||
redirect_to(single_user_mode? ? account_path(Account.first) : about_path) unless user_signed_in?
|
redirect_to(single_user_mode? ? account_path(Account.first) : about_path) unless user_signed_in?
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_initial_state_json
|
|
||||||
serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer)
|
|
||||||
@initial_state_json = serializable_resource.to_json
|
|
||||||
end
|
|
||||||
|
|
||||||
def initial_state_params
|
|
||||||
{
|
|
||||||
settings: Web::Setting.find_by(user: current_user)&.data || {},
|
|
||||||
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),
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class RemoteFollowController < ApplicationController
|
class RemoteFollowController < ApplicationController
|
||||||
layout 'modal'
|
layout 'public'
|
||||||
|
|
||||||
before_action :set_account
|
before_action :set_account
|
||||||
before_action :gone, if: :suspended_account?
|
before_action :gone, if: :suspended_account?
|
||||||
|
|||||||
@@ -34,13 +34,9 @@ class Settings::PreferencesController < ApplicationController
|
|||||||
def user_settings_params
|
def user_settings_params
|
||||||
params.require(:user).permit(
|
params.require(:user).permit(
|
||||||
:setting_default_privacy,
|
:setting_default_privacy,
|
||||||
:setting_default_sensitive,
|
|
||||||
:setting_unfollow_modal,
|
|
||||||
:setting_boost_modal,
|
:setting_boost_modal,
|
||||||
:setting_delete_modal,
|
:setting_delete_modal,
|
||||||
:setting_auto_play_gif,
|
:setting_auto_play_gif,
|
||||||
:setting_system_font_ui,
|
|
||||||
:setting_noindex,
|
|
||||||
notification_emails: %i(follow follow_request reblog favourite mention digest),
|
notification_emails: %i(follow follow_request reblog favourite mention digest),
|
||||||
interactions: %i(must_be_follower must_be_following)
|
interactions: %i(must_be_follower must_be_following)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Settings::SessionsController < ApplicationController
|
|
||||||
before_action :set_session, only: :destroy
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
@session.destroy!
|
|
||||||
flash[:notice] = I18n.t('sessions.revoke_success')
|
|
||||||
redirect_to edit_user_registration_path
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def set_session
|
|
||||||
@session = current_user.session_activations.find(params[:id])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -18,7 +18,7 @@ module Settings
|
|||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
if acceptable_code?
|
if current_user.validate_and_consume_otp!(confirmation_params[:code])
|
||||||
current_user.otp_required_for_login = false
|
current_user.otp_required_for_login = false
|
||||||
current_user.save!
|
current_user.save!
|
||||||
redirect_to settings_two_factor_authentication_path
|
redirect_to settings_two_factor_authentication_path
|
||||||
@@ -38,10 +38,5 @@ module Settings
|
|||||||
def verify_otp_required
|
def verify_otp_required
|
||||||
redirect_to settings_two_factor_authentication_path if current_user.otp_required_for_login?
|
redirect_to settings_two_factor_authentication_path if current_user.otp_required_for_login?
|
||||||
end
|
end
|
||||||
|
|
||||||
def acceptable_code?
|
|
||||||
current_user.validate_and_consume_otp!(confirmation_params[:code]) ||
|
|
||||||
current_user.invalidate_otp_backup_code!(confirmation_params[:code])
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -11,22 +11,10 @@ class StatusesController < ApplicationController
|
|||||||
before_action :check_account_suspension
|
before_action :check_account_suspension
|
||||||
|
|
||||||
def show
|
def show
|
||||||
respond_to do |format|
|
@ancestors = @status.reply? ? cache_collection(@status.ancestors(current_account), Status) : []
|
||||||
format.html do
|
@descendants = cache_collection(@status.descendants(current_account), Status)
|
||||||
@ancestors = @status.reply? ? cache_collection(@status.ancestors(current_account), Status) : []
|
|
||||||
@descendants = cache_collection(@status.descendants(current_account), Status)
|
|
||||||
|
|
||||||
render 'stream_entries/show'
|
render 'stream_entries/show'
|
||||||
end
|
|
||||||
|
|
||||||
format.json do
|
|
||||||
render json: @status, serializer: ActivityPub::NoteSerializer, adapter: ActivityPub::Adapter
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def activity
|
|
||||||
render json: @status, serializer: ActivityPub::ActivitySerializer, adapter: ActivityPub::Adapter
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
class StreamEntriesController < ApplicationController
|
class StreamEntriesController < ApplicationController
|
||||||
include Authorization
|
include Authorization
|
||||||
include SignatureVerification
|
|
||||||
|
|
||||||
layout 'public'
|
layout 'public'
|
||||||
|
|
||||||
@@ -19,7 +18,7 @@ class StreamEntriesController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
format.atom do
|
format.atom do
|
||||||
render xml: OStatus::AtomSerializer.render(OStatus::AtomSerializer.new.entry(@stream_entry, true))
|
render xml: AtomSerializer.render(AtomSerializer.new.entry(@stream_entry, true))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,26 +5,7 @@ class TagsController < ApplicationController
|
|||||||
|
|
||||||
def show
|
def show
|
||||||
@tag = Tag.find_by!(name: params[:id].downcase)
|
@tag = Tag.find_by!(name: params[:id].downcase)
|
||||||
@statuses = Status.as_tag_timeline(@tag, current_account, params[:local]).paginate_by_max_id(20, params[:max_id])
|
@statuses = @tag.nil? ? [] : Status.as_tag_timeline(@tag, current_account, params[:local]).paginate_by_max_id(20, params[:max_id])
|
||||||
@statuses = cache_collection(@statuses, Status)
|
@statuses = cache_collection(@statuses, Status)
|
||||||
|
|
||||||
respond_to do |format|
|
|
||||||
format.html
|
|
||||||
|
|
||||||
format.json do
|
|
||||||
render json: collection_presenter, serializer: ActivityPub::CollectionSerializer, adapter: ActivityPub::Adapter
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def collection_presenter
|
|
||||||
ActivityPub::CollectionPresenter.new(
|
|
||||||
id: tag_url(@tag),
|
|
||||||
type: :ordered,
|
|
||||||
size: @tag.statuses.count,
|
|
||||||
items: @statuses.map { |s| ActivityPub::TagManager.instance.uri_for(s) }
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
8
app/helpers/activitystreams2_builder_helper.rb
Normal file
8
app/helpers/activitystreams2_builder_helper.rb
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Activitystreams2BuilderHelper
|
||||||
|
# Gets a usable name for an account, using display name or username.
|
||||||
|
def account_name(account)
|
||||||
|
account.display_name.presence || account.username
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -6,21 +6,15 @@ module Admin::FilterHelper
|
|||||||
|
|
||||||
FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS
|
FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS
|
||||||
|
|
||||||
def filter_link_to(text, link_to_params, link_class_params = link_to_params)
|
def filter_link_to(text, more_params)
|
||||||
new_url = filtered_url_for(link_to_params)
|
new_url = filtered_url_for(more_params)
|
||||||
new_class = filtered_url_for(link_class_params)
|
link_to text, new_url, class: filter_link_class(new_url)
|
||||||
link_to text, new_url, class: filter_link_class(new_class)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def table_link_to(icon, text, path, options = {})
|
def table_link_to(icon, text, path, options = {})
|
||||||
link_to safe_join([fa_icon(icon), text]), path, options.merge(class: 'table-action-link')
|
link_to safe_join([fa_icon(icon), text]), path, options.merge(class: 'table-action-link')
|
||||||
end
|
end
|
||||||
|
|
||||||
def selected?(more_params)
|
|
||||||
new_url = filtered_url_for(more_params)
|
|
||||||
filter_link_class(new_url) == 'selected' ? true : false
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def filter_params(more_params)
|
def filter_params(more_params)
|
||||||
|
|||||||
@@ -31,11 +31,7 @@ module ApplicationHelper
|
|||||||
Rails.env.production? ? site_title : "#{site_title} (Dev)"
|
Rails.env.production? ? site_title : "#{site_title} (Dev)"
|
||||||
end
|
end
|
||||||
|
|
||||||
def fa_icon(icon, attributes = {})
|
def fa_icon(icon)
|
||||||
class_names = attributes[:class]&.split(' ') || []
|
content_tag(:i, nil, class: 'fa ' + icon.split(' ').map { |cl| "fa-#{cl}" }.join(' '))
|
||||||
class_names << 'fa'
|
|
||||||
class_names += icon.split(' ').map { |cl| "fa-#{cl}" }
|
|
||||||
|
|
||||||
content_tag(:i, nil, attributes.merge(class: class_names.join(' ')))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module EmojiHelper
|
|
||||||
def emojify(text)
|
|
||||||
return text if text.blank?
|
|
||||||
|
|
||||||
text.gsub(emoji_pattern) do |match|
|
|
||||||
emoji = Emoji.instance.unicode($1) # rubocop:disable Style/PerlBackrefs
|
|
||||||
|
|
||||||
if emoji
|
|
||||||
emoji
|
|
||||||
else
|
|
||||||
match
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def emoji_pattern
|
|
||||||
@emoji_pattern ||=
|
|
||||||
/(?<=[^[:alnum:]:]|\n|^)
|
|
||||||
(#{Emoji.instance.names.map { |name| Regexp.escape(name) }.join('|')})
|
|
||||||
(?=[^[:alnum:]:]|$)/x
|
|
||||||
end
|
|
||||||
end
|
|
||||||
17
app/helpers/http_helper.rb
Normal file
17
app/helpers/http_helper.rb
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module HttpHelper
|
||||||
|
def http_client(options = {})
|
||||||
|
timeout = { write: 10, connect: 10, read: 10 }.merge(options)
|
||||||
|
|
||||||
|
HTTP.headers(user_agent: user_agent)
|
||||||
|
.timeout(:per_operation, timeout)
|
||||||
|
.follow
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def user_agent
|
||||||
|
@user_agent ||= "#{HTTP::Request::USER_AGENT} (Mastodon/#{Mastodon::Version}; +http://#{Rails.configuration.x.local_domain}/)"
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
module InstanceHelper
|
module InstanceHelper
|
||||||
def site_title
|
def site_title
|
||||||
Setting.site_title.presence || site_hostname
|
Setting.site_title.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
def site_hostname
|
def site_hostname
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ module RoutingHelper
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def full_asset_url(source, options = {})
|
def full_asset_url(source)
|
||||||
Rails.configuration.x.use_s3 ? source : URI.join(root_url, ActionController::Base.helpers.asset_url(source, options)).to_s
|
Rails.configuration.x.use_s3 ? source : URI.join(root_url, ActionController::Base.helpers.asset_url(source)).to_s
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ module SettingsHelper
|
|||||||
io: 'Ido',
|
io: 'Ido',
|
||||||
it: 'Italiano',
|
it: 'Italiano',
|
||||||
ja: '日本語',
|
ja: '日本語',
|
||||||
ko: '한국어',
|
|
||||||
nl: 'Nederlands',
|
nl: 'Nederlands',
|
||||||
no: 'Norsk',
|
no: 'Norsk',
|
||||||
oc: 'Occitan',
|
oc: 'Occitan',
|
||||||
|
|||||||
Binary file not shown.
@@ -1,93 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
`actions/local_settings`
|
|
||||||
========================
|
|
||||||
|
|
||||||
> For more information on the contents of this file, please contact:
|
|
||||||
>
|
|
||||||
> - kibigo! [@kibi@glitch.social]
|
|
||||||
|
|
||||||
This file provides our Redux actions related to local settings. It
|
|
||||||
consists of the following:
|
|
||||||
|
|
||||||
- __`changesLocalSetting(key, value)` :__
|
|
||||||
Changes the local setting with the given `key` to the given
|
|
||||||
`value`. `key` **MUST** be an array of strings, as required by
|
|
||||||
`Immutable.Map.prototype.getIn()`.
|
|
||||||
|
|
||||||
- __`saveLocalSettings()` :__
|
|
||||||
Saves the local settings to `localStorage` as a JSON object. We
|
|
||||||
shouldn't ever need to call this ourselves.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Constants:
|
|
||||||
----------
|
|
||||||
|
|
||||||
We provide the following constants:
|
|
||||||
|
|
||||||
- __`LOCAL_SETTING_CHANGE` :__
|
|
||||||
This string constant is used to dispatch a setting change to our
|
|
||||||
reducer in `reducers/local_settings`, where the setting is
|
|
||||||
actually changed.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const LOCAL_SETTING_CHANGE = 'LOCAL_SETTING_CHANGE';
|
|
||||||
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
`changeLocalSetting(key, value)`:
|
|
||||||
---------------------------------
|
|
||||||
|
|
||||||
Changes the local setting with the given `key` to the given `value`.
|
|
||||||
`key` **MUST** be an array of strings, as required by
|
|
||||||
`Immutable.Map.prototype.getIn()`.
|
|
||||||
|
|
||||||
To accomplish this, we just dispatch a `LOCAL_SETTING_CHANGE` to our
|
|
||||||
reducer in `reducers/local_settings`.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function changeLocalSetting(key, value) {
|
|
||||||
return dispatch => {
|
|
||||||
dispatch({
|
|
||||||
type: LOCAL_SETTING_CHANGE,
|
|
||||||
key,
|
|
||||||
value,
|
|
||||||
});
|
|
||||||
|
|
||||||
dispatch(saveLocalSettings());
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
`saveLocalSettings()`:
|
|
||||||
----------------------
|
|
||||||
|
|
||||||
Saves the local settings to `localStorage` as a JSON object.
|
|
||||||
`changeLocalSetting()` calls this whenever it changes a setting. We
|
|
||||||
shouldn't ever need to call this ourselves.
|
|
||||||
|
|
||||||
> __TODO :__
|
|
||||||
> Right now `saveLocalSettings()` doesn't keep track of which user
|
|
||||||
> is currently signed in, but it might be better to give each user
|
|
||||||
> their *own* local settings.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function saveLocalSettings() {
|
|
||||||
return (_, getState) => {
|
|
||||||
const localSettings = getState().get('local_settings').toJS();
|
|
||||||
localStorage.setItem('mastodon-settings', JSON.stringify(localSettings));
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,237 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
`<AccountHeader>`
|
|
||||||
=================
|
|
||||||
|
|
||||||
> For more information on the contents of this file, please contact:
|
|
||||||
>
|
|
||||||
> - kibigo! [@kibi@glitch.social]
|
|
||||||
|
|
||||||
Original file by @gargron@mastodon.social et al as part of
|
|
||||||
tootsuite/mastodon. We've expanded it in order to handle user bio
|
|
||||||
frontmatter.
|
|
||||||
|
|
||||||
The `<AccountHeader>` component provides the header for account
|
|
||||||
timelines. It is a fairly simple component which mostly just consists
|
|
||||||
of a `render()` method.
|
|
||||||
|
|
||||||
__Props:__
|
|
||||||
|
|
||||||
- __`account` (`ImmutablePropTypes.map`) :__
|
|
||||||
The account to render a header for.
|
|
||||||
|
|
||||||
- __`me` (`PropTypes.number.isRequired`) :__
|
|
||||||
The id of the currently-signed-in account.
|
|
||||||
|
|
||||||
- __`onFollow` (`PropTypes.func.isRequired`) :__
|
|
||||||
The function to call when the user clicks the "follow" button.
|
|
||||||
|
|
||||||
- __`intl` (`PropTypes.object.isRequired`) :__
|
|
||||||
Our internationalization object, inserted by `@injectIntl`.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Imports:
|
|
||||||
--------
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Package imports //
|
|
||||||
import React from 'react';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import escapeTextContentForBrowser from 'escape-html';
|
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
|
|
||||||
// Mastodon imports //
|
|
||||||
import emojify from '../../../mastodon/emoji';
|
|
||||||
import IconButton from '../../../mastodon/components/icon_button';
|
|
||||||
import Avatar from '../../../mastodon/components/avatar';
|
|
||||||
|
|
||||||
// Our imports //
|
|
||||||
import { processBio } from '../../util/bio_metadata';
|
|
||||||
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Inital setup:
|
|
||||||
-------------
|
|
||||||
|
|
||||||
The `messages` constant is used to define any messages that we need
|
|
||||||
from inside props. In our case, these are the `unfollow`, `follow`, and
|
|
||||||
`requested` messages used in the `title` of our buttons.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
|
||||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
|
||||||
requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' },
|
|
||||||
});
|
|
||||||
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Implementation:
|
|
||||||
---------------
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
@injectIntl
|
|
||||||
export default class AccountHeader extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
account : ImmutablePropTypes.map,
|
|
||||||
me : PropTypes.number.isRequired,
|
|
||||||
onFollow : PropTypes.func.isRequired,
|
|
||||||
intl : PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
### `render()`
|
|
||||||
|
|
||||||
The `render()` function is used to render our component.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { account, me, intl } = this.props;
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
If no `account` is provided, then we can't render a header. Otherwise,
|
|
||||||
we get the `displayName` for the account, if available. If it's blank,
|
|
||||||
then we set the `displayName` to just be the `username` of the account.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (!account) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let displayName = account.get('display_name');
|
|
||||||
let info = '';
|
|
||||||
let actionBtn = '';
|
|
||||||
let following = false;
|
|
||||||
|
|
||||||
if (displayName.length === 0) {
|
|
||||||
displayName = account.get('username');
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Next, we handle the account relationships. If the account follows the
|
|
||||||
user, then we add an `info` message. If the user has requested a
|
|
||||||
follow, then we disable the `actionBtn` and display an hourglass.
|
|
||||||
Otherwise, if the account isn't blocked, we set the `actionBtn` to the
|
|
||||||
appropriate icon.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
if (me !== account.get('id')) {
|
|
||||||
if (account.getIn(['relationship', 'followed_by'])) {
|
|
||||||
info = (
|
|
||||||
<span className='account--follows-info'>
|
|
||||||
<FormattedMessage id='account.follows_you' defaultMessage='Follows you' />
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (account.getIn(['relationship', 'requested'])) {
|
|
||||||
actionBtn = (
|
|
||||||
<div className='account--action-button'>
|
|
||||||
<IconButton size={26} disabled icon='hourglass' title={intl.formatMessage(messages.requested)} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else if (!account.getIn(['relationship', 'blocking'])) {
|
|
||||||
following = account.getIn(['relationship', 'following']);
|
|
||||||
actionBtn = (
|
|
||||||
<div className='account--action-button'>
|
|
||||||
<IconButton
|
|
||||||
size={26}
|
|
||||||
icon={following ? 'user-times' : 'user-plus'}
|
|
||||||
active={following}
|
|
||||||
title={intl.formatMessage(following ? messages.unfollow : messages.follow)}
|
|
||||||
onClick={this.props.onFollow}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
`displayNameHTML` processes the `displayName` and prepares it for
|
|
||||||
insertion into the document. Meanwhile, we extract the `text` and
|
|
||||||
`metadata` from our account's `note` using `processBio()`.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
const displayNameHTML = {
|
|
||||||
__html : emojify(escapeTextContentForBrowser(displayName)),
|
|
||||||
};
|
|
||||||
const { text, metadata } = processBio(account.get('note'));
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Here, we render our component using all the things we've defined above.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='account__header__wrapper'>
|
|
||||||
<div
|
|
||||||
className='account__header'
|
|
||||||
style={{ backgroundImage: `url(${account.get('header')})` }}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<a href={account.get('url')} target='_blank' rel='noopener'>
|
|
||||||
<span className='account__header__avatar'>
|
|
||||||
<Avatar account={account} size={90} />
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className='account__header__display-name'
|
|
||||||
dangerouslySetInnerHTML={displayNameHTML}
|
|
||||||
/>
|
|
||||||
</a>
|
|
||||||
<span className='account__header__username'>
|
|
||||||
@{account.get('acct')}
|
|
||||||
{account.get('locked') ? <i className='fa fa-lock' /> : null}
|
|
||||||
</span>
|
|
||||||
<div className='account__header__content' dangerouslySetInnerHTML={{ __html: emojify(text) }} />
|
|
||||||
|
|
||||||
{info}
|
|
||||||
{actionBtn}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{metadata.length && (
|
|
||||||
<table className='account__metadata'>
|
|
||||||
<tbody>
|
|
||||||
{(() => {
|
|
||||||
let data = [];
|
|
||||||
for (let i = 0; i < metadata.length; i++) {
|
|
||||||
data.push(
|
|
||||||
<tr key={i}>
|
|
||||||
<th scope='row'><div dangerouslySetInnerHTML={{ __html: emojify(metadata[i][0]) }} /></th>
|
|
||||||
<td><div dangerouslySetInnerHTML={{ __html: emojify(metadata[i][1]) }} /></td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
})()}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
) || null}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
// <CommonAvatar>
|
|
||||||
// ========
|
|
||||||
|
|
||||||
// For code documentation, please see:
|
|
||||||
// https://glitch-soc.github.io/docs/javascript/glitch/common/avatar
|
|
||||||
|
|
||||||
// For more information, please contact:
|
|
||||||
// @kibi@glitch.social
|
|
||||||
|
|
||||||
// * * * * * * * //
|
|
||||||
|
|
||||||
// Imports
|
|
||||||
// -------
|
|
||||||
|
|
||||||
// Package imports.
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
|
|
||||||
// Stylesheet imports.
|
|
||||||
import './style';
|
|
||||||
|
|
||||||
// * * * * * * * //
|
|
||||||
|
|
||||||
// The component
|
|
||||||
// -------------
|
|
||||||
|
|
||||||
export default class CommonAvatar extends React.PureComponent {
|
|
||||||
|
|
||||||
// Props and state.
|
|
||||||
static propTypes = {
|
|
||||||
account: ImmutablePropTypes.map.isRequired,
|
|
||||||
animate: PropTypes.bool,
|
|
||||||
circular: PropTypes.bool,
|
|
||||||
className: PropTypes.string,
|
|
||||||
comrade: ImmutablePropTypes.map,
|
|
||||||
}
|
|
||||||
state = {
|
|
||||||
hovering: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Starts or stops animation on hover.
|
|
||||||
handleMouseEnter = () => {
|
|
||||||
if (this.props.animate) return;
|
|
||||||
this.setState({ hovering: true });
|
|
||||||
}
|
|
||||||
handleMouseLeave = () => {
|
|
||||||
if (this.props.animate) return;
|
|
||||||
this.setState({ hovering: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
// Renders the component.
|
|
||||||
render () {
|
|
||||||
const {
|
|
||||||
handleMouseEnter,
|
|
||||||
handleMouseLeave,
|
|
||||||
} = this;
|
|
||||||
const {
|
|
||||||
account,
|
|
||||||
animate,
|
|
||||||
circular,
|
|
||||||
className,
|
|
||||||
comrade,
|
|
||||||
...others
|
|
||||||
} = this.props;
|
|
||||||
const { hovering } = this.state;
|
|
||||||
const computedClass = classNames('glitch', 'glitch__common__avatar', {
|
|
||||||
_circular: circular,
|
|
||||||
}, className);
|
|
||||||
|
|
||||||
// We store the image srcs here for later.
|
|
||||||
const src = account.get('avatar');
|
|
||||||
const staticSrc = account.get('avatar_static');
|
|
||||||
const comradeSrc = comrade ? comrade.get('avatar') : null;
|
|
||||||
const comradeStaticSrc = comrade ? comrade.get('avatar_static') : null;
|
|
||||||
|
|
||||||
// Avatars are a straightforward div with image(s) inside.
|
|
||||||
return comrade ? (
|
|
||||||
<div
|
|
||||||
className={computedClass}
|
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
{...others}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
className='avatar\main'
|
|
||||||
src={hovering || animate ? src : staticSrc}
|
|
||||||
alt=''
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
className='avatar\comrade'
|
|
||||||
src={hovering || animate ? comradeSrc : comradeStaticSrc}
|
|
||||||
alt=''
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div
|
|
||||||
className={computedClass}
|
|
||||||
onMouseEnter={handleMouseEnter}
|
|
||||||
onMouseLeave={handleMouseLeave}
|
|
||||||
{...others}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
className='avatar\solo'
|
|
||||||
src={hovering || animate ? src : staticSrc}
|
|
||||||
alt=''
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
@import 'variables';
|
|
||||||
|
|
||||||
.glitch.glitch__common__avatar {
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
& > img {
|
|
||||||
display: block;
|
|
||||||
position: static;
|
|
||||||
margin: 0;
|
|
||||||
border-radius: $ui-avatar-border-size;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
&.avatar\\comrade {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 50%;
|
|
||||||
height: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.avatar\\main {
|
|
||||||
margin: 0 30% 30% 0;
|
|
||||||
width: 70%;
|
|
||||||
height: 70%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&._circular {
|
|
||||||
& > img {
|
|
||||||
transition: border-radius ($glitch-animation-speed * .3s);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:not(:hover) {
|
|
||||||
& > img {
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
// <CommonButton>
|
|
||||||
// ========
|
|
||||||
|
|
||||||
// For code documentation, please see:
|
|
||||||
// https://glitch-soc.github.io/docs/javascript/glitch/common/button
|
|
||||||
|
|
||||||
// For more information, please contact:
|
|
||||||
// @kibi@glitch.social
|
|
||||||
|
|
||||||
// * * * * * * * //
|
|
||||||
|
|
||||||
// Imports
|
|
||||||
// -------
|
|
||||||
|
|
||||||
// Package imports.
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
// Our imports.
|
|
||||||
import CommonLink from 'glitch/components/common/link';
|
|
||||||
import CommonIcon from 'glitch/components/common/icon';
|
|
||||||
|
|
||||||
// Stylesheet imports.
|
|
||||||
import './style';
|
|
||||||
|
|
||||||
// * * * * * * * //
|
|
||||||
|
|
||||||
// The component
|
|
||||||
// -------------
|
|
||||||
|
|
||||||
export default class CommonButton extends React.PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
active: PropTypes.bool,
|
|
||||||
animate: PropTypes.bool,
|
|
||||||
children: PropTypes.node,
|
|
||||||
className: PropTypes.string,
|
|
||||||
disabled: PropTypes.bool,
|
|
||||||
href: PropTypes.string,
|
|
||||||
icon: PropTypes.string,
|
|
||||||
onClick: PropTypes.func,
|
|
||||||
showTitle: PropTypes.bool,
|
|
||||||
title: PropTypes.string,
|
|
||||||
}
|
|
||||||
state = {
|
|
||||||
loaded: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
// The `loaded` state property activates our animations. We wait
|
|
||||||
// until an activation change in order to prevent unsightly
|
|
||||||
// animations when the component first mounts.
|
|
||||||
componentWillReceiveProps (nextProps) {
|
|
||||||
const { active } = this.props;
|
|
||||||
|
|
||||||
// The double "not"s here cast both arguments to booleans.
|
|
||||||
if (!nextProps.active !== !active) this.setState({ loaded: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
handleClick = (e) => {
|
|
||||||
const { onClick } = this.props;
|
|
||||||
if (!onClick) return;
|
|
||||||
onClick(e);
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rendering the component.
|
|
||||||
render () {
|
|
||||||
const { handleClick } = this;
|
|
||||||
const {
|
|
||||||
active,
|
|
||||||
animate,
|
|
||||||
children,
|
|
||||||
className,
|
|
||||||
disabled,
|
|
||||||
href,
|
|
||||||
icon,
|
|
||||||
onClick,
|
|
||||||
showTitle,
|
|
||||||
title,
|
|
||||||
...others
|
|
||||||
} = this.props;
|
|
||||||
const { loaded } = this.state;
|
|
||||||
const computedClass = classNames('glitch', 'glitch__common__button', className, {
|
|
||||||
_active: active && !href, // Links can't be active
|
|
||||||
_animated: animate && loaded,
|
|
||||||
_disabled: disabled,
|
|
||||||
_link: href,
|
|
||||||
_star: icon === 'star',
|
|
||||||
'_with-text': children || title && showTitle,
|
|
||||||
});
|
|
||||||
let conditionalProps = {};
|
|
||||||
|
|
||||||
// If href is provided, we render a link.
|
|
||||||
if (href) {
|
|
||||||
if (!disabled && href) conditionalProps.href = href;
|
|
||||||
if (title && !showTitle) {
|
|
||||||
if (!children) conditionalProps.title = title;
|
|
||||||
else conditionalProps['aria-label'] = title;
|
|
||||||
}
|
|
||||||
if (onClick) {
|
|
||||||
if (!disabled) conditionalProps.onClick = handleClick;
|
|
||||||
else conditionalProps['aria-disabled'] = true;
|
|
||||||
conditionalProps.role = 'button';
|
|
||||||
conditionalProps.tabIndex = 0;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<CommonLink
|
|
||||||
className={computedClass}
|
|
||||||
{...conditionalProps}
|
|
||||||
{...others}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
{title && showTitle ? <span className='button\title'>{title}</span> : null}
|
|
||||||
<CommonIcon name={icon} className='button\icon' />
|
|
||||||
</CommonLink>
|
|
||||||
);
|
|
||||||
|
|
||||||
// Otherwise, we render a button.
|
|
||||||
} else {
|
|
||||||
if (active !== void 0) conditionalProps['aria-pressed'] = active;
|
|
||||||
if (title && !showTitle) {
|
|
||||||
if (!children) conditionalProps.title = title;
|
|
||||||
else conditionalProps['aria-label'] = title;
|
|
||||||
}
|
|
||||||
if (onClick && !disabled) {
|
|
||||||
conditionalProps.onClick = handleClick;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
className={computedClass}
|
|
||||||
{...conditionalProps}
|
|
||||||
disabled={disabled}
|
|
||||||
{...others}
|
|
||||||
tabIndex='0'
|
|
||||||
type='button'
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
{title && showTitle ? <span className='button\title'>{title}</span> : null}
|
|
||||||
<CommonIcon name={icon} className='button\icon' />
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
@import 'variables';
|
|
||||||
|
|
||||||
.glitch.glitch__common__button {
|
|
||||||
display: inline-block;
|
|
||||||
border: none;
|
|
||||||
padding: 0;
|
|
||||||
color: $ui-base-lighter-color;
|
|
||||||
background: transparent;
|
|
||||||
outline: thin transparent dotted;
|
|
||||||
font-size: inherit;
|
|
||||||
text-decoration: none;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: color ($glitch-animation-speed * .15s) ease-in, outline-color ($glitch-animation-speed * .3s) ease-in-out;
|
|
||||||
|
|
||||||
&._animated .button\\icon {
|
|
||||||
animation-name: glitch__common__button__deactivate;
|
|
||||||
animation-duration: .9s;
|
|
||||||
animation-timing-function: ease-in-out;
|
|
||||||
|
|
||||||
@keyframes glitch__common__button__deactivate {
|
|
||||||
from {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
57% {
|
|
||||||
transform: rotate(-60deg);
|
|
||||||
}
|
|
||||||
86% {
|
|
||||||
transform: rotate(30deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&._active {
|
|
||||||
.button\\icon {
|
|
||||||
color: $ui-highlight-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&._animated .button\\icon {
|
|
||||||
animation-name: glitch__common__button__activate;
|
|
||||||
|
|
||||||
@keyframes glitch__common__button__activate {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
57% {
|
|
||||||
transform: rotate(420deg); // Blazin' 😎
|
|
||||||
}
|
|
||||||
86% {
|
|
||||||
transform: rotate(330deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
The special `._star` class is given to buttons which have a star
|
|
||||||
icon (see JS). When they are active, we give them a gold star ⭐️.
|
|
||||||
*/
|
|
||||||
&._star .button\\icon {
|
|
||||||
color: $gold-star;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
For links, we consider them disabled if they don't have an `href`
|
|
||||||
attribute (see JS).
|
|
||||||
*/
|
|
||||||
&._disabled {
|
|
||||||
opacity: $glitch-disabled-opacity;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
This is confusing becuase of the names, but the `color .3 ease-out`
|
|
||||||
transition is actually used when easing *in* to a hovering/active/
|
|
||||||
focusing state, and the default transition is used when leaving. Our
|
|
||||||
buttons are a little slower to glow than they are to fade.
|
|
||||||
*/
|
|
||||||
&:active,
|
|
||||||
&:focus,
|
|
||||||
&:hover {
|
|
||||||
color: $glitch-lighter-color;
|
|
||||||
transition: color ($glitch-animation-speed * .3s) ease-out, outline-color ($glitch-animation-speed * .15s) ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline-color: currentColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Buttons with text have a number of different styling rules and an
|
|
||||||
overall different appearance.
|
|
||||||
*/
|
|
||||||
&._with-text {
|
|
||||||
display: inline-block;
|
|
||||||
border: none;
|
|
||||||
border-radius: .35em;
|
|
||||||
padding: 0 .5em;
|
|
||||||
color: $glitch-texture-color;
|
|
||||||
background: $ui-base-lighter-color;
|
|
||||||
font-size: .75em;
|
|
||||||
font-weight: inherit;
|
|
||||||
text-transform: uppercase;
|
|
||||||
line-height: 1.6;
|
|
||||||
cursor: pointer;
|
|
||||||
vertical-align: baseline;
|
|
||||||
transition: background-color ($glitch-animation-speed * .15s) ease-in, outline-color ($glitch-animation-speed * .3s) ease-in-out;
|
|
||||||
|
|
||||||
.button\\icon {
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 1.25em;
|
|
||||||
vertical-align: -.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
& > *:not(:first-child) {
|
|
||||||
margin: 0 0 0 .4em;
|
|
||||||
border-left: 1px solid currentColor;
|
|
||||||
padding: 0 0 0 .3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active,
|
|
||||||
&:hover,
|
|
||||||
&:focus {
|
|
||||||
color: $glitch-texture-color;
|
|
||||||
background: $glitch-lighter-color;
|
|
||||||
transition: background-color ($glitch-animation-speed * .3s) ease-out, outline-color ($glitch-animation-speed * .15s) ease-in-out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
// <CommonIcon>
|
|
||||||
// ========
|
|
||||||
|
|
||||||
// For code documentation, please see:
|
|
||||||
// https://glitch-soc.github.io/docs/javascript/glitch/common/icon
|
|
||||||
|
|
||||||
// For more information, please contact:
|
|
||||||
// @kibi@glitch.social
|
|
||||||
|
|
||||||
// * * * * * * * //
|
|
||||||
|
|
||||||
// Imports
|
|
||||||
// -------
|
|
||||||
|
|
||||||
// Package imports.
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
|
|
||||||
// Stylesheet imports.
|
|
||||||
import './style';
|
|
||||||
|
|
||||||
// * * * * * * * //
|
|
||||||
|
|
||||||
// The component
|
|
||||||
// -------------
|
|
||||||
|
|
||||||
const CommonIcon = ({
|
|
||||||
className,
|
|
||||||
name,
|
|
||||||
proportional,
|
|
||||||
title,
|
|
||||||
...others
|
|
||||||
}) => name ? (
|
|
||||||
<span
|
|
||||||
className={classNames('glitch', 'glitch__common__icon', className)}
|
|
||||||
{...others}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
aria-hidden
|
|
||||||
className={`fa ${proportional ? '' : 'fa-fw'} fa-${name} icon\fa`}
|
|
||||||
{...(title ? { title } : {})}
|
|
||||||
/>
|
|
||||||
{title ? (
|
|
||||||
<span className='_for-screenreader'>{title}</span>
|
|
||||||
) : null}
|
|
||||||
</span>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
// Props.
|
|
||||||
CommonIcon.propTypes = {
|
|
||||||
className: PropTypes.string,
|
|
||||||
name: PropTypes.string,
|
|
||||||
proportional: PropTypes.bool,
|
|
||||||
title: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Export.
|
|
||||||
export default CommonIcon;
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
@import 'variables';
|
|
||||||
|
|
||||||
.glitch.glitch__common__icon {
|
|
||||||
display: inline-block;
|
|
||||||
|
|
||||||
._for-screenreader {
|
|
||||||
position: absolute;
|
|
||||||
margin: -1px -1px;
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
clip: rect(0, 0, 0, 0);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
// <CommonLink>
|
|
||||||
// ========
|
|
||||||
|
|
||||||
// For code documentation, please see:
|
|
||||||
// https://glitch-soc.github.io/docs/javascript/glitch/common/link
|
|
||||||
|
|
||||||
// For more information, please contact:
|
|
||||||
// @kibi@glitch.social
|
|
||||||
|
|
||||||
// * * * * * * * //
|
|
||||||
|
|
||||||
// Imports
|
|
||||||
// -------
|
|
||||||
|
|
||||||
// Package imports.
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
// Stylesheet imports.
|
|
||||||
import './style';
|
|
||||||
|
|
||||||
// * * * * * * * //
|
|
||||||
|
|
||||||
// The component
|
|
||||||
// -------------
|
|
||||||
|
|
||||||
export default class CommonLink extends React.PureComponent {
|
|
||||||
|
|
||||||
// Props.
|
|
||||||
static propTypes = {
|
|
||||||
children: PropTypes.node,
|
|
||||||
className: PropTypes.string,
|
|
||||||
destination: PropTypes.string,
|
|
||||||
history: PropTypes.object,
|
|
||||||
href: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
// We only reroute the link if it is an unadorned click, we have
|
|
||||||
// access to the router, and there is somewhere to reroute it *to*.
|
|
||||||
handleClick = (e) => {
|
|
||||||
const { destination, history } = this.props;
|
|
||||||
if (!history || !destination || e.button || e.ctrlKey || e.shiftKey || e.altKey || e.metaKey) return;
|
|
||||||
history.push(destination);
|
|
||||||
e.preventDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rendering.
|
|
||||||
render () {
|
|
||||||
const { handleClick } = this;
|
|
||||||
const { children, className, destination, history, href, ...others } = this.props;
|
|
||||||
const computedClass = classNames('glitch', 'glitch__common__link', className);
|
|
||||||
const conditionalProps = {};
|
|
||||||
if (href) {
|
|
||||||
conditionalProps.href = href;
|
|
||||||
conditionalProps.onClick = handleClick;
|
|
||||||
} else if (destination) {
|
|
||||||
conditionalProps.onClick = handleClick;
|
|
||||||
conditionalProps.role = 'link';
|
|
||||||
conditionalProps.tabIndex = 0;
|
|
||||||
} else conditionalProps.role = 'presentation';
|
|
||||||
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
className={computedClass}
|
|
||||||
{...conditionalProps}
|
|
||||||
{...others}
|
|
||||||
rel='noopener'
|
|
||||||
target='_blank'
|
|
||||||
>{children}</a>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
@import 'variables';
|
|
||||||
|
|
||||||
/*
|
|
||||||
Most link styling happens elsewhere but we disable text-decoration
|
|
||||||
here.
|
|
||||||
*/
|
|
||||||
.glitch.glitch__common__link {
|
|
||||||
display: inline;
|
|
||||||
color: $ui-secondary-color;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
// <CommonSeparator>
|
|
||||||
// ========
|
|
||||||
|
|
||||||
// For code documentation, please see:
|
|
||||||
// https://glitch-soc.github.io/docs/javascript/glitch/common/separator
|
|
||||||
|
|
||||||
// For more information, please contact:
|
|
||||||
// @kibi@glitch.social
|
|
||||||
|
|
||||||
// * * * * * * * //
|
|
||||||
|
|
||||||
// Imports
|
|
||||||
// -------
|
|
||||||
|
|
||||||
// Package imports.
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
// Stylesheet imports.
|
|
||||||
import './style';
|
|
||||||
|
|
||||||
// * * * * * * * //
|
|
||||||
|
|
||||||
// The component
|
|
||||||
// -------------
|
|
||||||
|
|
||||||
const CommonSeparator = ({
|
|
||||||
className,
|
|
||||||
visible,
|
|
||||||
...others
|
|
||||||
}) => visible ? (
|
|
||||||
<span
|
|
||||||
className={
|
|
||||||
classNames('glitch', 'glitch__common__separator', className)
|
|
||||||
}
|
|
||||||
{...others}
|
|
||||||
role='separator'
|
|
||||||
/> // Contents provided via CSS.
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
// Props.
|
|
||||||
CommonSeparator.propTypes = {
|
|
||||||
className: PropTypes.string,
|
|
||||||
visible: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Export.
|
|
||||||
export default CommonSeparator;
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
@import 'variables';
|
|
||||||
|
|
||||||
/*
|
|
||||||
The default contents for a separator is an interpunct, surrounded by
|
|
||||||
spaces. However, this can be changed using CSS selectors.
|
|
||||||
*/
|
|
||||||
.glitch.glitch__common__separator {
|
|
||||||
display: inline-block;
|
|
||||||
|
|
||||||
&::after {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0 .3em;
|
|
||||||
content: "·";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
`<ComposeAdvancedOptionsContainer>`
|
|
||||||
===================================
|
|
||||||
|
|
||||||
This container connects `<ComposeAdvancedOptions>` to the Redux store.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Imports:
|
|
||||||
--------
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Package imports //
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
|
|
||||||
// Mastodon imports //
|
|
||||||
import { toggleComposeAdvancedOption } from '../../../../mastodon/actions/compose';
|
|
||||||
|
|
||||||
// Our imports //
|
|
||||||
import ComposeAdvancedOptions from '.';
|
|
||||||
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
State mapping:
|
|
||||||
--------------
|
|
||||||
|
|
||||||
The `mapStateToProps()` function maps various state properties to the
|
|
||||||
props of our component. The only property we care about is
|
|
||||||
`compose.advanced_options`.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
|
||||||
values: state.getIn(['compose', 'advanced_options']),
|
|
||||||
});
|
|
||||||
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Dispatch mapping:
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
The `mapDispatchToProps()` function maps dispatches to our store to the
|
|
||||||
various props of our component. We just need to provide a dispatch for
|
|
||||||
when an advanced option toggle changes.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => ({
|
|
||||||
|
|
||||||
onChange (option) {
|
|
||||||
dispatch(toggleComposeAdvancedOption(option));
|
|
||||||
},
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ComposeAdvancedOptions);
|
|
||||||
@@ -1,241 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
`<ComposeAdvancedOptions>`
|
|
||||||
==========================
|
|
||||||
|
|
||||||
> For more information on the contents of this file, please contact:
|
|
||||||
>
|
|
||||||
> - surinna [@srn@dev.glitch.social]
|
|
||||||
|
|
||||||
This adds an advanced options dropdown to the toot compose box, for
|
|
||||||
toggles that don't necessarily fit elsewhere.
|
|
||||||
|
|
||||||
__Props:__
|
|
||||||
|
|
||||||
- __`values` (`ImmutablePropTypes.contains(…).isRequired`) :__
|
|
||||||
An Immutable map with the following values:
|
|
||||||
|
|
||||||
- __`do_not_federate` (`PropTypes.bool.isRequired`) :__
|
|
||||||
Specifies whether or not to federate the status.
|
|
||||||
|
|
||||||
- __`onChange` (`PropTypes.func.isRequired`) :__
|
|
||||||
The function to call when a toggle is changed. We pass this from
|
|
||||||
our container to the toggle.
|
|
||||||
|
|
||||||
- __`intl` (`PropTypes.object.isRequired`) :__
|
|
||||||
Our internationalization object, inserted by `@injectIntl`.
|
|
||||||
|
|
||||||
__State:__
|
|
||||||
|
|
||||||
- __`open` :__
|
|
||||||
This tells whether the dropdown is currently open or closed.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Imports:
|
|
||||||
--------
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Package imports //
|
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import { injectIntl, defineMessages } from 'react-intl';
|
|
||||||
|
|
||||||
// Mastodon imports //
|
|
||||||
import IconButton from '../../../../mastodon/components/icon_button';
|
|
||||||
|
|
||||||
// Our imports //
|
|
||||||
import ComposeAdvancedOptionsToggle from './toggle';
|
|
||||||
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Inital setup:
|
|
||||||
-------------
|
|
||||||
|
|
||||||
The `messages` constant is used to define any messages that we need
|
|
||||||
from inside props. These are the various titles and labels on our
|
|
||||||
toggles.
|
|
||||||
|
|
||||||
`iconStyle` styles the icon used for the dropdown button.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
local_only_short :
|
|
||||||
{ id: 'advanced-options.local-only.short', defaultMessage: 'Local-only' },
|
|
||||||
local_only_long :
|
|
||||||
{ id: 'advanced-options.local-only.long', defaultMessage: 'Do not post to other instances' },
|
|
||||||
advanced_options_icon_title :
|
|
||||||
{ id: 'advanced_options.icon_title', defaultMessage: 'Advanced options' },
|
|
||||||
});
|
|
||||||
|
|
||||||
const iconStyle = {
|
|
||||||
height : null,
|
|
||||||
lineHeight : '27px',
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Implementation:
|
|
||||||
---------------
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
@injectIntl
|
|
||||||
export default class ComposeAdvancedOptions extends React.PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
values : ImmutablePropTypes.contains({
|
|
||||||
do_not_federate : PropTypes.bool.isRequired,
|
|
||||||
}).isRequired,
|
|
||||||
onChange : PropTypes.func.isRequired,
|
|
||||||
intl : PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
state = {
|
|
||||||
open: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
### `onToggleDropdown()`
|
|
||||||
|
|
||||||
This function toggles the opening and closing of the advanced options
|
|
||||||
dropdown.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
onToggleDropdown = () => {
|
|
||||||
this.setState({ open: !this.state.open });
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
### `onGlobalClick(e)`
|
|
||||||
|
|
||||||
This function closes the advanced options dropdown if you click
|
|
||||||
anywhere else on the screen.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
onGlobalClick = (e) => {
|
|
||||||
if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) {
|
|
||||||
this.setState({ open: false });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
### `componentDidMount()`, `componentWillUnmount()`
|
|
||||||
|
|
||||||
This function closes the advanced options dropdown if you click
|
|
||||||
anywhere else on the screen.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
window.addEventListener('click', this.onGlobalClick);
|
|
||||||
window.addEventListener('touchstart', this.onGlobalClick);
|
|
||||||
}
|
|
||||||
componentWillUnmount () {
|
|
||||||
window.removeEventListener('click', this.onGlobalClick);
|
|
||||||
window.removeEventListener('touchstart', this.onGlobalClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
### `setRef(c)`
|
|
||||||
|
|
||||||
`setRef()` stores a reference to the dropdown's `<div> in `this.node`.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
setRef = (c) => {
|
|
||||||
this.node = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
### `render()`
|
|
||||||
|
|
||||||
`render()` actually puts our component on the screen.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { open } = this.state;
|
|
||||||
const { intl, values } = this.props;
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
The `options` array provides all of the available advanced options
|
|
||||||
alongside their icon, text, and name.
|
|
||||||
|
|
||||||
*/
|
|
||||||
const options = [
|
|
||||||
{ icon: 'wifi', shortText: messages.local_only_short, longText: messages.local_only_long, name: 'do_not_federate' },
|
|
||||||
];
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
`anyEnabled` tells us if any of our advanced options have been enabled.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
const anyEnabled = values.some((enabled) => enabled);
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
`optionElems` takes our `options` and creates
|
|
||||||
`<ComposeAdvancedOptionsToggle>`s out of them. We use the `name` of the
|
|
||||||
toggle as its `key` so that React can keep track of it.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
const optionElems = options.map((option) => {
|
|
||||||
return (
|
|
||||||
<ComposeAdvancedOptionsToggle
|
|
||||||
onChange={this.props.onChange}
|
|
||||||
active={values.get(option.name)}
|
|
||||||
key={option.name}
|
|
||||||
name={option.name}
|
|
||||||
shortText={intl.formatMessage(option.shortText)}
|
|
||||||
longText={intl.formatMessage(option.longText)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Finally, we can render our component.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div ref={this.setRef} className={`advanced-options-dropdown ${open ? 'open' : ''} ${anyEnabled ? 'active' : ''} `}>
|
|
||||||
<div className='advanced-options-dropdown__value'>
|
|
||||||
<IconButton
|
|
||||||
className='advanced-options-dropdown__value'
|
|
||||||
title={intl.formatMessage(messages.advanced_options_icon_title)}
|
|
||||||
icon='ellipsis-h' active={open || anyEnabled}
|
|
||||||
size={18}
|
|
||||||
style={iconStyle}
|
|
||||||
onClick={this.onToggleDropdown}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className='advanced-options-dropdown__dropdown'>
|
|
||||||
{optionElems}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
/*
|
|
||||||
|
|
||||||
`<ComposeAdvancedOptionsToggle>`
|
|
||||||
================================
|
|
||||||
|
|
||||||
> For more information on the contents of this file, please contact:
|
|
||||||
>
|
|
||||||
> - surinna [@srn@dev.glitch.social]
|
|
||||||
|
|
||||||
This creates the toggle used by `<ComposeAdvancedOptions>`.
|
|
||||||
|
|
||||||
__Props:__
|
|
||||||
|
|
||||||
- __`onChange` (`PropTypes.func`) :__
|
|
||||||
This provides the function to call when the toggle is
|
|
||||||
(de-?)activated.
|
|
||||||
|
|
||||||
- __`active` (`PropTypes.bool`) :__
|
|
||||||
This prop controls whether the toggle is currently active or not.
|
|
||||||
|
|
||||||
- __`name` (`PropTypes.string`) :__
|
|
||||||
This identifies the toggle, and is sent to `onChange()` when it is
|
|
||||||
called.
|
|
||||||
|
|
||||||
- __`shortText` (`PropTypes.string`) :__
|
|
||||||
This is a short string used as the title of the toggle.
|
|
||||||
|
|
||||||
- __`longText` (`PropTypes.string`) :__
|
|
||||||
This is a longer string used as a subtitle for the toggle.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Imports:
|
|
||||||
--------
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Package imports //
|
|
||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import Toggle from 'react-toggle';
|
|
||||||
|
|
||||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
Implementation:
|
|
||||||
---------------
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default class ComposeAdvancedOptionsToggle extends React.PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
onChange: PropTypes.func.isRequired,
|
|
||||||
active: PropTypes.bool.isRequired,
|
|
||||||
name: PropTypes.string.isRequired,
|
|
||||||
shortText: PropTypes.string.isRequired,
|
|
||||||
longText: PropTypes.string.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
### `onToggle()`
|
|
||||||
|
|
||||||
The `onToggle()` function simply calls the `onChange()` prop with the
|
|
||||||
toggle's `name`.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
onToggle = () => {
|
|
||||||
this.props.onChange(this.props.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
### `render()`
|
|
||||||
|
|
||||||
The `render()` function is used to render our component. We just render
|
|
||||||
a `<Toggle>` and place next to it our text.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { active, shortText, longText } = this.props;
|
|
||||||
return (
|
|
||||||
<div role='button' tabIndex='0' className='advanced-options-dropdown__option' onClick={this.onToggle}>
|
|
||||||
<div className='advanced-options-dropdown__option__toggle'>
|
|
||||||
<Toggle checked={active} onChange={this.onToggle} />
|
|
||||||
</div>
|
|
||||||
<div className='advanced-options-dropdown__option__content'>
|
|
||||||
<strong>{shortText}</strong>
|
|
||||||
{longText}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user