Merge branch 'kb_development' into kb_migration
|
@ -3,3 +3,8 @@ NODE_ENV=tests
|
||||||
# Federation
|
# Federation
|
||||||
LOCAL_DOMAIN=cb6e6126.ngrok.io
|
LOCAL_DOMAIN=cb6e6126.ngrok.io
|
||||||
LOCAL_HTTPS=true
|
LOCAL_HTTPS=true
|
||||||
|
# Elasticsearch
|
||||||
|
ES_ENABLED=true
|
||||||
|
ES_HOST=localhost
|
||||||
|
ES_PORT=9200
|
||||||
|
ES_PREFIX=test
|
||||||
|
|
2
.github/workflows/test-ruby.yml
vendored
|
@ -113,6 +113,7 @@ jobs:
|
||||||
CAS_ENABLED: true
|
CAS_ENABLED: true
|
||||||
BUNDLE_WITH: 'pam_authentication test'
|
BUNDLE_WITH: 'pam_authentication test'
|
||||||
CI_JOBS: ${{ matrix.ci_job }}/4
|
CI_JOBS: ${{ matrix.ci_job }}/4
|
||||||
|
ES_ENABLED: false
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
@ -192,6 +193,7 @@ jobs:
|
||||||
DISABLE_SIMPLECOV: true
|
DISABLE_SIMPLECOV: true
|
||||||
RAILS_ENV: test
|
RAILS_ENV: test
|
||||||
BUNDLE_WITH: test
|
BUNDLE_WITH: test
|
||||||
|
ES_ENABLED: false
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
|
|
|
@ -1,46 +1,36 @@
|
||||||
# Contributing
|
# CONTRIBUTING
|
||||||
|
|
||||||
Thank you for considering contributing to Mastodon 🐘
|
kmyblueは、コミュニティの意見も聞くには聞きますが導入する・しないは管理人が決定します。
|
||||||
|
|
||||||
You can contribute in the following ways:
|
## バグ報告
|
||||||
|
|
||||||
- Finding and reporting bugs
|
バグについて、最新よりも過去のバージョンへの対応は特別な場合以外は行いません。
|
||||||
- Translating the Mastodon interface into various languages
|
|
||||||
- Contributing code to Mastodon by fixing bugs or implementing features
|
|
||||||
- Improving the documentation
|
|
||||||
|
|
||||||
If your contributions are accepted into Mastodon, you can request to be paid through [our OpenCollective](https://opencollective.com/mastodon).
|
以下のいずれかの方法で報告してください。
|
||||||
|
|
||||||
## Bug reports
|
- [GitHub Issues](https://github.com/kmycode/mastodon/issues)
|
||||||
|
- [kmyblue開発者への連絡](https://kmy.blue/@askyq)
|
||||||
|
- [kmyblue開発者へのメール](https://kmy.blue/about)
|
||||||
|
|
||||||
Bug reports and feature suggestions must use descriptive and concise titles and be submitted to [GitHub Issues](https://github.com/mastodon/mastodon/issues). Please use the search function to make sure that you are not submitting duplicates, and that a similar report or request has not already been resolved or rejected.
|
## 翻訳、プルリクエスト
|
||||||
|
|
||||||
## Translations
|
新しい機能や既存機能の修正については、プルリクエストのためにコードを作成する前に、まずGitHub Issuesで機能の提案を行いkmyblue開発者の考えを聞くことをおすすめします。バグ修正、翻訳、テストコードなどは基本受け入れますが、依存モジュールのバージョンアップについては本家Mastodonよりも先に行かないようにしてください。
|
||||||
|
|
||||||
You can submit translations via [Crowdin](https://crowdin.com/project/mastodon). They are periodically merged into the codebase.
|
プルリクエストのタイトルには、プルリクエストの内容が明確になるようなものを設定してください。
|
||||||
|
|
||||||
[](https://crowdin.com/project/mastodon)
|
### kmyblueの開発方針
|
||||||
|
|
||||||
## Pull requests
|
下記のものに矛盾がなければ、あとは管理人の意向次第です。
|
||||||
|
|
||||||
**Please use clean, concise titles for your pull requests.** Unless the pull request is about refactoring code, updating dependencies or other internal tasks, assume that the person reading the pull request title is not a programmer or Mastodon developer, but instead a Mastodon user or server administrator, and **try to describe your change or fix from their perspective**. We use commit squashing, so the final commit in the main branch will carry the title of the pull request, and commits from the main branch are fed into the changelog. The changelog is separated into [keepachangelog.com categories](https://keepachangelog.com/en/1.0.0/), and while that spec does not prescribe how the entries ought to be named, for easier sorting, start your pull request titles using one of the verbs "Add", "Change", "Deprecate", "Remove", or "Fix" (present tense).
|
- **自分の投稿を見せたくない人に見せない**
|
||||||
|
- **他人の見たくない投稿を見ない**
|
||||||
|
- ただし本家Mastodonで上記原則に矛盾した機能が追加された場合は従う
|
||||||
|
- 画面を騒がしくするような機能(絵文字を大きく表示するなど)は追加しないか、控えめにする。ただし他のソフトウェアにも導入され利用者が多くいる場合などは別途判断して、オプトアウト可能な設定項目とともに追加する
|
||||||
|
- 負荷を著しく上げるような機能はできるだけ追加しない
|
||||||
|
|
||||||
Example:
|
kmyblueが意図的に実装していない機能は、例えば以下のものがあります。詳しい理由が知りたい場合は[この記事を参照するか](https://note.com/kmycode/n/n463410b5e03c)、別途お問い合わせください。もちろん明確な根拠がある場合、あなたはこれに抗議する権利を有しますが、あなたがこのkmyblueをフォークして新しいリポジトリを作るほうがより自由でしょう。
|
||||||
|
|
||||||
| Not ideal | Better |
|
- 引用機能
|
||||||
| ------------------------------------ | ------------------------------------------------------------- |
|
- お気に入り一覧の公開
|
||||||
| Fixed NoMethodError in RemovalWorker | Fix nil error when removing statuses caused by race condition |
|
- ブックマーク分類の公開
|
||||||
|
- 他のサーバーの投稿に対して他のサーバーのアカウントが行った絵文字リアクションの受け入れ
|
||||||
It is not always possible to phrase every change in such a manner, but it is desired.
|
|
||||||
|
|
||||||
**The smaller the set of changes in the pull request is, the quicker it can be reviewed and merged.** Splitting tasks into multiple smaller pull requests is often preferable.
|
|
||||||
|
|
||||||
**Pull requests that do not pass automated checks may not be reviewed**. In particular, you need to keep in mind:
|
|
||||||
|
|
||||||
- Unit and integration tests (rspec, jest)
|
|
||||||
- Code style rules (rubocop, eslint)
|
|
||||||
- Normalization of locale files (i18n-tasks)
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
The [Mastodon documentation](https://docs.joinmastodon.org) is a statically generated site. You can [submit merge requests to mastodon/documentation](https://github.com/mastodon/documentation).
|
|
||||||
|
|
60
INSTALL.md
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
# kmyblueインストール手順
|
||||||
|
|
||||||
|
## 共通の注意事項
|
||||||
|
|
||||||
|
### 必須ソフトウェアのバージョン
|
||||||
|
|
||||||
|
Ruby、ElasticSearch、ImageMagick、PostgreSQLなど必須ソフトウェアのバージョンは、本家Mastodonに準じます。リリースノートに対応する本家Mastodonバージョンが記載されていますので、本家Mastodonのリリースノートから対応するバージョンを探して調べてください。
|
||||||
|
|
||||||
|
### 一般的な注意事項
|
||||||
|
|
||||||
|
kmyblueは頻繁にバージョンアップを行います。
|
||||||
|
|
||||||
|
- 本家Mastodonの開発中のバージョンを平然と取り込みます
|
||||||
|
- バグが含まれていることがあります
|
||||||
|
- 特に最新コミットでは、デバッグ用コードや、`kmy.blue`本番サーバーで動作確認を行うためのコードが含まれている場合があります。ブランチの最新コミットではなく最新タグを取り込むことを強くおすすめします
|
||||||
|
|
||||||
|
### ElasticSearchを使用する場合
|
||||||
|
|
||||||
|
kmyblueでは、sudachiの使用を前提としています。
|
||||||
|
|
||||||
|
下記URLより、ElasticSearchにSudachiプラグインを追加してください。
|
||||||
|
ただし辞書ファイル(sudachi dictionary archive)は手順書で指示されたパスではなく`/etc/elasticsearch/sudachi`に格納してください。
|
||||||
|
|
||||||
|
https://github.com/WorksApplications/elasticsearch-sudachi
|
||||||
|
|
||||||
|
Sudachiインストール終了後、追加で`/etc/elasticsearch/sudachi/config.json`に下記を記述して保存してください。`system_full.dic`を使用する場合は適宜`systemDict`プロパティの内容を置き換えてください。
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"systemDict": "system_core.dic"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 新規インストールの場合
|
||||||
|
|
||||||
|
1. 本家Mastodonとセットアップ手順はほとんど一緒です。kmyblueが独自に必須ソフトウェアを追加したわけではありません。ただしkmyblueはMastodonの開発中コードを取り込んでいるので、Rubyなどのバージョンアップ作業が必要になる場合があります。Mastodon公式のセットアップ手順を盲信せず、画面の指示に従ってインストールを進めてください。CloudFlareを組み合わせてセットアップしたとき、サーバーに接続すると400が出るなどのトラブルが出ることがありますが、大抵はMastodon本家由来のトラブルだと思われるので基本サポートはしません
|
||||||
|
2. ただひとつ差異があります。Gitリポジトリはこのkmyblueに向けてください。`kb_development`ブランチの最新コミットではなく、`kb`で始まる最新のタグを取り込むことを強くおすすめします
|
||||||
|
|
||||||
|
## 本家Mastodonからのマイグレーションの場合
|
||||||
|
|
||||||
|
kmyblueから本家Mastodonに戻りたい場合もあると思いますので、**必ずデータベースのバックアップをとってください**。
|
||||||
|
|
||||||
|
1. kmyblueのリリースノートに、kmyblueバージョンに対応した本家Mastodonのバージョンが記載されています。それを参照して、まず本家Mastodonをそのバージョンまでバージョンアップしてください
|
||||||
|
2. Gitのリモートにkmyblueを追加して、そのままチェックアウトしてください
|
||||||
|
3. データベースのマイグレーションなどを行ってください
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo systemctl stop mastodon-*
|
||||||
|
|
||||||
|
bundle install
|
||||||
|
yarn install
|
||||||
|
RAILS_ENV=production bin/rails db:migrate
|
||||||
|
RAILS_ENV=production bin/rails assets:clobber
|
||||||
|
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
|
||||||
|
```
|
12
README.md
|
@ -6,11 +6,19 @@ kmyblueは[Mastodon](https://github.com/mastodon/mastodon)のフォークです
|
||||||
|
|
||||||
kmyblueはフォーク名であり、同時に[サーバー名](https://kmy.blue)でもあります。以下は特に記述がない限り、フォークとしてのkmyblueをさします。
|
kmyblueはフォーク名であり、同時に[サーバー名](https://kmy.blue)でもあります。以下は特に記述がない限り、フォークとしてのkmyblueをさします。
|
||||||
|
|
||||||
kmyblueは AGPL ライセンスで公開されているため、どなたでも自由にフォークし、このソースコードを元に自分でサーバーを立てて公開することができます。また ActivityPub に参加することもできます。サーバーkmyblueは創作作家向けのものですが、フォークとしてのkmyblueは作者の嫌いな政治に関する過激な話を取り扱うコミュニティ、創作活動の一部(エロ関係含む)または全体を否定するコミュニティなども平等にお使いいただけますし、サーバーkmyblueのルールを適用する必要もありません。
|
kmyblueは AGPL ライセンスで公開されているため、どなたでも自由にフォークし、このソースコードを元に自分でサーバーを立てて公開することができます。また ActivityPub に参加することもできます。確かにサーバーkmyblueは創作作家向けのものですが、フォークとしてのkmyblueは作者と政治的に対立するコミュニティ、創作活動の一部(エロ関係含む)または全体を否定するコミュニティなどにも平等にお使いいただけます。いかなるコミュニティがkmyblueフォークを使用しても、それ自体に作者が抗議することもありません。サーバーkmyblueのルールを適用する必要もなく、「Anyone But Kmyblue」なルールを設定することすら許容されます。
|
||||||
ただしkmyblueにおいてテストコードは飾りでしかないため、不具合が発生しても自己責任になります。既知のバグもいくつかありますし、直す予定のないものも含まれます。
|
ただしkmyblueにおいて**テストコードは飾り**でしかありません。独自機能のテストを記述するだけでなく、本家のテストコードの補強も行っておりますが、確認漏れは必ず発生するものです。不具合が発生しても自己責任になります。既知のバグもいくつかありますし、直す予定のないものも含まれます。
|
||||||
|
|
||||||
テストコード、Lint どちらも動いています。
|
テストコード、Lint どちらも動いています。
|
||||||
|
|
||||||
|
## インストール方法
|
||||||
|
|
||||||
|
INSTALL.mdを参照してください。
|
||||||
|
|
||||||
|
## 開発への参加方法
|
||||||
|
|
||||||
|
CONTRIBUTING.mdを参照してください。
|
||||||
|
|
||||||
## kmyblueの強み
|
## kmyblueの強み
|
||||||
|
|
||||||
### 本家Mastodonへの積極的追従
|
### 本家Mastodonへの積極的追従
|
||||||
|
|
|
@ -1,7 +1,72 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class AccountsIndex < Chewy::Index
|
class AccountsIndex < Chewy::Index
|
||||||
settings index: index_preset(refresh_interval: '30s'), analysis: {
|
DEVELOPMENT_SETTINGS = {
|
||||||
|
filter: {
|
||||||
|
english_stop: {
|
||||||
|
type: 'stop',
|
||||||
|
stopwords: '_english_',
|
||||||
|
},
|
||||||
|
|
||||||
|
english_stemmer: {
|
||||||
|
type: 'stemmer',
|
||||||
|
language: 'english',
|
||||||
|
},
|
||||||
|
|
||||||
|
english_possessive_stemmer: {
|
||||||
|
type: 'stemmer',
|
||||||
|
language: 'possessive_english',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
analyzer: {
|
||||||
|
natural: {
|
||||||
|
tokenizer: 'standard',
|
||||||
|
filter: %w(
|
||||||
|
lowercase
|
||||||
|
asciifolding
|
||||||
|
cjk_width
|
||||||
|
elision
|
||||||
|
english_possessive_stemmer
|
||||||
|
english_stop
|
||||||
|
english_stemmer
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
sudachi_analyzer: {
|
||||||
|
tokenizer: 'standard',
|
||||||
|
filter: %w(
|
||||||
|
lowercase
|
||||||
|
asciifolding
|
||||||
|
cjk_width
|
||||||
|
elision
|
||||||
|
english_possessive_stemmer
|
||||||
|
english_stop
|
||||||
|
english_stemmer
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
|
verbatim: {
|
||||||
|
tokenizer: 'standard',
|
||||||
|
filter: %w(lowercase asciifolding cjk_width),
|
||||||
|
},
|
||||||
|
|
||||||
|
edge_ngram: {
|
||||||
|
tokenizer: 'edge_ngram',
|
||||||
|
filter: %w(lowercase asciifolding cjk_width),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
tokenizer: {
|
||||||
|
edge_ngram: {
|
||||||
|
type: 'edge_ngram',
|
||||||
|
min_gram: 1,
|
||||||
|
max_gram: 15,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
PRODUCTION_SETTINGS = {
|
||||||
filter: {
|
filter: {
|
||||||
english_stop: {
|
english_stop: {
|
||||||
type: 'stop',
|
type: 'stop',
|
||||||
|
@ -77,7 +142,9 @@ class AccountsIndex < Chewy::Index
|
||||||
discard_punctuation: 'true',
|
discard_punctuation: 'true',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}.freeze
|
||||||
|
|
||||||
|
settings index: index_preset(refresh_interval: '30s'), analysis: Rails.env.test? ? DEVELOPMENT_SETTINGS : PRODUCTION_SETTINGS
|
||||||
|
|
||||||
index_scope ::Account.searchable.includes(:account_stat)
|
index_scope ::Account.searchable.includes(:account_stat)
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,19 @@ class PublicStatusesIndex < Chewy::Index
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sudachi_analyzer: {
|
||||||
|
tokenizer: 'standard',
|
||||||
|
filter: %w(
|
||||||
|
lowercase
|
||||||
|
asciifolding
|
||||||
|
cjk_width
|
||||||
|
elision
|
||||||
|
english_possessive_stemmer
|
||||||
|
english_stop
|
||||||
|
english_stemmer
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
hashtag: {
|
hashtag: {
|
||||||
tokenizer: 'keyword',
|
tokenizer: 'keyword',
|
||||||
filter: %w(
|
filter: %w(
|
||||||
|
@ -126,7 +139,7 @@ class PublicStatusesIndex < Chewy::Index
|
||||||
},
|
},
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
settings index: index_preset(refresh_interval: '30s', number_of_shards: 5), analysis: PRODUCTION_SETTINGS
|
settings index: index_preset(refresh_interval: '30s', number_of_shards: 5), analysis: Rails.env.test? ? DEVELOPMENT_SETTINGS : PRODUCTION_SETTINGS
|
||||||
|
|
||||||
index_scope ::Status.unscoped
|
index_scope ::Status.unscoped
|
||||||
.kept
|
.kept
|
||||||
|
|
|
@ -37,6 +37,19 @@ class StatusesIndex < Chewy::Index
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sudachi_analyzer: {
|
||||||
|
tokenizer: 'standard',
|
||||||
|
filter: %w(
|
||||||
|
lowercase
|
||||||
|
asciifolding
|
||||||
|
cjk_width
|
||||||
|
elision
|
||||||
|
english_possessive_stemmer
|
||||||
|
english_stop
|
||||||
|
english_stemmer
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
hashtag: {
|
hashtag: {
|
||||||
tokenizer: 'keyword',
|
tokenizer: 'keyword',
|
||||||
filter: %w(
|
filter: %w(
|
||||||
|
@ -129,7 +142,7 @@ class StatusesIndex < Chewy::Index
|
||||||
},
|
},
|
||||||
}.freeze
|
}.freeze
|
||||||
|
|
||||||
settings index: index_preset(refresh_interval: '30s', number_of_shards: 5), analysis: PRODUCTION_SETTINGS
|
settings index: index_preset(refresh_interval: '30s', number_of_shards: 5), analysis: Rails.env.test? ? DEVELOPMENT_SETTINGS : PRODUCTION_SETTINGS
|
||||||
|
|
||||||
index_scope ::Status.unscoped.kept.without_reblogs.includes(
|
index_scope ::Status.unscoped.kept.without_reblogs.includes(
|
||||||
:media_attachments,
|
:media_attachments,
|
||||||
|
|
|
@ -42,6 +42,6 @@ class Api::V1::AntennasController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def antenna_params
|
def antenna_params
|
||||||
params.permit(:title, :list_id, :insert_feeds, :stl, :with_media_only, :ignore_reblog)
|
params.permit(:title, :list_id, :insert_feeds, :stl, :ltl, :with_media_only, :ignore_reblog)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Settings::Preferences::OtherController < Settings::Preferences::BaseController
|
class Settings::Preferences::OtherController < Settings::Preferences::BaseController
|
||||||
|
include DtlHelper
|
||||||
|
|
||||||
|
def show
|
||||||
|
@dtl_enabled = DTL_ENABLED
|
||||||
|
@dtl_tag = DTL_TAG
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def after_update_redirect_path
|
def after_update_redirect_path
|
||||||
|
|
6
app/helpers/dtl_helper.rb
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module DtlHelper
|
||||||
|
DTL_ENABLED = ENV.fetch('DTL_ENABLED', 'false') == 'true'
|
||||||
|
DTL_TAG = ENV.fetch('DTL_TAG', 'kmyblue')
|
||||||
|
end
|
BIN
app/javascript/icons/android-chrome-144x144.png
Normal file → Executable file
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 8.6 KiB |
BIN
app/javascript/icons/android-chrome-192x192.png
Normal file → Executable file
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 11 KiB |
BIN
app/javascript/icons/android-chrome-256x256.png
Normal file → Executable file
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 14 KiB |
BIN
app/javascript/icons/android-chrome-36x36.png
Normal file → Executable file
Before Width: | Height: | Size: 950 B After Width: | Height: | Size: 1.8 KiB |
BIN
app/javascript/icons/android-chrome-384x384.png
Normal file → Executable file
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 24 KiB |
BIN
app/javascript/icons/android-chrome-48x48.png
Normal file → Executable file
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 3.1 KiB |
BIN
app/javascript/icons/android-chrome-512x512.png
Normal file → Executable file
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 37 KiB |
BIN
app/javascript/icons/android-chrome-72x72.png
Normal file → Executable file
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 4.6 KiB |
BIN
app/javascript/icons/android-chrome-96x96.png
Normal file → Executable file
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 6 KiB |
BIN
app/javascript/icons/apple-touch-icon-1024x1024.png
Normal file → Executable file
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 112 KiB |
BIN
app/javascript/icons/apple-touch-icon-114x114.png
Normal file → Executable file
Before Width: | Height: | Size: 4 KiB After Width: | Height: | Size: 6.9 KiB |
BIN
app/javascript/icons/apple-touch-icon-120x120.png
Normal file → Executable file
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 7.3 KiB |
BIN
app/javascript/icons/apple-touch-icon-144x144.png
Normal file → Executable file
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 8.6 KiB |
BIN
app/javascript/icons/apple-touch-icon-152x152.png
Normal file → Executable file
Before Width: | Height: | Size: 6 KiB After Width: | Height: | Size: 8.9 KiB |
BIN
app/javascript/icons/apple-touch-icon-167x167.png
Normal file → Executable file
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 9.8 KiB |
BIN
app/javascript/icons/apple-touch-icon-180x180.png
Normal file → Executable file
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 10 KiB |
BIN
app/javascript/icons/apple-touch-icon-57x57.png
Normal file → Executable file
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 3.7 KiB |
BIN
app/javascript/icons/apple-touch-icon-60x60.png
Normal file → Executable file
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 3.9 KiB |
BIN
app/javascript/icons/apple-touch-icon-72x72.png
Normal file → Executable file
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 4.6 KiB |
BIN
app/javascript/icons/apple-touch-icon-76x76.png
Normal file → Executable file
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 4.8 KiB |
BIN
app/javascript/icons/favicon-16x16.png
Normal file → Executable file
Before Width: | Height: | Size: 588 B After Width: | Height: | Size: 986 B |
BIN
app/javascript/icons/favicon-32x32.png
Normal file → Executable file
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.7 KiB |
BIN
app/javascript/icons/favicon-48x48.png
Normal file → Executable file
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 3.1 KiB |
|
@ -236,10 +236,10 @@ export const createAntennaFail = error => ({
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const updateAntenna = (id, title, shouldReset, list_id, stl, with_media_only, ignore_reblog, insert_feeds) => (dispatch, getState) => {
|
export const updateAntenna = (id, title, shouldReset, list_id, stl, ltl, with_media_only, ignore_reblog, insert_feeds) => (dispatch, getState) => {
|
||||||
dispatch(updateAntennaRequest(id));
|
dispatch(updateAntennaRequest(id));
|
||||||
|
|
||||||
api(getState).put(`/api/v1/antennas/${id}`, { title, list_id, stl, with_media_only, ignore_reblog, insert_feeds }).then(({ data }) => {
|
api(getState).put(`/api/v1/antennas/${id}`, { title, list_id, stl, ltl, with_media_only, ignore_reblog, insert_feeds }).then(({ data }) => {
|
||||||
dispatch(updateAntennaSuccess(data));
|
dispatch(updateAntennaSuccess(data));
|
||||||
|
|
||||||
if (shouldReset) {
|
if (shouldReset) {
|
||||||
|
|
|
@ -198,31 +198,37 @@ class AntennaSetting extends PureComponent {
|
||||||
onStlToggle = ({ target }) => {
|
onStlToggle = ({ target }) => {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
const { id } = this.props.params;
|
const { id } = this.props.params;
|
||||||
dispatch(updateAntenna(id, undefined, false, undefined, target.checked, undefined, undefined, undefined));
|
dispatch(updateAntenna(id, undefined, false, undefined, target.checked, undefined, undefined, undefined, undefined));
|
||||||
|
};
|
||||||
|
|
||||||
|
onLtlToggle = ({ target }) => {
|
||||||
|
const { dispatch } = this.props;
|
||||||
|
const { id } = this.props.params;
|
||||||
|
dispatch(updateAntenna(id, undefined, false, undefined, undefined, target.checked, undefined, undefined, undefined));
|
||||||
};
|
};
|
||||||
|
|
||||||
onMediaOnlyToggle = ({ target }) => {
|
onMediaOnlyToggle = ({ target }) => {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
const { id } = this.props.params;
|
const { id } = this.props.params;
|
||||||
dispatch(updateAntenna(id, undefined, false, undefined, undefined, target.checked, undefined, undefined));
|
dispatch(updateAntenna(id, undefined, false, undefined, undefined, undefined, target.checked, undefined, undefined));
|
||||||
};
|
};
|
||||||
|
|
||||||
onIgnoreReblogToggle = ({ target }) => {
|
onIgnoreReblogToggle = ({ target }) => {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
const { id } = this.props.params;
|
const { id } = this.props.params;
|
||||||
dispatch(updateAntenna(id, undefined, false, undefined, undefined, undefined, target.checked, undefined));
|
dispatch(updateAntenna(id, undefined, false, undefined, undefined, undefined, undefined, target.checked, undefined));
|
||||||
};
|
};
|
||||||
|
|
||||||
onNoInsertFeedsToggle = ({ target }) => {
|
onNoInsertFeedsToggle = ({ target }) => {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
const { id } = this.props.params;
|
const { id } = this.props.params;
|
||||||
dispatch(updateAntenna(id, undefined, false, undefined, undefined, undefined, undefined, target.checked));
|
dispatch(updateAntenna(id, undefined, false, undefined, undefined, undefined, undefined, undefined, target.checked));
|
||||||
};
|
};
|
||||||
|
|
||||||
onSelect = value => {
|
onSelect = value => {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props;
|
||||||
const { id } = this.props.params;
|
const { id } = this.props.params;
|
||||||
dispatch(updateAntenna(id, undefined, false, value.value, undefined, undefined, undefined, undefined));
|
dispatch(updateAntenna(id, undefined, false, value.value, undefined, undefined, undefined, undefined, undefined));
|
||||||
};
|
};
|
||||||
|
|
||||||
onHomeSelect = () => this.onSelect({ value: '0' });
|
onHomeSelect = () => this.onSelect({ value: '0' });
|
||||||
|
@ -293,6 +299,7 @@ class AntennaSetting extends PureComponent {
|
||||||
const pinned = !!columnId;
|
const pinned = !!columnId;
|
||||||
const title = antenna ? antenna.get('title') : id;
|
const title = antenna ? antenna.get('title') : id;
|
||||||
const isStl = antenna ? antenna.get('stl') : undefined;
|
const isStl = antenna ? antenna.get('stl') : undefined;
|
||||||
|
const isLtl = antenna ? antenna.get('ltl') : undefined;
|
||||||
const isMediaOnly = antenna ? antenna.get('with_media_only') : undefined;
|
const isMediaOnly = antenna ? antenna.get('with_media_only') : undefined;
|
||||||
const isIgnoreReblog = antenna ? antenna.get('ignore_reblog') : undefined;
|
const isIgnoreReblog = antenna ? antenna.get('ignore_reblog') : undefined;
|
||||||
const isInsertFeeds = antenna ? antenna.get('insert_feeds') : undefined;
|
const isInsertFeeds = antenna ? antenna.get('insert_feeds') : undefined;
|
||||||
|
@ -312,7 +319,7 @@ class AntennaSetting extends PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
let columnSettings;
|
let columnSettings;
|
||||||
if (!isStl) {
|
if (!isStl && !isLtl) {
|
||||||
columnSettings = (
|
columnSettings = (
|
||||||
<>
|
<>
|
||||||
<div className='setting-toggle'>
|
<div className='setting-toggle'>
|
||||||
|
@ -339,6 +346,12 @@ class AntennaSetting extends PureComponent {
|
||||||
<p><FormattedMessage id='antennas.in_stl_mode' defaultMessage='This antenna is in STL mode.' /></p>
|
<p><FormattedMessage id='antennas.in_stl_mode' defaultMessage='This antenna is in STL mode.' /></p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
} else if (isLtl) {
|
||||||
|
stlAlert = (
|
||||||
|
<div className='antenna-setting'>
|
||||||
|
<p><FormattedMessage id='antennas.in_ltl_mode' defaultMessage='This antenna is in LTL mode.' /></p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const rangeRadioValues = ImmutableList([
|
const rangeRadioValues = ImmutableList([
|
||||||
|
@ -384,12 +397,23 @@ class AntennaSetting extends PureComponent {
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='setting-toggle'>
|
{!isLtl && (
|
||||||
<Toggle id={`antenna-${id}-stl`} defaultChecked={isStl} onChange={this.onStlToggle} />
|
<div className='setting-toggle'>
|
||||||
<label htmlFor={`antenna-${id}-stl`} className='setting-toggle__label'>
|
<Toggle id={`antenna-${id}-stl`} defaultChecked={isStl} onChange={this.onStlToggle} />
|
||||||
<FormattedMessage id='antennas.stl' defaultMessage='STL mode' />
|
<label htmlFor={`antenna-${id}-stl`} className='setting-toggle__label'>
|
||||||
</label>
|
<FormattedMessage id='antennas.stl' defaultMessage='STL mode' />
|
||||||
</div>
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isStl && (
|
||||||
|
<div className='setting-toggle'>
|
||||||
|
<Toggle id={`antenna-${id}-ltl`} defaultChecked={isLtl} onChange={this.onLtlToggle} />
|
||||||
|
<label htmlFor={`antenna-${id}-ltl`} className='setting-toggle__label'>
|
||||||
|
<FormattedMessage id='antennas.ltl' defaultMessage='LTL mode' />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className='setting-toggle'>
|
<div className='setting-toggle'>
|
||||||
<Toggle id={`antenna-${id}-noinsertfeeds`} defaultChecked={isInsertFeeds} onChange={this.onNoInsertFeedsToggle} />
|
<Toggle id={`antenna-${id}-noinsertfeeds`} defaultChecked={isInsertFeeds} onChange={this.onNoInsertFeedsToggle} />
|
||||||
|
@ -429,7 +453,7 @@ class AntennaSetting extends PureComponent {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isStl && (
|
{!isStl && !isLtl && (
|
||||||
<>
|
<>
|
||||||
<h2><FormattedMessage id='antennas.filter' defaultMessage='Filter' /></h2>
|
<h2><FormattedMessage id='antennas.filter' defaultMessage='Filter' /></h2>
|
||||||
<RadioPanel values={rangeRadioValues} value={rangeRadioValue} onChange={this.onRangeRadioChanged} />
|
<RadioPanel values={rangeRadioValues} value={rangeRadioValue} onChange={this.onRangeRadioChanged} />
|
||||||
|
|
|
@ -22,6 +22,7 @@ import NewListForm from './components/new_list_form';
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
heading: { id: 'column.lists', defaultMessage: 'Lists' },
|
heading: { id: 'column.lists', defaultMessage: 'Lists' },
|
||||||
subheading: { id: 'lists.subheading', defaultMessage: 'Your lists' },
|
subheading: { id: 'lists.subheading', defaultMessage: 'Your lists' },
|
||||||
|
with_antenna: { id: 'lists.with_antenna', defaultMessage: 'Antenna' },
|
||||||
});
|
});
|
||||||
|
|
||||||
const getOrderedLists = createSelector([state => state.get('lists')], lists => {
|
const getOrderedLists = createSelector([state => state.get('lists')], lists => {
|
||||||
|
@ -76,7 +77,8 @@ class Lists extends ImmutablePureComponent {
|
||||||
bindToDocument={!multiColumn}
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
{lists.map(list =>
|
{lists.map(list =>
|
||||||
<ColumnLink key={list.get('id')} to={`/lists/${list.get('id')}`} icon='list-ul' text={list.get('title')} />,
|
(<ColumnLink key={list.get('id')} to={`/lists/${list.get('id')}`} icon='list-ul' text={list.get('title')}
|
||||||
|
badge={(list.get('antennas') && list.get('antennas').size > 0) ? intl.formatMessage(messages.with_antenna) : undefined} />),
|
||||||
)}
|
)}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { WordmarkLogo } from 'mastodon/components/logo';
|
import { WordmarkLogo } from 'mastodon/components/logo';
|
||||||
import NavigationPortal from 'mastodon/components/navigation_portal';
|
import NavigationPortal from 'mastodon/components/navigation_portal';
|
||||||
import { enableDtlMenu, timelinePreview, trendsEnabled } from 'mastodon/initial_state';
|
import { enableDtlMenu, timelinePreview, trendsEnabled, dtlTag } from 'mastodon/initial_state';
|
||||||
import { transientSingleColumn } from 'mastodon/is_mobile';
|
import { transientSingleColumn } from 'mastodon/is_mobile';
|
||||||
|
|
||||||
import ColumnLink from './column_link';
|
import ColumnLink from './column_link';
|
||||||
|
@ -93,8 +93,8 @@ class NavigationPanel extends Component {
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{signedIn && enableDtlMenu && (
|
{signedIn && enableDtlMenu && dtlTag && (
|
||||||
<ColumnLink transparent to='/tags/kmyblue' icon='users' text={intl.formatMessage(messages.deepLocal)} />
|
<ColumnLink transparent to={`/tags/${dtlTag}`} icon='users' text={intl.formatMessage(messages.deepLocal)} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!signedIn && explorer}
|
{!signedIn && explorer}
|
||||||
|
|
|
@ -58,6 +58,7 @@
|
||||||
* @property {string} display_media
|
* @property {string} display_media
|
||||||
* @property {boolean} display_media_expand
|
* @property {boolean} display_media_expand
|
||||||
* @property {string} domain
|
* @property {string} domain
|
||||||
|
* @property {string} dtl_tag
|
||||||
* @property {boolean} enable_login_privacy
|
* @property {boolean} enable_login_privacy
|
||||||
* @property {boolean} enable_dtl_menu
|
* @property {boolean} enable_dtl_menu
|
||||||
* @property {boolean=} expand_spoilers
|
* @property {boolean=} expand_spoilers
|
||||||
|
@ -124,6 +125,7 @@ export const disabledAccountId = getMeta('disabled_account_id');
|
||||||
export const displayMedia = getMeta('display_media');
|
export const displayMedia = getMeta('display_media');
|
||||||
export const displayMediaExpand = getMeta('display_media_expand');
|
export const displayMediaExpand = getMeta('display_media_expand');
|
||||||
export const domain = getMeta('domain');
|
export const domain = getMeta('domain');
|
||||||
|
export const dtlTag = getMeta('dtl_tag');
|
||||||
export const enableLoginPrivacy = getMeta('enable_login_privacy');
|
export const enableLoginPrivacy = getMeta('enable_login_privacy');
|
||||||
export const enableDtlMenu = getMeta('enable_dtl_menu');
|
export const enableDtlMenu = getMeta('enable_dtl_menu');
|
||||||
export const expandSpoilers = getMeta('expand_spoilers');
|
export const expandSpoilers = getMeta('expand_spoilers');
|
||||||
|
|
|
@ -206,7 +206,7 @@
|
||||||
"compose_form.direct_message_warning_learn_more": "もっと詳しく",
|
"compose_form.direct_message_warning_learn_more": "もっと詳しく",
|
||||||
"compose_form.encryption_warning": "Mastodonの投稿はエンドツーエンド暗号化に対応していません。安全に送受信されるべき情報をMastodonで共有しないでください。",
|
"compose_form.encryption_warning": "Mastodonの投稿はエンドツーエンド暗号化に対応していません。安全に送受信されるべき情報をMastodonで共有しないでください。",
|
||||||
"compose_form.hashtag_warning": "この投稿は公開設定ではないのでハッシュタグの一覧に表示されません。公開投稿だけがハッシュタグで検索できます。",
|
"compose_form.hashtag_warning": "この投稿は公開設定ではないのでハッシュタグの一覧に表示されません。公開投稿だけがハッシュタグで検索できます。",
|
||||||
"compose_form.limited_post_warning": "限定投稿は現状、ごく一部のMastodonサーバーにしか届きません(2023年8月時点でFedibird、kmyblue、Pawooなど一部のみです)",
|
"compose_form.limited_post_warning": "限定投稿は現状、ごく一部のMastodonサーバーにしか届きません(2023年9月時点でFedibird、kmyblueなど一部のみです)",
|
||||||
"compose_form.lock_disclaimer": "あなたのアカウントは{locked}になっていません。誰でもあなたをフォローすることができ、フォロワー限定の投稿を見ることができます。",
|
"compose_form.lock_disclaimer": "あなたのアカウントは{locked}になっていません。誰でもあなたをフォローすることができ、フォロワー限定の投稿を見ることができます。",
|
||||||
"compose_form.lock_disclaimer.lock": "承認制",
|
"compose_form.lock_disclaimer.lock": "承認制",
|
||||||
"compose_form.markdown.marked": "Markdown有効",
|
"compose_form.markdown.marked": "Markdown有効",
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
# stl :boolean default(FALSE), not null
|
# stl :boolean default(FALSE), not null
|
||||||
# ignore_reblog :boolean default(FALSE), not null
|
# ignore_reblog :boolean default(FALSE), not null
|
||||||
# insert_feeds :boolean default(FALSE), not null
|
# insert_feeds :boolean default(FALSE), not null
|
||||||
|
# ltl :boolean default(FALSE), not null
|
||||||
#
|
#
|
||||||
class Antenna < ApplicationRecord
|
class Antenna < ApplicationRecord
|
||||||
include Expireable
|
include Expireable
|
||||||
|
@ -45,16 +46,19 @@ class Antenna < ApplicationRecord
|
||||||
belongs_to :list, optional: true
|
belongs_to :list, optional: true
|
||||||
|
|
||||||
scope :stls, -> { where(stl: true) }
|
scope :stls, -> { where(stl: true) }
|
||||||
|
scope :ltls, -> { where(ltl: true) }
|
||||||
scope :all_keywords, -> { where(any_keywords: true) }
|
scope :all_keywords, -> { where(any_keywords: true) }
|
||||||
scope :all_domains, -> { where(any_domains: true) }
|
scope :all_domains, -> { where(any_domains: true) }
|
||||||
scope :all_accounts, -> { where(any_accounts: true) }
|
scope :all_accounts, -> { where(any_accounts: true) }
|
||||||
scope :all_tags, -> { where(any_tags: true) }
|
scope :all_tags, -> { where(any_tags: true) }
|
||||||
scope :availables, -> { where(available: true).where(Arel.sql('any_keywords = FALSE OR any_domains = FALSE OR any_accounts = FALSE OR any_tags = FALSE')) }
|
scope :availables, -> { where(available: true).where(Arel.sql('any_keywords = FALSE OR any_domains = FALSE OR any_accounts = FALSE OR any_tags = FALSE')) }
|
||||||
scope :available_stls, -> { where(available: true, stl: true) }
|
scope :available_stls, -> { where(available: true, stl: true) }
|
||||||
|
scope :available_ltls, -> { where(available: true, stl: false, ltl: true) }
|
||||||
|
|
||||||
validate :list_owner
|
validate :list_owner
|
||||||
validate :validate_limit
|
validate :validate_limit
|
||||||
validate :validate_stl_limit
|
validate :validate_stl_limit
|
||||||
|
validate :validate_ltl_limit
|
||||||
|
|
||||||
def list_owner
|
def list_owner
|
||||||
raise Mastodon::ValidationError, I18n.t('antennas.errors.invalid_list_owner') if !list_id.zero? && list.present? && list.account != account
|
raise Mastodon::ValidationError, I18n.t('antennas.errors.invalid_list_owner') if !list_id.zero? && list.present? && list.account != account
|
||||||
|
@ -235,6 +239,22 @@ class Antenna < ApplicationRecord
|
||||||
|
|
||||||
stls = account.antennas.where(stl: true).where.not(id: id)
|
stls = account.antennas.where(stl: true).where.not(id: id)
|
||||||
|
|
||||||
errors.add(:base, I18n.t('antennas.errors.over_stl_limit', limit: 1)) if list_id.zero? ? stls.any? { |tl| tl.list_id.zero? } : stls.any? { |tl| tl.list_id != 0 }
|
errors.add(:base, I18n.t('antennas.errors.over_stl_limit', limit: 1)) if if insert_feeds
|
||||||
|
list_id.zero? ? stls.any? { |tl| tl.list_id.zero? } : stls.any? { |tl| tl.list_id != 0 }
|
||||||
|
else
|
||||||
|
stls.any? { |tl| !tl.insert_feeds }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_ltl_limit
|
||||||
|
return unless ltl
|
||||||
|
|
||||||
|
ltls = account.antennas.where(ltl: true).where.not(id: id)
|
||||||
|
|
||||||
|
errors.add(:base, I18n.t('antennas.errors.over_ltl_limit', limit: 1)) if if insert_feeds
|
||||||
|
list_id.zero? ? ltls.any? { |tl| tl.list_id.zero? } : ltls.any? { |tl| tl.list_id != 0 }
|
||||||
|
else
|
||||||
|
ltls.any? { |tl| !tl.insert_feeds }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -43,6 +43,7 @@ class Status < ApplicationRecord
|
||||||
include RateLimitable
|
include RateLimitable
|
||||||
include StatusSafeReblogInsert
|
include StatusSafeReblogInsert
|
||||||
include StatusSearchConcern
|
include StatusSearchConcern
|
||||||
|
include DtlHelper
|
||||||
|
|
||||||
rate_limit by: :account, family: :statuses
|
rate_limit by: :account, family: :statuses
|
||||||
|
|
||||||
|
@ -291,7 +292,7 @@ class Status < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def dtl?
|
def dtl?
|
||||||
tags.where(name: 'kmyblue').exists?
|
tags.where(name: DTL_TAG).exists?
|
||||||
end
|
end
|
||||||
|
|
||||||
def emojis
|
def emojis
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
class InitialStateSerializer < ActiveModel::Serializer
|
class InitialStateSerializer < ActiveModel::Serializer
|
||||||
include RoutingHelper
|
include RoutingHelper
|
||||||
|
include DtlHelper
|
||||||
|
|
||||||
attributes :meta, :compose, :accounts,
|
attributes :meta, :compose, :accounts,
|
||||||
:media_attachments, :settings,
|
:media_attachments, :settings,
|
||||||
|
@ -35,6 +36,7 @@ class InitialStateSerializer < ActiveModel::Serializer
|
||||||
trends_as_landing_page: Setting.trends_as_landing_page,
|
trends_as_landing_page: Setting.trends_as_landing_page,
|
||||||
status_page_url: Setting.status_page_url,
|
status_page_url: Setting.status_page_url,
|
||||||
sso_redirect: sso_redirect,
|
sso_redirect: sso_redirect,
|
||||||
|
dtl_tag: DTL_ENABLED ? DTL_TAG : nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
if object.current_account
|
if object.current_account
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class REST::AntennaSerializer < ActiveModel::Serializer
|
class REST::AntennaSerializer < ActiveModel::Serializer
|
||||||
attributes :id, :title, :stl, :insert_feeds, :with_media_only, :ignore_reblog, :accounts_count, :domains_count, :tags_count, :keywords_count
|
attributes :id, :title, :stl, :ltl, :insert_feeds, :with_media_only, :ignore_reblog, :accounts_count, :domains_count, :tags_count, :keywords_count
|
||||||
|
|
||||||
class ListSerializer < ActiveModel::Serializer
|
class ListSerializer < ActiveModel::Serializer
|
||||||
attributes :id, :title
|
attributes :id, :title
|
||||||
|
|
|
@ -2,16 +2,21 @@
|
||||||
|
|
||||||
class DeliveryAntennaService
|
class DeliveryAntennaService
|
||||||
include FormattingHelper
|
include FormattingHelper
|
||||||
|
include DtlHelper
|
||||||
|
|
||||||
def call(status, update, stl_home)
|
def call(status, update, **options)
|
||||||
@status = status
|
@status = status
|
||||||
@account = @status.account
|
@account = @status.account
|
||||||
@update = update
|
@update = update
|
||||||
|
|
||||||
if stl_home
|
mode = options[:mode] || :home
|
||||||
delivery_stl!
|
case mode
|
||||||
else
|
when :home
|
||||||
delivery!
|
delivery!
|
||||||
|
when :stl
|
||||||
|
delivery_stl!
|
||||||
|
when :ltl
|
||||||
|
delivery_ltl!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -19,6 +24,8 @@ class DeliveryAntennaService
|
||||||
|
|
||||||
def delivery!
|
def delivery!
|
||||||
must_dtl_tag = @account.dissubscribable
|
must_dtl_tag = @account.dissubscribable
|
||||||
|
return if must_dtl_tag && !DTL_ENABLED
|
||||||
|
|
||||||
tag_ids = @status.tags.pluck(:id)
|
tag_ids = @status.tags.pluck(:id)
|
||||||
domain = @account.domain || Rails.configuration.x.local_domain
|
domain = @account.domain || Rails.configuration.x.local_domain
|
||||||
follower_ids = @status.unlisted_visibility? ? @status.account.followers.pluck(:id) : []
|
follower_ids = @status.unlisted_visibility? ? @status.account.followers.pluck(:id) : []
|
||||||
|
@ -31,7 +38,7 @@ class DeliveryAntennaService
|
||||||
|
|
||||||
antennas = Antenna.where(id: antennas.select(:id))
|
antennas = Antenna.where(id: antennas.select(:id))
|
||||||
if must_dtl_tag
|
if must_dtl_tag
|
||||||
dtl_tag = Tag.find_or_create_by_names('kmyblue').first
|
dtl_tag = Tag.find_or_create_by_names(DTL_TAG).first
|
||||||
return if !dtl_tag || tag_ids.exclude?(dtl_tag.id)
|
return if !dtl_tag || tag_ids.exclude?(dtl_tag.id)
|
||||||
|
|
||||||
antennas = antennas.left_joins(:antenna_tags).where(antenna_tags: { tag_id: dtl_tag.id })
|
antennas = antennas.left_joins(:antenna_tags).where(antenna_tags: { tag_id: dtl_tag.id })
|
||||||
|
@ -44,7 +51,7 @@ class DeliveryAntennaService
|
||||||
antennas = antennas.where(account: @status.mentioned_accounts) if @status.visibility.to_sym == :limited
|
antennas = antennas.where(account: @status.mentioned_accounts) if @status.visibility.to_sym == :limited
|
||||||
antennas = antennas.where(with_media_only: false) unless @status.with_media?
|
antennas = antennas.where(with_media_only: false) unless @status.with_media?
|
||||||
antennas = antennas.where(ignore_reblog: false) if @status.reblog?
|
antennas = antennas.where(ignore_reblog: false) if @status.reblog?
|
||||||
antennas = antennas.where(stl: false)
|
antennas = antennas.where(stl: false, ltl: false)
|
||||||
|
|
||||||
collection = AntennaCollection.new(@status, @update, false)
|
collection = AntennaCollection.new(@status, @update, false)
|
||||||
content = extract_status_plain_text_with_spoiler_text(@status)
|
content = extract_status_plain_text_with_spoiler_text(@status)
|
||||||
|
@ -72,7 +79,8 @@ class DeliveryAntennaService
|
||||||
antennas = antennas.where(account_id: Account.without_suspended.joins(:user).select('accounts.id').where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago))
|
antennas = antennas.where(account_id: Account.without_suspended.joins(:user).select('accounts.id').where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago))
|
||||||
|
|
||||||
home_post = !@account.domain.nil? || @status.reblog? || [:public, :public_unlisted, :login].exclude?(@status.visibility.to_sym)
|
home_post = !@account.domain.nil? || @status.reblog? || [:public, :public_unlisted, :login].exclude?(@status.visibility.to_sym)
|
||||||
antennas = antennas.where(account: @account.followers).or(antennas.where(account: @account)).where.not(list_id: 0) if home_post
|
antennas = antennas.where(account: @account.followers).or(antennas.where(account: @account)).where('insert_feeds IS FALSE OR list_id > 0') if home_post && !@status.limited_visibility?
|
||||||
|
antennas = antennas.where(account: @status.mentioned_accounts).or(antennas.where(account: @account)).where('insert_feeds IS FALSE OR list_id > 0') if @status.limited_visibility?
|
||||||
|
|
||||||
collection = AntennaCollection.new(@status, @update, home_post)
|
collection = AntennaCollection.new(@status, @update, home_post)
|
||||||
|
|
||||||
|
@ -87,6 +95,27 @@ class DeliveryAntennaService
|
||||||
collection.deliver!
|
collection.deliver!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def delivery_ltl!
|
||||||
|
return if %i(public public_unlisted login).exclude?(@status.visibility.to_sym)
|
||||||
|
return unless @account.local?
|
||||||
|
return if @status.reblog?
|
||||||
|
|
||||||
|
antennas = Antenna.available_ltls
|
||||||
|
antennas = antennas.where(account_id: Account.without_suspended.joins(:user).select('accounts.id').where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago))
|
||||||
|
|
||||||
|
collection = AntennaCollection.new(@status, @update, false)
|
||||||
|
|
||||||
|
antennas.in_batches do |ans|
|
||||||
|
ans.each do |antenna|
|
||||||
|
next if antenna.expired?
|
||||||
|
|
||||||
|
collection.push(antenna)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
collection.deliver!
|
||||||
|
end
|
||||||
|
|
||||||
class AntennaCollection
|
class AntennaCollection
|
||||||
def initialize(status, update, stl_home = false) # rubocop:disable Style/OptionalBooleanParameter
|
def initialize(status, update, stl_home = false) # rubocop:disable Style/OptionalBooleanParameter
|
||||||
@status = status
|
@status = status
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
class FanOutOnWriteService < BaseService
|
class FanOutOnWriteService < BaseService
|
||||||
include Redisable
|
include Redisable
|
||||||
|
include DtlHelper
|
||||||
|
|
||||||
# Push a status into home and mentions feeds
|
# Push a status into home and mentions feeds
|
||||||
# @param [Status] status
|
# @param [Status] status
|
||||||
|
@ -51,11 +52,13 @@ class FanOutOnWriteService < BaseService
|
||||||
when :public, :unlisted, :public_unlisted, :login, :private
|
when :public, :unlisted, :public_unlisted, :login, :private
|
||||||
deliver_to_all_followers!
|
deliver_to_all_followers!
|
||||||
deliver_to_lists!
|
deliver_to_lists!
|
||||||
deliver_to_antennas! if !@account.dissubscribable || (@status.dtl? && @account.user&.setting_dtl_force_subscribable && @status.tags.exists?(name: 'kmyblue'))
|
deliver_to_antennas! if !@account.dissubscribable || (@status.dtl? && DTL_ENABLED && @account.user&.setting_dtl_force_subscribable && @status.tags.exists?(name: DTL_TAG))
|
||||||
deliver_to_stl_antennas!
|
deliver_to_stl_antennas!
|
||||||
|
deliver_to_ltl_antennas!
|
||||||
when :limited
|
when :limited
|
||||||
deliver_to_lists_mentioned_accounts_only!
|
deliver_to_lists_mentioned_accounts_only!
|
||||||
deliver_to_antennas! unless @account.dissubscribable
|
deliver_to_antennas! unless @account.dissubscribable
|
||||||
|
deliver_to_stl_antennas!
|
||||||
deliver_to_mentioned_followers!
|
deliver_to_mentioned_followers!
|
||||||
else
|
else
|
||||||
deliver_to_mentioned_followers!
|
deliver_to_mentioned_followers!
|
||||||
|
@ -135,11 +138,15 @@ class FanOutOnWriteService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def deliver_to_stl_antennas!
|
def deliver_to_stl_antennas!
|
||||||
DeliveryAntennaService.new.call(@status, @options[:update], true)
|
DeliveryAntennaService.new.call(@status, @options[:update], mode: :stl)
|
||||||
|
end
|
||||||
|
|
||||||
|
def deliver_to_ltl_antennas!
|
||||||
|
DeliveryAntennaService.new.call(@status, @options[:update], mode: :ltl)
|
||||||
end
|
end
|
||||||
|
|
||||||
def deliver_to_antennas!
|
def deliver_to_antennas!
|
||||||
DeliveryAntennaService.new.call(@status, @options[:update], false)
|
DeliveryAntennaService.new.call(@status, @options[:update], mode: :home)
|
||||||
end
|
end
|
||||||
|
|
||||||
def deliver_to_mentioned_followers!
|
def deliver_to_mentioned_followers!
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
class PostStatusService < BaseService
|
class PostStatusService < BaseService
|
||||||
include Redisable
|
include Redisable
|
||||||
include LanguagesHelper
|
include LanguagesHelper
|
||||||
|
include DtlHelper
|
||||||
|
|
||||||
MIN_SCHEDULE_OFFSET = 5.minutes.freeze
|
MIN_SCHEDULE_OFFSET = 5.minutes.freeze
|
||||||
|
|
||||||
|
@ -101,8 +102,10 @@ class PostStatusService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def overwrite_dtl_post
|
def overwrite_dtl_post
|
||||||
|
return unless DTL_ENABLED
|
||||||
|
|
||||||
raw_tags = Extractor.extract_hashtags(@text)
|
raw_tags = Extractor.extract_hashtags(@text)
|
||||||
return if raw_tags.exclude?('kmyblue')
|
return if raw_tags.exclude?(DTL_TAG)
|
||||||
return unless %i(public public_unlisted unlisted).include?(@visibility)
|
return unless %i(public public_unlisted unlisted).include?(@visibility)
|
||||||
|
|
||||||
@visibility = :unlisted if @account.user&.setting_dtl_force_with_tag == :full
|
@visibility = :unlisted if @account.user&.setting_dtl_force_with_tag == :full
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
= render 'shared/error_messages', object: @admin_settings
|
= render 'shared/error_messages', object: @admin_settings
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :ng_words, wrapper: :with_label, as: :text, input_html: { rows: 12 }, label: t('admin.ng_words.keywords')
|
= f.input :ng_words, wrapper: :with_label, as: :text, input_html: { rows: 12 }, label: t('admin.ng_words.keywords'), hint: t('admin.ng_words.keywords_hint')
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :post_hash_tags_max, wrapper: :with_label, as: :integer, label: t('admin.ng_words.post_hash_tags_max')
|
= f.input :post_hash_tags_max, wrapper: :with_label, as: :integer, label: t('admin.ng_words.post_hash_tags_max')
|
||||||
|
|
|
@ -42,18 +42,20 @@
|
||||||
.fields-group
|
.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
|
= 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.dtl'
|
- if @dtl_enabled
|
||||||
|
|
||||||
%p.hint= t 'preferences.dtl_hint'
|
%h4= t 'preferences.dtl'
|
||||||
|
|
||||||
.fields-group
|
%p.hint= t 'preferences.dtl_hint', tag: @dtl_tag
|
||||||
= ff.input :'web.enable_dtl_menu', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_dtl_menu'), hint: I18n.t('simple_form.hints.defaults.setting_dtl_menu')
|
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= ff.input :dtl_force_with_tag, kmyblue: true, collection: ['full', 'searchability', 'none'], label_method: lambda { |item| safe_join([t("simple_form.labels.dtl_force_with_tag.#{item}")]) }, as: :radio_buttons, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li', wrapper: :with_floating_label, label: I18n.t('simple_form.labels.defaults.setting_dtl_force_with_tag'), hint: I18n.t('simple_form.hints.defaults.setting_dtl_force_with_tag')
|
= ff.input :'web.enable_dtl_menu', wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_dtl_menu')
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= ff.input :dtl_force_subscribable, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_dtl_force_subscribable'), hint: I18n.t('simple_form.hints.defaults.setting_dtl_force_subscribable')
|
= ff.input :dtl_force_with_tag, kmyblue: true, collection: ['full', 'searchability', 'none'], label_method: lambda { |item| safe_join([t("simple_form.labels.dtl_force_with_tag.#{item}")]) }, as: :radio_buttons, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li', wrapper: :with_floating_label, label: I18n.t('simple_form.labels.defaults.setting_dtl_force_with_tag'), hint: I18n.t('simple_form.hints.defaults.setting_dtl_force_with_tag', tag: @dtl_tag)
|
||||||
|
|
||||||
|
.fields-group
|
||||||
|
= ff.input :dtl_force_subscribable, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_dtl_force_subscribable'), hint: I18n.t('simple_form.hints.defaults.setting_dtl_force_subscribable')
|
||||||
|
|
||||||
%h4= t 'preferences.public_timelines'
|
%h4= t 'preferences.public_timelines'
|
||||||
|
|
||||||
|
|
|
@ -600,6 +600,7 @@ en:
|
||||||
enable_block_emoji_reaction_settings: Enable block emoji reactions settings for users
|
enable_block_emoji_reaction_settings: Enable block emoji reactions settings for users
|
||||||
hide_local_users_for_anonymous: Hide timeline local user posts from anonymous
|
hide_local_users_for_anonymous: Hide timeline local user posts from anonymous
|
||||||
keywords: Reject keywords
|
keywords: Reject keywords
|
||||||
|
keywords_hint: The first character of the line is "?". to use regular expressions
|
||||||
post_hash_tags_max: Hash tags max for posts
|
post_hash_tags_max: Hash tags max for posts
|
||||||
test_error: Testing is returned any errors
|
test_error: Testing is returned any errors
|
||||||
title: NG words and against spams
|
title: NG words and against spams
|
||||||
|
@ -780,6 +781,7 @@ en:
|
||||||
hint: This keywords is applied to public posts only..
|
hint: This keywords is applied to public posts only..
|
||||||
keywords: Sensitive keywords
|
keywords: Sensitive keywords
|
||||||
keywords_for_all: Sensitive keywords (Contains CW alert)
|
keywords_for_all: Sensitive keywords (Contains CW alert)
|
||||||
|
keywords_for_all_hint: The first character of the line is "?". to use regular expressions
|
||||||
title: Sensitive words and moderation options
|
title: Sensitive words and moderation options
|
||||||
settings:
|
settings:
|
||||||
about:
|
about:
|
||||||
|
@ -1111,6 +1113,7 @@ en:
|
||||||
invalid_context: None or invalid context supplied
|
invalid_context: None or invalid context supplied
|
||||||
invalid_list_owner: This list is not yours
|
invalid_list_owner: This list is not yours
|
||||||
over_limit: You have exceeded the limit of %{limit} antennas
|
over_limit: You have exceeded the limit of %{limit} antennas
|
||||||
|
over_ltl_limit: You have exceeded the limit of %{limit} ltl antennas
|
||||||
over_stl_limit: You have exceeded the limit of %{limit} stl antennas
|
over_stl_limit: You have exceeded the limit of %{limit} stl antennas
|
||||||
index:
|
index:
|
||||||
contexts: Antennas in %{contexts}
|
contexts: Antennas in %{contexts}
|
||||||
|
@ -1642,7 +1645,7 @@ en:
|
||||||
too_many_options: can't contain more than %{max} items
|
too_many_options: can't contain more than %{max} items
|
||||||
preferences:
|
preferences:
|
||||||
dtl: Deep timeline
|
dtl: Deep timeline
|
||||||
dtl_hint: "You can join deep timeline with #kmyblue tag. Following settings make convenient to use deep timeline."
|
dtl_hint: "You can join deep timeline with #%{tag} tag. Following settings make convenient to use deep timeline."
|
||||||
other: Other
|
other: Other
|
||||||
posting_defaults: Posting defaults
|
posting_defaults: Posting defaults
|
||||||
public_timelines: Public timelines
|
public_timelines: Public timelines
|
||||||
|
|
|
@ -598,6 +598,7 @@ ja:
|
||||||
enable_block_emoji_reaction_settings: 各ユーザーにスタンプ機能のブロック設定項目を解放する
|
enable_block_emoji_reaction_settings: 各ユーザーにスタンプ機能のブロック設定項目を解放する
|
||||||
hide_local_users_for_anonymous: ログインしていない状態でローカルユーザーの投稿をタイムラインから取得できないようにする
|
hide_local_users_for_anonymous: ログインしていない状態でローカルユーザーの投稿をタイムラインから取得できないようにする
|
||||||
keywords: 投稿できないキーワード
|
keywords: 投稿できないキーワード
|
||||||
|
keywords_hint: 行を「?」で始めると、正規表現が使えます
|
||||||
post_hash_tags_max: 投稿に設定可能なハッシュタグの最大数
|
post_hash_tags_max: 投稿に設定可能なハッシュタグの最大数
|
||||||
test_error: NGワードのテストに失敗しました。正規表現のミスが含まれているかもしれません
|
test_error: NGワードのテストに失敗しました。正規表現のミスが含まれているかもしれません
|
||||||
title: NGワードとスパム
|
title: NGワードとスパム
|
||||||
|
@ -775,7 +776,7 @@ ja:
|
||||||
hint: センシティブなキーワードの設定は、当サーバーのローカルユーザーによる公開範囲「公開」「ローカル公開」「ログインユーザーのみ」に対して適用されます。
|
hint: センシティブなキーワードの設定は、当サーバーのローカルユーザーによる公開範囲「公開」「ローカル公開」「ログインユーザーのみ」に対して適用されます。
|
||||||
keywords: センシティブなキーワード(警告文は除外)
|
keywords: センシティブなキーワード(警告文は除外)
|
||||||
keywords_for_all: センシティブなキーワード(警告文にも適用)
|
keywords_for_all: センシティブなキーワード(警告文にも適用)
|
||||||
keywords_for_all_hint: ここで指定したキーワードを含む投稿は強制的にCWになります。警告文にも含まれていればCWになります
|
keywords_for_all_hint: ここで指定したキーワードを含む投稿は強制的にCWになります。警告文にも含まれていればCWになります。行が「?」で始まっていれば正規表現が使えます
|
||||||
keywords_hint: ここで指定したキーワードを含む投稿は強制的にCWになります。ただし警告文に使用していた場合は無視されます
|
keywords_hint: ここで指定したキーワードを含む投稿は強制的にCWになります。ただし警告文に使用していた場合は無視されます
|
||||||
title: センシティブ単語と設定
|
title: センシティブ単語と設定
|
||||||
settings:
|
settings:
|
||||||
|
@ -1031,6 +1032,7 @@ ja:
|
||||||
keywords: 登録できるキーワード数の上限に達しています
|
keywords: 登録できるキーワード数の上限に達しています
|
||||||
tags: 登録できるタグ数の上限に達しています
|
tags: 登録できるタグ数の上限に達しています
|
||||||
over_limit: 所持できるアンテナ数 %{limit}を超えています
|
over_limit: 所持できるアンテナ数 %{limit}を超えています
|
||||||
|
over_ltl_limit: 所持できるLTLモード付きアンテナ数 (ホーム/リストそれぞれにつき%{limit}) を超えています
|
||||||
over_stl_limit: 所持できるSTLモード付きアンテナ数 (ホーム/リストそれぞれにつき%{limit}) を超えています
|
over_stl_limit: 所持できるSTLモード付きアンテナ数 (ホーム/リストそれぞれにつき%{limit}) を超えています
|
||||||
too_short_keyword: キーワードが短すぎます
|
too_short_keyword: キーワードが短すぎます
|
||||||
edit:
|
edit:
|
||||||
|
@ -1108,7 +1110,7 @@ ja:
|
||||||
help_html: CAPTCHAの解決に問題がある場合は、 %{email} までお問い合わせください。お手伝いいたします。
|
help_html: CAPTCHAの解決に問題がある場合は、 %{email} までお問い合わせください。お手伝いいたします。
|
||||||
hint_html: もう一つだけ!あなたが人間であることを確認する必要があります(スパムを防ぐためです!)。 以下のCAPTCHAを解き、「続ける」をクリックします。
|
hint_html: もう一つだけ!あなたが人間であることを確認する必要があります(スパムを防ぐためです!)。 以下のCAPTCHAを解き、「続ける」をクリックします。
|
||||||
title: セキュリティチェック
|
title: セキュリティチェック
|
||||||
cloudflare_with_registering: 登録時にCloudflareの画面が表示されます。登録できないときは tt@kmycode.net までご連絡ください
|
cloudflare_with_registering: 登録時にCloudflareの画面が表示されます。登録できないときは管理者へご連絡ください
|
||||||
confirmations:
|
confirmations:
|
||||||
wrong_email_hint: メールアドレスが正しくない場合は、アカウント設定で変更できます。
|
wrong_email_hint: メールアドレスが正しくない場合は、アカウント設定で変更できます。
|
||||||
delete_account: アカウントの削除
|
delete_account: アカウントの削除
|
||||||
|
@ -1583,7 +1585,7 @@ ja:
|
||||||
too_many_options: は%{max}個までです
|
too_many_options: は%{max}個までです
|
||||||
preferences:
|
preferences:
|
||||||
dtl: ディープタイムライン
|
dtl: ディープタイムライン
|
||||||
dtl_hint: "#kmyblue ハッシュタグに参加することで、ディープタイムラインに投稿できます。ここではディープタイムラインを利用しやすくするための設定ができます。"
|
dtl_hint: "#%{tag} ハッシュタグに参加することで、ディープタイムラインに投稿できます。ここではディープタイムラインを利用しやすくするための設定ができます。"
|
||||||
other: その他
|
other: その他
|
||||||
posting_defaults: デフォルトの投稿設定
|
posting_defaults: デフォルトの投稿設定
|
||||||
public_timelines: 公開タイムライン
|
public_timelines: 公開タイムライン
|
||||||
|
|
|
@ -67,7 +67,7 @@ en:
|
||||||
setting_display_media_hide_all: Always hide media
|
setting_display_media_hide_all: Always hide media
|
||||||
setting_display_media_show_all: Always show media
|
setting_display_media_show_all: Always show media
|
||||||
setting_dtl_force_subscribable: Your post can be detected local user's antenna to subscribe deep timeline
|
setting_dtl_force_subscribable: Your post can be detected local user's antenna to subscribe deep timeline
|
||||||
setting_dtl_force_with_tag: "With using #kmyblue tag, your post settings will be changed forcibly"
|
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_dtl_menu: Show DTL menu on web
|
||||||
setting_use_blurhash: Gradients are based on the colors of the hidden visuals but obfuscate any details
|
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
|
setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed
|
||||||
|
|
|
@ -69,7 +69,7 @@ ja:
|
||||||
setting_display_media_hide_all: メディアを常に隠す
|
setting_display_media_hide_all: メディアを常に隠す
|
||||||
setting_display_media_show_all: メディアを常に表示する
|
setting_display_media_show_all: メディアを常に表示する
|
||||||
setting_dtl_force_subscribable: 購読拒否設定に関係なく、ディープタイムラインに向けた投稿はアンテナに掲載されます。ディープタイムラインをアンテナ経由で閲覧している人にあなたの発言が届きます
|
setting_dtl_force_subscribable: 購読拒否設定に関係なく、ディープタイムラインに向けた投稿はアンテナに掲載されます。ディープタイムラインをアンテナ経由で閲覧している人にあなたの発言が届きます
|
||||||
setting_dtl_force_with_tag: "ハッシュタグ #kmyblue をつけて投稿するとき、公開範囲と検索許可を強制的に置き換えるかを設定します"
|
setting_dtl_force_with_tag: "ハッシュタグ #%{tag} をつけて投稿するとき、公開範囲と検索許可を強制的に置き換えるかを設定します"
|
||||||
setting_emoji_reaction_streaming_notify_impl2: 当該サーバーの独自機能に対応したアプリを利用時に、スタンプ機能を利用できます。動作確認していないため(そもそもそのようなアプリ自体を確認できていないため)正しく動かない場合があります
|
setting_emoji_reaction_streaming_notify_impl2: 当該サーバーの独自機能に対応したアプリを利用時に、スタンプ機能を利用できます。動作確認していないため(そもそもそのようなアプリ自体を確認できていないため)正しく動かない場合があります
|
||||||
setting_hide_network: フォローとフォロワーの情報がプロフィールページで見られないようにします
|
setting_hide_network: フォローとフォロワーの情報がプロフィールページで見られないようにします
|
||||||
setting_link_preview: プレビュー生成を停止することは、センシティブなサイトへのリンクを頻繁に投稿する人にも有効かもしれません
|
setting_link_preview: プレビュー生成を停止することは、センシティブなサイトへのリンクを頻繁に投稿する人にも有効かもしれません
|
||||||
|
|
15
db/migrate/20230911022527_add_ltl_to_antennas.rb
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
|
||||||
|
|
||||||
|
class AddLtlToAntennas < ActiveRecord::Migration[7.0]
|
||||||
|
include Mastodon::MigrationHelpers
|
||||||
|
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def change
|
||||||
|
safety_assured do
|
||||||
|
add_column_with_default :antennas, :ltl, :boolean, default: false, allow_null: false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
13
db/schema.rb
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema[7.0].define(version: 2023_09_07_150100) do
|
ActiveRecord::Schema[7.0].define(version: 2023_09_11_022527) do
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
|
||||||
|
@ -50,6 +50,15 @@ ActiveRecord::Schema[7.0].define(version: 2023_09_07_150100) do
|
||||||
t.index ["account_id", "domain"], name: "index_account_domain_blocks_on_account_id_and_domain", unique: true
|
t.index ["account_id", "domain"], name: "index_account_domain_blocks_on_account_id_and_domain", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "account_groups", force: :cascade do |t|
|
||||||
|
t.bigint "account_id", null: false
|
||||||
|
t.bigint "group_account_id", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["account_id"], name: "index_account_groups_on_account_id"
|
||||||
|
t.index ["group_account_id"], name: "index_account_groups_on_group_account_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "account_migrations", force: :cascade do |t|
|
create_table "account_migrations", force: :cascade do |t|
|
||||||
t.bigint "account_id"
|
t.bigint "account_id"
|
||||||
t.string "acct", default: "", null: false
|
t.string "acct", default: "", null: false
|
||||||
|
@ -313,6 +322,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_09_07_150100) do
|
||||||
t.boolean "stl", default: false, null: false
|
t.boolean "stl", default: false, null: false
|
||||||
t.boolean "ignore_reblog", default: false, null: false
|
t.boolean "ignore_reblog", default: false, null: false
|
||||||
t.boolean "insert_feeds", default: false, null: false
|
t.boolean "insert_feeds", default: false, null: false
|
||||||
|
t.boolean "ltl", default: false, null: false
|
||||||
t.index ["account_id"], name: "index_antennas_on_account_id"
|
t.index ["account_id"], name: "index_antennas_on_account_id"
|
||||||
t.index ["any_accounts"], name: "index_antennas_on_any_accounts"
|
t.index ["any_accounts"], name: "index_antennas_on_any_accounts"
|
||||||
t.index ["any_domains"], name: "index_antennas_on_any_domains"
|
t.index ["any_domains"], name: "index_antennas_on_any_domains"
|
||||||
|
@ -1357,6 +1367,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_09_07_150100) do
|
||||||
add_foreign_key "account_conversations", "conversations", on_delete: :cascade
|
add_foreign_key "account_conversations", "conversations", on_delete: :cascade
|
||||||
add_foreign_key "account_deletion_requests", "accounts", on_delete: :cascade
|
add_foreign_key "account_deletion_requests", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "account_domain_blocks", "accounts", name: "fk_206c6029bd", on_delete: :cascade
|
add_foreign_key "account_domain_blocks", "accounts", name: "fk_206c6029bd", on_delete: :cascade
|
||||||
|
add_foreign_key "account_groups", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "account_migrations", "accounts", column: "target_account_id", on_delete: :nullify
|
add_foreign_key "account_migrations", "accounts", column: "target_account_id", on_delete: :nullify
|
||||||
add_foreign_key "account_migrations", "accounts", on_delete: :cascade
|
add_foreign_key "account_migrations", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "account_moderation_notes", "accounts"
|
add_foreign_key "account_moderation_notes", "accounts"
|
||||||
|
|
|
@ -4,6 +4,14 @@ module Mastodon
|
||||||
module Version
|
module Version
|
||||||
module_function
|
module_function
|
||||||
|
|
||||||
|
def kmyblue_major
|
||||||
|
3
|
||||||
|
end
|
||||||
|
|
||||||
|
def kmyblue_minor
|
||||||
|
0
|
||||||
|
end
|
||||||
|
|
||||||
def major
|
def major
|
||||||
4
|
4
|
||||||
end
|
end
|
||||||
|
@ -24,8 +32,17 @@ module Mastodon
|
||||||
ENV['MASTODON_VERSION_PRERELEASE'].presence || default_prerelease
|
ENV['MASTODON_VERSION_PRERELEASE'].presence || default_prerelease
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_a_of_kmyblue
|
||||||
|
[kmyblue_major, kmyblue_minor].compact
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s_of_kmyblue
|
||||||
|
components = [to_a_of_kmyblue.join('.')]
|
||||||
|
components.join
|
||||||
|
end
|
||||||
|
|
||||||
def build_metadata
|
def build_metadata
|
||||||
ENV.fetch('MASTODON_VERSION_METADATA', nil)
|
['kmyblue', to_s_of_kmyblue, ENV.fetch('MASTODON_VERSION_METADATA', nil)].compact.join('.')
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_a
|
def to_a
|
||||||
|
|
BIN
public/favicon.ico
Normal file → Executable file
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 9.4 KiB |
|
@ -9,6 +9,8 @@ describe AccountInteractions do
|
||||||
let(:target_account) { Fabricate(:account, username: 'target') }
|
let(:target_account) { Fabricate(:account, username: 'target') }
|
||||||
let(:target_account_id) { target_account.id }
|
let(:target_account_id) { target_account.id }
|
||||||
let(:target_account_ids) { [target_account_id] }
|
let(:target_account_ids) { [target_account_id] }
|
||||||
|
let(:follower_account) { Fabricate(:account, username: 'follower') }
|
||||||
|
let(:followee_account) { Fabricate(:account, username: 'followee') }
|
||||||
|
|
||||||
describe '.following_map' do
|
describe '.following_map' do
|
||||||
subject { Account.following_map(target_account_ids, account_id) }
|
subject { Account.following_map(target_account_ids, account_id) }
|
||||||
|
@ -711,4 +713,20 @@ describe AccountInteractions do
|
||||||
expect(account.lists_for_local_distribution.to_a).to contain_exactly(follower_list, self_list)
|
expect(account.lists_for_local_distribution.to_a).to contain_exactly(follower_list, self_list)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#mutuals' do
|
||||||
|
subject { account.mutuals }
|
||||||
|
|
||||||
|
context 'when following target_account' do
|
||||||
|
it 'mutual one' do
|
||||||
|
account.follow!(target_account)
|
||||||
|
target_account.follow!(account)
|
||||||
|
follower_account.follow!(account)
|
||||||
|
account.follow!(followee_account)
|
||||||
|
|
||||||
|
expect(subject.count).to eq 1
|
||||||
|
expect(subject.first.id).to eq target_account.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -27,7 +27,7 @@ RSpec.describe DeliveryAntennaService, type: :service do
|
||||||
let!(:antenna) { nil }
|
let!(:antenna) { nil }
|
||||||
let!(:empty_antenna) { nil }
|
let!(:empty_antenna) { nil }
|
||||||
|
|
||||||
let(:stl_home) { false }
|
let(:mode) { :home }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
bob.follow!(alice)
|
bob.follow!(alice)
|
||||||
|
@ -35,7 +35,7 @@ RSpec.describe DeliveryAntennaService, type: :service do
|
||||||
|
|
||||||
allow(redis).to receive(:publish)
|
allow(redis).to receive(:publish)
|
||||||
|
|
||||||
subject.call(status, false, stl_home)
|
subject.call(status, false, mode: mode)
|
||||||
end
|
end
|
||||||
|
|
||||||
def home_feed_of(account)
|
def home_feed_of(account)
|
||||||
|
@ -323,4 +323,40 @@ RSpec.describe DeliveryAntennaService, type: :service do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when stl mode keyword is not working' do
|
||||||
|
let(:mode) { :stl }
|
||||||
|
let!(:antenna) { antenna_with_keyword(bob, 'anime', stl: true) }
|
||||||
|
|
||||||
|
it 'detecting antenna' do
|
||||||
|
expect(antenna_feed_of(antenna)).to include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when ltl mode keyword is not working' do
|
||||||
|
let(:mode) { :ltl }
|
||||||
|
let!(:antenna) { antenna_with_keyword(bob, 'anime', ltl: true) }
|
||||||
|
|
||||||
|
it 'detecting antenna' do
|
||||||
|
expect(antenna_feed_of(antenna)).to include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when stl mode exclude_keyword is not working' do
|
||||||
|
let(:mode) { :stl }
|
||||||
|
let!(:antenna) { antenna_with_keyword(bob, 'anime', exclude_keywords: ['body'], stl: true) }
|
||||||
|
|
||||||
|
it 'detecting antenna' do
|
||||||
|
expect(antenna_feed_of(antenna)).to include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when ltl mode exclude_keyword is not working' do
|
||||||
|
let(:mode) { :ltl }
|
||||||
|
let!(:antenna) { antenna_with_keyword(bob, 'anime', exclude_keywords: ['body'], ltl: true) }
|
||||||
|
|
||||||
|
it 'detecting antenna' do
|
||||||
|
expect(antenna_feed_of(antenna)).to include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -56,6 +56,10 @@ RSpec.describe FanOutOnWriteService, type: :service do
|
||||||
antenna
|
antenna
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def antenna_with_options(owner, **options)
|
||||||
|
Fabricate(:antenna, account: owner, **options)
|
||||||
|
end
|
||||||
|
|
||||||
context 'when status is public' do
|
context 'when status is public' do
|
||||||
let(:visibility) { 'public' }
|
let(:visibility) { 'public' }
|
||||||
|
|
||||||
|
@ -97,6 +101,26 @@ RSpec.describe FanOutOnWriteService, type: :service do
|
||||||
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with STL antenna' do
|
||||||
|
let!(:antenna) { antenna_with_options(bob, stl: true) }
|
||||||
|
let!(:empty_antenna) { antenna_with_options(tom) }
|
||||||
|
|
||||||
|
it 'is added to the antenna feed of antenna follower' do
|
||||||
|
expect(antenna_feed_of(antenna)).to include status.id
|
||||||
|
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with LTL antenna' do
|
||||||
|
let!(:antenna) { antenna_with_options(bob, ltl: true) }
|
||||||
|
let!(:empty_antenna) { antenna_with_options(tom) }
|
||||||
|
|
||||||
|
it 'is added to the antenna feed of antenna follower' do
|
||||||
|
expect(antenna_feed_of(antenna)).to include status.id
|
||||||
|
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when status is limited' do
|
context 'when status is limited' do
|
||||||
|
@ -138,6 +162,24 @@ RSpec.describe FanOutOnWriteService, type: :service do
|
||||||
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with STL antenna' do
|
||||||
|
let!(:antenna) { antenna_with_options(bob, stl: true) }
|
||||||
|
let!(:empty_antenna) { antenna_with_options(tom, stl: true) }
|
||||||
|
|
||||||
|
it 'is added to the antenna feed of antenna follower' do
|
||||||
|
expect(antenna_feed_of(antenna)).to include status.id
|
||||||
|
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with LTL antenna' do
|
||||||
|
let!(:empty_antenna) { antenna_with_options(bob, ltl: true) }
|
||||||
|
|
||||||
|
it 'is added to the antenna feed of antenna follower' do
|
||||||
|
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when status is private' do
|
context 'when status is private' do
|
||||||
|
@ -176,6 +218,24 @@ RSpec.describe FanOutOnWriteService, type: :service do
|
||||||
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with STL antenna' do
|
||||||
|
let!(:antenna) { antenna_with_options(bob, stl: true) }
|
||||||
|
let!(:empty_antenna) { antenna_with_options(ohagi, stl: true) }
|
||||||
|
|
||||||
|
it 'is added to the antenna feed of antenna follower' do
|
||||||
|
expect(antenna_feed_of(antenna)).to include status.id
|
||||||
|
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with LTL antenna' do
|
||||||
|
let!(:empty_antenna) { antenna_with_options(bob, ltl: true) }
|
||||||
|
|
||||||
|
it 'is added to the antenna feed of antenna follower' do
|
||||||
|
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when status is unlisted' do
|
context 'when status is unlisted' do
|
||||||
|
@ -215,6 +275,24 @@ RSpec.describe FanOutOnWriteService, type: :service do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with STL antenna' do
|
||||||
|
let!(:antenna) { antenna_with_options(bob, stl: true) }
|
||||||
|
let!(:empty_antenna) { antenna_with_options(ohagi, stl: true) }
|
||||||
|
|
||||||
|
it 'is added to the antenna feed of antenna follower' do
|
||||||
|
expect(antenna_feed_of(antenna)).to include status.id
|
||||||
|
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with LTL antenna' do
|
||||||
|
let!(:empty_antenna) { antenna_with_options(bob, ltl: true) }
|
||||||
|
|
||||||
|
it 'is added to the antenna feed of antenna follower' do
|
||||||
|
expect(antenna_feed_of(empty_antenna)).to_not include status.id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'with non-public searchability' do
|
context 'with non-public searchability' do
|
||||||
let(:searchability) { 'direct' }
|
let(:searchability) { 'direct' }
|
||||||
|
|
||||||
|
|
|
@ -173,12 +173,37 @@ RSpec.describe PostStatusService, type: :service do
|
||||||
|
|
||||||
it 'mutual visibility' do
|
it 'mutual visibility' do
|
||||||
account = Fabricate(:account)
|
account = Fabricate(:account)
|
||||||
|
mutual_account = Fabricate(:account)
|
||||||
|
other_account = Fabricate(:account)
|
||||||
text = 'This is an English text.'
|
text = 'This is an English text.'
|
||||||
|
|
||||||
|
mutual_account.follow!(account)
|
||||||
|
account.follow!(mutual_account)
|
||||||
|
other_account.follow!(account)
|
||||||
status = subject.call(account, text: text, visibility: 'mutual')
|
status = subject.call(account, text: text, visibility: 'mutual')
|
||||||
|
|
||||||
expect(status.visibility).to eq 'limited'
|
expect(status.visibility).to eq 'limited'
|
||||||
expect(status.limited_scope).to eq 'mutual'
|
expect(status.limited_scope).to eq 'mutual'
|
||||||
|
expect(status.mentioned_accounts.count).to eq 1
|
||||||
|
expect(status.mentioned_accounts.first.id).to eq mutual_account.id
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'circle visibility' do
|
||||||
|
account = Fabricate(:account)
|
||||||
|
circle_account = Fabricate(:account)
|
||||||
|
other_account = Fabricate(:account)
|
||||||
|
circle = Fabricate(:circle, account: account)
|
||||||
|
text = 'This is an English text.'
|
||||||
|
|
||||||
|
circle_account.follow!(account)
|
||||||
|
other_account.follow!(account)
|
||||||
|
circle.accounts << circle_account
|
||||||
|
status = subject.call(account, text: text, visibility: 'circle', circle_id: circle.id)
|
||||||
|
|
||||||
|
expect(status.visibility).to eq 'limited'
|
||||||
|
expect(status.limited_scope).to eq 'circle'
|
||||||
|
expect(status.mentioned_accounts.count).to eq 1
|
||||||
|
expect(status.mentioned_accounts.first.id).to eq circle_account.id
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'safeguards mentions' do
|
it 'safeguards mentions' do
|
||||||
|
|