diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb
index 2593ef7da5..875c0de153 100644
--- a/app/controllers/api/v1/statuses_controller.rb
+++ b/app/controllers/api/v1/statuses_controller.rb
@@ -76,7 +76,8 @@ class Api::V1::StatusesController < Api::BaseController
content_type: status_params[:content_type],
allowed_mentions: status_params[:allowed_mentions],
idempotency: request.headers['Idempotency-Key'],
- with_rate_limit: true
+ with_rate_limit: true,
+ quote_id: status_params[:quote_id].presence
)
render json: @status, serializer: serializer_for_status
@@ -159,6 +160,7 @@ class Api::V1::StatusesController < Api::BaseController
:visibility,
:language,
:scheduled_at,
+ :quote_id,
:content_type,
allowed_mentions: [],
media_ids: [],
diff --git a/app/helpers/context_helper.rb b/app/helpers/context_helper.rb
index d70b2a88fd..7517e5cf0d 100644
--- a/app/helpers/context_helper.rb
+++ b/app/helpers/context_helper.rb
@@ -42,6 +42,7 @@ module ContextHelper
'cipherText' => 'toot:cipherText',
},
suspended: { 'toot' => 'http://joinmastodon.org/ns#', 'suspended' => 'toot:suspended' },
+ quote_uri: { 'fedibird' => 'http://fedibird.com/ns#', 'quoteUri' => 'fedibird:quoteUri' },
}.freeze
def full_context
diff --git a/app/helpers/formatting_helper.rb b/app/helpers/formatting_helper.rb
index f0d583bc54..ede95735f7 100644
--- a/app/helpers/formatting_helper.rb
+++ b/app/helpers/formatting_helper.rb
@@ -19,7 +19,17 @@ module FormattingHelper
module_function :extract_status_plain_text
def status_content_format(status)
- html_aware_format(status.text, status.local?, preloaded_accounts: [status.account] + (status.respond_to?(:active_mentions) ? status.active_mentions.map(&:account) : []), content_type: status.content_type)
+ base = html_aware_format(status.text, status.local?, preloaded_accounts: [status.account] + (status.respond_to?(:active_mentions) ? status.active_mentions.map(&:account) : []), content_type: status.content_type)
+
+ if status.quote? && status.local?
+ after_html = begin
+ "#{status.quote.to_log_permalink}"
+ end.html_safe # rubocop:disable Rails/OutputSafety
+
+ base + after_html
+ else
+ base
+ end
end
def rss_status_content_format(status)
diff --git a/app/javascript/flavours/glitch/actions/compose.js b/app/javascript/flavours/glitch/actions/compose.js
index 8eab532f55..acf400fb1f 100644
--- a/app/javascript/flavours/glitch/actions/compose.js
+++ b/app/javascript/flavours/glitch/actions/compose.js
@@ -86,6 +86,9 @@ export const COMPOSE_CHANGE_MEDIA_ORDER = 'COMPOSE_CHANGE_MEDIA_ORDER';
export const COMPOSE_SET_STATUS = 'COMPOSE_SET_STATUS';
export const COMPOSE_FOCUS = 'COMPOSE_FOCUS';
+export const COMPOSE_QUOTE = 'COMPOSE_QUOTE';
+export const COMPOSE_QUOTE_CANCEL = 'COMPOSE_QUOTE_CANCEL';
+
const messages = defineMessages({
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
@@ -136,6 +139,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('/publish');
+ }
+ };
+}
+
+export function cancelQuoteCompose() {
+ return {
+ type: COMPOSE_QUOTE_CANCEL,
+ };
+};
+
export function resetCompose() {
return {
type: COMPOSE_RESET,
@@ -218,6 +240,7 @@ export function submitCompose(routerHistory, overridePrivacy = null) {
status,
content_type: getState().getIn(['compose', 'content_type']),
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
+ quote_id: getState().getIn(['compose', 'quote_id'], null),
media_ids: media.map(item => item.get('id')),
media_attributes,
sensitive: getState().getIn(['compose', 'sensitive']) || (spoilerText.length > 0 && media.size !== 0),
diff --git a/app/javascript/flavours/glitch/actions/importer/normalizer.js b/app/javascript/flavours/glitch/actions/importer/normalizer.js
index 5f10c8d889..53c6e2365f 100644
--- a/app/javascript/flavours/glitch/actions/importer/normalizer.js
+++ b/app/javascript/flavours/glitch/actions/importer/normalizer.js
@@ -59,6 +59,8 @@ export function normalizeStatus(status, normalOldStatus, settings) {
normalStatus.contentHtml = normalOldStatus.get('contentHtml');
normalStatus.spoilerHtml = normalOldStatus.get('spoilerHtml');
normalStatus.hidden = normalOldStatus.get('hidden');
+ normalStatus.quote = normalOldStatus.get('quote');
+ normalStatus.quote_hidden = normalOldStatus.get('quote_hidden');
if (normalOldStatus.get('translation')) {
normalStatus.translation = normalOldStatus.get('translation');
@@ -72,6 +74,35 @@ export function normalizeStatus(status, normalOldStatus, settings) {
normalStatus.contentHtml = emojify(normalStatus.content, emojiMap);
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap);
normalStatus.hidden = (spoilerText.length > 0 || normalStatus.sensitive) && autoHideCW(settings, spoilerText);
+
+ if (status.quote && status.quote.id) {
+ const quote_spoilerText = status.quote.spoiler_text || '';
+ const quote_searchContent = [quote_spoilerText, status.quote.content].join('\n\n').replace(/
/g, '\n').replace(/<\/p>
/g, '\n\n'); + + const quote_emojiMap = makeEmojiMap(normalStatus.quote.emojis); + + const quote_account_emojiMap = makeEmojiMap(status.quote.account.emojis); + const displayName = normalStatus.quote.account.display_name.length === 0 ? normalStatus.quote.account.username : normalStatus.quote.account.display_name; + normalStatus.quote.account.display_name_html = emojify(escapeTextContentForBrowser(displayName), quote_account_emojiMap); + normalStatus.quote.search_index = domParser.parseFromString(quote_searchContent, 'text/html').documentElement.textContent; + let docElem = domParser.parseFromString(normalStatus.quote.content, 'text/html').documentElement; + Array.from(docElem.querySelectorAll('span.quote-inline'), span => span.remove()); + Array.from(docElem.querySelectorAll('p,br'), line => { + let parentNode = line.parentNode; + if (line.nextSibling) { + parentNode.insertBefore(document.createTextNode(' '), line.nextSibling); + } + }); + let _contentHtml = docElem.textContent; + normalStatus.quote.contentHtml = '
'+emojify(_contentHtml.substr(0, 150), quote_emojiMap) + (_contentHtml.substr(150) ? '...' : '')+'
'; + normalStatus.quote.spoilerHtml = emojify(escapeTextContentForBrowser(quote_spoilerText), quote_emojiMap); + normalStatus.quote_hidden = (quote_spoilerText.length > 0 || normalStatus.quote.sensitive) && autoHideCW(settings, quote_spoilerText); + + // delete the quote link!!!! + let parentDocElem = domParser.parseFromString(normalStatus.contentHtml, 'text/html').documentElement; + Array.from(parentDocElem.querySelectorAll('span.quote-inline'), span => span.remove()); + normalStatus.contentHtml = parentDocElem.children[1].innerHTML; + } } if (normalOldStatus) { diff --git a/app/javascript/flavours/glitch/components/status.jsx b/app/javascript/flavours/glitch/components/status.jsx index 0915af2004..440e3ea621 100644 --- a/app/javascript/flavours/glitch/components/status.jsx +++ b/app/javascript/flavours/glitch/components/status.jsx @@ -85,6 +85,7 @@ class Status extends ImmutablePureComponent { rootId: PropTypes.string, onClick: PropTypes.func, onReply: PropTypes.func, + onQuote: PropTypes.func, onFavourite: PropTypes.func, onReblog: PropTypes.func, onBookmark: PropTypes.func, @@ -732,7 +733,7 @@ class Status extends ImmutablePureComponent { if (!status.get('sensitive') && !(status.get('spoiler_text').length > 0) && settings.getIn(['collapsed', 'backgrounds', 'preview_images'])) { background = attachments.getIn([0, 'preview_url']); } - } else if (status.get('card') && settings.get('inline_preview_cards') && !this.props.muted) { + } else if (!status.get('quote') && status.get('card') && settings.get('inline_preview_cards') && !this.props.muted) { media.push(