Merge branch 'kb_development' into kb_migration
This commit is contained in:
commit
9e2390f3c9
57 changed files with 227 additions and 64 deletions
|
@ -47,7 +47,7 @@ class AccountsController < ApplicationController
|
|||
end
|
||||
|
||||
def default_statuses
|
||||
@account.statuses.where(visibility: [:public, :unlisted])
|
||||
@account.statuses.where(visibility: [:public, :unlisted, :public_unlisted])
|
||||
end
|
||||
|
||||
def only_media_scope
|
||||
|
|
|
@ -32,7 +32,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
|
|||
|
||||
def set_replies
|
||||
@replies = only_other_accounts? ? Status.where.not(account_id: @account.id).joins(:account).merge(Account.without_suspended) : @account.statuses
|
||||
@replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted])
|
||||
@replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted, :public_unlisted])
|
||||
@replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id])
|
||||
end
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'fastimage'
|
||||
|
||||
module Admin
|
||||
class CustomEmojisController < BaseController
|
||||
def index
|
||||
|
@ -18,7 +20,11 @@ module Admin
|
|||
def create
|
||||
authorize :custom_emoji, :create?
|
||||
|
||||
image_size = FastImage.size(params[:custom_emoji][:image])
|
||||
|
||||
@custom_emoji = CustomEmoji.new(resource_params)
|
||||
@custom_emoji.image_width = image_size[0]
|
||||
@custom_emoji.image_height = image_size[1]
|
||||
|
||||
if @custom_emoji.save
|
||||
log_action :create, @custom_emoji
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
class Api::BaseController < ApplicationController
|
||||
DEFAULT_STATUSES_LIMIT = 20
|
||||
DEFAULT_ACCOUNTS_LIMIT = 40
|
||||
DEFAULT_EMOJI_REACTION_LIMIT = 10
|
||||
|
||||
include RateLimitHeaders
|
||||
include AccessTokenTrackingConcern
|
||||
|
|
|
@ -5,7 +5,7 @@ class Api::V1::Statuses::EmojiReactionsController < Api::BaseController
|
|||
|
||||
before_action -> { doorkeeper_authorize! :write, :'write:emoji_reactions' }
|
||||
before_action :require_user!
|
||||
before_action :set_status, only: %i(create update)
|
||||
before_action :set_status, only: %i(create update destroy)
|
||||
before_action :set_status_without_authorize, only: [:destroy]
|
||||
|
||||
def create
|
||||
|
@ -18,7 +18,7 @@ class Api::V1::Statuses::EmojiReactionsController < Api::BaseController
|
|||
end
|
||||
|
||||
def destroy
|
||||
emoji = params[:emoji]
|
||||
emoji = params[:emoji] || params[:id]
|
||||
|
||||
if emoji
|
||||
shortcode, domain = emoji.split('@')
|
||||
|
@ -42,7 +42,7 @@ class Api::V1::Statuses::EmojiReactionsController < Api::BaseController
|
|||
def create_private(emoji)
|
||||
count = EmojiReaction.where(account: current_account, status: @status).count
|
||||
|
||||
if count >= DEFAULT_EMOJI_REACTION_LIMIT
|
||||
if count >= EmojiReaction::EMOJI_REACTION_PER_ACCOUNT_LIMIT
|
||||
bad_request
|
||||
return
|
||||
end
|
||||
|
|
|
@ -25,7 +25,7 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
|
|||
end
|
||||
|
||||
def paginated_statuses
|
||||
Status.where(reblog_of_id: @status.id).where(visibility: [:public, :unlisted]).paginate_by_max_id(
|
||||
Status.where(reblog_of_id: @status.id).where(visibility: [:public, :unlisted, :public_unlisted]).paginate_by_max_id(
|
||||
limit_param(DEFAULT_ACCOUNTS_LIMIT),
|
||||
params[:max_id],
|
||||
params[:since_id]
|
||||
|
|
|
@ -122,6 +122,8 @@ module ApplicationHelper
|
|||
fa_icon('globe', title: I18n.t('statuses.visibilities.public'))
|
||||
elsif status.unlisted_visibility?
|
||||
fa_icon('unlock', title: I18n.t('statuses.visibilities.unlisted'))
|
||||
elsif status.public_unlisted_visibility?
|
||||
fa_icon('cloud', title: I18n.t('statuses.visibilities.public_unlisted'))
|
||||
elsif status.private_visibility? || status.limited_visibility?
|
||||
fa_icon('lock', title: I18n.t('statuses.visibilities.private'))
|
||||
elsif status.direct_visibility?
|
||||
|
@ -199,7 +201,7 @@ module ApplicationHelper
|
|||
text: [params[:title], params[:text], params[:url]].compact.join(' '),
|
||||
}
|
||||
|
||||
permit_visibilities = %w(public unlisted private direct)
|
||||
permit_visibilities = %w(public unlisted public_unlisted private direct)
|
||||
default_privacy = current_account&.user&.setting_default_privacy
|
||||
permit_visibilities.shift(permit_visibilities.index(default_privacy) + 1) if default_privacy.present?
|
||||
state_params[:visibility] = params[:visibility] if permit_visibilities.include? params[:visibility]
|
||||
|
|
|
@ -98,6 +98,8 @@ module StatusesHelper
|
|||
fa_icon 'globe fw'
|
||||
when 'unlisted'
|
||||
fa_icon 'unlock fw'
|
||||
when 'public_unlisted'
|
||||
fa_icon 'cloud fw'
|
||||
when 'private'
|
||||
fa_icon 'lock fw'
|
||||
when 'direct'
|
||||
|
|
|
@ -55,6 +55,7 @@ export const defaultMediaVisibility = (status) => {
|
|||
const messages = defineMessages({
|
||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||
edited: { id: 'status.edited', defaultMessage: 'Edited {date}' },
|
||||
|
@ -506,6 +507,7 @@ class Status extends ImmutablePureComponent {
|
|||
const visibilityIconInfo = {
|
||||
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
|
||||
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
|
||||
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
|
||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||
};
|
||||
|
|
|
@ -248,8 +248,8 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
const { signedIn, permissions } = this.context.identity;
|
||||
|
||||
const anonymousAccess = !signedIn;
|
||||
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
||||
const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility'));
|
||||
const publicStatus = ['public', 'unlisted', 'public_unlisted'].includes(status.get('visibility'));
|
||||
const pinnableStatus = ['public', 'unlisted', 'public_unlisted', 'private'].includes(status.get('visibility'));
|
||||
const mutingConversation = status.get('muted');
|
||||
const account = status.get('account');
|
||||
const writtenByMe = status.getIn(['account', 'id']) === me;
|
||||
|
@ -369,13 +369,17 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||
<IconButton className='status__action-bar__button' title={intl.formatMessage(messages.hide)} icon='eye' onClick={this.handleHideClick} />
|
||||
);
|
||||
|
||||
const emojiPickerButton = (
|
||||
<IconButton className='status__action-bar__button' icon='smile-o' />
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='status__action-bar'>
|
||||
<IconButton className='status__action-bar__button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} counter={status.get('replies_count')} obfuscateCount />
|
||||
<IconButton className={classNames('status__action-bar__button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} />
|
||||
<IconButton className='status__action-bar__button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} />
|
||||
<IconButton className='status__action-bar__button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} />
|
||||
<EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />
|
||||
<EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} button={emojiPickerButton} />
|
||||
|
||||
{shareButton}
|
||||
|
||||
|
|
|
@ -73,7 +73,7 @@ class StatusEmojiReactionsBar extends React.PureComponent {
|
|||
render () {
|
||||
const { emojiReactions } = this.props;
|
||||
|
||||
const emojiButtons = Array.from(emojiReactions).map((emoji, index) => (
|
||||
const emojiButtons = Array.from(emojiReactions).filter(emoji => emoji.get('count') != 0).map((emoji, index) => (
|
||||
<EmojiReactionButton
|
||||
key={index}
|
||||
name={emoji.get('name')}
|
||||
|
|
|
@ -217,7 +217,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||
} else if (this.props.privacy === 'private' || this.props.privacy === 'direct') {
|
||||
publishText = <span className='compose-form__publish-private'><Icon id='lock' /> {intl.formatMessage(messages.publish)}</span>;
|
||||
} else {
|
||||
publishText = this.props.privacy !== 'unlisted' ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish);
|
||||
publishText = (this.props.privacy !== 'unlisted' && this.props.privacy !== 'public_unlisted') ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -12,6 +12,8 @@ const messages = defineMessages({
|
|||
public_long: { id: 'privacy.public.long', defaultMessage: 'Visible for all' },
|
||||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||
unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Visible for all, but opted-out of discovery features' },
|
||||
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
||||
public_unlisted_long: { id: 'privacy.public_unlisted.long', defaultMessage: 'Visible for all without GTL' },
|
||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
||||
private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for followers only' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||
|
@ -218,6 +220,7 @@ class PrivacyDropdown extends React.PureComponent {
|
|||
|
||||
this.options = [
|
||||
{ icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
|
||||
{ icon: 'cloud', value: 'public_unlisted', text: formatMessage(messages.public_unlisted_short), meta: formatMessage(messages.public_unlisted_long) },
|
||||
{ icon: 'unlock', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
|
||||
{ icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
|
||||
];
|
||||
|
|
|
@ -154,7 +154,7 @@ class Footer extends ImmutablePureComponent {
|
|||
render () {
|
||||
const { status, intl, withOpenButton } = this.props;
|
||||
|
||||
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
||||
const publicStatus = ['public', 'unlisted', 'public_unlisted'].includes(status.get('visibility'));
|
||||
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private';
|
||||
|
||||
let replyIcon, replyTitle;
|
||||
|
|
|
@ -13,6 +13,7 @@ import Icon from 'mastodon/components/icon';
|
|||
const messages = defineMessages({
|
||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||
});
|
||||
|
@ -43,6 +44,7 @@ class StatusCheckBox extends React.PureComponent {
|
|||
const visibilityIconInfo = {
|
||||
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
|
||||
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
|
||||
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
|
||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||
};
|
||||
|
|
|
@ -190,8 +190,8 @@ class ActionBar extends React.PureComponent {
|
|||
const { status, relationship, intl } = this.props;
|
||||
const { signedIn, permissions } = this.context.identity;
|
||||
|
||||
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
|
||||
const pinnableStatus = ['public', 'unlisted', 'private'].includes(status.get('visibility'));
|
||||
const publicStatus = ['public', 'unlisted', 'public_unlisted'].includes(status.get('visibility'));
|
||||
const pinnableStatus = ['public', 'unlisted', 'public_unlisted', 'private'].includes(status.get('visibility'));
|
||||
const mutingConversation = status.get('muted');
|
||||
const account = status.get('account');
|
||||
const writtenByMe = status.getIn(['account', 'id']) === me;
|
||||
|
@ -267,6 +267,10 @@ class ActionBar extends React.PureComponent {
|
|||
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShare} /></div>
|
||||
);
|
||||
|
||||
const emojiPickerButton = (
|
||||
<IconButton icon='smile-o' />
|
||||
);
|
||||
|
||||
let replyIcon;
|
||||
if (status.get('in_reply_to_id', null) === null) {
|
||||
replyIcon = 'reply';
|
||||
|
@ -293,7 +297,7 @@ class ActionBar extends React.PureComponent {
|
|||
<div className='detailed-status__button' ><IconButton className={classNames({ reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className='bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /></div>
|
||||
<div className='detailed-status__button'><EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} /></div>
|
||||
<div className='detailed-status__button'><EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} button={emojiPickerButton} /></div>
|
||||
|
||||
{shareButton}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import EditedTimestamp from 'mastodon/components/edited_timestamp';
|
|||
const messages = defineMessages({
|
||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
||||
});
|
||||
|
@ -209,6 +210,7 @@ class DetailedStatus extends ImmutablePureComponent {
|
|||
const visibilityIconInfo = {
|
||||
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
|
||||
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
|
||||
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
|
||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||
};
|
||||
|
|
|
@ -20,6 +20,7 @@ const messages = defineMessages({
|
|||
reblog: { id: 'status.reblog', defaultMessage: 'Boost' },
|
||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
||||
});
|
||||
|
@ -87,6 +88,7 @@ class BoostModal extends ImmutablePureComponent {
|
|||
const visibilityIconInfo = {
|
||||
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
|
||||
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
|
||||
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
|
||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||
};
|
||||
|
|
|
@ -460,6 +460,8 @@
|
|||
"privacy.private.short": "Followers only",
|
||||
"privacy.public.long": "Visible for all",
|
||||
"privacy.public.short": "Public",
|
||||
"privacy.public_unlisted.long": "Visible for all without GTL",
|
||||
"privacy.public_unlisted.short": "Public unlisted",
|
||||
"privacy.unlisted.long": "Visible for all, but opted-out of discovery features",
|
||||
"privacy.unlisted.short": "Unlisted",
|
||||
"privacy_policy.last_updated": "Last updated {date}",
|
||||
|
|
|
@ -458,9 +458,11 @@
|
|||
"privacy.direct.short": "指定された相手のみ",
|
||||
"privacy.private.long": "フォロワーのみ閲覧可",
|
||||
"privacy.private.short": "フォロワーのみ",
|
||||
"privacy.public.long": "誰でも閲覧可",
|
||||
"privacy.public.long": "誰でも閲覧可、全てのTL",
|
||||
"privacy.public.short": "公開",
|
||||
"privacy.unlisted.long": "誰でも閲覧可、サイレント",
|
||||
"privacy.public_unlisted.long": "誰でも閲覧可、ローカル+ホームTL",
|
||||
"privacy.public_unlisted.short": "ローカル公開",
|
||||
"privacy.unlisted.long": "誰でも閲覧可、ホームTL",
|
||||
"privacy.unlisted.short": "未収載",
|
||||
"privacy_policy.last_updated": "{date}に更新",
|
||||
"privacy_policy.title": "プライバシーポリシー",
|
||||
|
|
|
@ -218,7 +218,7 @@ const insertEmoji = (state, position, emojiData, needsSpace) => {
|
|||
};
|
||||
|
||||
const privacyPreference = (a, b) => {
|
||||
const order = ['public', 'unlisted', 'private', 'direct'];
|
||||
const order = ['public', 'public_unlisted', 'unlisted', 'private', 'direct'];
|
||||
return order[Math.max(order.indexOf(a), order.indexOf(b), 0)];
|
||||
};
|
||||
|
||||
|
|
|
@ -802,7 +802,8 @@ body > [data-popper-placement] {
|
|||
vertical-align: middle;
|
||||
object-fit: contain;
|
||||
margin: -0.2ex 0.15em 0.2ex;
|
||||
width: 16px;
|
||||
min-width: 16px;
|
||||
max-width: min(8em, 100%);
|
||||
height: 16px;
|
||||
|
||||
img {
|
||||
|
@ -878,7 +879,8 @@ body > [data-popper-placement] {
|
|||
}
|
||||
|
||||
.emojione {
|
||||
width: 20px;
|
||||
min-width: 20px;
|
||||
max-width: min(8em, 100%);
|
||||
height: 20px;
|
||||
margin: -3px 0 0;
|
||||
}
|
||||
|
@ -959,7 +961,8 @@ body > [data-popper-placement] {
|
|||
overflow-y: auto;
|
||||
|
||||
.emojione {
|
||||
width: 20px;
|
||||
min-width: 20px;
|
||||
max-width: min(8em, 100%);
|
||||
height: 20px;
|
||||
margin: -3px 0 0;
|
||||
}
|
||||
|
@ -1334,7 +1337,8 @@ body > [data-popper-placement] {
|
|||
line-height: 24px;
|
||||
|
||||
.emojione {
|
||||
width: 24px;
|
||||
min-width: 24px;
|
||||
max-width: min(8em, 100%);
|
||||
height: 24px;
|
||||
margin: -1px 0 0;
|
||||
}
|
||||
|
@ -4122,6 +4126,10 @@ a.status-card.compact:hover {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.detailed-status__button .emoji-button {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.column-settings__outer {
|
||||
background: lighten($ui-base-color, 8%);
|
||||
padding: 15px;
|
||||
|
@ -5871,7 +5879,8 @@ a.status-card.compact:hover {
|
|||
line-height: 24px;
|
||||
|
||||
.emojione {
|
||||
width: 24px;
|
||||
min-width: 24px;
|
||||
max-width: min(8em, 100%);
|
||||
height: 24px;
|
||||
margin: -1px 0 0;
|
||||
}
|
||||
|
@ -7222,7 +7231,8 @@ noscript {
|
|||
}
|
||||
|
||||
.emojione {
|
||||
width: 22px;
|
||||
min-width: 22px;
|
||||
max-width: min(8em, 100%);
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
|
|
|
@ -166,6 +166,11 @@
|
|||
.emoji-mart-category .emoji-mart-emoji {
|
||||
cursor: pointer;
|
||||
|
||||
&.emoji-mart-emoji-custom img {
|
||||
width: auto !important;
|
||||
min-width: 22px;
|
||||
}
|
||||
|
||||
span {
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
|
|
|
@ -85,6 +85,13 @@
|
|||
sup {
|
||||
vertical-align: super;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.status__content__text {
|
||||
a.kmy-dangerous-link {
|
||||
color: red !important;
|
||||
}
|
||||
}
|
||||
|
||||
.reply-indicator__content {
|
||||
|
|
|
@ -299,7 +299,8 @@ a.table-action-link {
|
|||
margin-right: 10px;
|
||||
|
||||
.emojione {
|
||||
width: 32px;
|
||||
min-width: 32px;
|
||||
max-width: min(8em, 100%);
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,8 @@
|
|||
font-weight: 400;
|
||||
|
||||
.emojione {
|
||||
width: 20px;
|
||||
min-width: 20px;
|
||||
max-width: min(8em, 100%);
|
||||
height: 20px;
|
||||
margin: -3px 0 0;
|
||||
margin-left: 0.075em;
|
||||
|
|
|
@ -38,7 +38,7 @@ class ActivityPub::Activity::Like < ActivityPub::Activity
|
|||
end
|
||||
end
|
||||
|
||||
return if EmojiReaction.where(account: @account, status: @original_status).count >= 10
|
||||
return if EmojiReaction.where(account: @account, status: @original_status).count >= EmojiReaction::EMOJI_REACTION_PER_ACCOUNT_LIMIT
|
||||
|
||||
reaction = @original_status.emoji_reactions.create!(account: @account, name: shortcode, custom_emoji: emoji, uri: @json['id'])
|
||||
write_stream(reaction)
|
||||
|
|
|
@ -84,7 +84,7 @@ class ActivityPub::TagManager
|
|||
case status.visibility
|
||||
when 'public'
|
||||
[COLLECTIONS[:public]]
|
||||
when 'unlisted', 'private'
|
||||
when 'unlisted', 'public_unlisted', 'private'
|
||||
[account_followers_url(status.account)]
|
||||
when 'direct', 'limited'
|
||||
if status.account.silenced?
|
||||
|
@ -120,7 +120,7 @@ class ActivityPub::TagManager
|
|||
case status.visibility
|
||||
when 'public'
|
||||
cc << account_followers_url(status.account)
|
||||
when 'unlisted'
|
||||
when 'unlisted', 'public_unlisted'
|
||||
cc << COLLECTIONS[:public]
|
||||
end
|
||||
|
||||
|
|
|
@ -109,7 +109,7 @@ class FeedManager
|
|||
def merge_into_home(from_account, into_account)
|
||||
timeline_key = key(:home, into_account.id)
|
||||
aggregate = into_account.user&.aggregates_reblogs?
|
||||
query = from_account.statuses.where(visibility: [:public, :unlisted, :private]).includes(:preloadable_poll, :media_attachments, reblog: :account).limit(FeedManager::MAX_ITEMS / 4)
|
||||
query = from_account.statuses.where(visibility: [:public, :unlisted, :public_unlisted, :private]).includes(:preloadable_poll, :media_attachments, reblog: :account).limit(FeedManager::MAX_ITEMS / 4)
|
||||
|
||||
if redis.zcard(timeline_key) >= FeedManager::MAX_ITEMS / 4
|
||||
oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true).first.last.to_i
|
||||
|
@ -135,7 +135,7 @@ class FeedManager
|
|||
def merge_into_list(from_account, list)
|
||||
timeline_key = key(:list, list.id)
|
||||
aggregate = list.account.user&.aggregates_reblogs?
|
||||
query = from_account.statuses.where(visibility: [:public, :unlisted, :private]).includes(:preloadable_poll, :media_attachments, reblog: :account).limit(FeedManager::MAX_ITEMS / 4)
|
||||
query = from_account.statuses.where(visibility: [:public, :unlisted, :public_unlisted, :private]).includes(:preloadable_poll, :media_attachments, reblog: :account).limit(FeedManager::MAX_ITEMS / 4)
|
||||
|
||||
if redis.zcard(timeline_key) >= FeedManager::MAX_ITEMS / 4
|
||||
oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true).first.last.to_i
|
||||
|
@ -253,7 +253,7 @@ class FeedManager
|
|||
next if last_status_score < oldest_home_score
|
||||
end
|
||||
|
||||
statuses = target_account.statuses.where(visibility: [:public, :unlisted, :private]).includes(:preloadable_poll, :media_attachments, :account, reblog: :account).limit(limit)
|
||||
statuses = target_account.statuses.where(visibility: [:public, :unlisted, :public_unlisted, :private]).includes(:preloadable_poll, :media_attachments, :account, reblog: :account).limit(limit)
|
||||
crutches = build_crutches(account.id, statuses)
|
||||
|
||||
statuses.each do |status|
|
||||
|
|
|
@ -87,7 +87,7 @@ class StatusReachFinder
|
|||
end
|
||||
|
||||
def distributable?
|
||||
@status.public_visibility? || @status.unlisted_visibility?
|
||||
@status.public_visibility? || @status.unlisted_visibility? || @status.public_unlisted_visibility?
|
||||
end
|
||||
|
||||
def unsafe?
|
||||
|
|
|
@ -44,7 +44,6 @@ class TextFormatter
|
|||
end
|
||||
|
||||
# line first letter for blockquote
|
||||
p 'DEBUG ' + html.gsub(/^gt;/, '>')
|
||||
html = markdownify(html.gsub(/^>/, '>'))
|
||||
|
||||
# html = simple_format(html, {}, sanitize: false).delete("\n") if multiline?
|
||||
|
@ -202,6 +201,10 @@ class TextFormatter
|
|||
text.include?(':') ? nil : '<u>' + text + '</u>'
|
||||
end
|
||||
|
||||
def image(link, title, alt_text)
|
||||
nil
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def process_program_code(code)
|
||||
|
|
|
@ -35,7 +35,7 @@ class AccountStatusesFilter
|
|||
if suspended?
|
||||
Status.none
|
||||
elsif anonymous?
|
||||
account.statuses.where(visibility: %i(public unlisted))
|
||||
account.statuses.where(visibility: %i(public unlisted public_unlisted))
|
||||
elsif author?
|
||||
account.statuses.all # NOTE: #merge! does not work without the #all
|
||||
elsif blocked?
|
||||
|
@ -48,7 +48,7 @@ class AccountStatusesFilter
|
|||
def filtered_scope
|
||||
scope = account.statuses.left_outer_joins(:mentions)
|
||||
|
||||
scope.merge!(scope.where(visibility: follower? ? %i(public unlisted private) : %i(public unlisted)).or(scope.where(mentions: { account_id: current_account.id })).group(Status.arel_table[:id]))
|
||||
scope.merge!(scope.where(visibility: follower? ? %i(public unlisted public_unlisted private) : %i(public unlisted public_unlisted)).or(scope.where(mentions: { account_id: current_account.id })).group(Status.arel_table[:id]))
|
||||
scope.merge!(filtered_reblogs_scope) if reblogs_may_occur?
|
||||
|
||||
scope
|
||||
|
|
|
@ -14,7 +14,7 @@ class Admin::StatusFilter
|
|||
end
|
||||
|
||||
def results
|
||||
scope = @account.statuses.where(visibility: [:public, :unlisted])
|
||||
scope = @account.statuses.where(visibility: [:public, :unlisted, :public_unlisted])
|
||||
|
||||
params.each do |key, value|
|
||||
next if %w(page report_id).include?(key.to_s)
|
||||
|
|
|
@ -57,7 +57,7 @@ class Announcement < ApplicationRecord
|
|||
@statuses ||= if status_ids.nil?
|
||||
[]
|
||||
else
|
||||
Status.where(id: status_ids, visibility: [:public, :unlisted])
|
||||
Status.where(id: status_ids, visibility: [:public, :unlisted, :public_unlisted])
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ module StatusThreadingConcern
|
|||
end
|
||||
|
||||
def self_replies(limit)
|
||||
account.statuses.where(in_reply_to_id: id, visibility: [:public, :unlisted]).reorder(id: :asc).limit(limit)
|
||||
account.statuses.where(in_reply_to_id: id, visibility: [:public, :unlisted, :public_unlisted]).reorder(id: :asc).limit(limit)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
# visible_in_picker :boolean default(TRUE), not null
|
||||
# category_id :bigint(8)
|
||||
# image_storage_schema_version :integer
|
||||
# image_width :integer
|
||||
# image_height :integer
|
||||
#
|
||||
|
||||
class CustomEmoji < ApplicationRecord
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
class EmojiReaction < ApplicationRecord
|
||||
include Paginable
|
||||
|
||||
EMOJI_REACTION_LIMIT = 32767
|
||||
EMOJI_REACTION_PER_ACCOUNT_LIMIT = 5
|
||||
|
||||
update_index('statuses', :status)
|
||||
|
||||
belongs_to :account, inverse_of: :emoji_reactions
|
||||
|
|
|
@ -45,7 +45,7 @@ class FeaturedTag < ApplicationRecord
|
|||
end
|
||||
|
||||
def decrement(deleted_status_id)
|
||||
update(statuses_count: [0, statuses_count - 1].max, last_status_at: account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).where.not(id: deleted_status_id).select(:created_at).first&.created_at)
|
||||
update(statuses_count: [0, statuses_count - 1].max, last_status_at: account.statuses.where(visibility: %i(public unlisted public_unlisted)).tagged_with(tag).where.not(id: deleted_status_id).select(:created_at).first&.created_at)
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -59,8 +59,8 @@ class FeaturedTag < ApplicationRecord
|
|||
end
|
||||
|
||||
def reset_data
|
||||
self.statuses_count = account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).count
|
||||
self.last_status_at = account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).select(:created_at).first&.created_at
|
||||
self.statuses_count = account.statuses.where(visibility: %i(public unlisted public_unlisted)).tagged_with(tag).count
|
||||
self.last_status_at = account.statuses.where(visibility: %i(public unlisted public_unlisted)).tagged_with(tag).select(:created_at).first&.created_at
|
||||
end
|
||||
|
||||
def validate_featured_tags_limit
|
||||
|
|
|
@ -25,6 +25,7 @@ class PublicFeed
|
|||
scope.merge!(without_reblogs_scope) unless with_reblogs?
|
||||
scope.merge!(local_only_scope) if local_only?
|
||||
scope.merge!(remote_only_scope) if remote_only?
|
||||
scope.merge!(global_timeline_only_scope) if global_timeline?
|
||||
scope.merge!(account_filters_scope) if account?
|
||||
scope.merge!(media_only_scope) if media_only?
|
||||
scope.merge!(language_scope) if account&.chosen_languages.present?
|
||||
|
@ -52,6 +53,10 @@ class PublicFeed
|
|||
options[:remote]
|
||||
end
|
||||
|
||||
def global_timeline?
|
||||
!options[:remote] && !options[:local]
|
||||
end
|
||||
|
||||
def account?
|
||||
account.present?
|
||||
end
|
||||
|
@ -72,6 +77,10 @@ class PublicFeed
|
|||
Status.remote
|
||||
end
|
||||
|
||||
def global_timeline_only_scope
|
||||
Status.with_global_timeline_visibility.joins(:account).merge(Account.without_suspended.without_silenced)
|
||||
end
|
||||
|
||||
def without_replies_scope
|
||||
Status.without_replies
|
||||
end
|
||||
|
|
|
@ -51,7 +51,7 @@ class Status < ApplicationRecord
|
|||
|
||||
update_index('statuses', :proper)
|
||||
|
||||
enum visibility: { public: 0, unlisted: 1, private: 2, direct: 3, limited: 4 }, _suffix: :visibility
|
||||
enum visibility: { public: 0, unlisted: 1, private: 2, direct: 3, limited: 4, public_unlisted: 10 }, _suffix: :visibility
|
||||
|
||||
belongs_to :application, class_name: 'Doorkeeper::Application', optional: true
|
||||
|
||||
|
@ -99,7 +99,8 @@ class Status < ApplicationRecord
|
|||
scope :with_accounts, ->(ids) { where(id: ids).includes(:account) }
|
||||
scope :without_replies, -> { where('statuses.reply = FALSE OR statuses.in_reply_to_account_id = statuses.account_id') }
|
||||
scope :without_reblogs, -> { where(statuses: { reblog_of_id: nil }) }
|
||||
scope :with_public_visibility, -> { where(visibility: :public) }
|
||||
scope :with_public_visibility, -> { where(visibility: [:public, :public_unlisted]) }
|
||||
scope :with_global_timeline_visibility, -> { where(visibility: [:public]) }
|
||||
scope :tagged_with, ->(tag_ids) { joins(:statuses_tags).where(statuses_tags: { tag_id: tag_ids }) }
|
||||
scope :excluding_silenced_accounts, -> { left_outer_joins(:account).where(accounts: { silenced_at: nil }) }
|
||||
scope :including_silenced_accounts, -> { left_outer_joins(:account).where.not(accounts: { silenced_at: nil }) }
|
||||
|
@ -232,7 +233,7 @@ class Status < ApplicationRecord
|
|||
end
|
||||
|
||||
def distributable?
|
||||
public_visibility? || unlisted_visibility?
|
||||
public_visibility? || unlisted_visibility? || public_unlisted_visibility?
|
||||
end
|
||||
|
||||
def translatable?
|
||||
|
@ -306,8 +307,8 @@ class Status < ApplicationRecord
|
|||
if account.present?
|
||||
emoji_reactions.each do |emoji_reaction|
|
||||
emoji_reaction['me'] = emoji_reaction['account_ids'].include?(account.id.to_s)
|
||||
emoji_reaction['count'] = emoji_reaction['account_ids'].size
|
||||
emoji_reaction['account_ids'] -= account.excluded_from_timeline_account_ids.map(&:to_s)
|
||||
emoji_reaction['count'] = emoji_reaction['account_ids'].size
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -12,7 +12,7 @@ class Admin::StatusPolicy < ApplicationPolicy
|
|||
end
|
||||
|
||||
def show?
|
||||
role.can?(:manage_reports, :manage_users) && (record.public_visibility? || record.unlisted_visibility? || record.reported?)
|
||||
role.can?(:manage_reports, :manage_users) && (record.public_visibility? || record.unlisted_visibility? || record.public_unlisted_visibility? || record.reported?)
|
||||
end
|
||||
|
||||
def destroy?
|
||||
|
|
|
@ -17,7 +17,7 @@ class StatusRelationshipsPresenter
|
|||
statuses = statuses.compact
|
||||
status_ids = statuses.flat_map { |s| [s.id, s.reblog_of_id] }.uniq.compact
|
||||
conversation_ids = statuses.filter_map(&:conversation_id).uniq
|
||||
pinnable_status_ids = statuses.map(&:proper).filter_map { |s| s.id if s.account_id == current_account_id && %w(public unlisted private).include?(s.visibility) }
|
||||
pinnable_status_ids = statuses.map(&:proper).filter_map { |s| s.id if s.account_id == current_account_id && %w(public unlisted public_unlisted private).include?(s.visibility) }
|
||||
|
||||
@filters_map = build_filters_map(statuses, current_account_id).merge(options[:filters_map] || {})
|
||||
@reblogs_map = Status.reblogs_map(status_ids, current_account_id).merge(options[:reblogs_map] || {})
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
class REST::CustomEmojiSerializer < ActiveModel::Serializer
|
||||
include RoutingHelper
|
||||
|
||||
attributes :shortcode, :url, :static_url, :visible_in_picker
|
||||
attributes :shortcode, :url, :static_url, :visible_in_picker, :width, :height
|
||||
|
||||
attribute :category, if: :category_loaded?
|
||||
|
||||
|
@ -22,4 +22,12 @@ class REST::CustomEmojiSerializer < ActiveModel::Serializer
|
|||
def category_loaded?
|
||||
object.association(:category).loaded? && object.category.present?
|
||||
end
|
||||
|
||||
def width
|
||||
object.image_width
|
||||
end
|
||||
|
||||
def height
|
||||
object.image_height
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,6 +9,8 @@ class REST::EmojiReactionsGroupedByNameSerializer < ActiveModel::Serializer
|
|||
attribute :url, if: :custom_emoji?
|
||||
attribute :static_url, if: :custom_emoji?
|
||||
attribute :domain, if: :custom_emoji?
|
||||
attribute :width, if: :custom_emoji?
|
||||
attribute :height, if: :custom_emoji?
|
||||
attribute :account_ids, if: :account_ids?
|
||||
|
||||
def current_user?
|
||||
|
@ -34,4 +36,12 @@ class REST::EmojiReactionsGroupedByNameSerializer < ActiveModel::Serializer
|
|||
def domain
|
||||
object.custom_emoji.domain
|
||||
end
|
||||
|
||||
def width
|
||||
object.custom_emoji.image_width
|
||||
end
|
||||
|
||||
def height
|
||||
object.custom_emoji.image_height
|
||||
end
|
||||
end
|
||||
|
|
|
@ -77,6 +77,11 @@ class REST::InstanceSerializer < ActiveModel::Serializer
|
|||
translation: {
|
||||
enabled: TranslationService.configured?,
|
||||
},
|
||||
|
||||
emoji_reactions: {
|
||||
max_reactions: EmojiReaction::EMOJI_REACTION_LIMIT,
|
||||
max_reactions_per_account: EmojiReaction::EMOJI_REACTION_PER_ACCOUNT_LIMIT,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -92,6 +97,9 @@ class REST::InstanceSerializer < ActiveModel::Serializer
|
|||
def fedibird_capabilities
|
||||
capabilities = [
|
||||
:emoji_reaction,
|
||||
:visibility_public_unlisted,
|
||||
:enable_wide_emoji,
|
||||
:enable_wide_emoji_reaction,
|
||||
]
|
||||
|
||||
capabilities << :profile_search unless Chewy.enabled?
|
||||
|
|
|
@ -141,7 +141,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
|||
current_user? &&
|
||||
current_user.account_id == object.account_id &&
|
||||
!object.reblog? &&
|
||||
%w(public unlisted private).include?(object.visibility)
|
||||
%w(public unlisted public_unlisted private).include?(object.visibility)
|
||||
end
|
||||
|
||||
def source_requested?
|
||||
|
|
|
@ -83,6 +83,11 @@ class REST::V1::InstanceSerializer < ActiveModel::Serializer
|
|||
min_expiration: PollValidator::MIN_EXPIRATION,
|
||||
max_expiration: PollValidator::MAX_EXPIRATION,
|
||||
},
|
||||
|
||||
emoji_reactions: {
|
||||
max_reactions: EmojiReaction::EMOJI_REACTION_LIMIT,
|
||||
max_reactions_per_account: EmojiReaction::EMOJI_REACTION_PER_ACCOUNT_LIMIT,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -102,6 +107,9 @@ class REST::V1::InstanceSerializer < ActiveModel::Serializer
|
|||
def fedibird_capabilities
|
||||
capabilities = [
|
||||
:emoji_reaction,
|
||||
:visibility_public_unlisted,
|
||||
:enable_wide_emoji,
|
||||
:enable_wide_emoji_reaction,
|
||||
]
|
||||
|
||||
capabilities << :profile_search unless Chewy.enabled?
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
module AccountLimitable
|
||||
def scope_status(status)
|
||||
case status.visibility.to_sym
|
||||
when :public, :unlisted
|
||||
when :public, :unlisted, :public_unlisted
|
||||
#scope_local.merge(scope_list_following_account(status.account))
|
||||
scope_local
|
||||
when :private
|
||||
|
|
|
@ -17,8 +17,13 @@ class FanOutOnWriteService < BaseService
|
|||
warm_payload_cache!
|
||||
|
||||
fan_out_to_local_recipients!
|
||||
fan_out_to_public_recipients! if broadcastable?
|
||||
fan_out_to_public_streams! if broadcastable?
|
||||
if broadcastable?
|
||||
fan_out_to_public_recipients!
|
||||
fan_out_to_public_streams!
|
||||
elsif broadcastable_unlisted?
|
||||
fan_out_to_public_recipients!
|
||||
fan_out_to_public_unlisted_streams!
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -41,7 +46,7 @@ class FanOutOnWriteService < BaseService
|
|||
notify_about_update! if update?
|
||||
|
||||
case @status.visibility.to_sym
|
||||
when :public, :unlisted, :private
|
||||
when :public, :unlisted, :public_unlisted, :private
|
||||
deliver_to_all_followers!
|
||||
deliver_to_lists!
|
||||
when :limited
|
||||
|
@ -61,6 +66,11 @@ class FanOutOnWriteService < BaseService
|
|||
broadcast_to_public_streams!
|
||||
end
|
||||
|
||||
def fan_out_to_public_unlisted_streams!
|
||||
broadcast_to_hashtag_streams!
|
||||
broadcast_to_public_unlisted_streams!
|
||||
end
|
||||
|
||||
def deliver_to_self!
|
||||
FeedManager.instance.push_to_home(@account, @status, update: update?) if @account.local?
|
||||
end
|
||||
|
@ -132,6 +142,16 @@ class FanOutOnWriteService < BaseService
|
|||
end
|
||||
end
|
||||
|
||||
def broadcast_to_public_unlisted_streams!
|
||||
return if @status.reply? && @status.in_reply_to_account_id != @account.id
|
||||
|
||||
redis.publish(@status.local? ? 'timeline:public:local' : 'timeline:public:remote', anonymous_payload)
|
||||
|
||||
if @status.with_media?
|
||||
redis.publish(@status.local? ? 'timeline:public:local:media' : 'timeline:public:remote:media', anonymous_payload)
|
||||
end
|
||||
end
|
||||
|
||||
def deliver_to_conversation!
|
||||
AccountConversation.add_status(@account, @status) unless update?
|
||||
end
|
||||
|
@ -158,4 +178,8 @@ class FanOutOnWriteService < BaseService
|
|||
def broadcastable?
|
||||
@status.public_visibility? && !@status.reblog? && !@account.silenced?
|
||||
end
|
||||
|
||||
def broadcastable_unlisted?
|
||||
@status.public_unlisted_visibility? && !@status.reblog? && !@account.silenced?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -65,7 +65,7 @@ class PostStatusService < BaseService
|
|||
@sensitive = (@options[:sensitive].nil? ? @account.user&.setting_default_sensitive : @options[:sensitive]) || @options[:spoiler_text].present?
|
||||
@text = @options.delete(:spoiler_text) if @text.blank? && @options[:spoiler_text].present?
|
||||
@visibility = @options[:visibility] || @account.user&.setting_default_privacy
|
||||
@visibility = :unlisted if @visibility&.to_sym == :public && @account.silenced?
|
||||
@visibility = :unlisted if (@visibility&.to_sym == :public || @visibility&.to_sym == :public_unlisted) && @account.silenced?
|
||||
@scheduled_at = @options[:scheduled_at]&.to_datetime
|
||||
@scheduled_at = nil if scheduled_in_the_past?
|
||||
rescue ArgumentError
|
||||
|
|
|
@ -62,7 +62,7 @@ class ReportService < BaseService
|
|||
# If the account making reports is remote, it is likely anonymized so we have to relax the requirements for attaching statuses.
|
||||
domain = @source_account.domain.to_s.downcase
|
||||
has_followers = @target_account.followers.where(Account.arel_table[:domain].lower.eq(domain)).exists?
|
||||
visibility = has_followers ? %i(public unlisted private) : %i(public unlisted)
|
||||
visibility = has_followers ? %i(public unlisted public_unlisted private) : %i(public unlisted public_unlisted)
|
||||
scope = @target_account.statuses.with_discarded
|
||||
scope.merge!(scope.where(visibility: visibility).or(scope.where('EXISTS (SELECT 1 FROM mentions m JOIN accounts a ON m.account_id = a.id WHERE lower(a.domain) = ?)', domain)))
|
||||
# Allow missing posts to not drop reports that include e.g. a deleted post
|
||||
|
|
|
@ -16,8 +16,8 @@ class FeedAnyJsonWorker
|
|||
redis.publish("timeline:#{account.id}", payload_json) if redis.exists?("subscribed:timeline:#{account.id}")
|
||||
end
|
||||
|
||||
if status.visibility.to_sym != :public && status.visibility.to_sym != :unlisted && status.account_id != my_account_id &&
|
||||
redis.exists?("subscribed:timeline:#{status.account_id}")
|
||||
if status.visibility.to_sym != :public && status.visibility.to_sym != :unlisted && status.visibility.to_sym != :public_unlisted && status.account_id != my_account_id &&
|
||||
redis.exists?("subscribed:timeline:#{status.account_id}")
|
||||
redis.publish("timeline:#{status.account_id}", payload_json)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1521,9 +1521,11 @@ ja:
|
|||
private: フォロワー限定
|
||||
private_long: フォロワーにのみ表示されます
|
||||
public: 公開
|
||||
public_long: 誰でも見ることができ、かつ公開タイムラインに表示されます
|
||||
public_long: 誰でも見ることができ、かつ連合・ローカルタイムラインに表示されます
|
||||
public_unlisted: ローカル公開
|
||||
public_unlisted_long: 誰でも見ることができますが、連合タイムラインには表示されません
|
||||
unlisted: 未収載
|
||||
unlisted_long: 誰でも見ることができますが、公開タイムラインには表示されません
|
||||
unlisted_long: 誰でも見ることができますが、連合・ローカルタイムラインには表示されません
|
||||
statuses_cleanup:
|
||||
enabled: 古い投稿を自動的に削除する
|
||||
enabled_hint: 設定した期間を過ぎた投稿は、以下の例外に該当しない限り、自動的に削除されます
|
||||
|
|
|
@ -463,7 +463,7 @@ Rails.application.routes.draw do
|
|||
|
||||
post :translate, to: 'translations#create'
|
||||
|
||||
resources :emoji_reactions, only: [:create, :update], constraints: { id: /[^\/]+/ }
|
||||
resources :emoji_reactions, only: [:create, :update, :destroy], constraints: { id: /[^\/]+/ }
|
||||
post :emoji_unreaction, to: 'emoji_reactions#destroy'
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
class AddImageSizeToCustomEmojis < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
add_column :custom_emojis, :image_width, :integer
|
||||
add_column :custom_emojis, :image_height, :integer
|
||||
end
|
||||
end
|
|
@ -10,7 +10,7 @@
|
|||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2023_02_23_102416) do
|
||||
ActiveRecord::Schema.define(version: 2023_03_08_061833) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "plpgsql"
|
||||
|
@ -338,6 +338,8 @@ ActiveRecord::Schema.define(version: 2023_02_23_102416) do
|
|||
t.boolean "visible_in_picker", default: true, null: false
|
||||
t.bigint "category_id"
|
||||
t.integer "image_storage_schema_version"
|
||||
t.integer "image_width"
|
||||
t.integer "image_height"
|
||||
t.index ["shortcode", "domain"], name: "index_custom_emojis_on_shortcode_and_domain", unique: true
|
||||
end
|
||||
|
||||
|
|
|
@ -50,6 +50,26 @@ class Sanitize
|
|||
current_node.replace(Nokogiri::XML::Text.new(current_node.text, current_node.document)) unless LINK_PROTOCOLS.include?(scheme)
|
||||
end
|
||||
|
||||
PHISHING_SCAM_HREF_TRANSFORMER = lambda do |env|
|
||||
return unless env[:node_name] == 'a'
|
||||
|
||||
current_node = env[:node]
|
||||
href = current_node['href']
|
||||
text = current_node.text
|
||||
cls = current_node['class'] || ''
|
||||
|
||||
scheme = if current_node['href'] =~ Sanitize::REGEX_PROTOCOL
|
||||
Regexp.last_match(1).downcase
|
||||
else
|
||||
:relative
|
||||
end
|
||||
|
||||
if LINK_PROTOCOLS.include?(scheme) && href != text && href != 'https://' + text && !text.start_with?('#') && !text.start_with?('@')
|
||||
current_node['class'] = cls + ' kmy-dangerous-link'
|
||||
current_node.before(Nokogiri::XML::Text.new('⚠', current_node.document))
|
||||
end
|
||||
end
|
||||
|
||||
UNSUPPORTED_ELEMENTS_TRANSFORMER = lambda do |env|
|
||||
return unless %w(h1 h2 h3 h4 h5 h6).include?(env[:node_name])
|
||||
|
||||
|
@ -82,6 +102,7 @@ class Sanitize
|
|||
CLASS_WHITELIST_TRANSFORMER,
|
||||
UNSUPPORTED_ELEMENTS_TRANSFORMER,
|
||||
UNSUPPORTED_HREF_TRANSFORMER,
|
||||
PHISHING_SCAM_HREF_TRANSFORMER,
|
||||
]
|
||||
)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue