Merge pull request #3442 from ClearlyClaire/glitch-soc/merge-4.5
Merge upstream changes up to f37dc6c59e into stable-4.5
This commit is contained in:
@@ -14,6 +14,10 @@ import { useTimeout } from 'flavours/glitch/hooks/useTimeout';
|
|||||||
const offset = [-12, 4] as OffsetValue;
|
const offset = [-12, 4] as OffsetValue;
|
||||||
const enterDelay = 750;
|
const enterDelay = 750;
|
||||||
const leaveDelay = 150;
|
const leaveDelay = 150;
|
||||||
|
// Only open the card if the mouse was moved within this time,
|
||||||
|
// to avoid triggering the card without intentional mouse movement
|
||||||
|
// (e.g. when content changed underneath the mouse cursor)
|
||||||
|
const activeMovementThreshold = 150;
|
||||||
const popperConfig = { strategy: 'fixed' } as UsePopperOptions;
|
const popperConfig = { strategy: 'fixed' } as UsePopperOptions;
|
||||||
|
|
||||||
const isHoverCardAnchor = (element: HTMLElement) =>
|
const isHoverCardAnchor = (element: HTMLElement) =>
|
||||||
@@ -23,10 +27,10 @@ export const HoverCardController: React.FC = () => {
|
|||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [accountId, setAccountId] = useState<string | undefined>();
|
const [accountId, setAccountId] = useState<string | undefined>();
|
||||||
const [anchor, setAnchor] = useState<HTMLElement | null>(null);
|
const [anchor, setAnchor] = useState<HTMLElement | null>(null);
|
||||||
const isUsingTouchRef = useRef(false);
|
|
||||||
const cardRef = useRef<HTMLDivElement | null>(null);
|
const cardRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [setLeaveTimeout, cancelLeaveTimeout] = useTimeout();
|
const [setLeaveTimeout, cancelLeaveTimeout] = useTimeout();
|
||||||
const [setEnterTimeout, cancelEnterTimeout, delayEnterTimeout] = useTimeout();
|
const [setEnterTimeout, cancelEnterTimeout, delayEnterTimeout] = useTimeout();
|
||||||
|
const [setMoveTimeout, cancelMoveTimeout] = useTimeout();
|
||||||
const [setScrollTimeout] = useTimeout();
|
const [setScrollTimeout] = useTimeout();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
@@ -43,6 +47,8 @@ export const HoverCardController: React.FC = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isScrolling = false;
|
let isScrolling = false;
|
||||||
|
let isUsingTouch = false;
|
||||||
|
let isActiveMouseMovement = false;
|
||||||
let currentAnchor: HTMLElement | null = null;
|
let currentAnchor: HTMLElement | null = null;
|
||||||
let currentTitle: string | null = null;
|
let currentTitle: string | null = null;
|
||||||
|
|
||||||
@@ -64,7 +70,7 @@ export const HoverCardController: React.FC = () => {
|
|||||||
const handleTouchStart = () => {
|
const handleTouchStart = () => {
|
||||||
// Keeping track of touch events to prevent the
|
// Keeping track of touch events to prevent the
|
||||||
// hover card from being displayed on touch devices
|
// hover card from being displayed on touch devices
|
||||||
isUsingTouchRef.current = true;
|
isUsingTouch = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseEnter = (e: MouseEvent) => {
|
const handleMouseEnter = (e: MouseEvent) => {
|
||||||
@@ -76,13 +82,14 @@ export const HoverCardController: React.FC = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bail out if a touch is active
|
// Bail out if we're scrolling, a touch is active,
|
||||||
if (isUsingTouchRef.current) {
|
// or if there was no active mouse movement
|
||||||
|
if (isScrolling || !isActiveMouseMovement || isUsingTouch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We've entered an anchor
|
// We've entered an anchor
|
||||||
if (!isScrolling && isHoverCardAnchor(target)) {
|
if (isHoverCardAnchor(target)) {
|
||||||
cancelLeaveTimeout();
|
cancelLeaveTimeout();
|
||||||
|
|
||||||
currentAnchor?.removeAttribute('aria-describedby');
|
currentAnchor?.removeAttribute('aria-describedby');
|
||||||
@@ -97,10 +104,7 @@ export const HoverCardController: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We've entered the hover card
|
// We've entered the hover card
|
||||||
if (
|
if (target === currentAnchor || target === cardRef.current) {
|
||||||
!isScrolling &&
|
|
||||||
(target === currentAnchor || target === cardRef.current)
|
|
||||||
) {
|
|
||||||
cancelLeaveTimeout();
|
cancelLeaveTimeout();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -139,10 +143,17 @@ export const HoverCardController: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseMove = () => {
|
const handleMouseMove = () => {
|
||||||
if (isUsingTouchRef.current) {
|
if (isUsingTouch) {
|
||||||
isUsingTouchRef.current = false;
|
isUsingTouch = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
delayEnterTimeout(enterDelay);
|
delayEnterTimeout(enterDelay);
|
||||||
|
|
||||||
|
cancelMoveTimeout();
|
||||||
|
isActiveMouseMovement = true;
|
||||||
|
setMoveTimeout(() => {
|
||||||
|
isActiveMouseMovement = false;
|
||||||
|
}, activeMovementThreshold);
|
||||||
};
|
};
|
||||||
|
|
||||||
document.body.addEventListener('touchstart', handleTouchStart, {
|
document.body.addEventListener('touchstart', handleTouchStart, {
|
||||||
@@ -186,6 +197,8 @@ export const HoverCardController: React.FC = () => {
|
|||||||
setOpen,
|
setOpen,
|
||||||
setAccountId,
|
setAccountId,
|
||||||
setAnchor,
|
setAnchor,
|
||||||
|
setMoveTimeout,
|
||||||
|
cancelMoveTimeout,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ import { useTimeout } from 'mastodon/hooks/useTimeout';
|
|||||||
const offset = [-12, 4] as OffsetValue;
|
const offset = [-12, 4] as OffsetValue;
|
||||||
const enterDelay = 750;
|
const enterDelay = 750;
|
||||||
const leaveDelay = 150;
|
const leaveDelay = 150;
|
||||||
|
// Only open the card if the mouse was moved within this time,
|
||||||
|
// to avoid triggering the card without intentional mouse movement
|
||||||
|
// (e.g. when content changed underneath the mouse cursor)
|
||||||
|
const activeMovementThreshold = 150;
|
||||||
const popperConfig = { strategy: 'fixed' } as UsePopperOptions;
|
const popperConfig = { strategy: 'fixed' } as UsePopperOptions;
|
||||||
|
|
||||||
const isHoverCardAnchor = (element: HTMLElement) =>
|
const isHoverCardAnchor = (element: HTMLElement) =>
|
||||||
@@ -23,10 +27,10 @@ export const HoverCardController: React.FC = () => {
|
|||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [accountId, setAccountId] = useState<string | undefined>();
|
const [accountId, setAccountId] = useState<string | undefined>();
|
||||||
const [anchor, setAnchor] = useState<HTMLElement | null>(null);
|
const [anchor, setAnchor] = useState<HTMLElement | null>(null);
|
||||||
const isUsingTouchRef = useRef(false);
|
|
||||||
const cardRef = useRef<HTMLDivElement | null>(null);
|
const cardRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [setLeaveTimeout, cancelLeaveTimeout] = useTimeout();
|
const [setLeaveTimeout, cancelLeaveTimeout] = useTimeout();
|
||||||
const [setEnterTimeout, cancelEnterTimeout, delayEnterTimeout] = useTimeout();
|
const [setEnterTimeout, cancelEnterTimeout, delayEnterTimeout] = useTimeout();
|
||||||
|
const [setMoveTimeout, cancelMoveTimeout] = useTimeout();
|
||||||
const [setScrollTimeout] = useTimeout();
|
const [setScrollTimeout] = useTimeout();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
@@ -43,6 +47,8 @@ export const HoverCardController: React.FC = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isScrolling = false;
|
let isScrolling = false;
|
||||||
|
let isUsingTouch = false;
|
||||||
|
let isActiveMouseMovement = false;
|
||||||
let currentAnchor: HTMLElement | null = null;
|
let currentAnchor: HTMLElement | null = null;
|
||||||
let currentTitle: string | null = null;
|
let currentTitle: string | null = null;
|
||||||
|
|
||||||
@@ -64,7 +70,7 @@ export const HoverCardController: React.FC = () => {
|
|||||||
const handleTouchStart = () => {
|
const handleTouchStart = () => {
|
||||||
// Keeping track of touch events to prevent the
|
// Keeping track of touch events to prevent the
|
||||||
// hover card from being displayed on touch devices
|
// hover card from being displayed on touch devices
|
||||||
isUsingTouchRef.current = true;
|
isUsingTouch = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseEnter = (e: MouseEvent) => {
|
const handleMouseEnter = (e: MouseEvent) => {
|
||||||
@@ -76,13 +82,14 @@ export const HoverCardController: React.FC = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bail out if a touch is active
|
// Bail out if we're scrolling, a touch is active,
|
||||||
if (isUsingTouchRef.current) {
|
// or if there was no active mouse movement
|
||||||
|
if (isScrolling || !isActiveMouseMovement || isUsingTouch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We've entered an anchor
|
// We've entered an anchor
|
||||||
if (!isScrolling && isHoverCardAnchor(target)) {
|
if (isHoverCardAnchor(target)) {
|
||||||
cancelLeaveTimeout();
|
cancelLeaveTimeout();
|
||||||
|
|
||||||
currentAnchor?.removeAttribute('aria-describedby');
|
currentAnchor?.removeAttribute('aria-describedby');
|
||||||
@@ -97,10 +104,7 @@ export const HoverCardController: React.FC = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// We've entered the hover card
|
// We've entered the hover card
|
||||||
if (
|
if (target === currentAnchor || target === cardRef.current) {
|
||||||
!isScrolling &&
|
|
||||||
(target === currentAnchor || target === cardRef.current)
|
|
||||||
) {
|
|
||||||
cancelLeaveTimeout();
|
cancelLeaveTimeout();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -139,10 +143,17 @@ export const HoverCardController: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleMouseMove = () => {
|
const handleMouseMove = () => {
|
||||||
if (isUsingTouchRef.current) {
|
if (isUsingTouch) {
|
||||||
isUsingTouchRef.current = false;
|
isUsingTouch = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
delayEnterTimeout(enterDelay);
|
delayEnterTimeout(enterDelay);
|
||||||
|
|
||||||
|
cancelMoveTimeout();
|
||||||
|
isActiveMouseMovement = true;
|
||||||
|
setMoveTimeout(() => {
|
||||||
|
isActiveMouseMovement = false;
|
||||||
|
}, activeMovementThreshold);
|
||||||
};
|
};
|
||||||
|
|
||||||
document.body.addEventListener('touchstart', handleTouchStart, {
|
document.body.addEventListener('touchstart', handleTouchStart, {
|
||||||
@@ -186,6 +197,8 @@ export const HoverCardController: React.FC = () => {
|
|||||||
setOpen,
|
setOpen,
|
||||||
setAccountId,
|
setAccountId,
|
||||||
setAnchor,
|
setAnchor,
|
||||||
|
setMoveTimeout,
|
||||||
|
cancelMoveTimeout,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ class Request
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
signature_value = @signing.sign(signed_headers.without('User-Agent', 'Accept-Encoding'), @verb, Addressable::URI.parse(request.uri))
|
signature_value = @signing.sign(signed_headers.without('User-Agent', 'Accept-Encoding', 'Accept'), @verb, Addressable::URI.parse(request.uri))
|
||||||
request.headers['Signature'] = signature_value
|
request.headers['Signature'] = signature_value
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,10 @@ class AccountMigration < ApplicationRecord
|
|||||||
before_validation :set_target_account
|
before_validation :set_target_account
|
||||||
before_validation :set_followers_count
|
before_validation :set_followers_count
|
||||||
|
|
||||||
|
attribute :current_username, :string
|
||||||
|
|
||||||
normalizes :acct, with: ->(acct) { acct.strip.delete_prefix('@') }
|
normalizes :acct, with: ->(acct) { acct.strip.delete_prefix('@') }
|
||||||
|
normalizes :current_username, with: ->(value) { value.strip.delete_prefix('@') }
|
||||||
|
|
||||||
validates :acct, presence: true, domain: { acct: true }
|
validates :acct, presence: true, domain: { acct: true }
|
||||||
validate :validate_migration_cooldown
|
validate :validate_migration_cooldown
|
||||||
@@ -33,7 +36,7 @@ class AccountMigration < ApplicationRecord
|
|||||||
|
|
||||||
scope :within_cooldown, -> { where(created_at: cooldown_duration_ago..) }
|
scope :within_cooldown, -> { where(created_at: cooldown_duration_ago..) }
|
||||||
|
|
||||||
attr_accessor :current_password, :current_username
|
attr_accessor :current_password
|
||||||
|
|
||||||
def self.cooldown_duration_ago
|
def self.cooldown_duration_ago
|
||||||
Time.current - COOLDOWN_PERIOD
|
Time.current - COOLDOWN_PERIOD
|
||||||
|
|||||||
17
config/initializers/fog_connection_cache.rb
Normal file
17
config/initializers/fog_connection_cache.rb
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
if ENV['SWIFT_ENABLED'] == 'true'
|
||||||
|
module PaperclipFogConnectionCache
|
||||||
|
def connection
|
||||||
|
@connection ||= begin
|
||||||
|
key = fog_credentials.hash
|
||||||
|
Thread.current[:paperclip_fog_connections] ||= {}
|
||||||
|
Thread.current[:paperclip_fog_connections][key] ||= ::Fog::Storage.new(fog_credentials)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
Rails.application.config.after_initialize do
|
||||||
|
Paperclip::Storage::Fog.prepend(PaperclipFogConnectionCache)
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -7,6 +7,10 @@ RSpec.describe AccountMigration do
|
|||||||
describe 'acct' do
|
describe 'acct' do
|
||||||
it { is_expected.to normalize(:acct).from(' @username@domain ').to('username@domain') }
|
it { is_expected.to normalize(:acct).from(' @username@domain ').to('username@domain') }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'current_username' do
|
||||||
|
it { is_expected.to normalize(:current_username).from(' @username ').to('username') }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'Validations' do
|
describe 'Validations' do
|
||||||
|
|||||||
@@ -33,20 +33,36 @@ RSpec.describe 'Settings Migrations' do
|
|||||||
end
|
end
|
||||||
|
|
||||||
describe 'Creating migrations' do
|
describe 'Creating migrations' do
|
||||||
let(:user) { Fabricate(:user, password: '12345678') }
|
let(:user) { Fabricate(:user, password:) }
|
||||||
|
let(:password) { '12345678' }
|
||||||
|
|
||||||
before { sign_in(user) }
|
before { sign_in(user) }
|
||||||
|
|
||||||
context 'when migration account is changed' do
|
context 'when migration account is changed' do
|
||||||
let(:acct) { Fabricate(:account, also_known_as: [ActivityPub::TagManager.instance.uri_for(user.account)]) }
|
let(:acct) { Fabricate(:account, also_known_as: [ActivityPub::TagManager.instance.uri_for(user.account)]) }
|
||||||
|
|
||||||
it 'updates moved to account' do
|
context 'when user has encrypted password' do
|
||||||
visit settings_migration_path
|
it 'updates moved to account' do
|
||||||
|
visit settings_migration_path
|
||||||
|
|
||||||
expect { fill_in_and_submit }
|
expect { fill_in_and_submit }
|
||||||
.to(change { user.account.reload.moved_to_account_id }.to(acct.id))
|
.to(change { user.account.reload.moved_to_account_id }.to(acct.id))
|
||||||
expect(page)
|
expect(page)
|
||||||
.to have_content(I18n.t('settings.migrate'))
|
.to have_content(I18n.t('settings.migrate'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when user has blank encrypted password value' do
|
||||||
|
before { user.update! encrypted_password: '' }
|
||||||
|
|
||||||
|
it 'updates moved to account using at-username value' do
|
||||||
|
visit settings_migration_path
|
||||||
|
|
||||||
|
expect { fill_in_and_submit_via_username("@#{user.account.username}") }
|
||||||
|
.to(change { user.account.reload.moved_to_account_id }.to(acct.id))
|
||||||
|
expect(page)
|
||||||
|
.to have_content(I18n.t('settings.migrate'))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -92,8 +108,18 @@ RSpec.describe 'Settings Migrations' do
|
|||||||
|
|
||||||
def fill_in_and_submit
|
def fill_in_and_submit
|
||||||
fill_in 'account_migration_acct', with: acct.username
|
fill_in 'account_migration_acct', with: acct.username
|
||||||
fill_in 'account_migration_current_password', with: '12345678'
|
if block_given?
|
||||||
|
yield
|
||||||
|
else
|
||||||
|
fill_in 'account_migration_current_password', with: password
|
||||||
|
end
|
||||||
click_on I18n.t('migrations.proceed_with_move')
|
click_on I18n.t('migrations.proceed_with_move')
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fill_in_and_submit_via_username(username)
|
||||||
|
fill_in_and_submit do
|
||||||
|
fill_in 'account_migration_current_username', with: username
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user