Code Monkey home page Code Monkey logo

mastodon's People

Contributors

abcang avatar akihikodaki avatar alpaca-tc avatar ariasuni avatar blackle avatar clearlyclaire avatar dependabot-preview[bot] avatar dependabot[bot] avatar fvh-p avatar gargron avatar ineffyble avatar lnanase avatar lynlynlynx avatar mayaeh avatar mjankowski avatar nclm avatar noellabo avatar nolanlawson avatar nschonni avatar renatolond avatar shleeable avatar sorin-davidoi avatar takayamaki avatar tribela avatar trwnh avatar unarist avatar yiskah avatar ykzts avatar ysksn avatar zunda avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mastodon's Issues

フィルター機能の「日本語(im@s)」での訳語がズレている

表題の通り、フィルター機能で「日本語(im@s)」の訳語がズレています。
現行の実装は下記の画像を参照ください。

日本語
image

日本語(im@s)
image

正しい訳語は下表の通りと考えます。

日本語 日本語(im@s)(誤) 日本語(im@s)(正)
ホームタイムライン 楽屋 オフィス
公開タイムライン ライブステージ (public timeline に相当するim@s語ありましたっけ……)

  • I searched or browsed the repo’s other issues to ensure this is not a duplicate.
  • This bug happens on a tagged release and not on master (If you're a user, don't worry about this).

フォロー/フォロワー以外からの通知のブロック設定をReact上からしたい

image
https://github.com/imas/mastodon/blob/imastodon/app/views/settings/preferences/show.html.haml#L38-L41
ユーザ設定の上記箇所で設定する、通知のブロック設定をReact上の通知カラムのON/OFFスイッチから行いたいという要望がありました。

同じ通知に関する設定であるにも関わらず、この二項目だけ全く別の位置にあるのは(マストドンの構造上の理由はともかく)ユーザビリティとしてはあまり良くないためissueを立てます。

Colors of "action dropdown" ui are bad-contrast when using light theme.

Expected behavior

Colors of "action dropdown" ui are same as normal theme.

Actual behavior

These are bad-contrast for unknown reason.
imastodon_color_bug01

Steps to reproduce

  1. Set site theme "Mastodon (light)" from "Settings -> Preference"
  2. Click "more" button on any toot ui.

NOTE

It's not reproduce on vanilla version of mastodon 2.6.1 and 2.7.3 as far as I know.

v1.4.1への追従

v1.4系列にて行われたWebpackの導入ならびにJavascriptの置き場所変更が
アイマストドン独自機能のcommitとコンフリクトしているためマージに苦戦している件

メニューとカラムでキーボードショートカットのアイコンが異なる

キーボードショートカットヘルプについて、アイコンがimastodon-faqと被ってしまうためメニュー内は #137 で変更していましたが、 https://github.com/imas/mastodon/blob/imastodon/app/javascript/mastodon/features/keyboard_shortcuts/index.js#L24 を変更し忘れていました


  • I searched or browsed the repo’s other issues to ensure this is not a duplicate.
  • This bug happens on a tagged release and not on master (If you're a user, don't worry about this).

タイムライン上の画像を常にNSFW扱いにするオプションが欲しい

機能内容的には本家案件ですが要望が上がってきたのでメモ。

現在マストドンには「閲覧注意としてマークされたメディアも常に表示する」というオプションはありますが、「どのメディアも常に閲覧注意として扱う」オプションはありません。

そのためNSFWとしたほうがよいと考えられる画像にNSFWを付けないで/つけ忘れて投稿する人がいた場合に自衛する手段がありません。


  • I searched or browsed the repo’s other issues to ensure this is not a duplicate.

まれにトレンドタグの順位が正しくない

Sometimes the order of the trend tags is incorrect.
Immutable.fromJS() does not seem to preserve the order of the passed JSON. I guess it causes this problem.

#154#158 でトレンドタグの保存にソート済みセット型を使い始めてから、トレンドタグの順位が正しくない不具合がまれに発生します。
ChromeのJSコンソールを使用して検証したところ、Immutable.fromJS()が渡されたJSONの順番を保持していないように見えるので、それが原因ではないかと推測しています。

スクリーンショットなどを撮り忘れたので引き続き調査します。

トレンドタグ通知機能

定期的に現在流行のタグはこれ見たいなのをトゥートする
感じの機能の要望が挙がってます。

実況を未収載で行う人が増えた場合、LTLが静かになっている状況や、
LTLが盛り上がってる理由をいま来た人がすぐ察知できる、
等といった用途で使えそうではあります。

バッチでトゥートのタグを集計して何かする実装になるかなぁと思ってます。
ご検討お願い致します。

管理者メッセージ欄の要否

@takayamaki
#8 で管理者メッセージ機能を追加しましたが、
その後、 #131 でお知らせ機能の動的化を達成しました。

今後、お知らせ機能を使用するため、管理者メッセージ欄は不要となりますか?
不要の場合は削除するPRを作ろうと思いますので、
お手すきの際にご回答お願い致します。

特定のアカウントにおいて、LTLの大規模な取得漏れが発生する

ある方が報告していた大規模なLTLの取得漏れですが、私のアカウントでも発生しました。

  • LTLには画像付きのトゥート・ハッシュタグのついたトゥートだけが流れてきます。
  • 他の人からは私が公開でしたトゥートは見えているようです。
  • HTLは正常に使うことができます。
  • WebUIに限らず、他のクライアントでも発生します。
  • 取得漏れが発生するようになる前に行った操作は「NSFW付きの画像をデフォルトで表示するかどうか」のチェックの解除です。この設定は自分で有効にした覚えがないのに有効になっていました。

  • I searched or browsed the repo’s other issues to ensure this is not a duplicate.
  • This bug happens on a tagged release and not on master (If you're a user, don't worry about this).

日本語交じりのURLの一部が自動リンクの対象にならない

問題は既知ですが調査結果を共有しておきます。

good or bad 入力内容 自動リンク処理結果
👍 https://www.yahoo.co.jp/ yahoo.co.jp/
👍 http://日本語.jp/about 日本語.jp/about
👎 http://dic.nicovideo.jp/a/アイドルマスター dic.nicovideo.jp/a/アイドルマスター

ホスト名に使われている日本語(おそらく非ASCII文字)はPunycodeで符号化されるらしく、有効です。
パス名に使われている日本語は無効になります。パス名を頭から読んでいって最初に非ASCII文字が出てきたところでURLとしての解釈が終わります。

Statusに投げられたプレーンテキストの処理にはtwitter-textのExtractorが使われているため、twitter-textの仕様だろうと思われます。

twitext

しかし、面白いことに(?)LinkCardは正常に取得できています。
linkcard

FetchLinkCardServiceによれば、LinkCardを生成する際のURL認識は正規表現で行われているようです。

タグTL上にin_reply_to入りのreply投稿も表示されてしまう

related #32

返信元、返信先ともに自分でないin_reply_to入りの投稿がタグTL上に表示されてしまいます。
これは本家そのままの挙動ですが、Usa_botのようなユーザからの投稿に反応してreplyを行う類のbotのメッセージにハッシュタグが含まれていた場合、当該のタグTLがbotのreplyで埋まってしまう可能性があるので変更する必要があると思います。

僕個人としては

  • 返信元、返信先のどちらかが自分の場合には表示する
  • それ以外の場合には表示しない

くらいが落としどころかなぁ、と思っています

お気に入りタグ機能から英大文字交じりのタグを開くとストリーミングが動作しない

2017-09-03-222503_960x959_scrot

  • タグに用いられる英字は本家の内部的には全て小文字に直されて扱われる
  • api/v1/timelines/tags/[tag]は大文字でリクエストが飛んでも内部で小文字に直して処理する
  • api/v1/streaming/?tag=[tag]は大文字でリクエストが飛んでも小文字に直さない
  • pumaの時点で内部的に小文字に直されているため、sidekiqは小文字のRedis Pub/Subチャンネルにしか流さない
  • お気に入りタグ機能のFavouriteTagクラスは英字の大小が異なるタグが共存できる
  • お気に入りタグ機能に記録されているタグのリンクからタグTLを開くと、大文字を小文字に直さずにリクエストする

上記の挙動より、大文字で記録されているFavouriteTagからタグTLを開くと、pumaによる初期状態の取得のみ動作し、node側のstreamingから新しいstatusが受信されません。

  • お気に入りタグ機能からタグTLを開く際、英大文字を小文字に直すようにする
  • FavouriteTagクラスのvalidates uniquenessで大文字小文字を区別しないようにする

CW欄に書かれたハッシュタグが機能するようにしてほしい

機能内容的には本家案件ですが要望が上がってきたのでメモ。

ハッシュタグは投稿の内容を端的に表すものであるので、注意すべき内容を書くときにたたまれる際の見出しとなるCW欄と機能的に近い部分がある。

それであれば、CW欄へハッシュタグを入力した際にもハッシュタグとして機能してほしい。

pngファイルが正常なものと判定されない?

https://imastodon.net/@dousenP として利用させていただいている者です。

https://hhiro.net/tmp/data/1500861571_20170724-AJURIKA.png
このpngファイルをトゥートに入れるためにアップロードしようとすると、エラーメッセージが出て失敗します。
エラーメッセージ: 422 バリデーションに失敗しました: FilePaperclip::Errors::NotIdentifiedByImageMagickError, FilePaperclip::Errors::NotIdentifiedByImageMagickError
他のインスタンスでは問題なくアップロードできるので、このインスタンスにおける画像アップロードの取り扱いを変更した箇所によるものと思われます。
以上お知らせします。

タグTLのunlisted投稿がAPI経由で外から閲覧できる

GET /api/v1/timelines/tag/:hashtag
がアクセストークン不要になっているため、任意のタグ付きunlisted投稿が外からトークンなしで見れるようになってしまっています。

「タグ付きunlistedのタグTLへの掲載はネタバレ防止・会話の分離が目的」
「そもそも個別のアカウントページに行けばだれでも見られる」
なのでこのままでいいという考えもできます。

アイマストドンでのhashtagタイムラインの扱いがまだ曖昧な印象があるので、そこ含めて検討が必要かと思います。

個人的にはトークンなしのアクセスのときunlistedは掲載しないという対応が妥当なのかなと思います。(そのような動作ができるかは未検討です。。。)

本家v2.4.1rcで追加されたTrending hashtags機能への対応

mastodon#7638 でTrending hashtagsという機能が追加されました。
こちらの独自機能と被る機能なので何らかの対応が必要ではないかと思います。

考えられる対応策としては

  • 独自機能を消して本家の機能に合わせる
  • 本家の機能は取り込まずに独自機能を維持する
  • (両方取り込む)

ですが、それ以外にもAPIエンドポイントをどうするか (本家に合わせるかどうか) や、本家の機能で追加されたUIを利用できないかなど検討すべき点はあるかと思いますのでIssueを建てておきます。

被お気に入り通知において、お気に入りされたtootの内容がまれに表示されない

https://imastodon.net/@aqua_cat/3007723

image

  • 被お気に入り通知において、お気に入りされたtootの内容がまれに表示されない
  • お気に入りされたtoot( https://imastodon.net/@aqua_cat/2990044 )は現在も存在する
  • その場での通知では表示されるが、後から開いた場合には表示されない

状況証拠からすると、

  • /api/v1/streaming/notificationsを通じて取得した場合は問題なく表示され、
  • /api/v1/notifications から取得した場合に元tootの取得に失敗する場合がある

ものと思われる

シングルカラム状態で画面を切り替えるとお気に入りタグ機能の自動入力ロックが外れる

シングルカラム状態の投稿画面でお気に入りタグ機能のタグロックを行い画面を切り替えると、favourite_tagsコンポーネントのstateが失われてしまうため、投稿画面に戻った時に再度ロックしなければならず面倒です。

何らかの方法でfavourite_tagsコンポーネントの切り替え前後のstateを保持する必要があります。

フォローするユーザの推薦機能

新しく来られた方の定着率をちょっとだけ上げたいなと最近少し思いました。

本家では「新規登録時に指定されたアカウントまたは管理者を自動フォローする mastodon#4871 」という機能が入っておりアイマストドンではお知らせアカウント(imastodon)を設定していますが、これでは不十分だと思っています。

他インスタンスにはない、アイドルマスターというこのインスタンスの特徴を生かしてどのようにするかなと思い、以下のようなアイデアを思いつきました。

  • 「好きなアイドル」を複数人、プロフィールとして登録できるようにする
    • 表示する/しないも選択可能にする
  • 「興味があるかもしれないユーザ」として、下記のようなユーザを推薦する
    • 同じアイドルを好きなアイドルとして登録しているユーザ
    • 「好きなアイドルと、ユニットを組んだことがあるアイドル」のことを好きなユーザ

モバイル環境から閲覧時の検索カラム位置について

モバイル環境から閲覧した際に検索カラムが真ん中にあり、
スワイプですばやく通知やHTLに移れない状態です
検索の使用率は低いと思われますので左端に移動できないでしょうか?


  • I searched or browsed the repo’s other issues to ensure this is not a duplicate.
    f0644452d64d0b15

フィルター機能の「フィルターされました」を非表示に

ver2.4.3から実装されたフィルター機能にて、指定したワードがTLに現れると「フィルターされました」と表示されますが、これを非表示にできるようなオプションが欲しいです。
フィルター機能が実装される前の正規表現ミュートと同じ表示形式に戻す、と捉えてもらっても良いです。

そもそもフィルター機能は「見たくないものを見えないようにする(ことで、各々の快適さを向上する)」ためのものであり、「見たくないものが流れました」という通知は快適さを著しく損ねるものです。

この件に関しては本家でも議論されていますが、「ページネーションが正しく機能するため」という理由であるらしく(The placeholder is required for pagination to work properly.)、正常な動作のために快適性を阻害していると言えます。
また、フィルターしたトゥートを表示できるようにしてもいいというフィードバック(I have received a lot of positive feedback for the fact that the filter does not let you click to see what's underneath, or reveal which of the filters it is.)があったとも言っていますが、フィルターした時点で該当するワードを含むトゥートが表示されない責任は本人が負うべきというのが私の考えです。

本家の議論の推移によってはいずれ改善されるかも知れませんが、先日アイマストドン内で話題に上がった際にいくつかの賛成を頂いたので、可能であればそれよりも早く独自に実装して欲しいと思います。

fireshot-capture-180---im s

WebSocketのstreamingが一部条件において新しいメッセージを正しく配送していない

現在、アイマストドンのストリーミングはアクセストークンの有無とlocal/federatedとで2x2のマトリクスになっていますが、アクセストークン有り/local指定有りの際に新規statusのメッセージがWebsocketへ流れてきていません。

アクセストークン有り local指定有りの場合は、公開/未収載ともに流れてくる動作が期待されています。

左上:アクセストークン有り local指定有り 右上:アクセストークン無し local指定有り
左下:アクセストークン有り local指定無し 右下:アクセストークン無し local指定無し

公開の場合

image

未収載の場合

image

  • I searched or browsed the repo’s other issues to ensure this is not a duplicate.
  • This bug happens on a tagged release and not on master (If you're a user, don't worry about this).

シングルカラム状態において、未収載以下への返信の際に公開範囲が引き継がれない

  1. スマホ、またはブラウザの幅を狭くしてシングルカラム状態にします
  2. 未収載以下のtootに対する返信ボタンを押します
  3. 公開範囲が引き継がれません

一瞬引き継がれた後にpublicに再設定されている様子が稀に見えるので、

  1. カラム遷移
  2. 公開範囲引継ぎ
  3. react-redux actionのlockTagCompose
  4. ロックされているタグがないのでpublic
    というような処理をたどっているような予感がします

アイマス語を利用しているユーザーが投稿する言語を指定せずに日本語投稿を作成すると、アクティビティに含まれる言語情報が jaIM になる

発生する現象

ユーザー設定 にて表示言語を 日本語(im@s) かつ投稿する言語を 自動検出 にしている場合、投稿を作成すると、外部インスタンスにおいて当該投稿が lang="jaIm" として表示されます。
Based on v2.6.5 アイマストドンカスタムでは、まだウェブで投稿を閲覧する際に lang プロパティが指定されておらず目立った問題にはならないと思いますが、そのうち新しいバージョンに追従した場合に、日本語の文字が中華フォントで表示されるような環境が出てくるかもしれません。

期待する動作

div タグの lang プロパティに含めることができる値は IETF の BCP 47 でその構文が定義されているらしいので、これに準拠した文字列が含まれているべきです。

Steps to reproduce the problem

リモートフォローされているユーザーが [発生する現象] で示す条件で投稿を作成した場合、連合先の比較的新しい Mastodon で投稿文を閲覧した場合に、lang="jaIm" 属性が指定されていることが確認できます。

フォローリクエストを承認した後、承認したユーザのフォロー返しをその場でしたい

image

  • フォローリクエストの右辺りにフォローボタンを表示してほしい
  • フォローリクエスト承認後、現状の挙動だとそのまま消えるが、これを承認/拒否ボタンのみ消し、リクエスト元アカウントはカラム移動するまで消さない

上記二点の組み合わせによって、フォローリクエストを承認したアカウントへのフォロー返しが簡単になると嬉しいという要望がありました

微妙に本家案件のような気がしますがアイマストドンで先に実装してみてもいいかな、と思っています

実況をするとLTLを占領してしまう問題をどうにかする

現状のアイマストドンでは、ハッシュタグ付きのtootを未収載で行ってもタグカラムに表示されないので、「LTLは占領したくないけど実況はしたい」というニーズに対応できていません。

下記を実装することで、

  • 実況したい人
    • タグをいちいち手入力しなくてもタグ入りで連投できるようになる
    • 上記タグ自動入力機能の連動で未収載にできるようになる
      • タグを宣伝したいときには手動で公開範囲を設定すればよい
    • タグを追いたいときはタグカラムを開けば追える(しかもお気に入りタグ機能から簡単に開ける
  • 実況以外の話がしたい人
    • 流速の早いタグの大半が未収載になるのでLTLが占領されなくなる

というような棲み分けならびにアイマストドンの緩やかな規模の拡張ができるのではないかと思います。

  • 未収載タグ付きのtootがapi/v1/streaming/hashtagに出るようにする
  • friends.nicoからお気に入りタグ機能を拝借する
    • お気に入りタグ機能の自動入力連動で公開範囲をセットするようにする

一部の高解像度端末において端末画面のスクリーンショットが投稿できない

発生している事象

iPad Pro 12.9(2048x2732)やiPhone XS Max(1242x2688)などの高解像度端末において、画面のスクリーンショットを投稿できない場合があります。

原因

クライアントではなくWebUIから投稿されるスクリーンショットはiOS、Android、Windowsの区別なく、png形式で/api/v1/mediaへPOSTされます。

その際、画像はいったんUploadComposeアクションを経由してresizeImage関数に渡り、ここでメタデータによる画像回転がある場合にメタデータによる回転を削除してデータそのものを回転させる処理や画像サイズが大きすぎる場合に縮小するなどの処理を行いアップロードされますが、その圧縮形式は元ファイルの形式に従うためスクリーンショットの場合はpng形式で送信されます。

その際、高解像度のpng形式画像はその画像傾向によりアップロードできる画像ファイルのサイズ上限である8MBを超えてしまう場合があります。

アイマストドンはImgConverterにより透明部分のないpng画像をjpg形式に変換して保存するようになっていますが、paperclipによる画像ファイルのサイズ上限のvalidationはImgConverterによる変換よりも前に行われているようで、そのために変換が試みられることなくエラーレスポンスが返答されています。

本家v2.7.0追従へのハッシュタグタイムラインの改修方法

@fvh-P @takayamaki
お疲れ様です。 v2.7.0 マージPRを作っていて困ったことになりましたので相談させてください。

今回のverupで、Add joining several hashtags in a single column mastodon#8904 の改修が入っていて、
これが既に実装済みのハッシュタグタイムライン周りとコンフリクトとしてます。

アイマストドン v2.7.0
7e2afb48269033bc ede1da97087fece0

お手数ですが方針をご検討頂けないでしょうか。
よろしくお願い致します。
(一旦、本家の状態にタイムラインに未収載を含めるかどうかの分岐だけ
マージした状態でリリースして、後ほど必要な機能を復活させていくのがベターですかね)

ちなみに今コンフリクト起きてるdiffです(5ファイル)
diff --cc app/javascript/mastodon/actions/streaming.js
index 68279c4b3,cd319709d..000000000
--- a/app/javascript/mastodon/actions/streaming.js
+++ b/app/javascript/mastodon/actions/streaming.js
@@@ -51,6 -51,6 +51,6 @@@ const refreshHomeTimelineAndNotificatio
  export const connectUserStream      = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);
  export const connectCommunityStream = ({ onlyMedia } = {}) => connectTimelineStream(`community${onlyMedia ? ':media' : ''}`, `public:local${onlyMedia ? ':media' : ''}`);
  export const connectPublicStream    = ({ onlyMedia } = {}) => connectTimelineStream(`public${onlyMedia ? ':media' : ''}`, `public${onlyMedia ? ':media' : ''}`);
- export const connectHashtagStream   = (tag, isLocal) => connectTimelineStream(`hashtag:${tag}`, `hashtag${ isLocal ? ':local' : '' }&tag=${tag}`);
 -export const connectHashtagStream   = (id, tag, accept) => connectTimelineStream(`hashtag:${id}`, `hashtag&tag=${tag}`, null, accept);
++export const connectHashtagStream   = (id, tag, isLocal, accept) => connectTimelineStream(`hashtag:${id}`, `hashtag${ isLocal ? ':local' : '' }&tag=${tag}`, null, accept);
  export const connectDirectStream    = () => connectTimelineStream('direct', 'direct');
  export const connectListStream      = id => connectTimelineStream(`list:${id}`, `list&list=${id}`);
diff --cc app/javascript/mastodon/actions/timelines.js
index a693f4ca7,6e7bd027c..000000000
--- a/app/javascript/mastodon/actions/timelines.js
+++ b/app/javascript/mastodon/actions/timelines.js
@@@ -79,10 -96,17 +96,21 @@@ export const expandCommunityTimelin
  export const expandAccountTimeline         = (accountId, { maxId, withReplies } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies, max_id: maxId });
  export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
  export const expandAccountMediaTimeline    = (accountId, { maxId } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true });
++<<<<<<< HEAD
 +export const expandHashtagTimeline         = (hashtag, { maxId, isLocal } = {}, done = noOp) => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}${isLocal ? '?local=true' : ''}`, { max_id: maxId }, done);
++=======
++>>>>>>> v2.7.0
  export const expandListTimeline            = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done);
+ export const expandHashtagTimeline         = (hashtag, { maxId, tags } = {}, done = noOp) => {
+   return expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, {
+     max_id: maxId,
+     any:    parseTags(tags, 'any'),
+     all:    parseTags(tags, 'all'),
+     none:   parseTags(tags, 'none'),
+   }, done);
+ };
  
- export function expandTimelineRequest(timeline) {
+ export function expandTimelineRequest(timeline, isLoadingMore) {
    return {
      type: TIMELINE_EXPAND_REQUEST,
      timeline,
diff --cc app/javascript/mastodon/features/hashtag_timeline/components/column_settings.js
index f844a52f4,9c9f62d82..000000000
--- a/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.js
+++ b/app/javascript/mastodon/features/hashtag_timeline/components/column_settings.js
@@@ -1,93 -1,100 +1,190 @@@
  import React from 'react';
  import PropTypes from 'prop-types';
  import ImmutablePropTypes from 'react-immutable-proptypes';
++<<<<<<< HEAD
 +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 +import Button from '../../../components/button';
 +import SettingToggle from '../components/setting_toggle';
 +import SettingText from '../components/setting_text';
 +import { Map as ImmutableMap } from 'immutable';
 +
 +const messages = defineMessages({
 +  filter_regex: { id: 'tag.column_settings.filter_regex', defaultMessage: 'Filter out by regular expressions' },
 +  show_local_only: { id: 'tag.column_settings.show_local_only', defaultMessage: 'Show local only' },
 +  settings: { id: 'tag.settings', defaultMessage: 'Column settings' },
 +  add_favourite_tags_public: { id: 'tag.add_favourite.public', defaultMessage: 'add in the favourite tags (Public)' },
 +  add_favourite_tags_unlisted: { id: 'tag.add_favourite.unlisted', defaultMessage: 'add in the favourite tags (Unlisted)' },
 +  remove_favourite_tags: { id: 'tag.remove_favourite', defaultMessage: 'Remove from the favourite tags' },
 +});
 +
 +@injectIntl
 +export default class ColumnSettings extends React.PureComponent {
 +
 +  static propTypes = {
 +    tag: PropTypes.string.isRequired,
 +    settings: ImmutablePropTypes.map.isRequired,
 +    onChange: PropTypes.func.isRequired,
 +    addFavouriteTags: PropTypes.func.isRequired,
 +    removeFavouriteTags: PropTypes.func.isRequired,
 +    isRegistered: PropTypes.bool.isRequired,
 +    intl: PropTypes.object.isRequired,
 +  };
 +
 +  addFavouriteTags = (visibility) => {
 +    this.props.addFavouriteTags(this.props.tag, visibility);
 +  };
 +
 +  addPublic = () => {
 +    this.addFavouriteTags('public');
 +  };
 +
 +  addUnlisted = () => {
 +    this.addFavouriteTags('unlisted');
 +  };
 +
 +  removeFavouriteTags = () => {
 +    this.props.removeFavouriteTags(this.props.tag);
 +  };
 +
 +  render () {
 +    const { tag, settings, onChange, intl, isRegistered } = this.props;
 +    const initialSettings = ImmutableMap({
 +      shows: ImmutableMap({
 +        local: false,
 +      }),
 +
 +      regex: ImmutableMap({
 +        body: '',
 +      }),
 +    });
 +
 +    const favouriteTagButton = (isRegistered) => {
 +      if(isRegistered) {
 +        return (
 +          <div className='column-settings__row'>
 +            <Button className='favourite-tags__remove-button-in-column' text={intl.formatMessage(messages.remove_favourite_tags)} onClick={this.removeFavouriteTags} block />
 +          </div>
 +        );
 +      } else {
 +        return (
 +          <div className='column-settings__row'>
 +            <Button className='favourite-tags__add-button-in-column' text={intl.formatMessage(messages.add_favourite_tags_public)} onClick={this.addPublic} block />
 +            <Button className='favourite-tags__add-button-in-column' text={intl.formatMessage(messages.add_favourite_tags_unlisted)} onClick={this.addUnlisted} block />
 +          </div>
 +        );
 +      }
 +    };
 +
 +    return (
 +      <div>
 +        {favouriteTagButton(isRegistered)}
 +        <span className='column-settings__section'><FormattedMessage id='tag.column_settings.basic' defaultMessage='Basic' /></span>
 +
 +        <div className='column-settings__row'>
 +          <SettingToggle tag={tag} prefix='hashtag_timeline' settings={settings.get(`${tag}`, initialSettings)} settingKey={['shows', 'local']} onChange={onChange} label={intl.formatMessage(messages.show_local_only)} />
 +        </div>
 +
 +        <span className='column-settings__section'><FormattedMessage id='tag.column_settings.advanced' defaultMessage='Advanced' /></span>
 +
 +        <div className='column-settings__row'>
 +          <SettingText tag={tag} prefix='hashtag_timeline' settings={settings.get(`${tag}`, initialSettings)} settingKey={['regex', 'body']} onChange={onChange} label={intl.formatMessage(messages.filter_regex)} />
 +        </div>
++=======
+ import { injectIntl, FormattedMessage } from 'react-intl';
+ import Toggle from 'react-toggle';
+ import AsyncSelect from 'react-select/lib/Async';
+ 
+ export default @injectIntl
+ class ColumnSettings extends React.PureComponent {
+ 
+   static propTypes = {
+     settings: ImmutablePropTypes.map.isRequired,
+     onChange: PropTypes.func.isRequired,
+     onLoad: PropTypes.func.isRequired,
+     intl: PropTypes.object.isRequired,
+   };
+ 
+   state = {
+     open: this.hasTags(),
+   };
+ 
+   hasTags () {
+     return ['all', 'any', 'none'].map(mode => this.tags(mode).length > 0).includes(true);
+   }
+ 
+   tags (mode) {
+     let tags = this.props.settings.getIn(['tags', mode]) || [];
+     if (tags.toJSON) {
+       return tags.toJSON();
+     } else {
+       return tags;
+     }
+   };
+ 
+   onSelect = (mode) => {
+     return (value) => {
+       this.props.onChange(['tags', mode], value);
+     };
+   };
+ 
+   onToggle = () => {
+     if (this.state.open && this.hasTags()) {
+       this.props.onChange('tags', {});
+     }
+     this.setState({ open: !this.state.open });
+   };
+ 
+   modeSelect (mode) {
+     return (
+       <div className='column-settings__section'>
+         {this.modeLabel(mode)}
+         <AsyncSelect
+           isMulti
+           autoFocus
+           value={this.tags(mode)}
+           settings={this.props.settings}
+           settingPath={['tags', mode]}
+           onChange={this.onSelect(mode)}
+           loadOptions={this.props.onLoad}
+           classNamePrefix='column-settings__hashtag-select'
+           name='tags'
+         />
+       </div>
+     );
+   }
+ 
+   modeLabel (mode) {
+     switch(mode) {
+     case 'any':  return <FormattedMessage id='hashtag.column_settings.tag_mode.any' defaultMessage='Any of these' />;
+     case 'all':  return <FormattedMessage id='hashtag.column_settings.tag_mode.all' defaultMessage='All of these' />;
+     case 'none': return <FormattedMessage id='hashtag.column_settings.tag_mode.none' defaultMessage='None of these' />;
+     }
+     return '';
+   };
+ 
+   render () {
+     return (
+       <div>
+         <div className='column-settings__row'>
+           <div className='setting-toggle'>
+             <Toggle
+               id='hashtag.column_settings.tag_toggle'
+               onChange={this.onToggle}
+               checked={this.state.open}
+             />
+             <span className='setting-toggle__label'>
+               <FormattedMessage id='hashtag.column_settings.tag_toggle' defaultMessage='Include additional tags in this column' />
+             </span>
+           </div>
+         </div>
+         {this.state.open &&
+           <div className='column-settings__hashtags'>
+             {this.modeSelect('any')}
+             {this.modeSelect('all')}
+             {this.modeSelect('none')}
+           </div>
+         }
++>>>>>>> v2.7.0
        </div>
      );
    }
diff --cc app/javascript/mastodon/features/hashtag_timeline/containers/column_settings_container.js
index f1aca44dd,c5098052c..000000000
--- a/app/javascript/mastodon/features/hashtag_timeline/containers/column_settings_container.js
+++ b/app/javascript/mastodon/features/hashtag_timeline/containers/column_settings_container.js
@@@ -1,31 -1,31 +1,60 @@@
  import { connect } from 'react-redux';
  import ColumnSettings from '../components/column_settings';
++<<<<<<< HEAD
 +import { changeSetting, saveSettings } from '../../../actions/settings';
 +import { addFavouriteTags, removeFavouriteTags } from '../../../actions/favourite_tags';
 +
 +const mapStateToProps = (state, { tag }) => ({
 +  settings: state.getIn(['settings', 'tag']),
 +  isRegistered: state.getIn(['favourite_tags', 'tags']).some(t => t.get('name') === tag),
 +});
 +
 +const mapDispatchToProps = dispatch => ({
 +
 +  onChange (tag, key, checked) {
 +    dispatch(changeSetting(['tag', `${tag}`, ...key], checked));
 +  },
 +
 +  onSave () {
 +    dispatch(saveSettings());
 +  },
 +
 +  addFavouriteTags (tag, visibility) {
 +    dispatch(addFavouriteTags(tag, visibility));
 +  },
 +
 +  removeFavouriteTags (tag) {
 +    dispatch(removeFavouriteTags(tag));
 +  },
 +
++=======
+ import { changeColumnParams } from '../../../actions/columns';
+ import api from '../../../api';
+ 
+ const mapStateToProps = (state, { columnId }) => {
+   const columns = state.getIn(['settings', 'columns']);
+   const index   = columns.findIndex(c => c.get('uuid') === columnId);
+ 
+   if (!(columnId && index >= 0)) {
+     return {};
+   }
+ 
+   return { settings: columns.get(index).get('params') };
+ };
+ 
+ const mapDispatchToProps = (dispatch, { columnId }) => ({
+   onChange (key, value) {
+     dispatch(changeColumnParams(columnId, key, value));
+   },
+ 
+   onLoad (value) {
+     return api().get('/api/v2/search', { params: { q: value } }).then(response => {
+       return (response.data.hashtags || []).map((tag) => {
+         return { value: tag.name, label: `#${tag.name}` };
+       });
+     });
+   },
++>>>>>>> v2.7.0
  });
  
  export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings);
diff --cc app/javascript/mastodon/features/hashtag_timeline/index.js
index b877f6700,c2e026d13..000000000
--- a/app/javascript/mastodon/features/hashtag_timeline/index.js
+++ b/app/javascript/mastodon/features/hashtag_timeline/index.js
@@@ -5,7 -5,7 +5,11 @@@ import StatusListContainer from './cont
  import Column from '../../components/column';
  import ColumnHeader from '../../components/column_header';
  import ColumnSettingsContainer from './containers/column_settings_container';
++<<<<<<< HEAD
 +import { expandHashtagTimeline } from '../../actions/timelines';
++=======
+ import { expandHashtagTimeline, clearTimeline } from '../../actions/timelines';
++>>>>>>> v2.7.0
  import { addColumn, removeColumn, moveColumn } from '../../actions/columns';
  import { FormattedMessage } from 'react-intl';
  import { connectHashtagStream } from '../../actions/streaming';
@@@ -47,8 -72,18 +78,23 @@@ class HashtagTimeline extends React.Pur
      this.column.scrollTop();
    }
  
++<<<<<<< HEAD
 +  _subscribe (dispatch, id, isLocal) {
 +    this.disconnect = dispatch(connectHashtagStream(id, isLocal));
++=======
+   _subscribe (dispatch, id, tags = {}) {
+     let any  = (tags.any || []).map(tag => tag.value);
+     let all  = (tags.all || []).map(tag => tag.value);
+     let none = (tags.none || []).map(tag => tag.value);
+ 
+     [id, ...any].map((tag) => {
+       this.disconnects.push(dispatch(connectHashtagStream(id, tag, (status) => {
+         let tags = status.tags.map(tag => tag.name);
+         return all.filter(tag => tags.includes(tag)).length === all.length &&
+                none.filter(tag => tags.includes(tag)).length === 0;
+       })));
+     });
++>>>>>>> v2.7.0
    }
  
    _unsubscribe () {
@@@ -59,18 -92,20 +103,35 @@@
    }
  
    componentDidMount () {
++<<<<<<< HEAD
 +    const { dispatch, isLocal } = this.props;
 +    const { id } = this.props.params;
 +
 +    dispatch(expandHashtagTimeline(id, isLocal));
 +    this._subscribe(dispatch, id, isLocal);
 +  }
 +
 +  componentWillReceiveProps (nextProps) {
 +    if (nextProps.params.id !== this.props.params.id || nextProps.isLocal !== this.props.isLocal) {
 +      this.props.dispatch(expandHashtagTimeline(nextProps.params.id, nextProps.isLocal));
 +      this._unsubscribe();
 +      this._subscribe(this.props.dispatch, nextProps.params.id, nextProps.isLocal);
++=======
+     const { dispatch } = this.props;
+     const { id, tags } = this.props.params;
+ 
+     dispatch(expandHashtagTimeline(id, { tags }));
+   }
+ 
+   componentWillReceiveProps (nextProps) {
+     const { dispatch, params } = this.props;
+     const { id, tags } = nextProps.params;
+     if (id !== params.id || !isEqual(tags, params.tags)) {
+       this._unsubscribe();
+       this._subscribe(dispatch, id, tags);
+       this.props.dispatch(clearTimeline(`hashtag:${id}`));
+       this.props.dispatch(expandHashtagTimeline(id, { tags }));
++>>>>>>> v2.7.0
      }
    }
  
@@@ -83,7 -118,8 +144,12 @@@
    }
  
    handleLoadMore = maxId => {
++<<<<<<< HEAD
 +    this.props.dispatch(expandHashtagTimeline(this.props.params.id, { maxId, isLocal: this.props.isLocal }));
++=======
+     const { id, tags } = this.props.params;
+     this.props.dispatch(expandHashtagTimeline(id, { maxId, tags }));
++>>>>>>> v2.7.0
    }
  
    render () {
@@@ -104,9 -140,7 +170,13 @@@
            multiColumn={multiColumn}
            showBackButton
          >
++<<<<<<< HEAD
 +          <ColumnSettingsContainer
 +            tag={id}
 +          />
++=======
+           {columnId && <ColumnSettingsContainer columnId={columnId} />}
++>>>>>>> v2.7.0
          </ColumnHeader>
  
          <StatusListContainer

Android/iOSで撮影したスクリーンショットがpng形式なので画像サイズが嵩む

アイマストドンの画像投稿の利用傾向として、デレステ/ミリシタを始めとしたスクリーンショットの投稿が多いです。

Android/iOSでスクリーンショットを撮影するとpng形式で保存されるため、アイマストドンへ投稿された際に再圧縮を経てもファイルサイズが1MBを超える場合ほとんどです。

s3に蓄積されるメディアファイルは参照される可能性がある以上削除することは出来ないため、データ転送量とストレージ料金の観点からpng形式が不得意な傾向の画像をjpg形式に変換して保存する必要があります。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.