From 4f60c6845ddf0e602078829e32c0910f0dfeb96f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 6 Oct 2021 18:15:32 +0200 Subject: [PATCH 1/7] Improve account headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- .../features/account/components/header.js | 61 ++++++++++-- app/soapbox/pages/profile_page.js | 11 ++- app/styles/components/account-header.scss | 99 ++++++++++++++++++- app/styles/ui.scss | 4 + 4 files changed, 163 insertions(+), 12 deletions(-) diff --git a/app/soapbox/features/account/components/header.js b/app/soapbox/features/account/components/header.js index 2b8110d95..8f7d86d75 100644 --- a/app/soapbox/features/account/components/header.js +++ b/app/soapbox/features/account/components/header.js @@ -5,6 +5,7 @@ import { connect } from 'react-redux'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import Icon from 'soapbox/components/icon'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { isStaff, @@ -17,14 +18,18 @@ import { } from 'soapbox/utils/accounts'; import classNames from 'classnames'; import Avatar from 'soapbox/components/avatar'; +import { getAcct } from 'soapbox/utils/accounts'; +import { displayFqn } from 'soapbox/utils/state'; import DropdownMenuContainer from 'soapbox/containers/dropdown_menu_container'; import BundleContainer from 'soapbox/features/ui/containers/bundle_container'; import { ProfileInfoPanel } from 'soapbox/features/ui/util/async-components'; -import { debounce } from 'lodash'; +import { debounce, throttle } from 'lodash'; import StillImage from 'soapbox/components/still_image'; import ActionButton from 'soapbox/features/ui/components/action_button'; import SubscriptionButton from 'soapbox/features/ui/components/subscription_button'; import { openModal } from 'soapbox/actions/modal'; +import VerificationBadge from 'soapbox/components/verification_badge'; +import Badge from 'soapbox/components/badge'; import { List as ImmutableList, Map as ImmutableMap } from 'immutable'; import { getFeatures } from 'soapbox/utils/features'; @@ -65,6 +70,8 @@ const messages = defineMessages({ demoteToUser: { id: 'admin.users.actions.demote_to_user', defaultMessage: 'Demote @{name} to a regular user' }, subscribe: { id: 'account.subscribe', defaultMessage: 'Subscribe to notifications from @{name}' }, unsubscribe: { id: 'account.unsubscribe', defaultMessage: 'Unsubscribe to notifications from @{name}' }, + deactivated: { id: 'account.deactivated', defaultMessage: 'Deactivated' }, + bot: { id: 'account.badges.bot', defaultMessage: 'Bot' }, }); const mapStateToProps = state => { @@ -77,6 +84,8 @@ const mapStateToProps = state => { me, meAccount: account, features, + displayFqn: displayFqn(state), + }; }; @@ -91,10 +100,12 @@ class Header extends ImmutablePureComponent { intl: PropTypes.object.isRequired, username: PropTypes.string, features: PropTypes.object, + displayFqn: PropTypes.bool, }; state = { isSmallScreen: (window.innerWidth <= 895), + isLocked: false, } isStatusesPageActive = (match, location) => { @@ -106,19 +117,34 @@ class Header extends ImmutablePureComponent { } componentDidMount() { + window.addEventListener('scroll', this.handleScroll); window.addEventListener('resize', this.handleResize, { passive: true }); } componentWillUnmount() { + window.removeEventListener('scroll', this.handleScroll); window.removeEventListener('resize', this.handleResize); } + setRef = (c) => { + this.node = c; + } + handleResize = debounce(() => { this.setState({ isSmallScreen: (window.innerWidth <= 895) }); }, 5, { trailing: true, }); + handleScroll = throttle(() => { + const { top } = this.node.getBoundingClientRect(); + const isLocked = top <= 60; + + if (this.state.isLocked !== isLocked) { + this.setState({ isLocked }); + } + }, 100, { trailing: true }); + onAvatarClick = () => { const avatar_url = this.props.account.get('avatar'); const avatar = ImmutableMap({ @@ -295,16 +321,18 @@ class Header extends ImmutablePureComponent { } render() { - const { account, username, me, features } = this.props; - const { isSmallScreen } = this.state; + const { account, displayFqn, intl, username, me, features } = this.props; + const { isSmallScreen, isLocked } = this.state; if (!account) { return (
-
+
-
+
+
+
{isSmallScreen && (
@@ -326,6 +354,10 @@ class Header extends ImmutablePureComponent { const avatarSize = isSmallScreen ? 90 : 200; const deactivated = !account.getIn(['pleroma', 'is_active'], true); + const lockedIcon = account.get('locked') ? () : ''; + const displayNameHtml = deactivated ? { __html: intl.formatMessage(messages.deactivated) } : { __html: account.get('display_name_html') }; + const verified = account.getIn(['pleroma', 'tags'], ImmutableList()).includes('verified'); + return (
@@ -342,12 +374,23 @@ class Header extends ImmutablePureComponent {
}
-
+
- - - +
+ + + +
+ +
+ + {verified && } + {account.get('bot') && } + { @{getAcct(account, displayFqn)} {lockedIcon} } +
+
+
{isSmallScreen && (
diff --git a/app/soapbox/pages/profile_page.js b/app/soapbox/pages/profile_page.js index 251bf7190..82657b1a2 100644 --- a/app/soapbox/pages/profile_page.js +++ b/app/soapbox/pages/profile_page.js @@ -19,6 +19,8 @@ import { displayFqn } from 'soapbox/utils/state'; import { getFeatures } from 'soapbox/utils/features'; import { makeGetAccount } from '../selectors'; import { Redirect } from 'react-router-dom'; +import classNames from 'classnames'; + const mapStateToProps = (state, { params, withReplies = false }) => { const username = params.username || ''; @@ -75,13 +77,20 @@ class ProfilePage extends ImmutablePureComponent { return ; } + let headerMissing; + const header = account ? account.get('header', '') : undefined; + + if (header) { + headerMissing = !header || ['/images/banner.png', '/headers/original/missing.png'].some(path => header.endsWith(path)) || !account.getIn(['pleroma', 'is_active'], true); + } + return (
{account && @{getAcct(account, displayFqn)} } -
+
diff --git a/app/styles/components/account-header.scss b/app/styles/components/account-header.scss index 12ca0146c..7b1ff4376 100644 --- a/app/styles/components/account-header.scss +++ b/app/styles/components/account-header.scss @@ -114,15 +114,67 @@ } } - &__avatar { - display: block; + @keyframes fadeIn { + 1% { + visibility: visible; + } + + 100% { + visibility: visible; + } + } + + @keyframes fadeOut { + 1% { + visibility: visible; + } + + 100% { + visibility: hidden; + } + } + + &__card { + display: flex; + flex-direction: column; position: absolute; left: 0; top: -90px; + + &.is-locked { + .account__header__avatar { + top: -130px; + opacity: 0; + animation: 0.3s fadeOut; + animation-fill-mode: forwards; + } + + .account__header__name { + top: 90px; + opacity: 1; + animation: 0.3s fadeIn; + animation-fill-mode: forwards; + } + } + + @media screen and (max-width: 895px) { + top: -45px; + left: 10px; + } + } + + &__avatar { + display: block; + position: absolute; + top: 0; border-radius: 50%; height: 200px; width: 200px; background-color: var(--foreground-color); + opacity: 1; + animation: 0.3s fadeIn; + animation-fill-mode: forwards; + transition: top 0.3s, opacity 0.15s; // NOTE - patch fix for avatar size. Wrapper may not be needed when I do polish up on the page .account__avatar { @@ -163,6 +215,49 @@ } } + &__name { + display: flex; + align-items: center; + column-gap: 10px; + width: 265px; + height: 74px; + position: absolute; + top: 220px; + opacity: 0; + animation: 0.3s fadeOut; + animation-fill-mode: forwards; + transition: top 0.3s, opacity 0.15s; + + div:nth-child(2) { + width: calc(100% - 50px); + color: var(--primary-text-color); + + span:first-of-type { + display: inline-block; + max-width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 18px; + line-height: 1.25; + font-weight: 600; + + &.with-badge { + max-width: calc(100% - 20px); + } + } + + small { + display: block; + font-size: 14px; + line-height: 1.5; + font-weight: 400; + overflow: hidden; + text-overflow: ellipsis; + } + } + } + &__extra { display: flex; flex-direction: row; diff --git a/app/styles/ui.scss b/app/styles/ui.scss index 926900dea..dbad80cc7 100644 --- a/app/styles/ui.scss +++ b/app/styles/ui.scss @@ -351,6 +351,10 @@ @media (min-width: 896px) { top: -290px; position: sticky; + + &__no-header { + top: -75px; + } } } From 2fe8eee31aa6fcc1b234a5a1f4796d9c01d652a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 6 Oct 2021 20:46:03 +0200 Subject: [PATCH 2/7] fix breakpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/styles/components/account-header.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/styles/components/account-header.scss b/app/styles/components/account-header.scss index 7b1ff4376..9234ca7a0 100644 --- a/app/styles/components/account-header.scss +++ b/app/styles/components/account-header.scss @@ -201,7 +201,6 @@ } @media screen and (max-width: 895px) { - top: -45px; left: 20px; left: max(20px + env(safe-area-inset-left)); height: 90px; From 416310d302bb0ce9a9067b2b10daca4f19b09b23 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 19 Oct 2021 14:30:14 -0500 Subject: [PATCH 3/7] AccountHeader: make avatar transition less dramatic --- app/soapbox/features/account/components/header.js | 8 ++++++-- app/styles/components/account-header.scss | 8 ++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/app/soapbox/features/account/components/header.js b/app/soapbox/features/account/components/header.js index 7c1e26954..ba8828809 100644 --- a/app/soapbox/features/account/components/header.js +++ b/app/soapbox/features/account/components/header.js @@ -397,7 +397,6 @@ class Header extends ImmutablePureComponent { const avatarSize = isSmallScreen ? 90 : 200; const deactivated = !account.getIn(['pleroma', 'is_active'], true); - const lockedIcon = account.get('locked') ? () : ''; const displayNameHtml = deactivated ? { __html: intl.formatMessage(messages.deactivated) } : { __html: account.get('display_name_html') }; const verified = account.getIn(['pleroma', 'tags'], ImmutableList()).includes('verified'); @@ -430,7 +429,12 @@ class Header extends ImmutablePureComponent { {verified && } {account.get('bot') && } - { @{getAcct(account, displayFqn)} {lockedIcon} } + + @{getAcct(account, displayFqn)} + {account.get('locked') && ( + + )} +
diff --git a/app/styles/components/account-header.scss b/app/styles/components/account-header.scss index db6efa887..731f0c19b 100644 --- a/app/styles/components/account-header.scss +++ b/app/styles/components/account-header.scss @@ -143,7 +143,7 @@ &.is-locked { .account__header__avatar { - top: -130px; + top: -20px; opacity: 0; animation: 0.3s fadeOut; animation-fill-mode: forwards; @@ -221,7 +221,7 @@ width: 265px; height: 74px; position: absolute; - top: 220px; + top: 100px; opacity: 0; animation: 0.3s fadeOut; animation-fill-mode: forwards; @@ -247,9 +247,9 @@ } small { - display: block; + display: flex; font-size: 14px; - line-height: 1.5; + color: var(--primary-text-color--faint); font-weight: 400; overflow: hidden; text-overflow: ellipsis; From c3a82cb7771b466cefcb0c8bb6b610017f1c10b2 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 24 Jan 2022 17:31:34 -0600 Subject: [PATCH 4/7] StatusContent: delete inline quote from markup --- app/soapbox/components/status_content.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/soapbox/components/status_content.js b/app/soapbox/components/status_content.js index 717071a5c..9aa8b2486 100644 --- a/app/soapbox/components/status_content.js +++ b/app/soapbox/components/status_content.js @@ -75,6 +75,17 @@ class StatusContent extends React.PureComponent { } } + deleteInlineQuote() { + const node = this.node; + if (!node) return; + + const inlineQuote = node.querySelector('.quote-inline'); + + if (inlineQuote) { + inlineQuote.remove(); + } + } + setCollapse() { const node = this.node; @@ -106,6 +117,7 @@ class StatusContent extends React.PureComponent { refresh = () => { this.setCollapse(); this._updateStatusLinks(); + this.deleteInlineQuote(); this.setOnlyEmoji(); } From 240f68a4d612277987c798d62e326a6e9810e733 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Tue, 25 Jan 2022 00:37:44 +0100 Subject: [PATCH 5/7] AccountHeader: remove .page__top__no-header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/styles/ui.scss | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/styles/ui.scss b/app/styles/ui.scss index ac6616a6c..de2ce5e3b 100644 --- a/app/styles/ui.scss +++ b/app/styles/ui.scss @@ -366,10 +366,6 @@ article:last-child > .domain { @media (min-width: 896px) { top: -290px; position: sticky; - - &__no-header { - top: -75px; - } } } From 35ab65efcf2f704acb7ac4739aa7294e2797a071 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 24 Jan 2022 19:14:44 -0600 Subject: [PATCH 6/7] Hide recipients inlined by ForceMentionsInContent MRF --- app/soapbox/components/status_content.js | 10 +++++----- app/styles/components/status.scss | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/soapbox/components/status_content.js b/app/soapbox/components/status_content.js index 9aa8b2486..86b5736c3 100644 --- a/app/soapbox/components/status_content.js +++ b/app/soapbox/components/status_content.js @@ -75,15 +75,15 @@ class StatusContent extends React.PureComponent { } } - deleteInlineQuote() { + stripCompatibilityFeatures() { const node = this.node; if (!node) return; const inlineQuote = node.querySelector('.quote-inline'); + const inlineRecipients = node.querySelector('.recipients-inline'); - if (inlineQuote) { - inlineQuote.remove(); - } + if (inlineQuote) inlineQuote.remove(); + if (inlineRecipients) inlineRecipients.remove(); } setCollapse() { @@ -117,7 +117,7 @@ class StatusContent extends React.PureComponent { refresh = () => { this.setCollapse(); this._updateStatusLinks(); - this.deleteInlineQuote(); + this.stripCompatibilityFeatures(); this.setOnlyEmoji(); } diff --git a/app/styles/components/status.scss b/app/styles/components/status.scss index 8c68f2bd6..b8197d9f6 100644 --- a/app/styles/components/status.scss +++ b/app/styles/components/status.scss @@ -825,7 +825,8 @@ a.status-card.compact:hover { /* Fedibird quote post compatibility */ .status__content, .quoted-status__content { - .quote-inline { + .quote-inline, + .recipients-inline { display: none; } } From 462df8398952999f24ea3aa71198cb881fa665bc Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 24 Jan 2022 20:39:22 -0600 Subject: [PATCH 7/7] Status: strip compatibility features from markup when importing --- app/soapbox/actions/importer/normalizer.js | 4 +++- app/soapbox/components/status_content.js | 12 ------------ app/soapbox/utils/html.js | 20 ++++++++++++++++++++ app/styles/components/status.scss | 9 --------- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/app/soapbox/actions/importer/normalizer.js b/app/soapbox/actions/importer/normalizer.js index c8f1a83ef..66524b556 100644 --- a/app/soapbox/actions/importer/normalizer.js +++ b/app/soapbox/actions/importer/normalizer.js @@ -1,5 +1,7 @@ import escapeTextContentForBrowser from 'escape-html'; +import { stripCompatibilityFeatures } from 'soapbox/utils/html'; + import emojify from '../../features/emoji/emoji'; import { unescapeHTML } from '../../utils/html'; @@ -79,7 +81,7 @@ export function normalizeStatus(status, normalOldStatus, expandSpoilers) { const emojiMap = makeEmojiMap(normalStatus); normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent; - normalStatus.contentHtml = emojify(normalStatus.content, emojiMap); + normalStatus.contentHtml = stripCompatibilityFeatures(emojify(normalStatus.content, emojiMap)); normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap); normalStatus.hidden = expandSpoilers ? false : spoilerText.length > 0 || normalStatus.sensitive; } diff --git a/app/soapbox/components/status_content.js b/app/soapbox/components/status_content.js index 86b5736c3..717071a5c 100644 --- a/app/soapbox/components/status_content.js +++ b/app/soapbox/components/status_content.js @@ -75,17 +75,6 @@ class StatusContent extends React.PureComponent { } } - stripCompatibilityFeatures() { - const node = this.node; - if (!node) return; - - const inlineQuote = node.querySelector('.quote-inline'); - const inlineRecipients = node.querySelector('.recipients-inline'); - - if (inlineQuote) inlineQuote.remove(); - if (inlineRecipients) inlineRecipients.remove(); - } - setCollapse() { const node = this.node; @@ -117,7 +106,6 @@ class StatusContent extends React.PureComponent { refresh = () => { this.setCollapse(); this._updateStatusLinks(); - this.stripCompatibilityFeatures(); this.setOnlyEmoji(); } diff --git a/app/soapbox/utils/html.js b/app/soapbox/utils/html.js index 247e98c88..fec437c03 100644 --- a/app/soapbox/utils/html.js +++ b/app/soapbox/utils/html.js @@ -4,3 +4,23 @@ export const unescapeHTML = (html) => { wrapper.innerHTML = html.replace(//g, '\n').replace(/<\/p>

/g, '\n\n').replace(/<[^>]*>/g, ''); return wrapper.textContent; }; + +export const stripCompatibilityFeatures = html => { + const node = document.createElement('div'); + node.innerHTML = html; + + const selectors = [ + '.quote-inline', + '.recipients-inline', + ]; + + selectors.forEach(selector => { + const elem = node.querySelector(selector); + + if (elem) { + elem.remove(); + } + }); + + return node.innerHTML; +}; diff --git a/app/styles/components/status.scss b/app/styles/components/status.scss index b8197d9f6..9633ae100 100644 --- a/app/styles/components/status.scss +++ b/app/styles/components/status.scss @@ -821,12 +821,3 @@ a.status-card.compact:hover { margin-top: 5px !important; } } - -/* Fedibird quote post compatibility */ -.status__content, -.quoted-status__content { - .quote-inline, - .recipients-inline { - display: none; - } -}