search:places
This commit is contained in:
parent
854a745505
commit
6dad7aafbf
@ -21,7 +21,7 @@ interface GalleryLargeProps {
|
||||
sortBy?: FeedSortOption;
|
||||
categorySlugs?: string[];
|
||||
categoryIds?: string[];
|
||||
contentType?: 'posts' | 'pages' | 'pictures' | 'files';
|
||||
contentType?: 'posts' | 'pages' | 'pictures' | 'files' | 'places';
|
||||
visibilityFilter?: 'invisible' | 'private';
|
||||
center?: boolean;
|
||||
preset?: any;
|
||||
|
||||
@ -3,7 +3,7 @@ import React, { useState, useEffect } from "react";
|
||||
import { useFeedData, FeedSortOption } from "@/hooks/useFeedData";
|
||||
|
||||
import { useIsMobile } from "@/hooks/use-mobile";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useNavigate, Link } from "react-router-dom";
|
||||
import { formatDistanceToNow } from "date-fns";
|
||||
import { MessageCircle, Heart, ExternalLink } from "lucide-react";
|
||||
import UserAvatarBlock from "@/components/UserAvatarBlock";
|
||||
@ -19,7 +19,7 @@ interface ListLayoutProps {
|
||||
isOwner?: boolean; // Not strictly used for rendering list but good for consistency
|
||||
categorySlugs?: string[];
|
||||
categoryIds?: string[];
|
||||
contentType?: 'posts' | 'pages' | 'pictures' | 'files';
|
||||
contentType?: 'posts' | 'pages' | 'pictures' | 'files' | 'places';
|
||||
visibilityFilter?: 'invisible' | 'private';
|
||||
center?: boolean;
|
||||
preset?: any;
|
||||
@ -106,6 +106,7 @@ const ListItem = ({ item, isSelected, onClick, preset }: { item: any, isSelected
|
||||
const getSearchGroup = (post: any): string => {
|
||||
if (post.type === 'page-vfs-folder') return 'Folders';
|
||||
if (post._searchSource === 'picture') return 'Pictures';
|
||||
if (post._searchSource === 'place') return 'Places';
|
||||
if (post._searchSource === 'file') {
|
||||
if (post.thumbnail_url || post.cover || (post.pictures && post.pictures.length > 0)) return 'Pictures';
|
||||
return 'Files';
|
||||
@ -238,7 +239,7 @@ export const ListLayout = ({
|
||||
groups.get(group)!.push(post);
|
||||
}
|
||||
|
||||
const orderedGroups = ['Pages', 'Folders', 'Posts', 'Pictures', 'Files'];
|
||||
const orderedGroups = ['Pages', 'Folders', 'Posts', 'Places', 'Pictures', 'Files'];
|
||||
const elements: React.ReactNode[] = [];
|
||||
|
||||
for (const group of orderedGroups) {
|
||||
@ -311,6 +312,23 @@ export const ListLayout = ({
|
||||
);
|
||||
}
|
||||
|
||||
if (postAny?.type === 'place-search' && postAny.meta?.url) {
|
||||
return (
|
||||
<div className="h-full overflow-y-auto p-6 flex flex-col gap-4">
|
||||
<h2 className="text-xl font-semibold">{postAny.title}</h2>
|
||||
{postAny.description && (
|
||||
<p className="text-muted-foreground text-sm">{postAny.description}</p>
|
||||
)}
|
||||
<Link
|
||||
to={postAny.meta.url}
|
||||
className="text-primary font-medium hover:underline w-fit"
|
||||
>
|
||||
<T>Open place details</T>
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<React.Suspense fallback={<div className="h-full flex items-center justify-center text-muted-foreground">Loading...</div>}>
|
||||
<Post
|
||||
|
||||
@ -11,6 +11,7 @@ import type { CardPreset } from '@/modules/pages/PageCard';
|
||||
import { normalizeMediaType, MEDIA_TYPES, type MediaType } from '@/lib/mediaRegistry';
|
||||
import { getMimeCategory, CATEGORY_STYLE } from '@/modules/storage/helpers';
|
||||
import type { INode } from '@/modules/storage/types';
|
||||
import { MapPin } from 'lucide-react';
|
||||
|
||||
interface MediaCardProps {
|
||||
id: string;
|
||||
@ -178,6 +179,34 @@ const MediaCard: React.FC<MediaCardProps> = ({
|
||||
}
|
||||
}
|
||||
|
||||
if (normalizedType === MEDIA_TYPES.PLACE_SEARCH) {
|
||||
return (
|
||||
<div
|
||||
onClick={() => onClick?.(id)}
|
||||
className={`group relative overflow-hidden bg-card transition-all duration-300 cursor-pointer w-full ${preset?.showTitle ? '' : 'md:aspect-square'} flex flex-col border rounded-lg hover:border-primary/50`}
|
||||
>
|
||||
<div className="flex-1 w-full aspect-square md:aspect-auto flex items-center justify-center bg-muted/20 relative">
|
||||
<MapPin className="w-16 h-16 text-primary transition-transform duration-300 group-hover:scale-110" />
|
||||
</div>
|
||||
{(preset?.showTitle !== false || preset?.showDescription !== false) && (
|
||||
<div className="px-3 py-2 border-t bg-muted/40 absolute bottom-0 left-0 right-0 md:relative bg-background/95 backdrop-blur-sm md:bg-muted/40 md:backdrop-blur-none transition-transform pointer-events-none">
|
||||
{preset?.showTitle !== false && title && (
|
||||
<h3 className="text-sm font-medium truncate flex items-center gap-1.5" title={title}>
|
||||
<span className="truncate">{title}</span>
|
||||
</h3>
|
||||
)}
|
||||
{meta?.placeType && (
|
||||
<p className="text-xs text-muted-foreground/90 truncate mt-0.5">{meta.placeType}</p>
|
||||
)}
|
||||
{preset?.showDescription !== false && description && (
|
||||
<p className="text-xs text-muted-foreground truncate mt-0.5">{description}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (normalizedType === MEDIA_TYPES.PAGE ||
|
||||
normalizedType === MEDIA_TYPES.PAGE_EXTERNAL ||
|
||||
normalizedType === 'page-vfs-file' ||
|
||||
@ -242,6 +271,7 @@ const MediaCard: React.FC<MediaCardProps> = ({
|
||||
showDescription={showDescription}
|
||||
showAuthor={showAuthor}
|
||||
showActions={showActions}
|
||||
placeTypeLabel={meta?.placeType}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -50,6 +50,8 @@ interface PhotoCardProps {
|
||||
showActions?: boolean;
|
||||
showTitle?: boolean;
|
||||
showDescription?: boolean;
|
||||
/** Place search: types[0], shown above description */
|
||||
placeTypeLabel?: string | null;
|
||||
}
|
||||
|
||||
const PhotoCard = ({
|
||||
@ -83,7 +85,8 @@ const PhotoCard = ({
|
||||
showAuthor = true,
|
||||
showActions = true,
|
||||
showTitle = true,
|
||||
showDescription = true
|
||||
showDescription = true,
|
||||
placeTypeLabel
|
||||
}: PhotoCardProps) => {
|
||||
const { user } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
@ -320,11 +323,14 @@ const PhotoCard = ({
|
||||
</div>
|
||||
|
||||
|
||||
{variant === 'grid' && (title || description) && (
|
||||
{variant === 'grid' && (title || description || placeTypeLabel) && (
|
||||
<div className="px-2 py-1.5 border-t">
|
||||
{showTitle && title && !isLikelyFilename(title) && (
|
||||
<h3 className="text-sm font-medium truncate">{title}</h3>
|
||||
)}
|
||||
{placeTypeLabel && (
|
||||
<p className="text-xs text-muted-foreground/90 line-clamp-1 mt-0.5">{placeTypeLabel}</p>
|
||||
)}
|
||||
{showDescription && description && (
|
||||
<p className="text-xs text-muted-foreground line-clamp-1 mt-0.5">{description}</p>
|
||||
)}
|
||||
@ -565,6 +571,10 @@ const PhotoCard = ({
|
||||
<div className="font-semibold text-sm">{title}</div>
|
||||
)}
|
||||
|
||||
{placeTypeLabel && (
|
||||
<div className="text-xs text-muted-foreground line-clamp-2">{placeTypeLabel}</div>
|
||||
)}
|
||||
|
||||
{showDescription && description && (
|
||||
<div className="text-sm text-foreground/90 line-clamp-3">
|
||||
<MarkdownRenderer content={description} className="prose-sm dark:prose-invert" />
|
||||
|
||||
@ -57,7 +57,7 @@ interface MediaGridProps {
|
||||
categorySlugs?: string[];
|
||||
categoryIds?: string[];
|
||||
preset?: CardPreset;
|
||||
contentType?: 'posts' | 'pages' | 'pictures' | 'files';
|
||||
contentType?: 'posts' | 'pages' | 'pictures' | 'files' | 'places';
|
||||
visibilityFilter?: 'invisible' | 'private';
|
||||
center?: boolean;
|
||||
columns?: number | 'auto';
|
||||
@ -356,6 +356,7 @@ const MediaGrid = ({
|
||||
const getSearchGroup = (item: any): string => {
|
||||
if (item.type === 'page-vfs-folder') return 'Folders';
|
||||
if (item._searchSource === 'picture') return 'Pictures';
|
||||
if (item._searchSource === 'place') return 'Places';
|
||||
if (item._searchSource === 'file') {
|
||||
if (item.thumbnail_url || item.cover || (item.pictures && item.pictures.length > 0)) return 'Pictures';
|
||||
return 'Files';
|
||||
@ -374,7 +375,7 @@ const MediaGrid = ({
|
||||
groups.get(group)!.push(item);
|
||||
});
|
||||
|
||||
const orderedGroups = ['Pages', 'Folders', 'Posts', 'Pictures', 'Files'];
|
||||
const orderedGroups = ['Pages', 'Folders', 'Posts', 'Places', 'Pictures', 'Files'];
|
||||
const sections = [];
|
||||
for (const group of orderedGroups) {
|
||||
if (groups.has(group)) {
|
||||
|
||||
@ -93,7 +93,7 @@ export const FeedCard: React.FC<FeedCardProps> = ({
|
||||
};
|
||||
|
||||
if (carouselItems.length === 0) {
|
||||
if (post.type === 'page-vfs-file' || post.type === 'page-vfs-folder' || post.type === 'page-intern' || post.type === 'page-external') {
|
||||
if (post.type === 'page-vfs-file' || post.type === 'page-vfs-folder' || post.type === 'page-intern' || post.type === 'page-external' || post.type === 'place-search') {
|
||||
return (
|
||||
<article className="bg-background md:border md:mb-6 md:pb-0 overflow-hidden p-4">
|
||||
<div onClick={() => {
|
||||
|
||||
@ -17,7 +17,7 @@ interface MobileFeedProps {
|
||||
sortBy?: FeedSortOption;
|
||||
categorySlugs?: string[];
|
||||
categoryIds?: string[];
|
||||
contentType?: 'posts' | 'pages' | 'pictures' | 'files';
|
||||
contentType?: 'posts' | 'pages' | 'pictures' | 'files' | 'places';
|
||||
visibilityFilter?: 'invisible' | 'private';
|
||||
center?: boolean;
|
||||
showTitle?: boolean;
|
||||
@ -151,6 +151,7 @@ export const MobileFeed: React.FC<MobileFeedProps> = ({
|
||||
const getSearchGroup = (item: any): string => {
|
||||
if (item.type === 'page-vfs-folder') return 'Folders';
|
||||
if (item._searchSource === 'picture') return 'Pictures';
|
||||
if (item._searchSource === 'place') return 'Places';
|
||||
if (item._searchSource === 'file') {
|
||||
if (item.thumbnail_url || item.cover || (item.pictures && item.pictures.length > 0)) return 'Pictures';
|
||||
return 'Files';
|
||||
@ -167,7 +168,7 @@ export const MobileFeed: React.FC<MobileFeedProps> = ({
|
||||
groups.get(group)!.push(post);
|
||||
}
|
||||
|
||||
const orderedGroups = ['Pages', 'Folders', 'Posts', 'Pictures', 'Files'];
|
||||
const orderedGroups = ['Pages', 'Folders', 'Posts', 'Places', 'Pictures', 'Files'];
|
||||
const elements: React.ReactNode[] = [];
|
||||
|
||||
for (const group of orderedGroups) {
|
||||
|
||||
@ -20,7 +20,7 @@ export interface HomeWidgetProps {
|
||||
headingLevel?: 'h1' | 'h2' | 'h3' | 'h4';
|
||||
variables?: Record<string, any>;
|
||||
searchQuery?: string;
|
||||
initialContentType?: 'posts' | 'pages' | 'pictures' | 'files';
|
||||
initialContentType?: 'posts' | 'pages' | 'pictures' | 'files' | 'places';
|
||||
initialVisibilityFilter?: 'invisible' | 'private';
|
||||
}
|
||||
import type { FeedSortOption } from '@/hooks/useFeedData';
|
||||
@ -42,7 +42,7 @@ import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
||||
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuLabel, DropdownMenuSeparator } from '@/components/ui/dropdown-menu';
|
||||
import { ResizablePanelGroup, ResizablePanel, ResizableHandle } from '@/components/ui/resizable';
|
||||
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription } from '@/components/ui/sheet';
|
||||
import { LayoutGrid, GalleryVerticalEnd, TrendingUp, Clock, List, FolderTree, FileText, Image as ImageIcon, EyeOff, Lock, SlidersHorizontal, Layers, Camera } from 'lucide-react';
|
||||
import { LayoutGrid, GalleryVerticalEnd, TrendingUp, Clock, List, FolderTree, FileText, Image as ImageIcon, EyeOff, Lock, SlidersHorizontal, Layers, Camera, MapPin } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
@ -105,7 +105,7 @@ const HomeWidget: React.FC<HomeWidgetProps> = ({
|
||||
useEffect(() => { setViewMode(propViewMode); }, [propViewMode]);
|
||||
|
||||
// Content type filter
|
||||
const [contentType, setContentType] = useState<'posts' | 'pages' | 'pictures' | 'files' | undefined>(
|
||||
const [contentType, setContentType] = useState<'posts' | 'pages' | 'pictures' | 'files' | 'places' | undefined>(
|
||||
String(effectiveInitial) === 'all' ? undefined : (effectiveInitial as any)
|
||||
);
|
||||
|
||||
@ -115,7 +115,7 @@ const HomeWidget: React.FC<HomeWidgetProps> = ({
|
||||
}, [effectiveInitial]);
|
||||
|
||||
// Navigate URL when content type changes (only when rendered on a user profile or in search)
|
||||
const handleContentTypeChange = useCallback((newType: 'posts' | 'pages' | 'pictures' | 'files' | undefined) => {
|
||||
const handleContentTypeChange = useCallback((newType: 'posts' | 'pages' | 'pictures' | 'files' | 'places' | undefined) => {
|
||||
setContentType(newType);
|
||||
if (propUserId) {
|
||||
const basePath = `/user/${propUserId}`;
|
||||
@ -211,7 +211,7 @@ const HomeWidget: React.FC<HomeWidgetProps> = ({
|
||||
</ToggleGroup>
|
||||
<ToggleGroup type="single" value={contentType || 'all'} onValueChange={(v) => {
|
||||
if (!v || v === 'all') handleContentTypeChange(undefined);
|
||||
else handleContentTypeChange(v as 'posts' | 'pages' | 'pictures' | 'files');
|
||||
else handleContentTypeChange(v as 'posts' | 'pages' | 'pictures' | 'files' | 'places');
|
||||
}}>
|
||||
<ToggleGroupItem value="all" aria-label="All content" size={size}>
|
||||
<span className="hidden md:inline"><T>All</T></span>
|
||||
@ -225,6 +225,12 @@ const HomeWidget: React.FC<HomeWidgetProps> = ({
|
||||
<FileText className="h-4 w-4 md:mr-2" />
|
||||
<span className="hidden md:inline"><T>Pages</T></span>
|
||||
</ToggleGroupItem>
|
||||
{searchQuery && (
|
||||
<ToggleGroupItem value="places" aria-label="Places only" size={size}>
|
||||
<MapPin className="h-4 w-4 md:mr-2" />
|
||||
<span className="hidden md:inline"><T>Places</T></span>
|
||||
</ToggleGroupItem>
|
||||
)}
|
||||
{isOwnProfile && (
|
||||
<ToggleGroupItem value="pictures" aria-label="Pictures only" size={size}>
|
||||
<Camera className="h-4 w-4 md:mr-2" />
|
||||
@ -428,7 +434,7 @@ const HomeWidget: React.FC<HomeWidgetProps> = ({
|
||||
<DropdownMenuLabel className="text-xs"><T>Content</T></DropdownMenuLabel>
|
||||
<DropdownMenuRadioGroup value={contentType || 'all'} onValueChange={(v) => {
|
||||
if (v === 'all') handleContentTypeChange(undefined);
|
||||
else handleContentTypeChange(v as 'posts' | 'pages' | 'pictures');
|
||||
else handleContentTypeChange(v as 'posts' | 'pages' | 'pictures' | 'files' | 'places');
|
||||
}}>
|
||||
<DropdownMenuRadioItem value="all">
|
||||
<Layers className="h-4 w-4 mr-2" /><T>All</T>
|
||||
@ -439,6 +445,11 @@ const HomeWidget: React.FC<HomeWidgetProps> = ({
|
||||
<DropdownMenuRadioItem value="pages">
|
||||
<FileText className="h-4 w-4 mr-2" /><T>Pages</T>
|
||||
</DropdownMenuRadioItem>
|
||||
{searchQuery && (
|
||||
<DropdownMenuRadioItem value="places">
|
||||
<MapPin className="h-4 w-4 mr-2" /><T>Places</T>
|
||||
</DropdownMenuRadioItem>
|
||||
)}
|
||||
{isOwnProfile && (
|
||||
<DropdownMenuRadioItem value="pictures">
|
||||
<Camera className="h-4 w-4 mr-2" /><T>Pictures</T>
|
||||
|
||||
@ -22,7 +22,7 @@ interface UseFeedDataProps {
|
||||
supabaseClient?: any;
|
||||
categoryIds?: string[];
|
||||
categorySlugs?: string[];
|
||||
contentType?: 'posts' | 'pages' | 'pictures' | 'files';
|
||||
contentType?: 'posts' | 'pages' | 'pictures' | 'files' | 'places';
|
||||
visibilityFilter?: 'invisible' | 'private';
|
||||
}
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ export interface FetchFeedOptions {
|
||||
sortBy?: FeedSortOption;
|
||||
categoryIds?: string[];
|
||||
categorySlugs?: string[];
|
||||
contentType?: 'posts' | 'pages' | 'pictures' | 'files';
|
||||
contentType?: 'posts' | 'pages' | 'pictures' | 'files' | 'places';
|
||||
visibilityFilter?: 'invisible' | 'private';
|
||||
lang?: string;
|
||||
}
|
||||
|
||||
@ -178,14 +178,14 @@ export const mapFeedPostsToMediaItems = (posts: FeedPost[], sortBy: 'latest' | '
|
||||
|
||||
if (!cover) {
|
||||
// Support items without covers that should still be displayed
|
||||
const allowedWithoutCover = ['page-vfs-folder', 'page-vfs-file', 'page-external', 'page-intern', 'page-github'];
|
||||
const allowedWithoutCover = ['page-vfs-folder', 'page-vfs-file', 'page-external', 'page-intern', 'page-github', 'place-search'];
|
||||
if (allowedWithoutCover.includes(post.type)) {
|
||||
return {
|
||||
id: post.id,
|
||||
picture_id: post.id,
|
||||
title: post.title,
|
||||
description: post.description,
|
||||
image_url: post.meta?.url || '',
|
||||
image_url: post.type === 'place-search' ? '' : (post.meta?.url || ''),
|
||||
thumbnail_url: null,
|
||||
type: post.type as MediaType,
|
||||
meta: post.meta,
|
||||
|
||||
@ -3,7 +3,7 @@ const SERVER_API_URL = import.meta.env.VITE_SERVER_IMAGE_API_URL || '';
|
||||
export interface SearchOptions {
|
||||
q: string;
|
||||
limit?: number;
|
||||
type?: 'all' | 'pages' | 'posts' | 'pictures' | 'files';
|
||||
type?: 'all' | 'pages' | 'posts' | 'pictures' | 'files' | 'places';
|
||||
sizes?: string;
|
||||
formats?: string;
|
||||
token?: string;
|
||||
|
||||
@ -26,7 +26,7 @@ const SearchResults = () => {
|
||||
const columns = columnsParam === 'auto' ? 'auto' : (columnsParam ? parseInt(columnsParam, 10) : 4);
|
||||
const heading = searchParams.get('heading') || undefined;
|
||||
const headingLevel = (searchParams.get('headingLevel') as 'h1' | 'h2' | 'h3' | 'h4') || undefined;
|
||||
const initialContentType = (searchParams.get('type') || searchParams.get('initialContentType')) as 'posts' | 'pages' | 'pictures' | 'files' | undefined;
|
||||
const initialContentType = (searchParams.get('type') || searchParams.get('initialContentType')) as 'posts' | 'pages' | 'pictures' | 'files' | 'places' | undefined;
|
||||
const visibilityFilter = (searchParams.get('visibilityFilter')) as 'invisible' | 'private' | undefined;
|
||||
|
||||
// Sync input with URL query
|
||||
@ -68,7 +68,7 @@ const SearchResults = () => {
|
||||
<Input
|
||||
ref={searchInputRef}
|
||||
type="search"
|
||||
placeholder="Search pages, posts, and pictures..."
|
||||
placeholder="Search pages, posts, pictures, and places..."
|
||||
className="pl-10 pr-4 h-11 w-full bg-muted/50 border focus-visible:ring-1 focus-visible:ring-primary rounded-xl"
|
||||
value={inputQuery}
|
||||
onChange={(e) => setInputQuery(e.target.value)}
|
||||
@ -79,7 +79,7 @@ const SearchResults = () => {
|
||||
<div className="text-center py-12">
|
||||
<Search className="h-16 w-16 mx-auto mb-4 text-muted-foreground" />
|
||||
<h3 className="text-xl font-semibold mb-2">Enter a search term</h3>
|
||||
<p className="text-muted-foreground">Search for pages, posts, and pictures</p>
|
||||
<p className="text-muted-foreground">Search for pages, posts, pictures, and places</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -87,7 +87,22 @@ const SearchResults = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
<div className="min-h-screen bg-background pt-14">
|
||||
<div className="container mx-auto px-4 max-w-6xl pb-4">
|
||||
<form onSubmit={handleSearchSubmit} className="relative max-w-xl mx-auto">
|
||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" />
|
||||
<Input
|
||||
ref={searchInputRef}
|
||||
type="search"
|
||||
placeholder="Search pages, posts, pictures, and places..."
|
||||
className="pl-10 pr-4 h-11 w-full bg-muted/50 border focus-visible:ring-1 focus-visible:ring-primary rounded-xl"
|
||||
value={inputQuery}
|
||||
onChange={(e) => setInputQuery(e.target.value)}
|
||||
onKeyDown={handleSearchKeyDown}
|
||||
aria-label="Search"
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
<HomeWidget
|
||||
searchQuery={query}
|
||||
sortBy={sortBy}
|
||||
|
||||
@ -38,6 +38,7 @@ export const MEDIA_TYPES = {
|
||||
PAGE_EXTERNAL: 'page-external',
|
||||
PAGE_VFS_FILE: 'page-vfs-file',
|
||||
PAGE_VFS_FOLDER: 'page-vfs-folder',
|
||||
PLACE_SEARCH: 'place-search',
|
||||
} as const;
|
||||
|
||||
export type MediaType = typeof MEDIA_TYPES[keyof typeof MEDIA_TYPES] | null;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user