From 63146b25ba98481fcdbb8bbf05041a971589d793 Mon Sep 17 00:00:00 2001 From: Foysal Ahamed Date: Wed, 8 Jan 2025 17:41:28 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Advanced=20tag=20filters=20(#266)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :construction: WIP * :bug: Ignore keybinding for takedown unless user has permission * :sparkles: Remove cmd palette shortcuts * :lipstick: Fix styling for tetris page for dark mode * :lipstick: Fix tag list overflow in profile page * :lipstick: De-emphasize bluesky branding * :sparkles: Add tag filtering with exclusion * :broom: Cleanup * :sparkles: Show filters preview * :sparkles: Add arbitrary tag input --- app/actions/ModActionPanel/QuickAction.tsx | 23 +- app/surprise-me/page.tsx | 38 +-- components/common/SetupModal.tsx | 4 +- components/entertainment/tetris.tsx | 2 +- components/reports/QueueFilter/Language.tsx | 103 ------- components/reports/QueueFilter/Panel.tsx | 66 +++-- .../reports/QueueFilter/SubjectType.tsx | 91 +++---- components/reports/QueueFilter/Tag.tsx | 251 ++++++++++++++++++ components/reports/useQueueFilter.tsx | 82 ++---- components/repositories/AccountView.tsx | 2 +- .../shell/CommandPalette/ResultItem.tsx | 2 +- components/shell/CommandPalette/actions.ts | 9 - components/shell/MobileMenu.tsx | 2 +- components/shell/Shell.tsx | 2 +- components/tags/SubjectTag.tsx | 9 +- package.json | 1 + styles/globals.css | 57 ++++ yarn.lock | 5 + 18 files changed, 451 insertions(+), 298 deletions(-) delete mode 100644 components/reports/QueueFilter/Language.tsx create mode 100644 components/reports/QueueFilter/Tag.tsx diff --git a/app/actions/ModActionPanel/QuickAction.tsx b/app/actions/ModActionPanel/QuickAction.tsx index ee704ecf..ca3a5a30 100644 --- a/app/actions/ModActionPanel/QuickAction.tsx +++ b/app/actions/ModActionPanel/QuickAction.tsx @@ -32,7 +32,7 @@ import { ChevronUpIcon, } from '@heroicons/react/24/outline' import { LabelSelector } from '@/common/labels/Selector' -import { pluralize, takesKeyboardEvt } from '@/lib/util' +import { takesKeyboardEvt } from '@/lib/util' import { Loading } from '@/common/Loader' import { ActionDurationSelector } from '@/reports/ModerationForm/ActionDurationSelector' import { MOD_EVENTS } from '@/mod-event/constants' @@ -201,6 +201,7 @@ function Form( const shouldShowDurationInHoursField = isTakedownEvent || isMuteEvent || isMuteReporterEvent const canManageChat = usePermission('canManageChat') + const canTakedown = usePermission('canTakedown') // navigate to next or prev report const navigateQueue = (delta: 1 | -1) => { @@ -500,7 +501,13 @@ function Form( submitForm() } useKeyPressEvent('c', safeKeyHandler(onCancel)) - useKeyPressEvent('s', safeKeyHandler(submitForm)) + useKeyPressEvent( + 's', + safeKeyHandler((e) => { + e.stopImmediatePropagation() + submitForm() + }), + ) useKeyPressEvent('n', safeKeyHandler(submitAndGoNext)) useKeyPressEvent( 'a', @@ -522,9 +529,11 @@ function Form( ) useKeyPressEvent( 't', - safeKeyHandler(() => { - setModEventType(MOD_EVENTS.TAKEDOWN) - }), + canTakedown + ? safeKeyHandler(() => { + setModEventType(MOD_EVENTS.TAKEDOWN) + }) + : undefined, ) return ( @@ -644,8 +653,8 @@ function Form( {!!subjectStatus?.tags?.length && (
- - {subjectStatus.tags.map((tag) => { + + {subjectStatus.tags.sort().map((tag) => { return })} diff --git a/app/surprise-me/page.tsx b/app/surprise-me/page.tsx index 958a040c..4c546c1c 100644 --- a/app/surprise-me/page.tsx +++ b/app/surprise-me/page.tsx @@ -29,7 +29,7 @@ const Timer = () => { }, 1000) return ( -
+

{getDuration(seconds)}

) @@ -44,41 +44,7 @@ export default function SurpriseMePage() { <> {/* This is valid jsx but because of a known bug, typescript is confused */} {/* @ts-ignore:next-line */} - + diff --git a/components/common/SetupModal.tsx b/components/common/SetupModal.tsx index e043cde6..ddfe3b98 100644 --- a/components/common/SetupModal.tsx +++ b/components/common/SetupModal.tsx @@ -27,12 +27,12 @@ export function SetupModal({ className="mx-auto h-20 w-auto" title="Icon from Flaticon: https://www.flaticon.com/free-icons/lifeguard-tower" src="/img/logo-colorful.png" - alt="Ozone - Bluesky Admin" + alt="Ozone - ATProto Moderation Service" width={200} height={200} />

- Bluesky Admin Tools + Ozone Moderation Service

{title && (

diff --git a/components/entertainment/tetris.tsx b/components/entertainment/tetris.tsx index 253b0791..c78555ee 100644 --- a/components/entertainment/tetris.tsx +++ b/components/entertainment/tetris.tsx @@ -27,7 +27,7 @@ export default function TetrisGame () { state, controller, }) => ( -

+

Points: {points}

diff --git a/components/reports/QueueFilter/Language.tsx b/components/reports/QueueFilter/Language.tsx deleted file mode 100644 index b171e3a4..00000000 --- a/components/reports/QueueFilter/Language.tsx +++ /dev/null @@ -1,103 +0,0 @@ -import { getLanguageName } from '@/lib/locale/helpers' -import { CheckIcon } from '@heroicons/react/20/solid' -import { ActionButton } from '@/common/buttons' -import { availableLanguageCodes } from '@/common/LanguagePicker' -import { useQueueFilter } from '../useQueueFilter' -import { getLanguageFlag } from 'components/tags/SubjectTag' - -// Tags can be any arbitrary string, and lang tags are prefixed with lang:[code2] so we use this to get the lang code from tag string -const getLangFromTag = (tag: string) => tag.split(':')[1] - -export const QueueFilterLanguage = () => { - const { queueFilters, toggleLanguage, clearLanguages } = useQueueFilter() - const includedLanguages = - queueFilters.tags - ?.filter((tag) => tag.startsWith('lang:')) - .map(getLangFromTag) || [] - const excludedLanguages = - queueFilters.excludeTags - ?.filter((tag) => tag.startsWith('lang:')) - .map(getLangFromTag) || [] - - return ( -
-
-

Language Filters

-
-
- toggleLanguage('include', lang)} - /> - toggleLanguage('exclude', lang)} - /> -
- -

- Note:{' '} - - When multiple languages are selected, only subjects that are tagged - with all of those languages will be included/excluded. - -

- {(includedLanguages.length > 0 || excludedLanguages.length > 0) && ( - { - clearLanguages() - }} - > - Clear Language Filters - - )} -
- ) -} - -const LanguageList = ({ - header, - onSelect, - selected = [], - disabled = [], -}: { - selected: string[] - disabled: string[] - header: string - onSelect: (lang: string) => void -}) => { - return ( -
-

- {header} -

-
- {availableLanguageCodes.map((code2) => { - const isDisabled = disabled.includes(code2) - return ( - - ) - })} -
-
- ) -} diff --git a/components/reports/QueueFilter/Panel.tsx b/components/reports/QueueFilter/Panel.tsx index 52658ad7..cf86f357 100644 --- a/components/reports/QueueFilter/Panel.tsx +++ b/components/reports/QueueFilter/Panel.tsx @@ -1,6 +1,5 @@ import { Popover, Transition } from '@headlessui/react' import { ChevronDownIcon } from '@heroicons/react/20/solid' -import { QueueFilterLanguage } from './Language' import { QueueFilterSubjectType } from './SubjectType' import { useSearchParams } from 'next/navigation' import { useQueueFilterBuilder } from '../useQueueFilter' @@ -8,6 +7,34 @@ import { ToolsOzoneModerationQueryStatuses } from '@atproto/api' import { getLanguageFlag } from 'components/tags/SubjectTag' import { getCollectionName } from '../helpers/subject' import { classNames } from '@/lib/util' +import { QueueFilterTags } from './Tag' + +const buildTagFilterSummary = (tags: string[]) => { + const filtered = tags.filter(Boolean) + if (!filtered.length) { + return '' + } + + const list = filtered.map((tag) => { + return tag + .split('&&') + .map((t) => { + t = t.trim() + if (t.startsWith('lang:')) { + const langCode = t.split(':')[1] + return getLanguageFlag(langCode) || t + } + return t + }) + .join(' AND ') + }) + + if (list.length === 1) { + return list[0] + } + + return `(${list.join(') OR (')})` +} // Takes all the queue filters manageable in the panel and displays a summary of selections made const FilterSummary = ({ @@ -17,7 +44,7 @@ const FilterSummary = ({ }) => { const { tags, excludeTags, collections, subjectType } = queueFilters if ( - !tags?.length && + !tags?.filter(Boolean).length && !excludeTags?.length && !collections?.length && !subjectType @@ -36,51 +63,49 @@ const FilterSummary = ({ inclusions.push('Only Records') } - tags?.forEach((tag) => { - if (tag.startsWith('lang:')) { - const langCode = tag.split(':')[1] - inclusions.push(getLanguageFlag(langCode) || langCode) - } - - if (tag.startsWith('embed:')) { - inclusions.push(tag.split(':')[1]) - } - }) + if (tags?.length) { + inclusions.push(buildTagFilterSummary(tags)) + } excludeTags?.forEach((tag) => { if (tag.startsWith('lang:')) { const langCode = tag.split(':')[1] exclusions.push(getLanguageFlag(langCode) || langCode) + return } if (tag.startsWith('embed:')) { exclusions.push(tag.split(':')[1]) + return } + + exclusions.push(tag) }) + return ( <> {!!inclusions.length && inclusions.join(' ')} - {!!exclusions.length && ( + {!!collections?.length && ( - {exclusions.join(' ')} + Collections: {collections.map(getCollectionName).join(', ')} )} - {!!collections?.length && ( + {!!exclusions.length && ( - Collections: {collections.map(getCollectionName).join(', ')} + {exclusions.join(' ')} )} @@ -117,12 +142,13 @@ export const QueueFilterPanel = () => { leaveFrom="transform scale-100 opacity-100" leaveTo="transform scale-95 opacity-0" > - -
+ +
-
+ +
diff --git a/components/reports/QueueFilter/SubjectType.tsx b/components/reports/QueueFilter/SubjectType.tsx index f9ae891c..17865069 100644 --- a/components/reports/QueueFilter/SubjectType.tsx +++ b/components/reports/QueueFilter/SubjectType.tsx @@ -1,32 +1,40 @@ +import { XCircleIcon } from '@heroicons/react/24/solid' import { ButtonGroup } from '@/common/buttons' -import { - CollectionId, - EmbedTypes, - getCollectionName, - getEmbedTypeName, -} from '../helpers/subject' +import { CollectionId, getCollectionName } from '../helpers/subject' import { Checkbox } from '@/common/forms' import { useQueueFilter } from '../useQueueFilter' export const QueueFilterSubjectType = () => { - const { queueFilters, toggleCollection, toggleSubjectType, toggleEmbedType } = - useQueueFilter() - const allEmbedTypes = Object.values(EmbedTypes) + const { + queueFilters, + toggleCollection, + toggleSubjectType, + clearSubjectType, + } = useQueueFilter() const selectedCollections = queueFilters.collections || [] + const hasSubjectTypeFilter = + !!queueFilters.subjectType || !!selectedCollections.length const selectedIncludeEmbedTypes: string[] = queueFilters.tags?.filter((tag) => { return tag.startsWith('embed:') }) || [] - const selectedExcludeEmbedTypes: string[] = - queueFilters.excludeTags?.filter((tag) => { - return tag.startsWith('embed:') - }) || [] return (

- Subject Type Filters +

{ Record Collection - {Object.values(CollectionId).map((collectionId) => { - const isSelected = selectedCollections.includes(collectionId) - return ( - { - toggleCollection(collectionId) - }} - /> - ) - })} -
-
-

- Record Embed -

- {[...allEmbedTypes, 'noEmbed'].map((embedType) => { - const isNoEmbed = embedType === 'noEmbed' - const isSelected = isNoEmbed - ? allEmbedTypes.every((et) => - selectedExcludeEmbedTypes.includes(et), - ) - : selectedIncludeEmbedTypes.includes(embedType) - return ( - { - toggleEmbedType(embedType) - }} - /> - ) - })} +
+ {Object.values(CollectionId).map((collectionId) => { + const isSelected = selectedCollections.includes(collectionId) + return ( + { + toggleCollection(collectionId) + }} + /> + ) + })} +
)} diff --git a/components/reports/QueueFilter/Tag.tsx b/components/reports/QueueFilter/Tag.tsx new file mode 100644 index 00000000..322de208 --- /dev/null +++ b/components/reports/QueueFilter/Tag.tsx @@ -0,0 +1,251 @@ +import { XCircleIcon } from '@heroicons/react/24/solid' +import { classNames } from '@/lib/util' +import { useQueueFilter } from '../useQueueFilter' +import { ActionButton } from '@/common/buttons' +import Select from 'react-tailwindcss-select' +import { useState } from 'react' + +const availableTagOptions = { + report: { + text: 'Report Type', + options: [ + { text: 'Spam', value: 'report:spam' }, + { text: 'Rude', value: 'report:rude' }, + { text: 'Other', value: 'report:other' }, + { text: 'Violation', value: 'report:violation' }, + { text: 'Misleading', value: 'report:misleading' }, + ], + }, + embed: { + text: 'Embedded Content', + options: [ + { text: 'Image', value: 'embed:image' }, + { text: 'Video', value: 'embed:video' }, + { text: 'Link/External', value: 'embed:external' }, + ], + }, + lang: { + text: 'Language', + options: [ + { text: 'English', value: 'lang:en' }, + { text: 'Portuguese', value: 'lang:pt' }, + { text: 'Spanish', value: 'lang:es' }, + { text: 'French', value: 'lang:fr' }, + { text: 'Japanese', value: 'lang:ja' }, + { text: 'German', value: 'lang:de' }, + { text: 'Italian', value: 'lang:it' }, + { text: 'Korean', value: 'lang:ko' }, + { text: 'Russian', value: 'lang:ru' }, + { text: 'Chinese', value: 'lang:zh' }, + { text: 'Arabic', value: 'lang:ar' }, + { text: 'Unknown', value: 'lang:und' }, + ], + }, +} + +const getTagOptions = (subjectType?: string) => { + const { embed, ...rest } = availableTagOptions + return Object.values(subjectType === 'account' ? rest : availableTagOptions) +} + +const selectClassNames = { + tagItemIconContainer: + 'flex items-center px-1 cursor-pointer rounded-r-sm hover:bg-red-200 hover:text-red-600 dark:text-slate-900', + menuButton: ({ isDisabled }: { isDisabled?: boolean } = {}) => + classNames( + isDisabled ? 'bg-gray-200' : 'bg-white hover:border-gray-400 focus:ring', + 'flex text-sm text-gray-500 border border-gray-300 rounded shadow-sm transition-all duration-300 focus:outline-none dark:bg-slate-700 dark:text-gray-100', + ), + menu: 'absolute z-10 w-full bg-white shadow-lg border rounded py-1 mt-1.5 text-sm text-gray-700 dark:bg-slate-700 dark:text-gray-100', + searchBox: + 'w-full py-2 pl-8 text-sm text-gray-500 bg-gray-100 border border-gray-200 rounded focus:border-gray-200 focus:ring-0 focus:outline-none dark:bg-slate-800 dark:text-gray-100', + listGroupLabel: + 'pr-2 py-2 cursor-default select-none truncate text-gray-700 dark:text-gray-100', + listItem: ({ isSelected }: { isSelected?: boolean } = {}) => { + const baseClass = + 'block transition duration-200 px-2 py-2 cursor-pointer select-none truncate rounded dark:hover:bg-slate-800 dark:hover:text-gray-200' + const selectedClass = isSelected + ? `text-white` + : `text-gray-500 dark:text-gray-300` + + return classNames(baseClass, selectedClass) + }, + tagItemText: `text-gray-600 text-xs truncate cursor-default select-none`, +} + +export const QueueFilterTags = () => { + const [tagSearchInput, setTagSearchInput] = useState('') + const [exclusionTagSearchInput, setExclusionTagSearchInput] = useState('') + const { addTags, updateTagExclusions, clearTags, queueFilters } = + useQueueFilter() + const currentTags = queueFilters.tags ?? [''] + const hasTagFilters = currentTags.filter(Boolean).length > 0 + const lastFragmentHasTags = !!currentTags[currentTags.length - 1].length + const allExcludedTags = queueFilters.excludeTags?.join() + const allTagFilters = currentTags.join() + + const allTagOptions = getTagOptions(queueFilters.subjectType) + // If a tag is already set to be excluded, don't let that tag be set as a filter + const tagOptions = allExcludedTags?.length + ? allTagOptions.map((group) => { + return { + ...group, + options: group.options.filter( + (option) => !allExcludedTags.includes(option.value), + ), + } + }) + : allTagOptions + + if (tagSearchInput) { + tagOptions.push({ + text: 'Custom Tag', + options: [ + { + text: tagSearchInput, + value: tagSearchInput, + }, + ], + }) + } + + // If a tag is already set in the filters, don't let that tag be set as an exclusion + const exclusionTagOptions = allTagFilters.length + ? allTagOptions.map((group) => { + return { + ...group, + options: group.options.filter( + (option) => !allTagFilters.includes(option.value), + ), + } + }) + : allTagOptions + + if (exclusionTagSearchInput) { + exclusionTagOptions.push({ + text: 'Custom Tag', + options: [ + { + text: exclusionTagSearchInput, + value: exclusionTagSearchInput, + }, + ], + }) + } + + const currentTagExclusions = queueFilters.excludeTags ?? [] + const hasTagExclusions = currentTagExclusions.length > 0 + + return ( +
+

+ +

+ {currentTags.map((tags, i) => { + const fragments = tags.split('&&').filter(Boolean) + return ( + <> + {i > 0 && ( +
+ +
+ )} + ({ label: tag, value: tag }))} + onSearchInputChange={(e) => setExclusionTagSearchInput(e.target.value)} + onChange={(selections) => + updateTagExclusions( + Array.isArray(selections) ? selections.map((s) => s.value) : [], + ) + } + options={exclusionTagOptions.map((group) => ({ + label: group.text, + options: group.options.map((option) => ({ + label: option.text, + value: option.value, + isSelected: currentTagExclusions.includes(option.value), + })), + }))} + /> +
+ ) +} diff --git a/components/reports/useQueueFilter.tsx b/components/reports/useQueueFilter.tsx index 04ef625e..8ef07d93 100644 --- a/components/reports/useQueueFilter.tsx +++ b/components/reports/useQueueFilter.tsx @@ -5,8 +5,7 @@ import { useRouter, useSearchParams, } from 'next/navigation' -import { useCallback, useMemo } from 'react' -import { EmbedTypes } from './helpers/subject' +import { useMemo } from 'react' export const useQueueFilterBuilder = ( searchParams: ReadonlyURLSearchParams, @@ -54,10 +53,6 @@ export const useQueueFilter = () => { router.replace(`${pathname}?${updatedParams.toString()}`) } - const resetFilters = useCallback(() => { - router.replace(pathname) - }, [router, pathname]) - const toggleSubjectType = (targetType: 'account' | 'record') => { const subjectType = queueFilters.subjectType === targetType ? undefined : targetType @@ -86,38 +81,10 @@ export const useQueueFilter = () => { updateFilters(newParams) } - const toggleEmbedType = (embedType: string) => { - const allEmbedTypes = Object.values(EmbedTypes) - - const newTags = new Set(queueFilters.tags ?? []) - const newExcludeTags = new Set(queueFilters.excludeTags ?? []) - - if (embedType.startsWith('embed:')) { - if (newTags.has(embedType)) { - newTags.delete(embedType) - } else { - allEmbedTypes.forEach((embed) => newExcludeTags.delete(embed)) - newTags.add(embedType) - } - } else if (embedType === 'noEmbed') { - const hasAllEmbedExcludes = allEmbedTypes.every((embed) => - newExcludeTags.has(embed), - ) - - if (hasAllEmbedExcludes) { - allEmbedTypes.forEach((embed) => newExcludeTags.delete(embed)) - } else { - allEmbedTypes.forEach((embed) => { - newTags.delete(embed) - newExcludeTags.add(embed) - }) - } - } - + const clearSubjectType = () => { updateFilters({ - tags: newTags.size > 0 ? Array.from(newTags) : undefined, - excludeTags: - newExcludeTags.size > 0 ? Array.from(newExcludeTags) : undefined, + subjectType: undefined, + collections: undefined, }) } @@ -136,43 +103,34 @@ export const useQueueFilter = () => { }) } - const toggleLanguage = (section: 'include' | 'exclude', newLang: string) => { - const filterKey = section === 'include' ? 'tags' : 'excludeTags' - const currentTags = - section === 'include' ? queueFilters.tags : queueFilters.excludeTags + const addTags = (index: number, tags: string[]) => { + const newTags = queueFilters.tags ?? [] - const newTags = new Set(currentTags ?? []) - if (newTags.has(`lang:${newLang}`)) { - newTags.delete(`lang:${newLang}`) - } else { - newTags.add(`lang:${newLang}`) + if (!tags.length) { + newTags.splice(index, 1) + return updateFilters({ tags: newTags }) } - updateFilters({ - [filterKey]: newTags.size > 0 ? Array.from(newTags) : undefined, - }) + newTags[index] = tags.join('&&') + updateFilters({ tags: newTags }) } - const clearLanguages = () => { - const newTags = queueFilters.tags?.filter((tag) => !tag.startsWith('lang:')) - const newExcludeTags = queueFilters.excludeTags?.filter( - (tag) => !tag.startsWith('lang:'), - ) + const updateTagExclusions = (excludeTags: string[]) => { + updateFilters({ excludeTags: excludeTags.length ? excludeTags : undefined }) + } - updateFilters({ - tags: newTags?.length ? newTags : undefined, - excludeTags: newExcludeTags?.length ? newExcludeTags : undefined, - }) + const clearTags = () => { + updateFilters({ tags: [] }) } return { queueFilters, updateFilters, + updateTagExclusions, + addTags, toggleCollection, - resetFilters, toggleSubjectType, - toggleEmbedType, - clearLanguages, - toggleLanguage, + clearSubjectType, + clearTags, } } diff --git a/components/repositories/AccountView.tsx b/components/repositories/AccountView.tsx index 16dcd717..eec6c9ba 100644 --- a/components/repositories/AccountView.tsx +++ b/components/repositories/AccountView.tsx @@ -640,7 +640,7 @@ function Details({ - + {!tags.length && } {tags.map((tag) => ( diff --git a/components/shell/CommandPalette/ResultItem.tsx b/components/shell/CommandPalette/ResultItem.tsx index c12ad211..11820075 100644 --- a/components/shell/CommandPalette/ResultItem.tsx +++ b/components/shell/CommandPalette/ResultItem.tsx @@ -28,7 +28,7 @@ const ResultItem = ( active ? 'bg-blue-400 rounded-lg text-gray-100 ' : 'transparent text-gray-500' - } 'rounded-lg px-4 py-2 flex items-center cursor-pointer justify-between `} + } rounded-lg px-4 py-3 flex items-center cursor-pointer justify-between `} >
{action.icon && action.icon} diff --git a/components/shell/CommandPalette/actions.ts b/components/shell/CommandPalette/actions.ts index 5f112ba7..ddaa0beb 100644 --- a/components/shell/CommandPalette/actions.ts +++ b/components/shell/CommandPalette/actions.ts @@ -4,7 +4,6 @@ export const getStaticActions = ({ router }: { router: AppRouterInstance }) => [ { id: 'quick-action-modal', name: 'Open Quick Action Panel', - shortcut: ['q'], keywords: 'quick,action,panel', perform: () => { router.push('/reports?quickOpen=true') @@ -13,7 +12,6 @@ export const getStaticActions = ({ router }: { router: AppRouterInstance }) => [ { id: 'workspace-modal', name: 'Open Workspace', - shortcut: ['w'], keywords: 'workspace,panel', perform: () => { const pathname = window.location.pathname @@ -25,7 +23,6 @@ export const getStaticActions = ({ router }: { router: AppRouterInstance }) => [ { id: 'unresolved-queue', name: 'Open Unresolved Queue', - shortcut: ['u'], keywords: 'unresolved,queue', perform: () => router.push( @@ -35,7 +32,6 @@ export const getStaticActions = ({ router }: { router: AppRouterInstance }) => [ { id: 'resolved-queue', name: 'Open Resolved Queue', - shortcut: ['r'], keywords: 'resolved,queue', perform: () => router.push( @@ -45,7 +41,6 @@ export const getStaticActions = ({ router }: { router: AppRouterInstance }) => [ { id: 'escalated-queue', name: 'Open Escalated Queue', - shortcut: ['e'], keywords: 'escalated,queue', perform: () => router.push( @@ -55,14 +50,12 @@ export const getStaticActions = ({ router }: { router: AppRouterInstance }) => [ { id: 'all-queue', name: 'Open Moderation Queue', - shortcut: ['a'], keywords: 'all,queue', perform: () => router.push('/reports'), }, { id: 'appeal-queue', name: 'Open Appeal Queue', - shortcut: ['e'], keywords: 'appealed,queue', perform: () => router.push( @@ -72,14 +65,12 @@ export const getStaticActions = ({ router }: { router: AppRouterInstance }) => [ { id: 'filter-macros', name: 'Manage Filter Macros', - shortcut: ['f'], keywords: 'filter,macros', perform: () => router.push('/events/filters/macros'), }, { id: 'view-sets', name: 'See All Sets', - shortcut: ['s'], keywords: 'sets,settings', perform: () => { router.push('/configure?tab=sets') diff --git a/components/shell/MobileMenu.tsx b/components/shell/MobileMenu.tsx index 1d0d386c..5c7b9b2f 100644 --- a/components/shell/MobileMenu.tsx +++ b/components/shell/MobileMenu.tsx @@ -109,7 +109,7 @@ export function MobileMenu({ toggleTheme }: { toggleTheme: () => void }) { title="Icon from Flaticon: https://www.flaticon.com/free-icons/lifeguard-tower" className="h-8 w-auto" src="/img/logo-white.png" - alt="Ozone - Bluesky Admin" + alt="Ozone - ATProto Moderation Service" />
diff --git a/components/shell/Shell.tsx b/components/shell/Shell.tsx index 752313d3..f280604c 100644 --- a/components/shell/Shell.tsx +++ b/components/shell/Shell.tsx @@ -28,7 +28,7 @@ export function Shell({ children }: React.PropsWithChildren) { height={100} className="h-8 w-auto" src="/img/logo-white.png" - alt="Ozone - Bluesky Admin" + alt="Ozone - ATProto Moderation Service" title="Icon from Flaticon: https://www.flaticon.com/free-icons/lifeguard-tower" />
diff --git a/components/tags/SubjectTag.tsx b/components/tags/SubjectTag.tsx index e3b0cf9b..41817de1 100644 --- a/components/tags/SubjectTag.tsx +++ b/components/tags/SubjectTag.tsx @@ -1,5 +1,6 @@ import { LabelChip } from '@/common/labels' import { LANGUAGES_MAP_CODE2 } from '@/lib/locale/languages' +import { ComponentProps } from 'react' export const getLanguageFlag = (langCode: string) => { if (!langCode) return undefined @@ -8,7 +9,10 @@ export const getLanguageFlag = (langCode: string) => { return langDetails?.flag } -export const SubjectTag = ({ tag }: { tag: string }) => { +export const SubjectTag = ({ + tag, + ...rest +}: { tag: string } & ComponentProps) => { if (tag.startsWith('lang:')) { const langCode = tag.split(':')[1]?.toLowerCase() const langDetails = LANGUAGES_MAP_CODE2[langCode] @@ -21,5 +25,6 @@ export const SubjectTag = ({ tag }: { tag: string }) => { ) } } - return {tag} + + return {tag} } diff --git a/package.json b/package.json index ad69fb60..87b31163 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "react-dom": "18.2.0", "react-dropzone": "^14.3.5", "react-json-view": "1.21.3", + "react-tailwindcss-select": "^1.8.5", "react-tetris": "^0.3.0", "react-toastify": "^9.1.1", "react-use": "^17.4.0", diff --git a/styles/globals.css b/styles/globals.css index 1a0afa6d..9d8e6efc 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -5,4 +5,61 @@ .wmde-markdown-var[data-color-mode*='dark'] { --color-canvas-default: theme(colors.slate.800) !important; --color-border-default: theme(colors.teal.500) !important; +} + +.game-block { + @apply m-0 p-0 w-6 h-6 border border-gray-300; +} +.piece-i { + @apply bg-pink-400; +} +.piece-j { + @apply bg-orange-300; +} +.piece-l { + @apply bg-yellow-200; +} +.piece-o { + @apply bg-yellow-500; +} +.piece-s { + @apply bg-gray-700; +} +.piece-t { + @apply bg-purple-400; +} +.piece-z { + @apply bg-red-400; +} +.piece-preview { + @apply bg-gray-200; +} + + +.dark .game-block { + @apply border-gray-600; +} +.dark .piece-i { + @apply bg-pink-700; +} +.dark .piece-j { + @apply bg-orange-500; +} +.dark .piece-l { + @apply bg-yellow-400; +} +.dark .piece-o { + @apply bg-yellow-700; +} +.dark .piece-s { + @apply bg-gray-800; +} +.dark .piece-t { + @apply bg-purple-600; +} +.dark .piece-z { + @apply bg-red-600; +} +.dark .piece-preview { + @apply bg-gray-700; } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index db35a056..c34617b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4497,6 +4497,11 @@ react-markdown@~8.0.0: unist-util-visit "^4.0.0" vfile "^5.0.0" +react-tailwindcss-select@^1.8.5: + version "1.8.5" + resolved "https://registry.yarnpkg.com/react-tailwindcss-select/-/react-tailwindcss-select-1.8.5.tgz#adf752c7e54889c38fd76bd1aa5cf8cf6d2b9788" + integrity sha512-x29IrLiqBT5FnkC9oFQReOr05tEOZHtDtZdha84nlSWcj3qD67yonKFHXIf69yc8ElFlKUxCEv0zCllN8jHBFA== + react-tetris@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/react-tetris/-/react-tetris-0.3.0.tgz#b846e1dff9af37880a54877783aedddc2258bed5"