Merge pull request #1951 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes
This commit is contained in:
commit
381137c94e
|
@ -37,7 +37,8 @@ jobs:
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
|
builder: ${{ steps.buildx.outputs.name }}
|
||||||
push: ${{ github.event_name != 'pull_request' }}
|
push: ${{ github.event_name != 'pull_request' }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/mastodon:edge
|
cache-from: type=gha
|
||||||
cache-to: type=inline
|
cache-to: type=gha,mode=max
|
||||||
|
|
|
@ -243,6 +243,10 @@ Style/HashTransformKeys:
|
||||||
Style/HashTransformValues:
|
Style/HashTransformValues:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
|
Style/HashSyntax:
|
||||||
|
Enabled: true
|
||||||
|
EnforcedStyle: ruby19_no_mixed_keys
|
||||||
|
|
||||||
Style/IfUnlessModifier:
|
Style/IfUnlessModifier:
|
||||||
Enabled: false
|
Enabled: false
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
# syntax=docker/dockerfile:1.4
|
||||||
FROM ubuntu:20.04 as build-dep
|
FROM ubuntu:20.04 as build-dep
|
||||||
|
|
||||||
# Use bash for the shell
|
# Use bash for the shell
|
||||||
|
@ -65,8 +66,8 @@ RUN cd /opt/mastodon && \
|
||||||
FROM ubuntu:20.04
|
FROM ubuntu:20.04
|
||||||
|
|
||||||
# Copy over all the langs needed for runtime
|
# Copy over all the langs needed for runtime
|
||||||
COPY --from=build-dep /opt/node /opt/node
|
COPY --from=build-dep --link /opt/node /opt/node
|
||||||
COPY --from=build-dep /opt/ruby /opt/ruby
|
COPY --from=build-dep --link /opt/ruby /opt/ruby
|
||||||
|
|
||||||
# Add more PATHs to the PATH
|
# Add more PATHs to the PATH
|
||||||
ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin:/opt/mastodon/bin"
|
ENV PATH="${PATH}:/opt/ruby/bin:/opt/node/bin:/opt/mastodon/bin"
|
||||||
|
|
|
@ -17,6 +17,8 @@ class AccountsController < ApplicationController
|
||||||
respond_to do |format|
|
respond_to do |format|
|
||||||
format.html do
|
format.html do
|
||||||
expires_in 0, public: true unless user_signed_in?
|
expires_in 0, public: true unless user_signed_in?
|
||||||
|
|
||||||
|
@rss_url = rss_url
|
||||||
end
|
end
|
||||||
|
|
||||||
format.rss do
|
format.rss do
|
||||||
|
|
|
@ -9,9 +9,9 @@ module Admin
|
||||||
@form = Form::DomainBlockBatch.new(form_domain_block_batch_params.merge(current_account: current_account, action: action_from_button))
|
@form = Form::DomainBlockBatch.new(form_domain_block_batch_params.merge(current_account: current_account, action: action_from_button))
|
||||||
@form.save
|
@form.save
|
||||||
rescue ActionController::ParameterMissing
|
rescue ActionController::ParameterMissing
|
||||||
flash[:alert] = I18n.t('admin.email_domain_blocks.no_domain_block_selected')
|
flash[:alert] = I18n.t('admin.domain_blocks.no_domain_block_selected')
|
||||||
rescue Mastodon::NotPermittedError
|
rescue Mastodon::NotPermittedError
|
||||||
flash[:alert] = I18n.t('admin.domain_blocks.created_msg')
|
flash[:alert] = I18n.t('admin.domain_blocks.not_permitted')
|
||||||
else
|
else
|
||||||
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
|
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,7 +19,7 @@ module Admin
|
||||||
rescue ActionController::ParameterMissing
|
rescue ActionController::ParameterMissing
|
||||||
flash[:alert] = I18n.t('admin.email_domain_blocks.no_email_domain_block_selected')
|
flash[:alert] = I18n.t('admin.email_domain_blocks.no_email_domain_block_selected')
|
||||||
rescue Mastodon::NotPermittedError
|
rescue Mastodon::NotPermittedError
|
||||||
flash[:alert] = I18n.t('admin.custom_emojis.not_permitted')
|
flash[:alert] = I18n.t('admin.email_domain_blocks.not_permitted')
|
||||||
ensure
|
ensure
|
||||||
redirect_to admin_email_domain_blocks_path
|
redirect_to admin_email_domain_blocks_path
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
class Api::V1::FollowedTagsController < Api::BaseController
|
class Api::V1::FollowedTagsController < Api::BaseController
|
||||||
TAGS_LIMIT = 100
|
TAGS_LIMIT = 100
|
||||||
|
|
||||||
before_action -> { doorkeeper_authorize! :follow, :read, :'read:follows' }, except: :show
|
before_action -> { doorkeeper_authorize! :follow, :read, :'read:follows' }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
before_action :set_results
|
before_action :set_results
|
||||||
|
|
||||||
after_action :insert_pagination_headers, only: :show
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render json: @results.map(&:tag), each_serializer: REST::TagSerializer, relationships: TagRelationshipsPresenter.new(@results.map(&:tag), current_user&.account_id)
|
render json: @results.map(&:tag), each_serializer: REST::TagSerializer, relationships: TagRelationshipsPresenter.new(@results.map(&:tag), current_user&.account_id)
|
||||||
|
@ -43,7 +43,7 @@ class Api::V1::FollowedTagsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def records_continue?
|
def records_continue?
|
||||||
@results.size == limit_param(TAG_LIMIT)
|
@results.size == limit_param(TAGS_LIMIT)
|
||||||
end
|
end
|
||||||
|
|
||||||
def pagination_params(core_params)
|
def pagination_params(core_params)
|
||||||
|
|
|
@ -12,7 +12,7 @@ class Api::V1::TagsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow
|
def follow
|
||||||
TagFollow.create!(tag: @tag, account: current_account, rate_limit: true)
|
TagFollow.first_or_create!(tag: @tag, account: current_account, rate_limit: true)
|
||||||
render json: @tag, serializer: REST::TagSerializer
|
render json: @tag, serializer: REST::TagSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,10 @@ class StatusesCleanupController < ApplicationController
|
||||||
# Do nothing
|
# Do nothing
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def require_functional!
|
||||||
|
redirect_to edit_user_registration_path unless current_user.functional_or_moved?
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_pack
|
def set_pack
|
||||||
|
|
|
@ -19,8 +19,6 @@ const emojiFilename = (filename) => {
|
||||||
return borderedEmoji.includes(filename) ? (filename + '_border') : filename;
|
return borderedEmoji.includes(filename) ? (filename + '_border') : filename;
|
||||||
};
|
};
|
||||||
|
|
||||||
const domParser = new DOMParser();
|
|
||||||
|
|
||||||
const emojifyTextNode = (node, customEmojis) => {
|
const emojifyTextNode = (node, customEmojis) => {
|
||||||
let str = node.textContent;
|
let str = node.textContent;
|
||||||
|
|
||||||
|
@ -39,7 +37,7 @@ const emojifyTextNode = (node, customEmojis) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let rend, replacement = '';
|
let rend, replacement = null;
|
||||||
if (i === str.length) {
|
if (i === str.length) {
|
||||||
break;
|
break;
|
||||||
} else if (str[i] === ':') {
|
} else if (str[i] === ':') {
|
||||||
|
@ -51,7 +49,14 @@ const emojifyTextNode = (node, customEmojis) => {
|
||||||
// if you want additional emoji handler, add statements below which set replacement and return true.
|
// if you want additional emoji handler, add statements below which set replacement and return true.
|
||||||
if (shortname in customEmojis) {
|
if (shortname in customEmojis) {
|
||||||
const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url;
|
const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url;
|
||||||
replacement = `<img draggable="false" class="emojione custom-emoji" alt="${shortname}" title="${shortname}" src="${filename}" data-original="${customEmojis[shortname].url}" data-static="${customEmojis[shortname].static_url}" />`;
|
replacement = document.createElement('img');
|
||||||
|
replacement.setAttribute('draggable', false);
|
||||||
|
replacement.setAttribute('class', 'emojione custom-emoji');
|
||||||
|
replacement.setAttribute('alt', shortname);
|
||||||
|
replacement.setAttribute('title', shortname);
|
||||||
|
replacement.setAttribute('src', filename);
|
||||||
|
replacement.setAttribute('data-original', customEmojis[shortname].url);
|
||||||
|
replacement.setAttribute('data-static', customEmojis[shortname].static_url);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -59,7 +64,12 @@ const emojifyTextNode = (node, customEmojis) => {
|
||||||
} else if (!useSystemEmojiFont) { // matched to unicode emoji
|
} else if (!useSystemEmojiFont) { // matched to unicode emoji
|
||||||
const { filename, shortCode } = unicodeMapping[match];
|
const { filename, shortCode } = unicodeMapping[match];
|
||||||
const title = shortCode ? `:${shortCode}:` : '';
|
const title = shortCode ? `:${shortCode}:` : '';
|
||||||
replacement = `<img draggable="false" class="emojione" alt="${match}" title="${title}" src="${assetHost}/emoji/${emojiFilename(filename)}.svg" />`;
|
replacement = document.createElement('img');
|
||||||
|
replacement.setAttribute('draggable', false);
|
||||||
|
replacement.setAttribute('class', 'emojione');
|
||||||
|
replacement.setAttribute('alt', match);
|
||||||
|
replacement.setAttribute('title', title);
|
||||||
|
replacement.setAttribute('src', `${assetHost}/emoji/${emojiFilename(filename)}.svg`);
|
||||||
rend = i + match.length;
|
rend = i + match.length;
|
||||||
// If the matched character was followed by VS15 (for selecting text presentation), skip it.
|
// If the matched character was followed by VS15 (for selecting text presentation), skip it.
|
||||||
if (str.codePointAt(rend) === 65038) {
|
if (str.codePointAt(rend) === 65038) {
|
||||||
|
@ -69,9 +79,8 @@ const emojifyTextNode = (node, customEmojis) => {
|
||||||
|
|
||||||
fragment.append(document.createTextNode(str.slice(0, i)));
|
fragment.append(document.createTextNode(str.slice(0, i)));
|
||||||
if (replacement) {
|
if (replacement) {
|
||||||
fragment.append(domParser.parseFromString(replacement, 'text/html').documentElement.getElementsByTagName('img')[0]);
|
fragment.append(replacement);
|
||||||
}
|
}
|
||||||
node.textContent = str.slice(0, i);
|
|
||||||
str = str.slice(rend);
|
str = str.slice(rend);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,16 @@ const mapStateToProps = state => ({
|
||||||
isSearching: state.getIn(['search', 'submitted']) || !showTrends,
|
isSearching: state.getIn(['search', 'submitted']) || !showTrends,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Fix strange bug on Safari where <span> (rendered by FormattedMessage) disappears
|
||||||
|
// after clicking around Explore top bar (issue #20885).
|
||||||
|
// Removing width=100% from <a> also fixes it, as well as replacing <span> with <div>
|
||||||
|
// We're choosing to wrap span with div to keep the changes local only to this tool bar.
|
||||||
|
const WrapFormattedMessage = ({ children, ...props }) => <div><FormattedMessage {...props}>{children}</FormattedMessage></div>;
|
||||||
|
WrapFormattedMessage.propTypes = {
|
||||||
|
children: PropTypes.any,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
export default @connect(mapStateToProps)
|
||||||
@injectIntl
|
@injectIntl
|
||||||
class Explore extends React.PureComponent {
|
class Explore extends React.PureComponent {
|
||||||
|
@ -70,10 +80,10 @@ class Explore extends React.PureComponent {
|
||||||
) : (
|
) : (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<div className='account__section-headline'>
|
<div className='account__section-headline'>
|
||||||
<NavLink exact to='/explore'><FormattedMessage id='explore.trending_statuses' defaultMessage='Posts' /></NavLink>
|
<NavLink exact to='/explore'><WrapFormattedMessage id='explore.trending_statuses' defaultMessage='Posts' /></NavLink>
|
||||||
<NavLink exact to='/explore/tags'><FormattedMessage id='explore.trending_tags' defaultMessage='Hashtags' /></NavLink>
|
<NavLink exact to='/explore/tags'><WrapFormattedMessage id='explore.trending_tags' defaultMessage='Hashtags' /></NavLink>
|
||||||
<NavLink exact to='/explore/links'><FormattedMessage id='explore.trending_links' defaultMessage='News' /></NavLink>
|
<NavLink exact to='/explore/links'><WrapFormattedMessage id='explore.trending_links' defaultMessage='News' /></NavLink>
|
||||||
{signedIn && <NavLink exact to='/explore/suggestions'><FormattedMessage id='explore.suggested_follows' defaultMessage='For you' /></NavLink>}
|
{signedIn && <NavLink exact to='/explore/suggestions'><WrapFormattedMessage id='explore.suggested_follows' defaultMessage='For you' /></NavLink>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Switch>
|
<Switch>
|
||||||
|
|
|
@ -36,7 +36,7 @@ class Header extends React.PureComponent {
|
||||||
if (signedIn) {
|
if (signedIn) {
|
||||||
content = (
|
content = (
|
||||||
<>
|
<>
|
||||||
{location.pathname !== '/publish' && <Link to='/publish' className='button'><FormattedMessage id='compose_form.publish' defaultMessage='Publish' /></Link>}
|
{location.pathname !== '/publish' && <Link to='/publish' className='button'><FormattedMessage id='compose_form.publish_form' defaultMessage='Publish' /></Link>}
|
||||||
<Account />
|
<Account />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -88,5 +88,10 @@ describe('emoji', () => {
|
||||||
expect(emojify('💂♀️💂♂️'))
|
expect(emojify('💂♀️💂♂️'))
|
||||||
.toEqual('<img draggable="false" class="emojione" alt="💂\u200D♀️" title=":female-guard:" src="/emoji/1f482-200d-2640-fe0f_border.svg"><img draggable="false" class="emojione" alt="💂\u200D♂️" title=":male-guard:" src="/emoji/1f482-200d-2642-fe0f_border.svg">');
|
.toEqual('<img draggable="false" class="emojione" alt="💂\u200D♀️" title=":female-guard:" src="/emoji/1f482-200d-2640-fe0f_border.svg"><img draggable="false" class="emojione" alt="💂\u200D♂️" title=":male-guard:" src="/emoji/1f482-200d-2642-fe0f_border.svg">');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('keeps ordering as expected (issue fixed by PR 20677)', () => {
|
||||||
|
expect(emojify('<p>💕 <a class="hashtag" href="https://example.com/tags/foo" rel="nofollow noopener noreferrer" target="_blank">#<span>foo</span></a> test: foo.</p>'))
|
||||||
|
.toEqual('<p><img draggable="false" class="emojione" alt="💕" title=":two_hearts:" src="/emoji/1f495.svg"> <a class="hashtag" href="https://example.com/tags/foo" rel="nofollow noopener noreferrer" target="_blank">#<span>foo</span></a> test: foo.</p>');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,8 +19,6 @@ const emojiFilename = (filename) => {
|
||||||
return borderedEmoji.includes(filename) ? (filename + '_border') : filename;
|
return borderedEmoji.includes(filename) ? (filename + '_border') : filename;
|
||||||
};
|
};
|
||||||
|
|
||||||
const domParser = new DOMParser();
|
|
||||||
|
|
||||||
const emojifyTextNode = (node, customEmojis) => {
|
const emojifyTextNode = (node, customEmojis) => {
|
||||||
let str = node.textContent;
|
let str = node.textContent;
|
||||||
|
|
||||||
|
@ -39,7 +37,7 @@ const emojifyTextNode = (node, customEmojis) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let rend, replacement = '';
|
let rend, replacement = null;
|
||||||
if (i === str.length) {
|
if (i === str.length) {
|
||||||
break;
|
break;
|
||||||
} else if (str[i] === ':') {
|
} else if (str[i] === ':') {
|
||||||
|
@ -51,7 +49,14 @@ const emojifyTextNode = (node, customEmojis) => {
|
||||||
// if you want additional emoji handler, add statements below which set replacement and return true.
|
// if you want additional emoji handler, add statements below which set replacement and return true.
|
||||||
if (shortname in customEmojis) {
|
if (shortname in customEmojis) {
|
||||||
const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url;
|
const filename = autoPlayGif ? customEmojis[shortname].url : customEmojis[shortname].static_url;
|
||||||
replacement = `<img draggable="false" class="emojione custom-emoji" alt="${shortname}" title="${shortname}" src="${filename}" data-original="${customEmojis[shortname].url}" data-static="${customEmojis[shortname].static_url}" />`;
|
replacement = document.createElement('img');
|
||||||
|
replacement.setAttribute('draggable', false);
|
||||||
|
replacement.setAttribute('class', 'emojione custom-emoji');
|
||||||
|
replacement.setAttribute('alt', shortname);
|
||||||
|
replacement.setAttribute('title', shortname);
|
||||||
|
replacement.setAttribute('src', filename);
|
||||||
|
replacement.setAttribute('data-original', customEmojis[shortname].url);
|
||||||
|
replacement.setAttribute('data-static', customEmojis[shortname].static_url);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -59,7 +64,12 @@ const emojifyTextNode = (node, customEmojis) => {
|
||||||
} else { // matched to unicode emoji
|
} else { // matched to unicode emoji
|
||||||
const { filename, shortCode } = unicodeMapping[match];
|
const { filename, shortCode } = unicodeMapping[match];
|
||||||
const title = shortCode ? `:${shortCode}:` : '';
|
const title = shortCode ? `:${shortCode}:` : '';
|
||||||
replacement = `<img draggable="false" class="emojione" alt="${match}" title="${title}" src="${assetHost}/emoji/${emojiFilename(filename)}.svg" />`;
|
replacement = document.createElement('img');
|
||||||
|
replacement.setAttribute('draggable', false);
|
||||||
|
replacement.setAttribute('class', 'emojione');
|
||||||
|
replacement.setAttribute('alt', match);
|
||||||
|
replacement.setAttribute('title', title);
|
||||||
|
replacement.setAttribute('src', `${assetHost}/emoji/${emojiFilename(filename)}.svg`);
|
||||||
rend = i + match.length;
|
rend = i + match.length;
|
||||||
// If the matched character was followed by VS15 (for selecting text presentation), skip it.
|
// If the matched character was followed by VS15 (for selecting text presentation), skip it.
|
||||||
if (str.codePointAt(rend) === 65038) {
|
if (str.codePointAt(rend) === 65038) {
|
||||||
|
@ -69,9 +79,8 @@ const emojifyTextNode = (node, customEmojis) => {
|
||||||
|
|
||||||
fragment.append(document.createTextNode(str.slice(0, i)));
|
fragment.append(document.createTextNode(str.slice(0, i)));
|
||||||
if (replacement) {
|
if (replacement) {
|
||||||
fragment.append(domParser.parseFromString(replacement, 'text/html').documentElement.getElementsByTagName('img')[0]);
|
fragment.append(replacement);
|
||||||
}
|
}
|
||||||
node.textContent = str.slice(0, i);
|
|
||||||
str = str.slice(rend);
|
str = str.slice(rend);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,16 @@ const mapStateToProps = state => ({
|
||||||
isSearching: state.getIn(['search', 'submitted']) || !showTrends,
|
isSearching: state.getIn(['search', 'submitted']) || !showTrends,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Fix strange bug on Safari where <span> (rendered by FormattedMessage) disappears
|
||||||
|
// after clicking around Explore top bar (issue #20885).
|
||||||
|
// Removing width=100% from <a> also fixes it, as well as replacing <span> with <div>
|
||||||
|
// We're choosing to wrap span with div to keep the changes local only to this tool bar.
|
||||||
|
const WrapFormattedMessage = ({ children, ...props }) => <div><FormattedMessage {...props}>{children}</FormattedMessage></div>;
|
||||||
|
WrapFormattedMessage.propTypes = {
|
||||||
|
children: PropTypes.any,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
export default @connect(mapStateToProps)
|
||||||
@injectIntl
|
@injectIntl
|
||||||
class Explore extends React.PureComponent {
|
class Explore extends React.PureComponent {
|
||||||
|
@ -70,10 +80,10 @@ class Explore extends React.PureComponent {
|
||||||
) : (
|
) : (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<div className='account__section-headline'>
|
<div className='account__section-headline'>
|
||||||
<NavLink exact to='/explore'><FormattedMessage id='explore.trending_statuses' defaultMessage='Posts' /></NavLink>
|
<NavLink exact to='/explore'><WrapFormattedMessage id='explore.trending_statuses' defaultMessage='Posts' /></NavLink>
|
||||||
<NavLink exact to='/explore/tags'><FormattedMessage id='explore.trending_tags' defaultMessage='Hashtags' /></NavLink>
|
<NavLink exact to='/explore/tags'><WrapFormattedMessage id='explore.trending_tags' defaultMessage='Hashtags' /></NavLink>
|
||||||
<NavLink exact to='/explore/links'><FormattedMessage id='explore.trending_links' defaultMessage='News' /></NavLink>
|
<NavLink exact to='/explore/links'><WrapFormattedMessage id='explore.trending_links' defaultMessage='News' /></NavLink>
|
||||||
{signedIn && <NavLink exact to='/explore/suggestions'><FormattedMessage id='explore.suggested_follows' defaultMessage='For you' /></NavLink>}
|
{signedIn && <NavLink exact to='/explore/suggestions'><WrapFormattedMessage id='explore.suggested_follows' defaultMessage='For you' /></NavLink>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Switch>
|
<Switch>
|
||||||
|
|
|
@ -35,7 +35,7 @@ class Header extends React.PureComponent {
|
||||||
if (signedIn) {
|
if (signedIn) {
|
||||||
content = (
|
content = (
|
||||||
<>
|
<>
|
||||||
{location.pathname !== '/publish' && <Link to='/publish' className='button'><FormattedMessage id='compose_form.publish' defaultMessage='Publish' /></Link>}
|
{location.pathname !== '/publish' && <Link to='/publish' className='button'><FormattedMessage id='compose_form.publish_form' defaultMessage='Publish' /></Link>}
|
||||||
<Account />
|
<Account />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -3989,7 +3989,7 @@
|
||||||
"descriptors": [
|
"descriptors": [
|
||||||
{
|
{
|
||||||
"defaultMessage": "Publish",
|
"defaultMessage": "Publish",
|
||||||
"id": "compose_form.publish"
|
"id": "compose_form.publish_form"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"defaultMessage": "Sign in",
|
"defaultMessage": "Sign in",
|
||||||
|
|
|
@ -115,6 +115,10 @@ class Form::AccountBatch
|
||||||
authorize(account, :suspend?)
|
authorize(account, :suspend?)
|
||||||
log_action(:suspend, account)
|
log_action(:suspend, account)
|
||||||
account.suspend!(origin: :local)
|
account.suspend!(origin: :local)
|
||||||
|
account.strikes.create!(
|
||||||
|
account: current_account,
|
||||||
|
action: :suspend
|
||||||
|
)
|
||||||
Admin::SuspensionWorker.perform_async(account.id)
|
Admin::SuspensionWorker.perform_async(account.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -237,6 +237,11 @@ class User < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def functional?
|
def functional?
|
||||||
|
|
||||||
|
functional_or_moved?
|
||||||
|
end
|
||||||
|
|
||||||
|
def functional_or_moved?
|
||||||
confirmed? && approved? && !disabled? && !account.suspended? && !account.memorial?
|
confirmed? && approved? && !disabled? && !account.suspended? && !account.memorial?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,10 @@ class EmailDomainBlockPolicy < ApplicationPolicy
|
||||||
role.can?(:manage_blocks)
|
role.can?(:manage_blocks)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def show?
|
||||||
|
role.can?(:manage_blocks)
|
||||||
|
end
|
||||||
|
|
||||||
def create?
|
def create?
|
||||||
role.can?(:manage_blocks)
|
role.can?(:manage_blocks)
|
||||||
end
|
end
|
||||||
|
|
|
@ -35,6 +35,7 @@ class ManifestSerializer < ActiveModel::Serializer
|
||||||
src: full_pack_url("media/icons/android-chrome-#{size}x#{size}.png"),
|
src: full_pack_url("media/icons/android-chrome-#{size}x#{size}.png"),
|
||||||
sizes: "#{size}x#{size}",
|
sizes: "#{size}x#{size}",
|
||||||
type: 'image/png',
|
type: 'image/png',
|
||||||
|
purpose: 'any maskable',
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -28,7 +28,7 @@ class VerifyLinkService < BaseService
|
||||||
|
|
||||||
links = Nokogiri::HTML(@body).xpath('//a[contains(concat(" ", normalize-space(@rel), " "), " me ")]|//link[contains(concat(" ", normalize-space(@rel), " "), " me ")]')
|
links = Nokogiri::HTML(@body).xpath('//a[contains(concat(" ", normalize-space(@rel), " "), " me ")]|//link[contains(concat(" ", normalize-space(@rel), " "), " me ")]')
|
||||||
|
|
||||||
if links.any? { |link| link['href'].downcase == @link_back.downcase }
|
if links.any? { |link| link['href']&.downcase == @link_back.downcase }
|
||||||
true
|
true
|
||||||
elsif links.empty?
|
elsif links.empty?
|
||||||
false
|
false
|
||||||
|
@ -38,6 +38,8 @@ class VerifyLinkService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def link_redirects_back?(test_url)
|
def link_redirects_back?(test_url)
|
||||||
|
return false if test_url.blank?
|
||||||
|
|
||||||
redirect_to_url = Request.new(:head, test_url, follow: false).perform do |res|
|
redirect_to_url = Request.new(:head, test_url, follow: false).perform do |res|
|
||||||
res.headers['Location']
|
res.headers['Location']
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,4 +13,4 @@
|
||||||
%strong.emojify.p-name= display_name(account, custom_emojify: true)
|
%strong.emojify.p-name= display_name(account, custom_emojify: true)
|
||||||
%span
|
%span
|
||||||
= acct(account)
|
= acct(account)
|
||||||
= fa_icon('lock', { :data => ({hidden: true} unless account.locked?)})
|
= fa_icon('lock', { data: ({hidden: true} unless account.locked?)})
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
= f.input :return_to, as: :hidden
|
= f.input :return_to, as: :hidden
|
||||||
|
|
||||||
.field-group
|
.field-group
|
||||||
= f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'current-password', :autofocus => true }, label: t('challenge.prompt'), required: true
|
= f.input :current_password, wrapper: :with_block_label, input_html: { autocomplete: 'current-password', autofocus: true }, label: t('challenge.prompt'), required: true
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.button :button, t('challenge.confirm'), type: :submit
|
= f.button :button, t('challenge.confirm'), type: :submit
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
= render 'shared/error_messages', object: resource
|
= render 'shared/error_messages', object: resource
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :email, autofocus: true, wrapper: :with_label, label: t('simple_form.labels.defaults.email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, hint: false
|
= f.input :email, autofocus: true, wrapper: :with_label, label: t('simple_form.labels.defaults.email'), input_html: { 'aria-label': t('simple_form.labels.defaults.email') }, hint: false
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.button :button, t('auth.resend_confirmation'), type: :submit
|
= f.button :button, t('auth.resend_confirmation'), type: :submit
|
||||||
|
|
|
@ -8,9 +8,9 @@
|
||||||
= f.input :reset_password_token, as: :hidden
|
= f.input :reset_password_token, as: :hidden
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :password, wrapper: :with_label, autofocus: true, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'new-password', :minlength => User.password_length.first, :maxlength => User.password_length.last }, required: true
|
= f.input :password, wrapper: :with_label, autofocus: true, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label': t('simple_form.labels.defaults.new_password'), autocomplete: 'new-password', minlength: User.password_length.first, maxlength: User.password_length.last }, required: true
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'new-password' }, required: true
|
= f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label': t('simple_form.labels.defaults.confirm_new_password'), autocomplete: 'new-password' }, required: true
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.button :button, t('auth.set_new_password'), type: :submit
|
= f.button :button, t('auth.set_new_password'), type: :submit
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
= render 'shared/error_messages', object: resource
|
= render 'shared/error_messages', object: resource
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :email, autofocus: true, wrapper: :with_label, label: t('simple_form.labels.defaults.email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, hint: false
|
= f.input :email, autofocus: true, wrapper: :with_label, label: t('simple_form.labels.defaults.email'), input_html: { 'aria-label': t('simple_form.labels.defaults.email') }, hint: false
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.button :button, t('auth.reset_password'), type: :submit
|
= f.button :button, t('auth.reset_password'), type: :submit
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
%tr
|
%tr
|
||||||
%td
|
%td
|
||||||
%span{ title: session.user_agent }<
|
%span{ title: session.user_agent }<
|
||||||
= fa_icon "#{session_device_icon(session)} fw", 'aria-label' => session_device_icon(session)
|
= fa_icon "#{session_device_icon(session)} fw", 'aria-label': session_device_icon(session)
|
||||||
= ' '
|
= ' '
|
||||||
= t 'sessions.description', browser: t("sessions.browsers.#{session.browser}", default: "#{session.browser}"), platform: t("sessions.platforms.#{session.platform}", default: "#{session.platform}")
|
= t 'sessions.description', browser: t("sessions.browsers.#{session.browser}", default: "#{session.browser}"), platform: t("sessions.platforms.#{session.platform}", default: "#{session.platform}")
|
||||||
%td
|
%td
|
||||||
|
|
|
@ -11,15 +11,15 @@
|
||||||
- if !use_seamless_external_login? || resource.encrypted_password.present?
|
- if !use_seamless_external_login? || resource.encrypted_password.present?
|
||||||
.fields-row
|
.fields-row
|
||||||
.fields-row__column.fields-group.fields-row__column-6
|
.fields-row__column.fields-group.fields-row__column-6
|
||||||
= f.input :email, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, required: true, disabled: current_account.suspended?
|
= f.input :email, wrapper: :with_label, input_html: { 'aria-label': t('simple_form.labels.defaults.email') }, required: true, disabled: current_account.suspended?
|
||||||
.fields-row__column.fields-group.fields-row__column-6
|
.fields-row__column.fields-group.fields-row__column-6
|
||||||
= f.input :current_password, wrapper: :with_label, input_html: { 'aria-label' => t('simple_form.labels.defaults.current_password'), :autocomplete => 'current-password' }, required: true, disabled: current_account.suspended?, hint: false
|
= f.input :current_password, wrapper: :with_label, input_html: { 'aria-label': t('simple_form.labels.defaults.current_password'), autocomplete: 'current-password' }, required: true, disabled: current_account.suspended?, hint: false
|
||||||
|
|
||||||
.fields-row
|
.fields-row
|
||||||
.fields-row__column.fields-group.fields-row__column-6
|
.fields-row__column.fields-group.fields-row__column-6
|
||||||
= f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.new_password'), :autocomplete => 'new-password', :minlength => User.password_length.first, :maxlength => User.password_length.last }, hint: t('simple_form.hints.defaults.password'), disabled: current_account.suspended?
|
= f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.new_password'), input_html: { 'aria-label': t('simple_form.labels.defaults.new_password'), autocomplete: 'new-password', minlength: User.password_length.first, maxlength: User.password_length.last }, hint: t('simple_form.hints.defaults.password'), disabled: current_account.suspended?
|
||||||
.fields-row__column.fields-group.fields-row__column-6
|
.fields-row__column.fields-group.fields-row__column-6
|
||||||
= f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_new_password'), :autocomplete => 'new-password' }, disabled: current_account.suspended?
|
= f.input :password_confirmation, wrapper: :with_label, label: t('simple_form.labels.defaults.confirm_new_password'), input_html: { 'aria-label': t('simple_form.labels.defaults.confirm_new_password'), autocomplete: 'new-password' }, disabled: current_account.suspended?
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.button :button, t('generic.save_changes'), type: :submit, class: 'button', disabled: current_account.suspended?
|
= f.button :button, t('generic.save_changes'), type: :submit, class: 'button', disabled: current_account.suspended?
|
||||||
|
|
|
@ -17,13 +17,13 @@
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.simple_fields_for :account do |ff|
|
= f.simple_fields_for :account do |ff|
|
||||||
= ff.input :display_name, wrapper: :with_label, label: false, required: false, input_html: { 'aria-label' => t('simple_form.labels.defaults.display_name'), :autocomplete => 'off', placeholder: t('simple_form.labels.defaults.display_name') }
|
= ff.input :display_name, wrapper: :with_label, label: false, required: false, input_html: { 'aria-label': t('simple_form.labels.defaults.display_name'), autocomplete: 'off', placeholder: t('simple_form.labels.defaults.display_name') }
|
||||||
= ff.input :username, wrapper: :with_label, label: false, required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.username'), :autocomplete => 'off', placeholder: t('simple_form.labels.defaults.username'), pattern: '[a-zA-Z0-9_]+', maxlength: 30 }, append: "@#{site_hostname}", hint: false
|
= ff.input :username, wrapper: :with_label, label: false, required: true, input_html: { 'aria-label': t('simple_form.labels.defaults.username'), autocomplete: 'off', placeholder: t('simple_form.labels.defaults.username'), pattern: '[a-zA-Z0-9_]+', maxlength: 30 }, append: "@#{site_hostname}", hint: false
|
||||||
= f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'username' }, hint: false
|
= f.input :email, placeholder: t('simple_form.labels.defaults.email'), required: true, input_html: { 'aria-label': t('simple_form.labels.defaults.email'), autocomplete: 'username' }, hint: false
|
||||||
= f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'new-password', :minlength => User.password_length.first, :maxlength => User.password_length.last }, hint: false
|
= f.input :password, placeholder: t('simple_form.labels.defaults.password'), required: true, input_html: { 'aria-label': t('simple_form.labels.defaults.password'), autocomplete: 'new-password', minlength: User.password_length.first, maxlength: User.password_length.last }, hint: false
|
||||||
= f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label' => t('simple_form.labels.defaults.confirm_password'), :autocomplete => 'new-password' }, hint: false
|
= f.input :password_confirmation, placeholder: t('simple_form.labels.defaults.confirm_password'), required: true, input_html: { 'aria-label': t('simple_form.labels.defaults.confirm_password'), autocomplete: 'new-password' }, hint: false
|
||||||
= f.input :confirm_password, as: :string, placeholder: t('simple_form.labels.defaults.honeypot', label: t('simple_form.labels.defaults.password')), required: false, input_html: { 'aria-label' => t('simple_form.labels.defaults.honeypot', label: t('simple_form.labels.defaults.password')), :autocomplete => 'off' }, hint: false
|
= f.input :confirm_password, as: :string, placeholder: t('simple_form.labels.defaults.honeypot', label: t('simple_form.labels.defaults.password')), required: false, input_html: { 'aria-label': t('simple_form.labels.defaults.honeypot', label: t('simple_form.labels.defaults.password')), autocomplete: 'off' }, hint: false
|
||||||
= f.input :website, as: :url, wrapper: :with_label, label: t('simple_form.labels.defaults.honeypot', label: 'Website'), required: false, input_html: { 'aria-label' => t('simple_form.labels.defaults.honeypot', label: 'Website'), :autocomplete => 'off' }
|
= f.input :website, as: :url, wrapper: :with_label, label: t('simple_form.labels.defaults.honeypot', label: 'Website'), required: false, input_html: { 'aria-label': t('simple_form.labels.defaults.honeypot', label: 'Website'), autocomplete: 'off' }
|
||||||
|
|
||||||
- if approved_registrations? && !@invite.present?
|
- if approved_registrations? && !@invite.present?
|
||||||
.fields-group
|
.fields-group
|
||||||
|
|
|
@ -8,11 +8,11 @@
|
||||||
= simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
|
= simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
|
||||||
.fields-group
|
.fields-group
|
||||||
- if use_seamless_external_login?
|
- if use_seamless_external_login?
|
||||||
= f.input :email, autofocus: true, wrapper: :with_label, label: t('simple_form.labels.defaults.username_or_email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.username_or_email') }, hint: false
|
= f.input :email, autofocus: true, wrapper: :with_label, label: t('simple_form.labels.defaults.username_or_email'), input_html: { 'aria-label': t('simple_form.labels.defaults.username_or_email') }, hint: false
|
||||||
- else
|
- else
|
||||||
= f.input :email, autofocus: true, wrapper: :with_label, label: t('simple_form.labels.defaults.email'), input_html: { 'aria-label' => t('simple_form.labels.defaults.email') }, hint: false
|
= f.input :email, autofocus: true, wrapper: :with_label, label: t('simple_form.labels.defaults.email'), input_html: { 'aria-label': t('simple_form.labels.defaults.email') }, hint: false
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.password'), input_html: { 'aria-label' => t('simple_form.labels.defaults.password'), :autocomplete => 'current-password' }, hint: false
|
= f.input :password, wrapper: :with_label, label: t('simple_form.labels.defaults.password'), input_html: { 'aria-label': t('simple_form.labels.defaults.password'), autocomplete: 'current-password' }, hint: false
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.button :button, t('auth.login'), type: :submit
|
= f.button :button, t('auth.login'), type: :submit
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
%p.hint.authentication-hint= t('simple_form.hints.sessions.otp')
|
%p.hint.authentication-hint= t('simple_form.hints.sessions.otp')
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :otp_attempt, type: :number, wrapper: :with_label, label: t('simple_form.labels.defaults.otp_attempt'), input_html: { 'aria-label' => t('simple_form.labels.defaults.otp_attempt'), :autocomplete => 'one-time-code' }, autofocus: true
|
= f.input :otp_attempt, type: :number, wrapper: :with_label, label: t('simple_form.labels.defaults.otp_attempt'), input_html: { 'aria-label': t('simple_form.labels.defaults.otp_attempt'), autocomplete: 'one-time-code' }, autofocus: true
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.button :button, t('auth.login'), type: :submit
|
= f.button :button, t('auth.login'), type: :submit
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
%p.hint= t('auth.setup.email_below_hint_html')
|
%p.hint= t('auth.setup.email_below_hint_html')
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :email, required: true, hint: false, input_html: { 'aria-label' => t('simple_form.labels.defaults.email'), :autocomplete => 'off' }
|
= f.input :email, required: true, hint: false, input_html: { 'aria-label': t('simple_form.labels.defaults.email'), autocomplete: 'off' }
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.submit t('admin.accounts.change_email.label'), class: 'button'
|
= f.submit t('admin.accounts.change_email.label'), class: 'button'
|
||||||
|
|
|
@ -21,9 +21,9 @@
|
||||||
%hr.spacer/
|
%hr.spacer/
|
||||||
|
|
||||||
- if current_user.encrypted_password.present?
|
- if current_user.encrypted_password.present?
|
||||||
= f.input :password, wrapper: :with_block_label, input_html: { :autocomplete => 'current-password' }, hint: t('deletes.confirm_password')
|
= f.input :password, wrapper: :with_block_label, input_html: { autocomplete: 'current-password' }, hint: t('deletes.confirm_password')
|
||||||
- else
|
- else
|
||||||
= f.input :username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, hint: t('deletes.confirm_username')
|
= f.input :username, wrapper: :with_block_label, input_html: { autocomplete: 'off' }, hint: t('deletes.confirm_username')
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.button :button, t('deletes.proceed'), type: :submit, class: 'negative'
|
= f.button :button, t('deletes.proceed'), type: :submit, class: 'negative'
|
||||||
|
|
|
@ -19,9 +19,9 @@
|
||||||
|
|
||||||
.fields-row__column.fields-group.fields-row__column-6
|
.fields-row__column.fields-group.fields-row__column-6
|
||||||
- if current_user.encrypted_password.present?
|
- if current_user.encrypted_password.present?
|
||||||
= f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'current-password' }, required: true
|
= f.input :current_password, wrapper: :with_block_label, input_html: { autocomplete: 'current-password' }, required: true
|
||||||
- else
|
- else
|
||||||
= f.input :current_username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true
|
= f.input :current_username, wrapper: :with_block_label, input_html: { autocomplete: 'off' }, required: true
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.button :button, t('migrations.set_redirect'), type: :submit, class: 'button button--destructive'
|
= f.button :button, t('migrations.set_redirect'), type: :submit, class: 'button button--destructive'
|
||||||
|
|
|
@ -48,9 +48,9 @@
|
||||||
|
|
||||||
.fields-row__column.fields-group.fields-row__column-6
|
.fields-row__column.fields-group.fields-row__column-6
|
||||||
- if current_user.encrypted_password.present?
|
- if current_user.encrypted_password.present?
|
||||||
= f.input :current_password, wrapper: :with_block_label, input_html: { :autocomplete => 'current-password' }, required: true, disabled: on_cooldown?
|
= f.input :current_password, wrapper: :with_block_label, input_html: { autocomplete: 'current-password' }, required: true, disabled: on_cooldown?
|
||||||
- else
|
- else
|
||||||
= f.input :current_username, wrapper: :with_block_label, input_html: { :autocomplete => 'off' }, required: true, disabled: on_cooldown?
|
= f.input :current_username, wrapper: :with_block_label, input_html: { autocomplete: 'off' }, required: true, disabled: on_cooldown?
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.button :button, t('migrations.proceed_with_move'), type: :submit, class: 'button button--destructive', disabled: on_cooldown?
|
= f.button :button, t('migrations.proceed_with_move'), type: :submit, class: 'button button--destructive', disabled: on_cooldown?
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
%samp.qr-alternative__code= @new_otp_secret.scan(/.{4}/).join(' ')
|
%samp.qr-alternative__code= @new_otp_secret.scan(/.{4}/).join(' ')
|
||||||
|
|
||||||
.fields-group
|
.fields-group
|
||||||
= f.input :otp_attempt, wrapper: :with_label, hint: t('otp_authentication.code_hint'), label: t('simple_form.labels.defaults.otp_attempt'), input_html: { :autocomplete => 'off' }, required: true
|
= f.input :otp_attempt, wrapper: :with_label, hint: t('otp_authentication.code_hint'), label: t('simple_form.labels.defaults.otp_attempt'), input_html: { autocomplete: 'off' }, required: true
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.button :button, t('otp_authentication.enable'), type: :submit
|
= f.button :button, t('otp_authentication.enable'), type: :submit
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
%p.hint= t('webauthn_credentials.description_html')
|
%p.hint= t('webauthn_credentials.description_html')
|
||||||
|
|
||||||
.fields_group
|
.fields_group
|
||||||
= f.input :nickname, wrapper: :with_block_label, hint: t('webauthn_credentials.nickname_hint'), input_html: { :autocomplete => 'off' }, required: true
|
= f.input :nickname, wrapper: :with_block_label, hint: t('webauthn_credentials.nickname_hint'), input_html: { autocomplete: 'off' }, required: true
|
||||||
|
|
||||||
.actions
|
.actions
|
||||||
= f.button :button, t('webauthn_credentials.add'), class: 'js-webauthn', type: :submit
|
= f.button :button, t('webauthn_credentials.add'), class: 'js-webauthn', type: :submit
|
||||||
|
|
|
@ -15,12 +15,12 @@
|
||||||
|
|
||||||
= account_action_button(status.account)
|
= account_action_button(status.account)
|
||||||
|
|
||||||
.status__content.emojify{ :data => ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }<
|
.status__content.emojify{ data: ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }<
|
||||||
- if status.spoiler_text?
|
- if status.spoiler_text?
|
||||||
%p<
|
%p<
|
||||||
%span.p-summary> #{prerender_custom_emojis(h(status.spoiler_text), status.emojis)}
|
%span.p-summary> #{prerender_custom_emojis(h(status.spoiler_text), status.emojis)}
|
||||||
%button.status__content__spoiler-link= t('statuses.show_more')
|
%button.status__content__spoiler-link= t('statuses.show_more')
|
||||||
.e-content{ :lang => status.language }
|
.e-content{ lang: status.language }
|
||||||
= prerender_custom_emojis(status_content_format(status), status.emojis)
|
= prerender_custom_emojis(status_content_format(status), status.emojis)
|
||||||
|
|
||||||
- if status.preloadable_poll
|
- if status.preloadable_poll
|
||||||
|
|
|
@ -27,12 +27,12 @@
|
||||||
%span.display-name__account
|
%span.display-name__account
|
||||||
= acct(status.account)
|
= acct(status.account)
|
||||||
= fa_icon('lock') if status.account.locked?
|
= fa_icon('lock') if status.account.locked?
|
||||||
.status__content.emojify{ :data => ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }<
|
.status__content.emojify{ data: ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }<
|
||||||
- if status.spoiler_text?
|
- if status.spoiler_text?
|
||||||
%p<
|
%p<
|
||||||
%span.p-summary> #{prerender_custom_emojis(h(status.spoiler_text), status.emojis)}
|
%span.p-summary> #{prerender_custom_emojis(h(status.spoiler_text), status.emojis)}
|
||||||
%button.status__content__spoiler-link= t('statuses.show_more')
|
%button.status__content__spoiler-link= t('statuses.show_more')
|
||||||
.e-content{ :lang => status.language }<
|
.e-content{ lang: status.language }<
|
||||||
= prerender_custom_emojis(status_content_format(status), status.emojis)
|
= prerender_custom_emojis(status_content_format(status), status.emojis)
|
||||||
|
|
||||||
- if status.preloadable_poll
|
- if status.preloadable_poll
|
||||||
|
|
|
@ -15,12 +15,12 @@ type: application
|
||||||
# This is the chart version. This version number should be incremented each time you make changes
|
# This is the chart version. This version number should be incremented each time you make changes
|
||||||
# to the chart and its templates, including the app version.
|
# to the chart and its templates, including the app version.
|
||||||
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
# Versions are expected to follow Semantic Versioning (https://semver.org/)
|
||||||
version: 2.3.0
|
version: 3.0.0
|
||||||
|
|
||||||
# This is the version number of the application being deployed. This version number should be
|
# This is the version number of the application being deployed. This version number should be
|
||||||
# incremented each time you make changes to the application. Versions are not expected to
|
# incremented each time you make changes to the application. Versions are not expected to
|
||||||
# follow Semantic Versioning. They should reflect the version the application is using.
|
# follow Semantic Versioning. They should reflect the version the application is using.
|
||||||
appVersion: v3.5.3
|
appVersion: v4.0.2
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
- name: elasticsearch
|
- name: elasticsearch
|
||||||
|
|
|
@ -116,18 +116,18 @@ Rails.application.configure do
|
||||||
end
|
end
|
||||||
|
|
||||||
config.action_mailer.smtp_settings = {
|
config.action_mailer.smtp_settings = {
|
||||||
:port => ENV['SMTP_PORT'],
|
port: ENV['SMTP_PORT'],
|
||||||
:address => ENV['SMTP_SERVER'],
|
address: ENV['SMTP_SERVER'],
|
||||||
:user_name => ENV['SMTP_LOGIN'].presence,
|
user_name: ENV['SMTP_LOGIN'].presence,
|
||||||
:password => ENV['SMTP_PASSWORD'].presence,
|
password: ENV['SMTP_PASSWORD'].presence,
|
||||||
:domain => ENV['SMTP_DOMAIN'] || ENV['LOCAL_DOMAIN'],
|
domain: ENV['SMTP_DOMAIN'] || ENV['LOCAL_DOMAIN'],
|
||||||
:authentication => ENV['SMTP_AUTH_METHOD'] == 'none' ? nil : ENV['SMTP_AUTH_METHOD'] || :plain,
|
authentication: ENV['SMTP_AUTH_METHOD'] == 'none' ? nil : ENV['SMTP_AUTH_METHOD'] || :plain,
|
||||||
:ca_file => ENV['SMTP_CA_FILE'].presence || '/etc/ssl/certs/ca-certificates.crt',
|
ca_file: ENV['SMTP_CA_FILE'].presence || '/etc/ssl/certs/ca-certificates.crt',
|
||||||
:openssl_verify_mode => ENV['SMTP_OPENSSL_VERIFY_MODE'],
|
openssl_verify_mode: ENV['SMTP_OPENSSL_VERIFY_MODE'],
|
||||||
:enable_starttls => enable_starttls,
|
enable_starttls: enable_starttls,
|
||||||
:enable_starttls_auto => enable_starttls_auto,
|
enable_starttls_auto: enable_starttls_auto,
|
||||||
:tls => ENV['SMTP_TLS'].presence && ENV['SMTP_TLS'] == 'true',
|
tls: ENV['SMTP_TLS'].presence && ENV['SMTP_TLS'] == 'true',
|
||||||
:ssl => ENV['SMTP_SSL'].presence && ENV['SMTP_SSL'] == 'true',
|
ssl: ENV['SMTP_SSL'].presence && ENV['SMTP_SSL'] == 'true',
|
||||||
}
|
}
|
||||||
|
|
||||||
config.action_mailer.delivery_method = ENV.fetch('SMTP_DELIVERY_METHOD', 'smtp').to_sym
|
config.action_mailer.delivery_method = ENV.fetch('SMTP_DELIVERY_METHOD', 'smtp').to_sym
|
||||||
|
|
|
@ -98,9 +98,19 @@ Doorkeeper.configure do
|
||||||
:'admin:read',
|
:'admin:read',
|
||||||
:'admin:read:accounts',
|
:'admin:read:accounts',
|
||||||
:'admin:read:reports',
|
:'admin:read:reports',
|
||||||
|
:'admin:read:domain_allows',
|
||||||
|
:'admin:read:domain_blocks',
|
||||||
|
:'admin:read:ip_blocks',
|
||||||
|
:'admin:read:email_domain_blocks',
|
||||||
|
:'admin:read:canonical_email_blocks',
|
||||||
:'admin:write',
|
:'admin:write',
|
||||||
:'admin:write:accounts',
|
:'admin:write:accounts',
|
||||||
:'admin:write:reports',
|
:'admin:write:reports',
|
||||||
|
:'admin:write:domain_allows',
|
||||||
|
:'admin:write:domain_blocks',
|
||||||
|
:'admin:write:ip_blocks',
|
||||||
|
:'admin:write:email_domain_blocks',
|
||||||
|
:'admin:write:canonical_email_blocks',
|
||||||
:crypto
|
:crypto
|
||||||
|
|
||||||
# Change the way client credentials are retrieved from the request object.
|
# Change the way client credentials are retrieved from the request object.
|
||||||
|
|
|
@ -4,26 +4,6 @@ en:
|
||||||
custom_emojis:
|
custom_emojis:
|
||||||
batch_copy_error: 'An error occurred when copying some of the selected emoji: %{message}'
|
batch_copy_error: 'An error occurred when copying some of the selected emoji: %{message}'
|
||||||
batch_error: 'An error occurred: %{message}'
|
batch_error: 'An error occurred: %{message}'
|
||||||
domain_allows:
|
|
||||||
export: Export
|
|
||||||
import: Import
|
|
||||||
domain_blocks:
|
|
||||||
export: Export
|
|
||||||
import: Import
|
|
||||||
export_domain_allows:
|
|
||||||
new:
|
|
||||||
title: Import domain allows
|
|
||||||
no_file: No file selected
|
|
||||||
export_domain_blocks:
|
|
||||||
import:
|
|
||||||
description_html: You are about to import a list of domain blocks. Please review this list very carefully, especially if you have not authored this list yourself.
|
|
||||||
existing_relationships_warning: Existing follow relationships
|
|
||||||
private_comment_description_html: 'To help you track where imported blocks come from, imported blocks will be created with the following private comment: <q>%{comment}</q>'
|
|
||||||
private_comment_template: Imported from %{source} on %{date}
|
|
||||||
title: Import domain blocks
|
|
||||||
new:
|
|
||||||
title: Import domain blocks
|
|
||||||
no_file: No file selected
|
|
||||||
settings:
|
settings:
|
||||||
captcha_enabled:
|
captcha_enabled:
|
||||||
desc_html: This relies on external scripts from hCaptcha, which may be a security and privacy concern. In addition, <strong>this can make the registration process significantly less accessible to some (especially disabled) people</strong>. For these reasons, please consider alternative measures such as approval-based or invite-based registration.<br>Users that have been invited through a limited-use invite will not need to solve a CAPTCHA
|
desc_html: This relies on external scripts from hCaptcha, which may be a security and privacy concern. In addition, <strong>this can make the registration process significantly less accessible to some (especially disabled) people</strong>. For these reasons, please consider alternative measures such as approval-based or invite-based registration.<br>Users that have been invited through a limited-use invite will not need to solve a CAPTCHA
|
||||||
|
|
|
@ -4,26 +4,6 @@ ko:
|
||||||
custom_emojis:
|
custom_emojis:
|
||||||
batch_copy_error: '선택된 에모지를 복사하던 중 오류가 발생했습니다: %{message}'
|
batch_copy_error: '선택된 에모지를 복사하던 중 오류가 발생했습니다: %{message}'
|
||||||
batch_error: '에러가 발생했습니다: %{message}'
|
batch_error: '에러가 발생했습니다: %{message}'
|
||||||
domain_allows:
|
|
||||||
export: 내보내기
|
|
||||||
import: 불러오기
|
|
||||||
domain_blocks:
|
|
||||||
export: 내보내기
|
|
||||||
import: 불러오기
|
|
||||||
export_domain_allows:
|
|
||||||
new:
|
|
||||||
title: 도메인 허용 불러오기
|
|
||||||
no_file: 파일이 선택되지 않았습니다
|
|
||||||
export_domain_blocks:
|
|
||||||
import:
|
|
||||||
description_html: 도메인 차단 목록을 불러오려고 합니다. 이 목록을 조심스럽게 검토하세요, 특히 자신이 직접 작성한 목록이 아니라면 더 조심하세요.
|
|
||||||
existing_relationships_warning: 팔로우 관계가 존재합니다
|
|
||||||
private_comment_description_html: '어디서 불러온 것인지 추적을 원활하게 하기 위해서, 불러온 차단들은 다음과 같은 비공개 주석과 함께 생성될 것입니다: <q>%{comment}</q>'
|
|
||||||
private_comment_template: '%{date}에 %{source}에서 불러옴'
|
|
||||||
title: 도메인 차단 불러오기
|
|
||||||
new:
|
|
||||||
title: 도메인 차단 불러오기
|
|
||||||
no_file: 파일이 선택되지 않았습니다
|
|
||||||
settings:
|
settings:
|
||||||
captcha_enabled:
|
captcha_enabled:
|
||||||
desc_html: 이것은 hCaptcha의 외부 스크립트에 의존합니다, 이것은 개인정보 보호에 위협을 가할 수도 있습니다. 추가적으로, <strong>이것은 몇몇 사람들(특히나 장애인들)에게 가입 절차의 접근성을 심각하게 떨어트릴 수 있습니다</strong>. 이러한 이유로, 대체제로 승인 전용이나 초대제를 통한 가입을 고려해보세요.<br>한정된 사용만 가능한 초대장을 통한 가입자들은 CAPTCHA를 풀지 않아도 됩니다
|
desc_html: 이것은 hCaptcha의 외부 스크립트에 의존합니다, 이것은 개인정보 보호에 위협을 가할 수도 있습니다. 추가적으로, <strong>이것은 몇몇 사람들(특히나 장애인들)에게 가입 절차의 접근성을 심각하게 떨어트릴 수 있습니다</strong>. 이러한 이유로, 대체제로 승인 전용이나 초대제를 통한 가입을 고려해보세요.<br>한정된 사용만 가능한 초대장을 통한 가입자들은 CAPTCHA를 풀지 않아도 됩니다
|
||||||
|
|
|
@ -373,6 +373,8 @@ en:
|
||||||
add_new: Allow federation with domain
|
add_new: Allow federation with domain
|
||||||
created_msg: Domain has been successfully allowed for federation
|
created_msg: Domain has been successfully allowed for federation
|
||||||
destroyed_msg: Domain has been disallowed from federation
|
destroyed_msg: Domain has been disallowed from federation
|
||||||
|
export: Export
|
||||||
|
import: Import
|
||||||
undo: Disallow federation with domain
|
undo: Disallow federation with domain
|
||||||
domain_blocks:
|
domain_blocks:
|
||||||
add_new: Add new domain block
|
add_new: Add new domain block
|
||||||
|
@ -382,15 +384,19 @@ en:
|
||||||
edit: Edit domain block
|
edit: Edit domain block
|
||||||
existing_domain_block: You have already imposed stricter limits on %{name}.
|
existing_domain_block: You have already imposed stricter limits on %{name}.
|
||||||
existing_domain_block_html: You have already imposed stricter limits on %{name}, you need to <a href="%{unblock_url}">unblock it</a> first.
|
existing_domain_block_html: You have already imposed stricter limits on %{name}, you need to <a href="%{unblock_url}">unblock it</a> first.
|
||||||
|
export: Export
|
||||||
|
import: Import
|
||||||
new:
|
new:
|
||||||
create: Create block
|
create: Create block
|
||||||
hint: The domain block will not prevent creation of account entries in the database, but will retroactively and automatically apply specific moderation methods on those accounts.
|
hint: The domain block will not prevent creation of account entries in the database, but will retroactively and automatically apply specific moderation methods on those accounts.
|
||||||
severity:
|
severity:
|
||||||
desc_html: "<strong>Silence</strong> will make the account's posts invisible to anyone who isn't following them. <strong>Suspend</strong> will remove all of the account's content, media, and profile data. Use <strong>None</strong> if you just want to reject media files."
|
desc_html: "<strong>Limit</strong> will make posts from accounts at this domain invisible to anyone who isn't following them. <strong>Suspend</strong> will remove all content, media, and profile data for this domain's accounts from your server. Use <strong>None</strong> if you just want to reject media files."
|
||||||
noop: None
|
noop: None
|
||||||
silence: Silence
|
silence: Limit
|
||||||
suspend: Suspend
|
suspend: Suspend
|
||||||
title: New domain block
|
title: New domain block
|
||||||
|
no_domain_block_selected: No domain blocks were changed as none were selected
|
||||||
|
not_permitted: You are not permitted to perform this action
|
||||||
obfuscate: Obfuscate domain name
|
obfuscate: Obfuscate domain name
|
||||||
obfuscate_hint: Partially obfuscate the domain name in the list if advertising the list of domain limitations is enabled
|
obfuscate_hint: Partially obfuscate the domain name in the list if advertising the list of domain limitations is enabled
|
||||||
private_comment: Private comment
|
private_comment: Private comment
|
||||||
|
@ -422,6 +428,20 @@ en:
|
||||||
resolved_dns_records_hint_html: The domain name resolves to the following MX domains, which are ultimately responsible for accepting e-mail. Blocking an MX domain will block sign-ups from any e-mail address which uses the same MX domain, even if the visible domain name is different. <strong>Be careful not to block major e-mail providers.</strong>
|
resolved_dns_records_hint_html: The domain name resolves to the following MX domains, which are ultimately responsible for accepting e-mail. Blocking an MX domain will block sign-ups from any e-mail address which uses the same MX domain, even if the visible domain name is different. <strong>Be careful not to block major e-mail providers.</strong>
|
||||||
resolved_through_html: Resolved through %{domain}
|
resolved_through_html: Resolved through %{domain}
|
||||||
title: Blocked e-mail domains
|
title: Blocked e-mail domains
|
||||||
|
export_domain_allows:
|
||||||
|
new:
|
||||||
|
title: Import domain allows
|
||||||
|
no_file: No file selected
|
||||||
|
export_domain_blocks:
|
||||||
|
import:
|
||||||
|
description_html: You are about to import a list of domain blocks. Please review this list very carefully, especially if you have not authored this list yourself.
|
||||||
|
existing_relationships_warning: Existing follow relationships
|
||||||
|
private_comment_description_html: 'To help you track where imported blocks come from, imported blocks will be created with the following private comment: <q>%{comment}</q>'
|
||||||
|
private_comment_template: Imported from %{source} on %{date}
|
||||||
|
title: Import domain blocks
|
||||||
|
new:
|
||||||
|
title: Import domain blocks
|
||||||
|
no_file: No file selected
|
||||||
follow_recommendations:
|
follow_recommendations:
|
||||||
description_html: "<strong>Follow recommendations help new users quickly find interesting content</strong>. When a user has not interacted with others enough to form personalized follow recommendations, these accounts are recommended instead. They are re-calculated on a daily basis from a mix of accounts with the highest recent engagements and highest local follower counts for a given language."
|
description_html: "<strong>Follow recommendations help new users quickly find interesting content</strong>. When a user has not interacted with others enough to form personalized follow recommendations, these accounts are recommended instead. They are re-calculated on a daily basis from a mix of accounts with the highest recent engagements and highest local follower counts for a given language."
|
||||||
language: For language
|
language: For language
|
||||||
|
@ -914,7 +934,7 @@ en:
|
||||||
warning: Be very careful with this data. Never share it with anyone!
|
warning: Be very careful with this data. Never share it with anyone!
|
||||||
your_token: Your access token
|
your_token: Your access token
|
||||||
auth:
|
auth:
|
||||||
apply_for_account: Get on waitlist
|
apply_for_account: Request an account
|
||||||
change_password: Password
|
change_password: Password
|
||||||
delete_account: Delete account
|
delete_account: Delete account
|
||||||
delete_account_html: If you wish to delete your account, you can <a href="%{path}">proceed here</a>. You will be asked for confirmation.
|
delete_account_html: If you wish to delete your account, you can <a href="%{path}">proceed here</a>. You will be asked for confirmation.
|
||||||
|
|
|
@ -23,7 +23,7 @@ SimpleNavigation::Configuration.run do |navigation|
|
||||||
|
|
||||||
n.item :relationships, safe_join([fa_icon('users fw'), t('settings.relationships')]), relationships_path, if: -> { current_user.functional? }
|
n.item :relationships, safe_join([fa_icon('users fw'), t('settings.relationships')]), relationships_path, if: -> { current_user.functional? }
|
||||||
n.item :filters, safe_join([fa_icon('filter fw'), t('filters.index.title')]), filters_path, highlights_on: %r{/filters}, if: -> { current_user.functional? }
|
n.item :filters, safe_join([fa_icon('filter fw'), t('filters.index.title')]), filters_path, highlights_on: %r{/filters}, if: -> { current_user.functional? }
|
||||||
n.item :statuses_cleanup, safe_join([fa_icon('history fw'), t('settings.statuses_cleanup')]), statuses_cleanup_path, if: -> { current_user.functional? }
|
n.item :statuses_cleanup, safe_join([fa_icon('history fw'), t('settings.statuses_cleanup')]), statuses_cleanup_path, if: -> { current_user.functional_or_moved? }
|
||||||
|
|
||||||
n.item :security, safe_join([fa_icon('lock fw'), t('settings.account')]), edit_user_registration_path do |s|
|
n.item :security, safe_join([fa_icon('lock fw'), t('settings.account')]), edit_user_registration_path do |s|
|
||||||
s.item :password, safe_join([fa_icon('lock fw'), t('settings.account_settings')]), edit_user_registration_path, highlights_on: %r{/auth/edit|/settings/delete|/settings/migration|/settings/aliases|/settings/login_activities|^/disputes}
|
s.item :password, safe_join([fa_icon('lock fw'), t('settings.account_settings')]), edit_user_registration_path, highlights_on: %r{/auth/edit|/settings/delete|/settings/migration|/settings/aliases|/settings/login_activities|^/disputes}
|
||||||
|
|
|
@ -41,7 +41,7 @@ describe Admin::StatusesController do
|
||||||
|
|
||||||
describe 'POST #batch' do
|
describe 'POST #batch' do
|
||||||
before do
|
before do
|
||||||
post :batch, params: { :account_id => account.id, action => '', :admin_status_batch_action => { status_ids: status_ids } }
|
post :batch, params: { account_id: account.id, action => '', admin_status_batch_action: { status_ids: status_ids } }
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:status_ids) { [media_attached_status.id] }
|
let(:status_ids) { [media_attached_status.id] }
|
||||||
|
|
|
@ -23,7 +23,7 @@ RSpec.describe FavouriteService, type: :service do
|
||||||
let(:status) { Fabricate(:status, account: bob) }
|
let(:status) { Fabricate(:status, account: bob) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_request(:post, "http://example.com/inbox").to_return(:status => 200, :body => "", :headers => {})
|
stub_request(:post, "http://example.com/inbox").to_return(status: 200, body: "", headers: {})
|
||||||
subject.call(sender, status)
|
subject.call(sender, status)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -140,7 +140,7 @@ RSpec.describe FollowService, type: :service do
|
||||||
let(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox') }
|
let(:bob) { Fabricate(:account, username: 'bob', domain: 'example.com', protocol: :activitypub, inbox_url: 'http://example.com/inbox') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
stub_request(:post, "http://example.com/inbox").to_return(:status => 200, :body => "", :headers => {})
|
stub_request(:post, "http://example.com/inbox").to_return(status: 200, body: "", headers: {})
|
||||||
subject.call(sender, bob)
|
subject.call(sender, bob)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -76,7 +76,25 @@ RSpec.describe VerifyLinkService, type: :service do
|
||||||
context 'when a link does not contain a link back' do
|
context 'when a link does not contain a link back' do
|
||||||
let(:html) { '' }
|
let(:html) { '' }
|
||||||
|
|
||||||
it 'marks the field as verified' do
|
it 'does not mark the field as verified' do
|
||||||
|
expect(field.verified?).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when link has no `href` attribute' do
|
||||||
|
let(:html) do
|
||||||
|
<<-HTML
|
||||||
|
<!doctype html>
|
||||||
|
<head>
|
||||||
|
<link type="text/html" rel="me" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a rel="me" target="_blank">Follow me on Mastodon</a>
|
||||||
|
</body>
|
||||||
|
HTML
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not mark the field as verified' do
|
||||||
expect(field.verified?).to be false
|
expect(field.verified?).to be false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,7 +4,7 @@ require 'rails_helper'
|
||||||
|
|
||||||
describe 'statuses/show.html.haml', without_verify_partial_doubles: true do
|
describe 'statuses/show.html.haml', without_verify_partial_doubles: true do
|
||||||
before do
|
before do
|
||||||
double(:api_oembed_url => '')
|
double(api_oembed_url: '')
|
||||||
allow(view).to receive(:show_landing_strip?).and_return(true)
|
allow(view).to receive(:show_landing_strip?).and_return(true)
|
||||||
allow(view).to receive(:site_title).and_return('example site')
|
allow(view).to receive(:site_title).and_return('example site')
|
||||||
allow(view).to receive(:site_hostname).and_return('example.com')
|
allow(view).to receive(:site_hostname).and_return('example.com')
|
||||||
|
|
Loading…
Reference in New Issue