Merge branch 'main' of https://github.com/glitch-soc/mastodon
This commit is contained in:
commit
c29dfcdf53
|
@ -17,7 +17,7 @@
|
|||
LOCAL_DOMAIN=example.com
|
||||
|
||||
# Use this only if you need to run mastodon on a different domain than the one used for federation.
|
||||
# You can read more about this option on https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Serving_a_different_domain.md
|
||||
# You can read more about this option on https://docs.joinmastodon.org/admin/config/#web-domain
|
||||
# DO *NOT* USE THIS UNLESS YOU KNOW *EXACTLY* WHAT YOU ARE DOING.
|
||||
# WEB_DOMAIN=mastodon.example.com
|
||||
|
||||
|
@ -279,6 +279,10 @@ MAX_POLL_OPTION_CHARS=100
|
|||
# Only relevant when elasticsearch is installed
|
||||
# MAX_SEARCH_RESULTS=20
|
||||
|
||||
# Maximum hashtags to display
|
||||
# Customize the number of hashtags shown in 'Explore'
|
||||
# MAX_TRENDING_TAGS=10
|
||||
|
||||
# Maximum custom emoji file sizes
|
||||
# If undefined or smaller than MAX_EMOJI_SIZE, the value
|
||||
# of MAX_EMOJI_SIZE will be used for MAX_REMOTE_EMOJI_SIZE
|
||||
|
|
|
@ -31,6 +31,11 @@ body:
|
|||
description: What happened?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Detailed description
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Specifications
|
||||
|
@ -38,5 +43,14 @@ body:
|
|||
What version or commit hash of Mastodon did you find this bug in?
|
||||
|
||||
If a front-end issue, what browser and operating systems were you using?
|
||||
placeholder: |
|
||||
Mastodon 3.5.3 (or Edge)
|
||||
Ruby 2.7.6 (or v3.1.2)
|
||||
Node.js 16.18.0
|
||||
|
||||
Google Chrome 106.0.5249.119
|
||||
Firefox 105.0.3
|
||||
|
||||
etc...
|
||||
validations:
|
||||
required: true
|
||||
|
|
|
@ -3,6 +3,3 @@ contact_links:
|
|||
- name: GitHub Discussions
|
||||
url: https://github.com/mastodon/mastodon/discussions
|
||||
about: Please ask and answer questions here.
|
||||
- name: Bug Bounty Program
|
||||
url: https://app.intigriti.com/programs/mastodon/mastodonio/detail
|
||||
about: Please report security vulnerabilities here.
|
||||
|
|
|
@ -4,14 +4,13 @@ on:
|
|||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/build-image.yml
|
||||
- Dockerfile
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-image:
|
||||
|
@ -30,17 +29,16 @@ jobs:
|
|||
id: meta
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository_owner }}/mastodon
|
||||
flavor: |
|
||||
latest=auto
|
||||
tags: |
|
||||
type=raw,value=latest,enable={{is_default_branch}}
|
||||
type=edge,branch=main
|
||||
type=match,pattern=v(.*),group=0
|
||||
type=ref,event=pr
|
||||
type=sha,prefix=,format=long
|
||||
- uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/mastodon:latest
|
||||
cache-to: type=inline
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
# This is a GitHub workflow defining a set of jobs with a set of steps.
|
||||
# ref: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions
|
||||
#
|
||||
name: Test chart
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "chart/**"
|
||||
- "!**.md"
|
||||
- ".github/workflows/test-chart.yml"
|
||||
push:
|
||||
paths:
|
||||
- "chart/**"
|
||||
- "!**.md"
|
||||
- ".github/workflows/test-chart.yml"
|
||||
branches-ignore:
|
||||
- "dependabot/**"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: chart
|
||||
|
||||
jobs:
|
||||
lint-templates:
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.x"
|
||||
|
||||
- name: Install dependencies (yamllint)
|
||||
run: pip install yamllint
|
||||
|
||||
- run: helm dependency update
|
||||
|
||||
- name: helm lint
|
||||
run: |
|
||||
helm lint . \
|
||||
--values dev-values.yaml
|
||||
|
||||
- name: helm template
|
||||
run: |
|
||||
helm template . \
|
||||
--values dev-values.yaml \
|
||||
--output-dir rendered-templates
|
||||
|
||||
- name: yamllint (only on templates we manage)
|
||||
run: |
|
||||
rm -rf rendered-templates/mastodon/charts
|
||||
|
||||
yamllint rendered-templates \
|
||||
--config-data "{rules: {indentation: {spaces: 2}, line-length: disable}}"
|
||||
|
||||
# This job helps us validate that rendered templates are valid k8s resources
|
||||
# against a k8s api-server, via "helm template --validate", but also that a
|
||||
# basic configuration can be used to successfully startup mastodon.
|
||||
#
|
||||
test-install:
|
||||
runs-on: ubuntu-22.04
|
||||
timeout-minutes: 15
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
# k3s-channel reference: https://update.k3s.io/v1-release/channels
|
||||
- k3s-channel: latest
|
||||
- k3s-channel: stable
|
||||
|
||||
# This represents the oldest configuration we test against.
|
||||
#
|
||||
# The k8s version chosen is based on the oldest still supported k8s
|
||||
# version among two managed k8s services, GKE, EKS.
|
||||
# - GKE: https://endoflife.date/google-kubernetes-engine
|
||||
# - EKS: https://endoflife.date/amazon-eks
|
||||
#
|
||||
# The helm client's version can influence what helper functions is
|
||||
# available for use in the templates, currently we need v3.6.0 or
|
||||
# higher.
|
||||
#
|
||||
- k3s-channel: v1.21
|
||||
helm-version: v3.6.0
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# This action starts a k8s cluster with NetworkPolicy enforcement and
|
||||
# installs both kubectl and helm.
|
||||
#
|
||||
# ref: https://github.com/jupyterhub/action-k3s-helm#readme
|
||||
#
|
||||
- uses: jupyterhub/action-k3s-helm@v3
|
||||
with:
|
||||
k3s-channel: ${{ matrix.k3s-channel }}
|
||||
helm-version: ${{ matrix.helm-version }}
|
||||
metrics-enabled: false
|
||||
traefik-enabled: false
|
||||
docker-enabled: false
|
||||
|
||||
- run: helm dependency update
|
||||
|
||||
# Validate rendered helm templates against the k8s api-server
|
||||
- name: helm template --validate
|
||||
run: |
|
||||
helm template --validate mastodon . \
|
||||
--values dev-values.yaml
|
||||
|
||||
- name: helm install
|
||||
run: |
|
||||
helm install mastodon . \
|
||||
--values dev-values.yaml \
|
||||
--timeout 10m
|
||||
|
||||
# This actions provides a report about the state of the k8s cluster,
|
||||
# providing logs etc on anything that has failed and workloads marked as
|
||||
# important.
|
||||
#
|
||||
# ref: https://github.com/jupyterhub/action-k8s-namespace-report#readme
|
||||
#
|
||||
- name: Kubernetes namespace report
|
||||
uses: jupyterhub/action-k8s-namespace-report@v1
|
||||
if: always()
|
||||
with:
|
||||
important-workloads: >-
|
||||
deploy/mastodon-sidekiq
|
||||
deploy/mastodon-streaming
|
||||
deploy/mastodon-web
|
||||
job/mastodon-assets-precompile
|
||||
job/mastodon-chewy-upgrade
|
||||
job/mastodon-create-admin
|
||||
job/mastodon-db-migrate
|
|
@ -44,6 +44,9 @@
|
|||
/redis
|
||||
/elasticsearch
|
||||
|
||||
# ignore Helm charts
|
||||
/chart/*.tgz
|
||||
|
||||
# ignore Helm dependency charts
|
||||
/chart/charts/*.tgz
|
||||
|
||||
|
|
|
@ -243,6 +243,10 @@ Style/HashTransformKeys:
|
|||
Style/HashTransformValues:
|
||||
Enabled: false
|
||||
|
||||
Style/HashSyntax:
|
||||
Enabled: true
|
||||
EnforcedStyle: ruby19_no_mixed_keys
|
||||
|
||||
Style/IfUnlessModifier:
|
||||
Enabled: false
|
||||
|
||||
|
|
1121
AUTHORS.md
1121
AUTHORS.md
File diff suppressed because it is too large
Load Diff
4
Aptfile
4
Aptfile
|
@ -1,8 +1,8 @@
|
|||
ffmpeg
|
||||
libicu[0-9][0-9]
|
||||
libicu-dev
|
||||
libidn11
|
||||
libidn11-dev
|
||||
libidn12
|
||||
libidn-dev
|
||||
libpq-dev
|
||||
libxdamage1
|
||||
libxfixes3
|
||||
|
|
214
CHANGELOG.md
214
CHANGELOG.md
|
@ -3,6 +3,216 @@ Changelog
|
|||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [4.0.2] - 2022-11-15
|
||||
### Fixed
|
||||
|
||||
- Fix wrong color on mentions hidden behind content warning in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/20724))
|
||||
- Fix filters from other users being used in the streaming service ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20719))
|
||||
- Fix `unsafe-eval` being used when `wasm-unsafe-eval` is enough in Content Security Policy ([Gargron](https://github.com/mastodon/mastodon/pull/20729), [prplecake](https://github.com/mastodon/mastodon/pull/20606))
|
||||
|
||||
## [4.0.1] - 2022-11-14
|
||||
### Fixed
|
||||
|
||||
- Fix nodes order being sometimes mangled when rewriting emoji ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20677))
|
||||
|
||||
## [4.0.0] - 2022-11-14
|
||||
|
||||
Some of the features in this release have been funded through the [NGI0 Discovery](https://nlnet.nl/discovery) Fund, a fund established by [NLnet](https://nlnet.nl/) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu/) programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 825322.
|
||||
|
||||
### Added
|
||||
|
||||
- Add ability to filter followed accounts' posts by language ([Gargron](https://github.com/mastodon/mastodon/pull/19095), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19268))
|
||||
- **Add ability to follow hashtags** ([Gargron](https://github.com/mastodon/mastodon/pull/18809), [Gargron](https://github.com/mastodon/mastodon/pull/18862), [Gargron](https://github.com/mastodon/mastodon/pull/19472), [noellabo](https://github.com/mastodon/mastodon/pull/18924))
|
||||
- Add ability to filter individual posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18945))
|
||||
- **Add ability to translate posts** ([Gargron](https://github.com/mastodon/mastodon/pull/19218), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19433), [Gargron](https://github.com/mastodon/mastodon/pull/19453), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19434), [Gargron](https://github.com/mastodon/mastodon/pull/19388), [ykzts](https://github.com/mastodon/mastodon/pull/19244), [Gargron](https://github.com/mastodon/mastodon/pull/19245))
|
||||
- Add featured tags to web UI ([noellabo](https://github.com/mastodon/mastodon/pull/19408), [noellabo](https://github.com/mastodon/mastodon/pull/19380), [noellabo](https://github.com/mastodon/mastodon/pull/19358), [noellabo](https://github.com/mastodon/mastodon/pull/19409), [Gargron](https://github.com/mastodon/mastodon/pull/19382), [ykzts](https://github.com/mastodon/mastodon/pull/19418), [noellabo](https://github.com/mastodon/mastodon/pull/19403), [noellabo](https://github.com/mastodon/mastodon/pull/19404), [Gargron](https://github.com/mastodon/mastodon/pull/19398), [Gargron](https://github.com/mastodon/mastodon/pull/19712), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/20018))
|
||||
- **Add support for language preferences for trending statuses and links** ([Gargron](https://github.com/mastodon/mastodon/pull/18288), [Gargron](https://github.com/mastodon/mastodon/pull/19349), [ykzts](https://github.com/mastodon/mastodon/pull/19335))
|
||||
- Previously, you could only see trends in your current language
|
||||
- For less popular languages, that meant empty trends
|
||||
- Now, trends in your preferred languages' are shown on top, with others beneath
|
||||
- Add server rules to sign-up flow ([Gargron](https://github.com/mastodon/mastodon/pull/19296))
|
||||
- Add privacy icons to report modal in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19190))
|
||||
- Add `noopener` to links to remote profiles in web UI ([shleeable](https://github.com/mastodon/mastodon/pull/19014))
|
||||
- Add option to open original page in dropdowns of remote content in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/20299))
|
||||
- Add warning for sensitive audio posts in web UI ([rgroothuijsen](https://github.com/mastodon/mastodon/pull/17885))
|
||||
- Add language attribute to posts in web UI ([tribela](https://github.com/mastodon/mastodon/pull/18544))
|
||||
- Add support for uploading WebP files ([Saiv46](https://github.com/mastodon/mastodon/pull/18506))
|
||||
- Add support for uploading `audio/vnd.wave` files ([tribela](https://github.com/mastodon/mastodon/pull/18737))
|
||||
- Add support for uploading AVIF files ([txt-file](https://github.com/mastodon/mastodon/pull/19647))
|
||||
- Add support for uploading HEIC files ([Gargron](https://github.com/mastodon/mastodon/pull/19618))
|
||||
- Add more debug information when processing remote accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/15605), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19209))
|
||||
- **Add retention policy for cached content and media** ([Gargron](https://github.com/mastodon/mastodon/pull/19232), [zunda](https://github.com/mastodon/mastodon/pull/19478), [Gargron](https://github.com/mastodon/mastodon/pull/19458), [Gargron](https://github.com/mastodon/mastodon/pull/19248))
|
||||
- Set for how long remote posts or media should be cached on your server
|
||||
- Hands-off alternative to `tootctl` commands
|
||||
- **Add customizable user roles** ([Gargron](https://github.com/mastodon/mastodon/pull/18641), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/18812), [Gargron](https://github.com/mastodon/mastodon/pull/19040), [tribela](https://github.com/mastodon/mastodon/pull/18825), [tribela](https://github.com/mastodon/mastodon/pull/18826), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/18776), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/18777), [unextro](https://github.com/mastodon/mastodon/pull/18786), [tribela](https://github.com/mastodon/mastodon/pull/18824), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19436))
|
||||
- Previously, there were 3 hard-coded roles, user, moderator, and admin
|
||||
- Create your own roles and decide which permissions they should have
|
||||
- Add notifications for new reports ([Gargron](https://github.com/mastodon/mastodon/pull/18697), [Gargron](https://github.com/mastodon/mastodon/pull/19475))
|
||||
- Add ability to select all accounts matching search for batch actions in admin UI ([Gargron](https://github.com/mastodon/mastodon/pull/19053), [Gargron](https://github.com/mastodon/mastodon/pull/19054))
|
||||
- Add ability to view previous edits of a status in admin UI ([Gargron](https://github.com/mastodon/mastodon/pull/19462))
|
||||
- Add ability to block sign-ups from IP ([Gargron](https://github.com/mastodon/mastodon/pull/19037))
|
||||
- **Add webhooks to admin UI** ([Gargron](https://github.com/mastodon/mastodon/pull/18510))
|
||||
- Add admin API for managing domain allows ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18668))
|
||||
- Add admin API for managing domain blocks ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18247))
|
||||
- Add admin API for managing e-mail domain blocks ([Gargron](https://github.com/mastodon/mastodon/pull/19066))
|
||||
- Add admin API for managing canonical e-mail blocks ([Gargron](https://github.com/mastodon/mastodon/pull/19067))
|
||||
- Add admin API for managing IP blocks ([Gargron](https://github.com/mastodon/mastodon/pull/19065), [trwnh](https://github.com/mastodon/mastodon/pull/20207))
|
||||
- Add `sensitized` attribute to accounts in admin REST API ([trwnh](https://github.com/mastodon/mastodon/pull/20094))
|
||||
- Add `services` and `metadata` to the NodeInfo endpoint ([MFTabriz](https://github.com/mastodon/mastodon/pull/18563))
|
||||
- Add `--remove-role` option to `tootctl accounts modify` ([Gargron](https://github.com/mastodon/mastodon/pull/19477))
|
||||
- Add `--days` option to `tootctl media refresh` ([tribela](https://github.com/mastodon/mastodon/pull/18425))
|
||||
- Add `EMAIL_DOMAIN_LISTS_APPLY_AFTER_CONFIRMATION` environment variable ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18642))
|
||||
- Add `IP_RETENTION_PERIOD` and `SESSION_RETENTION_PERIOD` environment variables ([kescherCode](https://github.com/mastodon/mastodon/pull/18757))
|
||||
- Add `http_hidden_proxy` environment variable ([tribela](https://github.com/mastodon/mastodon/pull/18427))
|
||||
- Add `ENABLE_STARTTLS` environment variable ([erbridge](https://github.com/mastodon/mastodon/pull/20321))
|
||||
- Add caching for payload serialization during fan-out ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19637), [Gargron](https://github.com/mastodon/mastodon/pull/19642), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19746), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19747), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19963))
|
||||
- Add assets from Twemoji 14.0 ([Gargron](https://github.com/mastodon/mastodon/pull/19733))
|
||||
- Add reputation and followers score boost to SQL-only account search ([Gargron](https://github.com/mastodon/mastodon/pull/19251))
|
||||
- Add Scots, Balaibalan, Láadan, Lingua Franca Nova, Lojban, Toki Pona to languages list ([VyrCossont](https://github.com/mastodon/mastodon/pull/20168))
|
||||
- Set autocomplete hints for e-mail, password and OTP fields ([rcombs](https://github.com/mastodon/mastodon/pull/19833), [offbyone](https://github.com/mastodon/mastodon/pull/19946), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/20071))
|
||||
- Add support for DigitalOcean Spaces in setup wizard ([v-aisac](https://github.com/mastodon/mastodon/pull/20573))
|
||||
|
||||
### Changed
|
||||
|
||||
- **Change brand color and logotypes** ([Gargron](https://github.com/mastodon/mastodon/pull/18592), [Gargron](https://github.com/mastodon/mastodon/pull/18639), [Gargron](https://github.com/mastodon/mastodon/pull/18691), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/18634), [Gargron](https://github.com/mastodon/mastodon/pull/19254), [mayaeh](https://github.com/mastodon/mastodon/pull/18710))
|
||||
- **Change post editing to be enabled in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/19103))
|
||||
- **Change web UI to work for logged-out users** ([Gargron](https://github.com/mastodon/mastodon/pull/18961), [Gargron](https://github.com/mastodon/mastodon/pull/19250), [Gargron](https://github.com/mastodon/mastodon/pull/19294), [Gargron](https://github.com/mastodon/mastodon/pull/19306), [Gargron](https://github.com/mastodon/mastodon/pull/19315), [ykzts](https://github.com/mastodon/mastodon/pull/19322), [Gargron](https://github.com/mastodon/mastodon/pull/19412), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19437), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19415), [Gargron](https://github.com/mastodon/mastodon/pull/19348), [Gargron](https://github.com/mastodon/mastodon/pull/19295), [Gargron](https://github.com/mastodon/mastodon/pull/19422), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19414), [Gargron](https://github.com/mastodon/mastodon/pull/19319), [Gargron](https://github.com/mastodon/mastodon/pull/19345), [Gargron](https://github.com/mastodon/mastodon/pull/19310), [Gargron](https://github.com/mastodon/mastodon/pull/19301), [Gargron](https://github.com/mastodon/mastodon/pull/19423), [ykzts](https://github.com/mastodon/mastodon/pull/19471), [ykzts](https://github.com/mastodon/mastodon/pull/19333), [ykzts](https://github.com/mastodon/mastodon/pull/19337), [ykzts](https://github.com/mastodon/mastodon/pull/19272), [ykzts](https://github.com/mastodon/mastodon/pull/19468), [Gargron](https://github.com/mastodon/mastodon/pull/19466), [Gargron](https://github.com/mastodon/mastodon/pull/19457), [Gargron](https://github.com/mastodon/mastodon/pull/19426), [Gargron](https://github.com/mastodon/mastodon/pull/19427), [Gargron](https://github.com/mastodon/mastodon/pull/19421), [Gargron](https://github.com/mastodon/mastodon/pull/19417), [Gargron](https://github.com/mastodon/mastodon/pull/19413), [Gargron](https://github.com/mastodon/mastodon/pull/19397), [Gargron](https://github.com/mastodon/mastodon/pull/19387), [Gargron](https://github.com/mastodon/mastodon/pull/19396), [Gargron](https://github.com/mastodon/mastodon/pull/19385), [ykzts](https://github.com/mastodon/mastodon/pull/19334), [ykzts](https://github.com/mastodon/mastodon/pull/19329), [Gargron](https://github.com/mastodon/mastodon/pull/19324), [Gargron](https://github.com/mastodon/mastodon/pull/19318), [Gargron](https://github.com/mastodon/mastodon/pull/19316), [Gargron](https://github.com/mastodon/mastodon/pull/19263), [trwnh](https://github.com/mastodon/mastodon/pull/19305), [ykzts](https://github.com/mastodon/mastodon/pull/19273), [Gargron](https://github.com/mastodon/mastodon/pull/19801), [Gargron](https://github.com/mastodon/mastodon/pull/19790), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19773), [Gargron](https://github.com/mastodon/mastodon/pull/19798), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19724), [Gargron](https://github.com/mastodon/mastodon/pull/19709), [Gargron](https://github.com/mastodon/mastodon/pull/19514), [Gargron](https://github.com/mastodon/mastodon/pull/19562), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19981), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19978), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/20148), [Gargron](https://github.com/mastodon/mastodon/pull/20302), [cutls](https://github.com/mastodon/mastodon/pull/20400))
|
||||
- The web app can now be accessed without being logged in
|
||||
- No more `/web` prefix on web app paths
|
||||
- Profiles, posts, and other public pages now use the same interface for logged in and logged out users
|
||||
- The web app displays a server information banner
|
||||
- Pop-up windows for remote interaction have been replaced with a modal window
|
||||
- No need to type in your username for remote interaction, copy-paste-to-search method explained
|
||||
- Various hints throughout the app explain what the different timelines are
|
||||
- New about page design
|
||||
- New privacy policy page design shows when the policy was last updated
|
||||
- All sections of the web app now have appropriate window titles
|
||||
- The layout of the interface has been streamlined between different screen sizes
|
||||
- Posts now use more horizontal space
|
||||
- Change label of publish button to be "Publish" again in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/18583))
|
||||
- Change language to be carried over on reply in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18557))
|
||||
- Change "Unfollow" to "Cancel follow request" when request still pending in web UI ([prplecake](https://github.com/mastodon/mastodon/pull/19363))
|
||||
- **Change post filtering system** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18058), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19050), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/18894), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19051), [noellabo](https://github.com/mastodon/mastodon/pull/18923), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/18956), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/18744), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/19878), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/20567))
|
||||
- Filtered keywords and phrases can now be grouped into named categories
|
||||
- Filtered posts show which exact filter was hit
|
||||
- Individual posts can be added to a filter
|
||||
- You can peek inside filtered posts anyway
|
||||
- Change path of privacy policy page from `/terms` to `/privacy-policy` ([Gargron](https://github.com/mastodon/mastodon/pull/19249))
|
||||
- Change how hashtags are normalized ([Gargron](https://github.com/mastodon/mastodon/pull/18795), [Gargron](https://github.com/mastodon/mastodon/pull/18863), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/18854))
|
||||
- Change settings area to be separated into categories in admin UI ([Gargron](https://github.com/mastodon/mastodon/pull/19407), [Gargron](https://github.com/mastodon/mastodon/pull/19533))
|
||||
- Change "No accounts selected" errors to use the appropriate noun in admin UI ([prplecake](https://github.com/mastodon/mastodon/pull/19356))
|
||||
- Change e-mail domain blocks to match subdomains of blocked domains ([Gargron](https://github.com/mastodon/mastodon/pull/18979))
|
||||
- Change custom emoji file size limit from 50 KB to 256 KB ([Gargron](https://github.com/mastodon/mastodon/pull/18788))
|
||||
- Change "Allow trends without prior review" setting to also work for trending posts ([Gargron](https://github.com/mastodon/mastodon/pull/17977))
|
||||
- Change admin announcements form to use single inputs for date and time in admin UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18321))
|
||||
- Change search API to be accessible without being logged in ([Gargron](https://github.com/mastodon/mastodon/pull/18963), [Gargron](https://github.com/mastodon/mastodon/pull/19326))
|
||||
- Change following and followers API to be accessible without being logged in ([Gargron](https://github.com/mastodon/mastodon/pull/18964))
|
||||
- Change `AUTHORIZED_FETCH` to not block unauthenticated REST API access ([Gargron](https://github.com/mastodon/mastodon/pull/19803))
|
||||
- Change Helm configuration ([deepy](https://github.com/mastodon/mastodon/pull/18997), [jgsmith](https://github.com/mastodon/mastodon/pull/18415), [deepy](https://github.com/mastodon/mastodon/pull/18941))
|
||||
- Change mentions of blocked users to not be processed ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19725))
|
||||
- Change max. thumbnail dimensions to 640x360px (360p) ([Gargron](https://github.com/mastodon/mastodon/pull/19619))
|
||||
- Change post-processing to be deferred only for large media types ([Gargron](https://github.com/mastodon/mastodon/pull/19617))
|
||||
- Change link verification to only work for https links without unicode ([Gargron](https://github.com/mastodon/mastodon/pull/20304), [Gargron](https://github.com/mastodon/mastodon/pull/20295))
|
||||
- Change account deletion requests to spread out over time ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20222))
|
||||
- Change larger reblogs/favourites numbers to be shortened in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/20303))
|
||||
- Change incoming activity processing to happen in `ingress` queue ([Gargron](https://github.com/mastodon/mastodon/pull/20264))
|
||||
- Change notifications to not link show preview cards in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20335))
|
||||
- Change amount of replies returned for logged out users in REST API ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20355))
|
||||
- Change in-app links to keep you in-app in web UI ([trwnh](https://github.com/mastodon/mastodon/pull/20540), [Gargron](https://github.com/mastodon/mastodon/pull/20628))
|
||||
- Change table header to be sticky in admin UI ([sk22](https://github.com/mastodon/mastodon/pull/20442))
|
||||
|
||||
### Removed
|
||||
|
||||
- Remove setting that disables account deletes ([Gargron](https://github.com/mastodon/mastodon/pull/17683))
|
||||
- Remove digest e-mails ([Gargron](https://github.com/mastodon/mastodon/pull/17985))
|
||||
- Remove unnecessary sections from welcome e-mail ([Gargron](https://github.com/mastodon/mastodon/pull/19299))
|
||||
- Remove item titles from RSS feeds ([Gargron](https://github.com/mastodon/mastodon/pull/18640))
|
||||
- Remove volume number from hashtags in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/19253))
|
||||
- Remove Nanobox configuration ([tonyjiang](https://github.com/mastodon/mastodon/pull/17881))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix rules with same priority being sorted non-deterministically ([Gargron](https://github.com/mastodon/mastodon/pull/20623))
|
||||
- Fix error when invalid domain name is submitted ([Gargron](https://github.com/mastodon/mastodon/pull/19474))
|
||||
- Fix icons having an image role ([Gargron](https://github.com/mastodon/mastodon/pull/20600))
|
||||
- Fix connections to IPv6-only servers ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20108))
|
||||
- Fix unnecessary service worker registration and preloading when logged out in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20341))
|
||||
- Fix unnecessary and slow regex construction ([raggi](https://github.com/mastodon/mastodon/pull/20215))
|
||||
- Fix `mailers` queue not being used for mailers ([Gargron](https://github.com/mastodon/mastodon/pull/20274))
|
||||
- Fix error in webfinger redirect handling ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20260))
|
||||
- Fix report category not being set to `violation` if rule IDs are provided ([trwnh](https://github.com/mastodon/mastodon/pull/20137))
|
||||
- Fix nodeinfo metadata attribute being an array instead of an object ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20114))
|
||||
- Fix account endorsements not being idempotent ([trwnh](https://github.com/mastodon/mastodon/pull/20118))
|
||||
- Fix status and rule IDs not being strings in admin reports REST API ([trwnh](https://github.com/mastodon/mastodon/pull/20122))
|
||||
- Fix error on invalid `replies_policy` in REST API ([trwnh](https://github.com/mastodon/mastodon/pull/20126))
|
||||
- Fix redrafting a currently-editing post not leaving edit mode in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20023))
|
||||
- Fix performance by avoiding method cache busts ([raggi](https://github.com/mastodon/mastodon/pull/19957))
|
||||
- Fix opening the language picker scrolling the single-column view to the top in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19983))
|
||||
- Fix content warning button missing `aria-expanded` attribute in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19975))
|
||||
- Fix redundant `aria-pressed` attributes in web UI ([Brawaru](https://github.com/mastodon/mastodon/pull/19912))
|
||||
- Fix crash when external auth provider has no display name set ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19962))
|
||||
- Fix followers count not being updated when migrating follows ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19998))
|
||||
- Fix double button to clear emoji search input in web UI ([sunny](https://github.com/mastodon/mastodon/pull/19888))
|
||||
- Fix missing null check on applications on strike disputes ([kescherCode](https://github.com/mastodon/mastodon/pull/19851))
|
||||
- Fix featured tags not saving preferred casing ([Gargron](https://github.com/mastodon/mastodon/pull/19732))
|
||||
- Fix language not being saved when editing status ([Gargron](https://github.com/mastodon/mastodon/pull/19543))
|
||||
- Fix not being able to input featured tag with hash symbol ([Gargron](https://github.com/mastodon/mastodon/pull/19535))
|
||||
- Fix user clean-up scheduler crash when an unconfirmed account has a moderation note ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19629))
|
||||
- Fix being unable to withdraw follow request when confirmation modal is disabled in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19687))
|
||||
- Fix inaccurate admin log entry for re-sending confirmation e-mails ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19674))
|
||||
- Fix edits not being immediately reflected ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19673))
|
||||
- Fix bookmark import stopping at the first failure ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19669))
|
||||
- Fix account action type validation ([Gargron](https://github.com/mastodon/mastodon/pull/19476))
|
||||
- Fix upload progress not communicating processing phase in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/19530))
|
||||
- Fix wrong host being used for custom.css when asset host configured ([Gargron](https://github.com/mastodon/mastodon/pull/19521))
|
||||
- Fix account migration form ever using outdated account data ([Gargron](https://github.com/mastodon/mastodon/pull/18429), [nightpool](https://github.com/mastodon/mastodon/pull/19883))
|
||||
- Fix error when uploading malformed CSV import ([Gargron](https://github.com/mastodon/mastodon/pull/19509))
|
||||
- Fix avatars not using image tags in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/19488))
|
||||
- Fix handling of duplicate and out-of-order notifications in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19693))
|
||||
- Fix reblogs being discarded after the reblogged status ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19731))
|
||||
- Fix indexing scheduler trying to index when Elasticsearch is disabled ([Gargron](https://github.com/mastodon/mastodon/pull/19805))
|
||||
- Fix n+1 queries when rendering initial state JSON ([Gargron](https://github.com/mastodon/mastodon/pull/19795))
|
||||
- Fix n+1 query during status removal ([Gargron](https://github.com/mastodon/mastodon/pull/19753))
|
||||
- Fix OCR not working due to Content Security Policy in web UI ([prplecake](https://github.com/mastodon/mastodon/pull/18817))
|
||||
- Fix `nofollow` rel being removed in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/19455))
|
||||
- Fix language dropdown causing zoom on mobile devices in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/19428))
|
||||
- Fix button to dismiss suggestions not showing up in search results in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19325))
|
||||
- Fix language dropdown sometimes not appearing in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/19246))
|
||||
- Fix quickly switching notification filters resulting in empty or incorrect list in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19052), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/18960))
|
||||
- Fix media modal link button in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18877))
|
||||
- Fix error upon successful account migration ([Gargron](https://github.com/mastodon/mastodon/pull/19386))
|
||||
- Fix negatives values in search index causing queries to fail ([Gargron](https://github.com/mastodon/mastodon/pull/19464), [Gargron](https://github.com/mastodon/mastodon/pull/19481))
|
||||
- Fix error when searching for invalid URL ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18580))
|
||||
- Fix IP blocks not having a unique index ([Gargron](https://github.com/mastodon/mastodon/pull/19456))
|
||||
- Fix remote account in contact account setting not being used ([Gargron](https://github.com/mastodon/mastodon/pull/19351))
|
||||
- Fix swallowing mentions of unconfirmed/unapproved users ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19191))
|
||||
- Fix incorrect and slow cache invalidation when blocking domain and removing media attachments ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19062))
|
||||
- Fix HTTPs redirect behaviour when running as I2P service ([gi-yt](https://github.com/mastodon/mastodon/pull/18929))
|
||||
- Fix deleted pinned posts potentially counting towards the pinned posts limit ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19005))
|
||||
- Fix compatibility with OpenSSL 3.0 ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18449))
|
||||
- Fix error when a remote report includes a private post the server has no access to ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18760))
|
||||
- Fix suspicious sign-in mails never being sent ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18599))
|
||||
- Fix fallback locale when somehow user's locale is an empty string ([tribela](https://github.com/mastodon/mastodon/pull/18543))
|
||||
- Fix avatar/header not being deleted locally when deleted on remote account ([tribela](https://github.com/mastodon/mastodon/pull/18973))
|
||||
- Fix missing `,` in Blurhash validation ([noellabo](https://github.com/mastodon/mastodon/pull/18660))
|
||||
- Fix order by most recent not working for relationships page in admin UI ([tribela](https://github.com/mastodon/mastodon/pull/18996))
|
||||
- Fix uncaught error when invalid date is supplied to API ([Gargron](https://github.com/mastodon/mastodon/pull/19480))
|
||||
- Fix REST API sometimes returning HTML on error ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19135))
|
||||
- Fix ambiguous column names in `tootctl media refresh` ([tribela](https://github.com/mastodon/mastodon/pull/19206))
|
||||
- Fix ambiguous column names in `tootctl search deploy` ([mashirozx](https://github.com/mastodon/mastodon/pull/18993))
|
||||
- Fix `CDN_HOST` not being used in some asset URLs ([tribela](https://github.com/mastodon/mastodon/pull/18662))
|
||||
- Fix `CAS_DISPLAY_NAME`, `SAML_DISPLAY_NAME` and `OIDC_DISPLAY_NAME` being ignored ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18568))
|
||||
- Fix various typos in comments throughout the codebase ([luzpaz](https://github.com/mastodon/mastodon/pull/18604))
|
||||
- Fix CSV import error when rows include unicode characters ([HamptonMakes](https://github.com/mastodon/mastodon/pull/20592))
|
||||
|
||||
### Security
|
||||
|
||||
- Fix being able to spoof link verification ([Gargron](https://github.com/mastodon/mastodon/pull/20217))
|
||||
- Fix emoji substitution not applying only to text nodes in backend code ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20641))
|
||||
- Fix emoji substitution not applying only to text nodes in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20640))
|
||||
- Fix rate limiting for paths with formats ([Gargron](https://github.com/mastodon/mastodon/pull/20675))
|
||||
- Fix out-of-bound reads in blurhash transcoder ([delroth](https://github.com/mastodon/mastodon/pull/20388))
|
||||
|
||||
## [3.5.3] - 2022-05-26
|
||||
### Added
|
||||
|
||||
|
@ -122,7 +332,7 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
### Fixed
|
||||
|
||||
- Fix error resposes for `from` search prefix ([single-right-quote](https://github.com/mastodon/mastodon/pull/17963))
|
||||
- Fix error responses for `from` search prefix ([single-right-quote](https://github.com/mastodon/mastodon/pull/17963))
|
||||
- Fix dangling language-specific trends ([Gargron](https://github.com/mastodon/mastodon/pull/17997))
|
||||
- Fix extremely rare race condition when deleting a status or account ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17994))
|
||||
- Fix trends returning less results per page when filtered in REST API ([Gargron](https://github.com/mastodon/mastodon/pull/17996))
|
||||
|
@ -257,7 +467,7 @@ All notable changes to this project will be documented in this file.
|
|||
- Remove profile directory link from main navigation panel in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/17688))
|
||||
- **Remove language detection through cld3** ([Gargron](https://github.com/mastodon/mastodon/pull/17478), [ykzts](https://github.com/mastodon/mastodon/pull/17539), [Gargron](https://github.com/mastodon/mastodon/pull/17496), [Gargron](https://github.com/mastodon/mastodon/pull/17722))
|
||||
- cld3 is very inaccurate on short-form content even with unique alphabets
|
||||
- Post language can be overriden individually using `language` param
|
||||
- Post language can be overridden individually using `language` param
|
||||
- Otherwise, it defaults to the user's interface language
|
||||
- Remove support for `OAUTH_REDIRECT_AT_SIGN_IN` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17287))
|
||||
- Use `OMNIAUTH_ONLY` instead
|
||||
|
|
|
@ -27,7 +27,7 @@ See the guidelines below.
|
|||
|
||||
- - -
|
||||
|
||||
You should also try to follow the guidelines set out in the original `CONTRIBUTING.md` from `tootsuite/mastodon`, reproduced below.
|
||||
You should also try to follow the guidelines set out in the original `CONTRIBUTING.md` from `mastodon/mastodon`, reproduced below.
|
||||
|
||||
<blockquote>
|
||||
|
||||
|
@ -76,6 +76,8 @@ It is not always possible to phrase every change in such a manner, but it is des
|
|||
- Code style rules (rubocop, eslint)
|
||||
- Normalization of locale files (i18n-tasks)
|
||||
|
||||
**Note**: You may need to log in and authorise the GitHub account your fork of this repository belongs to with CircleCI to enable some of the automated checks to run.
|
||||
|
||||
## Documentation
|
||||
|
||||
The [Mastodon documentation](https://docs.joinmastodon.org) is a statically generated site. You can [submit merge requests to mastodon/documentation](https://github.com/mastodon/documentation).
|
||||
|
|
167
Dockerfile
167
Dockerfile
|
@ -1,121 +1,96 @@
|
|||
FROM ubuntu:20.04 as build-dep
|
||||
# syntax=docker/dockerfile:1.4
|
||||
# This needs to be bullseye-slim because the Ruby image is built on bullseye-slim
|
||||
ARG NODE_VERSION="16.17.1-bullseye-slim"
|
||||
|
||||
# Use bash for the shell
|
||||
SHELL ["/bin/bash", "-c"]
|
||||
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
|
||||
FROM ghcr.io/moritzheiber/ruby-jemalloc:3.0.4-slim as ruby
|
||||
FROM node:${NODE_VERSION} as build
|
||||
|
||||
# Install Node v16 (LTS)
|
||||
ENV NODE_VER="16.17.1"
|
||||
RUN ARCH= && \
|
||||
dpkgArch="$(dpkg --print-architecture)" && \
|
||||
case "${dpkgArch##*-}" in \
|
||||
amd64) ARCH='x64';; \
|
||||
ppc64el) ARCH='ppc64le';; \
|
||||
s390x) ARCH='s390x';; \
|
||||
arm64) ARCH='arm64';; \
|
||||
armhf) ARCH='armv7l';; \
|
||||
i386) ARCH='x86';; \
|
||||
*) echo "unsupported architecture"; exit 1 ;; \
|
||||
esac && \
|
||||
echo "Etc/UTC" > /etc/localtime && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends ca-certificates wget python3 apt-utils && \
|
||||
cd ~ && \
|
||||
wget -q https://nodejs.org/download/release/v$NODE_VER/node-v$NODE_VER-linux-$ARCH.tar.gz && \
|
||||
tar xf node-v$NODE_VER-linux-$ARCH.tar.gz && \
|
||||
rm node-v$NODE_VER-linux-$ARCH.tar.gz && \
|
||||
mv node-v$NODE_VER-linux-$ARCH /opt/node
|
||||
COPY --link --from=ruby /opt/ruby /opt/ruby
|
||||
|
||||
# Install Ruby 3.0
|
||||
ENV RUBY_VER="3.0.4"
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends build-essential \
|
||||
bison libyaml-dev libgdbm-dev libreadline-dev libjemalloc-dev \
|
||||
libncurses5-dev libffi-dev zlib1g-dev libssl-dev && \
|
||||
cd ~ && \
|
||||
wget https://cache.ruby-lang.org/pub/ruby/${RUBY_VER%.*}/ruby-$RUBY_VER.tar.gz && \
|
||||
tar xf ruby-$RUBY_VER.tar.gz && \
|
||||
cd ruby-$RUBY_VER && \
|
||||
./configure --prefix=/opt/ruby \
|
||||
--with-jemalloc \
|
||||
--with-shared \
|
||||
--disable-install-doc && \
|
||||
make -j"$(nproc)" > /dev/null && \
|
||||
make install && \
|
||||
rm -rf ../ruby-$RUBY_VER.tar.gz ../ruby-$RUBY_VER
|
||||
ENV DEBIAN_FRONTEND="noninteractive" \
|
||||
PATH="${PATH}:/opt/ruby/bin"
|
||||
|
||||
ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin"
|
||||
|
||||
RUN npm install -g npm@latest && \
|
||||
npm install -g yarn && \
|
||||
gem install bundler && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends git libicu-dev libidn11-dev \
|
||||
libpq-dev shared-mime-info
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
|
||||
WORKDIR /opt/mastodon
|
||||
COPY Gemfile* package.json yarn.lock /opt/mastodon/
|
||||
|
||||
RUN cd /opt/mastodon && \
|
||||
bundle config set --local deployment 'true' && \
|
||||
bundle config set --local without 'development test' && \
|
||||
bundle config set silence_root_warning true && \
|
||||
bundle install -j"$(nproc)" && \
|
||||
yarn install --pure-lockfile
|
||||
RUN apt update && \
|
||||
apt-get install -y --no-install-recommends build-essential \
|
||||
ca-certificates \
|
||||
git \
|
||||
libicu-dev \
|
||||
libidn11-dev \
|
||||
libpq-dev \
|
||||
libjemalloc-dev \
|
||||
zlib1g-dev \
|
||||
libgdbm-dev \
|
||||
libgmp-dev \
|
||||
libssl-dev \
|
||||
libyaml-0-2 \
|
||||
ca-certificates \
|
||||
libreadline8 \
|
||||
python3 \
|
||||
shared-mime-info && \
|
||||
bundle config set --local deployment 'true' && \
|
||||
bundle config set --local without 'development test' && \
|
||||
bundle config set silence_root_warning true && \
|
||||
bundle install -j"$(nproc)" && \
|
||||
yarn install --pure-lockfile
|
||||
|
||||
FROM ubuntu:20.04
|
||||
FROM node:${NODE_VERSION}
|
||||
|
||||
# Copy over all the langs needed for runtime
|
||||
COPY --from=build-dep /opt/node /opt/node
|
||||
COPY --from=build-dep /opt/ruby /opt/ruby
|
||||
ARG UID="991"
|
||||
ARG GID="991"
|
||||
|
||||
# Add more PATHs to the PATH
|
||||
ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin:/opt/mastodon/bin"
|
||||
COPY --link --from=ruby /opt/ruby /opt/ruby
|
||||
|
||||
# Create the mastodon user
|
||||
ARG UID=991
|
||||
ARG GID=991
|
||||
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||
RUN apt-get update && \
|
||||
echo "Etc/UTC" > /etc/localtime && \
|
||||
apt-get install -y --no-install-recommends whois wget && \
|
||||
addgroup --gid $GID mastodon && \
|
||||
useradd -m -u $UID -g $GID -d /opt/mastodon mastodon && \
|
||||
echo "mastodon:$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c 24 | mkpasswd -s -m sha-256)" | chpasswd && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install mastodon runtime deps
|
||||
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
|
||||
RUN apt-get update && \
|
||||
apt-get -y --no-install-recommends install \
|
||||
libssl1.1 libpq5 imagemagick ffmpeg libjemalloc2 \
|
||||
libicu66 libidn11 libyaml-0-2 \
|
||||
file ca-certificates tzdata libreadline8 gcc tini apt-utils && \
|
||||
ln -s /opt/mastodon /mastodon && \
|
||||
gem install bundler && \
|
||||
rm -rf /var/cache && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
ENV DEBIAN_FRONTEND="noninteractive" \
|
||||
PATH="${PATH}:/opt/ruby/bin:/opt/mastodon/bin"
|
||||
|
||||
RUN apt-get update && \
|
||||
echo "Etc/UTC" > /etc/localtime && \
|
||||
groupadd -g "${GID}" mastodon && \
|
||||
useradd -u "$UID" -g "${GID}" -m -d /opt/mastodon mastodon && \
|
||||
apt-get -y --no-install-recommends install whois \
|
||||
wget \
|
||||
procps \
|
||||
libssl1.1 \
|
||||
libpq5 \
|
||||
imagemagick \
|
||||
ffmpeg \
|
||||
libjemalloc2 \
|
||||
libicu67 \
|
||||
libidn11 \
|
||||
libyaml-0-2 \
|
||||
file \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
libreadline8 \
|
||||
tini && \
|
||||
ln -s /opt/mastodon /mastodon
|
||||
|
||||
# Note: no, cleaning here since Debian does this automatically
|
||||
# See the file /etc/apt/apt.conf.d/docker-clean within the Docker image's filesystem
|
||||
|
||||
# Copy over mastodon source, and dependencies from building, and set permissions
|
||||
COPY --chown=mastodon:mastodon . /opt/mastodon
|
||||
COPY --from=build-dep --chown=mastodon:mastodon /opt/mastodon /opt/mastodon
|
||||
COPY --chown=mastodon:mastodon --from=build /opt/mastodon /opt/mastodon
|
||||
|
||||
# Run mastodon services in prod mode
|
||||
ENV RAILS_ENV="production"
|
||||
ENV NODE_ENV="production"
|
||||
|
||||
# Tell rails to serve static files
|
||||
ENV RAILS_SERVE_STATIC_FILES="true"
|
||||
ENV BIND="0.0.0.0"
|
||||
ENV RAILS_ENV="production" \
|
||||
NODE_ENV="production" \
|
||||
RAILS_SERVE_STATIC_FILES="true" \
|
||||
BIND="0.0.0.0"
|
||||
|
||||
# Set the run user
|
||||
USER mastodon
|
||||
WORKDIR /opt/mastodon
|
||||
|
||||
# Precompile assets
|
||||
RUN cd ~ && \
|
||||
OTP_SECRET=precompile_placeholder SECRET_KEY_BASE=precompile_placeholder rails assets:precompile && \
|
||||
yarn cache clean
|
||||
RUN OTP_SECRET=precompile_placeholder SECRET_KEY_BASE=precompile_placeholder rails assets:precompile && \
|
||||
yarn cache clean
|
||||
|
||||
# Set the work dir and the container entry point
|
||||
WORKDIR /opt/mastodon
|
||||
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||
EXPOSE 3000 4000
|
||||
|
|
7
Gemfile
7
Gemfile
|
@ -72,6 +72,7 @@ gem 'rack-attack', '~> 6.6'
|
|||
gem 'rack-cors', '~> 1.1', require: 'rack/cors'
|
||||
gem 'rails-i18n', '~> 6.0'
|
||||
gem 'rails-settings-cached', '~> 0.6'
|
||||
gem 'redcarpet', '~> 3.5'
|
||||
gem 'redis', '~> 4.5', require: ['redis', 'redis/connection/hiredis']
|
||||
gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
|
||||
gem 'rqrcode', '~> 2.1'
|
||||
|
@ -91,15 +92,13 @@ gem 'tty-prompt', '~> 0.23', require: false
|
|||
gem 'twitter-text', '~> 3.1.0'
|
||||
gem 'tzinfo-data', '~> 1.2022'
|
||||
gem 'webpacker', '~> 5.4'
|
||||
gem 'webpush', git: 'https://github.com/ClearlyClaire/webpush.git', ref: 'f14a4d52e201128b1b00245d11b6de80d6cfdcd9'
|
||||
gem 'webpush', github: 'ClearlyClaire/webpush', ref: 'f14a4d52e201128b1b00245d11b6de80d6cfdcd9'
|
||||
gem 'webauthn', '~> 2.5'
|
||||
|
||||
gem 'json-ld'
|
||||
gem 'json-ld-preloaded', '~> 3.2'
|
||||
gem 'rdf-normalize', '~> 0.5'
|
||||
|
||||
gem 'redcarpet', '~> 3.5'
|
||||
|
||||
group :development, :test do
|
||||
gem 'fabrication', '~> 2.30'
|
||||
gem 'fuubar', '~> 2.5'
|
||||
|
@ -123,6 +122,7 @@ group :test do
|
|||
gem 'simplecov', '~> 0.21', require: false
|
||||
gem 'webmock', '~> 3.18'
|
||||
gem 'rspec_junit_formatter', '~> 0.6'
|
||||
gem 'rack-test', '~> 2.0'
|
||||
end
|
||||
|
||||
group :development do
|
||||
|
@ -153,7 +153,6 @@ end
|
|||
|
||||
gem 'concurrent-ruby', require: false
|
||||
gem 'connection_pool', require: false
|
||||
|
||||
gem 'xorcist', '~> 1.1'
|
||||
|
||||
gem 'hcaptcha', '~> 7.1'
|
||||
|
|
29
Gemfile.lock
29
Gemfile.lock
|
@ -272,7 +272,7 @@ GEM
|
|||
fog-json (>= 1.0)
|
||||
ipaddress (>= 0.8)
|
||||
formatador (0.2.5)
|
||||
fugit (1.5.3)
|
||||
fugit (1.7.1)
|
||||
et-orbi (~> 1, >= 1.2.7)
|
||||
raabro (~> 1.4)
|
||||
fuubar (2.5.1)
|
||||
|
@ -343,7 +343,7 @@ GEM
|
|||
multi_json (~> 1.15)
|
||||
rack (~> 2.2)
|
||||
rdf (~> 3.2, >= 3.2.9)
|
||||
json-ld-preloaded (3.2.0)
|
||||
json-ld-preloaded (3.2.2)
|
||||
json-ld (~> 3.2)
|
||||
rdf (~> 3.2)
|
||||
jsonapi-renderer (0.2.2)
|
||||
|
@ -395,7 +395,7 @@ GEM
|
|||
mario-redis-lock (1.2.1)
|
||||
redis (>= 3.0.5)
|
||||
matrix (0.4.2)
|
||||
memory_profiler (1.0.0)
|
||||
memory_profiler (1.0.1)
|
||||
method_source (1.0.0)
|
||||
microformats (4.4.1)
|
||||
json (~> 2.2)
|
||||
|
@ -414,7 +414,7 @@ GEM
|
|||
net-ssh (>= 2.6.5, < 8.0.0)
|
||||
net-ssh (7.0.1)
|
||||
nio4r (2.5.8)
|
||||
nokogiri (1.13.8)
|
||||
nokogiri (1.13.9)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
nsa (0.2.8)
|
||||
|
@ -422,7 +422,7 @@ GEM
|
|||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
sidekiq (>= 3.5)
|
||||
statsd-ruby (~> 1.4, >= 1.4.0)
|
||||
oj (3.13.21)
|
||||
oj (3.13.23)
|
||||
omniauth (1.9.2)
|
||||
hashie (>= 3.4.6)
|
||||
rack (>= 1.6.2, < 3)
|
||||
|
@ -457,7 +457,7 @@ GEM
|
|||
parslet (2.0.0)
|
||||
pastel (0.8.0)
|
||||
tty-color (~> 0.5)
|
||||
pg (1.4.3)
|
||||
pg (1.4.4)
|
||||
pghero (2.8.3)
|
||||
activerecord (>= 5)
|
||||
pkg-config (1.4.9)
|
||||
|
@ -600,7 +600,7 @@ GEM
|
|||
nokogiri (>= 1.10.5)
|
||||
rexml
|
||||
ruby2_keywords (0.0.5)
|
||||
rufus-scheduler (3.8.1)
|
||||
rufus-scheduler (3.8.2)
|
||||
fugit (~> 1.1, >= 1.1.6)
|
||||
safety_net_attestation (0.4.0)
|
||||
jwt (~> 2.0)
|
||||
|
@ -611,16 +611,16 @@ GEM
|
|||
activerecord (>= 4.0.0)
|
||||
railties (>= 4.0.0)
|
||||
semantic_range (3.0.0)
|
||||
sidekiq (6.5.7)
|
||||
connection_pool (>= 2.2.5)
|
||||
sidekiq (6.5.8)
|
||||
connection_pool (>= 2.2.5, < 3)
|
||||
rack (~> 2.0)
|
||||
redis (>= 4.5.0, < 5)
|
||||
sidekiq-bulk (0.2.0)
|
||||
sidekiq
|
||||
sidekiq-scheduler (4.0.2)
|
||||
sidekiq-scheduler (4.0.3)
|
||||
redis (>= 4.2.0)
|
||||
rufus-scheduler (~> 3.2)
|
||||
sidekiq (>= 4)
|
||||
sidekiq (>= 4, < 7)
|
||||
tilt (>= 1.4.0)
|
||||
sidekiq-unique-jobs (7.1.27)
|
||||
brpoplpush-redis_script (> 0.1.1, <= 2.0.0)
|
||||
|
@ -649,7 +649,7 @@ GEM
|
|||
sshkit (1.21.2)
|
||||
net-scp (>= 1.1.2)
|
||||
net-ssh (>= 2.8.0)
|
||||
stackprof (0.2.21)
|
||||
stackprof (0.2.22)
|
||||
statsd-ruby (1.5.0)
|
||||
stoplight (3.0.0)
|
||||
strong_migrations (0.7.9)
|
||||
|
@ -664,7 +664,7 @@ GEM
|
|||
terrapin (0.6.0)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
thor (1.2.1)
|
||||
tilt (2.0.10)
|
||||
tilt (2.0.11)
|
||||
tpm-key_attestation (0.11.0)
|
||||
bindata (~> 2.4)
|
||||
openssl (> 2.0, < 3.1)
|
||||
|
@ -684,7 +684,7 @@ GEM
|
|||
unf (~> 0.1.0)
|
||||
tzinfo (2.0.5)
|
||||
concurrent-ruby (~> 1.0)
|
||||
tzinfo-data (1.2022.4)
|
||||
tzinfo-data (1.2022.6)
|
||||
tzinfo (>= 1.0.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
|
@ -818,6 +818,7 @@ DEPENDENCIES
|
|||
rack (~> 2.2.4)
|
||||
rack-attack (~> 6.6)
|
||||
rack-cors (~> 1.1)
|
||||
rack-test (~> 2.0)
|
||||
rails (~> 6.1.7)
|
||||
rails-controller-testing (~> 1.0)
|
||||
rails-i18n (~> 6.0)
|
||||
|
|
15
SECURITY.md
15
SECURITY.md
|
@ -1,6 +1,6 @@
|
|||
# Security Policy
|
||||
|
||||
If you believe you've identified a security vulnerability in Mastodon (a bug that allows something to happen that shouldn't be possible), you should submit the report through our [Bug Bounty Program][bug-bounty]. Alternatively, you can reach us at <hello@joinmastodon.org>.
|
||||
If you believe you've identified a security vulnerability in Mastodon (a bug that allows something to happen that shouldn't be possible), you can reach us at <security@joinmastodon.org>.
|
||||
|
||||
You should *not* report such issues on GitHub or in other public spaces to give us time to publish a fix for the issue without exposing Mastodon's users to increased risk.
|
||||
|
||||
|
@ -10,11 +10,8 @@ A "vulnerability in Mastodon" is a vulnerability in the code distributed through
|
|||
|
||||
## Supported Versions
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 3.5.x | Yes |
|
||||
| 3.4.x | Yes |
|
||||
| 3.3.x | No |
|
||||
| < 3.3 | No |
|
||||
|
||||
[bug-bounty]: https://app.intigriti.com/programs/mastodon/mastodonio/detail
|
||||
| Version | Supported |
|
||||
| ------- | ----------|
|
||||
| 4.0.x | Yes |
|
||||
| 3.5.x | Yes |
|
||||
| < 3.5 | No |
|
||||
|
|
7
app.json
7
app.json
|
@ -79,8 +79,13 @@
|
|||
"description": "SMTP server certificate verification mode. Defaults is 'peer'.",
|
||||
"required": false
|
||||
},
|
||||
"SMTP_ENABLE_STARTTLS": {
|
||||
"description": "Enable STARTTLS? Default is 'auto'.",
|
||||
"value": "auto",
|
||||
"required": false
|
||||
},
|
||||
"SMTP_ENABLE_STARTTLS_AUTO": {
|
||||
"description": "Enable STARTTLS if SMTP server supports it? Default is true.",
|
||||
"description": "Enable STARTTLS if SMTP server supports it? Deprecated by SMTP_ENABLE_STARTTLS.",
|
||||
"required": false
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,72 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AboutController < ApplicationController
|
||||
include RegistrationSpamConcern
|
||||
include WebAppControllerConcern
|
||||
|
||||
before_action :set_pack
|
||||
skip_before_action :require_functional!
|
||||
|
||||
layout 'public'
|
||||
|
||||
before_action :require_open_federation!, only: [:show, :more]
|
||||
before_action :set_body_classes, only: :show
|
||||
before_action :set_instance_presenter
|
||||
before_action :set_expires_in, only: [:more]
|
||||
before_action :set_registration_form_time, only: :show
|
||||
|
||||
skip_before_action :require_functional!, only: [:more]
|
||||
|
||||
def show; end
|
||||
|
||||
def more
|
||||
flash.now[:notice] = I18n.t('about.instance_actor_flash') if params[:instance_actor]
|
||||
|
||||
toc_generator = TOCGenerator.new(@instance_presenter.extended_description)
|
||||
|
||||
@rules = Rule.ordered
|
||||
@contents = toc_generator.html
|
||||
@table_of_contents = toc_generator.toc
|
||||
@blocks = DomainBlock.with_user_facing_limitations.by_severity if display_blocks?
|
||||
def show
|
||||
expires_in 0, public: true unless user_signed_in?
|
||||
end
|
||||
|
||||
helper_method :display_blocks?
|
||||
helper_method :display_blocks_rationale?
|
||||
helper_method :public_fetch_mode?
|
||||
helper_method :new_user
|
||||
|
||||
private
|
||||
|
||||
def require_open_federation!
|
||||
not_found if whitelist_mode?
|
||||
end
|
||||
|
||||
def display_blocks?
|
||||
Setting.show_domain_blocks == 'all' || (Setting.show_domain_blocks == 'users' && user_signed_in?)
|
||||
end
|
||||
|
||||
def display_blocks_rationale?
|
||||
Setting.show_domain_blocks_rationale == 'all' || (Setting.show_domain_blocks_rationale == 'users' && user_signed_in?)
|
||||
end
|
||||
|
||||
def new_user
|
||||
User.new.tap do |user|
|
||||
user.build_account
|
||||
user.build_invite_request
|
||||
end
|
||||
end
|
||||
|
||||
def set_pack
|
||||
use_pack 'public'
|
||||
end
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@hide_navbar = true
|
||||
end
|
||||
|
||||
def set_expires_in
|
||||
expires_in 0, public: true
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AccountFollowController < ApplicationController
|
||||
include AccountControllerConcern
|
||||
|
||||
before_action :authenticate_user!
|
||||
|
||||
def create
|
||||
FollowService.new.call(current_user.account, @account, with_rate_limit: true)
|
||||
redirect_to account_path(@account)
|
||||
end
|
||||
end
|
|
@ -1,12 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AccountUnfollowController < ApplicationController
|
||||
include AccountControllerConcern
|
||||
|
||||
before_action :authenticate_user!
|
||||
|
||||
def create
|
||||
UnfollowService.new.call(current_user.account, @account)
|
||||
redirect_to account_path(@account)
|
||||
end
|
||||
end
|
|
@ -9,7 +9,6 @@ class AccountsController < ApplicationController
|
|||
|
||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_cache_headers
|
||||
before_action :set_body_classes
|
||||
|
||||
skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format&.to_sym) }
|
||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||
|
@ -17,26 +16,9 @@ class AccountsController < ApplicationController
|
|||
def show
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
use_pack 'public'
|
||||
expires_in 0, public: true unless user_signed_in?
|
||||
|
||||
@pinned_statuses = []
|
||||
@endorsed_accounts = @account.endorsed_accounts.to_a.sample(4)
|
||||
@featured_hashtags = @account.featured_tags.order(statuses_count: :desc)
|
||||
|
||||
if current_account && @account.blocking?(current_account)
|
||||
@statuses = []
|
||||
return
|
||||
end
|
||||
|
||||
@pinned_statuses = cached_filtered_status_pins if show_pinned_statuses?
|
||||
@statuses = cached_filtered_status_page
|
||||
@rss_url = rss_url
|
||||
|
||||
unless @statuses.empty?
|
||||
@older_url = older_url if @statuses.last.id > filtered_statuses.last.id
|
||||
@newer_url = newer_url if @statuses.first.id < filtered_statuses.first.id
|
||||
end
|
||||
@rss_url = rss_url
|
||||
end
|
||||
|
||||
format.rss do
|
||||
|
@ -56,18 +38,6 @@ class AccountsController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'with-modals'
|
||||
end
|
||||
|
||||
def show_pinned_statuses?
|
||||
[replies_requested?, media_requested?, tag_requested?, params[:max_id].present?, params[:min_id].present?].none?
|
||||
end
|
||||
|
||||
def filtered_pinned_statuses
|
||||
@account.pinned_statuses.not_local_only.where(visibility: [:public, :unlisted])
|
||||
end
|
||||
|
||||
def filtered_statuses
|
||||
default_statuses.tap do |statuses|
|
||||
statuses.merge!(hashtag_scope) if tag_requested?
|
||||
|
@ -114,26 +84,6 @@ class AccountsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def older_url
|
||||
pagination_url(max_id: @statuses.last.id)
|
||||
end
|
||||
|
||||
def newer_url
|
||||
pagination_url(min_id: @statuses.first.id)
|
||||
end
|
||||
|
||||
def pagination_url(max_id: nil, min_id: nil)
|
||||
if tag_requested?
|
||||
short_account_tag_url(@account, params[:tag], max_id: max_id, min_id: min_id)
|
||||
elsif media_requested?
|
||||
short_account_media_url(@account, max_id: max_id, min_id: min_id)
|
||||
elsif replies_requested?
|
||||
short_account_with_replies_url(@account, max_id: max_id, min_id: min_id)
|
||||
else
|
||||
short_account_url(@account, max_id: max_id, min_id: min_id)
|
||||
end
|
||||
end
|
||||
|
||||
def media_requested?
|
||||
request.path.split('.').first.end_with?('/media') && !tag_requested?
|
||||
end
|
||||
|
@ -146,13 +96,6 @@ class AccountsController < ApplicationController
|
|||
request.path.split('.').first.end_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
|
||||
end
|
||||
|
||||
def cached_filtered_status_pins
|
||||
cache_collection(
|
||||
filtered_pinned_statuses,
|
||||
Status
|
||||
)
|
||||
end
|
||||
|
||||
def cached_filtered_status_page
|
||||
cache_collection_paginated_by_id(
|
||||
filtered_statuses,
|
||||
|
|
|
@ -17,7 +17,7 @@ module Admin
|
|||
|
||||
@user.resend_confirmation_instructions
|
||||
|
||||
log_action :confirm, @user
|
||||
log_action :resend, @user
|
||||
|
||||
flash[:notice] = I18n.t('admin.accounts.resend_confirmation.success')
|
||||
redirect_to admin_accounts_path
|
||||
|
|
|
@ -34,7 +34,7 @@ module Admin
|
|||
@form = Form::CustomEmojiBatch.new(form_custom_emoji_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.accounts.no_account_selected')
|
||||
flash[:alert] = I18n.t('admin.custom_emojis.no_emoji_selected')
|
||||
rescue Mastodon::NotPermittedError
|
||||
flash[:alert] = I18n.t('admin.custom_emojis.not_permitted')
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
|
|
|
@ -9,9 +9,9 @@ module Admin
|
|||
@form = Form::DomainBlockBatch.new(form_domain_block_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.email_domain_blocks.no_domain_block_selected')
|
||||
flash[:alert] = I18n.t('admin.domain_blocks.no_domain_block_selected')
|
||||
rescue Mastodon::NotPermittedError
|
||||
flash[:alert] = I18n.t('admin.domain_blocks.created_msg')
|
||||
flash[:alert] = I18n.t('admin.domain_blocks.not_permitted')
|
||||
else
|
||||
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
|
||||
end
|
||||
|
|
|
@ -19,7 +19,7 @@ module Admin
|
|||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.email_domain_blocks.no_email_domain_block_selected')
|
||||
rescue Mastodon::NotPermittedError
|
||||
flash[:alert] = I18n.t('admin.custom_emojis.not_permitted')
|
||||
flash[:alert] = I18n.t('admin.email_domain_blocks.not_permitted')
|
||||
ensure
|
||||
redirect_to admin_email_domain_blocks_path
|
||||
end
|
||||
|
|
|
@ -8,8 +8,6 @@ module Admin
|
|||
|
||||
before_action :set_dummy_import!, only: [:new]
|
||||
|
||||
ROWS_PROCESSING_LIMIT = 20_000
|
||||
|
||||
def new
|
||||
authorize :domain_allow, :create?
|
||||
end
|
||||
|
@ -23,9 +21,11 @@ module Admin
|
|||
authorize :domain_allow, :create?
|
||||
begin
|
||||
@import = Admin::Import.new(import_params)
|
||||
return render :new unless @import.validate
|
||||
|
||||
parse_import_data!(export_headers)
|
||||
|
||||
@data.take(ROWS_PROCESSING_LIMIT).each do |row|
|
||||
@data.take(Admin::Import::ROWS_PROCESSING_LIMIT).each do |row|
|
||||
domain = row['#domain'].strip
|
||||
next if DomainAllow.allowed?(domain)
|
||||
|
||||
|
|
|
@ -8,8 +8,6 @@ module Admin
|
|||
|
||||
before_action :set_dummy_import!, only: [:new]
|
||||
|
||||
ROWS_PROCESSING_LIMIT = 20_000
|
||||
|
||||
def new
|
||||
authorize :domain_block, :create?
|
||||
end
|
||||
|
@ -23,12 +21,14 @@ module Admin
|
|||
authorize :domain_block, :create?
|
||||
|
||||
@import = Admin::Import.new(import_params)
|
||||
return render :new unless @import.validate
|
||||
|
||||
parse_import_data!(export_headers)
|
||||
|
||||
@global_private_comment = I18n.t('admin.export_domain_blocks.import.private_comment_template', source: @import.data_file_name, date: I18n.l(Time.now.utc))
|
||||
|
||||
@form = Form::DomainBlockBatch.new
|
||||
@domain_blocks = @data.take(ROWS_PROCESSING_LIMIT).filter_map do |row|
|
||||
@domain_blocks = @data.take(Admin::Import::ROWS_PROCESSING_LIMIT).filter_map do |row|
|
||||
domain = row['#domain'].strip
|
||||
next if DomainBlock.rule_for(domain).present?
|
||||
|
||||
|
@ -62,7 +62,7 @@ module Admin
|
|||
|
||||
def export_data
|
||||
CSV.generate(headers: export_headers, write_headers: true) do |content|
|
||||
DomainBlock.with_user_facing_limitations.each do |instance|
|
||||
DomainBlock.with_limitations.each do |instance|
|
||||
content << [instance.domain, instance.severity, instance.reject_media, instance.reject_reports, instance.public_comment, instance.obfuscate]
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ module Admin
|
|||
def index
|
||||
authorize :ip_block, :index?
|
||||
|
||||
@ip_blocks = IpBlock.page(params[:page])
|
||||
@ip_blocks = IpBlock.order(ip: :asc).page(params[:page])
|
||||
@form = Form::IpBlockBatch.new
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Settings::AboutController < Admin::SettingsController
|
||||
private
|
||||
|
||||
def after_update_redirect_path
|
||||
admin_settings_about_path
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Settings::AppearanceController < Admin::SettingsController
|
||||
private
|
||||
|
||||
def after_update_redirect_path
|
||||
admin_settings_appearance_path
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Settings::BrandingController < Admin::SettingsController
|
||||
private
|
||||
|
||||
def after_update_redirect_path
|
||||
admin_settings_branding_path
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Settings::ContentRetentionController < Admin::SettingsController
|
||||
private
|
||||
|
||||
def after_update_redirect_path
|
||||
admin_settings_content_retention_path
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Settings::DiscoveryController < Admin::SettingsController
|
||||
private
|
||||
|
||||
def after_update_redirect_path
|
||||
admin_settings_discovery_path
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Settings::OtherController < Admin::SettingsController
|
||||
private
|
||||
|
||||
def after_update_redirect_path
|
||||
admin_settings_other_path
|
||||
end
|
||||
end
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Settings::RegistrationsController < Admin::SettingsController
|
||||
private
|
||||
|
||||
def after_update_redirect_path
|
||||
admin_settings_registrations_path
|
||||
end
|
||||
end
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module Admin
|
||||
class SettingsController < BaseController
|
||||
def edit
|
||||
def show
|
||||
authorize :settings, :show?
|
||||
|
||||
@admin_settings = Form::AdminSettings.new
|
||||
|
@ -15,14 +15,18 @@ module Admin
|
|||
|
||||
if @admin_settings.save
|
||||
flash[:notice] = I18n.t('generic.changes_saved_msg')
|
||||
redirect_to edit_admin_settings_path
|
||||
redirect_to after_update_redirect_path
|
||||
else
|
||||
render :edit
|
||||
render :show
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def after_update_redirect_path
|
||||
raise NotImplementedError
|
||||
end
|
||||
|
||||
def settings_params
|
||||
params.require(:form_admin_settings).permit(*Form::AdminSettings::KEYS)
|
||||
end
|
||||
|
|
|
@ -9,7 +9,7 @@ module Admin
|
|||
|
||||
@site_upload.destroy!
|
||||
|
||||
redirect_to edit_admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg')
|
||||
redirect_to admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg')
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -3,18 +3,23 @@
|
|||
module Admin
|
||||
class StatusesController < BaseController
|
||||
before_action :set_account
|
||||
before_action :set_statuses
|
||||
before_action :set_statuses, except: :show
|
||||
before_action :set_status, only: :show
|
||||
|
||||
PER_PAGE = 20
|
||||
|
||||
def index
|
||||
authorize :status, :index?
|
||||
authorize [:admin, :status], :index?
|
||||
|
||||
@status_batch_action = Admin::StatusBatchAction.new
|
||||
end
|
||||
|
||||
def show
|
||||
authorize [:admin, @status], :show?
|
||||
end
|
||||
|
||||
def batch
|
||||
authorize :status, :index?
|
||||
authorize [:admin, :status], :index?
|
||||
|
||||
@status_batch_action = Admin::StatusBatchAction.new(admin_status_batch_action_params.merge(current_account: current_account, report_id: params[:report_id], type: action_from_button))
|
||||
@status_batch_action.save!
|
||||
|
@ -32,6 +37,7 @@ module Admin
|
|||
|
||||
def after_create_redirect_path
|
||||
report_id = @status_batch_action&.report_id || params[:report_id]
|
||||
|
||||
if report_id.present?
|
||||
admin_report_path(report_id)
|
||||
else
|
||||
|
@ -43,6 +49,10 @@ module Admin
|
|||
@account = Account.find(params[:account_id])
|
||||
end
|
||||
|
||||
def set_status
|
||||
@status = @account.statuses.find(params[:id])
|
||||
end
|
||||
|
||||
def set_statuses
|
||||
@statuses = Admin::StatusFilter.new(@account, filter_params).results.preload(:application, :preloadable_poll, :media_attachments, active_mentions: :account, reblog: [:account, :application, :preloadable_poll, :media_attachments, active_mentions: :account]).page(params[:page]).per(PER_PAGE)
|
||||
end
|
||||
|
|
|
@ -14,7 +14,7 @@ class Admin::Trends::Links::PreviewCardProvidersController < Admin::BaseControll
|
|||
@form = Trends::PreviewCardProviderBatch.new(trends_preview_card_provider_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.accounts.no_account_selected')
|
||||
flash[:alert] = I18n.t('admin.trends.links.publishers.no_publisher_selected')
|
||||
ensure
|
||||
redirect_to admin_trends_links_preview_card_providers_path(filter_params)
|
||||
end
|
||||
|
|
|
@ -4,6 +4,7 @@ class Admin::Trends::LinksController < Admin::BaseController
|
|||
def index
|
||||
authorize :preview_card, :review?
|
||||
|
||||
@locales = PreviewCardTrend.pluck('distinct language')
|
||||
@preview_cards = filtered_preview_cards.page(params[:page])
|
||||
@form = Trends::PreviewCardBatch.new
|
||||
end
|
||||
|
@ -14,7 +15,7 @@ class Admin::Trends::LinksController < Admin::BaseController
|
|||
@form = Trends::PreviewCardBatch.new(trends_preview_card_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.accounts.no_account_selected')
|
||||
flash[:alert] = I18n.t('admin.trends.links.no_link_selected')
|
||||
ensure
|
||||
redirect_to admin_trends_links_path(filter_params)
|
||||
end
|
||||
|
|
|
@ -2,19 +2,20 @@
|
|||
|
||||
class Admin::Trends::StatusesController < Admin::BaseController
|
||||
def index
|
||||
authorize :status, :review?
|
||||
authorize [:admin, :status], :review?
|
||||
|
||||
@locales = StatusTrend.pluck('distinct language')
|
||||
@statuses = filtered_statuses.page(params[:page])
|
||||
@form = Trends::StatusBatch.new
|
||||
end
|
||||
|
||||
def batch
|
||||
authorize :status, :review?
|
||||
authorize [:admin, :status], :review?
|
||||
|
||||
@form = Trends::StatusBatch.new(trends_status_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.accounts.no_account_selected')
|
||||
flash[:alert] = I18n.t('admin.trends.statuses.no_status_selected')
|
||||
ensure
|
||||
redirect_to admin_trends_statuses_path(filter_params)
|
||||
end
|
||||
|
|
|
@ -14,7 +14,7 @@ class Admin::Trends::TagsController < Admin::BaseController
|
|||
@form = Trends::TagBatch.new(trends_tag_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||
@form.save
|
||||
rescue ActionController::ParameterMissing
|
||||
flash[:alert] = I18n.t('admin.accounts.no_account_selected')
|
||||
flash[:alert] = I18n.t('admin.trends.tags.no_tag_selected')
|
||||
ensure
|
||||
redirect_to admin_trends_tags_path(filter_params)
|
||||
end
|
||||
|
|
|
@ -24,6 +24,10 @@ class Api::BaseController < ApplicationController
|
|||
render json: { error: 'Duplicate record' }, status: 422
|
||||
end
|
||||
|
||||
rescue_from Date::Error do
|
||||
render json: { error: 'Invalid date supplied' }, status: 422
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do
|
||||
render json: { error: 'Record not found' }, status: 404
|
||||
end
|
||||
|
@ -53,7 +57,7 @@ class Api::BaseController < ApplicationController
|
|||
render json: { error: I18n.t('errors.429') }, status: 429
|
||||
end
|
||||
|
||||
rescue_from ActionController::ParameterMissing do |e|
|
||||
rescue_from ActionController::ParameterMissing, Mastodon::InvalidParameterError do |e|
|
||||
render json: { error: e.to_s }, status: 400
|
||||
end
|
||||
|
||||
|
@ -125,11 +129,11 @@ class Api::BaseController < ApplicationController
|
|||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
|
||||
response.headers['Cache-Control'] = 'private, no-store'
|
||||
end
|
||||
|
||||
def disallow_unauthenticated_api_access?
|
||||
authorized_fetch_mode?
|
||||
ENV['DISALLOW_UNAUTHENTICATED_API_ACCESS'] == 'true' || Rails.configuration.x.whitelist_mode
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -8,7 +8,7 @@ class Api::V1::Accounts::PinsController < Api::BaseController
|
|||
before_action :set_account
|
||||
|
||||
def create
|
||||
AccountPin.create!(account: current_account, target_account: @account)
|
||||
AccountPin.find_or_create_by!(account: current_account, target_account: @account)
|
||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter
|
||||
end
|
||||
|
||||
|
|
|
@ -60,14 +60,13 @@ class Api::V1::Admin::AccountsController < Api::BaseController
|
|||
def reject
|
||||
authorize @account.user, :reject?
|
||||
DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
|
||||
render json: @account, serializer: REST::Admin::AccountSerializer
|
||||
render_empty
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @account, :destroy?
|
||||
json = render_to_body json: @account, serializer: REST::Admin::AccountSerializer
|
||||
Admin::AccountDeletionWorker.perform_async(@account.id)
|
||||
render json: json
|
||||
render_empty
|
||||
end
|
||||
|
||||
def unsensitive
|
||||
|
|
|
@ -35,20 +35,16 @@ class Api::V1::Admin::CanonicalEmailBlocksController < Api::BaseController
|
|||
|
||||
def create
|
||||
authorize :canonical_email_block, :create?
|
||||
|
||||
@canonical_email_block = CanonicalEmailBlock.create!(resource_params)
|
||||
log_action :create, @canonical_email_block
|
||||
|
||||
render json: @canonical_email_block, serializer: REST::Admin::CanonicalEmailBlockSerializer
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @canonical_email_block, :destroy?
|
||||
|
||||
@canonical_email_block.destroy!
|
||||
log_action :destroy, @canonical_email_block
|
||||
|
||||
render json: @canonical_email_block, serializer: REST::Admin::CanonicalEmailBlockSerializer
|
||||
render_empty
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -43,7 +43,7 @@ class Api::V1::Admin::DomainAllowsController < Api::BaseController
|
|||
authorize @domain_allow, :destroy?
|
||||
UnallowDomainService.new.call(@domain_allow)
|
||||
log_action :destroy, @domain_allow
|
||||
render json: @domain_allow, serializer: REST::Admin::DomainAllowSerializer
|
||||
render_empty
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -40,7 +40,6 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
|
|||
|
||||
def update
|
||||
authorize @domain_block, :update?
|
||||
|
||||
@domain_block.update(domain_block_params)
|
||||
severity_changed = @domain_block.severity_changed?
|
||||
@domain_block.save!
|
||||
|
@ -53,7 +52,7 @@ class Api::V1::Admin::DomainBlocksController < Api::BaseController
|
|||
authorize @domain_block, :destroy?
|
||||
UnblockDomainService.new.call(@domain_block)
|
||||
log_action :destroy, @domain_block
|
||||
render json: @domain_block, serializer: REST::Admin::DomainBlockSerializer
|
||||
render_empty
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -39,11 +39,9 @@ class Api::V1::Admin::EmailDomainBlocksController < Api::BaseController
|
|||
|
||||
def destroy
|
||||
authorize @email_domain_block, :destroy?
|
||||
|
||||
@email_domain_block.destroy!
|
||||
log_action :destroy, @email_domain_block
|
||||
|
||||
render json: @email_domain_block, serializer: REST::Admin::EmailDomainBlockSerializer
|
||||
render_empty
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -20,10 +20,8 @@ class Api::V1::Admin::IpBlocksController < Api::BaseController
|
|||
|
||||
def create
|
||||
authorize :ip_block, :create?
|
||||
|
||||
@ip_block = IpBlock.create!(resource_params)
|
||||
log_action :create, @ip_block
|
||||
|
||||
render json: @ip_block, serializer: REST::Admin::IpBlockSerializer
|
||||
end
|
||||
|
||||
|
@ -39,20 +37,16 @@ class Api::V1::Admin::IpBlocksController < Api::BaseController
|
|||
|
||||
def update
|
||||
authorize @ip_block, :update?
|
||||
|
||||
@ip_block.update(resource_params)
|
||||
log_action :update, @ip_block
|
||||
|
||||
render json: @ip_block, serializer: REST::Admin::IpBlockSerializer
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @ip_block, :destroy?
|
||||
|
||||
@ip_block.destroy!
|
||||
log_action :destroy, @ip_block
|
||||
|
||||
render json: @ip_block, serializer: REST::Admin::IpBlockSerializer
|
||||
render_empty
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -13,12 +13,12 @@ class Api::V1::FeaturedTagsController < Api::BaseController
|
|||
end
|
||||
|
||||
def create
|
||||
@featured_tag = current_account.featured_tags.create!(featured_tag_params)
|
||||
render json: @featured_tag, serializer: REST::FeaturedTagSerializer
|
||||
featured_tag = CreateFeaturedTagService.new.call(current_account, featured_tag_params[:name])
|
||||
render json: featured_tag, serializer: REST::FeaturedTagSerializer
|
||||
end
|
||||
|
||||
def destroy
|
||||
@featured_tag.destroy!
|
||||
RemoveFeaturedTagWorker.perform_async(current_account.id, @featured_tag.id)
|
||||
render_empty
|
||||
end
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ class Api::V1::FiltersController < Api::BaseController
|
|||
end
|
||||
|
||||
def resource_params
|
||||
params.permit(:phrase, :expires_in, :irreversible, :whole_word, context: [])
|
||||
params.permit(:phrase, :expires_in, :irreversible, context: [])
|
||||
end
|
||||
|
||||
def filter_params
|
||||
|
|
|
@ -3,11 +3,11 @@
|
|||
class Api::V1::FollowedTagsController < Api::BaseController
|
||||
TAGS_LIMIT = 100
|
||||
|
||||
before_action -> { doorkeeper_authorize! :follow, :read, :'read:follows' }, except: :show
|
||||
before_action -> { doorkeeper_authorize! :follow, :read, :'read:follows' }
|
||||
before_action :require_user!
|
||||
before_action :set_results
|
||||
|
||||
after_action :insert_pagination_headers, only: :show
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
def index
|
||||
render json: @results.map(&:tag), each_serializer: REST::TagSerializer, relationships: TagRelationshipsPresenter.new(@results.map(&:tag), current_user&.account_id)
|
||||
|
@ -43,7 +43,7 @@ class Api::V1::FollowedTagsController < Api::BaseController
|
|||
end
|
||||
|
||||
def records_continue?
|
||||
@results.size == limit_param(TAG_LIMIT)
|
||||
@results.size == limit_param(TAGS_LIMIT)
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Instances::DomainBlocksController < Api::BaseController
|
||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||
|
||||
before_action :require_enabled_api!
|
||||
before_action :set_domain_blocks
|
||||
|
||||
def index
|
||||
expires_in 3.minutes, public: true
|
||||
render json: @domain_blocks, each_serializer: REST::DomainBlockSerializer, with_comment: (Setting.show_domain_blocks_rationale == 'all' || (Setting.show_domain_blocks_rationale == 'users' && user_signed_in?))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_enabled_api!
|
||||
head 404 unless Setting.show_domain_blocks == 'all' || (Setting.show_domain_blocks == 'users' && user_signed_in?)
|
||||
end
|
||||
|
||||
def set_domain_blocks
|
||||
@domain_blocks = DomainBlock.with_user_facing_limitations.by_severity
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Instances::ExtendedDescriptionsController < Api::BaseController
|
||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||
|
||||
before_action :set_extended_description
|
||||
|
||||
def show
|
||||
expires_in 3.minutes, public: true
|
||||
render json: @extended_description, serializer: REST::ExtendedDescriptionSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_extended_description
|
||||
@extended_description = ExtendedDescription.current
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Instances::PrivacyPoliciesController < Api::BaseController
|
||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||
|
||||
before_action :set_privacy_policy
|
||||
|
||||
def show
|
||||
expires_in 1.day, public: true
|
||||
render json: @privacy_policy, serializer: REST::PrivacyPolicySerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_privacy_policy
|
||||
@privacy_policy = PrivacyPolicy.current
|
||||
end
|
||||
end
|
|
@ -7,6 +7,10 @@ class Api::V1::ListsController < Api::BaseController
|
|||
before_action :require_user!
|
||||
before_action :set_list, except: [:index, :create]
|
||||
|
||||
rescue_from ArgumentError do |e|
|
||||
render json: { error: e.to_s }, status: 422
|
||||
end
|
||||
|
||||
def index
|
||||
@lists = List.where(account: current_account).all
|
||||
render json: @lists, each_serializer: REST::ListSerializer
|
||||
|
|
|
@ -18,14 +18,29 @@ class Api::V1::StatusesController < Api::BaseController
|
|||
# than this anyway
|
||||
CONTEXT_LIMIT = 4_096
|
||||
|
||||
# This remains expensive and we don't want to show everything to logged-out users
|
||||
ANCESTORS_LIMIT = 40
|
||||
DESCENDANTS_LIMIT = 60
|
||||
DESCENDANTS_DEPTH_LIMIT = 20
|
||||
|
||||
def show
|
||||
@status = cache_collection([@status], Status).first
|
||||
render json: @status, serializer: REST::StatusSerializer
|
||||
end
|
||||
|
||||
def context
|
||||
ancestors_results = @status.in_reply_to_id.nil? ? [] : @status.ancestors(CONTEXT_LIMIT, current_account)
|
||||
descendants_results = @status.descendants(CONTEXT_LIMIT, current_account)
|
||||
ancestors_limit = CONTEXT_LIMIT
|
||||
descendants_limit = CONTEXT_LIMIT
|
||||
descendants_depth_limit = nil
|
||||
|
||||
if current_account.nil?
|
||||
ancestors_limit = ANCESTORS_LIMIT
|
||||
descendants_limit = DESCENDANTS_LIMIT
|
||||
descendants_depth_limit = DESCENDANTS_DEPTH_LIMIT
|
||||
end
|
||||
|
||||
ancestors_results = @status.in_reply_to_id.nil? ? [] : @status.ancestors(ancestors_limit, current_account)
|
||||
descendants_results = @status.descendants(descendants_limit, current_account, descendants_depth_limit)
|
||||
loaded_ancestors = cache_collection(ancestors_results, Status)
|
||||
loaded_descendants = cache_collection(descendants_results, Status)
|
||||
|
||||
|
@ -66,6 +81,7 @@ class Api::V1::StatusesController < Api::BaseController
|
|||
text: status_params[:status],
|
||||
media_ids: status_params[:media_ids],
|
||||
sensitive: status_params[:sensitive],
|
||||
language: status_params[:language],
|
||||
spoiler_text: status_params[:spoiler_text],
|
||||
poll: status_params[:poll],
|
||||
content_type: status_params[:content_type]
|
||||
|
@ -78,7 +94,8 @@ class Api::V1::StatusesController < Api::BaseController
|
|||
@status = Status.where(account: current_account).find(params[:id])
|
||||
authorize @status, :destroy?
|
||||
|
||||
@status.discard
|
||||
@status.discard_with_reblogs
|
||||
StatusPin.find_by(status: @status)&.destroy
|
||||
@status.account.statuses_count = @status.account.statuses_count - 1
|
||||
json = render_to_body json: @status, serializer: REST::StatusSerializer, source_requested: true
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ class Api::V1::TagsController < Api::BaseController
|
|||
end
|
||||
|
||||
def follow
|
||||
TagFollow.create!(tag: @tag, account: current_account, rate_limit: true)
|
||||
TagFollow.create_with(rate_limit: true).find_or_create_by!(tag: @tag, account: current_account)
|
||||
render json: @tag, serializer: REST::TagSerializer
|
||||
end
|
||||
|
||||
|
@ -24,7 +24,7 @@ class Api::V1::TagsController < Api::BaseController
|
|||
private
|
||||
|
||||
def set_or_create_tag
|
||||
return not_found unless /\A(#{Tag::HASHTAG_NAME_RE})\z/.match?(params[:id])
|
||||
return not_found unless Tag::HASHTAG_NAME_RE.match?(params[:id])
|
||||
@tag = Tag.find_normalized(params[:id]) || Tag.new(name: Tag.normalize(params[:id]), display_name: params[:id])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -28,7 +28,9 @@ class Api::V1::Trends::LinksController < Api::BaseController
|
|||
end
|
||||
|
||||
def links_from_trends
|
||||
Trends.links.query.allowed.in_locale(content_locale)
|
||||
scope = Trends.links.query.allowed.in_locale(content_locale)
|
||||
scope = scope.filtered_for(current_account) if user_signed_in?
|
||||
scope
|
||||
end
|
||||
|
||||
def insert_pagination_headers
|
||||
|
|
|
@ -5,7 +5,7 @@ class Api::V1::Trends::TagsController < Api::BaseController
|
|||
|
||||
after_action :insert_pagination_headers
|
||||
|
||||
DEFAULT_TAGS_LIMIT = 10
|
||||
DEFAULT_TAGS_LIMIT = (ENV['MAX_TRENDING_TAGS'] || 10).to_i
|
||||
|
||||
def index
|
||||
render json: @tags, each_serializer: REST::TagSerializer, relationships: TagRelationshipsPresenter.new(@tags, current_user&.account_id)
|
||||
|
|
|
@ -33,7 +33,7 @@ class Api::V2::Admin::AccountsController < Api::V1::Admin::AccountsController
|
|||
end
|
||||
|
||||
def filter_params
|
||||
params.permit(*FILTER_PARAMS)
|
||||
params.permit(*FILTER_PARAMS, role_ids: [])
|
||||
end
|
||||
|
||||
def pagination_params(core_params)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Filters::KeywordsController < Api::BaseController
|
||||
class Api::V2::Filters::KeywordsController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:filters' }, only: [:index, :show]
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:filters' }, except: [:index, :show]
|
||||
before_action :require_user!
|
|
@ -1,6 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Filters::StatusesController < Api::BaseController
|
||||
class Api::V2::Filters::StatusesController < Api::BaseController
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:filters' }, only: [:index, :show]
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:filters' }, except: [:index, :show]
|
||||
before_action :require_user!
|
|
@ -3,7 +3,7 @@
|
|||
class Api::V2::MediaController < Api::V1::MediaController
|
||||
def create
|
||||
@media_attachment = current_account.media_attachments.create!({ delay_processing: true }.merge(media_attachment_params))
|
||||
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: 202
|
||||
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: @media_attachment.not_processed? ? 202 : 200
|
||||
rescue Paperclip::Errors::NotIdentifiedByImageMagickError
|
||||
render json: file_type_error, status: 422
|
||||
rescue Paperclip::Error
|
||||
|
|
|
@ -5,8 +5,8 @@ class Api::V2::SearchController < Api::BaseController
|
|||
|
||||
RESULTS_LIMIT = (ENV['MAX_SEARCH_RESULTS'] || 20).to_i
|
||||
|
||||
before_action -> { doorkeeper_authorize! :read, :'read:search' }
|
||||
before_action :require_user!
|
||||
before_action -> { authorize_if_got_token! :read, :'read:search' }
|
||||
before_action :validate_search_params!
|
||||
|
||||
def index
|
||||
@search = Search.new(search_results)
|
||||
|
@ -19,6 +19,16 @@ class Api::V2::SearchController < Api::BaseController
|
|||
|
||||
private
|
||||
|
||||
def validate_search_params!
|
||||
params.require(:q)
|
||||
|
||||
return if user_signed_in?
|
||||
|
||||
return render json: { error: 'Search queries pagination is not supported without authentication' }, status: 401 if params[:offset].present?
|
||||
|
||||
render json: { error: 'Search queries that resolve remote resources are not supported without authentication' }, status: 401 if truthy_param?(:resolve)
|
||||
end
|
||||
|
||||
def search_results
|
||||
SearchService.new.call(
|
||||
params[:q],
|
||||
|
|
|
@ -18,7 +18,8 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||
)
|
||||
|
||||
sign_in_and_redirect @user, event: :authentication
|
||||
set_flash_message(:notice, :success, kind: Devise.omniauth_configs[provider].strategy.display_name.capitalize) if is_navigational_format?
|
||||
label = Devise.omniauth_configs[provider]&.strategy&.display_name.presence || I18n.t("auth.providers.#{provider}", default: provider.to_s.chomp('_oauth2').capitalize)
|
||||
set_flash_message(:notice, :success, kind: label) if is_navigational_format?
|
||||
else
|
||||
session["devise.#{provider}_data"] = request.env['omniauth.auth']
|
||||
redirect_to new_user_registration_url
|
||||
|
|
|
@ -15,6 +15,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
before_action :set_body_classes, only: [:new, :create, :edit, :update]
|
||||
before_action :require_not_suspended!, only: [:update]
|
||||
before_action :set_cache_headers, only: [:edit, :update]
|
||||
before_action :set_rules, only: :new
|
||||
before_action :require_rules_acceptance!, only: :new
|
||||
before_action :set_registration_form_time, only: :new
|
||||
|
||||
skip_before_action :require_functional!, only: [:edit, :update]
|
||||
|
@ -56,7 +58,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
|
||||
def configure_sign_up_params
|
||||
devise_parameter_sanitizer.permit(:sign_up) do |u|
|
||||
u.permit({ account_attributes: [:username], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement, :website, :confirm_password)
|
||||
u.permit({ account_attributes: [:username, :display_name], invite_request_attributes: [:text] }, :email, :password, :password_confirmation, :invite_code, :agreement, :website, :confirm_password)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -143,7 +145,20 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
forbidden if current_account.suspended?
|
||||
end
|
||||
|
||||
def set_rules
|
||||
@rules = Rule.ordered
|
||||
end
|
||||
|
||||
def require_rules_acceptance!
|
||||
return if @rules.empty? || (session[:accept_token].present? && params[:accept] == session[:accept_token])
|
||||
|
||||
@accept_token = session[:accept_token] = SecureRandom.hex
|
||||
@invite_code = invite_code
|
||||
|
||||
set_locale { render :rules }
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
|
||||
response.headers['Cache-Control'] = 'private, no-store'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,6 +15,10 @@ class Auth::SessionsController < Devise::SessionsController
|
|||
before_action :set_instance_presenter, only: [:new]
|
||||
before_action :set_body_classes
|
||||
|
||||
content_security_policy only: :new do |p|
|
||||
p.form_action(false)
|
||||
end
|
||||
|
||||
def check_suspicious!
|
||||
user = find_user
|
||||
@login_is_suspicious = suspicious_sign_in?(user) unless user.nil?
|
||||
|
|
|
@ -3,13 +3,12 @@
|
|||
module AccountControllerConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
include WebAppControllerConcern
|
||||
include AccountOwnedConcern
|
||||
|
||||
FOLLOW_PER_PAGE = 12
|
||||
|
||||
included do
|
||||
layout 'public'
|
||||
|
||||
before_action :set_instance_presenter
|
||||
before_action :set_link_headers, if: -> { request.format.nil? || request.format == :html }
|
||||
end
|
||||
|
|
|
@ -27,13 +27,13 @@ module AdminExportControllerConcern
|
|||
params.require(:admin_import).permit(:data)
|
||||
end
|
||||
|
||||
def import_data
|
||||
Paperclip.io_adapters.for(@import.data).read
|
||||
def import_data_path
|
||||
params[:admin_import][:data].path
|
||||
end
|
||||
|
||||
def parse_import_data!(default_headers)
|
||||
data = CSV.parse(import_data, headers: true)
|
||||
data = CSV.parse(import_data, headers: default_headers) unless data.headers&.first&.strip&.include?(default_headers[0])
|
||||
data = CSV.read(import_data_path, headers: true, encoding: 'UTF-8')
|
||||
data = CSV.read(import_data_path, headers: default_headers, encoding: 'UTF-8') unless data.headers&.first&.strip&.include?(default_headers[0])
|
||||
@data = data.reject(&:blank?)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module StatusControllerConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
ANCESTORS_LIMIT = 40
|
||||
DESCENDANTS_LIMIT = 60
|
||||
DESCENDANTS_DEPTH_LIMIT = 20
|
||||
|
||||
def create_descendant_thread(starting_depth, statuses)
|
||||
depth = starting_depth + statuses.size
|
||||
|
||||
if depth < DESCENDANTS_DEPTH_LIMIT
|
||||
{
|
||||
statuses: statuses,
|
||||
starting_depth: starting_depth,
|
||||
}
|
||||
else
|
||||
next_status = statuses.pop
|
||||
|
||||
{
|
||||
statuses: statuses,
|
||||
starting_depth: starting_depth,
|
||||
next_status: next_status,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def set_ancestors
|
||||
@ancestors = @status.reply? ? cache_collection(@status.ancestors(ANCESTORS_LIMIT, current_account), Status) : []
|
||||
@next_ancestor = @ancestors.size < ANCESTORS_LIMIT ? nil : @ancestors.shift
|
||||
end
|
||||
|
||||
def set_descendants
|
||||
@max_descendant_thread_id = params[:max_descendant_thread_id]&.to_i
|
||||
@since_descendant_thread_id = params[:since_descendant_thread_id]&.to_i
|
||||
|
||||
descendants = cache_collection(
|
||||
@status.descendants(
|
||||
DESCENDANTS_LIMIT,
|
||||
current_account,
|
||||
@max_descendant_thread_id,
|
||||
@since_descendant_thread_id,
|
||||
DESCENDANTS_DEPTH_LIMIT
|
||||
),
|
||||
Status
|
||||
)
|
||||
|
||||
@descendant_threads = []
|
||||
|
||||
if descendants.present?
|
||||
statuses = [descendants.first]
|
||||
starting_depth = 0
|
||||
|
||||
descendants.drop(1).each_with_index do |descendant, index|
|
||||
if descendants[index].id == descendant.in_reply_to_id
|
||||
statuses << descendant
|
||||
else
|
||||
@descendant_threads << create_descendant_thread(starting_depth, statuses)
|
||||
|
||||
# The thread is broken, assume it's a reply to the root status
|
||||
starting_depth = 0
|
||||
|
||||
# ... unless we can find its ancestor in one of the already-processed threads
|
||||
@descendant_threads.reverse_each do |descendant_thread|
|
||||
statuses = descendant_thread[:statuses]
|
||||
|
||||
index = statuses.find_index do |thread_status|
|
||||
thread_status.id == descendant.in_reply_to_id
|
||||
end
|
||||
|
||||
if index.present?
|
||||
starting_depth = descendant_thread[:starting_depth] + index + 1
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
statuses = [descendant]
|
||||
end
|
||||
end
|
||||
|
||||
@descendant_threads << create_descendant_thread(starting_depth, statuses)
|
||||
end
|
||||
|
||||
@max_descendant_thread_id = @descendant_threads.pop[:statuses].first.id if descendants.size >= DESCENDANTS_LIMIT
|
||||
end
|
||||
end
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module WebAppControllerConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :set_pack
|
||||
before_action :redirect_unauthenticated_to_permalinks!
|
||||
before_action :set_app_body_class
|
||||
before_action :set_referrer_policy_header
|
||||
end
|
||||
|
||||
def set_app_body_class
|
||||
@body_classes = 'app-body'
|
||||
end
|
||||
|
||||
def set_referrer_policy_header
|
||||
response.headers['Referrer-Policy'] = 'origin'
|
||||
end
|
||||
|
||||
def redirect_unauthenticated_to_permalinks!
|
||||
return if user_signed_in?
|
||||
|
||||
redirect_path = PermalinkRedirector.new(request.path).redirect_path
|
||||
|
||||
redirect_to(redirect_path) if redirect_path.present?
|
||||
end
|
||||
|
||||
def set_pack
|
||||
use_pack 'home'
|
||||
end
|
||||
end
|
|
@ -1,37 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class DirectoriesController < ApplicationController
|
||||
layout 'public'
|
||||
|
||||
before_action :authenticate_user!, if: :whitelist_mode?
|
||||
before_action :require_enabled!
|
||||
before_action :set_instance_presenter
|
||||
before_action :set_accounts
|
||||
before_action :set_pack
|
||||
|
||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||
|
||||
def index
|
||||
render :index
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_pack
|
||||
use_pack 'share'
|
||||
end
|
||||
|
||||
def require_enabled!
|
||||
return not_found unless Setting.profile_directory
|
||||
end
|
||||
|
||||
def set_accounts
|
||||
@accounts = Account.local.discoverable.by_recent_status.page(params[:page]).per(20).tap do |query|
|
||||
query.merge!(Account.not_excluded_by_account(current_account)) if current_account
|
||||
end
|
||||
end
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
end
|
|
@ -3,6 +3,7 @@
|
|||
class FollowerAccountsController < ApplicationController
|
||||
include AccountControllerConcern
|
||||
include SignatureVerification
|
||||
include WebAppControllerConcern
|
||||
|
||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_cache_headers
|
||||
|
@ -13,12 +14,7 @@ class FollowerAccountsController < ApplicationController
|
|||
def index
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
use_pack 'public'
|
||||
expires_in 0, public: true unless user_signed_in?
|
||||
|
||||
next if @account.hide_collections?
|
||||
|
||||
follows
|
||||
end
|
||||
|
||||
format.json do
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
class FollowingAccountsController < ApplicationController
|
||||
include AccountControllerConcern
|
||||
include SignatureVerification
|
||||
include WebAppControllerConcern
|
||||
|
||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_cache_headers
|
||||
|
@ -13,12 +14,7 @@ class FollowingAccountsController < ApplicationController
|
|||
def index
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
use_pack 'public'
|
||||
expires_in 0, public: true unless user_signed_in?
|
||||
|
||||
next if @account.hide_collections?
|
||||
|
||||
follows
|
||||
end
|
||||
|
||||
format.json do
|
||||
|
|
|
@ -1,47 +1,16 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class HomeController < ApplicationController
|
||||
before_action :redirect_unauthenticated_to_permalinks!
|
||||
include WebAppControllerConcern
|
||||
|
||||
before_action :set_pack
|
||||
before_action :set_referrer_policy_header
|
||||
before_action :set_instance_presenter
|
||||
|
||||
def index
|
||||
@body_classes = 'app-body'
|
||||
expires_in 0, public: true unless user_signed_in?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def redirect_unauthenticated_to_permalinks!
|
||||
return if user_signed_in?
|
||||
|
||||
redirect_path = PermalinkRedirector.new(request.path).redirect_path
|
||||
redirect_path ||= default_redirect_path
|
||||
|
||||
redirect_to(redirect_path) if redirect_path.present?
|
||||
end
|
||||
|
||||
def set_pack
|
||||
use_pack 'home'
|
||||
end
|
||||
|
||||
def default_redirect_path
|
||||
if whitelist_mode?
|
||||
new_user_session_path
|
||||
elsif request.path.start_with?('/web')
|
||||
nil
|
||||
elsif single_user_mode?
|
||||
short_account_path(Account.local.without_suspended.where('id > 0').first)
|
||||
else
|
||||
about_path
|
||||
end
|
||||
end
|
||||
|
||||
def set_referrer_policy_header
|
||||
response.headers['Referrer-Policy'] = 'origin'
|
||||
end
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
|
|
|
@ -8,6 +8,10 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
|
|||
before_action :set_pack
|
||||
before_action :set_cache_headers
|
||||
|
||||
content_security_policy do |p|
|
||||
p.form_action(false)
|
||||
end
|
||||
|
||||
include Localized
|
||||
|
||||
private
|
||||
|
@ -35,6 +39,6 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
|
|||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
|
||||
response.headers['Cache-Control'] = 'private, no-store'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,28 +1,19 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class PrivacyController < ApplicationController
|
||||
layout 'public'
|
||||
|
||||
before_action :set_pack
|
||||
|
||||
before_action :set_instance_presenter
|
||||
before_action :set_expires_in
|
||||
include WebAppControllerConcern
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def show; end
|
||||
before_action :set_instance_presenter
|
||||
|
||||
def show
|
||||
expires_in 0, public: true if current_account.nil?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_pack
|
||||
use_pack 'public'
|
||||
end
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
|
||||
def set_expires_in
|
||||
expires_in 0, public: true
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class PublicTimelinesController < ApplicationController
|
||||
before_action :set_pack
|
||||
layout 'public'
|
||||
|
||||
before_action :authenticate_user!, if: :whitelist_mode?
|
||||
before_action :require_enabled!
|
||||
before_action :set_body_classes
|
||||
before_action :set_instance_presenter
|
||||
|
||||
def show; end
|
||||
|
||||
private
|
||||
|
||||
def require_enabled!
|
||||
not_found unless Setting.timeline_preview
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'with-modals'
|
||||
end
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
|
||||
def set_pack
|
||||
use_pack 'about'
|
||||
end
|
||||
end
|
|
@ -1,46 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoteFollowController < ApplicationController
|
||||
include AccountOwnedConcern
|
||||
|
||||
layout 'modal'
|
||||
|
||||
before_action :set_pack
|
||||
before_action :set_body_classes
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
def new
|
||||
@remote_follow = RemoteFollow.new(session_params)
|
||||
end
|
||||
|
||||
def create
|
||||
@remote_follow = RemoteFollow.new(resource_params)
|
||||
|
||||
if @remote_follow.valid?
|
||||
session[:remote_follow] = @remote_follow.acct
|
||||
redirect_to @remote_follow.subscribe_address_for(@account)
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resource_params
|
||||
params.require(:remote_follow).permit(:acct)
|
||||
end
|
||||
|
||||
def session_params
|
||||
{ acct: session[:remote_follow] || current_account&.username }
|
||||
end
|
||||
|
||||
def set_pack
|
||||
use_pack 'modal'
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'modal-layout'
|
||||
@hide_header = true
|
||||
end
|
||||
end
|
|
@ -1,60 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RemoteInteractionController < ApplicationController
|
||||
include Authorization
|
||||
|
||||
layout 'modal'
|
||||
|
||||
before_action :authenticate_user!, if: :whitelist_mode?
|
||||
before_action :set_interaction_type
|
||||
before_action :set_status
|
||||
before_action :set_body_classes
|
||||
before_action :set_pack
|
||||
|
||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||
|
||||
def new
|
||||
@remote_follow = RemoteFollow.new(session_params)
|
||||
end
|
||||
|
||||
def create
|
||||
@remote_follow = RemoteFollow.new(resource_params)
|
||||
|
||||
if @remote_follow.valid?
|
||||
session[:remote_follow] = @remote_follow.acct
|
||||
redirect_to @remote_follow.interact_address_for(@status)
|
||||
else
|
||||
render :new
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def resource_params
|
||||
params.require(:remote_follow).permit(:acct)
|
||||
end
|
||||
|
||||
def session_params
|
||||
{ acct: session[:remote_follow] || current_account&.username }
|
||||
end
|
||||
|
||||
def set_status
|
||||
@status = Status.find(params[:id])
|
||||
authorize @status, :show?
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'modal-layout'
|
||||
@hide_header = true
|
||||
end
|
||||
|
||||
def set_pack
|
||||
use_pack 'modal'
|
||||
end
|
||||
|
||||
def set_interaction_type
|
||||
@interaction_type = %w(reply reblog favourite).include?(params[:type]) ? params[:type] : 'reply'
|
||||
end
|
||||
end
|
|
@ -19,7 +19,7 @@ class Settings::BaseController < ApplicationController
|
|||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
|
||||
response.headers['Cache-Control'] = 'private, no-store'
|
||||
end
|
||||
|
||||
def require_not_suspended!
|
||||
|
|
|
@ -4,7 +4,6 @@ class Settings::DeletesController < Settings::BaseController
|
|||
skip_before_action :require_functional!
|
||||
|
||||
before_action :require_not_suspended!
|
||||
before_action :check_enabled_deletion
|
||||
|
||||
def show
|
||||
@confirmation = Form::DeleteConfirmation.new
|
||||
|
@ -21,10 +20,6 @@ class Settings::DeletesController < Settings::BaseController
|
|||
|
||||
private
|
||||
|
||||
def check_enabled_deletion
|
||||
redirect_to root_path unless Setting.open_deletion
|
||||
end
|
||||
|
||||
def resource_params
|
||||
params.require(:form_delete_confirmation).permit(:password, :username)
|
||||
end
|
||||
|
|
|
@ -10,9 +10,9 @@ class Settings::FeaturedTagsController < Settings::BaseController
|
|||
end
|
||||
|
||||
def create
|
||||
@featured_tag = current_account.featured_tags.new(featured_tag_params)
|
||||
@featured_tag = CreateFeaturedTagService.new.call(current_account, featured_tag_params[:name], force: false)
|
||||
|
||||
if @featured_tag.save
|
||||
if @featured_tag.valid?
|
||||
redirect_to settings_featured_tags_path
|
||||
else
|
||||
set_featured_tags
|
||||
|
@ -23,7 +23,7 @@ class Settings::FeaturedTagsController < Settings::BaseController
|
|||
end
|
||||
|
||||
def destroy
|
||||
@featured_tag.destroy!
|
||||
RemoveFeaturedTagService.new.call(current_account, @featured_tag)
|
||||
redirect_to settings_featured_tags_path
|
||||
end
|
||||
|
||||
|
|
|
@ -20,6 +20,10 @@ class StatusesCleanupController < ApplicationController
|
|||
# Do nothing
|
||||
end
|
||||
|
||||
def require_functional!
|
||||
redirect_to edit_user_registration_path unless current_user.functional_or_moved?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_pack
|
||||
|
|
|
@ -1,21 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class StatusesController < ApplicationController
|
||||
include StatusControllerConcern
|
||||
include WebAppControllerConcern
|
||||
include SignatureAuthentication
|
||||
include Authorization
|
||||
include AccountOwnedConcern
|
||||
|
||||
layout 'public'
|
||||
|
||||
before_action :require_account_signature!, only: [:show, :activity], if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :set_status
|
||||
before_action :set_instance_presenter
|
||||
before_action :set_link_headers
|
||||
before_action :redirect_to_original, only: :show
|
||||
before_action :set_referrer_policy_header, only: :show
|
||||
before_action :set_cache_headers
|
||||
before_action :set_body_classes
|
||||
before_action :set_body_classes, only: :embed
|
||||
|
||||
skip_around_action :set_locale, if: -> { request.format == :json }
|
||||
skip_before_action :require_functional!, only: [:show, :embed], unless: :whitelist_mode?
|
||||
|
@ -27,11 +24,7 @@ class StatusesController < ApplicationController
|
|||
def show
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
use_pack 'public'
|
||||
|
||||
expires_in 10.seconds, public: true if current_account.nil?
|
||||
set_ancestors
|
||||
set_descendants
|
||||
end
|
||||
|
||||
format.json do
|
||||
|
@ -80,8 +73,4 @@ class StatusesController < ApplicationController
|
|||
def redirect_to_original
|
||||
redirect_to ActivityPub::TagManager.instance.url_for(@status.reblog) if @status.reblog?
|
||||
end
|
||||
|
||||
def set_referrer_policy_header
|
||||
response.headers['Referrer-Policy'] = 'origin' unless @status.distributable?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,18 +2,16 @@
|
|||
|
||||
class TagsController < ApplicationController
|
||||
include SignatureVerification
|
||||
include WebAppControllerConcern
|
||||
|
||||
PAGE_SIZE = 20
|
||||
PAGE_SIZE_MAX = 200
|
||||
|
||||
layout 'public'
|
||||
|
||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :authenticate_user!, if: :whitelist_mode?
|
||||
before_action :set_local
|
||||
before_action :set_tag
|
||||
before_action :set_statuses
|
||||
before_action :set_body_classes
|
||||
before_action :set_instance_presenter
|
||||
|
||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||
|
@ -21,8 +19,7 @@ class TagsController < ApplicationController
|
|||
def show
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
use_pack 'about'
|
||||
expires_in 0, public: true
|
||||
expires_in 0, public: true unless user_signed_in?
|
||||
end
|
||||
|
||||
format.rss do
|
||||
|
@ -55,10 +52,6 @@ class TagsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'with-modals'
|
||||
end
|
||||
|
||||
def set_instance_presenter
|
||||
@instance_presenter = InstancePresenter.new
|
||||
end
|
||||
|
|
|
@ -20,54 +20,10 @@ module AccountsHelper
|
|||
end
|
||||
|
||||
def account_action_button(account)
|
||||
if user_signed_in?
|
||||
if account.id == current_user.account_id
|
||||
link_to settings_profile_url, class: 'button logo-button' do
|
||||
safe_join([logo_as_symbol, t('settings.edit_profile')])
|
||||
end
|
||||
elsif current_account.following?(account) || current_account.requested?(account)
|
||||
link_to account_unfollow_path(account), class: 'button logo-button button--destructive', data: { method: :post } do
|
||||
safe_join([logo_as_symbol, t('accounts.unfollow')])
|
||||
end
|
||||
elsif !(account.memorial? || account.moved?)
|
||||
link_to account_follow_path(account), class: "button logo-button#{account.blocking?(current_account) ? ' disabled' : ''}", data: { method: :post } do
|
||||
safe_join([logo_as_symbol, t('accounts.follow')])
|
||||
end
|
||||
end
|
||||
elsif !(account.memorial? || account.moved?)
|
||||
link_to account_remote_follow_path(account), class: 'button logo-button modal-button', target: '_new' do
|
||||
safe_join([logo_as_symbol, t('accounts.follow')])
|
||||
end
|
||||
end
|
||||
end
|
||||
return if account.memorial? || account.moved?
|
||||
|
||||
def minimal_account_action_button(account)
|
||||
if user_signed_in?
|
||||
return if account.id == current_user.account_id
|
||||
|
||||
if current_account.following?(account) || current_account.requested?(account)
|
||||
link_to account_unfollow_path(account), class: 'icon-button active', data: { method: :post }, title: t('accounts.unfollow') do
|
||||
fa_icon('user-times fw')
|
||||
end
|
||||
elsif !(account.memorial? || account.moved?)
|
||||
link_to account_follow_path(account), class: "icon-button#{account.blocking?(current_account) ? ' disabled' : ''}", data: { method: :post }, title: t('accounts.follow') do
|
||||
fa_icon('user-plus fw')
|
||||
end
|
||||
end
|
||||
elsif !(account.memorial? || account.moved?)
|
||||
link_to account_remote_follow_path(account), class: 'icon-button modal-button', target: '_new', title: t('accounts.follow') do
|
||||
fa_icon('user-plus fw')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def account_badge(account)
|
||||
if account.bot?
|
||||
content_tag(:div, content_tag(:div, t('accounts.roles.bot'), class: 'account-role bot'), class: 'roles')
|
||||
elsif account.group?
|
||||
content_tag(:div, content_tag(:div, t('accounts.roles.group'), class: 'account-role group'), class: 'roles')
|
||||
elsif account.user_role&.highlighted?
|
||||
content_tag(:div, content_tag(:div, account.user_role.name, class: "account-role user-role-#{account.user_role.id}"), class: 'roles')
|
||||
link_to ActivityPub::TagManager.instance.url_for(account), class: 'button logo-button', target: '_new' do
|
||||
safe_join([logo_as_symbol, t('accounts.follow')])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -4,15 +4,19 @@ module Admin::ActionLogsHelper
|
|||
def log_target(log)
|
||||
case log.target_type
|
||||
when 'Account'
|
||||
link_to log.human_identifier, admin_account_path(log.target_id)
|
||||
link_to (log.human_identifier.presence || I18n.t('admin.action_logs.deleted_account')), admin_account_path(log.target_id)
|
||||
when 'User'
|
||||
link_to log.human_identifier, admin_account_path(log.route_param)
|
||||
if log.route_param.present?
|
||||
link_to log.human_identifier, admin_account_path(log.route_param)
|
||||
else
|
||||
I18n.t('admin.action_logs.deleted_account')
|
||||
end
|
||||
when 'UserRole'
|
||||
link_to log.human_identifier, admin_roles_path(log.target_id)
|
||||
when 'Report'
|
||||
link_to "##{log.human_identifier}", admin_report_path(log.target_id)
|
||||
link_to "##{log.human_identifier.presence || log.target_id}", admin_report_path(log.target_id)
|
||||
when 'DomainBlock', 'DomainAllow', 'EmailDomainBlock', 'UnavailableDomain'
|
||||
link_to log.human_identifier, "https://#{log.human_identifier}"
|
||||
link_to log.human_identifier, "https://#{log.human_identifier.presence}"
|
||||
when 'Status'
|
||||
link_to log.human_identifier, log.permalink
|
||||
when 'AccountWarning'
|
||||
|
@ -22,9 +26,13 @@ module Admin::ActionLogsHelper
|
|||
when 'IpBlock', 'Instance', 'CustomEmoji'
|
||||
log.human_identifier
|
||||
when 'CanonicalEmailBlock'
|
||||
content_tag(:samp, log.human_identifier[0...7], title: log.human_identifier)
|
||||
content_tag(:samp, (log.human_identifier.presence || '')[0...7], title: log.human_identifier)
|
||||
when 'Appeal'
|
||||
link_to log.human_identifier, disputes_strike_path(log.route_param)
|
||||
if log.route_param.present?
|
||||
link_to log.human_identifier, disputes_strike_path(log.route_param.presence)
|
||||
else
|
||||
I18n.t('admin.action_logs.deleted_account')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin::SettingsHelper
|
||||
def site_upload_delete_hint(hint, var)
|
||||
upload = SiteUpload.find_by(var: var.to_s)
|
||||
return hint unless upload
|
||||
|
||||
link = link_to t('admin.site_uploads.delete'), admin_site_upload_path(upload), data: { method: :delete }
|
||||
safe_join([hint, link], '<br/>'.html_safe)
|
||||
end
|
||||
|
||||
def captcha_available?
|
||||
ENV['HCAPTCHA_SECRET_KEY'].present? && ENV['HCAPTCHA_SITE_KEY'].present?
|
||||
end
|
||||
|
|
|
@ -87,10 +87,6 @@ module ApplicationHelper
|
|||
link_to label, omniauth_authorize_path(:user, provider), class: "button button-#{provider}", method: :post
|
||||
end
|
||||
|
||||
def open_deletion?
|
||||
Setting.open_deletion
|
||||
end
|
||||
|
||||
def locale_direction
|
||||
if RTL_LOCALES.include?(I18n.locale)
|
||||
'rtl'
|
||||
|
@ -199,10 +195,7 @@ module ApplicationHelper
|
|||
|
||||
def render_initial_state
|
||||
state_params = {
|
||||
settings: {
|
||||
known_fediverse: Setting.show_known_fediverse_at_about_page,
|
||||
},
|
||||
|
||||
settings: {},
|
||||
text: [params[:title], params[:text], params[:url]].compact.join(' '),
|
||||
}
|
||||
|
||||
|
@ -211,7 +204,7 @@ module ApplicationHelper
|
|||
permit_visibilities.shift(permit_visibilities.index(default_privacy) + 1) if default_privacy.present?
|
||||
state_params[:visibility] = params[:visibility] if permit_visibilities.include? params[:visibility]
|
||||
|
||||
if user_signed_in?
|
||||
if user_signed_in? && current_user.functional?
|
||||
state_params[:settings] = state_params[:settings].merge(Web::Setting.find_by(user: current_user)&.data || {})
|
||||
state_params[:push_subscription] = current_account.user.web_push_subscription(current_session)
|
||||
state_params[:current_account] = current_account
|
||||
|
@ -219,6 +212,15 @@ module ApplicationHelper
|
|||
state_params[:admin] = Account.find_local(Setting.site_contact_username.strip.gsub(/\A@/, ''))
|
||||
end
|
||||
|
||||
if user_signed_in? && !current_user.functional?
|
||||
state_params[:disabled_account] = current_account
|
||||
state_params[:moved_to_account] = current_account.moved_to_account
|
||||
end
|
||||
|
||||
if single_user_mode?
|
||||
state_params[:owner] = Account.local.without_suspended.where('id > 0').first
|
||||
end
|
||||
|
||||
json = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(state_params), serializer: InitialStateSerializer).to_json
|
||||
# rubocop:disable Rails/OutputSafety
|
||||
content_tag(:script, json_escape(json).html_safe, id: 'initial-state', type: 'application/json')
|
||||
|
|
|
@ -23,7 +23,7 @@ module HomeHelper
|
|||
else
|
||||
link_to(path || ActivityPub::TagManager.instance.url_for(account), class: 'account__display-name') do
|
||||
content_tag(:div, class: 'account__avatar-wrapper') do
|
||||
image_tag(full_asset_url(current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url), class: 'account__avatar')
|
||||
image_tag(full_asset_url(current_account&.user&.setting_auto_play_gif ? account.avatar_original_url : account.avatar_static_url), class: 'account__avatar', width: 46, height: 46)
|
||||
end +
|
||||
content_tag(:span, class: 'display-name') do
|
||||
content_tag(:bdi) do
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# frozen_string_literal: true
|
||||
# rubocop:disable Metrics/ModuleLength, Style/WordArray
|
||||
|
||||
module LanguagesHelper
|
||||
ISO_639_1 = {
|
||||
|
@ -97,7 +98,7 @@ module LanguagesHelper
|
|||
lg: ['Ganda', 'Luganda'].freeze,
|
||||
li: ['Limburgish', 'Limburgs'].freeze,
|
||||
ln: ['Lingala', 'Lingála'].freeze,
|
||||
lo: ['Lao', 'ພາສາ'].freeze,
|
||||
lo: ['Lao', 'ລາວ'].freeze,
|
||||
lt: ['Lithuanian', 'lietuvių kalba'].freeze,
|
||||
lu: ['Luba-Katanga', 'Tshiluba'].freeze,
|
||||
lv: ['Latvian', 'latviešu valoda'].freeze,
|
||||
|
@ -189,8 +190,14 @@ module LanguagesHelper
|
|||
ISO_639_3 = {
|
||||
ast: ['Asturian', 'Asturianu'].freeze,
|
||||
ckb: ['Sorani (Kurdish)', 'سۆرانی'].freeze,
|
||||
jbo: ['Lojban', 'la .lojban.'].freeze,
|
||||
kab: ['Kabyle', 'Taqbaylit'].freeze,
|
||||
kmr: ['Kurmanji (Kurdish)', 'Kurmancî'].freeze,
|
||||
ldn: ['Láadan', 'Láadan'].freeze,
|
||||
lfn: ['Lingua Franca Nova', 'lingua franca nova'].freeze,
|
||||
sco: ['Scots', 'Scots'].freeze,
|
||||
tok: ['Toki Pona', 'toki pona'].freeze,
|
||||
zba: ['Balaibalan', 'باليبلن'].freeze,
|
||||
zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze,
|
||||
}.freeze
|
||||
|
||||
|
@ -259,3 +266,5 @@ module LanguagesHelper
|
|||
locale_name.to_sym if locale_name.present? && I18n.available_locales.include?(locale_name.to_sym)
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop:enable Metrics/ModuleLength, Style/WordArray
|
||||
|
|
|
@ -4,6 +4,24 @@ import 'packs/public-path';
|
|||
import { delegate } from '@rails/ujs';
|
||||
import ready from '../mastodon/ready';
|
||||
|
||||
const setAnnouncementEndsAttributes = (target) => {
|
||||
const valid = target?.value && target?.validity?.valid;
|
||||
const element = document.querySelector('input[type="datetime-local"]#announcement_ends_at');
|
||||
if (valid) {
|
||||
element.classList.remove('optional');
|
||||
element.required = true;
|
||||
element.min = target.value;
|
||||
} else {
|
||||
element.classList.add('optional');
|
||||
element.removeAttribute('required');
|
||||
element.removeAttribute('min');
|
||||
}
|
||||
};
|
||||
|
||||
delegate(document, 'input[type="datetime-local"]#announcement_starts_at', 'change', ({ target }) => {
|
||||
setAnnouncementEndsAttributes(target);
|
||||
});
|
||||
|
||||
const batchCheckboxClassName = '.batch-checkbox input[type="checkbox"]';
|
||||
|
||||
const showSelectAll = () => {
|
||||
|
@ -143,6 +161,20 @@ const onChangeRegistrationMode = (target) => {
|
|||
});
|
||||
};
|
||||
|
||||
const convertUTCDateTimeToLocal = (value) => {
|
||||
const date = new Date(value + 'Z');
|
||||
const twoChars = (x) => (x.toString().padStart(2, '0'));
|
||||
return `${date.getFullYear()}-${twoChars(date.getMonth()+1)}-${twoChars(date.getDate())}T${twoChars(date.getHours())}:${twoChars(date.getMinutes())}`;
|
||||
};
|
||||
|
||||
const convertLocalDatetimeToUTC = (value) => {
|
||||
const re = /^([0-9]{4,})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2})/;
|
||||
const match = re.exec(value);
|
||||
const date = new Date(match[1], match[2] - 1, match[3], match[4], match[5]);
|
||||
const fullISO8601 = date.toISOString();
|
||||
return fullISO8601.slice(0, fullISO8601.indexOf('T') + 6);
|
||||
};
|
||||
|
||||
delegate(document, '#form_admin_settings_registrations_mode', 'change', ({ target }) => onChangeRegistrationMode(target));
|
||||
|
||||
ready(() => {
|
||||
|
@ -170,4 +202,26 @@ ready(() => {
|
|||
e.target.href = url;
|
||||
}
|
||||
});
|
||||
|
||||
[].forEach.call(document.querySelectorAll('input[type="datetime-local"]'), element => {
|
||||
if (element.value) {
|
||||
element.value = convertUTCDateTimeToLocal(element.value);
|
||||
}
|
||||
if (element.placeholder) {
|
||||
element.placeholder = convertUTCDateTimeToLocal(element.placeholder);
|
||||
}
|
||||
});
|
||||
|
||||
delegate(document, 'form', 'submit', ({ target }) => {
|
||||
[].forEach.call(target.querySelectorAll('input[type="datetime-local"]'), element => {
|
||||
if (element.value && element.validity.valid) {
|
||||
element.value = convertLocalDatetimeToUTC(element.value);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const announcementStartsAt = document.querySelector('input[type="datetime-local"]#announcement_starts_at');
|
||||
if (announcementStartsAt) {
|
||||
setAnnouncementEndsAttributes(announcementStartsAt);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -6,28 +6,6 @@ import ready from '../mastodon/ready';
|
|||
const { delegate } = require('@rails/ujs');
|
||||
const { length } = require('stringz');
|
||||
|
||||
delegate(document, '.webapp-btn', 'click', ({ target, button }) => {
|
||||
if (button !== 0) {
|
||||
return true;
|
||||
}
|
||||
window.location.href = target.href;
|
||||
return false;
|
||||
});
|
||||
|
||||
delegate(document, '.modal-button', 'click', e => {
|
||||
e.preventDefault();
|
||||
|
||||
let href;
|
||||
|
||||
if (e.target.nodeName !== 'A') {
|
||||
href = e.target.parentNode.href;
|
||||
} else {
|
||||
href = e.target.href;
|
||||
}
|
||||
|
||||
window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
|
||||
});
|
||||
|
||||
const getProfileAvatarAnimationHandler = (swapTo) => {
|
||||
//animate avatar gifs on the profile page when moused over
|
||||
return ({ target }) => {
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
import api from '../api';
|
||||
import { CancelToken, isCancel } from 'axios';
|
||||
import axios from 'axios';
|
||||
import { throttle } from 'lodash';
|
||||
import { defineMessages } from 'react-intl';
|
||||
import api from 'flavours/glitch/api';
|
||||
import { search as emojiSearch } from 'flavours/glitch/features/emoji/emoji_mart_search_light';
|
||||
import { useEmoji } from './emojis';
|
||||
import { tagHistory } from '../settings';
|
||||
import { tagHistory } from 'flavours/glitch/settings';
|
||||
import { recoverHashtags } from 'flavours/glitch/utils/hashtag';
|
||||
import resizeImage from 'flavours/glitch/utils/resize_image';
|
||||
import { importFetchedAccounts } from './importer';
|
||||
import { updateTimeline } from './timelines';
|
||||
import { showAlertForError } from './alerts';
|
||||
import { showAlert } from './alerts';
|
||||
import { showAlert, showAlertForError } from './alerts';
|
||||
import { useEmoji } from './emojis';
|
||||
import { importFetchedAccounts, importFetchedStatus } from './importer';
|
||||
import { openModal } from './modal';
|
||||
import { defineMessages } from 'react-intl';
|
||||
import { updateTimeline } from './timelines';
|
||||
|
||||
let cancelFetchComposeSuggestionsAccounts, cancelFetchComposeSuggestionsTags;
|
||||
/** @type {AbortController | undefined} */
|
||||
let fetchComposeSuggestionsAccountsController;
|
||||
/** @type {AbortController | undefined} */
|
||||
let fetchComposeSuggestionsTagsController;
|
||||
|
||||
export const COMPOSE_CHANGE = 'COMPOSE_CHANGE';
|
||||
export const COMPOSE_CYCLE_ELEFRIEND = 'COMPOSE_CYCLE_ELEFRIEND';
|
||||
|
@ -25,11 +27,13 @@ export const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL';
|
|||
export const COMPOSE_DIRECT = 'COMPOSE_DIRECT';
|
||||
export const COMPOSE_MENTION = 'COMPOSE_MENTION';
|
||||
export const COMPOSE_RESET = 'COMPOSE_RESET';
|
||||
export const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST';
|
||||
export const COMPOSE_UPLOAD_SUCCESS = 'COMPOSE_UPLOAD_SUCCESS';
|
||||
export const COMPOSE_UPLOAD_FAIL = 'COMPOSE_UPLOAD_FAIL';
|
||||
export const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS';
|
||||
export const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO';
|
||||
|
||||
export const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST';
|
||||
export const COMPOSE_UPLOAD_SUCCESS = 'COMPOSE_UPLOAD_SUCCESS';
|
||||
export const COMPOSE_UPLOAD_FAIL = 'COMPOSE_UPLOAD_FAIL';
|
||||
export const COMPOSE_UPLOAD_PROGRESS = 'COMPOSE_UPLOAD_PROGRESS';
|
||||
export const COMPOSE_UPLOAD_PROCESSING = 'COMPOSE_UPLOAD_PROCESSING';
|
||||
export const COMPOSE_UPLOAD_UNDO = 'COMPOSE_UPLOAD_UNDO';
|
||||
|
||||
export const THUMBNAIL_UPLOAD_REQUEST = 'THUMBNAIL_UPLOAD_REQUEST';
|
||||
export const THUMBNAIL_UPLOAD_SUCCESS = 'THUMBNAIL_UPLOAD_SUCCESS';
|
||||
|
@ -83,10 +87,8 @@ const messages = defineMessages({
|
|||
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
|
||||
});
|
||||
|
||||
const COMPOSE_PANEL_BREAKPOINT = 600 + (285 * 1) + (10 * 1);
|
||||
|
||||
export const ensureComposeIsVisible = (getState, routerHistory) => {
|
||||
if (!getState().getIn(['compose', 'mounted']) && window.innerWidth < COMPOSE_PANEL_BREAKPOINT) {
|
||||
if (!getState().getIn(['compose', 'mounted'])) {
|
||||
routerHistory.push('/publish');
|
||||
}
|
||||
};
|
||||
|
@ -221,6 +223,10 @@ export function submitCompose(routerHistory) {
|
|||
}
|
||||
};
|
||||
|
||||
if (statusId) {
|
||||
dispatch(importFetchedStatus({ ...response.data }));
|
||||
}
|
||||
|
||||
if (statusId === null) {
|
||||
insertIfOnline('home');
|
||||
}
|
||||
|
@ -307,13 +313,16 @@ export function uploadCompose(files) {
|
|||
if (status === 200) {
|
||||
dispatch(uploadComposeSuccess(data, f));
|
||||
} else if (status === 202) {
|
||||
dispatch(uploadComposeProcessing());
|
||||
|
||||
let tryCount = 1;
|
||||
|
||||
const poll = () => {
|
||||
api(getState).get(`/api/v1/media/${data.id}`).then(response => {
|
||||
if (response.status === 200) {
|
||||
dispatch(uploadComposeSuccess(response.data, f));
|
||||
} else if (response.status === 206) {
|
||||
let retryAfter = (Math.log2(tryCount) || 1) * 1000;
|
||||
const retryAfter = (Math.log2(tryCount) || 1) * 1000;
|
||||
tryCount += 1;
|
||||
setTimeout(() => poll(), retryAfter);
|
||||
}
|
||||
|
@ -328,6 +337,10 @@ export function uploadCompose(files) {
|
|||
};
|
||||
};
|
||||
|
||||
export const uploadComposeProcessing = () => ({
|
||||
type: COMPOSE_UPLOAD_PROCESSING,
|
||||
});
|
||||
|
||||
export const uploadThumbnail = (id, file) => (dispatch, getState) => {
|
||||
dispatch(uploadThumbnailRequest());
|
||||
|
||||
|
@ -472,8 +485,8 @@ export function undoUploadCompose(media_id) {
|
|||
};
|
||||
|
||||
export function clearComposeSuggestions() {
|
||||
if (cancelFetchComposeSuggestionsAccounts) {
|
||||
cancelFetchComposeSuggestionsAccounts();
|
||||
if (fetchComposeSuggestionsAccountsController) {
|
||||
fetchComposeSuggestionsAccountsController.abort();
|
||||
}
|
||||
return {
|
||||
type: COMPOSE_SUGGESTIONS_CLEAR,
|
||||
|
@ -481,14 +494,14 @@ export function clearComposeSuggestions() {
|
|||
};
|
||||
|
||||
const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => {
|
||||
if (cancelFetchComposeSuggestionsAccounts) {
|
||||
cancelFetchComposeSuggestionsAccounts();
|
||||
if (fetchComposeSuggestionsAccountsController) {
|
||||
fetchComposeSuggestionsAccountsController.abort();
|
||||
}
|
||||
|
||||
fetchComposeSuggestionsAccountsController = new AbortController();
|
||||
|
||||
api(getState).get('/api/v1/accounts/search', {
|
||||
cancelToken: new CancelToken(cancel => {
|
||||
cancelFetchComposeSuggestionsAccounts = cancel;
|
||||
}),
|
||||
signal: fetchComposeSuggestionsAccountsController.signal,
|
||||
|
||||
params: {
|
||||
q: token.slice(1),
|
||||
|
@ -499,9 +512,11 @@ const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) =>
|
|||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(readyComposeSuggestionsAccounts(token, response.data));
|
||||
}).catch(error => {
|
||||
if (!isCancel(error)) {
|
||||
if (!axios.isCancel(error)) {
|
||||
dispatch(showAlertForError(error));
|
||||
}
|
||||
}).finally(() => {
|
||||
fetchComposeSuggestionsAccountsController = undefined;
|
||||
});
|
||||
}, 200, { leading: true, trailing: true });
|
||||
|
||||
|
@ -511,16 +526,16 @@ const fetchComposeSuggestionsEmojis = (dispatch, getState, token) => {
|
|||
};
|
||||
|
||||
const fetchComposeSuggestionsTags = throttle((dispatch, getState, token) => {
|
||||
if (cancelFetchComposeSuggestionsTags) {
|
||||
cancelFetchComposeSuggestionsTags();
|
||||
if (fetchComposeSuggestionsTagsController) {
|
||||
fetchComposeSuggestionsTagsController.abort();
|
||||
}
|
||||
|
||||
dispatch(updateSuggestionTags(token));
|
||||
|
||||
fetchComposeSuggestionsTagsController = new AbortController();
|
||||
|
||||
api(getState).get('/api/v2/search', {
|
||||
cancelToken: new CancelToken(cancel => {
|
||||
cancelFetchComposeSuggestionsTags = cancel;
|
||||
}),
|
||||
signal: fetchComposeSuggestionsTagsController.signal,
|
||||
|
||||
params: {
|
||||
type: 'hashtags',
|
||||
|
@ -531,9 +546,11 @@ const fetchComposeSuggestionsTags = throttle((dispatch, getState, token) => {
|
|||
}).then(({ data }) => {
|
||||
dispatch(readyComposeSuggestionsTags(token, data.hashtags));
|
||||
}).catch(error => {
|
||||
if (!isCancel(error)) {
|
||||
if (!axios.isCancel(error)) {
|
||||
dispatch(showAlertForError(error));
|
||||
}
|
||||
}).finally(() => {
|
||||
fetchComposeSuggestionsTagsController = undefined;
|
||||
});
|
||||
}, 200, { leading: true, trailing: true });
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import api from '../api';
|
||||
|
||||
export const FEATURED_TAGS_FETCH_REQUEST = 'FEATURED_TAGS_FETCH_REQUEST';
|
||||
export const FEATURED_TAGS_FETCH_SUCCESS = 'FEATURED_TAGS_FETCH_SUCCESS';
|
||||
export const FEATURED_TAGS_FETCH_FAIL = 'FEATURED_TAGS_FETCH_FAIL';
|
||||
|
||||
export const fetchFeaturedTags = (id) => (dispatch, getState) => {
|
||||
if (getState().getIn(['user_lists', 'featured_tags', id, 'items'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(fetchFeaturedTagsRequest(id));
|
||||
|
||||
api(getState).get(`/api/v1/accounts/${id}/featured_tags`)
|
||||
.then(({ data }) => dispatch(fetchFeaturedTagsSuccess(id, data)))
|
||||
.catch(err => dispatch(fetchFeaturedTagsFail(id, err)));
|
||||
};
|
||||
|
||||
export const fetchFeaturedTagsRequest = (id) => ({
|
||||
type: FEATURED_TAGS_FETCH_REQUEST,
|
||||
id,
|
||||
});
|
||||
|
||||
export const fetchFeaturedTagsSuccess = (id, tags) => ({
|
||||
type: FEATURED_TAGS_FETCH_SUCCESS,
|
||||
id,
|
||||
tags,
|
||||
});
|
||||
|
||||
export const fetchFeaturedTagsFail = (id, error) => ({
|
||||
type: FEATURED_TAGS_FETCH_FAIL,
|
||||
id,
|
||||
error,
|
||||
});
|
|
@ -43,7 +43,7 @@ export const fetchFilters = () => (dispatch, getState) => {
|
|||
export const createFilterStatus = (params, onSuccess, onFail) => (dispatch, getState) => {
|
||||
dispatch(createFilterStatusRequest());
|
||||
|
||||
api(getState).post(`/api/v1/filters/${params.filter_id}/statuses`, params).then(response => {
|
||||
api(getState).post(`/api/v2/filters/${params.filter_id}/statuses`, params).then(response => {
|
||||
dispatch(createFilterStatusSuccess(response.data));
|
||||
if (onSuccess) onSuccess();
|
||||
}).catch(error => {
|
||||
|
|
|
@ -29,7 +29,8 @@ export function clearSearch() {
|
|||
|
||||
export function submitSearch() {
|
||||
return (dispatch, getState) => {
|
||||
const value = getState().getIn(['search', 'value']);
|
||||
const value = getState().getIn(['search', 'value']);
|
||||
const signedIn = !!getState().getIn(['meta', 'me']);
|
||||
|
||||
if (value.length === 0) {
|
||||
dispatch(fetchSearchSuccess({ accounts: [], statuses: [], hashtags: [] }, ''));
|
||||
|
@ -41,7 +42,7 @@ export function submitSearch() {
|
|||
api(getState).get('/api/v2/search', {
|
||||
params: {
|
||||
q: value,
|
||||
resolve: true,
|
||||
resolve: signedIn,
|
||||
limit: 10,
|
||||
},
|
||||
}).then(response => {
|
||||
|
|
|
@ -5,6 +5,14 @@ export const SERVER_FETCH_REQUEST = 'Server_FETCH_REQUEST';
|
|||
export const SERVER_FETCH_SUCCESS = 'Server_FETCH_SUCCESS';
|
||||
export const SERVER_FETCH_FAIL = 'Server_FETCH_FAIL';
|
||||
|
||||
export const EXTENDED_DESCRIPTION_REQUEST = 'EXTENDED_DESCRIPTION_REQUEST';
|
||||
export const EXTENDED_DESCRIPTION_SUCCESS = 'EXTENDED_DESCRIPTION_SUCCESS';
|
||||
export const EXTENDED_DESCRIPTION_FAIL = 'EXTENDED_DESCRIPTION_FAIL';
|
||||
|
||||
export const SERVER_DOMAIN_BLOCKS_FETCH_REQUEST = 'SERVER_DOMAIN_BLOCKS_FETCH_REQUEST';
|
||||
export const SERVER_DOMAIN_BLOCKS_FETCH_SUCCESS = 'SERVER_DOMAIN_BLOCKS_FETCH_SUCCESS';
|
||||
export const SERVER_DOMAIN_BLOCKS_FETCH_FAIL = 'SERVER_DOMAIN_BLOCKS_FETCH_FAIL';
|
||||
|
||||
export const fetchServer = () => (dispatch, getState) => {
|
||||
dispatch(fetchServerRequest());
|
||||
|
||||
|
@ -28,3 +36,56 @@ const fetchServerFail = error => ({
|
|||
type: SERVER_FETCH_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
export const fetchExtendedDescription = () => (dispatch, getState) => {
|
||||
dispatch(fetchExtendedDescriptionRequest());
|
||||
|
||||
api(getState)
|
||||
.get('/api/v1/instance/extended_description')
|
||||
.then(({ data }) => dispatch(fetchExtendedDescriptionSuccess(data)))
|
||||
.catch(err => dispatch(fetchExtendedDescriptionFail(err)));
|
||||
};
|
||||
|
||||
const fetchExtendedDescriptionRequest = () => ({
|
||||
type: EXTENDED_DESCRIPTION_REQUEST,
|
||||
});
|
||||
|
||||
const fetchExtendedDescriptionSuccess = description => ({
|
||||
type: EXTENDED_DESCRIPTION_SUCCESS,
|
||||
description,
|
||||
});
|
||||
|
||||
const fetchExtendedDescriptionFail = error => ({
|
||||
type: EXTENDED_DESCRIPTION_FAIL,
|
||||
error,
|
||||
});
|
||||
|
||||
export const fetchDomainBlocks = () => (dispatch, getState) => {
|
||||
dispatch(fetchDomainBlocksRequest());
|
||||
|
||||
api(getState)
|
||||
.get('/api/v1/instance/domain_blocks')
|
||||
.then(({ data }) => dispatch(fetchDomainBlocksSuccess(true, data)))
|
||||
.catch(err => {
|
||||
if (err.response.status === 404) {
|
||||
dispatch(fetchDomainBlocksSuccess(false, []));
|
||||
} else {
|
||||
dispatch(fetchDomainBlocksFail(err));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const fetchDomainBlocksRequest = () => ({
|
||||
type: SERVER_DOMAIN_BLOCKS_FETCH_REQUEST,
|
||||
});
|
||||
|
||||
const fetchDomainBlocksSuccess = (isAvailable, blocks) => ({
|
||||
type: SERVER_DOMAIN_BLOCKS_FETCH_SUCCESS,
|
||||
isAvailable,
|
||||
blocks,
|
||||
});
|
||||
|
||||
const fetchDomainBlocksFail = error => ({
|
||||
type: SERVER_DOMAIN_BLOCKS_FETCH_FAIL,
|
||||
error,
|
||||
});
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue