From d137d2ab878520dbcf7c45812b1e390aefdc6442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A9lanie=20Chauvel?= Date: Mon, 17 May 2021 22:31:35 +0200 Subject: [PATCH 01/32] =?UTF-8?q?Replace=20=E2=80=9Cstatus=E2=80=9D=20and?= =?UTF-8?q?=20=E2=80=9Cmessage=E2=80=9D=20by=20=E2=80=9Cpost=E2=80=9D=20in?= =?UTF-8?q?=20WebUI=20(#16271)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/locales/activerecord.en.yml | 2 +- config/locales/doorkeeper.en.yml | 8 ++++---- config/locales/en.yml | 2 +- config/locales/simple_form.en.yml | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/config/locales/activerecord.en.yml b/config/locales/activerecord.en.yml index ec8dad1b1..d5f19ca64 100644 --- a/config/locales/activerecord.en.yml +++ b/config/locales/activerecord.en.yml @@ -24,7 +24,7 @@ en: status: attributes: reblog: - taken: of status already exists + taken: of post already exists user: attributes: email: diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml index ec322f071..8aa099284 100644 --- a/config/locales/doorkeeper.en.yml +++ b/config/locales/doorkeeper.en.yml @@ -138,12 +138,12 @@ en: read:notifications: see your notifications read:reports: see your reports read:search: search on your behalf - read:statuses: see all statuses + read:statuses: see all posts write: modify all your account's data write:accounts: modify your profile write:blocks: block accounts and domains - write:bookmarks: bookmark statuses - write:favourites: favourite statuses + write:bookmarks: bookmark posts + write:favourites: favourite posts write:filters: create filters write:follows: follow people write:lists: create lists @@ -151,4 +151,4 @@ en: write:mutes: mute people and conversations write:notifications: clear your notifications write:reports: report other people - write:statuses: publish statuses + write:statuses: publish posts diff --git a/config/locales/en.yml b/config/locales/en.yml index d8ad5bd84..6056baf32 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1431,7 +1431,7 @@ en: edit_profile_step: You can customize your profile by uploading an avatar, header, changing your display name and more. If you’d like to review new followers before they’re allowed to follow you, you can lock your account. explanation: Here are some tips to get you started final_action: Start posting - final_step: 'Start posting! Even without followers your public messages may be seen by others, for example on the local timeline and in hashtags. You may want to introduce yourself on the #introductions hashtag.' + final_step: 'Start posting! Even without followers your public posts may be seen by others, for example on the local timeline and in hashtags. You may want to introduce yourself on the #introductions hashtag.' full_handle: Your full handle full_handle_hint: This is what you would tell your friends so they can message or follow you from another server. review_preferences_action: Change preferences diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index c4388ffc5..113aef2d3 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -52,7 +52,7 @@ en: setting_display_media_hide_all: Always hide media setting_display_media_show_all: Always show media setting_hide_network: Who you follow and who follows you will be hidden on your profile - setting_noindex: Affects your public profile and status pages + setting_noindex: Affects your public profile and post pages setting_show_application: The application you use to post will be displayed in the detailed view of your posts setting_use_blurhash: Gradients are based on the colors of the hidden visuals but obfuscate any details setting_use_pending_items: Hide timeline updates behind a click instead of automatically scrolling the feed @@ -197,12 +197,12 @@ en: severity: Rule notification_emails: digest: Send digest e-mails - favourite: Someone favourited your status + favourite: Someone favourited your post follow: Someone followed you follow_request: Someone requested to follow you mention: Someone mentioned you pending_account: New account needs review - reblog: Someone boosted your status + reblog: Someone boosted your post report: New report is submitted trending_tag: An unreviewed hashtag is trending rule: From 97539b6a96c1a3773aceb492d213620132d2a1fb Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 17 May 2021 22:36:08 +0200 Subject: [PATCH 02/32] Fix host check on healthcheck path not being disabled (#16270) Fixes #16251 There was a typo in #16243 --- config/initializers/1_hosts.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/1_hosts.rb b/config/initializers/1_hosts.rb index f470fddb3..6ff0845c4 100644 --- a/config/initializers/1_hosts.rb +++ b/config/initializers/1_hosts.rb @@ -31,6 +31,6 @@ Rails.application.configure do config.hosts << host if host.present? config.hosts << web_host if web_host.present? config.hosts.concat(alternate_domains) if alternate_domains.present? - config.hosts_authorization = { exclude: ->(request) { request.path == '/health' } } + config.host_authorization = { exclude: ->(request) { request.path == '/health' } } end end From 3f599c34334a6a75f95a3e22e405a9f56dc33dd0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 May 2021 22:36:29 +0200 Subject: [PATCH 03/32] Bump puma from 5.3.0 to 5.3.1 (#16257) Bumps [puma](https://github.com/puma/puma) from 5.3.0 to 5.3.1. - [Release notes](https://github.com/puma/puma/releases) - [Changelog](https://github.com/puma/puma/blob/master/History.md) - [Commits](https://github.com/puma/puma/compare/v5.3.0...v5.3.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 913e40b1e..ea92c032e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -428,7 +428,7 @@ GEM pry-rails (0.3.9) pry (>= 0.10.4) public_suffix (4.0.6) - puma (5.3.0) + puma (5.3.1) nio4r (~> 2.0) pundit (2.1.0) activesupport (>= 3.0.0) From 08d6a7764ffb957b2ee442dd75b54c295c013588 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 May 2021 22:36:44 +0200 Subject: [PATCH 04/32] Bump nokogiri from 1.11.3 to 1.11.4 (#16252) Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.11.3 to 1.11.4. - [Release notes](https://github.com/sparklemotion/nokogiri/releases) - [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md) - [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.11.3...v1.11.4) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index ea92c032e..1696facde 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -364,7 +364,7 @@ GEM net-ssh (>= 2.6.5, < 7.0.0) net-ssh (6.1.0) nio4r (2.5.7) - nokogiri (1.11.3) + nokogiri (1.11.4) mini_portile2 (~> 2.5.0) racc (~> 1.4) nokogumbo (2.0.4) From 689974b1ed081c238560d6b368609acc50dc7336 Mon Sep 17 00:00:00 2001 From: Zero King Date: Tue, 18 May 2021 18:13:44 +0000 Subject: [PATCH 05/32] Remove duplicate CSS property of margin (#16277) --- app/javascript/styles/mastodon/components.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index b0216f5ab..7ca027e81 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -3611,7 +3611,6 @@ a.status-card.compact:hover { span { display: block; float: left; - margin-left: 50%; transform: translateX(-50%); margin: 82px 0 0 50%; white-space: nowrap; From 94ba7b284ae0834605a0cc25dfea047e09f3a4e0 Mon Sep 17 00:00:00 2001 From: Genbu Hase Date: Sun, 8 Apr 2018 16:56:25 +0900 Subject: [PATCH 06/32] [New] Implement a feature of quote --- app/javascript/mastodon/actions/compose.js | 32 ++++++++- .../mastodon/components/status_action_bar.js | 7 ++ .../mastodon/components/status_content.js | 17 +++++ .../mastodon/containers/status_container.js | 5 ++ .../compose/components/compose_form.js | 2 + .../compose/components/quote_indicator.js | 67 +++++++++++++++++++ .../containers/quote_indicator_container.js | 24 +++++++ .../features/status/components/action_bar.js | 7 ++ .../features/status/components/card.js | 4 ++ .../mastodon/features/status/index.js | 6 ++ .../mastodon/locales/defaultMessages.json | 13 ++++ app/javascript/mastodon/locales/en.json | 2 + app/javascript/mastodon/locales/ja.json | 2 + app/javascript/mastodon/reducers/compose.js | 21 ++++++ .../styles/mastodon/components.scss | 28 ++++++-- app/services/fetch_link_card_service.rb | 4 +- 16 files changed, 231 insertions(+), 10 deletions(-) create mode 100644 app/javascript/mastodon/features/compose/components/quote_indicator.js create mode 100644 app/javascript/mastodon/features/compose/containers/quote_indicator_container.js diff --git a/app/javascript/mastodon/actions/compose.js b/app/javascript/mastodon/actions/compose.js index 891403969..26e7bfab2 100644 --- a/app/javascript/mastodon/actions/compose.js +++ b/app/javascript/mastodon/actions/compose.js @@ -20,6 +20,8 @@ export const COMPOSE_SUBMIT_FAIL = 'COMPOSE_SUBMIT_FAIL'; export const COMPOSE_REPLY = 'COMPOSE_REPLY'; export const COMPOSE_REPLY_CANCEL = 'COMPOSE_REPLY_CANCEL'; export const COMPOSE_DIRECT = 'COMPOSE_DIRECT'; +export const COMPOSE_QUOTE = 'COMPOSE_QUOTE'; +export const COMPOSE_QUOTE_CANCEL = 'COMPOSE_QUOTE_CANCEL'; export const COMPOSE_MENTION = 'COMPOSE_MENTION'; export const COMPOSE_RESET = 'COMPOSE_RESET'; export const COMPOSE_UPLOAD_REQUEST = 'COMPOSE_UPLOAD_REQUEST'; @@ -100,6 +102,25 @@ export function cancelReplyCompose() { }; }; +export function quoteCompose(status, router) { + return (dispatch, getState) => { + dispatch({ + type: COMPOSE_QUOTE, + status: status, + }); + + if (!getState().getIn(['compose', 'mounted'])) { + router.push('/statuses/new'); + } + }; +}; + +export function cancelQuoteCompose() { + return { + type: COMPOSE_QUOTE_CANCEL, + }; +}; + export function resetCompose() { return { type: COMPOSE_RESET, @@ -130,13 +151,22 @@ export function directCompose(account, routerHistory) { export function submitCompose(routerHistory) { return function (dispatch, getState) { - const status = getState().getIn(['compose', 'text'], ''); + let status = getState().getIn(['compose', 'text'], ''); const media = getState().getIn(['compose', 'media_attachments']); + const quoteId = getState().getIn(['compose', 'quote_from'], null); if ((!status || !status.length) && media.size === 0) { return; } + if (quoteId) { + status = [ + status, + "~~~~~~~~~~", + `[${quoteId}][${getState().getIn(['compose', 'quote_from_uri'], null)}]` + ].join("\n"); + } + dispatch(submitComposeRequest()); api(getState).post('/api/v1/statuses', { diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js index 9981f2449..804e05717 100644 --- a/app/javascript/mastodon/components/status_action_bar.js +++ b/app/javascript/mastodon/components/status_action_bar.js @@ -24,6 +24,7 @@ const messages = defineMessages({ reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' }, cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' }, cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' }, + quote: { id: 'status.quote', defaultMessage: 'Quote' }, favourite: { id: 'status.favourite', defaultMessage: 'Favourite' }, bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' }, removeBookmark: { id: 'status.remove_bookmark', defaultMessage: 'Remove bookmark' }, @@ -61,6 +62,7 @@ class StatusActionBar extends ImmutablePureComponent { onReply: PropTypes.func, onFavourite: PropTypes.func, onReblog: PropTypes.func, + onQuote: PropTypes.func, onDelete: PropTypes.func, onDirect: PropTypes.func, onMention: PropTypes.func, @@ -129,6 +131,10 @@ class StatusActionBar extends ImmutablePureComponent { this.props.onBookmark(this.props.status); } + handleQuoteClick = () => { + this.props.onQuote(this.props.status, this.context.router.history); + } + handleDeleteClick = () => { this.props.onDelete(this.props.status, this.context.router.history); } @@ -326,6 +332,7 @@ class StatusActionBar extends ImmutablePureComponent { + {shareButton} diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js index bf21a9fd6..5cf7ca802 100644 --- a/app/javascript/mastodon/components/status_content.js +++ b/app/javascript/mastodon/components/status_content.js @@ -38,6 +38,8 @@ export default class StatusContent extends React.PureComponent { } const links = node.querySelectorAll('a'); + const QuoteUrlFormat = /(?:https?|ftp):\/\/[-_.!~*\'()a-zA-Z0-9;\/?:\@&=+\$,%#]+\/users\/[\w-_]+(\/statuses\/\w+)/; + const quote = node.innerText.match(new RegExp(`\\[(\\w+)\\]\\[${QuoteUrlFormat.source}\\]`)); for (var i = 0; i < links.length; ++i) { let link = links[i]; @@ -46,6 +48,12 @@ export default class StatusContent extends React.PureComponent { } link.classList.add('status-link'); + if (quote) { + if (link.href.match(QuoteUrlFormat)) { + link.addEventListener('click', this.onQuoteClick.bind(this, quote[1]), false); + } + } + let mention = this.props.status.get('mentions').find(item => link.href === item.get('url')); if (mention) { @@ -125,6 +133,15 @@ export default class StatusContent extends React.PureComponent { } } + onQuoteClick = (statusId, e) => { + let statusUrl = `/statuses/${statusId}`; + + if (this.context.router && e.button === 0) { + e.preventDefault(); + this.context.router.history.push(statusUrl); + } + } + handleMouseDown = (e) => { this.startXY = [e.clientX, e.clientY]; } diff --git a/app/javascript/mastodon/containers/status_container.js b/app/javascript/mastodon/containers/status_container.js index 9abdec138..43acf597d 100644 --- a/app/javascript/mastodon/containers/status_container.js +++ b/app/javascript/mastodon/containers/status_container.js @@ -4,6 +4,7 @@ import Status from '../components/status'; import { makeGetStatus, makeGetPictureInPicture } from '../selectors'; import { replyCompose, + quoteCompose, mentionCompose, directCompose, } from '../actions/compose'; @@ -99,6 +100,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ } }, + onQuote (status, router) { + dispatch(quoteCompose(status, router)); + }, + onFavourite (status) { if (status.get('favourited')) { dispatch(unfavourite(status)); diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index ba2d20cc7..8f9a8a261 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -4,6 +4,7 @@ import Button from '../../../components/button'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import ReplyIndicatorContainer from '../containers/reply_indicator_container'; +import QuoteIndicatorContainer from '../containers/quote_indicator_container'; import AutosuggestTextarea from '../../../components/autosuggest_textarea'; import AutosuggestInput from '../../../components/autosuggest_input'; import PollButtonContainer from '../containers/poll_button_container'; @@ -209,6 +210,7 @@ class ComposeForm extends ImmutablePureComponent { +
{ + this.props.onCancel(); + } + + handleAccountClick = (e) => { + if (e.button === 0) { + e.preventDefault(); + this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); + } + } + + render () { + const { status, intl } = this.props; + + if (!status) { + return null; + } + + const content = { __html: status.get('contentHtml') }; + const style = { + direction: isRtl(status.get('search_index')) ? 'rtl' : 'ltr', + }; + + return ( +
+
+
+ + +
+ +
+
+ +
+
+ ); + } + +} diff --git a/app/javascript/mastodon/features/compose/containers/quote_indicator_container.js b/app/javascript/mastodon/features/compose/containers/quote_indicator_container.js new file mode 100644 index 000000000..eb67f3939 --- /dev/null +++ b/app/javascript/mastodon/features/compose/containers/quote_indicator_container.js @@ -0,0 +1,24 @@ +import { connect } from 'react-redux'; +import { cancelQuoteCompose } from '../../../actions/compose'; +import { makeGetStatus } from '../../../selectors'; +import QuoteIndicator from '../components/quote_indicator'; + +const makeMapStateToProps = () => { + const getStatus = makeGetStatus(); + + const mapStateToProps = state => ({ + status: getStatus(state, state.getIn(['compose', 'quote_from'])), + }); + + return mapStateToProps; +}; + +const mapDispatchToProps = dispatch => ({ + + onCancel () { + dispatch(cancelQuoteCompose()); + }, + +}); + +export default connect(makeMapStateToProps, mapDispatchToProps)(QuoteIndicator); diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js index ffa2510c0..1da4b52a4 100644 --- a/app/javascript/mastodon/features/status/components/action_bar.js +++ b/app/javascript/mastodon/features/status/components/action_bar.js @@ -18,6 +18,7 @@ const messages = defineMessages({ reblog_private: { id: 'status.reblog_private', defaultMessage: 'Boost with original visibility' }, cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' }, cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be boosted' }, + quote: { id: 'status.quote', defaultMessage: 'Quote' }, favourite: { id: 'status.favourite', defaultMessage: 'Favourite' }, bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' }, more: { id: 'status.more', defaultMessage: 'More' }, @@ -56,6 +57,7 @@ class ActionBar extends React.PureComponent { relationship: ImmutablePropTypes.map, onReply: PropTypes.func.isRequired, onReblog: PropTypes.func.isRequired, + onQuote: PropTypes.func.isRequired, onFavourite: PropTypes.func.isRequired, onBookmark: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired, @@ -82,6 +84,10 @@ class ActionBar extends React.PureComponent { this.props.onReblog(this.props.status, e); } + handleQuoteClick = () => { + this.props.onQuote(this.props.status, this.context.router.history); + } + handleFavouriteClick = () => { this.props.onFavourite(this.props.status); } @@ -277,6 +283,7 @@ class ActionBar extends React.PureComponent {
+
{shareButton}
diff --git a/app/javascript/mastodon/features/status/components/card.js b/app/javascript/mastodon/features/status/components/card.js index 90f9ae7ae..317163648 100644 --- a/app/javascript/mastodon/features/status/components/card.js +++ b/app/javascript/mastodon/features/status/components/card.js @@ -60,6 +60,10 @@ const addAutoPlay = html => { export default class Card extends React.PureComponent { + static contextTypes = { + router: PropTypes.object, + }; + static propTypes = { card: ImmutablePropTypes.map, maxDescription: PropTypes.number, diff --git a/app/javascript/mastodon/features/status/index.js b/app/javascript/mastodon/features/status/index.js index df8362a1b..0cd25955b 100644 --- a/app/javascript/mastodon/features/status/index.js +++ b/app/javascript/mastodon/features/status/index.js @@ -22,6 +22,7 @@ import { } from '../../actions/interactions'; import { replyCompose, + quoteCompose, mentionCompose, directCompose, } from '../../actions/compose'; @@ -259,6 +260,10 @@ class Status extends ImmutablePureComponent { } } + handleQuoteClick = (status) => { + this.props.dispatch(quoteCompose(status, this.context.router.history)); + } + handleDeleteClick = (status, history, withRedraft = false) => { const { dispatch, intl } = this.props; @@ -566,6 +571,7 @@ class Status extends ImmutablePureComponent { onFavourite={this.handleFavouriteClick} onReblog={this.handleReblogClick} onBookmark={this.handleBookmarkClick} + onQuote={this.handleQuoteClick} onDelete={this.handleDeleteClick} onDirect={this.handleDirectClick} onMention={this.handleMentionClick} diff --git a/app/javascript/mastodon/locales/defaultMessages.json b/app/javascript/mastodon/locales/defaultMessages.json index 6d1db70ab..f0522d148 100644 --- a/app/javascript/mastodon/locales/defaultMessages.json +++ b/app/javascript/mastodon/locales/defaultMessages.json @@ -449,6 +449,10 @@ "defaultMessage": "This post cannot be boosted", "id": "status.cannot_reblog" }, + { + "defaultMessage": "Quote", + "id": "status.quote" + }, { "defaultMessage": "Favourite", "id": "status.favourite" @@ -1229,6 +1233,15 @@ ], "path": "app/javascript/mastodon/features/compose/components/privacy_dropdown.json" }, + { + "descriptors": [ + { + "defaultMessage": "Cancel", + "id": "quote_indicator.cancel" + } + ], + "path": "app/javascript/mastodon/features/compose/components/quote_indicator.json" + }, { "descriptors": [ { diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json index d67ad6862..7ddd4908b 100644 --- a/app/javascript/mastodon/locales/en.json +++ b/app/javascript/mastodon/locales/en.json @@ -350,6 +350,7 @@ "privacy.public.short": "Public", "privacy.unlisted.long": "Visible for all, but not in public timelines", "privacy.unlisted.short": "Unlisted", + "quote_indicator.cancel": "Cancel", "refresh": "Refresh", "regeneration_indicator.label": "Loading…", "regeneration_indicator.sublabel": "Your home feed is being prepared!", @@ -401,6 +402,7 @@ "status.pin": "Pin on profile", "status.pinned": "Pinned post", "status.read_more": "Read more", + "status.quote": "Quote", "status.reblog": "Boost", "status.reblog_private": "Boost with original visibility", "status.reblogged_by": "{name} boosted", diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json index ae0e0f5da..d979a08fd 100644 --- a/app/javascript/mastodon/locales/ja.json +++ b/app/javascript/mastodon/locales/ja.json @@ -350,6 +350,7 @@ "privacy.public.short": "公開", "privacy.unlisted.long": "誰でも閲覧可、公開TLに非表示", "privacy.unlisted.short": "未収載", + "quote_indicator.cancel": "キャンセル", "refresh": "更新", "regeneration_indicator.label": "読み込み中…", "regeneration_indicator.sublabel": "ホームタイムラインは準備中です!", @@ -401,6 +402,7 @@ "status.pin": "プロフィールに固定表示", "status.pinned": "固定された投稿", "status.read_more": "もっと見る", + "status.quote": "引用ブースト", "status.reblog": "ブースト", "status.reblog_private": "ブースト", "status.reblogged_by": "{name}さんがブースト", diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js index 4c0ba1c36..596e1a68b 100644 --- a/app/javascript/mastodon/reducers/compose.js +++ b/app/javascript/mastodon/reducers/compose.js @@ -5,6 +5,8 @@ import { COMPOSE_REPLY, COMPOSE_REPLY_CANCEL, COMPOSE_DIRECT, + COMPOSE_QUOTE, + COMPOSE_QUOTE_CANCEL, COMPOSE_MENTION, COMPOSE_SUBMIT_REQUEST, COMPOSE_SUBMIT_SUCCESS, @@ -59,6 +61,8 @@ const initialState = ImmutableMap({ caretPosition: null, preselectDate: null, in_reply_to: null, + quote_from: null, + quote_from_uri: null, is_composing: false, is_submitting: false, is_changing_upload: false, @@ -102,6 +106,7 @@ function clearAll(state) { map.set('is_submitting', false); map.set('is_changing_upload', false); map.set('in_reply_to', null); + map.set('quote_from', null); map.set('privacy', state.get('default_privacy')); map.set('sensitive', false); map.update('media_attachments', list => list.clear()); @@ -292,6 +297,8 @@ export default function compose(state = initialState, action) { case COMPOSE_REPLY: return state.withMutations(map => { map.set('in_reply_to', action.status.get('id')); + map.set('quote_from', null); + map.set('quote_from_uri', null); map.set('text', statusToTextMentions(state, action.status)); map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy'))); map.set('focusDate', new Date()); @@ -307,10 +314,24 @@ export default function compose(state = initialState, action) { map.set('spoiler_text', ''); } }); + case COMPOSE_QUOTE: + return state.withMutations(map => { + map.set('in_reply_to', null); + map.set('quote_from', action.status.get('id')); + map.set('quote_from_uri', action.status.get('uri')); + map.set('text', ''); + map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy'))); + map.set('focusDate', new Date()); + map.set('preselectDate', new Date()); + map.set('idempotencyKey', uuid()); + }); case COMPOSE_REPLY_CANCEL: + case COMPOSE_QUOTE_CANCEL: case COMPOSE_RESET: return state.withMutations(map => { map.set('in_reply_to', null); + map.set('quote_from', null); + map.set('quote_from_uri', null); map.set('text', ''); map.set('spoiler', false); map.set('spoiler_text', ''); diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 7ca027e81..00bdd4d33 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -761,26 +761,37 @@ } .reply-indicator { + background: $ui-primary-color; +} + +.quote-indicator { + background: $success-green; +} + +.reply-indicator, +.quote-indicator { border-radius: 4px; margin-bottom: 10px; - background: $ui-primary-color; padding: 10px; min-height: 23px; overflow-y: auto; flex: 0 2 auto; } -.reply-indicator__header { +.reply-indicator__header, +.quote-indicator__header { margin-bottom: 5px; overflow: hidden; } -.reply-indicator__cancel { +.reply-indicator__cancel, +.quote-indicator__cancel { float: right; line-height: 24px; } -.reply-indicator__display-name { +.reply-indicator__display-name, +.quote-indicator__display-name { color: $inverted-text-color; display: block; max-width: 100%; @@ -790,7 +801,8 @@ text-decoration: none; } -.reply-indicator__display-avatar { +.reply-indicator__display-avatar, +.quote-indicator__display-avatar { float: left; margin-right: 5px; } @@ -804,7 +816,8 @@ } .status__content, -.reply-indicator__content { +.reply-indicator__content, +.quote-indicator__content { position: relative; font-size: 15px; line-height: 20px; @@ -1254,7 +1267,8 @@ margin-left: 6px; } -.reply-indicator__content { +.reply-indicator__content, +.quote-indicator__content { color: $inverted-text-color; font-size: 14px; diff --git a/app/services/fetch_link_card_service.rb b/app/services/fetch_link_card_service.rb index fa1636e41..c5cd3096a 100644 --- a/app/services/fetch_link_card_service.rb +++ b/app/services/fetch_link_card_service.rb @@ -76,7 +76,7 @@ class FetchLinkCardService < BaseService def bad_url?(uri) # Avoid local instance URLs and invalid URLs - uri.host.blank? || TagManager.instance.local_url?(uri.to_s) || !%w(http https).include?(uri.scheme) + uri.host.blank? || (TagManager.instance.local_url?(uri.to_s) && uri.to_s !~ %r(/users/[\w_-]+/statuses/\w+)) || !%w(http https).include?(uri.scheme) end # rubocop:disable Naming/MethodParameterName @@ -132,7 +132,7 @@ class FetchLinkCardService < BaseService # Most providers rely on