- {layout !== 'single-column' && (
-
-
-
- )}
-
+
diff --git a/app/javascript/flavours/glitch/features/account_timeline/hooks/useFieldHtml.tsx b/app/javascript/flavours/glitch/features/account_timeline/hooks/useFieldHtml.tsx
new file mode 100644
index 0000000000..fe7210294b
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/account_timeline/hooks/useFieldHtml.tsx
@@ -0,0 +1,38 @@
+import type { Key } from 'react';
+import { useCallback } from 'react';
+
+import htmlConfig from '@/config/html-tags.json';
+import type { OnElementHandler } from '@/flavours/glitch/utils/html';
+
+export function useFieldHtml(
+ hasCustomEmoji: boolean,
+ onElement?: OnElementHandler,
+): OnElementHandler {
+ return useCallback(
+ (element, props, children, extra) => {
+ if (element instanceof HTMLAnchorElement) {
+ // Don't allow custom emoji and links in the same field to prevent verification spoofing.
+ if (hasCustomEmoji) {
+ return (
+
+ {children}
+
+ );
+ }
+ return onElement?.(element, props, children, extra);
+ }
+ return undefined;
+ },
+ [onElement, hasCustomEmoji],
+ );
+}
+
+function filterAttributesForSpan(props: Record) {
+ const validAttributes: Record = {};
+ for (const key of Object.keys(props)) {
+ if (key in htmlConfig.tags.span.attributes) {
+ validAttributes[key] = props[key];
+ }
+ }
+ return validAttributes;
+}
diff --git a/app/javascript/flavours/glitch/features/account_timeline/modals/field_modal.tsx b/app/javascript/flavours/glitch/features/account_timeline/modals/field_modal.tsx
new file mode 100644
index 0000000000..5522f4ae1e
--- /dev/null
+++ b/app/javascript/flavours/glitch/features/account_timeline/modals/field_modal.tsx
@@ -0,0 +1,44 @@
+import type { FC } from 'react';
+
+import { FormattedMessage } from 'react-intl';
+
+import { EmojiHTML } from '@/flavours/glitch/components/emoji/html';
+
+import type { AccountField } from '../common';
+import { useFieldHtml } from '../hooks/useFieldHtml';
+
+import classes from './styles.module.css';
+
+export const AccountFieldModal: FC<{
+ onClose: () => void;
+ field: AccountField;
+}> = ({ onClose, field }) => {
+ const handleLabelElement = useFieldHtml(field.nameHasEmojis);
+ const handleValueElement = useFieldHtml(field.valueHasEmojis);
+ return (
+
+ );
+};
diff --git a/app/javascript/flavours/glitch/features/account_timeline/modals/note_modal.tsx b/app/javascript/flavours/glitch/features/account_timeline/modals/note_modal.tsx
index 72feedad45..3c55d15024 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/modals/note_modal.tsx
+++ b/app/javascript/flavours/glitch/features/account_timeline/modals/note_modal.tsx
@@ -13,7 +13,7 @@ import { useAppDispatch, useAppSelector } from '@/flavours/glitch/store';
import { ConfirmationModal } from '../../ui/components/confirmation_modals';
-import classes from './modals.module.css';
+import classes from './styles.module.css';
const messages = defineMessages({
newTitle: {
diff --git a/app/javascript/flavours/glitch/features/account_timeline/modals/modals.module.css b/app/javascript/flavours/glitch/features/account_timeline/modals/styles.module.css
similarity index 80%
rename from app/javascript/flavours/glitch/features/account_timeline/modals/modals.module.css
rename to app/javascript/flavours/glitch/features/account_timeline/modals/styles.module.css
index cee0bc498a..4740a42cb9 100644
--- a/app/javascript/flavours/glitch/features/account_timeline/modals/modals.module.css
+++ b/app/javascript/flavours/glitch/features/account_timeline/modals/styles.module.css
@@ -19,3 +19,9 @@
outline: var(--outline-focus-default);
outline-offset: 2px;
}
+
+.fieldValue {
+ color: var(--color-text-primary);
+ font-weight: 600;
+ margin-top: 4px;
+}
diff --git a/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx b/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx
index ef440f84a4..6c0c9d11cc 100644
--- a/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx
+++ b/app/javascript/flavours/glitch/features/ui/components/modal_root.jsx
@@ -100,6 +100,7 @@ export const MODAL_COMPONENTS = {
'ANNUAL_REPORT': AnnualReportModal,
'COMPOSE_PRIVACY': () => Promise.resolve({ default: VisibilityModal }),
'ACCOUNT_NOTE': () => import('@/flavours/glitch/features/account_timeline/modals/note_modal').then(module => ({ default: module.AccountNoteModal })),
+ 'ACCOUNT_FIELD_OVERFLOW': () => import('@/flavours/glitch/features/account_timeline/modals/field_modal').then(module => ({ default: module.AccountFieldModal })),
'ACCOUNT_EDIT_NAME': () => import('@/flavours/glitch/features/account_edit/components/name_modal').then(module => ({ default: module.NameModal })),
'ACCOUNT_EDIT_BIO': () => import('@/flavours/glitch/features/account_edit/components/bio_modal').then(module => ({ default: module.BioModal })),
};
diff --git a/app/javascript/flavours/glitch/features/ui/index.jsx b/app/javascript/flavours/glitch/features/ui/index.jsx
index a75d3c64d2..f434bf7a8e 100644
--- a/app/javascript/flavours/glitch/features/ui/index.jsx
+++ b/app/javascript/flavours/glitch/features/ui/index.jsx
@@ -25,7 +25,7 @@ import { layoutFromWindow } from 'flavours/glitch/is_mobile';
import { selectUnreadNotificationGroupsCount } from 'flavours/glitch/selectors/notifications';
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
import { checkAnnualReport } from '@/flavours/glitch/reducers/slices/annual_report';
-import { isClientFeatureEnabled, isServerFeatureEnabled } from '@/flavours/glitch/utils/environment';
+import { isClientFeatureEnabled } from '@/flavours/glitch/utils/environment';
import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose';
import { clearHeight } from '../../actions/height_cache';
@@ -83,7 +83,6 @@ import {
PrivacyPolicy,
TermsOfService,
AccountFeatured,
- AccountAbout,
AccountEdit,
AccountEditFeaturedTags,
Quotes,
@@ -174,36 +173,6 @@ class SwitchingColumnsArea extends PureComponent {
}
const profileRedesignRoutes = [];
- if (isServerFeatureEnabled('profile_redesign')) {
- profileRedesignRoutes.push(
- ,
- );
- // Check if we're in single-column mode. Confusingly, the singleColumn prop includes mobile.
- if (this.props.layout === 'single-column') {
- // When in single column mode (desktop w/o advanced view), redirect both the root and about to the posts tab.
- profileRedesignRoutes.push(
- ,
- ,
- ,
- ,
- );
- } else {
- // Otherwise, provide and redirect to the /about page.
- profileRedesignRoutes.push(
- ,
- ,
-
- );
- }
- } else {
- profileRedesignRoutes.push(
- ,
- // If the redesign is not enabled but someone shares an /about link, redirect to the root.
- ,
-
- );
- }
-
if (isClientFeatureEnabled('profile_editing')) {
profileRedesignRoutes.push(
,
@@ -265,6 +234,7 @@ class SwitchingColumnsArea extends PureComponent {
{...profileRedesignRoutes}
+
diff --git a/app/javascript/flavours/glitch/features/ui/util/async-components.js b/app/javascript/flavours/glitch/features/ui/util/async-components.js
index f9b32705fe..ad386a8da7 100644
--- a/app/javascript/flavours/glitch/features/ui/util/async-components.js
+++ b/app/javascript/flavours/glitch/features/ui/util/async-components.js
@@ -93,11 +93,6 @@ export function AccountFeatured() {
return import('../../account_featured');
}
-export function AccountAbout() {
- return import('../../account_about')
- .then((module) => ({ default: module.AccountAbout }));
-}
-
export function AccountEdit() {
return import('../../account_edit')
.then((module) => ({ default: module.AccountEdit }));
diff --git a/app/javascript/flavours/glitch/hooks/useObserver.ts b/app/javascript/flavours/glitch/hooks/useObserver.ts
new file mode 100644
index 0000000000..4b979f6d32
--- /dev/null
+++ b/app/javascript/flavours/glitch/hooks/useObserver.ts
@@ -0,0 +1,29 @@
+import { useEffect, useRef } from 'react';
+
+export function useResizeObserver(callback: ResizeObserverCallback) {
+ const observerRef = useRef(null);
+ observerRef.current ??= new ResizeObserver(callback);
+
+ useEffect(() => {
+ const observer = observerRef.current;
+ return () => {
+ observer?.disconnect();
+ };
+ }, []);
+
+ return observerRef.current;
+}
+
+export function useMutationObserver(callback: MutationCallback) {
+ const observerRef = useRef(null);
+ observerRef.current ??= new MutationObserver(callback);
+
+ useEffect(() => {
+ const observer = observerRef.current;
+ return () => {
+ observer?.disconnect();
+ };
+ }, []);
+
+ return observerRef.current;
+}
diff --git a/app/javascript/flavours/glitch/hooks/useOverflow.ts b/app/javascript/flavours/glitch/hooks/useOverflow.ts
index b306fb4871..b85222cf56 100644
--- a/app/javascript/flavours/glitch/hooks/useOverflow.ts
+++ b/app/javascript/flavours/glitch/hooks/useOverflow.ts
@@ -1,6 +1,8 @@
import type { MutableRefObject, RefCallback } from 'react';
import { useState, useRef, useCallback, useEffect } from 'react';
+import { useMutationObserver, useResizeObserver } from './useObserver';
+
/**
* Hook to manage overflow of items in a container with a "more" button.
*
@@ -182,48 +184,30 @@ export function useOverflowObservers({
// This is the item container element.
const listRef = useRef(null);
- // Set up observers to watch for size and content changes.
- const resizeObserverRef = useRef(null);
- const mutationObserverRef = useRef(null);
-
- // Helper to get or create the resize observer.
- const resizeObserver = useCallback(() => {
- const observer = (resizeObserverRef.current ??= new ResizeObserver(
- onRecalculate,
- ));
- return observer;
- }, [onRecalculate]);
+ const resizeObserver = useResizeObserver(onRecalculate);
// Iterate through children and observe them for size changes.
const handleChildrenChange = useCallback(() => {
const listEle = listRef.current;
- const observer = resizeObserver();
-
if (listEle) {
for (const child of listEle.children) {
if (child instanceof HTMLElement) {
- observer.observe(child);
+ resizeObserver.observe(child);
}
}
}
onRecalculate();
}, [onRecalculate, resizeObserver]);
- // Helper to get or create the mutation observer.
- const mutationObserver = useCallback(() => {
- const observer = (mutationObserverRef.current ??= new MutationObserver(
- handleChildrenChange,
- ));
- return observer;
- }, [handleChildrenChange]);
+ const mutationObserver = useMutationObserver(handleChildrenChange);
// Set up observers.
const handleObserve = useCallback(() => {
if (wrapperRef.current) {
- resizeObserver().observe(wrapperRef.current);
+ resizeObserver.observe(wrapperRef.current);
}
if (listRef.current) {
- mutationObserver().observe(listRef.current, { childList: true });
+ mutationObserver.observe(listRef.current, { childList: true });
handleChildrenChange();
}
}, [handleChildrenChange, mutationObserver, resizeObserver]);
@@ -233,12 +217,12 @@ export function useOverflowObservers({
const wrapperRefCallback = useCallback(
(node: HTMLElement | null) => {
if (node) {
- wrapperRef.current = node;
+ wrapperRef.current = node; // eslint-disable-line react-hooks/immutability -- https://github.com/facebook/react/issues/34955
handleObserve();
if (typeof onWrapperRef === 'function') {
onWrapperRef(node);
} else if (onWrapperRef && 'current' in onWrapperRef) {
- onWrapperRef.current = node;
+ onWrapperRef.current = node; // eslint-disable-line react-hooks/immutability -- https://github.com/facebook/react/issues/34955
}
}
},
@@ -254,28 +238,13 @@ export function useOverflowObservers({
if (typeof onListRef === 'function') {
onListRef(node);
} else if (onListRef && 'current' in onListRef) {
- onListRef.current = node;
+ onListRef.current = node; // eslint-disable-line react-hooks/immutability -- https://github.com/facebook/react/issues/34955
}
}
},
[handleObserve, onListRef],
);
- useEffect(() => {
- handleObserve();
-
- return () => {
- if (resizeObserverRef.current) {
- resizeObserverRef.current.disconnect();
- resizeObserverRef.current = null;
- }
- if (mutationObserverRef.current) {
- mutationObserverRef.current.disconnect();
- mutationObserverRef.current = null;
- }
- };
- }, [handleObserve]);
-
return {
wrapperRefCallback,
listRefCallback,