imas / mastodon Goto Github PK
View Code? Open in Web Editor NEWThis project forked from mastodon/mastodon
アイマストドン / A microblogging server, variety of Mastodon
Home Page: https://imastodon.net/
License: GNU Affero General Public License v3.0
This project forked from mastodon/mastodon
アイマストドン / A microblogging server, variety of Mastodon
Home Page: https://imastodon.net/
License: GNU Affero General Public License v3.0
リリース後 #39 の修正を戻す
master
(If you're a user, don't worry about this).(おそらく) 本日のアップデート以降、タグロック中の投稿後、カーソル位置がハッシュタグの後ろに行ってしまいます。
カーソル位置を | で示すと、
従来は
「| #hogehoge」
だったのが、今は
「#hogehoge|」
になってしまいます。
表題の通り、フィルター機能で「日本語(im@s)」の訳語がズレています。
現行の実装は下記の画像を参照ください。
正しい訳語は下表の通りと考えます。
日本語 | 日本語(im@s)(誤) | 日本語(im@s)(正) |
---|---|---|
ホームタイムライン | 楽屋 | オフィス |
公開タイムライン | ライブステージ | (public timeline に相当するim@s語ありましたっけ……) |
master
(If you're a user, don't worry about this).問題の内容はあとで書く
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 same as normal theme.
These are bad-contrast for unknown reason.
It's not reproduce on vanilla version of mastodon 2.6.1 and 2.7.3 as far as I know.
v1.4系列にて行われたWebpackの導入ならびにJavascriptの置き場所変更が
アイマストドン独自機能のcommitとコンフリクトしているためマージに苦戦している件
Github pagesでカスタムドメインのhttpsが使えるようになったのでアイマストドンFAQのリンク先をhttpsにしたいです。
https://blog.github.com/2018-05-01-github-pages-custom-domains-https/
キーボードショートカットヘルプについて、アイコンがimastodon-faqと被ってしまうためメニュー内は #137 で変更していましたが、 https://github.com/imas/mastodon/blob/imastodon/app/javascript/mastodon/features/keyboard_shortcuts/index.js#L24 を変更し忘れていました
master
(If you're a user, don't worry about this).機能内容的には本家案件ですが要望が上がってきたのでメモ。
現在マストドンには「閲覧注意としてマークされたメディアも常に表示する」というオプションはありますが、「どのメディアも常に閲覧注意として扱う」オプションはありません。
そのためNSFWとしたほうがよいと考えられる画像にNSFWを付けないで/つけ忘れて投稿する人がいた場合に自衛する手段がありません。
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を作ろうと思いますので、
お手すきの際にご回答お願い致します。
以前はunlistedまたはprivate投稿には鍵のアイコンが付いていたと記憶しています
アプデ時の巻戻りと考えられますので一応ご報告を
タグロックをすると投稿ボタンのラベルとなっているトゥート!
の!
が無くなります。
master
(If you're a user, don't worry about this).ある方が報告していた大規模なLTLの取得漏れですが、私のアカウントでも発生しました。
master
(If you're a user, don't worry about this).問題は既知ですが調査結果を共有しておきます。
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の仕様だろうと思われます。
しかし、面白いことに(?)LinkCardは正常に取得できています。
FetchLinkCardServiceによれば、LinkCardを生成する際のURL認識は正規表現で行われているようです。
related #32
返信元、返信先ともに自分でないin_reply_to入りの投稿がタグTL上に表示されてしまいます。
これは本家そのままの挙動ですが、Usa_botのようなユーザからの投稿に反応してreplyを行う類のbotのメッセージにハッシュタグが含まれていた場合、当該のタグTLがbotのreplyで埋まってしまう可能性があるので変更する必要があると思います。
僕個人としては
くらいが落としどころかなぁ、と思っています
上記の挙動より、大文字で記録されているFavouriteTagからタグTLを開くと、pumaによる初期状態の取得のみ動作し、node側のstreamingから新しいstatusが受信されません。
https://imastodon.net/@gomao/100304178152914876
2.4.1から発生しているように見えます。他のインスタンス( mstdn.maud.io )では見たことがないので2.4.1かアイマストドン固有の問題な気がします。
master
(If you're a user, don't worry about this).機能内容的には本家案件ですが要望が上がってきたのでメモ。
ハッシュタグは投稿の内容を端的に表すものであるので、注意すべき内容を書くときにたたまれる際の見出しとなるCW欄と機能的に近い部分がある。
それであれば、CW欄へハッシュタグを入力した際にもハッシュタグとして機能してほしい。
https://imastodon.net/@dousenP として利用させていただいている者です。
https://hhiro.net/tmp/data/1500861571_20170724-AJURIKA.png
このpngファイルをトゥートに入れるためにアップロードしようとすると、エラーメッセージが出て失敗します。
エラーメッセージ: 422 バリデーションに失敗しました: FilePaperclip::Errors::NotIdentifiedByImageMagickError, FilePaperclip::Errors::NotIdentifiedByImageMagickError
他のインスタンスでは問題なくアップロードできるので、このインスタンスにおける画像アップロードの取り扱いを変更した箇所によるものと思われます。
以上お知らせします。
詳細は分かり次第書きますが、任意のタグTLにおいて、Stramingが動作していないようです。
GET /api/v1/timelines/tag/:hashtag
がアクセストークン不要になっているため、任意のタグ付きunlisted投稿が外からトークンなしで見れるようになってしまっています。
「タグ付きunlistedのタグTLへの掲載はネタバレ防止・会話の分離
が目的」
「そもそも個別のアカウントページに行けばだれでも見られる」
なのでこのままでいいという考えもできます。
アイマストドンでのhashtagタイムラインの扱いがまだ曖昧な印象があるので、そこ含めて検討が必要かと思います。
個人的にはトークンなしのアクセスのときunlistedは掲載しないという対応が妥当なのかなと思います。(そのような動作ができるかは未検討です。。。)
投稿内容を入力するテキストボックスでTabを連打すれば再現できます。
master
(If you're a user, don't worry about this).mastodon#7638 でTrending hashtagsという機能が追加されました。
こちらの独自機能と被る機能なので何らかの対応が必要ではないかと思います。
考えられる対応策としては
ですが、それ以外にもAPIエンドポイントをどうするか (本家に合わせるかどうか) や、本家の機能で追加されたUIを利用できないかなど検討すべき点はあるかと思いますのでIssueを建てておきます。
https://imastodon.net/@aqua_cat/3007723
状況証拠からすると、
ものと思われる
シングルカラム状態の投稿画面でお気に入りタグ機能のタグロックを行い画面を切り替えると、favourite_tagsコンポーネントのstateが失われてしまうため、投稿画面に戻った時に再度ロックしなければならず面倒です。
何らかの方法でfavourite_tagsコンポーネントの切り替え前後のstateを保持する必要があります。
新しく来られた方の定着率をちょっとだけ上げたいなと最近少し思いました。
本家では「新規登録時に指定されたアカウントまたは管理者を自動フォローする mastodon#4871 」という機能が入っておりアイマストドンではお知らせアカウント(imastodon)を設定していますが、これでは不十分だと思っています。
他インスタンスにはない、アイドルマスターというこのインスタンスの特徴を生かしてどのようにするかなと思い、以下のようなアイデアを思いつきました。
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.)があったとも言っていますが、フィルターした時点で該当するワードを含むトゥートが表示されない責任は本人が負うべきというのが私の考えです。
本家の議論の推移によってはいずれ改善されるかも知れませんが、先日アイマストドン内で話題に上がった際にいくつかの賛成を頂いたので、可能であればそれよりも早く独自に実装して欲しいと思います。
現在、アイマストドンのストリーミングはアクセストークンの有無とlocal/federatedとで2x2のマトリクスになっていますが、アクセストークン有り/local指定有りの際に新規statusのメッセージがWebsocketへ流れてきていません。
アクセストークン有り local指定有りの場合は、公開/未収載ともに流れてくる動作が期待されています。
左上:アクセストークン有り local指定有り 右上:アクセストークン無し local指定有り
左下:アクセストークン有り local指定無し 右下:アクセストークン無し local指定無し
master
(If you're a user, don't worry about this).一瞬引き継がれた後にpublicに再設定されている様子が稀に見えるので、
ユーザー設定 にて表示言語を 日本語(im@s) かつ投稿する言語を 自動検出 にしている場合、投稿を作成すると、外部インスタンスにおいて当該投稿が lang="jaIm"
として表示されます。
Based on v2.6.5 アイマストドンカスタムでは、まだウェブで投稿を閲覧する際に lang
プロパティが指定されておらず目立った問題にはならないと思いますが、そのうち新しいバージョンに追従した場合に、日本語の文字が中華フォントで表示されるような環境が出てくるかもしれません。
div
タグの lang
プロパティに含めることができる値は IETF の BCP 47 でその構文が定義されているらしいので、これに準拠した文字列が含まれているべきです。
リモートフォローされているユーザーが [発生する現象] で示す条件で投稿を作成した場合、連合先の比較的新しい Mastodon で投稿文を閲覧した場合に、lang="jaIm"
属性が指定されていることが確認できます。
現状のアイマストドンでは、ハッシュタグ付きのtootを未収載で行ってもタグカラムに表示されないので、「LTLは占領したくないけど実況はしたい」というニーズに対応できていません。
下記を実装することで、
というような棲み分けならびにアイマストドンの緩やかな規模の拡張ができるのではないかと思います。
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による変換よりも前に行われているようで、そのために変換が試みられることなくエラーレスポンスが返答されています。
@fvh-P @takayamaki
お疲れ様です。 v2.7.0 マージPRを作っていて困ったことになりましたので相談させてください。
今回のverupで、Add joining several hashtags in a single column mastodon#8904 の改修が入っていて、
これが既に実装済みのハッシュタグタイムライン周りとコンフリクトとしてます。
アイマストドン | v2.7.0 |
---|---|
![]() |
![]() |
お手数ですが方針をご検討頂けないでしょうか。
よろしくお願い致します。
(一旦、本家の状態にタイムラインに未収載を含めるかどうかの分岐だけ
マージした状態でリリースして、後ほど必要な機能を復活させていくのがベターですかね)
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形式で保存されるため、アイマストドンへ投稿された際に再圧縮を経てもファイルサイズが1MBを超える場合ほとんどです。
s3に蓄積されるメディアファイルは参照される可能性がある以上削除することは出来ないため、データ転送量とストレージ料金の観点からpng形式が不得意な傾向の画像をjpg形式に変換して保存する必要があります。
https://imastodon.net/@hacusk/100586763790959359
mask-icon.svgが黒と透明な背景のみで構成されなければならないところが、フルグラフィックな画像になっていることが原因と思われます。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.