Merge branch 'main' of https://github.com/mastodon/mastodon into features/main
This commit is contained in:
commit
c0cf1ebdd3
@ -1,3 +0,0 @@
|
||||
---
|
||||
ignore:
|
||||
- CVE-2015-9284 # Mitigation following https://github.com/omniauth/omniauth/wiki/Resolving-CVE-2015-9284#mitigating-in-rails-applications
|
56
.github/ISSUE_TEMPLATE/1.bug_report.yml
vendored
56
.github/ISSUE_TEMPLATE/1.bug_report.yml
vendored
@ -1,56 +0,0 @@
|
||||
name: Bug Report
|
||||
description: If something isn't working as expected
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Make sure that you are submitting a new bug that was not previously reported or already fixed.
|
||||
|
||||
Please use a concise and distinct title for the issue.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce the problem
|
||||
description: What were you trying to do?
|
||||
value: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
...
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Expected behaviour
|
||||
description: What should have happened?
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Actual behaviour
|
||||
description: What happened?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Detailed description
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Specifications
|
||||
description: |
|
||||
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
|
76
.github/ISSUE_TEMPLATE/1.web_bug_report.yml
vendored
Normal file
76
.github/ISSUE_TEMPLATE/1.web_bug_report.yml
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
name: Bug Report (Web Interface)
|
||||
description: If you are using Mastodon's web interface and something is not working as expected
|
||||
labels: [bug, 'status/to triage', 'area/web interface']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Make sure that you are submitting a new bug that was not previously reported or already fixed.
|
||||
|
||||
Please use a concise and distinct title for the issue.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce the problem
|
||||
description: What were you trying to do?
|
||||
value: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
...
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Expected behaviour
|
||||
description: What should have happened?
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Actual behaviour
|
||||
description: What happened?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Detailed description
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
attributes:
|
||||
label: Mastodon instance
|
||||
description: The address of the Mastodon instance where you experienced the issue
|
||||
placeholder: mastodon.social
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Mastodon version
|
||||
description: |
|
||||
This is displayed at the bottom of the About page, eg. `v4.1.2+nightly-20230627`
|
||||
placeholder: v4.1.2
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Browser name and version
|
||||
description: |
|
||||
What browser are you using when getting this bug? Please specify the version as well.
|
||||
placeholder: Firefox 105.0.3
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Operating system
|
||||
description: |
|
||||
What OS are you running? Please specify the version as well.
|
||||
placeholder: macOS 13.4.1
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Technical details
|
||||
description: |
|
||||
Any additional technical details you may have. This can include the full error log, inspector's output…
|
||||
validations:
|
||||
required: false
|
65
.github/ISSUE_TEMPLATE/2.server_bug_report.yml
vendored
Normal file
65
.github/ISSUE_TEMPLATE/2.server_bug_report.yml
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
name: Bug Report (server / API)
|
||||
description: |
|
||||
If something is not working as expected, but is not from using the web interface.
|
||||
labels: [bug, 'status/to triage']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Make sure that you are submitting a new bug that was not previously reported or already fixed.
|
||||
|
||||
Please use a concise and distinct title for the issue.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce the problem
|
||||
description: What were you trying to do?
|
||||
value: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
...
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Expected behaviour
|
||||
description: What should have happened?
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
label: Actual behaviour
|
||||
description: What happened?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Detailed description
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
attributes:
|
||||
label: Mastodon instance
|
||||
description: The address of the Mastodon instance where you experienced the issue
|
||||
placeholder: mastodon.social
|
||||
validations:
|
||||
required: false
|
||||
- type: input
|
||||
attributes:
|
||||
label: Mastodon version
|
||||
description: |
|
||||
This is displayed at the bottom of the About page, eg. `v4.1.2+nightly-20230627`
|
||||
placeholder: v4.1.2
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Technical details
|
||||
description: |
|
||||
Any additional technical details you may have, like logs or error traces
|
||||
value: |
|
||||
If this is happening on your own Mastodon server, please fill out those:
|
||||
- Ruby version: (from `ruby --version`, eg. v3.1.2)
|
||||
- Node.js version: (from `node --version`, eg. v18.16.0)
|
||||
validations:
|
||||
required: false
|
29
.github/renovate.json5
vendored
29
.github/renovate.json5
vendored
@ -1,20 +1,21 @@
|
||||
{
|
||||
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
|
||||
extends: [
|
||||
'config:base',
|
||||
':dependencyDashboard',
|
||||
'config:recommended',
|
||||
':labels(dependencies)',
|
||||
':maintainLockFilesMonthly', // update non-direct dependencies monthly
|
||||
':prConcurrentLimit10', // only 10 open PRs at the same time
|
||||
':prConcurrentLimitNone', // Remove limit for open PRs at any time.
|
||||
':prHourlyLimit2' // Rate limit PR creation to a maximum of two per hour.
|
||||
],
|
||||
stabilityDays: 3, // Wait 3 days after the package has been published before upgrading it
|
||||
minimumReleaseAge: "3", // Wait 3 days after the package has been published before upgrading it
|
||||
// packageRules order is important, they are applied from top to bottom and are merged,
|
||||
// meaning the most important ones must be at the bottom, for example grouping rules
|
||||
// If we do not want a package to be grouped with others, we need to set its groupName
|
||||
// to `null` after any other rule set it to something.
|
||||
dependencyDashboardHeader: 'This issue lists Renovate updates and detected dependencies. Read the [Dependency Dashboard](https://docs.renovatebot.com/key-concepts/dashboard/) docs to learn more. Before approving any upgrade: read the description and comments in the [`renovate.json5` file](https://github.com/mastodon/mastodon/blob/main/.github/renovate.json5).',
|
||||
packageRules: [
|
||||
{
|
||||
// Ignore major version bumps for these node packages
|
||||
// Require Dependency Dashboard Approval for major version bumps of these node packages
|
||||
matchManagers: ['npm'],
|
||||
matchPackageNames: [
|
||||
'tesseract.js', // Requires code changes
|
||||
@ -41,10 +42,10 @@
|
||||
'react-router-dom',
|
||||
],
|
||||
matchUpdateTypes: ['major'],
|
||||
enabled: false,
|
||||
"dependencyDashboardApproval": true
|
||||
},
|
||||
{
|
||||
// Ignore major version bumps for these Ruby packages
|
||||
// Require Dependency Dashboard Approval for major version bumps of these Ruby packages
|
||||
matchManagers: ['bundler'],
|
||||
matchPackageNames: [
|
||||
'rack', // Needs to be synced with Rails version
|
||||
@ -55,7 +56,7 @@
|
||||
'redis', // Requires manual upgrade and sync with Sidekiq version
|
||||
],
|
||||
matchUpdateTypes: ['major'],
|
||||
enabled: false,
|
||||
"dependencyDashboardApproval": true
|
||||
},
|
||||
{
|
||||
// Update Github Actions and Docker images weekly
|
||||
@ -63,25 +64,25 @@
|
||||
extends: ['schedule:weekly'],
|
||||
},
|
||||
{
|
||||
// Ignore major & minor bumps for the ruby image, this needs to be synced with .ruby-version
|
||||
// Require Dependency Dashboard Approval for major & minor bumps for the ruby image, this needs to be synced with .ruby-version
|
||||
matchManagers: ['dockerfile'],
|
||||
matchPackageNames: ['moritzheiber/ruby-jemalloc'],
|
||||
matchUpdateTypes: ['minor', 'major'],
|
||||
enabled: false,
|
||||
"dependencyDashboardApproval": true
|
||||
},
|
||||
{
|
||||
// Ignore major bump for the node image, this needs to be synced with .nvmrc
|
||||
// Require Dependency Dashboard Approval for major bumps for the node image, this needs to be synced with .nvmrc
|
||||
matchManagers: ['dockerfile'],
|
||||
matchPackageNames: ['node'],
|
||||
matchUpdateTypes: ['major'],
|
||||
enabled: false,
|
||||
"dependencyDashboardApproval": true
|
||||
},
|
||||
{
|
||||
// Ignore major postgres bumps in the docker-compose file, as those break dev environments
|
||||
// Require Dependency Dashboard Approval for major postgres bumps in the docker-compose file, as those break dev environments
|
||||
matchManagers: ['docker-compose'],
|
||||
matchPackageNames: ['postgres'],
|
||||
matchUpdateTypes: ['major'],
|
||||
enabled: false,
|
||||
"dependencyDashboardApproval": true
|
||||
},
|
||||
{
|
||||
// Update devDependencies every week, with one grouped PR
|
||||
|
94
.github/workflows/build-container-image.yml
vendored
Normal file
94
.github/workflows/build-container-image.yml
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
platforms:
|
||||
required: true
|
||||
type: string
|
||||
use_native_arm64_builder:
|
||||
type: boolean
|
||||
push_to_images:
|
||||
type: string
|
||||
version_suffix:
|
||||
type: string
|
||||
flavor:
|
||||
type: string
|
||||
tags:
|
||||
type: string
|
||||
labels:
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build-image:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: docker/setup-qemu-action@v2
|
||||
if: contains(inputs.platforms, 'linux/arm64') && !inputs.use_native_arm64_builder
|
||||
|
||||
- uses: docker/setup-buildx-action@v2
|
||||
id: buildx
|
||||
if: ${{ !(inputs.use_native_arm64_builder && contains(inputs.platforms, 'linux/arm64')) }}
|
||||
|
||||
- name: Start a local Docker Builder
|
||||
if: inputs.use_native_arm64_builder && contains(inputs.platforms, 'linux/arm64')
|
||||
run: |
|
||||
docker run --rm -d --name buildkitd -p 1234:1234 --privileged moby/buildkit:latest --addr tcp://0.0.0.0:1234
|
||||
|
||||
- uses: docker/setup-buildx-action@v2
|
||||
id: buildx-native
|
||||
if: inputs.use_native_arm64_builder && contains(inputs.platforms, 'linux/arm64')
|
||||
with:
|
||||
driver: remote
|
||||
endpoint: tcp://localhost:1234
|
||||
platforms: linux/amd64
|
||||
append: |
|
||||
- endpoint: tcp://${{ vars.DOCKER_BUILDER_HETZNER_ARM64_01_HOST }}:13865
|
||||
platforms: linux/arm64
|
||||
name: mastodon-docker-builder-arm64-01
|
||||
driver-opts:
|
||||
- servername=mastodon-docker-builder-arm64-01
|
||||
env:
|
||||
BUILDER_NODE_1_AUTH_TLS_CACERT: ${{ secrets.DOCKER_BUILDER_HETZNER_ARM64_01_CACERT }}
|
||||
BUILDER_NODE_1_AUTH_TLS_CERT: ${{ secrets.DOCKER_BUILDER_HETZNER_ARM64_01_CERT }}
|
||||
BUILDER_NODE_1_AUTH_TLS_KEY: ${{ secrets.DOCKER_BUILDER_HETZNER_ARM64_01_KEY }}
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
if: contains(inputs.push_to_images, 'tootsuite')
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Log in to the Github Container registry
|
||||
if: contains(inputs.push_to_images, 'ghcr.io')
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: docker/metadata-action@v4
|
||||
id: meta
|
||||
if: ${{ inputs.push_to_images != '' }}
|
||||
with:
|
||||
images: ${{ inputs.push_to_images }}
|
||||
# Only tag with latest when ran against the latest stable branch
|
||||
# This needs to be updated after each minor version release
|
||||
flavor: ${{ inputs.flavor }}
|
||||
tags: ${{ inputs.tags }}
|
||||
labels: ${{ inputs.labels }}
|
||||
|
||||
- uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
build-args: MASTODON_VERSION_SUFFIX=${{ inputs.version_suffix }}
|
||||
platforms: ${{ inputs.platforms }}
|
||||
provenance: false
|
||||
builder: ${{ steps.buildx.outputs.name || steps.buildx-native.outputs.name }}
|
||||
push: ${{ inputs.push_to_images != '' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
79
.github/workflows/build-image.yml
vendored
79
.github/workflows/build-image.yml
vendored
@ -1,79 +0,0 @@
|
||||
name: Build container image
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- 'main'
|
||||
tags:
|
||||
- '*'
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/build-image.yml
|
||||
- Dockerfile
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-image:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: hadolint/hadolint-action@v3.1.0
|
||||
- uses: docker/setup-qemu-action@v2
|
||||
- uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
if: github.repository == 'mastodon/mastodon' && github.event_name != 'pull_request'
|
||||
|
||||
- name: Log in to the Github Container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
if: github.repository == 'mastodon/mastodon' && github.event_name != 'pull_request'
|
||||
|
||||
- uses: docker/metadata-action@v4
|
||||
id: meta
|
||||
with:
|
||||
images: |
|
||||
tootsuite/mastodon
|
||||
ghcr.io/mastodon/mastodon
|
||||
# Only tag with latest when ran against the latest stable branch
|
||||
# This needs to be updated after each minor version release
|
||||
flavor: |
|
||||
latest=${{ startsWith(github.ref, 'refs/tags/v4.1.') }}
|
||||
tags: |
|
||||
type=edge,branch=main
|
||||
type=pep440,pattern={{raw}}
|
||||
type=pep440,pattern=v{{major}}.{{minor}}
|
||||
type=ref,event=pr
|
||||
|
||||
- name: Generate version suffix
|
||||
id: version_vars
|
||||
if: github.repository == 'mastodon/mastodon' && github.event_name == 'push' && github.ref_name == 'main'
|
||||
run: |
|
||||
echo mastodon_version_suffix=+edge-$(git rev-parse --short HEAD) >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
build-args: MASTODON_VERSION_SUFFIX=${{ steps.version_vars.outputs.mastodon_version_suffix }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
provenance: false
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
push: ${{ github.repository == 'mastodon/mastodon' && github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
74
.github/workflows/build-nightly.yml
vendored
74
.github/workflows/build-nightly.yml
vendored
@ -3,58 +3,40 @@ on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 2 * * *' # run at 2 AM UTC
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-nightly-image:
|
||||
compute-suffix:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: hadolint/hadolint-action@v3.1.0
|
||||
- uses: docker/setup-qemu-action@v2
|
||||
- uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Log in to the Github Container registry
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- uses: docker/metadata-action@v4
|
||||
id: meta
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/mastodon/mastodon
|
||||
flavor: |
|
||||
latest=auto
|
||||
tags: |
|
||||
type=raw,value=nightly
|
||||
type=schedule,pattern=nightly-{{date 'YYYY-MM-DD' tz='Etc/UTC'}}
|
||||
labels: |
|
||||
org.opencontainers.image.description=Nightly build image used for testing purposes
|
||||
|
||||
- name: Generate version suffix
|
||||
id: version_vars
|
||||
- id: version_vars
|
||||
env:
|
||||
TZ: Etc/UTC
|
||||
run: |
|
||||
echo mastodon_version_suffix=+nightly-$(date +'%Y%m%d') >> $GITHUB_OUTPUT
|
||||
echo mastodon_version_suffix=nightly-$(date +'%Y-%m-%d')>> $GITHUB_OUTPUT
|
||||
outputs:
|
||||
suffix: ${{ steps.version_vars.outputs.mastodon_version_suffix }}
|
||||
|
||||
- uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
build-args: MASTODON_VERSION_SUFFIX=${{ steps.version_vars.outputs.mastodon_version_suffix }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
provenance: false
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
push: ${{ github.repository == 'mastodon/mastodon' && github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
build-image:
|
||||
needs: compute-suffix
|
||||
uses: ./.github/workflows/build-container-image.yml
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
use_native_arm64_builder: true
|
||||
push_to_images: |
|
||||
tootsuite/mastodon
|
||||
ghcr.io/mastodon/mastodon
|
||||
# The `+` is important here, result will be v4.1.2+nightly-2022-03-05
|
||||
version_suffix: +${{ needs.compute-suffix.outputs.suffix }}
|
||||
labels: |
|
||||
org.opencontainers.image.description=Nightly build image used for testing purposes
|
||||
flavor: |
|
||||
latest=auto
|
||||
tags: |
|
||||
type=raw,value=edge
|
||||
type=raw,value=nightly
|
||||
type=schedule,pattern=${{ needs.compute-suffix.outputs.suffix }}
|
||||
secrets: inherit
|
||||
|
41
.github/workflows/build-push-pr.yml
vendored
Normal file
41
.github/workflows/build-push-pr.yml
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
name: Build container image for PR
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled, synchronize, reopened, ready_for_review, opened]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
compute-suffix:
|
||||
runs-on: ubuntu-latest
|
||||
# This is only allowed to run if:
|
||||
# - the PR branch is in the `mastodon/mastodon` repository
|
||||
# - the PR is not a draft
|
||||
# - the PR has the "build-image" label
|
||||
if: ${{ github.event.pull_request.head.repo.full_name == github.repository && !github.event.pull_request.draft && contains(github.event.pull_request.labels.*.name, 'build-image') }}
|
||||
steps:
|
||||
# Repository needs to be cloned so `git rev-parse` below works
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v3
|
||||
- id: version_vars
|
||||
run: |
|
||||
echo mastodon_version_suffix=+pr-${{ github.event.pull_request.number }}-$(git rev-parse --short HEAD) >> $GITHUB_OUTPUT
|
||||
outputs:
|
||||
suffix: ${{ steps.version_vars.outputs.mastodon_version_suffix }}
|
||||
|
||||
build-image:
|
||||
needs: compute-suffix
|
||||
uses: ./.github/workflows/build-container-image.yml
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
use_native_arm64_builder: true
|
||||
push_to_images: |
|
||||
ghcr.io/mastodon/mastodon
|
||||
version_suffix: ${{ needs.compute-suffix.outputs.suffix }}
|
||||
flavor: |
|
||||
latest=auto
|
||||
tags: |
|
||||
type=ref,event=pr
|
||||
secrets: inherit
|
25
.github/workflows/build-releases.yml
vendored
Normal file
25
.github/workflows/build-releases.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
name: Build container release images
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
build-image:
|
||||
uses: ./.github/workflows/build-container-image.yml
|
||||
with:
|
||||
platforms: linux/amd64,linux/arm64
|
||||
use_native_arm64_builder: true
|
||||
push_to_images: |
|
||||
tootsuite/mastodon
|
||||
ghcr.io/mastodon/mastodon
|
||||
flavor: |
|
||||
latest=${{ startsWith(github.ref, 'refs/tags/v4.1.') }}
|
||||
tags: |
|
||||
type=pep440,pattern={{raw}}
|
||||
type=pep440,pattern=v{{major}}.{{minor}}
|
||||
secrets: inherit
|
76
.github/workflows/crowdin-download.yml
vendored
Normal file
76
.github/workflows/crowdin-download.yml
vendored
Normal file
@ -0,0 +1,76 @@
|
||||
name: Crowdin / Download translations
|
||||
on:
|
||||
schedule:
|
||||
- cron: '17 4 * * *' # Every day
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
download-translations:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Increase Git http.postBuffer
|
||||
# This is needed due to a bug in Ubuntu's cURL version?
|
||||
# See https://github.com/orgs/community/discussions/55820
|
||||
run: |
|
||||
git config --global http.version HTTP/1.1
|
||||
git config --global http.postBuffer 157286400
|
||||
|
||||
# Download the translation files from Crowdin
|
||||
- name: crowdin action
|
||||
uses: crowdin/github-action@v1
|
||||
with:
|
||||
upload_sources: false
|
||||
upload_translations: false
|
||||
download_translations: true
|
||||
crowdin_branch_name: main
|
||||
push_translations: false
|
||||
create_pull_request: false
|
||||
env:
|
||||
CROWDIN_PROJECT_ID: ${{ vars.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
||||
|
||||
# As the files are extracted from a Docker container, they belong to root:root
|
||||
# We need to fix this before the next steps
|
||||
- name: Fix file permissions
|
||||
run: sudo chown -R runner:docker .
|
||||
|
||||
# This is needed to run the normalize step
|
||||
- name: Install native Ruby dependencies
|
||||
run: sudo apt-get install -y libicu-dev libidn11-dev
|
||||
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: .ruby-version
|
||||
bundler-cache: true
|
||||
|
||||
- name: Run i18n normalize task
|
||||
run: bundle exec i18n-tasks normalize
|
||||
|
||||
# Create or update the pull request
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5.0.2
|
||||
with:
|
||||
commit-message: 'New Crowdin translations'
|
||||
title: 'New Crowdin Translations (automated)'
|
||||
author: 'GitHub Actions <noreply@github.com>'
|
||||
body: |
|
||||
New Crowdin translations, automated with Github Actions
|
||||
|
||||
See `.github/workflows/crowdin-download.yml`
|
||||
|
||||
This PR will be updated every day with new translations.
|
||||
|
||||
Due to a limitation in Github Actions, checks are not running on this PR without manual action.
|
||||
If you want to run the checks, then close and re-open it.
|
||||
branch: i18n/crowdin/translations
|
||||
base: main
|
||||
labels: i18n
|
35
.github/workflows/crowdin-upload.yml
vendored
Normal file
35
.github/workflows/crowdin-upload.yml
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
name: Crowdin / Upload translations
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- crowdin.yml
|
||||
- app/javascript/mastodon/locales/en.json
|
||||
- config/locales/en.yml
|
||||
- config/locales/simple_form.en.yml
|
||||
- config/locales/activerecord.en.yml
|
||||
- config/locales/devise.en.yml
|
||||
- config/locales/doorkeeper.en.yml
|
||||
- .github/workflows/crowdin-upload.yml
|
||||
|
||||
jobs:
|
||||
upload-translations:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: crowdin action
|
||||
uses: crowdin/github-action@v1
|
||||
with:
|
||||
upload_sources: true
|
||||
upload_translations: false
|
||||
download_translations: false
|
||||
crowdin_branch_name: main
|
||||
|
||||
env:
|
||||
CROWDIN_PROJECT_ID: ${{ vars.CROWDIN_PROJECT_ID }}
|
||||
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
|
21
.github/workflows/test-image-build.yml
vendored
Normal file
21
.github/workflows/test-image-build.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
name: Test container image build
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/build-nightly.yml
|
||||
- .github/workflows/build-push-pr.yml
|
||||
- .github/workflows/build-releases.yml
|
||||
- .github/workflows/test-image-build.yml
|
||||
- Dockerfile
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-image:
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
uses: ./.github/workflows/build-container-image.yml
|
||||
with:
|
||||
platforms: linux/amd64 # Testing only on native platform so it is performant
|
101
.github/workflows/test-ruby.yml
vendored
101
.github/workflows/test-ruby.yml
vendored
@ -107,6 +107,10 @@ jobs:
|
||||
PAM_ENABLED: true
|
||||
PAM_DEFAULT_SERVICE: pam_test
|
||||
PAM_CONTROLLED_SERVICE: pam_test_controlled
|
||||
OIDC_ENABLED: true
|
||||
OIDC_SCOPE: read
|
||||
SAML_ENABLED: true
|
||||
CAS_ENABLED: true
|
||||
BUNDLE_WITH: 'pam_authentication test'
|
||||
CI_JOBS: ${{ matrix.ci_job }}/4
|
||||
|
||||
@ -149,3 +153,100 @@ jobs:
|
||||
run: './bin/rails db:create db:schema:load db:seed'
|
||||
|
||||
- run: bundle exec rake rspec_chunked
|
||||
|
||||
test-e2e:
|
||||
name: End to End testing
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
needs:
|
||||
- build
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:14-alpine
|
||||
env:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_USER: postgres
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
env:
|
||||
DB_HOST: localhost
|
||||
DB_USER: postgres
|
||||
DB_PASS: postgres
|
||||
DISABLE_SIMPLECOV: true
|
||||
RAILS_ENV: test
|
||||
BUNDLE_WITH: test
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
ruby-version:
|
||||
- '3.0'
|
||||
- '3.1'
|
||||
- '.ruby-version'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: './public'
|
||||
name: ${{ github.sha }}
|
||||
|
||||
- name: Update package index
|
||||
run: sudo apt-get update
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: yarn
|
||||
node-version-file: '.nvmrc'
|
||||
|
||||
- name: Install native Ruby dependencies
|
||||
run: sudo apt-get install -y libicu-dev libidn11-dev
|
||||
|
||||
- name: Install additional system dependencies
|
||||
run: sudo apt-get install -y ffmpeg imagemagick
|
||||
|
||||
- name: Set up bundler cache
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: ${{ matrix.ruby-version}}
|
||||
bundler-cache: true
|
||||
|
||||
- run: yarn --frozen-lockfile
|
||||
|
||||
- name: Load database schema
|
||||
run: './bin/rails db:create db:schema:load db:seed'
|
||||
|
||||
- run: bundle exec rake spec:system
|
||||
|
||||
- name: Archive logs
|
||||
uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
with:
|
||||
name: e2e-logs-${{ matrix.ruby-version }}
|
||||
path: log/
|
||||
|
||||
- name: Archive test screenshots
|
||||
uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
with:
|
||||
name: e2e-screenshots
|
||||
path: tmp/screenshots/
|
||||
|
@ -1,13 +1,13 @@
|
||||
# This configuration was generated by
|
||||
# `haml-lint --auto-gen-config`
|
||||
# on 2023-07-17 15:30:11 -0400 using Haml-Lint version 0.48.0.
|
||||
# on 2023-07-20 09:47:50 -0400 using Haml-Lint version 0.48.0.
|
||||
# The point is for the user to remove these configuration records
|
||||
# one by one as the lints are removed from the code base.
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
# versions of Haml-Lint, may require this file to be generated again.
|
||||
|
||||
linters:
|
||||
# Offense count: 959
|
||||
# Offense count: 951
|
||||
LineLength:
|
||||
enabled: false
|
||||
|
||||
@ -15,7 +15,7 @@ linters:
|
||||
UnnecessaryStringOutput:
|
||||
enabled: false
|
||||
|
||||
# Offense count: 67
|
||||
# Offense count: 57
|
||||
RuboCop:
|
||||
enabled: false
|
||||
|
||||
@ -26,20 +26,18 @@ linters:
|
||||
- 'app/views/admin/reports/show.html.haml'
|
||||
- 'app/views/disputes/strikes/show.html.haml'
|
||||
|
||||
# Offense count: 41
|
||||
# Offense count: 32
|
||||
InstanceVariables:
|
||||
exclude:
|
||||
- 'app/views/admin/reports/_actions.html.haml'
|
||||
- 'app/views/admin/roles/_form.html.haml'
|
||||
- 'app/views/admin/webhooks/_form.html.haml'
|
||||
- 'app/views/auth/registrations/_sessions.html.haml'
|
||||
- 'app/views/auth/registrations/_status.html.haml'
|
||||
- 'app/views/auth/sessions/two_factor/_otp_authentication_form.html.haml'
|
||||
- 'app/views/authorize_interactions/_post_follow_actions.html.haml'
|
||||
- 'app/views/invites/_form.html.haml'
|
||||
- 'app/views/relationships/_account.html.haml'
|
||||
- 'app/views/shared/_og.html.haml'
|
||||
- 'app/views/statuses/_status.html.haml'
|
||||
|
||||
# Offense count: 3
|
||||
IdNames:
|
||||
|
15
.rubocop.yml
15
.rubocop.yml
@ -38,14 +38,7 @@ Layout/FirstHashElementIndentation:
|
||||
# Reason: Currently disabled in .rubocop_todo.yml
|
||||
# https://docs.rubocop.org/rubocop/cops_layout.html#layoutlinelength
|
||||
Layout/LineLength:
|
||||
AllowedPatterns:
|
||||
# Allow comments to be long lines
|
||||
- !ruby/regexp / \# .*$/
|
||||
- !ruby/regexp /^\# .*$/
|
||||
Exclude:
|
||||
- 'lib/mastodon/cli/*.rb'
|
||||
- db/*migrate/**/*
|
||||
- db/seeds/**/*
|
||||
Max: 320 # Default of 120 causes a duplicate entry in generated todo file
|
||||
|
||||
# Reason:
|
||||
# https://docs.rubocop.org/rubocop/cops_lint.html#lintuselessaccessmodifier
|
||||
@ -131,12 +124,6 @@ RSpec/FilePath:
|
||||
Exclude:
|
||||
- 'spec/config/initializers/rack_attack_spec.rb' # namespaces usually have separate folder
|
||||
- 'spec/lib/sanitize_config_spec.rb' # namespaces usually have separate folder
|
||||
- 'spec/controllers/concerns/account_controller_concern_spec.rb' # Concerns describe ApplicationController and don't fit naming
|
||||
- 'spec/controllers/concerns/export_controller_concern_spec.rb'
|
||||
- 'spec/controllers/concerns/localized_spec.rb'
|
||||
- 'spec/controllers/concerns/rate_limit_headers_spec.rb'
|
||||
- 'spec/controllers/concerns/signature_verification_spec.rb'
|
||||
- 'spec/controllers/concerns/user_tracking_concern_spec.rb'
|
||||
|
||||
# Reason:
|
||||
# https://docs.rubocop.org/rubocop-rspec/cops_rspec.html#rspecnamedsubject
|
||||
|
@ -39,6 +39,13 @@ Layout/LeadingCommentSpace:
|
||||
- 'config/application.rb'
|
||||
- 'config/initializers/omniauth.rb'
|
||||
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns.
|
||||
# URISchemes: http, https
|
||||
Layout/LineLength:
|
||||
Exclude:
|
||||
- 'app/models/account.rb'
|
||||
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: EnforcedStyle.
|
||||
# SupportedStyles: require_no_space, require_space
|
||||
@ -112,7 +119,6 @@ Lint/UselessAssignment:
|
||||
- 'config/initializers/omniauth.rb'
|
||||
- 'db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb'
|
||||
- 'db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb'
|
||||
- 'spec/controllers/api/v1/bookmarks_controller_spec.rb'
|
||||
- 'spec/controllers/api/v1/favourites_controller_spec.rb'
|
||||
- 'spec/controllers/concerns/account_controller_concern_spec.rb'
|
||||
- 'spec/helpers/jsonld_helper_spec.rb'
|
||||
@ -127,15 +133,9 @@ Lint/UselessAssignment:
|
||||
- 'spec/services/resolve_url_service_spec.rb'
|
||||
- 'spec/views/statuses/show.html.haml_spec.rb'
|
||||
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: CheckForMethodsWithNoSideEffects.
|
||||
Lint/Void:
|
||||
Exclude:
|
||||
- 'spec/services/resolve_account_service_spec.rb'
|
||||
|
||||
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
|
||||
Metrics/AbcSize:
|
||||
Max: 150
|
||||
Max: 146
|
||||
|
||||
# Configuration parameters: CountBlocks, Max.
|
||||
Metrics/BlockNesting:
|
||||
@ -150,13 +150,6 @@ Metrics/CyclomaticComplexity:
|
||||
Metrics/PerceivedComplexity:
|
||||
Max: 27
|
||||
|
||||
# Configuration parameters: ExpectMatchingDefinition, CheckDefinitionPathHierarchy, CheckDefinitionPathHierarchyRoots, Regex, IgnoreExecutableScripts, AllowedAcronyms.
|
||||
# CheckDefinitionPathHierarchyRoots: lib, spec, test, src
|
||||
# AllowedAcronyms: CLI, DSL, ACL, API, ASCII, CPU, CSS, DNS, EOF, GUID, HTML, HTTP, HTTPS, ID, IP, JSON, LHS, QPS, RAM, RHS, RPC, SLA, SMTP, SQL, SSH, TCP, TLS, TTL, UDP, UI, UID, UUID, URI, URL, UTF8, VM, XML, XMPP, XSRF, XSS
|
||||
Naming/FileName:
|
||||
Exclude:
|
||||
- 'config/locales/sr-Latn.rb'
|
||||
|
||||
# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
|
||||
# SupportedStyles: snake_case, normalcase, non_integer
|
||||
# AllowedIdentifiers: capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64
|
||||
@ -171,14 +164,6 @@ Naming/VariableNumber:
|
||||
- 'spec/models/domain_block_spec.rb'
|
||||
- 'spec/models/user_spec.rb'
|
||||
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Performance/UnfreezeString:
|
||||
Exclude:
|
||||
- 'app/lib/rss/builder.rb'
|
||||
- 'app/lib/text_formatter.rb'
|
||||
- 'app/validators/status_length_validator.rb'
|
||||
- 'lib/tasks/mastodon.rake'
|
||||
|
||||
RSpec/AnyInstance:
|
||||
Exclude:
|
||||
- 'spec/controllers/activitypub/inboxes_controller_spec.rb'
|
||||
@ -198,41 +183,6 @@ RSpec/AnyInstance:
|
||||
- 'spec/workers/activitypub/delivery_worker_spec.rb'
|
||||
- 'spec/workers/web/push_notification_worker_spec.rb'
|
||||
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
RSpec/EmptyExampleGroup:
|
||||
Exclude:
|
||||
- 'spec/helpers/admin/action_logs_helper_spec.rb'
|
||||
- 'spec/models/account_alias_spec.rb'
|
||||
- 'spec/models/account_deletion_request_spec.rb'
|
||||
- 'spec/models/account_moderation_note_spec.rb'
|
||||
- 'spec/models/announcement_mute_spec.rb'
|
||||
- 'spec/models/announcement_reaction_spec.rb'
|
||||
- 'spec/models/announcement_spec.rb'
|
||||
- 'spec/models/backup_spec.rb'
|
||||
- 'spec/models/conversation_mute_spec.rb'
|
||||
- 'spec/models/custom_filter_keyword_spec.rb'
|
||||
- 'spec/models/custom_filter_spec.rb'
|
||||
- 'spec/models/device_spec.rb'
|
||||
- 'spec/models/encrypted_message_spec.rb'
|
||||
- 'spec/models/featured_tag_spec.rb'
|
||||
- 'spec/models/follow_recommendation_suppression_spec.rb'
|
||||
- 'spec/models/list_account_spec.rb'
|
||||
- 'spec/models/list_spec.rb'
|
||||
- 'spec/models/login_activity_spec.rb'
|
||||
- 'spec/models/mute_spec.rb'
|
||||
- 'spec/models/preview_card_spec.rb'
|
||||
- 'spec/models/preview_card_trend_spec.rb'
|
||||
- 'spec/models/relay_spec.rb'
|
||||
- 'spec/models/scheduled_status_spec.rb'
|
||||
- 'spec/models/status_stat_spec.rb'
|
||||
- 'spec/models/status_trend_spec.rb'
|
||||
- 'spec/models/system_key_spec.rb'
|
||||
- 'spec/models/tag_follow_spec.rb'
|
||||
- 'spec/models/unavailable_domain_spec.rb'
|
||||
- 'spec/models/user_invite_request_spec.rb'
|
||||
- 'spec/models/web/setting_spec.rb'
|
||||
- 'spec/services/unmute_service_spec.rb'
|
||||
|
||||
# Configuration parameters: CountAsOne.
|
||||
RSpec/ExampleLength:
|
||||
Max: 22
|
||||
@ -285,7 +235,6 @@ RSpec/LetSetup:
|
||||
- 'spec/controllers/api/v2/admin/accounts_controller_spec.rb'
|
||||
- 'spec/controllers/api/v2/filters/keywords_controller_spec.rb'
|
||||
- 'spec/controllers/api/v2/filters/statuses_controller_spec.rb'
|
||||
- 'spec/controllers/api/v2/filters_controller_spec.rb'
|
||||
- 'spec/controllers/auth/confirmations_controller_spec.rb'
|
||||
- 'spec/controllers/auth/passwords_controller_spec.rb'
|
||||
- 'spec/controllers/auth/sessions_controller_spec.rb'
|
||||
@ -295,6 +244,7 @@ RSpec/LetSetup:
|
||||
- 'spec/controllers/oauth/tokens_controller_spec.rb'
|
||||
- 'spec/controllers/settings/imports_controller_spec.rb'
|
||||
- 'spec/lib/activitypub/activity/delete_spec.rb'
|
||||
- 'spec/lib/vacuum/applications_vacuum_spec.rb'
|
||||
- 'spec/lib/vacuum/preview_cards_vacuum_spec.rb'
|
||||
- 'spec/models/account_spec.rb'
|
||||
- 'spec/models/account_statuses_cleanup_policy_spec.rb'
|
||||
@ -365,43 +315,6 @@ Rails/ApplicationController:
|
||||
Exclude:
|
||||
- 'app/controllers/health_controller.rb'
|
||||
|
||||
# Configuration parameters: Database, Include.
|
||||
# SupportedDatabases: mysql, postgresql
|
||||
# Include: db/**/*.rb
|
||||
Rails/BulkChangeTable:
|
||||
Exclude:
|
||||
- 'db/migrate/20160222143943_add_profile_fields_to_accounts.rb'
|
||||
- 'db/migrate/20160223162837_add_metadata_to_statuses.rb'
|
||||
- 'db/migrate/20160305115639_add_devise_to_users.rb'
|
||||
- 'db/migrate/20160314164231_add_owner_to_application.rb'
|
||||
- 'db/migrate/20160926213048_remove_owner_from_application.rb'
|
||||
- 'db/migrate/20161003142332_add_confirmable_to_users.rb'
|
||||
- 'db/migrate/20170112154826_migrate_settings.rb'
|
||||
- 'db/migrate/20170127165745_add_devise_two_factor_to_users.rb'
|
||||
- 'db/migrate/20170322143850_change_primary_key_to_bigint_on_statuses.rb'
|
||||
- 'db/migrate/20170330021336_add_counter_caches.rb'
|
||||
- 'db/migrate/20170425202925_add_oembed_to_preview_cards.rb'
|
||||
- 'db/migrate/20170427011934_re_add_owner_to_application.rb'
|
||||
- 'db/migrate/20170520145338_change_language_filter_to_opt_out.rb'
|
||||
- 'db/migrate/20170624134742_add_description_to_session_activations.rb'
|
||||
- 'db/migrate/20170718211102_add_activitypub_to_accounts.rb'
|
||||
- 'db/migrate/20171006142024_add_uri_to_custom_emojis.rb'
|
||||
- 'db/migrate/20180812123222_change_relays_enabled.rb'
|
||||
- 'db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb'
|
||||
- 'db/migrate/20190805123746_add_capabilities_to_tags.rb'
|
||||
- 'db/migrate/20190807135426_add_comments_to_domain_blocks.rb'
|
||||
- 'db/migrate/20190815225426_add_last_status_at_to_tags.rb'
|
||||
- 'db/migrate/20190901035623_add_max_score_to_tags.rb'
|
||||
- 'db/migrate/20200417125749_add_storage_schema_version.rb'
|
||||
- 'db/migrate/20200608113046_add_sign_in_token_to_users.rb'
|
||||
- 'db/migrate/20211112011713_add_language_to_preview_cards.rb'
|
||||
- 'db/migrate/20211231080958_add_category_to_reports.rb'
|
||||
- 'db/migrate/20220202200743_add_trendable_to_accounts.rb'
|
||||
- 'db/migrate/20220224010024_add_ips_to_email_domain_blocks.rb'
|
||||
- 'db/migrate/20220227041951_add_last_used_at_to_oauth_access_tokens.rb'
|
||||
- 'db/migrate/20220303000827_add_ordered_media_attachment_ids_to_status_edits.rb'
|
||||
- 'db/migrate/20220824164433_add_human_identifier_to_admin_action_logs.rb'
|
||||
|
||||
# Configuration parameters: Include.
|
||||
# Include: db/**/*.rb
|
||||
Rails/CreateTableWithTimestamps:
|
||||
@ -676,7 +589,7 @@ Style/FetchEnvVar:
|
||||
- 'app/lib/translation_service.rb'
|
||||
- 'config/environments/development.rb'
|
||||
- 'config/environments/production.rb'
|
||||
- 'config/initializers/2_whitelist_mode.rb'
|
||||
- 'config/initializers/2_limited_federation_mode.rb'
|
||||
- 'config/initializers/blacklists.rb'
|
||||
- 'config/initializers/cache_buster.rb'
|
||||
- 'config/initializers/content_security_policy.rb'
|
||||
@ -838,8 +751,6 @@ Style/RedundantConstantBase:
|
||||
Exclude:
|
||||
- 'config/environments/production.rb'
|
||||
- 'config/initializers/sidekiq.rb'
|
||||
- 'config/locales/sr-Latn.rb'
|
||||
- 'config/locales/sr.rb'
|
||||
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
# Configuration parameters: SafeForConstants.
|
||||
@ -941,9 +852,3 @@ Style/WordArray:
|
||||
- 'config/initializers/cors.rb'
|
||||
- 'spec/controllers/settings/imports_controller_spec.rb'
|
||||
- 'spec/models/form/import_spec.rb'
|
||||
|
||||
# This cop supports safe autocorrection (--autocorrect).
|
||||
# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns.
|
||||
# URISchemes: http, https
|
||||
Layout/LineLength:
|
||||
Max: 701
|
||||
|
262
CHANGELOG.md
262
CHANGELOG.md
@ -2,6 +2,268 @@
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [4.2.0] - UNRELEASED
|
||||
|
||||
The following changelog entries focus on changes visible to users, administrators, client developers or federated software developers, but there has also been a lot of code modernization, refactoring, and tooling work, in particular by [@danielmbrasil](https://github.com/danielmbrasil), [@mjankowski](https://github.com/mjankowski), [@nschonni](https://github.com/nschonni), [@renchap](https://github.com/renchap), and [@takayamaki](https://github.com/takayamaki).
|
||||
|
||||
### Added
|
||||
|
||||
- **Add role badges to the web interface** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25649), [Gargron](https://github.com/mastodon/mastodon/pull/26281))
|
||||
- **Add ability to pick domains to forward reports to using the `forward_to_domains` parameter in `POST /api/v1/reports`** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25866))
|
||||
The `forward_to_domains` REST API parameter is a list of strings. If it is empty or omitted, the previous behavior is maintained.
|
||||
The `forward` parameter still needs to be set for `forward_to_domains` to be taken into account.
|
||||
The forwarded-to domains can only include that of the original author and people being replied to.
|
||||
- **Add forwarding of reported replies to servers being replied to** ([Gargron](https://github.com/mastodon/mastodon/pull/25341), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26189))
|
||||
- Add direct link to the Single-Sign On provider if there is only one sign up method available ([CSDUMMI](https://github.com/mastodon/mastodon/pull/26083), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26368))
|
||||
- **Add webhook templating** ([Gargron](https://github.com/mastodon/mastodon/pull/23289))
|
||||
- **Add webhooks for local `status.created`, `status.updated`, `account.updated` and `report.updated`** ([VyrCossont](https://github.com/mastodon/mastodon/pull/24133), [VyrCossont](https://github.com/mastodon/mastodon/pull/24243), [VyrCossont](https://github.com/mastodon/mastodon/pull/24211))
|
||||
- **Add exclusive lists** ([dariusk](https://github.com/mastodon/mastodon/pull/22048), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25324))
|
||||
- **Add a confirmation screen when suspending a domain** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25144), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25603))
|
||||
- **Add support for importing lists** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25203), [mgmn](https://github.com/mastodon/mastodon/pull/26120), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26372))
|
||||
- **Add optional hCaptcha support** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25019), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25057), [Gargron](https://github.com/mastodon/mastodon/pull/25395), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26388))
|
||||
- **Add lines to threads in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/24549), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24677), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24696), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24711), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24714), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24713), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24715), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24800), [teeerevor](https://github.com/mastodon/mastodon/pull/25706), [renchap](https://github.com/mastodon/mastodon/pull/25807))
|
||||
- **Add new onboarding flow to web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/24619), [Gargron](https://github.com/mastodon/mastodon/pull/24646), [Gargron](https://github.com/mastodon/mastodon/pull/24705), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24872), [ThisIsMissEm](https://github.com/mastodon/mastodon/pull/24883), [Gargron](https://github.com/mastodon/mastodon/pull/24954), [stevenjlm](https://github.com/mastodon/mastodon/pull/24959), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25010), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25275), [Gargron](https://github.com/mastodon/mastodon/pull/25559), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25561))
|
||||
- Add `GET /api/v1/instance/languages` to REST API ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24443))
|
||||
- Add primary key to `preview_cards_statuses` join table ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25243), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26384))
|
||||
- Add client-side timeout on resend confirmation button ([Gargron](https://github.com/mastodon/mastodon/pull/26300))
|
||||
- Add published date and author to news on the explore screen in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26155))
|
||||
- Add `lang` attribute to various UI components ([c960657](https://github.com/mastodon/mastodon/pull/23869), [c960657](https://github.com/mastodon/mastodon/pull/23891), [c960657](https://github.com/mastodon/mastodon/pull/26111), [c960657](https://github.com/mastodon/mastodon/pull/26149))
|
||||
- Add stricter protocol fields validation for accounts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25937))
|
||||
- Add support for Azure blob storage ([mistydemeo](https://github.com/mastodon/mastodon/pull/23607), [mistydemeo](https://github.com/mastodon/mastodon/pull/26080))
|
||||
- Add toast with option to open post after publishing in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25564), [Signez](https://github.com/mastodon/mastodon/pull/25919))
|
||||
- Add canonical link tags in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25715))
|
||||
- Add button to see results for polls in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25726))
|
||||
- Add at-symbol prepended to mention span title ([forsamori](https://github.com/mastodon/mastodon/pull/25684))
|
||||
- Add users index on `unconfirmed_email` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25672), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25702))
|
||||
- Add superapp index on `oauth_applications` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25670))
|
||||
- Add index to backups on `user_id` column ([mjankowski](https://github.com/mastodon/mastodon/pull/25647))
|
||||
- Add onboarding prompt when home feed too slow in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25267), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25556), [Gargron](https://github.com/mastodon/mastodon/pull/25579), [renchap](https://github.com/mastodon/mastodon/pull/25580), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25581), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25617), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25917))
|
||||
- Add `POST /api/v1/conversations/:id/unread` API endpoint to mark a conversation as unread ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25509))
|
||||
- Add `translate="no"` to outgoing mentions and links ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25524))
|
||||
- Add unsubscribe link and headers to e-mails ([Gargron](https://github.com/mastodon/mastodon/pull/25378), [c960657](https://github.com/mastodon/mastodon/pull/26085))
|
||||
- Add logging of websocket send errors ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/25280))
|
||||
- Add time zone preference ([Gargron](https://github.com/mastodon/mastodon/pull/25342), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26025))
|
||||
- Add `legal` as report category ([Gargron](https://github.com/mastodon/mastodon/pull/23941), [renchap](https://github.com/mastodon/mastodon/pull/25400))
|
||||
- Add `data-nosnippet` so Google doesn't use trending posts in snippets for `/` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25279))
|
||||
- Add card with who invited you to join when displaying rules on sign-up ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23475))
|
||||
- Add missing primary keys to `accounts_tags` and `statuses_tags` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25210))
|
||||
- Add support for custom sign-up URLs ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25014), [renchap](https://github.com/mastodon/mastodon/pull/25108), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25190), [mgmn](https://github.com/mastodon/mastodon/pull/25531))
|
||||
This is set using `SSO_ACCOUNT_SIGN_UP` and reflected in the REST API by adding `registrations.sign_up_url` to the `/api/v2/instance` endpoint.
|
||||
- Add polling and automatic redirection to `/start` on email confirmation ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25013))
|
||||
- Add ability to block sign-ups from IP using the CLI ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24870))
|
||||
- Add ALT badges to media that has alternative text in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24782), [c960657](https://github.com/mastodon/mastodon/pull/26166)
|
||||
- Add ability to include accounts with pending follow requests in lists ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/19727), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24810))
|
||||
- Add trend management to admin API ([rrgeorge](https://github.com/mastodon/mastodon/pull/24257))
|
||||
- `POST /api/v1/admin/trends/statuses/:id/approve`
|
||||
- `POST /api/v1/admin/trends/statuses/:id/reject`
|
||||
- `POST /api/v1/admin/trends/links/:id/approve`
|
||||
- `POST /api/v1/admin/trends/links/:id/reject`
|
||||
- `POST /api/v1/admin/trends/tags/:id/approve`
|
||||
- `POST /api/v1/admin/trends/tags/:id/reject`
|
||||
- `GET /api/v1/admin/trends/links/publishers`
|
||||
- `POST /api/v1/admin/trends/links/publishers/:id/approve`
|
||||
- `POST /api/v1/admin/trends/links/publishers/:id/reject`
|
||||
- Add user handle to notification mail recipient address ([HeitorMC](https://github.com/mastodon/mastodon/pull/24240))
|
||||
- Add progress indicator to sign-up flow ([Gargron](https://github.com/mastodon/mastodon/pull/24545))
|
||||
- Add client-side validation for taken username in sign-up form ([Gargron](https://github.com/mastodon/mastodon/pull/24546))
|
||||
- Add `--approve` option to `tootctl accounts create` ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24533))
|
||||
- Add “In Memoriam” banner back to profiles ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23591), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/23614))
|
||||
This adds the `memorial` attribute to the `Account` REST API entity.
|
||||
- Add colour to follow button when hashtag is being followed ([c960657](https://github.com/mastodon/mastodon/pull/24361))
|
||||
- Add further explanations to the profile link verification instructions ([drzax](https://github.com/mastodon/mastodon/pull/19723))
|
||||
- Add a link to Identity provider's account settings from the account settings ([CSDUMMI](https://github.com/mastodon/mastodon/pull/24100), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24628))
|
||||
- Add support for streaming server to connect to postgres with self-signed certs through the `sslmode` URL parameter ([ramuuns](https://github.com/mastodon/mastodon/pull/21431))
|
||||
- Add support for specifying S3 storage classes through the `S3_STORAGE_CLASS` environment variable ([hyl](https://github.com/mastodon/mastodon/pull/22480))
|
||||
- Add support for incoming rich text ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23913))
|
||||
- Add support for Ruby 3.2 ([tenderlove](https://github.com/mastodon/mastodon/pull/22928), [casperisfine](https://github.com/mastodon/mastodon/pull/24142), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24202))
|
||||
- Add API parameter to safeguard unexpected mentions in new posts ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/18350))
|
||||
|
||||
### Changed
|
||||
|
||||
- **Change reblogs to be excluded from "Posts and replies" tab in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/26302))
|
||||
- **Change interaction modal in web interface** ([Gargron, ClearlyClaire](https://github.com/mastodon/mastodon/pull/26075), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26269), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26268), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26267))
|
||||
- **Change design of link previews in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/26136), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26151), [Gargron](https://github.com/mastodon/mastodon/pull/26153), [Gargron](https://github.com/mastodon/mastodon/pull/26250), [Gargron](https://github.com/mastodon/mastodon/pull/26287), [Gargron](https://github.com/mastodon/mastodon/pull/26286), [c960657](https://github.com/mastodon/mastodon/pull/26184))
|
||||
- **Change "direct message" nomenclature to "private mention" in web UI** ([Gargron](https://github.com/mastodon/mastodon/pull/24248))
|
||||
- **Change translation feature to cover Content Warnings, poll options and media descriptions** ([c960657](https://github.com/mastodon/mastodon/pull/24175), [S-H-GAMELINKS](https://github.com/mastodon/mastodon/pull/25251), [c960657](https://github.com/mastodon/mastodon/pull/26168))
|
||||
- **Change account search to match by text when opted-in** ([jsgoldstein](https://github.com/mastodon/mastodon/pull/25599), [Gargron](https://github.com/mastodon/mastodon/pull/26378))
|
||||
- **Change import feature to be clearer, less error-prone and more reliable** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/21054), [mgmn](https://github.com/mastodon/mastodon/pull/24874))
|
||||
- **Change local and federated timelines to be in a single “Live feeds” column** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25641), [Gargron](https://github.com/mastodon/mastodon/pull/25683), [mgmn](https://github.com/mastodon/mastodon/pull/25694), [Plastikmensch](https://github.com/mastodon/mastodon/pull/26247))
|
||||
- **Change user archive export to be faster and more reliable, and export `.zip` archives instead of `.tar.gz` ones** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23360), [TheEssem](https://github.com/mastodon/mastodon/pull/25034))
|
||||
- **Change `mastodon-streaming` systemd unit files to be templated** ([e-nomem](https://github.com/mastodon/mastodon/pull/24751))
|
||||
- **Change `statsd` integration to disable sidekiq metrics by default** ([mjankowski](https://github.com/mastodon/mastodon/pull/25265), [mjankowski](https://github.com/mastodon/mastodon/pull/25336), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26310))
|
||||
This deprecates `statsd` support and disables the sidekiq integration unless `STATSD_SIDEKIQ` is set to `true`.
|
||||
This is because the `nsa` gem is unmaintained, and its sidekiq integration is known to add very significant overhead.
|
||||
Later versions of Mastodon will have other ways to get the same metrics.
|
||||
- **Change replica support to native Rails adapter** ([krainboltgreene](https://github.com/mastodon/mastodon/pull/25693), [Gargron](https://github.com/mastodon/mastodon/pull/25849), [Gargron](https://github.com/mastodon/mastodon/pull/25874), [Gargron](https://github.com/mastodon/mastodon/pull/25851), [Gargron](https://github.com/mastodon/mastodon/pull/25977), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26074), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26326), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26386))
|
||||
This is a breaking change, dropping `makara` support, and requiring you to update your database configuration if you are using replicas.
|
||||
To tell Mastodon to use a read replica, you can either set the `REPLICA_DB_NAME` environment variable (along with `REPLICA_DB_USER`, `REPLICA_DB_PASS`, `REPLICA_DB_HOST`, and `REPLICA_DB_PORT`, if they differ from the primary database), or the `REPLICA_DATABASE_URL` environment variable if your configuration is based on `DATABASE_URL`.
|
||||
- Change header of hashtag timelines in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26362))
|
||||
- Change streaming `/metrics` to include additional metrics ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/26299))
|
||||
- Change indexing frequency from 5 minutes to 1 minute, add locks to schedulers ([Gargron](https://github.com/mastodon/mastodon/pull/26304))
|
||||
- Change column link to add a better keyboard focus indicator ([teeerevor](https://github.com/mastodon/mastodon/pull/26278))
|
||||
- Change poll form element colors to fit with the rest of the ui ([teeerevor](https://github.com/mastodon/mastodon/pull/26139), [teeerevor](https://github.com/mastodon/mastodon/pull/26162), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26164))
|
||||
- Change 'favourite' to 'favorite' for American English ([marekr](https://github.com/mastodon/mastodon/pull/24667), [gunchleoc](https://github.com/mastodon/mastodon/pull/26009), [nabijaczleweli](https://github.com/mastodon/mastodon/pull/26109))
|
||||
- Change ActivityStreams representation of suspended accounts to not use a blank `name` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25276))
|
||||
- Change focus UI for keyboard only input ([teeerevor](https://github.com/mastodon/mastodon/pull/25935), [Gargron](https://github.com/mastodon/mastodon/pull/26125))
|
||||
- Change thread view to scroll to the selected post rather than the post being replied to ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24685))
|
||||
- Change links in multi-column mode so tabs are open in single-column mode ([Signez](https://github.com/mastodon/mastodon/pull/25893), [Signez](https://github.com/mastodon/mastodon/pull/26070), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25973))
|
||||
- Change searching with `#` to include account index ([jsgoldstein](https://github.com/mastodon/mastodon/pull/25638))
|
||||
- Change label and design of sensitive and unavailable media in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25712), [Gargron](https://github.com/mastodon/mastodon/pull/26135), [Gargron](https://github.com/mastodon/mastodon/pull/26330))
|
||||
- Change button colors to increase hover/focus contrast and consistency ([teeerevor](https://github.com/mastodon/mastodon/pull/25677), [Gargron](https://github.com/mastodon/mastodon/pull/25679))
|
||||
- Change dropdown icon above compose form from ellipsis to bars in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25661))
|
||||
- Change header backgrounds to use fewer different colors in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25577))
|
||||
- Change files to be deleted in batches instead of one-by-one ([Gargron](https://github.com/mastodon/mastodon/pull/23302), [S-H-GAMELINKS](https://github.com/mastodon/mastodon/pull/25586), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25587))
|
||||
- Change emoji picker icon ([iparr](https://github.com/mastodon/mastodon/pull/25479))
|
||||
- Change edit profile page ([Gargron](https://github.com/mastodon/mastodon/pull/25413))
|
||||
- Change "bot" label to "automated" ([Gargron](https://github.com/mastodon/mastodon/pull/25356))
|
||||
- Change design of dropdowns in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25107))
|
||||
- Change wording of “Content cache retention period” setting to highlight destructive implications ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23261))
|
||||
- Change autolinking to allow carets in URL search params ([renchap](https://github.com/mastodon/mastodon/pull/25216))
|
||||
- Change share action from being in action bar to being in dropdown in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25105))
|
||||
- Change remote report processing to accept reports with long comments, but truncate them ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/25028))
|
||||
- Change sessions to be ordered from most-recent to least-recently updated ([frankieroberto](https://github.com/mastodon/mastodon/pull/25005))
|
||||
- Change vacuum scheduler to also delete expired tokens and unused application records ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24868), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24871))
|
||||
- Change "Sign in" to "Login" ([Gargron](https://github.com/mastodon/mastodon/pull/24942))
|
||||
- Change domain suspensions to also be checked before trying to fetch unknown remote resources ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24535))
|
||||
- Change media components to use aspect-ratio rather than compute height themselves ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24686), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24943))
|
||||
- Change logo version in header based on screen size in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24707))
|
||||
- Change label from "For you" to "People" on explore screen in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24706))
|
||||
- Change logged-out WebUI HTML pages to be cached for a few seconds ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24708))
|
||||
- Change unauthenticated responses to be cached in REST API ([Gargron](https://github.com/mastodon/mastodon/pull/24348), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24662), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24665))
|
||||
- Change HTTP caching logic ([Gargron](https://github.com/mastodon/mastodon/pull/24347), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24604))
|
||||
- Change hashtags and mentions in bios to open in-app in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24643))
|
||||
- Change styling of the recommended accounts to allow bio to be more visible ([chike00](https://github.com/mastodon/mastodon/pull/24480))
|
||||
- Change account search in moderation interface to allow searching by username including the leading `@` ([HeitorMC](https://github.com/mastodon/mastodon/pull/24242))
|
||||
- Change all components to use the same error page in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24512))
|
||||
- Change search pop-out in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24305))
|
||||
- Change user settings to be stored in a more optimal way ([Gargron](https://github.com/mastodon/mastodon/pull/23630), [c960657](https://github.com/mastodon/mastodon/pull/24321), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24453), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24460), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24558), [Gargron](https://github.com/mastodon/mastodon/pull/24761), [Gargron](https://github.com/mastodon/mastodon/pull/24783), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25508), [jsgoldstein](https://github.com/mastodon/mastodon/pull/25340))
|
||||
- Change media upload limits and remove client-side resizing ([Gargron](https://github.com/mastodon/mastodon/pull/23726))
|
||||
- Change design of account rows in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24247), [Gargron](https://github.com/mastodon/mastodon/pull/24343), [Gargron](https://github.com/mastodon/mastodon/pull/24956), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25131))
|
||||
- Change log-out to use Single Logout when using external log-in through OIDC ([CSDUMMI](https://github.com/mastodon/mastodon/pull/24020))
|
||||
- Change sidekiq-bulk's batch size from 10,000 to 1,000 jobs in one Redis call ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24034))
|
||||
- Change translation to only be offered for supported languages ([c960657](https://github.com/mastodon/mastodon/pull/23879), [c960657](https://github.com/mastodon/mastodon/pull/24037))
|
||||
This adds the `/api/v1/instance/translation_languages` REST API endpoint that returns an object with the supported translation language pairs in the form:
|
||||
```json
|
||||
{
|
||||
"fr": ["en", "de"]
|
||||
}
|
||||
```
|
||||
(where `fr` is a supported source language and `en` and `de` or supported output language when translating a `fr` string)
|
||||
- Change compose form checkbox to native input with `appearance: none` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/22949))
|
||||
- Change posts' clickable area to be larger ([c960657](https://github.com/mastodon/mastodon/pull/23621))
|
||||
- Change `followed_by` link to `location=all` if account is local on /admin/accounts/:id page ([tribela](https://github.com/mastodon/mastodon/pull/23467))
|
||||
|
||||
### Removed
|
||||
|
||||
- **Remove support for Node.js 14** ([renchap](https://github.com/mastodon/mastodon/pull/25198))
|
||||
- **Remove support for Ruby 2.7** ([nschonni](https://github.com/mastodon/mastodon/pull/24237))
|
||||
- **Remove clustering from streaming API** ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/24655))
|
||||
- **Remove anonymous access to the streaming API** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23989))
|
||||
- Remove 16:9 cropping from web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26132))
|
||||
- Remove back button from bookmarks, favourites and lists screens in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26126))
|
||||
- Remove display name input from sign-up form ([Gargron](https://github.com/mastodon/mastodon/pull/24704))
|
||||
- Remove `tai` locale ([c960657](https://github.com/mastodon/mastodon/pull/23880))
|
||||
- Remove empty Kushubian (csb) local files ([nschonni](https://github.com/mastodon/mastodon/pull/24151))
|
||||
- Remove `Permissions-Policy` header from all responses ([Gargron](https://github.com/mastodon/mastodon/pull/24124))
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Fix filters not being applying in the explore page** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25887))
|
||||
- **Fix being unable to load past a full page of filtered posts in Home timeline** ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24930))
|
||||
- **Fix log-in flow when involving both OAuth and external authentication** ([CSDUMMI](https://github.com/mastodon/mastodon/pull/24073))
|
||||
- **Fix broken links in account gallery** ([c960657](https://github.com/mastodon/mastodon/pull/24218))
|
||||
- Fix adding column with default value taking longer on Postgres >= 11 ([Gargron](https://github.com/mastodon/mastodon/pull/26375))
|
||||
- Fix light theme select option for hashtags ([teeerevor](https://github.com/mastodon/mastodon/pull/26311))
|
||||
- Fix AVIF attachments ([c960657](https://github.com/mastodon/mastodon/pull/26264))
|
||||
- Fix incorrect URL normalization when fetching remote resources ([c960657](https://github.com/mastodon/mastodon/pull/26219), [c960657](https://github.com/mastodon/mastodon/pull/26285))
|
||||
- Fix being unable to filter posts for individual Chinese languages ([gunchleoc](https://github.com/mastodon/mastodon/pull/26066))
|
||||
- Fix preview card sometimes linking to 4xx error pages ([c960657](https://github.com/mastodon/mastodon/pull/26200))
|
||||
- Fix emoji picker button scrolling with textarea content in single-column view ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25304))
|
||||
- Fix missing border on error screen in light theme in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/26152))
|
||||
- Fix UI overlap with the loupe icon in the Explore Tab ([gol-cha](https://github.com/mastodon/mastodon/pull/26113))
|
||||
- Fix unexpected redirection to `/explore` after sign-in ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26143))
|
||||
- Fix `/api/v1/statuses/:id/unfavourite` and `/api/v1/statuses/:id/unreblog` returning non-updated counts ([c960657](https://github.com/mastodon/mastodon/pull/24365))
|
||||
- Fix clicking the “Back” button sometimes leading out of Mastodon ([c960657](https://github.com/mastodon/mastodon/pull/23953), [CSFlorin](https://github.com/mastodon/mastodon/pull/24835), [S-H-GAMELINKS](https://github.com/mastodon/mastodon/pull/24867), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25281))
|
||||
- Fix processing of `null` ActivityPub activities ([tribela](https://github.com/mastodon/mastodon/pull/26021))
|
||||
- Fix hashtag posts not being removed from home feed on hashtag unfollow ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26028))
|
||||
- Fix for "follows you" indicator in light web UI not readable ([vmstan](https://github.com/mastodon/mastodon/pull/25993))
|
||||
- Fix incorrect line break between icon and number of reposts & favourites ([edent](https://github.com/mastodon/mastodon/pull/26004))
|
||||
- Fix sounds not being loaded from assets host ([Signez](https://github.com/mastodon/mastodon/pull/25931))
|
||||
- Fix buttons showing inconsistent styles ([teeerevor](https://github.com/mastodon/mastodon/pull/25903), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25965), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/26341))
|
||||
- Fix trend calculation working on too many items at a time ([Gargron](https://github.com/mastodon/mastodon/pull/25835))
|
||||
- Fix dropdowns being disabled for logged out users in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25714), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25964))
|
||||
- Fix explore page being inaccessible when opted-out of trends in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25716))
|
||||
- Fix re-activated accounts possibly getting deleted by `AccountDeletionWorker` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25711))
|
||||
- Fix `/api/v2/search` not working with following query param ([danielmbrasil](https://github.com/mastodon/mastodon/pull/25681))
|
||||
- Fix inefficient query when requesting a new confirmation email from a logged-in account ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25669))
|
||||
- Fix unnecessary concurrent calls to `/api/*/instance` in web UI ([mgmn](https://github.com/mastodon/mastodon/pull/25663))
|
||||
- Fix resolving local URL for remote content ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25637))
|
||||
- Fix search not being easily findable on smaller screens in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25576), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/25631))
|
||||
- Fix j/k keyboard shortcuts on some status lists ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25554))
|
||||
- Fix missing validation on `default_privacy` setting ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25513))
|
||||
- Fix incorrect pagination headers in `/api/v2/admin/accounts` ([danielmbrasil](https://github.com/mastodon/mastodon/pull/25477))
|
||||
- Fix non-interactive upload container being given a `button` role and tabIndex ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25462))
|
||||
- Fix always redirecting to onboarding in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/25396))
|
||||
- Fix inconsistent use of middle dot (·) instead of bullet (•) to separate items ([j-f1](https://github.com/mastodon/mastodon/pull/25248))
|
||||
- Fix spacing of middle dots in the detailed status meta section ([j-f1](https://github.com/mastodon/mastodon/pull/25247))
|
||||
- Fix prev/next buttons color in media viewer ([renchap](https://github.com/mastodon/mastodon/pull/25231))
|
||||
- Fix email addresses not being properly updated in `tootctl maintenance fix-duplicates` ([mjankowski](https://github.com/mastodon/mastodon/pull/25118))
|
||||
- Fix unicode surrogate pairs sometimes being broken in page title ([eai04191](https://github.com/mastodon/mastodon/pull/25148))
|
||||
- Fix various inefficient queries against account domains ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25126))
|
||||
- Fix video player offering to expand in a lightbox when it's in an `iframe` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25067))
|
||||
- Fix post embed previews ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25071))
|
||||
- Fix inadequate error handling in several API controllers when given invalid parameters ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24947), [danielmbrasil](https://github.com/mastodon/mastodon/pull/24958), [danielmbrasil](https://github.com/mastodon/mastodon/pull/25063), [danielmbrasil](https://github.com/mastodon/mastodon/pull/25072), [danielmbrasil](https://github.com/mastodon/mastodon/pull/25386), [danielmbrasil](https://github.com/mastodon/mastodon/pull/25595))
|
||||
- Fix uncaught `ActiveRecord::StatementInvalid` in Mastodon::IpBlocksCLI ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24861))
|
||||
- Fix various edge cases with local moves ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24812))
|
||||
- Fix `tootctl accounts cull` crashing when encountering a domain resolving to a private address ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23378))
|
||||
- Fix `tootctl accounts approve --number N` not aproving the N earliest registrations ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24605))
|
||||
- Fix being unable to clear media description when editing posts ([c960657](https://github.com/mastodon/mastodon/pull/24720))
|
||||
- Fix unavailable translations not falling back to English ([mgmn](https://github.com/mastodon/mastodon/pull/24727))
|
||||
- Fix anonymous visitors getting a session cookie on first visit ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24584), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24650), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24664))
|
||||
- Fix cutting off first letter of hashtag links sometimes in web UI ([Gargron](https://github.com/mastodon/mastodon/pull/24623))
|
||||
- Fix crash in `tootctl accounts create --reattach --force` ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/24557), [danielmbrasil](https://github.com/mastodon/mastodon/pull/24680))
|
||||
- Fix characters being emojified even when using Variation Selector 15 (text) ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/20949), [ClearlyClaire](https://github.com/mastodon/mastodon/pull/24615))
|
||||
- Fix uncaught ActiveRecord::StatementInvalid exception in `Mastodon::AccountsCLI#approve` ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24590))
|
||||
- Fix email confirmation skip option in `tootctl accounts modify USERNAME --email EMAIL --confirm` ([danielmbrasil](https://github.com/mastodon/mastodon/pull/24578))
|
||||
- Fix tooltip for dates without time ([c960657](https://github.com/mastodon/mastodon/pull/24244))
|
||||
- Fix missing loading spinner and loading more on scroll in Private Mentions column ([c960657](https://github.com/mastodon/mastodon/pull/24446))
|
||||
- Fix account header image missing from `/settings/profile` on narrow screens ([c960657](https://github.com/mastodon/mastodon/pull/24433))
|
||||
- Fix height of announcements not being updated when using reduced animations ([c960657](https://github.com/mastodon/mastodon/pull/24354))
|
||||
- Fix inconsistent radius in advanced interface drawer ([thislight](https://github.com/mastodon/mastodon/pull/24407))
|
||||
- Fix loading more trending posts on scroll in the advanced interface ([OmmyZhang](https://github.com/mastodon/mastodon/pull/24314))
|
||||
- Fix poll ending notification for edited polls ([c960657](https://github.com/mastodon/mastodon/pull/24311))
|
||||
- Fix max width of media in `/about` and `/privacy-policy` ([mgmn](https://github.com/mastodon/mastodon/pull/24180))
|
||||
- Fix streaming API not being usable without `DATABASE_URL` ([Gargron](https://github.com/mastodon/mastodon/pull/23960))
|
||||
- Fix external authentication not running onboarding code for new users ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/23458))
|
||||
|
||||
## [4.1.6] - 2023-07-31
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix memory leak in streaming server ([ThisIsMissEm](https://github.com/mastodon/mastodon/pull/26228))
|
||||
- Fix wrong filters sometimes applying in streaming ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26159), [ThisIsMissEm](https://github.com/mastodon/mastodon/pull/26213), [renchap](https://github.com/mastodon/mastodon/pull/26233))
|
||||
- Fix incorrect connect timeout in outgoing requests ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26116))
|
||||
|
||||
## [4.1.5] - 2023-07-21
|
||||
|
||||
### Added
|
||||
|
||||
- Add check preventing Sidekiq workers from running with Makara configured ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25850))
|
||||
|
||||
### Changed
|
||||
|
||||
- Change request timeout handling to use a longer deadline ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26055))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix moderation interface for remote instances with a .zip TLD ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25885))
|
||||
- Fix remote accounts being possibly persisted to database with incomplete protocol values ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/25886))
|
||||
- Fix trending publishers table not rendering correctly on narrow screens ([vmstan](https://github.com/mastodon/mastodon/pull/25945))
|
||||
|
||||
### Security
|
||||
|
||||
- Fix CSP headers being unintentionally wide ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/26105))
|
||||
|
||||
## [4.1.4] - 2023-07-07
|
||||
|
||||
### Fixed
|
||||
|
29
Gemfile
29
Gemfile
@ -18,6 +18,7 @@ gem 'aws-sdk-s3', '~> 1.123', require: false
|
||||
gem 'fog-core', '<= 2.4.0'
|
||||
gem 'fog-openstack', '~> 0.3', require: false
|
||||
gem 'kt-paperclip', '~> 7.2'
|
||||
gem 'md-paperclip-azure', '~> 2.2', require: false
|
||||
gem 'blurhash', '~> 0.1'
|
||||
|
||||
gem 'active_model_serializers', '~> 0.10'
|
||||
@ -34,11 +35,14 @@ group :pam_authentication, optional: true do
|
||||
end
|
||||
|
||||
gem 'net-ldap', '~> 0.18'
|
||||
gem 'omniauth-cas', '~> 2.0'
|
||||
gem 'omniauth-saml', '~> 1.10'
|
||||
|
||||
# TODO: Point back at released omniauth-cas gem when PR merged
|
||||
# https://github.com/dlindahl/omniauth-cas/pull/68
|
||||
gem 'omniauth-cas', github: 'stanhu/omniauth-cas', ref: '4211e6d05941b4a981f9a36b49ec166cecd0e271'
|
||||
gem 'omniauth-saml', '~> 2.0'
|
||||
gem 'omniauth_openid_connect', '~> 0.6.1'
|
||||
gem 'omniauth', '~> 1.9'
|
||||
gem 'omniauth-rails_csrf_protection', '~> 0.1'
|
||||
gem 'omniauth', '~> 2.0'
|
||||
gem 'omniauth-rails_csrf_protection', '~> 1.0'
|
||||
|
||||
gem 'color_diff', '~> 0.1'
|
||||
gem 'discard', '~> 1.2'
|
||||
@ -55,8 +59,9 @@ gem 'httplog', '~> 1.6.2'
|
||||
gem 'idn-ruby', require: 'idn'
|
||||
gem 'kaminari', '~> 1.2'
|
||||
gem 'link_header', '~> 0.0'
|
||||
gem 'mime-types', '~> 3.4.1', require: 'mime/types/columnar'
|
||||
gem 'mime-types', '~> 3.5.0', require: 'mime/types/columnar'
|
||||
gem 'nokogiri', '~> 1.15'
|
||||
gem 'nsa', github: 'jhawthorn/nsa', ref: 'e020fcc3a54d993ab45b7194d89ab720296c111b'
|
||||
gem 'oj', '~> 3.14'
|
||||
gem 'ox', '~> 2.14'
|
||||
gem 'parslet'
|
||||
@ -98,9 +103,6 @@ gem 'rdf-normalize', '~> 0.5'
|
||||
gem 'private_address_check', '~> 0.5'
|
||||
|
||||
group :test do
|
||||
# RSpec runner for rails
|
||||
gem 'rspec-rails', '~> 6.0'
|
||||
|
||||
# Used to split testing into chunks in CI
|
||||
gem 'rspec_chunked', '~> 0.6'
|
||||
|
||||
@ -112,6 +114,10 @@ group :test do
|
||||
|
||||
# Browser integration testing
|
||||
gem 'capybara', '~> 3.39'
|
||||
gem 'selenium-webdriver'
|
||||
|
||||
# Used to reset the database between system tests
|
||||
gem 'database_cleaner-active_record'
|
||||
|
||||
# Used to mock environment variables
|
||||
gem 'climate_control', '~> 0.2'
|
||||
@ -172,10 +178,17 @@ group :development do
|
||||
|
||||
# Validate missing i18n keys
|
||||
gem 'i18n-tasks', '~> 1.0', require: false
|
||||
end
|
||||
|
||||
group :development, :test do
|
||||
# Profiling tools
|
||||
gem 'memory_profiler', require: false
|
||||
gem 'ruby-prof', require: false
|
||||
gem 'stackprof', require: false
|
||||
gem 'test-prof'
|
||||
|
||||
# RSpec runner for rails
|
||||
gem 'rspec-rails', '~> 6.0'
|
||||
end
|
||||
|
||||
group :production do
|
||||
|
237
Gemfile.lock
237
Gemfile.lock
@ -7,6 +7,17 @@ GIT
|
||||
hkdf (~> 0.2)
|
||||
jwt (~> 2.0)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/jhawthorn/nsa.git
|
||||
revision: e020fcc3a54d993ab45b7194d89ab720296c111b
|
||||
ref: e020fcc3a54d993ab45b7194d89ab720296c111b
|
||||
specs:
|
||||
nsa (0.2.8)
|
||||
activesupport (>= 4.2, < 7.2)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
sidekiq (>= 3.5)
|
||||
statsd-ruby (~> 1.4, >= 1.4.0)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/mastodon/rails-settings-cached.git
|
||||
revision: 86328ef0bd04ce21cc0504ff5e334591e8c2ccab
|
||||
@ -15,50 +26,60 @@ GIT
|
||||
rails-settings-cached (0.6.6)
|
||||
rails (>= 4.2.0)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/stanhu/omniauth-cas.git
|
||||
revision: 4211e6d05941b4a981f9a36b49ec166cecd0e271
|
||||
ref: 4211e6d05941b4a981f9a36b49ec166cecd0e271
|
||||
specs:
|
||||
omniauth-cas (2.0.0)
|
||||
addressable (~> 2.3)
|
||||
nokogiri (~> 1.5)
|
||||
omniauth (>= 1.2, < 3)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (7.0.6)
|
||||
actionpack (= 7.0.6)
|
||||
activesupport (= 7.0.6)
|
||||
actioncable (7.0.7)
|
||||
actionpack (= 7.0.7)
|
||||
activesupport (= 7.0.7)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (7.0.6)
|
||||
actionpack (= 7.0.6)
|
||||
activejob (= 7.0.6)
|
||||
activerecord (= 7.0.6)
|
||||
activestorage (= 7.0.6)
|
||||
activesupport (= 7.0.6)
|
||||
actionmailbox (7.0.7)
|
||||
actionpack (= 7.0.7)
|
||||
activejob (= 7.0.7)
|
||||
activerecord (= 7.0.7)
|
||||
activestorage (= 7.0.7)
|
||||
activesupport (= 7.0.7)
|
||||
mail (>= 2.7.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
actionmailer (7.0.6)
|
||||
actionpack (= 7.0.6)
|
||||
actionview (= 7.0.6)
|
||||
activejob (= 7.0.6)
|
||||
activesupport (= 7.0.6)
|
||||
actionmailer (7.0.7)
|
||||
actionpack (= 7.0.7)
|
||||
actionview (= 7.0.7)
|
||||
activejob (= 7.0.7)
|
||||
activesupport (= 7.0.7)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (7.0.6)
|
||||
actionview (= 7.0.6)
|
||||
activesupport (= 7.0.6)
|
||||
actionpack (7.0.7)
|
||||
actionview (= 7.0.7)
|
||||
activesupport (= 7.0.7)
|
||||
rack (~> 2.0, >= 2.2.4)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (7.0.6)
|
||||
actionpack (= 7.0.6)
|
||||
activerecord (= 7.0.6)
|
||||
activestorage (= 7.0.6)
|
||||
activesupport (= 7.0.6)
|
||||
actiontext (7.0.7)
|
||||
actionpack (= 7.0.7)
|
||||
activerecord (= 7.0.7)
|
||||
activestorage (= 7.0.7)
|
||||
activesupport (= 7.0.7)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (7.0.6)
|
||||
activesupport (= 7.0.6)
|
||||
actionview (7.0.7)
|
||||
activesupport (= 7.0.7)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
@ -68,22 +89,22 @@ GEM
|
||||
activemodel (>= 4.1, < 7.1)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
activejob (7.0.6)
|
||||
activesupport (= 7.0.6)
|
||||
activejob (7.0.7)
|
||||
activesupport (= 7.0.7)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (7.0.6)
|
||||
activesupport (= 7.0.6)
|
||||
activerecord (7.0.6)
|
||||
activemodel (= 7.0.6)
|
||||
activesupport (= 7.0.6)
|
||||
activestorage (7.0.6)
|
||||
actionpack (= 7.0.6)
|
||||
activejob (= 7.0.6)
|
||||
activerecord (= 7.0.6)
|
||||
activesupport (= 7.0.6)
|
||||
activemodel (7.0.7)
|
||||
activesupport (= 7.0.7)
|
||||
activerecord (7.0.7)
|
||||
activemodel (= 7.0.7)
|
||||
activesupport (= 7.0.7)
|
||||
activestorage (7.0.7)
|
||||
actionpack (= 7.0.7)
|
||||
activejob (= 7.0.7)
|
||||
activerecord (= 7.0.7)
|
||||
activesupport (= 7.0.7)
|
||||
marcel (~> 1.0)
|
||||
mini_mime (>= 1.1.0)
|
||||
activesupport (7.0.6)
|
||||
activesupport (7.0.7)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
@ -103,8 +124,8 @@ GEM
|
||||
attr_required (1.0.1)
|
||||
awrence (1.2.1)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.786.0)
|
||||
aws-sdk-core (3.178.0)
|
||||
aws-partitions (1.793.0)
|
||||
aws-sdk-core (3.180.3)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
aws-partitions (~> 1, >= 1.651.0)
|
||||
aws-sigv4 (~> 1.5)
|
||||
@ -112,12 +133,20 @@ GEM
|
||||
aws-sdk-kms (1.71.0)
|
||||
aws-sdk-core (~> 3, >= 3.177.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.130.0)
|
||||
aws-sdk-core (~> 3, >= 3.177.0)
|
||||
aws-sdk-s3 (1.132.1)
|
||||
aws-sdk-core (~> 3, >= 3.179.0)
|
||||
aws-sdk-kms (~> 1)
|
||||
aws-sigv4 (~> 1.6)
|
||||
aws-sigv4 (1.6.0)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
azure-storage-blob (2.0.3)
|
||||
azure-storage-common (~> 2.0)
|
||||
nokogiri (~> 1, >= 1.10.8)
|
||||
azure-storage-common (2.0.4)
|
||||
faraday (~> 1.0)
|
||||
faraday_middleware (~> 1.0, >= 1.0.0.rc1)
|
||||
net-http-persistent (~> 4.0)
|
||||
nokogiri (~> 1, >= 1.10.8)
|
||||
bcrypt (3.1.18)
|
||||
better_errors (2.10.1)
|
||||
erubi (>= 1.0.0)
|
||||
@ -136,7 +165,7 @@ GEM
|
||||
blurhash (0.1.7)
|
||||
bootsnap (1.16.0)
|
||||
msgpack (~> 1.2)
|
||||
brakeman (6.0.0)
|
||||
brakeman (6.0.1)
|
||||
browser (5.3.1)
|
||||
brpoplpush-redis_script (0.1.3)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.5)
|
||||
@ -191,6 +220,10 @@ GEM
|
||||
crass (1.0.6)
|
||||
css_parser (1.14.0)
|
||||
addressable
|
||||
database_cleaner-active_record (2.1.0)
|
||||
activerecord (>= 5.a)
|
||||
database_cleaner-core (~> 2.0.0)
|
||||
database_cleaner-core (2.0.1)
|
||||
date (3.3.3)
|
||||
debug_inspector (1.1.0)
|
||||
devise (4.9.2)
|
||||
@ -261,6 +294,8 @@ GEM
|
||||
faraday-patron (1.0.0)
|
||||
faraday-rack (1.0.0)
|
||||
faraday-retry (1.0.3)
|
||||
faraday_middleware (1.2.0)
|
||||
faraday (~> 1.0)
|
||||
fast_blank (1.0.1)
|
||||
fastimage (2.2.7)
|
||||
ffi (1.15.5)
|
||||
@ -297,7 +332,7 @@ GEM
|
||||
activesupport (>= 5.1)
|
||||
haml (>= 4.0.6)
|
||||
railties (>= 5.1)
|
||||
haml_lint (0.48.0)
|
||||
haml_lint (0.49.3)
|
||||
haml (>= 4.0, < 6.2)
|
||||
parallel (~> 1.10)
|
||||
rainbow
|
||||
@ -393,7 +428,7 @@ GEM
|
||||
llhttp-ffi (0.4.0)
|
||||
ffi-compiler (~> 1.0)
|
||||
rake (~> 13.0)
|
||||
lograge (0.12.0)
|
||||
lograge (0.13.0)
|
||||
actionpack (>= 4)
|
||||
activesupport (>= 4)
|
||||
railties (>= 4)
|
||||
@ -410,20 +445,26 @@ GEM
|
||||
mario-redis-lock (1.2.1)
|
||||
redis (>= 3.0.5)
|
||||
matrix (0.4.2)
|
||||
md-paperclip-azure (2.2.0)
|
||||
addressable (~> 2.5)
|
||||
azure-storage-blob (~> 2.0.1)
|
||||
hashie (~> 5.0)
|
||||
memory_profiler (1.0.1)
|
||||
method_source (1.0.0)
|
||||
mime-types (3.4.1)
|
||||
mime-types (3.5.0)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2023.0218.1)
|
||||
mini_mime (1.1.2)
|
||||
mini_portile2 (2.8.2)
|
||||
minitest (5.18.1)
|
||||
mime-types-data (3.2023.0808)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.4)
|
||||
minitest (5.19.0)
|
||||
msgpack (1.7.1)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.3.0)
|
||||
net-http (0.3.2)
|
||||
uri
|
||||
net-imap (0.3.6)
|
||||
net-http-persistent (4.0.2)
|
||||
connection_pool (~> 2.2)
|
||||
net-imap (0.3.7)
|
||||
date
|
||||
net-protocol
|
||||
net-ldap (0.18.0)
|
||||
@ -441,19 +482,16 @@ GEM
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
oj (3.15.0)
|
||||
omniauth (1.9.2)
|
||||
omniauth (2.1.1)
|
||||
hashie (>= 3.4.6)
|
||||
rack (>= 1.6.2, < 3)
|
||||
omniauth-cas (2.0.0)
|
||||
addressable (~> 2.3)
|
||||
nokogiri (~> 1.5)
|
||||
omniauth (~> 1.2)
|
||||
omniauth-rails_csrf_protection (0.1.2)
|
||||
rack (>= 2.2.3)
|
||||
rack-protection
|
||||
omniauth-rails_csrf_protection (1.0.1)
|
||||
actionpack (>= 4.2)
|
||||
omniauth (>= 1.3.1)
|
||||
omniauth-saml (1.10.3)
|
||||
omniauth (~> 1.3, >= 1.3.2)
|
||||
ruby-saml (~> 1.9)
|
||||
omniauth (~> 2.0)
|
||||
omniauth-saml (2.1.0)
|
||||
omniauth (~> 2.0)
|
||||
ruby-saml (~> 1.12)
|
||||
omniauth_openid_connect (0.6.1)
|
||||
omniauth (>= 1.9, < 3)
|
||||
openid_connect (~> 1.1)
|
||||
@ -500,9 +538,9 @@ GEM
|
||||
activesupport (>= 3.0.0)
|
||||
raabro (1.4.0)
|
||||
racc (1.7.1)
|
||||
rack (2.2.7)
|
||||
rack-attack (6.6.1)
|
||||
rack (>= 1.0, < 3)
|
||||
rack (2.2.8)
|
||||
rack-attack (6.7.0)
|
||||
rack (>= 1.0, < 4)
|
||||
rack-cors (2.0.1)
|
||||
rack (>= 2.0.0)
|
||||
rack-oauth2 (1.21.3)
|
||||
@ -511,30 +549,33 @@ GEM
|
||||
httpclient
|
||||
json-jwt (>= 1.11.0)
|
||||
rack (>= 2.1.0)
|
||||
rack-protection (3.0.5)
|
||||
rack
|
||||
rack-proxy (0.7.6)
|
||||
rack
|
||||
rack-test (2.1.0)
|
||||
rack (>= 1.3)
|
||||
rails (7.0.6)
|
||||
actioncable (= 7.0.6)
|
||||
actionmailbox (= 7.0.6)
|
||||
actionmailer (= 7.0.6)
|
||||
actionpack (= 7.0.6)
|
||||
actiontext (= 7.0.6)
|
||||
actionview (= 7.0.6)
|
||||
activejob (= 7.0.6)
|
||||
activemodel (= 7.0.6)
|
||||
activerecord (= 7.0.6)
|
||||
activestorage (= 7.0.6)
|
||||
activesupport (= 7.0.6)
|
||||
rails (7.0.7)
|
||||
actioncable (= 7.0.7)
|
||||
actionmailbox (= 7.0.7)
|
||||
actionmailer (= 7.0.7)
|
||||
actionpack (= 7.0.7)
|
||||
actiontext (= 7.0.7)
|
||||
actionview (= 7.0.7)
|
||||
activejob (= 7.0.7)
|
||||
activemodel (= 7.0.7)
|
||||
activerecord (= 7.0.7)
|
||||
activestorage (= 7.0.7)
|
||||
activesupport (= 7.0.7)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 7.0.6)
|
||||
railties (= 7.0.7)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
actionview (>= 5.0.1.rc1)
|
||||
activesupport (>= 5.0.1.rc1)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
rails-dom-testing (2.1.1)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.6.0)
|
||||
loofah (~> 2.21)
|
||||
@ -542,9 +583,9 @@ GEM
|
||||
rails-i18n (7.0.7)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 6.0.0, < 8)
|
||||
railties (7.0.6)
|
||||
actionpack (= 7.0.6)
|
||||
activesupport (= 7.0.6)
|
||||
railties (7.0.7)
|
||||
actionpack (= 7.0.7)
|
||||
activesupport (= 7.0.7)
|
||||
method_source
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0)
|
||||
@ -553,7 +594,7 @@ GEM
|
||||
rake (13.0.6)
|
||||
rdf (3.2.11)
|
||||
link_header (~> 0.0, >= 0.0.8)
|
||||
rdf-normalize (0.6.0)
|
||||
rdf-normalize (0.6.1)
|
||||
rdf (~> 3.2)
|
||||
redcarpet (3.6.0)
|
||||
redis (4.8.1)
|
||||
@ -567,7 +608,7 @@ GEM
|
||||
responders (3.1.0)
|
||||
actionpack (>= 5.2)
|
||||
railties (>= 5.2)
|
||||
rexml (3.2.5)
|
||||
rexml (3.2.6)
|
||||
rotp (6.2.2)
|
||||
rouge (4.1.2)
|
||||
rpam2 (4.0.2)
|
||||
@ -624,6 +665,7 @@ GEM
|
||||
rubocop (~> 1.33)
|
||||
rubocop-capybara (~> 2.17)
|
||||
rubocop-factory_bot (~> 2.22)
|
||||
ruby-prof (1.6.3)
|
||||
ruby-progressbar (1.13.0)
|
||||
ruby-saml (1.15.0)
|
||||
nokogiri (>= 1.13.10)
|
||||
@ -640,6 +682,10 @@ GEM
|
||||
scenic (1.7.0)
|
||||
activerecord (>= 4.0.0)
|
||||
railties (>= 4.0.0)
|
||||
selenium-webdriver (4.11.0)
|
||||
rexml (~> 3.2, >= 3.2.5)
|
||||
rubyzip (>= 1.2.2, < 3.0)
|
||||
websocket (~> 1.0)
|
||||
semantic_range (3.0.0)
|
||||
sidekiq (6.5.9)
|
||||
connection_pool (>= 2.2.5, < 3)
|
||||
@ -680,6 +726,7 @@ GEM
|
||||
net-scp (>= 1.1.2)
|
||||
net-ssh (>= 2.8.0)
|
||||
stackprof (0.2.25)
|
||||
statsd-ruby (1.5.0)
|
||||
stoplight (3.0.1)
|
||||
redlock (~> 1.0)
|
||||
strong_migrations (0.8.0)
|
||||
@ -694,6 +741,7 @@ GEM
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
terrapin (0.6.0)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
test-prof (1.2.2)
|
||||
thor (1.2.2)
|
||||
tilt (2.2.0)
|
||||
timeout (0.4.0)
|
||||
@ -752,14 +800,15 @@ GEM
|
||||
rack-proxy (>= 0.6.1)
|
||||
railties (>= 5.2)
|
||||
semantic_range (>= 2.3.0)
|
||||
websocket-driver (0.7.5)
|
||||
websocket (1.2.9)
|
||||
websocket-driver (0.7.6)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
wisper (2.0.1)
|
||||
xorcist (1.1.3)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
zeitwerk (2.6.8)
|
||||
zeitwerk (2.6.11)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
@ -788,6 +837,7 @@ DEPENDENCIES
|
||||
color_diff (~> 0.1)
|
||||
concurrent-ruby
|
||||
connection_pool
|
||||
database_cleaner-active_record
|
||||
devise (~> 4.9)
|
||||
devise-two-factor (~> 4.1)
|
||||
devise_pam_authenticatable2 (~> 9.2)
|
||||
@ -822,16 +872,18 @@ DEPENDENCIES
|
||||
link_header (~> 0.0)
|
||||
lograge (~> 0.12)
|
||||
mario-redis-lock (~> 1.2)
|
||||
md-paperclip-azure (~> 2.2)
|
||||
memory_profiler
|
||||
mime-types (~> 3.4.1)
|
||||
mime-types (~> 3.5.0)
|
||||
net-http (~> 0.3.2)
|
||||
net-ldap (~> 0.18)
|
||||
nokogiri (~> 1.15)
|
||||
nsa!
|
||||
oj (~> 3.14)
|
||||
omniauth (~> 1.9)
|
||||
omniauth-cas (~> 2.0)
|
||||
omniauth-rails_csrf_protection (~> 0.1)
|
||||
omniauth-saml (~> 1.10)
|
||||
omniauth (~> 2.0)
|
||||
omniauth-cas!
|
||||
omniauth-rails_csrf_protection (~> 1.0)
|
||||
omniauth-saml (~> 2.0)
|
||||
omniauth_openid_connect (~> 0.6.1)
|
||||
ox (~> 2.14)
|
||||
parslet
|
||||
@ -864,10 +916,12 @@ DEPENDENCIES
|
||||
rubocop-performance
|
||||
rubocop-rails
|
||||
rubocop-rspec
|
||||
ruby-prof
|
||||
ruby-progressbar (~> 1.13)
|
||||
rubyzip (~> 2.3)
|
||||
sanitize (~> 6.0)
|
||||
scenic (~> 1.7)
|
||||
selenium-webdriver
|
||||
sidekiq (~> 6.5)
|
||||
sidekiq-bulk (~> 0.2.0)
|
||||
sidekiq-scheduler (~> 5.0)
|
||||
@ -880,6 +934,7 @@ DEPENDENCIES
|
||||
stackprof
|
||||
stoplight (~> 3.0.1)
|
||||
strong_migrations (~> 0.8)
|
||||
test-prof
|
||||
thor (~> 1.2)
|
||||
tty-prompt (~> 0.23)
|
||||
twitter-text (~> 3.1.0)
|
||||
|
10
README.md
10
README.md
@ -11,7 +11,7 @@
|
||||
[releases]: https://github.com/mastodon/mastodon/releases
|
||||
[crowdin]: https://crowdin.com/project/mastodon
|
||||
|
||||
Mastodon is a **free, open-source social network server** based on ActivityPub where users can follow friends and discover new ones. On Mastodon, users can publish anything they want: links, pictures, text, video. All Mastodon servers are interoperable as a federated network (users on one server can seamlessly communicate with users from another one, including non-Mastodon software that implements ActivityPub!)
|
||||
Mastodon is a **free, open-source social network server** based on ActivityPub where users can follow friends and discover new ones. On Mastodon, users can publish anything they want: links, pictures, text, and video. All Mastodon servers are interoperable as a federated network (users on one server can seamlessly communicate with users from another one, including non-Mastodon software that implements ActivityPub!)
|
||||
|
||||
Click below to **learn more** in a video:
|
||||
|
||||
@ -51,7 +51,7 @@ Upload and view images and WebM/MP4 videos attached to the updates. Videos with
|
||||
|
||||
### Safety and moderation tools
|
||||
|
||||
Mastodon includes private posts, locked accounts, phrase filtering, muting, blocking and all sorts of other features, along with a reporting and moderation system. [Learn more](https://blog.joinmastodon.org/2018/07/cage-the-mastodon/)
|
||||
Mastodon includes private posts, locked accounts, phrase filtering, muting, blocking, and all sorts of other features, along with a reporting and moderation system. [Learn more](https://blog.joinmastodon.org/2018/07/cage-the-mastodon/)
|
||||
|
||||
### OAuth2 and a straightforward REST API
|
||||
|
||||
@ -74,7 +74,7 @@ Mastodon acts as an OAuth2 provider, so 3rd party apps can use the REST and Stre
|
||||
|
||||
The repository includes deployment configurations for **Docker and docker-compose** as well as specific platforms like **Heroku**, **Scalingo**, and **Nanobox**. For Helm charts, reference the [mastodon/chart repository](https://github.com/mastodon/chart). The [**standalone** installation guide](https://docs.joinmastodon.org/admin/install/) is available in the documentation.
|
||||
|
||||
A **Vagrant** configuration is included for development purposes. To use it, complete following steps:
|
||||
A **Vagrant** configuration is included for development purposes. To use it, complete the following steps:
|
||||
|
||||
- Install Vagrant and Virtualbox
|
||||
- Install the `vagrant-hostsupdater` plugin: `vagrant plugin install vagrant-hostsupdater`
|
||||
@ -100,9 +100,9 @@ To get started, create a codespace for this repository by clicking this 👇
|
||||
|
||||
[](https://github.com/codespaces/new?hide_repo_select=true&ref=main&repo=52281283)
|
||||
|
||||
A codespace will open in a web-based version of Visual Studio Code. The [dev container](.devcontainer/devcontainer.json) is fully configured with software needed for this project.
|
||||
A codespace will open in a web-based version of Visual Studio Code. The [dev container](.devcontainer/devcontainer.json) is fully configured with the software needed for this project.
|
||||
|
||||
**Note**: Dev containers is an open spec which is supported by [GitHub Codespaces](https://github.com/codespaces) and [other tools](https://containers.dev/supporting).
|
||||
**Note**: Dev containers are an open spec that is supported by [GitHub Codespaces](https://github.com/codespaces) and [other tools](https://containers.dev/supporting).
|
||||
|
||||
## Contributing
|
||||
|
||||
|
@ -33,7 +33,7 @@ class AccountsIndex < Chewy::Index
|
||||
},
|
||||
|
||||
verbatim: {
|
||||
tokenizer: 'whitespace',
|
||||
tokenizer: 'standard',
|
||||
filter: %w(lowercase asciifolding cjk_width),
|
||||
},
|
||||
|
||||
|
12
app/chewy/instances_index.rb
Normal file
12
app/chewy/instances_index.rb
Normal file
@ -0,0 +1,12 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class InstancesIndex < Chewy::Index
|
||||
settings index: { refresh_interval: '30s' }
|
||||
|
||||
index_scope ::Instance.searchable
|
||||
|
||||
root date_detection: false do
|
||||
field :domain, type: 'text', index_prefixes: { min_chars: 1 }
|
||||
field :accounts_count, type: 'long'
|
||||
end
|
||||
end
|
@ -12,7 +12,7 @@ class AccountsController < ApplicationController
|
||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
|
||||
skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format&.to_sym) }
|
||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||
skip_before_action :require_functional!, unless: :limited_federation_mode?
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
|
@ -40,7 +40,7 @@ module Admin
|
||||
end
|
||||
|
||||
# Allow transparently upgrading a domain block
|
||||
if existing_domain_block.present?
|
||||
if existing_domain_block.present? && existing_domain_block.domain == TagManager.instance.normalize_domain(@domain_block.domain.strip)
|
||||
@domain_block = existing_domain_block
|
||||
@domain_block.assign_attributes(resource_params)
|
||||
end
|
||||
|
@ -65,7 +65,7 @@ module Admin
|
||||
end
|
||||
|
||||
def filtered_instances
|
||||
InstanceFilter.new(whitelist_mode? ? { allowed: true } : filter_params).results
|
||||
InstanceFilter.new(limited_federation_mode? ? { allowed: true } : filter_params).results
|
||||
end
|
||||
|
||||
def filter_params
|
||||
|
@ -8,7 +8,7 @@ class Api::BaseController < ApplicationController
|
||||
include AccessTokenTrackingConcern
|
||||
include ApiCachingConcern
|
||||
|
||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||
skip_before_action :require_functional!, unless: :limited_federation_mode?
|
||||
|
||||
before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access?
|
||||
before_action :require_not_suspended!
|
||||
@ -150,7 +150,7 @@ class Api::BaseController < ApplicationController
|
||||
end
|
||||
|
||||
def disallow_unauthenticated_api_access?
|
||||
ENV['DISALLOW_UNAUTHENTICATED_API_ACCESS'] == 'true' || Rails.configuration.x.whitelist_mode
|
||||
ENV['DISALLOW_UNAUTHENTICATED_API_ACCESS'] == 'true' || Rails.configuration.x.limited_federation_mode
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -3,7 +3,7 @@
|
||||
class Api::V1::Instances::ActivityController < Api::BaseController
|
||||
before_action :require_enabled_api!
|
||||
|
||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
||||
|
||||
vary_by ''
|
||||
|
||||
@ -33,6 +33,6 @@ class Api::V1::Instances::ActivityController < Api::BaseController
|
||||
end
|
||||
|
||||
def require_enabled_api!
|
||||
head 404 unless Setting.activity_api_enabled && !whitelist_mode?
|
||||
head 404 unless Setting.activity_api_enabled && !limited_federation_mode?
|
||||
end
|
||||
end
|
||||
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Instances::DomainBlocksController < Api::BaseController
|
||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
||||
|
||||
before_action :require_enabled_api!
|
||||
before_action :set_domain_blocks
|
||||
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Instances::ExtendedDescriptionsController < Api::BaseController
|
||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
||||
skip_around_action :set_locale
|
||||
|
||||
before_action :set_extended_description
|
||||
@ -10,7 +10,7 @@ class Api::V1::Instances::ExtendedDescriptionsController < Api::BaseController
|
||||
|
||||
# Override `current_user` to avoid reading session cookies unless in whitelist mode
|
||||
def current_user
|
||||
super if whitelist_mode?
|
||||
super if limited_federation_mode?
|
||||
end
|
||||
|
||||
def show
|
||||
|
21
app/controllers/api/v1/instances/languages_controller.rb
Normal file
21
app/controllers/api/v1/instances/languages_controller.rb
Normal file
@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Instances::LanguagesController < Api::BaseController
|
||||
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
||||
skip_around_action :set_locale
|
||||
|
||||
before_action :set_languages
|
||||
|
||||
vary_by ''
|
||||
|
||||
def show
|
||||
cache_even_if_authenticated!
|
||||
render json: @languages, each_serializer: REST::LanguageSerializer
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_languages
|
||||
@languages = LanguagesHelper::SUPPORTED_LOCALES.keys.map { |code| LanguagePresenter.new(code) }
|
||||
end
|
||||
end
|
@ -3,24 +3,24 @@
|
||||
class Api::V1::Instances::PeersController < Api::BaseController
|
||||
before_action :require_enabled_api!
|
||||
|
||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
||||
skip_around_action :set_locale
|
||||
|
||||
vary_by ''
|
||||
|
||||
# Override `current_user` to avoid reading session cookies unless in whitelist mode
|
||||
def current_user
|
||||
super if whitelist_mode?
|
||||
super if limited_federation_mode?
|
||||
end
|
||||
|
||||
def index
|
||||
cache_even_if_authenticated!
|
||||
render_with_cache(expires_in: 1.day) { Instance.where.not(domain: DomainBlock.select(:domain)).pluck(:domain) }
|
||||
render_with_cache(expires_in: 1.day) { Instance.searchable.pluck(:domain) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_enabled_api!
|
||||
head 404 unless Setting.peers_api_enabled && !whitelist_mode?
|
||||
head 404 unless Setting.peers_api_enabled && !limited_federation_mode?
|
||||
end
|
||||
end
|
||||
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Instances::PrivacyPoliciesController < Api::BaseController
|
||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
||||
|
||||
before_action :set_privacy_policy
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Instances::RulesController < Api::BaseController
|
||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
||||
skip_around_action :set_locale
|
||||
|
||||
before_action :set_rules
|
||||
@ -10,7 +10,7 @@ class Api::V1::Instances::RulesController < Api::BaseController
|
||||
|
||||
# Override `current_user` to avoid reading session cookies unless in whitelist mode
|
||||
def current_user
|
||||
super if whitelist_mode?
|
||||
super if limited_federation_mode?
|
||||
end
|
||||
|
||||
def index
|
||||
|
@ -1,7 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Instances::TranslationLanguagesController < Api::BaseController
|
||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
||||
|
||||
before_action :set_languages
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::InstancesController < Api::BaseController
|
||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
||||
skip_around_action :set_locale
|
||||
|
||||
vary_by ''
|
||||
|
||||
# Override `current_user` to avoid reading session cookies unless in whitelist mode
|
||||
def current_user
|
||||
super if whitelist_mode?
|
||||
super if limited_federation_mode?
|
||||
end
|
||||
|
||||
def show
|
||||
|
45
app/controllers/api/v1/peers/search_controller.rb
Normal file
45
app/controllers/api/v1/peers/search_controller.rb
Normal file
@ -0,0 +1,45 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Api::V1::Peers::SearchController < Api::BaseController
|
||||
before_action :require_enabled_api!
|
||||
before_action :set_domains
|
||||
|
||||
skip_before_action :require_authenticated_user!, unless: :limited_federation_mode?
|
||||
skip_around_action :set_locale
|
||||
|
||||
vary_by ''
|
||||
|
||||
def index
|
||||
cache_even_if_authenticated!
|
||||
render json: @domains
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def require_enabled_api!
|
||||
head 404 unless Setting.peers_api_enabled && !limited_federation_mode?
|
||||
end
|
||||
|
||||
def set_domains
|
||||
return if params[:q].blank?
|
||||
|
||||
if Chewy.enabled?
|
||||
@domains = InstancesIndex.query(function_score: {
|
||||
query: {
|
||||
prefix: {
|
||||
domain: TagManager.instance.normalize_domain(params[:q].strip),
|
||||
},
|
||||
},
|
||||
|
||||
field_value_factor: {
|
||||
field: 'accounts_count',
|
||||
modifier: 'log2p',
|
||||
},
|
||||
}).limit(10).pluck(:domain)
|
||||
else
|
||||
domain = params[:q].strip
|
||||
domain = TagManager.instance.normalize_domain(domain)
|
||||
@domains = Instance.searchable.where(Instance.arel_table[:domain].matches("#{Instance.sanitize_sql_like(domain)}%", false, true)).limit(10).pluck(:domain)
|
||||
end
|
||||
end
|
||||
end
|
@ -17,13 +17,16 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController
|
||||
|
||||
if fav
|
||||
@status = fav.status
|
||||
count = [@status.favourites_count - 1, 0].max
|
||||
UnfavouriteWorker.perform_async(current_account.id, @status.id)
|
||||
else
|
||||
@status = Status.find(params[:status_id])
|
||||
count = @status.favourites_count
|
||||
authorize @status, :show?
|
||||
end
|
||||
|
||||
render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, favourites_map: { @status.id => false })
|
||||
relationships = StatusRelationshipsPresenter.new([@status], current_account.id, favourites_map: { @status.id => false }, attributes_map: { @status.id => { favourites_count: count } })
|
||||
render json: @status, serializer: REST::StatusSerializer, relationships: relationships
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
@ -24,15 +24,18 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
|
||||
|
||||
if @status
|
||||
authorize @status, :unreblog?
|
||||
@reblog = @status.reblog
|
||||
count = [@reblog.reblogs_count - 1, 0].max
|
||||
@status.discard
|
||||
RemovalWorker.perform_async(@status.id)
|
||||
@reblog = @status.reblog
|
||||
else
|
||||
@reblog = Status.find(params[:status_id])
|
||||
count = @reblog.reblogs_count
|
||||
authorize @reblog, :show?
|
||||
end
|
||||
|
||||
render json: @reblog, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_account.id, reblogs_map: { @reblog.id => false })
|
||||
relationships = StatusRelationshipsPresenter.new([@status], current_account.id, reblogs_map: { @reblog.id => false }, attributes_map: { @reblog.id => { reblogs_count: count } })
|
||||
render json: @reblog, serializer: REST::StatusSerializer, relationships: relationships
|
||||
rescue Mastodon::NotPermittedError
|
||||
not_found
|
||||
end
|
||||
|
@ -19,7 +19,7 @@ class ApplicationController < ActionController::Base
|
||||
helper_method :use_seamless_external_login?
|
||||
helper_method :omniauth_only?
|
||||
helper_method :sso_account_settings
|
||||
helper_method :whitelist_mode?
|
||||
helper_method :limited_federation_mode?
|
||||
helper_method :body_class_string
|
||||
helper_method :skip_csrf_meta_tags?
|
||||
|
||||
@ -52,7 +52,7 @@ class ApplicationController < ActionController::Base
|
||||
private
|
||||
|
||||
def authorized_fetch_mode?
|
||||
ENV['AUTHORIZED_FETCH'] == 'true' || Rails.configuration.x.whitelist_mode
|
||||
ENV['AUTHORIZED_FETCH'] == 'true' || Rails.configuration.x.limited_federation_mode
|
||||
end
|
||||
|
||||
def public_fetch_mode?
|
||||
|
@ -5,21 +5,13 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||
|
||||
def self.provides_callback_for(provider)
|
||||
define_method provider do
|
||||
@provider = provider
|
||||
@user = User.find_for_oauth(request.env['omniauth.auth'], current_user)
|
||||
|
||||
if @user.persisted?
|
||||
LoginActivity.create(
|
||||
user: @user,
|
||||
success: true,
|
||||
authentication_method: :omniauth,
|
||||
provider: provider,
|
||||
ip: request.remote_ip,
|
||||
user_agent: request.user_agent
|
||||
)
|
||||
|
||||
record_login_activity
|
||||
sign_in_and_redirect @user, event: :authentication
|
||||
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?
|
||||
set_flash_message(:notice, :success, kind: label_for_provider) if is_navigational_format?
|
||||
else
|
||||
session["devise.#{provider}_data"] = request.env['omniauth.auth']
|
||||
redirect_to new_user_registration_url
|
||||
@ -38,4 +30,29 @@ class Auth::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||
auth_setup_path(missing_email: '1')
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def record_login_activity
|
||||
LoginActivity.create(
|
||||
user: @user,
|
||||
success: true,
|
||||
authentication_method: :omniauth,
|
||||
provider: @provider,
|
||||
ip: request.remote_ip,
|
||||
user_agent: request.user_agent
|
||||
)
|
||||
end
|
||||
|
||||
def label_for_provider
|
||||
provider_display_name || configured_provider_name
|
||||
end
|
||||
|
||||
def provider_display_name
|
||||
Devise.omniauth_configs[@provider]&.strategy&.display_name.presence
|
||||
end
|
||||
|
||||
def configured_provider_name
|
||||
I18n.t("auth.providers.#{@provider}", default: @provider.to_s.chomp('_oauth2').capitalize)
|
||||
end
|
||||
end
|
||||
|
@ -108,7 +108,7 @@ class Auth::SessionsController < Devise::SessionsController
|
||||
end
|
||||
|
||||
def home_paths(resource)
|
||||
paths = [about_path]
|
||||
paths = [about_path, '/explore']
|
||||
|
||||
paths << short_account_path(username: resource.account) if single_user_mode? && resource.is_a?(User)
|
||||
|
||||
|
@ -3,32 +3,19 @@
|
||||
class AuthorizeInteractionsController < ApplicationController
|
||||
include Authorization
|
||||
|
||||
layout 'modal'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :set_body_classes
|
||||
before_action :set_resource
|
||||
|
||||
def show
|
||||
if @resource.is_a?(Account)
|
||||
render :show
|
||||
redirect_to web_url("@#{@resource.pretty_acct}")
|
||||
elsif @resource.is_a?(Status)
|
||||
redirect_to web_url("@#{@resource.account.pretty_acct}/#{@resource.id}")
|
||||
else
|
||||
render :error
|
||||
not_found
|
||||
end
|
||||
end
|
||||
|
||||
def create
|
||||
if @resource.is_a?(Account) && FollowService.new.call(current_account, @resource, with_rate_limit: true)
|
||||
render :success
|
||||
else
|
||||
render :error
|
||||
end
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render :error
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_resource
|
||||
@ -61,8 +48,4 @@ class AuthorizeInteractionsController < ApplicationController
|
||||
def uri_param
|
||||
params[:uri] || params.fetch(:acct, '').delete_prefix('acct:')
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'modal-layout'
|
||||
end
|
||||
end
|
||||
|
@ -10,7 +10,7 @@ class BackupsController < ApplicationController
|
||||
|
||||
def download
|
||||
case Paperclip::Attachment.default_options[:storage]
|
||||
when :s3
|
||||
when :s3, :azure
|
||||
redirect_to @backup.dump.expiring_url(10), allow_other_host: true
|
||||
when :fog
|
||||
if Paperclip::Attachment.default_options.dig(:fog_credentials, :openstack_temp_url_key).present?
|
||||
|
@ -4,7 +4,7 @@ module AccountOwnedConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
before_action :authenticate_user!, if: -> { whitelist_mode? && request.format != :json }
|
||||
before_action :authenticate_user!, if: -> { limited_federation_mode? && request.format != :json }
|
||||
before_action :set_account, if: :account_required?
|
||||
before_action :check_account_approval, if: :account_required?
|
||||
before_action :check_account_suspension, if: :account_required?
|
||||
|
@ -8,6 +8,6 @@ module ApiCachingConcern
|
||||
end
|
||||
|
||||
def cache_even_if_authenticated!
|
||||
expires_in(5.minutes, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless whitelist_mode?
|
||||
expires_in(5.minutes, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day) unless limited_federation_mode?
|
||||
end
|
||||
end
|
||||
|
@ -42,7 +42,7 @@ module CaptchaConcern
|
||||
end
|
||||
|
||||
def extend_csp_for_captcha!
|
||||
policy = request.content_security_policy
|
||||
policy = request.content_security_policy&.clone
|
||||
|
||||
return unless captcha_required? && policy.present?
|
||||
|
||||
@ -54,6 +54,8 @@ module CaptchaConcern
|
||||
|
||||
policy.send(directive, *values)
|
||||
end
|
||||
|
||||
request.content_security_policy = policy
|
||||
end
|
||||
|
||||
def render_captcha
|
||||
|
@ -11,7 +11,7 @@ module WebAppControllerConcern
|
||||
end
|
||||
|
||||
def skip_csrf_meta_tags?
|
||||
current_user.nil?
|
||||
!(ENV['OMNIAUTH_ONLY'] == 'true' && Devise.omniauth_providers.length == 1) && current_user.nil?
|
||||
end
|
||||
|
||||
def set_app_body_class
|
||||
|
@ -10,7 +10,7 @@ class FollowerAccountsController < ApplicationController
|
||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
|
||||
skip_around_action :set_locale, if: -> { request.format == :json }
|
||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||
skip_before_action :require_functional!, unless: :limited_federation_mode?
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
|
@ -10,7 +10,7 @@ class FollowingAccountsController < ApplicationController
|
||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
|
||||
skip_around_action :set_locale, if: -> { request.format == :json }
|
||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||
skip_before_action :require_functional!, unless: :limited_federation_mode?
|
||||
|
||||
def index
|
||||
respond_to do |format|
|
||||
|
@ -9,6 +9,8 @@ class MailSubscriptionsController < ApplicationController
|
||||
before_action :set_user
|
||||
before_action :set_type
|
||||
|
||||
protect_from_forgery with: :null_session
|
||||
|
||||
def show; end
|
||||
|
||||
def create
|
||||
@ -20,6 +22,7 @@ class MailSubscriptionsController < ApplicationController
|
||||
|
||||
def set_user
|
||||
@user = GlobalID::Locator.locate_signed(params[:token], for: 'unsubscribe')
|
||||
not_found unless @user
|
||||
end
|
||||
|
||||
def set_body_classes
|
||||
@ -35,7 +38,7 @@ class MailSubscriptionsController < ApplicationController
|
||||
when 'follow', 'reblog', 'favourite', 'mention', 'follow_request'
|
||||
"notification_emails.#{params[:type]}"
|
||||
else
|
||||
raise ArgumentError
|
||||
not_found
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -3,9 +3,9 @@
|
||||
class MediaController < ApplicationController
|
||||
include Authorization
|
||||
|
||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||
skip_before_action :require_functional!, unless: :limited_federation_mode?
|
||||
|
||||
before_action :authenticate_user!, if: :whitelist_mode?
|
||||
before_action :authenticate_user!, if: :limited_federation_mode?
|
||||
before_action :set_media_attachment
|
||||
before_action :verify_permitted_status!
|
||||
before_action :check_playable, only: :player
|
||||
|
@ -8,7 +8,7 @@ class MediaProxyController < ApplicationController
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
before_action :authenticate_user!, if: :whitelist_mode?
|
||||
before_action :authenticate_user!, if: :limited_federation_mode?
|
||||
|
||||
rescue_from ActiveRecord::RecordInvalid, with: :not_found
|
||||
rescue_from Mastodon::UnexpectedResponseError, with: :not_found
|
||||
|
43
app/controllers/remote_interaction_helper_controller.rb
Normal file
43
app/controllers/remote_interaction_helper_controller.rb
Normal file
@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class RemoteInteractionHelperController < ApplicationController
|
||||
vary_by ''
|
||||
|
||||
skip_before_action :require_functional!
|
||||
skip_around_action :set_locale
|
||||
skip_before_action :update_user_sign_in
|
||||
|
||||
content_security_policy do |p|
|
||||
# We inherit the normal `script-src`
|
||||
|
||||
# Set every directive that does not have a fallback
|
||||
p.default_src :none
|
||||
p.form_action :none
|
||||
p.base_uri :none
|
||||
|
||||
# Disable every directive with a fallback to cut on response size
|
||||
p.base_uri false
|
||||
p.font_src false
|
||||
p.img_src false
|
||||
p.style_src false
|
||||
p.media_src false
|
||||
p.frame_src false
|
||||
p.manifest_src false
|
||||
p.connect_src false
|
||||
p.child_src false
|
||||
p.worker_src false
|
||||
|
||||
# Widen the directives that we do need
|
||||
p.frame_ancestors :self
|
||||
p.connect_src :https
|
||||
end
|
||||
|
||||
def index
|
||||
expires_in(5.minutes, public: true, stale_while_revalidate: 30.seconds, stale_if_error: 1.day)
|
||||
|
||||
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
|
||||
response.headers['Referrer-Policy'] = 'no-referrer'
|
||||
|
||||
render layout: 'helper_frame'
|
||||
end
|
||||
end
|
@ -17,7 +17,7 @@ class StatusesController < ApplicationController
|
||||
after_action :set_link_headers
|
||||
|
||||
skip_around_action :set_locale, if: -> { request.format == :json }
|
||||
skip_before_action :require_functional!, only: [:show, :embed], unless: :whitelist_mode?
|
||||
skip_before_action :require_functional!, only: [:show, :embed], unless: :limited_federation_mode?
|
||||
|
||||
content_security_policy only: :embed do |policy|
|
||||
policy.frame_ancestors(false)
|
||||
|
@ -10,13 +10,13 @@ class TagsController < ApplicationController
|
||||
vary_by -> { public_fetch_mode? ? 'Accept, Accept-Language, Cookie' : 'Accept, Accept-Language, Cookie, Signature' }
|
||||
|
||||
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
|
||||
before_action :authenticate_user!, if: :whitelist_mode?
|
||||
before_action :authenticate_user!, if: :limited_federation_mode?
|
||||
before_action :set_local
|
||||
before_action :set_tag
|
||||
before_action :set_statuses, if: -> { request.format == :rss }
|
||||
before_action :set_instance_presenter
|
||||
|
||||
skip_before_action :require_functional!, unless: :whitelist_mode?
|
||||
skip_before_action :require_functional!, unless: :limited_federation_mode?
|
||||
|
||||
def show
|
||||
respond_to do |format|
|
||||
|
@ -19,6 +19,7 @@ module WellKnown
|
||||
|
||||
def set_account
|
||||
username = username_from_resource
|
||||
|
||||
@account = begin
|
||||
if username == Rails.configuration.x.local_domain
|
||||
Account.representative
|
||||
|
@ -235,6 +235,6 @@ module ApplicationHelper
|
||||
private
|
||||
|
||||
def storage_host_var
|
||||
ENV.fetch('S3_ALIAS_HOST', nil) || ENV.fetch('S3_CLOUDFRONT_HOST', nil)
|
||||
ENV.fetch('S3_ALIAS_HOST', nil) || ENV.fetch('S3_CLOUDFRONT_HOST', nil) || ENV.fetch('AZURE_ALIAS_HOST', nil)
|
||||
end
|
||||
end
|
||||
|
@ -21,7 +21,14 @@ module ContextHelper
|
||||
blurhash: { 'toot' => 'http://joinmastodon.org/ns#', 'blurhash' => 'toot:blurhash' },
|
||||
discoverable: { 'toot' => 'http://joinmastodon.org/ns#', 'discoverable' => 'toot:discoverable' },
|
||||
voters_count: { 'toot' => 'http://joinmastodon.org/ns#', 'votersCount' => 'toot:votersCount' },
|
||||
olm: { 'toot' => 'http://joinmastodon.org/ns#', 'Device' => 'toot:Device', 'Ed25519Signature' => 'toot:Ed25519Signature', 'Ed25519Key' => 'toot:Ed25519Key', 'Curve25519Key' => 'toot:Curve25519Key', 'EncryptedMessage' => 'toot:EncryptedMessage', 'publicKeyBase64' => 'toot:publicKeyBase64', 'deviceId' => 'toot:deviceId', 'claim' => { '@type' => '@id', '@id' => 'toot:claim' }, 'fingerprintKey' => { '@type' => '@id', '@id' => 'toot:fingerprintKey' }, 'identityKey' => { '@type' => '@id', '@id' => 'toot:identityKey' }, 'devices' => { '@type' => '@id', '@id' => 'toot:devices' }, 'messageFranking' => 'toot:messageFranking', 'messageType' => 'toot:messageType', 'cipherText' => 'toot:cipherText' },
|
||||
olm: {
|
||||
'toot' => 'http://joinmastodon.org/ns#', 'Device' => 'toot:Device', 'Ed25519Signature' => 'toot:Ed25519Signature', 'Ed25519Key' => 'toot:Ed25519Key', 'Curve25519Key' => 'toot:Curve25519Key', 'EncryptedMessage' => 'toot:EncryptedMessage', 'publicKeyBase64' => 'toot:publicKeyBase64', 'deviceId' => 'toot:deviceId',
|
||||
'claim' => { '@type' => '@id', '@id' => 'toot:claim' },
|
||||
'fingerprintKey' => { '@type' => '@id', '@id' => 'toot:fingerprintKey' },
|
||||
'identityKey' => { '@type' => '@id', '@id' => 'toot:identityKey' },
|
||||
'devices' => { '@type' => '@id', '@id' => 'toot:devices' },
|
||||
'messageFranking' => 'toot:messageFranking', 'messageType' => 'toot:messageType', 'cipherText' => 'toot:cipherText'
|
||||
},
|
||||
suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' },
|
||||
quoteUrl: { 'quoteUrl' => 'as:quoteUrl' },
|
||||
}.freeze
|
||||
|
@ -10,14 +10,14 @@ module DomainControlHelper
|
||||
uri_or_domain
|
||||
end
|
||||
|
||||
if whitelist_mode?
|
||||
if limited_federation_mode?
|
||||
!DomainAllow.allowed?(domain)
|
||||
else
|
||||
DomainBlock.blocked?(domain)
|
||||
end
|
||||
end
|
||||
|
||||
def whitelist_mode?
|
||||
Rails.configuration.x.whitelist_mode
|
||||
def limited_federation_mode?
|
||||
Rails.configuration.x.limited_federation_mode
|
||||
end
|
||||
end
|
||||
|
@ -204,7 +204,17 @@ module LanguagesHelper
|
||||
zgh: ['Standard Moroccan Tamazight', 'ⵜⴰⵎⴰⵣⵉⵖⵜ'].freeze,
|
||||
}.freeze
|
||||
|
||||
SUPPORTED_LOCALES = {}.merge(ISO_639_1).merge(ISO_639_3).freeze
|
||||
# e.g. For Chinese, which is not a language,
|
||||
# but a language family in spite of sharing the main locale code
|
||||
# We need to be able to filter these
|
||||
ISO_639_1_REGIONAL = {
|
||||
'zh-CN': ['Chinese (China)', '简体中文'].freeze,
|
||||
'zh-HK': ['Chinese (Hong Kong)', '繁體中文(香港)'].freeze,
|
||||
'zh-TW': ['Chinese (Taiwan)', '繁體中文(臺灣)'].freeze,
|
||||
'zh-YUE': ['Cantonese', '廣東話'].freeze,
|
||||
}.freeze
|
||||
|
||||
SUPPORTED_LOCALES = {}.merge(ISO_639_1).merge(ISO_639_1_REGIONAL).merge(ISO_639_3).freeze
|
||||
|
||||
# For ISO-639-1 and ISO-639-3 language codes, we have their official
|
||||
# names, but for some translations, we need the names of the
|
||||
@ -217,9 +227,6 @@ module LanguagesHelper
|
||||
'pt-BR': 'Português (Brasil)',
|
||||
'pt-PT': 'Português (Portugal)',
|
||||
'sr-Latn': 'Srpski (latinica)',
|
||||
'zh-CN': '简体中文',
|
||||
'zh-HK': '繁體中文(香港)',
|
||||
'zh-TW': '繁體中文(臺灣)',
|
||||
}.freeze
|
||||
|
||||
def native_locale_name(locale)
|
||||
|
@ -65,33 +65,6 @@ module StatusesHelper
|
||||
embedded_view? ? '_blank' : nil
|
||||
end
|
||||
|
||||
def style_classes(status, is_predecessor, is_successor, include_threads)
|
||||
classes = ['entry']
|
||||
classes << 'entry-predecessor' if is_predecessor
|
||||
classes << 'entry-reblog' if status.reblog?
|
||||
classes << 'entry-successor' if is_successor
|
||||
classes << 'entry-center' if include_threads
|
||||
classes.join(' ')
|
||||
end
|
||||
|
||||
def microformats_classes(status, is_direct_parent, is_direct_child)
|
||||
classes = []
|
||||
classes << 'p-in-reply-to' if is_direct_parent
|
||||
classes << 'p-repost-of' if status.reblog? && is_direct_parent
|
||||
classes << 'p-comment' if is_direct_child
|
||||
classes.join(' ')
|
||||
end
|
||||
|
||||
def microformats_h_class(status, is_predecessor, is_successor, include_threads)
|
||||
if is_predecessor || status.reblog? || is_successor
|
||||
'h-cite'
|
||||
elsif include_threads
|
||||
''
|
||||
else
|
||||
'h-entry'
|
||||
end
|
||||
end
|
||||
|
||||
def fa_visibility_icon(status)
|
||||
case status.visibility
|
||||
when 'public'
|
||||
|
@ -85,7 +85,7 @@ export function importFetchedStatuses(statuses) {
|
||||
}
|
||||
|
||||
if (status.poll && status.poll.id) {
|
||||
pushUnique(polls, normalizePoll(status.poll));
|
||||
pushUnique(polls, normalizePoll(status.poll, getState().getIn(['polls', status.poll.id])));
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,7 +99,7 @@ export function importFetchedStatuses(statuses) {
|
||||
}
|
||||
|
||||
export function importFetchedPoll(poll) {
|
||||
return dispatch => {
|
||||
dispatch(importPolls([normalizePoll(poll)]));
|
||||
return (dispatch, getState) => {
|
||||
dispatch(importPolls([normalizePoll(poll, getState().getIn(['polls', poll.id]))]));
|
||||
};
|
||||
}
|
||||
|
@ -78,6 +78,10 @@ export function normalizeStatus(status, normalOldStatus) {
|
||||
normalStatus.hidden = normalOldStatus.get('hidden');
|
||||
normalStatus.quote = normalOldStatus.get('quote');
|
||||
normalStatus.quote_hidden = normalOldStatus.get('quote_hidden');
|
||||
|
||||
if (normalOldStatus.get('translation')) {
|
||||
normalStatus.translation = normalOldStatus.get('translation');
|
||||
}
|
||||
} else {
|
||||
// If the status has a CW but no contents, treat the CW as if it were the
|
||||
// status' contents, to avoid having a CW toggle with seemingly no effect.
|
||||
@ -101,6 +105,18 @@ export function normalizeStatus(status, normalOldStatus) {
|
||||
}
|
||||
}
|
||||
|
||||
if (normalOldStatus) {
|
||||
const list = normalOldStatus.get('media_attachments');
|
||||
if (normalStatus.media_attachments && list) {
|
||||
normalStatus.media_attachments.forEach(item => {
|
||||
const oldItem = list.find(i => i.get('id') === item.id);
|
||||
if (oldItem && oldItem.get('description') === item.description) {
|
||||
item.translation = oldItem.get('translation')
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return normalStatus;
|
||||
}
|
||||
|
||||
@ -119,15 +135,23 @@ export function normalizeStatusTranslation(translation, status) {
|
||||
return normalTranslation;
|
||||
}
|
||||
|
||||
export function normalizePoll(poll) {
|
||||
export function normalizePoll(poll, normalOldPoll) {
|
||||
const normalPoll = { ...poll };
|
||||
const emojiMap = makeEmojiMap(poll.emojis);
|
||||
|
||||
normalPoll.options = poll.options.map((option, index) => ({
|
||||
...option,
|
||||
voted: poll.own_votes && poll.own_votes.includes(index),
|
||||
titleHtml: emojify(escapeTextContentForBrowser(option.title), emojiMap),
|
||||
}));
|
||||
normalPoll.options = poll.options.map((option, index) => {
|
||||
const normalOption = {
|
||||
...option,
|
||||
voted: poll.own_votes && poll.own_votes.includes(index),
|
||||
titleHtml: emojify(escapeTextContentForBrowser(option.title), emojiMap),
|
||||
}
|
||||
|
||||
if (normalOldPoll && normalOldPoll.getIn(['options', index, 'title']) === option.title) {
|
||||
normalOption.translation = normalOldPoll.getIn(['options', index, 'translation']);
|
||||
}
|
||||
|
||||
return normalOption
|
||||
});
|
||||
|
||||
return normalPoll;
|
||||
}
|
||||
|
@ -150,7 +150,7 @@ export function fillTimelineGaps(timelineId, path, params = {}, done = noOp) {
|
||||
export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done);
|
||||
export const expandPublicTimeline = ({ maxId, onlyMedia, onlyRemote } = {}, done = noOp) => expandTimeline(`public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { remote: !!onlyRemote, max_id: maxId, only_media: !!onlyMedia }, done);
|
||||
export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { local: true, max_id: maxId, only_media: !!onlyMedia }, done);
|
||||
export const expandAccountTimeline = (accountId, { maxId, withReplies, tagged } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}${tagged ? `:${tagged}` : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, tagged, max_id: maxId });
|
||||
export const expandAccountTimeline = (accountId, { maxId, withReplies, tagged } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}${tagged ? `:${tagged}` : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, exclude_reblogs: withReplies, tagged, max_id: maxId });
|
||||
export const expandAccountFeaturedTimeline = (accountId, { tagged } = {}) => expandTimeline(`account:${accountId}:pinned${tagged ? `:${tagged}` : ''}`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true, tagged });
|
||||
export const expandAccountMediaTimeline = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: 40 });
|
||||
export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done);
|
||||
|
34
app/javascript/mastodon/components/badge.jsx
Normal file
34
app/javascript/mastodon/components/badge.jsx
Normal file
@ -0,0 +1,34 @@
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { ReactComponent as GroupsIcon } from '@material-design-icons/svg/outlined/group.svg';
|
||||
import { ReactComponent as PersonIcon } from '@material-design-icons/svg/outlined/person.svg';
|
||||
import { ReactComponent as SmartToyIcon } from '@material-design-icons/svg/outlined/smart_toy.svg';
|
||||
|
||||
|
||||
export const Badge = ({ icon, label, domain }) => (
|
||||
<div className='account-role'>
|
||||
{icon}
|
||||
{label}
|
||||
{domain && <span className='account-role__domain'>{domain}</span>}
|
||||
</div>
|
||||
);
|
||||
|
||||
Badge.propTypes = {
|
||||
icon: PropTypes.node,
|
||||
label: PropTypes.node,
|
||||
domain: PropTypes.node,
|
||||
};
|
||||
|
||||
Badge.defaultProps = {
|
||||
icon: <PersonIcon />,
|
||||
};
|
||||
|
||||
export const GroupBadge = () => (
|
||||
<Badge icon={<GroupsIcon />} label={<FormattedMessage id='account.badges.group' defaultMessage='Group' />} />
|
||||
);
|
||||
|
||||
export const AutomatedBadge = () => (
|
||||
<Badge icon={<SmartToyIcon />} label={<FormattedMessage id='account.badges.bot' defaultMessage='Automated' />} />
|
||||
);
|
@ -297,7 +297,7 @@ export default class Dropdown extends PureComponent {
|
||||
onKeyPress: this.handleKeyPress,
|
||||
}) : (
|
||||
<IconButton
|
||||
icon={icon}
|
||||
icon={!open ? icon : 'close'}
|
||||
title={title}
|
||||
active={open}
|
||||
disabled={disabled}
|
||||
|
@ -114,7 +114,7 @@ export default class IntersectionObserverArticle extends Component {
|
||||
aria-setsize={listLength}
|
||||
style={{ height: `${this.height || cachedHeight}px`, opacity: 0, overflow: 'hidden' }}
|
||||
data-id={id}
|
||||
tabIndex={0}
|
||||
tabIndex={-1}
|
||||
>
|
||||
{children && cloneElement(children, { hidden: true })}
|
||||
</article>
|
||||
@ -122,7 +122,7 @@ export default class IntersectionObserverArticle extends Component {
|
||||
}
|
||||
|
||||
return (
|
||||
<article ref={this.handleRef} aria-posinset={index + 1} aria-setsize={listLength} data-id={id} tabIndex={0}>
|
||||
<article ref={this.handleRef} aria-posinset={index + 1} aria-setsize={listLength} data-id={id} tabIndex={-1}>
|
||||
{children && cloneElement(children, { hidden: false })}
|
||||
</article>
|
||||
);
|
||||
|
@ -12,7 +12,7 @@ import { debounce } from 'lodash';
|
||||
|
||||
import { Blurhash } from 'mastodon/components/blurhash';
|
||||
|
||||
import { autoPlayGif, cropImages, displayMedia, useBlurhash } from '../initial_state';
|
||||
import { autoPlayGif, displayMedia, useBlurhash } from '../initial_state';
|
||||
|
||||
import { IconButton } from './icon_button';
|
||||
|
||||
@ -209,7 +209,6 @@ class MediaGallery extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
sensitive: PropTypes.bool,
|
||||
standalone: PropTypes.bool,
|
||||
media: ImmutablePropTypes.list.isRequired,
|
||||
lang: PropTypes.string,
|
||||
size: PropTypes.object,
|
||||
@ -224,11 +223,6 @@ class MediaGallery extends PureComponent {
|
||||
quote: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
standalone: false,
|
||||
quote: false,
|
||||
};
|
||||
|
||||
state = {
|
||||
visible: this.props.visible !== undefined ? this.props.visible : (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all'),
|
||||
width: this.props.defaultWidth,
|
||||
@ -297,7 +291,7 @@ class MediaGallery extends PureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { media, lang, intl, sensitive, defaultWidth, standalone, autoplay, quote } = this.props;
|
||||
const { media, lang, intl, sensitive, defaultWidth, autoplay, quote } = this.props;
|
||||
const { visible } = this.state;
|
||||
const width = this.state.width || defaultWidth;
|
||||
|
||||
@ -305,10 +299,10 @@ class MediaGallery extends PureComponent {
|
||||
|
||||
const style = {};
|
||||
|
||||
if (this.isFullSizeEligible() && (standalone || !cropImages)) {
|
||||
if (this.isFullSizeEligible()) {
|
||||
style.aspectRatio = `${this.props.media.getIn([0, 'meta', 'small', 'aspect'])}`;
|
||||
} else {
|
||||
style.aspectRatio = '16 / 9';
|
||||
style.aspectRatio = '3 / 2';
|
||||
}
|
||||
|
||||
if (quote && style.height) {
|
||||
@ -322,7 +316,7 @@ class MediaGallery extends PureComponent {
|
||||
const size = media.take(4).size;
|
||||
const uncached = media.every(attachment => attachment.get('type') === 'unknown');
|
||||
|
||||
if (standalone && this.isFullSizeEligible()) {
|
||||
if (this.isFullSizeEligible()) {
|
||||
children = <Item standalone autoplay={autoplay} onClick={this.handleClick} attachment={media.get(0)} lang={lang} displayWidth={width} visible={visible} />;
|
||||
} else {
|
||||
children = media.take(4).map((attachment, i) => <Item key={attachment.get('id')} autoplay={autoplay} onClick={this.handleClick} attachment={attachment} index={i} lang={lang} size={size} displayWidth={width} visible={visible || uncached} />);
|
||||
|
@ -12,6 +12,7 @@ class PictureInPicturePlaceholder extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
aspectRatio: PropTypes.string,
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
@ -20,8 +21,10 @@ class PictureInPicturePlaceholder extends PureComponent {
|
||||
};
|
||||
|
||||
render () {
|
||||
const { aspectRatio } = this.props;
|
||||
|
||||
return (
|
||||
<div className='picture-in-picture-placeholder' role='button' tabIndex={0} onClick={this.handleClick}>
|
||||
<div className='picture-in-picture-placeholder' style={{ aspectRatio }} role='button' tabIndex={0} onClick={this.handleClick}>
|
||||
<Icon id='window-restore' />
|
||||
<FormattedMessage id='picture_in_picture.restore' defaultMessage='Put it back' />
|
||||
</div>
|
||||
|
@ -6,7 +6,6 @@ import classNames from 'classnames';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
|
||||
@ -20,7 +19,6 @@ import Bundle from '../features/ui/components/bundle';
|
||||
import { MediaGallery, Video, Audio } from '../features/ui/util/async-components';
|
||||
import { displayMedia } from '../initial_state';
|
||||
|
||||
import AttachmentList from './attachment_list';
|
||||
import { Avatar } from './avatar';
|
||||
import { AvatarOverlay } from './avatar_overlay';
|
||||
import { DisplayName } from './display_name';
|
||||
@ -292,17 +290,35 @@ class Status extends ImmutablePureComponent {
|
||||
this.props.onQuoteToggleHidden(this._properStatus());
|
||||
}
|
||||
|
||||
renderLoadingMediaGallery () {
|
||||
return <div className='media-gallery' style={{ height: '110px' }} />;
|
||||
getAttachmentAspectRatio () {
|
||||
const attachments = this._properStatus().get('media_attachments');
|
||||
|
||||
if (attachments.getIn([0, 'type']) === 'video') {
|
||||
return `${attachments.getIn([0, 'meta', 'original', 'width'])} / ${attachments.getIn([0, 'meta', 'original', 'height'])}`;
|
||||
} else if (attachments.getIn([0, 'type']) === 'audio') {
|
||||
return '16 / 9';
|
||||
} else {
|
||||
return (attachments.size === 1 && attachments.getIn([0, 'meta', 'small', 'aspect'])) ? attachments.getIn([0, 'meta', 'small', 'aspect']) : '3 / 2'
|
||||
}
|
||||
}
|
||||
|
||||
renderLoadingVideoPlayer () {
|
||||
return <div className='video-player' style={{ height: '110px' }} />;
|
||||
}
|
||||
renderLoadingMediaGallery = () => {
|
||||
return (
|
||||
<div className='media-gallery' style={{ aspectRatio: this.getAttachmentAspectRatio() }} />
|
||||
);
|
||||
};
|
||||
|
||||
renderLoadingAudioPlayer () {
|
||||
return <div className='audio-player' style={{ height: '110px' }} />;
|
||||
}
|
||||
renderLoadingVideoPlayer = () => {
|
||||
return (
|
||||
<div className='video-player' style={{ aspectRatio: this.getAttachmentAspectRatio() }} />
|
||||
);
|
||||
};
|
||||
|
||||
renderLoadingAudioPlayer = () => {
|
||||
return (
|
||||
<div className='audio-player' style={{ aspectRatio: this.getAttachmentAspectRatio() }} />
|
||||
);
|
||||
};
|
||||
|
||||
handleOpenVideo = (options) => {
|
||||
const status = this._properStatus();
|
||||
@ -428,7 +444,7 @@ class Status extends ImmutablePureComponent {
|
||||
};
|
||||
|
||||
render () {
|
||||
const { intl, hidden, featured, unread, showThread, scrollKey, pictureInPicture, previousId, nextInReplyToId, rootId, quoteMuted, contextType } = this.props;
|
||||
const { intl, hidden, featured, unread, showThread, scrollKey, pictureInPicture, previousId, quoteMuted, nextInReplyToId, rootId, contextType } = this.props;
|
||||
|
||||
let { status, account, ...other } = this.props;
|
||||
|
||||
@ -525,69 +541,61 @@ class Status extends ImmutablePureComponent {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const media = (status, quote = false) => {
|
||||
if (pictureInPicture.get('inUse')) {
|
||||
return <PictureInPicturePlaceholder width={this.props.cachedMediaWidth} />;
|
||||
return <PictureInPicturePlaceholder aspectRatio={this.getAttachmentAspectRatio()} />;
|
||||
} else if (status.get('media_attachments').size > 0) {
|
||||
const language = status.getIn(['translation', 'language']) || status.get('language');
|
||||
|
||||
if (this.props.muted) {
|
||||
return (
|
||||
<AttachmentList
|
||||
compact
|
||||
media={status.get('media_attachments')}
|
||||
/>
|
||||
);
|
||||
} else if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
|
||||
if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
|
||||
const attachment = status.getIn(['media_attachments', 0]);
|
||||
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
|
||||
|
||||
return (
|
||||
<Bundle fetchComponent={Audio} loading={this.renderLoadingAudioPlayer} >
|
||||
{Component => (
|
||||
<Component
|
||||
src={attachment.get('url')}
|
||||
alt={description}
|
||||
lang={language}
|
||||
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
|
||||
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
|
||||
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
|
||||
accentColor={attachment.getIn(['meta', 'colors', 'accent'])}
|
||||
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
|
||||
width={this.props.cachedMediaWidth}
|
||||
height={110}
|
||||
cacheWidth={this.props.cacheMediaWidth}
|
||||
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
|
||||
sensitive={status.get('sensitive')}
|
||||
blurhash={attachment.get('blurhash')}
|
||||
visible={this.state.showMedia}
|
||||
onToggleVisibility={this.handleToggleMediaVisibility}
|
||||
quote={quote}
|
||||
/>
|
||||
)}
|
||||
</Bundle>
|
||||
);
|
||||
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||
const attachment = status.getIn(['media_attachments', 0]);
|
||||
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
|
||||
return (
|
||||
<Bundle fetchComponent={Audio} loading={this.renderLoadingAudioPlayer} >
|
||||
{Component => (
|
||||
<Component
|
||||
src={attachment.get('url')}
|
||||
alt={description}
|
||||
lang={language}
|
||||
poster={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
|
||||
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
|
||||
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
|
||||
accentColor={attachment.getIn(['meta', 'colors', 'accent'])}
|
||||
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
|
||||
width={this.props.cachedMediaWidth}
|
||||
height={110}
|
||||
cacheWidth={this.props.cacheMediaWidth}
|
||||
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
|
||||
sensitive={status.get('sensitive')}
|
||||
blurhash={attachment.get('blurhash')}
|
||||
visible={this.state.showMedia}
|
||||
onToggleVisibility={this.handleToggleMediaVisibility}
|
||||
quote={quote}
|
||||
/>
|
||||
)}
|
||||
</Bundle>
|
||||
);
|
||||
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||
const attachment = status.getIn(['media_attachments', 0]);
|
||||
const description = attachment.getIn(['translation', 'description']) || attachment.get('description');
|
||||
|
||||
return (
|
||||
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer}>
|
||||
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
|
||||
{Component => (
|
||||
<Component
|
||||
preview={attachment.get('preview_url')}
|
||||
frameRate={attachment.getIn(['meta', 'original', 'frame_rate'])}
|
||||
aspectRatio={`${attachment.getIn(['meta', 'original', 'width'])} / ${attachment.getIn(['meta', 'original', 'height'])}`}
|
||||
blurhash={attachment.get('blurhash')}
|
||||
src={attachment.get('url')}
|
||||
alt={description}
|
||||
lang={language}
|
||||
inline
|
||||
sensitive={status.get('sensitive')}
|
||||
onOpenVideo={this.handleOpenVideo}
|
||||
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
|
||||
visible={quote ? this.state.showQuoteMedia : this.state.showMedia}
|
||||
onToggleVisibility={quote ? this.handleToggleQuoteMediaVisibility : this.handleToggleMediaVisibility}
|
||||
visible={this.state.showMedia}
|
||||
onToggleVisibility={this.handleToggleMediaVisibility}
|
||||
quote={quote}
|
||||
/>
|
||||
)}
|
||||
@ -605,15 +613,15 @@ class Status extends ImmutablePureComponent {
|
||||
onOpenMedia={this.handleOpenMedia}
|
||||
cacheWidth={this.props.cacheMediaWidth}
|
||||
defaultWidth={this.props.cachedMediaWidth}
|
||||
visible={quote ? this.state.showQuoteMedia : this.state.showMedia}
|
||||
onToggleVisibility={quote ? this.handleToggleQuoteMediaVisibility : this.handleToggleMediaVisibility}
|
||||
visible={this.state.showMedia}
|
||||
onToggleVisibility={this.handleToggleMediaVisibility}
|
||||
quote={quote}
|
||||
/>
|
||||
)}
|
||||
</Bundle>
|
||||
);
|
||||
}
|
||||
} else if (status.get('spoiler_text').length === 0 && status.get('card') && !this.props.muted) {
|
||||
} else if (status.get('spoiler_text').length === 0 && status.get('card')) {
|
||||
return (
|
||||
<Card
|
||||
onOpenMedia={this.handleOpenMedia}
|
||||
@ -624,8 +632,7 @@ class Status extends ImmutablePureComponent {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
return null;
|
||||
};
|
||||
|
||||
const statusAvatar = (status, account) => {
|
||||
@ -670,6 +677,14 @@ class Status extends ImmutablePureComponent {
|
||||
<RelativeTimestamp timestamp={status.get('created_at')} />{status.get('edited_at') && <abbr title={intl.formatMessage(messages.edited, { date: intl.formatDate(status.get('edited_at'), { hour12: false, year: 'numeric', month: 'short', day: '2-digit', hour: '2-digit', minute: '2-digit' }) })}> *</abbr>}
|
||||
</a>
|
||||
|
||||
{/* <a onClick={this.handleAccountClick} href={`/@${status.getIn(['account', 'acct'])}`} title={status.getIn(['account', 'acct'])} className='status__display-name' target='_blank' rel='noopener noreferrer'>
|
||||
<div className='status__avatar'>
|
||||
{statusAvatar}
|
||||
</div>
|
||||
|
||||
<DisplayName account={status.get('account')} />
|
||||
</a> */}
|
||||
|
||||
{identity(status, account, false)}
|
||||
</div>
|
||||
|
||||
@ -683,7 +698,7 @@ class Status extends ImmutablePureComponent {
|
||||
onCollapsedToggle={this.handleCollapsedToggle}
|
||||
/>
|
||||
|
||||
{media(status)}
|
||||
{media}
|
||||
|
||||
{quote(status, this.props.muted, quoteMuted, this.handleQuoteClick, this.handleExpandedQuoteToggle, identity, media, this.context.router, contextType)}
|
||||
|
||||
@ -696,4 +711,4 @@ class Status extends ImmutablePureComponent {
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(injectIntl(Status));
|
||||
export default injectIntl(Status);
|
@ -33,7 +33,7 @@ const messages = defineMessages({
|
||||
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
|
||||
quote: { id: 'status.quote', defaultMessage: 'Quote' },
|
||||
cannot_quote: { id: 'status.cannot_quote', defaultMessage: 'This post cannot be quoted' },
|
||||
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
|
||||
favourite: { id: 'status.favourite', defaultMessage: 'Favorite' },
|
||||
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
|
||||
removeBookmark: { id: 'status.remove_bookmark', defaultMessage: 'Remove bookmark' },
|
||||
open: { id: 'status.open', defaultMessage: 'Expand this status' },
|
||||
|
@ -57,7 +57,7 @@ const messages = defineMessages({
|
||||
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
||||
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
|
||||
redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
|
||||
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
|
||||
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favorites and boosts will be lost, and replies to the original post will be orphaned.' },
|
||||
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
||||
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||
quoteConfirm: { id: 'confirmations.quote.confirm', defaultMessage: 'Quote' },
|
||||
@ -307,7 +307,7 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
|
||||
modalProps: {
|
||||
type,
|
||||
accountId: status.getIn(['account', 'id']),
|
||||
url: status.get('url'),
|
||||
url: status.get('uri'),
|
||||
},
|
||||
}));
|
||||
},
|
||||
|
@ -10,6 +10,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import { Avatar } from 'mastodon/components/avatar';
|
||||
import { Badge, AutomatedBadge, GroupBadge } from 'mastodon/components/badge';
|
||||
import Button from 'mastodon/components/button';
|
||||
import { FollowersCounter, FollowingCounter, StatusesCounter } from 'mastodon/components/counters';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
@ -48,7 +49,7 @@ const messages = defineMessages({
|
||||
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
|
||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
||||
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
|
||||
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
|
||||
followed_tags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' },
|
||||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
||||
@ -370,16 +371,18 @@ class Header extends ImmutablePureComponent {
|
||||
const acct = isLocal && domain ? `${account.get('acct')}@${domain}` : account.get('acct');
|
||||
const isIndexable = !account.get('noindex');
|
||||
|
||||
let badge;
|
||||
const badges = [];
|
||||
|
||||
if (account.get('bot')) {
|
||||
badge = (<div className='account-role bot'><FormattedMessage id='account.badges.bot' defaultMessage='Automated' /></div>);
|
||||
badges.push(<AutomatedBadge key='bot-badge' />);
|
||||
} else if (account.get('group')) {
|
||||
badge = (<div className='account-role group'><FormattedMessage id='account.badges.group' defaultMessage='Group' /></div>);
|
||||
} else {
|
||||
badge = null;
|
||||
badges.push(<GroupBadge key='group-badge' />);
|
||||
}
|
||||
|
||||
account.get('roles', []).forEach((role) => {
|
||||
badges.push(<Badge key={`role-badge-${role.get('id')}`} label={<span>{role.get('name')}</span>} domain={domain} />);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={classNames('account__header', { inactive: !!account.get('moved') })} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
{!(suspended || hidden || account.get('moved')) && account.getIn(['relationship', 'requested_by']) && <FollowRequestNoteContainer account={account} />}
|
||||
@ -414,13 +417,19 @@ class Header extends ImmutablePureComponent {
|
||||
|
||||
<div className='account__header__tabs__name'>
|
||||
<h1>
|
||||
<span dangerouslySetInnerHTML={displayNameHtml} /> {badge}
|
||||
<span dangerouslySetInnerHTML={displayNameHtml} />
|
||||
<small>
|
||||
<span>@{acct}</span> {lockedIcon}
|
||||
</small>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
{badges.length > 0 && (
|
||||
<div className='account__header__badges'>
|
||||
{badges}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!(suspended || hidden) && (
|
||||
<div className='account__header__extra'>
|
||||
<div className='account__header__bio' ref={this.setRef}>
|
||||
|
@ -128,7 +128,11 @@ export default class MediaItem extends ImmutablePureComponent {
|
||||
<div className='media-gallery__gifv'>
|
||||
{content}
|
||||
|
||||
{label && <span className='media-gallery__gifv__label'>{label}</span>}
|
||||
{label && (
|
||||
<div className='media-gallery__item__badges'>
|
||||
<span className='media-gallery__gifv__label'>{label}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
modalProps: {
|
||||
type: 'follow',
|
||||
accountId: account.get('id'),
|
||||
url: account.get('url'),
|
||||
url: account.get('uri'),
|
||||
},
|
||||
}));
|
||||
},
|
||||
|
@ -475,6 +475,7 @@ class Audio extends PureComponent {
|
||||
const progress = Math.min((currentTime / duration) * 100, 100);
|
||||
|
||||
let warning;
|
||||
|
||||
if (sensitive) {
|
||||
warning = <FormattedMessage id='status.sensitive_warning' defaultMessage='Sensitive content' />;
|
||||
} else {
|
||||
@ -520,7 +521,10 @@ class Audio extends PureComponent {
|
||||
|
||||
<div className={classNames('spoiler-button', { 'spoiler-button--hidden': revealed || editable })}>
|
||||
<button type='button' className='spoiler-button__overlay' onClick={this.toggleReveal}>
|
||||
<span className='spoiler-button__overlay__label'>{warning}</span>
|
||||
<span className='spoiler-button__overlay__label'>
|
||||
{warning}
|
||||
<span className='spoiler-button__overlay__action'><FormattedMessage id='status.media.show' defaultMessage='Click to show' /></span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
@ -86,7 +86,6 @@ class Bookmarks extends ImmutablePureComponent {
|
||||
onClick={this.handleHeaderClick}
|
||||
pinned={pinned}
|
||||
multiColumn={multiColumn}
|
||||
showBackButton
|
||||
/>
|
||||
|
||||
<StatusList
|
||||
|
@ -12,7 +12,7 @@ const messages = defineMessages({
|
||||
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned posts' },
|
||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
||||
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
|
||||
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
|
||||
followed_tags: { id: 'navigation_bar.followed_tags', defaultMessage: 'Followed hashtags' },
|
||||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
||||
|
@ -291,13 +291,12 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
autoFocus={autoFocus}
|
||||
lang={this.props.lang}
|
||||
>
|
||||
<EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />
|
||||
|
||||
<div className='compose-form__modifiers'>
|
||||
<UploadFormContainer />
|
||||
<PollFormContainer />
|
||||
</div>
|
||||
</AutosuggestTextarea>
|
||||
<EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />
|
||||
|
||||
<div className='compose-form__buttons-wrapper'>
|
||||
<div className='compose-form__buttons'>
|
||||
|
@ -8,7 +8,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import { Avatar } from '../../../components/avatar';
|
||||
import { IconButton } from '../../../components/icon_button';
|
||||
|
||||
import ActionBar from './action_bar';
|
||||
|
||||
@ -21,23 +20,27 @@ export default class NavigationBar extends ImmutablePureComponent {
|
||||
};
|
||||
|
||||
render () {
|
||||
const username = this.props.account.get('acct')
|
||||
return (
|
||||
<div className='navigation-bar'>
|
||||
<Link to={`/@${this.props.account.get('acct')}`}>
|
||||
<span style={{ display: 'none' }}>{this.props.account.get('acct')}</span>
|
||||
<Link to={`/@${username}`}>
|
||||
<span style={{ display: 'none' }}>{username}</span>
|
||||
<Avatar account={this.props.account} size={46} />
|
||||
</Link>
|
||||
|
||||
<div className='navigation-bar__profile'>
|
||||
<Link to={`/@${this.props.account.get('acct')}`}>
|
||||
<strong className='navigation-bar__profile-account'>@{this.props.account.get('acct')}</strong>
|
||||
</Link>
|
||||
<span>
|
||||
<Link to={`/@${username}`}>
|
||||
<strong className='navigation-bar__profile-account'>@{username}</strong>
|
||||
</Link>
|
||||
</span>
|
||||
|
||||
<a href='/settings/profile' className='navigation-bar__profile-edit'><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a>
|
||||
<span>
|
||||
<a href='/settings/profile' className='navigation-bar__profile-edit'><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className='navigation-bar__actions'>
|
||||
<IconButton className='close' title='' icon='close' onClick={this.props.onClose} />
|
||||
<ActionBar account={this.props.account} onLogout={this.props.onLogout} />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -139,10 +139,6 @@ class Search extends PureComponent {
|
||||
this.setState({ expanded: false, selectedOption: -1 });
|
||||
};
|
||||
|
||||
findTarget = () => {
|
||||
return this.searchForm;
|
||||
};
|
||||
|
||||
handleHashtagClick = () => {
|
||||
const { router } = this.context;
|
||||
const { value, onClickSearchResult } = this.props;
|
||||
|
@ -1,10 +1,13 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { Blurhash } from 'mastodon/components/blurhash';
|
||||
import { accountsCountRenderer } from 'mastodon/components/hashtag';
|
||||
import { RelativeTimestamp } from 'mastodon/components/relative_timestamp';
|
||||
import { ShortNumber } from 'mastodon/components/short_number';
|
||||
import { Skeleton } from 'mastodon/components/skeleton';
|
||||
|
||||
@ -13,10 +16,15 @@ export default class Story extends PureComponent {
|
||||
static propTypes = {
|
||||
url: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
lang: PropTypes.string,
|
||||
publisher: PropTypes.string,
|
||||
publishedAt: PropTypes.string,
|
||||
author: PropTypes.string,
|
||||
sharedTimes: PropTypes.number,
|
||||
thumbnail: PropTypes.string,
|
||||
thumbnailDescription: PropTypes.string,
|
||||
blurhash: PropTypes.string,
|
||||
expanded: PropTypes.bool,
|
||||
};
|
||||
|
||||
state = {
|
||||
@ -26,23 +34,23 @@ export default class Story extends PureComponent {
|
||||
handleImageLoad = () => this.setState({ thumbnailLoaded: true });
|
||||
|
||||
render () {
|
||||
const { url, title, publisher, sharedTimes, thumbnail, blurhash } = this.props;
|
||||
const { expanded, url, title, lang, publisher, author, publishedAt, sharedTimes, thumbnail, thumbnailDescription, blurhash } = this.props;
|
||||
|
||||
const { thumbnailLoaded } = this.state;
|
||||
|
||||
return (
|
||||
<a className='story' href={url} target='blank' rel='noopener'>
|
||||
<a className={classNames('story', { expanded })} href={url} target='blank' rel='noopener'>
|
||||
<div className='story__details'>
|
||||
<div className='story__details__publisher'>{publisher ? publisher : <Skeleton width={50} />}</div>
|
||||
<div className='story__details__title'>{title ? title : <Skeleton />}</div>
|
||||
<div className='story__details__shared'>{typeof sharedTimes === 'number' ? <ShortNumber value={sharedTimes} renderer={accountsCountRenderer} /> : <Skeleton width={100} />}</div>
|
||||
<div className='story__details__publisher'>{publisher ? <span lang={lang}>{publisher}</span> : <Skeleton width={50} />}{publishedAt && <> · <RelativeTimestamp timestamp={publishedAt} /></>}</div>
|
||||
<div className='story__details__title' lang={lang}>{title ? title : <Skeleton />}</div>
|
||||
<div className='story__details__shared'>{author && <><FormattedMessage id='link_preview.author' defaultMessage='By {name}' values={{ name: <strong>{author}</strong> }} /> · </>}{typeof sharedTimes === 'number' ? <ShortNumber value={sharedTimes} renderer={accountsCountRenderer} /> : <Skeleton width={100} />}</div>
|
||||
</div>
|
||||
|
||||
<div className='story__thumbnail'>
|
||||
{thumbnail ? (
|
||||
<>
|
||||
<div className={classNames('story__thumbnail__preview', { 'story__thumbnail__preview--hidden': thumbnailLoaded })}><Blurhash hash={blurhash} /></div>
|
||||
<img src={thumbnail} onLoad={this.handleImageLoad} alt='' role='presentation' />
|
||||
<img src={thumbnail} onLoad={this.handleImageLoad} alt={thumbnailDescription} title={thumbnailDescription} lang={lang} />
|
||||
</>
|
||||
) : <Skeleton />}
|
||||
</div>
|
||||
|
@ -55,14 +55,19 @@ class Links extends PureComponent {
|
||||
<div className='explore__links'>
|
||||
{banner}
|
||||
|
||||
{isLoading ? (<LoadingIndicator />) : links.map(link => (
|
||||
{isLoading ? (<LoadingIndicator />) : links.map((link, i) => (
|
||||
<Story
|
||||
key={link.get('id')}
|
||||
expanded={i === 0}
|
||||
lang={link.get('language')}
|
||||
url={link.get('url')}
|
||||
title={link.get('title')}
|
||||
publisher={link.get('provider_name')}
|
||||
publishedAt={link.get('published_at')}
|
||||
author={link.get('author_name')}
|
||||
sharedTimes={link.getIn(['history', 0, 'accounts']) * 1 + link.getIn(['history', 1, 'accounts']) * 1}
|
||||
thumbnail={link.get('image')}
|
||||
thumbnailDescription={link.get('image_description')}
|
||||
blurhash={link.get('blurhash')}
|
||||
/>
|
||||
))}
|
||||
|
@ -47,7 +47,7 @@ class Statuses extends PureComponent {
|
||||
return (
|
||||
<>
|
||||
<DismissableBanner id='explore/statuses'>
|
||||
<FormattedMessage id='dismissable_banner.explore_statuses' defaultMessage='These are posts from across the social web that are gaining traction today. Newer posts with more boosts and favourites are ranked higher.' />
|
||||
<FormattedMessage id='dismissable_banner.explore_statuses' defaultMessage='These are posts from across the social web that are gaining traction today. Newer posts with more boosts and favorites are ranked higher.' />
|
||||
</DismissableBanner>
|
||||
|
||||
<StatusList
|
||||
|
@ -18,7 +18,7 @@ import Column from 'mastodon/features/ui/components/column';
|
||||
import { getStatusList } from 'mastodon/selectors';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.favourites', defaultMessage: 'Favourites' },
|
||||
heading: { id: 'column.favourites', defaultMessage: 'Favorites' },
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
@ -74,7 +74,7 @@ class Favourites extends ImmutablePureComponent {
|
||||
const { intl, statusIds, columnId, multiColumn, hasMore, isLoading } = this.props;
|
||||
const pinned = !!columnId;
|
||||
|
||||
const emptyMessage = <FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any favourite posts yet. When you favourite one, it will show up here." />;
|
||||
const emptyMessage = <FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any favorite posts yet. When you favorite one, it will show up here." />;
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)}>
|
||||
@ -86,7 +86,6 @@ class Favourites extends ImmutablePureComponent {
|
||||
onClick={this.handleHeaderClick}
|
||||
pinned={pinned}
|
||||
multiColumn={multiColumn}
|
||||
showBackButton
|
||||
/>
|
||||
|
||||
<StatusList
|
||||
|
@ -61,7 +61,7 @@ class Favourites extends ImmutablePureComponent {
|
||||
);
|
||||
}
|
||||
|
||||
const emptyMessage = <FormattedMessage id='empty_column.favourites' defaultMessage='No one has favourited this post yet. When someone does, they will show up here.' />;
|
||||
const emptyMessage = <FormattedMessage id='empty_column.favourites' defaultMessage='No one has favorited this post yet. When someone does, they will show up here.' />;
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn}>
|
||||
|
@ -32,7 +32,7 @@ const messages = defineMessages({
|
||||
bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
|
||||
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
||||
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
|
||||
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favorites' },
|
||||
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
||||
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' },
|
||||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
||||
|
@ -0,0 +1,79 @@
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
|
||||
import Button from 'mastodon/components/button';
|
||||
import { ShortNumber } from 'mastodon/components/short_number';
|
||||
|
||||
const messages = defineMessages({
|
||||
followHashtag: { id: 'hashtag.follow', defaultMessage: 'Follow hashtag' },
|
||||
unfollowHashtag: { id: 'hashtag.unfollow', defaultMessage: 'Unfollow hashtag' },
|
||||
});
|
||||
|
||||
const usesRenderer = (displayNumber, pluralReady) => (
|
||||
<FormattedMessage
|
||||
id='hashtag.counter_by_uses'
|
||||
defaultMessage='{count, plural, one {{counter} post} other {{counter} posts}}'
|
||||
values={{
|
||||
count: pluralReady,
|
||||
counter: <strong>{displayNumber}</strong>,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const peopleRenderer = (displayNumber, pluralReady) => (
|
||||
<FormattedMessage
|
||||
id='hashtag.counter_by_accounts'
|
||||
defaultMessage='{count, plural, one {{counter} participant} other {{counter} participants}}'
|
||||
values={{
|
||||
count: pluralReady,
|
||||
counter: <strong>{displayNumber}</strong>,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const usesTodayRenderer = (displayNumber, pluralReady) => (
|
||||
<FormattedMessage
|
||||
id='hashtag.counter_by_uses_today'
|
||||
defaultMessage='{count, plural, one {{counter} post} other {{counter} posts}} today'
|
||||
values={{
|
||||
count: pluralReady,
|
||||
counter: <strong>{displayNumber}</strong>,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
export const HashtagHeader = injectIntl(({ tag, intl, disabled, onClick }) => {
|
||||
if (!tag) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [uses, people] = tag.get('history').reduce((arr, day) => [arr[0] + day.get('uses') * 1, arr[1] + day.get('accounts') * 1], [0, 0]);
|
||||
const dividingCircle = <span aria-hidden>{' · '}</span>;
|
||||
|
||||
return (
|
||||
<div className='hashtag-header'>
|
||||
<div className='hashtag-header__header'>
|
||||
<h1>#{tag.get('name')}</h1>
|
||||
<Button onClick={onClick} text={intl.formatMessage(tag.get('following') ? messages.unfollowHashtag : messages.followHashtag)} disabled={disabled} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<ShortNumber value={uses} renderer={usesRenderer} />
|
||||
{dividingCircle}
|
||||
<ShortNumber value={people} renderer={peopleRenderer} />
|
||||
{dividingCircle}
|
||||
<ShortNumber value={tag.getIn(['history', 0, 'uses']) * 1} renderer={usesTodayRenderer} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
HashtagHeader.propTypes = {
|
||||
tag: ImmutablePropTypes.map,
|
||||
disabled: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
intl: PropTypes.object,
|
||||
};
|
@ -1,9 +1,8 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { injectIntl, FormattedMessage, defineMessages } from 'react-intl';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
@ -17,17 +16,12 @@ import { fetchHashtag, followHashtag, unfollowHashtag } from 'mastodon/actions/t
|
||||
import { expandHashtagTimeline, clearTimeline } from 'mastodon/actions/timelines';
|
||||
import Column from 'mastodon/components/column';
|
||||
import ColumnHeader from 'mastodon/components/column_header';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
|
||||
import StatusListContainer from '../ui/containers/status_list_container';
|
||||
|
||||
import { HashtagHeader } from './components/hashtag_header';
|
||||
import ColumnSettingsContainer from './containers/column_settings_container';
|
||||
|
||||
const messages = defineMessages({
|
||||
followHashtag: { id: 'hashtag.follow', defaultMessage: 'Follow hashtag' },
|
||||
unfollowHashtag: { id: 'hashtag.unfollow', defaultMessage: 'Unfollow hashtag' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
hasUnread: state.getIn(['timelines', `hashtag:${props.params.id}${props.params.local ? ':local' : ''}`, 'unread']) > 0,
|
||||
tag: state.getIn(['tags', props.params.id]),
|
||||
@ -48,7 +42,6 @@ class HashtagTimeline extends PureComponent {
|
||||
hasUnread: PropTypes.bool,
|
||||
tag: ImmutablePropTypes.map,
|
||||
multiColumn: PropTypes.bool,
|
||||
intl: PropTypes.object,
|
||||
};
|
||||
|
||||
handlePin = () => {
|
||||
@ -188,27 +181,11 @@ class HashtagTimeline extends PureComponent {
|
||||
};
|
||||
|
||||
render () {
|
||||
const { hasUnread, columnId, multiColumn, tag, intl } = this.props;
|
||||
const { hasUnread, columnId, multiColumn, tag } = this.props;
|
||||
const { id, local } = this.props.params;
|
||||
const pinned = !!columnId;
|
||||
const { signedIn } = this.context.identity;
|
||||
|
||||
let followButton;
|
||||
|
||||
if (tag) {
|
||||
const following = tag.get('following');
|
||||
|
||||
const classes = classNames('column-header__button', {
|
||||
active: following,
|
||||
});
|
||||
|
||||
followButton = (
|
||||
<button className={classes} onClick={this.handleFollow} disabled={!signedIn} title={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)} aria-label={intl.formatMessage(following ? messages.unfollowHashtag : messages.followHashtag)}>
|
||||
<Icon id={following ? 'user-times' : 'user-plus'} fixedWidth className='column-header__icon' />
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} label={`#${id}`}>
|
||||
<ColumnHeader
|
||||
@ -220,13 +197,14 @@ class HashtagTimeline extends PureComponent {
|
||||
onClick={this.handleHeaderClick}
|
||||
pinned={pinned}
|
||||
multiColumn={multiColumn}
|
||||
extraButton={followButton}
|
||||
showBackButton
|
||||
>
|
||||
{columnId && <ColumnSettingsContainer columnId={columnId} />}
|
||||
</ColumnHeader>
|
||||
|
||||
<StatusListContainer
|
||||
prepend={pinned ? null : <HashtagHeader tag={tag} disabled={!signedIn} onClick={this.handleFollow} />}
|
||||
alwaysPrepend
|
||||
trackScroll={!pinned}
|
||||
scrollKey={`hashtag_timeline-${columnId}`}
|
||||
timelineId={`hashtag:${id}${local ? ':local' : ''}`}
|
||||
@ -245,4 +223,4 @@ class HashtagTimeline extends PureComponent {
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(injectIntl(HashtagTimeline));
|
||||
export default connect(mapStateToProps)(HashtagTimeline);
|
||||
|
@ -22,7 +22,7 @@ export const ExplorePrompt = () => (
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id='home.explore_prompt.body'
|
||||
defaultMessage="Your home feed will have a mix of posts from the hashtags you've chosen to follow, the people you've chosen to follow, and the posts they boost. It's looking pretty quiet right now, so how about:"
|
||||
defaultMessage="Your home feed will have a mix of posts from the hashtags you've chosen to follow, the people you've chosen to follow, and the posts they boost. If that feels too quiet, you may want to:"
|
||||
/>
|
||||
</p>
|
||||
|
||||
|
@ -1,15 +1,23 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { throttle, escapeRegExp } from 'lodash';
|
||||
|
||||
import { openModal, closeModal } from 'mastodon/actions/modal';
|
||||
import api from 'mastodon/api';
|
||||
import Button from 'mastodon/components/button';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import { registrationsOpen } from 'mastodon/initial_state';
|
||||
import { registrationsOpen, sso_redirect } from 'mastodon/initial_state';
|
||||
|
||||
const messages = defineMessages({
|
||||
loginPrompt: { id: 'interaction_modal.login.prompt', defaultMessage: 'Domain of your home server, e.g. mastodon.social' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { accountId }) => ({
|
||||
displayNameHtml: state.getIn(['accounts', accountId, 'display_name_html']),
|
||||
@ -19,70 +27,271 @@ const mapStateToProps = (state, { accountId }) => ({
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
onSignupClick() {
|
||||
dispatch(closeModal({
|
||||
modalType: undefined,
|
||||
ignoreFocus: false,
|
||||
}));
|
||||
modalType: undefined,
|
||||
ignoreFocus: false,
|
||||
}));
|
||||
dispatch(openModal({ modalType: 'CLOSED_REGISTRATIONS' }));
|
||||
},
|
||||
});
|
||||
|
||||
class Copypaste extends PureComponent {
|
||||
const PERSISTENCE_KEY = 'mastodon_home';
|
||||
|
||||
const isValidDomain = value => {
|
||||
const url = new URL('https:///path');
|
||||
url.hostname = value;
|
||||
return url.hostname === value;
|
||||
};
|
||||
|
||||
const valueToDomain = value => {
|
||||
// If the user starts typing an URL
|
||||
if (/^https?:\/\//.test(value)) {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
|
||||
// Consider that if there is a path, the URL is more meaningful than a bare domain
|
||||
if (url.pathname.length > 1) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return url.host;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
// If the user writes their full handle including username
|
||||
} else if (value.includes('@')) {
|
||||
if (value.replace(/^@/, '').split('@').length > 2) {
|
||||
return undefined;
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
const addInputToOptions = (value, options) => {
|
||||
value = value.trim();
|
||||
|
||||
if (value.includes('.') && isValidDomain(value)) {
|
||||
return [value].concat(options.filter((x) => x !== value));
|
||||
}
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
class LoginForm extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
value: PropTypes.string,
|
||||
resourceUrl: PropTypes.string,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
copied: false,
|
||||
value: localStorage ? (localStorage.getItem(PERSISTENCE_KEY) || '') : '',
|
||||
expanded: false,
|
||||
selectedOption: -1,
|
||||
isLoading: false,
|
||||
isSubmitting: false,
|
||||
error: false,
|
||||
options: [],
|
||||
networkOptions: [],
|
||||
};
|
||||
|
||||
setRef = c => {
|
||||
this.input = c;
|
||||
};
|
||||
|
||||
handleInputClick = () => {
|
||||
this.setState({ copied: false });
|
||||
this.input.focus();
|
||||
this.input.select();
|
||||
this.input.setSelectionRange(0, this.input.value.length);
|
||||
handleChange = ({ target }) => {
|
||||
this.setState(state => ({ value: target.value, isLoading: true, error: false, options: addInputToOptions(target.value, state.networkOptions) }), () => this._loadOptions());
|
||||
};
|
||||
|
||||
handleButtonClick = () => {
|
||||
const { value } = this.props;
|
||||
navigator.clipboard.writeText(value);
|
||||
this.input.blur();
|
||||
this.setState({ copied: true });
|
||||
this.timeout = setTimeout(() => this.setState({ copied: false }), 700);
|
||||
handleMessage = (event) => {
|
||||
const { resourceUrl } = this.props;
|
||||
|
||||
if (event.origin !== window.origin || event.source !== this.iframeRef.contentWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.data?.type === 'fetchInteractionURL-failure') {
|
||||
this.setState({ isSubmitting: false, error: true });
|
||||
} else if (event.data?.type === 'fetchInteractionURL-success') {
|
||||
if (/^https?:\/\//.test(event.data.template)) {
|
||||
if (localStorage) {
|
||||
localStorage.setItem(PERSISTENCE_KEY, event.data.uri_or_domain);
|
||||
}
|
||||
|
||||
window.location.href = event.data.template.replace('{uri}', encodeURIComponent(resourceUrl));
|
||||
} else {
|
||||
this.setState({ isSubmitting: false, error: true });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this.timeout) clearTimeout(this.timeout);
|
||||
componentDidMount () {
|
||||
window.addEventListener('message', this.handleMessage);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('message', this.handleMessage);
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
const { value } = this.state;
|
||||
|
||||
this.setState({ isSubmitting: true });
|
||||
|
||||
this.iframeRef.contentWindow.postMessage({
|
||||
type: 'fetchInteractionURL',
|
||||
uri_or_domain: value.trim(),
|
||||
}, window.origin);
|
||||
};
|
||||
|
||||
setIFrameRef = (iframe) => {
|
||||
this.iframeRef = iframe;
|
||||
}
|
||||
|
||||
handleFocus = () => {
|
||||
this.setState({ expanded: true });
|
||||
};
|
||||
|
||||
handleBlur = () => {
|
||||
this.setState({ expanded: false });
|
||||
};
|
||||
|
||||
handleKeyDown = (e) => {
|
||||
const { options, selectedOption } = this.state;
|
||||
|
||||
switch(e.key) {
|
||||
case 'ArrowDown':
|
||||
e.preventDefault();
|
||||
|
||||
if (options.length > 0) {
|
||||
this.setState({ selectedOption: Math.min(selectedOption + 1, options.length - 1) });
|
||||
}
|
||||
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
e.preventDefault();
|
||||
|
||||
if (options.length > 0) {
|
||||
this.setState({ selectedOption: Math.max(selectedOption - 1, -1) });
|
||||
}
|
||||
|
||||
break;
|
||||
case 'Enter':
|
||||
e.preventDefault();
|
||||
|
||||
if (selectedOption === -1) {
|
||||
this.handleSubmit();
|
||||
} else if (options.length > 0) {
|
||||
this.setState({ value: options[selectedOption], error: false }, () => this.handleSubmit());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
handleOptionClick = e => {
|
||||
const index = Number(e.currentTarget.getAttribute('data-index'));
|
||||
const option = this.state.options[index];
|
||||
|
||||
e.preventDefault();
|
||||
this.setState({ selectedOption: index, value: option, error: false }, () => this.handleSubmit());
|
||||
};
|
||||
|
||||
_loadOptions = throttle(() => {
|
||||
const { value } = this.state;
|
||||
|
||||
const domain = valueToDomain(value.trim());
|
||||
|
||||
if (typeof domain === 'undefined') {
|
||||
this.setState({ options: [], networkOptions: [], isLoading: false, error: true });
|
||||
return;
|
||||
}
|
||||
|
||||
if (domain.length === 0) {
|
||||
this.setState({ options: [], networkOptions: [], isLoading: false });
|
||||
return;
|
||||
}
|
||||
|
||||
api().get('/api/v1/peers/search', { params: { q: domain } }).then(({ data }) => {
|
||||
if (!data) {
|
||||
data = [];
|
||||
}
|
||||
|
||||
this.setState((state) => ({ networkOptions: data, options: addInputToOptions(state.value, data), isLoading: false }));
|
||||
}).catch(() => {
|
||||
this.setState({ isLoading: false });
|
||||
});
|
||||
}, 200, { leading: true, trailing: true });
|
||||
|
||||
render () {
|
||||
const { value } = this.props;
|
||||
const { copied } = this.state;
|
||||
const { intl } = this.props;
|
||||
const { value, expanded, options, selectedOption, error, isSubmitting } = this.state;
|
||||
const domain = (valueToDomain(value) || '').trim();
|
||||
const domainRegExp = new RegExp(`(${escapeRegExp(domain)})`, 'gi');
|
||||
const hasPopOut = domain.length > 0 && options.length > 0;
|
||||
|
||||
return (
|
||||
<div className={classNames('copypaste', { copied })}>
|
||||
<input
|
||||
type='text'
|
||||
ref={this.setRef}
|
||||
value={value}
|
||||
readOnly
|
||||
onClick={this.handleInputClick}
|
||||
<div className={classNames('interaction-modal__login', { focused: expanded, expanded: hasPopOut, invalid: error })}>
|
||||
|
||||
<iframe
|
||||
ref={this.setIFrameRef}
|
||||
style={{display: 'none'}}
|
||||
src='/remote_interaction_helper'
|
||||
sandbox='allow-scripts allow-same-origin'
|
||||
title='remote interaction helper'
|
||||
/>
|
||||
|
||||
<button className='button' onClick={this.handleButtonClick}>
|
||||
{copied ? <FormattedMessage id='copypaste.copied' defaultMessage='Copied' /> : <FormattedMessage id='copypaste.copy' defaultMessage='Copy' />}
|
||||
</button>
|
||||
<div className='interaction-modal__login__input'>
|
||||
<input
|
||||
ref={this.setRef}
|
||||
type='text'
|
||||
value={value}
|
||||
placeholder={intl.formatMessage(messages.loginPrompt)}
|
||||
aria-label={intl.formatMessage(messages.loginPrompt)}
|
||||
autoFocus
|
||||
onChange={this.handleChange}
|
||||
onFocus={this.handleFocus}
|
||||
onBlur={this.handleBlur}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
autocomplete='off'
|
||||
autocapitalize='off'
|
||||
spellcheck='false'
|
||||
/>
|
||||
|
||||
<Button onClick={this.handleSubmit} disabled={isSubmitting}><FormattedMessage id='interaction_modal.login.action' defaultMessage='Take me home' /></Button>
|
||||
</div>
|
||||
|
||||
{hasPopOut && (
|
||||
<div className='search__popout'>
|
||||
<div className='search__popout__menu'>
|
||||
{options.map((option, i) => (
|
||||
<button key={option} onMouseDown={this.handleOptionClick} data-index={i} className={classNames('search__popout__menu__item', { selected: selectedOption === i })}>
|
||||
{option.split(domainRegExp).map((part, i) => (
|
||||
part.toLowerCase() === domain.toLowerCase() ? (
|
||||
<mark key={i}>
|
||||
{part}
|
||||
</mark>
|
||||
) : (
|
||||
<span key={i}>
|
||||
{part}
|
||||
</span>
|
||||
)
|
||||
))}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class InteractionModal extends PureComponent {
|
||||
const IntlLoginForm = injectIntl(LoginForm);
|
||||
|
||||
class InteractionModal extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
displayNameHtml: PropTypes.string,
|
||||
@ -116,8 +325,8 @@ class InteractionModal extends PureComponent {
|
||||
break;
|
||||
case 'favourite':
|
||||
icon = <Icon id='star' />;
|
||||
title = <FormattedMessage id='interaction_modal.title.favourite' defaultMessage="Favourite {name}'s post" values={{ name }} />;
|
||||
actionDescription = <FormattedMessage id='interaction_modal.description.favourite' defaultMessage='With an account on Mastodon, you can favourite this post to let the author know you appreciate it and save it for later.' />;
|
||||
title = <FormattedMessage id='interaction_modal.title.favourite' defaultMessage="Favorite {name}'s post" values={{ name }} />;
|
||||
actionDescription = <FormattedMessage id='interaction_modal.description.favourite' defaultMessage='With an account on Mastodon, you can favorite this post to let the author know you appreciate it and save it for later.' />;
|
||||
break;
|
||||
case 'follow':
|
||||
icon = <Icon id='user-plus' />;
|
||||
@ -128,15 +337,21 @@ class InteractionModal extends PureComponent {
|
||||
|
||||
let signupButton;
|
||||
|
||||
if (registrationsOpen) {
|
||||
if (sso_redirect) {
|
||||
signupButton = (
|
||||
<a href={signupUrl} className='button button--block button-tertiary'>
|
||||
<a href={sso_redirect} data-method='post' className='link-button'>
|
||||
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
||||
</a>
|
||||
);
|
||||
} else if (registrationsOpen) {
|
||||
signupButton = (
|
||||
<a href={signupUrl} className='link-button'>
|
||||
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
signupButton = (
|
||||
<button className='button button--block button-tertiary' onClick={this.handleSignupClick}>
|
||||
<button className='link-button' onClick={this.handleSignupClick}>
|
||||
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
||||
</button>
|
||||
);
|
||||
@ -146,22 +361,13 @@ class InteractionModal extends PureComponent {
|
||||
<div className='modal-root__modal interaction-modal'>
|
||||
<div className='interaction-modal__lead'>
|
||||
<h3><span className='interaction-modal__icon'>{icon}</span> {title}</h3>
|
||||
<p>{actionDescription} <FormattedMessage id='interaction_modal.preamble' defaultMessage="Since Mastodon is decentralized, you can use your existing account hosted by another Mastodon server or compatible platform if you don't have an account on this one." /></p>
|
||||
<p>{actionDescription} <strong><FormattedMessage id='interaction_modal.sign_in' defaultMessage='You are not logged in to this server. Where is your account hosted?' /></strong></p>
|
||||
</div>
|
||||
|
||||
<div className='interaction-modal__choices'>
|
||||
<div className='interaction-modal__choices__choice'>
|
||||
<h3><FormattedMessage id='interaction_modal.on_this_server' defaultMessage='On this server' /></h3>
|
||||
<a href='/auth/sign_in' className='button button--block'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Login' /></a>
|
||||
{signupButton}
|
||||
</div>
|
||||
<IntlLoginForm resourceUrl={url} />
|
||||
|
||||
<div className='interaction-modal__choices__choice'>
|
||||
<h3><FormattedMessage id='interaction_modal.on_another_server' defaultMessage='On a different server' /></h3>
|
||||
<p><FormattedMessage id='interaction_modal.other_server_instructions' defaultMessage='Copy and paste this URL into the search field of your favourite Mastodon app or the web interface of your Mastodon server.' /></p>
|
||||
<Copypaste value={url} />
|
||||
</div>
|
||||
</div>
|
||||
<p className='hint'><FormattedMessage id='interaction_modal.sign_in_hint' defaultMessage="Tip: That's the website where you signed up. If you don't remember, look for the welcome e-mail in your inbox. You can also enter your full username! (e.g. @Mastodon@mastodon.social)" /></p>
|
||||
<p><FormattedMessage id='interaction_modal.no_account_yet' defaultMessage='Not on Mastodon?' /> {signupButton}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ class KeyboardShortcuts extends ImmutablePureComponent {
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>f</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.favourite' defaultMessage='to favourite' /></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.favourite' defaultMessage='to favorite' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>b</kbd></td>
|
||||
@ -138,7 +138,7 @@ class KeyboardShortcuts extends ImmutablePureComponent {
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>g</kbd>+<kbd>f</kbd></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.favourites' defaultMessage='to open favourites list' /></td>
|
||||
<td><FormattedMessage id='keyboard_shortcuts.favourites' defaultMessage='to open favorites list' /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><kbd>g</kbd>+<kbd>p</kbd></td>
|
||||
|
@ -65,7 +65,7 @@ class Lists extends ImmutablePureComponent {
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.heading)}>
|
||||
<ColumnHeader title={intl.formatMessage(messages.heading)} icon='list-ul' multiColumn={multiColumn} showBackButton />
|
||||
<ColumnHeader title={intl.formatMessage(messages.heading)} icon='list-ul' multiColumn={multiColumn} />
|
||||
|
||||
<NewListForm />
|
||||
|
||||
|
@ -109,7 +109,7 @@ export default class ColumnSettings extends PureComponent {
|
||||
</div>
|
||||
|
||||
<div role='group' aria-labelledby='notifications-favourite'>
|
||||
<span id='notifications-favourite' className='column-settings__section'><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favourites:' /></span>
|
||||
<span id='notifications-favourite' className='column-settings__section'><FormattedMessage id='notifications.column_settings.favourite' defaultMessage='Favorites:' /></span>
|
||||
|
||||
<div className='column-settings__row'>
|
||||
<SettingToggle disabled={browserPermission === 'denied'} prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'favourite']} onChange={onChange} label={alertStr} />
|
||||
|
@ -7,7 +7,7 @@ import { Icon } from 'mastodon/components/icon';
|
||||
|
||||
const tooltips = defineMessages({
|
||||
mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
|
||||
favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Favourites' },
|
||||
favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Favorites' },
|
||||
boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Boosts' },
|
||||
polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' },
|
||||
follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
|
||||
|
@ -20,7 +20,7 @@ import FollowRequestContainer from '../containers/follow_request_container';
|
||||
import Report from './report';
|
||||
|
||||
const messages = defineMessages({
|
||||
favourite: { id: 'notification.favourite', defaultMessage: '{name} favourited your status' },
|
||||
favourite: { id: 'notification.favourite', defaultMessage: '{name} favorited your status' },
|
||||
follow: { id: 'notification.follow', defaultMessage: '{name} followed you' },
|
||||
ownPoll: { id: 'notification.own_poll', defaultMessage: 'Your poll has ended' },
|
||||
poll: { id: 'notification.poll', defaultMessage: 'A poll you have voted in has ended' },
|
||||
@ -198,7 +198,7 @@ class Notification extends ImmutablePureComponent {
|
||||
</div>
|
||||
|
||||
<span title={notification.get('created_at')}>
|
||||
<FormattedMessage id='notification.favourite' defaultMessage='{name} favourited your status' values={{ name: link }} />
|
||||
<FormattedMessage id='notification.favourite' defaultMessage='{name} favorited your status' values={{ name: link }} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
@ -25,7 +25,7 @@ const messages = defineMessages({
|
||||
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' },
|
||||
quote: { id: 'status.quote', defaultMessage: 'Quote' },
|
||||
cannot_quote: { id: 'status.cannot_quote', defaultMessage: 'This post cannot be quoted' },
|
||||
favourite: { id: 'status.favourite', defaultMessage: 'Favourite' },
|
||||
favourite: { id: 'status.favourite', defaultMessage: 'Favorite' },
|
||||
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
||||
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||
quoteConfirm: { id: 'confirmations.quote.confirm', defaultMessage: 'Quote' },
|
||||
@ -95,7 +95,7 @@ class Footer extends ImmutablePureComponent {
|
||||
modalProps: {
|
||||
type: 'reply',
|
||||
accountId: status.getIn(['account', 'id']),
|
||||
url: status.get('url'),
|
||||
url: status.get('uri'),
|
||||
},
|
||||
}));
|
||||
}
|
||||
@ -117,7 +117,7 @@ class Footer extends ImmutablePureComponent {
|
||||
modalProps: {
|
||||
type: 'favourite',
|
||||
accountId: status.getIn(['account', 'id']),
|
||||
url: status.get('url'),
|
||||
url: status.get('uri'),
|
||||
},
|
||||
}));
|
||||
}
|
||||
@ -146,7 +146,7 @@ class Footer extends ImmutablePureComponent {
|
||||
modalProps: {
|
||||
type: 'reblog',
|
||||
accountId: status.getIn(['account', 'id']),
|
||||
url: status.get('url'),
|
||||
url: status.get('uri'),
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ const mapStateToProps = (state, { columnId }) => {
|
||||
const index = columns.findIndex(c => c.get('uuid') === uuid);
|
||||
const onlyMedia = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'public', 'other', 'onlyMedia']);
|
||||
const onlyRemote = (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyRemote']) : state.getIn(['settings', 'public', 'other', 'onlyRemote']);
|
||||
const timelineState = state.getIn(['timelines', `public${onlyMedia ? ':media' : ''}`]);
|
||||
const timelineState = state.getIn(['timelines', `public${onlyRemote ? ':remote' : ''}${onlyMedia ? ':media' : ''}`]);
|
||||
|
||||
return {
|
||||
hasUnread: !!timelineState && timelineState.get('unread') > 0,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user