Merge branch 'kb_development' into kb_migration
This commit is contained in:
commit
74d4062f9d
60 changed files with 647 additions and 514 deletions
|
@ -4,7 +4,7 @@ NODE_ENV=tests
|
|||
LOCAL_DOMAIN=cb6e6126.ngrok.io
|
||||
LOCAL_HTTPS=true
|
||||
# Elasticsearch
|
||||
ES_ENABLED=true
|
||||
ES_ENABLED=false
|
||||
ES_HOST=localhost
|
||||
ES_PORT=9200
|
||||
ES_PREFIX=test
|
||||
|
|
96
.github/workflows/build-container-image.yml
vendored
96
.github/workflows/build-container-image.yml
vendored
|
@ -1,96 +0,0 @@
|
|||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
platforms:
|
||||
required: true
|
||||
type: string
|
||||
use_native_arm64_builder:
|
||||
type: boolean
|
||||
push_to_images:
|
||||
type: string
|
||||
version_prerelease:
|
||||
type: string
|
||||
version_metadata:
|
||||
type: string
|
||||
flavor:
|
||||
type: string
|
||||
tags:
|
||||
type: string
|
||||
labels:
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build-image:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- 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, 'kmycode/mastodon')
|
||||
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 }}
|
||||
flavor: ${{ inputs.flavor }}
|
||||
tags: ${{ inputs.tags }}
|
||||
labels: ${{ inputs.labels }}
|
||||
|
||||
- uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
build-args: |
|
||||
MASTODON_VERSION_PRERELEASE=${{ inputs.version_prerelease }}
|
||||
MASTODON_VERSION_METADATA=${{ inputs.version_metadata }}
|
||||
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
|
41
.github/workflows/build-nightly.yml
vendored
41
.github/workflows/build-nightly.yml
vendored
|
@ -1,41 +0,0 @@
|
|||
name: Build nightly container image
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 2 * * *' # run at 2 AM UTC
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
compute-suffix:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- id: version_vars
|
||||
env:
|
||||
TZ: Etc/UTC
|
||||
run: |
|
||||
echo mastodon_version_prerelease=nightly.$(date +'%Y-%m-%d')>> $GITHUB_OUTPUT
|
||||
outputs:
|
||||
prerelease: ${{ steps.version_vars.outputs.mastodon_version_prerelease }}
|
||||
|
||||
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: |
|
||||
kmycode/mastodon
|
||||
ghcr.io/kmycode/mastodon
|
||||
version_prerelease: ${{ needs.compute-suffix.outputs.prerelease }}
|
||||
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.prerelease }}
|
||||
secrets: inherit
|
41
.github/workflows/build-push-pr.yml
vendored
41
.github/workflows/build-push-pr.yml
vendored
|
@ -1,41 +0,0 @@
|
|||
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@v4
|
||||
- id: version_vars
|
||||
run: |
|
||||
echo mastodon_version_metadata=pr-${{ github.event.pull_request.number }}-$(git rev-parse --short HEAD) >> $GITHUB_OUTPUT
|
||||
outputs:
|
||||
metadata: ${{ steps.version_vars.outputs.mastodon_version_metadata }}
|
||||
|
||||
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_metadata: ${{ needs.compute-suffix.outputs.metadata }}
|
||||
flavor: |
|
||||
latest=auto
|
||||
tags: |
|
||||
type=ref,event=pr
|
||||
secrets: inherit
|
27
.github/workflows/build-releases.yml
vendored
27
.github/workflows/build-releases.yml
vendored
|
@ -1,27 +0,0 @@
|
|||
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
|
||||
# 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=pep440,pattern={{raw}}
|
||||
type=pep440,pattern=v{{major}}.{{minor}}
|
||||
secrets: inherit
|
76
.github/workflows/crowdin-download.yml
vendored
76
.github/workflows/crowdin-download.yml
vendored
|
@ -1,76 +0,0 @@
|
|||
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@v4
|
||||
|
||||
- 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
35
.github/workflows/crowdin-upload.yml
vendored
|
@ -1,35 +0,0 @@
|
|||
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@v4
|
||||
|
||||
- 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
21
.github/workflows/test-image-build.yml
vendored
|
@ -1,21 +0,0 @@
|
|||
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
|
32
INSTALL.md
32
INSTALL.md
|
@ -14,6 +14,8 @@ kmyblueは頻繁にバージョンアップを行います。
|
|||
- バグが含まれていることがあります
|
||||
- 特に最新コミットでは、デバッグ用コードや、`kmy.blue`本番サーバーで動作確認を行うためのコードが含まれている場合があります。ブランチの最新コミットではなく最新タグを取り込むことを強くおすすめします
|
||||
|
||||
Mastodonの最新バージョンでは、`dist`フォルダに`mastodon-streaming@.service`が追加されています。これは現在の一般的な手順書には存在しません。各サービスファイルをコピーするとき、`mastodon-streaming@.service`をコピーし忘れないようにしてください。
|
||||
|
||||
### ElasticSearchを使用する場合
|
||||
|
||||
kmyblueでは、sudachiの使用を前提としています。
|
||||
|
@ -56,5 +58,33 @@ RAILS_ENV=production bin/rails assets:precompile
|
|||
# ElasticSearchを使用する場合
|
||||
RAILS_ENV=production bin/tootctl search deploy
|
||||
|
||||
sudo systemctl start mastodon-web mastodon-streaming@4000 mastodon-sidekiq
|
||||
RAILS_ENV=production bin/tootctl cache clear
|
||||
sudo systemctl start mastodon-web mastodon-streaming mastodon-sidekiq
|
||||
```
|
||||
|
||||
## kmyblueのバージョンをアップデートする
|
||||
|
||||
リリースノートを参照して、自分に必要な作業を特定してください。面倒な場合は毎回全部実行してしまっても問題ありません。(プリコンパイルが失敗する可能性があるのでご注意ください)
|
||||
|
||||
```
|
||||
# Rubyパッケージアップデート
|
||||
bundle intall
|
||||
|
||||
# JSパッケージアップデート
|
||||
yarn install
|
||||
|
||||
# DBマイグレーション
|
||||
RAILS_ENV=production bin/rails db:migrate
|
||||
|
||||
# プリコンパイル
|
||||
# うまくいかない場合(エラーは出ないのにWeb表示が崩れる)はclobberしてからprecompile
|
||||
# それでもうまくいかない場合はsudo systemctl stop mastodon-webしてから試す
|
||||
# それでもうまくいかない場合はサーバーOSを再起動してから試す
|
||||
RAILS_ENV=production bin/rails assets:clobber # プリコンパイルがうまくいかない場合
|
||||
RAILS_ENV=production bin/rails assets:precompile
|
||||
|
||||
# サーバー再起動
|
||||
sudo systemctl restart mastodon-web
|
||||
sudo systemctl restart mastodon-streaming
|
||||
sudo systemctl restart mastodon-sidekiq
|
||||
```
|
||||
|
|
14
README.md
14
README.md
|
@ -19,6 +19,20 @@ INSTALL.mdを参照してください。
|
|||
|
||||
CONTRIBUTING.mdを参照してください。
|
||||
|
||||
## テスト
|
||||
|
||||
```
|
||||
# デバッグ実行(以下のいずれか)
|
||||
foreman start
|
||||
DB_USER=postgres DB_PASS=password foreman start
|
||||
|
||||
# 一部を除く全てのテストを行う
|
||||
RAILS_ENV=test bundle exec rspec spec
|
||||
|
||||
# ElasticSearch連携テストを行う
|
||||
RAILS_ENV=test ES_ENABLED=true RUN_SEARCH_SPECS=true bundle exec rspec spec/search
|
||||
```
|
||||
|
||||
## kmyblueの強み
|
||||
|
||||
### 本家Mastodonへの積極的追従
|
||||
|
|
|
@ -15,6 +15,10 @@ class Api::V1::Statuses::EmojiReactionedByAccountsController < Api::BaseControll
|
|||
private
|
||||
|
||||
def load_accounts
|
||||
return [] unless Setting.enable_emoji_reaction
|
||||
return [] if current_account.nil? && @status.account.emoji_reaction_policy != :allow
|
||||
return [] if current_account.present? && !@status.account.show_emoji_reaction?(current_account)
|
||||
|
||||
scope = default_accounts
|
||||
scope = scope.where.not(account_id: current_account.excluded_from_timeline_account_ids) unless current_account.nil?
|
||||
scope.merge(paginated_emoji_reactions).to_a
|
||||
|
|
|
@ -42,6 +42,7 @@ class Api::V1::Statuses::EmojiReactionsController < Api::BaseController
|
|||
def create_private(emoji)
|
||||
count = EmojiReaction.where(account: current_account, status: @status).count
|
||||
raise Mastodon::ValidationError, I18n.t('reactions.errors.limit_reached') if count >= EmojiReaction::EMOJI_REACTION_PER_ACCOUNT_LIMIT
|
||||
raise Mastodon::ValidationError, I18n.t('reactions.errors.disabled') unless Setting.enable_emoji_reaction
|
||||
|
||||
EmojiReactService.new.call(current_account, @status, emoji)
|
||||
render json: @status, serializer: REST::StatusSerializer
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Settings::Preferences::ReachingController < Settings::Preferences::BaseController
|
||||
private
|
||||
|
||||
def after_update_redirect_path
|
||||
settings_preferences_reaching_path
|
||||
end
|
||||
end
|
|
@ -18,7 +18,7 @@ import Card from '../features/status/components/card';
|
|||
// to use the progress bar to show download progress
|
||||
import Bundle from '../features/ui/components/bundle';
|
||||
import { MediaGallery, Video, Audio } from '../features/ui/util/async-components';
|
||||
import { displayMedia } from '../initial_state';
|
||||
import { displayMedia, enableEmojiReaction, showEmojiReactionOnTimeline } from '../initial_state';
|
||||
|
||||
import { Avatar } from './avatar';
|
||||
import { AvatarOverlay } from './avatar_overlay';
|
||||
|
@ -577,8 +577,8 @@ class Status extends ImmutablePureComponent {
|
|||
let emojiReactionsBar = null;
|
||||
if (!this.props.withoutEmojiReactions && status.get('emoji_reactions')) {
|
||||
const emojiReactions = status.get('emoji_reactions');
|
||||
if (emojiReactions.size > 0) {
|
||||
emojiReactionsBar = <StatusEmojiReactionsBar emojiReactions={emojiReactions} status={status} onEmojiReact={this.props.onEmojiReact} onUnEmojiReact={this.props.onUnEmojiReact} />;
|
||||
if (emojiReactions.size > 0 && enableEmojiReaction) {
|
||||
emojiReactionsBar = <StatusEmojiReactionsBar emojiReactions={emojiReactions} myReactionOnly={!showEmojiReactionOnTimeline} status={status} onEmojiReact={this.props.onEmojiReact} onUnEmojiReact={this.props.onUnEmojiReact} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -622,7 +622,7 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
{(!isCardMediaWithSensitive || !status.get('hidden')) && media}
|
||||
|
||||
{expanded && hashtagBar}
|
||||
{(!status.get('spoiler_text') || expanded) && hashtagBar}
|
||||
|
||||
{emojiReactionsBar}
|
||||
|
||||
|
|
|
@ -10,9 +10,10 @@ import { connect } from 'react-redux';
|
|||
|
||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
|
||||
|
||||
|
||||
import DropdownMenuContainer from '../containers/dropdown_menu_container';
|
||||
import EmojiPickerDropdown from '../features/compose/containers/emoji_picker_dropdown_container';
|
||||
import { bookmarkCategoryNeeded, me } from '../initial_state';
|
||||
import { enableEmojiReaction , bookmarkCategoryNeeded, me } from '../initial_state';
|
||||
|
||||
import { IconButton } from './icon_button';
|
||||
|
||||
|
@ -409,13 +410,16 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
<IconButton className='status__action-bar__button' title={intl.formatMessage(messages.hide)} icon='eye' onClick={this.handleHideClick} />
|
||||
);
|
||||
|
||||
const following = !account.getIn(['other_settings', 'emoji_reaction_must_follower']) || (relationship && relationship.get('following'));
|
||||
const followed = !account.getIn(['other_settings', 'emoji_reaction_must_following']) || (relationship && relationship.get('followed_by'));
|
||||
const denyFromAll = !account.getIn(['other_settings', 'emoji_reaction_deny_from_all']);
|
||||
const emojiReactionPolicy = account.getIn(['other_settings', 'emoji_reaction_policy']) || 'allow';
|
||||
const following = emojiReactionPolicy !== 'following_only' || (relationship && relationship.get('following'));
|
||||
const followed = emojiReactionPolicy !== 'followers_only' || (relationship && relationship.get('followed_by'));
|
||||
const mutual = emojiReactionPolicy !== 'mutuals_only' || (relationship && relationship.get('following') && relationship.get('followed_by'));
|
||||
const outside = emojiReactionPolicy !== 'outside_only' || (relationship && (relationship.get('following') || relationship.get('followed_by')));
|
||||
const denyFromAll = emojiReactionPolicy !== 'block' && emojiReactionPolicy !== 'block';
|
||||
const emojiPickerButton = (
|
||||
<IconButton className='status__action-bar__button' title={intl.formatMessage(messages.emojiReaction)} icon='smile-o' onClick={this.handleEmojiPickInnerButton} />
|
||||
);
|
||||
const emojiPickerDropdown = (writtenByMe || ((denyFromAll) && (following) && (followed))) && (
|
||||
const emojiPickerDropdown = enableEmojiReaction && denyFromAll && (writtenByMe || (following && followed && mutual && outside)) && (
|
||||
<EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} button={emojiPickerButton} />
|
||||
);
|
||||
|
||||
|
|
|
@ -60,6 +60,7 @@ class StatusEmojiReactionsBar extends PureComponent {
|
|||
status: ImmutablePropTypes.map,
|
||||
onEmojiReact: PropTypes.func,
|
||||
onUnEmojiReact: PropTypes.func,
|
||||
myReactionOnly: PropTypes.bool,
|
||||
};
|
||||
|
||||
onEmojiReact = (name) => {
|
||||
|
@ -73,20 +74,23 @@ class StatusEmojiReactionsBar extends PureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { emojiReactions } = this.props;
|
||||
const { emojiReactions, myReactionOnly } = this.props;
|
||||
|
||||
const emojiButtons = Array.from(emojiReactions).filter(emoji => emoji.get('count') !== 0).map((emoji, index) => (
|
||||
<EmojiReactionButton
|
||||
key={index}
|
||||
name={emoji.get('name')}
|
||||
count={emoji.get('count')}
|
||||
me={emoji.get('me')}
|
||||
url={emoji.get('url')}
|
||||
staticUrl={emoji.get('static_url')}
|
||||
domain={emoji.get('domain')}
|
||||
onEmojiReact={this.onEmojiReact}
|
||||
onUnEmojiReact={this.onUnEmojiReact}
|
||||
/>));
|
||||
const emojiButtons = Array.from(emojiReactions)
|
||||
.filter(emoji => emoji.get('count') !== 0)
|
||||
.filter(emoji => !myReactionOnly || emoji.get('me'))
|
||||
.map((emoji, index) => (
|
||||
<EmojiReactionButton
|
||||
key={index}
|
||||
name={emoji.get('name')}
|
||||
count={myReactionOnly ? 1 : emoji.get('count')}
|
||||
me={emoji.get('me')}
|
||||
url={emoji.get('url')}
|
||||
staticUrl={emoji.get('static_url')}
|
||||
domain={emoji.get('domain')}
|
||||
onEmojiReact={this.onEmojiReact}
|
||||
onUnEmojiReact={this.onUnEmojiReact}
|
||||
/>));
|
||||
|
||||
return (
|
||||
<div className='status__emoji-reactions-bar'>
|
||||
|
|
|
@ -10,9 +10,10 @@ import { connect } from 'react-redux';
|
|||
|
||||
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
|
||||
|
||||
|
||||
import { IconButton } from '../../../components/icon_button';
|
||||
import DropdownMenuContainer from '../../../containers/dropdown_menu_container';
|
||||
import { bookmarkCategoryNeeded, me } from '../../../initial_state';
|
||||
import { enableEmojiReaction , bookmarkCategoryNeeded, me } from '../../../initial_state';
|
||||
import EmojiPickerDropdown from '../../compose/containers/emoji_picker_dropdown_container';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -305,10 +306,6 @@ class ActionBar extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
const emojiPickerButton = (
|
||||
<IconButton icon='smile-o' onClick={this.handleEmojiPickInnerButton} title={intl.formatMessage(messages.pickEmoji)} />
|
||||
);
|
||||
|
||||
let replyIcon;
|
||||
if (status.get('in_reply_to_id', null) === null) {
|
||||
replyIcon = 'reply';
|
||||
|
@ -329,13 +326,26 @@ class ActionBar extends PureComponent {
|
|||
reblogTitle = intl.formatMessage(messages.cannot_reblog);
|
||||
}
|
||||
|
||||
const emojiReactionPolicy = account.getIn(['other_settings', 'emoji_reaction_policy']) || 'allow';
|
||||
const following = emojiReactionPolicy !== 'following_only' || (relationship && relationship.get('following'));
|
||||
const followed = emojiReactionPolicy !== 'followers_only' || (relationship && relationship.get('followed_by'));
|
||||
const mutual = emojiReactionPolicy !== 'mutuals_only' || (relationship && relationship.get('following') && relationship.get('followed_by'));
|
||||
const outside = emojiReactionPolicy !== 'outside_only' || (relationship && (relationship.get('following') || relationship.get('followed_by')));
|
||||
const denyFromAll = emojiReactionPolicy !== 'block' && emojiReactionPolicy !== 'block';
|
||||
const emojiPickerButton = (
|
||||
<IconButton icon='smile-o' onClick={this.handleEmojiPickInnerButton} title={intl.formatMessage(messages.pickEmoji)} />
|
||||
);
|
||||
const emojiPickerDropdown = enableEmojiReaction && denyFromAll && (writtenByMe || (following && followed && mutual && outside)) && (
|
||||
<div className='detailed-status__button'><EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} button={emojiPickerButton} /></div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='detailed-status__action-bar'>
|
||||
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className={classNames({ reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className='bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /></div>
|
||||
<div className='detailed-status__button'><EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} button={emojiPickerButton} /></div>
|
||||
{emojiPickerDropdown}
|
||||
|
||||
<div className='detailed-status__action-bar-dropdown'>
|
||||
<DropdownMenuContainer size={18} icon='ellipsis-h' status={status} items={menu} direction='left' title={intl.formatMessage(messages.more)} />
|
||||
|
|
|
@ -13,6 +13,7 @@ import EditedTimestamp from 'mastodon/components/edited_timestamp';
|
|||
import { getHashtagBarForStatus } from 'mastodon/components/hashtag_bar';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
import PictureInPicturePlaceholder from 'mastodon/components/picture_in_picture_placeholder';
|
||||
import { enableEmojiReaction } from 'mastodon/initial_state';
|
||||
|
||||
import { Avatar } from '../../../components/avatar';
|
||||
import { DisplayName } from '../../../components/display_name';
|
||||
|
@ -240,7 +241,8 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
let emojiReactionsBar = null;
|
||||
if (status.get('emoji_reactions')) {
|
||||
const emojiReactions = status.get('emoji_reactions');
|
||||
if (emojiReactions.size > 0) {
|
||||
const emojiReactionPolicy = status.getIn(['account', 'other_settings', 'emoji_reaction_policy']) || 'allow';
|
||||
if (emojiReactions.size > 0 && enableEmojiReaction && emojiReactionPolicy !== 'block') {
|
||||
emojiReactionsBar = <StatusEmojiReactionsBar emojiReactions={emojiReactions} status={status} onEmojiReact={this.props.onEmojiReact} onUnEmojiReact={this.props.onUnEmojiReact} />;
|
||||
}
|
||||
}
|
||||
|
@ -398,7 +400,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
|
||||
{(!isCardMediaWithSensitive || !status.get('hidden')) && media}
|
||||
|
||||
{expanded && hashtagBar}
|
||||
{(!status.get('spoiler_text') || expanded) && hashtagBar}
|
||||
|
||||
{emojiReactionsBar}
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@
|
|||
* @property {boolean} display_media_expand
|
||||
* @property {string} domain
|
||||
* @property {string} dtl_tag
|
||||
* @property {boolean} enable_emoji_reaction
|
||||
* @property {boolean} enable_login_privacy
|
||||
* @property {boolean} enable_dtl_menu
|
||||
* @property {boolean=} expand_spoilers
|
||||
|
@ -75,6 +76,7 @@
|
|||
* @property {string} repository
|
||||
* @property {boolean} search_enabled
|
||||
* @property {boolean} trends_enabled
|
||||
* @property {boolean} show_emoji_reaction_on_timeline
|
||||
* @property {boolean} single_user_mode
|
||||
* @property {string} source_url
|
||||
* @property {string} streaming_api_base_url
|
||||
|
@ -126,6 +128,7 @@ export const displayMedia = getMeta('display_media');
|
|||
export const displayMediaExpand = getMeta('display_media_expand');
|
||||
export const domain = getMeta('domain');
|
||||
export const dtlTag = getMeta('dtl_tag');
|
||||
export const enableEmojiReaction = getMeta('enable_emoji_reaction');
|
||||
export const enableLoginPrivacy = getMeta('enable_login_privacy');
|
||||
export const enableDtlMenu = getMeta('enable_dtl_menu');
|
||||
export const expandSpoilers = getMeta('expand_spoilers');
|
||||
|
@ -142,6 +145,7 @@ export const registrationsOpen = getMeta('registrations_open');
|
|||
export const repository = getMeta('repository');
|
||||
export const searchEnabled = getMeta('search_enabled');
|
||||
export const trendsEnabled = getMeta('trends_enabled');
|
||||
export const showEmojiReactionOnTimeline = getMeta('show_emoji_reaction_on_timeline');
|
||||
export const showTrends = getMeta('show_trends');
|
||||
export const singleUserMode = getMeta('single_user_mode');
|
||||
export const source_url = getMeta('source_url');
|
||||
|
|
|
@ -32,7 +32,11 @@ textarea {
|
|||
color: $primary-text-color;
|
||||
}
|
||||
|
||||
.compose-form .compose-form__warning {
|
||||
.compose-form .compose-form__warning,
|
||||
.reply-indicator__content,
|
||||
.reply-indicator__display-name,
|
||||
.reply-indicator__cancel,
|
||||
.autosuggest-textarea__suggestions__item {
|
||||
color: $ui-base-color;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ class ActivityPub::Activity::Like < ActivityPub::Activity
|
|||
def perform
|
||||
@original_status = status_from_uri(object_uri)
|
||||
|
||||
return if @original_status.nil? || !@original_status.account.local? || delete_arrived_first?(@json['id']) || reject_favourite?
|
||||
return if @original_status.nil? || delete_arrived_first?(@json['id']) || reject_favourite?
|
||||
|
||||
if shortcode.nil?
|
||||
process_favourite
|
||||
|
@ -32,6 +32,8 @@ class ActivityPub::Activity::Like < ActivityPub::Activity
|
|||
end
|
||||
|
||||
def process_emoji_reaction
|
||||
return if !@original_status.account.local? && !Setting.receive_other_servers_emoji_reaction
|
||||
|
||||
if emoji_tag.present?
|
||||
return if emoji_tag['id'].blank? || emoji_tag['name'].blank? || emoji_tag['icon'].blank? || emoji_tag['icon']['url'].blank?
|
||||
|
||||
|
@ -106,10 +108,10 @@ class ActivityPub::Activity::Like < ActivityPub::Activity
|
|||
end
|
||||
|
||||
def write_stream(emoji_reaction)
|
||||
emoji_group = @original_status.emoji_reactions_grouped_by_name
|
||||
emoji_group = @original_status.emoji_reactions_grouped_by_name(nil, force: true)
|
||||
.find { |reaction_group| reaction_group['name'] == emoji_reaction.name && (!reaction_group.key?(:domain) || reaction_group['domain'] == emoji_reaction.custom_emoji&.domain) }
|
||||
emoji_group['status_id'] = @original_status.id.to_s
|
||||
DeliveryEmojiReactionWorker.perform_async(render_emoji_reaction(emoji_group), @original_status.id, emoji_reaction.account_id)
|
||||
DeliveryEmojiReactionWorker.perform_async(render_emoji_reaction(emoji_group), @original_status.id, emoji_reaction.account_id) if @original_status.local? || Setting.streaming_other_servers_emoji_reaction
|
||||
end
|
||||
|
||||
def render_emoji_reaction(emoji_group)
|
||||
|
|
|
@ -149,7 +149,7 @@ class ActivityPub::Activity::Undo < ActivityPub::Activity
|
|||
emoji_group = { 'name' => emoji_reaction.name, 'count' => 0, 'account_ids' => [], 'status_id' => @original_status.id.to_s }
|
||||
emoji_group['domain'] = emoji_reaction.custom_emoji.domain if emoji_reaction.custom_emoji
|
||||
end
|
||||
DeliveryEmojiReactionWorker.perform_async(render_emoji_reaction(emoji_group), @original_status.id, emoji_reaction.account_id)
|
||||
DeliveryEmojiReactionWorker.perform_async(render_emoji_reaction(emoji_group), @original_status.id, emoji_reaction.account_id) if @original_status.local? || Setting.streaming_other_servers_emoji_reaction
|
||||
end
|
||||
|
||||
def render_emoji_reaction(emoji_group)
|
||||
|
|
|
@ -61,19 +61,19 @@ class SearchQueryTransformer < Parslet::Transform
|
|||
when 'library'
|
||||
[StatusesIndex]
|
||||
else
|
||||
[PublicStatusesIndex, StatusesIndex]
|
||||
@options[:current_account].user&.setting_use_public_index ? [PublicStatusesIndex, StatusesIndex] : [StatusesIndex]
|
||||
end
|
||||
end
|
||||
|
||||
def default_filter
|
||||
definition_should = [
|
||||
default_should1,
|
||||
default_should2,
|
||||
non_publicly_searchable,
|
||||
public_index,
|
||||
searchability_limited,
|
||||
]
|
||||
definition_should << searchability_public if %i(public).include?(@searchability)
|
||||
definition_should << searchability_private if %i(public unlisted private).include?(@searchability)
|
||||
definition_should << searchable_by_me if %i(public unlisted private direct).include?(@searchability)
|
||||
definition_should << self_posts if %i(public unlisted private direct).exclude?(@searchability)
|
||||
|
||||
{
|
||||
bool: {
|
||||
|
@ -83,7 +83,7 @@ class SearchQueryTransformer < Parslet::Transform
|
|||
}
|
||||
end
|
||||
|
||||
def default_should1
|
||||
def public_index
|
||||
{
|
||||
term: {
|
||||
_index: PublicStatusesIndex.index_name,
|
||||
|
@ -91,24 +91,7 @@ class SearchQueryTransformer < Parslet::Transform
|
|||
}
|
||||
end
|
||||
|
||||
def default_should2
|
||||
{
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
term: { _index: StatusesIndex.index_name },
|
||||
},
|
||||
{
|
||||
term: {
|
||||
searchable_by: @options[:current_account].id,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
def non_publicly_searchable
|
||||
def searchable_by_me
|
||||
{
|
||||
bool: {
|
||||
must: [
|
||||
|
@ -128,6 +111,21 @@ class SearchQueryTransformer < Parslet::Transform
|
|||
}
|
||||
end
|
||||
|
||||
def self_posts
|
||||
{
|
||||
bool: {
|
||||
must: [
|
||||
{
|
||||
term: { _index: StatusesIndex.index_name },
|
||||
},
|
||||
{
|
||||
term: { account_id: @options[:current_account].id },
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
def searchability_public
|
||||
{
|
||||
bool: {
|
||||
|
@ -349,6 +347,6 @@ class SearchQueryTransformer < Parslet::Transform
|
|||
end
|
||||
|
||||
rule(query: sequence(:clauses)) do
|
||||
Query.new(clauses, current_account: current_account)
|
||||
Query.new(clauses, current_account: current_account, searchability: searchability)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,6 +6,8 @@ class StatusCacheHydrator
|
|||
end
|
||||
|
||||
def hydrate(account_id)
|
||||
account = Account.find(account_id)
|
||||
|
||||
# The cache of the serialized hash is generated by the fan-out-on-write service
|
||||
payload = Rails.cache.fetch("fan-out/#{@status.id}") { InlineRenderer.render(@status, nil, :status) }
|
||||
|
||||
|
@ -33,6 +35,7 @@ class StatusCacheHydrator
|
|||
payload[:reblog][:bookmarked] = Bookmark.where(account_id: account_id, status_id: @status.reblog_of_id).exists?
|
||||
payload[:reblog][:pinned] = StatusPin.where(account_id: account_id, status_id: @status.reblog_of_id).exists? if @status.reblog.account_id == account_id
|
||||
payload[:reblog][:filtered] = payload[:filtered]
|
||||
payload[:reblog][:emoji_reactions] = @status.emoji_reactions_grouped_by_name(account)
|
||||
|
||||
if payload[:reblog][:poll]
|
||||
if @status.reblog.account_id == account_id
|
||||
|
@ -56,6 +59,7 @@ class StatusCacheHydrator
|
|||
payload[:filtered] = CustomFilter
|
||||
.apply_cached_filters(CustomFilter.cached_filters_for(account_id), @status, following?(account_id))
|
||||
.map { |filter| serialized_filter(filter) }
|
||||
payload[:emoji_reactions] = @status.emoji_reactions_grouped_by_name(account)
|
||||
|
||||
if payload[:poll]
|
||||
payload[:poll][:voted] = @status.account_id == account_id
|
||||
|
|
|
@ -363,28 +363,37 @@ class Account < ApplicationRecord
|
|||
false
|
||||
end
|
||||
|
||||
def emoji_reactions_must_following?
|
||||
return false unless Setting.enable_block_emoji_reaction_settings
|
||||
return user&.settings&.[]('emoji_reactions.must_be_following') || false if user.present?
|
||||
return settings['emoji_reactions_must_be_following'] || false if settings.present?
|
||||
def emoji_reaction_policy
|
||||
return settings['emoji_reaction_policy']&.to_sym || :allow if settings.present? && user.nil?
|
||||
return :allow if user.nil?
|
||||
return :block if local? && !Setting.enable_emoji_reaction
|
||||
|
||||
false
|
||||
user.setting_emoji_reaction_policy&.to_sym
|
||||
end
|
||||
|
||||
def emoji_reactions_must_follower?
|
||||
return false unless Setting.enable_block_emoji_reaction_settings
|
||||
return user&.settings&.[]('emoji_reactions.must_be_follower') || false if user.present?
|
||||
return settings['emoji_reaction_must_be_follower'] || false if settings.present?
|
||||
def show_emoji_reaction?(account)
|
||||
return false unless Setting.enable_emoji_reaction
|
||||
|
||||
false
|
||||
case emoji_reaction_policy
|
||||
when :block
|
||||
false
|
||||
when :following_only
|
||||
account.present? && (id == account.id || following?(account))
|
||||
when :followers_only
|
||||
account.present? && (id == account.id || followed_by?(account))
|
||||
when :mutuals_only
|
||||
account.present? && (id == account.id || mutual?(account))
|
||||
when :outside_only
|
||||
account.present? && (id == account.id || following?(account) || followed_by?(account))
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def emoji_reactions_deny_from_all?
|
||||
return false unless Setting.enable_block_emoji_reaction_settings
|
||||
return user&.settings&.[]('emoji_reactions.deny_from_all') || false if user.present?
|
||||
return settings['emoji_reaction_deny_from_all'] || false if settings.present?
|
||||
def allow_emoji_reaction?(account)
|
||||
return false if account.nil?
|
||||
|
||||
false
|
||||
show_emoji_reaction?(account)
|
||||
end
|
||||
|
||||
def public_settings
|
||||
|
@ -398,17 +407,27 @@ class Account < ApplicationRecord
|
|||
'translatable_private' => translatable_private?,
|
||||
'link_preview' => link_preview?,
|
||||
}
|
||||
if Setting.enable_block_emoji_reaction_settings
|
||||
if Setting.enable_emoji_reaction
|
||||
config = config.merge({
|
||||
'emoji_reaction_must_following' => emoji_reactions_must_following?,
|
||||
'emoji_reaction_must_follower' => emoji_reactions_must_follower?,
|
||||
'emoji_reaction_deny_from_all' => emoji_reactions_deny_from_all?,
|
||||
'emoji_reaction_policy' => emoji_reaction_policy,
|
||||
})
|
||||
end
|
||||
config = config.merge(settings) if settings.present?
|
||||
config
|
||||
end
|
||||
|
||||
def public_settings_for_local
|
||||
config = public_settings
|
||||
|
||||
unless Setting.enable_emoji_reaction
|
||||
config = config.merge({
|
||||
'emoji_reaction_policy' => :block,
|
||||
})
|
||||
end
|
||||
|
||||
config
|
||||
end
|
||||
|
||||
def previous_strikes_count
|
||||
strikes.where(overruled_at: nil).count
|
||||
end
|
||||
|
|
|
@ -211,6 +211,10 @@ module AccountInteractions
|
|||
other_account.following?(self)
|
||||
end
|
||||
|
||||
def mutual?(other_account)
|
||||
following?(other_account) && followed_by?(other_account)
|
||||
end
|
||||
|
||||
def blocking?(other_account)
|
||||
block_relationships.where(target_account: other_account).exists?
|
||||
end
|
||||
|
|
|
@ -43,6 +43,14 @@ module HasUserSettings
|
|||
settings['web.hide_recent_emojis']
|
||||
end
|
||||
|
||||
def setting_enable_emoji_reaction
|
||||
settings['web.enable_emoji_reaction']
|
||||
end
|
||||
|
||||
def setting_show_emoji_reaction_on_timeline
|
||||
settings['web.show_emoji_reaction_on_timeline']
|
||||
end
|
||||
|
||||
def setting_default_sensitive
|
||||
settings['default_sensitive']
|
||||
end
|
||||
|
@ -75,6 +83,10 @@ module HasUserSettings
|
|||
false
|
||||
end
|
||||
|
||||
def setting_emoji_reaction_policy
|
||||
settings['emoji_reaction_policy']
|
||||
end
|
||||
|
||||
def setting_unfollow_modal
|
||||
settings['web.unfollow_modal']
|
||||
end
|
||||
|
@ -204,7 +216,15 @@ module HasUserSettings
|
|||
end
|
||||
|
||||
def setting_default_searchability
|
||||
settings['default_searchability'] || 'private'
|
||||
settings['default_searchability'] || 'direct'
|
||||
end
|
||||
|
||||
def setting_default_searchability_of_search
|
||||
settings['default_searchability_of_search']
|
||||
end
|
||||
|
||||
def setting_use_public_index
|
||||
settings['use_public_index']
|
||||
end
|
||||
|
||||
def setting_disallow_unlisted_public_searchability
|
||||
|
|
|
@ -18,7 +18,7 @@ class EmojiReaction < ApplicationRecord
|
|||
include Paginable
|
||||
|
||||
EMOJI_REACTION_LIMIT = 32_767
|
||||
EMOJI_REACTION_PER_ACCOUNT_LIMIT = 3
|
||||
EMOJI_REACTION_PER_ACCOUNT_LIMIT = ENV.fetch('EMOJI_REACTION_PER_ACCOUNT_LIMIT', 3)
|
||||
|
||||
update_index('statuses', :status)
|
||||
|
||||
|
|
|
@ -37,12 +37,14 @@ class Form::AdminSettings
|
|||
status_page_url
|
||||
captcha_enabled
|
||||
ng_words
|
||||
enable_block_emoji_reaction_settings
|
||||
hide_local_users_for_anonymous
|
||||
post_hash_tags_max
|
||||
sensitive_words
|
||||
sensitive_words_for_full
|
||||
authorized_fetch
|
||||
receive_other_servers_emoji_reaction
|
||||
streaming_other_servers_emoji_reaction
|
||||
enable_emoji_reaction
|
||||
).freeze
|
||||
|
||||
INTEGER_KEYS = %i(
|
||||
|
@ -64,9 +66,11 @@ class Form::AdminSettings
|
|||
noindex
|
||||
require_invite_text
|
||||
captcha_enabled
|
||||
enable_block_emoji_reaction_settings
|
||||
hide_local_users_for_anonymous
|
||||
authorized_fetch
|
||||
receive_other_servers_emoji_reaction
|
||||
streaming_other_servers_emoji_reaction
|
||||
enable_emoji_reaction
|
||||
).freeze
|
||||
|
||||
UPLOAD_KEYS = %i(
|
||||
|
|
|
@ -349,14 +349,25 @@ class Status < ApplicationRecord
|
|||
update_status_stat!(status_referred_by_count: [public_send(:status_referred_by_count) + diff, 0].max)
|
||||
end
|
||||
|
||||
def emoji_reactions_grouped_by_name(account = nil)
|
||||
def emoji_reactions_grouped_by_name(account = nil, **options)
|
||||
return [] if account.present? && !self.account.show_emoji_reaction?(account)
|
||||
return [] if account.nil? && !options[:force] && self.account.emoji_reaction_policy != :allow
|
||||
|
||||
(Oj.load(status_stat&.emoji_reactions || '', mode: :strict) || []).tap do |emoji_reactions|
|
||||
if account.present?
|
||||
remove_emoji_reactions = []
|
||||
emoji_reactions.each do |emoji_reaction|
|
||||
emoji_reaction['me'] = emoji_reaction['account_ids'].include?(account.id.to_s)
|
||||
emoji_reaction['account_ids'] -= account.excluded_from_timeline_account_ids.map(&:to_s)
|
||||
|
||||
accounts = Account.where(id: emoji_reaction['account_ids'], silenced_at: nil, suspended_at: nil)
|
||||
accounts = accounts.where('domain IS NULL OR domain NOT IN (?)', account.excluded_from_timeline_domains) if account.excluded_from_timeline_domains.size.positive?
|
||||
emoji_reaction['account_ids'] = accounts.pluck(:id).map(&:to_s)
|
||||
|
||||
emoji_reaction['count'] = emoji_reaction['account_ids'].size
|
||||
remove_emoji_reactions << emoji_reaction if emoji_reaction['count'] <= 0
|
||||
end
|
||||
emoji_reactions - remove_emoji_reactions
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ class Trends::Tags < Trends::Base
|
|||
}
|
||||
|
||||
def register(status, at_time = Time.now.utc)
|
||||
return unless !status.reblog? && status.public_visibility? && !status.account.silenced?
|
||||
return unless !status.reblog? && %i(public public_unlisted login).include?(status.visibility.to_sym) && !status.account.silenced?
|
||||
|
||||
status.tags.each do |tag|
|
||||
add(tag, status.account_id, at_time) if tag.usable?
|
||||
|
|
|
@ -26,6 +26,8 @@ class UserSettings
|
|||
setting :stay_privacy, default: false
|
||||
setting :default_reblog_privacy, default: nil
|
||||
setting :default_searchability, default: :direct, in: %w(public private direct limited)
|
||||
setting :default_searchability_of_search, default: :public, in: %w(public private direct limited)
|
||||
setting :use_public_index, default: true
|
||||
setting :disallow_unlisted_public_searchability, default: false
|
||||
setting :public_post_to_unlisted, default: false
|
||||
setting :reject_public_unlisted_subscription, default: false
|
||||
|
@ -34,6 +36,7 @@ class UserSettings
|
|||
setting :reaction_deck, default: nil
|
||||
setting :stop_emoji_reaction_streaming, default: false
|
||||
setting :emoji_reaction_streaming_notify_impl2, default: false
|
||||
setting :emoji_reaction_policy, default: :allow, in: %w(allow outside_only followers_only following_only mutuals_only block)
|
||||
setting :unsafe_limited_distribution, default: false
|
||||
setting :dtl_force_with_tag, default: :none, in: %w(full searchability none)
|
||||
setting :dtl_force_subscribable, default: false
|
||||
|
@ -52,6 +55,8 @@ class UserSettings
|
|||
setting :enable_login_privacy, default: false
|
||||
setting :enable_dtl_menu, default: false
|
||||
setting :hide_recent_emojis, default: false
|
||||
setting :enable_emoji_reaction, default: true
|
||||
setting :show_emoji_reaction_on_timeline, default: true
|
||||
setting :reblog_modal, default: false
|
||||
setting :unfollow_modal, default: true
|
||||
setting :reduce_motion, default: false
|
||||
|
@ -80,12 +85,6 @@ class UserSettings
|
|||
setting :must_be_following_dm, default: false
|
||||
end
|
||||
|
||||
namespace :emoji_reactions do
|
||||
setting :must_be_follower, default: false
|
||||
setting :must_be_following, default: false
|
||||
setting :deny_from_all, default: false
|
||||
end
|
||||
|
||||
def initialize(original_hash)
|
||||
@original_hash = original_hash || {}
|
||||
end
|
||||
|
|
|
@ -48,6 +48,8 @@ class InitialStateSerializer < ActiveModel::Serializer
|
|||
store[:display_media] = object.current_account.user.setting_display_media
|
||||
store[:display_media_expand] = object.current_account.user.setting_display_media_expand
|
||||
store[:expand_spoilers] = object.current_account.user.setting_expand_spoilers
|
||||
store[:enable_emoji_reaction] = object.current_account.user.setting_enable_emoji_reaction
|
||||
store[:show_emoji_reaction_on_timeline] = object.current_account.user.setting_show_emoji_reaction_on_timeline
|
||||
store[:enable_login_privacy] = object.current_account.user.setting_enable_login_privacy
|
||||
store[:enable_dtl_menu] = object.current_account.user.setting_enable_dtl_menu
|
||||
store[:hide_recent_emojis] = object.current_account.user.setting_hide_recent_emojis
|
||||
|
@ -63,6 +65,7 @@ class InitialStateSerializer < ActiveModel::Serializer
|
|||
store[:display_media] = Setting.display_media
|
||||
store[:reduce_motion] = Setting.reduce_motion
|
||||
store[:use_blurhash] = Setting.use_blurhash
|
||||
store[:enable_emoji_reaction] = Setting.enable_emoji_reaction
|
||||
end
|
||||
|
||||
store[:disabled_account_id] = object.disabled_account.id.to_s if object.disabled_account
|
||||
|
|
|
@ -168,6 +168,6 @@ class REST::AccountSerializer < ActiveModel::Serializer
|
|||
end
|
||||
|
||||
def other_settings
|
||||
object.suspended? ? {} : object.public_settings
|
||||
object.suspended? ? {} : object.public_settings_for_local
|
||||
end
|
||||
end
|
||||
|
|
|
@ -108,7 +108,6 @@ class REST::InstanceSerializer < ActiveModel::Serializer
|
|||
# for third party apps
|
||||
def fedibird_capabilities
|
||||
capabilities = [
|
||||
:emoji_reaction,
|
||||
:kmyblue_visibility_public_unlisted,
|
||||
:enable_wide_emoji,
|
||||
:enable_wide_emoji_reaction,
|
||||
|
@ -126,6 +125,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
|
|||
]
|
||||
|
||||
capabilities << :profile_search unless Chewy.enabled?
|
||||
capabilities << :emoji_reaction if Setting.enable_emoji_reaction
|
||||
|
||||
capabilities
|
||||
end
|
||||
|
|
|
@ -125,6 +125,16 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||
object.emoji_reactions_grouped_by_name(current_user&.account)
|
||||
end
|
||||
|
||||
def emoji_reactions_count
|
||||
if current_user&.account.nil?
|
||||
return 0 unless Setting.enable_emoji_reaction
|
||||
|
||||
object.account.emoji_reaction_policy == :allow ? object.emoji_reactions_count : 0
|
||||
else
|
||||
object.account.show_emoji_reaction?(current_user.account) ? object.emoji_reactions_count : 0
|
||||
end
|
||||
end
|
||||
|
||||
def reactions
|
||||
emoji_reactions.tap do |rs|
|
||||
rs.each do |emoji_reaction|
|
||||
|
|
|
@ -117,7 +117,6 @@ class REST::V1::InstanceSerializer < ActiveModel::Serializer
|
|||
# for third party apps
|
||||
def fedibird_capabilities
|
||||
capabilities = [
|
||||
:emoji_reaction,
|
||||
:kmyblue_visibility_public_unlisted,
|
||||
:enable_wide_emoji,
|
||||
:enable_wide_emoji_reaction,
|
||||
|
@ -135,6 +134,7 @@ class REST::V1::InstanceSerializer < ActiveModel::Serializer
|
|||
]
|
||||
|
||||
capabilities << :profile_search unless Chewy.enabled?
|
||||
capabilities << :emoji_reaction if Setting.enable_emoji_reaction
|
||||
|
||||
capabilities
|
||||
end
|
||||
|
|
|
@ -46,8 +46,10 @@ class EmojiReactService < BaseService
|
|||
status = emoji_reaction.status
|
||||
|
||||
if status.account.local?
|
||||
LocalNotificationWorker.perform_async(status.account_id, emoji_reaction.id, 'EmojiReaction', 'reaction') if status.account.user&.setting_emoji_reaction_streaming_notify_impl2
|
||||
LocalNotificationWorker.perform_async(status.account_id, emoji_reaction.id, 'EmojiReaction', 'emoji_reaction')
|
||||
if status.account.user&.setting_enable_emoji_reaction
|
||||
LocalNotificationWorker.perform_async(status.account_id, emoji_reaction.id, 'EmojiReaction', 'reaction') if status.account.user&.setting_emoji_reaction_streaming_notify_impl2
|
||||
LocalNotificationWorker.perform_async(status.account_id, emoji_reaction.id, 'EmojiReaction', 'emoji_reaction')
|
||||
end
|
||||
elsif status.account.activitypub?
|
||||
ActivityPub::DeliveryWorker.perform_async(build_json(emoji_reaction), emoji_reaction.account_id, status.account.inbox_url)
|
||||
end
|
||||
|
@ -63,7 +65,7 @@ class EmojiReactService < BaseService
|
|||
end
|
||||
|
||||
def write_stream(emoji_reaction)
|
||||
emoji_group = emoji_reaction.status.emoji_reactions_grouped_by_name
|
||||
emoji_group = emoji_reaction.status.emoji_reactions_grouped_by_name(nil, force: true)
|
||||
.find { |reaction_group| reaction_group['name'] == emoji_reaction.name && (!reaction_group.key?(:domain) || reaction_group['domain'] == emoji_reaction.custom_emoji&.domain) }
|
||||
emoji_group['status_id'] = emoji_reaction.status_id.to_s
|
||||
DeliveryEmojiReactionWorker.perform_async(render_emoji_reaction(emoji_group), emoji_reaction.status_id, emoji_reaction.account_id)
|
||||
|
|
|
@ -9,7 +9,6 @@ class NotifyService < BaseService
|
|||
update
|
||||
poll
|
||||
emoji_reaction
|
||||
status
|
||||
warning
|
||||
).freeze
|
||||
|
||||
|
@ -53,18 +52,6 @@ class NotifyService < BaseService
|
|||
@recipient.user.settings['interactions.must_be_following'] && !following_sender?
|
||||
end
|
||||
|
||||
def optional_non_follower_emoji_reaction?
|
||||
emoji_reaction? && @recipient.user.settings['emoji_reactions.must_be_follower'] && !@notification.from_account.following?(@recipient)
|
||||
end
|
||||
|
||||
def optional_non_following_emoji_reaction?
|
||||
emoji_reaction? && @recipient.user.settings['emoji_reactions.must_be_following'] && !following_sender?
|
||||
end
|
||||
|
||||
def emoji_reaction?
|
||||
@notification.type == :emoji_reaction
|
||||
end
|
||||
|
||||
def message?
|
||||
@notification.type == :mention
|
||||
end
|
||||
|
@ -134,8 +121,6 @@ class NotifyService < BaseService
|
|||
blocked ||= optional_non_follower?
|
||||
blocked ||= optional_non_following?
|
||||
blocked ||= optional_non_following_and_direct?
|
||||
blocked ||= optional_non_follower_emoji_reaction?
|
||||
blocked ||= optional_non_following_emoji_reaction?
|
||||
blocked ||= conversation_muted?
|
||||
blocked ||= blocked_mention? if @notification.type == :mention
|
||||
blocked
|
||||
|
|
|
@ -11,7 +11,7 @@ class SearchService < BaseService
|
|||
@offset = options[:type].blank? ? 0 : options[:offset].to_i
|
||||
@resolve = options[:resolve] || false
|
||||
@following = options[:following] || false
|
||||
@searchability = options[:searchability] || 'public'
|
||||
@searchability = options[:searchability] || account&.user&.setting_default_searchability_of_search.to_s || 'public'
|
||||
|
||||
default_results.tap do |results|
|
||||
next if @query.blank? || @limit.zero?
|
||||
|
|
|
@ -22,22 +22,6 @@ class EmojiReactionValidator < ActiveModel::Validator
|
|||
end
|
||||
|
||||
def deny_emoji_reactions?(emoji_reaction)
|
||||
return false unless Setting.enable_block_emoji_reaction_settings
|
||||
return false if emoji_reaction.status.account.user.nil?
|
||||
return false if emoji_reaction.status.account_id == emoji_reaction.account_id
|
||||
|
||||
deny_from_all?(emoji_reaction) || non_follower?(emoji_reaction) || non_following?(emoji_reaction)
|
||||
end
|
||||
|
||||
def deny_from_all?(emoji_reaction)
|
||||
emoji_reaction.status.account.user.settings['emoji_reactions.deny_from_all']
|
||||
end
|
||||
|
||||
def non_following?(emoji_reaction)
|
||||
emoji_reaction.status.account.user.settings['emoji_reactions.must_be_following'] && !emoji_reaction.status.account.following?(emoji_reaction.account)
|
||||
end
|
||||
|
||||
def non_follower?(emoji_reaction)
|
||||
emoji_reaction.status.account.user.settings['emoji_reactions.must_be_follower'] && !emoji_reaction.account.following?(emoji_reaction.status.account)
|
||||
!emoji_reaction.status.account.allow_emoji_reaction?(emoji_reaction.account)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,9 +13,6 @@
|
|||
.fields-group
|
||||
= f.input :post_hash_tags_max, wrapper: :with_label, as: :integer, label: t('admin.ng_words.post_hash_tags_max')
|
||||
|
||||
.fields-group
|
||||
= f.input :enable_block_emoji_reaction_settings, wrapper: :with_label, as: :boolean, label: t('admin.ng_words.enable_block_emoji_reaction_settings')
|
||||
|
||||
.fields-group
|
||||
= f.input :hide_local_users_for_anonymous, wrapper: :with_label, as: :boolean, label: t('admin.ng_words.hide_local_users_for_anonymous')
|
||||
|
||||
|
|
|
@ -29,6 +29,17 @@
|
|||
.fields-group
|
||||
= f.input :noindex, as: :boolean, wrapper: :with_label, label: t('admin.settings.default_noindex.title'), hint: t('admin.settings.default_noindex.desc_html')
|
||||
|
||||
%h4= t('admin.settings.discovery.emoji_reactions')
|
||||
|
||||
.fields-group
|
||||
= f.input :enable_emoji_reaction, as: :boolean, wrapper: :with_label, kmyblue: true, hint: false
|
||||
|
||||
.fields-group
|
||||
= f.input :receive_other_servers_emoji_reaction, as: :boolean, wrapper: :with_label, kmyblue: true
|
||||
|
||||
.fields-group
|
||||
= f.input :streaming_other_servers_emoji_reaction, as: :boolean, wrapper: :with_label, kmyblue: true
|
||||
|
||||
%h4= t('admin.settings.discovery.publish_statistics')
|
||||
|
||||
.fields-group
|
||||
|
|
|
@ -4,9 +4,6 @@
|
|||
- content_for :heading_actions do
|
||||
= link_to t('antennas.new.title'), new_antenna_path, class: 'button'
|
||||
|
||||
.flash-message.alert
|
||||
%strong= t('antennas.beta')
|
||||
|
||||
- if @antennas.empty?
|
||||
.muted-hint.center-text= t 'antennas.index.empty'
|
||||
- else
|
||||
|
|
|
@ -39,6 +39,9 @@
|
|||
|
||||
.fields-group
|
||||
= ff.input :'web.hide_recent_emojis', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_hide_recent_emojis'), hint: false
|
||||
- if Setting.enable_emoji_reaction
|
||||
= ff.input :'web.enable_emoji_reaction', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_enable_emoji_reaction'), hint: I18n.t('simple_form.hints.defaults.setting_enable_emoji_reaction')
|
||||
= ff.input :'web.show_emoji_reaction_on_timeline', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_show_emoji_reaction_on_timeline')
|
||||
|
||||
.fields-group
|
||||
= ff.input :'web.bookmark_category_needed', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_bookmark_category_needed'), hint: I18n.t('simple_form.hints.defaults.setting_bookmark_category_needed')
|
||||
|
|
|
@ -42,11 +42,8 @@
|
|||
= ff.input :'interactions.must_be_follower', wrapper: :with_label, label: I18n.t('simple_form.labels.interactions.must_be_follower')
|
||||
= ff.input :'interactions.must_be_following', wrapper: :with_label, label: I18n.t('simple_form.labels.interactions.must_be_following')
|
||||
= ff.input :'interactions.must_be_following_dm', wrapper: :with_label, label: I18n.t('simple_form.labels.interactions.must_be_following_dm')
|
||||
- if Setting.enable_block_emoji_reaction_settings
|
||||
= ff.input :'emoji_reactions.must_be_follower', kmyblue: true, wrapper: :with_label, label: I18n.t('simple_form.labels.emoji_reactions.must_be_follower')
|
||||
= ff.input :'emoji_reactions.must_be_following', kmyblue: true, wrapper: :with_label, label: I18n.t('simple_form.labels.emoji_reactions.must_be_following')
|
||||
= ff.input :'emoji_reactions.deny_from_all', kmyblue: true, wrapper: :with_label, label: I18n.t('simple_form.labels.emoji_reactions.deny_from_all')
|
||||
|
||||
= f.simple_fields_for :settings, current_user.settings do |ff|
|
||||
.fields-group
|
||||
= ff.input :stop_emoji_reaction_streaming, as: :boolean, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_stop_emoji_reaction_streaming'), hint: I18n.t('simple_form.hints.defaults.setting_stop_emoji_reaction_streaming')
|
||||
- if Setting.enable_emoji_reaction
|
||||
= f.simple_fields_for :settings, current_user.settings do |ff|
|
||||
.fields-group
|
||||
= ff.input :stop_emoji_reaction_streaming, as: :boolean, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_stop_emoji_reaction_streaming'), hint: I18n.t('simple_form.hints.defaults.setting_stop_emoji_reaction_streaming')
|
||||
|
|
|
@ -14,33 +14,16 @@
|
|||
%h4= t 'preferences.posting_defaults'
|
||||
|
||||
.fields-row
|
||||
.fields-group.fields-row__column.fields-row__column-6
|
||||
= ff.input :default_privacy, collection: Status.selectable_visibilities, wrapper: :with_label, include_blank: false, label_method: ->(visibility) { safe_join([I18n.t("statuses.visibilities.#{visibility}"), I18n.t("statuses.visibilities.#{visibility}_long")], ' - ') }, required: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_privacy')
|
||||
|
||||
.fields-group.fields-row__column.fields-row__column-6
|
||||
= ff.input :default_reblog_privacy, collection: Status.selectable_reblog_visibilities, wrapper: :with_label, kmyblue: true, include_blank: false, label_method: lambda { |visibility| safe_join([I18n.t("statuses.visibilities.#{visibility}"), I18n.t("statuses.visibilities.#{visibility}_long")], ' - ') }, required: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_reblog_privacy')
|
||||
|
||||
.fields-row
|
||||
.fields-group.fields-row__column.fields-row__column-6
|
||||
= ff.input :default_searchability, collection: Status.selectable_searchabilities, wrapper: :with_label, kmyblue: true, include_blank: false, label_method: lambda { |searchability| safe_join([I18n.t("statuses.searchabilities.#{searchability}"), I18n.t("statuses.searchabilities.#{searchability}_long")], ' - ') }, required: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_searchability')
|
||||
|
||||
.fields-group.fields-row__column.fields-row__column-6
|
||||
.fields-group.fields-row__column.fields-row__column-12
|
||||
= ff.input :default_language, collection: [nil] + filterable_languages, wrapper: :with_label, label_method: ->(locale) { locale.nil? ? I18n.t('statuses.default_language') : native_locale_name(locale) }, required: false, include_blank: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_language')
|
||||
|
||||
.fields-group
|
||||
= ff.input :stay_privacy, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_stay_privacy')
|
||||
|
||||
.fields-group
|
||||
= ff.input :public_post_to_unlisted, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_public_post_to_unlisted'), hint: I18n.t('simple_form.hints.defaults.setting_public_post_to_unlisted')
|
||||
|
||||
.fields-group
|
||||
= ff.input :disallow_unlisted_public_searchability, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_disallow_unlisted_public_searchability'), hint: I18n.t('simple_form.hints.defaults.setting_disallow_unlisted_public_searchability')
|
||||
|
||||
.fields-group
|
||||
= ff.input :default_sensitive, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_default_sensitive'), hint: I18n.t('simple_form.hints.defaults.setting_default_sensitive')
|
||||
|
||||
.fields-group
|
||||
= ff.input :'web.enable_login_privacy', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_enable_login_privacy'), hint: false
|
||||
- if Setting.enable_emoji_reaction
|
||||
.fields-row
|
||||
.fields-group.fields-row__column.fields-row__column-12
|
||||
= ff.input :emoji_reaction_policy, kmyblue: true, collection: ['allow', 'outside_only', 'followers_only', 'following_only', 'mutuals_only', 'block'], label_method: lambda { |item| safe_join([t("simple_form.labels.defaults.setting_emoji_reaction_policy_items.#{item}")]) }, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li', include_blank: false, wrapper: :with_label, label: I18n.t('simple_form.labels.defaults.setting_emoji_reaction_policy'), hint: false, warning_hint: I18n.t('simple_form.hints.defaults.setting_emoji_reaction_policy')
|
||||
|
||||
- if @dtl_enabled
|
||||
|
||||
|
|
49
app/views/settings/preferences/reaching/show.html.haml
Normal file
49
app/views/settings/preferences/reaching/show.html.haml
Normal file
|
@ -0,0 +1,49 @@
|
|||
- content_for :page_title do
|
||||
= t('settings.preferences')
|
||||
|
||||
- content_for :heading_actions do
|
||||
= button_tag t('generic.save_changes'), class: 'button', form: 'edit_preferences'
|
||||
|
||||
= simple_form_for current_user, url: settings_preferences_reaching_path, html: { method: :put, id: 'edit_preferences' } do |f|
|
||||
= render 'shared/error_messages', object: current_user
|
||||
|
||||
= f.simple_fields_for :settings, current_user.settings do |ff|
|
||||
|
||||
%h4= t 'preferences.visibility'
|
||||
|
||||
.fields-row
|
||||
.fields-group.fields-row__column.fields-row__column-6
|
||||
= ff.input :default_privacy, collection: Status.selectable_visibilities, wrapper: :with_label, include_blank: false, label_method: ->(visibility) { safe_join([I18n.t("statuses.visibilities.#{visibility}"), I18n.t("statuses.visibilities.#{visibility}_long")], ' - ') }, required: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_privacy')
|
||||
|
||||
.fields-group.fields-row__column.fields-row__column-6
|
||||
= ff.input :default_reblog_privacy, collection: Status.selectable_reblog_visibilities, wrapper: :with_label, kmyblue: true, include_blank: false, label_method: lambda { |visibility| safe_join([I18n.t("statuses.visibilities.#{visibility}"), I18n.t("statuses.visibilities.#{visibility}_long")], ' - ') }, required: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_reblog_privacy')
|
||||
|
||||
.fields-group
|
||||
= ff.input :stay_privacy, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_stay_privacy')
|
||||
|
||||
.fields-group
|
||||
= ff.input :public_post_to_unlisted, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_public_post_to_unlisted'), hint: I18n.t('simple_form.hints.defaults.setting_public_post_to_unlisted')
|
||||
|
||||
.fields-group
|
||||
= ff.input :'web.enable_login_privacy', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_enable_login_privacy'), hint: false
|
||||
|
||||
%h4= t 'preferences.searchability'
|
||||
|
||||
.fields-row
|
||||
.fields-group.fields-row__column.fields-row__column-12
|
||||
= ff.input :default_searchability, collection: Status.selectable_searchabilities, wrapper: :with_label, kmyblue: true, include_blank: false, label_method: lambda { |searchability| safe_join([I18n.t("statuses.searchabilities.#{searchability}"), I18n.t("statuses.searchabilities.#{searchability}_long")], ' - ') }, required: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_searchability')
|
||||
|
||||
.fields-group
|
||||
= ff.input :disallow_unlisted_public_searchability, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_disallow_unlisted_public_searchability'), hint: I18n.t('simple_form.hints.defaults.setting_disallow_unlisted_public_searchability')
|
||||
|
||||
%h4= t 'preferences.search'
|
||||
|
||||
.fields-row
|
||||
.fields-group.fields-row__column.fields-row__column-12
|
||||
= ff.input :default_searchability_of_search, collection: Status.selectable_searchabilities, wrapper: :with_label, kmyblue: true, include_blank: false, label_method: lambda { |searchability| safe_join([I18n.t("statuses.searchabilities.#{searchability}"), I18n.t("statuses.searchabilities.#{searchability}_search_long")], ' - ') }, required: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_searchability_of_search')
|
||||
|
||||
.fields-group
|
||||
= ff.input :use_public_index, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_use_public_index')
|
||||
|
||||
.actions
|
||||
= f.button :button, t('generic.save_changes'), type: :submit
|
|
@ -6,12 +6,24 @@ class DeliveryEmojiReactionWorker
|
|||
include Lockable
|
||||
include AccountScope
|
||||
|
||||
def perform(payload_json, status_id, _my_account_id = nil)
|
||||
status = Status.find(status_id.to_i)
|
||||
def perform(payload_json, status_id, reacted_account_id)
|
||||
return unless Setting.enable_emoji_reaction
|
||||
|
||||
status = Status.find(status_id)
|
||||
reacted_account = Account.find(reacted_account_id)
|
||||
|
||||
if status.present?
|
||||
scope_status(status).includes(:user).find_each do |account|
|
||||
redis.publish("timeline:#{account.id}", payload_json) if (account.user.nil? || !account.user&.setting_stop_emoji_reaction_streaming) && redis.exists?("subscribed:timeline:#{account.id}")
|
||||
scope = scope_status(status)
|
||||
|
||||
policy = status.account.emoji_reaction_policy
|
||||
return if policy == :block
|
||||
|
||||
scope.select(:id).merge(policy_scope(status.account, policy)).includes(:user).find_each do |account|
|
||||
next if account.user.present? && (account.user.setting_stop_emoji_reaction_streaming || !account.user.setting_enable_emoji_reaction)
|
||||
next unless redis.exists?("subscribed:timeline:#{account.id}")
|
||||
next if !reacted_account.local? && account.excluded_from_timeline_domains.include?(reacted_account.domain)
|
||||
|
||||
redis.publish("timeline:#{account.id}", payload_json)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -19,4 +31,21 @@ class DeliveryEmojiReactionWorker
|
|||
rescue ActiveRecord::RecordNotFound
|
||||
true
|
||||
end
|
||||
|
||||
def policy_scope(account, policy)
|
||||
case policy
|
||||
when :block
|
||||
Account.where(id: 0)
|
||||
when :mutuals_only
|
||||
account.mutuals.local.or(Account.where(id: account))
|
||||
when :following_only
|
||||
account.following.local.or(Account.where(id: account))
|
||||
when :followers_only
|
||||
account.followers.local.or(Account.where(id: account))
|
||||
when :outside_only
|
||||
account.followers.local.or(Account.where(id: account.following.local)).or(Account.where(id: account))
|
||||
else
|
||||
Account.local
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -597,7 +597,6 @@ en:
|
|||
media_attachments:
|
||||
title: Media attachments
|
||||
ng_words:
|
||||
enable_block_emoji_reaction_settings: Enable block emoji reactions settings for users
|
||||
hide_local_users_for_anonymous: Hide timeline local user posts from anonymous
|
||||
keywords: Reject keywords
|
||||
keywords_hint: The first character of the line is "?". to use regular expressions
|
||||
|
@ -805,6 +804,7 @@ en:
|
|||
desc_html: Affects all users who have not changed this setting themselves
|
||||
title: Opt users out of search engine indexing by default
|
||||
discovery:
|
||||
emoji_reactions: Stamp
|
||||
follow_recommendations: Follow recommendations
|
||||
preamble: Surfacing interesting content is instrumental in onboarding new users who may not know anyone Mastodon. Control how various discovery features work on your server.
|
||||
profile_directory: Profile directory
|
||||
|
@ -1068,7 +1068,6 @@ en:
|
|||
hint_html: If you want to move from another account to this one, here you can create an alias, which is required before you can proceed with moving followers from the old account to this one. This action by itself is <strong>harmless and reversible</strong>. <strong>The account migration is initiated from the old account</strong>.
|
||||
remove: Unlink alias
|
||||
antennas:
|
||||
beta: This function is in beta.
|
||||
contexts:
|
||||
account: Accounts
|
||||
domain: Domains
|
||||
|
@ -1649,6 +1648,10 @@ en:
|
|||
other: Other
|
||||
posting_defaults: Posting defaults
|
||||
public_timelines: Public timelines
|
||||
reaching: Visibility and search
|
||||
search: Search
|
||||
searchability: Searchability of your post
|
||||
visibility: Visibility
|
||||
privacy:
|
||||
hint_html: "<strong>Customize how you want your profile and your posts to be found.</strong> A variety of features in Mastodon can help you reach a wider audience when enabled. Take a moment to review these settings to make sure they fit your use case."
|
||||
privacy: Privacy
|
||||
|
@ -1671,6 +1674,7 @@ en:
|
|||
reactions:
|
||||
errors:
|
||||
banned: Banned reaction from the user
|
||||
disabled: Stamp is disabled on this server
|
||||
duplication: Cannot react same things
|
||||
limit_reached: Limit of different reactions reached
|
||||
unrecognized_emoji: is not a recognized emoji
|
||||
|
@ -1817,12 +1821,16 @@ en:
|
|||
searchabilities:
|
||||
direct: Reactionners
|
||||
direct_long: Reacter of this post can find
|
||||
direct_search_long: You can search you reacted posts only
|
||||
limited: Self only
|
||||
limited_long: Nobody can find, but you can
|
||||
limited_search_long: You can search your posts only
|
||||
private: Followers and reactionners
|
||||
private_long: Your followers and reactionners can find
|
||||
private_search_long: You can search you are following or reacted posts only
|
||||
public: Public
|
||||
public_long: Anyone can find
|
||||
public_search_long: You can search all posts permitted to search
|
||||
show_more: Show more
|
||||
show_newer: Show newer
|
||||
show_older: Show older
|
||||
|
|
|
@ -595,7 +595,6 @@ ja:
|
|||
media_attachments:
|
||||
title: 投稿された画像
|
||||
ng_words:
|
||||
enable_block_emoji_reaction_settings: 各ユーザーにスタンプ機能のブロック設定項目を解放する
|
||||
hide_local_users_for_anonymous: ログインしていない状態でローカルユーザーの投稿をタイムラインから取得できないようにする
|
||||
keywords: 投稿できないキーワード
|
||||
keywords_hint: 行を「?」で始めると、正規表現が使えます
|
||||
|
@ -801,6 +800,7 @@ ja:
|
|||
desc_html: この設定を自分で変更していない全ユーザーに影響します
|
||||
title: デフォルトで検索エンジンによるインデックスを拒否する
|
||||
discovery:
|
||||
emoji_reactions: スタンプ
|
||||
follow_recommendations: おすすめフォロー
|
||||
preamble: Mastodon を知らないユーザーを取り込むには、興味深いコンテンツを浮上させることが重要です。サーバー上で様々なディスカバリー機能がどのように機能するかを制御します。
|
||||
profile_directory: ディレクトリ
|
||||
|
@ -1017,7 +1017,6 @@ ja:
|
|||
hint_html: 他のアカウントからこのアカウントにフォロワーを引き継いで引っ越したい場合、ここでエイリアスを作成しておく必要があります。エイリアス自体は<strong>無害で、取り消す</strong>ことができます。<strong>引っ越しは以前のアカウント側から開始する必要があります</strong>。
|
||||
remove: エイリアスを削除
|
||||
antennas:
|
||||
beta: アンテナ機能はベータ版です。今後、予告なく全データリセット・機能削除を行う場合があります。
|
||||
contexts:
|
||||
account: アカウント
|
||||
domain: ドメイン
|
||||
|
@ -1589,6 +1588,10 @@ ja:
|
|||
other: その他
|
||||
posting_defaults: デフォルトの投稿設定
|
||||
public_timelines: 公開タイムライン
|
||||
reaching: 公開範囲と検索
|
||||
search: 検索
|
||||
searchability: あなたの投稿の検索許可
|
||||
visibility: 公開範囲
|
||||
privacy:
|
||||
hint_html: "<strong>Customize how you want your profile and your posts to be found.</strong> A variety of features in Mastodon can help you reach a wider audience when enabled. Take a moment to review these settings to make sure they fit your use case."
|
||||
privacy: Privacy
|
||||
|
@ -1610,6 +1613,7 @@ ja:
|
|||
reactions:
|
||||
errors:
|
||||
banned: 指定ユーザーからのリアクションは禁止されています
|
||||
disabled: このサーバーではスタンプ機能は無効になっています
|
||||
duplication: 同じリアクションを複数行おうとしました
|
||||
limit_reached: リアクションの種類が上限に達しました
|
||||
unrecognized_emoji: は絵文字として認識されていません
|
||||
|
@ -1750,12 +1754,16 @@ ja:
|
|||
searchabilities:
|
||||
direct: 反応者
|
||||
direct_long: この投稿に反応した人しか検索できません
|
||||
direct_search_long: あなたが反応した投稿のみが検索されます
|
||||
limited: 自分のみ
|
||||
limited_long: この投稿はあなたしか検索できません
|
||||
limited_search_long: あなたの投稿のみが検索されます
|
||||
private: フォロワーと反応者
|
||||
private_long: この投稿はフォロワーと反応者のみが検索できます
|
||||
private_search_long: あなたのフォロー相手またはあなたが反応した投稿のみが検索されます
|
||||
public: 全て
|
||||
public_long: この投稿は誰でも検索できます
|
||||
public_search_long: 検索が許可された全ての投稿が検索できます
|
||||
show_more: もっと見る
|
||||
show_newer: 新しいものを表示
|
||||
show_older: 古いものを表示
|
||||
|
|
|
@ -69,6 +69,8 @@ en:
|
|||
setting_dtl_force_subscribable: Your post can be detected local user's antenna to subscribe deep timeline
|
||||
setting_dtl_force_with_tag: "With using #%{tag} tag, your post settings will be changed forcibly"
|
||||
setting_dtl_menu: Show DTL menu on web
|
||||
setting_emoji_reaction_policy: Even with this setting, users on other servers are free to put their stamp on the post and share it within the same server. If you simply want to remove the stamp from your own screen, you can disable it from the appearance settings
|
||||
setting_enable_emoji_reaction: If turn off, other users still can react your posts
|
||||
setting_use_blurhash: Gradients are based on the colors of the hidden visuals but obfuscate any details
|
||||
setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed
|
||||
username: You can use letters, numbers, and underscores
|
||||
|
@ -96,6 +98,7 @@ en:
|
|||
media_cache_retention_period: Downloaded media files will be deleted after the specified number of days when set to a positive value, and re-downloaded on demand.
|
||||
peers_api_enabled: A list of domain names this server has encountered in the fediverse. No data is included here about whether you federate with a given server, just that your server knows about it. This is used by services that collect statistics on federation in a general sense.
|
||||
profile_directory: The profile directory lists all users who have opted-in to be discoverable.
|
||||
receive_other_servers_emoji_reaction: It can cause load. It is recommended to enable it only when there are few people.
|
||||
require_invite_text: When sign-ups require manual approval, make the “Why do you want to join?” text input mandatory rather than optional
|
||||
site_contact_email: How people can reach you for legal or support inquiries.
|
||||
site_contact_username: How people can reach you on Mastodon.
|
||||
|
@ -104,6 +107,7 @@ en:
|
|||
site_terms: Use your own privacy policy or leave blank to use the default. Can be structured with Markdown syntax.
|
||||
site_title: How people may refer to your server besides its domain name.
|
||||
status_page_url: URL of a page where people can see the status of this server during an outage
|
||||
streaming_other_servers_emoji_reaction: Check your server network capacity. A lot of data streaming.
|
||||
theme: Theme that logged out visitors and new users see.
|
||||
thumbnail: A roughly 2:1 image displayed alongside your server information.
|
||||
timeline_preview: Logged out visitors will be able to browse the most recent public posts available on the server.
|
||||
|
@ -227,7 +231,8 @@ en:
|
|||
setting_default_language: Posting language
|
||||
setting_default_privacy: Posting privacy
|
||||
setting_default_reblog_privacy: Reblogging privacy
|
||||
setting_default_searchability: Searchability
|
||||
setting_default_searchability: Searchability of your own post
|
||||
setting_default_searchability_of_search: Your search setting
|
||||
setting_default_sensitive: Always mark media as sensitive
|
||||
setting_delete_modal: Show confirmation dialog before deleting a post
|
||||
setting_disable_swiping: Disable swiping motions
|
||||
|
@ -240,7 +245,16 @@ en:
|
|||
setting_dtl_force_subscribable: Ignore your dissubscribable setting when using the DTL tag
|
||||
setting_dtl_force_with_tag: Post with DTL tag
|
||||
setting_emoji_reaction_streaming_notify_impl2: Enable stamp notification compat with Nyastodon, Catstodon, glitch-soc
|
||||
setting_enable_emoji_reaction: Use stamp function
|
||||
setting_enable_login_privacy: Enable login visibility
|
||||
setting_emoji_reaction_policy: Stamp receive/display policy
|
||||
setting_emoji_reaction_policy_items:
|
||||
allow: Allow all
|
||||
block: Block
|
||||
followers_only: Followers only
|
||||
following_only: Followings only
|
||||
mutuals_only: Mutuals only
|
||||
outside_only: Followings or followers only
|
||||
setting_expand_spoilers: Always expand posts marked with content warnings
|
||||
setting_hide_followers_count: Hide followers count
|
||||
setting_hide_following_count: Hide following count
|
||||
|
@ -255,6 +269,7 @@ en:
|
|||
setting_reject_unlisted_subscription: Reject sending unlisted posts to Misskey, Calckey
|
||||
setting_send_without_domain_blocks: Send your post to all server with administrator set as rejecting-post-server for protect you [DEPRECATED]
|
||||
setting_show_application: Disclose application used to send posts
|
||||
setting_show_emoji_reaction_on_timeline: Show all stamps on timeline
|
||||
setting_stay_privacy: Not change privacy after post
|
||||
setting_stop_emoji_reaction_streaming: Disable stamp streamings
|
||||
setting_system_font_ui: Use system's default font
|
||||
|
@ -265,6 +280,7 @@ en:
|
|||
setting_unsafe_limited_distribution: Send limit posts with unsafe way to other servers
|
||||
setting_use_blurhash: Show colorful gradients for hidden media
|
||||
setting_use_pending_items: Slow mode
|
||||
setting_use_public_index: Include permitted accounts post to results of search
|
||||
severity: Severity
|
||||
sign_in_token_attempt: Security code
|
||||
title: Title
|
||||
|
@ -298,10 +314,12 @@ en:
|
|||
closed_registrations_message: Custom message when sign-ups are not available
|
||||
content_cache_retention_period: Content cache retention period
|
||||
custom_css: Custom CSS
|
||||
enable_emoji_reaction: Enable stamp function
|
||||
mascot: Custom mascot (legacy)
|
||||
media_cache_retention_period: Media cache retention period
|
||||
peers_api_enabled: Publish list of discovered servers in the API
|
||||
profile_directory: Enable profile directory
|
||||
receive_other_servers_emoji_reaction: Receive stamp between other server users
|
||||
registrations_mode: Who can sign-up
|
||||
require_invite_text: Require a reason to join
|
||||
show_domain_blocks: Show domain blocks
|
||||
|
@ -313,6 +331,7 @@ en:
|
|||
site_terms: Privacy Policy
|
||||
site_title: Server name
|
||||
status_page_url: Status page URL
|
||||
streaming_other_servers_emoji_reaction: Streaming stamp between other server users
|
||||
theme: Default theme
|
||||
thumbnail: Server thumbnail
|
||||
timeline_preview: Allow unauthenticated access to public timelines
|
||||
|
|
|
@ -70,7 +70,9 @@ ja:
|
|||
setting_display_media_show_all: メディアを常に表示する
|
||||
setting_dtl_force_subscribable: 購読拒否設定に関係なく、ディープタイムラインに向けた投稿はアンテナに掲載されます。ディープタイムラインをアンテナ経由で閲覧している人にあなたの発言が届きます
|
||||
setting_dtl_force_with_tag: "ハッシュタグ #%{tag} をつけて投稿するとき、公開範囲と検索許可を強制的に置き換えるかを設定します"
|
||||
setting_emoji_reaction_policy: この設定をしても他のサーバーのユーザーはその投稿に自由にスタンプをつけ、同じサーバーの中で共有できます。単にあなた自身の画面からスタンプを除去したいだけなら、外観設定からスタンプを無効にすることができます
|
||||
setting_emoji_reaction_streaming_notify_impl2: 当該サーバーの独自機能に対応したアプリを利用時に、スタンプ機能を利用できます。動作確認していないため(そもそもそのようなアプリ自体を確認できていないため)正しく動かない場合があります
|
||||
setting_enable_emoji_reaction: この機能を無効にしても、他の人はあなたの投稿にスタンプをつけられます
|
||||
setting_hide_network: フォローとフォロワーの情報がプロフィールページで見られないようにします
|
||||
setting_link_preview: プレビュー生成を停止することは、センシティブなサイトへのリンクを頻繁に投稿する人にも有効かもしれません
|
||||
setting_noai: AI学習への利用を禁止するメタタグをプロフィールページに追加します。ただし実効性があるとは限りません
|
||||
|
@ -106,6 +108,7 @@ ja:
|
|||
media_cache_retention_period: 正の値に設定されている場合、ダウンロードされたメディアファイルは指定された日数の後に削除され、リクエストに応じて再ダウンロードされます。
|
||||
peers_api_enabled: このサーバーが Fediverse で遭遇したドメイン名のリストです。このサーバーが知っているだけで、特定のサーバーと連合しているかのデータは含まれません。これは一般的に Fediverse に関する統計情報を収集するサービスによって使用されます。
|
||||
profile_directory: ディレクトリには、掲載する設定をしたすべてのユーザーが一覧表示されます。
|
||||
receive_other_servers_emoji_reaction: 負荷の原因になります。人が少ない場合にのみ有効にすることをおすすめします。
|
||||
require_invite_text: アカウント登録が承認制の場合、登録の際の申請事由の入力を必須にします
|
||||
site_contact_email: 法律またはサポートに関する問い合わせ先
|
||||
site_contact_username: マストドンでの連絡方法
|
||||
|
@ -114,6 +117,7 @@ ja:
|
|||
site_terms: 独自のプライバシーポリシーを使用するか空白にしてデフォルトのプライバシーポリシーを使用します。Markdownが使えます。
|
||||
site_title: ドメイン名以外でサーバーを参照する方法
|
||||
status_page_url: 障害発生時などにユーザーがサーバーの状態を確認できるページのURL
|
||||
streaming_other_servers_emoji_reaction: ストリーミングサーバーの通信許容量、および各ユーザーの通信容量を必ず確認してください。大量のデータが配信されます。
|
||||
theme: ログインしていない人と新規ユーザーに表示されるテーマ。
|
||||
thumbnail: サーバー情報と共に表示される、アスペクト比が約 2:1 の画像。
|
||||
timeline_preview: ログインしていないユーザーがサーバー上の最新の公開投稿を閲覧できるようにします。
|
||||
|
@ -236,7 +240,8 @@ ja:
|
|||
setting_default_language: 投稿する言語
|
||||
setting_default_privacy: 投稿の公開範囲
|
||||
setting_default_reblog_privacy: BTの公開範囲
|
||||
setting_default_searchability: 投稿の検索を許可する範囲
|
||||
setting_default_searchability: 自分の投稿の検索を許可する範囲
|
||||
setting_default_searchability_of_search: 自分が検索するときの検索許可設定
|
||||
setting_default_sensitive: メディアを常に閲覧注意としてマークする
|
||||
setting_delete_modal: 投稿を削除する前に確認ダイアログを表示する
|
||||
setting_disable_swiping: スワイプでの切り替えを無効にする
|
||||
|
@ -250,7 +255,16 @@ ja:
|
|||
setting_dtl_force_with_tag: DTL参加時の投稿設定
|
||||
setting_dtl_menu: Webクライアントのメニューにディープタイムラインを追加する
|
||||
setting_enable_login_privacy: 公開範囲「ログインユーザーのみ」をWeb UIで選択可能にする
|
||||
setting_emoji_reaction_policy: スタンプ受け入れと表示設定
|
||||
setting_emoji_reaction_policy_items:
|
||||
allow: 全員に許可
|
||||
block: 全員禁止
|
||||
followers_only: フォロワーのみ許可
|
||||
following_only: フォロー中の相手のみ許可
|
||||
mutuals_only: 相互のみ許可
|
||||
outside_only: フォロー中、またはフォロワーのみに許可
|
||||
setting_emoji_reaction_streaming_notify_impl2: Nyastodon, Catstodon, glitch-soc互換のスタンプ機能を有効にする
|
||||
setting_enable_emoji_reaction: スタンプ機能を使用する
|
||||
setting_expand_spoilers: 閲覧注意としてマークされた投稿を常に展開する
|
||||
setting_hide_followers_count: フォロワー数を隠す
|
||||
setting_hide_following_count: フォロー数を隠す
|
||||
|
@ -266,6 +280,7 @@ ja:
|
|||
setting_reject_unlisted_subscription: Misskey系サーバーに「未収載」投稿を「フォロワーのみ」に変換して配送する
|
||||
setting_send_without_domain_blocks: 管理人の設定した配送停止設定を拒否する (非推奨)
|
||||
setting_show_application: 送信したアプリを開示する
|
||||
setting_show_emoji_reaction_on_timeline: タイムライン上に他の人のつけたスタンプを表示する
|
||||
setting_stay_privacy: 投稿時に公開範囲を保存する
|
||||
setting_stop_emoji_reaction_streaming: スタンプのストリーミングを停止する
|
||||
setting_system_font_ui: システムのデフォルトフォントを使う
|
||||
|
@ -276,6 +291,7 @@ ja:
|
|||
setting_unsafe_limited_distribution: 安全でない方法で限定投稿を他サーバーに配信する (非推奨)
|
||||
setting_use_blurhash: 非表示のメディアを色付きのぼかしで表示する
|
||||
setting_use_pending_items: 手動更新モード
|
||||
setting_use_public_index: Mastodonの標準設定によって検索が許可されたアカウントの公開投稿を検索結果に含める
|
||||
severity: 重大性
|
||||
sign_in_token_attempt: セキュリティコード
|
||||
title: タイトル
|
||||
|
@ -309,10 +325,12 @@ ja:
|
|||
closed_registrations_message: アカウント作成を停止している時のカスタムメッセージ
|
||||
content_cache_retention_period: コンテンツキャッシュの保持期間
|
||||
custom_css: カスタムCSS
|
||||
enable_emoji_reaction: スタンプ機能を有効にする
|
||||
mascot: カスタムマスコット(レガシー)
|
||||
media_cache_retention_period: メディアキャッシュの保持期間
|
||||
peers_api_enabled: 発見したサーバーのリストをAPIで公開する
|
||||
profile_directory: ディレクトリを有効にする
|
||||
receive_other_servers_emoji_reaction: 他のサーバーのユーザーが他のサーバーの投稿につけたスタンプを受け入れる
|
||||
registrations_mode: 新規登録が可能な人
|
||||
require_invite_text: 申請事由の入力を必須にする
|
||||
show_domain_blocks: ドメインブロックを表示
|
||||
|
@ -324,6 +342,7 @@ ja:
|
|||
site_terms: プライバシーポリシー
|
||||
site_title: サーバーの名前
|
||||
status_page_url: ステータスページのURL
|
||||
streaming_other_servers_emoji_reaction: 他のサーバーのユーザーが他のサーバーの投稿につけたスタンプをストリーミングする
|
||||
theme: デフォルトテーマ
|
||||
thumbnail: サーバーのサムネイル
|
||||
timeline_preview: 公開タイムラインへの未認証のアクセスを許可する
|
||||
|
|
|
@ -11,6 +11,7 @@ SimpleNavigation::Configuration.run do |navigation|
|
|||
n.item :preferences, safe_join([fa_icon('cog fw'), t('settings.preferences')]), settings_preferences_path, if: -> { current_user.functional? } do |s|
|
||||
s.item :appearance, safe_join([fa_icon('desktop fw'), t('settings.appearance')]), settings_preferences_appearance_path
|
||||
s.item :notifications, safe_join([fa_icon('bell fw'), t('settings.notifications')]), settings_preferences_notifications_path
|
||||
s.item :reaching, safe_join([fa_icon('search fw'), t('preferences.reaching')]), settings_preferences_reaching_path
|
||||
s.item :other, safe_join([fa_icon('cog fw'), t('preferences.other')]), settings_preferences_other_path
|
||||
end
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ namespace :settings do
|
|||
namespace :preferences do
|
||||
resource :appearance, only: [:show, :update], controller: :appearance
|
||||
resource :notifications, only: [:show, :update]
|
||||
resource :reaching, only: [:show, :update], controller: :reaching
|
||||
resource :other, only: [:show, :update], controller: :other
|
||||
end
|
||||
|
||||
|
|
|
@ -38,6 +38,9 @@ defaults: &defaults
|
|||
require_invite_text: false
|
||||
backups_retention_period: 7
|
||||
captcha_enabled: false
|
||||
receive_other_servers_emoji_reaction: false
|
||||
streaming_other_servers_emoji_reaction: false
|
||||
enable_emoji_reaction: true
|
||||
|
||||
development:
|
||||
<<: *defaults
|
||||
|
|
|
@ -12,6 +12,7 @@ require_relative 'feeds'
|
|||
require_relative 'ip_blocks'
|
||||
require_relative 'maintenance'
|
||||
require_relative 'media'
|
||||
require_relative 'ohagi'
|
||||
require_relative 'preview_cards'
|
||||
require_relative 'search'
|
||||
require_relative 'settings'
|
||||
|
@ -65,6 +66,9 @@ module Mastodon::CLI
|
|||
desc 'maintenance SUBCOMMAND ...ARGS', 'Various maintenance utilities'
|
||||
subcommand 'maintenance', Maintenance
|
||||
|
||||
desc 'ohagi SUBCOMMAND ...ARGS', 'Ohagis'
|
||||
subcommand 'ohagi', Ohagi
|
||||
|
||||
option :dry_run, type: :boolean
|
||||
desc 'self-destruct', 'Erase the server from the federation'
|
||||
long_desc <<~LONG_DESC
|
||||
|
|
33
lib/mastodon/cli/ohagi.rb
Normal file
33
lib/mastodon/cli/ohagi.rb
Normal file
|
@ -0,0 +1,33 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'concurrent'
|
||||
require_relative 'base'
|
||||
|
||||
module Mastodon::CLI
|
||||
class Ohagi < Base
|
||||
desc 'good', 'Ohagi is good'
|
||||
def good
|
||||
say('Thanks!', :green)
|
||||
end
|
||||
|
||||
desc 'bad', 'Ohagi is bad'
|
||||
def bad
|
||||
say('Sorry...', :red)
|
||||
end
|
||||
|
||||
desc 'tsubuan', 'Ohagi is tsubuan'
|
||||
def tsubuan
|
||||
say('Thanks! You are knight in shining armor!', :green)
|
||||
end
|
||||
|
||||
desc 'koshian', 'Ohagi is koshian'
|
||||
def koshian
|
||||
say('Let the WAR begin.', :red)
|
||||
end
|
||||
|
||||
desc 'kokuraan', 'Ohagi is kokuraan'
|
||||
def kokuraan
|
||||
say('I hate you.', :yellow)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -3,7 +3,7 @@
|
|||
require 'rails_helper'
|
||||
|
||||
describe SearchQueryTransformer do
|
||||
subject { described_class.new.apply(parser, current_account: account) }
|
||||
subject { described_class.new.apply(parser, current_account: account, searchability: :public) }
|
||||
|
||||
let(:account) { Fabricate(:account) }
|
||||
let(:parser) { SearchQueryParser.new.parse(query) }
|
||||
|
|
177
spec/search/services/statuses_search_service_spec.rb
Normal file
177
spec/search/services/statuses_search_service_spec.rb
Normal file
|
@ -0,0 +1,177 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe StatusesSearchService do
|
||||
describe 'a local user posts with searchability' do
|
||||
subject do
|
||||
described_class.new.call('ohagi', account, limit: 10, searchability: searchability).map(&:id)
|
||||
end
|
||||
|
||||
let(:alice) { Fabricate(:user).account }
|
||||
let(:following) { Fabricate(:user).account }
|
||||
let(:reacted) { Fabricate(:user).account }
|
||||
let(:other) { Fabricate(:user).account }
|
||||
let(:account) { nil }
|
||||
let(:searchability) { :public }
|
||||
let!(:status) { Fabricate(:status, text: 'Hello, ohagi', account: alice, searchability: searchability) }
|
||||
|
||||
before do
|
||||
alice.update!(indexable: true)
|
||||
following.follow!(alice)
|
||||
Fabricate(:favourite, account: reacted, status: status)
|
||||
PublicStatusesIndex.import!
|
||||
StatusesIndex.import!
|
||||
end
|
||||
|
||||
context 'when public searchability' do
|
||||
let(:searchability) { :public }
|
||||
let(:account) { other }
|
||||
|
||||
context 'with other account' do
|
||||
it 'search status' do
|
||||
expect(subject.count).to eq 1
|
||||
expect(subject).to include status.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'with follower' do
|
||||
let(:account) { following }
|
||||
|
||||
it 'search status' do
|
||||
expect(subject.count).to eq 1
|
||||
expect(subject).to include status.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'with reacted user' do
|
||||
let(:account) { reacted }
|
||||
|
||||
it 'search status' do
|
||||
expect(subject.count).to eq 1
|
||||
expect(subject).to include status.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'with self' do
|
||||
let(:account) { alice }
|
||||
|
||||
it 'search status' do
|
||||
expect(subject.count).to eq 1
|
||||
expect(subject).to include status.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when private searchability' do
|
||||
let(:searchability) { :private }
|
||||
let(:account) { other }
|
||||
|
||||
context 'with other account' do
|
||||
it 'search status' do
|
||||
expect(subject.count).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
context 'with follower' do
|
||||
let(:account) { following }
|
||||
|
||||
it 'search status' do
|
||||
expect(subject.count).to eq 1
|
||||
expect(subject).to include status.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'with reacted user' do
|
||||
let(:account) { reacted }
|
||||
|
||||
it 'search status' do
|
||||
expect(subject.count).to eq 1
|
||||
expect(subject).to include status.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'with self' do
|
||||
let(:account) { alice }
|
||||
|
||||
it 'search status' do
|
||||
expect(subject.count).to eq 1
|
||||
expect(subject).to include status.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when direct searchability' do
|
||||
let(:searchability) { :direct }
|
||||
let(:account) { other }
|
||||
|
||||
context 'with other account' do
|
||||
it 'search status' do
|
||||
expect(subject.count).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
context 'with follower' do
|
||||
let(:account) { following }
|
||||
|
||||
it 'search status' do
|
||||
expect(subject.count).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
context 'with reacted user' do
|
||||
let(:account) { reacted }
|
||||
|
||||
it 'search status' do
|
||||
expect(subject.count).to eq 1
|
||||
expect(subject).to include status.id
|
||||
end
|
||||
end
|
||||
|
||||
context 'with self' do
|
||||
let(:account) { alice }
|
||||
|
||||
it 'search status' do
|
||||
expect(subject.count).to eq 1
|
||||
expect(subject).to include status.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when limited searchability' do
|
||||
let(:searchability) { :limited }
|
||||
let(:account) { other }
|
||||
|
||||
context 'with other account' do
|
||||
it 'search status' do
|
||||
expect(subject.count).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
context 'with follower' do
|
||||
let(:account) { following }
|
||||
|
||||
it 'search status' do
|
||||
expect(subject.count).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
context 'with reacted user' do
|
||||
let(:account) { reacted }
|
||||
|
||||
it 'search status' do
|
||||
expect(subject.count).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
context 'with self' do
|
||||
let(:account) { alice }
|
||||
|
||||
it 'search status' do
|
||||
expect(subject.count).to eq 1
|
||||
expect(subject).to include status.id
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Add a link
Reference in a new issue