diff --git a/Gemfile b/Gemfile
index 00f9e451..fd6fca4b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -56,6 +56,7 @@ gem 'http_parser.rb', '~> 0.6', git: 'https://github.com/tmm1/http_parser.rb', r
gem 'httplog', '~> 1.3'
gem 'idn-ruby', require: 'idn'
gem 'kaminari', '~> 1.1'
+gem 'kramdown', '~> 2.1.0'
gem 'link_header', '~> 0.0'
gem 'mime-types', '~> 3.2', require: 'mime/types/columnar'
gem 'nokogiri', '~> 1.10'
@@ -89,6 +90,7 @@ gem 'twitter-text', '~> 1.14'
gem 'tzinfo-data', '~> 1.2019'
gem 'webpacker', '~> 4.0'
gem 'webpush'
+gem 'redcarpet', '~> 3.5.0'
gem 'json-ld', '~> 3.0'
gem 'json-ld-preloaded', '~> 3.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index a5c48dfd..4efcac86 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -311,6 +311,7 @@ GEM
activerecord
kaminari-core (= 1.1.1)
kaminari-core (1.1.1)
+ kramdown (2.1.0)
launchy (2.4.3)
addressable (~> 2.3)
letter_opener (1.7.0)
@@ -478,6 +479,7 @@ GEM
link_header (~> 0.0, >= 0.0.8)
rdf-normalize (0.3.3)
rdf (>= 2.2, < 4.0)
+ redcarpet (3.5.0)
redis (4.1.2)
redis-actionpack (5.0.2)
actionpack (>= 4.0, < 6)
@@ -701,6 +703,7 @@ DEPENDENCIES
json-ld (~> 3.0)
json-ld-preloaded (~> 3.0)
kaminari (~> 1.1)
+ kramdown (~> 2.1.0)
letter_opener (~> 1.7)
letter_opener_web (~> 1.3)
link_header (~> 0.0)
@@ -739,6 +742,7 @@ DEPENDENCIES
rails-i18n (~> 5.1)
rails-settings-cached (~> 0.6)
rdf-normalize (~> 0.3)
+ redcarpet (~> 3.5.0)
redis (~> 4.1)
redis-namespace (~> 1.5)
redis-rails (~> 5.0)
diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb
index 1a0dab99..43059ebf 100644
--- a/app/controllers/api/v1/statuses_controller.rb
+++ b/app/controllers/api/v1/statuses_controller.rb
@@ -5,8 +5,8 @@ class Api::V1::StatusesController < Api::BaseController
before_action -> { authorize_if_got_token! :read, :'read:statuses' }, except: [:create, :update, :destroy]
before_action -> { doorkeeper_authorize! :write, :'write:statuses' }, only: [:create, :update, :destroy]
- before_action :require_user!, except: [:show, :context, :card]
- before_action :set_status, only: [:show, :context, :card, :update, :revisions]
+ before_action :require_user!, except: [:show, :comments, :context, :card]
+ before_action :set_status, only: [:show, :comments, :context, :card, :update, :revisions]
respond_to :json
@@ -14,13 +14,24 @@ class Api::V1::StatusesController < Api::BaseController
# breaking backwards-compatibility. Arbitrarily high number to cover most
# conversations as quasi-unlimited, it would be too much work to render more
# than this anyway
- CONTEXT_LIMIT = 4_096
+ # : TODO :
+ CONTEXT_LIMIT = 4_096
def show
@status = cache_collection([@status], Status).first
render json: @status, serializer: REST::StatusSerializer
end
+ def comments
+ descendants_results = @status.descendants(CONTEXT_LIMIT, current_account)
+ loaded_descendants = cache_collection(descendants_results, Status)
+
+ @context = Context.new(descendants: loaded_descendants)
+ statuses = [@status] + @context.descendants
+
+ render json: @context, serializer: REST::ContextSerializer, relationships: StatusRelationshipsPresenter.new(statuses, current_user&.account_id)
+ end
+
def context
ancestors_results = @status.in_reply_to_id.nil? ? [] : @status.ancestors(CONTEXT_LIMIT, current_account)
descendants_results = @status.descendants(CONTEXT_LIMIT, current_account)
@@ -42,6 +53,7 @@ class Api::V1::StatusesController < Api::BaseController
def create
@status = PostStatusService.new.call(current_user.account,
text: status_params[:status],
+ markdown: status_params[:markdown],
thread: status_params[:in_reply_to_id].blank? ? nil : Status.find(status_params[:in_reply_to_id]),
media_ids: status_params[:media_ids],
sensitive: status_params[:sensitive],
@@ -93,6 +105,7 @@ class Api::V1::StatusesController < Api::BaseController
def status_params
params.permit(
:status,
+ :markdown,
:in_reply_to_id,
:quote_of_id,
:sensitive,
diff --git a/app/javascript/gabsocial/actions/compose.js b/app/javascript/gabsocial/actions/compose.js
index 2a54b1d3..dddda92f 100644
--- a/app/javascript/gabsocial/actions/compose.js
+++ b/app/javascript/gabsocial/actions/compose.js
@@ -77,10 +77,12 @@ export const ensureComposeIsVisible = (getState, routerHistory) => {
}
};
-export function changeCompose(text) {
+export function changeCompose(text, markdown) {
+ console.log("changeCompose:", markdown)
return {
type: COMPOSE_CHANGE,
text: text,
+ markdown: markdown,
};
};
@@ -173,7 +175,7 @@ export function submitCompose(routerHistory, group) {
if (!me) return;
let status = getState().getIn(['compose', 'text'], '');
- const statusMarkdown = getState().getIn(['compose', 'text_markdown'], '');
+ const markdown = getState().getIn(['compose', 'markdown'], '');
const media = getState().getIn(['compose', 'media_attachments']);
// : hack :
@@ -182,11 +184,13 @@ export function submitCompose(routerHistory, group) {
const hasProtocol = match.startsWith('https://') || match.startsWith('http://')
return hasProtocol ? match : `http://${match}`
})
- // statusMarkdown = statusMarkdown.replace(urlRegex, (match) =>{
+ // markdown = statusMarkdown.replace(urlRegex, (match) =>{
// const hasProtocol = match.startsWith('https://') || match.startsWith('http://')
// return hasProtocol ? match : `http://${match}`
// })
+ console.log("markdown:", markdown)
+
dispatch(submitComposeRequest());
dispatch(closeModal());
@@ -202,7 +206,7 @@ export function submitCompose(routerHistory, group) {
api(getState)[method](endpoint, {
status,
- // statusMarkdown,
+ markdown,
scheduled_at,
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
quote_of_id: getState().getIn(['compose', 'quote_of_id'], null),
diff --git a/app/javascript/gabsocial/actions/importer/index.js b/app/javascript/gabsocial/actions/importer/index.js
index 768535ad..9953a74e 100644
--- a/app/javascript/gabsocial/actions/importer/index.js
+++ b/app/javascript/gabsocial/actions/importer/index.js
@@ -1,4 +1,5 @@
import { normalizeAccount, normalizeStatus, normalizePoll } from './normalizer';
+import { fetchContext } from '../statuses'
export const ACCOUNT_IMPORT = 'ACCOUNT_IMPORT';
export const ACCOUNTS_IMPORT = 'ACCOUNTS_IMPORT';
@@ -78,6 +79,10 @@ export function importFetchedStatuses(statuses) {
if (status.poll && status.poll.id) {
pushUnique(polls, normalizePoll(status.poll));
}
+
+ // if (status.replies_count > 0) {
+ // dispatch(fetchComments(status.id));
+ // }
}
statuses.forEach(processStatus);
diff --git a/app/javascript/gabsocial/actions/importer/normalizer.js b/app/javascript/gabsocial/actions/importer/normalizer.js
index bfc4bc4b..c542523f 100644
--- a/app/javascript/gabsocial/actions/importer/normalizer.js
+++ b/app/javascript/gabsocial/actions/importer/normalizer.js
@@ -62,9 +62,10 @@ export function normalizeStatus(status, normalOldStatus) {
const spoilerText = normalStatus.spoiler_text || '';
const searchContent = [spoilerText, status.content].join('\n\n').replace(/
/g, '\n').replace(/<\/p>
/g, '\n\n');
const emojiMap = makeEmojiMap(normalStatus);
+ const theContent = !!normalStatus.rich_content ? normalStatus.rich_content : normalStatus.content;
normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
- normalStatus.contentHtml = emojify(normalStatus.content, emojiMap);
+ normalStatus.contentHtml = emojify(theContent, emojiMap);
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap);
normalStatus.hidden = expandSpoilers ? false : spoilerText.length > 0 || normalStatus.sensitive;
}
diff --git a/app/javascript/gabsocial/actions/statuses.js b/app/javascript/gabsocial/actions/statuses.js
index c993c640..83d59512 100644
--- a/app/javascript/gabsocial/actions/statuses.js
+++ b/app/javascript/gabsocial/actions/statuses.js
@@ -18,6 +18,10 @@ export const CONTEXT_FETCH_REQUEST = 'CONTEXT_FETCH_REQUEST';
export const CONTEXT_FETCH_SUCCESS = 'CONTEXT_FETCH_SUCCESS';
export const CONTEXT_FETCH_FAIL = 'CONTEXT_FETCH_FAIL';
+export const COMMENTS_FETCH_REQUEST = 'COMMENTS_FETCH_REQUEST';
+export const COMMENTS_FETCH_SUCCESS = 'COMMENTS_FETCH_SUCCESS';
+export const COMMENTS_FETCH_FAIL = 'COMMENTS_FETCH_FAIL';
+
export const STATUS_MUTE_REQUEST = 'STATUS_MUTE_REQUEST';
export const STATUS_MUTE_SUCCESS = 'STATUS_MUTE_SUCCESS';
export const STATUS_MUTE_FAIL = 'STATUS_MUTE_FAIL';
@@ -85,8 +89,6 @@ export function fetchStatus(id) {
return (dispatch, getState) => {
const skipLoading = getState().getIn(['statuses', id], null) !== null;
- dispatch(fetchContext(id));
-
if (skipLoading) {
return;
}
@@ -205,6 +207,24 @@ export function fetchContext(id) {
};
};
+export function fetchComments(id) {
+ return (dispatch, getState) => {
+ dispatch(fetchCommentsRequest(id));
+
+ api(getState).get(`/api/v1/statuses/${id}/comments`).then(response => {
+ dispatch(importFetchedStatuses(response.data.descendants));
+ dispatch(fetchCommentsSuccess(id, response.data.descendants));
+
+ }).catch(error => {
+ if (error.response && error.response.status === 404) {
+ dispatch(deleteFromTimelines(id));
+ }
+
+ dispatch(fetchCommentsFail(id, error));
+ });
+ };
+};
+
export function fetchContextRequest(id) {
return {
type: CONTEXT_FETCH_REQUEST,
@@ -231,6 +251,30 @@ export function fetchContextFail(id, error) {
};
};
+export function fetchCommentsRequest(id) {
+ return {
+ type: COMMENTS_FETCH_REQUEST,
+ id,
+ };
+};
+
+export function fetchCommentsSuccess(id, descendants) {
+ return {
+ type: COMMENTS_FETCH_SUCCESS,
+ id,
+ descendants,
+ };
+};
+
+export function fetchCommentsFail(id, error) {
+ return {
+ type: COMMENTS_FETCH_FAIL,
+ id,
+ error,
+ skipAlert: true,
+ };
+};
+
export function muteStatus(id) {
return (dispatch, getState) => {
if (!me) return;
diff --git a/app/javascript/gabsocial/components/account.js b/app/javascript/gabsocial/components/account.js
index 0c2421fb..a6df881e 100644
--- a/app/javascript/gabsocial/components/account.js
+++ b/app/javascript/gabsocial/components/account.js
@@ -92,7 +92,7 @@ class Account extends ImmutablePureComponent {
onMute: PropTypes.func.isRequired,
onMuteNotifications: PropTypes.func,
intl: PropTypes.object.isRequired,
- hidden: PropTypes.bool,
+ isHidden: PropTypes.bool,
actionIcon: PropTypes.string,
actionTitle: PropTypes.string,
onActionClick: PropTypes.func,
@@ -134,7 +134,7 @@ class Account extends ImmutablePureComponent {
const {
account,
intl,
- hidden,
+ isHidden,
onActionClick,
actionIcon,
actionTitle,
@@ -146,7 +146,7 @@ class Account extends ImmutablePureComponent {
if (!account) return null
- if (hidden) {
+ if (isHidden) {
return (
{account.get('display_name')}
@@ -207,7 +207,6 @@ class Account extends ImmutablePureComponent {
const dismissBtn = (