diff options
Diffstat (limited to 'app')
71 files changed, 508 insertions, 234 deletions
diff --git a/app/controllers/accounts_controller.rb b/app/controllers/accounts_controller.rb index ee48da177..52d09cff8 100644 --- a/app/controllers/accounts_controller.rb +++ b/app/controllers/accounts_controller.rb @@ -28,7 +28,7 @@ class AccountsController < ApplicationController end @pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses? - @statuses = filtered_status_page(params) + @statuses = filtered_status_page @statuses = cache_collection(@statuses, Status) @rss_url = rss_url @@ -141,12 +141,12 @@ class AccountsController < ApplicationController request.path.split('.').first.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize) end - def filtered_status_page(params) - if params[:min_id].present? - filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse - else - filtered_statuses.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id]).to_a - end + def filtered_status_page + filtered_statuses.paginate_by_id(PAGE_SIZE, params_slice(:max_id, :min_id, :since_id)) + end + + def params_slice(*keys) + params.slice(*keys).permit(*keys) end def restrict_fields_to diff --git a/app/controllers/activitypub/collections_controller.rb b/app/controllers/activitypub/collections_controller.rb index 910fefb1c..c1e7aa550 100644 --- a/app/controllers/activitypub/collections_controller.rb +++ b/app/controllers/activitypub/collections_controller.rb @@ -24,20 +24,23 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController def set_size case params[:id] when 'featured' - @account.pinned_statuses.count + @size = @account.pinned_statuses.count else - raise ActiveRecord::RecordNotFound + not_found end end def scope_for_collection case params[:id] when 'featured' - return Status.none if @account.blocking?(signed_request_account) - - @account.pinned_statuses - else - raise ActiveRecord::RecordNotFound + # Because in public fetch mode we cache the response, there would be no + # benefit from performing the check below, since a blocked account or domain + # would likely be served the cache from the reverse proxy anyway + if authorized_fetch_mode? && !signed_request_account.nil? && (@account.blocking?(signed_request_account) || (!signed_request_account.domain.nil? && @account.domain_blocking?(signed_request_account.domain))) + Status.none + else + @account.pinned_statuses + end end end diff --git a/app/controllers/activitypub/outboxes_controller.rb b/app/controllers/activitypub/outboxes_controller.rb index 891756b7e..e25a4bc07 100644 --- a/app/controllers/activitypub/outboxes_controller.rb +++ b/app/controllers/activitypub/outboxes_controller.rb @@ -11,7 +11,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController before_action :set_cache_headers def show - expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?) + expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode? && !(signed_request_account.present? && page_requested?)) render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json' end @@ -50,12 +50,12 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController return unless page_requested? @statuses = @account.statuses.permitted_for(@account, signed_request_account) - @statuses = params[:min_id].present? ? @statuses.paginate_by_min_id(LIMIT, params[:min_id]).reverse : @statuses.paginate_by_max_id(LIMIT, params[:max_id]) + @statuses = @statuses.paginate_by_id(LIMIT, params_slice(:max_id, :min_id, :since_id)) @statuses = cache_collection(@statuses, Status) end def page_requested? - params[:page] == 'true' + truthy_param?(:page) end def page_params diff --git a/app/controllers/activitypub/replies_controller.rb b/app/controllers/activitypub/replies_controller.rb index c62061555..43bf4e657 100644 --- a/app/controllers/activitypub/replies_controller.rb +++ b/app/controllers/activitypub/replies_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class ActivityPub::RepliesController < ActivityPub::BaseController - include SignatureAuthentication + include SignatureVerification include Authorization include AccountOwnedConcern @@ -19,15 +19,19 @@ class ActivityPub::RepliesController < ActivityPub::BaseController private + def pundit_user + signed_request_account + end + def set_status @status = @account.statuses.find(params[:status_id]) authorize @status, :show? rescue Mastodon::NotPermittedError - raise ActiveRecord::RecordNotFound + not_found end def set_replies - @replies = page_params[:only_other_accounts] ? Status.where.not(account_id: @account.id) : @account.statuses + @replies = only_other_accounts? ? Status.where.not(account_id: @account.id) : @account.statuses @replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted]) @replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id]) end @@ -38,7 +42,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController type: :unordered, part_of: account_status_replies_url(@account, @status), next: next_page, - items: @replies.map { |status| status.local ? status : status.uri } + items: @replies.map { |status| status.local? ? status : status.uri } ) return page if page_requested? @@ -51,16 +55,21 @@ class ActivityPub::RepliesController < ActivityPub::BaseController end def page_requested? - params[:page] == 'true' + truthy_param?(:page) + end + + def only_other_accounts? + truthy_param?(:only_other_accounts) end def next_page only_other_accounts = !(@replies&.last&.account_id == @account.id && @replies.size == DESCENDANTS_LIMIT) + account_status_replies_url( @account, @status, page: true, - min_id: only_other_accounts && !page_params[:only_other_accounts] ? nil : @replies&.last&.id, + min_id: only_other_accounts && !only_other_accounts? ? nil : @replies&.last&.id, only_other_accounts: only_other_accounts ) end diff --git a/app/controllers/api/v1/polls/votes_controller.rb b/app/controllers/api/v1/polls/votes_controller.rb index e1d26106a..513b937ef 100644 --- a/app/controllers/api/v1/polls/votes_controller.rb +++ b/app/controllers/api/v1/polls/votes_controller.rb @@ -18,7 +18,7 @@ class Api::V1::Polls::VotesController < Api::BaseController @poll = Poll.attached.find(params[:poll_id]) authorize @poll.status, :show? rescue Mastodon::NotPermittedError - raise ActiveRecord::RecordNotFound + not_found end def vote_params diff --git a/app/controllers/api/v1/polls_controller.rb b/app/controllers/api/v1/polls_controller.rb index 744baf7bb..6435e9f0d 100644 --- a/app/controllers/api/v1/polls_controller.rb +++ b/app/controllers/api/v1/polls_controller.rb @@ -17,7 +17,7 @@ class Api::V1::PollsController < Api::BaseController @poll = Poll.attached.find(params[:id]) authorize @poll.status, :show? rescue Mastodon::NotPermittedError - raise ActiveRecord::RecordNotFound + not_found end def refresh_poll diff --git a/app/controllers/api/v1/push/subscriptions_controller.rb b/app/controllers/api/v1/push/subscriptions_controller.rb index 1cbc92b93..d34b333eb 100644 --- a/app/controllers/api/v1/push/subscriptions_controller.rb +++ b/app/controllers/api/v1/push/subscriptions_controller.rb @@ -4,6 +4,7 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController before_action -> { doorkeeper_authorize! :push } before_action :require_user! before_action :set_web_push_subscription + before_action :check_web_push_subscription, only: [:show, :update] def create @web_subscription&.destroy! @@ -21,16 +22,11 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController end def show - raise ActiveRecord::RecordNotFound if @web_subscription.nil? - render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer end def update - raise ActiveRecord::RecordNotFound if @web_subscription.nil? - @web_subscription.update!(data: data_params) - render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer end @@ -45,12 +41,17 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController @web_subscription = ::Web::PushSubscription.find_by(access_token_id: doorkeeper_token.id) end + def check_web_push_subscription + not_found if @web_subscription.nil? + end + def subscription_params params.require(:subscription).permit(:endpoint, keys: [:auth, :p256dh]) end def data_params return {} if params[:data].blank? + params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll]) end end diff --git a/app/controllers/api/v1/statuses/mutes_controller.rb b/app/controllers/api/v1/statuses/mutes_controller.rb index 43c7a525a..87071a2b9 100644 --- a/app/controllers/api/v1/statuses/mutes_controller.rb +++ b/app/controllers/api/v1/statuses/mutes_controller.rb @@ -28,8 +28,7 @@ class Api::V1::Statuses::MutesController < Api::BaseController @status = Status.find(params[:status_id]) authorize @status, :show? rescue Mastodon::NotPermittedError - # Reraise in order to get a 404 instead of a 403 error code - raise ActiveRecord::RecordNotFound + not_found end def set_conversation diff --git a/app/controllers/api/v1/statuses_controller.rb b/app/controllers/api/v1/statuses_controller.rb index 29ae91762..b3edce676 100644 --- a/app/controllers/api/v1/statuses_controller.rb +++ b/app/controllers/api/v1/statuses_controller.rb @@ -68,7 +68,7 @@ class Api::V1::StatusesController < Api::BaseController @status = Status.find(params[:id]) authorize @status, :show? rescue Mastodon::NotPermittedError - raise ActiveRecord::RecordNotFound + not_found end def set_thread diff --git a/app/controllers/media_controller.rb b/app/controllers/media_controller.rb index 05cf09c28..1d166d6e7 100644 --- a/app/controllers/media_controller.rb +++ b/app/controllers/media_controller.rb @@ -33,7 +33,7 @@ class MediaController < ApplicationController def verify_permitted_status! authorize @media_attachment.status, :show? rescue Mastodon::NotPermittedError - raise ActiveRecord::RecordNotFound + not_found end def check_playable diff --git a/app/controllers/remote_interaction_controller.rb b/app/controllers/remote_interaction_controller.rb index e058d0ed5..51bb9bdea 100644 --- a/app/controllers/remote_interaction_controller.rb +++ b/app/controllers/remote_interaction_controller.rb @@ -42,7 +42,7 @@ class RemoteInteractionController < ApplicationController @status = Status.find(params[:id]) authorize @status, :show? rescue Mastodon::NotPermittedError - raise ActiveRecord::RecordNotFound + not_found end def set_body_classes diff --git a/app/controllers/statuses_controller.rb b/app/controllers/statuses_controller.rb index 588063d01..a1b7f4320 100644 --- a/app/controllers/statuses_controller.rb +++ b/app/controllers/statuses_controller.rb @@ -49,7 +49,7 @@ class StatusesController < ApplicationController def embed use_pack 'embed' - return not_found if @status.hidden? + return not_found if @status.hidden? || @status.reblog? expires_in 180, public: true response.headers['X-Frame-Options'] = 'ALLOWALL' diff --git a/app/javascript/core/settings.js b/app/javascript/core/settings.js index e02c91cc7..9fe03f90c 100644 --- a/app/javascript/core/settings.js +++ b/app/javascript/core/settings.js @@ -10,7 +10,7 @@ delegate(document, '#account_display_name', 'input', ({ target }) => { if (target.value) { name.innerHTML = emojify(escapeTextContentForBrowser(target.value)); } else { - name.textContent = document.querySelector('#default_account_display_name').textContent; + name.textContent = name.textContent = target.dataset.default; } } }); diff --git a/app/javascript/flavours/glitch/actions/timelines.js b/app/javascript/flavours/glitch/actions/timelines.js index d9c75e063..50e36531e 100644 --- a/app/javascript/flavours/glitch/actions/timelines.js +++ b/app/javascript/flavours/glitch/actions/timelines.js @@ -55,7 +55,7 @@ export function updateTimeline(timeline, status, accept) { export function deleteFromTimelines(id) { return (dispatch, getState) => { const accountId = getState().getIn(['statuses', id, 'account']); - const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => [status.get('id'), status.get('account')]); + const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => status.get('id')); const reblogOf = getState().getIn(['statuses', id, 'reblog'], null); dispatch({ diff --git a/app/javascript/flavours/glitch/components/dropdown_menu.js b/app/javascript/flavours/glitch/components/dropdown_menu.js index b3da4fc2f..60ed859a3 100644 --- a/app/javascript/flavours/glitch/components/dropdown_menu.js +++ b/app/javascript/flavours/glitch/components/dropdown_menu.js @@ -46,7 +46,7 @@ class DropdownMenu extends React.PureComponent { document.addEventListener('keydown', this.handleKeyDown, false); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); if (this.focusedItem && this.props.openedViaKeyboard) { - this.focusedItem.focus(); + this.focusedItem.focus({ preventScroll: true }); } this.setState({ mounted: true }); } diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js index 84c040a86..bee06e64c 100644 --- a/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js +++ b/app/javascript/flavours/glitch/features/compose/components/dropdown_menu.js @@ -64,9 +64,9 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent document.addEventListener('click', this.handleDocumentClick, false); document.addEventListener('touchend', this.handleDocumentClick, withPassive); if (this.focusedItem) { - this.focusedItem.focus(); + this.focusedItem.focus({ preventScroll: true }); } else { - this.node.firstChild.focus(); + this.node.firstChild.focus({ preventScroll: true }); } this.setState({ mounted: true }); } diff --git a/app/javascript/flavours/glitch/packs/public.js b/app/javascript/flavours/glitch/packs/public.js index e5a567205..58febcf5b 100644 --- a/app/javascript/flavours/glitch/packs/public.js +++ b/app/javascript/flavours/glitch/packs/public.js @@ -99,15 +99,13 @@ function main() { delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static')); delegate(document, '.status__content__spoiler-link', 'click', function() { - const contentEl = this.parentNode.parentNode.querySelector('.e-content'); + const statusEl = this.parentNode.parentNode; - if (contentEl.style.display === 'block') { - contentEl.style.display = 'none'; - this.parentNode.style.marginBottom = 0; + if (statusEl.dataset.spoiler === 'expanded') { + statusEl.dataset.spoiler = 'folded'; this.textContent = (new IntlMessageFormat(messages['status.show_more'] || 'Show more', locale)).format(); } else { - contentEl.style.display = 'block'; - this.parentNode.style.marginBottom = null; + statusEl.dataset.spoiler = 'expanded'; this.textContent = (new IntlMessageFormat(messages['status.show_less'] || 'Show less', locale)).format(); } @@ -115,8 +113,8 @@ function main() { }); [].forEach.call(document.querySelectorAll('.status__content__spoiler-link'), (spoilerLink) => { - const contentEl = spoilerLink.parentNode.parentNode.querySelector('.e-content'); - const message = (contentEl.style.display === 'block') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more'); + const statusEl = spoilerLink.parentNode.parentNode; + const message = (statusEl.dataset.spoiler === 'expanded') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more'); spoilerLink.textContent = (new IntlMessageFormat(message, locale)).format(); }); }); diff --git a/app/javascript/flavours/glitch/reducers/statuses.js b/app/javascript/flavours/glitch/reducers/statuses.js index 8926e49f1..5db766b96 100644 --- a/app/javascript/flavours/glitch/reducers/statuses.js +++ b/app/javascript/flavours/glitch/reducers/statuses.js @@ -24,7 +24,7 @@ const importStatuses = (state, statuses) => const deleteStatus = (state, id, references) => { references.forEach(ref => { - state = deleteStatus(state, ref[0], []); + state = deleteStatus(state, ref, []); }); return state.delete(id); diff --git a/app/javascript/flavours/glitch/reducers/timelines.js b/app/javascript/flavours/glitch/reducers/timelines.js index be7b2441b..882b48790 100644 --- a/app/javascript/flavours/glitch/reducers/timelines.js +++ b/app/javascript/flavours/glitch/reducers/timelines.js @@ -94,7 +94,7 @@ const updateTimeline = (state, timeline, status, usePendingItems, filtered) => { })); }; -const deleteStatus = (state, id, accountId, references, exclude_account = null) => { +const deleteStatus = (state, id, references, exclude_account = null) => { state.keySeq().forEach(timeline => { if (exclude_account === null || (timeline !== `account:${exclude_account}` && !timeline.startsWith(`account:${exclude_account}:`))) { const helper = list => list.filterNot(item => item === id); @@ -104,7 +104,7 @@ const deleteStatus = (state, id, accountId, references, exclude_account = null) // Remove reblogs of deleted status references.forEach(ref => { - state = deleteStatus(state, ref[0], ref[1], [], exclude_account); + state = deleteStatus(state, ref, [], exclude_account); }); return state; @@ -122,8 +122,8 @@ const filterTimelines = (state, relationship, statuses) => { return; } - references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => [item.get('id'), item.get('account')]); - state = deleteStatus(state, status.get('id'), status.get('account'), references, relationship.id); + references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => item.get('id')); + state = deleteStatus(state, status.get('id'), references, relationship.id); }); return state; @@ -155,7 +155,7 @@ export default function timelines(state = initialState, action) { case TIMELINE_UPDATE: return updateTimeline(state, action.timeline, fromJS(action.status), action.usePendingItems, action.filtered); case TIMELINE_DELETE: - return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf); + return deleteStatus(state, action.id, action.references, action.reblogOf); case TIMELINE_CLEAR: return clearTimeline(state, action.timeline); case ACCOUNT_BLOCK_SUCCESS: diff --git a/app/javascript/flavours/glitch/styles/about.scss b/app/javascript/flavours/glitch/styles/about.scss index a38ca99b4..f0a44aa94 100644 --- a/app/javascript/flavours/glitch/styles/about.scss +++ b/app/javascript/flavours/glitch/styles/about.scss @@ -760,8 +760,13 @@ $small-breakpoint: 960px; } } + &__counters__wrapper { + display: flex; + } + &__counter { padding: 10px; + width: 50%; strong { font-family: $font-display, sans-serif; diff --git a/app/javascript/flavours/glitch/styles/admin.scss b/app/javascript/flavours/glitch/styles/admin.scss index 0d24da4dd..1c8f2271f 100644 --- a/app/javascript/flavours/glitch/styles/admin.scss +++ b/app/javascript/flavours/glitch/styles/admin.scss @@ -567,6 +567,18 @@ body, } } +.special-action-button, +.back-link { + text-align: right; + flex: 1 1 auto; +} + +.action-buttons { + display: flex; + overflow: hidden; + justify-content: space-between; +} + .spacer { flex: 1 1 auto; } @@ -904,3 +916,11 @@ a.name-tag, } } } + +.account-badges { + margin: -2px 0; +} + +.dashboard__counters.admin-account-counters { + margin-top: 10px; +} diff --git a/app/javascript/flavours/glitch/styles/basics.scss b/app/javascript/flavours/glitch/styles/basics.scss index 77631097a..9ff3f3bac 100644 --- a/app/javascript/flavours/glitch/styles/basics.scss +++ b/app/javascript/flavours/glitch/styles/basics.scss @@ -150,3 +150,19 @@ button { height: 100%; } } + +.logo-resources { + display: none; +} + +// NoScript adds a __ns__pop2top class to the full ancestry of blocked elements, +// to set the z-index to a high value, which messes with modals and dropdowns. +// Blocked elements can in theory only be media and frames/embeds, so they +// should only appear in statuses, under divs and articles. +body, +div, +article { + .__ns__pop2top { + z-index: unset !important; + } +} diff --git a/app/javascript/flavours/glitch/styles/components/accounts.scss b/app/javascript/flavours/glitch/styles/components/accounts.scss index 491ceb6ec..ccd620215 100644 --- a/app/javascript/flavours/glitch/styles/components/accounts.scss +++ b/app/javascript/flavours/glitch/styles/components/accounts.scss @@ -80,6 +80,12 @@ &-base { @include avatar-radius(); @include avatar-size(36px); + + img { + @include avatar-radius; + width: 100%; + height: 100%; + } } &-overlay { @@ -90,6 +96,12 @@ bottom: 0; right: 0; z-index: 1; + + img { + @include avatar-radius; + width: 100%; + height: 100%; + } } } diff --git a/app/javascript/flavours/glitch/styles/forms.scss b/app/javascript/flavours/glitch/styles/forms.scss index 396e87c6c..5de650f0a 100644 --- a/app/javascript/flavours/glitch/styles/forms.scss +++ b/app/javascript/flavours/glitch/styles/forms.scss @@ -133,6 +133,10 @@ code { } } + .otp-hint { + margin-bottom: 25px; + } + .card { margin-bottom: 15px; } @@ -276,6 +280,14 @@ code { margin-bottom: 25px; } } + + .fields-group.invited-by { + margin-bottom: 30px; + + .hint { + text-align: center; + } + } } .input.radio_buttons .radio label { @@ -626,6 +638,15 @@ code { @media screen and (max-width: 740px) and (min-width: 441px) { margin-top: 40px; } + + &.translation-prompt { + text-align: unset; + color: unset; + + a { + text-decoration: underline; + } + } } .form-footer { diff --git a/app/javascript/flavours/glitch/styles/polls.scss b/app/javascript/flavours/glitch/styles/polls.scss index 44338338f..5fc41ed9e 100644 --- a/app/javascript/flavours/glitch/styles/polls.scss +++ b/app/javascript/flavours/glitch/styles/polls.scss @@ -25,6 +25,36 @@ } } + progress { + border: 0; + display: block; + width: 100%; + height: 5px; + appearance: none; + background: transparent; + + &::-webkit-progress-bar { + background: transparent; + } + + // Those rules need to be entirely separate or they won't work, hence the + // duplication + &::-moz-progress-bar { + border-radius: 4px; + background: darken($ui-primary-color, 5%); + } + + &::-ms-fill { + border-radius: 4px; + background: darken($ui-primary-color, 5%); + } + + &::-webkit-progress-value { + border-radius: 4px; + background: darken($ui-primary-color, 5%); + } + } + &__option { position: relative; display: flex; diff --git a/app/javascript/flavours/glitch/styles/statuses.scss b/app/javascript/flavours/glitch/styles/statuses.scss index 22fa7b3fd..4122e121a 100644 --- a/app/javascript/flavours/glitch/styles/statuses.scss +++ b/app/javascript/flavours/glitch/styles/statuses.scss @@ -124,6 +124,16 @@ .embed, .public-layout { + .status__content[data-spoiler=folded] { + .e-content { + display: none; + } + + p:first-child { + margin-bottom: 0; + } + } + .detailed-status { padding: 15px; } @@ -162,6 +172,13 @@ .video-player { margin-top: 10px; } + + &__action-bar-button { + font-size: 18px; + width: 23.1429px; + height: 23.1429px; + line-height: 23.15px; + } } } diff --git a/app/javascript/mastodon/actions/timelines.js b/app/javascript/mastodon/actions/timelines.js index 50840cacc..861827d33 100644 --- a/app/javascript/mastodon/actions/timelines.js +++ b/app/javascript/mastodon/actions/timelines.js @@ -42,7 +42,7 @@ export function updateTimeline(timeline, status, accept) { export function deleteFromTimelines(id) { return (dispatch, getState) => { const accountId = getState().getIn(['statuses', id, 'account']); - const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => [status.get('id'), status.get('account')]); + const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => status.get('id')); const reblogOf = getState().getIn(['statuses', id, 'reblog'], null); dispatch({ diff --git a/app/javascript/mastodon/components/dropdown_menu.js b/app/javascript/mastodon/components/dropdown_menu.js index 31c02d735..4734e0f3f 100644 --- a/app/javascript/mastodon/components/dropdown_menu.js +++ b/app/javascript/mastodon/components/dropdown_menu.js @@ -46,7 +46,7 @@ class DropdownMenu extends React.PureComponent { document.addEventListener('keydown', this.handleKeyDown, false); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); if (this.focusedItem && this.props.openedViaKeyboard) { - this.focusedItem.focus(); + this.focusedItem.focus({ preventScroll: true }); } this.setState({ mounted: true }); } diff --git a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js index 57588fe96..96028e042 100644 --- a/app/javascript/mastodon/features/compose/components/privacy_dropdown.js +++ b/app/javascript/mastodon/features/compose/components/privacy_dropdown.js @@ -100,7 +100,7 @@ class PrivacyDropdownMenu extends React.PureComponent { componentDidMount () { document.addEventListener('click', this.handleDocumentClick, false); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); - if (this.focusedItem) this.focusedItem.focus(); + if (this.focusedItem) this.focusedItem.focus({ preventScroll: true }); this.setState({ mounted: true }); } diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js index 2554c008d..53dec9585 100644 --- a/app/javascript/mastodon/reducers/statuses.js +++ b/app/javascript/mastodon/reducers/statuses.js @@ -25,7 +25,7 @@ const importStatuses = (state, statuses) => const deleteStatus = (state, id, references) => { references.forEach(ref => { - state = deleteStatus(state, ref[0], []); + state = deleteStatus(state, ref, []); }); return state.delete(id); diff --git a/app/javascript/mastodon/reducers/timelines.js b/app/javascript/mastodon/reducers/timelines.js index 63b76773d..9156db021 100644 --- a/app/javascript/mastodon/reducers/timelines.js +++ b/app/javascript/mastodon/reducers/timelines.js @@ -89,7 +89,7 @@ const updateTimeline = (state, timeline, status, usePendingItems) => { })); }; -const deleteStatus = (state, id, accountId, references, exclude_account = null) => { +const deleteStatus = (state, id, references, exclude_account = null) => { state.keySeq().forEach(timeline => { if (exclude_account === null || (timeline !== `account:${exclude_account}` && !timeline.startsWith(`account:${exclude_account}:`))) { const helper = list => list.filterNot(item => item === id); @@ -99,7 +99,7 @@ const deleteStatus = (state, id, accountId, references, exclude_account = null) // Remove reblogs of deleted status references.forEach(ref => { - state = deleteStatus(state, ref[0], ref[1], [], exclude_account); + state = deleteStatus(state, ref, [], exclude_account); }); return state; @@ -117,8 +117,8 @@ const filterTimelines = (state, relationship, statuses) => { return; } - references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => [item.get('id'), item.get('account')]); - state = deleteStatus(state, status.get('id'), status.get('account'), references, relationship.id); + references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => item.get('id')); + state = deleteStatus(state, status.get('id'), references, relationship.id); }); return state; @@ -150,7 +150,7 @@ export default function timelines(state = initialState, action) { case TIMELINE_UPDATE: return updateTimeline(state, action.timeline, fromJS(action.status), action.usePendingItems); case TIMELINE_DELETE: - return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf); + return deleteStatus(state, action.id, action.references, action.reblogOf); case TIMELINE_CLEAR: return clearTimeline(state, action.timeline); case ACCOUNT_BLOCK_SUCCESS: diff --git a/app/javascript/packs/public.js b/app/javascript/packs/public.js index 5b699e767..3d190d2da 100644 --- a/app/javascript/packs/public.js +++ b/app/javascript/packs/public.js @@ -103,15 +103,13 @@ function main() { delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static')); delegate(document, '.status__content__spoiler-link', 'click', function() { - const contentEl = this.parentNode.parentNode.querySelector('.e-content'); + const statusEl = this.parentNode.parentNode; - if (contentEl.style.display === 'block') { - contentEl.style.display = 'none'; - this.parentNode.style.marginBottom = 0; + if (statusEl.dataset.spoiler === 'expanded') { + statusEl.dataset.spoiler = 'folded'; this.textContent = (new IntlMessageFormat(messages['status.show_more'] || 'Show more', locale)).format(); } else { - contentEl.style.display = 'block'; - this.parentNode.style.marginBottom = null; + statusEl.dataset.spoiler = 'expanded'; this.textContent = (new IntlMessageFormat(messages['status.show_less'] || 'Show less', locale)).format(); } @@ -119,8 +117,8 @@ function main() { }); [].forEach.call(document.querySelectorAll('.status__content__spoiler-link'), (spoilerLink) => { - const contentEl = spoilerLink.parentNode.parentNode.querySelector('.e-content'); - const message = (contentEl.style.display === 'block') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more'); + const statusEl = spoilerLink.parentNode.parentNode; + const message = (statusEl.dataset.spoiler === 'expanded') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more'); spoilerLink.textContent = (new IntlMessageFormat(message, locale)).format(); }); }); diff --git a/app/javascript/styles/mastodon/about.scss b/app/javascript/styles/mastodon/about.scss index cf16b54ac..711f34965 100644 --- a/app/javascript/styles/mastodon/about.scss +++ b/app/javascript/styles/mastodon/about.scss @@ -757,8 +757,13 @@ $small-breakpoint: 960px; } } + &__counters__wrapper { + display: flex; + } + &__counter { padding: 10px; + width: 50%; strong { font-family: $font-display, sans-serif; diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss index 7bff2daa1..78dea92b9 100644 --- a/app/javascript/styles/mastodon/admin.scss +++ b/app/javascript/styles/mastodon/admin.scss @@ -583,6 +583,18 @@ body, } } +.special-action-button, +.back-link { + text-align: right; + flex: 1 1 auto; +} + +.action-buttons { + display: flex; + overflow: hidden; + justify-content: space-between; +} + .spacer { flex: 1 1 auto; } @@ -920,3 +932,11 @@ a.name-tag, } } } + +.account-badges { + margin: -2px 0; +} + +.dashboard__counters.admin-account-counters { + margin-top: 10px; +} diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss index 2b10b5ad3..a5dbe75fb 100644 --- a/app/javascript/styles/mastodon/basics.scss +++ b/app/javascript/styles/mastodon/basics.scss @@ -229,3 +229,19 @@ button { } } } + +.logo-resources { + display: none; +} + +// NoScript adds a __ns__pop2top class to the full ancestry of blocked elements, +// to set the z-index to a high value, which messes with modals and dropdowns. +// Blocked elements can in theory only be media and frames/embeds, so they +// should only appear in statuses, under divs and articles. +body, +div, +article { + .__ns__pop2top { + z-index: unset !important; + } +} diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index c7835b878..6c33b709d 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -1362,6 +1362,12 @@ a .account__avatar { &-base { @include avatar-radius; @include avatar-size(36px); + + img { + @include avatar-radius; + width: 100%; + height: 100%; + } } &-overlay { @@ -1372,6 +1378,12 @@ a .account__avatar { bottom: 0; right: 0; z-index: 1; + + img { + @include avatar-radius; + width: 100%; + height: 100%; + } } } diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss index c9ad68f94..0e5b00e8f 100644 --- a/app/javascript/styles/mastodon/forms.scss +++ b/app/javascript/styles/mastodon/forms.scss @@ -142,6 +142,10 @@ code { } } + .otp-hint { + margin-bottom: 25px; + } + .card { margin-bottom: 15px; } @@ -285,6 +289,14 @@ code { margin-bottom: 25px; } } + + .fields-group.invited-by { + margin-bottom: 30px; + + .hint { + text-align: center; + } + } } .input.radio_buttons .radio label { @@ -635,6 +647,15 @@ code { @media screen and (max-width: 740px) and (min-width: 441px) { margin-top: 40px; } + + &.translation-prompt { + text-align: unset; + color: unset; + + a { + text-decoration: underline; + } + } } .form-footer { diff --git a/app/javascript/styles/mastodon/polls.scss b/app/javascript/styles/mastodon/polls.scss index 1ecc8434d..ad7088982 100644 --- a/app/javascript/styles/mastodon/polls.scss +++ b/app/javascript/styles/mastodon/polls.scss @@ -19,6 +19,36 @@ } } + progress { + border: 0; + display: block; + width: 100%; + height: 5px; + appearance: none; + background: transparent; + + &::-webkit-progress-bar { + background: transparent; + } + + // Those rules need to be entirely separate or they won't work, hence the + // duplication + &::-moz-progress-bar { + border-radius: 4px; + background: darken($ui-primary-color, 5%); + } + + &::-ms-fill { + border-radius: 4px; + background: darken($ui-primary-color, 5%); + } + + &::-webkit-progress-value { + border-radius: 4px; + background: darken($ui-primary-color, 5%); + } + } + &__option { position: relative; display: flex; diff --git a/app/javascript/styles/mastodon/statuses.scss b/app/javascript/styles/mastodon/statuses.scss index 19ce0ab8f..0b7be7afd 100644 --- a/app/javascript/styles/mastodon/statuses.scss +++ b/app/javascript/styles/mastodon/statuses.scss @@ -128,6 +128,16 @@ .embed, .public-layout { + .status__content[data-spoiler=folded] { + .e-content { + display: none; + } + + p:first-child { + margin-bottom: 0; + } + } + .detailed-status { padding: 15px; } @@ -159,5 +169,12 @@ .video-player { margin-top: 10px; } + + &__action-bar-button { + font-size: 18px; + width: 23.1429px; + height: 23.1429px; + line-height: 23.15px; + } } } diff --git a/app/models/account.rb b/app/models/account.rb index e56db3126..5038d4768 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -3,50 +3,52 @@ # # Table name: accounts # -# id :bigint(8) not null, primary key -# username :string default(""), not null -# domain :string -# secret :string default(""), not null -# private_key :text -# public_key :text default(""), not null -# remote_url :string default(""), not null -# salmon_url :string default(""), not null -# hub_url :string default(""), not null -# created_at :datetime not null -# updated_at :datetime not null -# note :text default(""), not null -# display_name :string default(""), not null -# uri :string default(""), not null -# url :string -# avatar_file_name :string -# avatar_content_type :string -# avatar_file_size :integer -# avatar_updated_at :datetime -# header_file_name :string -# header_content_type :string -# header_file_size :integer -# header_updated_at :datetime -# avatar_remote_url :string -# subscription_expires_at :datetime -# locked :boolean default(FALSE), not null -# header_remote_url :string default(""), not null -# last_webfingered_at :datetime -# inbox_url :string default(""), not null -# outbox_url :string default(""), not null -# shared_inbox_url :string default(""), not null -# followers_url :string default(""), not null -# protocol :integer default("ostatus"), not null -# memorial :boolean default(FALSE), not null -# moved_to_account_id :bigint(8) -# featured_collection_url :string -# fields :jsonb -# actor_type :string -# discoverable :boolean -# also_known_as :string is an Array -# silenced_at :datetime -# suspended_at :datetime -# trust_level :integer -# hide_collections :boolean +# id :bigint(8) not null, primary key +# username :string default(""), not null +# domain :string +# secret :string default(""), not null +# private_key :text +# public_key :text default(""), not null +# remote_url :string default(""), not null +# salmon_url :string default(""), not null +# hub_url :string default(""), not null +# created_at :datetime not null +# updated_at :datetime not null +# note :text default(""), not null +# display_name :string default(""), not null +# uri :string default(""), not null +# url :string +# avatar_file_name :string +# avatar_content_type :string +# avatar_file_size :integer +# avatar_updated_at :datetime +# header_file_name :string +# header_content_type :string +# header_file_size :integer +# header_updated_at :datetime +# avatar_remote_url :string +# subscription_expires_at :datetime +# locked :boolean default(FALSE), not null +# header_remote_url :string default(""), not null +# last_webfingered_at :datetime +# inbox_url :string default(""), not null +# outbox_url :string default(""), not null +# shared_inbox_url :string default(""), not null +# followers_url :string default(""), not null +# protocol :integer default("ostatus"), not null +# memorial :boolean default(FALSE), not null +# moved_to_account_id :bigint(8) +# featured_collection_url :string +# fields :jsonb +# actor_type :string +# discoverable :boolean +# also_known_as :string is an Array +# silenced_at :datetime +# suspended_at :datetime +# trust_level :integer +# hide_collections :boolean +# avatar_storage_schema_version :integer +# header_storage_schema_version :integer # class Account < ApplicationRecord diff --git a/app/models/concerns/omniauthable.rb b/app/models/concerns/omniauthable.rb index 960784222..736da6c1d 100644 --- a/app/models/concerns/omniauthable.rb +++ b/app/models/concerns/omniauthable.rb @@ -82,7 +82,7 @@ module Omniauthable username = starting_username i = 0 - while Account.exists?(username: username) + while Account.exists?(username: username, domain: nil) i += 1 username = "#{starting_username}_#{i}" end diff --git a/app/models/custom_emoji.rb b/app/models/custom_emoji.rb index d177cf281..7cb03b819 100644 --- a/app/models/custom_emoji.rb +++ b/app/models/custom_emoji.rb @@ -3,20 +3,21 @@ # # Table name: custom_emojis # -# id :bigint(8) not null, primary key -# shortcode :string default(""), not null -# domain :string -# image_file_name :string -# image_content_type :string -# image_file_size :integer -# image_updated_at :datetime -# created_at :datetime not null -# updated_at :datetime not null -# disabled :boolean default(FALSE), not null -# uri :string -# image_remote_url :string -# visible_in_picker :boolean default(TRUE), not null -# category_id :bigint(8) +# id :bigint(8) not null, primary key +# shortcode :string default(""), not null +# domain :string +# image_file_name :string +# image_content_type :string +# image_file_size :integer +# image_updated_at :datetime +# created_at :datetime not null +# updated_at :datetime not null +# disabled :boolean default(FALSE), not null +# uri :string +# image_remote_url :string +# visible_in_picker :boolean default(TRUE), not null +# category_id :bigint(8) +# image_storage_schema_version :integer # class CustomEmoji < ApplicationRecord diff --git a/app/models/media_attachment.rb b/app/models/media_attachment.rb index 40624c73c..f789bdc55 100644 --- a/app/models/media_attachment.rb +++ b/app/models/media_attachment.rb @@ -3,23 +3,24 @@ # # Table name: media_attachments # -# id :bigint(8) not null, primary key -# status_id :bigint(8) -# file_file_name :string -# file_content_type :string -# file_file_size :integer -# file_updated_at :datetime -# remote_url :string default(""), not null -# created_at :datetime not null -# updated_at :datetime not null -# shortcode :string -# type :integer default("image"), not null -# file_meta :json -# account_id :bigint(8) -# description :text -# scheduled_status_id :bigint(8) -# blurhash :string -# processing :integer +# id :bigint(8) not null, primary key +# status_id :bigint(8) +# file_file_name :string +# file_content_type :string +# file_file_size :integer +# file_updated_at :datetime +# remote_url :string default(""), not null +# created_at :datetime not null +# updated_at :datetime not null +# shortcode :string +# type :integer default("image"), not null +# file_meta :json +# account_id :bigint(8) +# description :text +# scheduled_status_id :bigint(8) +# blurhash :string +# processing :integer +# file_storage_schema_version :integer # class MediaAttachment < ApplicationRecord diff --git a/app/models/preview_card.rb b/app/models/preview_card.rb index 4e89fbf85..2802f4667 100644 --- a/app/models/preview_card.rb +++ b/app/models/preview_card.rb @@ -3,25 +3,26 @@ # # Table name: preview_cards # -# id :bigint(8) not null, primary key -# url :string default(""), not null -# title :string default(""), not null -# description :string default(""), not null -# image_file_name :string -# image_content_type :string -# image_file_size :integer -# image_updated_at :datetime -# type :integer default("link"), not null -# html :text default(""), not null -# author_name :string default(""), not null -# author_url :string default(""), not null -# provider_name :string default(""), not null -# provider_url :string default(""), not null -# width :integer default(0), not null -# height :integer default(0), not null -# created_at :datetime not null -# updated_at :datetime not null -# embed_url :string default(""), not null +# id :bigint(8) not null, primary key +# url :string default(""), not null +# title :string default(""), not null +# description :string default(""), not null +# image_file_name :string +# image_content_type :string +# image_file_size :integer +# image_updated_at :datetime +# type :integer default("link"), not null +# html :text default(""), not null +# author_name :string default(""), not null +# author_url :string default(""), not null +# provider_name :string default(""), not null +# provider_url :string default(""), not null +# width :integer default(0), not null +# height :integer default(0), not null +# created_at :datetime not null +# updated_at :datetime not null +# embed_url :string default(""), not null +# image_storage_schema_version :integer # class PreviewCard < ApplicationRecord @@ -47,6 +48,10 @@ class PreviewCard < ApplicationRecord before_save :extract_dimensions, if: :link? + def local? + false + end + def missing_image? width.present? && height.present? && image_file_name.blank? end diff --git a/app/models/status.rb b/app/models/status.rb index 31e77770d..34fa00912 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -206,12 +206,8 @@ class Status < ApplicationRecord def title if destroyed? "#{account.acct} deleted status" - elsif reblog? - preview = sensitive ? '<sensitive>' : text.slice(0, 10).split("\n")[0] - "#{account.acct} shared #{reblog.account.acct}'s: #{preview}" else - preview = sensitive ? '<sensitive>' : text.slice(0, 20).split("\n")[0] - "#{account.acct}: #{preview}" + reblog? ? "#{account.acct} shared a status by #{reblog.account.acct}" : "New status by #{account.acct}" end end @@ -404,7 +400,7 @@ class Status < ApplicationRecord if account.nil? where(visibility: visibility).not_local_only - elsif target_account.blocking?(account) # get rid of blocked peeps + elsif target_account.blocking?(account) || (account.domain.present? && target_account.domain_blocking?(account.domain)) # get rid of blocked peeps none elsif account.id == target_account.id # author can see own stuff all diff --git a/app/serializers/rest/instance_serializer.rb b/app/serializers/rest/instance_serializer.rb index e913f0c64..54e7c450c 100644 --- a/app/serializers/rest/instance_serializer.rb +++ b/app/serializers/rest/instance_serializer.rb @@ -5,7 +5,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer attributes :uri, :title, :short_description, :description, :email, :version, :urls, :stats, :thumbnail, :max_toot_chars, :poll_limits, - :languages, :registrations, :approval_required + :languages, :registrations, :approval_required, :invites_enabled has_one :contact_account, serializer: REST::AccountSerializer @@ -76,6 +76,10 @@ class REST::InstanceSerializer < ActiveModel::Serializer Setting.registrations_mode == 'approved' end + def invites_enabled + Setting.min_invite_role == 'user' + end + private def instance_presenter diff --git a/app/services/fetch_resource_service.rb b/app/services/fetch_resource_service.rb index 880cdde92..6c0093cd4 100644 --- a/app/services/fetch_resource_service.rb +++ b/app/services/fetch_resource_service.rb @@ -25,7 +25,18 @@ class FetchResourceService < BaseService end def perform_request(&block) - Request.new(:get, @url).add_headers('Accept' => ACCEPT_HEADER).on_behalf_of(Account.representative).perform(&block) + Request.new(:get, @url).tap do |request| + request.add_headers('Accept' => ACCEPT_HEADER) + + # In a real setting we want to sign all outgoing requests, + # in case the remote server has secure mode enabled and requires + # authentication on all resources. However, during development, + # sending request signatures with an inaccessible host is useless + # and prevents even public resources from being fetched, so + # don't do it + + request.on_behalf_of(Account.representative) unless Rails.env.development? + end.perform(&block) end def process_response(response, terminal = false) diff --git a/app/views/about/show.html.haml b/app/views/about/show.html.haml index e0ec98ec9..07e06100a 100644 --- a/app/views/about/show.html.haml +++ b/app/views/about/show.html.haml @@ -68,11 +68,11 @@ .hero-widget__footer__column %h4= t 'about.server_stats' - %div{ style: 'display: flex' } - .hero-widget__counter{ style: 'width: 50%' } + .hero-widget__counters__wrapper + .hero-widget__counter %strong= number_to_human @instance_presenter.user_count, strip_insignificant_zeros: true %span= t 'about.user_count_after', count: @instance_presenter.user_count - .hero-widget__counter{ style: 'width: 50%' } + .hero-widget__counter %strong= number_to_human @instance_presenter.active_user_count, strip_insignificant_zeros: true %span = t 'about.active_count_after' diff --git a/app/views/accounts/_moved.html.haml b/app/views/accounts/_moved.html.haml index a82f277b1..4f71b062d 100644 --- a/app/views/accounts/_moved.html.haml +++ b/app/views/accounts/_moved.html.haml @@ -9,8 +9,10 @@ = link_to ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'me noopener noreferrer' do .detailed-status__display-avatar .account__avatar-overlay - .account__avatar-overlay-base{ style: "background-image: url('#{moved_to_account.avatar.url(:original)}')" } - .account__avatar-overlay-overlay{ style: "background-image: url('#{account.avatar.url(:original)}')" } + .account__avatar-overlay-base + = image_tag moved_to_account.avatar_static_url + .account__avatar-overlay-overlay + = image_tag account.avatar_static_url %span.display-name %bdi diff --git a/app/views/admin/accounts/_account.html.haml b/app/views/admin/accounts/_account.html.haml index 44b10af6e..c9bd8c686 100644 --- a/app/views/admin/accounts/_account.html.haml +++ b/app/views/admin/accounts/_account.html.haml @@ -2,7 +2,7 @@ %td = admin_account_link_to(account) %td - %div{ style: 'margin: -2px 0' }= account_badge(account, all: true) + %div.account-badges= account_badge(account, all: true) %td - if account.user_current_sign_in_ip %samp.ellipsized-ip{ title: account.user_current_sign_in_ip }= account.user_current_sign_in_ip diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml index 408f94eed..e6461aad0 100644 --- a/app/views/admin/accounts/show.html.haml +++ b/app/views/admin/accounts/show.html.haml @@ -31,7 +31,7 @@ %div .account__header__content.emojify= Formatter.instance.simplified_format(account, custom_emojify: true) -.dashboard__counters{ style: 'margin-top: 10px' } +.dashboard__counters.admin-account-counters %div = link_to admin_account_statuses_path(@account.id) do .dashboard__counters__num= number_with_delimiter @account.statuses_count @@ -178,18 +178,8 @@ = @account.shared_inbox_url = fa_icon DeliveryFailureTracker.available?(@account.shared_inbox_url) ? 'check': 'times' - %div{ style: 'overflow: hidden' } - %div{ style: 'float: right' } - - if @account.local? - = link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' if can?(:reset_password, @account.user) - - if @account.user&.otp_required_for_login? - = link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user) - - if !@account.memorial? && @account.user_approved? - = link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, @account) - - else - = link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account) - - %div{ style: 'float: left' } + %div.action-buttons + %div - if @account.local? && @account.user_approved? = link_to t('admin.accounts.warn'), new_admin_account_action_path(@account.id, type: 'none'), class: 'button' if can?(:warn, @account) - if @account.silenced? @@ -216,6 +206,16 @@ - else = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @account.domain), class: 'button button--destructive' + %div + - if @account.local? + = link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' if can?(:reset_password, @account.user) + - if @account.user&.otp_required_for_login? + = link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user) + - if !@account.memorial? && @account.user_approved? + = link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, @account) + - else + = link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account) + %hr.spacer/ - unless @warnings.empty? diff --git a/app/views/admin/instances/index.html.haml b/app/views/admin/instances/index.html.haml index 0b299acc5..bd67eb4fc 100644 --- a/app/views/admin/instances/index.html.haml +++ b/app/views/admin/instances/index.html.haml @@ -10,7 +10,7 @@ - unless whitelist_mode? %li= filter_link_to t('admin.instances.moderation.limited'), limited: '1' - %div{ style: 'flex: 1 1 auto; text-align: right' } + %div.special-action-button - if whitelist_mode? = link_to t('admin.domain_allows.add_new'), new_admin_domain_allow_path, class: 'button' - else diff --git a/app/views/admin/instances/show.html.haml b/app/views/admin/instances/show.html.haml index 49a666a5a..92e14c0df 100644 --- a/app/views/admin/instances/show.html.haml +++ b/app/views/admin/instances/show.html.haml @@ -45,11 +45,11 @@ %hr.spacer/ -%div{ style: 'overflow: hidden' } - %div{ style: 'float: left' } +%div.action-buttons + %div = link_to t('admin.accounts.title'), admin_accounts_path(remote: '1', by_domain: @instance.domain), class: 'button' - %div{ style: 'float: right' } + %div - if @domain_allow = link_to t('admin.domain_allows.undo'), admin_domain_allow_path(@domain_allow), class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete } - elsif @domain_block diff --git a/app/views/admin/pending_accounts/index.html.haml b/app/views/admin/pending_accounts/index.html.haml index 171976e33..8101d7f99 100644 --- a/app/views/admin/pending_accounts/index.html.haml +++ b/app/views/admin/pending_accounts/index.html.haml @@ -22,9 +22,9 @@ %hr.spacer/ -%div{ style: 'overflow: hidden' } - %div{ style: 'float: right' } - = link_to t('admin.accounts.reject_all'), reject_all_admin_pending_accounts_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' - +%div.action-buttons %div = link_to t('admin.accounts.approve_all'), approve_all_admin_pending_accounts_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' + + %div + = link_to t('admin.accounts.reject_all'), reject_all_admin_pending_accounts_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' diff --git a/app/views/admin/relationships/index.html.haml b/app/views/admin/relationships/index.html.haml index 3afaff615..907477f24 100644 --- a/app/views/admin/relationships/index.html.haml +++ b/app/views/admin/relationships/index.html.haml @@ -17,7 +17,7 @@ %li= filter_link_to t('admin.accounts.location.local'), location: 'local' %li= filter_link_to t('admin.accounts.location.remote'), location: 'remote' - .back-link{ style: 'flex: 1 1 auto; text-align: right' } + .back-link = link_to admin_account_path(@account.id) do = fa_icon 'chevron-left fw' = t('admin.statuses.back_to_account') diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml index b12ea4270..4ecc8dc93 100644 --- a/app/views/admin/reports/show.html.haml +++ b/app/views/admin/reports/show.html.haml @@ -65,9 +65,11 @@ %hr.spacer -%div{ style: 'overflow: hidden; margin-bottom: 20px; clear: both' } +%div.action-buttons + %div + - if @report.unresolved? - %div{ style: 'float: right' } + %div - if @report.target_account.local? = link_to t('admin.accounts.warn'), new_admin_account_action_path(@report.target_account_id, type: 'none', report_id: @report.id), class: 'button' = link_to t('admin.accounts.disable'), new_admin_account_action_path(@report.target_account_id, type: 'disable', report_id: @report.id), class: 'button button--destructive' diff --git a/app/views/admin/statuses/index.html.haml b/app/views/admin/statuses/index.html.haml index 55926f3b3..5414d69d5 100644 --- a/app/views/admin/statuses/index.html.haml +++ b/app/views/admin/statuses/index.html.haml @@ -9,7 +9,7 @@ %ul %li= link_to t('admin.statuses.no_media'), admin_account_statuses_path(@account.id, current_params.merge(media: nil)), class: !params[:media] && 'selected' %li= link_to t('admin.statuses.with_media'), admin_account_statuses_path(@account.id, current_params.merge(media: true)), class: params[:media] && 'selected' - .back-link{ style: 'flex: 1 1 auto; text-align: right' } + .back-link = link_to admin_account_path(@account.id) do = fa_icon 'chevron-left fw' = t('admin.statuses.back_to_account') diff --git a/app/views/admin/statuses/show.html.haml b/app/views/admin/statuses/show.html.haml index a7a392272..e2470198d 100644 --- a/app/views/admin/statuses/show.html.haml +++ b/app/views/admin/statuses/show.html.haml @@ -4,7 +4,7 @@ = "@#{@account.acct}" .filters - .back-link{ style: 'flex: 1 1 auto; text-align: right' } + .back-link = link_to admin_account_path(@account.id) do %i.fa.fa-chevron-left.fa-fw = t('admin.statuses.back_to_account') diff --git a/app/views/admin/tags/index.html.haml b/app/views/admin/tags/index.html.haml index d20ed80f8..e64802275 100644 --- a/app/views/admin/tags/index.html.haml +++ b/app/views/admin/tags/index.html.haml @@ -68,9 +68,9 @@ - if params[:pending_review] == '1' || params[:unreviewed] == '1' %hr.spacer/ - %div{ style: 'overflow: hidden' } - %div{ style: 'float: right' } - = link_to t('admin.accounts.reject_all'), reject_all_admin_tags_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' - + %div.action-buttons %div = link_to t('admin.accounts.approve_all'), approve_all_admin_tags_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' + + %div + = link_to t('admin.accounts.reject_all'), reject_all_admin_tags_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' diff --git a/app/views/application/_card.html.haml b/app/views/application/_card.html.haml index 808dce514..e7ecfecd9 100644 --- a/app/views/application/_card.html.haml +++ b/app/views/application/_card.html.haml @@ -9,7 +9,6 @@ = image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo' .display-name - %span{ id: "default_account_display_name", style: "display: none" }= account.username %bdi %strong.emojify.p-name= display_name(account, custom_emojify: true) %span diff --git a/app/views/auth/registrations/new.html.haml b/app/views/auth/registrations/new.html.haml index bcd66fb8a..457bc1d23 100644 --- a/app/views/auth/registrations/new.html.haml +++ b/app/views/auth/registrations/new.html.haml @@ -8,8 +8,8 @@ = render 'shared/error_messages', object: resource - if @invite.present? && @invite.autofollow? - .fields-group{ style: 'margin-bottom: 30px' } - %p.hint{ style: 'text-align: center' }= t('invites.invited_by') + .fields-group.invited-by + %p.hint= t('invites.invited_by') = render 'application/card', account: @invite.user.account = f.simple_fields_for :account do |ff| diff --git a/app/views/auth/sessions/two_factor.html.haml b/app/views/auth/sessions/two_factor.html.haml index 4e6bbd7a9..b2e36f6bc 100644 --- a/app/views/auth/sessions/two_factor.html.haml +++ b/app/views/auth/sessions/two_factor.html.haml @@ -2,7 +2,7 @@ = t('auth.login') = simple_form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f| - %p.hint{ style: 'margin-bottom: 25px' }= t('simple_form.hints.sessions.otp') + %p.hint.otp-hint= t('simple_form.hints.sessions.otp') .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 => 'off' }, autofocus: true diff --git a/app/views/directories/index.html.haml b/app/views/directories/index.html.haml index ecf12b649..1170332ff 100644 --- a/app/views/directories/index.html.haml +++ b/app/views/directories/index.html.haml @@ -28,7 +28,6 @@ = image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo' .display-name - %span{ id: "default_account_display_name", style: "display: none" }= account.username %bdi %strong.emojify.p-name= display_name(account, custom_emojify: true) %span= acct(account) diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 2be9427c5..99ab3729e 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -40,6 +40,6 @@ %body{ class: body_classes } = content_for?(:content) ? yield(:content) : yield - %div{ style: 'display: none'} + .logo-resources = render file: Rails.root.join('app', 'javascript', 'images', 'logo_transparent.svg') = render file: Rails.root.join('app', 'javascript', 'images', 'logo_full.svg') diff --git a/app/views/layouts/embedded.html.haml b/app/views/layouts/embedded.html.haml index 6695b12dd..75441b452 100644 --- a/app/views/layouts/embedded.html.haml +++ b/app/views/layouts/embedded.html.haml @@ -23,5 +23,5 @@ %body.embed = yield - %div{ style: 'display: none'} + .logo-resources = render file: Rails.root.join('app', 'javascript', 'images', 'logo_transparent.svg') diff --git a/app/views/public_timelines/show.html.haml b/app/views/public_timelines/show.html.haml index 063089a7f..e32bd49ec 100644 --- a/app/views/public_timelines/show.html.haml +++ b/app/views/public_timelines/show.html.haml @@ -12,5 +12,5 @@ - else %p= t('about.browse_local_posts') -#mastodon-timeline{ data: { props: Oj.dump(default_props) }} +#mastodon-timeline{ data: { props: Oj.dump(default_props.merge(local: !Setting.show_known_fediverse_at_about_page)) }} #modal-container diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml index 5453177fd..5fc865814 100644 --- a/app/views/settings/preferences/appearance/show.html.haml +++ b/app/views/settings/preferences/appearance/show.html.haml @@ -9,8 +9,8 @@ = f.input :locale, collection: I18n.available_locales, wrapper: :with_label, include_blank: false, label_method: lambda { |locale| human_locale(locale) }, selected: I18n.locale, hint: false - unless I18n.locale == :en - .flash-message{ style: "text-align: unset; color: unset" } - #{t 'appearance.localization.body'} #{content_tag(:a, t('appearance.localization.guide_link_text'), href: t('appearance.localization.guide_link'), target: "_blank", rel: "noopener", style: "text-decoration: underline")} + .flash-message.translation-prompt + #{t 'appearance.localization.body'} #{content_tag(:a, t('appearance.localization.guide_link_text'), href: t('appearance.localization.guide_link'), target: "_blank", rel: "noopener")} %h4= t 'appearance.advanced_web_interface' diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml index 841c01fd7..6061e9cfd 100644 --- a/app/views/settings/profiles/show.html.haml +++ b/app/views/settings/profiles/show.html.haml @@ -9,7 +9,7 @@ .fields-row .fields-row__column.fields-group.fields-row__column-6 - = f.input :display_name, wrapper: :with_label, input_html: { maxlength: Account::MAX_DISPLAY_NAME_LENGTH }, hint: false + = f.input :display_name, wrapper: :with_label, input_html: { maxlength: Account::MAX_DISPLAY_NAME_LENGTH, data: { default: @account.username } }, hint: false = f.input :note, wrapper: :with_label, input_html: { maxlength: Account::MAX_NOTE_LENGTH }, hint: false .fields-row diff --git a/app/views/statuses/_detailed_status.html.haml b/app/views/statuses/_detailed_status.html.haml index 021390e47..544b92330 100644 --- a/app/views/statuses/_detailed_status.html.haml +++ b/app/views/statuses/_detailed_status.html.haml @@ -15,12 +15,12 @@ = account_action_button(status.account) - .status__content.emojify< + .status__content.emojify{ :data => ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }< - if status.spoiler_text? - %p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }< + %p< %span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)} %button.status__content__spoiler-link= t('statuses.show_more') - .e-content{ style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" } + .e-content{ dir: rtl_status?(status) ? 'rtl' : 'ltr' } = Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay) - if status.preloadable_poll = react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do diff --git a/app/views/statuses/_poll.html.haml b/app/views/statuses/_poll.html.haml index de5357e6d..64e62e97c 100644 --- a/app/views/statuses/_poll.html.haml +++ b/app/views/statuses/_poll.html.haml @@ -10,13 +10,15 @@ - percent = total_votes_count > 0 ? 100 * option.votes_count / total_votes_count : 0 %label.poll__option>< %span.poll__number>< - - if own_votes.include?(index) - %i.poll__voted__mark.fa.fa-check = "#{percent.round}%" %span.poll__option__text = Formatter.instance.format_poll_option(status, option, autoplay: autoplay) + - if own_votes.include?(index) + %span.poll__voted + %i.poll__voted__mark.fa.fa-check - %span.poll__chart{ style: "width: #{percent}%" } + %progress{ max: 100, value: percent < 1 ? 1 : percent, 'aria-hidden': 'true' } + %span.poll__chart - else %label.poll__option>< %span.poll__input{ class: poll.multiple? ? 'checkbox' : nil}>< diff --git a/app/views/statuses/_simple_status.html.haml b/app/views/statuses/_simple_status.html.haml index 8a418a1d5..f959056cd 100644 --- a/app/views/statuses/_simple_status.html.haml +++ b/app/views/statuses/_simple_status.html.haml @@ -19,12 +19,12 @@ %span.display-name__account = acct(status.account) = fa_icon('lock') if status.account.locked? - .status__content.emojify< + .status__content.emojify{ :data => ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }< - if status.spoiler_text? - %p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }< + %p< %span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)} %button.status__content__spoiler-link= t('statuses.show_more') - .e-content{ style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }< + .e-content{ dir: rtl_status?(status) ? 'rtl' : 'ltr' }< = Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay) - if status.preloadable_poll = react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do @@ -51,18 +51,18 @@ .status__action-bar .status__action-bar__counter - = link_to remote_interaction_path(status, type: :reply), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do + = link_to remote_interaction_path(status, type: :reply), class: 'status__action-bar-button icon-button modal-button' do - if status.in_reply_to_id.nil? = fa_icon 'reply fw' - else = fa_icon 'reply-all fw' .status__action-bar__counter__label= obscured_counter status.replies_count - = link_to remote_interaction_path(status, type: :reblog), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do + = link_to remote_interaction_path(status, type: :reblog), class: 'status__action-bar-button icon-button modal-button' do - if status.distributable? = fa_icon 'retweet fw' - elsif status.private_visibility? || status.limited_visibility? = fa_icon 'lock fw' - else = fa_icon 'envelope fw' - = link_to remote_interaction_path(status, type: :favourite), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do + = link_to remote_interaction_path(status, type: :favourite), class: 'status__action-bar-button icon-button modal-button' do = fa_icon 'star fw' |