diff options
author | Minseo Lee <itoupluk427@gmail.com> | 2024-02-29 13:05:45 +0900 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-02-29 13:05:45 +0900 |
commit | 200c4c1d379e591e82d6d1bd065a443f6abc03f5 (patch) | |
tree | db7257f0178b2d9514642a7faf3e003d60d2b418 /src | |
parent | a1127bfcfc7ad080a5bd6210c6561788f1643db8 (diff) | |
parent | a35976cdc9b6467ad8b6e0c4ff46ba684fee9064 (diff) | |
download | voidsky-200c4c1d379e591e82d6d1bd065a443f6abc03f5.tar.zst |
Merge branch 'bluesky-social:main' into patch-3
Diffstat (limited to 'src')
-rw-r--r-- | src/components/Dialog/context.ts | 13 | ||||
-rw-r--r-- | src/components/Dialog/index.tsx | 8 | ||||
-rw-r--r-- | src/components/Dialog/index.web.tsx | 8 | ||||
-rw-r--r-- | src/components/Dialog/types.ts | 19 | ||||
-rw-r--r-- | src/lib/__tests__/moderatePost_wrapped.test.ts | 89 | ||||
-rw-r--r-- | src/lib/moderatePost_wrapped.ts | 9 | ||||
-rw-r--r-- | src/state/dialogs/index.tsx | 28 | ||||
-rw-r--r-- | src/state/util.ts | 8 |
8 files changed, 162 insertions, 20 deletions
diff --git a/src/components/Dialog/context.ts b/src/components/Dialog/context.ts index f0c7c983a..eb717d8e2 100644 --- a/src/components/Dialog/context.ts +++ b/src/components/Dialog/context.ts @@ -3,7 +3,7 @@ import React from 'react' import {useDialogStateContext} from '#/state/dialogs' import { DialogContextProps, - DialogControlProps, + DialogControlRefProps, DialogOuterProps, } from '#/components/Dialog/types' @@ -17,7 +17,7 @@ export function useDialogContext() { export function useDialogControl(): DialogOuterProps['control'] { const id = React.useId() - const control = React.useRef<DialogControlProps>({ + const control = React.useRef<DialogControlRefProps>({ open: () => {}, close: () => {}, }) @@ -32,8 +32,13 @@ export function useDialogControl(): DialogOuterProps['control'] { }, [id, activeDialogs]) return { + id, ref: control, - open: () => control.current.open(), - close: cb => control.current.close(cb), + open: () => { + control.current.open() + }, + close: cb => { + control.current.close(cb) + }, } } diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx index 5c0350274..6dfc24f3b 100644 --- a/src/components/Dialog/index.tsx +++ b/src/components/Dialog/index.tsx @@ -12,6 +12,7 @@ import {useTheme, atoms as a, flatten} from '#/alf' import {Portal} from '#/components/Portal' import {createInput} from '#/components/forms/TextField' import {logger} from '#/logger' +import {useDialogStateContext} from '#/state/dialogs' import { DialogOuterProps, @@ -37,6 +38,7 @@ export function Outer({ const hasSnapPoints = !!sheetOptions.snapPoints const insets = useSafeAreaInsets() const closeCallback = React.useRef<() => void>() + const {openDialogs} = useDialogStateContext() /* * Used to manage open/closed, but index is otherwise handled internally by `BottomSheet` @@ -50,10 +52,11 @@ export function Outer({ const open = React.useCallback<DialogControlProps['open']>( ({index} = {}) => { + openDialogs.current.add(control.id) // can be set to any index of `snapPoints`, but `0` is the first i.e. "open" setOpenIndex(index || 0) }, - [setOpenIndex], + [setOpenIndex, openDialogs, control.id], ) const close = React.useCallback<DialogControlProps['close']>(cb => { @@ -85,11 +88,12 @@ export function Outer({ closeCallback.current = undefined } + openDialogs.current.delete(control.id) onClose?.() setOpenIndex(-1) } }, - [onClose, setOpenIndex], + [onClose, setOpenIndex, openDialogs, control.id], ) const context = React.useMemo(() => ({close}), [close]) diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx index ff05fed91..32163e735 100644 --- a/src/components/Dialog/index.web.tsx +++ b/src/components/Dialog/index.web.tsx @@ -12,6 +12,7 @@ import {DialogOuterProps, DialogInnerProps} from '#/components/Dialog/types' import {Context} from '#/components/Dialog/context' import {Button, ButtonIcon} from '#/components/Button' import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' +import {useDialogStateContext} from '#/state/dialogs' export {useDialogControl, useDialogContext} from '#/components/Dialog/context' export * from '#/components/Dialog/types' @@ -29,18 +30,21 @@ export function Outer({ const {gtMobile} = useBreakpoints() const [isOpen, setIsOpen] = React.useState(false) const [isVisible, setIsVisible] = React.useState(true) + const {openDialogs} = useDialogStateContext() const open = React.useCallback(() => { setIsOpen(true) - }, [setIsOpen]) + openDialogs.current.add(control.id) + }, [setIsOpen, openDialogs, control.id]) const close = React.useCallback(async () => { setIsVisible(false) await new Promise(resolve => setTimeout(resolve, 150)) setIsOpen(false) setIsVisible(true) + openDialogs.current.delete(control.id) onClose?.() - }, [onClose, setIsOpen]) + }, [onClose, setIsOpen, openDialogs, control.id]) useImperativeHandle( control.ref, diff --git a/src/components/Dialog/types.ts b/src/components/Dialog/types.ts index 161c03734..78dfedf5a 100644 --- a/src/components/Dialog/types.ts +++ b/src/components/Dialog/types.ts @@ -6,11 +6,24 @@ import {ViewStyleProp} from '#/alf' type A11yProps = Required<AccessibilityProps> -export type DialogControlProps = { +/** + * Mutated by useImperativeHandle to provide a public API for controlling the + * dialog. The methods here will actually become the handlers defined within + * the `Dialog.Outer` component. + */ +export type DialogControlRefProps = { open: (options?: DialogControlOpenOptions) => void close: (callback?: () => void) => void } +/** + * The return type of the useDialogControl hook. + */ +export type DialogControlProps = DialogControlRefProps & { + id: string + ref: React.RefObject<DialogControlRefProps> +} + export type DialogContextProps = { close: DialogControlProps['close'] } @@ -26,9 +39,7 @@ export type DialogControlOpenOptions = { } export type DialogOuterProps = { - control: { - ref: React.RefObject<DialogControlProps> - } & DialogControlProps + control: DialogControlProps onClose?: () => void nativeOptions?: { sheet?: Omit<BottomSheetProps, 'children'> diff --git a/src/lib/__tests__/moderatePost_wrapped.test.ts b/src/lib/__tests__/moderatePost_wrapped.test.ts index c35c1ef77..45566281a 100644 --- a/src/lib/__tests__/moderatePost_wrapped.test.ts +++ b/src/lib/__tests__/moderatePost_wrapped.test.ts @@ -16,6 +16,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: ['outlineTag'], + isOwnPost: false, }) expect(match).toBe(true) @@ -32,6 +33,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: ['outlineTag'], + isOwnPost: false, }) expect(match).toBe(true) @@ -48,6 +50,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: ['outlineTag'], + isOwnPost: false, }) expect(match).toBe(true) @@ -64,6 +67,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(false) @@ -85,6 +89,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -101,6 +106,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(false) @@ -117,6 +123,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -135,6 +142,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -151,6 +159,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(false) @@ -167,6 +176,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -183,6 +193,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -202,6 +213,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -213,6 +225,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -231,6 +244,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -243,6 +257,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -261,6 +276,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -272,6 +288,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -291,6 +308,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -309,6 +327,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -320,6 +339,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -336,6 +356,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(false) @@ -354,6 +375,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -365,6 +387,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -383,6 +406,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -394,6 +418,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -405,6 +430,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -416,6 +442,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(false) @@ -434,6 +461,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -448,6 +476,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(false) @@ -460,6 +489,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -471,6 +501,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(false) @@ -489,6 +520,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -500,6 +532,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -511,6 +544,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -522,6 +556,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -540,6 +575,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -560,6 +596,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -571,6 +608,7 @@ describe(`hasMutedWord`, () => { text: rt.text, facets: rt.facets, outlineTags: [], + isOwnPost: false, }) expect(match).toBe(true) @@ -594,10 +632,61 @@ describe(`hasMutedWord`, () => { facets: rt.facets, outlineTags: [], languages: ['ja'], + isOwnPost: false, }) expect(match).toBe(true) }) }) }) + + describe(`doesn't mute own post`, () => { + it(`does mute if it isn't own post`, () => { + const rt = new RichText({ + text: `Mute words!`, + }) + + const match = hasMutedWord({ + mutedWords: [{value: 'words', targets: ['content']}], + text: rt.text, + facets: rt.facets, + outlineTags: [], + isOwnPost: false, + }) + + expect(match).toBe(true) + }) + + it(`doesn't mute own post when muted word is in text`, () => { + const rt = new RichText({ + text: `Mute words!`, + }) + + const match = hasMutedWord({ + mutedWords: [{value: 'words', targets: ['content']}], + text: rt.text, + facets: rt.facets, + outlineTags: [], + isOwnPost: true, + }) + + expect(match).toBe(false) + }) + + it(`doesn't mute own post when muted word is in tags`, () => { + const rt = new RichText({ + text: `Mute #words!`, + }) + + const match = hasMutedWord({ + mutedWords: [{value: 'words', targets: ['tags']}], + text: rt.text, + facets: rt.facets, + outlineTags: [], + isOwnPost: true, + }) + + expect(match).toBe(false) + }) + }) }) diff --git a/src/lib/moderatePost_wrapped.ts b/src/lib/moderatePost_wrapped.ts index 428dbabf4..92543b42c 100644 --- a/src/lib/moderatePost_wrapped.ts +++ b/src/lib/moderatePost_wrapped.ts @@ -41,13 +41,17 @@ export function hasMutedWord({ facets, outlineTags, languages, + isOwnPost, }: { mutedWords: AppBskyActorDefs.MutedWord[] text: string facets?: AppBskyRichtextFacet.Main[] outlineTags?: string[] languages?: string[] + isOwnPost: boolean }) { + if (isOwnPost) return false + const exception = LANGUAGE_EXCEPTIONS.includes(languages?.[0] || '') const tags = ([] as string[]) .concat(outlineTags || []) @@ -142,6 +146,7 @@ export function moderatePost_wrapped( ) { const {hiddenPosts = [], mutedWords = [], ...options} = opts const moderations = moderatePost(subject, options) + const isOwnPost = subject.author.did === opts.userDid if (hiddenPosts.includes(subject.uri)) { moderations.content.filter = true @@ -163,6 +168,7 @@ export function moderatePost_wrapped( facets: subject.record.facets || [], outlineTags: subject.record.tags || [], languages: subject.record.langs, + isOwnPost, }) if ( @@ -178,6 +184,7 @@ export function moderatePost_wrapped( facets: [], outlineTags: [], languages: subject.record.langs, + isOwnPost, }) } } @@ -210,6 +217,7 @@ export function moderatePost_wrapped( facets: subject.embed.record.value.facets, outlineTags: subject.embed.record.value.tags, languages: subject.embed.record.value.langs, + isOwnPost, }) if (AppBskyEmbedImages.isMain(subject.embed.record.value.embed)) { @@ -222,6 +230,7 @@ export function moderatePost_wrapped( facets: [], outlineTags: [], languages: subject.embed.record.value.langs, + isOwnPost, }) } } diff --git a/src/state/dialogs/index.tsx b/src/state/dialogs/index.tsx index ae762bd97..9fc70c178 100644 --- a/src/state/dialogs/index.tsx +++ b/src/state/dialogs/index.tsx @@ -1,21 +1,32 @@ import React from 'react' -import {DialogControlProps} from '#/components/Dialog' +import {DialogControlRefProps} from '#/components/Dialog' import {Provider as GlobalDialogsProvider} from '#/components/dialogs/Context' const DialogContext = React.createContext<{ + /** + * The currently active `useDialogControl` hooks. + */ activeDialogs: React.MutableRefObject< - Map<string, React.MutableRefObject<DialogControlProps>> + Map<string, React.MutableRefObject<DialogControlRefProps>> > + /** + * The currently open dialogs, referenced by their IDs, generated from + * `useId`. + */ + openDialogs: React.MutableRefObject<Set<string>> }>({ activeDialogs: { current: new Map(), }, + openDialogs: { + current: new Set(), + }, }) const DialogControlContext = React.createContext<{ - closeAllDialogs(): void + closeAllDialogs(): boolean }>({ - closeAllDialogs: () => {}, + closeAllDialogs: () => false, }) export function useDialogStateContext() { @@ -28,13 +39,18 @@ export function useDialogStateControlContext() { export function Provider({children}: React.PropsWithChildren<{}>) { const activeDialogs = React.useRef< - Map<string, React.MutableRefObject<DialogControlProps>> + Map<string, React.MutableRefObject<DialogControlRefProps>> >(new Map()) + const openDialogs = React.useRef<Set<string>>(new Set()) + const closeAllDialogs = React.useCallback(() => { activeDialogs.current.forEach(dialog => dialog.current.close()) + return openDialogs.current.size > 0 }, []) - const context = React.useMemo(() => ({activeDialogs}), []) + + const context = React.useMemo(() => ({activeDialogs, openDialogs}), []) const controls = React.useMemo(() => ({closeAllDialogs}), [closeAllDialogs]) + return ( <DialogContext.Provider value={context}> <DialogControlContext.Provider value={controls}> diff --git a/src/state/util.ts b/src/state/util.ts index 7b49b5b46..f65d14a84 100644 --- a/src/state/util.ts +++ b/src/state/util.ts @@ -3,7 +3,7 @@ import {useLightboxControls} from './lightbox' import {useModalControls} from './modals' import {useComposerControls} from './shell/composer' import {useSetDrawerOpen} from './shell/drawer-open' -import {useDialogStateControlContext} from 'state/dialogs' +import {useDialogStateControlContext} from '#/state/dialogs' /** * returns true if something was closed @@ -13,6 +13,7 @@ export function useCloseAnyActiveElement() { const {closeLightbox} = useLightboxControls() const {closeModal} = useModalControls() const {closeComposer} = useComposerControls() + const {closeAllDialogs} = useDialogStateControlContext() const setDrawerOpen = useSetDrawerOpen() return useCallback(() => { if (closeLightbox()) { @@ -24,9 +25,12 @@ export function useCloseAnyActiveElement() { if (closeComposer()) { return true } + if (closeAllDialogs()) { + return true + } setDrawerOpen(false) return false - }, [closeLightbox, closeModal, closeComposer, setDrawerOpen]) + }, [closeLightbox, closeModal, closeComposer, setDrawerOpen, closeAllDialogs]) } /** |