{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "editor-x", "title": "Editor X", "description": "A rich text editor built on Lexical with plugins, nodes, extensions, and toolbar support.", "dependencies": [ "@lexical/code", "@lexical/code-shiki", "@lexical/code-prism", "@lexical/extension", "@lexical/file", "@lexical/history", "@lexical/hashtag", "@lexical/link", "@lexical/list", "@lexical/markdown", "@lexical/overflow", "@lexical/react", "@lexical/rich-text", "@lexical/selection", "@lexical/table", "@lexical/text", "@lexical/utils", "@radix-ui/react-slot", "lexical", "lodash", "lucide-react" ], "devDependencies": [ "@types/lodash" ], "registryDependencies": [ "button", "button-group", "calendar", "checkbox", "command", "dialog", "dropdown-menu", "field", "input", "input-group", "label", "popover", "scroll-area", "select", "separator", "tabs", "textarea", "toggle", "toggle-group", "tooltip", "sonner" ], "files": [ { "path": "src/components/editor/context/toolbar-context.tsx", "content": "/* eslint-disable react-refresh/only-export-components */\nimport { type JSX, createContext, useContext } from \"react\";\n\nimport type { LexicalEditor } from \"lexical\";\n\nconst Context = createContext<{\n activeEditor: LexicalEditor;\n $updateToolbar: () => void;\n blockType: string;\n setBlockType: (blockType: string) => void;\n showModal: (\n title: string,\n showModal: (onClose: () => void) => JSX.Element,\n ) => void;\n}>({\n activeEditor: {} as LexicalEditor,\n $updateToolbar: () => {},\n blockType: \"paragraph\",\n setBlockType: () => {},\n showModal: () => {},\n});\n\nexport function ToolbarContext({\n activeEditor,\n $updateToolbar,\n blockType,\n setBlockType,\n showModal,\n children,\n}: {\n activeEditor: LexicalEditor;\n $updateToolbar: () => void;\n blockType: string;\n setBlockType: (blockType: string) => void;\n showModal: (\n title: string,\n showModal: (onClose: () => void) => JSX.Element,\n ) => void;\n children: React.ReactNode;\n}) {\n return (\n \n {children}\n \n );\n}\n\nexport function useToolbarContext() {\n return useContext(Context);\n}\n", "type": "registry:component" }, { "path": "src/components/editor/editor-hooks/use-debounce.ts", "content": "import { useMemo, useRef } from \"react\";\n\nimport { debounce } from \"lodash\";\n\nexport function useDebounce void>(\n fn: T,\n ms: number,\n maxWait?: number,\n) {\n const funcRef = useRef(null);\n funcRef.current = fn;\n\n return useMemo(\n () =>\n debounce(\n (...args: Parameters) => {\n if (funcRef.current) {\n funcRef.current(...args);\n }\n },\n ms,\n { maxWait },\n ),\n [ms, maxWait],\n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/editor-hooks/use-modal.tsx", "content": "import { type JSX, useCallback, useMemo, useState } from \"react\";\n\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n} from \"@/components/ui/dialog\";\n\nexport function useEditorModal(): [\n JSX.Element | null,\n (title: string, showModal: (onClose: () => void) => JSX.Element) => void,\n] {\n const [modalContent, setModalContent] = useState(null);\n\n const onClose = useCallback(() => {\n setModalContent(null);\n }, []);\n\n const modal = useMemo(() => {\n if (modalContent === null) {\n return null;\n }\n const { title, content } = modalContent;\n return (\n \n \n \n {title}\n \n {content}\n \n \n );\n }, [modalContent, onClose]);\n\n const showModal = useCallback(\n (\n title: string,\n getContent: (onClose: () => void) => JSX.Element,\n closeOnClickOutside = false,\n ) => {\n setModalContent({\n closeOnClickOutside,\n content: getContent(onClose),\n title,\n });\n },\n [onClose],\n );\n\n return [modal, showModal];\n}\n", "type": "registry:component" }, { "path": "src/components/editor/editor-hooks/use-report.ts", "content": "import { useCallback, useEffect, useRef } from \"react\";\n\nconst getElement = (): HTMLElement => {\n let element = document.getElementById(\"report-container\");\n\n if (element === null) {\n element = document.createElement(\"div\");\n element.id = \"report-container\";\n element.style.position = \"fixed\";\n element.style.top = \"50%\";\n element.style.left = \"50%\";\n element.style.fontSize = \"32px\";\n element.style.transform = \"translate(-50%, -50px)\";\n element.style.padding = \"20px\";\n element.style.background = \"rgba(240, 240, 240, 0.4)\";\n element.style.borderRadius = \"20px\";\n\n if (document.body) {\n document.body.appendChild(element);\n }\n }\n\n return element;\n};\n\nexport function useReport(): (arg0: string) => ReturnType {\n const timer = useRef | null>(null);\n const cleanup = useCallback(() => {\n if (timer.current !== null) {\n clearTimeout(timer.current);\n timer.current = null;\n }\n\n if (document.body) {\n document.body.removeChild(getElement());\n }\n }, []);\n\n useEffect(() => {\n return cleanup;\n }, [cleanup]);\n\n return useCallback(\n (content) => {\n console.log(content);\n const element = getElement();\n if (timer.current !== null) {\n clearTimeout(timer.current);\n }\n element.innerHTML = content;\n timer.current = setTimeout(cleanup, 1000);\n return timer.current;\n },\n [cleanup],\n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/editor-hooks/use-update-toolbar.ts", "content": "import { useEffect, useRef } from \"react\";\n\nimport {\n $getSelection,\n type BaseSelection,\n COMMAND_PRIORITY_CRITICAL,\n SELECTION_CHANGE_COMMAND,\n} from \"lexical\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\n\nexport function useUpdateToolbarHandler(\n callback: (selection: BaseSelection) => void,\n) {\n const { activeEditor } = useToolbarContext();\n const callbackRef = useRef(callback);\n callbackRef.current = callback;\n\n useEffect(() => {\n return activeEditor.registerCommand(\n SELECTION_CHANGE_COMMAND,\n () => {\n const selection = $getSelection();\n if (selection) {\n callbackRef.current(selection);\n }\n return false;\n },\n COMMAND_PRIORITY_CRITICAL,\n );\n }, [activeEditor]);\n\n useEffect(() => {\n activeEditor.getEditorState().read(() => {\n const selection = $getSelection();\n if (selection) {\n callbackRef.current(selection);\n }\n });\n }, [activeEditor]);\n}\n", "type": "registry:component" }, { "path": "src/components/editor/editor-ui/code-button.tsx", "content": "import { useState } from \"react\";\n\nimport { $isCodeNode } from \"@lexical/code\";\nimport {\n $getNearestNodeFromDOMNode,\n $getSelection,\n $setSelection,\n type LexicalEditor,\n} from \"lexical\";\n\nimport { CircleCheckIcon, CopyIcon } from \"lucide-react\";\n\nimport { useDebounce } from \"@/components/editor/editor-hooks/use-debounce\";\n\ninterface Props {\n editor: LexicalEditor;\n getCodeDOMNode: () => HTMLElement | null;\n}\n\nexport function CopyButton({ editor, getCodeDOMNode }: Props) {\n const [isCopyCompleted, setCopyCompleted] = useState(false);\n\n const removeSuccessIcon = useDebounce(() => {\n setCopyCompleted(false);\n }, 1000);\n\n async function handleClick(): Promise {\n const codeDOMNode = getCodeDOMNode();\n\n if (!codeDOMNode) {\n return;\n }\n\n let content = \"\";\n\n editor.update(() => {\n const codeNode = $getNearestNodeFromDOMNode(codeDOMNode);\n\n if ($isCodeNode(codeNode)) {\n content = codeNode.getTextContent();\n }\n\n const selection = $getSelection();\n $setSelection(selection);\n });\n\n try {\n await navigator.clipboard.writeText(content);\n setCopyCompleted(true);\n removeSuccessIcon();\n } catch (err) {\n console.error(\"Failed to copy: \", err);\n }\n }\n\n return (\n \n {isCopyCompleted ? (\n \n ) : (\n \n )}\n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/editor-ui/color-picker.tsx", "content": "import * as React from \"react\";\n\nimport { type VariantProps, cva } from \"class-variance-authority\";\nimport { PipetteIcon } from \"lucide-react\";\nimport { Slot as SlotPrimitive } from \"@radix-ui/react-slot\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Slider } from \"@/components/ui/slider\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * @see https://github.com/radix-ui/primitives/blob/main/packages/react/compose-refs/src/compose-refs.tsx\n */\n\ntype PossibleRef = React.Ref | undefined;\n\n/**\n * Set a given ref to a given value\n * This utility takes care of different types of refs: callback refs and RefObject(s)\n */\nfunction setRef(ref: PossibleRef, value: T) {\n if (typeof ref === \"function\") {\n return ref(value);\n }\n\n if (ref !== null && ref !== undefined) {\n ref.current = value;\n }\n}\n\n/**\n * A utility to compose multiple refs together\n * Accepts callback refs and RefObject(s)\n */\nfunction composeRefs(...refs: PossibleRef[]): React.RefCallback {\n return (node) => {\n let hasCleanup = false;\n const cleanups = refs.map((ref) => {\n const cleanup = setRef(ref, node);\n if (!hasCleanup && typeof cleanup === \"function\") {\n hasCleanup = true;\n }\n return cleanup;\n });\n\n // React <19 will log an error to the console if a callback ref returns a\n // value. We don't use ref cleanups internally so this will only happen if a\n // user's ref callback returns a value, which we only expect if they are\n // using the cleanup functionality added in React 19.\n if (hasCleanup) {\n return () => {\n for (let i = 0; i < cleanups.length; i++) {\n const cleanup = cleanups[i];\n if (typeof cleanup === \"function\") {\n cleanup();\n } else {\n setRef(refs[i], null);\n }\n }\n };\n }\n };\n}\n\n/**\n * A custom hook that composes multiple refs\n * Accepts callback refs and RefObject(s)\n */\nfunction useComposedRefs(...refs: PossibleRef[]): React.RefCallback {\n // biome-ignore lint/correctness/useExhaustiveDependencies: we want to memoize by all values\n return React.useCallback(composeRefs(...refs), refs);\n}\n\ntype InputValue = string[] | string;\n\ninterface VisuallyHiddenInputProps extends Omit<\n React.InputHTMLAttributes,\n \"value\" | \"checked\" | \"onReset\"\n> {\n value?: T;\n checked?: boolean;\n control: HTMLElement | null;\n bubbles?: boolean;\n}\n\nfunction VisuallyHiddenInput(\n props: VisuallyHiddenInputProps,\n) {\n const {\n control,\n value,\n checked,\n bubbles = true,\n type = \"hidden\",\n style,\n ...inputProps\n } = props;\n\n const isCheckInput = React.useMemo(\n () => type === \"checkbox\" || type === \"radio\" || type === \"switch\",\n [type],\n );\n const inputRef = React.useRef(null);\n\n const prevValueRef = React.useRef<{\n value: T | boolean | undefined;\n previous: T | boolean | undefined;\n }>({\n value: isCheckInput ? checked : value,\n previous: isCheckInput ? checked : value,\n });\n\n const prevValue = React.useMemo(() => {\n const currentValue = isCheckInput ? checked : value;\n if (prevValueRef.current.value !== currentValue) {\n prevValueRef.current.previous = prevValueRef.current.value;\n prevValueRef.current.value = currentValue;\n }\n return prevValueRef.current.previous;\n }, [isCheckInput, value, checked]);\n\n const [controlSize, setControlSize] = React.useState<{\n width?: number;\n height?: number;\n }>({});\n\n React.useLayoutEffect(() => {\n if (!control) {\n setControlSize({});\n return;\n }\n\n setControlSize({\n width: control.offsetWidth,\n height: control.offsetHeight,\n });\n\n if (typeof window === \"undefined\") return;\n\n const resizeObserver = new ResizeObserver((entries) => {\n if (!Array.isArray(entries) || !entries.length) return;\n\n const entry = entries[0];\n if (!entry) return;\n\n let width: number;\n let height: number;\n\n if (\"borderBoxSize\" in entry) {\n const borderSizeEntry = entry.borderBoxSize;\n const borderSize = Array.isArray(borderSizeEntry)\n ? borderSizeEntry[0]\n : borderSizeEntry;\n width = borderSize.inlineSize;\n height = borderSize.blockSize;\n } else {\n width = control.offsetWidth;\n height = control.offsetHeight;\n }\n\n setControlSize({ width, height });\n });\n\n resizeObserver.observe(control, { box: \"border-box\" });\n return () => {\n resizeObserver.disconnect();\n };\n }, [control]);\n\n React.useEffect(() => {\n const input = inputRef.current;\n if (!input) return;\n\n const inputProto = window.HTMLInputElement.prototype;\n const propertyKey = isCheckInput ? \"checked\" : \"value\";\n const eventType = isCheckInput ? \"click\" : \"input\";\n const currentValue = isCheckInput ? checked : value;\n\n const serializedCurrentValue = isCheckInput\n ? checked\n : typeof value === \"object\" && value !== null\n ? JSON.stringify(value)\n : value;\n\n const descriptor = Object.getOwnPropertyDescriptor(inputProto, propertyKey);\n\n const setter = descriptor?.set;\n\n if (prevValue !== currentValue && setter) {\n const event = new Event(eventType, { bubbles });\n setter.call(input, serializedCurrentValue);\n input.dispatchEvent(event);\n }\n }, [prevValue, value, checked, bubbles, isCheckInput]);\n\n const composedStyle = React.useMemo(() => {\n return {\n ...style,\n ...(controlSize.width !== undefined && controlSize.height !== undefined\n ? controlSize\n : {}),\n border: 0,\n clip: \"rect(0 0 0 0)\",\n clipPath: \"inset(50%)\",\n height: \"1px\",\n margin: \"-1px\",\n overflow: \"hidden\",\n padding: 0,\n position: \"absolute\",\n whiteSpace: \"nowrap\",\n width: \"1px\",\n };\n }, [style, controlSize]);\n\n return (\n \n );\n}\n\n/**\n * @see https://gist.github.com/bkrmendy/f4582173f50fab209ddfef1377ab31e3\n */\ninterface EyeDropper {\n open: (options?: { signal?: AbortSignal }) => Promise<{ sRGBHex: string }>;\n}\n\ndeclare global {\n interface Window {\n EyeDropper?: {\n new (): EyeDropper;\n };\n }\n}\n\nconst colorFormats = [\"hex\", \"rgb\", \"hsl\", \"hsb\"] as const;\ntype ColorFormat = (typeof colorFormats)[number];\n\ninterface ColorValue {\n r: number;\n g: number;\n b: number;\n a: number;\n}\n\ninterface HSVColorValue {\n h: number;\n s: number;\n v: number;\n a: number;\n}\n\nfunction hexToRgb(hex: string, alpha?: number): ColorValue {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n return result\n ? {\n r: Number.parseInt(result[1] ?? \"0\", 16),\n g: Number.parseInt(result[2] ?? \"0\", 16),\n b: Number.parseInt(result[3] ?? \"0\", 16),\n a: alpha ?? 1,\n }\n : { r: 0, g: 0, b: 0, a: alpha ?? 1 };\n}\n\nfunction rgbToHex(color: ColorValue): string {\n const toHex = (n: number) => {\n const hex = Math.round(n).toString(16);\n return hex.length === 1 ? `0${hex}` : hex;\n };\n return `#${toHex(color.r)}${toHex(color.g)}${toHex(color.b)}`;\n}\n\nfunction rgbToHsv(color: ColorValue): HSVColorValue {\n const r = color.r / 255;\n const g = color.g / 255;\n const b = color.b / 255;\n\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n const diff = max - min;\n\n let h = 0;\n if (diff !== 0) {\n switch (max) {\n case r:\n h = ((g - b) / diff) % 6;\n break;\n case g:\n h = (b - r) / diff + 2;\n break;\n case b:\n h = (r - g) / diff + 4;\n break;\n }\n }\n h = Math.round(h * 60);\n if (h < 0) h += 360;\n\n const s = max === 0 ? 0 : diff / max;\n const v = max;\n\n return {\n h,\n s: Math.round(s * 100),\n v: Math.round(v * 100),\n a: color.a,\n };\n}\n\nfunction hsvToRgb(hsv: HSVColorValue): ColorValue {\n const h = hsv.h / 360;\n const s = hsv.s / 100;\n const v = hsv.v / 100;\n\n const i = Math.floor(h * 6);\n const f = h * 6 - i;\n const p = v * (1 - s);\n const q = v * (1 - f * s);\n const t = v * (1 - (1 - f) * s);\n\n let r: number;\n let g: number;\n let b: number;\n\n switch (i % 6) {\n case 0: {\n r = v;\n g = t;\n b = p;\n break;\n }\n case 1: {\n r = q;\n g = v;\n b = p;\n break;\n }\n case 2: {\n r = p;\n g = v;\n b = t;\n break;\n }\n case 3: {\n r = p;\n g = q;\n b = v;\n break;\n }\n case 4: {\n r = t;\n g = p;\n b = v;\n break;\n }\n case 5: {\n r = v;\n g = p;\n b = q;\n break;\n }\n default: {\n r = 0;\n g = 0;\n b = 0;\n }\n }\n\n return {\n r: Math.round(r * 255),\n g: Math.round(g * 255),\n b: Math.round(b * 255),\n a: hsv.a,\n };\n}\n\nfunction colorToString(color: ColorValue, format: ColorFormat = \"hex\"): string {\n switch (format) {\n case \"hex\":\n return rgbToHex(color);\n case \"rgb\":\n return color.a < 1\n ? `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`\n : `rgb(${color.r}, ${color.g}, ${color.b})`;\n case \"hsl\": {\n const hsl = rgbToHsl(color);\n return color.a < 1\n ? `hsla(${hsl.h}, ${hsl.s}%, ${hsl.l}%, ${color.a})`\n : `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`;\n }\n case \"hsb\": {\n const hsv = rgbToHsv(color);\n return color.a < 1\n ? `hsba(${hsv.h}, ${hsv.s}%, ${hsv.v}%, ${color.a})`\n : `hsb(${hsv.h}, ${hsv.s}%, ${hsv.v}%)`;\n }\n default:\n return rgbToHex(color);\n }\n}\n\nfunction rgbToHsl(color: ColorValue) {\n const r = color.r / 255;\n const g = color.g / 255;\n const b = color.b / 255;\n\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n const diff = max - min;\n const sum = max + min;\n\n const l = sum / 2;\n\n let h = 0;\n let s = 0;\n\n if (diff !== 0) {\n s = l > 0.5 ? diff / (2 - sum) : diff / sum;\n\n if (max === r) {\n h = (g - b) / diff + (g < b ? 6 : 0);\n } else if (max === g) {\n h = (b - r) / diff + 2;\n } else if (max === b) {\n h = (r - g) / diff + 4;\n }\n h /= 6;\n }\n\n return {\n h: Math.round(h * 360),\n s: Math.round(s * 100),\n l: Math.round(l * 100),\n };\n}\n\nfunction hslToRgb(\n hsl: { h: number; s: number; l: number },\n alpha = 1,\n): ColorValue {\n const h = hsl.h / 360;\n const s = hsl.s / 100;\n const l = hsl.l / 100;\n\n const c = (1 - Math.abs(2 * l - 1)) * s;\n const x = c * (1 - Math.abs(((h * 6) % 2) - 1));\n const m = l - c / 2;\n\n let r = 0;\n let g = 0;\n let b = 0;\n\n if (h >= 0 && h < 1 / 6) {\n r = c;\n g = x;\n b = 0;\n } else if (h >= 1 / 6 && h < 2 / 6) {\n r = x;\n g = c;\n b = 0;\n } else if (h >= 2 / 6 && h < 3 / 6) {\n r = 0;\n g = c;\n b = x;\n } else if (h >= 3 / 6 && h < 4 / 6) {\n r = 0;\n g = x;\n b = c;\n } else if (h >= 4 / 6 && h < 5 / 6) {\n r = x;\n g = 0;\n b = c;\n } else if (h >= 5 / 6 && h < 1) {\n r = c;\n g = 0;\n b = x;\n }\n\n return {\n r: Math.round((r + m) * 255),\n g: Math.round((g + m) * 255),\n b: Math.round((b + m) * 255),\n a: alpha,\n };\n}\n\nfunction parseColorString(value: string): ColorValue | null {\n const trimmed = value.trim();\n\n // Parse hex colors\n if (trimmed.startsWith(\"#\")) {\n const hexMatch = trimmed.match(/^#([a-fA-F0-9]{3}|[a-fA-F0-9]{6})$/);\n if (hexMatch) {\n return hexToRgb(trimmed);\n }\n }\n\n // Parse rgb/rgba colors\n const rgbMatch = trimmed.match(\n /^rgba?\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*(?:,\\s*([\\d.]+))?\\s*\\)$/,\n );\n if (rgbMatch) {\n return {\n r: Number.parseInt(rgbMatch[1] ?? \"0\", 10),\n g: Number.parseInt(rgbMatch[2] ?? \"0\", 10),\n b: Number.parseInt(rgbMatch[3] ?? \"0\", 10),\n a: rgbMatch[4] ? Number.parseFloat(rgbMatch[4]) : 1,\n };\n }\n\n // Parse hsl/hsla colors\n const hslMatch = trimmed.match(\n /^hsla?\\(\\s*(\\d+)\\s*,\\s*(\\d+)%\\s*,\\s*(\\d+)%\\s*(?:,\\s*([\\d.]+))?\\s*\\)$/,\n );\n if (hslMatch) {\n const h = Number.parseInt(hslMatch[1] ?? \"0\", 10);\n const s = Number.parseInt(hslMatch[2] ?? \"0\", 10) / 100;\n const l = Number.parseInt(hslMatch[3] ?? \"0\", 10) / 100;\n const a = hslMatch[4] ? Number.parseFloat(hslMatch[4]) : 1;\n\n // Convert HSL to RGB\n const c = (1 - Math.abs(2 * l - 1)) * s;\n const x = c * (1 - Math.abs(((h / 60) % 2) - 1));\n const m = l - c / 2;\n\n let r = 0;\n let g = 0;\n let b = 0;\n\n if (h >= 0 && h < 60) {\n r = c;\n g = x;\n b = 0;\n } else if (h >= 60 && h < 120) {\n r = x;\n g = c;\n b = 0;\n } else if (h >= 120 && h < 180) {\n r = 0;\n g = c;\n b = x;\n } else if (h >= 180 && h < 240) {\n r = 0;\n g = x;\n b = c;\n } else if (h >= 240 && h < 300) {\n r = x;\n g = 0;\n b = c;\n } else if (h >= 300 && h < 360) {\n r = c;\n g = 0;\n b = x;\n }\n\n return {\n r: Math.round((r + m) * 255),\n g: Math.round((g + m) * 255),\n b: Math.round((b + m) * 255),\n a,\n };\n }\n\n // Parse hsb/hsba colors\n const hsbMatch = trimmed.match(\n /^hsba?\\(\\s*(\\d+)\\s*,\\s*(\\d+)%\\s*,\\s*(\\d+)%\\s*(?:,\\s*([\\d.]+))?\\s*\\)$/,\n );\n if (hsbMatch) {\n const h = Number.parseInt(hsbMatch[1] ?? \"0\", 10);\n const s = Number.parseInt(hsbMatch[2] ?? \"0\", 10);\n const v = Number.parseInt(hsbMatch[3] ?? \"0\", 10);\n const a = hsbMatch[4] ? Number.parseFloat(hsbMatch[4]) : 1;\n\n return hsvToRgb({ h, s, v, a });\n }\n\n return null;\n}\n\ntype Direction = \"ltr\" | \"rtl\";\n\nconst DirectionContext = React.createContext(undefined);\n\nfunction useDirection(dirProp?: Direction): Direction {\n const contextDir = React.useContext(DirectionContext);\n return dirProp ?? contextDir ?? \"ltr\";\n}\n\nfunction useLazyRef(fn: () => T) {\n const ref = React.useRef(null);\n\n if (ref.current === null) {\n ref.current = fn();\n }\n\n return ref as React.RefObject;\n}\n\ninterface ColorPickerStoreState {\n color: ColorValue;\n hsv: HSVColorValue;\n open: boolean;\n format: ColorFormat;\n}\n\ninterface ColorPickerStoreCallbacks {\n onColorChange?: (colorString: string) => void;\n onOpenChange?: (open: boolean) => void;\n onFormatChange?: (format: ColorFormat) => void;\n}\n\ninterface ColorPickerStore {\n subscribe: (cb: () => void) => () => void;\n getState: () => ColorPickerStoreState;\n setColor: (value: ColorValue) => void;\n setHsv: (value: HSVColorValue) => void;\n setOpen: (value: boolean) => void;\n setFormat: (value: ColorFormat) => void;\n notify: () => void;\n}\n\nfunction createColorPickerStore(\n listenersRef: React.RefObject void>>,\n stateRef: React.RefObject,\n callbacks?: ColorPickerStoreCallbacks,\n): ColorPickerStore {\n const store: ColorPickerStore = {\n subscribe: (cb) => {\n if (listenersRef.current) {\n listenersRef.current.add(cb);\n return () => listenersRef.current?.delete(cb);\n }\n return () => {};\n },\n getState: () =>\n stateRef.current || {\n color: { r: 0, g: 0, b: 0, a: 1 },\n hsv: { h: 0, s: 0, v: 0, a: 1 },\n open: false,\n format: \"hex\" as ColorFormat,\n },\n setColor: (value: ColorValue) => {\n if (!stateRef.current) return;\n if (Object.is(stateRef.current.color, value)) return;\n\n const prevState = { ...stateRef.current };\n stateRef.current.color = value;\n\n if (callbacks?.onColorChange) {\n const colorString = colorToString(value, prevState.format);\n callbacks.onColorChange(colorString);\n }\n\n store.notify();\n },\n setHsv: (value: HSVColorValue) => {\n if (!stateRef.current) return;\n if (Object.is(stateRef.current.hsv, value)) return;\n\n const prevState = { ...stateRef.current };\n stateRef.current.hsv = value;\n\n if (callbacks?.onColorChange) {\n const colorValue = hsvToRgb(value);\n const colorString = colorToString(colorValue, prevState.format);\n callbacks.onColorChange(colorString);\n }\n\n store.notify();\n },\n setOpen: (value: boolean) => {\n if (!stateRef.current) return;\n if (Object.is(stateRef.current.open, value)) return;\n\n stateRef.current.open = value;\n\n if (callbacks?.onOpenChange) {\n callbacks.onOpenChange(value);\n }\n\n store.notify();\n },\n setFormat: (value: ColorFormat) => {\n if (!stateRef.current) return;\n if (Object.is(stateRef.current.format, value)) return;\n\n stateRef.current.format = value;\n\n if (callbacks?.onFormatChange) {\n callbacks.onFormatChange(value);\n }\n\n store.notify();\n },\n notify: () => {\n if (listenersRef.current) {\n for (const cb of listenersRef.current) {\n cb();\n }\n }\n },\n };\n\n return store;\n}\n\nfunction useColorPickerStoreContext(consumerName: string) {\n const context = React.useContext(ColorPickerStoreContext);\n if (!context) {\n throw new Error(\n `\\`${consumerName}\\` must be used within \\`ColorPickerRoot\\``,\n );\n }\n return context;\n}\n\nfunction useColorPickerStore(\n selector: (state: ColorPickerStoreState) => U,\n): U {\n const store = useColorPickerStoreContext(\"useColorPickerStoreSelector\");\n\n const getSnapshot = React.useCallback(\n () => selector(store.getState()),\n [store, selector],\n );\n\n return React.useSyncExternalStore(store.subscribe, getSnapshot, getSnapshot);\n}\n\ninterface ColorPickerContextValue {\n dir: Direction;\n disabled?: boolean;\n inline?: boolean;\n readOnly?: boolean;\n required?: boolean;\n}\n\nconst ColorPickerStoreContext = React.createContext(\n null,\n);\nconst ColorPickerContext = React.createContext(\n null,\n);\n\nfunction useColorPickerContext(consumerName: string) {\n const context = React.useContext(ColorPickerContext);\n if (!context) {\n throw new Error(\n `\\`${consumerName}\\` must be used within \\`ColorPickerRoot\\``,\n );\n }\n return context;\n}\n\ninterface ColorPickerRootProps\n extends\n Omit, \"onValueChange\">,\n Pick<\n React.ComponentProps,\n \"defaultOpen\" | \"open\" | \"onOpenChange\" | \"modal\"\n > {\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n dir?: Direction;\n format?: ColorFormat;\n defaultFormat?: ColorFormat;\n onFormatChange?: (format: ColorFormat) => void;\n name?: string;\n asChild?: boolean;\n disabled?: boolean;\n inline?: boolean;\n readOnly?: boolean;\n required?: boolean;\n}\n\nconst ColorPickerRoot = React.memo((props: ColorPickerRootProps) => {\n const {\n value: valueProp,\n defaultValue = \"#000000\",\n onValueChange,\n format: formatProp,\n defaultFormat = \"hex\",\n onFormatChange,\n defaultOpen,\n open: openProp,\n onOpenChange,\n name,\n disabled,\n inline,\n readOnly,\n required,\n ...rootProps\n } = props;\n\n const initialColor = React.useMemo(() => {\n const colorString = valueProp ?? defaultValue;\n const color = hexToRgb(colorString);\n\n return {\n color,\n hsv: rgbToHsv(color),\n open: openProp ?? defaultOpen ?? false,\n format: formatProp ?? defaultFormat,\n };\n }, [\n valueProp,\n defaultValue,\n formatProp,\n defaultFormat,\n openProp,\n defaultOpen,\n ]);\n\n const stateRef = useLazyRef(() => initialColor);\n const listenersRef = useLazyRef(() => new Set<() => void>());\n\n const storeCallbacks = React.useMemo(\n () => ({\n onColorChange: onValueChange,\n onOpenChange: onOpenChange,\n onFormatChange: onFormatChange,\n }),\n [onValueChange, onOpenChange, onFormatChange],\n );\n\n const store = React.useMemo(\n () => createColorPickerStore(listenersRef, stateRef, storeCallbacks),\n [listenersRef, stateRef, storeCallbacks],\n );\n\n return (\n \n \n \n );\n});\n\ninterface ColorPickerRootImplProps extends Omit<\n ColorPickerRootProps,\n | \"defaultValue\"\n | \"onValueChange\"\n | \"format\"\n | \"defaultFormat\"\n | \"onFormatChange\"\n> {}\n\nfunction ColorPickerRootImpl(props: ColorPickerRootImplProps) {\n const {\n value: valueProp,\n dir: dirProp,\n defaultOpen,\n open: openProp,\n onOpenChange,\n name,\n ref,\n asChild,\n disabled,\n inline,\n modal,\n readOnly,\n required,\n ...rootProps\n } = props;\n\n const store = useColorPickerStoreContext(\"ColorPickerRootImpl\");\n\n const dir = useDirection(dirProp);\n\n const [formTrigger, setFormTrigger] = React.useState(\n null,\n );\n const composedRef = useComposedRefs(ref, (node) => setFormTrigger(node));\n\n const isFormControl = formTrigger ? !!formTrigger.closest(\"form\") : true;\n\n React.useEffect(() => {\n if (valueProp !== undefined) {\n const currentState = store.getState();\n const color = hexToRgb(valueProp, currentState.color.a);\n const hsv = rgbToHsv(color);\n store.setColor(color);\n store.setHsv(hsv);\n }\n }, [valueProp, store]);\n\n React.useEffect(() => {\n if (openProp !== undefined) {\n store.setOpen(openProp);\n }\n }, [openProp, store]);\n\n const contextValue = React.useMemo(\n () => ({\n dir,\n disabled,\n inline,\n readOnly,\n required,\n }),\n [dir, disabled, inline, readOnly, required],\n );\n\n const value = useColorPickerStore((state) => rgbToHex(state.color));\n\n const open = useColorPickerStore((state) => state.open);\n\n const onPopoverOpenChange = React.useCallback(\n (newOpen: boolean) => {\n store.setOpen(newOpen);\n onOpenChange?.(newOpen);\n },\n [store.setOpen, onOpenChange],\n );\n\n const RootPrimitive = asChild ? SlotPrimitive : \"div\";\n\n if (inline) {\n return (\n \n \n {isFormControl && (\n \n )}\n \n );\n }\n\n return (\n \n \n \n {isFormControl && (\n \n )}\n \n \n );\n}\n\ninterface ColorPickerTriggerProps extends React.ComponentProps<\n typeof PopoverTrigger\n> {}\n\nfunction ColorPickerTrigger(props: ColorPickerTriggerProps) {\n const { asChild, ...triggerProps } = props;\n const context = useColorPickerContext(\"ColorPickerTrigger\");\n\n const TriggerPrimitive = asChild ? SlotPrimitive : Button;\n\n return (\n \n \n \n );\n}\n\ninterface ColorPickerContentProps extends React.ComponentProps<\n typeof PopoverContent\n> {}\n\nfunction ColorPickerContent(props: ColorPickerContentProps) {\n const { asChild, className, children, ...popoverContentProps } = props;\n const context = useColorPickerContext(\"ColorPickerContent\");\n\n if (context.inline) {\n const ContentPrimitive = asChild ? SlotPrimitive : \"div\";\n\n return (\n \n {children}\n \n );\n }\n\n return (\n \n {children}\n \n );\n}\n\ninterface ColorPickerAreaProps extends React.ComponentProps<\"div\"> {\n asChild?: boolean;\n}\n\nfunction ColorPickerArea(props: ColorPickerAreaProps) {\n const { asChild, className, ref, ...areaProps } = props;\n const context = useColorPickerContext(\"ColorPickerArea\");\n const store = useColorPickerStoreContext(\"ColorPickerArea\");\n\n const hsv = useColorPickerStore((state) => state.hsv);\n\n const isDraggingRef = React.useRef(false);\n const areaRef = React.useRef(null);\n const composedRef = useComposedRefs(ref, areaRef);\n\n const updateColorFromPosition = React.useCallback(\n (clientX: number, clientY: number) => {\n if (!areaRef.current) return;\n\n const rect = areaRef.current.getBoundingClientRect();\n const x = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));\n const y = Math.max(\n 0,\n Math.min(1, 1 - (clientY - rect.top) / rect.height),\n );\n\n const newHsv: HSVColorValue = {\n h: hsv?.h ?? 0,\n s: Math.round(x * 100),\n v: Math.round(y * 100),\n a: hsv?.a ?? 1,\n };\n\n store.setHsv(newHsv);\n store.setColor(hsvToRgb(newHsv));\n },\n [hsv, store],\n );\n\n const onPointerDown = React.useCallback(\n (event: React.PointerEvent) => {\n if (context.disabled) return;\n\n isDraggingRef.current = true;\n areaRef.current?.setPointerCapture(event.pointerId);\n updateColorFromPosition(event.clientX, event.clientY);\n },\n [context.disabled, updateColorFromPosition],\n );\n\n const onPointerMove = React.useCallback(\n (event: React.PointerEvent) => {\n if (isDraggingRef.current) {\n updateColorFromPosition(event.clientX, event.clientY);\n }\n },\n [updateColorFromPosition],\n );\n\n const onPointerUp = React.useCallback((event: React.PointerEvent) => {\n isDraggingRef.current = false;\n areaRef.current?.releasePointerCapture(event.pointerId);\n }, []);\n\n const hue = hsv?.h ?? 0;\n const backgroundHue = hsvToRgb({ h: hue, s: 100, v: 100, a: 1 });\n\n const AreaPrimitive = asChild ? SlotPrimitive : \"div\";\n\n return (\n \n \n \n \n \n \n \n \n );\n}\n\ninterface ColorPickerHueSliderProps extends React.ComponentProps {}\n\nfunction ColorPickerHueSlider(props: ColorPickerHueSliderProps) {\n const { className, ...sliderProps } = props;\n const context = useColorPickerContext(\"ColorPickerHueSlider\");\n const store = useColorPickerStoreContext(\"ColorPickerHueSlider\");\n\n const hsv = useColorPickerStore((state) => state.hsv);\n\n const onValueChange = React.useCallback(\n (values: number[]) => {\n const newHsv: HSVColorValue = {\n h: values[0] ?? 0,\n s: hsv?.s ?? 0,\n v: hsv?.v ?? 0,\n a: hsv?.a ?? 1,\n };\n store.setHsv(newHsv);\n store.setColor(hsvToRgb(newHsv));\n },\n [hsv, store],\n );\n\n return (\n \n );\n}\n\ninterface ColorPickerAlphaSliderProps extends React.ComponentProps {}\n\nfunction ColorPickerAlphaSlider(props: ColorPickerAlphaSliderProps) {\n const { className, ...sliderProps } = props;\n const context = useColorPickerContext(\"ColorPickerAlphaSlider\");\n const store = useColorPickerStoreContext(\"ColorPickerAlphaSlider\");\n\n const color = useColorPickerStore((state) => state.color);\n const hsv = useColorPickerStore((state) => state.hsv);\n\n const onValueChange = React.useCallback(\n (values: number[]) => {\n const alpha = (values[0] ?? 0) / 100;\n const newColor = { ...color, a: alpha };\n const newHsv = { ...hsv, a: alpha };\n store.setColor(newColor);\n store.setHsv(newHsv);\n },\n [color, hsv, store],\n );\n\n const gradientColor = `rgb(${color?.r ?? 0}, ${color?.g ?? 0}, ${color?.b ?? 0})`;\n\n return (\n \n );\n}\n\ninterface ColorPickerSwatchProps extends React.ComponentProps<\"div\"> {\n asChild?: boolean;\n}\n\nfunction ColorPickerSwatch(props: ColorPickerSwatchProps) {\n const { asChild, className, ...swatchProps } = props;\n const context = useColorPickerContext(\"ColorPickerSwatch\");\n\n const color = useColorPickerStore((state) => state.color);\n const format = useColorPickerStore((state) => state.format);\n\n const backgroundStyle = React.useMemo(() => {\n if (!color) {\n return {\n background:\n \"linear-gradient(to bottom right, transparent calc(50% - 1px), var(--destructive) calc(50% - 1px) calc(50% + 1px), transparent calc(50% + 1px)) no-repeat\",\n };\n }\n\n const colorString = `rgba(${color.r}, ${color.g}, ${color.b}, ${color.a})`;\n\n if (color.a < 1) {\n return {\n background: `linear-gradient(${colorString}, ${colorString}), repeating-conic-gradient(var(--border) 0% 25%, var(--background) 0% 50%) 0% 50% / 8px 8px`,\n };\n }\n\n return {\n backgroundColor: colorString,\n };\n }, [color]);\n\n const ariaLabel = !color\n ? \"No color selected\"\n : `Current color: ${colorToString(color, format)}`;\n\n const SwatchPrimitive = asChild ? SlotPrimitive : \"div\";\n\n return (\n \n );\n}\n\ninterface ColorPickerEyeDropperProps extends React.ComponentProps<\n typeof Button\n> {}\n\nfunction ColorPickerEyeDropper(props: ColorPickerEyeDropperProps) {\n const { children, size, ...buttonProps } = props;\n const context = useColorPickerContext(\"ColorPickerEyeDropper\");\n const store = useColorPickerStoreContext(\"ColorPickerEyeDropper\");\n\n const color = useColorPickerStore((state) => state.color);\n\n const onEyeDropper = React.useCallback(async () => {\n if (!window.EyeDropper) return;\n\n try {\n const eyeDropper = new window.EyeDropper();\n const result = await eyeDropper.open();\n\n if (result.sRGBHex) {\n const currentAlpha = color?.a ?? 1;\n const newColor = hexToRgb(result.sRGBHex, currentAlpha);\n const newHsv = rgbToHsv(newColor);\n store.setColor(newColor);\n store.setHsv(newHsv);\n }\n } catch (error) {\n console.warn(\"EyeDropper error:\", error);\n }\n }, [color, store]);\n\n const hasEyeDropper = typeof window !== \"undefined\" && !!window.EyeDropper;\n\n if (!hasEyeDropper) return null;\n\n const buttonSize = size ?? (children ? \"default\" : \"icon\");\n\n return (\n \n {children ?? }\n \n );\n}\n\ninterface ColorPickerFormatSelectProps\n extends\n Omit, \"value\" | \"onValueChange\">,\n Pick, \"size\" | \"className\"> {}\n\nfunction ColorPickerFormatSelect(props: ColorPickerFormatSelectProps) {\n const { size, className, ...selectProps } = props;\n const context = useColorPickerContext(\"ColorPickerFormatSelector\");\n const store = useColorPickerStoreContext(\"ColorPickerFormatSelector\");\n\n const format = useColorPickerStore((state) => state.format);\n\n const onFormatChange = React.useCallback(\n (value: ColorFormat) => {\n store.setFormat(value);\n },\n [store],\n );\n\n return (\n \n \n \n \n \n {colorFormats.map((format) => (\n \n {format.toUpperCase()}\n \n ))}\n \n \n );\n}\n\ninterface ColorPickerInputProps extends Omit<\n React.ComponentProps,\n \"value\" | \"onChange\" | \"color\"\n> {\n withoutAlpha?: boolean;\n}\n\nfunction ColorPickerInput(props: ColorPickerInputProps) {\n const context = useColorPickerContext(\"ColorPickerInput\");\n const store = useColorPickerStoreContext(\"ColorPickerInput\");\n\n const color = useColorPickerStore((state) => state.color);\n const format = useColorPickerStore((state) => state.format);\n const hsv = useColorPickerStore((state) => state.hsv);\n\n const onColorChange = React.useCallback(\n (newColor: ColorValue) => {\n const newHsv = rgbToHsv(newColor);\n store.setColor(newColor);\n store.setHsv(newHsv);\n },\n [store],\n );\n\n if (format === \"hex\") {\n return (\n \n );\n }\n\n if (format === \"rgb\") {\n return (\n \n );\n }\n\n if (format === \"hsl\") {\n return (\n \n );\n }\n\n if (format === \"hsb\") {\n return (\n \n );\n }\n}\n\nconst inputGroupItemVariants = cva(\n \"h-8 [-moz-appearance:_textfield] focus-visible:z-10 focus-visible:ring-1 [&::-webkit-inner-spin-button]:m-0 [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:m-0 [&::-webkit-outer-spin-button]:appearance-none\",\n {\n variants: {\n position: {\n first: \"rounded-e-none\",\n middle: \"-ms-px rounded-none border-l-0\",\n last: \"-ms-px rounded-s-none border-l-0\",\n isolated: \"\",\n },\n },\n defaultVariants: {\n position: \"isolated\",\n },\n },\n);\n\ninterface InputGroupItemProps\n extends\n React.ComponentProps,\n VariantProps {}\n\nfunction InputGroupItem({\n className,\n position,\n ...props\n}: InputGroupItemProps) {\n return (\n \n );\n}\n\ninterface FormatInputProps extends ColorPickerInputProps {\n color: ColorValue;\n onColorChange: (color: ColorValue) => void;\n context: ColorPickerContextValue;\n}\n\nfunction HexInput(props: FormatInputProps) {\n const {\n color,\n onColorChange,\n context,\n withoutAlpha,\n className,\n ...inputProps\n } = props;\n\n const hexValue = rgbToHex(color);\n const alphaValue = Math.round((color?.a ?? 1) * 100);\n\n const onHexChange = React.useCallback(\n (event: React.ChangeEvent) => {\n const value = event.target.value;\n const parsedColor = parseColorString(value);\n if (parsedColor) {\n onColorChange({ ...parsedColor, a: color?.a ?? 1 });\n }\n },\n [color, onColorChange],\n );\n\n const onAlphaChange = React.useCallback(\n (event: React.ChangeEvent) => {\n const value = Number.parseInt(event.target.value, 10);\n if (!Number.isNaN(value) && value >= 0 && value <= 100) {\n onColorChange({ ...color, a: value / 100 });\n }\n },\n [color, onColorChange],\n );\n\n if (withoutAlpha) {\n return (\n \n );\n }\n\n return (\n \n \n \n \n );\n}\n\nfunction RgbInput(props: FormatInputProps) {\n const {\n color,\n onColorChange,\n context,\n withoutAlpha,\n className,\n ...inputProps\n } = props;\n\n const rValue = Math.round(color?.r ?? 0);\n const gValue = Math.round(color?.g ?? 0);\n const bValue = Math.round(color?.b ?? 0);\n const alphaValue = Math.round((color?.a ?? 1) * 100);\n\n const onChannelChange = React.useCallback(\n (channel: \"r\" | \"g\" | \"b\" | \"a\", max: number, isAlpha = false) =>\n (event: React.ChangeEvent) => {\n const value = Number.parseInt(event.target.value, 10);\n if (!Number.isNaN(value) && value >= 0 && value <= max) {\n const newValue = isAlpha ? value / 100 : value;\n onColorChange({ ...color, [channel]: newValue });\n }\n },\n [color, onColorChange],\n );\n\n return (\n \n \n \n \n {!withoutAlpha && (\n \n )}\n \n );\n}\n\nfunction HslInput(props: FormatInputProps) {\n const {\n color,\n onColorChange,\n context,\n withoutAlpha,\n className,\n ...inputProps\n } = props;\n\n const hsl = React.useMemo(() => rgbToHsl(color), [color]);\n const alphaValue = Math.round((color?.a ?? 1) * 100);\n\n const onHslChannelChange = React.useCallback(\n (channel: \"h\" | \"s\" | \"l\", max: number) =>\n (event: React.ChangeEvent) => {\n const value = Number.parseInt(event.target.value, 10);\n if (!Number.isNaN(value) && value >= 0 && value <= max) {\n const newHsl = { ...hsl, [channel]: value };\n const newColor = hslToRgb(newHsl, color?.a ?? 1);\n onColorChange(newColor);\n }\n },\n [hsl, color, onColorChange],\n );\n\n const onAlphaChange = React.useCallback(\n (event: React.ChangeEvent) => {\n const value = Number.parseInt(event.target.value, 10);\n if (!Number.isNaN(value) && value >= 0 && value <= 100) {\n onColorChange({ ...color, a: value / 100 });\n }\n },\n [color, onColorChange],\n );\n\n return (\n \n \n \n \n {!withoutAlpha && (\n \n )}\n \n );\n}\n\ninterface HsbInputProps extends Omit {\n hsv: HSVColorValue;\n}\n\nfunction HsbInput(props: HsbInputProps) {\n const {\n hsv,\n onColorChange,\n context,\n withoutAlpha,\n className,\n ...inputProps\n } = props;\n\n const alphaValue = Math.round((hsv?.a ?? 1) * 100);\n\n const onHsvChannelChange = React.useCallback(\n (channel: \"h\" | \"s\" | \"v\", max: number) =>\n (event: React.ChangeEvent) => {\n const value = Number.parseInt(event.target.value, 10);\n if (!Number.isNaN(value) && value >= 0 && value <= max) {\n const newHsv = { ...hsv, [channel]: value };\n const newColor = hsvToRgb(newHsv);\n onColorChange(newColor);\n }\n },\n [hsv, onColorChange],\n );\n\n const onAlphaChange = React.useCallback(\n (event: React.ChangeEvent) => {\n const value = Number.parseInt(event.target.value, 10);\n if (!Number.isNaN(value) && value >= 0 && value <= 100) {\n const currentColor = hsvToRgb(hsv);\n onColorChange({ ...currentColor, a: value / 100 });\n }\n },\n [hsv, onColorChange],\n );\n\n return (\n \n \n \n \n {!withoutAlpha && (\n \n )}\n \n );\n}\n\nexport {\n ColorPickerRoot as ColorPicker,\n ColorPickerTrigger,\n ColorPickerContent,\n ColorPickerArea,\n ColorPickerHueSlider,\n ColorPickerAlphaSlider,\n ColorPickerSwatch,\n ColorPickerEyeDropper,\n ColorPickerFormatSelect,\n ColorPickerInput,\n //\n ColorPickerRoot as Root,\n ColorPickerTrigger as Trigger,\n ColorPickerContent as Content,\n ColorPickerArea as Area,\n ColorPickerHueSlider as HueSlider,\n ColorPickerAlphaSlider as AlphaSlider,\n ColorPickerSwatch as Swatch,\n ColorPickerEyeDropper as EyeDropper,\n ColorPickerFormatSelect as FormatSelect,\n ColorPickerInput as Input,\n //\n useColorPickerStore as useColorPicker,\n};\n", "type": "registry:component" }, { "path": "src/components/editor/editor-ui/content-editable.tsx", "content": "import type { JSX } from \"react\";\n\nimport { ContentEditable as LexicalContentEditable } from \"@lexical/react/LexicalContentEditable\";\n\ntype Props = {\n placeholder: string;\n className?: string;\n placeholderClassName?: string;\n};\n\nexport function ContentEditable({\n placeholder,\n className,\n placeholderClassName,\n}: Props): JSX.Element {\n return (\n \n {placeholder}\n \n }\n />\n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/editor-ui/date-time-component.tsx", "content": "import type { JSX } from \"react\";\nimport * as React from \"react\";\nimport { useState } from \"react\";\n\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { useLexicalNodeSelection } from \"@lexical/react/useLexicalNodeSelection\";\nimport {\n $getNodeByKey,\n IS_BOLD,\n IS_HIGHLIGHT,\n IS_ITALIC,\n IS_STRIKETHROUGH,\n IS_UNDERLINE,\n type NodeKey,\n} from \"lexical\";\n\nimport { format } from \"date-fns\";\nimport { setHours, setMinutes } from \"date-fns\";\nimport { CalendarIcon } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { Calendar } from \"@/components/ui/calendar\";\nimport { Checkbox } from \"@/components/ui/checkbox\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/components/ui/popover\";\nimport { cn } from \"@/lib/utils\";\n\nimport { $isDateTimeNode, type DateTimeNode } from \"../nodes/date-time-node\";\n\nconst userTimeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;\n\nexport default function DateTimeComponent({\n dateTime,\n format: textFormat,\n nodeKey,\n}: {\n dateTime: Date | undefined;\n format: number;\n nodeKey: NodeKey;\n}): JSX.Element {\n const [editor] = useLexicalComposerContext();\n const [selected, setSelected] = useState(dateTime);\n const [includeTime, setIncludeTime] = useState(() => {\n if (!dateTime) return false;\n return dateTime.getHours() !== 0 || dateTime.getMinutes() !== 0;\n });\n const [timeValue, setTimeValue] = useState(() => {\n if (!dateTime) return \"00:00\";\n const h = dateTime.getHours();\n const m = dateTime.getMinutes();\n if (h !== 0 || m !== 0) {\n return `${h.toString().padStart(2, \"0\")}:${m.toString().padStart(2, \"0\")}`;\n }\n return \"00:00\";\n });\n const [isNodeSelected] = useLexicalNodeSelection(nodeKey);\n\n const withDateTimeNode = (cb: (node: DateTimeNode) => void): void => {\n editor.update(() => {\n const node = $getNodeByKey(nodeKey);\n if ($isDateTimeNode(node)) {\n cb(node);\n }\n });\n };\n\n const handleCheckedChange = (checked: boolean) => {\n withDateTimeNode((node) => {\n if (checked) {\n setIncludeTime(true);\n } else {\n if (selected) {\n node.setDateTime(setHours(setMinutes(selected, 0), 0));\n }\n setIncludeTime(false);\n setTimeValue(\"00:00\");\n }\n });\n };\n\n const handleTimeChange = (e: React.ChangeEvent) => {\n withDateTimeNode((node) => {\n const time = e.target.value;\n if (!selected) {\n setTimeValue(time);\n return;\n }\n const [hours, minutes] = time.split(\":\").map((s) => parseInt(s, 10));\n const newDate = setHours(setMinutes(selected, minutes), hours);\n setSelected(newDate);\n node.setDateTime(newDate);\n setTimeValue(time);\n });\n };\n\n const handleDaySelect = (date: Date | undefined) => {\n withDateTimeNode((node) => {\n if (!date) {\n setSelected(date);\n return;\n }\n const [hours, minutes] = timeValue.split(\":\").map((s) => parseInt(s, 10));\n const newDate = new Date(\n date.getFullYear(),\n date.getMonth(),\n date.getDate(),\n hours,\n minutes,\n );\n node.setDateTime(newDate);\n setSelected(newDate);\n });\n };\n\n const displayLabel = dateTime\n ? format(dateTime, includeTime ? \"PPP p\" : \"PPP\")\n : null;\n\n return (\n \n \n \n \n {displayLabel ?? Pick a date}\n \n \n \n \n \n \n \n handleCheckedChange(checked === true)\n }\n />\n \n \n \n {userTimeZone}\n \n \n \n \n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/editor-ui/image-component.tsx", "content": "import type { JSX } from \"react\";\nimport {\n Suspense,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { useLexicalEditable } from \"@lexical/react/useLexicalEditable\";\nimport { useLexicalNodeSelection } from \"@lexical/react/useLexicalNodeSelection\";\nimport { mergeRegister } from \"@lexical/utils\";\nimport type { LexicalCommand, LexicalEditor, NodeKey } from \"lexical\";\nimport {\n $getSelection,\n $isNodeSelection,\n $isRangeSelection,\n $setSelection,\n CLICK_COMMAND,\n COMMAND_PRIORITY_LOW,\n DRAGSTART_COMMAND,\n KEY_ENTER_COMMAND,\n KEY_ESCAPE_COMMAND,\n SELECTION_CHANGE_COMMAND,\n createCommand,\n} from \"lexical\";\n\ntype ImageStatus =\n | { error: true }\n | { error: false; width: number; height: number };\n\nconst imageCache = new Map | ImageStatus>();\n\nexport const RIGHT_CLICK_IMAGE_COMMAND: LexicalCommand =\n createCommand(\"RIGHT_CLICK_IMAGE_COMMAND\");\n\nfunction useSuspenseImage(src: string): ImageStatus {\n let cached = imageCache.get(src);\n if (cached && \"error\" in cached && typeof cached.error === \"boolean\") {\n return cached;\n } else if (!cached) {\n cached = new Promise((resolve) => {\n const img = new Image();\n img.src = src;\n img.onload = () =>\n resolve({\n error: false,\n height: img.naturalHeight,\n width: img.naturalWidth,\n });\n img.onerror = () => resolve({ error: true });\n }).then((rval) => {\n imageCache.set(src, rval);\n return rval;\n });\n imageCache.set(src, cached);\n throw cached;\n }\n throw cached;\n}\n\nfunction isSVG(src: string): boolean {\n const lowerCaseSrc = src.toLowerCase();\n return (\n lowerCaseSrc.endsWith(\".svg\") ||\n lowerCaseSrc.startsWith(\"data:image/svg+xml\")\n );\n}\n\nfunction LazyImage({\n altText,\n className,\n imageRef,\n src,\n width,\n height,\n maxWidth,\n onError,\n}: {\n altText: string;\n className: string | null;\n height: \"inherit\" | number;\n imageRef: { current: null | HTMLImageElement };\n maxWidth: number;\n src: string;\n width: \"inherit\" | number;\n onError: () => void;\n}): JSX.Element {\n const status = useSuspenseImage(src);\n\n useEffect(() => {\n if (status.error) {\n onError();\n }\n }, [status.error, onError]);\n\n if (status.error) {\n return ;\n }\n\n // Calculate final dimensions with proper scaling\n const calculateDimensions = () => {\n if (width !== \"inherit\" && height !== \"inherit\") {\n return {\n height,\n maxWidth,\n width,\n };\n }\n\n const isActuallySVG = isSVG(src);\n\n // For standard images, Lexical expects 'inherit'\n if (!isActuallySVG) {\n return {\n height,\n maxWidth,\n width,\n };\n }\n\n // Use natural dimensions if available, otherwise fallback to defaults\n const naturalWidth = status.width;\n const naturalHeight = status.height;\n\n // If SVG has no intrinsic dimensions (0), fallback to a sensible default (maxWidth)\n let finalWidth = naturalWidth || maxWidth;\n let finalHeight = naturalHeight || finalWidth;\n\n // Scale down if width exceeds maxWidth while maintaining aspect ratio\n if (finalWidth > maxWidth) {\n const scale = maxWidth / finalWidth;\n finalWidth = maxWidth;\n finalHeight = Math.round(finalHeight * scale);\n }\n\n // Scale down if height exceeds maxHeight while maintaining aspect ratio\n const maxHeight = 500;\n if (finalHeight > maxHeight) {\n const scale = maxHeight / finalHeight;\n finalHeight = maxHeight;\n finalWidth = Math.round(finalWidth * scale);\n }\n\n return {\n height: finalHeight,\n maxWidth,\n width: finalWidth,\n };\n };\n\n const imageStyle = calculateDimensions();\n\n return (\n \n );\n}\n\nfunction BrokenImage(): JSX.Element {\n return (\n \n );\n}\n\nexport default function ImageComponent({\n src,\n altText,\n nodeKey,\n width,\n height,\n maxWidth,\n}: {\n altText: string;\n height: \"inherit\" | number;\n maxWidth: number;\n nodeKey: NodeKey;\n src: string;\n width: \"inherit\" | number;\n}): JSX.Element {\n const imageRef = useRef(null);\n const buttonRef = useRef(null);\n const [isSelected, setSelected, clearSelection] =\n useLexicalNodeSelection(nodeKey);\n const [editor] = useLexicalComposerContext();\n const activeEditorRef = useRef(null);\n const [isLoadError, setIsLoadError] = useState(false);\n const isEditable = useLexicalEditable();\n const isInNodeSelection = useMemo(\n () =>\n isSelected &&\n editor.getEditorState().read(() => {\n const selection = $getSelection();\n return $isNodeSelection(selection) && selection.has(nodeKey);\n }),\n [editor, isSelected, nodeKey],\n );\n\n const $onEnter = useCallback(\n (event: KeyboardEvent) => {\n const latestSelection = $getSelection();\n const buttonElem = buttonRef.current;\n if (\n $isNodeSelection(latestSelection) &&\n latestSelection.has(nodeKey) &&\n latestSelection.getNodes().length === 1\n ) {\n if (buttonElem !== null && buttonElem !== document.activeElement) {\n event.preventDefault();\n buttonElem.focus();\n return true;\n }\n }\n return false;\n },\n [nodeKey],\n );\n\n const $onEscape = useCallback(\n (event: KeyboardEvent) => {\n if (buttonRef.current === event.target) {\n $setSelection(null);\n editor.update(() => {\n setSelected(true);\n const parentRootElement = editor.getRootElement();\n if (parentRootElement !== null) {\n parentRootElement.focus();\n }\n });\n return true;\n }\n return false;\n },\n [editor, setSelected],\n );\n\n const onClick = useCallback(\n (payload: MouseEvent) => {\n const event = payload;\n\n if (event.target === imageRef.current) {\n if (event.shiftKey) {\n setSelected(!isSelected);\n } else {\n clearSelection();\n setSelected(true);\n }\n return true;\n }\n\n return false;\n },\n [isSelected, setSelected, clearSelection],\n );\n\n const onRightClick = useCallback(\n (event: MouseEvent): void => {\n editor.getEditorState().read(() => {\n const latestSelection = $getSelection();\n const domElement = event.target as HTMLElement;\n if (\n domElement.tagName === \"IMG\" &&\n $isRangeSelection(latestSelection) &&\n latestSelection.getNodes().length === 1\n ) {\n editor.dispatchCommand(RIGHT_CLICK_IMAGE_COMMAND, event);\n }\n });\n },\n [editor],\n );\n\n useEffect(() => {\n return mergeRegister(\n editor.registerCommand(\n SELECTION_CHANGE_COMMAND,\n (_, activeEditor) => {\n activeEditorRef.current = activeEditor;\n return false;\n },\n COMMAND_PRIORITY_LOW,\n ),\n editor.registerCommand(\n DRAGSTART_COMMAND,\n (event) => {\n if (event.target === imageRef.current) {\n // TODO This is just a temporary workaround for FF to behave like other browsers.\n // Ideally, this handles drag & drop too (and all browsers).\n event.preventDefault();\n return true;\n }\n return false;\n },\n COMMAND_PRIORITY_LOW,\n ),\n );\n }, [editor]);\n useEffect(() => {\n return mergeRegister(\n editor.registerCommand(\n CLICK_COMMAND,\n onClick,\n COMMAND_PRIORITY_LOW,\n ),\n editor.registerCommand(\n RIGHT_CLICK_IMAGE_COMMAND,\n onClick,\n COMMAND_PRIORITY_LOW,\n ),\n editor.registerCommand(KEY_ENTER_COMMAND, $onEnter, COMMAND_PRIORITY_LOW),\n editor.registerCommand(\n KEY_ESCAPE_COMMAND,\n $onEscape,\n COMMAND_PRIORITY_LOW,\n ),\n editor.registerRootListener((rootElement) => {\n if (rootElement) {\n rootElement.addEventListener(\"contextmenu\", onRightClick);\n return () =>\n rootElement.removeEventListener(\"contextmenu\", onRightClick);\n }\n }),\n );\n }, [editor, $onEnter, $onEscape, onClick, onRightClick]);\n\n const draggable = isInNodeSelection;\n const isFocused = isSelected && isEditable;\n return (\n \n <>\n \n {isLoadError ? (\n \n ) : (\n setIsLoadError(true)}\n />\n )}\n \n >\n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/editor-ui/image-resizer.tsx", "content": "import * as React from \"react\";\nimport { type JSX, useRef } from \"react\";\n\nimport { calculateZoomLevel } from \"@lexical/utils\";\nimport type { LexicalEditor } from \"lexical\";\n\nfunction clamp(value: number, min: number, max: number) {\n return Math.min(Math.max(value, min), max);\n}\n\nconst Direction = {\n east: 1 << 0,\n north: 1 << 3,\n south: 1 << 1,\n west: 1 << 2,\n};\n\nexport function ImageResizer({\n onResizeStart,\n onResizeEnd,\n imageRef,\n maxWidth,\n editor,\n}: {\n editor: LexicalEditor;\n imageRef: { current: null | HTMLElement };\n maxWidth?: number;\n onResizeEnd: (width: \"inherit\" | number, height: \"inherit\" | number) => void;\n onResizeStart: () => void;\n}): JSX.Element {\n const controlWrapperRef = useRef(null);\n const userSelect = useRef({\n priority: \"\",\n value: \"default\",\n });\n const positioningRef = useRef<{\n currentHeight: \"inherit\" | number;\n currentWidth: \"inherit\" | number;\n direction: number;\n isResizing: boolean;\n ratio: number;\n startHeight: number;\n startWidth: number;\n startX: number;\n startY: number;\n }>({\n currentHeight: 0,\n currentWidth: 0,\n direction: 0,\n isResizing: false,\n ratio: 0,\n startHeight: 0,\n startWidth: 0,\n startX: 0,\n startY: 0,\n });\n const editorRootElement = editor.getRootElement();\n // Find max width, accounting for editor padding.\n const maxWidthContainer = maxWidth\n ? maxWidth\n : editorRootElement !== null\n ? editorRootElement.getBoundingClientRect().width - 20\n : 100;\n const maxHeightContainer =\n editorRootElement !== null\n ? editorRootElement.getBoundingClientRect().height - 20\n : 100;\n\n const minWidth = 100;\n const minHeight = 100;\n\n const setStartCursor = (direction: number) => {\n const ew = direction === Direction.east || direction === Direction.west;\n const ns = direction === Direction.north || direction === Direction.south;\n const nwse =\n (direction & Direction.north && direction & Direction.west) ||\n (direction & Direction.south && direction & Direction.east);\n\n const cursorDir = ew ? \"ew\" : ns ? \"ns\" : nwse ? \"nwse\" : \"nesw\";\n\n if (editorRootElement !== null) {\n editorRootElement.style.setProperty(\n \"cursor\",\n `${cursorDir}-resize`,\n \"important\",\n );\n }\n if (document.body !== null) {\n document.body.style.setProperty(\n \"cursor\",\n `${cursorDir}-resize`,\n \"important\",\n );\n userSelect.current.value = document.body.style.getPropertyValue(\n \"-webkit-user-select\",\n );\n userSelect.current.priority = document.body.style.getPropertyPriority(\n \"-webkit-user-select\",\n );\n document.body.style.setProperty(\n \"-webkit-user-select\",\n `none`,\n \"important\",\n );\n }\n };\n\n const setEndCursor = () => {\n if (editorRootElement !== null) {\n editorRootElement.style.setProperty(\"cursor\", \"text\");\n }\n if (document.body !== null) {\n document.body.style.setProperty(\"cursor\", \"default\");\n document.body.style.setProperty(\n \"-webkit-user-select\",\n userSelect.current.value,\n userSelect.current.priority,\n );\n }\n };\n\n const handlePointerDown = (\n event: React.PointerEvent,\n direction: number,\n ) => {\n if (!editor.isEditable()) {\n return;\n }\n\n const image = imageRef.current;\n const controlWrapper = controlWrapperRef.current;\n\n if (image !== null && controlWrapper !== null) {\n event.preventDefault();\n const { width, height } = image.getBoundingClientRect();\n const zoom = calculateZoomLevel(image);\n const positioning = positioningRef.current;\n positioning.startWidth = width;\n positioning.startHeight = height;\n positioning.ratio = width / height;\n positioning.currentWidth = width;\n positioning.currentHeight = height;\n positioning.startX = event.clientX / zoom;\n positioning.startY = event.clientY / zoom;\n positioning.isResizing = true;\n positioning.direction = direction;\n\n setStartCursor(direction);\n onResizeStart();\n\n controlWrapper.classList.add(\"touch-action-none\");\n image.style.height = `${height}px`;\n image.style.width = `${width}px`;\n\n document.addEventListener(\"pointermove\", handlePointerMove);\n document.addEventListener(\"pointerup\", handlePointerUp);\n }\n };\n const handlePointerMove = (event: PointerEvent) => {\n const image = imageRef.current;\n const positioning = positioningRef.current;\n\n const isHorizontal =\n positioning.direction & (Direction.east | Direction.west);\n const isVertical =\n positioning.direction & (Direction.south | Direction.north);\n\n if (image !== null && positioning.isResizing) {\n const zoom = calculateZoomLevel(image);\n // Corner cursor\n if (isHorizontal && isVertical) {\n let diff = Math.floor(positioning.startX - event.clientX / zoom);\n diff = positioning.direction & Direction.east ? -diff : diff;\n\n const width = clamp(\n positioning.startWidth + diff,\n minWidth,\n maxWidthContainer,\n );\n\n const height = width / positioning.ratio;\n image.style.width = `${width}px`;\n image.style.height = `${height}px`;\n positioning.currentHeight = height;\n positioning.currentWidth = width;\n } else if (isVertical) {\n let diff = Math.floor(positioning.startY - event.clientY / zoom);\n diff = positioning.direction & Direction.south ? -diff : diff;\n\n const height = clamp(\n positioning.startHeight + diff,\n minHeight,\n maxHeightContainer,\n );\n\n image.style.height = `${height}px`;\n positioning.currentHeight = height;\n } else {\n let diff = Math.floor(positioning.startX - event.clientX / zoom);\n diff = positioning.direction & Direction.east ? -diff : diff;\n\n const width = clamp(\n positioning.startWidth + diff,\n minWidth,\n maxWidthContainer,\n );\n\n image.style.width = `${width}px`;\n positioning.currentWidth = width;\n }\n }\n };\n const handlePointerUp = () => {\n const image = imageRef.current;\n const positioning = positioningRef.current;\n const controlWrapper = controlWrapperRef.current;\n if (image !== null && controlWrapper !== null && positioning.isResizing) {\n const width = positioning.currentWidth;\n const height = positioning.currentHeight;\n positioning.startWidth = 0;\n positioning.startHeight = 0;\n positioning.ratio = 0;\n positioning.startX = 0;\n positioning.startY = 0;\n positioning.currentWidth = 0;\n positioning.currentHeight = 0;\n positioning.isResizing = false;\n\n controlWrapper.classList.remove(\"touch-action-none\");\n\n setEndCursor();\n onResizeEnd(width, height);\n\n document.removeEventListener(\"pointermove\", handlePointerMove);\n document.removeEventListener(\"pointerup\", handlePointerUp);\n }\n };\n return (\n \n {\n handlePointerDown(event, Direction.north);\n }}\n />\n {\n handlePointerDown(event, Direction.north | Direction.east);\n }}\n />\n {\n handlePointerDown(event, Direction.east);\n }}\n />\n {\n handlePointerDown(event, Direction.south | Direction.east);\n }}\n />\n {\n handlePointerDown(event, Direction.south);\n }}\n />\n {\n handlePointerDown(event, Direction.south | Direction.west);\n }}\n />\n {\n handlePointerDown(event, Direction.west);\n }}\n />\n {\n handlePointerDown(event, Direction.north | Direction.west);\n }}\n />\n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/extensions/auto-link-extension.tsx", "content": "import { $isCodeNode } from \"@lexical/code\";\nimport {\n AutoLinkExtension as LexicalAutoLinkExtension,\n createLinkMatcherWithRegExp,\n} from \"@lexical/link\";\nimport { configExtension } from \"lexical\";\n\nconst URL_REGEX =\n /((https?:\\/\\/(www\\.)?)|(www\\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)(?()[\\]\\\\.,;:\\s@\"]+(\\.[^<>()[\\]\\\\.,;:\\s@\"]+)*)|(\".+\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))/;\n\nexport const AutoLinkExtension = configExtension(LexicalAutoLinkExtension, {\n excludeParents: [$isCodeNode],\n matchers: [\n createLinkMatcherWithRegExp(URL_REGEX, (text) => {\n return text.startsWith(\"http\") ? text : `https://${text}`;\n }),\n createLinkMatcherWithRegExp(EMAIL_REGEX, (text) => {\n return `mailto:${text}`;\n }),\n ],\n});\n", "type": "registry:component" }, { "path": "src/components/editor/extensions/date-time-extension.tsx", "content": "import { $insertNodeIntoLeaf, $wrapNodeInElement } from \"@lexical/utils\";\nimport {\n $createParagraphNode,\n $getSelection,\n $isRangeSelection,\n $isRootOrShadowRoot,\n COMMAND_PRIORITY_EDITOR,\n type LexicalCommand,\n createCommand,\n defineExtension,\n} from \"lexical\";\n\nimport { $createDateTimeNode, DateTimeNode } from \"../nodes/date-time-node\";\n\ntype CommandPayload = {\n dateTime: Date;\n};\n\nexport const INSERT_DATETIME_COMMAND: LexicalCommand =\n createCommand(\"INSERT_DATETIME_COMMAND\");\n\nexport const DateTimeExtension = defineExtension({\n name: \"@shadcn-editor/DateTime\",\n nodes: [DateTimeNode],\n register: (editor) =>\n editor.registerCommand(\n INSERT_DATETIME_COMMAND,\n (payload) => {\n const { dateTime } = payload;\n const dateTimeNode = $createDateTimeNode(dateTime);\n\n const selection = $getSelection();\n if ($isRangeSelection(selection)) {\n dateTimeNode.setFormat(selection.format);\n }\n $insertNodeIntoLeaf(dateTimeNode);\n if ($isRootOrShadowRoot(dateTimeNode.getParent())) {\n $wrapNodeInElement(dateTimeNode, $createParagraphNode).selectEnd();\n }\n\n return true;\n },\n COMMAND_PRIORITY_EDITOR,\n ),\n});\n", "type": "registry:component" }, { "path": "src/components/editor/extensions/drag-drop-paste-extension.tsx", "content": "import { DRAG_DROP_PASTE } from \"@lexical/rich-text\";\nimport { isMimeType, mediaFileReader } from \"@lexical/utils\";\nimport { COMMAND_PRIORITY_LOW, defineExtension } from \"lexical\";\n\nimport { INSERT_IMAGE_COMMAND } from \"@/components/editor/extensions/images-extension\";\n\nconst ACCEPTABLE_IMAGE_TYPES = [\n \"image/\",\n \"image/heic\",\n \"image/heif\",\n \"image/gif\",\n \"image/webp\",\n];\n\nexport const DragDropPasteExtension = defineExtension({\n name: \"@shadcn-editor/DragDropPaste\",\n register: (editor) =>\n editor.registerCommand(\n DRAG_DROP_PASTE,\n (files) => {\n (async () => {\n const filesResult = await mediaFileReader(\n files,\n [ACCEPTABLE_IMAGE_TYPES].flatMap((x) => x),\n );\n for (const { file, result } of filesResult) {\n if (isMimeType(file, ACCEPTABLE_IMAGE_TYPES)) {\n editor.dispatchCommand(INSERT_IMAGE_COMMAND, {\n altText: file.name,\n src: result,\n });\n }\n }\n })();\n return true;\n },\n COMMAND_PRIORITY_LOW,\n ),\n});\n", "type": "registry:component" }, { "path": "src/components/editor/extensions/emojis-extension.tsx", "content": "import { TextNode, defineExtension } from \"lexical\";\n\nimport {\n $createEmojiNode,\n EmojiNode,\n} from \"@/components/editor/nodes/emoji-node\";\n\nconst emojis: Map = new Map([\n [\":)\", [\"emoji happysmile\", \"🙂\"]],\n [\":D\", [\"emoji veryhappysmile\", \"😀\"]],\n [\":(\", [\"emoji unhappysmile\", \"🙁\"]],\n [\"<3\", [\"emoji heart\", \"❤\"]],\n]);\n\nfunction $findAndTransformEmoji(node: TextNode): null | TextNode {\n const text = node.getTextContent();\n\n for (let i = 0; i < text.length; i++) {\n const emojiData = emojis.get(text[i]) || emojis.get(text.slice(i, i + 2));\n\n if (emojiData !== undefined) {\n const [emojiStyle, emojiText] = emojiData;\n let targetNode;\n\n if (i === 0) {\n [targetNode] = node.splitText(i + 2);\n } else {\n [, targetNode] = node.splitText(i, i + 2);\n }\n\n const emojiNode = $createEmojiNode(emojiStyle, emojiText);\n targetNode.replace(emojiNode);\n return emojiNode;\n }\n }\n\n return null;\n}\n\nfunction $textNodeTransform(node: TextNode): void {\n let targetNode: TextNode | null = node;\n\n while (targetNode !== null) {\n if (!targetNode.isSimpleText()) {\n return;\n }\n\n targetNode = $findAndTransformEmoji(targetNode);\n }\n}\n\nexport const EmojisExtension = defineExtension({\n name: \"@shadcn-editor/Emojis\",\n nodes: [EmojiNode],\n register: (editor) =>\n editor.registerNodeTransform(TextNode, $textNodeTransform),\n});\n", "type": "registry:component" }, { "path": "src/components/editor/extensions/images-extension.tsx", "content": "import { type JSX, useEffect, useRef, useState } from \"react\";\n\nimport {\n $isAutoLinkNode,\n $isLinkNode,\n type LinkNode,\n TOGGLE_LINK_COMMAND,\n} from \"@lexical/link\";\nimport { $wrapNodeInElement, mergeRegister } from \"@lexical/utils\";\nimport {\n $createParagraphNode,\n $createRangeSelection,\n $findMatchingParent,\n $getSelection,\n $insertNodes,\n $isNodeSelection,\n $isRootOrShadowRoot,\n $setSelection,\n COMMAND_PRIORITY_EDITOR,\n COMMAND_PRIORITY_HIGH,\n COMMAND_PRIORITY_LOW,\n DRAGOVER_COMMAND,\n DRAGSTART_COMMAND,\n DROP_COMMAND,\n type LexicalCommand,\n type LexicalEditor,\n createCommand,\n defineExtension,\n getDOMSelectionFromTarget,\n isHTMLElement,\n} from \"lexical\";\n\nimport {\n $createImageNode,\n $isImageNode,\n ImageNode,\n type ImagePayload,\n} from \"@/components/editor/nodes/image-node\";\nimport { Button } from \"@/components/ui/button\";\nimport { DialogFooter } from \"@/components/ui/dialog\";\nimport { Field, FieldGroup, FieldLabel } from \"@/components/ui/field\";\nimport { Input } from \"@/components/ui/input\";\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from \"@/components/ui/tabs\";\n\nexport type InsertImagePayload = Readonly;\n\nexport const INSERT_IMAGE_COMMAND: LexicalCommand =\n createCommand(\"INSERT_IMAGE_COMMAND\");\n\nexport function InsertImageUriDialogBody({\n onClick,\n}: {\n onClick: (payload: InsertImagePayload) => void;\n}) {\n const [src, setSrc] = useState(\"\");\n const [altText, setAltText] = useState(\"\");\n\n const isDisabled = src === \"\";\n\n return (\n \n \n Image URL\n setSrc(e.target.value)}\n value={src}\n data-test-id=\"image-modal-url-input\"\n />\n \n \n Alt Text\n setAltText(e.target.value)}\n value={altText}\n data-test-id=\"image-modal-alt-text-input\"\n />\n \n \n onClick({ altText, src })}\n data-test-id=\"image-modal-confirm-btn\"\n >\n Confirm\n \n \n \n );\n}\n\nexport function InsertImageUploadedDialogBody({\n onClick,\n}: {\n onClick: (payload: InsertImagePayload) => void;\n}) {\n const [src, setSrc] = useState(\"\");\n const [altText, setAltText] = useState(\"\");\n\n const isDisabled = src === \"\";\n\n const loadImage = (files: FileList | null) => {\n const reader = new FileReader();\n reader.onload = function () {\n if (typeof reader.result === \"string\") {\n setSrc(reader.result);\n }\n return \"\";\n };\n if (files !== null) {\n reader.readAsDataURL(files[0]);\n }\n };\n\n return (\n \n \n Image Upload\n loadImage(e.target.files)}\n accept=\"image/*\"\n data-test-id=\"image-modal-file-upload\"\n />\n \n \n Alt Text\n setAltText(e.target.value)}\n value={altText}\n data-test-id=\"image-modal-alt-text-input\"\n />\n \n \n onClick({ altText, src })}\n data-test-id=\"image-modal-file-upload-btn\"\n >\n Confirm\n \n \n \n );\n}\n\nexport function InsertImageDialog({\n activeEditor,\n onClose,\n}: {\n activeEditor: LexicalEditor;\n onClose: () => void;\n}): JSX.Element {\n const hasModifier = useRef(false);\n\n useEffect(() => {\n hasModifier.current = false;\n const handler = (e: KeyboardEvent) => {\n hasModifier.current = e.altKey;\n };\n document.addEventListener(\"keydown\", handler);\n return () => {\n document.removeEventListener(\"keydown\", handler);\n };\n }, [activeEditor]);\n\n const onClick = (payload: InsertImagePayload) => {\n console.log(\"onClick\", payload);\n activeEditor.dispatchCommand(INSERT_IMAGE_COMMAND, payload);\n onClose();\n };\n\n return (\n \n \n URL\n File\n \n \n \n \n \n \n \n \n );\n}\n\nexport const ImagesExtension = defineExtension({\n name: \"@shadcn-editor/Images\",\n nodes: [ImageNode],\n register: (editor) =>\n mergeRegister(\n editor.registerCommand(\n INSERT_IMAGE_COMMAND,\n (payload) => {\n const imageNode = $createImageNode(payload);\n $insertNodes([imageNode]);\n if ($isRootOrShadowRoot(imageNode.getParentOrThrow())) {\n $wrapNodeInElement(imageNode, $createParagraphNode).selectEnd();\n }\n\n return true;\n },\n COMMAND_PRIORITY_EDITOR,\n ),\n editor.registerCommand(\n DRAGSTART_COMMAND,\n (event) => $onDragStart(event),\n COMMAND_PRIORITY_HIGH,\n ),\n editor.registerCommand(\n DRAGOVER_COMMAND,\n (event) => $onDragover(event),\n COMMAND_PRIORITY_LOW,\n ),\n editor.registerCommand(\n DROP_COMMAND,\n (event) => $onDrop(event, editor),\n COMMAND_PRIORITY_HIGH,\n ),\n ),\n});\n\nconst TRANSPARENT_IMAGE =\n \"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\";\nconst img = document.createElement(\"img\");\nimg.src = TRANSPARENT_IMAGE;\n\nfunction $onDragStart(event: DragEvent): boolean {\n const node = $getImageNodeInSelection();\n if (!node) {\n return false;\n }\n const dataTransfer = event.dataTransfer;\n if (!dataTransfer) {\n return false;\n }\n dataTransfer.setData(\"text/plain\", \"_\");\n dataTransfer.setDragImage(img, 0, 0);\n dataTransfer.setData(\n \"application/x-lexical-drag\",\n JSON.stringify({\n data: {\n altText: node.__altText,\n height: node.__height,\n key: node.getKey(),\n maxWidth: node.__maxWidth,\n src: node.__src,\n width: node.__width,\n },\n type: \"image\",\n }),\n );\n\n return true;\n}\n\nfunction $onDragover(event: DragEvent): boolean {\n const node = $getImageNodeInSelection();\n if (!node) {\n return false;\n }\n if (!canDropImage(event)) {\n event.preventDefault();\n }\n return false;\n}\n\nfunction $onDrop(event: DragEvent, editor: LexicalEditor): boolean {\n const node = $getImageNodeInSelection();\n if (!node) {\n return false;\n }\n const data = getDragImageData(event);\n if (!data) {\n return false;\n }\n const existingLink = $findMatchingParent(\n node,\n (parent): parent is LinkNode =>\n !$isAutoLinkNode(parent) && $isLinkNode(parent),\n );\n event.preventDefault();\n if (canDropImage(event)) {\n const range = getDragSelection(event);\n node.remove();\n const rangeSelection = $createRangeSelection();\n if (range !== null && range !== undefined) {\n rangeSelection.applyDOMRange(range);\n }\n $setSelection(rangeSelection);\n editor.dispatchCommand(INSERT_IMAGE_COMMAND, data);\n if (existingLink) {\n editor.dispatchCommand(TOGGLE_LINK_COMMAND, existingLink.getURL());\n }\n }\n return true;\n}\n\nfunction $getImageNodeInSelection(): ImageNode | null {\n const selection = $getSelection();\n if (!$isNodeSelection(selection)) {\n return null;\n }\n const nodes = selection.getNodes();\n const node = nodes[0];\n return $isImageNode(node) ? node : null;\n}\n\nfunction getDragImageData(event: DragEvent): null | InsertImagePayload {\n const dragData = event.dataTransfer?.getData(\"application/x-lexical-drag\");\n if (!dragData) {\n return null;\n }\n const { type, data } = JSON.parse(dragData);\n if (type !== \"image\") {\n return null;\n }\n\n return data;\n}\n\ndeclare global {\n interface DragEvent {\n rangeOffset?: number;\n rangeParent?: Node;\n }\n}\n\nfunction canDropImage(event: DragEvent): boolean {\n const target = event.target;\n return !!(\n isHTMLElement(target) &&\n !target.closest(\"code, span.editor-image\") &&\n isHTMLElement(target.parentElement) &&\n target.parentElement.closest(\"div.ContentEditable__root\")\n );\n}\n\nfunction getDragSelection(event: DragEvent): Range | null | undefined {\n let range;\n const domSelection = getDOMSelectionFromTarget(event.target);\n if (document.caretRangeFromPoint) {\n range = document.caretRangeFromPoint(event.clientX, event.clientY);\n } else if (event.rangeParent && domSelection !== null) {\n domSelection.collapse(event.rangeParent, event.rangeOffset || 0);\n range = domSelection.getRangeAt(0);\n } else {\n throw Error(`Cannot get the selection when dragging`);\n }\n\n return range;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/extensions/keywords-extension.tsx", "content": "import { registerLexicalTextEntity } from \"@lexical/text\";\nimport { TextNode, defineExtension, mergeRegister } from \"lexical\";\n\nimport { $createKeywordNode, KeywordNode } from \"../nodes/keyword-node\";\n\nconst KEYWORDS_REGEX =\n /(^|$|[^A-Za-zªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԧԱ-Ֆՙա-ևא-תװ-ײؠ-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺࠀ-ࠕࠚࠤࠨࡀ-ࡘࢠࢢ-ࢬऄ-हऽॐक़-ॡॱ-ॷॹ-ॿঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-ళవ-హఽౘౙౠౡಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡೱೲഅ-ഌഎ-ഐഒ-ഺഽൎൠൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ະາຳຽເ-ໄໆໜ-ໟༀཀ-ཇཉ-ཬྈ-ྌက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏼᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᢰ-ᣵᤀ-ᤜᥐ-ᥭᥰ-ᥴᦀ-ᦫᧁ-ᧇᨀ-ᨖᨠ-ᩔᪧᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᮺ-ᯥᰀ-ᰣᱍ-ᱏᱚ-ᱽᳩ-ᳬᳮ-ᳱᳵᳶᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℹℼ-ℿⅅ-ⅉⅎↃↄⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞⸯ々〆〱-〵〻〼ぁ-ゖゝ-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿌ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙿ-ꚗꚠ-ꛥꜗ-ꜟꜢ-ꞈꞋ-ꞎꞐ-ꞓꞠ-Ɦꟸ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꣲ-ꣷꣻꤊ-ꤥꤰ-ꥆꥠ-ꥼꦄ-ꦲꧏꨀ-ꨨꩀ-ꩂꩄ-ꩋꩠ-ꩶꩺꪀ-ꪯꪱꪵꪶꪹ-ꪽꫀꫂꫛ-ꫝꫠ-ꫪꫲ-ꫴꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꯀ-ꯢ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ])(congrats|congratulations|gratuluju|gratuluji|gratulujeme|blahopřeju|blahopřeji|blahopřejeme|Til lykke|Tillykke|Glückwunsch|Gratuliere|felicitaciones|enhorabuena|paljon onnea|onnittelut|Félicitations|gratula|gratulálok|gratulálunk|congratulazioni|complimenti|おめでとう|おめでとうございます|축하해|축하해요|gratulerer|Gefeliciteerd|gratulacje|Parabéns|parabéns|felicitações|felicitări|мои поздравления|поздравляем|поздравляю|gratulujem|blahoželám|ยินดีด้วย|ขอแสดงความยินดี|tebrikler|tebrik ederim|恭喜|祝贺你|恭喜你|恭喜|恭喜|baie geluk|veels geluk|অভিনন্দন|Čestitam|Čestitke|Čestitamo|Συγχαρητήρια|Μπράβο|અભિનંદન|badhai|बधाई|अभिनंदन|Честитам|Свака част|hongera|வாழ்த்துகள்|வாழ்த்துக்கள்|అభినందనలు|അഭിനന്ദനങ്ങൾ|Chúc mừng|מזל טוב|mazel tov|mazal tov)(^|$|[^A-Za-zªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԧԱ-Ֆՙա-ևא-תװ-ײؠ-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺࠀ-ࠕࠚࠤࠨࡀ-ࡘࢠࢢ-ࢬऄ-हऽॐक़-ॡॱ-ॷॹ-ॿঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-ళవ-హఽౘౙౠౡಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡೱೲഅ-ഌഎ-ഐഒ-ഺഽൎൠൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ະາຳຽເ-ໄໆໜ-ໟༀཀ-ཇཉ-ཬྈ-ྌက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏼᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᢰ-ᣵᤀ-ᤜᥐ-ᥭᥰ-ᥴᦀ-ᦫᧁ-ᧇᨀ-ᨖᨠ-ᩔᪧᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᮺ-ᯥᰀ-ᰣᱍ-ᱏᱚ-ᱽᳩ-ᳬᳮ-ᳱᳵᳶᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℹℼ-ℿⅅ-ⅉⅎↃↄⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞⸯ々〆〱-〵〻〼ぁ-ゖゝ-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿌ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙿ-ꚗꚠ-ꛥꜗ-ꜟꜢ-ꞈꞋ-ꞎꞐ-ꞓꞠ-Ɦꟸ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꣲ-ꣷꣻꤊ-ꤥꤰ-ꥆꥠ-ꥼꦄ-ꦲꧏꨀ-ꨨꩀ-ꩂꩄ-ꩋꩠ-ꩶꩺꪀ-ꪯꪱꪵꪶꪹ-ꪽꫀꫂꫛ-ꫝꫠ-ꫪꫲ-ꫴꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꯀ-ꯢ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ])/i;\n\nfunction $convertToKeywordNode(textNode: TextNode): KeywordNode {\n return $createKeywordNode(textNode.getTextContent());\n}\n\nfunction getKeywordMatch(text: string) {\n const matchArr = KEYWORDS_REGEX.exec(text);\n\n if (matchArr === null) {\n return null;\n }\n\n const hashtagLength = matchArr[2].length;\n const startOffset = matchArr.index + matchArr[1].length;\n const endOffset = startOffset + hashtagLength;\n return {\n end: endOffset,\n start: startOffset,\n };\n}\n\nexport const KeywordsExtension = defineExtension({\n name: \"@shadcn-editor/LexicalKeywords\",\n nodes: () => [KeywordNode],\n register(editor) {\n return mergeRegister(\n ...registerLexicalTextEntity(\n editor,\n getKeywordMatch,\n KeywordNode,\n $convertToKeywordNode,\n ),\n );\n },\n});\n", "type": "registry:component" }, { "path": "src/components/editor/extensions/markdown-shortcuts-extension.tsx", "content": "import { effect, namedSignals } from \"@lexical/extension\";\nimport { type Transformer, registerMarkdownShortcuts } from \"@lexical/markdown\";\nimport { defineExtension, safeCast } from \"lexical\";\n\n// This is not a published extension because markdown transformers\n// should get a refactor to require less manual configuration\nexport const MarkdownShortcutsExtension = defineExtension({\n build: (_, config) => namedSignals(config),\n config: safeCast<{ transformers: Array }>({ transformers: [] }),\n name: \"@shadcn-editor/MarkdownShortcuts\",\n register: (editor, _, state) =>\n effect(() => {\n registerMarkdownShortcuts(editor, state.getOutput().transformers.value);\n }),\n});\n", "type": "registry:component" }, { "path": "src/components/editor/extensions/max-length-extension.tsx", "content": "import { effect, namedSignals } from \"@lexical/extension\";\nimport { $trimTextContentFromAnchor } from \"@lexical/selection\";\nimport { $restoreEditorState } from \"@lexical/utils\";\nimport {\n $getSelection,\n $isRangeSelection,\n type EditorState,\n RootNode,\n defineExtension,\n safeCast,\n} from \"lexical\";\n\nexport interface MaxLengthConfig {\n disabled: boolean;\n maxLength: number;\n}\n\nexport const MaxLengthExtension = defineExtension({\n build: (_, config) => namedSignals(config),\n config: safeCast({ disabled: false, maxLength: 3000 }),\n name: \"@shadcn-editor/MaxLength\",\n register: (editor, _, state) =>\n effect(() => {\n const output = state.getOutput();\n if (output.disabled.value) {\n return;\n }\n const maxLength = output.maxLength.value;\n let lastRestoredEditorState: EditorState | null = null;\n return editor.registerNodeTransform(RootNode, (rootNode: RootNode) => {\n const selection = $getSelection();\n if (!$isRangeSelection(selection) || !selection.isCollapsed()) {\n return;\n }\n const prevEditorState = editor.getEditorState();\n const prevTextContentSize = prevEditorState.read(() =>\n rootNode.getTextContentSize(),\n );\n const textContentSize = rootNode.getTextContentSize();\n if (prevTextContentSize !== textContentSize) {\n const delCount = textContentSize - maxLength;\n const anchor = selection.anchor;\n\n if (delCount > 0) {\n // Restore the old editor state instead if the last\n // text content was already at the limit.\n if (\n prevTextContentSize === maxLength &&\n lastRestoredEditorState !== prevEditorState\n ) {\n lastRestoredEditorState = prevEditorState;\n $restoreEditorState(editor, prevEditorState);\n } else {\n $trimTextContentFromAnchor(editor, anchor, delCount);\n }\n }\n }\n });\n }),\n});\n", "type": "registry:component" }, { "path": "src/components/editor/nodes/autocomplete-node.tsx", "content": "import type {\n DOMExportOutput,\n EditorConfig,\n LexicalEditor,\n NodeKey,\n SerializedTextNode,\n Spread,\n} from \"lexical\";\nimport { TextNode } from \"lexical\";\n\nimport { uuid as UUID } from \"@/components/editor/plugins/auto-complete-plugin\";\n\nexport type SerializedAutocompleteNode = Spread<\n {\n uuid: string;\n },\n SerializedTextNode\n>;\n\nexport class AutocompleteNode extends TextNode {\n /**\n * A unique uuid is generated for each session and assigned to the instance.\n * This helps to:\n * - Ensures max one Autocomplete node per session.\n * - Ensure that when collaboration is enabled, this node is not shown in\n * other sessions.\n * See https://github.com/facebook/lexical/blob/main/packages/lexical-playground/src/plugins/AutocompletePlugin/index.tsx\n */\n __uuid: string;\n\n static clone(node: AutocompleteNode): AutocompleteNode {\n return new AutocompleteNode(node.__text, node.__uuid, node.__key);\n }\n\n static getType(): \"autocomplete\" {\n return \"autocomplete\";\n }\n\n static importDOM() {\n // Never import from DOM\n return null;\n }\n\n static importJSON(\n serializedNode: SerializedAutocompleteNode,\n ): AutocompleteNode {\n return $createAutocompleteNode(\n serializedNode.text,\n serializedNode.uuid,\n ).updateFromJSON(serializedNode);\n }\n\n exportJSON(): SerializedAutocompleteNode {\n return {\n ...super.exportJSON(),\n uuid: this.__uuid,\n };\n }\n\n constructor(text: string, uuid: string, key?: NodeKey) {\n super(text, key);\n this.__uuid = uuid;\n }\n\n updateDOM(\n _prevNode: this,\n _dom: HTMLElement,\n _config: EditorConfig,\n ): boolean {\n return false;\n }\n\n exportDOM(_: LexicalEditor): DOMExportOutput {\n return { element: null };\n }\n\n excludeFromCopy() {\n return true;\n }\n\n createDOM(config: EditorConfig): HTMLElement {\n const dom = super.createDOM(config);\n dom.classList.add(config.theme.autocomplete);\n if (this.__uuid !== UUID) {\n dom.style.display = \"none\";\n }\n return dom;\n }\n}\n\nexport function $createAutocompleteNode(\n text: string,\n uuid: string,\n): AutocompleteNode {\n return new AutocompleteNode(text, uuid).setMode(\"token\");\n}\n", "type": "registry:component" }, { "path": "src/components/editor/nodes/date-time-node.tsx", "content": "import type { JSX } from \"react\";\nimport * as React from \"react\";\n\nimport {\n DecoratorTextNode,\n type SerializedDecoratorTextNode,\n applyFormatFromStyle,\n applyFormatToDom,\n} from \"@lexical/extension\";\nimport {\n $getState,\n $isTextNode,\n $setState,\n type DOMConversionOutput,\n type DOMExportOutput,\n type LexicalNode,\n type Spread,\n type StateConfigValue,\n type StateValueOrUpdater,\n buildImportMap,\n createState,\n} from \"lexical\";\n\nconst DateTimeComponent = React.lazy(\n () => import(\"@/components/editor/editor-ui/date-time-component\"),\n);\n\nconst tagToFormat = {\n b: \"bold\",\n i: \"italic\",\n mark: \"highlight\",\n s: \"strikethrough\",\n u: \"underline\",\n} as const;\n\nconst getDateTimeText = (dateTime: Date) => {\n if (dateTime === undefined) {\n return \"\";\n }\n const hours = dateTime?.getHours();\n const minutes = dateTime?.getMinutes();\n return (\n dateTime.toDateString() +\n (hours === 0 && minutes === 0\n ? \"\"\n : ` ${hours.toString().padStart(2, \"0\")}:${minutes\n .toString()\n .padStart(2, \"0\")}`)\n );\n};\n\nexport type SerializedDateTimeNode = Spread<\n {\n dateTime?: string;\n },\n SerializedDecoratorTextNode\n>;\n\nfunction $convertDateTimeElement(\n domNode: HTMLElement,\n): DOMConversionOutput | null {\n const dateTimeValue = domNode.getAttribute(\"data-lexical-datetime\");\n if (dateTimeValue) {\n const node = $createDateTimeNode(new Date(Date.parse(dateTimeValue)));\n return {\n after: (childLexicalNodes) => {\n // exportDOM returns only one child text, so only the first node of the array is taken\n const firstChild = childLexicalNodes[0];\n if ($isTextNode(firstChild)) {\n node.setFormat(firstChild.getFormat());\n }\n return childLexicalNodes;\n },\n node,\n };\n }\n const gDocsDateTimePayload = domNode.getAttribute(\"data-rich-links\");\n if (!gDocsDateTimePayload) {\n return null;\n }\n const parsed = JSON.parse(gDocsDateTimePayload);\n const parsedDate =\n parsed?.dat_df?.dfie_ts?.tv?.tv_s * 1000 ||\n Date.parse(parsed?.dat_df?.dfie_dt || \"\");\n if (isNaN(parsedDate)) {\n return null;\n }\n const dateTimeNode = $createDateTimeNode(new Date(parsedDate));\n return { node: applyFormatFromStyle(dateTimeNode, domNode.style) };\n}\n\nconst dateTimeState = createState(\"dateTime\", {\n parse: (v) => new Date(v as string),\n unparse: (v) => v.toISOString(),\n});\n\nexport class DateTimeNode extends DecoratorTextNode {\n $config() {\n return this.config(\"datetime\", {\n extends: DecoratorTextNode,\n importDOM: buildImportMap({\n span: (domNode) =>\n domNode.getAttribute(\"data-lexical-datetime\") !== null ||\n // GDocs Support\n (domNode.getAttribute(\"data-rich-links\") !== null &&\n JSON.parse(domNode.getAttribute(\"data-rich-links\") || \"{}\").type ===\n \"date\")\n ? {\n conversion: $convertDateTimeElement,\n priority: 2,\n }\n : null,\n }),\n stateConfigs: [{ flat: true, stateConfig: dateTimeState }],\n });\n }\n\n getDateTime(): StateConfigValue {\n return $getState(this, dateTimeState);\n }\n\n setDateTime(valueOrUpdater: StateValueOrUpdater): this {\n return $setState(this, dateTimeState, valueOrUpdater);\n }\n\n getTextContent(): string {\n const dateTime = this.getDateTime();\n return getDateTimeText(dateTime);\n }\n\n exportDOM(): DOMExportOutput {\n const element = document.createElement(\"span\");\n const textDom: HTMLElement | Text = document.createTextNode(\n getDateTimeText(this.getDateTime()),\n );\n element.setAttribute(\n \"data-lexical-datetime\",\n this.getDateTime()?.toString() || \"\",\n );\n element.appendChild(applyFormatToDom(this, textDom, tagToFormat));\n\n return { element };\n }\n\n createDOM(): HTMLElement {\n const element = document.createElement(\"span\");\n element.setAttribute(\n \"data-lexical-datetime\",\n this.getDateTime()?.toString() || \"\",\n );\n element.style.display = \"inline-block\";\n return element;\n }\n\n updateDOM(): false {\n return false;\n }\n\n decorate(): JSX.Element {\n return (\n \n );\n }\n}\n\nexport function $createDateTimeNode(dateTime: Date): DateTimeNode {\n return new DateTimeNode().setDateTime(dateTime);\n}\n\nexport function $isDateTimeNode(\n node: LexicalNode | null | undefined,\n): node is DateTimeNode {\n return node instanceof DateTimeNode;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/nodes/embeds/tweet-node.tsx", "content": "import { type JSX, useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { BlockWithAlignableContents } from \"@lexical/react/LexicalBlockWithAlignableContents\";\nimport {\n DecoratorBlockNode,\n type SerializedDecoratorBlockNode,\n} from \"@lexical/react/LexicalDecoratorBlockNode\";\nimport type {\n DOMConversionMap,\n DOMConversionOutput,\n DOMExportOutput,\n EditorConfig,\n ElementFormatType,\n LexicalEditor,\n LexicalNode,\n NodeKey,\n Spread,\n} from \"lexical\";\n\nconst WIDGET_SCRIPT_URL = \"https://platform.twitter.com/widgets.js\";\n\ntype TweetComponentProps = Readonly<{\n className: Readonly<{\n base: string;\n focus: string;\n }>;\n format: ElementFormatType | null;\n loadingComponent?: JSX.Element | string;\n nodeKey: NodeKey;\n onError?: (error: string) => void;\n onLoad?: () => void;\n tweetID: string;\n}>;\n\nfunction $convertTweetElement(\n domNode: HTMLDivElement,\n): DOMConversionOutput | null {\n const id = domNode.getAttribute(\"data-lexical-tweet-id\");\n if (id) {\n const node = $createTweetNode(id);\n return { node };\n }\n return null;\n}\n\nlet isTwitterScriptLoading = true;\n\nfunction TweetComponent({\n className,\n format,\n loadingComponent,\n nodeKey,\n onError,\n onLoad,\n tweetID,\n}: TweetComponentProps) {\n const containerRef = useRef(null);\n\n const previousTweetIDRef = useRef(\"\");\n const [isTweetLoading, setIsTweetLoading] = useState(false);\n\n const createTweet = useCallback(async () => {\n try {\n // @ts-expect-error Twitter is attached to the window.\n await window.twttr.widgets.createTweet(tweetID, containerRef.current);\n\n setIsTweetLoading(false);\n isTwitterScriptLoading = false;\n\n if (onLoad) {\n onLoad();\n }\n } catch (error) {\n if (onError) {\n onError(String(error));\n }\n }\n }, [onError, onLoad, tweetID]);\n\n useEffect(() => {\n if (tweetID !== previousTweetIDRef.current) {\n setIsTweetLoading(true);\n\n if (isTwitterScriptLoading) {\n const script = document.createElement(\"script\");\n script.src = WIDGET_SCRIPT_URL;\n script.async = true;\n document.body?.appendChild(script);\n script.onload = createTweet;\n if (onError) {\n script.onerror = onError as OnErrorEventHandler;\n }\n } else {\n createTweet();\n }\n\n if (previousTweetIDRef) {\n previousTweetIDRef.current = tweetID;\n }\n }\n }, [createTweet, onError, tweetID]);\n\n return (\n \n {isTweetLoading ? loadingComponent : null}\n \n \n );\n}\n\nexport type SerializedTweetNode = Spread<\n {\n id: string;\n },\n SerializedDecoratorBlockNode\n>;\n\nexport class TweetNode extends DecoratorBlockNode {\n __id: string;\n\n static getType(): string {\n return \"tweet\";\n }\n\n static clone(node: TweetNode): TweetNode {\n return new TweetNode(node.__id, node.__format, node.__key);\n }\n\n static importJSON(serializedNode: SerializedTweetNode): TweetNode {\n const node = $createTweetNode(serializedNode.id);\n node.setFormat(serializedNode.format);\n return node;\n }\n\n exportJSON(): SerializedTweetNode {\n return {\n ...super.exportJSON(),\n id: this.getId(),\n type: \"tweet\",\n version: 1,\n };\n }\n\n static importDOM(): DOMConversionMap | null {\n return {\n div: (domNode: HTMLDivElement) => {\n if (!domNode.hasAttribute(\"data-lexical-tweet-id\")) {\n return null;\n }\n return {\n conversion: $convertTweetElement,\n priority: 2,\n };\n },\n };\n }\n\n exportDOM(): DOMExportOutput {\n const element = document.createElement(\"div\");\n element.setAttribute(\"data-lexical-tweet-id\", this.__id);\n const text = document.createTextNode(this.getTextContent());\n element.append(text);\n return { element };\n }\n\n constructor(id: string, format?: ElementFormatType, key?: NodeKey) {\n super(format, key);\n this.__id = id;\n }\n\n getId(): string {\n return this.__id;\n }\n\n getTextContent(\n _includeInert?: boolean | undefined,\n\n _includeDirectionless?: false | undefined,\n ): string {\n return `https://x.com/i/web/status/${this.__id}`;\n }\n\n decorate(_editor: LexicalEditor, config: EditorConfig): JSX.Element {\n const embedBlockTheme = config.theme.embedBlock || {};\n const className = {\n base: embedBlockTheme.base || \"\",\n focus: embedBlockTheme.focus || \"\",\n };\n return (\n \n );\n }\n}\n\nexport function $createTweetNode(tweetID: string): TweetNode {\n return new TweetNode(tweetID);\n}\n\nexport function $isTweetNode(\n node: TweetNode | LexicalNode | null | undefined,\n): node is TweetNode {\n return node instanceof TweetNode;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/nodes/embeds/youtube-node.tsx", "content": "import type { JSX } from \"react\";\n\nimport { BlockWithAlignableContents } from \"@lexical/react/LexicalBlockWithAlignableContents\";\nimport {\n DecoratorBlockNode,\n type SerializedDecoratorBlockNode,\n} from \"@lexical/react/LexicalDecoratorBlockNode\";\nimport type {\n DOMConversionMap,\n DOMConversionOutput,\n DOMExportOutput,\n EditorConfig,\n ElementFormatType,\n LexicalEditor,\n LexicalNode,\n NodeKey,\n Spread,\n} from \"lexical\";\n\ntype YouTubeComponentProps = Readonly<{\n className: Readonly<{\n base: string;\n focus: string;\n }>;\n format: ElementFormatType | null;\n nodeKey: NodeKey;\n videoID: string;\n}>;\n\nfunction YouTubeComponent({\n className,\n format,\n nodeKey,\n videoID,\n}: YouTubeComponentProps) {\n return (\n \n \n \n );\n}\n\nexport type SerializedYouTubeNode = Spread<\n {\n videoID: string;\n },\n SerializedDecoratorBlockNode\n>;\n\nfunction $convertYoutubeElement(\n domNode: HTMLElement,\n): null | DOMConversionOutput {\n const videoID = domNode.getAttribute(\"data-lexical-youtube\");\n if (videoID) {\n const node = $createYouTubeNode(videoID);\n return { node };\n }\n return null;\n}\n\nexport class YouTubeNode extends DecoratorBlockNode {\n __id: string;\n\n static getType(): string {\n return \"youtube\";\n }\n\n static clone(node: YouTubeNode): YouTubeNode {\n return new YouTubeNode(node.__id, node.__format, node.__key);\n }\n\n static importJSON(serializedNode: SerializedYouTubeNode): YouTubeNode {\n const node = $createYouTubeNode(serializedNode.videoID);\n node.setFormat(serializedNode.format);\n return node;\n }\n\n exportJSON(): SerializedYouTubeNode {\n return {\n ...super.exportJSON(),\n type: \"youtube\",\n version: 1,\n videoID: this.__id,\n };\n }\n\n constructor(id: string, format?: ElementFormatType, key?: NodeKey) {\n super(format, key);\n this.__id = id;\n }\n\n exportDOM(): DOMExportOutput {\n const element = document.createElement(\"iframe\");\n element.setAttribute(\"data-lexical-youtube\", this.__id);\n element.setAttribute(\"width\", \"560\");\n element.setAttribute(\"height\", \"315\");\n element.setAttribute(\n \"src\",\n `https://www.youtube-nocookie.com/embed/${this.__id}`,\n );\n element.setAttribute(\"frameborder\", \"0\");\n element.setAttribute(\n \"allow\",\n \"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\",\n );\n element.setAttribute(\"allowfullscreen\", \"true\");\n element.setAttribute(\"title\", \"YouTube video\");\n return { element };\n }\n\n static importDOM(): DOMConversionMap | null {\n return {\n iframe: (domNode: HTMLElement) => {\n if (!domNode.hasAttribute(\"data-lexical-youtube\")) {\n return null;\n }\n return {\n conversion: $convertYoutubeElement,\n priority: 1,\n };\n },\n };\n }\n\n updateDOM(): false {\n return false;\n }\n\n getId(): string {\n return this.__id;\n }\n\n getTextContent(\n _includeInert?: boolean | undefined,\n\n _includeDirectionless?: false | undefined,\n ): string {\n return `https://www.youtube.com/watch?v=${this.__id}`;\n }\n\n decorate(_editor: LexicalEditor, config: EditorConfig): JSX.Element {\n const embedBlockTheme = config.theme.embedBlock || {};\n const className = {\n base: embedBlockTheme.base || \"\",\n focus: embedBlockTheme.focus || \"\",\n };\n return (\n \n );\n }\n}\n\nexport function $createYouTubeNode(videoID: string): YouTubeNode {\n return new YouTubeNode(videoID);\n}\n\nexport function $isYouTubeNode(\n node: YouTubeNode | LexicalNode | null | undefined,\n): node is YouTubeNode {\n return node instanceof YouTubeNode;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/nodes/emoji-node.tsx", "content": "import type {\n EditorConfig,\n LexicalNode,\n NodeKey,\n SerializedTextNode,\n Spread,\n} from \"lexical\";\nimport { $applyNodeReplacement, TextNode } from \"lexical\";\n\nexport type SerializedEmojiNode = Spread<\n {\n className: string;\n },\n SerializedTextNode\n>;\n\nexport class EmojiNode extends TextNode {\n __className: string;\n\n static getType(): string {\n return \"emoji\";\n }\n\n static clone(node: EmojiNode): EmojiNode {\n return new EmojiNode(node.__className, node.__text, node.__key);\n }\n\n constructor(className: string, text: string, key?: NodeKey) {\n super(text, key);\n this.__className = className;\n }\n\n createDOM(config: EditorConfig): HTMLElement {\n const dom = document.createElement(\"span\");\n const inner = super.createDOM(config);\n dom.className = this.__className;\n inner.className = \"emoji-inner\";\n dom.appendChild(inner);\n return dom;\n }\n\n updateDOM(prevNode: this, dom: HTMLElement, config: EditorConfig): boolean {\n const inner = dom.firstChild;\n if (inner === null) {\n return true;\n }\n super.updateDOM(prevNode, inner as HTMLElement, config);\n return false;\n }\n\n static importJSON(serializedNode: SerializedEmojiNode): EmojiNode {\n return $createEmojiNode(\n serializedNode.className,\n serializedNode.text,\n ).updateFromJSON(serializedNode);\n }\n\n exportJSON(): SerializedEmojiNode {\n return {\n ...super.exportJSON(),\n className: this.getClassName(),\n };\n }\n\n getClassName(): string {\n const self = this.getLatest();\n return self.__className;\n }\n}\n\nexport function $isEmojiNode(\n node: LexicalNode | null | undefined,\n): node is EmojiNode {\n return node instanceof EmojiNode;\n}\n\nexport function $createEmojiNode(\n className: string,\n emojiText: string,\n): EmojiNode {\n const node = new EmojiNode(className, emojiText).setMode(\"token\");\n return $applyNodeReplacement(node);\n}\n", "type": "registry:component" }, { "path": "src/components/editor/nodes/image-node.tsx", "content": "import type { JSX } from \"react\";\nimport * as React from \"react\";\n\nimport type {\n DOMConversionMap,\n DOMConversionOutput,\n DOMExportOutput,\n EditorConfig,\n LexicalNode,\n LexicalUpdateJSON,\n NodeKey,\n SerializedLexicalNode,\n Spread,\n} from \"lexical\";\nimport { $applyNodeReplacement, DecoratorNode } from \"lexical\";\n\nconst ImageComponent = React.lazy(() => import(\"../editor-ui/image-component\"));\n\nexport interface ImagePayload {\n altText: string;\n height?: number;\n key?: NodeKey;\n maxWidth?: number;\n src: string;\n width?: number;\n}\n\nfunction isGoogleDocCheckboxImg(img: HTMLImageElement): boolean {\n return (\n img.parentElement != null &&\n img.parentElement.tagName === \"LI\" &&\n img.previousSibling === null &&\n img.getAttribute(\"aria-roledescription\") === \"checkbox\"\n );\n}\n\nfunction $convertImageElement(domNode: Node): null | DOMConversionOutput {\n const img = domNode as HTMLImageElement;\n const src = img.getAttribute(\"src\");\n if (!src || src.startsWith(\"file:///\") || isGoogleDocCheckboxImg(img)) {\n return null;\n }\n const { alt: altText, width, height } = img;\n const node = $createImageNode({ altText, height, src, width });\n return { node };\n}\n\nexport type SerializedImageNode = Spread<\n {\n altText: string;\n height?: number;\n maxWidth: number;\n src: string;\n width?: number;\n },\n SerializedLexicalNode\n>;\n\nexport class ImageNode extends DecoratorNode {\n __src: string;\n __altText: string;\n __width: \"inherit\" | number;\n __height: \"inherit\" | number;\n __maxWidth: number;\n\n static getType(): string {\n return \"image\";\n }\n\n static clone(node: ImageNode): ImageNode {\n return new ImageNode(\n node.__src,\n node.__altText,\n node.__maxWidth,\n node.__width,\n node.__height,\n node.__key,\n );\n }\n\n static importJSON(serializedNode: SerializedImageNode): ImageNode {\n const { altText, height, width, maxWidth, src } = serializedNode;\n return $createImageNode({\n altText,\n height,\n maxWidth,\n src,\n width,\n }).updateFromJSON(serializedNode);\n }\n\n updateFromJSON(serializedNode: LexicalUpdateJSON): this {\n const node = super.updateFromJSON(serializedNode);\n\n return node;\n }\n\n exportDOM(): DOMExportOutput {\n const imgElement = document.createElement(\"img\");\n imgElement.setAttribute(\"src\", this.__src);\n imgElement.setAttribute(\"alt\", this.__altText);\n imgElement.setAttribute(\"width\", this.__width.toString());\n imgElement.setAttribute(\"height\", this.__height.toString());\n\n return { element: imgElement };\n }\n\n static importDOM(): DOMConversionMap | null {\n return {\n img: () => ({\n conversion: $convertImageElement,\n priority: 0,\n }),\n };\n }\n\n constructor(\n src: string,\n altText: string,\n maxWidth: number,\n width?: \"inherit\" | number,\n height?: \"inherit\" | number,\n key?: NodeKey,\n ) {\n super(key);\n this.__src = src;\n this.__altText = altText;\n this.__maxWidth = maxWidth;\n this.__width = width || \"inherit\";\n this.__height = height || \"inherit\";\n }\n\n exportJSON(): SerializedImageNode {\n return {\n ...super.exportJSON(),\n altText: this.getAltText(),\n height: this.__height === \"inherit\" ? 0 : this.__height,\n maxWidth: this.__maxWidth,\n src: this.getSrc(),\n width: this.__width === \"inherit\" ? 0 : this.__width,\n };\n }\n\n setWidthAndHeight(\n width: \"inherit\" | number,\n height: \"inherit\" | number,\n ): this {\n const writable = this.getWritable();\n writable.__width = width;\n writable.__height = height;\n return writable;\n }\n\n // View\n\n createDOM(config: EditorConfig): HTMLElement {\n const span = document.createElement(\"span\");\n const theme = config.theme;\n const className = theme.image;\n if (className !== undefined) {\n span.className = className;\n }\n return span;\n }\n\n updateDOM(): false {\n return false;\n }\n\n getSrc(): string {\n return this.getLatest().__src;\n }\n\n getAltText(): string {\n return this.getLatest().__altText;\n }\n\n decorate(): JSX.Element {\n return (\n \n );\n }\n}\n\nexport function $createImageNode({\n altText,\n height,\n maxWidth = 500,\n src,\n width,\n key,\n}: ImagePayload): ImageNode {\n return $applyNodeReplacement(\n new ImageNode(src, altText, maxWidth, width, height, key),\n );\n}\n\nexport function $isImageNode(\n node: LexicalNode | null | undefined,\n): node is ImageNode {\n return node instanceof ImageNode;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/nodes/keyword-node.tsx", "content": "import type { EditorConfig, LexicalNode, SerializedTextNode } from \"lexical\";\nimport { $applyNodeReplacement, TextNode } from \"lexical\";\n\nexport type SerializedKeywordNode = SerializedTextNode;\n\nexport class KeywordNode extends TextNode {\n static getType(): string {\n return \"keyword\";\n }\n\n static clone(node: KeywordNode): KeywordNode {\n return new KeywordNode(node.__text, node.__key);\n }\n\n static importJSON(serializedNode: SerializedKeywordNode): KeywordNode {\n return $createKeywordNode().updateFromJSON(serializedNode);\n }\n\n createDOM(config: EditorConfig): HTMLElement {\n const dom = super.createDOM(config);\n dom.style.cursor = \"default\";\n dom.className = [\"keyword\", config.theme.keyword].filter(Boolean).join(\" \");\n return dom;\n }\n\n canInsertTextBefore(): boolean {\n return false;\n }\n\n canInsertTextAfter(): boolean {\n return false;\n }\n\n isTextEntity(): true {\n return true;\n }\n}\n\nexport function $createKeywordNode(keyword: string = \"\"): KeywordNode {\n return $applyNodeReplacement(new KeywordNode(keyword));\n}\n\nexport function $isKeywordNode(node: LexicalNode | null | undefined): boolean {\n return node instanceof KeywordNode;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/nodes/layout-container-node.tsx", "content": "import { addClassNamesToElement } from \"@lexical/utils\";\nimport type {\n DOMConversionMap,\n DOMConversionOutput,\n DOMExportOutput,\n EditorConfig,\n LexicalNode,\n NodeKey,\n SerializedElementNode,\n Spread,\n} from \"lexical\";\nimport { ElementNode } from \"lexical\";\n\nexport type SerializedLayoutContainerNode = Spread<\n {\n templateColumns: string;\n },\n SerializedElementNode\n>;\n\nfunction $convertLayoutContainerElement(\n domNode: HTMLElement,\n): DOMConversionOutput | null {\n const styleAttributes = window.getComputedStyle(domNode);\n const templateColumns = styleAttributes.getPropertyValue(\n \"grid-template-columns\",\n );\n if (templateColumns) {\n const node = $createLayoutContainerNode(templateColumns);\n return { node };\n }\n return null;\n}\n\nexport class LayoutContainerNode extends ElementNode {\n __templateColumns: string;\n\n constructor(templateColumns: string, key?: NodeKey) {\n super(key);\n this.__templateColumns = templateColumns;\n }\n\n static getType(): string {\n return \"layout-container\";\n }\n\n static clone(node: LayoutContainerNode): LayoutContainerNode {\n return new LayoutContainerNode(node.__templateColumns, node.__key);\n }\n\n createDOM(config: EditorConfig): HTMLElement {\n const dom = document.createElement(\"div\");\n dom.style.gridTemplateColumns = this.__templateColumns;\n if (typeof config.theme.layoutContainer === \"string\") {\n addClassNamesToElement(dom, config.theme.layoutContainer);\n }\n return dom;\n }\n\n exportDOM(): DOMExportOutput {\n const element = document.createElement(\"div\");\n element.style.gridTemplateColumns = this.__templateColumns;\n element.setAttribute(\"data-lexical-layout-container\", \"true\");\n return { element };\n }\n\n updateDOM(prevNode: LayoutContainerNode, dom: HTMLElement): boolean {\n if (prevNode.__templateColumns !== this.__templateColumns) {\n dom.style.gridTemplateColumns = this.__templateColumns;\n }\n return false;\n }\n\n static importDOM(): DOMConversionMap | null {\n return {\n div: (domNode: HTMLElement) => {\n if (!domNode.hasAttribute(\"data-lexical-layout-container\")) {\n return null;\n }\n return {\n conversion: $convertLayoutContainerElement,\n priority: 2,\n };\n },\n };\n }\n\n static importJSON(json: SerializedLayoutContainerNode): LayoutContainerNode {\n return $createLayoutContainerNode(json.templateColumns);\n }\n\n isShadowRoot(): boolean {\n return true;\n }\n\n canBeEmpty(): boolean {\n return false;\n }\n\n exportJSON(): SerializedLayoutContainerNode {\n return {\n ...super.exportJSON(),\n templateColumns: this.__templateColumns,\n type: \"layout-container\",\n version: 1,\n };\n }\n\n getTemplateColumns(): string {\n return this.getLatest().__templateColumns;\n }\n\n setTemplateColumns(templateColumns: string) {\n this.getWritable().__templateColumns = templateColumns;\n }\n}\n\nexport function $createLayoutContainerNode(\n templateColumns: string,\n): LayoutContainerNode {\n return new LayoutContainerNode(templateColumns);\n}\n\nexport function $isLayoutContainerNode(\n node: LexicalNode | null | undefined,\n): node is LayoutContainerNode {\n return node instanceof LayoutContainerNode;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/nodes/layout-item-node.tsx", "content": "import { addClassNamesToElement } from \"@lexical/utils\";\nimport type {\n DOMConversionMap,\n EditorConfig,\n LexicalNode,\n SerializedElementNode,\n} from \"lexical\";\nimport { ElementNode } from \"lexical\";\n\nexport type SerializedLayoutItemNode = SerializedElementNode;\n\nexport class LayoutItemNode extends ElementNode {\n static getType(): string {\n return \"layout-item\";\n }\n\n static clone(node: LayoutItemNode): LayoutItemNode {\n return new LayoutItemNode(node.__key);\n }\n\n createDOM(config: EditorConfig): HTMLElement {\n const dom = document.createElement(\"div\");\n if (typeof config.theme.layoutItem === \"string\") {\n addClassNamesToElement(dom, config.theme.layoutItem);\n }\n return dom;\n }\n\n updateDOM(): boolean {\n return false;\n }\n\n static importDOM(): DOMConversionMap | null {\n return {};\n }\n\n static importJSON(): LayoutItemNode {\n return $createLayoutItemNode();\n }\n\n isShadowRoot(): boolean {\n return true;\n }\n\n exportJSON(): SerializedLayoutItemNode {\n return {\n ...super.exportJSON(),\n type: \"layout-item\",\n version: 1,\n };\n }\n}\n\nexport function $createLayoutItemNode(): LayoutItemNode {\n return new LayoutItemNode();\n}\n\nexport function $isLayoutItemNode(\n node: LexicalNode | null | undefined,\n): node is LayoutItemNode {\n return node instanceof LayoutItemNode;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/nodes/mention-node.ts", "content": "import {\n $applyNodeReplacement,\n type DOMConversionMap,\n type DOMConversionOutput,\n type DOMExportOutput,\n type EditorConfig,\n type LexicalNode,\n type NodeKey,\n type SerializedTextNode,\n type Spread,\n TextNode,\n} from \"lexical\";\n\nexport type SerializedMentionNode = Spread<\n {\n mentionName: string;\n },\n SerializedTextNode\n>;\n\nfunction $convertMentionElement(\n domNode: HTMLElement,\n): DOMConversionOutput | null {\n const textContent = domNode.textContent;\n\n if (textContent !== null) {\n const node = $createMentionNode(textContent);\n return {\n node,\n };\n }\n\n return null;\n}\n\nexport class MentionNode extends TextNode {\n __mention: string;\n\n static getType(): string {\n return \"mention\";\n }\n\n static clone(node: MentionNode): MentionNode {\n return new MentionNode(node.__mention, node.__text, node.__key);\n }\n static importJSON(serializedNode: SerializedMentionNode): MentionNode {\n const node = $createMentionNode(serializedNode.mentionName);\n node.setTextContent(serializedNode.text);\n node.setFormat(serializedNode.format);\n node.setDetail(serializedNode.detail);\n node.setMode(serializedNode.mode);\n node.setStyle(serializedNode.style);\n return node;\n }\n\n constructor(mentionName: string, text?: string, key?: NodeKey) {\n super(text ?? mentionName, key);\n this.__mention = mentionName;\n }\n\n exportJSON(): SerializedMentionNode {\n return {\n ...super.exportJSON(),\n mentionName: this.__mention,\n type: \"mention\",\n version: 1,\n };\n }\n\n createDOM(config: EditorConfig): HTMLElement {\n const dom = super.createDOM(config);\n dom.className = \"mention text-primary bg-primary/10 rounded-sm px-1\";\n return dom;\n }\n\n exportDOM(): DOMExportOutput {\n const element = document.createElement(\"span\");\n element.setAttribute(\"data-lexical-mention\", \"true\");\n element.textContent = this.__text;\n return { element };\n }\n\n static importDOM(): DOMConversionMap | null {\n return {\n span: (domNode: HTMLElement) => {\n if (!domNode.hasAttribute(\"data-lexical-mention\")) {\n return null;\n }\n return {\n conversion: $convertMentionElement,\n priority: 1,\n };\n },\n };\n }\n\n isTextEntity(): true {\n return true;\n }\n\n canInsertTextBefore(): boolean {\n return false;\n }\n\n canInsertTextAfter(): boolean {\n return false;\n }\n}\n\nexport function $createMentionNode(mentionName: string): MentionNode {\n const mentionNode = new MentionNode(mentionName);\n mentionNode.setMode(\"segmented\").toggleDirectionless();\n return $applyNodeReplacement(mentionNode);\n}\n\nexport function $isMentionNode(\n node: LexicalNode | null | undefined,\n): node is MentionNode {\n return node instanceof MentionNode;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/nodes/special-text-node.tsx", "content": "import { addClassNamesToElement } from \"@lexical/utils\";\nimport type { EditorConfig, LexicalNode, SerializedTextNode } from \"lexical\";\nimport { $applyNodeReplacement, TextNode } from \"lexical\";\n\nexport class SpecialTextNode extends TextNode {\n static getType(): string {\n return \"specialText\";\n }\n\n static clone(node: SpecialTextNode): SpecialTextNode {\n return new SpecialTextNode(node.__text, node.__key);\n }\n\n createDOM(config: EditorConfig): HTMLElement {\n const dom = document.createElement(\"span\");\n addClassNamesToElement(dom, config.theme.specialText);\n dom.textContent = this.getTextContent();\n return dom;\n }\n\n updateDOM(prevNode: this, dom: HTMLElement, config: EditorConfig): boolean {\n if (prevNode.__text.startsWith(\"[\") && prevNode.__text.endsWith(\"]\")) {\n const strippedText = this.__text.substring(1, this.__text.length - 1); // Strip brackets again\n dom.textContent = strippedText; // Update the text content\n }\n\n addClassNamesToElement(dom, config.theme.specialText);\n\n return false;\n }\n\n static importJSON(serializedNode: SerializedTextNode): SpecialTextNode {\n return $createSpecialTextNode().updateFromJSON(serializedNode);\n }\n\n isTextEntity(): true {\n return true;\n }\n canInsertTextAfter(): boolean {\n return false; // Prevents appending text to this node\n }\n}\n\nexport function $createSpecialTextNode(text: string = \"\"): SpecialTextNode {\n return $applyNodeReplacement(new SpecialTextNode(text));\n}\n\nexport function $isSpecialTextNode(\n node: LexicalNode | null | undefined,\n): node is SpecialTextNode {\n return node instanceof SpecialTextNode;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/actions/actions-plugin.tsx", "content": "export function ActionsPlugin({ children }: { children: React.ReactNode }) {\n return children;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/actions/clear-editor-plugin.tsx", "content": "import { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { CLEAR_EDITOR_COMMAND } from \"lexical\";\n\nimport { Trash2Icon } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n Dialog,\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from \"@/components/ui/dialog\";\nimport {\n Tooltip,\n TooltipContent,\n TooltipTrigger,\n} from \"@/components/ui/tooltip\";\n\nexport function ClearEditorActionPlugin() {\n const [editor] = useLexicalComposerContext();\n\n return (\n \n \n \n \n \n \n \n \n \n Clear Editor\n \n\n \n \n Clear Editor\n \n Are you sure you want to clear the editor?\n \n \n \n \n Cancel\n \n\n \n {\n editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined);\n }}\n >\n Clear\n \n \n \n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/actions/counter-character-plugin.tsx", "content": "import { useEffect, useState } from \"react\";\n\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { $rootTextContent } from \"@lexical/text\";\n\nlet textEncoderInstance: null | TextEncoder = null;\n\nfunction textEncoder(): null | TextEncoder {\n if (window.TextEncoder === undefined) {\n return null;\n }\n\n if (textEncoderInstance === null) {\n textEncoderInstance = new window.TextEncoder();\n }\n\n return textEncoderInstance;\n}\n\nfunction utf8Length(text: string) {\n const currentTextEncoder = textEncoder();\n\n if (currentTextEncoder === null) {\n // http://stackoverflow.com/a/5515960/210370\n const m = encodeURIComponent(text).match(/%[89ABab]/g);\n return text.length + (m ? m.length : 0);\n }\n\n return currentTextEncoder.encode(text).length;\n}\n\ninterface CounterCharacterPluginProps {\n charset?: \"UTF-8\" | \"UTF-16\";\n}\n\nconst strlen = (text: string, charset: \"UTF-8\" | \"UTF-16\") => {\n if (charset === \"UTF-8\") {\n return utf8Length(text);\n } else if (charset === \"UTF-16\") {\n return text.length;\n }\n};\n\nconst countWords = (text: string) => {\n return text.split(/\\s+/).filter((word) => word.length > 0).length;\n};\n\nexport function CounterCharacterPlugin({\n charset = \"UTF-16\",\n}: CounterCharacterPluginProps) {\n const [editor] = useLexicalComposerContext();\n const [stats, setStats] = useState(() => {\n const initialText = editor.getEditorState().read($rootTextContent);\n return {\n characters: strlen(initialText, charset),\n words: countWords(initialText),\n };\n });\n\n useEffect(() => {\n return editor.registerTextContentListener((currentText: string) => {\n setStats({\n characters: strlen(currentText, charset),\n words: countWords(currentText),\n });\n });\n }, [editor, charset]);\n\n return (\n \n {stats.characters} characters|{stats.words} words\n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/actions/edit-mode-toggle-plugin.tsx", "content": "import { useState } from \"react\";\n\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\n\nimport { LockIcon, UnlockIcon } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n Tooltip,\n TooltipContent,\n TooltipTrigger,\n} from \"@/components/ui/tooltip\";\n\nexport function EditModeTogglePlugin() {\n const [editor] = useLexicalComposerContext();\n const [isEditable, setIsEditable] = useState(() => editor.isEditable());\n\n return (\n \n \n {\n editor.setEditable(!editor.isEditable());\n setIsEditable(editor.isEditable());\n }}\n title=\"Read-Only Mode\"\n aria-label={`${!isEditable ? \"Unlock\" : \"Lock\"} read-only mode`}\n size={\"sm\"}\n className=\"p-2\"\n >\n {isEditable ? (\n \n ) : (\n \n )}\n \n \n \n {isEditable ? \"View Only Mode\" : \"Edit Mode\"}\n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/actions/import-export-plugin.tsx", "content": "import { exportFile, importFile } from \"@lexical/file\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\n\nimport { DownloadIcon, UploadIcon } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n Tooltip,\n TooltipContent,\n TooltipTrigger,\n} from \"@/components/ui/tooltip\";\n\nexport function ImportExportPlugin() {\n const [editor] = useLexicalComposerContext();\n return (\n <>\n \n \n importFile(editor)}\n title=\"Import\"\n aria-label=\"Import editor state from JSON\"\n size={\"sm\"}\n className=\"p-2\"\n >\n \n \n \n Import Content\n \n\n \n \n \n exportFile(editor, {\n fileName: `Editor ${new Date().toISOString()}`,\n source: \"Editor\",\n })\n }\n title=\"Export\"\n aria-label=\"Export editor state to JSON\"\n size={\"sm\"}\n className=\"p-2\"\n >\n \n \n \n Export Content\n \n >\n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/actions/markdown-toggle-plugin.tsx", "content": "import { useCallback } from \"react\";\n\nimport { $createCodeNode, $isCodeNode } from \"@lexical/code\";\nimport {\n $convertFromMarkdownString,\n $convertToMarkdownString,\n type Transformer,\n} from \"@lexical/markdown\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { $createTextNode, $getRoot } from \"lexical\";\n\nimport { FileTextIcon } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\n\nexport function MarkdownTogglePlugin({\n shouldPreserveNewLinesInMarkdown,\n transformers,\n}: {\n shouldPreserveNewLinesInMarkdown: boolean;\n transformers: Array;\n}) {\n const [editor] = useLexicalComposerContext();\n\n const handleMarkdownToggle = useCallback(() => {\n editor.update(() => {\n const root = $getRoot();\n const firstChild = root.getFirstChild();\n if ($isCodeNode(firstChild) && firstChild.getLanguage() === \"markdown\") {\n $convertFromMarkdownString(\n firstChild.getTextContent(),\n transformers,\n undefined, // node\n shouldPreserveNewLinesInMarkdown,\n );\n } else {\n const markdown = $convertToMarkdownString(\n transformers,\n undefined, //node\n shouldPreserveNewLinesInMarkdown,\n );\n const codeNode = $createCodeNode(\"markdown\");\n codeNode.append($createTextNode(markdown));\n root.clear().append(codeNode);\n if (markdown.length === 0) {\n codeNode.select();\n }\n }\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [editor, shouldPreserveNewLinesInMarkdown]);\n\n return (\n \n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/actions/share-content-plugin.tsx", "content": "import { useEffect } from \"react\";\n\nimport {\n type SerializedDocument,\n editorStateFromSerializedDocument,\n serializedDocumentFromEditorState,\n} from \"@lexical/file\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { CLEAR_HISTORY_COMMAND } from \"lexical\";\n\nimport { SendIcon } from \"lucide-react\";\nimport { toast } from \"sonner\";\n\nimport {\n docFromHash,\n docToHash,\n} from \"@/components/editor/utils/doc-serialization\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n Tooltip,\n TooltipContent,\n TooltipTrigger,\n} from \"@/components/ui/tooltip\";\n\nexport function ShareContentPlugin() {\n const [editor] = useLexicalComposerContext();\n async function shareDoc(doc: SerializedDocument): Promise {\n const url = new URL(window.location.toString());\n url.hash = await docToHash(doc);\n const newUrl = url.toString();\n window.history.replaceState({}, \"\", newUrl);\n await window.navigator.clipboard.writeText(newUrl);\n }\n useEffect(() => {\n docFromHash(window.location.hash).then((doc) => {\n if (doc && doc.source === \"editor\") {\n editor.setEditorState(editorStateFromSerializedDocument(editor, doc));\n editor.dispatchCommand(CLEAR_HISTORY_COMMAND, undefined);\n }\n });\n }, [editor]);\n\n return (\n \n \n \n shareDoc(\n serializedDocumentFromEditorState(editor.getEditorState(), {\n source: \"editor\",\n }),\n ).then(\n () => toast.success(\"URL copied to clipboard\"),\n () => toast.error(\"URL could not be copied to clipboard\"),\n )\n }\n title=\"Share\"\n aria-label=\"Share Playground link to current editor state\"\n size={\"sm\"}\n className=\"p-2\"\n >\n \n \n \n Share Content\n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/actions/speech-to-text-plugin.tsx", "content": "import { useEffect, useRef, useState } from \"react\";\n\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport type { LexicalCommand, LexicalEditor, RangeSelection } from \"lexical\";\nimport {\n $getSelection,\n $isRangeSelection,\n COMMAND_PRIORITY_EDITOR,\n REDO_COMMAND,\n UNDO_COMMAND,\n createCommand,\n} from \"lexical\";\n\nimport { MicIcon } from \"lucide-react\";\n\nimport { useReport } from \"@/components/editor/editor-hooks/use-report\";\nimport { CAN_USE_DOM } from \"@/components/editor/shared/can-use-dom\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n Tooltip,\n TooltipContent,\n TooltipTrigger,\n} from \"@/components/ui/tooltip\";\n\nexport const SPEECH_TO_TEXT_COMMAND: LexicalCommand = createCommand(\n \"SPEECH_TO_TEXT_COMMAND\",\n);\n\nconst VOICE_COMMANDS: Readonly<\n Record<\n string,\n (arg0: { editor: LexicalEditor; selection: RangeSelection }) => void\n >\n> = {\n \"\\n\": ({ selection }) => {\n selection.insertParagraph();\n },\n redo: ({ editor }) => {\n editor.dispatchCommand(REDO_COMMAND, undefined);\n },\n undo: ({ editor }) => {\n editor.dispatchCommand(UNDO_COMMAND, undefined);\n },\n};\n\nexport const SUPPORT_SPEECH_RECOGNITION: boolean =\n CAN_USE_DOM &&\n (\"SpeechRecognition\" in window || \"webkitSpeechRecognition\" in window);\n\nfunction SpeechToTextPluginImpl() {\n const [editor] = useLexicalComposerContext();\n const [isEnabled, setIsEnabled] = useState(false);\n const [isSpeechToText, setIsSpeechToText] = useState(false);\n const SpeechRecognition =\n // @ts-expect-error missing type\n window.SpeechRecognition || window.webkitSpeechRecognition;\n const recognition = useRef(null);\n const report = useReport();\n\n useEffect(() => {\n if (isEnabled && recognition.current === null) {\n recognition.current = new SpeechRecognition();\n recognition.current.continuous = true;\n recognition.current.interimResults = true;\n recognition.current.addEventListener(\n \"result\",\n (event: typeof SpeechRecognition) => {\n const resultItem = event.results.item(event.resultIndex);\n const { transcript } = resultItem.item(0);\n report(transcript);\n\n if (!resultItem.isFinal) {\n return;\n }\n\n editor.update(() => {\n const selection = $getSelection();\n\n if ($isRangeSelection(selection)) {\n const command = VOICE_COMMANDS[transcript.toLowerCase().trim()];\n\n if (command) {\n command({\n editor,\n selection,\n });\n } else if (transcript.match(/\\s*\\n\\s*/)) {\n selection.insertParagraph();\n } else {\n selection.insertText(transcript);\n }\n }\n });\n },\n );\n }\n\n if (recognition.current) {\n if (isEnabled) {\n recognition.current.start();\n } else {\n recognition.current.stop();\n }\n }\n\n return () => {\n if (recognition.current !== null) {\n recognition.current.stop();\n }\n };\n }, [SpeechRecognition, editor, isEnabled, report]);\n useEffect(() => {\n return editor.registerCommand(\n SPEECH_TO_TEXT_COMMAND,\n (_isEnabled: boolean) => {\n setIsEnabled(_isEnabled);\n return true;\n },\n COMMAND_PRIORITY_EDITOR,\n );\n }, [editor]);\n\n return (\n \n \n {\n editor.dispatchCommand(SPEECH_TO_TEXT_COMMAND, !isSpeechToText);\n setIsSpeechToText(!isSpeechToText);\n }}\n variant={isSpeechToText ? \"secondary\" : \"ghost\"}\n title=\"Speech To Text\"\n aria-label={`${isSpeechToText ? \"Enable\" : \"Disable\"} speech to text`}\n className=\"p-2\"\n size={\"sm\"}\n >\n \n \n \n Speech To Text\n \n );\n}\n\nexport const SpeechToTextPlugin = SUPPORT_SPEECH_RECOGNITION\n ? SpeechToTextPluginImpl\n : () => null;\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/actions/tree-view-plugin.tsx", "content": "import type { JSX } from \"react\";\n\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { TreeView } from \"@lexical/react/LexicalTreeView\";\n\nimport { NotebookPenIcon } from \"lucide-react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport {\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from \"@/components/ui/dialog\";\nimport { ScrollArea, ScrollBar } from \"@/components/ui/scroll-area\";\n\nexport function TreeViewPlugin(): JSX.Element {\n const [editor] = useLexicalComposerContext();\n return (\n \n \n \n \n \n \n \n \n Tree View\n \n \n \n \n \n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/auto-complete-plugin.tsx", "content": "import type { JSX } from \"react\";\nimport { useCallback, useEffect } from \"react\";\n\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport {\n $getSelectionStyleValueForProperty,\n $isAtNodeEnd,\n} from \"@lexical/selection\";\nimport { mergeRegister } from \"@lexical/utils\";\nimport type { BaseSelection, NodeKey, TextNode } from \"lexical\";\nimport {\n $addUpdateTag,\n $createTextNode,\n $getNodeByKey,\n $getSelection,\n $isRangeSelection,\n $isTextNode,\n $setSelection,\n COMMAND_PRIORITY_LOW,\n HISTORY_MERGE_TAG,\n KEY_ARROW_RIGHT_COMMAND,\n KEY_TAB_COMMAND,\n} from \"lexical\";\n\nimport {\n $createAutocompleteNode,\n AutocompleteNode,\n} from \"@/components/editor/nodes/autocomplete-node\";\nimport { addSwipeRightListener } from \"@/components/editor/utils/swipe\";\n\nconst HISTORY_MERGE = { tag: HISTORY_MERGE_TAG };\n\ndeclare global {\n interface Navigator {\n userAgentData?: {\n mobile: boolean;\n };\n }\n}\n\ntype SearchPromise = {\n dismiss: () => void;\n promise: Promise;\n};\n\nexport const uuid = Math.random()\n .toString(36)\n .replace(/[^a-z]+/g, \"\")\n .substring(0, 5);\n\n// TODO lookup should be custom\nfunction $search(selection: null | BaseSelection): [boolean, string] {\n if (!$isRangeSelection(selection) || !selection.isCollapsed()) {\n return [false, \"\"];\n }\n const node = selection.getNodes()[0];\n const anchor = selection.anchor;\n // Check siblings?\n if (!$isTextNode(node) || !node.isSimpleText() || !$isAtNodeEnd(anchor)) {\n return [false, \"\"];\n }\n const word = [];\n const text = node.getTextContent();\n let i = node.getTextContentSize();\n let c;\n while (i-- && i >= 0 && (c = text[i]) !== \" \") {\n word.push(c);\n }\n if (word.length === 0) {\n return [false, \"\"];\n }\n return [true, word.reverse().join(\"\")];\n}\n\n// TODO query should be custom\nfunction useQuery(): (searchText: string) => SearchPromise {\n return useCallback((searchText: string) => {\n const server = new AutocompleteServer();\n console.time(\"query\");\n const response = server.query(searchText);\n console.timeEnd(\"query\");\n return response;\n }, []);\n}\n\nfunction formatSuggestionText(suggestion: string): string {\n const userAgentData = window.navigator.userAgentData;\n const isMobile =\n userAgentData !== undefined\n ? userAgentData.mobile\n : window.innerWidth <= 800 && window.innerHeight <= 600;\n\n return `${suggestion} ${isMobile ? \"(SWIPE \\u2B95)\" : \"(TAB)\"}`;\n}\n\nexport function AutoCompletePlugin(): JSX.Element | null {\n const [editor] = useLexicalComposerContext();\n const query = useQuery();\n // const {toolbarState} = useToolbarState();\n\n useEffect(() => {\n let autocompleteNodeKey: null | NodeKey = null;\n let lastMatch: null | string = null;\n let lastSuggestion: null | string = null;\n let searchPromise: null | SearchPromise = null;\n let prevNodeFormat: number = 0;\n function $clearSuggestion() {\n const autocompleteNode =\n autocompleteNodeKey !== null\n ? $getNodeByKey(autocompleteNodeKey)\n : null;\n if (autocompleteNode !== null && autocompleteNode.isAttached()) {\n autocompleteNode.remove();\n autocompleteNodeKey = null;\n }\n if (searchPromise !== null) {\n searchPromise.dismiss();\n searchPromise = null;\n }\n lastMatch = null;\n lastSuggestion = null;\n prevNodeFormat = 0;\n }\n function updateAsyncSuggestion(\n refSearchPromise: SearchPromise,\n newSuggestion: null | string,\n ) {\n if (searchPromise !== refSearchPromise || newSuggestion === null) {\n // Outdated or no suggestion\n return;\n }\n editor.update(() => {\n const selection = $getSelection();\n const [hasMatch, match] = $search(selection);\n if (!hasMatch || match !== lastMatch || !$isRangeSelection(selection)) {\n // Outdated\n return;\n }\n const fontSize = $getSelectionStyleValueForProperty(\n selection,\n \"font-size\",\n \"16px\",\n );\n\n const selectionCopy = selection.clone();\n const prevNode = selection.getNodes()[0] as TextNode;\n prevNodeFormat = prevNode.getFormat();\n const node = $createAutocompleteNode(\n formatSuggestionText(newSuggestion),\n uuid,\n )\n .setFormat(prevNodeFormat)\n .setStyle(`font-size: ${fontSize}`);\n autocompleteNodeKey = node.getKey();\n selection.insertNodes([node]);\n $setSelection(selectionCopy);\n lastSuggestion = newSuggestion;\n }, HISTORY_MERGE);\n }\n\n function $handleAutocompleteNodeTransform(node: AutocompleteNode) {\n const key = node.getKey();\n if (node.__uuid === uuid && key !== autocompleteNodeKey) {\n // Max one Autocomplete node per session\n $clearSuggestion();\n }\n }\n function handleUpdate() {\n editor.update(() => {\n const selection = $getSelection();\n const [hasMatch, match] = $search(selection);\n if (!hasMatch) {\n $clearSuggestion();\n return;\n }\n if (match === lastMatch) {\n return;\n }\n $clearSuggestion();\n searchPromise = query(match);\n searchPromise.promise\n .then((newSuggestion) => {\n if (searchPromise !== null) {\n updateAsyncSuggestion(searchPromise, newSuggestion);\n }\n })\n .catch((e) => {\n if (e !== \"Dismissed\") {\n console.error(e);\n }\n });\n lastMatch = match;\n }, HISTORY_MERGE);\n }\n function $handleAutocompleteIntent(): boolean {\n if (lastSuggestion === null || autocompleteNodeKey === null) {\n return false;\n }\n const autocompleteNode = $getNodeByKey(autocompleteNodeKey);\n if (autocompleteNode === null) {\n return false;\n }\n\n const selection = $getSelection();\n if (!$isRangeSelection(selection)) {\n // Outdated\n return false;\n }\n const fontSize = $getSelectionStyleValueForProperty(\n selection,\n \"font-size\",\n \"16px\",\n );\n\n const textNode = $createTextNode(lastSuggestion)\n .setFormat(prevNodeFormat)\n .setStyle(`font-size: ${fontSize}`);\n autocompleteNode.replace(textNode);\n textNode.selectNext();\n $clearSuggestion();\n return true;\n }\n function $handleKeypressCommand(e: Event) {\n if ($handleAutocompleteIntent()) {\n e.preventDefault();\n return true;\n }\n return false;\n }\n function handleSwipeRight(_force: number, e: TouchEvent) {\n editor.update(() => {\n if ($handleAutocompleteIntent()) {\n e.preventDefault();\n } else {\n $addUpdateTag(HISTORY_MERGE.tag);\n }\n });\n }\n function unmountSuggestion() {\n editor.update(() => {\n $clearSuggestion();\n }, HISTORY_MERGE);\n }\n\n const rootElem = editor.getRootElement();\n\n return mergeRegister(\n editor.registerNodeTransform(\n AutocompleteNode,\n $handleAutocompleteNodeTransform,\n ),\n editor.registerUpdateListener(handleUpdate),\n editor.registerCommand(\n KEY_TAB_COMMAND,\n $handleKeypressCommand,\n COMMAND_PRIORITY_LOW,\n ),\n editor.registerCommand(\n KEY_ARROW_RIGHT_COMMAND,\n $handleKeypressCommand,\n COMMAND_PRIORITY_LOW,\n ),\n ...(rootElem !== null\n ? [addSwipeRightListener(rootElem, handleSwipeRight)]\n : []),\n unmountSuggestion,\n );\n }, [editor, query]);\n\n return null;\n}\n\n/*\n * Simulate an asynchronous autocomplete server (typical in more common use cases like GMail where\n * the data is not static).\n */\nclass AutocompleteServer {\n DATABASE = DICTIONARY;\n LATENCY = 200;\n\n query = (searchText: string): SearchPromise => {\n let isDismissed = false;\n\n const dismiss = () => {\n isDismissed = true;\n };\n const promise: Promise = new Promise((resolve, reject) => {\n setTimeout(() => {\n if (isDismissed) {\n // TODO cache result\n return reject(\"Dismissed\");\n }\n const searchTextLength = searchText.length;\n if (searchText === \"\" || searchTextLength < 4) {\n return resolve(null);\n }\n const char0 = searchText.charCodeAt(0);\n const isCapitalized = char0 >= 65 && char0 <= 90;\n const caseInsensitiveSearchText = isCapitalized\n ? String.fromCharCode(char0 + 32) + searchText.substring(1)\n : searchText;\n const match = this.DATABASE.find(\n (dictionaryWord) =>\n dictionaryWord.startsWith(caseInsensitiveSearchText) ?? null,\n );\n if (match === undefined) {\n return resolve(null);\n }\n const matchCapitalized = isCapitalized\n ? String.fromCharCode(match.charCodeAt(0) - 32) + match.substring(1)\n : match;\n const autocompleteChunk = matchCapitalized.substring(searchTextLength);\n if (autocompleteChunk === \"\") {\n return resolve(null);\n }\n return resolve(autocompleteChunk);\n }, this.LATENCY);\n });\n\n return {\n dismiss,\n promise,\n };\n };\n}\n\n// https://raw.githubusercontent.com/first20hours/google-10000-english/master/google-10000-english-usa-no-swears-long.txt\nconst DICTIONARY = [\n \"information\",\n \"available\",\n \"copyright\",\n \"university\",\n \"management\",\n \"international\",\n \"development\",\n \"education\",\n \"community\",\n \"technology\",\n \"following\",\n \"resources\",\n \"including\",\n \"directory\",\n \"government\",\n \"department\",\n \"description\",\n \"insurance\",\n \"different\",\n \"categories\",\n \"conditions\",\n \"accessories\",\n \"september\",\n \"questions\",\n \"application\",\n \"financial\",\n \"equipment\",\n \"performance\",\n \"experience\",\n \"important\",\n \"activities\",\n \"additional\",\n \"something\",\n \"professional\",\n \"committee\",\n \"washington\",\n \"california\",\n \"reference\",\n \"companies\",\n \"computers\",\n \"president\",\n \"australia\",\n \"discussion\",\n \"entertainment\",\n \"agreement\",\n \"marketing\",\n \"association\",\n \"collection\",\n \"solutions\",\n \"electronics\",\n \"technical\",\n \"microsoft\",\n \"conference\",\n \"environment\",\n \"statement\",\n \"downloads\",\n \"applications\",\n \"requirements\",\n \"individual\",\n \"subscribe\",\n \"everything\",\n \"production\",\n \"commercial\",\n \"advertising\",\n \"treatment\",\n \"newsletter\",\n \"knowledge\",\n \"currently\",\n \"construction\",\n \"registered\",\n \"protection\",\n \"engineering\",\n \"published\",\n \"corporate\",\n \"customers\",\n \"materials\",\n \"countries\",\n \"standards\",\n \"political\",\n \"advertise\",\n \"environmental\",\n \"availability\",\n \"employment\",\n \"commission\",\n \"administration\",\n \"institute\",\n \"sponsored\",\n \"electronic\",\n \"condition\",\n \"effective\",\n \"organization\",\n \"selection\",\n \"corporation\",\n \"executive\",\n \"necessary\",\n \"according\",\n \"particular\",\n \"facilities\",\n \"opportunities\",\n \"appropriate\",\n \"statistics\",\n \"investment\",\n \"christmas\",\n \"registration\",\n \"furniture\",\n \"wednesday\",\n \"structure\",\n \"distribution\",\n \"industrial\",\n \"potential\",\n \"responsible\",\n \"communications\",\n \"associated\",\n \"foundation\",\n \"documents\",\n \"communication\",\n \"independent\",\n \"operating\",\n \"developed\",\n \"telephone\",\n \"population\",\n \"navigation\",\n \"operations\",\n \"therefore\",\n \"christian\",\n \"understand\",\n \"publications\",\n \"worldwide\",\n \"connection\",\n \"publisher\",\n \"introduction\",\n \"properties\",\n \"accommodation\",\n \"excellent\",\n \"opportunity\",\n \"assessment\",\n \"especially\",\n \"interface\",\n \"operation\",\n \"restaurants\",\n \"beautiful\",\n \"locations\",\n \"significant\",\n \"technologies\",\n \"manufacturer\",\n \"providing\",\n \"authority\",\n \"considered\",\n \"programme\",\n \"enterprise\",\n \"educational\",\n \"employees\",\n \"alternative\",\n \"processing\",\n \"responsibility\",\n \"resolution\",\n \"publication\",\n \"relations\",\n \"photography\",\n \"components\",\n \"assistance\",\n \"completed\",\n \"organizations\",\n \"otherwise\",\n \"transportation\",\n \"disclaimer\",\n \"membership\",\n \"recommended\",\n \"background\",\n \"character\",\n \"maintenance\",\n \"functions\",\n \"trademarks\",\n \"phentermine\",\n \"submitted\",\n \"television\",\n \"interested\",\n \"throughout\",\n \"established\",\n \"programming\",\n \"regarding\",\n \"instructions\",\n \"increased\",\n \"understanding\",\n \"beginning\",\n \"associates\",\n \"instruments\",\n \"businesses\",\n \"specified\",\n \"restaurant\",\n \"procedures\",\n \"relationship\",\n \"traditional\",\n \"sometimes\",\n \"themselves\",\n \"transport\",\n \"interesting\",\n \"evaluation\",\n \"implementation\",\n \"galleries\",\n \"references\",\n \"presented\",\n \"literature\",\n \"respective\",\n \"definition\",\n \"secretary\",\n \"networking\",\n \"australian\",\n \"magazines\",\n \"francisco\",\n \"individuals\",\n \"guidelines\",\n \"installation\",\n \"described\",\n \"attention\",\n \"difference\",\n \"regulations\",\n \"certificate\",\n \"directions\",\n \"documentation\",\n \"automotive\",\n \"successful\",\n \"communities\",\n \"situation\",\n \"publishing\",\n \"emergency\",\n \"developing\",\n \"determine\",\n \"temperature\",\n \"announcements\",\n \"historical\",\n \"ringtones\",\n \"difficult\",\n \"scientific\",\n \"satellite\",\n \"particularly\",\n \"functional\",\n \"monitoring\",\n \"architecture\",\n \"recommend\",\n \"dictionary\",\n \"accounting\",\n \"manufacturing\",\n \"professor\",\n \"generally\",\n \"continued\",\n \"techniques\",\n \"permission\",\n \"generation\",\n \"component\",\n \"guarantee\",\n \"processes\",\n \"interests\",\n \"paperback\",\n \"classifieds\",\n \"supported\",\n \"competition\",\n \"providers\",\n \"characters\",\n \"thousands\",\n \"apartments\",\n \"generated\",\n \"administrative\",\n \"practices\",\n \"reporting\",\n \"essential\",\n \"affiliate\",\n \"immediately\",\n \"designated\",\n \"integrated\",\n \"configuration\",\n \"comprehensive\",\n \"universal\",\n \"presentation\",\n \"languages\",\n \"compliance\",\n \"improvement\",\n \"pennsylvania\",\n \"challenge\",\n \"acceptance\",\n \"strategies\",\n \"affiliates\",\n \"multimedia\",\n \"certified\",\n \"computing\",\n \"interactive\",\n \"procedure\",\n \"leadership\",\n \"religious\",\n \"breakfast\",\n \"developer\",\n \"approximately\",\n \"recommendations\",\n \"comparison\",\n \"automatically\",\n \"minnesota\",\n \"adventure\",\n \"institutions\",\n \"assistant\",\n \"advertisement\",\n \"headlines\",\n \"yesterday\",\n \"determined\",\n \"wholesale\",\n \"extension\",\n \"statements\",\n \"completely\",\n \"electrical\",\n \"applicable\",\n \"manufacturers\",\n \"classical\",\n \"dedicated\",\n \"direction\",\n \"basketball\",\n \"wisconsin\",\n \"personnel\",\n \"identified\",\n \"professionals\",\n \"advantage\",\n \"newsletters\",\n \"estimated\",\n \"anonymous\",\n \"miscellaneous\",\n \"integration\",\n \"interview\",\n \"framework\",\n \"installed\",\n \"massachusetts\",\n \"associate\",\n \"frequently\",\n \"discussions\",\n \"laboratory\",\n \"destination\",\n \"intelligence\",\n \"specifications\",\n \"tripadvisor\",\n \"residential\",\n \"decisions\",\n \"industries\",\n \"partnership\",\n \"editorial\",\n \"expression\",\n \"provisions\",\n \"principles\",\n \"suggestions\",\n \"replacement\",\n \"strategic\",\n \"economics\",\n \"compatible\",\n \"apartment\",\n \"netherlands\",\n \"consulting\",\n \"recreation\",\n \"participants\",\n \"favorites\",\n \"translation\",\n \"estimates\",\n \"protected\",\n \"philadelphia\",\n \"officials\",\n \"contained\",\n \"legislation\",\n \"parameters\",\n \"relationships\",\n \"tennessee\",\n \"representative\",\n \"frequency\",\n \"introduced\",\n \"departments\",\n \"residents\",\n \"displayed\",\n \"performed\",\n \"administrator\",\n \"addresses\",\n \"permanent\",\n \"agriculture\",\n \"constitutes\",\n \"portfolio\",\n \"practical\",\n \"delivered\",\n \"collectibles\",\n \"infrastructure\",\n \"exclusive\",\n \"originally\",\n \"utilities\",\n \"philosophy\",\n \"regulation\",\n \"reduction\",\n \"nutrition\",\n \"recording\",\n \"secondary\",\n \"wonderful\",\n \"announced\",\n \"prevention\",\n \"mentioned\",\n \"automatic\",\n \"healthcare\",\n \"maintained\",\n \"increasing\",\n \"connected\",\n \"directors\",\n \"participation\",\n \"containing\",\n \"combination\",\n \"amendment\",\n \"guaranteed\",\n \"libraries\",\n \"distributed\",\n \"singapore\",\n \"enterprises\",\n \"convention\",\n \"principal\",\n \"certification\",\n \"previously\",\n \"buildings\",\n \"household\",\n \"batteries\",\n \"positions\",\n \"subscription\",\n \"contemporary\",\n \"panasonic\",\n \"permalink\",\n \"signature\",\n \"provision\",\n \"certainly\",\n \"newspaper\",\n \"liability\",\n \"trademark\",\n \"trackback\",\n \"americans\",\n \"promotion\",\n \"conversion\",\n \"reasonable\",\n \"broadband\",\n \"influence\",\n \"importance\",\n \"webmaster\",\n \"prescription\",\n \"specifically\",\n \"represent\",\n \"conservation\",\n \"louisiana\",\n \"javascript\",\n \"marketplace\",\n \"evolution\",\n \"certificates\",\n \"objectives\",\n \"suggested\",\n \"concerned\",\n \"structures\",\n \"encyclopedia\",\n \"continuing\",\n \"interracial\",\n \"competitive\",\n \"suppliers\",\n \"preparation\",\n \"receiving\",\n \"accordance\",\n \"discussed\",\n \"elizabeth\",\n \"reservations\",\n \"playstation\",\n \"instruction\",\n \"annotation\",\n \"differences\",\n \"establish\",\n \"expressed\",\n \"paragraph\",\n \"mathematics\",\n \"compensation\",\n \"conducted\",\n \"percentage\",\n \"mississippi\",\n \"requested\",\n \"connecticut\",\n \"personals\",\n \"immediate\",\n \"agricultural\",\n \"supporting\",\n \"collections\",\n \"participate\",\n \"specialist\",\n \"experienced\",\n \"investigation\",\n \"institution\",\n \"searching\",\n \"proceedings\",\n \"transmission\",\n \"characteristics\",\n \"experiences\",\n \"extremely\",\n \"verzeichnis\",\n \"contracts\",\n \"concerning\",\n \"developers\",\n \"equivalent\",\n \"chemistry\",\n \"neighborhood\",\n \"variables\",\n \"continues\",\n \"curriculum\",\n \"psychology\",\n \"responses\",\n \"circumstances\",\n \"identification\",\n \"appliances\",\n \"elementary\",\n \"unlimited\",\n \"printable\",\n \"enforcement\",\n \"hardcover\",\n \"celebrity\",\n \"chocolate\",\n \"hampshire\",\n \"bluetooth\",\n \"controlled\",\n \"requirement\",\n \"authorities\",\n \"representatives\",\n \"pregnancy\",\n \"biography\",\n \"attractions\",\n \"transactions\",\n \"authorized\",\n \"retirement\",\n \"financing\",\n \"efficiency\",\n \"efficient\",\n \"commitment\",\n \"specialty\",\n \"interviews\",\n \"qualified\",\n \"discovery\",\n \"classified\",\n \"confidence\",\n \"lifestyle\",\n \"consistent\",\n \"clearance\",\n \"connections\",\n \"inventory\",\n \"converter\",\n \"organisation\",\n \"objective\",\n \"indicated\",\n \"securities\",\n \"volunteer\",\n \"democratic\",\n \"switzerland\",\n \"parameter\",\n \"processor\",\n \"dimensions\",\n \"contribute\",\n \"challenges\",\n \"recognition\",\n \"submission\",\n \"encourage\",\n \"regulatory\",\n \"inspection\",\n \"consumers\",\n \"territory\",\n \"transaction\",\n \"manchester\",\n \"contributions\",\n \"continuous\",\n \"resulting\",\n \"cambridge\",\n \"initiative\",\n \"execution\",\n \"disability\",\n \"increases\",\n \"contractor\",\n \"examination\",\n \"indicates\",\n \"committed\",\n \"extensive\",\n \"affordable\",\n \"candidate\",\n \"databases\",\n \"outstanding\",\n \"perspective\",\n \"messenger\",\n \"tournament\",\n \"consideration\",\n \"discounts\",\n \"catalogue\",\n \"publishers\",\n \"caribbean\",\n \"reservation\",\n \"remaining\",\n \"depending\",\n \"expansion\",\n \"purchased\",\n \"performing\",\n \"collected\",\n \"absolutely\",\n \"featuring\",\n \"implement\",\n \"scheduled\",\n \"calculator\",\n \"significantly\",\n \"temporary\",\n \"sufficient\",\n \"awareness\",\n \"vancouver\",\n \"contribution\",\n \"measurement\",\n \"constitution\",\n \"packaging\",\n \"consultation\",\n \"northwest\",\n \"classroom\",\n \"democracy\",\n \"wallpaper\",\n \"merchandise\",\n \"resistance\",\n \"baltimore\",\n \"candidates\",\n \"charlotte\",\n \"biological\",\n \"transition\",\n \"preferences\",\n \"instrument\",\n \"classification\",\n \"physician\",\n \"hollywood\",\n \"wikipedia\",\n \"spiritual\",\n \"photographs\",\n \"relatively\",\n \"satisfaction\",\n \"represents\",\n \"pittsburgh\",\n \"preferred\",\n \"intellectual\",\n \"comfortable\",\n \"interaction\",\n \"listening\",\n \"effectively\",\n \"experimental\",\n \"revolution\",\n \"consolidation\",\n \"landscape\",\n \"dependent\",\n \"mechanical\",\n \"consultants\",\n \"applicant\",\n \"cooperation\",\n \"acquisition\",\n \"implemented\",\n \"directories\",\n \"recognized\",\n \"notification\",\n \"licensing\",\n \"textbooks\",\n \"diversity\",\n \"cleveland\",\n \"investments\",\n \"accessibility\",\n \"sensitive\",\n \"templates\",\n \"completion\",\n \"universities\",\n \"technique\",\n \"contractors\",\n \"subscriptions\",\n \"calculate\",\n \"alexander\",\n \"broadcast\",\n \"converted\",\n \"anniversary\",\n \"improvements\",\n \"specification\",\n \"accessible\",\n \"accessory\",\n \"typically\",\n \"representation\",\n \"arrangements\",\n \"conferences\",\n \"uniprotkb\",\n \"consumption\",\n \"birmingham\",\n \"afternoon\",\n \"consultant\",\n \"controller\",\n \"ownership\",\n \"committees\",\n \"legislative\",\n \"researchers\",\n \"unsubscribe\",\n \"molecular\",\n \"residence\",\n \"attorneys\",\n \"operators\",\n \"sustainable\",\n \"philippines\",\n \"statistical\",\n \"innovation\",\n \"employers\",\n \"definitions\",\n \"elections\",\n \"stainless\",\n \"newspapers\",\n \"hospitals\",\n \"exception\",\n \"successfully\",\n \"indonesia\",\n \"primarily\",\n \"capabilities\",\n \"recommendation\",\n \"recruitment\",\n \"organized\",\n \"improving\",\n \"expensive\",\n \"organisations\",\n \"explained\",\n \"programmes\",\n \"expertise\",\n \"mechanism\",\n \"jewellery\",\n \"eventually\",\n \"agreements\",\n \"considering\",\n \"innovative\",\n \"conclusion\",\n \"disorders\",\n \"collaboration\",\n \"detection\",\n \"formation\",\n \"engineers\",\n \"proposals\",\n \"moderator\",\n \"tutorials\",\n \"settlement\",\n \"collectables\",\n \"fantastic\",\n \"governments\",\n \"purchasing\",\n \"appointed\",\n \"operational\",\n \"corresponding\",\n \"descriptions\",\n \"determination\",\n \"animation\",\n \"productions\",\n \"telecommunications\",\n \"instructor\",\n \"approaches\",\n \"highlights\",\n \"designers\",\n \"melbourne\",\n \"scientists\",\n \"blackjack\",\n \"argentina\",\n \"possibility\",\n \"commissioner\",\n \"dangerous\",\n \"reliability\",\n \"unfortunately\",\n \"respectively\",\n \"volunteers\",\n \"attachment\",\n \"appointment\",\n \"workshops\",\n \"hurricane\",\n \"represented\",\n \"mortgages\",\n \"responsibilities\",\n \"carefully\",\n \"productivity\",\n \"investors\",\n \"underground\",\n \"diagnosis\",\n \"principle\",\n \"vacations\",\n \"calculated\",\n \"appearance\",\n \"incorporated\",\n \"notebooks\",\n \"algorithm\",\n \"valentine\",\n \"involving\",\n \"investing\",\n \"christopher\",\n \"admission\",\n \"terrorism\",\n \"parliament\",\n \"situations\",\n \"allocated\",\n \"corrections\",\n \"structural\",\n \"municipal\",\n \"describes\",\n \"disabilities\",\n \"substance\",\n \"prohibited\",\n \"addressed\",\n \"simulation\",\n \"initiatives\",\n \"concentration\",\n \"interpretation\",\n \"bankruptcy\",\n \"optimization\",\n \"substances\",\n \"discovered\",\n \"restrictions\",\n \"participating\",\n \"exhibition\",\n \"composition\",\n \"nationwide\",\n \"definitely\",\n \"existence\",\n \"commentary\",\n \"limousines\",\n \"developments\",\n \"immigration\",\n \"destinations\",\n \"necessarily\",\n \"attribute\",\n \"apparently\",\n \"surrounding\",\n \"mountains\",\n \"popularity\",\n \"postposted\",\n \"coordinator\",\n \"obviously\",\n \"fundamental\",\n \"substantial\",\n \"progressive\",\n \"championship\",\n \"sacramento\",\n \"impossible\",\n \"depression\",\n \"testimonials\",\n \"memorabilia\",\n \"cartridge\",\n \"explanation\",\n \"cincinnati\",\n \"subsection\",\n \"electricity\",\n \"permitted\",\n \"workplace\",\n \"confirmed\",\n \"wallpapers\",\n \"infection\",\n \"eligibility\",\n \"involvement\",\n \"placement\",\n \"observations\",\n \"vbulletin\",\n \"subsequent\",\n \"motorcycle\",\n \"disclosure\",\n \"establishment\",\n \"presentations\",\n \"undergraduate\",\n \"occupation\",\n \"donations\",\n \"associations\",\n \"citysearch\",\n \"radiation\",\n \"seriously\",\n \"elsewhere\",\n \"pollution\",\n \"conservative\",\n \"guestbook\",\n \"effectiveness\",\n \"demonstrate\",\n \"atmosphere\",\n \"experiment\",\n \"purchases\",\n \"federation\",\n \"assignment\",\n \"chemicals\",\n \"everybody\",\n \"nashville\",\n \"counseling\",\n \"acceptable\",\n \"satisfied\",\n \"measurements\",\n \"milwaukee\",\n \"medication\",\n \"warehouse\",\n \"shareware\",\n \"violation\",\n \"configure\",\n \"stability\",\n \"southwest\",\n \"institutional\",\n \"expectations\",\n \"independence\",\n \"metabolism\",\n \"personally\",\n \"excellence\",\n \"somewhere\",\n \"attributes\",\n \"recognize\",\n \"screening\",\n \"thumbnail\",\n \"forgotten\",\n \"intelligent\",\n \"edinburgh\",\n \"obligation\",\n \"regardless\",\n \"restricted\",\n \"republican\",\n \"merchants\",\n \"attendance\",\n \"arguments\",\n \"amsterdam\",\n \"adventures\",\n \"announcement\",\n \"appreciate\",\n \"regularly\",\n \"mechanisms\",\n \"customize\",\n \"tradition\",\n \"indicators\",\n \"emissions\",\n \"physicians\",\n \"complaint\",\n \"experiments\",\n \"afghanistan\",\n \"scholarship\",\n \"governance\",\n \"supplements\",\n \"camcorder\",\n \"implementing\",\n \"ourselves\",\n \"conversation\",\n \"capability\",\n \"producing\",\n \"precision\",\n \"contributed\",\n \"reproduction\",\n \"ingredients\",\n \"franchise\",\n \"complaints\",\n \"promotions\",\n \"rehabilitation\",\n \"maintaining\",\n \"environments\",\n \"reception\",\n \"correctly\",\n \"consequences\",\n \"geography\",\n \"appearing\",\n \"integrity\",\n \"discrimination\",\n \"processed\",\n \"implications\",\n \"functionality\",\n \"intermediate\",\n \"emotional\",\n \"platforms\",\n \"overnight\",\n \"geographic\",\n \"preliminary\",\n \"districts\",\n \"introduce\",\n \"promotional\",\n \"chevrolet\",\n \"specialists\",\n \"generator\",\n \"suspension\",\n \"correction\",\n \"authentication\",\n \"communicate\",\n \"supplement\",\n \"showtimes\",\n \"promoting\",\n \"machinery\",\n \"bandwidth\",\n \"probability\",\n \"dimension\",\n \"schedules\",\n \"admissions\",\n \"quarterly\",\n \"illustrated\",\n \"continental\",\n \"alternate\",\n \"achievement\",\n \"limitations\",\n \"automated\",\n \"passenger\",\n \"convenient\",\n \"orientation\",\n \"childhood\",\n \"flexibility\",\n \"jurisdiction\",\n \"displaying\",\n \"encouraged\",\n \"cartridges\",\n \"declaration\",\n \"automation\",\n \"advantages\",\n \"preparing\",\n \"recipient\",\n \"extensions\",\n \"athletics\",\n \"southeast\",\n \"alternatives\",\n \"determining\",\n \"personalized\",\n \"conditioning\",\n \"partnerships\",\n \"destruction\",\n \"increasingly\",\n \"migration\",\n \"basically\",\n \"conventional\",\n \"applicants\",\n \"occupational\",\n \"adjustment\",\n \"treatments\",\n \"camcorders\",\n \"difficulty\",\n \"collective\",\n \"coalition\",\n \"enrollment\",\n \"producers\",\n \"collector\",\n \"interfaces\",\n \"advertisers\",\n \"representing\",\n \"observation\",\n \"restoration\",\n \"convenience\",\n \"returning\",\n \"opposition\",\n \"container\",\n \"defendant\",\n \"confirmation\",\n \"supervisor\",\n \"peripherals\",\n \"bestsellers\",\n \"departure\",\n \"minneapolis\",\n \"interactions\",\n \"intervention\",\n \"attraction\",\n \"modification\",\n \"customized\",\n \"understood\",\n \"assurance\",\n \"happening\",\n \"amendments\",\n \"metropolitan\",\n \"compilation\",\n \"verification\",\n \"attractive\",\n \"recordings\",\n \"jefferson\",\n \"gardening\",\n \"obligations\",\n \"orchestra\",\n \"polyphonic\",\n \"outsourcing\",\n \"adjustable\",\n \"allocation\",\n \"discipline\",\n \"demonstrated\",\n \"identifying\",\n \"alphabetical\",\n \"dispatched\",\n \"installing\",\n \"voluntary\",\n \"photographer\",\n \"messaging\",\n \"constructed\",\n \"additions\",\n \"requiring\",\n \"engagement\",\n \"refinance\",\n \"calendars\",\n \"arrangement\",\n \"conclusions\",\n \"bibliography\",\n \"compatibility\",\n \"furthermore\",\n \"cooperative\",\n \"measuring\",\n \"jacksonville\",\n \"headquarters\",\n \"transfers\",\n \"transformation\",\n \"attachments\",\n \"administrators\",\n \"personality\",\n \"facilitate\",\n \"subscriber\",\n \"priorities\",\n \"bookstore\",\n \"parenting\",\n \"incredible\",\n \"commonwealth\",\n \"pharmaceutical\",\n \"manhattan\",\n \"workforce\",\n \"organizational\",\n \"portuguese\",\n \"everywhere\",\n \"discharge\",\n \"halloween\",\n \"hazardous\",\n \"methodology\",\n \"housewares\",\n \"reputation\",\n \"resistant\",\n \"democrats\",\n \"recycling\",\n \"qualifications\",\n \"slideshow\",\n \"variation\",\n \"transferred\",\n \"photograph\",\n \"distributor\",\n \"underlying\",\n \"wrestling\",\n \"photoshop\",\n \"gathering\",\n \"projection\",\n \"mathematical\",\n \"specialized\",\n \"diagnostic\",\n \"indianapolis\",\n \"corporations\",\n \"criticism\",\n \"automobile\",\n \"confidential\",\n \"statutory\",\n \"accommodations\",\n \"northeast\",\n \"downloaded\",\n \"paintings\",\n \"injection\",\n \"yorkshire\",\n \"populations\",\n \"protective\",\n \"initially\",\n \"indicator\",\n \"eliminate\",\n \"sunglasses\",\n \"preference\",\n \"threshold\",\n \"venezuela\",\n \"exploration\",\n \"sequences\",\n \"astronomy\",\n \"translate\",\n \"announces\",\n \"compression\",\n \"establishing\",\n \"constitutional\",\n \"perfectly\",\n \"instantly\",\n \"litigation\",\n \"submissions\",\n \"broadcasting\",\n \"horizontal\",\n \"terrorist\",\n \"informational\",\n \"ecommerce\",\n \"suffering\",\n \"prospective\",\n \"ultimately\",\n \"artificial\",\n \"spectacular\",\n \"coordination\",\n \"connector\",\n \"affiliated\",\n \"activation\",\n \"naturally\",\n \"subscribers\",\n \"mitsubishi\",\n \"underwear\",\n \"potentially\",\n \"constraints\",\n \"inclusive\",\n \"dimensional\",\n \"considerable\",\n \"selecting\",\n \"processors\",\n \"pantyhose\",\n \"difficulties\",\n \"complexity\",\n \"constantly\",\n \"barcelona\",\n \"presidential\",\n \"documentary\",\n \"territories\",\n \"palestinian\",\n \"legislature\",\n \"hospitality\",\n \"procurement\",\n \"theoretical\",\n \"exercises\",\n \"surveillance\",\n \"protocols\",\n \"highlight\",\n \"substitute\",\n \"inclusion\",\n \"hopefully\",\n \"brilliant\",\n \"evaluated\",\n \"assignments\",\n \"termination\",\n \"households\",\n \"authentic\",\n \"montgomery\",\n \"architectural\",\n \"louisville\",\n \"macintosh\",\n \"movements\",\n \"amenities\",\n \"virtually\",\n \"authorization\",\n \"projector\",\n \"comparative\",\n \"psychological\",\n \"surprised\",\n \"genealogy\",\n \"expenditure\",\n \"liverpool\",\n \"connectivity\",\n \"algorithms\",\n \"similarly\",\n \"collaborative\",\n \"excluding\",\n \"commander\",\n \"suggestion\",\n \"spotlight\",\n \"investigate\",\n \"connecting\",\n \"logistics\",\n \"proportion\",\n \"significance\",\n \"symposium\",\n \"essentials\",\n \"protecting\",\n \"transmitted\",\n \"screenshots\",\n \"intensive\",\n \"switching\",\n \"correspondence\",\n \"supervision\",\n \"expenditures\",\n \"separation\",\n \"testimony\",\n \"celebrities\",\n \"mandatory\",\n \"boundaries\",\n \"syndication\",\n \"celebration\",\n \"filtering\",\n \"luxembourg\",\n \"offensive\",\n \"deployment\",\n \"colleagues\",\n \"separated\",\n \"directive\",\n \"governing\",\n \"retailers\",\n \"occasionally\",\n \"attending\",\n \"recruiting\",\n \"instructional\",\n \"traveling\",\n \"permissions\",\n \"biotechnology\",\n \"prescribed\",\n \"catherine\",\n \"reproduced\",\n \"calculation\",\n \"consolidated\",\n \"occasions\",\n \"equations\",\n \"exceptional\",\n \"respondents\",\n \"considerations\",\n \"queensland\",\n \"musicians\",\n \"composite\",\n \"unavailable\",\n \"essentially\",\n \"designing\",\n \"assessments\",\n \"brunswick\",\n \"sensitivity\",\n \"preservation\",\n \"streaming\",\n \"intensity\",\n \"technological\",\n \"syndicate\",\n \"antivirus\",\n \"addressing\",\n \"discounted\",\n \"bangladesh\",\n \"constitute\",\n \"concluded\",\n \"desperate\",\n \"demonstration\",\n \"governmental\",\n \"manufactured\",\n \"graduation\",\n \"variations\",\n \"addiction\",\n \"springfield\",\n \"synthesis\",\n \"undefined\",\n \"unemployment\",\n \"enhancement\",\n \"newcastle\",\n \"performances\",\n \"societies\",\n \"brazilian\",\n \"identical\",\n \"petroleum\",\n \"norwegian\",\n \"retention\",\n \"exchanges\",\n \"soundtrack\",\n \"wondering\",\n \"profession\",\n \"separately\",\n \"physiology\",\n \"collecting\",\n \"participant\",\n \"scholarships\",\n \"recreational\",\n \"dominican\",\n \"friendship\",\n \"expanding\",\n \"provincial\",\n \"investigations\",\n \"medications\",\n \"rochester\",\n \"advertiser\",\n \"encryption\",\n \"downloadable\",\n \"sophisticated\",\n \"possession\",\n \"laboratories\",\n \"vegetables\",\n \"thumbnails\",\n \"stockings\",\n \"respondent\",\n \"destroyed\",\n \"manufacture\",\n \"wordpress\",\n \"vulnerability\",\n \"accountability\",\n \"celebrate\",\n \"accredited\",\n \"appliance\",\n \"compressed\",\n \"scheduling\",\n \"perspectives\",\n \"mortality\",\n \"christians\",\n \"therapeutic\",\n \"impressive\",\n \"accordingly\",\n \"architect\",\n \"challenging\",\n \"microwave\",\n \"accidents\",\n \"relocation\",\n \"contributors\",\n \"violations\",\n \"temperatures\",\n \"competitions\",\n \"discretion\",\n \"cosmetics\",\n \"repository\",\n \"concentrations\",\n \"christianity\",\n \"negotiations\",\n \"realistic\",\n \"generating\",\n \"christina\",\n \"congressional\",\n \"photographic\",\n \"modifications\",\n \"millennium\",\n \"achieving\",\n \"fisheries\",\n \"exceptions\",\n \"reactions\",\n \"macromedia\",\n \"companion\",\n \"divisions\",\n \"additionally\",\n \"fellowship\",\n \"victorian\",\n \"copyrights\",\n \"lithuania\",\n \"mastercard\",\n \"chronicles\",\n \"obtaining\",\n \"distribute\",\n \"decorative\",\n \"enlargement\",\n \"campaigns\",\n \"conjunction\",\n \"instances\",\n \"indigenous\",\n \"validation\",\n \"corruption\",\n \"incentives\",\n \"cholesterol\",\n \"differential\",\n \"scientist\",\n \"starsmerchant\",\n \"arthritis\",\n \"nevertheless\",\n \"practitioners\",\n \"transcript\",\n \"inflation\",\n \"compounds\",\n \"contracting\",\n \"structured\",\n \"reasonably\",\n \"graduates\",\n \"recommends\",\n \"controlling\",\n \"distributors\",\n \"arlington\",\n \"particles\",\n \"extraordinary\",\n \"indicating\",\n \"coordinate\",\n \"exclusively\",\n \"limitation\",\n \"widescreen\",\n \"illustration\",\n \"construct\",\n \"inquiries\",\n \"inspiration\",\n \"affecting\",\n \"downloading\",\n \"aggregate\",\n \"forecasts\",\n \"complicated\",\n \"shopzilla\",\n \"decorating\",\n \"expressions\",\n \"shakespeare\",\n \"connectors\",\n \"conflicts\",\n \"travelers\",\n \"offerings\",\n \"incorrect\",\n \"furnishings\",\n \"guatemala\",\n \"perception\",\n \"renaissance\",\n \"pathology\",\n \"ordinance\",\n \"photographers\",\n \"infections\",\n \"configured\",\n \"festivals\",\n \"possibilities\",\n \"contributing\",\n \"analytical\",\n \"circulation\",\n \"assumption\",\n \"jerusalem\",\n \"transexuales\",\n \"invention\",\n \"technician\",\n \"executives\",\n \"enquiries\",\n \"cognitive\",\n \"exploring\",\n \"registrar\",\n \"supporters\",\n \"withdrawal\",\n \"predicted\",\n \"saskatchewan\",\n \"cancellation\",\n \"ministers\",\n \"veterinary\",\n \"prostores\",\n \"relevance\",\n \"incentive\",\n \"butterfly\",\n \"mechanics\",\n \"numerical\",\n \"reflection\",\n \"accompanied\",\n \"invitation\",\n \"princeton\",\n \"spirituality\",\n \"meanwhile\",\n \"proprietary\",\n \"childrens\",\n \"thumbzilla\",\n \"porcelain\",\n \"pichunter\",\n \"translated\",\n \"columnists\",\n \"consensus\",\n \"delivering\",\n \"journalism\",\n \"intention\",\n \"undertaken\",\n \"statewide\",\n \"semiconductor\",\n \"illustrations\",\n \"happiness\",\n \"substantially\",\n \"identifier\",\n \"calculations\",\n \"conducting\",\n \"accomplished\",\n \"calculators\",\n \"impression\",\n \"correlation\",\n \"fragrance\",\n \"neighbors\",\n \"transparent\",\n \"charleston\",\n \"champions\",\n \"selections\",\n \"projectors\",\n \"inappropriate\",\n \"comparing\",\n \"vocational\",\n \"pharmacies\",\n \"introducing\",\n \"appreciated\",\n \"albuquerque\",\n \"distinguished\",\n \"projected\",\n \"assumptions\",\n \"shareholders\",\n \"developmental\",\n \"regulated\",\n \"anticipated\",\n \"completing\",\n \"comparable\",\n \"confusion\",\n \"copyrighted\",\n \"warranties\",\n \"documented\",\n \"paperbacks\",\n \"keyboards\",\n \"vulnerable\",\n \"reflected\",\n \"respiratory\",\n \"notifications\",\n \"transexual\",\n \"mainstream\",\n \"evaluating\",\n \"subcommittee\",\n \"maternity\",\n \"journalists\",\n \"foundations\",\n \"volleyball\",\n \"liabilities\",\n \"decreased\",\n \"tolerance\",\n \"creativity\",\n \"describing\",\n \"lightning\",\n \"quotations\",\n \"inspector\",\n \"bookmarks\",\n \"behavioral\",\n \"riverside\",\n \"bathrooms\",\n \"abilities\",\n \"initiated\",\n \"nonprofit\",\n \"lancaster\",\n \"suspended\",\n \"containers\",\n \"attitudes\",\n \"simultaneously\",\n \"integrate\",\n \"sociology\",\n \"screenshot\",\n \"exhibitions\",\n \"confident\",\n \"retrieved\",\n \"officially\",\n \"consortium\",\n \"recipients\",\n \"delicious\",\n \"traditions\",\n \"periodically\",\n \"hungarian\",\n \"referring\",\n \"transform\",\n \"educators\",\n \"vegetable\",\n \"humanities\",\n \"independently\",\n \"alignment\",\n \"henderson\",\n \"britannica\",\n \"competitors\",\n \"visibility\",\n \"consciousness\",\n \"encounter\",\n \"resolutions\",\n \"accessing\",\n \"attempted\",\n \"witnesses\",\n \"administered\",\n \"strengthen\",\n \"frederick\",\n \"aggressive\",\n \"advertisements\",\n \"sublimedirectory\",\n \"disturbed\",\n \"determines\",\n \"sculpture\",\n \"motivation\",\n \"pharmacology\",\n \"passengers\",\n \"quantities\",\n \"petersburg\",\n \"consistently\",\n \"powerpoint\",\n \"obituaries\",\n \"punishment\",\n \"appreciation\",\n \"subsequently\",\n \"providence\",\n \"restriction\",\n \"incorporate\",\n \"backgrounds\",\n \"treasurer\",\n \"lightweight\",\n \"transcription\",\n \"complications\",\n \"scripting\",\n \"remembered\",\n \"synthetic\",\n \"testament\",\n \"specifics\",\n \"partially\",\n \"wilderness\",\n \"generations\",\n \"tournaments\",\n \"sponsorship\",\n \"headphones\",\n \"proceeding\",\n \"volkswagen\",\n \"uncertainty\",\n \"breakdown\",\n \"reconstruction\",\n \"subsidiary\",\n \"strengths\",\n \"encouraging\",\n \"furnished\",\n \"terrorists\",\n \"comparisons\",\n \"beneficial\",\n \"distributions\",\n \"viewpicture\",\n \"threatened\",\n \"republicans\",\n \"discusses\",\n \"responded\",\n \"abstracts\",\n \"prediction\",\n \"pharmaceuticals\",\n \"thesaurus\",\n \"individually\",\n \"battlefield\",\n \"literally\",\n \"ecological\",\n \"appraisal\",\n \"consisting\",\n \"submitting\",\n \"citations\",\n \"geographical\",\n \"mozambique\",\n \"disclaimers\",\n \"championships\",\n \"sheffield\",\n \"finishing\",\n \"wellington\",\n \"prospects\",\n \"bulgarian\",\n \"aboriginal\",\n \"remarkable\",\n \"preventing\",\n \"productive\",\n \"boulevard\",\n \"compliant\",\n \"penalties\",\n \"imagination\",\n \"refurbished\",\n \"activated\",\n \"conferencing\",\n \"armstrong\",\n \"politicians\",\n \"trackbacks\",\n \"accommodate\",\n \"christine\",\n \"accepting\",\n \"precipitation\",\n \"isolation\",\n \"sustained\",\n \"approximate\",\n \"programmer\",\n \"greetings\",\n \"inherited\",\n \"incomplete\",\n \"chronicle\",\n \"legitimate\",\n \"biographies\",\n \"investigator\",\n \"plaintiff\",\n \"prisoners\",\n \"mediterranean\",\n \"nightlife\",\n \"architects\",\n \"entrepreneur\",\n \"freelance\",\n \"excessive\",\n \"screensaver\",\n \"valuation\",\n \"unexpected\",\n \"cigarette\",\n \"characteristic\",\n \"metallica\",\n \"consequently\",\n \"appointments\",\n \"narrative\",\n \"academics\",\n \"quantitative\",\n \"screensavers\",\n \"subdivision\",\n \"distinction\",\n \"livestock\",\n \"exemption\",\n \"sustainability\",\n \"formatting\",\n \"nutritional\",\n \"nicaragua\",\n \"affiliation\",\n \"relatives\",\n \"satisfactory\",\n \"revolutionary\",\n \"bracelets\",\n \"telephony\",\n \"breathing\",\n \"thickness\",\n \"adjustments\",\n \"graphical\",\n \"discussing\",\n \"aerospace\",\n \"meaningful\",\n \"maintains\",\n \"shortcuts\",\n \"voyeurweb\",\n \"extending\",\n \"specifies\",\n \"accreditation\",\n \"blackberry\",\n \"meditation\",\n \"microphone\",\n \"macedonia\",\n \"combining\",\n \"instrumental\",\n \"organizing\",\n \"moderators\",\n \"kazakhstan\",\n \"standings\",\n \"partition\",\n \"invisible\",\n \"translations\",\n \"commodity\",\n \"kilometers\",\n \"thanksgiving\",\n \"guarantees\",\n \"indication\",\n \"congratulations\",\n \"cigarettes\",\n \"controllers\",\n \"consultancy\",\n \"conventions\",\n \"coordinates\",\n \"responding\",\n \"physically\",\n \"stakeholders\",\n \"hydrocodone\",\n \"consecutive\",\n \"attempting\",\n \"representations\",\n \"competing\",\n \"peninsula\",\n \"accurately\",\n \"considers\",\n \"ministries\",\n \"vacancies\",\n \"parliamentary\",\n \"acknowledge\",\n \"thoroughly\",\n \"nottingham\",\n \"identifies\",\n \"questionnaire\",\n \"qualification\",\n \"modelling\",\n \"miniature\",\n \"interstate\",\n \"consequence\",\n \"systematic\",\n \"perceived\",\n \"madagascar\",\n \"presenting\",\n \"troubleshooting\",\n \"uzbekistan\",\n \"centuries\",\n \"magnitude\",\n \"richardson\",\n \"fragrances\",\n \"vocabulary\",\n \"earthquake\",\n \"fundraising\",\n \"geological\",\n \"assessing\",\n \"introduces\",\n \"webmasters\",\n \"computational\",\n \"acdbentity\",\n \"participated\",\n \"handhelds\",\n \"answering\",\n \"impressed\",\n \"conspiracy\",\n \"organizer\",\n \"combinations\",\n \"preceding\",\n \"cumulative\",\n \"amplifier\",\n \"arbitrary\",\n \"prominent\",\n \"lexington\",\n \"contacted\",\n \"recorders\",\n \"occasional\",\n \"innovations\",\n \"postcards\",\n \"reviewing\",\n \"explicitly\",\n \"transsexual\",\n \"citizenship\",\n \"informative\",\n \"girlfriend\",\n \"bloomberg\",\n \"hierarchy\",\n \"influenced\",\n \"abandoned\",\n \"complement\",\n \"mauritius\",\n \"checklist\",\n \"requesting\",\n \"lauderdale\",\n \"scenarios\",\n \"extraction\",\n \"elevation\",\n \"utilization\",\n \"beverages\",\n \"calibration\",\n \"efficiently\",\n \"entertaining\",\n \"prerequisite\",\n \"hypothesis\",\n \"medicines\",\n \"regression\",\n \"enhancements\",\n \"renewable\",\n \"intersection\",\n \"passwords\",\n \"consistency\",\n \"collectors\",\n \"azerbaijan\",\n \"astrology\",\n \"occurring\",\n \"supplemental\",\n \"travelling\",\n \"induction\",\n \"precisely\",\n \"spreading\",\n \"provinces\",\n \"widespread\",\n \"incidence\",\n \"incidents\",\n \"enhancing\",\n \"interference\",\n \"palestine\",\n \"listprice\",\n \"atmospheric\",\n \"knowledgestorm\",\n \"referenced\",\n \"publicity\",\n \"proposition\",\n \"allowance\",\n \"designation\",\n \"duplicate\",\n \"criterion\",\n \"civilization\",\n \"vietnamese\",\n \"tremendous\",\n \"corrected\",\n \"encountered\",\n \"internationally\",\n \"surrounded\",\n \"creatures\",\n \"commented\",\n \"accomplish\",\n \"vegetarian\",\n \"newfoundland\",\n \"investigated\",\n \"ambassador\",\n \"stephanie\",\n \"contacting\",\n \"vegetation\",\n \"findarticles\",\n \"specially\",\n \"infectious\",\n \"continuity\",\n \"phenomenon\",\n \"conscious\",\n \"referrals\",\n \"differently\",\n \"integrating\",\n \"revisions\",\n \"reasoning\",\n \"charitable\",\n \"annotated\",\n \"convinced\",\n \"burlington\",\n \"replacing\",\n \"researcher\",\n \"watershed\",\n \"occupations\",\n \"acknowledged\",\n \"equilibrium\",\n \"characterized\",\n \"privilege\",\n \"qualifying\",\n \"estimation\",\n \"pediatric\",\n \"techrepublic\",\n \"institutes\",\n \"brochures\",\n \"traveller\",\n \"appropriations\",\n \"suspected\",\n \"benchmark\",\n \"beginners\",\n \"instructors\",\n \"highlighted\",\n \"stationery\",\n \"unauthorized\",\n \"competent\",\n \"contributor\",\n \"demonstrates\",\n \"gradually\",\n \"desirable\",\n \"journalist\",\n \"afterwards\",\n \"religions\",\n \"explosion\",\n \"signatures\",\n \"disciplines\",\n \"daughters\",\n \"conversations\",\n \"simplified\",\n \"motherboard\",\n \"bibliographic\",\n \"champagne\",\n \"deviation\",\n \"superintendent\",\n \"housewives\",\n \"influences\",\n \"inspections\",\n \"irrigation\",\n \"hydraulic\",\n \"robertson\",\n \"penetration\",\n \"conviction\",\n \"omissions\",\n \"retrieval\",\n \"qualities\",\n \"prototype\",\n \"importantly\",\n \"apparatus\",\n \"explaining\",\n \"nomination\",\n \"empirical\",\n \"dependence\",\n \"sexuality\",\n \"polyester\",\n \"commitments\",\n \"suggesting\",\n \"remainder\",\n \"privileges\",\n \"televisions\",\n \"specializing\",\n \"commodities\",\n \"motorcycles\",\n \"concentrate\",\n \"reproductive\",\n \"molecules\",\n \"refrigerator\",\n \"intervals\",\n \"sentences\",\n \"exclusion\",\n \"workstation\",\n \"holocaust\",\n \"receivers\",\n \"disposition\",\n \"navigator\",\n \"investigators\",\n \"marijuana\",\n \"cathedral\",\n \"fairfield\",\n \"fascinating\",\n \"landscapes\",\n \"lafayette\",\n \"computation\",\n \"cardiovascular\",\n \"salvation\",\n \"predictions\",\n \"accompanying\",\n \"selective\",\n \"arbitration\",\n \"configuring\",\n \"editorials\",\n \"sacrifice\",\n \"removable\",\n \"convergence\",\n \"gibraltar\",\n \"anthropology\",\n \"malpractice\",\n \"reporters\",\n \"necessity\",\n \"rendering\",\n \"hepatitis\",\n \"nationally\",\n \"waterproof\",\n \"specialties\",\n \"humanitarian\",\n \"invitations\",\n \"functioning\",\n \"economies\",\n \"alexandria\",\n \"bacterial\",\n \"undertake\",\n \"continuously\",\n \"achievements\",\n \"convertible\",\n \"secretariat\",\n \"paragraphs\",\n \"adolescent\",\n \"nominations\",\n \"cancelled\",\n \"introductory\",\n \"reservoir\",\n \"occurrence\",\n \"worcester\",\n \"demographic\",\n \"disciplinary\",\n \"respected\",\n \"portraits\",\n \"interpreted\",\n \"evaluations\",\n \"elimination\",\n \"hypothetical\",\n \"immigrants\",\n \"complimentary\",\n \"helicopter\",\n \"performer\",\n \"commissions\",\n \"powerseller\",\n \"graduated\",\n \"surprising\",\n \"unnecessary\",\n \"dramatically\",\n \"yugoslavia\",\n \"characterization\",\n \"likelihood\",\n \"fundamentals\",\n \"contamination\",\n \"endangered\",\n \"compromise\",\n \"expiration\",\n \"namespace\",\n \"peripheral\",\n \"negotiation\",\n \"opponents\",\n \"nominated\",\n \"confidentiality\",\n \"electoral\",\n \"changelog\",\n \"alternatively\",\n \"greensboro\",\n \"controversial\",\n \"recovered\",\n \"upgrading\",\n \"frontpage\",\n \"demanding\",\n \"defensive\",\n \"forbidden\",\n \"programmers\",\n \"monitored\",\n \"installations\",\n \"deutschland\",\n \"practitioner\",\n \"motivated\",\n \"smithsonian\",\n \"examining\",\n \"revelation\",\n \"delegation\",\n \"dictionaries\",\n \"greenhouse\",\n \"transparency\",\n \"currencies\",\n \"survivors\",\n \"positioning\",\n \"descending\",\n \"temporarily\",\n \"frequencies\",\n \"reflections\",\n \"municipality\",\n \"detective\",\n \"experiencing\",\n \"fireplace\",\n \"endorsement\",\n \"psychiatry\",\n \"persistent\",\n \"summaries\",\n \"looksmart\",\n \"magnificent\",\n \"colleague\",\n \"adaptation\",\n \"paintball\",\n \"enclosure\",\n \"supervisors\",\n \"westminster\",\n \"distances\",\n \"absorption\",\n \"treasures\",\n \"transcripts\",\n \"disappointed\",\n \"continually\",\n \"communist\",\n \"collectible\",\n \"entrepreneurs\",\n \"creations\",\n \"acquisitions\",\n \"biodiversity\",\n \"excitement\",\n \"presently\",\n \"mysterious\",\n \"librarian\",\n \"subsidiaries\",\n \"stockholm\",\n \"indonesian\",\n \"therapist\",\n \"promising\",\n \"relaxation\",\n \"thereafter\",\n \"commissioners\",\n \"forwarding\",\n \"nightmare\",\n \"reductions\",\n \"southampton\",\n \"organisms\",\n \"telescope\",\n \"portsmouth\",\n \"advancement\",\n \"harassment\",\n \"generators\",\n \"generates\",\n \"replication\",\n \"inexpensive\",\n \"receptors\",\n \"interventions\",\n \"huntington\",\n \"internship\",\n \"aluminium\",\n \"snowboard\",\n \"beastality\",\n \"evanescence\",\n \"coordinated\",\n \"shipments\",\n \"antarctica\",\n \"chancellor\",\n \"controversy\",\n \"legendary\",\n \"beautifully\",\n \"antibodies\",\n \"examinations\",\n \"immunology\",\n \"departmental\",\n \"terminology\",\n \"gentleman\",\n \"reproduce\",\n \"convicted\",\n \"roommates\",\n \"threatening\",\n \"spokesman\",\n \"activists\",\n \"frankfurt\",\n \"encourages\",\n \"assembled\",\n \"restructuring\",\n \"terminals\",\n \"simulations\",\n \"sufficiently\",\n \"conditional\",\n \"crossword\",\n \"conceptual\",\n \"liechtenstein\",\n \"translator\",\n \"automobiles\",\n \"continent\",\n \"longitude\",\n \"challenged\",\n \"telecharger\",\n \"insertion\",\n \"instrumentation\",\n \"constraint\",\n \"groundwater\",\n \"strengthening\",\n \"insulation\",\n \"infringement\",\n \"subjective\",\n \"swaziland\",\n \"varieties\",\n \"mediawiki\",\n \"configurations\",\n];\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/code-action-menu-plugin.tsx", "content": "import { type JSX, useEffect, useRef, useState } from \"react\";\n\nimport { createPortal } from \"react-dom\";\n\nimport { $isCodeNode, CodeNode } from \"@lexical/code\";\nimport { getCodeLanguageOptions } from \"@lexical/code-shiki\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { $getNearestNodeFromDOMNode, isHTMLElement } from \"lexical\";\n\nimport { useDebounce } from \"@/components/editor/editor-hooks/use-debounce\";\nimport { CopyButton } from \"@/components/editor/editor-ui/code-button\";\n\nconst CODE_PADDING = 8;\n\ninterface Position {\n top: string;\n right: string;\n}\n\nfunction CodeActionMenuContainer({\n anchorElem,\n}: {\n anchorElem: HTMLElement;\n}): JSX.Element {\n const [editor] = useLexicalComposerContext();\n\n const [lang, setLang] = useState(\"\");\n const [isShown, setShown] = useState(false);\n const [shouldListenMouseMove, setShouldListenMouseMove] =\n useState(false);\n const [position, setPosition] = useState({\n right: \"0\",\n top: \"0\",\n });\n const codeSetRef = useRef>(new Set());\n const codeDOMNodeRef = useRef(null);\n\n function getCodeDOMNode(): HTMLElement | null {\n return codeDOMNodeRef.current;\n }\n\n const debouncedOnMouseMove = useDebounce(\n (event: MouseEvent) => {\n const { codeDOMNode, isOutside } = getMouseInfo(event);\n if (isOutside) {\n setShown(false);\n return;\n }\n\n if (!codeDOMNode) {\n return;\n }\n\n codeDOMNodeRef.current = codeDOMNode;\n\n let codeNode: CodeNode | null = null;\n let _lang = \"\";\n\n editor.update(() => {\n const maybeCodeNode = $getNearestNodeFromDOMNode(codeDOMNode);\n\n if ($isCodeNode(maybeCodeNode)) {\n codeNode = maybeCodeNode;\n _lang = codeNode.getLanguage() || \"\";\n }\n });\n\n if (codeNode) {\n const { y: editorElemY, right: editorElemRight } =\n anchorElem.getBoundingClientRect();\n const { y, right } = codeDOMNode.getBoundingClientRect();\n setLang(_lang);\n setShown(true);\n setPosition({\n right: `${editorElemRight - right + CODE_PADDING}px`,\n top: `${y - editorElemY}px`,\n });\n }\n },\n 50,\n 1000,\n );\n\n useEffect(() => {\n if (!shouldListenMouseMove) {\n return;\n }\n\n document.addEventListener(\"mousemove\", debouncedOnMouseMove);\n\n return () => {\n setShown(false);\n debouncedOnMouseMove.cancel();\n document.removeEventListener(\"mousemove\", debouncedOnMouseMove);\n };\n }, [shouldListenMouseMove, debouncedOnMouseMove]);\n\n useEffect(() => {\n return editor.registerMutationListener(\n CodeNode,\n (mutations) => {\n editor.getEditorState().read(() => {\n for (const [key, type] of mutations) {\n switch (type) {\n case \"created\":\n codeSetRef.current.add(key);\n break;\n\n case \"destroyed\":\n codeSetRef.current.delete(key);\n break;\n\n default:\n break;\n }\n }\n });\n setShouldListenMouseMove(codeSetRef.current.size > 0);\n },\n { skipInitialization: false },\n );\n }, [editor]);\n\n const codeFriendlyName = getCodeLanguageOptions().find(\n ([key]) => key === lang,\n )?.[1];\n\n return (\n <>\n {isShown ? (\n \n {codeFriendlyName}\n \n \n ) : null}\n >\n );\n}\n\nfunction getMouseInfo(event: MouseEvent): {\n codeDOMNode: HTMLElement | null;\n isOutside: boolean;\n} {\n const target = event.target;\n\n if (isHTMLElement(target)) {\n const codeDOMNode = target.closest(\n \"code.PlaygroundEditorTheme__code\",\n );\n const isOutside = !(\n codeDOMNode ||\n target.closest(\"div.code-action-menu-container\")\n );\n\n return { codeDOMNode, isOutside };\n } else {\n return { codeDOMNode: null, isOutside: true };\n }\n}\n\nexport function CodeActionMenuPlugin({\n anchorElem = document.body,\n}: {\n anchorElem: HTMLElement | null;\n}): React.ReactPortal | null {\n if (!anchorElem) {\n return null;\n }\n\n return createPortal(\n ,\n anchorElem,\n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/code-highlight-plugin.tsx", "content": "import type { JSX } from \"react\";\nimport { useEffect } from \"react\";\n\nimport { registerCodeHighlighting } from \"@lexical/code\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\n\nexport function CodeHighlightPlugin(): JSX.Element | null {\n const [editor] = useLexicalComposerContext();\n\n useEffect(() => {\n return registerCodeHighlighting(editor);\n }, [editor]);\n\n return null;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/component-picker-menu-plugin.tsx", "content": "import {\n type JSX,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport { createPortal } from \"react-dom\";\n\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { useBasicTypeaheadTriggerMatch } from \"@lexical/react/LexicalTypeaheadMenuPlugin\";\nimport { LexicalTypeaheadMenuPlugin } from \"@lexical/react/LexicalTypeaheadMenuPlugin\";\nimport { TextNode } from \"lexical\";\n\nimport { useEditorModal } from \"@/components/editor/editor-hooks/use-modal\";\nimport {\n Command,\n CommandGroup,\n CommandItem,\n CommandList,\n} from \"@/components/ui/command\";\nimport { cn } from \"@/lib/utils\";\n\nimport { ComponentPickerOption } from \"./picker/component-picker-option\";\n\n// const LexicalTypeaheadMenuPlugin = lazy(\n// () =>\n// import(\"@lexical/react/LexicalTypeaheadMenuPlugin\").then(\n// (mod) => mod.LexicalTypeaheadMenuPlugin\n// ),\n// )\n\nfunction ComponentPickerMenu({\n options,\n selectedIndex,\n selectOptionAndCleanUp,\n setHighlightedIndex,\n}: {\n options: Array;\n selectedIndex: number | null;\n selectOptionAndCleanUp: (option: ComponentPickerOption) => void;\n setHighlightedIndex: (index: number) => void;\n}) {\n const itemRefs = useRef<(HTMLDivElement | null)[]>([]);\n\n useEffect(() => {\n if (selectedIndex !== null && itemRefs.current[selectedIndex]) {\n itemRefs.current[selectedIndex]?.scrollIntoView({\n block: \"nearest\",\n behavior: \"auto\",\n });\n }\n }, [selectedIndex]);\n\n return (\n \n {\n if (e.key === \"ArrowUp\") {\n e.preventDefault();\n setHighlightedIndex(\n selectedIndex !== null\n ? (selectedIndex - 1 + options.length) % options.length\n : options.length - 1,\n );\n } else if (e.key === \"ArrowDown\") {\n e.preventDefault();\n setHighlightedIndex(\n selectedIndex !== null ? (selectedIndex + 1) % options.length : 0,\n );\n }\n }}\n >\n \n \n {options.map((option, index) => (\n {\n itemRefs.current[index] = el;\n }}\n value={option.title}\n onSelect={() => {\n selectOptionAndCleanUp(option);\n }}\n className={cn(\n \"flex items-center gap-2\",\n selectedIndex === index\n ? \"bg-accent text-accent-foreground\"\n : \"bg-transparent!\",\n )}\n >\n {option.icon}\n {option.title}\n \n ))}\n \n \n \n \n );\n}\n\nexport function ComponentPickerMenuPlugin({\n baseOptions = [],\n dynamicOptionsFn,\n}: {\n baseOptions?: Array;\n dynamicOptionsFn?: ({\n queryString,\n }: {\n queryString: string;\n }) => Array;\n}): JSX.Element {\n const [editor] = useLexicalComposerContext();\n const [modal, showModal] = useEditorModal();\n const [queryString, setQueryString] = useState(null);\n\n const checkForTriggerMatch = useBasicTypeaheadTriggerMatch(\"/\", {\n minLength: 0,\n });\n\n const options = useMemo(() => {\n if (!queryString) {\n return baseOptions;\n }\n\n const regex = new RegExp(queryString, \"i\");\n\n return [\n ...(dynamicOptionsFn?.({ queryString }) || []),\n ...baseOptions.filter(\n (option) =>\n regex.test(option.title) ||\n option.keywords.some((keyword) => regex.test(keyword)),\n ),\n ];\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [editor, queryString, showModal]);\n\n const onSelectOption = useCallback(\n (\n selectedOption: ComponentPickerOption,\n nodeToRemove: TextNode | null,\n closeMenu: () => void,\n matchingString: string,\n ) => {\n editor.update(() => {\n nodeToRemove?.remove();\n selectedOption.onSelect(matchingString, editor, showModal);\n closeMenu();\n });\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [editor],\n );\n\n return (\n <>\n {modal}\n {\n return anchorElementRef.current && options.length\n ? createPortal(\n ,\n anchorElementRef.current,\n )\n : null;\n }}\n />\n >\n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/context-menu-plugin.tsx", "content": "import type { JSX } from \"react\";\nimport { useMemo } from \"react\";\n\nimport { $isLinkNode, TOGGLE_LINK_COMMAND } from \"@lexical/link\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport {\n NodeContextMenuOption,\n NodeContextMenuPlugin,\n NodeContextMenuSeparator,\n} from \"@lexical/react/LexicalNodeContextMenuPlugin\";\nimport {\n $getSelection,\n $isDecoratorNode,\n $isNodeSelection,\n $isRangeSelection,\n COPY_COMMAND,\n CUT_COMMAND,\n type LexicalNode,\n PASTE_COMMAND,\n} from \"lexical\";\n\nimport {\n Clipboard,\n ClipboardType,\n Copy,\n Link2Off,\n Scissors,\n Trash2,\n} from \"lucide-react\";\n\nexport function ContextMenuPlugin(): JSX.Element {\n const [editor] = useLexicalComposerContext();\n\n const items = useMemo(() => {\n return [\n new NodeContextMenuOption(`Remove Link`, {\n $onSelect: () => {\n editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);\n },\n $showOn: (node: LexicalNode) => $isLinkNode(node.getParent()),\n disabled: false,\n icon: ,\n }),\n new NodeContextMenuSeparator({\n $showOn: (node: LexicalNode) => $isLinkNode(node.getParent()),\n }),\n new NodeContextMenuOption(`Cut`, {\n $onSelect: () => {\n editor.dispatchCommand(CUT_COMMAND, null);\n },\n disabled: false,\n icon: ,\n }),\n new NodeContextMenuOption(`Copy`, {\n $onSelect: () => {\n editor.dispatchCommand(COPY_COMMAND, null);\n },\n disabled: false,\n icon: ,\n }),\n new NodeContextMenuOption(`Paste`, {\n $onSelect: () => {\n navigator.clipboard.read().then(async function (..._args) {\n const data = new DataTransfer();\n\n const readClipboardItems = await navigator.clipboard.read();\n const item = readClipboardItems[0];\n\n const permission = await navigator.permissions.query({\n // @ts-expect-error These types are incorrect.\n name: \"clipboard-read\",\n });\n if (permission.state === \"denied\") {\n alert(\"Not allowed to paste from clipboard.\");\n return;\n }\n\n for (const type of item.types) {\n const dataString = await (await item.getType(type)).text();\n data.setData(type, dataString);\n }\n\n const event = new ClipboardEvent(\"paste\", {\n clipboardData: data,\n });\n\n editor.dispatchCommand(PASTE_COMMAND, event);\n });\n },\n disabled: false,\n icon: ,\n }),\n new NodeContextMenuOption(`Paste as Plain Text`, {\n $onSelect: () => {\n navigator.clipboard.read().then(async function (..._args) {\n const permission = await navigator.permissions.query({\n // @ts-expect-error These types are incorrect.\n name: \"clipboard-read\",\n });\n\n if (permission.state === \"denied\") {\n alert(\"Not allowed to paste from clipboard.\");\n return;\n }\n\n const data = new DataTransfer();\n const clipboardText = await navigator.clipboard.readText();\n data.setData(\"text/plain\", clipboardText);\n\n const event = new ClipboardEvent(\"paste\", {\n clipboardData: data,\n });\n editor.dispatchCommand(PASTE_COMMAND, event);\n });\n },\n disabled: false,\n icon: ,\n }),\n new NodeContextMenuSeparator(),\n new NodeContextMenuOption(`Delete Node`, {\n $onSelect: () => {\n const selection = $getSelection();\n if ($isRangeSelection(selection)) {\n const currentNode = selection.anchor.getNode();\n const ancestorNodeWithRootAsParent = currentNode\n .getParents()\n .at(-2);\n\n ancestorNodeWithRootAsParent?.remove();\n } else if ($isNodeSelection(selection)) {\n const selectedNodes = selection.getNodes();\n selectedNodes.forEach((node) => {\n if ($isDecoratorNode(node)) {\n node.remove();\n }\n });\n }\n },\n disabled: false,\n icon: ,\n }),\n ];\n }, [editor]);\n\n return (\n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/draggable-block-plugin.tsx", "content": "import {\n type JSX,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport * as ReactDOM from \"react-dom\";\n\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { DraggableBlockPlugin_EXPERIMENTAL } from \"@lexical/react/LexicalDraggableBlockPlugin\";\nimport {\n $createParagraphNode,\n $createTextNode,\n $getNearestNodeFromDOMNode,\n $getNodeByKey,\n $isParagraphNode,\n $isTextNode,\n type NodeKey,\n} from \"lexical\";\n\nimport { GripVerticalIcon, PlusIcon } from \"lucide-react\";\n\nimport { useEditorModal } from \"@/components/editor/editor-hooks/use-modal\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from \"@/components/ui/command\";\n\nimport { ComponentPickerOption } from \"./picker/component-picker-option\";\n\nconst DRAGGABLE_BLOCK_MENU_CLASSNAME = \"draggable-block-menu\";\n\ntype PickerState = {\n insertBefore: boolean;\n targetNodeKey: NodeKey;\n};\n\nfunction isOnMenu(element: HTMLElement): boolean {\n return !!element.closest(`.${DRAGGABLE_BLOCK_MENU_CLASSNAME}`);\n}\n\nexport function DraggableBlockPlugin({\n anchorElem,\n baseOptions = [],\n dynamicOptionsFn,\n}: {\n anchorElem: HTMLElement | null;\n baseOptions?: Array;\n dynamicOptionsFn?: ({\n queryString,\n }: {\n queryString: string;\n }) => Array;\n}): JSX.Element | null {\n const [editor] = useLexicalComposerContext();\n const [modal, showModal] = useEditorModal();\n const menuRef = useRef(null);\n const pickerRef = useRef(null);\n const targetLineRef = useRef(null);\n const [draggableElement, setDraggableElement] = useState(\n null,\n );\n const [pickerState, setPickerState] = useState(null);\n const [isPickerOpen, setIsPickerOpen] = useState(false);\n const [queryString, setQueryString] = useState(\"\");\n const [highlightedIndex, setHighlightedIndex] = useState(0);\n const [pickerPosition, setPickerPosition] = useState<{\n left: number;\n top: number;\n } | null>(null);\n\n const options = useMemo(() => {\n if (!queryString) {\n return baseOptions;\n }\n const regex = new RegExp(queryString, \"i\");\n return [\n ...(dynamicOptionsFn?.({ queryString }) ?? []),\n ...baseOptions.filter(\n (option) =>\n regex.test(option.title) ||\n option.keywords.some((keyword) => regex.test(keyword)),\n ),\n ];\n }, [baseOptions, dynamicOptionsFn, queryString]);\n\n useEffect(() => {\n if (!isPickerOpen) return;\n setHighlightedIndex((current) =>\n Math.min(current, Math.max(options.length - 1, 0)),\n );\n }, [isPickerOpen, options.length]);\n\n useEffect(() => {\n if (!isPickerOpen) return;\n const handleClickOutside = (event: MouseEvent) => {\n const target = event.target as Node | null;\n if (\n (pickerRef.current && pickerRef.current.contains(target)) ||\n (menuRef.current && menuRef.current.contains(target))\n ) {\n return;\n }\n setIsPickerOpen(false);\n setPickerState(null);\n };\n document.addEventListener(\"mousedown\", handleClickOutside);\n return () => document.removeEventListener(\"mousedown\", handleClickOutside);\n }, [isPickerOpen]);\n\n const selectOption = useCallback(\n (option: ComponentPickerOption) => {\n if (!pickerState) {\n setIsPickerOpen(false);\n return;\n }\n setIsPickerOpen(false);\n editor.update(() => {\n const node = $getNodeByKey(pickerState.targetNodeKey);\n if (!node) return;\n const placeholder = $createParagraphNode();\n const textNode = $createTextNode(\"\");\n placeholder.append(textNode);\n if (pickerState.insertBefore) {\n node.insertBefore(placeholder);\n } else {\n node.insertAfter(placeholder);\n }\n textNode.select();\n option.onSelect(queryString, editor, showModal);\n const latestPlaceholder = placeholder.getLatest();\n if ($isParagraphNode(latestPlaceholder)) {\n const onlyChild = latestPlaceholder.getFirstChild();\n if (\n $isTextNode(onlyChild) &&\n onlyChild.getTextContent().length === 0 &&\n latestPlaceholder.getChildrenSize() === 1\n ) {\n latestPlaceholder.remove();\n }\n }\n });\n },\n [editor, pickerState, queryString, showModal],\n );\n\n useEffect(() => {\n if (!isPickerOpen) return;\n const handleKeyDown = (event: KeyboardEvent) => {\n if (!options.length) return;\n if (event.key === \"ArrowDown\") {\n event.preventDefault();\n setHighlightedIndex((i) => (i + 1 >= options.length ? 0 : i + 1));\n } else if (event.key === \"ArrowUp\") {\n event.preventDefault();\n setHighlightedIndex((i) => (i - 1 < 0 ? options.length - 1 : i - 1));\n } else if (event.key === \"Enter\") {\n event.preventDefault();\n const option = options[highlightedIndex];\n if (option) selectOption(option);\n } else if (event.key === \"Escape\") {\n event.preventDefault();\n setIsPickerOpen(false);\n setPickerState(null);\n }\n };\n window.addEventListener(\"keydown\", handleKeyDown);\n return () => window.removeEventListener(\"keydown\", handleKeyDown);\n }, [highlightedIndex, isPickerOpen, options, selectOption]);\n\n function openComponentPicker(e: React.MouseEvent) {\n if (!draggableElement || !editor) return;\n\n let targetNodeKey: NodeKey | null = null;\n editor.read(() => {\n const resolvedNode = $getNearestNodeFromDOMNode(draggableElement);\n if (resolvedNode) {\n targetNodeKey = resolvedNode.getKey();\n }\n });\n\n if (!targetNodeKey) return;\n\n const insertBefore = e.altKey || e.ctrlKey;\n const rect = menuRef.current?.getBoundingClientRect();\n setPickerPosition(\n rect\n ? {\n left: rect.left + rect.width + window.scrollX + 8,\n top: rect.top + window.scrollY,\n }\n : null,\n );\n setPickerState({ insertBefore, targetNodeKey });\n setQueryString(\"\");\n setHighlightedIndex(0);\n setIsPickerOpen(true);\n }\n\n if (!anchorElem) return null;\n\n return (\n <>\n {modal}\n {isPickerOpen && pickerPosition\n ? ReactDOM.createPortal(\n \n \n \n \n No results found.\n \n {options.map((option, i) => (\n {\n setHighlightedIndex(i);\n selectOption(option);\n }}\n onMouseEnter={() => setHighlightedIndex(i)}\n className={`flex items-center gap-2 ${\n highlightedIndex === i\n ? \"bg-accent\"\n : \"!bg-transparent\"\n }`}\n >\n {option.icon}\n {option.title}\n \n ))}\n \n \n \n ,\n document.body,\n )\n : null}\n }\n targetLineRef={targetLineRef as React.RefObject}\n menuComponent={\n \n \n \n \n \n \n \n \n }\n targetLineComponent={\n \n }\n isOnMenu={isOnMenu}\n onElementChanged={setDraggableElement}\n />\n >\n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/embeds/auto-embed-plugin.tsx", "content": "import { type JSX, useMemo, useState } from \"react\";\n\nimport {\n AutoEmbedOption,\n type EmbedConfig,\n type EmbedMatchResult,\n LexicalAutoEmbedPlugin,\n URL_MATCHER,\n} from \"@lexical/react/LexicalAutoEmbedPlugin\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport type { LexicalEditor } from \"lexical\";\n\nimport { Popover as PopoverPrimitive } from \"radix-ui\";\n\n// import { TwitterIcon, YoutubeIcon } from \"lucide-react\"\n\nimport { useEditorModal } from \"@/components/editor/editor-hooks/use-modal\";\nimport { INSERT_TWEET_COMMAND } from \"@/components/editor/plugins/embeds/twitter-plugin\";\nimport { INSERT_YOUTUBE_COMMAND } from \"@/components/editor/plugins/embeds/youtube-plugin\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n Command,\n CommandGroup,\n CommandItem,\n CommandList,\n} from \"@/components/ui/command\";\nimport { DialogFooter } from \"@/components/ui/dialog\";\nimport { Input } from \"@/components/ui/input\";\nimport {\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@/components/ui/popover\";\n\nconst YoutubeIcon = (\n \n \n \n \n);\n\nconst TwitterIcon = (\n \n \n \n);\n\nexport interface CustomEmbedConfig extends EmbedConfig {\n // Human readable name of the embeded content e.g. Tweet or Google Map.\n contentName: string;\n\n // Icon for display.\n icon?: JSX.Element;\n\n // An example of a matching url https://twitter.com/jack/status/20\n exampleUrl: string;\n\n // For extra searching.\n keywords: Array;\n\n // Embed a Project.\n description?: string;\n}\n\nexport const YoutubeEmbedConfig: CustomEmbedConfig = {\n contentName: \"Youtube Video\",\n\n exampleUrl: \"https://www.youtube.com/watch?v=jNQXAC9IVRw\",\n\n // Icon for display.\n icon: YoutubeIcon,\n\n insertNode: (editor: LexicalEditor, result: EmbedMatchResult) => {\n editor.dispatchCommand(INSERT_YOUTUBE_COMMAND, result.id);\n },\n\n keywords: [\"youtube\", \"video\"],\n\n // Determine if a given URL is a match and return url data.\n parseUrl: async (url: string) => {\n const match =\n /^.*(youtu\\.be\\/|v\\/|u\\/\\w\\/|embed\\/|watch\\?v=|&v=)([^#&?]*).*/.exec(url);\n\n const id = match ? (match?.[2].length === 11 ? match[2] : null) : null;\n\n if (id != null) {\n return {\n id,\n url,\n };\n }\n\n return null;\n },\n\n type: \"youtube-video\",\n};\n\nexport const TwitterEmbedConfig: CustomEmbedConfig = {\n // e.g. Tweet or Google Map.\n contentName: \"Tweet\",\n\n exampleUrl: \"https://twitter.com/jack/status/20\",\n\n // Icon for display.\n icon: TwitterIcon,\n\n // Create the Lexical embed node from the url data.\n insertNode: (editor: LexicalEditor, result: EmbedMatchResult) => {\n editor.dispatchCommand(INSERT_TWEET_COMMAND, result.id);\n },\n\n // For extra searching.\n keywords: [\"tweet\", \"twitter\"],\n\n // Determine if a given URL is a match and return url data.\n parseUrl: (text: string) => {\n const match =\n /^https:\\/\\/(twitter|x)\\.com\\/(#!\\/)?(\\w+)\\/status(es)*\\/(\\d+)/.exec(\n text,\n );\n\n if (match != null) {\n return {\n id: match[5],\n url: match[1],\n };\n }\n\n return null;\n },\n\n type: \"tweet\",\n};\n\nexport const EmbedConfigs = [TwitterEmbedConfig, YoutubeEmbedConfig];\n\nconst debounce = (callback: (text: string) => void, delay: number) => {\n let timeoutId: number;\n return (text: string) => {\n window.clearTimeout(timeoutId);\n timeoutId = window.setTimeout(() => {\n callback(text);\n }, delay);\n };\n};\n\nexport function AutoEmbedDialog({\n embedConfig,\n onClose,\n}: {\n embedConfig: CustomEmbedConfig;\n onClose: () => void;\n}): JSX.Element {\n const [text, setText] = useState(\"\");\n const [editor] = useLexicalComposerContext();\n const [embedResult, setEmbedResult] = useState(null);\n\n const validateText = useMemo(\n () =>\n debounce((inputText: string) => {\n const urlMatch = URL_MATCHER.exec(inputText);\n if (embedConfig != null && inputText != null && urlMatch != null) {\n Promise.resolve(embedConfig.parseUrl(inputText)).then(\n (parseResult) => {\n setEmbedResult(parseResult);\n },\n );\n } else if (embedResult != null) {\n setEmbedResult(null);\n }\n }, 200),\n [embedConfig, embedResult],\n );\n\n const onClick = () => {\n if (embedResult != null) {\n embedConfig.insertNode(editor, embedResult);\n onClose();\n }\n };\n\n return (\n \n \n {\n const { value } = e.target;\n setText(value);\n validateText(value);\n }}\n />\n \n \n Embed\n \n \n \n \n );\n}\n\nexport function AutoEmbedPlugin(): JSX.Element {\n const [modal, showModal] = useEditorModal();\n\n const openEmbedModal = (embedConfig: CustomEmbedConfig) => {\n showModal(`Embed ${embedConfig.contentName}`, (onClose) => (\n \n ));\n };\n\n const getMenuOptions = (\n activeEmbedConfig: CustomEmbedConfig,\n embedFn: () => void,\n dismissFn: () => void,\n ) => {\n return [\n new AutoEmbedOption(\"Dismiss\", {\n onSelect: dismissFn,\n }),\n new AutoEmbedOption(`Embed ${activeEmbedConfig.contentName}`, {\n onSelect: embedFn,\n }),\n ];\n };\n\n return (\n <>\n {modal}\n \n embedConfigs={EmbedConfigs}\n onOpenEmbedModalForConfig={openEmbedModal}\n getMenuOptions={getMenuOptions}\n menuRenderFn={(\n anchorElementRef,\n {\n selectedIndex: _selectedIndex,\n options,\n selectOptionAndCleanUp,\n setHighlightedIndex: _setHighlightedIndex,\n },\n ) => {\n return anchorElementRef.current ? (\n \n \n \n \n \n \n \n \n {options.map((option, _i: number) => (\n {\n selectOptionAndCleanUp(option);\n }}\n className=\"flex items-center gap-2\"\n >\n {option.title}\n \n ))}\n \n \n \n \n \n \n \n ) : null;\n }}\n />\n >\n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/embeds/twitter-plugin.tsx", "content": "import { type JSX, useEffect } from \"react\";\n\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { $insertNodeToNearestRoot } from \"@lexical/utils\";\nimport {\n COMMAND_PRIORITY_EDITOR,\n type LexicalCommand,\n createCommand,\n} from \"lexical\";\n\nimport {\n $createTweetNode,\n TweetNode,\n} from \"@/components/editor/nodes/embeds/tweet-node\";\n\nexport const INSERT_TWEET_COMMAND: LexicalCommand = createCommand(\n \"INSERT_TWEET_COMMAND\",\n);\n\nexport function TwitterPlugin(): JSX.Element | null {\n const [editor] = useLexicalComposerContext();\n\n useEffect(() => {\n if (!editor.hasNodes([TweetNode])) {\n throw new Error(\"TwitterPlugin: TweetNode not registered on editor\");\n }\n\n return editor.registerCommand(\n INSERT_TWEET_COMMAND,\n (payload) => {\n const tweetNode = $createTweetNode(payload);\n $insertNodeToNearestRoot(tweetNode);\n\n return true;\n },\n COMMAND_PRIORITY_EDITOR,\n );\n }, [editor]);\n\n return null;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/embeds/youtube-plugin.tsx", "content": "import { type JSX, useEffect } from \"react\";\n\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { $insertNodeToNearestRoot } from \"@lexical/utils\";\nimport {\n COMMAND_PRIORITY_EDITOR,\n type LexicalCommand,\n createCommand,\n} from \"lexical\";\n\nimport {\n $createYouTubeNode,\n YouTubeNode,\n} from \"@/components/editor/nodes/embeds/youtube-node\";\n\nexport const INSERT_YOUTUBE_COMMAND: LexicalCommand = createCommand(\n \"INSERT_YOUTUBE_COMMAND\",\n);\n\nexport function YouTubePlugin(): JSX.Element | null {\n const [editor] = useLexicalComposerContext();\n\n useEffect(() => {\n if (!editor.hasNodes([YouTubeNode])) {\n throw new Error(\"YouTubePlugin: YouTubeNode not registered on editor\");\n }\n\n return editor.registerCommand(\n INSERT_YOUTUBE_COMMAND,\n (payload) => {\n const youTubeNode = $createYouTubeNode(payload);\n $insertNodeToNearestRoot(youTubeNode);\n\n return true;\n },\n COMMAND_PRIORITY_EDITOR,\n );\n }, [editor]);\n\n return null;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/emoji-picker-plugin.tsx", "content": "import { useCallback, useEffect, useMemo, useState } from \"react\";\n\nimport { createPortal } from \"react-dom\";\n\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport {\n MenuOption,\n useBasicTypeaheadTriggerMatch,\n} from \"@lexical/react/LexicalTypeaheadMenuPlugin\";\nimport { LexicalTypeaheadMenuPlugin } from \"@lexical/react/LexicalTypeaheadMenuPlugin\";\nimport {\n $createTextNode,\n $getSelection,\n $isRangeSelection,\n TextNode,\n} from \"lexical\";\n\nimport {\n Command,\n CommandGroup,\n CommandItem,\n CommandList,\n} from \"@/components/ui/command\";\nimport { cn } from \"@/lib/utils\";\n\n// const LexicalTypeaheadMenuPlugin = dynamic(\n// () =>\n// import(\"@lexical/react/LexicalTypeaheadMenuPlugin\").then(\n// (mod) => mod.LexicalTypeaheadMenuPlugin\n// ),\n// { ssr: false }\n// )\n\nclass EmojiOption extends MenuOption {\n title: string;\n emoji: string;\n keywords: Array;\n\n constructor(\n title: string,\n emoji: string,\n options: {\n keywords?: Array;\n },\n ) {\n super(title);\n this.title = title;\n this.emoji = emoji;\n this.keywords = options.keywords || [];\n }\n}\n\ntype Emoji = {\n emoji: string;\n description: string;\n category: string;\n aliases: Array;\n tags: Array;\n unicode_version: string;\n ios_version: string;\n skin_tones?: boolean;\n};\n\nconst MAX_EMOJI_SUGGESTION_COUNT = 10;\n\nexport function EmojiPickerPlugin() {\n const [editor] = useLexicalComposerContext();\n const [queryString, setQueryString] = useState(null);\n const [emojis, setEmojis] = useState>([]);\n useEffect(() => {\n import(\"../utils/emoji-list\").then((file) => setEmojis(file.default));\n }, []);\n\n const emojiOptions = useMemo(\n () =>\n emojis != null\n ? emojis.map(\n ({ emoji, aliases, tags }) =>\n new EmojiOption(aliases[0], emoji, {\n keywords: [...aliases, ...tags],\n }),\n )\n : [],\n [emojis],\n );\n\n const checkForTriggerMatch = useBasicTypeaheadTriggerMatch(\":\", {\n minLength: 0,\n });\n\n const options: Array = useMemo(() => {\n return emojiOptions\n .filter((option: EmojiOption) => {\n return queryString != null\n ? new RegExp(queryString, \"gi\").exec(option.title) ||\n option.keywords != null\n ? option.keywords.some((keyword: string) =>\n new RegExp(queryString, \"gi\").exec(keyword),\n )\n : false\n : emojiOptions;\n })\n .slice(0, MAX_EMOJI_SUGGESTION_COUNT);\n }, [emojiOptions, queryString]);\n\n const onSelectOption = useCallback(\n (\n selectedOption: EmojiOption,\n nodeToRemove: TextNode | null,\n closeMenu: () => void,\n ) => {\n editor.update(() => {\n const selection = $getSelection();\n\n if (!$isRangeSelection(selection) || selectedOption == null) {\n return;\n }\n\n if (nodeToRemove) {\n nodeToRemove.remove();\n }\n\n selection.insertNodes([$createTextNode(selectedOption.emoji)]);\n\n closeMenu();\n });\n },\n [editor],\n );\n\n return (\n {\n return anchorElementRef.current && options.length\n ? createPortal(\n \n {\n if (e.key === \"ArrowUp\") {\n e.preventDefault();\n setHighlightedIndex(\n selectedIndex !== null\n ? (selectedIndex - 1 + options.length) %\n options.length\n : options.length - 1,\n );\n } else if (e.key === \"ArrowDown\") {\n e.preventDefault();\n setHighlightedIndex(\n selectedIndex !== null\n ? (selectedIndex + 1) % options.length\n : 0,\n );\n }\n }}\n >\n \n \n {options.map((option, index) => (\n {\n selectOptionAndCleanUp(option);\n }}\n className={cn(\n \"flex items-center gap-2\",\n selectedIndex === index\n ? \"bg-accent text-accent-foreground\"\n : \"!bg-transparent\",\n )}\n >\n {option.emoji} {option.title}\n \n ))}\n \n \n \n ,\n anchorElementRef.current,\n )\n : null;\n }}\n />\n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/floating-link-editor-plugin.tsx", "content": "import {\n type Dispatch,\n type JSX,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\nimport { createPortal } from \"react-dom\";\n\nimport {\n $createLinkNode,\n $isAutoLinkNode,\n $isLinkNode,\n TOGGLE_LINK_COMMAND,\n} from \"@lexical/link\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { $findMatchingParent, mergeRegister } from \"@lexical/utils\";\nimport {\n $getSelection,\n $isLineBreakNode,\n $isNodeSelection,\n $isRangeSelection,\n type BaseSelection,\n CLICK_COMMAND,\n COMMAND_PRIORITY_CRITICAL,\n COMMAND_PRIORITY_HIGH,\n COMMAND_PRIORITY_LOW,\n KEY_ESCAPE_COMMAND,\n type LexicalEditor,\n SELECTION_CHANGE_COMMAND,\n} from \"lexical\";\n\nimport { Check, Pencil, Trash, X } from \"lucide-react\";\n\nimport { getSelectedNode } from \"@/components/editor/utils/get-selected-node\";\nimport { setFloatingElemPositionForLinkEditor } from \"@/components/editor/utils/set-floating-elem-position-for-link-editor\";\nimport { sanitizeUrl } from \"@/components/editor/utils/url\";\nimport { Button } from \"@/components/ui/button\";\nimport { Input } from \"@/components/ui/input\";\n\nfunction FloatingLinkEditor({\n editor,\n isLink,\n setIsLink,\n anchorElem,\n isLinkEditMode,\n setIsLinkEditMode,\n}: {\n editor: LexicalEditor;\n isLink: boolean;\n setIsLink: Dispatch;\n anchorElem: HTMLElement;\n isLinkEditMode: boolean;\n setIsLinkEditMode: Dispatch;\n}): JSX.Element {\n const editorRef = useRef(null);\n const inputRef = useRef(null);\n const [linkUrl, setLinkUrl] = useState(\"\");\n const [editedLinkUrl, setEditedLinkUrl] = useState(\"https://\");\n const [lastSelection, setLastSelection] = useState(\n null,\n );\n\n const $updateLinkEditor = useCallback(() => {\n const selection = $getSelection();\n if ($isRangeSelection(selection)) {\n const node = getSelectedNode(selection);\n const linkParent = $findMatchingParent(node, $isLinkNode);\n\n if (linkParent) {\n setLinkUrl(linkParent.getURL());\n } else if ($isLinkNode(node)) {\n setLinkUrl(node.getURL());\n } else {\n setLinkUrl(\"\");\n }\n if (isLinkEditMode) {\n setEditedLinkUrl(linkUrl);\n }\n }\n const editorElem = editorRef.current;\n const nativeSelection = window.getSelection();\n const activeElement = document.activeElement;\n\n if (editorElem === null) {\n return;\n }\n\n const rootElement = editor.getRootElement();\n\n if (\n selection !== null &&\n nativeSelection !== null &&\n rootElement !== null &&\n rootElement.contains(nativeSelection.anchorNode) &&\n editor.isEditable()\n ) {\n const domRect: DOMRect | undefined =\n nativeSelection.focusNode?.parentElement?.getBoundingClientRect();\n if (domRect) {\n domRect.y += 40;\n setFloatingElemPositionForLinkEditor(domRect, editorElem, anchorElem);\n }\n setLastSelection(selection);\n } else if (!activeElement || activeElement.className !== \"link-input\") {\n if (rootElement !== null) {\n setFloatingElemPositionForLinkEditor(null, editorElem, anchorElem);\n }\n setLastSelection(null);\n setIsLinkEditMode(false);\n setLinkUrl(\"\");\n }\n\n return true;\n }, [anchorElem, editor, setIsLinkEditMode, isLinkEditMode, linkUrl]);\n\n useEffect(() => {\n const scrollerElem = anchorElem.parentElement;\n\n const update = () => {\n editor.getEditorState().read(() => {\n $updateLinkEditor();\n });\n };\n\n window.addEventListener(\"resize\", update);\n\n if (scrollerElem) {\n scrollerElem.addEventListener(\"scroll\", update);\n }\n\n return () => {\n window.removeEventListener(\"resize\", update);\n\n if (scrollerElem) {\n scrollerElem.removeEventListener(\"scroll\", update);\n }\n };\n }, [anchorElem.parentElement, editor, $updateLinkEditor]);\n\n useEffect(() => {\n return mergeRegister(\n editor.registerUpdateListener(({ editorState }) => {\n editorState.read(() => {\n $updateLinkEditor();\n });\n }),\n\n editor.registerCommand(\n SELECTION_CHANGE_COMMAND,\n () => {\n $updateLinkEditor();\n return true;\n },\n COMMAND_PRIORITY_LOW,\n ),\n editor.registerCommand(\n KEY_ESCAPE_COMMAND,\n () => {\n if (isLink) {\n setIsLink(false);\n return true;\n }\n return false;\n },\n COMMAND_PRIORITY_HIGH,\n ),\n );\n }, [editor, $updateLinkEditor, setIsLink, isLink]);\n\n useEffect(() => {\n editor.getEditorState().read(() => {\n $updateLinkEditor();\n });\n }, [editor, $updateLinkEditor]);\n\n useEffect(() => {\n if (isLinkEditMode && inputRef.current) {\n inputRef.current.focus();\n setIsLink(true);\n }\n }, [isLinkEditMode, isLink]);\n\n const monitorInputInteraction = (\n event: React.KeyboardEvent,\n ) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n handleLinkSubmission();\n } else if (event.key === \"Escape\") {\n event.preventDefault();\n setIsLinkEditMode(false);\n }\n };\n\n const handleLinkSubmission = () => {\n if (lastSelection !== null) {\n if (linkUrl !== \"\") {\n editor.dispatchCommand(TOGGLE_LINK_COMMAND, sanitizeUrl(editedLinkUrl));\n editor.update(() => {\n const selection = $getSelection();\n if ($isRangeSelection(selection)) {\n const parent = getSelectedNode(selection).getParent();\n if ($isAutoLinkNode(parent)) {\n const linkNode = $createLinkNode(parent.getURL(), {\n rel: parent.__rel,\n target: parent.__target,\n title: parent.__title,\n });\n parent.replace(linkNode, true);\n }\n }\n });\n }\n setEditedLinkUrl(\"https://\");\n setIsLinkEditMode(false);\n }\n };\n return (\n \n {!isLink ? null : isLinkEditMode ? (\n \n setEditedLinkUrl(event.target.value)}\n onKeyDown={monitorInputInteraction}\n className=\"h-8 flex-grow\"\n />\n {\n setIsLinkEditMode(false);\n setIsLink(false);\n }}\n className=\"shrink-0\"\n aria-label=\"Cancel\"\n >\n \n \n \n \n \n \n ) : (\n \n \n {linkUrl}\n \n \n {\n setEditedLinkUrl(linkUrl);\n setIsLinkEditMode(true);\n }}\n aria-label=\"Edit link\"\n >\n \n \n {\n editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);\n }}\n className=\"text-destructive hover:text-destructive\"\n aria-label=\"Remove link\"\n >\n \n \n \n \n )}\n \n );\n}\n\nfunction useFloatingLinkEditorToolbar(\n editor: LexicalEditor,\n anchorElem: HTMLDivElement | null,\n isLinkEditMode: boolean,\n setIsLinkEditMode: Dispatch,\n): JSX.Element | null {\n const [activeEditor, setActiveEditor] = useState(editor);\n const [isLink, setIsLink] = useState(false);\n\n useEffect(() => {\n function $updateToolbar() {\n const selection = $getSelection();\n if ($isRangeSelection(selection)) {\n const focusNode = getSelectedNode(selection);\n const focusLinkNode = $findMatchingParent(focusNode, $isLinkNode);\n const focusAutoLinkNode = $findMatchingParent(\n focusNode,\n $isAutoLinkNode,\n );\n if (!(focusLinkNode || focusAutoLinkNode)) {\n setIsLink(false);\n return;\n }\n const badNode = selection\n .getNodes()\n .filter((node) => !$isLineBreakNode(node))\n .find((node) => {\n const linkNode = $findMatchingParent(node, $isLinkNode);\n const autoLinkNode = $findMatchingParent(node, $isAutoLinkNode);\n return (\n (focusLinkNode && !focusLinkNode.is(linkNode)) ||\n (linkNode && !linkNode.is(focusLinkNode)) ||\n (focusAutoLinkNode && !focusAutoLinkNode.is(autoLinkNode)) ||\n (autoLinkNode &&\n (!autoLinkNode.is(focusAutoLinkNode) ||\n autoLinkNode.getIsUnlinked()))\n );\n });\n if (!badNode) {\n setIsLink(true);\n } else {\n setIsLink(false);\n }\n } else if ($isNodeSelection(selection)) {\n const nodes = selection.getNodes();\n if (nodes.length === 0) {\n setIsLink(false);\n return;\n }\n const node = nodes[0];\n const parent = node.getParent();\n if ($isLinkNode(parent) || $isLinkNode(node)) {\n setIsLink(true);\n } else {\n setIsLink(false);\n }\n }\n }\n return mergeRegister(\n editor.registerUpdateListener(({ editorState }) => {\n editorState.read(() => {\n $updateToolbar();\n });\n }),\n editor.registerCommand(\n SELECTION_CHANGE_COMMAND,\n (_payload, newEditor) => {\n $updateToolbar();\n setActiveEditor(newEditor);\n return false;\n },\n COMMAND_PRIORITY_CRITICAL,\n ),\n editor.registerCommand(\n CLICK_COMMAND,\n (payload) => {\n const selection = $getSelection();\n if ($isRangeSelection(selection)) {\n const node = getSelectedNode(selection);\n const linkNode = $findMatchingParent(node, $isLinkNode);\n if ($isLinkNode(linkNode) && (payload.metaKey || payload.ctrlKey)) {\n window.open(linkNode.getURL(), \"_blank\");\n return true;\n }\n }\n return false;\n },\n COMMAND_PRIORITY_LOW,\n ),\n );\n }, [editor]);\n\n if (!anchorElem) {\n return null;\n }\n\n return createPortal(\n ,\n anchorElem,\n );\n}\n\nexport function FloatingLinkEditorPlugin({\n anchorElem,\n isLinkEditMode,\n setIsLinkEditMode,\n}: {\n anchorElem: HTMLDivElement | null;\n isLinkEditMode: boolean;\n setIsLinkEditMode: Dispatch;\n}): JSX.Element | null {\n const [editor] = useLexicalComposerContext();\n\n return useFloatingLinkEditorToolbar(\n editor,\n anchorElem,\n isLinkEditMode,\n setIsLinkEditMode,\n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/floating-text-format-plugin.tsx", "content": "import {\n type Dispatch,\n type JSX,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\nimport { createPortal } from \"react-dom\";\n\nimport { $isCodeHighlightNode } from \"@lexical/code\";\nimport { $isLinkNode, TOGGLE_LINK_COMMAND } from \"@lexical/link\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { mergeRegister } from \"@lexical/utils\";\nimport {\n $getSelection,\n $isParagraphNode,\n $isRangeSelection,\n $isTextNode,\n COMMAND_PRIORITY_LOW,\n FORMAT_TEXT_COMMAND,\n type LexicalEditor,\n SELECTION_CHANGE_COMMAND,\n} from \"lexical\";\n\nimport {\n BoldIcon,\n CodeIcon,\n ItalicIcon,\n LinkIcon,\n StrikethroughIcon,\n SubscriptIcon,\n SuperscriptIcon,\n UnderlineIcon,\n} from \"lucide-react\";\n\nimport { getDOMRangeRect } from \"@/components/editor/utils/get-dom-range-rect\";\nimport { getSelectedNode } from \"@/components/editor/utils/get-selected-node\";\nimport { setFloatingElemPosition } from \"@/components/editor/utils/set-floating-elem-position\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { ToggleGroup, ToggleGroupItem } from \"@/components/ui/toggle-group\";\n\nfunction TextFormatFloatingToolbar({\n editor,\n anchorElem,\n isLink,\n isBold,\n isItalic,\n isUnderline,\n isCode,\n isStrikethrough,\n isSubscript,\n isSuperscript,\n setIsLinkEditMode,\n}: {\n editor: LexicalEditor;\n anchorElem: HTMLElement;\n isBold: boolean;\n isCode: boolean;\n isItalic: boolean;\n isLink: boolean;\n isStrikethrough: boolean;\n isSubscript: boolean;\n isSuperscript: boolean;\n isUnderline: boolean;\n setIsLinkEditMode: Dispatch;\n}): JSX.Element {\n const popupCharStylesEditorRef = useRef(null);\n\n const insertLink = useCallback(() => {\n if (!isLink) {\n setIsLinkEditMode(true);\n editor.dispatchCommand(TOGGLE_LINK_COMMAND, \"https://\");\n } else {\n setIsLinkEditMode(false);\n editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);\n }\n }, [editor, isLink, setIsLinkEditMode]);\n\n function mouseMoveListener(e: MouseEvent) {\n if (\n popupCharStylesEditorRef?.current &&\n (e.buttons === 1 || e.buttons === 3)\n ) {\n if (popupCharStylesEditorRef.current.style.pointerEvents !== \"none\") {\n const x = e.clientX;\n const y = e.clientY;\n const elementUnderMouse = document.elementFromPoint(x, y);\n\n if (!popupCharStylesEditorRef.current.contains(elementUnderMouse)) {\n // Mouse is not over the target element => not a normal click, but probably a drag\n popupCharStylesEditorRef.current.style.pointerEvents = \"none\";\n }\n }\n }\n }\n function mouseUpListener(_e: MouseEvent) {\n if (popupCharStylesEditorRef?.current) {\n if (popupCharStylesEditorRef.current.style.pointerEvents !== \"auto\") {\n popupCharStylesEditorRef.current.style.pointerEvents = \"auto\";\n }\n }\n }\n\n useEffect(() => {\n if (popupCharStylesEditorRef?.current) {\n document.addEventListener(\"mousemove\", mouseMoveListener);\n document.addEventListener(\"mouseup\", mouseUpListener);\n\n return () => {\n document.removeEventListener(\"mousemove\", mouseMoveListener);\n document.removeEventListener(\"mouseup\", mouseUpListener);\n };\n }\n }, [popupCharStylesEditorRef]);\n\n const $updateTextFormatFloatingToolbar = useCallback(() => {\n const selection = $getSelection();\n\n const popupCharStylesEditorElem = popupCharStylesEditorRef.current;\n const nativeSelection = window.getSelection();\n\n if (popupCharStylesEditorElem === null) {\n return;\n }\n\n const rootElement = editor.getRootElement();\n if (\n selection !== null &&\n nativeSelection !== null &&\n !nativeSelection.isCollapsed &&\n rootElement !== null &&\n rootElement.contains(nativeSelection.anchorNode)\n ) {\n const rangeRect = getDOMRangeRect(nativeSelection, rootElement);\n\n setFloatingElemPosition(\n rangeRect,\n popupCharStylesEditorElem,\n anchorElem,\n isLink,\n );\n }\n }, [editor, anchorElem, isLink]);\n\n useEffect(() => {\n const scrollerElem = anchorElem.parentElement;\n\n const update = () => {\n editor.getEditorState().read(() => {\n $updateTextFormatFloatingToolbar();\n });\n };\n\n window.addEventListener(\"resize\", update);\n if (scrollerElem) {\n scrollerElem.addEventListener(\"scroll\", update);\n }\n\n return () => {\n window.removeEventListener(\"resize\", update);\n if (scrollerElem) {\n scrollerElem.removeEventListener(\"scroll\", update);\n }\n };\n }, [editor, $updateTextFormatFloatingToolbar, anchorElem]);\n\n useEffect(() => {\n editor.getEditorState().read(() => {\n $updateTextFormatFloatingToolbar();\n });\n return mergeRegister(\n editor.registerUpdateListener(({ editorState }) => {\n editorState.read(() => {\n $updateTextFormatFloatingToolbar();\n });\n }),\n\n editor.registerCommand(\n SELECTION_CHANGE_COMMAND,\n () => {\n $updateTextFormatFloatingToolbar();\n return false;\n },\n COMMAND_PRIORITY_LOW,\n ),\n );\n }, [editor, $updateTextFormatFloatingToolbar]);\n\n return (\n \n {editor.isEditable() && (\n <>\n \n {\n editor.dispatchCommand(FORMAT_TEXT_COMMAND, \"bold\");\n }}\n size=\"sm\"\n >\n \n \n {\n editor.dispatchCommand(FORMAT_TEXT_COMMAND, \"italic\");\n }}\n size=\"sm\"\n >\n \n \n {\n editor.dispatchCommand(FORMAT_TEXT_COMMAND, \"underline\");\n }}\n size=\"sm\"\n >\n \n \n {\n editor.dispatchCommand(FORMAT_TEXT_COMMAND, \"strikethrough\");\n }}\n size=\"sm\"\n >\n \n \n \n {\n editor.dispatchCommand(FORMAT_TEXT_COMMAND, \"code\");\n }}\n size=\"sm\"\n >\n \n \n \n \n \n \n \n \n {\n editor.dispatchCommand(FORMAT_TEXT_COMMAND, \"subscript\");\n }}\n size=\"sm\"\n >\n \n \n {\n editor.dispatchCommand(FORMAT_TEXT_COMMAND, \"superscript\");\n }}\n size=\"sm\"\n >\n \n \n \n >\n )}\n \n );\n}\n\nfunction useFloatingTextFormatToolbar(\n editor: LexicalEditor,\n anchorElem: HTMLDivElement | null,\n setIsLinkEditMode: Dispatch,\n): JSX.Element | null {\n const [isText, setIsText] = useState(false);\n const [isLink, setIsLink] = useState(false);\n const [isBold, setIsBold] = useState(false);\n const [isItalic, setIsItalic] = useState(false);\n const [isUnderline, setIsUnderline] = useState(false);\n const [isStrikethrough, setIsStrikethrough] = useState(false);\n const [isSubscript, setIsSubscript] = useState(false);\n const [isSuperscript, setIsSuperscript] = useState(false);\n const [isCode, setIsCode] = useState(false);\n\n const updatePopup = useCallback(() => {\n editor.getEditorState().read(() => {\n // Should not to pop up the floating toolbar when using IME input\n if (editor.isComposing()) {\n return;\n }\n const selection = $getSelection();\n const nativeSelection = window.getSelection();\n const rootElement = editor.getRootElement();\n\n if (\n nativeSelection !== null &&\n (!$isRangeSelection(selection) ||\n rootElement === null ||\n !rootElement.contains(nativeSelection.anchorNode))\n ) {\n setIsText(false);\n return;\n }\n\n if (!$isRangeSelection(selection)) {\n return;\n }\n\n const node = getSelectedNode(selection);\n\n // Update text format\n setIsBold(selection.hasFormat(\"bold\"));\n setIsItalic(selection.hasFormat(\"italic\"));\n setIsUnderline(selection.hasFormat(\"underline\"));\n setIsStrikethrough(selection.hasFormat(\"strikethrough\"));\n setIsSubscript(selection.hasFormat(\"subscript\"));\n setIsSuperscript(selection.hasFormat(\"superscript\"));\n setIsCode(selection.hasFormat(\"code\"));\n\n // Update links\n const parent = node.getParent();\n if ($isLinkNode(parent) || $isLinkNode(node)) {\n setIsLink(true);\n } else {\n setIsLink(false);\n }\n\n if (\n !$isCodeHighlightNode(selection.anchor.getNode()) &&\n selection.getTextContent() !== \"\"\n ) {\n setIsText($isTextNode(node) || $isParagraphNode(node));\n } else {\n setIsText(false);\n }\n\n const rawTextContent = selection.getTextContent().replace(/\\n/g, \"\");\n if (!selection.isCollapsed() && rawTextContent === \"\") {\n setIsText(false);\n return;\n }\n });\n }, [editor]);\n\n useEffect(() => {\n document.addEventListener(\"selectionchange\", updatePopup);\n return () => {\n document.removeEventListener(\"selectionchange\", updatePopup);\n };\n }, [updatePopup]);\n\n useEffect(() => {\n return mergeRegister(\n editor.registerUpdateListener(() => {\n updatePopup();\n }),\n editor.registerRootListener(() => {\n if (editor.getRootElement() === null) {\n setIsText(false);\n }\n }),\n );\n }, [editor, updatePopup]);\n\n if (!isText || !anchorElem) {\n return null;\n }\n\n return createPortal(\n ,\n anchorElem,\n );\n}\n\nexport function FloatingTextFormatToolbarPlugin({\n anchorElem,\n setIsLinkEditMode,\n}: {\n anchorElem: HTMLDivElement | null;\n setIsLinkEditMode: Dispatch;\n}): JSX.Element | null {\n const [editor] = useLexicalComposerContext();\n\n return useFloatingTextFormatToolbar(editor, anchorElem, setIsLinkEditMode);\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/layout-plugin.tsx", "content": "import { type JSX, useEffect, useState } from \"react\";\n\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport {\n $findMatchingParent,\n $insertNodeToNearestRoot,\n mergeRegister,\n} from \"@lexical/utils\";\nimport {\n $createParagraphNode,\n $getNodeByKey,\n $getSelection,\n $isRangeSelection,\n COMMAND_PRIORITY_EDITOR,\n COMMAND_PRIORITY_LOW,\n KEY_ARROW_DOWN_COMMAND,\n KEY_ARROW_LEFT_COMMAND,\n KEY_ARROW_RIGHT_COMMAND,\n KEY_ARROW_UP_COMMAND,\n type LexicalEditor,\n createCommand,\n} from \"lexical\";\nimport type {\n ElementNode,\n LexicalCommand,\n LexicalNode,\n NodeKey,\n} from \"lexical\";\n\nimport {\n $createLayoutContainerNode,\n $isLayoutContainerNode,\n LayoutContainerNode,\n} from \"@/components/editor/nodes/layout-container-node\";\nimport {\n $createLayoutItemNode,\n $isLayoutItemNode,\n LayoutItemNode,\n} from \"@/components/editor/nodes/layout-item-node\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\n\nconst LAYOUTS = [\n { label: \"2 columns (equal width)\", value: \"1fr 1fr\" },\n { label: \"2 columns (25% - 75%)\", value: \"1fr 3fr\" },\n { label: \"3 columns (equal width)\", value: \"1fr 1fr 1fr\" },\n { label: \"3 columns (25% - 50% - 25%)\", value: \"1fr 2fr 1fr\" },\n { label: \"4 columns (equal width)\", value: \"1fr 1fr 1fr 1fr\" },\n];\n\nexport function InsertLayoutDialog({\n activeEditor,\n onClose,\n}: {\n activeEditor: LexicalEditor;\n onClose: () => void;\n}): JSX.Element {\n const [layout, setLayout] = useState(LAYOUTS[0].value);\n const buttonLabel = LAYOUTS.find((item) => item.value === layout)?.label;\n\n const onClick = () => {\n activeEditor.dispatchCommand(INSERT_LAYOUT_COMMAND, layout);\n onClose();\n };\n\n return (\n <>\n \n \n \n \n \n {LAYOUTS.map(({ label, value }) => (\n \n {label}\n \n ))}\n \n \n Insert\n >\n );\n}\n\nexport const INSERT_LAYOUT_COMMAND: LexicalCommand =\n createCommand();\n\nexport const UPDATE_LAYOUT_COMMAND: LexicalCommand<{\n template: string;\n nodeKey: NodeKey;\n}> = createCommand<{ template: string; nodeKey: NodeKey }>();\n\nexport function LayoutPlugin(): null {\n const [editor] = useLexicalComposerContext();\n useEffect(() => {\n if (!editor.hasNodes([LayoutContainerNode, LayoutItemNode])) {\n throw new Error(\n \"LayoutPlugin: LayoutContainerNode, or LayoutItemNode not registered on editor\",\n );\n }\n\n const $onEscape = (before: boolean) => {\n const selection = $getSelection();\n if (\n $isRangeSelection(selection) &&\n selection.isCollapsed() &&\n selection.anchor.offset === 0\n ) {\n const container = $findMatchingParent(\n selection.anchor.getNode(),\n $isLayoutContainerNode,\n );\n\n if ($isLayoutContainerNode(container)) {\n const parent = container.getParent();\n const child =\n parent &&\n (before\n ? parent.getFirstChild()\n : parent?.getLastChild());\n const descendant = before\n ? container.getFirstDescendant()?.getKey()\n : container.getLastDescendant()?.getKey();\n\n if (\n parent !== null &&\n child === container &&\n selection.anchor.key === descendant\n ) {\n if (before) {\n container.insertBefore($createParagraphNode());\n } else {\n container.insertAfter($createParagraphNode());\n }\n }\n }\n }\n\n return false;\n };\n\n return mergeRegister(\n // When layout is the last child pressing down/right arrow will insert paragraph\n // below it to allow adding more content. It's similar what $insertBlockNode\n // (mainly for decorators), except it'll always be possible to continue adding\n // new content even if trailing paragraph is accidentally deleted\n editor.registerCommand(\n KEY_ARROW_DOWN_COMMAND,\n () => $onEscape(false),\n COMMAND_PRIORITY_LOW,\n ),\n editor.registerCommand(\n KEY_ARROW_RIGHT_COMMAND,\n () => $onEscape(false),\n COMMAND_PRIORITY_LOW,\n ),\n // When layout is the first child pressing up/left arrow will insert paragraph\n // above it to allow adding more content. It's similar what $insertBlockNode\n // (mainly for decorators), except it'll always be possible to continue adding\n // new content even if leading paragraph is accidentally deleted\n editor.registerCommand(\n KEY_ARROW_UP_COMMAND,\n () => $onEscape(true),\n COMMAND_PRIORITY_LOW,\n ),\n editor.registerCommand(\n KEY_ARROW_LEFT_COMMAND,\n () => $onEscape(true),\n COMMAND_PRIORITY_LOW,\n ),\n editor.registerCommand(\n INSERT_LAYOUT_COMMAND,\n (template) => {\n editor.update(() => {\n const container = $createLayoutContainerNode(template);\n const itemsCount = getItemsCountFromTemplate(template);\n\n for (let i = 0; i < itemsCount; i++) {\n container.append(\n $createLayoutItemNode().append($createParagraphNode()),\n );\n }\n\n $insertNodeToNearestRoot(container);\n container.selectStart();\n });\n\n return true;\n },\n COMMAND_PRIORITY_EDITOR,\n ),\n editor.registerCommand(\n UPDATE_LAYOUT_COMMAND,\n ({ template, nodeKey }) => {\n editor.update(() => {\n const container = $getNodeByKey(nodeKey);\n\n if (!$isLayoutContainerNode(container)) {\n return;\n }\n\n const itemsCount = getItemsCountFromTemplate(template);\n const prevItemsCount = getItemsCountFromTemplate(\n container.getTemplateColumns(),\n );\n\n // Add or remove extra columns if new template does not match existing one\n if (itemsCount > prevItemsCount) {\n for (let i = prevItemsCount; i < itemsCount; i++) {\n container.append(\n $createLayoutItemNode().append($createParagraphNode()),\n );\n }\n } else if (itemsCount < prevItemsCount) {\n for (let i = prevItemsCount - 1; i >= itemsCount; i--) {\n const layoutItem = container.getChildAtIndex(i);\n\n if ($isLayoutItemNode(layoutItem)) {\n layoutItem.remove();\n }\n }\n }\n\n container.setTemplateColumns(template);\n });\n\n return true;\n },\n COMMAND_PRIORITY_EDITOR,\n ),\n // Structure enforcing transformers for each node type. In case nesting structure is not\n // \"Container > Item\" it'll unwrap nodes and convert it back\n // to regular content.\n editor.registerNodeTransform(LayoutItemNode, (node) => {\n const parent = node.getParent();\n if (!$isLayoutContainerNode(parent)) {\n const children = node.getChildren();\n for (const child of children) {\n node.insertBefore(child);\n }\n node.remove();\n }\n }),\n editor.registerNodeTransform(LayoutContainerNode, (node) => {\n const children = node.getChildren();\n if (!children.every($isLayoutItemNode)) {\n for (const child of children) {\n node.insertBefore(child);\n }\n node.remove();\n }\n }),\n );\n }, [editor]);\n\n return null;\n}\n\nfunction getItemsCountFromTemplate(template: string): number {\n return template.trim().split(/\\s+/).length;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/mentions-plugin.tsx", "content": "import { type JSX, useCallback, useEffect, useMemo, useState } from \"react\";\n\nimport { createPortal } from \"react-dom\";\n\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport {\n MenuOption,\n type MenuTextMatch,\n useBasicTypeaheadTriggerMatch,\n} from \"@lexical/react/LexicalTypeaheadMenuPlugin\";\nimport { LexicalTypeaheadMenuPlugin } from \"@lexical/react/LexicalTypeaheadMenuPlugin\";\nimport { TextNode } from \"lexical\";\n\nimport { CircleUserRoundIcon } from \"lucide-react\";\n\nimport { $createMentionNode } from \"@/components/editor/nodes/mention-node\";\nimport {\n Command,\n CommandGroup,\n CommandItem,\n CommandList,\n} from \"@/components/ui/command\";\n\n// const LexicalTypeaheadMenuPlugin = dynamic(\n// () =>\n// import(\"@lexical/react/LexicalTypeaheadMenuPlugin\").then(\n// (mod) => mod.LexicalTypeaheadMenuPlugin\n// ),\n// { ssr: false }\n// )\n\nconst PUNCTUATION =\n \"\\\\.,\\\\+\\\\*\\\\?\\\\$\\\\@\\\\|#{}\\\\(\\\\)\\\\^\\\\-\\\\[\\\\]\\\\\\\\/!%'\\\"~=<>_:;\";\nconst NAME = \"\\\\b[A-Z][^\\\\s\" + PUNCTUATION + \"]\";\n\nconst DocumentMentionsRegex = {\n NAME,\n PUNCTUATION,\n};\n\nconst PUNC = DocumentMentionsRegex.PUNCTUATION;\n\nconst TRIGGERS = [\"@\"].join(\"\");\n\n// Chars we expect to see in a mention (non-space, non-punctuation).\nconst VALID_CHARS = \"[^\" + TRIGGERS + PUNC + \"\\\\s]\";\n\n// Non-standard series of chars. Each series must be preceded and followed by\n// a valid char.\nconst VALID_JOINS =\n \"(?:\" +\n \"\\\\.[ |$]|\" + // E.g. \"r. \" in \"Mr. Smith\"\n \" |\" + // E.g. \" \" in \"Josh Duck\"\n \"[\" +\n PUNC +\n \"]|\" + // E.g. \"-' in \"Salier-Hellendag\"\n \")\";\n\nconst LENGTH_LIMIT = 75;\n\nconst AtSignMentionsRegex = new RegExp(\n \"(^|\\\\s|\\\\()(\" +\n \"[\" +\n TRIGGERS +\n \"]\" +\n \"((?:\" +\n VALID_CHARS +\n VALID_JOINS +\n \"){0,\" +\n LENGTH_LIMIT +\n \"})\" +\n \")$\",\n);\n\n// 50 is the longest alias length limit.\nconst ALIAS_LENGTH_LIMIT = 50;\n\n// Regex used to match alias.\nconst AtSignMentionsRegexAliasRegex = new RegExp(\n \"(^|\\\\s|\\\\()(\" +\n \"[\" +\n TRIGGERS +\n \"]\" +\n \"((?:\" +\n VALID_CHARS +\n \"){0,\" +\n ALIAS_LENGTH_LIMIT +\n \"})\" +\n \")$\",\n);\n\n// At most, 5 suggestions are shown in the popup.\nconst SUGGESTION_LIST_LENGTH_LIMIT = 5;\n\nconst mentionsCache = new Map();\n\nconst dummyMentionsData = [\n \"Aayla Secura\",\n \"Adi Gallia\",\n \"Admiral Dodd Rancit\",\n \"Admiral Firmus Piett\",\n \"Admiral Gial Ackbar\",\n \"Admiral Ozzel\",\n \"Admiral Raddus\",\n \"Admiral Terrinald Screed\",\n \"Admiral Trench\",\n \"Admiral U.O. Statura\",\n \"Agen Kolar\",\n \"Agent Kallus\",\n \"Aiolin and Morit Astarte\",\n \"Aks Moe\",\n \"Almec\",\n \"Alton Kastle\",\n \"Amee\",\n \"AP-5\",\n \"Armitage Hux\",\n \"Artoo\",\n \"Arvel Crynyd\",\n \"Asajj Ventress\",\n \"Aurra Sing\",\n \"AZI-3\",\n \"Bala-Tik\",\n \"Barada\",\n \"Bargwill Tomder\",\n \"Baron Papanoida\",\n \"Barriss Offee\",\n \"Baze Malbus\",\n \"Bazine Netal\",\n \"BB-8\",\n \"BB-9E\",\n \"Ben Quadinaros\",\n \"Berch Teller\",\n \"Beru Lars\",\n \"Bib Fortuna\",\n \"Biggs Darklighter\",\n \"Black Krrsantan\",\n \"Bo-Katan Kryze\",\n \"Boba Fett\",\n \"Bobbajo\",\n \"Bodhi Rook\",\n \"Borvo the Hutt\",\n \"Boss Nass\",\n \"Bossk\",\n \"Breha Antilles-Organa\",\n \"Bren Derlin\",\n \"Brendol Hux\",\n \"BT-1\",\n \"C-3PO\",\n \"C1-10P\",\n \"Cad Bane\",\n \"Caluan Ematt\",\n \"Captain Gregor\",\n \"Captain Phasma\",\n \"Captain Quarsh Panaka\",\n \"Captain Rex\",\n \"Carlist Rieekan\",\n \"Casca Panzoro\",\n \"Cassian Andor\",\n \"Cassio Tagge\",\n \"Cham Syndulla\",\n \"Che Amanwe Papanoida\",\n \"Chewbacca\",\n \"Chi Eekway Papanoida\",\n \"Chief Chirpa\",\n \"Chirrut Îmwe\",\n \"Ciena Ree\",\n \"Cin Drallig\",\n \"Clegg Holdfast\",\n \"Cliegg Lars\",\n \"Coleman Kcaj\",\n \"Coleman Trebor\",\n \"Colonel Kaplan\",\n \"Commander Bly\",\n \"Commander Cody (CC-2224)\",\n \"Commander Fil (CC-3714)\",\n \"Commander Fox\",\n \"Commander Gree\",\n \"Commander Jet\",\n \"Commander Wolffe\",\n \"Conan Antonio Motti\",\n \"Conder Kyl\",\n \"Constable Zuvio\",\n \"Cordé\",\n \"Cpatain Typho\",\n \"Crix Madine\",\n \"Cut Lawquane\",\n \"Dak Ralter\",\n \"Dapp\",\n \"Darth Bane\",\n \"Darth Maul\",\n \"Darth Tyranus\",\n \"Daultay Dofine\",\n \"Del Meeko\",\n \"Delian Mors\",\n \"Dengar\",\n \"Depa Billaba\",\n \"Derek Klivian\",\n \"Dexter Jettster\",\n \"Dineé Ellberger\",\n \"DJ\",\n \"Doctor Aphra\",\n \"Doctor Evazan\",\n \"Dogma\",\n \"Dormé\",\n \"Dr. Cylo\",\n \"Droidbait\",\n \"Droopy McCool\",\n \"Dryden Vos\",\n \"Dud Bolt\",\n \"Ebe E. Endocott\",\n \"Echuu Shen-Jon\",\n \"Eeth Koth\",\n \"Eighth Brother\",\n \"Eirtaé\",\n \"Eli Vanto\",\n \"Ellé\",\n \"Ello Asty\",\n \"Embo\",\n \"Eneb Ray\",\n \"Enfys Nest\",\n \"EV-9D9\",\n \"Evaan Verlaine\",\n \"Even Piell\",\n \"Ezra Bridger\",\n \"Faro Argyus\",\n \"Feral\",\n \"Fifth Brother\",\n \"Finis Valorum\",\n \"Finn\",\n \"Fives\",\n \"FN-1824\",\n \"FN-2003\",\n \"Fodesinbeed Annodue\",\n \"Fulcrum\",\n \"FX-7\",\n \"GA-97\",\n \"Galen Erso\",\n \"Gallius Rax\",\n 'Garazeb \"Zeb\" Orrelios',\n \"Gardulla the Hutt\",\n \"Garrick Versio\",\n \"Garven Dreis\",\n \"Gavyn Sykes\",\n \"Gideon Hask\",\n \"Gizor Dellso\",\n \"Gonk droid\",\n \"Grand Inquisitor\",\n \"Greeata Jendowanian\",\n \"Greedo\",\n \"Greer Sonnel\",\n \"Grievous\",\n \"Grummgar\",\n \"Gungi\",\n \"Hammerhead\",\n \"Han Solo\",\n \"Harter Kalonia\",\n \"Has Obbit\",\n \"Hera Syndulla\",\n \"Hevy\",\n \"Hondo Ohnaka\",\n \"Huyang\",\n \"Iden Versio\",\n \"IG-88\",\n \"Ima-Gun Di\",\n \"Inquisitors\",\n \"Inspector Thanoth\",\n \"Jabba\",\n \"Jacen Syndulla\",\n \"Jan Dodonna\",\n \"Jango Fett\",\n \"Janus Greejatus\",\n \"Jar Jar Binks\",\n \"Jas Emari\",\n \"Jaxxon\",\n \"Jek Tono Porkins\",\n \"Jeremoch Colton\",\n \"Jira\",\n \"Jobal Naberrie\",\n \"Jocasta Nu\",\n \"Joclad Danva\",\n \"Joh Yowza\",\n \"Jom Barell\",\n \"Joph Seastriker\",\n \"Jova Tarkin\",\n \"Jubnuk\",\n \"Jyn Erso\",\n \"K-2SO\",\n \"Kanan Jarrus\",\n \"Karbin\",\n \"Karina the Great\",\n \"Kes Dameron\",\n \"Ketsu Onyo\",\n \"Ki-Adi-Mundi\",\n \"King Katuunko\",\n \"Kit Fisto\",\n \"Kitster Banai\",\n \"Klaatu\",\n \"Klik-Klak\",\n \"Korr Sella\",\n \"Kylo Ren\",\n \"L3-37\",\n \"Lama Su\",\n \"Lando Calrissian\",\n \"Lanever Villecham\",\n \"Leia Organa\",\n \"Letta Turmond\",\n \"Lieutenant Kaydel Ko Connix\",\n \"Lieutenant Thire\",\n \"Lobot\",\n \"Logray\",\n \"Lok Durd\",\n \"Longo Two-Guns\",\n \"Lor San Tekka\",\n \"Lorth Needa\",\n \"Lott Dod\",\n \"Luke Skywalker\",\n \"Lumat\",\n \"Luminara Unduli\",\n \"Lux Bonteri\",\n \"Lyn Me\",\n \"Lyra Erso\",\n \"Mace Windu\",\n \"Malakili\",\n \"Mama the Hutt\",\n \"Mars Guo\",\n \"Mas Amedda\",\n \"Mawhonic\",\n \"Max Rebo\",\n \"Maximilian Veers\",\n \"Maz Kanata\",\n \"ME-8D9\",\n \"Meena Tills\",\n \"Mercurial Swift\",\n \"Mina Bonteri\",\n \"Miraj Scintel\",\n \"Mister Bones\",\n \"Mod Terrik\",\n \"Moden Canady\",\n \"Mon Mothma\",\n \"Moradmin Bast\",\n \"Moralo Eval\",\n \"Morley\",\n \"Mother Talzin\",\n \"Nahdar Vebb\",\n \"Nahdonnis Praji\",\n \"Nien Nunb\",\n \"Niima the Hutt\",\n \"Nines\",\n \"Norra Wexley\",\n \"Nute Gunray\",\n \"Nuvo Vindi\",\n \"Obi-Wan Kenobi\",\n \"Odd Ball\",\n \"Ody Mandrell\",\n \"Omi\",\n \"Onaconda Farr\",\n \"Oola\",\n \"OOM-9\",\n \"Oppo Rancisis\",\n \"Orn Free Taa\",\n \"Oro Dassyne\",\n \"Orrimarko\",\n \"Osi Sobeck\",\n \"Owen Lars\",\n \"Pablo-Jill\",\n \"Padmé Amidala\",\n \"Pagetti Rook\",\n \"Paige Tico\",\n \"Paploo\",\n \"Petty Officer Thanisson\",\n \"Pharl McQuarrie\",\n \"Plo Koon\",\n \"Po Nudo\",\n \"Poe Dameron\",\n \"Poggle the Lesser\",\n \"Pong Krell\",\n \"Pooja Naberrie\",\n \"PZ-4CO\",\n \"Quarrie\",\n \"Quay Tolsite\",\n \"Queen Apailana\",\n \"Queen Jamillia\",\n \"Queen Neeyutnee\",\n \"Qui-Gon Jinn\",\n \"Quiggold\",\n \"Quinlan Vos\",\n \"R2-D2\",\n \"R2-KT\",\n \"R3-S6\",\n \"R4-P17\",\n \"R5-D4\",\n \"RA-7\",\n \"Rabé\",\n \"Rako Hardeen\",\n \"Ransolm Casterfo\",\n \"Rappertunie\",\n \"Ratts Tyerell\",\n \"Raymus Antilles\",\n \"Ree-Yees\",\n \"Reeve Panzoro\",\n \"Rey\",\n \"Ric Olié\",\n \"Riff Tamson\",\n \"Riley\",\n \"Rinnriyin Di\",\n \"Rio Durant\",\n \"Rogue Squadron\",\n \"Romba\",\n \"Roos Tarpals\",\n \"Rose Tico\",\n \"Rotta the Hutt\",\n \"Rukh\",\n \"Rune Haako\",\n \"Rush Clovis\",\n \"Ruwee Naberrie\",\n \"Ryoo Naberrie\",\n \"Sabé\",\n \"Sabine Wren\",\n \"Saché\",\n \"Saelt-Marae\",\n \"Saesee Tiin\",\n \"Salacious B. Crumb\",\n \"San Hill\",\n \"Sana Starros\",\n \"Sarco Plank\",\n \"Sarkli\",\n \"Satine Kryze\",\n \"Savage Opress\",\n \"Sebulba\",\n \"Senator Organa\",\n \"Sergeant Kreel\",\n \"Seventh Sister\",\n \"Shaak Ti\",\n \"Shara Bey\",\n \"Shmi Skywalker\",\n \"Shu Mai\",\n \"Sidon Ithano\",\n \"Sifo-Dyas\",\n \"Sim Aloo\",\n \"Siniir Rath Velus\",\n \"Sio Bibble\",\n \"Sixth Brother\",\n \"Slowen Lo\",\n \"Sly Moore\",\n \"Snaggletooth\",\n \"Snap Wexley\",\n \"Snoke\",\n \"Sola Naberrie\",\n \"Sora Bulq\",\n \"Strono Tuggs\",\n \"Sy Snootles\",\n \"Tallissan Lintra\",\n \"Tarfful\",\n \"Tasu Leech\",\n \"Taun We\",\n \"TC-14\",\n \"Tee Watt Kaa\",\n \"Teebo\",\n \"Teedo\",\n \"Teemto Pagalies\",\n \"Temiri Blagg\",\n \"Tessek\",\n \"Tey How\",\n \"Thane Kyrell\",\n \"The Bendu\",\n \"The Smuggler\",\n \"Thrawn\",\n \"Tiaan Jerjerrod\",\n \"Tion Medon\",\n \"Tobias Beckett\",\n \"Tulon Voidgazer\",\n \"Tup\",\n \"U9-C4\",\n \"Unkar Plutt\",\n \"Val Beckett\",\n \"Vanden Willard\",\n \"Vice Admiral Amilyn Holdo\",\n \"Vober Dand\",\n \"WAC-47\",\n \"Wag Too\",\n \"Wald\",\n \"Walrus Man\",\n \"Warok\",\n \"Wat Tambor\",\n \"Watto\",\n \"Wedge Antilles\",\n \"Wes Janson\",\n \"Wicket W. Warrick\",\n \"Wilhuff Tarkin\",\n \"Wollivan\",\n \"Wuher\",\n \"Wullf Yularen\",\n \"Xamuel Lennox\",\n \"Yaddle\",\n \"Yarael Poof\",\n \"Yoda\",\n \"Zam Wesell\",\n \"Zev Senesca\",\n \"Ziro the Hutt\",\n \"Zuckuss\",\n];\n\nconst dummyLookupService = {\n search(string: string, callback: (results: Array) => void): void {\n setTimeout(() => {\n const results = dummyMentionsData.filter((mention) =>\n mention.toLowerCase().includes(string.toLowerCase()),\n );\n callback(results);\n }, 500);\n },\n};\n\nfunction useMentionLookupService(mentionString: string | null) {\n const [results, setResults] = useState>([]);\n\n useEffect(() => {\n const cachedResults = mentionsCache.get(mentionString);\n\n if (mentionString == null) {\n setResults([]);\n return;\n }\n\n if (cachedResults === null) {\n return;\n } else if (cachedResults !== undefined) {\n setResults(cachedResults);\n return;\n }\n\n mentionsCache.set(mentionString, null);\n dummyLookupService.search(mentionString, (newResults) => {\n mentionsCache.set(mentionString, newResults);\n setResults(newResults);\n });\n }, [mentionString]);\n\n return results;\n}\n\nfunction checkForAtSignMentions(\n text: string,\n minMatchLength: number,\n): MenuTextMatch | null {\n let match = AtSignMentionsRegex.exec(text);\n\n if (match === null) {\n match = AtSignMentionsRegexAliasRegex.exec(text);\n }\n if (match !== null) {\n // The strategy ignores leading whitespace but we need to know it's\n // length to add it to the leadOffset\n\n const maybeLeadingWhitespace = match[1];\n\n const matchingString = match[3];\n if (matchingString.length >= minMatchLength) {\n return {\n leadOffset: match.index + maybeLeadingWhitespace.length,\n matchingString,\n replaceableString: match[2],\n };\n }\n }\n return null;\n}\n\nfunction getPossibleQueryMatch(text: string): MenuTextMatch | null {\n return checkForAtSignMentions(text, 1);\n}\n\nclass MentionTypeaheadOption extends MenuOption {\n name: string;\n picture: JSX.Element;\n\n constructor(name: string, picture: JSX.Element) {\n super(name);\n this.name = name;\n this.picture = picture;\n }\n}\n\nexport function MentionsPlugin(): JSX.Element | null {\n const [editor] = useLexicalComposerContext();\n\n const [queryString, setQueryString] = useState(null);\n\n const results = useMentionLookupService(queryString);\n\n const checkForSlashTriggerMatch = useBasicTypeaheadTriggerMatch(\"/\", {\n minLength: 0,\n });\n\n const options = useMemo(\n () =>\n results\n .map(\n (result) =>\n new MentionTypeaheadOption(\n result,\n ,\n ),\n )\n .slice(0, SUGGESTION_LIST_LENGTH_LIMIT),\n [results],\n );\n\n const onSelectOption = useCallback(\n (\n selectedOption: MentionTypeaheadOption,\n nodeToReplace: TextNode | null,\n closeMenu: () => void,\n ) => {\n editor.update(() => {\n const mentionNode = $createMentionNode(selectedOption.name);\n if (nodeToReplace) {\n nodeToReplace.replace(mentionNode);\n }\n mentionNode.select();\n closeMenu();\n });\n },\n [editor],\n );\n\n const checkForMentionMatch = useCallback(\n (text: string) => {\n const slashMatch = checkForSlashTriggerMatch(text, editor);\n if (slashMatch !== null) {\n return null;\n }\n return getPossibleQueryMatch(text);\n },\n [checkForSlashTriggerMatch, editor],\n );\n\n return (\n {\n return anchorElementRef.current && results.length\n ? createPortal(\n \n {\n if (e.key === \"ArrowUp\") {\n e.preventDefault();\n setHighlightedIndex(\n selectedIndex !== null\n ? (selectedIndex - 1 + options.length) %\n options.length\n : options.length - 1,\n );\n } else if (e.key === \"ArrowDown\") {\n e.preventDefault();\n setHighlightedIndex(\n selectedIndex !== null\n ? (selectedIndex + 1) % options.length\n : 0,\n );\n }\n }}\n >\n \n \n {options.map((option, index) => (\n {\n selectOptionAndCleanUp(option);\n }}\n className={`flex items-center gap-2 ${\n selectedIndex === index\n ? \"bg-accent\"\n : \"!bg-transparent\"\n }`}\n >\n {option.picture}\n {option.name}\n \n ))}\n \n \n \n ,\n anchorElementRef.current,\n )\n : null;\n }}\n />\n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/picker/alignment-picker-plugin.tsx", "content": "import { FORMAT_ELEMENT_COMMAND } from \"lexical\";\n\nimport {\n AlignCenterIcon,\n AlignJustifyIcon,\n AlignLeftIcon,\n AlignRightIcon,\n} from \"lucide-react\";\n\nimport { ComponentPickerOption } from \"@/components/editor/plugins/picker/component-picker-option\";\n\nexport function AlignmentPickerPlugin({\n alignment,\n}: {\n alignment: \"left\" | \"center\" | \"right\" | \"justify\";\n}) {\n return new ComponentPickerOption(`Align ${alignment}`, {\n icon: ,\n keywords: [\"align\", \"justify\", alignment],\n onSelect: (_, editor) =>\n editor.dispatchCommand(FORMAT_ELEMENT_COMMAND, alignment),\n });\n}\n\nfunction AlignIcons({\n alignment,\n}: {\n alignment: \"left\" | \"center\" | \"right\" | \"justify\";\n}) {\n switch (alignment) {\n case \"left\":\n return ;\n case \"center\":\n return ;\n case \"right\":\n return ;\n case \"justify\":\n return ;\n }\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/picker/bulleted-list-picker-plugin.tsx", "content": "import { INSERT_UNORDERED_LIST_COMMAND } from \"@lexical/list\";\n\nimport { ListIcon } from \"lucide-react\";\n\nimport { ComponentPickerOption } from \"@/components/editor/plugins/picker/component-picker-option\";\n\nexport function BulletedListPickerPlugin() {\n return new ComponentPickerOption(\"Bulleted List\", {\n icon: ,\n keywords: [\"bulleted list\", \"unordered list\", \"ul\"],\n onSelect: (_, editor) =>\n editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined),\n });\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/picker/check-list-picker-plugin.tsx", "content": "import { INSERT_CHECK_LIST_COMMAND } from \"@lexical/list\";\n\nimport { ListTodoIcon } from \"lucide-react\";\n\nimport { ComponentPickerOption } from \"@/components/editor/plugins/picker/component-picker-option\";\n\nexport function CheckListPickerPlugin() {\n return new ComponentPickerOption(\"Check List\", {\n icon: ,\n keywords: [\"check list\", \"todo list\"],\n onSelect: (_, editor) =>\n editor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined),\n });\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/picker/code-picker-plugin.tsx", "content": "import { $createCodeNode } from \"@lexical/code\";\nimport { $setBlocksType } from \"@lexical/selection\";\nimport { $getSelection, $isRangeSelection } from \"lexical\";\n\nimport { CodeIcon } from \"lucide-react\";\n\nimport { ComponentPickerOption } from \"@/components/editor/plugins/picker/component-picker-option\";\n\nexport function CodePickerPlugin() {\n return new ComponentPickerOption(\"Code\", {\n icon: ,\n keywords: [\"javascript\", \"python\", \"js\", \"codeblock\"],\n onSelect: (_, editor) =>\n editor.update(() => {\n const selection = $getSelection();\n\n if ($isRangeSelection(selection)) {\n if (selection.isCollapsed()) {\n $setBlocksType(selection, () => $createCodeNode());\n } else {\n // Will this ever happen?\n const textContent = selection.getTextContent();\n const codeNode = $createCodeNode();\n selection.insertNodes([codeNode]);\n selection.insertRawText(textContent);\n }\n }\n }),\n });\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/picker/columns-layout-picker-plugin.tsx", "content": "import { Columns3Icon } from \"lucide-react\";\n\nimport { InsertLayoutDialog } from \"@/components/editor/plugins/layout-plugin\";\nimport { ComponentPickerOption } from \"@/components/editor/plugins/picker/component-picker-option\";\n\nexport function ColumnsLayoutPickerPlugin() {\n return new ComponentPickerOption(\"Columns Layout\", {\n icon: ,\n keywords: [\"columns\", \"layout\", \"grid\"],\n onSelect: (_, editor, showModal) =>\n showModal(\"Insert Columns Layout\", (onClose) => (\n \n )),\n });\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/picker/component-picker-option.tsx", "content": "import { type JSX } from \"react\";\n\nimport { MenuOption } from \"@lexical/react/LexicalTypeaheadMenuPlugin\";\nimport { type LexicalEditor } from \"lexical\";\n\nexport class ComponentPickerOption extends MenuOption {\n // What shows up in the editor\n title: string;\n // Icon for display\n icon?: JSX.Element;\n // For extra searching.\n keywords: Array;\n // TBD\n keyboardShortcut?: string;\n // What happens when you select this option?\n onSelect: (\n queryString: string,\n editor: LexicalEditor,\n showModal: (\n title: string,\n showModal: (onClose: () => void) => JSX.Element,\n ) => void,\n ) => void;\n\n constructor(\n title: string,\n options: {\n icon?: JSX.Element;\n keywords?: Array;\n keyboardShortcut?: string;\n onSelect: (\n queryString: string,\n editor: LexicalEditor,\n showModal: (\n title: string,\n showModal: (onClose: () => void) => JSX.Element,\n ) => void,\n ) => void;\n },\n ) {\n super(title);\n this.title = title;\n this.keywords = options.keywords || [];\n this.icon = options.icon;\n this.keyboardShortcut = options.keyboardShortcut;\n this.onSelect = options.onSelect.bind(this);\n }\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/picker/date-time-picker-plugin.tsx", "content": "import { CalendarIcon } from \"lucide-react\";\n\nimport { INSERT_DATETIME_COMMAND } from \"@/components/editor/extensions/date-time-extension\";\nimport { ComponentPickerOption } from \"@/components/editor/plugins/picker/component-picker-option\";\n\nexport function DateTimePickerPlugin() {\n return new ComponentPickerOption(\"Date\", {\n icon: ,\n keywords: [\"date\", \"calendar\", \"time\", \"today\"],\n onSelect: (_, editor) => {\n const dateTime = new Date();\n dateTime.setHours(0, 0, 0, 0); // Set time to midnight\n editor.dispatchCommand(INSERT_DATETIME_COMMAND, { dateTime });\n },\n });\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/picker/divider-picker-plugin.tsx", "content": "import { INSERT_HORIZONTAL_RULE_COMMAND } from \"@lexical/react/LexicalHorizontalRuleNode\";\n\nimport { MinusIcon } from \"lucide-react\";\n\nimport { ComponentPickerOption } from \"@/components/editor/plugins/picker/component-picker-option\";\n\nexport function DividerPickerPlugin() {\n return new ComponentPickerOption(\"Divider\", {\n icon: ,\n keywords: [\"horizontal rule\", \"divider\", \"hr\"],\n onSelect: (_, editor) =>\n editor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined),\n });\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/picker/embeds-picker-plugin.tsx", "content": "import { INSERT_EMBED_COMMAND } from \"@lexical/react/LexicalAutoEmbedPlugin\";\n\nimport {\n type CustomEmbedConfig,\n EmbedConfigs,\n} from \"@/components/editor/plugins/embeds/auto-embed-plugin\";\nimport { ComponentPickerOption } from \"@/components/editor/plugins/picker/component-picker-option\";\n\nexport function EmbedsPickerPlugin({\n embed,\n}: {\n embed: \"tweet\" | \"youtube-video\";\n}) {\n const embedConfig = EmbedConfigs.find(\n (config) => config.type === embed,\n ) as CustomEmbedConfig;\n\n return new ComponentPickerOption(`Embed ${embedConfig.contentName}`, {\n icon: embedConfig.icon,\n keywords: [...embedConfig.keywords, \"embed\"],\n onSelect: (_, editor) =>\n editor.dispatchCommand(INSERT_EMBED_COMMAND, embedConfig.type),\n });\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/picker/heading-picker-plugin.tsx", "content": "import { $createHeadingNode } from \"@lexical/rich-text\";\nimport { $setBlocksType } from \"@lexical/selection\";\nimport { $getSelection, $isRangeSelection } from \"lexical\";\n\nimport { Heading1Icon, Heading2Icon, Heading3Icon } from \"lucide-react\";\n\nimport { ComponentPickerOption } from \"@/components/editor/plugins/picker/component-picker-option\";\n\nexport function HeadingPickerPlugin({ n }: { n: 1 | 2 | 3 }) {\n return new ComponentPickerOption(`Heading ${n}`, {\n icon: ,\n keywords: [\"heading\", \"header\", `h${n}`],\n onSelect: (_, editor) =>\n editor.update(() => {\n const selection = $getSelection();\n if ($isRangeSelection(selection)) {\n $setBlocksType(selection, () => $createHeadingNode(`h${n}`));\n }\n }),\n });\n}\n\nfunction HeadingIcons({ n }: { n: number }) {\n switch (n) {\n case 1:\n return ;\n case 2:\n return ;\n case 3:\n return ;\n }\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/picker/image-picker-plugin.tsx", "content": "import { ImageIcon } from \"lucide-react\";\n\nimport { InsertImageDialog } from \"@/components/editor/extensions/images-extension\";\nimport { ComponentPickerOption } from \"@/components/editor/plugins/picker/component-picker-option\";\n\nexport function ImagePickerPlugin() {\n return new ComponentPickerOption(\"Image\", {\n icon: ,\n keywords: [\"image\", \"photo\", \"picture\", \"file\"],\n onSelect: (_, editor, showModal) =>\n showModal(\"Insert Image\", (onClose) => (\n \n )),\n });\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/picker/numbered-list-picker-plugin.tsx", "content": "import { INSERT_ORDERED_LIST_COMMAND } from \"@lexical/list\";\n\nimport { ListOrderedIcon } from \"lucide-react\";\n\nimport { ComponentPickerOption } from \"@/components/editor/plugins/picker/component-picker-option\";\n\nexport function NumberedListPickerPlugin() {\n return new ComponentPickerOption(\"Numbered List\", {\n icon: ,\n keywords: [\"numbered list\", \"ordered list\", \"ol\"],\n onSelect: (_, editor) =>\n editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined),\n });\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/picker/paragraph-picker-plugin.tsx", "content": "import { $setBlocksType } from \"@lexical/selection\";\nimport {\n $createParagraphNode,\n $getSelection,\n $isRangeSelection,\n} from \"lexical\";\n\nimport { TextIcon } from \"lucide-react\";\n\nimport { ComponentPickerOption } from \"@/components/editor/plugins/picker/component-picker-option\";\n\nexport function ParagraphPickerPlugin() {\n return new ComponentPickerOption(\"Paragraph\", {\n icon: ,\n keywords: [\"normal\", \"paragraph\", \"p\", \"text\"],\n onSelect: (_, editor) =>\n editor.update(() => {\n const selection = $getSelection();\n if ($isRangeSelection(selection)) {\n $setBlocksType(selection, () => $createParagraphNode());\n }\n }),\n });\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/picker/quote-picker-plugin.tsx", "content": "import { $createQuoteNode } from \"@lexical/rich-text\";\nimport { $setBlocksType } from \"@lexical/selection\";\nimport { $getSelection, $isRangeSelection } from \"lexical\";\n\nimport { QuoteIcon } from \"lucide-react\";\n\nimport { ComponentPickerOption } from \"@/components/editor/plugins/picker/component-picker-option\";\n\nexport function QuotePickerPlugin() {\n return new ComponentPickerOption(\"Quote\", {\n icon: ,\n keywords: [\"block quote\"],\n onSelect: (_, editor) =>\n editor.update(() => {\n const selection = $getSelection();\n if ($isRangeSelection(selection)) {\n $setBlocksType(selection, () => $createQuoteNode());\n }\n }),\n });\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/picker/table-picker-plugin.tsx", "content": "import { INSERT_TABLE_COMMAND } from \"@lexical/table\";\n\nimport { TableIcon } from \"lucide-react\";\n\nimport { ComponentPickerOption } from \"@/components/editor/plugins/picker/component-picker-option\";\nimport { InsertTableDialog } from \"@/components/editor/plugins/table-plugin\";\n\nexport function TablePickerPlugin() {\n return new ComponentPickerOption(\"Table\", {\n icon: ,\n keywords: [\"table\", \"grid\", \"spreadsheet\", \"rows\", \"columns\"],\n onSelect: (_, editor, showModal) =>\n showModal(\"Insert Table\", (onClose) => (\n \n )),\n });\n}\n\nexport function DynamicTablePickerPlugin({\n queryString,\n}: {\n queryString: string;\n}) {\n const options: Array = [];\n\n if (queryString == null) {\n return options;\n }\n\n const tableMatch = queryString.match(/^([1-9]\\d?)(?:x([1-9]\\d?)?)?$/);\n\n if (tableMatch !== null) {\n const rows = tableMatch[1];\n const colOptions = tableMatch[2]\n ? [tableMatch[2]]\n : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(String);\n\n options.push(\n ...colOptions.map(\n (columns) =>\n new ComponentPickerOption(`${rows}x${columns} Table`, {\n icon: ,\n keywords: [\"table\"],\n onSelect: (_, editor) =>\n editor.dispatchCommand(INSERT_TABLE_COMMAND, { columns, rows }),\n }),\n ),\n );\n }\n\n return options;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/special-text-plugin.tsx", "content": "import type { JSX } from \"react\";\nimport { useEffect } from \"react\";\n\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport type { LexicalEditor } from \"lexical\";\nimport { TextNode } from \"lexical\";\n\nimport {\n $createSpecialTextNode,\n SpecialTextNode,\n} from \"@/components/editor/nodes/special-text-node\";\n\nconst BRACKETED_TEXT_REGEX = /\\[([^\\[\\]]+)\\]/; // eslint-disable-line\n\nfunction $findAndTransformText(node: TextNode): null | TextNode {\n const text = node.getTextContent();\n\n const match = BRACKETED_TEXT_REGEX.exec(text);\n if (match) {\n const matchedText = match[1];\n const startIndex = match.index;\n\n let targetNode;\n if (startIndex === 0) {\n [targetNode] = node.splitText(startIndex + match[0].length);\n } else {\n [, targetNode] = node.splitText(startIndex, startIndex + match[0].length);\n }\n\n const specialTextNode = $createSpecialTextNode(matchedText);\n targetNode.replace(specialTextNode);\n return specialTextNode;\n }\n\n return null;\n}\n\nfunction $textNodeTransform(node: TextNode): void {\n let targetNode: TextNode | null = node;\n\n while (targetNode !== null) {\n if (!targetNode.isSimpleText()) {\n return;\n }\n\n targetNode = $findAndTransformText(targetNode);\n }\n}\n\nfunction useTextTransformation(editor: LexicalEditor): void {\n useEffect(() => {\n if (!editor.hasNodes([SpecialTextNode])) {\n throw new Error(\n \"SpecialTextPlugin: SpecialTextNode not registered on editor\",\n );\n }\n\n return editor.registerNodeTransform(TextNode, $textNodeTransform);\n }, [editor]);\n}\n\nexport function SpecialTextPlugin(): JSX.Element | null {\n const [editor] = useLexicalComposerContext();\n useTextTransformation(editor);\n return null;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/tab-focus-plugin.tsx", "content": "import { useEffect } from \"react\";\n\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport {\n $getSelection,\n $isRangeSelection,\n $setSelection,\n FOCUS_COMMAND,\n} from \"lexical\";\n\nconst COMMAND_PRIORITY_LOW = 1;\nconst TAB_TO_FOCUS_INTERVAL = 100;\n\nlet lastTabKeyDownTimestamp = 0;\nlet hasRegisteredKeyDownListener = false;\n\nfunction registerKeyTimeStampTracker() {\n window.addEventListener(\n \"keydown\",\n (event: KeyboardEvent) => {\n // Tab\n if (event.key === \"Tab\") {\n lastTabKeyDownTimestamp = event.timeStamp;\n }\n },\n true,\n );\n}\n\nexport function TabFocusPlugin(): null {\n const [editor] = useLexicalComposerContext();\n\n useEffect(() => {\n if (!hasRegisteredKeyDownListener) {\n registerKeyTimeStampTracker();\n hasRegisteredKeyDownListener = true;\n }\n\n return editor.registerCommand(\n FOCUS_COMMAND,\n (event: FocusEvent) => {\n const selection = $getSelection();\n if ($isRangeSelection(selection)) {\n if (\n lastTabKeyDownTimestamp + TAB_TO_FOCUS_INTERVAL >\n event.timeStamp\n ) {\n $setSelection(selection.clone());\n }\n }\n return false;\n },\n COMMAND_PRIORITY_LOW,\n );\n }, [editor]);\n\n return null;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/table-plugin.tsx", "content": "/**\n * Copyright (c) Meta Platforms, Inc. and affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n *\n */\n\nimport type { JSX } from \"react\";\n\nimport { INSERT_TABLE_COMMAND } from \"@lexical/table\";\nimport { type LexicalEditor } from \"lexical\";\nimport { useEffect, useState } from \"react\";\n\nimport { Button } from \"@/components/ui/button\";\nimport { DialogFooter } from \"@/components/ui/dialog\";\nimport { Input } from \"@/components/ui/input\";\nimport { Label } from \"@/components/ui/label\";\n\nexport function InsertTableDialog({\n activeEditor,\n onClose,\n}: {\n activeEditor: LexicalEditor;\n onClose: () => void;\n}): JSX.Element {\n const [rows, setRows] = useState(\"5\");\n const [columns, setColumns] = useState(\"5\");\n const [isDisabled, setIsDisabled] = useState(true);\n\n useEffect(() => {\n const row = Number(rows);\n const column = Number(columns);\n if (row && row > 0 && row <= 500 && column && column > 0 && column <= 50) {\n setIsDisabled(false);\n } else {\n setIsDisabled(true);\n }\n }, [rows, columns]);\n\n const onClick = () => {\n activeEditor.dispatchCommand(INSERT_TABLE_COMMAND, {\n columns,\n rows,\n });\n\n onClose();\n };\n\n return (\n <>\n \n Rows\n setRows(e.target.value)}\n value={rows}\n data-test-id=\"table-modal-rows\"\n type=\"number\"\n />\n \n \n Columns\n setColumns(e.target.value)}\n value={columns}\n data-test-id=\"table-modal-columns\"\n type=\"number\"\n />\n \n \n \n Confirm\n \n \n >\n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/block-format/block-format-data.tsx", "content": "import {\n CodeIcon,\n Heading1Icon,\n Heading2Icon,\n Heading3Icon,\n ListIcon,\n ListOrderedIcon,\n ListTodoIcon,\n QuoteIcon,\n TextIcon,\n} from \"lucide-react\";\n\nexport const blockTypeToBlockName: Record<\n string,\n { label: string; icon: React.ReactNode }\n> = {\n paragraph: {\n label: \"Paragraph\",\n icon: ,\n },\n h1: {\n label: \"Heading 1\",\n icon: ,\n },\n h2: {\n label: \"Heading 2\",\n icon: ,\n },\n h3: {\n label: \"Heading 3\",\n icon: ,\n },\n number: {\n label: \"Numbered List\",\n icon: ,\n },\n bullet: {\n label: \"Bulleted List\",\n icon: ,\n },\n check: {\n label: \"Check List\",\n icon: ,\n },\n code: {\n label: \"Code Block\",\n icon: ,\n },\n quote: {\n label: \"Quote\",\n icon: ,\n },\n};\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/block-format/format-bulleted-list.tsx", "content": "import { INSERT_UNORDERED_LIST_COMMAND } from \"@lexical/list\";\nimport { $setBlocksType } from \"@lexical/selection\";\nimport {\n $createParagraphNode,\n $getSelection,\n $isRangeSelection,\n} from \"lexical\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { blockTypeToBlockName } from \"@/components/editor/plugins/toolbar/block-format/block-format-data\";\nimport { DropdownMenuItem } from \"@/components/ui/dropdown-menu\";\n\nconst BLOCK_FORMAT_VALUE = \"bullet\";\n\nexport function FormatBulletedList() {\n const { activeEditor, blockType } = useToolbarContext();\n\n const formatParagraph = () => {\n activeEditor.update(() => {\n const selection = $getSelection();\n if ($isRangeSelection(selection)) {\n $setBlocksType(selection, () => $createParagraphNode());\n }\n });\n };\n\n const formatBulletedList = () => {\n if (blockType !== \"bullet\") {\n activeEditor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND, undefined);\n } else {\n formatParagraph();\n }\n };\n\n return (\n \n \n {blockTypeToBlockName[BLOCK_FORMAT_VALUE].icon}\n {blockTypeToBlockName[BLOCK_FORMAT_VALUE].label}\n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/block-format/format-check-list.tsx", "content": "import { INSERT_CHECK_LIST_COMMAND } from \"@lexical/list\";\nimport { $setBlocksType } from \"@lexical/selection\";\nimport {\n $createParagraphNode,\n $getSelection,\n $isRangeSelection,\n} from \"lexical\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { blockTypeToBlockName } from \"@/components/editor/plugins/toolbar/block-format/block-format-data\";\nimport { DropdownMenuItem } from \"@/components/ui/dropdown-menu\";\n\nconst BLOCK_FORMAT_VALUE = \"check\";\n\nexport function FormatCheckList() {\n const { activeEditor, blockType } = useToolbarContext();\n\n const formatParagraph = () => {\n activeEditor.update(() => {\n const selection = $getSelection();\n if ($isRangeSelection(selection)) {\n $setBlocksType(selection, () => $createParagraphNode());\n }\n });\n };\n\n const formatCheckList = () => {\n if (blockType !== \"check\") {\n activeEditor.dispatchCommand(INSERT_CHECK_LIST_COMMAND, undefined);\n } else {\n formatParagraph();\n }\n };\n\n return (\n \n \n {blockTypeToBlockName[BLOCK_FORMAT_VALUE].icon}\n {blockTypeToBlockName[BLOCK_FORMAT_VALUE].label}\n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/block-format/format-code-block.tsx", "content": "import { $createCodeNode } from \"@lexical/code\";\nimport { $setBlocksType } from \"@lexical/selection\";\nimport { $getSelection, $isRangeSelection } from \"lexical\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { blockTypeToBlockName } from \"@/components/editor/plugins/toolbar/block-format/block-format-data\";\nimport { DropdownMenuItem } from \"@/components/ui/dropdown-menu\";\n\nconst BLOCK_FORMAT_VALUE = \"code\";\n\nexport function FormatCodeBlock() {\n const { activeEditor, blockType } = useToolbarContext();\n\n const formatCode = () => {\n if (blockType !== \"code\") {\n activeEditor.update(() => {\n let selection = $getSelection();\n\n if (selection !== null) {\n if (selection.isCollapsed()) {\n $setBlocksType(selection, () => $createCodeNode());\n } else {\n const textContent = selection.getTextContent();\n const codeNode = $createCodeNode();\n selection.insertNodes([codeNode]);\n selection = $getSelection();\n if ($isRangeSelection(selection)) {\n selection.insertRawText(textContent);\n }\n }\n }\n });\n }\n };\n\n return (\n \n \n {blockTypeToBlockName[BLOCK_FORMAT_VALUE].icon}\n {blockTypeToBlockName[BLOCK_FORMAT_VALUE].label}\n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/block-format/format-heading.tsx", "content": "import { $createHeadingNode, type HeadingTagType } from \"@lexical/rich-text\";\nimport { $setBlocksType } from \"@lexical/selection\";\nimport {\n $getRoot,\n $getSelection,\n $isElementNode,\n $isRangeSelection,\n} from \"lexical\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { blockTypeToBlockName } from \"@/components/editor/plugins/toolbar/block-format/block-format-data\";\nimport { DropdownMenuItem } from \"@/components/ui/dropdown-menu\";\n\nexport function FormatHeading({ levels = [] }: { levels: HeadingTagType[] }) {\n const { activeEditor, blockType } = useToolbarContext();\n\n const formatHeading = (headingSize: HeadingTagType) => {\n if (blockType !== headingSize) {\n activeEditor.focus();\n activeEditor.update(() => {\n const selection = $getSelection();\n const root = $getRoot();\n const anchorKey = $isRangeSelection(selection)\n ? selection.anchor.key\n : null;\n const noUsableSelection =\n !$isRangeSelection(selection) || anchorKey === root.getKey();\n\n if (noUsableSelection) {\n const heading = $createHeadingNode(headingSize);\n const firstChild = root.getFirstChild();\n if (\n firstChild &&\n $isElementNode(firstChild) &&\n firstChild.isEmpty()\n ) {\n firstChild.replace(heading);\n } else {\n root.append(heading);\n }\n heading.select();\n return;\n }\n\n $setBlocksType(selection, () => $createHeadingNode(headingSize));\n });\n }\n };\n\n return levels.map((level) => (\n formatHeading(level)}>\n \n {blockTypeToBlockName[level].icon}\n {blockTypeToBlockName[level].label}\n \n \n ));\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/block-format/format-numbered-list.tsx", "content": "import { INSERT_ORDERED_LIST_COMMAND } from \"@lexical/list\";\nimport { $setBlocksType } from \"@lexical/selection\";\nimport {\n $createParagraphNode,\n $getSelection,\n $isRangeSelection,\n} from \"lexical\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { blockTypeToBlockName } from \"@/components/editor/plugins/toolbar/block-format/block-format-data\";\nimport { DropdownMenuItem } from \"@/components/ui/dropdown-menu\";\n\nconst BLOCK_FORMAT_VALUE = \"number\";\n\nexport function FormatNumberedList() {\n const { activeEditor, blockType } = useToolbarContext();\n\n const formatParagraph = () => {\n activeEditor.update(() => {\n const selection = $getSelection();\n if ($isRangeSelection(selection)) {\n $setBlocksType(selection, () => $createParagraphNode());\n }\n });\n };\n\n const formatNumberedList = () => {\n if (blockType !== \"number\") {\n activeEditor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND, undefined);\n } else {\n formatParagraph();\n }\n };\n\n return (\n \n \n {blockTypeToBlockName[BLOCK_FORMAT_VALUE].icon}\n {blockTypeToBlockName[BLOCK_FORMAT_VALUE].label}\n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/block-format/format-paragraph.tsx", "content": "import { $setBlocksType } from \"@lexical/selection\";\nimport {\n $createParagraphNode,\n $getSelection,\n $isRangeSelection,\n} from \"lexical\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { blockTypeToBlockName } from \"@/components/editor/plugins/toolbar/block-format/block-format-data\";\nimport { DropdownMenuItem } from \"@/components/ui/dropdown-menu\";\n\nconst BLOCK_FORMAT_VALUE = \"paragraph\";\n\nexport function FormatParagraph() {\n const { activeEditor } = useToolbarContext();\n\n const formatParagraph = () => {\n activeEditor.update(() => {\n const selection = $getSelection();\n if ($isRangeSelection(selection)) {\n $setBlocksType(selection, () => $createParagraphNode());\n }\n });\n };\n\n return (\n \n \n {blockTypeToBlockName[BLOCK_FORMAT_VALUE].icon}\n {blockTypeToBlockName[BLOCK_FORMAT_VALUE].label}\n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/block-format/format-quote.tsx", "content": "import { $createQuoteNode } from \"@lexical/rich-text\";\nimport { $setBlocksType } from \"@lexical/selection\";\nimport { $getSelection } from \"lexical\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { blockTypeToBlockName } from \"@/components/editor/plugins/toolbar/block-format/block-format-data\";\nimport { DropdownMenuItem } from \"@/components/ui/dropdown-menu\";\n\nconst BLOCK_FORMAT_VALUE = \"quote\";\n\nexport function FormatQuote() {\n const { activeEditor, blockType } = useToolbarContext();\n\n const formatQuote = () => {\n if (blockType !== \"quote\") {\n activeEditor.update(() => {\n const selection = $getSelection();\n $setBlocksType(selection, () => $createQuoteNode());\n });\n }\n };\n\n return (\n \n \n {blockTypeToBlockName[BLOCK_FORMAT_VALUE].icon}\n {blockTypeToBlockName[BLOCK_FORMAT_VALUE].label}\n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/block-format-toolbar-plugin.tsx", "content": "import { $isListNode, ListNode } from \"@lexical/list\";\nimport { $isHeadingNode } from \"@lexical/rich-text\";\nimport { $findMatchingParent, $getNearestNodeOfType } from \"@lexical/utils\";\nimport {\n $isRangeSelection,\n $isRootOrShadowRoot,\n type BaseSelection,\n} from \"lexical\";\n\nimport { ChevronDownIcon } from \"lucide-react\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { useUpdateToolbarHandler } from \"@/components/editor/editor-hooks/use-update-toolbar\";\nimport { blockTypeToBlockName } from \"@/components/editor/plugins/toolbar/block-format/block-format-data\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\n\nexport function BlockFormatDropDown({\n children,\n}: {\n children: React.ReactNode;\n}) {\n const { activeEditor, blockType, setBlockType } = useToolbarContext();\n\n function $updateToolbar(selection: BaseSelection) {\n if ($isRangeSelection(selection)) {\n const anchorNode = selection.anchor.getNode();\n let element =\n anchorNode.getKey() === \"root\"\n ? anchorNode\n : $findMatchingParent(anchorNode, (e) => {\n const parent = e.getParent();\n return parent !== null && $isRootOrShadowRoot(parent);\n });\n\n if (element === null) {\n element = anchorNode.getTopLevelElementOrThrow();\n }\n\n const elementKey = element.getKey();\n const elementDOM = activeEditor.getElementByKey(elementKey);\n\n if (elementDOM !== null) {\n if ($isListNode(element)) {\n const parentList = $getNearestNodeOfType(\n anchorNode,\n ListNode,\n );\n const type = parentList\n ? parentList.getListType()\n : element.getListType();\n setBlockType(type);\n } else {\n const type = $isHeadingNode(element)\n ? element.getTag()\n : element.getType();\n if (type in blockTypeToBlockName) {\n setBlockType(type as keyof typeof blockTypeToBlockName);\n }\n }\n }\n }\n }\n\n useUpdateToolbarHandler($updateToolbar);\n\n const { label, icon } =\n blockTypeToBlockName[blockType] ?? blockTypeToBlockName.paragraph;\n\n return (\n \n \n \n {icon}\n {label}\n \n \n \n {children}\n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/block-insert/insert-columns-layout.tsx", "content": "import { Columns3Icon } from \"lucide-react\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { InsertLayoutDialog } from \"@/components/editor/plugins/layout-plugin\";\nimport { DropdownMenuItem } from \"@/components/ui/dropdown-menu\";\n\nexport function InsertColumnsLayout() {\n const { activeEditor, showModal } = useToolbarContext();\n\n return (\n \n showModal(\"Insert Columns Layout\", (onClose) => (\n \n ))\n }\n >\n \n \n Columns Layout\n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/block-insert/insert-embeds.tsx", "content": "import { INSERT_EMBED_COMMAND } from \"@lexical/react/LexicalAutoEmbedPlugin\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { EmbedConfigs } from \"@/components/editor/plugins/embeds/auto-embed-plugin\";\nimport { DropdownMenuItem } from \"@/components/ui/dropdown-menu\";\n\nexport function InsertEmbeds() {\n const { activeEditor } = useToolbarContext();\n return EmbedConfigs.map((embedConfig) => (\n {\n activeEditor.dispatchCommand(INSERT_EMBED_COMMAND, embedConfig.type);\n }}\n >\n \n {embedConfig.icon}\n {embedConfig.contentName}\n \n \n ));\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/block-insert/insert-horizontal-rule.tsx", "content": "import { INSERT_HORIZONTAL_RULE_COMMAND } from \"@lexical/react/LexicalHorizontalRuleNode\";\n\nimport { ScissorsIcon } from \"lucide-react\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { DropdownMenuItem } from \"@/components/ui/dropdown-menu\";\n\nexport function InsertHorizontalRule() {\n const { activeEditor } = useToolbarContext();\n\n return (\n \n activeEditor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined)\n }\n >\n \n \n Horizontal Rule\n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/block-insert/insert-image.tsx", "content": "import { ImageIcon } from \"lucide-react\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { InsertImageDialog } from \"@/components/editor/extensions/images-extension\";\nimport { DropdownMenuItem } from \"@/components/ui/dropdown-menu\";\n\nexport function InsertImage() {\n const { activeEditor, showModal } = useToolbarContext();\n\n return (\n {\n showModal(\"Insert Image\", (onClose) => (\n \n ));\n }}\n >\n \n \n Image\n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/block-insert/insert-table.tsx", "content": "import { TableIcon } from \"lucide-react\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { InsertTableDialog } from \"@/components/editor/plugins/table-plugin\";\nimport { DropdownMenuItem } from \"@/components/ui/dropdown-menu\";\n\nexport function InsertTable() {\n const { activeEditor, showModal } = useToolbarContext();\n\n return (\n \n showModal(\"Insert Table\", (onClose) => (\n \n ))\n }\n >\n \n \n Table\n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/block-insert-plugin.tsx", "content": "import { PlusIcon } from \"lucide-react\";\n\nimport { useEditorModal } from \"@/components/editor/editor-hooks/use-modal\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\n\nexport function BlockInsertPlugin({ children }: { children: React.ReactNode }) {\n const [modal] = useEditorModal();\n\n return (\n <>\n {modal}\n \n \n \n \n Insert\n \n \n {children}\n \n >\n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/clear-formatting-toolbar-plugin.tsx", "content": "import { useCallback } from \"react\";\n\nimport { $isDecoratorBlockNode } from \"@lexical/react/LexicalDecoratorBlockNode\";\nimport { $isHeadingNode, $isQuoteNode } from \"@lexical/rich-text\";\nimport { $isTableSelection } from \"@lexical/table\";\nimport { $getNearestBlockElementAncestorOrThrow } from \"@lexical/utils\";\nimport {\n $createParagraphNode,\n $getSelection,\n $isRangeSelection,\n $isTextNode,\n} from \"lexical\";\n\nimport { EraserIcon } from \"lucide-react\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { Button } from \"@/components/ui/button\";\n\nexport function ClearFormattingToolbarPlugin() {\n const { activeEditor } = useToolbarContext();\n\n const clearFormatting = useCallback(() => {\n activeEditor.update(() => {\n const selection = $getSelection();\n if ($isRangeSelection(selection) || $isTableSelection(selection)) {\n const anchor = selection.anchor;\n const focus = selection.focus;\n const nodes = selection.getNodes();\n const extractedNodes = selection.extract();\n\n if (anchor.key === focus.key && anchor.offset === focus.offset) {\n return;\n }\n\n nodes.forEach((node, idx) => {\n // We split the first and last node by the selection\n // So that we don't format unselected text inside those nodes\n if ($isTextNode(node)) {\n // Use a separate variable to ensure TS does not lose the refinement\n let textNode = node;\n if (idx === 0 && anchor.offset !== 0) {\n textNode = textNode.splitText(anchor.offset)[1] || textNode;\n }\n if (idx === nodes.length - 1) {\n textNode = textNode.splitText(focus.offset)[0] || textNode;\n }\n /**\n * If the selected text has one format applied\n * selecting a portion of the text, could\n * clear the format to the wrong portion of the text.\n *\n * The cleared text is based on the length of the selected text.\n */\n // We need this in case the selected text only has one format\n const extractedTextNode = extractedNodes[0];\n if (nodes.length === 1 && $isTextNode(extractedTextNode)) {\n textNode = extractedTextNode;\n }\n\n if (textNode.__style !== \"\") {\n textNode.setStyle(\"\");\n }\n if (textNode.__format !== 0) {\n textNode.setFormat(0);\n $getNearestBlockElementAncestorOrThrow(textNode).setFormat(\"\");\n }\n node = textNode;\n } else if ($isHeadingNode(node) || $isQuoteNode(node)) {\n node.replace($createParagraphNode(), true);\n } else if ($isDecoratorBlockNode(node)) {\n node.setFormat(\"\");\n }\n });\n }\n });\n }, [activeEditor]);\n\n return (\n \n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/code-language-toolbar-plugin.tsx", "content": "import { useCallback, useState } from \"react\";\n\nimport { $isCodeNode } from \"@lexical/code\";\nimport {\n CODE_LANGUAGE_FRIENDLY_NAME_MAP,\n CODE_LANGUAGE_MAP,\n // getLanguageFriendlyName,\n} from \"@lexical/code-prism\";\nimport { $isListNode } from \"@lexical/list\";\nimport { $findMatchingParent } from \"@lexical/utils\";\nimport {\n $getNodeByKey,\n $isRangeSelection,\n $isRootOrShadowRoot,\n type BaseSelection,\n} from \"lexical\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { useUpdateToolbarHandler } from \"@/components/editor/editor-hooks/use-update-toolbar\";\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@/components/ui/select\";\n\nfunction getCodeLanguageOptions(): [string, string][] {\n const options: [string, string][] = [];\n\n for (const [lang, friendlyName] of Object.entries(\n CODE_LANGUAGE_FRIENDLY_NAME_MAP,\n )) {\n options.push([lang, friendlyName]);\n }\n\n return options;\n}\n\nconst CODE_LANGUAGE_OPTIONS = getCodeLanguageOptions();\n\nexport function CodeLanguageToolbarPlugin() {\n const { activeEditor } = useToolbarContext();\n const [codeLanguage, setCodeLanguage] = useState(\"\");\n const [selectedElementKey, setSelectedElementKey] = useState(\n null,\n );\n\n const $updateToolbar = (selection: BaseSelection) => {\n if ($isRangeSelection(selection)) {\n const anchorNode = selection.anchor.getNode();\n let element =\n anchorNode.getKey() === \"root\"\n ? anchorNode\n : $findMatchingParent(anchorNode, (e) => {\n const parent = e.getParent();\n return parent !== null && $isRootOrShadowRoot(parent);\n });\n\n if (element === null) {\n element = anchorNode.getTopLevelElementOrThrow();\n }\n\n const elementKey = element.getKey();\n const elementDOM = activeEditor.getElementByKey(elementKey);\n\n if (elementDOM !== null) {\n setSelectedElementKey(elementKey);\n\n if (!$isListNode(element) && $isCodeNode(element)) {\n const language =\n element.getLanguage() as keyof typeof CODE_LANGUAGE_MAP;\n setCodeLanguage(\n language ? CODE_LANGUAGE_MAP[language] || language : \"\",\n );\n return;\n }\n }\n }\n };\n\n useUpdateToolbarHandler($updateToolbar);\n\n const onCodeLanguageSelect = useCallback(\n (value: string) => {\n activeEditor.update(() => {\n if (selectedElementKey !== null) {\n const node = $getNodeByKey(selectedElementKey);\n if ($isCodeNode(node)) {\n node.setLanguage(value);\n }\n }\n });\n },\n [activeEditor, selectedElementKey],\n );\n\n return (\n \n e.stopPropagation()}>\n \n \n e.preventDefault()}>\n {CODE_LANGUAGE_OPTIONS.map(([value, label]) => (\n \n {label}\n \n ))}\n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/element-format-toolbar-plugin.tsx", "content": "import { useState } from \"react\";\n\nimport { $isLinkNode } from \"@lexical/link\";\nimport { $findMatchingParent } from \"@lexical/utils\";\nimport {\n $isElementNode,\n $isRangeSelection,\n type BaseSelection,\n type ElementFormatType,\n FORMAT_ELEMENT_COMMAND,\n INDENT_CONTENT_COMMAND,\n OUTDENT_CONTENT_COMMAND,\n} from \"lexical\";\n\nimport {\n AlignCenterIcon,\n AlignJustifyIcon,\n AlignLeftIcon,\n AlignRightIcon,\n IndentDecreaseIcon,\n IndentIncreaseIcon,\n} from \"lucide-react\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { useUpdateToolbarHandler } from \"@/components/editor/editor-hooks/use-update-toolbar\";\nimport { getSelectedNode } from \"@/components/editor/utils/get-selected-node\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { ToggleGroup, ToggleGroupItem } from \"@/components/ui/toggle-group\";\n\nconst ELEMENT_FORMAT_OPTIONS: {\n [key in Exclude]: {\n icon: React.ReactNode;\n iconRTL: string;\n name: string;\n };\n} = {\n left: {\n icon: ,\n iconRTL: \"left-align\",\n name: \"Left Align\",\n },\n center: {\n icon: ,\n iconRTL: \"center-align\",\n name: \"Center Align\",\n },\n right: {\n icon: ,\n iconRTL: \"right-align\",\n name: \"Right Align\",\n },\n justify: {\n icon: ,\n iconRTL: \"justify-align\",\n name: \"Justify Align\",\n },\n} as const;\n\nexport function ElementFormatToolbarPlugin({\n separator = true,\n}: {\n separator?: boolean;\n}) {\n const { activeEditor } = useToolbarContext();\n const [elementFormat, setElementFormat] = useState(\"left\");\n\n const $updateToolbar = (selection: BaseSelection) => {\n if ($isRangeSelection(selection)) {\n const node = getSelectedNode(selection);\n const parent = node.getParent();\n\n let matchingParent;\n if ($isLinkNode(parent)) {\n // If node is a link, we need to fetch the parent paragraph node to set format\n matchingParent = $findMatchingParent(\n node,\n (parentNode) => $isElementNode(parentNode) && !parentNode.isInline(),\n );\n }\n setElementFormat(\n $isElementNode(matchingParent)\n ? matchingParent.getFormatType()\n : $isElementNode(node)\n ? node.getFormatType()\n : parent?.getFormatType() || \"left\",\n );\n }\n };\n\n useUpdateToolbarHandler($updateToolbar);\n\n const handleValueChange = (value: string) => {\n if (!value) return; // Prevent unselecting current value\n\n setElementFormat(value as ElementFormatType);\n\n if (value === \"indent\") {\n activeEditor.dispatchCommand(INDENT_CONTENT_COMMAND, undefined);\n } else if (value === \"outdent\") {\n activeEditor.dispatchCommand(OUTDENT_CONTENT_COMMAND, undefined);\n } else {\n activeEditor.dispatchCommand(\n FORMAT_ELEMENT_COMMAND,\n value as ElementFormatType,\n );\n }\n };\n\n return (\n <>\n \n {/* Alignment toggles */}\n {Object.entries(ELEMENT_FORMAT_OPTIONS).map(([value, option]) => (\n \n {option.icon}\n \n ))}\n \n {separator && }\n {/* Indentation toggles */}\n \n \n \n \n\n \n \n \n \n >\n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/font-background-toolbar-plugin.tsx", "content": "import { useCallback, useState } from \"react\";\n\nconst getThemeBackgroundColor = () => {\n if (typeof window === \"undefined\") return \"#fff\";\n return (\n getComputedStyle(document.documentElement).backgroundColor || \"#fff\"\n );\n};\n\nimport {\n $getSelectionStyleValueForProperty,\n $patchStyleText,\n} from \"@lexical/selection\";\nimport { $getSelection, $isRangeSelection, type BaseSelection } from \"lexical\";\n\nimport { PaintBucketIcon } from \"lucide-react\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { useUpdateToolbarHandler } from \"@/components/editor/editor-hooks/use-update-toolbar\";\nimport {\n ColorPicker,\n ColorPickerAlphaSlider,\n ColorPickerArea,\n ColorPickerContent,\n ColorPickerEyeDropper,\n ColorPickerFormatSelect,\n ColorPickerHueSlider,\n ColorPickerInput,\n ColorPickerTrigger,\n} from \"@/components/editor/editor-ui/color-picker\";\nimport { Button } from \"@/components/ui/button\";\n\nexport function FontBackgroundToolbarPlugin() {\n const { activeEditor } = useToolbarContext();\n\n const [bgColor, setBgColor] = useState(getThemeBackgroundColor);\n\n const $updateToolbar = (selection: BaseSelection) => {\n if ($isRangeSelection(selection)) {\n setBgColor(\n $getSelectionStyleValueForProperty(\n selection,\n \"background-color\",\n getThemeBackgroundColor(),\n ),\n );\n }\n };\n\n useUpdateToolbarHandler($updateToolbar);\n\n const applyStyleText = useCallback(\n (styles: Record, _skipHistoryStack?: boolean) => {\n activeEditor.update(\n () => {\n const selection = $getSelection();\n activeEditor.setEditable(false);\n if (selection !== null) {\n $patchStyleText(selection, styles);\n }\n },\n { tag: \"historic\" },\n );\n },\n [activeEditor],\n );\n\n const onBgColorSelect = useCallback(\n (value: string) => {\n applyStyleText({ \"background-color\": value }, true);\n },\n [applyStyleText],\n );\n\n return (\n {\n if (!open) {\n activeEditor.setEditable(true);\n activeEditor.focus();\n }\n }}\n >\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/font-color-toolbar-plugin.tsx", "content": "import { useCallback, useState } from \"react\";\n\nconst getThemeForegroundColor = () => {\n if (typeof window === \"undefined\") return \"#000\";\n return (\n getComputedStyle(document.documentElement).color || \"#000\"\n );\n};\n\nimport {\n $getSelectionStyleValueForProperty,\n $patchStyleText,\n} from \"@lexical/selection\";\nimport { $getSelection, $isRangeSelection, type BaseSelection } from \"lexical\";\n\nimport { BaselineIcon } from \"lucide-react\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { useUpdateToolbarHandler } from \"@/components/editor/editor-hooks/use-update-toolbar\";\nimport {\n ColorPicker,\n ColorPickerAlphaSlider,\n ColorPickerArea,\n ColorPickerContent,\n ColorPickerEyeDropper,\n ColorPickerFormatSelect,\n ColorPickerHueSlider,\n ColorPickerInput,\n ColorPickerTrigger,\n} from \"@/components/editor/editor-ui/color-picker\";\nimport { Button } from \"@/components/ui/button\";\n\nexport function FontColorToolbarPlugin() {\n const { activeEditor } = useToolbarContext();\n\n const [fontColor, setFontColor] = useState(getThemeForegroundColor);\n\n const $updateToolbar = (selection: BaseSelection) => {\n if ($isRangeSelection(selection)) {\n setFontColor(\n $getSelectionStyleValueForProperty(\n selection,\n \"color\",\n getThemeForegroundColor(),\n ),\n );\n }\n };\n\n useUpdateToolbarHandler($updateToolbar);\n\n const applyStyleText = useCallback(\n (styles: Record) => {\n activeEditor.update(() => {\n const selection = $getSelection();\n activeEditor.setEditable(false);\n if (selection !== null) {\n $patchStyleText(selection, styles);\n }\n });\n },\n [activeEditor],\n );\n\n const onFontColorSelect = useCallback(\n (value: string) => {\n applyStyleText({ color: value });\n },\n [applyStyleText],\n );\n\n return (\n {\n if (!open) {\n activeEditor.setEditable(true);\n activeEditor.focus();\n }\n }}\n >\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/font-family-toolbar-plugin.tsx", "content": "import { useCallback, useState } from \"react\";\n\nimport {\n $getSelectionStyleValueForProperty,\n $patchStyleText,\n} from \"@lexical/selection\";\nimport { $getSelection, $isRangeSelection, type BaseSelection } from \"lexical\";\n\nimport { ChevronDownIcon, TypeIcon } from \"lucide-react\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { useUpdateToolbarHandler } from \"@/components/editor/editor-hooks/use-update-toolbar\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n} from \"@/components/ui/dropdown-menu\";\n\nconst FONT_FAMILY_OPTIONS = [\n \"Arial\",\n \"Verdana\",\n \"Times New Roman\",\n \"Georgia\",\n \"Courier New\",\n \"Trebuchet MS\",\n];\n\nexport function FontFamilyToolbarPlugin() {\n const style = \"font-family\";\n const [fontFamily, setFontFamily] = useState(\"Arial\");\n\n const { activeEditor } = useToolbarContext();\n\n const $updateToolbar = useCallback((selection: BaseSelection) => {\n if ($isRangeSelection(selection)) {\n setFontFamily(\n $getSelectionStyleValueForProperty(selection, \"font-family\", \"Arial\"),\n );\n }\n }, []);\n\n useUpdateToolbarHandler($updateToolbar);\n\n const handleClick = useCallback(\n (option: string) => {\n activeEditor.update(() => {\n const selection = $getSelection();\n if (selection !== null) {\n $patchStyleText(selection, {\n [style]: option,\n });\n }\n });\n // Selection doesn't move when only inline styles change, so\n // SELECTION_CHANGE_COMMAND won't run — sync label here.\n setFontFamily(option);\n },\n [activeEditor, style],\n );\n\n const buttonAriaLabel = \"Formatting options for font family\";\n\n return (\n \n \n \n \n {fontFamily}\n \n \n \n \n {FONT_FAMILY_OPTIONS.map((option) => (\n handleClick(option)}\n >\n {option}\n \n ))}\n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/font-format-toolbar-plugin.tsx", "content": "import { useCallback, useState } from \"react\";\n\nimport { $isTableSelection } from \"@lexical/table\";\nimport {\n $isRangeSelection,\n type BaseSelection,\n FORMAT_TEXT_COMMAND,\n type TextFormatType,\n} from \"lexical\";\n\nimport {\n BoldIcon,\n ItalicIcon,\n StrikethroughIcon,\n UnderlineIcon,\n} from \"lucide-react\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { useUpdateToolbarHandler } from \"@/components/editor/editor-hooks/use-update-toolbar\";\nimport { ToggleGroup, ToggleGroupItem } from \"@/components/ui/toggle-group\";\n\nconst FORMATS = [\n { format: \"bold\", icon: BoldIcon, label: \"Bold\" },\n { format: \"italic\", icon: ItalicIcon, label: \"Italic\" },\n { format: \"underline\", icon: UnderlineIcon, label: \"Underline\" },\n { format: \"strikethrough\", icon: StrikethroughIcon, label: \"Strikethrough\" },\n] as const;\n\nexport function FontFormatToolbarPlugin() {\n const { activeEditor } = useToolbarContext();\n const [activeFormats, setActiveFormats] = useState([]);\n\n const $updateToolbar = useCallback((selection: BaseSelection) => {\n if ($isRangeSelection(selection) || $isTableSelection(selection)) {\n const formats: string[] = [];\n FORMATS.forEach(({ format }) => {\n if (selection.hasFormat(format as TextFormatType)) {\n formats.push(format);\n }\n });\n setActiveFormats((prev) => {\n // Only update if formats have changed\n if (\n prev.length !== formats.length ||\n !formats.every((f) => prev.includes(f))\n ) {\n return formats;\n }\n return prev;\n });\n }\n }, []);\n\n useUpdateToolbarHandler($updateToolbar);\n\n return (\n \n {FORMATS.map(({ format, icon: Icon, label }) => (\n {\n activeEditor.dispatchCommand(\n FORMAT_TEXT_COMMAND,\n format as TextFormatType,\n );\n }}\n >\n \n \n ))}\n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/font-size-toolbar-plugin.tsx", "content": "import { useCallback, useState } from \"react\";\n\nimport {\n $getSelectionStyleValueForProperty,\n $patchStyleText,\n} from \"@lexical/selection\";\nimport { $getSelection, $isRangeSelection, type BaseSelection } from \"lexical\";\n\nimport { Minus, Plus } from \"lucide-react\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { useUpdateToolbarHandler } from \"@/components/editor/editor-hooks/use-update-toolbar\";\nimport { Button } from \"@/components/ui/button\";\nimport { ButtonGroup } from \"@/components/ui/button-group\";\nimport { Input } from \"@/components/ui/input\";\n\nconst DEFAULT_FONT_SIZE = 16;\nconst MIN_FONT_SIZE = 1;\nconst MAX_FONT_SIZE = 72;\n\nexport function FontSizeToolbarPlugin() {\n const style = \"font-size\";\n const [fontSize, setFontSize] = useState(DEFAULT_FONT_SIZE);\n\n const { activeEditor } = useToolbarContext();\n\n const $updateToolbar = (selection: BaseSelection) => {\n if ($isRangeSelection(selection)) {\n const value = $getSelectionStyleValueForProperty(\n selection,\n \"font-size\",\n `${DEFAULT_FONT_SIZE}px`,\n );\n setFontSize(parseInt(value) || DEFAULT_FONT_SIZE);\n }\n };\n\n useUpdateToolbarHandler($updateToolbar);\n\n const updateFontSize = useCallback(\n (newSize: number) => {\n const size = Math.min(Math.max(newSize, MIN_FONT_SIZE), MAX_FONT_SIZE);\n activeEditor.update(() => {\n const selection = $getSelection();\n if (selection !== null) {\n $patchStyleText(selection, {\n [style]: `${size}px`,\n });\n }\n });\n setFontSize(size);\n },\n [activeEditor, style],\n );\n\n return (\n \n updateFontSize(fontSize - 1)}\n disabled={fontSize <= MIN_FONT_SIZE}\n >\n \n \n \n updateFontSize(parseInt(e.target.value) || DEFAULT_FONT_SIZE)\n }\n className=\"w-12 text-center h-8\"\n min={MIN_FONT_SIZE}\n max={MAX_FONT_SIZE}\n />\n updateFontSize(fontSize + 1)}\n disabled={fontSize >= MAX_FONT_SIZE}\n >\n \n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/history-toolbar-plugin.tsx", "content": "import { useEffect, useState } from \"react\";\n\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { IS_APPLE, mergeRegister } from \"@lexical/utils\";\nimport {\n CAN_REDO_COMMAND,\n CAN_UNDO_COMMAND,\n COMMAND_PRIORITY_CRITICAL,\n REDO_COMMAND,\n UNDO_COMMAND,\n} from \"lexical\";\n\nimport { RedoIcon, UndoIcon } from \"lucide-react\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { Button } from \"@/components/ui/button\";\nimport { ButtonGroup } from \"@/components/ui/button-group\";\n\nexport function HistoryToolbarPlugin() {\n const [editor] = useLexicalComposerContext();\n const { activeEditor, $updateToolbar } = useToolbarContext();\n const [isEditable, setIsEditable] = useState(editor.isEditable());\n const [canUndo, setCanUndo] = useState(false);\n const [canRedo, setCanRedo] = useState(false);\n\n useEffect(() => {\n return mergeRegister(\n editor.registerEditableListener((editable) => {\n setIsEditable(editable);\n }),\n activeEditor.registerUpdateListener(({ editorState }) => {\n editorState.read(() => {\n $updateToolbar();\n });\n }),\n activeEditor.registerCommand(\n CAN_UNDO_COMMAND,\n (payload) => {\n setCanUndo(payload);\n return false;\n },\n COMMAND_PRIORITY_CRITICAL,\n ),\n activeEditor.registerCommand(\n CAN_REDO_COMMAND,\n (payload) => {\n setCanRedo(payload);\n return false;\n },\n COMMAND_PRIORITY_CRITICAL,\n ),\n );\n }, [$updateToolbar, activeEditor, editor]);\n\n return (\n \n {\n activeEditor.dispatchCommand(UNDO_COMMAND, undefined);\n }}\n title={IS_APPLE ? \"Undo (⌘Z)\" : \"Undo (Ctrl+Z)\"}\n type=\"button\"\n aria-label=\"Undo\"\n size=\"icon-sm\"\n variant={\"outline\"}\n >\n \n \n {\n activeEditor.dispatchCommand(REDO_COMMAND, undefined);\n }}\n title={IS_APPLE ? \"Redo (⇧⌘Z)\" : \"Redo (Ctrl+Y)\"}\n type=\"button\"\n aria-label=\"Redo\"\n variant={\"outline\"}\n size=\"icon-sm\"\n >\n \n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/link-toolbar-plugin.tsx", "content": "import { useCallback, useState } from \"react\";\n\nimport { $isLinkNode, TOGGLE_LINK_COMMAND } from \"@lexical/link\";\nimport {\n $isRangeSelection,\n // COMMAND_PRIORITY_NORMAL,\n // KEY_MODIFIER_COMMAND,\n type BaseSelection,\n} from \"lexical\";\n\nimport { LinkIcon } from \"lucide-react\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { useUpdateToolbarHandler } from \"@/components/editor/editor-hooks/use-update-toolbar\";\nimport { getSelectedNode } from \"@/components/editor/utils/get-selected-node\";\nimport { sanitizeUrl } from \"@/components/editor/utils/url\";\nimport { Toggle } from \"@/components/ui/toggle\";\n\nexport function LinkToolbarPlugin({\n setIsLinkEditMode,\n}: {\n setIsLinkEditMode: (isEditMode: boolean) => void;\n}) {\n const { activeEditor } = useToolbarContext();\n const [isLink, setIsLink] = useState(false);\n\n const $updateToolbar = (selection: BaseSelection) => {\n if ($isRangeSelection(selection)) {\n const node = getSelectedNode(selection);\n const parent = node.getParent();\n if ($isLinkNode(parent) || $isLinkNode(node)) {\n setIsLink(true);\n } else {\n setIsLink(false);\n }\n }\n };\n\n useUpdateToolbarHandler($updateToolbar);\n\n // useEffect(() => {\n // return activeEditor.registerCommand(\n // KEY_MODIFIER_COMMAND,\n // (payload) => {\n // const event: KeyboardEvent = payload\n // const { code, ctrlKey, metaKey } = event\n\n // if (code === \"KeyK\" && (ctrlKey || metaKey)) {\n // event.preventDefault()\n // let url: string | null\n // if (!isLink) {\n // setIsLinkEditMode(true)\n // url = sanitizeUrl(\"https://\")\n // } else {\n // setIsLinkEditMode(false)\n // url = null\n // }\n // return activeEditor.dispatchCommand(TOGGLE_LINK_COMMAND, url)\n // }\n // return false\n // },\n // COMMAND_PRIORITY_NORMAL\n // )\n // }, [activeEditor, isLink, setIsLinkEditMode])\n\n const insertLink = useCallback(() => {\n if (!isLink) {\n setIsLinkEditMode(true);\n activeEditor.dispatchCommand(\n TOGGLE_LINK_COMMAND,\n sanitizeUrl(\"https://\"),\n );\n } else {\n setIsLinkEditMode(false);\n activeEditor.dispatchCommand(TOGGLE_LINK_COMMAND, null);\n }\n }, [activeEditor, isLink, setIsLinkEditMode]);\n\n return (\n \n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/subsuper-toolbar-plugin.tsx", "content": "import { useState } from \"react\";\n\nimport { $isTableSelection } from \"@lexical/table\";\nimport {\n $isRangeSelection,\n type BaseSelection,\n FORMAT_TEXT_COMMAND,\n} from \"lexical\";\n\nimport { SubscriptIcon, SuperscriptIcon } from \"lucide-react\";\n\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { useUpdateToolbarHandler } from \"@/components/editor/editor-hooks/use-update-toolbar\";\nimport { ToggleGroup, ToggleGroupItem } from \"@/components/ui/toggle-group\";\n\nexport function SubSuperToolbarPlugin() {\n const { activeEditor } = useToolbarContext();\n const [isSubscript, setIsSubscript] = useState(false);\n const [isSuperscript, setIsSuperscript] = useState(false);\n\n const $updateToolbar = (selection: BaseSelection) => {\n if ($isRangeSelection(selection) || $isTableSelection(selection)) {\n // @ts-ignore\n setIsSubscript(selection.hasFormat(\"subscript\"));\n // @ts-ignore\n setIsSuperscript(selection.hasFormat(\"superscript\"));\n }\n };\n\n useUpdateToolbarHandler($updateToolbar);\n\n return (\n \n {\n activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, \"subscript\");\n }}\n variant={\"outline\"}\n >\n \n \n {\n activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, \"superscript\");\n }}\n variant={\"outline\"}\n >\n \n \n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/toolbar/toolbar-plugin.tsx", "content": "import { useEffect, useState } from \"react\";\n\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { COMMAND_PRIORITY_CRITICAL, SELECTION_CHANGE_COMMAND } from \"lexical\";\n\nimport { ToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { useEditorModal } from \"@/components/editor/editor-hooks/use-modal\";\n\nexport function ToolbarPlugin({\n children,\n}: {\n children: (props: { blockType: string }) => React.ReactNode;\n}) {\n const [editor] = useLexicalComposerContext();\n\n const [activeEditor, setActiveEditor] = useState(editor);\n const [blockType, setBlockType] = useState(\"paragraph\");\n\n const [modal, showModal] = useEditorModal();\n\n const $updateToolbar = () => {};\n\n useEffect(() => {\n return activeEditor.registerCommand(\n SELECTION_CHANGE_COMMAND,\n (_payload, newEditor) => {\n setActiveEditor(newEditor);\n return false;\n },\n COMMAND_PRIORITY_CRITICAL,\n );\n }, [activeEditor]);\n\n return (\n \n {modal}\n\n {children({ blockType })}\n \n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/plugins/typing-pref-plugin.tsx", "content": "import { type JSX, useEffect } from \"react\";\n\nimport { useReport } from \"@/components/editor/editor-hooks/use-report\";\n\nconst validInputTypes = new Set([\n \"insertText\",\n \"insertCompositionText\",\n \"insertFromComposition\",\n \"insertLineBreak\",\n \"insertParagraph\",\n \"deleteCompositionText\",\n \"deleteContentBackward\",\n \"deleteByComposition\",\n \"deleteContent\",\n \"deleteContentForward\",\n \"deleteWordBackward\",\n \"deleteWordForward\",\n \"deleteHardLineBackward\",\n \"deleteSoftLineBackward\",\n \"deleteHardLineForward\",\n \"deleteSoftLineForward\",\n]);\n\nexport function TypingPerfPlugin(): JSX.Element | null {\n const report = useReport();\n useEffect(() => {\n let start = 0;\n let timerId: ReturnType | null;\n let keyPressTimerId: ReturnType | null;\n let log: Array = [];\n let invalidatingEvent = false;\n\n const measureEventEnd = function logKeyPress() {\n if (keyPressTimerId != null) {\n if (invalidatingEvent) {\n invalidatingEvent = false;\n } else {\n log.push(performance.now() - start);\n }\n\n clearTimeout(keyPressTimerId);\n keyPressTimerId = null;\n }\n };\n\n const measureEventStart = function measureEvent() {\n if (timerId != null) {\n clearTimeout(timerId);\n timerId = null;\n }\n\n // We use a setTimeout(0) instead of requestAnimationFrame, due to\n // inconsistencies between the sequencing of rAF in different browsers.\n keyPressTimerId = setTimeout(measureEventEnd, 0);\n // Schedule a timer to report the results.\n timerId = setTimeout(() => {\n const total = log.reduce((a, b) => a + b, 0);\n const reportedText =\n \"Typing Perf: \" + Math.round((total / log.length) * 100) / 100 + \"ms\";\n report(reportedText);\n log = [];\n }, 2000);\n // Make the time after we do the previous logic, so we don't measure the overhead\n // for it all.\n start = performance.now();\n };\n\n const beforeInputHandler = function beforeInputHandler(event: InputEvent) {\n if (!validInputTypes.has(event.inputType) || invalidatingEvent) {\n invalidatingEvent = false;\n return;\n }\n\n measureEventStart();\n };\n\n const keyDownHandler = function keyDownHandler(event: KeyboardEvent) {\n const key = event.key;\n\n if (key === \"Backspace\" || key === \"Enter\") {\n measureEventStart();\n }\n };\n\n const pasteHandler = function pasteHandler() {\n invalidatingEvent = true;\n };\n\n const cutHandler = function cutHandler() {\n invalidatingEvent = true;\n };\n\n window.addEventListener(\"keydown\", keyDownHandler, true);\n window.addEventListener(\"selectionchange\", measureEventEnd, true);\n window.addEventListener(\"beforeinput\", beforeInputHandler, true);\n window.addEventListener(\"paste\", pasteHandler, true);\n window.addEventListener(\"cut\", cutHandler, true);\n\n return () => {\n window.removeEventListener(\"keydown\", keyDownHandler, true);\n window.removeEventListener(\"selectionchange\", measureEventEnd, true);\n window.removeEventListener(\"beforeinput\", beforeInputHandler, true);\n window.removeEventListener(\"paste\", pasteHandler, true);\n window.removeEventListener(\"cut\", cutHandler, true);\n };\n }, [report]);\n\n return null;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/shared/can-use-dom.ts", "content": "export const CAN_USE_DOM: boolean =\n typeof window !== \"undefined\" &&\n typeof window.document !== \"undefined\" &&\n typeof window.document.createElement !== \"undefined\";\n", "type": "registry:component" }, { "path": "src/components/editor/shared/caret-from-point.ts", "content": "export function caretFromPoint(\n x: number,\n y: number,\n): null | {\n offset: number;\n node: Node;\n} {\n if (typeof document.caretRangeFromPoint !== \"undefined\") {\n const range = document.caretRangeFromPoint(x, y);\n if (range === null) {\n return null;\n }\n return {\n node: range.startContainer,\n offset: range.startOffset,\n };\n // @ts-ignore\n } else if (document.caretPositionFromPoint !== \"undefined\") {\n // @ts-ignore FF - no types\n const range = document.caretPositionFromPoint(x, y);\n if (range === null) {\n return null;\n }\n return {\n node: range.offsetNode,\n offset: range.offset,\n };\n } else {\n // Gracefully handle IE\n return null;\n }\n}\n", "type": "registry:component" }, { "path": "src/components/editor/shared/environment.ts", "content": "import { CAN_USE_DOM } from \"@/components/editor/shared/can-use-dom\";\n\ndeclare global {\n interface Document {\n documentMode?: unknown;\n }\n\n interface Window {\n MSStream?: unknown;\n }\n}\n\nconst documentMode =\n CAN_USE_DOM && \"documentMode\" in document ? document.documentMode : null;\n\nexport const IS_APPLE: boolean =\n CAN_USE_DOM && /Mac|iPod|iPhone|iPad/.test(navigator.platform);\n\nexport const IS_FIREFOX: boolean =\n CAN_USE_DOM && /^(?!.*Seamonkey)(?=.*Firefox).*/i.test(navigator.userAgent);\n\nexport const CAN_USE_BEFORE_INPUT: boolean =\n CAN_USE_DOM && \"InputEvent\" in window && !documentMode\n ? \"getTargetRanges\" in new window.InputEvent(\"input\")\n : false;\n\nexport const IS_SAFARI: boolean =\n CAN_USE_DOM && /Version\\/[\\d.]+.*Safari/.test(navigator.userAgent);\n\nexport const IS_IOS: boolean =\n CAN_USE_DOM &&\n /iPad|iPhone|iPod/.test(navigator.userAgent) &&\n !window.MSStream;\n\nexport const IS_ANDROID: boolean =\n CAN_USE_DOM && /Android/.test(navigator.userAgent);\n\n// Keep these in case we need to use them in the future.\n// export const IS_WINDOWS: boolean = CAN_USE_DOM && /Win/.test(navigator.platform);\nexport const IS_CHROME: boolean =\n CAN_USE_DOM && /^(?=.*Chrome).*/i.test(navigator.userAgent);\n// export const canUseTextInputEvent: boolean = CAN_USE_DOM && 'TextEvent' in window && !documentMode;\n\nexport const IS_ANDROID_CHROME: boolean =\n CAN_USE_DOM && IS_ANDROID && IS_CHROME;\n\nexport const IS_APPLE_WEBKIT =\n CAN_USE_DOM && /AppleWebKit\\/[\\d.]+/.test(navigator.userAgent) && !IS_CHROME;\n", "type": "registry:component" }, { "path": "src/components/editor/shared/invariant.ts", "content": "// invariant(condition, message) will refine types based on \"condition\", and\n// if \"condition\" is false will throw an error. This function is special-cased\n// in flow itself, so we can't name it anything else.\nexport function invariant(\n cond?: boolean,\n message?: string,\n ..._args: string[]\n): asserts cond {\n if (cond) {\n return;\n }\n\n throw new Error(\n \"Internal Lexical error: invariant() is meant to be replaced at compile \" +\n \"time. There is no runtime version. Error: \" +\n message,\n );\n}\n", "type": "registry:component" }, { "path": "src/components/editor/shared/normalize-class-names.ts", "content": "export default function normalizeClassNames(\n ...classNames: Array\n): Array {\n const rval = [];\n for (const className of classNames) {\n if (className && typeof className === \"string\") {\n for (const [s] of Array.from(className.matchAll(/\\S+/g))) {\n rval.push(s);\n }\n }\n }\n return rval;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/shared/react-patches.ts", "content": "import React from \"react\";\n\n// Webpack + React 17 fails to compile on the usage of `React.startTransition` or\n// `React[\"startTransition\"]` even if it's behind a feature detection of\n// `\"startTransition\" in React`. Moving this to a constant avoids the issue :/\nconst START_TRANSITION = \"startTransition\";\n\nexport function startTransition(callback: () => void) {\n if (START_TRANSITION in React) {\n React[START_TRANSITION](callback);\n } else {\n callback();\n }\n}\n", "type": "registry:component" }, { "path": "src/components/editor/shared/react-test-utils.ts", "content": "import * as React from \"react\";\n\nimport * as ReactTestUtils from \"react-dom/test-utils\";\n\n/**\n * React 19 moved act from react-dom/test-utils to react\n * https://react.dev/blog/2024/04/25/react-19-upgrade-guide#removed-react-dom-test-utils\n */\nexport const act =\n \"act\" in React\n ? (React.act as typeof ReactTestUtils.act)\n : ReactTestUtils.act;\n", "type": "registry:component" }, { "path": "src/components/editor/shared/simple-diff-with-cursor.ts", "content": "export default function simpleDiffWithCursor(\n a: string,\n b: string,\n cursor: number,\n): { index: number; insert: string; remove: number } {\n const aLength = a.length;\n const bLength = b.length;\n let left = 0; // number of same characters counting from left\n let right = 0; // number of same characters counting from right\n // Iterate left to the right until we find a changed character\n // First iteration considers the current cursor position\n while (\n left < aLength &&\n left < bLength &&\n a[left] === b[left] &&\n left < cursor\n ) {\n left++;\n }\n // Iterate right to the left until we find a changed character\n while (\n right + left < aLength &&\n right + left < bLength &&\n a[aLength - right - 1] === b[bLength - right - 1]\n ) {\n right++;\n }\n // Try to iterate left further to the right without caring about the current cursor position\n while (\n right + left < aLength &&\n right + left < bLength &&\n a[left] === b[left]\n ) {\n left++;\n }\n return {\n index: left,\n insert: b.slice(left, bLength - right),\n remove: aLength - left - right,\n };\n}\n", "type": "registry:component" }, { "path": "src/components/editor/shared/use-layout-effect.ts", "content": "import { useEffect, useLayoutEffect } from \"react\";\n\nimport { CAN_USE_DOM } from \"@/components/editor/shared/can-use-dom\";\n\n// This workaround is no longer necessary in React 19,\n// but we currently support React >=17.x\n// https://github.com/facebook/react/pull/26395\nconst useLayoutEffectImpl: typeof useLayoutEffect = CAN_USE_DOM\n ? useLayoutEffect\n : useEffect;\n\nexport default useLayoutEffectImpl;\n", "type": "registry:component" }, { "path": "src/components/editor/themes/editor-theme.css", "content": ".EditorTheme__code {\n background-color: var(--muted);\n color: var(--foreground);\n font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, Monaco, monospace);\n display: block;\n padding: 8px 8px 8px 52px;\n line-height: 1.53;\n font-size: 13px;\n margin: 0;\n margin-top: 8px;\n margin-bottom: 8px;\n overflow-x: auto;\n border: 1px solid var(--border);\n position: relative;\n border-radius: var(--radius-md, 8px);\n tab-size: 2;\n}\n.EditorTheme__code:before {\n content: attr(data-gutter);\n position: absolute;\n background-color: transparent;\n border-right: 1px solid var(--border);\n left: 0;\n top: 0;\n padding: 8px;\n color: var(--muted-foreground);\n white-space: pre-wrap;\n text-align: right;\n min-width: 25px;\n}\n.EditorTheme__table {\n border-collapse: collapse;\n border-spacing: 0;\n overflow-y: scroll;\n overflow-x: scroll;\n table-layout: fixed;\n width: fit-content;\n width: 100%;\n margin: 0px 0px 30px 0px;\n}\n\n/* Prism-style syntax palette — kept chromatic on purpose. */\n.EditorTheme__tokenComment {\n color: #6a737d;\n}\n.EditorTheme__tokenPunctuation {\n color: #999;\n}\n.EditorTheme__tokenProperty {\n color: #905;\n}\n.EditorTheme__tokenSelector {\n color: #690;\n}\n.EditorTheme__tokenOperator {\n color: #9a6e3a;\n}\n.EditorTheme__tokenAttr {\n color: #07a;\n}\n.EditorTheme__tokenVariable {\n color: #e90;\n}\n.EditorTheme__tokenFunction {\n color: #dd4a68;\n}\n\n.dark .EditorTheme__tokenComment {\n color: #8b949e;\n}\n.dark .EditorTheme__tokenPunctuation {\n color: #c9d1d9;\n}\n.dark .EditorTheme__tokenProperty {\n color: #ff7b72;\n}\n.dark .EditorTheme__tokenSelector {\n color: #a5d6ff;\n}\n.dark .EditorTheme__tokenOperator {\n color: #ffa657;\n}\n.dark .EditorTheme__tokenAttr {\n color: #79c0ff;\n}\n.dark .EditorTheme__tokenVariable {\n color: #ffa657;\n}\n.dark .EditorTheme__tokenFunction {\n color: #d2a8ff;\n}\n\n.Collapsible__container {\n background-color: var(--background);\n border: 1px solid var(--border);\n border-radius: var(--radius-md, 0.5rem);\n margin-bottom: 0.5rem;\n}\n\n.Collapsible__title {\n padding: 0.25rem;\n padding-left: 1rem;\n position: relative;\n font-weight: bold;\n outline: none;\n cursor: pointer;\n list-style-type: disclosure-closed;\n list-style-position: inside;\n}\n\n.Collapsible__title p {\n display: inline-flex;\n}\n.Collapsible__title::marker {\n color: var(--muted-foreground);\n}\n.Collapsible__container[open] > .Collapsible__title {\n list-style-type: disclosure-open;\n}\n", "type": "registry:file", "target": "src/components/editor/themes/editor-theme.css" }, { "path": "src/components/editor/themes/editor-theme.ts", "content": "import { type EditorThemeClasses } from \"lexical\";\n\nimport \"./editor-theme.css\";\n\nexport const editorTheme: EditorThemeClasses = {\n ltr: \"text-left\",\n rtl: \"text-right\",\n heading: {\n h1: \"scroll-m-20 text-4xl font-extrabold tracking-tight lg:text-5xl\",\n h2: \"scroll-m-20 border-b pb-2 text-3xl font-semibold tracking-tight first:mt-0\",\n h3: \"scroll-m-20 text-2xl font-semibold tracking-tight\",\n h4: \"scroll-m-20 text-xl font-semibold tracking-tight\",\n h5: \"scroll-m-20 text-lg font-semibold tracking-tight\",\n h6: \"scroll-m-20 text-base font-semibold tracking-tight\",\n },\n paragraph: \"leading-7 [&:not(:first-child)]:mt-6\",\n quote: \"mt-6 border-l-2 pl-6 italic\",\n link: \"text-primary underline-offset-4 hover:underline hover:cursor-pointer\",\n list: {\n checklist: \"relative\",\n listitem: \"mx-8\",\n listitemChecked:\n 'relative mx-2 px-6 list-none outline-none line-through before:content-[\"\"] before:w-4 before:h-4 before:top-0.5 before:left-0 before:cursor-pointer before:block before:bg-cover before:absolute before:border before:border-primary before:rounded before:bg-primary before:bg-no-repeat after:content-[\"\"] after:cursor-pointer after:border-white after:border-solid after:absolute after:block after:top-[6px] after:w-[3px] after:left-[7px] after:right-[7px] after:h-[6px] after:rotate-45 after:border-r-2 after:border-b-2 after:border-l-0 after:border-t-0',\n listitemUnchecked:\n 'relative mx-2 px-6 list-none outline-none before:content-[\"\"] before:w-4 before:h-4 before:top-0.5 before:left-0 before:cursor-pointer before:block before:bg-cover before:absolute before:border before:border-primary before:rounded',\n nested: {\n listitem: \"list-none before:hidden after:hidden\",\n },\n ol: \"m-0 p-0 list-decimal [&>li]:mt-2\",\n olDepth: [\n \"list-outside !list-decimal\",\n \"list-outside !list-[upper-roman]\",\n \"list-outside !list-[lower-roman]\",\n \"list-outside !list-[upper-alpha]\",\n \"list-outside !list-[lower-alpha]\",\n ],\n ul: \"m-0 p-0 list-outside [&>li]:mt-2\",\n ulDepth: [\n \"list-outside !list-disc\",\n \"list-outside !list-disc\",\n \"list-outside !list-disc\",\n \"list-outside !list-disc\",\n \"list-outside !list-disc\",\n ],\n },\n hashtag: \"text-primary bg-primary/10 rounded-md px-1\",\n text: {\n bold: \"font-bold\",\n code: \"bg-muted text-foreground px-1 py-0.5 rounded-sm font-mono text-[0.9em]\",\n italic: \"italic\",\n strikethrough: \"line-through\",\n subscript: \"sub\",\n superscript: \"sup\",\n underline: \"underline\",\n underlineStrikethrough: \"[text-decoration:underline_line-through]\",\n },\n image: \"relative inline-block user-select-none cursor-default editor-image\",\n inlineImage:\n \"relative inline-block user-select-none cursor-default inline-editor-image\",\n keyword: \"text-accent-foreground bg-accent rounded-sm px-1 font-semibold\",\n code: \"EditorTheme__code\",\n codeHighlight: {\n atrule: \"EditorTheme__tokenAttr\",\n attr: \"EditorTheme__tokenAttr\",\n boolean: \"EditorTheme__tokenProperty\",\n builtin: \"EditorTheme__tokenSelector\",\n cdata: \"EditorTheme__tokenComment\",\n char: \"EditorTheme__tokenSelector\",\n class: \"EditorTheme__tokenFunction\",\n \"class-name\": \"EditorTheme__tokenFunction\",\n comment: \"EditorTheme__tokenComment\",\n constant: \"EditorTheme__tokenProperty\",\n deleted: \"EditorTheme__tokenProperty\",\n doctype: \"EditorTheme__tokenComment\",\n entity: \"EditorTheme__tokenOperator\",\n function: \"EditorTheme__tokenFunction\",\n important: \"EditorTheme__tokenVariable\",\n inserted: \"EditorTheme__tokenSelector\",\n keyword: \"EditorTheme__tokenAttr\",\n namespace: \"EditorTheme__tokenVariable\",\n number: \"EditorTheme__tokenProperty\",\n operator: \"EditorTheme__tokenOperator\",\n prolog: \"EditorTheme__tokenComment\",\n property: \"EditorTheme__tokenProperty\",\n punctuation: \"EditorTheme__tokenPunctuation\",\n regex: \"EditorTheme__tokenVariable\",\n selector: \"EditorTheme__tokenSelector\",\n string: \"EditorTheme__tokenSelector\",\n symbol: \"EditorTheme__tokenProperty\",\n tag: \"EditorTheme__tokenProperty\",\n url: \"EditorTheme__tokenOperator\",\n variable: \"EditorTheme__tokenVariable\",\n },\n characterLimit: \"!bg-destructive/50\",\n table: \"EditorTheme__table w-fit overflow-scroll border-collapse\",\n tableCell:\n 'EditorTheme__tableCell w-24 relative border px-4 py-2 text-left [&[align=center]]:text-center [&[align=right]]:text-right\"',\n tableCellActionButton:\n \"EditorTheme__tableCellActionButton bg-background block border-0 rounded-2xl w-5 h-5 text-foreground cursor-pointer\",\n tableCellActionButtonContainer:\n \"EditorTheme__tableCellActionButtonContainer block right-1 top-1.5 absolute z-10 w-5 h-5\",\n tableCellEditing: \"EditorTheme__tableCellEditing rounded-sm shadow-sm\",\n tableCellHeader:\n \"EditorTheme__tableCellHeader bg-muted border px-4 py-2 text-left font-bold [&[align=center]]:text-center [&[align=right]]:text-right\",\n tableCellPrimarySelected:\n \"EditorTheme__tableCellPrimarySelected border border-primary border-solid block h-[calc(100%-2px)] w-[calc(100%-2px)] absolute -left-[1px] -top-[1px] z-10 \",\n tableCellResizer:\n \"EditorTheme__tableCellResizer absolute -right-1 h-full w-2 cursor-ew-resize z-10 top-0\",\n tableCellSelected: \"EditorTheme__tableCellSelected bg-muted\",\n tableCellSortedIndicator:\n \"EditorTheme__tableCellSortedIndicator block opacity-50 bsolute bottom-0 left-0 w-full h-1 bg-muted\",\n tableResizeRuler:\n \"EditorTheme__tableCellResizeRuler block absolute w-[1px] h-full bg-primary top-0\",\n tableRowStriping:\n \"EditorTheme__tableRowStriping m-0 border-t p-0 even:bg-muted\",\n tableSelected: \"EditorTheme__tableSelected ring-2 ring-primary ring-offset-2\",\n tableSelection: \"EditorTheme__tableSelection bg-transparent\",\n layoutItem: \"border border-dashed px-4 py-2\",\n layoutContainer: \"grid gap-2.5 my-2.5 mx-0\",\n autocomplete: \"text-muted-foreground\",\n blockCursor: \"\",\n embedBlock: {\n base: \"user-select-none\",\n focus: \"ring-2 ring-primary ring-offset-2\",\n },\n hr: 'p-0.5 border-none my-1 mx-0 cursor-pointer after:content-[\"\"] after:block after:h-0.5 after:bg-muted selected:ring-2 selected:ring-primary selected:ring-offset-2 selected:user-select-none',\n indent: \"[--lexical-indent-base-value:40px]\",\n mark: \"\",\n markOverlap: \"\",\n};\n", "type": "registry:component" }, { "path": "src/components/editor/transformers/markdown-emoji-transformer.ts", "content": "import { type TextMatchTransformer } from \"@lexical/markdown\";\nimport { $createTextNode } from \"lexical\";\n\nimport emojiList from \"@/components/editor/utils/emoji-list\";\n\nexport const EMOJI: TextMatchTransformer = {\n dependencies: [],\n export: () => null,\n importRegExp: /:([a-z0-9_]+):/,\n regExp: /:([a-z0-9_]+):/,\n replace: (textNode, [, name]) => {\n const emoji = emojiList.find((e) => e.aliases.includes(name))?.emoji;\n if (emoji) {\n textNode.replace($createTextNode(emoji));\n }\n },\n trigger: \":\",\n type: \"text-match\",\n};\n", "type": "registry:component" }, { "path": "src/components/editor/transformers/markdown-hr-transformer.ts", "content": "import {\n $createHorizontalRuleNode,\n $isHorizontalRuleNode,\n HorizontalRuleNode,\n} from \"@lexical/extension\";\nimport type { ElementTransformer } from \"@lexical/markdown\";\nimport type { LexicalNode } from \"lexical\";\n\nexport const HR: ElementTransformer = {\n dependencies: [HorizontalRuleNode],\n export: (node: LexicalNode) => {\n return $isHorizontalRuleNode(node) ? \"***\" : null;\n },\n regExp: /^(---|\\*\\*\\*|___)\\s?$/,\n replace: (parentNode, _1, _2, isImport) => {\n const line = $createHorizontalRuleNode();\n\n // TODO: Get rid of isImport flag\n if (isImport || parentNode.getNextSibling() != null) {\n parentNode.replace(line);\n } else {\n parentNode.insertBefore(line);\n }\n\n line.selectNext();\n },\n type: \"element\",\n};\n", "type": "registry:component" }, { "path": "src/components/editor/transformers/markdown-image-transformer.ts", "content": "import { type TextMatchTransformer } from \"@lexical/markdown\";\n\nimport {\n $createImageNode,\n $isImageNode,\n ImageNode,\n} from \"@/components/editor/nodes/image-node\";\n\nexport const IMAGE: TextMatchTransformer = {\n dependencies: [ImageNode],\n export: (node) => {\n if (!$isImageNode(node)) {\n return null;\n }\n\n return `})`;\n },\n importRegExp: /!(?:\\[([^[]*)\\])(?:\\(([^(]+)\\))/,\n regExp: /!(?:\\[([^[]*)\\])(?:\\(([^(]+)\\))$/,\n replace: (textNode, match) => {\n const [, altText, src] = match;\n const imageNode = $createImageNode({\n altText,\n maxWidth: 800,\n src,\n });\n textNode.replace(imageNode);\n },\n trigger: \")\",\n type: \"text-match\",\n};\n", "type": "registry:component" }, { "path": "src/components/editor/transformers/markdown-table-transformer.ts", "content": "import {\n $convertFromMarkdownString,\n $convertToMarkdownString,\n CHECK_LIST,\n ELEMENT_TRANSFORMERS,\n type ElementTransformer,\n MULTILINE_ELEMENT_TRANSFORMERS,\n TEXT_FORMAT_TRANSFORMERS,\n TEXT_MATCH_TRANSFORMERS,\n} from \"@lexical/markdown\";\nimport {\n $createTableCellNode,\n $createTableNode,\n $createTableRowNode,\n $isTableCellNode,\n $isTableNode,\n $isTableRowNode,\n TableCellHeaderStates,\n TableCellNode,\n TableNode,\n TableRowNode,\n} from \"@lexical/table\";\nimport { $isParagraphNode, $isTextNode, type LexicalNode } from \"lexical\";\n\n// import { EMOJI } from \"@/registry/new-york-v4/editor/transformers/markdown-emoji-transformer\"\nimport { HR } from \"@/components/editor/transformers/markdown-hr-transformer\";\nimport { IMAGE } from \"@/components/editor/transformers/markdown-image-transformer\";\n\n// import { TWEET } from \"@/registry/new-york-v4/editor/transformers/markdown-tweet-transformer\"\n\n// Very primitive table setup\nconst TABLE_ROW_REG_EXP = /^(?:\\|)(.+)(?:\\|)\\s?$/;\nconst TABLE_ROW_DIVIDER_REG_EXP = /^(\\| ?:?-*:? ?)+\\|\\s?$/;\n\nconst OTHER_MARKDOWN_TRANSFORMERS = [\n HR,\n IMAGE,\n // EMOJI,\n // TWEET,\n CHECK_LIST,\n ...ELEMENT_TRANSFORMERS,\n ...MULTILINE_ELEMENT_TRANSFORMERS,\n ...TEXT_FORMAT_TRANSFORMERS,\n ...TEXT_MATCH_TRANSFORMERS,\n];\n\nexport const TABLE: ElementTransformer = {\n dependencies: [TableNode, TableRowNode, TableCellNode],\n export: (node: LexicalNode) => {\n if (!$isTableNode(node)) {\n return null;\n }\n\n const output: string[] = [];\n\n for (const row of node.getChildren()) {\n const rowOutput = [];\n if (!$isTableRowNode(row)) {\n continue;\n }\n\n let isHeaderRow = false;\n for (const cell of row.getChildren()) {\n // It's TableCellNode so it's just to make flow happy\n if ($isTableCellNode(cell)) {\n rowOutput.push(\n $convertToMarkdownString(OTHER_MARKDOWN_TRANSFORMERS, cell).replace(\n /\\n/g,\n \"\\\\n\",\n ),\n );\n if (cell.__headerState === TableCellHeaderStates.ROW) {\n isHeaderRow = true;\n }\n }\n }\n\n output.push(`| ${rowOutput.join(\" | \")} |`);\n if (isHeaderRow) {\n output.push(`| ${rowOutput.map((_) => \"---\").join(\" | \")} |`);\n }\n }\n\n return output.join(\"\\n\");\n },\n regExp: TABLE_ROW_REG_EXP,\n replace: (parentNode, _1, match) => {\n // Header row\n if (TABLE_ROW_DIVIDER_REG_EXP.test(match[0])) {\n const table = parentNode.getPreviousSibling();\n if (!table || !$isTableNode(table)) {\n return;\n }\n\n const rows = table.getChildren();\n const lastRow = rows[rows.length - 1];\n if (!lastRow || !$isTableRowNode(lastRow)) {\n return;\n }\n\n // Add header state to row cells\n lastRow.getChildren().forEach((cell) => {\n if (!$isTableCellNode(cell)) {\n return;\n }\n cell.setHeaderStyles(\n TableCellHeaderStates.ROW,\n TableCellHeaderStates.ROW,\n );\n });\n\n // Remove line\n parentNode.remove();\n return;\n }\n\n const matchCells = mapToTableCells(match[0]);\n\n if (matchCells == null) {\n return;\n }\n\n const rows = [matchCells];\n let sibling = parentNode.getPreviousSibling();\n let maxCells = matchCells.length;\n\n while (sibling) {\n if (!$isParagraphNode(sibling)) {\n break;\n }\n\n if (sibling.getChildrenSize() !== 1) {\n break;\n }\n\n const firstChild = sibling.getFirstChild();\n\n if (!$isTextNode(firstChild)) {\n break;\n }\n\n const cells = mapToTableCells(firstChild.getTextContent());\n\n if (cells == null) {\n break;\n }\n\n maxCells = Math.max(maxCells, cells.length);\n rows.unshift(cells);\n const previousSibling = sibling.getPreviousSibling();\n sibling.remove();\n sibling = previousSibling;\n }\n\n const table = $createTableNode();\n\n for (const cells of rows) {\n const tableRow = $createTableRowNode();\n table.append(tableRow);\n\n for (let i = 0; i < maxCells; i++) {\n tableRow.append(i < cells.length ? cells[i] : $createTableCell(\"\"));\n }\n }\n\n const previousSibling = parentNode.getPreviousSibling();\n if (\n $isTableNode(previousSibling) &&\n getTableColumnsSize(previousSibling) === maxCells\n ) {\n previousSibling.append(...table.getChildren());\n parentNode.remove();\n } else {\n parentNode.replace(table);\n }\n\n table.selectEnd();\n },\n type: \"element\",\n};\n\nfunction getTableColumnsSize(table: TableNode) {\n const row = table.getFirstChild();\n return $isTableRowNode(row) ? row.getChildrenSize() : 0;\n}\n\nconst $createTableCell = (textContent: string): TableCellNode => {\n textContent = textContent.replace(/\\\\n/g, \"\\n\");\n const cell = $createTableCellNode(TableCellHeaderStates.NO_STATUS);\n $convertFromMarkdownString(textContent, OTHER_MARKDOWN_TRANSFORMERS, cell);\n return cell;\n};\n\nconst mapToTableCells = (textContent: string): Array | null => {\n const match = textContent.match(TABLE_ROW_REG_EXP);\n if (!match || !match[1]) {\n return null;\n }\n return match[1].split(\"|\").map((text) => $createTableCell(text));\n};\n", "type": "registry:component" }, { "path": "src/components/editor/transformers/markdown-tweet-transformer.ts", "content": "import { type ElementTransformer } from \"@lexical/markdown\";\n\nimport {\n $createTweetNode,\n $isTweetNode,\n TweetNode,\n} from \"@/components/editor/nodes/embeds/tweet-node\";\n\nexport const TWEET: ElementTransformer = {\n dependencies: [TweetNode],\n export: (node) => {\n if (!$isTweetNode(node)) {\n return null;\n }\n\n return ``;\n },\n regExp: /\\s?$/,\n replace: (textNode, _1, match) => {\n const [, id] = match;\n const tweetNode = $createTweetNode(id);\n textNode.replace(tweetNode);\n },\n type: \"element\",\n};\n", "type": "registry:component" }, { "path": "src/components/editor/utils/doc-serialization.ts", "content": "import { type SerializedDocument } from \"@lexical/file\";\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nasync function* generateReader(\n reader: ReadableStreamDefaultReader,\n) {\n let done = false;\n while (!done) {\n const res = await reader.read();\n const { value } = res;\n if (value !== undefined) {\n yield value;\n }\n done = res.done;\n }\n}\n\nasync function readBytestoString(\n reader: ReadableStreamDefaultReader,\n): Promise {\n const output = [];\n const chunkSize = 0x8000;\n for await (const value of generateReader(reader)) {\n for (let i = 0; i < value.length; i += chunkSize) {\n output.push(String.fromCharCode(...value.subarray(i, i + chunkSize)));\n }\n }\n return output.join(\"\");\n}\n\nexport async function docToHash(doc: SerializedDocument): Promise {\n const cs = new CompressionStream(\"gzip\");\n const writer = cs.writable.getWriter();\n const [, output] = await Promise.all([\n writer\n .write(new TextEncoder().encode(JSON.stringify(doc)))\n .then(() => writer.close()),\n readBytestoString(cs.readable.getReader()),\n ]);\n return `#doc=${btoa(output)\n .replace(/\\//g, \"_\")\n .replace(/\\+/g, \"-\")\n .replace(/=+$/, \"\")}`;\n}\n\nexport async function docFromHash(\n hash: string,\n): Promise {\n const m = /^#doc=(.*)$/.exec(hash);\n if (!m) {\n return null;\n }\n const ds = new DecompressionStream(\"gzip\");\n const writer = ds.writable.getWriter();\n const b64 = atob(m[1].replace(/_/g, \"/\").replace(/-/g, \"+\"));\n const array = new Uint8Array(b64.length);\n for (let i = 0; i < b64.length; i++) {\n array[i] = b64.charCodeAt(i);\n }\n const closed = writer.write(array).then(() => writer.close());\n const output = [];\n for await (const chunk of generateReader(\n ds.readable.pipeThrough(new TextDecoderStream()).getReader(),\n )) {\n output.push(chunk);\n }\n await closed;\n return JSON.parse(output.join(\"\"));\n}\n", "type": "registry:component" }, { "path": "src/components/editor/utils/emoji-list.ts", "content": "// This list was sourced from Github (MIT License)\n// https://github.com/github/gemoji/blob/master/db/emoji.json\n\nexport default [\n {\n description: \"grinning face\",\n emoji: \"😀\",\n category: \"Smileys & Emotion\",\n aliases: [\"grinning\"],\n tags: [\"smile\", \"happy\"],\n unicode_version: \"6.1\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😃\",\n description: \"grinning face with big eyes\",\n category: \"Smileys & Emotion\",\n aliases: [\"smiley\"],\n tags: [\"happy\", \"joy\", \"haha\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😄\",\n description: \"grinning face with smiling eyes\",\n category: \"Smileys & Emotion\",\n aliases: [\"smile\"],\n tags: [\"happy\", \"joy\", \"laugh\", \"pleased\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😁\",\n description: \"beaming face with smiling eyes\",\n category: \"Smileys & Emotion\",\n aliases: [\"grin\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😆\",\n description: \"grinning squinting face\",\n category: \"Smileys & Emotion\",\n aliases: [\"laughing\", \"satisfied\"],\n tags: [\"happy\", \"haha\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😅\",\n description: \"grinning face with sweat\",\n category: \"Smileys & Emotion\",\n aliases: [\"sweat_smile\"],\n tags: [\"hot\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🤣\",\n description: \"rolling on the floor laughing\",\n category: \"Smileys & Emotion\",\n aliases: [\"rofl\"],\n tags: [\"lol\", \"laughing\"],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"😂\",\n description: \"face with tears of joy\",\n category: \"Smileys & Emotion\",\n aliases: [\"joy\"],\n tags: [\"tears\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🙂\",\n description: \"slightly smiling face\",\n category: \"Smileys & Emotion\",\n aliases: [\"slightly_smiling_face\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🙃\",\n description: \"upside-down face\",\n category: \"Smileys & Emotion\",\n aliases: [\"upside_down_face\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"😉\",\n description: \"winking face\",\n category: \"Smileys & Emotion\",\n aliases: [\"wink\"],\n tags: [\"flirt\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😊\",\n description: \"smiling face with smiling eyes\",\n category: \"Smileys & Emotion\",\n aliases: [\"blush\"],\n tags: [\"proud\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😇\",\n description: \"smiling face with halo\",\n category: \"Smileys & Emotion\",\n aliases: [\"innocent\"],\n tags: [\"angel\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🥰\",\n description: \"smiling face with hearts\",\n category: \"Smileys & Emotion\",\n aliases: [\"smiling_face_with_three_hearts\"],\n tags: [\"love\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"😍\",\n description: \"smiling face with heart-eyes\",\n category: \"Smileys & Emotion\",\n aliases: [\"heart_eyes\"],\n tags: [\"love\", \"crush\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🤩\",\n description: \"star-struck\",\n category: \"Smileys & Emotion\",\n aliases: [\"star_struck\"],\n tags: [\"eyes\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"😘\",\n description: \"face blowing a kiss\",\n category: \"Smileys & Emotion\",\n aliases: [\"kissing_heart\"],\n tags: [\"flirt\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😗\",\n description: \"kissing face\",\n category: \"Smileys & Emotion\",\n aliases: [\"kissing\"],\n tags: [],\n unicode_version: \"6.1\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"☺️\",\n description: \"smiling face\",\n category: \"Smileys & Emotion\",\n aliases: [\"relaxed\"],\n tags: [\"blush\", \"pleased\"],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😚\",\n description: \"kissing face with closed eyes\",\n category: \"Smileys & Emotion\",\n aliases: [\"kissing_closed_eyes\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😙\",\n description: \"kissing face with smiling eyes\",\n category: \"Smileys & Emotion\",\n aliases: [\"kissing_smiling_eyes\"],\n tags: [],\n unicode_version: \"6.1\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🥲\",\n description: \"smiling face with tear\",\n category: \"Smileys & Emotion\",\n aliases: [\"smiling_face_with_tear\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"😋\",\n description: \"face savoring food\",\n category: \"Smileys & Emotion\",\n aliases: [\"yum\"],\n tags: [\"tongue\", \"lick\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😛\",\n description: \"face with tongue\",\n category: \"Smileys & Emotion\",\n aliases: [\"stuck_out_tongue\"],\n tags: [],\n unicode_version: \"6.1\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😜\",\n description: \"winking face with tongue\",\n category: \"Smileys & Emotion\",\n aliases: [\"stuck_out_tongue_winking_eye\"],\n tags: [\"prank\", \"silly\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🤪\",\n description: \"zany face\",\n category: \"Smileys & Emotion\",\n aliases: [\"zany_face\"],\n tags: [\"goofy\", \"wacky\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"😝\",\n description: \"squinting face with tongue\",\n category: \"Smileys & Emotion\",\n aliases: [\"stuck_out_tongue_closed_eyes\"],\n tags: [\"prank\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🤑\",\n description: \"money-mouth face\",\n category: \"Smileys & Emotion\",\n aliases: [\"money_mouth_face\"],\n tags: [\"rich\"],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🤗\",\n description: \"hugging face\",\n category: \"Smileys & Emotion\",\n aliases: [\"hugs\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🤭\",\n description: \"face with hand over mouth\",\n category: \"Smileys & Emotion\",\n aliases: [\"hand_over_mouth\"],\n tags: [\"quiet\", \"whoops\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🤫\",\n description: \"shushing face\",\n category: \"Smileys & Emotion\",\n aliases: [\"shushing_face\"],\n tags: [\"silence\", \"quiet\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🤔\",\n description: \"thinking face\",\n category: \"Smileys & Emotion\",\n aliases: [\"thinking\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🤐\",\n description: \"zipper-mouth face\",\n category: \"Smileys & Emotion\",\n aliases: [\"zipper_mouth_face\"],\n tags: [\"silence\", \"hush\"],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🤨\",\n description: \"face with raised eyebrow\",\n category: \"Smileys & Emotion\",\n aliases: [\"raised_eyebrow\"],\n tags: [\"suspicious\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"😐\",\n description: \"neutral face\",\n category: \"Smileys & Emotion\",\n aliases: [\"neutral_face\"],\n tags: [\"meh\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😑\",\n description: \"expressionless face\",\n category: \"Smileys & Emotion\",\n aliases: [\"expressionless\"],\n tags: [],\n unicode_version: \"6.1\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😶\",\n description: \"face without mouth\",\n category: \"Smileys & Emotion\",\n aliases: [\"no_mouth\"],\n tags: [\"mute\", \"silence\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😶🌫️\",\n description: \"face in clouds\",\n category: \"Smileys & Emotion\",\n aliases: [\"face_in_clouds\"],\n tags: [],\n unicode_version: \"13.1\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"😏\",\n description: \"smirking face\",\n category: \"Smileys & Emotion\",\n aliases: [\"smirk\"],\n tags: [\"smug\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😒\",\n description: \"unamused face\",\n category: \"Smileys & Emotion\",\n aliases: [\"unamused\"],\n tags: [\"meh\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🙄\",\n description: \"face with rolling eyes\",\n category: \"Smileys & Emotion\",\n aliases: [\"roll_eyes\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"😬\",\n description: \"grimacing face\",\n category: \"Smileys & Emotion\",\n aliases: [\"grimacing\"],\n tags: [],\n unicode_version: \"6.1\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😮💨\",\n description: \"face exhaling\",\n category: \"Smileys & Emotion\",\n aliases: [\"face_exhaling\"],\n tags: [],\n unicode_version: \"13.1\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🤥\",\n description: \"lying face\",\n category: \"Smileys & Emotion\",\n aliases: [\"lying_face\"],\n tags: [\"liar\"],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"😌\",\n description: \"relieved face\",\n category: \"Smileys & Emotion\",\n aliases: [\"relieved\"],\n tags: [\"whew\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😔\",\n description: \"pensive face\",\n category: \"Smileys & Emotion\",\n aliases: [\"pensive\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😪\",\n description: \"sleepy face\",\n category: \"Smileys & Emotion\",\n aliases: [\"sleepy\"],\n tags: [\"tired\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🤤\",\n description: \"drooling face\",\n category: \"Smileys & Emotion\",\n aliases: [\"drooling_face\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"😴\",\n description: \"sleeping face\",\n category: \"Smileys & Emotion\",\n aliases: [\"sleeping\"],\n tags: [\"zzz\"],\n unicode_version: \"6.1\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😷\",\n description: \"face with medical mask\",\n category: \"Smileys & Emotion\",\n aliases: [\"mask\"],\n tags: [\"sick\", \"ill\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🤒\",\n description: \"face with thermometer\",\n category: \"Smileys & Emotion\",\n aliases: [\"face_with_thermometer\"],\n tags: [\"sick\"],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🤕\",\n description: \"face with head-bandage\",\n category: \"Smileys & Emotion\",\n aliases: [\"face_with_head_bandage\"],\n tags: [\"hurt\"],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🤢\",\n description: \"nauseated face\",\n category: \"Smileys & Emotion\",\n aliases: [\"nauseated_face\"],\n tags: [\"sick\", \"barf\", \"disgusted\"],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🤮\",\n description: \"face vomiting\",\n category: \"Smileys & Emotion\",\n aliases: [\"vomiting_face\"],\n tags: [\"barf\", \"sick\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🤧\",\n description: \"sneezing face\",\n category: \"Smileys & Emotion\",\n aliases: [\"sneezing_face\"],\n tags: [\"achoo\", \"sick\"],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🥵\",\n description: \"hot face\",\n category: \"Smileys & Emotion\",\n aliases: [\"hot_face\"],\n tags: [\"heat\", \"sweating\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🥶\",\n description: \"cold face\",\n category: \"Smileys & Emotion\",\n aliases: [\"cold_face\"],\n tags: [\"freezing\", \"ice\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🥴\",\n description: \"woozy face\",\n category: \"Smileys & Emotion\",\n aliases: [\"woozy_face\"],\n tags: [\"groggy\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"😵\",\n description: \"knocked-out face\",\n category: \"Smileys & Emotion\",\n aliases: [\"dizzy_face\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😵💫\",\n description: \"face with spiral eyes\",\n category: \"Smileys & Emotion\",\n aliases: [\"face_with_spiral_eyes\"],\n tags: [],\n unicode_version: \"13.1\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🤯\",\n description: \"exploding head\",\n category: \"Smileys & Emotion\",\n aliases: [\"exploding_head\"],\n tags: [\"mind\", \"blown\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🤠\",\n description: \"cowboy hat face\",\n category: \"Smileys & Emotion\",\n aliases: [\"cowboy_hat_face\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🥳\",\n description: \"partying face\",\n category: \"Smileys & Emotion\",\n aliases: [\"partying_face\"],\n tags: [\"celebration\", \"birthday\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🥸\",\n description: \"disguised face\",\n category: \"Smileys & Emotion\",\n aliases: [\"disguised_face\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"😎\",\n description: \"smiling face with sunglasses\",\n category: \"Smileys & Emotion\",\n aliases: [\"sunglasses\"],\n tags: [\"cool\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🤓\",\n description: \"nerd face\",\n category: \"Smileys & Emotion\",\n aliases: [\"nerd_face\"],\n tags: [\"geek\", \"glasses\"],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🧐\",\n description: \"face with monocle\",\n category: \"Smileys & Emotion\",\n aliases: [\"monocle_face\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"😕\",\n description: \"confused face\",\n category: \"Smileys & Emotion\",\n aliases: [\"confused\"],\n tags: [],\n unicode_version: \"6.1\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😟\",\n description: \"worried face\",\n category: \"Smileys & Emotion\",\n aliases: [\"worried\"],\n tags: [\"nervous\"],\n unicode_version: \"6.1\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🙁\",\n description: \"slightly frowning face\",\n category: \"Smileys & Emotion\",\n aliases: [\"slightly_frowning_face\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"☹️\",\n description: \"frowning face\",\n category: \"Smileys & Emotion\",\n aliases: [\"frowning_face\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"😮\",\n description: \"face with open mouth\",\n category: \"Smileys & Emotion\",\n aliases: [\"open_mouth\"],\n tags: [\"surprise\", \"impressed\", \"wow\"],\n unicode_version: \"6.1\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😯\",\n description: \"hushed face\",\n category: \"Smileys & Emotion\",\n aliases: [\"hushed\"],\n tags: [\"silence\", \"speechless\"],\n unicode_version: \"6.1\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😲\",\n description: \"astonished face\",\n category: \"Smileys & Emotion\",\n aliases: [\"astonished\"],\n tags: [\"amazed\", \"gasp\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😳\",\n description: \"flushed face\",\n category: \"Smileys & Emotion\",\n aliases: [\"flushed\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🥺\",\n description: \"pleading face\",\n category: \"Smileys & Emotion\",\n aliases: [\"pleading_face\"],\n tags: [\"puppy\", \"eyes\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"😦\",\n description: \"frowning face with open mouth\",\n category: \"Smileys & Emotion\",\n aliases: [\"frowning\"],\n tags: [],\n unicode_version: \"6.1\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😧\",\n description: \"anguished face\",\n category: \"Smileys & Emotion\",\n aliases: [\"anguished\"],\n tags: [\"stunned\"],\n unicode_version: \"6.1\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😨\",\n description: \"fearful face\",\n category: \"Smileys & Emotion\",\n aliases: [\"fearful\"],\n tags: [\"scared\", \"shocked\", \"oops\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😰\",\n description: \"anxious face with sweat\",\n category: \"Smileys & Emotion\",\n aliases: [\"cold_sweat\"],\n tags: [\"nervous\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😥\",\n description: \"sad but relieved face\",\n category: \"Smileys & Emotion\",\n aliases: [\"disappointed_relieved\"],\n tags: [\"phew\", \"sweat\", \"nervous\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😢\",\n description: \"crying face\",\n category: \"Smileys & Emotion\",\n aliases: [\"cry\"],\n tags: [\"sad\", \"tear\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😭\",\n description: \"loudly crying face\",\n category: \"Smileys & Emotion\",\n aliases: [\"sob\"],\n tags: [\"sad\", \"cry\", \"bawling\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😱\",\n description: \"face screaming in fear\",\n category: \"Smileys & Emotion\",\n aliases: [\"scream\"],\n tags: [\"horror\", \"shocked\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😖\",\n description: \"confounded face\",\n category: \"Smileys & Emotion\",\n aliases: [\"confounded\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😣\",\n description: \"persevering face\",\n category: \"Smileys & Emotion\",\n aliases: [\"persevere\"],\n tags: [\"struggling\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😞\",\n description: \"disappointed face\",\n category: \"Smileys & Emotion\",\n aliases: [\"disappointed\"],\n tags: [\"sad\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😓\",\n description: \"downcast face with sweat\",\n category: \"Smileys & Emotion\",\n aliases: [\"sweat\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😩\",\n description: \"weary face\",\n category: \"Smileys & Emotion\",\n aliases: [\"weary\"],\n tags: [\"tired\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😫\",\n description: \"tired face\",\n category: \"Smileys & Emotion\",\n aliases: [\"tired_face\"],\n tags: [\"upset\", \"whine\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🥱\",\n description: \"yawning face\",\n category: \"Smileys & Emotion\",\n aliases: [\"yawning_face\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"😤\",\n description: \"face with steam from nose\",\n category: \"Smileys & Emotion\",\n aliases: [\"triumph\"],\n tags: [\"smug\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😡\",\n description: \"pouting face\",\n category: \"Smileys & Emotion\",\n aliases: [\"rage\", \"pout\"],\n tags: [\"angry\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😠\",\n description: \"angry face\",\n category: \"Smileys & Emotion\",\n aliases: [\"angry\"],\n tags: [\"mad\", \"annoyed\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🤬\",\n description: \"face with symbols on mouth\",\n category: \"Smileys & Emotion\",\n aliases: [\"cursing_face\"],\n tags: [\"foul\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"😈\",\n description: \"smiling face with horns\",\n category: \"Smileys & Emotion\",\n aliases: [\"smiling_imp\"],\n tags: [\"devil\", \"evil\", \"horns\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👿\",\n description: \"angry face with horns\",\n category: \"Smileys & Emotion\",\n aliases: [\"imp\"],\n tags: [\"angry\", \"devil\", \"evil\", \"horns\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💀\",\n description: \"skull\",\n category: \"Smileys & Emotion\",\n aliases: [\"skull\"],\n tags: [\"dead\", \"danger\", \"poison\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"☠️\",\n description: \"skull and crossbones\",\n category: \"Smileys & Emotion\",\n aliases: [\"skull_and_crossbones\"],\n tags: [\"danger\", \"pirate\"],\n unicode_version: \"\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"💩\",\n description: \"pile of poo\",\n category: \"Smileys & Emotion\",\n aliases: [\"hankey\", \"poop\", \"shit\"],\n tags: [\"crap\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🤡\",\n description: \"clown face\",\n category: \"Smileys & Emotion\",\n aliases: [\"clown_face\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"👹\",\n description: \"ogre\",\n category: \"Smileys & Emotion\",\n aliases: [\"japanese_ogre\"],\n tags: [\"monster\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👺\",\n description: \"goblin\",\n category: \"Smileys & Emotion\",\n aliases: [\"japanese_goblin\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👻\",\n description: \"ghost\",\n category: \"Smileys & Emotion\",\n aliases: [\"ghost\"],\n tags: [\"halloween\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👽\",\n description: \"alien\",\n category: \"Smileys & Emotion\",\n aliases: [\"alien\"],\n tags: [\"ufo\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👾\",\n description: \"alien monster\",\n category: \"Smileys & Emotion\",\n aliases: [\"space_invader\"],\n tags: [\"game\", \"retro\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🤖\",\n description: \"robot\",\n category: \"Smileys & Emotion\",\n aliases: [\"robot\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"😺\",\n description: \"grinning cat\",\n category: \"Smileys & Emotion\",\n aliases: [\"smiley_cat\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😸\",\n description: \"grinning cat with smiling eyes\",\n category: \"Smileys & Emotion\",\n aliases: [\"smile_cat\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😹\",\n description: \"cat with tears of joy\",\n category: \"Smileys & Emotion\",\n aliases: [\"joy_cat\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😻\",\n description: \"smiling cat with heart-eyes\",\n category: \"Smileys & Emotion\",\n aliases: [\"heart_eyes_cat\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😼\",\n description: \"cat with wry smile\",\n category: \"Smileys & Emotion\",\n aliases: [\"smirk_cat\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😽\",\n description: \"kissing cat\",\n category: \"Smileys & Emotion\",\n aliases: [\"kissing_cat\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🙀\",\n description: \"weary cat\",\n category: \"Smileys & Emotion\",\n aliases: [\"scream_cat\"],\n tags: [\"horror\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😿\",\n description: \"crying cat\",\n category: \"Smileys & Emotion\",\n aliases: [\"crying_cat_face\"],\n tags: [\"sad\", \"tear\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"😾\",\n description: \"pouting cat\",\n category: \"Smileys & Emotion\",\n aliases: [\"pouting_cat\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🙈\",\n description: \"see-no-evil monkey\",\n category: \"Smileys & Emotion\",\n aliases: [\"see_no_evil\"],\n tags: [\"monkey\", \"blind\", \"ignore\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🙉\",\n description: \"hear-no-evil monkey\",\n category: \"Smileys & Emotion\",\n aliases: [\"hear_no_evil\"],\n tags: [\"monkey\", \"deaf\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🙊\",\n description: \"speak-no-evil monkey\",\n category: \"Smileys & Emotion\",\n aliases: [\"speak_no_evil\"],\n tags: [\"monkey\", \"mute\", \"hush\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💋\",\n description: \"kiss mark\",\n category: \"Smileys & Emotion\",\n aliases: [\"kiss\"],\n tags: [\"lipstick\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💌\",\n description: \"love letter\",\n category: \"Smileys & Emotion\",\n aliases: [\"love_letter\"],\n tags: [\"email\", \"envelope\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💘\",\n description: \"heart with arrow\",\n category: \"Smileys & Emotion\",\n aliases: [\"cupid\"],\n tags: [\"love\", \"heart\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💝\",\n description: \"heart with ribbon\",\n category: \"Smileys & Emotion\",\n aliases: [\"gift_heart\"],\n tags: [\"chocolates\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💖\",\n description: \"sparkling heart\",\n category: \"Smileys & Emotion\",\n aliases: [\"sparkling_heart\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💗\",\n description: \"growing heart\",\n category: \"Smileys & Emotion\",\n aliases: [\"heartpulse\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💓\",\n description: \"beating heart\",\n category: \"Smileys & Emotion\",\n aliases: [\"heartbeat\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💞\",\n description: \"revolving hearts\",\n category: \"Smileys & Emotion\",\n aliases: [\"revolving_hearts\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💕\",\n description: \"two hearts\",\n category: \"Smileys & Emotion\",\n aliases: [\"two_hearts\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💟\",\n description: \"heart decoration\",\n category: \"Smileys & Emotion\",\n aliases: [\"heart_decoration\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"❣️\",\n description: \"heart exclamation\",\n category: \"Smileys & Emotion\",\n aliases: [\"heavy_heart_exclamation\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"💔\",\n description: \"broken heart\",\n category: \"Smileys & Emotion\",\n aliases: [\"broken_heart\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"❤️🔥\",\n description: \"heart on fire\",\n category: \"Smileys & Emotion\",\n aliases: [\"heart_on_fire\"],\n tags: [],\n unicode_version: \"13.1\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"❤️🩹\",\n description: \"mending heart\",\n category: \"Smileys & Emotion\",\n aliases: [\"mending_heart\"],\n tags: [],\n unicode_version: \"13.1\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"❤️\",\n description: \"red heart\",\n category: \"Smileys & Emotion\",\n aliases: [\"heart\"],\n tags: [\"love\"],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🧡\",\n description: \"orange heart\",\n category: \"Smileys & Emotion\",\n aliases: [\"orange_heart\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"💛\",\n description: \"yellow heart\",\n category: \"Smileys & Emotion\",\n aliases: [\"yellow_heart\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💚\",\n description: \"green heart\",\n category: \"Smileys & Emotion\",\n aliases: [\"green_heart\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💙\",\n description: \"blue heart\",\n category: \"Smileys & Emotion\",\n aliases: [\"blue_heart\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💜\",\n description: \"purple heart\",\n category: \"Smileys & Emotion\",\n aliases: [\"purple_heart\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🤎\",\n description: \"brown heart\",\n category: \"Smileys & Emotion\",\n aliases: [\"brown_heart\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🖤\",\n description: \"black heart\",\n category: \"Smileys & Emotion\",\n aliases: [\"black_heart\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🤍\",\n description: \"white heart\",\n category: \"Smileys & Emotion\",\n aliases: [\"white_heart\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"💯\",\n description: \"hundred points\",\n category: \"Smileys & Emotion\",\n aliases: [\"100\"],\n tags: [\"score\", \"perfect\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💢\",\n description: \"anger symbol\",\n category: \"Smileys & Emotion\",\n aliases: [\"anger\"],\n tags: [\"angry\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💥\",\n description: \"collision\",\n category: \"Smileys & Emotion\",\n aliases: [\"boom\", \"collision\"],\n tags: [\"explode\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💫\",\n description: \"dizzy\",\n category: \"Smileys & Emotion\",\n aliases: [\"dizzy\"],\n tags: [\"star\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💦\",\n description: \"sweat droplets\",\n category: \"Smileys & Emotion\",\n aliases: [\"sweat_drops\"],\n tags: [\"water\", \"workout\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💨\",\n description: \"dashing away\",\n category: \"Smileys & Emotion\",\n aliases: [\"dash\"],\n tags: [\"wind\", \"blow\", \"fast\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕳️\",\n description: \"hole\",\n category: \"Smileys & Emotion\",\n aliases: [\"hole\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"💣\",\n description: \"bomb\",\n category: \"Smileys & Emotion\",\n aliases: [\"bomb\"],\n tags: [\"boom\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💬\",\n description: \"speech balloon\",\n category: \"Smileys & Emotion\",\n aliases: [\"speech_balloon\"],\n tags: [\"comment\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👁️🗨️\",\n description: \"eye in speech bubble\",\n category: \"Smileys & Emotion\",\n aliases: [\"eye_speech_bubble\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🗨️\",\n description: \"left speech bubble\",\n category: \"Smileys & Emotion\",\n aliases: [\"left_speech_bubble\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🗯️\",\n description: \"right anger bubble\",\n category: \"Smileys & Emotion\",\n aliases: [\"right_anger_bubble\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"💭\",\n description: \"thought balloon\",\n category: \"Smileys & Emotion\",\n aliases: [\"thought_balloon\"],\n tags: [\"thinking\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💤\",\n description: \"zzz\",\n category: \"Smileys & Emotion\",\n aliases: [\"zzz\"],\n tags: [\"sleeping\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👋\",\n description: \"waving hand\",\n category: \"People & Body\",\n aliases: [\"wave\"],\n tags: [\"goodbye\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🤚\",\n description: \"raised back of hand\",\n category: \"People & Body\",\n aliases: [\"raised_back_of_hand\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🖐️\",\n description: \"hand with fingers splayed\",\n category: \"People & Body\",\n aliases: [\"raised_hand_with_fingers_splayed\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n skin_tones: true,\n },\n {\n emoji: \"✋\",\n description: \"raised hand\",\n category: \"People & Body\",\n aliases: [\"hand\", \"raised_hand\"],\n tags: [\"highfive\", \"stop\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🖖\",\n description: \"vulcan salute\",\n category: \"People & Body\",\n aliases: [\"vulcan_salute\"],\n tags: [\"prosper\", \"spock\"],\n unicode_version: \"7.0\",\n ios_version: \"8.3\",\n skin_tones: true,\n },\n {\n emoji: \"👌\",\n description: \"OK hand\",\n category: \"People & Body\",\n aliases: [\"ok_hand\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🤌\",\n description: \"pinched fingers\",\n category: \"People & Body\",\n aliases: [\"pinched_fingers\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n skin_tones: true,\n },\n {\n emoji: \"🤏\",\n description: \"pinching hand\",\n category: \"People & Body\",\n aliases: [\"pinching_hand\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n skin_tones: true,\n },\n {\n emoji: \"✌️\",\n description: \"victory hand\",\n category: \"People & Body\",\n aliases: [\"v\"],\n tags: [\"victory\", \"peace\"],\n unicode_version: \"\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🤞\",\n description: \"crossed fingers\",\n category: \"People & Body\",\n aliases: [\"crossed_fingers\"],\n tags: [\"luck\", \"hopeful\"],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🤟\",\n description: \"love-you gesture\",\n category: \"People & Body\",\n aliases: [\"love_you_gesture\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🤘\",\n description: \"sign of the horns\",\n category: \"People & Body\",\n aliases: [\"metal\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n skin_tones: true,\n },\n {\n emoji: \"🤙\",\n description: \"call me hand\",\n category: \"People & Body\",\n aliases: [\"call_me_hand\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"👈\",\n description: \"backhand index pointing left\",\n category: \"People & Body\",\n aliases: [\"point_left\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"👉\",\n description: \"backhand index pointing right\",\n category: \"People & Body\",\n aliases: [\"point_right\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"👆\",\n description: \"backhand index pointing up\",\n category: \"People & Body\",\n aliases: [\"point_up_2\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🖕\",\n description: \"middle finger\",\n category: \"People & Body\",\n aliases: [\"middle_finger\", \"fu\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n skin_tones: true,\n },\n {\n emoji: \"👇\",\n description: \"backhand index pointing down\",\n category: \"People & Body\",\n aliases: [\"point_down\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"☝️\",\n description: \"index pointing up\",\n category: \"People & Body\",\n aliases: [\"point_up\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"👍\",\n description: \"thumbs up\",\n category: \"People & Body\",\n aliases: [\"+1\", \"thumbsup\"],\n tags: [\"approve\", \"ok\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"👎\",\n description: \"thumbs down\",\n category: \"People & Body\",\n aliases: [\"-1\", \"thumbsdown\"],\n tags: [\"disapprove\", \"bury\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"✊\",\n description: \"raised fist\",\n category: \"People & Body\",\n aliases: [\"fist_raised\", \"fist\"],\n tags: [\"power\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"👊\",\n description: \"oncoming fist\",\n category: \"People & Body\",\n aliases: [\"fist_oncoming\", \"facepunch\", \"punch\"],\n tags: [\"attack\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🤛\",\n description: \"left-facing fist\",\n category: \"People & Body\",\n aliases: [\"fist_left\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🤜\",\n description: \"right-facing fist\",\n category: \"People & Body\",\n aliases: [\"fist_right\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"👏\",\n description: \"clapping hands\",\n category: \"People & Body\",\n aliases: [\"clap\"],\n tags: [\"praise\", \"applause\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🙌\",\n description: \"raising hands\",\n category: \"People & Body\",\n aliases: [\"raised_hands\"],\n tags: [\"hooray\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"👐\",\n description: \"open hands\",\n category: \"People & Body\",\n aliases: [\"open_hands\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🤲\",\n description: \"palms up together\",\n category: \"People & Body\",\n aliases: [\"palms_up_together\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🤝\",\n description: \"handshake\",\n category: \"People & Body\",\n aliases: [\"handshake\"],\n tags: [\"deal\"],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🙏\",\n description: \"folded hands\",\n category: \"People & Body\",\n aliases: [\"pray\"],\n tags: [\"please\", \"hope\", \"wish\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"✍️\",\n description: \"writing hand\",\n category: \"People & Body\",\n aliases: [\"writing_hand\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"9.1\",\n skin_tones: true,\n },\n {\n emoji: \"💅\",\n description: \"nail polish\",\n category: \"People & Body\",\n aliases: [\"nail_care\"],\n tags: [\"beauty\", \"manicure\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🤳\",\n description: \"selfie\",\n category: \"People & Body\",\n aliases: [\"selfie\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"💪\",\n description: \"flexed biceps\",\n category: \"People & Body\",\n aliases: [\"muscle\"],\n tags: [\"flex\", \"bicep\", \"strong\", \"workout\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🦾\",\n description: \"mechanical arm\",\n category: \"People & Body\",\n aliases: [\"mechanical_arm\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🦿\",\n description: \"mechanical leg\",\n category: \"People & Body\",\n aliases: [\"mechanical_leg\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🦵\",\n description: \"leg\",\n category: \"People & Body\",\n aliases: [\"leg\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🦶\",\n description: \"foot\",\n category: \"People & Body\",\n aliases: [\"foot\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"👂\",\n description: \"ear\",\n category: \"People & Body\",\n aliases: [\"ear\"],\n tags: [\"hear\", \"sound\", \"listen\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🦻\",\n description: \"ear with hearing aid\",\n category: \"People & Body\",\n aliases: [\"ear_with_hearing_aid\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n skin_tones: true,\n },\n {\n emoji: \"👃\",\n description: \"nose\",\n category: \"People & Body\",\n aliases: [\"nose\"],\n tags: [\"smell\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🧠\",\n description: \"brain\",\n category: \"People & Body\",\n aliases: [\"brain\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🫀\",\n description: \"anatomical heart\",\n category: \"People & Body\",\n aliases: [\"anatomical_heart\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🫁\",\n description: \"lungs\",\n category: \"People & Body\",\n aliases: [\"lungs\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🦷\",\n description: \"tooth\",\n category: \"People & Body\",\n aliases: [\"tooth\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🦴\",\n description: \"bone\",\n category: \"People & Body\",\n aliases: [\"bone\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"👀\",\n description: \"eyes\",\n category: \"People & Body\",\n aliases: [\"eyes\"],\n tags: [\"look\", \"see\", \"watch\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👁️\",\n description: \"eye\",\n category: \"People & Body\",\n aliases: [\"eye\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"👅\",\n description: \"tongue\",\n category: \"People & Body\",\n aliases: [\"tongue\"],\n tags: [\"taste\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👄\",\n description: \"mouth\",\n category: \"People & Body\",\n aliases: [\"lips\"],\n tags: [\"kiss\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👶\",\n description: \"baby\",\n category: \"People & Body\",\n aliases: [\"baby\"],\n tags: [\"child\", \"newborn\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🧒\",\n description: \"child\",\n category: \"People & Body\",\n aliases: [\"child\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"👦\",\n description: \"boy\",\n category: \"People & Body\",\n aliases: [\"boy\"],\n tags: [\"child\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"👧\",\n description: \"girl\",\n category: \"People & Body\",\n aliases: [\"girl\"],\n tags: [\"child\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🧑\",\n description: \"person\",\n category: \"People & Body\",\n aliases: [\"adult\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"👱\",\n description: \"person: blond hair\",\n category: \"People & Body\",\n aliases: [\"blond_haired_person\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"👨\",\n description: \"man\",\n category: \"People & Body\",\n aliases: [\"man\"],\n tags: [\"mustache\", \"father\", \"dad\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🧔\",\n description: \"person: beard\",\n category: \"People & Body\",\n aliases: [\"bearded_person\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧔♂️\",\n description: \"man: beard\",\n category: \"People & Body\",\n aliases: [\"man_beard\"],\n tags: [],\n unicode_version: \"13.1\",\n ios_version: \"14.0\",\n skin_tones: true,\n },\n {\n emoji: \"🧔♀️\",\n description: \"woman: beard\",\n category: \"People & Body\",\n aliases: [\"woman_beard\"],\n tags: [],\n unicode_version: \"13.1\",\n ios_version: \"14.0\",\n skin_tones: true,\n },\n {\n emoji: \"👨🦰\",\n description: \"man: red hair\",\n category: \"People & Body\",\n aliases: [\"red_haired_man\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"👨🦱\",\n description: \"man: curly hair\",\n category: \"People & Body\",\n aliases: [\"curly_haired_man\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"👨🦳\",\n description: \"man: white hair\",\n category: \"People & Body\",\n aliases: [\"white_haired_man\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"👨🦲\",\n description: \"man: bald\",\n category: \"People & Body\",\n aliases: [\"bald_man\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"👩\",\n description: \"woman\",\n category: \"People & Body\",\n aliases: [\"woman\"],\n tags: [\"girls\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"👩🦰\",\n description: \"woman: red hair\",\n category: \"People & Body\",\n aliases: [\"red_haired_woman\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧑🦰\",\n description: \"person: red hair\",\n category: \"People & Body\",\n aliases: [\"person_red_hair\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👩🦱\",\n description: \"woman: curly hair\",\n category: \"People & Body\",\n aliases: [\"curly_haired_woman\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧑🦱\",\n description: \"person: curly hair\",\n category: \"People & Body\",\n aliases: [\"person_curly_hair\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👩🦳\",\n description: \"woman: white hair\",\n category: \"People & Body\",\n aliases: [\"white_haired_woman\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧑🦳\",\n description: \"person: white hair\",\n category: \"People & Body\",\n aliases: [\"person_white_hair\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👩🦲\",\n description: \"woman: bald\",\n category: \"People & Body\",\n aliases: [\"bald_woman\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧑🦲\",\n description: \"person: bald\",\n category: \"People & Body\",\n aliases: [\"person_bald\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👱♀️\",\n description: \"woman: blond hair\",\n category: \"People & Body\",\n aliases: [\"blond_haired_woman\", \"blonde_woman\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"👱♂️\",\n description: \"man: blond hair\",\n category: \"People & Body\",\n aliases: [\"blond_haired_man\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧓\",\n description: \"older person\",\n category: \"People & Body\",\n aliases: [\"older_adult\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"👴\",\n description: \"old man\",\n category: \"People & Body\",\n aliases: [\"older_man\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"👵\",\n description: \"old woman\",\n category: \"People & Body\",\n aliases: [\"older_woman\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🙍\",\n description: \"person frowning\",\n category: \"People & Body\",\n aliases: [\"frowning_person\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🙍♂️\",\n description: \"man frowning\",\n category: \"People & Body\",\n aliases: [\"frowning_man\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"🙍♀️\",\n description: \"woman frowning\",\n category: \"People & Body\",\n aliases: [\"frowning_woman\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🙎\",\n description: \"person pouting\",\n category: \"People & Body\",\n aliases: [\"pouting_face\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🙎♂️\",\n description: \"man pouting\",\n category: \"People & Body\",\n aliases: [\"pouting_man\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"🙎♀️\",\n description: \"woman pouting\",\n category: \"People & Body\",\n aliases: [\"pouting_woman\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🙅\",\n description: \"person gesturing NO\",\n category: \"People & Body\",\n aliases: [\"no_good\"],\n tags: [\"stop\", \"halt\", \"denied\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🙅♂️\",\n description: \"man gesturing NO\",\n category: \"People & Body\",\n aliases: [\"no_good_man\", \"ng_man\"],\n tags: [\"stop\", \"halt\", \"denied\"],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"🙅♀️\",\n description: \"woman gesturing NO\",\n category: \"People & Body\",\n aliases: [\"no_good_woman\", \"ng_woman\"],\n tags: [\"stop\", \"halt\", \"denied\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🙆\",\n description: \"person gesturing OK\",\n category: \"People & Body\",\n aliases: [\"ok_person\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🙆♂️\",\n description: \"man gesturing OK\",\n category: \"People & Body\",\n aliases: [\"ok_man\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"🙆♀️\",\n description: \"woman gesturing OK\",\n category: \"People & Body\",\n aliases: [\"ok_woman\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"💁\",\n description: \"person tipping hand\",\n category: \"People & Body\",\n aliases: [\"tipping_hand_person\", \"information_desk_person\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"💁♂️\",\n description: \"man tipping hand\",\n category: \"People & Body\",\n aliases: [\"tipping_hand_man\", \"sassy_man\"],\n tags: [\"information\"],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"💁♀️\",\n description: \"woman tipping hand\",\n category: \"People & Body\",\n aliases: [\"tipping_hand_woman\", \"sassy_woman\"],\n tags: [\"information\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🙋\",\n description: \"person raising hand\",\n category: \"People & Body\",\n aliases: [\"raising_hand\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🙋♂️\",\n description: \"man raising hand\",\n category: \"People & Body\",\n aliases: [\"raising_hand_man\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"🙋♀️\",\n description: \"woman raising hand\",\n category: \"People & Body\",\n aliases: [\"raising_hand_woman\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧏\",\n description: \"deaf person\",\n category: \"People & Body\",\n aliases: [\"deaf_person\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n skin_tones: true,\n },\n {\n emoji: \"🧏♂️\",\n description: \"deaf man\",\n category: \"People & Body\",\n aliases: [\"deaf_man\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n skin_tones: true,\n },\n {\n emoji: \"🧏♀️\",\n description: \"deaf woman\",\n category: \"People & Body\",\n aliases: [\"deaf_woman\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n skin_tones: true,\n },\n {\n emoji: \"🙇\",\n description: \"person bowing\",\n category: \"People & Body\",\n aliases: [\"bow\"],\n tags: [\"respect\", \"thanks\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🙇♂️\",\n description: \"man bowing\",\n category: \"People & Body\",\n aliases: [\"bowing_man\"],\n tags: [\"respect\", \"thanks\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🙇♀️\",\n description: \"woman bowing\",\n category: \"People & Body\",\n aliases: [\"bowing_woman\"],\n tags: [\"respect\", \"thanks\"],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"🤦\",\n description: \"person facepalming\",\n category: \"People & Body\",\n aliases: [\"facepalm\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🤦♂️\",\n description: \"man facepalming\",\n category: \"People & Body\",\n aliases: [\"man_facepalming\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🤦♀️\",\n description: \"woman facepalming\",\n category: \"People & Body\",\n aliases: [\"woman_facepalming\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🤷\",\n description: \"person shrugging\",\n category: \"People & Body\",\n aliases: [\"shrug\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🤷♂️\",\n description: \"man shrugging\",\n category: \"People & Body\",\n aliases: [\"man_shrugging\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🤷♀️\",\n description: \"woman shrugging\",\n category: \"People & Body\",\n aliases: [\"woman_shrugging\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🧑⚕️\",\n description: \"health worker\",\n category: \"People & Body\",\n aliases: [\"health_worker\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👨⚕️\",\n description: \"man health worker\",\n category: \"People & Body\",\n aliases: [\"man_health_worker\"],\n tags: [\"doctor\", \"nurse\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"👩⚕️\",\n description: \"woman health worker\",\n category: \"People & Body\",\n aliases: [\"woman_health_worker\"],\n tags: [\"doctor\", \"nurse\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🧑🎓\",\n description: \"student\",\n category: \"People & Body\",\n aliases: [\"student\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👨🎓\",\n description: \"man student\",\n category: \"People & Body\",\n aliases: [\"man_student\"],\n tags: [\"graduation\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"👩🎓\",\n description: \"woman student\",\n category: \"People & Body\",\n aliases: [\"woman_student\"],\n tags: [\"graduation\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🧑🏫\",\n description: \"teacher\",\n category: \"People & Body\",\n aliases: [\"teacher\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👨🏫\",\n description: \"man teacher\",\n category: \"People & Body\",\n aliases: [\"man_teacher\"],\n tags: [\"school\", \"professor\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"👩🏫\",\n description: \"woman teacher\",\n category: \"People & Body\",\n aliases: [\"woman_teacher\"],\n tags: [\"school\", \"professor\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🧑⚖️\",\n description: \"judge\",\n category: \"People & Body\",\n aliases: [\"judge\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👨⚖️\",\n description: \"man judge\",\n category: \"People & Body\",\n aliases: [\"man_judge\"],\n tags: [\"justice\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"👩⚖️\",\n description: \"woman judge\",\n category: \"People & Body\",\n aliases: [\"woman_judge\"],\n tags: [\"justice\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🧑🌾\",\n description: \"farmer\",\n category: \"People & Body\",\n aliases: [\"farmer\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👨🌾\",\n description: \"man farmer\",\n category: \"People & Body\",\n aliases: [\"man_farmer\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"👩🌾\",\n description: \"woman farmer\",\n category: \"People & Body\",\n aliases: [\"woman_farmer\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🧑🍳\",\n description: \"cook\",\n category: \"People & Body\",\n aliases: [\"cook\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👨🍳\",\n description: \"man cook\",\n category: \"People & Body\",\n aliases: [\"man_cook\"],\n tags: [\"chef\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"👩🍳\",\n description: \"woman cook\",\n category: \"People & Body\",\n aliases: [\"woman_cook\"],\n tags: [\"chef\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🧑🔧\",\n description: \"mechanic\",\n category: \"People & Body\",\n aliases: [\"mechanic\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👨🔧\",\n description: \"man mechanic\",\n category: \"People & Body\",\n aliases: [\"man_mechanic\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"👩🔧\",\n description: \"woman mechanic\",\n category: \"People & Body\",\n aliases: [\"woman_mechanic\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🧑🏭\",\n description: \"factory worker\",\n category: \"People & Body\",\n aliases: [\"factory_worker\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👨🏭\",\n description: \"man factory worker\",\n category: \"People & Body\",\n aliases: [\"man_factory_worker\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"👩🏭\",\n description: \"woman factory worker\",\n category: \"People & Body\",\n aliases: [\"woman_factory_worker\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🧑💼\",\n description: \"office worker\",\n category: \"People & Body\",\n aliases: [\"office_worker\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👨💼\",\n description: \"man office worker\",\n category: \"People & Body\",\n aliases: [\"man_office_worker\"],\n tags: [\"business\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"👩💼\",\n description: \"woman office worker\",\n category: \"People & Body\",\n aliases: [\"woman_office_worker\"],\n tags: [\"business\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🧑🔬\",\n description: \"scientist\",\n category: \"People & Body\",\n aliases: [\"scientist\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👨🔬\",\n description: \"man scientist\",\n category: \"People & Body\",\n aliases: [\"man_scientist\"],\n tags: [\"research\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"👩🔬\",\n description: \"woman scientist\",\n category: \"People & Body\",\n aliases: [\"woman_scientist\"],\n tags: [\"research\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🧑💻\",\n description: \"technologist\",\n category: \"People & Body\",\n aliases: [\"technologist\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👨💻\",\n description: \"man technologist\",\n category: \"People & Body\",\n aliases: [\"man_technologist\"],\n tags: [\"coder\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"👩💻\",\n description: \"woman technologist\",\n category: \"People & Body\",\n aliases: [\"woman_technologist\"],\n tags: [\"coder\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🧑🎤\",\n description: \"singer\",\n category: \"People & Body\",\n aliases: [\"singer\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👨🎤\",\n description: \"man singer\",\n category: \"People & Body\",\n aliases: [\"man_singer\"],\n tags: [\"rockstar\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"👩🎤\",\n description: \"woman singer\",\n category: \"People & Body\",\n aliases: [\"woman_singer\"],\n tags: [\"rockstar\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🧑🎨\",\n description: \"artist\",\n category: \"People & Body\",\n aliases: [\"artist\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👨🎨\",\n description: \"man artist\",\n category: \"People & Body\",\n aliases: [\"man_artist\"],\n tags: [\"painter\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"👩🎨\",\n description: \"woman artist\",\n category: \"People & Body\",\n aliases: [\"woman_artist\"],\n tags: [\"painter\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🧑✈️\",\n description: \"pilot\",\n category: \"People & Body\",\n aliases: [\"pilot\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👨✈️\",\n description: \"man pilot\",\n category: \"People & Body\",\n aliases: [\"man_pilot\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"👩✈️\",\n description: \"woman pilot\",\n category: \"People & Body\",\n aliases: [\"woman_pilot\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🧑🚀\",\n description: \"astronaut\",\n category: \"People & Body\",\n aliases: [\"astronaut\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👨🚀\",\n description: \"man astronaut\",\n category: \"People & Body\",\n aliases: [\"man_astronaut\"],\n tags: [\"space\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"👩🚀\",\n description: \"woman astronaut\",\n category: \"People & Body\",\n aliases: [\"woman_astronaut\"],\n tags: [\"space\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🧑🚒\",\n description: \"firefighter\",\n category: \"People & Body\",\n aliases: [\"firefighter\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👨🚒\",\n description: \"man firefighter\",\n category: \"People & Body\",\n aliases: [\"man_firefighter\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"👩🚒\",\n description: \"woman firefighter\",\n category: \"People & Body\",\n aliases: [\"woman_firefighter\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"👮\",\n description: \"police officer\",\n category: \"People & Body\",\n aliases: [\"police_officer\", \"cop\"],\n tags: [\"law\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"👮♂️\",\n description: \"man police officer\",\n category: \"People & Body\",\n aliases: [\"policeman\"],\n tags: [\"law\", \"cop\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"👮♀️\",\n description: \"woman police officer\",\n category: \"People & Body\",\n aliases: [\"policewoman\"],\n tags: [\"law\", \"cop\"],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"🕵️\",\n description: \"detective\",\n category: \"People & Body\",\n aliases: [\"detective\"],\n tags: [\"sleuth\"],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n skin_tones: true,\n },\n {\n emoji: \"🕵️♂️\",\n description: \"man detective\",\n category: \"People & Body\",\n aliases: [\"male_detective\"],\n tags: [\"sleuth\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🕵️♀️\",\n description: \"woman detective\",\n category: \"People & Body\",\n aliases: [\"female_detective\"],\n tags: [\"sleuth\"],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"💂\",\n description: \"guard\",\n category: \"People & Body\",\n aliases: [\"guard\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"💂♂️\",\n description: \"man guard\",\n category: \"People & Body\",\n aliases: [\"guardsman\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"💂♀️\",\n description: \"woman guard\",\n category: \"People & Body\",\n aliases: [\"guardswoman\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"🥷\",\n description: \"ninja\",\n category: \"People & Body\",\n aliases: [\"ninja\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n skin_tones: true,\n },\n {\n emoji: \"👷\",\n description: \"construction worker\",\n category: \"People & Body\",\n aliases: [\"construction_worker\"],\n tags: [\"helmet\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"👷♂️\",\n description: \"man construction worker\",\n category: \"People & Body\",\n aliases: [\"construction_worker_man\"],\n tags: [\"helmet\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"👷♀️\",\n description: \"woman construction worker\",\n category: \"People & Body\",\n aliases: [\"construction_worker_woman\"],\n tags: [\"helmet\"],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"🤴\",\n description: \"prince\",\n category: \"People & Body\",\n aliases: [\"prince\"],\n tags: [\"crown\", \"royal\"],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"👸\",\n description: \"princess\",\n category: \"People & Body\",\n aliases: [\"princess\"],\n tags: [\"crown\", \"royal\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"👳\",\n description: \"person wearing turban\",\n category: \"People & Body\",\n aliases: [\"person_with_turban\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"👳♂️\",\n description: \"man wearing turban\",\n category: \"People & Body\",\n aliases: [\"man_with_turban\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"👳♀️\",\n description: \"woman wearing turban\",\n category: \"People & Body\",\n aliases: [\"woman_with_turban\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"👲\",\n description: \"person with skullcap\",\n category: \"People & Body\",\n aliases: [\"man_with_gua_pi_mao\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🧕\",\n description: \"woman with headscarf\",\n category: \"People & Body\",\n aliases: [\"woman_with_headscarf\"],\n tags: [\"hijab\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🤵\",\n description: \"person in tuxedo\",\n category: \"People & Body\",\n aliases: [\"person_in_tuxedo\"],\n tags: [\"groom\", \"marriage\", \"wedding\"],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🤵♂️\",\n description: \"man in tuxedo\",\n category: \"People & Body\",\n aliases: [\"man_in_tuxedo\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n skin_tones: true,\n },\n {\n emoji: \"🤵♀️\",\n description: \"woman in tuxedo\",\n category: \"People & Body\",\n aliases: [\"woman_in_tuxedo\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n skin_tones: true,\n },\n {\n emoji: \"👰\",\n description: \"person with veil\",\n category: \"People & Body\",\n aliases: [\"person_with_veil\"],\n tags: [\"marriage\", \"wedding\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"👰♂️\",\n description: \"man with veil\",\n category: \"People & Body\",\n aliases: [\"man_with_veil\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n skin_tones: true,\n },\n {\n emoji: \"👰♀️\",\n description: \"woman with veil\",\n category: \"People & Body\",\n aliases: [\"woman_with_veil\", \"bride_with_veil\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n skin_tones: true,\n },\n {\n emoji: \"🤰\",\n description: \"pregnant woman\",\n category: \"People & Body\",\n aliases: [\"pregnant_woman\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🤱\",\n description: \"breast-feeding\",\n category: \"People & Body\",\n aliases: [\"breast_feeding\"],\n tags: [\"nursing\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"👩🍼\",\n description: \"woman feeding baby\",\n category: \"People & Body\",\n aliases: [\"woman_feeding_baby\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n skin_tones: true,\n },\n {\n emoji: \"👨🍼\",\n description: \"man feeding baby\",\n category: \"People & Body\",\n aliases: [\"man_feeding_baby\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n skin_tones: true,\n },\n {\n emoji: \"🧑🍼\",\n description: \"person feeding baby\",\n category: \"People & Body\",\n aliases: [\"person_feeding_baby\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n skin_tones: true,\n },\n {\n emoji: \"👼\",\n description: \"baby angel\",\n category: \"People & Body\",\n aliases: [\"angel\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🎅\",\n description: \"Santa Claus\",\n category: \"People & Body\",\n aliases: [\"santa\"],\n tags: [\"christmas\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🤶\",\n description: \"Mrs. Claus\",\n category: \"People & Body\",\n aliases: [\"mrs_claus\"],\n tags: [\"santa\"],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🧑🎄\",\n description: \"mx claus\",\n category: \"People & Body\",\n aliases: [\"mx_claus\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n skin_tones: true,\n },\n {\n emoji: \"🦸\",\n description: \"superhero\",\n category: \"People & Body\",\n aliases: [\"superhero\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🦸♂️\",\n description: \"man superhero\",\n category: \"People & Body\",\n aliases: [\"superhero_man\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🦸♀️\",\n description: \"woman superhero\",\n category: \"People & Body\",\n aliases: [\"superhero_woman\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🦹\",\n description: \"supervillain\",\n category: \"People & Body\",\n aliases: [\"supervillain\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🦹♂️\",\n description: \"man supervillain\",\n category: \"People & Body\",\n aliases: [\"supervillain_man\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🦹♀️\",\n description: \"woman supervillain\",\n category: \"People & Body\",\n aliases: [\"supervillain_woman\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧙\",\n description: \"mage\",\n category: \"People & Body\",\n aliases: [\"mage\"],\n tags: [\"wizard\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧙♂️\",\n description: \"man mage\",\n category: \"People & Body\",\n aliases: [\"mage_man\"],\n tags: [\"wizard\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧙♀️\",\n description: \"woman mage\",\n category: \"People & Body\",\n aliases: [\"mage_woman\"],\n tags: [\"wizard\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧚\",\n description: \"fairy\",\n category: \"People & Body\",\n aliases: [\"fairy\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧚♂️\",\n description: \"man fairy\",\n category: \"People & Body\",\n aliases: [\"fairy_man\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧚♀️\",\n description: \"woman fairy\",\n category: \"People & Body\",\n aliases: [\"fairy_woman\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧛\",\n description: \"vampire\",\n category: \"People & Body\",\n aliases: [\"vampire\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧛♂️\",\n description: \"man vampire\",\n category: \"People & Body\",\n aliases: [\"vampire_man\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧛♀️\",\n description: \"woman vampire\",\n category: \"People & Body\",\n aliases: [\"vampire_woman\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧜\",\n description: \"merperson\",\n category: \"People & Body\",\n aliases: [\"merperson\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧜♂️\",\n description: \"merman\",\n category: \"People & Body\",\n aliases: [\"merman\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧜♀️\",\n description: \"mermaid\",\n category: \"People & Body\",\n aliases: [\"mermaid\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧝\",\n description: \"elf\",\n category: \"People & Body\",\n aliases: [\"elf\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧝♂️\",\n description: \"man elf\",\n category: \"People & Body\",\n aliases: [\"elf_man\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧝♀️\",\n description: \"woman elf\",\n category: \"People & Body\",\n aliases: [\"elf_woman\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧞\",\n description: \"genie\",\n category: \"People & Body\",\n aliases: [\"genie\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🧞♂️\",\n description: \"man genie\",\n category: \"People & Body\",\n aliases: [\"genie_man\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🧞♀️\",\n description: \"woman genie\",\n category: \"People & Body\",\n aliases: [\"genie_woman\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🧟\",\n description: \"zombie\",\n category: \"People & Body\",\n aliases: [\"zombie\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🧟♂️\",\n description: \"man zombie\",\n category: \"People & Body\",\n aliases: [\"zombie_man\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🧟♀️\",\n description: \"woman zombie\",\n category: \"People & Body\",\n aliases: [\"zombie_woman\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"💆\",\n description: \"person getting massage\",\n category: \"People & Body\",\n aliases: [\"massage\"],\n tags: [\"spa\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"💆♂️\",\n description: \"man getting massage\",\n category: \"People & Body\",\n aliases: [\"massage_man\"],\n tags: [\"spa\"],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"💆♀️\",\n description: \"woman getting massage\",\n category: \"People & Body\",\n aliases: [\"massage_woman\"],\n tags: [\"spa\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"💇\",\n description: \"person getting haircut\",\n category: \"People & Body\",\n aliases: [\"haircut\"],\n tags: [\"beauty\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"💇♂️\",\n description: \"man getting haircut\",\n category: \"People & Body\",\n aliases: [\"haircut_man\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"💇♀️\",\n description: \"woman getting haircut\",\n category: \"People & Body\",\n aliases: [\"haircut_woman\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🚶\",\n description: \"person walking\",\n category: \"People & Body\",\n aliases: [\"walking\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🚶♂️\",\n description: \"man walking\",\n category: \"People & Body\",\n aliases: [\"walking_man\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🚶♀️\",\n description: \"woman walking\",\n category: \"People & Body\",\n aliases: [\"walking_woman\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"🧍\",\n description: \"person standing\",\n category: \"People & Body\",\n aliases: [\"standing_person\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n skin_tones: true,\n },\n {\n emoji: \"🧍♂️\",\n description: \"man standing\",\n category: \"People & Body\",\n aliases: [\"standing_man\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n skin_tones: true,\n },\n {\n emoji: \"🧍♀️\",\n description: \"woman standing\",\n category: \"People & Body\",\n aliases: [\"standing_woman\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n skin_tones: true,\n },\n {\n emoji: \"🧎\",\n description: \"person kneeling\",\n category: \"People & Body\",\n aliases: [\"kneeling_person\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n skin_tones: true,\n },\n {\n emoji: \"🧎♂️\",\n description: \"man kneeling\",\n category: \"People & Body\",\n aliases: [\"kneeling_man\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n skin_tones: true,\n },\n {\n emoji: \"🧎♀️\",\n description: \"woman kneeling\",\n category: \"People & Body\",\n aliases: [\"kneeling_woman\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n skin_tones: true,\n },\n {\n emoji: \"🧑🦯\",\n description: \"person with white cane\",\n category: \"People & Body\",\n aliases: [\"person_with_probing_cane\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👨🦯\",\n description: \"man with white cane\",\n category: \"People & Body\",\n aliases: [\"man_with_probing_cane\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n skin_tones: true,\n },\n {\n emoji: \"👩🦯\",\n description: \"woman with white cane\",\n category: \"People & Body\",\n aliases: [\"woman_with_probing_cane\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n skin_tones: true,\n },\n {\n emoji: \"🧑🦼\",\n description: \"person in motorized wheelchair\",\n category: \"People & Body\",\n aliases: [\"person_in_motorized_wheelchair\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👨🦼\",\n description: \"man in motorized wheelchair\",\n category: \"People & Body\",\n aliases: [\"man_in_motorized_wheelchair\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n skin_tones: true,\n },\n {\n emoji: \"👩🦼\",\n description: \"woman in motorized wheelchair\",\n category: \"People & Body\",\n aliases: [\"woman_in_motorized_wheelchair\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n skin_tones: true,\n },\n {\n emoji: \"🧑🦽\",\n description: \"person in manual wheelchair\",\n category: \"People & Body\",\n aliases: [\"person_in_manual_wheelchair\"],\n tags: [],\n unicode_version: \"12.1\",\n ios_version: \"13.2\",\n skin_tones: true,\n },\n {\n emoji: \"👨🦽\",\n description: \"man in manual wheelchair\",\n category: \"People & Body\",\n aliases: [\"man_in_manual_wheelchair\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n skin_tones: true,\n },\n {\n emoji: \"👩🦽\",\n description: \"woman in manual wheelchair\",\n category: \"People & Body\",\n aliases: [\"woman_in_manual_wheelchair\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n skin_tones: true,\n },\n {\n emoji: \"🏃\",\n description: \"person running\",\n category: \"People & Body\",\n aliases: [\"runner\", \"running\"],\n tags: [\"exercise\", \"workout\", \"marathon\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🏃♂️\",\n description: \"man running\",\n category: \"People & Body\",\n aliases: [\"running_man\"],\n tags: [\"exercise\", \"workout\", \"marathon\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🏃♀️\",\n description: \"woman running\",\n category: \"People & Body\",\n aliases: [\"running_woman\"],\n tags: [\"exercise\", \"workout\", \"marathon\"],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"💃\",\n description: \"woman dancing\",\n category: \"People & Body\",\n aliases: [\"woman_dancing\", \"dancer\"],\n tags: [\"dress\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🕺\",\n description: \"man dancing\",\n category: \"People & Body\",\n aliases: [\"man_dancing\"],\n tags: [\"dancer\"],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🕴️\",\n description: \"person in suit levitating\",\n category: \"People & Body\",\n aliases: [\"business_suit_levitating\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n skin_tones: true,\n },\n {\n emoji: \"👯\",\n description: \"people with bunny ears\",\n category: \"People & Body\",\n aliases: [\"dancers\"],\n tags: [\"bunny\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👯♂️\",\n description: \"men with bunny ears\",\n category: \"People & Body\",\n aliases: [\"dancing_men\"],\n tags: [\"bunny\"],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n },\n {\n emoji: \"👯♀️\",\n description: \"women with bunny ears\",\n category: \"People & Body\",\n aliases: [\"dancing_women\"],\n tags: [\"bunny\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🧖\",\n description: \"person in steamy room\",\n category: \"People & Body\",\n aliases: [\"sauna_person\"],\n tags: [\"steamy\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧖♂️\",\n description: \"man in steamy room\",\n category: \"People & Body\",\n aliases: [\"sauna_man\"],\n tags: [\"steamy\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧖♀️\",\n description: \"woman in steamy room\",\n category: \"People & Body\",\n aliases: [\"sauna_woman\"],\n tags: [\"steamy\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧗\",\n description: \"person climbing\",\n category: \"People & Body\",\n aliases: [\"climbing\"],\n tags: [\"bouldering\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧗♂️\",\n description: \"man climbing\",\n category: \"People & Body\",\n aliases: [\"climbing_man\"],\n tags: [\"bouldering\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧗♀️\",\n description: \"woman climbing\",\n category: \"People & Body\",\n aliases: [\"climbing_woman\"],\n tags: [\"bouldering\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🤺\",\n description: \"person fencing\",\n category: \"People & Body\",\n aliases: [\"person_fencing\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🏇\",\n description: \"horse racing\",\n category: \"People & Body\",\n aliases: [\"horse_racing\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"⛷️\",\n description: \"skier\",\n category: \"People & Body\",\n aliases: [\"skier\"],\n tags: [],\n unicode_version: \"5.2\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🏂\",\n description: \"snowboarder\",\n category: \"People & Body\",\n aliases: [\"snowboarder\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🏌️\",\n description: \"person golfing\",\n category: \"People & Body\",\n aliases: [\"golfing\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n skin_tones: true,\n },\n {\n emoji: \"🏌️♂️\",\n description: \"man golfing\",\n category: \"People & Body\",\n aliases: [\"golfing_man\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🏌️♀️\",\n description: \"woman golfing\",\n category: \"People & Body\",\n aliases: [\"golfing_woman\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"🏄\",\n description: \"person surfing\",\n category: \"People & Body\",\n aliases: [\"surfer\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🏄♂️\",\n description: \"man surfing\",\n category: \"People & Body\",\n aliases: [\"surfing_man\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🏄♀️\",\n description: \"woman surfing\",\n category: \"People & Body\",\n aliases: [\"surfing_woman\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"🚣\",\n description: \"person rowing boat\",\n category: \"People & Body\",\n aliases: [\"rowboat\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🚣♂️\",\n description: \"man rowing boat\",\n category: \"People & Body\",\n aliases: [\"rowing_man\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🚣♀️\",\n description: \"woman rowing boat\",\n category: \"People & Body\",\n aliases: [\"rowing_woman\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"🏊\",\n description: \"person swimming\",\n category: \"People & Body\",\n aliases: [\"swimmer\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🏊♂️\",\n description: \"man swimming\",\n category: \"People & Body\",\n aliases: [\"swimming_man\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🏊♀️\",\n description: \"woman swimming\",\n category: \"People & Body\",\n aliases: [\"swimming_woman\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"⛹️\",\n description: \"person bouncing ball\",\n category: \"People & Body\",\n aliases: [\"bouncing_ball_person\"],\n tags: [\"basketball\"],\n unicode_version: \"5.2\",\n ios_version: \"9.1\",\n skin_tones: true,\n },\n {\n emoji: \"⛹️♂️\",\n description: \"man bouncing ball\",\n category: \"People & Body\",\n aliases: [\"bouncing_ball_man\", \"basketball_man\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"⛹️♀️\",\n description: \"woman bouncing ball\",\n category: \"People & Body\",\n aliases: [\"bouncing_ball_woman\", \"basketball_woman\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"🏋️\",\n description: \"person lifting weights\",\n category: \"People & Body\",\n aliases: [\"weight_lifting\"],\n tags: [\"gym\", \"workout\"],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n skin_tones: true,\n },\n {\n emoji: \"🏋️♂️\",\n description: \"man lifting weights\",\n category: \"People & Body\",\n aliases: [\"weight_lifting_man\"],\n tags: [\"gym\", \"workout\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🏋️♀️\",\n description: \"woman lifting weights\",\n category: \"People & Body\",\n aliases: [\"weight_lifting_woman\"],\n tags: [\"gym\", \"workout\"],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"🚴\",\n description: \"person biking\",\n category: \"People & Body\",\n aliases: [\"bicyclist\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🚴♂️\",\n description: \"man biking\",\n category: \"People & Body\",\n aliases: [\"biking_man\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🚴♀️\",\n description: \"woman biking\",\n category: \"People & Body\",\n aliases: [\"biking_woman\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"🚵\",\n description: \"person mountain biking\",\n category: \"People & Body\",\n aliases: [\"mountain_bicyclist\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🚵♂️\",\n description: \"man mountain biking\",\n category: \"People & Body\",\n aliases: [\"mountain_biking_man\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🚵♀️\",\n description: \"woman mountain biking\",\n category: \"People & Body\",\n aliases: [\"mountain_biking_woman\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n skin_tones: true,\n },\n {\n emoji: \"🤸\",\n description: \"person cartwheeling\",\n category: \"People & Body\",\n aliases: [\"cartwheeling\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🤸♂️\",\n description: \"man cartwheeling\",\n category: \"People & Body\",\n aliases: [\"man_cartwheeling\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🤸♀️\",\n description: \"woman cartwheeling\",\n category: \"People & Body\",\n aliases: [\"woman_cartwheeling\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🤼\",\n description: \"people wrestling\",\n category: \"People & Body\",\n aliases: [\"wrestling\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🤼♂️\",\n description: \"men wrestling\",\n category: \"People & Body\",\n aliases: [\"men_wrestling\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🤼♀️\",\n description: \"women wrestling\",\n category: \"People & Body\",\n aliases: [\"women_wrestling\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🤽\",\n description: \"person playing water polo\",\n category: \"People & Body\",\n aliases: [\"water_polo\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🤽♂️\",\n description: \"man playing water polo\",\n category: \"People & Body\",\n aliases: [\"man_playing_water_polo\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🤽♀️\",\n description: \"woman playing water polo\",\n category: \"People & Body\",\n aliases: [\"woman_playing_water_polo\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🤾\",\n description: \"person playing handball\",\n category: \"People & Body\",\n aliases: [\"handball_person\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🤾♂️\",\n description: \"man playing handball\",\n category: \"People & Body\",\n aliases: [\"man_playing_handball\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🤾♀️\",\n description: \"woman playing handball\",\n category: \"People & Body\",\n aliases: [\"woman_playing_handball\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🤹\",\n description: \"person juggling\",\n category: \"People & Body\",\n aliases: [\"juggling_person\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🤹♂️\",\n description: \"man juggling\",\n category: \"People & Body\",\n aliases: [\"man_juggling\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🤹♀️\",\n description: \"woman juggling\",\n category: \"People & Body\",\n aliases: [\"woman_juggling\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n skin_tones: true,\n },\n {\n emoji: \"🧘\",\n description: \"person in lotus position\",\n category: \"People & Body\",\n aliases: [\"lotus_position\"],\n tags: [\"meditation\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧘♂️\",\n description: \"man in lotus position\",\n category: \"People & Body\",\n aliases: [\"lotus_position_man\"],\n tags: [\"meditation\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧘♀️\",\n description: \"woman in lotus position\",\n category: \"People & Body\",\n aliases: [\"lotus_position_woman\"],\n tags: [\"meditation\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"🛀\",\n description: \"person taking bath\",\n category: \"People & Body\",\n aliases: [\"bath\"],\n tags: [\"shower\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"🛌\",\n description: \"person in bed\",\n category: \"People & Body\",\n aliases: [\"sleeping_bed\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n skin_tones: true,\n },\n {\n emoji: \"🧑🤝🧑\",\n description: \"people holding hands\",\n category: \"People & Body\",\n aliases: [\"people_holding_hands\"],\n tags: [\"couple\", \"date\"],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n skin_tones: true,\n },\n {\n emoji: \"👭\",\n description: \"women holding hands\",\n category: \"People & Body\",\n aliases: [\"two_women_holding_hands\"],\n tags: [\"couple\", \"date\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"👫\",\n description: \"woman and man holding hands\",\n category: \"People & Body\",\n aliases: [\"couple\"],\n tags: [\"date\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"👬\",\n description: \"men holding hands\",\n category: \"People & Body\",\n aliases: [\"two_men_holding_hands\"],\n tags: [\"couple\", \"date\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"💏\",\n description: \"kiss\",\n category: \"People & Body\",\n aliases: [\"couplekiss\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"👩❤️💋👨\",\n description: \"kiss: woman, man\",\n category: \"People & Body\",\n aliases: [\"couplekiss_man_woman\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"👨❤️💋👨\",\n description: \"kiss: man, man\",\n category: \"People & Body\",\n aliases: [\"couplekiss_man_man\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n skin_tones: true,\n },\n {\n emoji: \"👩❤️💋👩\",\n description: \"kiss: woman, woman\",\n category: \"People & Body\",\n aliases: [\"couplekiss_woman_woman\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n skin_tones: true,\n },\n {\n emoji: \"💑\",\n description: \"couple with heart\",\n category: \"People & Body\",\n aliases: [\"couple_with_heart\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n skin_tones: true,\n },\n {\n emoji: \"👩❤️👨\",\n description: \"couple with heart: woman, man\",\n category: \"People & Body\",\n aliases: [\"couple_with_heart_woman_man\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n skin_tones: true,\n },\n {\n emoji: \"👨❤️👨\",\n description: \"couple with heart: man, man\",\n category: \"People & Body\",\n aliases: [\"couple_with_heart_man_man\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n skin_tones: true,\n },\n {\n emoji: \"👩❤️👩\",\n description: \"couple with heart: woman, woman\",\n category: \"People & Body\",\n aliases: [\"couple_with_heart_woman_woman\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n skin_tones: true,\n },\n {\n emoji: \"👪\",\n description: \"family\",\n category: \"People & Body\",\n aliases: [\"family\"],\n tags: [\"home\", \"parents\", \"child\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👨👩👦\",\n description: \"family: man, woman, boy\",\n category: \"People & Body\",\n aliases: [\"family_man_woman_boy\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"👨👩👧\",\n description: \"family: man, woman, girl\",\n category: \"People & Body\",\n aliases: [\"family_man_woman_girl\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"👨👩👧👦\",\n description: \"family: man, woman, girl, boy\",\n category: \"People & Body\",\n aliases: [\"family_man_woman_girl_boy\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"👨👩👦👦\",\n description: \"family: man, woman, boy, boy\",\n category: \"People & Body\",\n aliases: [\"family_man_woman_boy_boy\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"👨👩👧👧\",\n description: \"family: man, woman, girl, girl\",\n category: \"People & Body\",\n aliases: [\"family_man_woman_girl_girl\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"👨👨👦\",\n description: \"family: man, man, boy\",\n category: \"People & Body\",\n aliases: [\"family_man_man_boy\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"👨👨👧\",\n description: \"family: man, man, girl\",\n category: \"People & Body\",\n aliases: [\"family_man_man_girl\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"👨👨👧👦\",\n description: \"family: man, man, girl, boy\",\n category: \"People & Body\",\n aliases: [\"family_man_man_girl_boy\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"👨👨👦👦\",\n description: \"family: man, man, boy, boy\",\n category: \"People & Body\",\n aliases: [\"family_man_man_boy_boy\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"👨👨👧👧\",\n description: \"family: man, man, girl, girl\",\n category: \"People & Body\",\n aliases: [\"family_man_man_girl_girl\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"👩👩👦\",\n description: \"family: woman, woman, boy\",\n category: \"People & Body\",\n aliases: [\"family_woman_woman_boy\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"👩👩👧\",\n description: \"family: woman, woman, girl\",\n category: \"People & Body\",\n aliases: [\"family_woman_woman_girl\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"👩👩👧👦\",\n description: \"family: woman, woman, girl, boy\",\n category: \"People & Body\",\n aliases: [\"family_woman_woman_girl_boy\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"👩👩👦👦\",\n description: \"family: woman, woman, boy, boy\",\n category: \"People & Body\",\n aliases: [\"family_woman_woman_boy_boy\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"👩👩👧👧\",\n description: \"family: woman, woman, girl, girl\",\n category: \"People & Body\",\n aliases: [\"family_woman_woman_girl_girl\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"👨👦\",\n description: \"family: man, boy\",\n category: \"People & Body\",\n aliases: [\"family_man_boy\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n },\n {\n emoji: \"👨👦👦\",\n description: \"family: man, boy, boy\",\n category: \"People & Body\",\n aliases: [\"family_man_boy_boy\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n },\n {\n emoji: \"👨👧\",\n description: \"family: man, girl\",\n category: \"People & Body\",\n aliases: [\"family_man_girl\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n },\n {\n emoji: \"👨👧👦\",\n description: \"family: man, girl, boy\",\n category: \"People & Body\",\n aliases: [\"family_man_girl_boy\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n },\n {\n emoji: \"👨👧👧\",\n description: \"family: man, girl, girl\",\n category: \"People & Body\",\n aliases: [\"family_man_girl_girl\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n },\n {\n emoji: \"👩👦\",\n description: \"family: woman, boy\",\n category: \"People & Body\",\n aliases: [\"family_woman_boy\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n },\n {\n emoji: \"👩👦👦\",\n description: \"family: woman, boy, boy\",\n category: \"People & Body\",\n aliases: [\"family_woman_boy_boy\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n },\n {\n emoji: \"👩👧\",\n description: \"family: woman, girl\",\n category: \"People & Body\",\n aliases: [\"family_woman_girl\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n },\n {\n emoji: \"👩👧👦\",\n description: \"family: woman, girl, boy\",\n category: \"People & Body\",\n aliases: [\"family_woman_girl_boy\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n },\n {\n emoji: \"👩👧👧\",\n description: \"family: woman, girl, girl\",\n category: \"People & Body\",\n aliases: [\"family_woman_girl_girl\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n },\n {\n emoji: \"🗣️\",\n description: \"speaking head\",\n category: \"People & Body\",\n aliases: [\"speaking_head\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"👤\",\n description: \"bust in silhouette\",\n category: \"People & Body\",\n aliases: [\"bust_in_silhouette\"],\n tags: [\"user\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👥\",\n description: \"busts in silhouette\",\n category: \"People & Body\",\n aliases: [\"busts_in_silhouette\"],\n tags: [\"users\", \"group\", \"team\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🫂\",\n description: \"people hugging\",\n category: \"People & Body\",\n aliases: [\"people_hugging\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"👣\",\n description: \"footprints\",\n category: \"People & Body\",\n aliases: [\"footprints\"],\n tags: [\"feet\", \"tracks\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐵\",\n description: \"monkey face\",\n category: \"Animals & Nature\",\n aliases: [\"monkey_face\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐒\",\n description: \"monkey\",\n category: \"Animals & Nature\",\n aliases: [\"monkey\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🦍\",\n description: \"gorilla\",\n category: \"Animals & Nature\",\n aliases: [\"gorilla\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🦧\",\n description: \"orangutan\",\n category: \"Animals & Nature\",\n aliases: [\"orangutan\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🐶\",\n description: \"dog face\",\n category: \"Animals & Nature\",\n aliases: [\"dog\"],\n tags: [\"pet\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐕\",\n description: \"dog\",\n category: \"Animals & Nature\",\n aliases: [\"dog2\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🦮\",\n description: \"guide dog\",\n category: \"Animals & Nature\",\n aliases: [\"guide_dog\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🐕🦺\",\n description: \"service dog\",\n category: \"Animals & Nature\",\n aliases: [\"service_dog\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🐩\",\n description: \"poodle\",\n category: \"Animals & Nature\",\n aliases: [\"poodle\"],\n tags: [\"dog\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐺\",\n description: \"wolf\",\n category: \"Animals & Nature\",\n aliases: [\"wolf\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🦊\",\n description: \"fox\",\n category: \"Animals & Nature\",\n aliases: [\"fox_face\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🦝\",\n description: \"raccoon\",\n category: \"Animals & Nature\",\n aliases: [\"raccoon\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🐱\",\n description: \"cat face\",\n category: \"Animals & Nature\",\n aliases: [\"cat\"],\n tags: [\"pet\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐈\",\n description: \"cat\",\n category: \"Animals & Nature\",\n aliases: [\"cat2\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐈⬛\",\n description: \"black cat\",\n category: \"Animals & Nature\",\n aliases: [\"black_cat\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🦁\",\n description: \"lion\",\n category: \"Animals & Nature\",\n aliases: [\"lion\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🐯\",\n description: \"tiger face\",\n category: \"Animals & Nature\",\n aliases: [\"tiger\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐅\",\n description: \"tiger\",\n category: \"Animals & Nature\",\n aliases: [\"tiger2\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐆\",\n description: \"leopard\",\n category: \"Animals & Nature\",\n aliases: [\"leopard\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐴\",\n description: \"horse face\",\n category: \"Animals & Nature\",\n aliases: [\"horse\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐎\",\n description: \"horse\",\n category: \"Animals & Nature\",\n aliases: [\"racehorse\"],\n tags: [\"speed\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🦄\",\n description: \"unicorn\",\n category: \"Animals & Nature\",\n aliases: [\"unicorn\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🦓\",\n description: \"zebra\",\n category: \"Animals & Nature\",\n aliases: [\"zebra\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🦌\",\n description: \"deer\",\n category: \"Animals & Nature\",\n aliases: [\"deer\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🦬\",\n description: \"bison\",\n category: \"Animals & Nature\",\n aliases: [\"bison\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🐮\",\n description: \"cow face\",\n category: \"Animals & Nature\",\n aliases: [\"cow\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐂\",\n description: \"ox\",\n category: \"Animals & Nature\",\n aliases: [\"ox\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐃\",\n description: \"water buffalo\",\n category: \"Animals & Nature\",\n aliases: [\"water_buffalo\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐄\",\n description: \"cow\",\n category: \"Animals & Nature\",\n aliases: [\"cow2\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐷\",\n description: \"pig face\",\n category: \"Animals & Nature\",\n aliases: [\"pig\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐖\",\n description: \"pig\",\n category: \"Animals & Nature\",\n aliases: [\"pig2\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐗\",\n description: \"boar\",\n category: \"Animals & Nature\",\n aliases: [\"boar\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐽\",\n description: \"pig nose\",\n category: \"Animals & Nature\",\n aliases: [\"pig_nose\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐏\",\n description: \"ram\",\n category: \"Animals & Nature\",\n aliases: [\"ram\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐑\",\n description: \"ewe\",\n category: \"Animals & Nature\",\n aliases: [\"sheep\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐐\",\n description: \"goat\",\n category: \"Animals & Nature\",\n aliases: [\"goat\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐪\",\n description: \"camel\",\n category: \"Animals & Nature\",\n aliases: [\"dromedary_camel\"],\n tags: [\"desert\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐫\",\n description: \"two-hump camel\",\n category: \"Animals & Nature\",\n aliases: [\"camel\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🦙\",\n description: \"llama\",\n category: \"Animals & Nature\",\n aliases: [\"llama\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🦒\",\n description: \"giraffe\",\n category: \"Animals & Nature\",\n aliases: [\"giraffe\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🐘\",\n description: \"elephant\",\n category: \"Animals & Nature\",\n aliases: [\"elephant\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🦣\",\n description: \"mammoth\",\n category: \"Animals & Nature\",\n aliases: [\"mammoth\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🦏\",\n description: \"rhinoceros\",\n category: \"Animals & Nature\",\n aliases: [\"rhinoceros\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🦛\",\n description: \"hippopotamus\",\n category: \"Animals & Nature\",\n aliases: [\"hippopotamus\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🐭\",\n description: \"mouse face\",\n category: \"Animals & Nature\",\n aliases: [\"mouse\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐁\",\n description: \"mouse\",\n category: \"Animals & Nature\",\n aliases: [\"mouse2\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐀\",\n description: \"rat\",\n category: \"Animals & Nature\",\n aliases: [\"rat\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐹\",\n description: \"hamster\",\n category: \"Animals & Nature\",\n aliases: [\"hamster\"],\n tags: [\"pet\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐰\",\n description: \"rabbit face\",\n category: \"Animals & Nature\",\n aliases: [\"rabbit\"],\n tags: [\"bunny\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐇\",\n description: \"rabbit\",\n category: \"Animals & Nature\",\n aliases: [\"rabbit2\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐿️\",\n description: \"chipmunk\",\n category: \"Animals & Nature\",\n aliases: [\"chipmunk\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🦫\",\n description: \"beaver\",\n category: \"Animals & Nature\",\n aliases: [\"beaver\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🦔\",\n description: \"hedgehog\",\n category: \"Animals & Nature\",\n aliases: [\"hedgehog\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🦇\",\n description: \"bat\",\n category: \"Animals & Nature\",\n aliases: [\"bat\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🐻\",\n description: \"bear\",\n category: \"Animals & Nature\",\n aliases: [\"bear\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐻❄️\",\n description: \"polar bear\",\n category: \"Animals & Nature\",\n aliases: [\"polar_bear\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🐨\",\n description: \"koala\",\n category: \"Animals & Nature\",\n aliases: [\"koala\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐼\",\n description: \"panda\",\n category: \"Animals & Nature\",\n aliases: [\"panda_face\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🦥\",\n description: \"sloth\",\n category: \"Animals & Nature\",\n aliases: [\"sloth\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🦦\",\n description: \"otter\",\n category: \"Animals & Nature\",\n aliases: [\"otter\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🦨\",\n description: \"skunk\",\n category: \"Animals & Nature\",\n aliases: [\"skunk\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🦘\",\n description: \"kangaroo\",\n category: \"Animals & Nature\",\n aliases: [\"kangaroo\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🦡\",\n description: \"badger\",\n category: \"Animals & Nature\",\n aliases: [\"badger\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🐾\",\n description: \"paw prints\",\n category: \"Animals & Nature\",\n aliases: [\"feet\", \"paw_prints\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🦃\",\n description: \"turkey\",\n category: \"Animals & Nature\",\n aliases: [\"turkey\"],\n tags: [\"thanksgiving\"],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🐔\",\n description: \"chicken\",\n category: \"Animals & Nature\",\n aliases: [\"chicken\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐓\",\n description: \"rooster\",\n category: \"Animals & Nature\",\n aliases: [\"rooster\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐣\",\n description: \"hatching chick\",\n category: \"Animals & Nature\",\n aliases: [\"hatching_chick\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐤\",\n description: \"baby chick\",\n category: \"Animals & Nature\",\n aliases: [\"baby_chick\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐥\",\n description: \"front-facing baby chick\",\n category: \"Animals & Nature\",\n aliases: [\"hatched_chick\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐦\",\n description: \"bird\",\n category: \"Animals & Nature\",\n aliases: [\"bird\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐧\",\n description: \"penguin\",\n category: \"Animals & Nature\",\n aliases: [\"penguin\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕊️\",\n description: \"dove\",\n category: \"Animals & Nature\",\n aliases: [\"dove\"],\n tags: [\"peace\"],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🦅\",\n description: \"eagle\",\n category: \"Animals & Nature\",\n aliases: [\"eagle\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🦆\",\n description: \"duck\",\n category: \"Animals & Nature\",\n aliases: [\"duck\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🦢\",\n description: \"swan\",\n category: \"Animals & Nature\",\n aliases: [\"swan\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🦉\",\n description: \"owl\",\n category: \"Animals & Nature\",\n aliases: [\"owl\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🦤\",\n description: \"dodo\",\n category: \"Animals & Nature\",\n aliases: [\"dodo\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🪶\",\n description: \"feather\",\n category: \"Animals & Nature\",\n aliases: [\"feather\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🦩\",\n description: \"flamingo\",\n category: \"Animals & Nature\",\n aliases: [\"flamingo\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🦚\",\n description: \"peacock\",\n category: \"Animals & Nature\",\n aliases: [\"peacock\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🦜\",\n description: \"parrot\",\n category: \"Animals & Nature\",\n aliases: [\"parrot\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🐸\",\n description: \"frog\",\n category: \"Animals & Nature\",\n aliases: [\"frog\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐊\",\n description: \"crocodile\",\n category: \"Animals & Nature\",\n aliases: [\"crocodile\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐢\",\n description: \"turtle\",\n category: \"Animals & Nature\",\n aliases: [\"turtle\"],\n tags: [\"slow\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🦎\",\n description: \"lizard\",\n category: \"Animals & Nature\",\n aliases: [\"lizard\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🐍\",\n description: \"snake\",\n category: \"Animals & Nature\",\n aliases: [\"snake\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐲\",\n description: \"dragon face\",\n category: \"Animals & Nature\",\n aliases: [\"dragon_face\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐉\",\n description: \"dragon\",\n category: \"Animals & Nature\",\n aliases: [\"dragon\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🦕\",\n description: \"sauropod\",\n category: \"Animals & Nature\",\n aliases: [\"sauropod\"],\n tags: [\"dinosaur\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🦖\",\n description: \"T-Rex\",\n category: \"Animals & Nature\",\n aliases: [\"t-rex\"],\n tags: [\"dinosaur\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🐳\",\n description: \"spouting whale\",\n category: \"Animals & Nature\",\n aliases: [\"whale\"],\n tags: [\"sea\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐋\",\n description: \"whale\",\n category: \"Animals & Nature\",\n aliases: [\"whale2\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐬\",\n description: \"dolphin\",\n category: \"Animals & Nature\",\n aliases: [\"dolphin\", \"flipper\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🦭\",\n description: \"seal\",\n category: \"Animals & Nature\",\n aliases: [\"seal\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🐟\",\n description: \"fish\",\n category: \"Animals & Nature\",\n aliases: [\"fish\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐠\",\n description: \"tropical fish\",\n category: \"Animals & Nature\",\n aliases: [\"tropical_fish\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐡\",\n description: \"blowfish\",\n category: \"Animals & Nature\",\n aliases: [\"blowfish\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🦈\",\n description: \"shark\",\n category: \"Animals & Nature\",\n aliases: [\"shark\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🐙\",\n description: \"octopus\",\n category: \"Animals & Nature\",\n aliases: [\"octopus\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐚\",\n description: \"spiral shell\",\n category: \"Animals & Nature\",\n aliases: [\"shell\"],\n tags: [\"sea\", \"beach\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐌\",\n description: \"snail\",\n category: \"Animals & Nature\",\n aliases: [\"snail\"],\n tags: [\"slow\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🦋\",\n description: \"butterfly\",\n category: \"Animals & Nature\",\n aliases: [\"butterfly\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🐛\",\n description: \"bug\",\n category: \"Animals & Nature\",\n aliases: [\"bug\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐜\",\n description: \"ant\",\n category: \"Animals & Nature\",\n aliases: [\"ant\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🐝\",\n description: \"honeybee\",\n category: \"Animals & Nature\",\n aliases: [\"bee\", \"honeybee\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🪲\",\n description: \"beetle\",\n category: \"Animals & Nature\",\n aliases: [\"beetle\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🐞\",\n description: \"lady beetle\",\n category: \"Animals & Nature\",\n aliases: [\"lady_beetle\"],\n tags: [\"bug\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🦗\",\n description: \"cricket\",\n category: \"Animals & Nature\",\n aliases: [\"cricket\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🪳\",\n description: \"cockroach\",\n category: \"Animals & Nature\",\n aliases: [\"cockroach\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🕷️\",\n description: \"spider\",\n category: \"Animals & Nature\",\n aliases: [\"spider\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🕸️\",\n description: \"spider web\",\n category: \"Animals & Nature\",\n aliases: [\"spider_web\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🦂\",\n description: \"scorpion\",\n category: \"Animals & Nature\",\n aliases: [\"scorpion\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🦟\",\n description: \"mosquito\",\n category: \"Animals & Nature\",\n aliases: [\"mosquito\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🪰\",\n description: \"fly\",\n category: \"Animals & Nature\",\n aliases: [\"fly\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🪱\",\n description: \"worm\",\n category: \"Animals & Nature\",\n aliases: [\"worm\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🦠\",\n description: \"microbe\",\n category: \"Animals & Nature\",\n aliases: [\"microbe\"],\n tags: [\"germ\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"💐\",\n description: \"bouquet\",\n category: \"Animals & Nature\",\n aliases: [\"bouquet\"],\n tags: [\"flowers\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌸\",\n description: \"cherry blossom\",\n category: \"Animals & Nature\",\n aliases: [\"cherry_blossom\"],\n tags: [\"flower\", \"spring\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💮\",\n description: \"white flower\",\n category: \"Animals & Nature\",\n aliases: [\"white_flower\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏵️\",\n description: \"rosette\",\n category: \"Animals & Nature\",\n aliases: [\"rosette\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🌹\",\n description: \"rose\",\n category: \"Animals & Nature\",\n aliases: [\"rose\"],\n tags: [\"flower\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🥀\",\n description: \"wilted flower\",\n category: \"Animals & Nature\",\n aliases: [\"wilted_flower\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🌺\",\n description: \"hibiscus\",\n category: \"Animals & Nature\",\n aliases: [\"hibiscus\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌻\",\n description: \"sunflower\",\n category: \"Animals & Nature\",\n aliases: [\"sunflower\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌼\",\n description: \"blossom\",\n category: \"Animals & Nature\",\n aliases: [\"blossom\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌷\",\n description: \"tulip\",\n category: \"Animals & Nature\",\n aliases: [\"tulip\"],\n tags: [\"flower\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌱\",\n description: \"seedling\",\n category: \"Animals & Nature\",\n aliases: [\"seedling\"],\n tags: [\"plant\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🪴\",\n description: \"potted plant\",\n category: \"Animals & Nature\",\n aliases: [\"potted_plant\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🌲\",\n description: \"evergreen tree\",\n category: \"Animals & Nature\",\n aliases: [\"evergreen_tree\"],\n tags: [\"wood\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌳\",\n description: \"deciduous tree\",\n category: \"Animals & Nature\",\n aliases: [\"deciduous_tree\"],\n tags: [\"wood\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌴\",\n description: \"palm tree\",\n category: \"Animals & Nature\",\n aliases: [\"palm_tree\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌵\",\n description: \"cactus\",\n category: \"Animals & Nature\",\n aliases: [\"cactus\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌾\",\n description: \"sheaf of rice\",\n category: \"Animals & Nature\",\n aliases: [\"ear_of_rice\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌿\",\n description: \"herb\",\n category: \"Animals & Nature\",\n aliases: [\"herb\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"☘️\",\n description: \"shamrock\",\n category: \"Animals & Nature\",\n aliases: [\"shamrock\"],\n tags: [],\n unicode_version: \"4.1\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🍀\",\n description: \"four leaf clover\",\n category: \"Animals & Nature\",\n aliases: [\"four_leaf_clover\"],\n tags: [\"luck\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍁\",\n description: \"maple leaf\",\n category: \"Animals & Nature\",\n aliases: [\"maple_leaf\"],\n tags: [\"canada\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍂\",\n description: \"fallen leaf\",\n category: \"Animals & Nature\",\n aliases: [\"fallen_leaf\"],\n tags: [\"autumn\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍃\",\n description: \"leaf fluttering in wind\",\n category: \"Animals & Nature\",\n aliases: [\"leaves\"],\n tags: [\"leaf\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍇\",\n description: \"grapes\",\n category: \"Food & Drink\",\n aliases: [\"grapes\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍈\",\n description: \"melon\",\n category: \"Food & Drink\",\n aliases: [\"melon\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍉\",\n description: \"watermelon\",\n category: \"Food & Drink\",\n aliases: [\"watermelon\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍊\",\n description: \"tangerine\",\n category: \"Food & Drink\",\n aliases: [\"tangerine\", \"orange\", \"mandarin\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍋\",\n description: \"lemon\",\n category: \"Food & Drink\",\n aliases: [\"lemon\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍌\",\n description: \"banana\",\n category: \"Food & Drink\",\n aliases: [\"banana\"],\n tags: [\"fruit\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍍\",\n description: \"pineapple\",\n category: \"Food & Drink\",\n aliases: [\"pineapple\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🥭\",\n description: \"mango\",\n category: \"Food & Drink\",\n aliases: [\"mango\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🍎\",\n description: \"red apple\",\n category: \"Food & Drink\",\n aliases: [\"apple\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍏\",\n description: \"green apple\",\n category: \"Food & Drink\",\n aliases: [\"green_apple\"],\n tags: [\"fruit\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍐\",\n description: \"pear\",\n category: \"Food & Drink\",\n aliases: [\"pear\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍑\",\n description: \"peach\",\n category: \"Food & Drink\",\n aliases: [\"peach\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍒\",\n description: \"cherries\",\n category: \"Food & Drink\",\n aliases: [\"cherries\"],\n tags: [\"fruit\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍓\",\n description: \"strawberry\",\n category: \"Food & Drink\",\n aliases: [\"strawberry\"],\n tags: [\"fruit\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🫐\",\n description: \"blueberries\",\n category: \"Food & Drink\",\n aliases: [\"blueberries\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🥝\",\n description: \"kiwi fruit\",\n category: \"Food & Drink\",\n aliases: [\"kiwi_fruit\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🍅\",\n description: \"tomato\",\n category: \"Food & Drink\",\n aliases: [\"tomato\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🫒\",\n description: \"olive\",\n category: \"Food & Drink\",\n aliases: [\"olive\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🥥\",\n description: \"coconut\",\n category: \"Food & Drink\",\n aliases: [\"coconut\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🥑\",\n description: \"avocado\",\n category: \"Food & Drink\",\n aliases: [\"avocado\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🍆\",\n description: \"eggplant\",\n category: \"Food & Drink\",\n aliases: [\"eggplant\"],\n tags: [\"aubergine\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🥔\",\n description: \"potato\",\n category: \"Food & Drink\",\n aliases: [\"potato\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🥕\",\n description: \"carrot\",\n category: \"Food & Drink\",\n aliases: [\"carrot\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🌽\",\n description: \"ear of corn\",\n category: \"Food & Drink\",\n aliases: [\"corn\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌶️\",\n description: \"hot pepper\",\n category: \"Food & Drink\",\n aliases: [\"hot_pepper\"],\n tags: [\"spicy\"],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🫑\",\n description: \"bell pepper\",\n category: \"Food & Drink\",\n aliases: [\"bell_pepper\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🥒\",\n description: \"cucumber\",\n category: \"Food & Drink\",\n aliases: [\"cucumber\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🥬\",\n description: \"leafy green\",\n category: \"Food & Drink\",\n aliases: [\"leafy_green\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🥦\",\n description: \"broccoli\",\n category: \"Food & Drink\",\n aliases: [\"broccoli\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🧄\",\n description: \"garlic\",\n category: \"Food & Drink\",\n aliases: [\"garlic\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🧅\",\n description: \"onion\",\n category: \"Food & Drink\",\n aliases: [\"onion\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🍄\",\n description: \"mushroom\",\n category: \"Food & Drink\",\n aliases: [\"mushroom\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🥜\",\n description: \"peanuts\",\n category: \"Food & Drink\",\n aliases: [\"peanuts\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🌰\",\n description: \"chestnut\",\n category: \"Food & Drink\",\n aliases: [\"chestnut\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍞\",\n description: \"bread\",\n category: \"Food & Drink\",\n aliases: [\"bread\"],\n tags: [\"toast\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🥐\",\n description: \"croissant\",\n category: \"Food & Drink\",\n aliases: [\"croissant\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🥖\",\n description: \"baguette bread\",\n category: \"Food & Drink\",\n aliases: [\"baguette_bread\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🫓\",\n description: \"flatbread\",\n category: \"Food & Drink\",\n aliases: [\"flatbread\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🥨\",\n description: \"pretzel\",\n category: \"Food & Drink\",\n aliases: [\"pretzel\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🥯\",\n description: \"bagel\",\n category: \"Food & Drink\",\n aliases: [\"bagel\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🥞\",\n description: \"pancakes\",\n category: \"Food & Drink\",\n aliases: [\"pancakes\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🧇\",\n description: \"waffle\",\n category: \"Food & Drink\",\n aliases: [\"waffle\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🧀\",\n description: \"cheese wedge\",\n category: \"Food & Drink\",\n aliases: [\"cheese\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🍖\",\n description: \"meat on bone\",\n category: \"Food & Drink\",\n aliases: [\"meat_on_bone\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍗\",\n description: \"poultry leg\",\n category: \"Food & Drink\",\n aliases: [\"poultry_leg\"],\n tags: [\"meat\", \"chicken\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🥩\",\n description: \"cut of meat\",\n category: \"Food & Drink\",\n aliases: [\"cut_of_meat\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🥓\",\n description: \"bacon\",\n category: \"Food & Drink\",\n aliases: [\"bacon\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🍔\",\n description: \"hamburger\",\n category: \"Food & Drink\",\n aliases: [\"hamburger\"],\n tags: [\"burger\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍟\",\n description: \"french fries\",\n category: \"Food & Drink\",\n aliases: [\"fries\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍕\",\n description: \"pizza\",\n category: \"Food & Drink\",\n aliases: [\"pizza\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌭\",\n description: \"hot dog\",\n category: \"Food & Drink\",\n aliases: [\"hotdog\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🥪\",\n description: \"sandwich\",\n category: \"Food & Drink\",\n aliases: [\"sandwich\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🌮\",\n description: \"taco\",\n category: \"Food & Drink\",\n aliases: [\"taco\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🌯\",\n description: \"burrito\",\n category: \"Food & Drink\",\n aliases: [\"burrito\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🫔\",\n description: \"tamale\",\n category: \"Food & Drink\",\n aliases: [\"tamale\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🥙\",\n description: \"stuffed flatbread\",\n category: \"Food & Drink\",\n aliases: [\"stuffed_flatbread\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🧆\",\n description: \"falafel\",\n category: \"Food & Drink\",\n aliases: [\"falafel\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🥚\",\n description: \"egg\",\n category: \"Food & Drink\",\n aliases: [\"egg\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🍳\",\n description: \"cooking\",\n category: \"Food & Drink\",\n aliases: [\"fried_egg\"],\n tags: [\"breakfast\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🥘\",\n description: \"shallow pan of food\",\n category: \"Food & Drink\",\n aliases: [\"shallow_pan_of_food\"],\n tags: [\"paella\", \"curry\"],\n unicode_version: \"\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🍲\",\n description: \"pot of food\",\n category: \"Food & Drink\",\n aliases: [\"stew\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🫕\",\n description: \"fondue\",\n category: \"Food & Drink\",\n aliases: [\"fondue\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🥣\",\n description: \"bowl with spoon\",\n category: \"Food & Drink\",\n aliases: [\"bowl_with_spoon\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🥗\",\n description: \"green salad\",\n category: \"Food & Drink\",\n aliases: [\"green_salad\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🍿\",\n description: \"popcorn\",\n category: \"Food & Drink\",\n aliases: [\"popcorn\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🧈\",\n description: \"butter\",\n category: \"Food & Drink\",\n aliases: [\"butter\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🧂\",\n description: \"salt\",\n category: \"Food & Drink\",\n aliases: [\"salt\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🥫\",\n description: \"canned food\",\n category: \"Food & Drink\",\n aliases: [\"canned_food\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🍱\",\n description: \"bento box\",\n category: \"Food & Drink\",\n aliases: [\"bento\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍘\",\n description: \"rice cracker\",\n category: \"Food & Drink\",\n aliases: [\"rice_cracker\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍙\",\n description: \"rice ball\",\n category: \"Food & Drink\",\n aliases: [\"rice_ball\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍚\",\n description: \"cooked rice\",\n category: \"Food & Drink\",\n aliases: [\"rice\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍛\",\n description: \"curry rice\",\n category: \"Food & Drink\",\n aliases: [\"curry\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍜\",\n description: \"steaming bowl\",\n category: \"Food & Drink\",\n aliases: [\"ramen\"],\n tags: [\"noodle\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍝\",\n description: \"spaghetti\",\n category: \"Food & Drink\",\n aliases: [\"spaghetti\"],\n tags: [\"pasta\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍠\",\n description: \"roasted sweet potato\",\n category: \"Food & Drink\",\n aliases: [\"sweet_potato\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍢\",\n description: \"oden\",\n category: \"Food & Drink\",\n aliases: [\"oden\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍣\",\n description: \"sushi\",\n category: \"Food & Drink\",\n aliases: [\"sushi\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍤\",\n description: \"fried shrimp\",\n category: \"Food & Drink\",\n aliases: [\"fried_shrimp\"],\n tags: [\"tempura\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍥\",\n description: \"fish cake with swirl\",\n category: \"Food & Drink\",\n aliases: [\"fish_cake\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🥮\",\n description: \"moon cake\",\n category: \"Food & Drink\",\n aliases: [\"moon_cake\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🍡\",\n description: \"dango\",\n category: \"Food & Drink\",\n aliases: [\"dango\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🥟\",\n description: \"dumpling\",\n category: \"Food & Drink\",\n aliases: [\"dumpling\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🥠\",\n description: \"fortune cookie\",\n category: \"Food & Drink\",\n aliases: [\"fortune_cookie\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🥡\",\n description: \"takeout box\",\n category: \"Food & Drink\",\n aliases: [\"takeout_box\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🦀\",\n description: \"crab\",\n category: \"Food & Drink\",\n aliases: [\"crab\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🦞\",\n description: \"lobster\",\n category: \"Food & Drink\",\n aliases: [\"lobster\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🦐\",\n description: \"shrimp\",\n category: \"Food & Drink\",\n aliases: [\"shrimp\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🦑\",\n description: \"squid\",\n category: \"Food & Drink\",\n aliases: [\"squid\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🦪\",\n description: \"oyster\",\n category: \"Food & Drink\",\n aliases: [\"oyster\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🍦\",\n description: \"soft ice cream\",\n category: \"Food & Drink\",\n aliases: [\"icecream\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍧\",\n description: \"shaved ice\",\n category: \"Food & Drink\",\n aliases: [\"shaved_ice\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍨\",\n description: \"ice cream\",\n category: \"Food & Drink\",\n aliases: [\"ice_cream\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍩\",\n description: \"doughnut\",\n category: \"Food & Drink\",\n aliases: [\"doughnut\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍪\",\n description: \"cookie\",\n category: \"Food & Drink\",\n aliases: [\"cookie\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎂\",\n description: \"birthday cake\",\n category: \"Food & Drink\",\n aliases: [\"birthday\"],\n tags: [\"party\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍰\",\n description: \"shortcake\",\n category: \"Food & Drink\",\n aliases: [\"cake\"],\n tags: [\"dessert\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🧁\",\n description: \"cupcake\",\n category: \"Food & Drink\",\n aliases: [\"cupcake\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🥧\",\n description: \"pie\",\n category: \"Food & Drink\",\n aliases: [\"pie\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🍫\",\n description: \"chocolate bar\",\n category: \"Food & Drink\",\n aliases: [\"chocolate_bar\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍬\",\n description: \"candy\",\n category: \"Food & Drink\",\n aliases: [\"candy\"],\n tags: [\"sweet\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍭\",\n description: \"lollipop\",\n category: \"Food & Drink\",\n aliases: [\"lollipop\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍮\",\n description: \"custard\",\n category: \"Food & Drink\",\n aliases: [\"custard\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍯\",\n description: \"honey pot\",\n category: \"Food & Drink\",\n aliases: [\"honey_pot\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍼\",\n description: \"baby bottle\",\n category: \"Food & Drink\",\n aliases: [\"baby_bottle\"],\n tags: [\"milk\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🥛\",\n description: \"glass of milk\",\n category: \"Food & Drink\",\n aliases: [\"milk_glass\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"☕\",\n description: \"hot beverage\",\n category: \"Food & Drink\",\n aliases: [\"coffee\"],\n tags: [\"cafe\", \"espresso\"],\n unicode_version: \"4.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🫖\",\n description: \"teapot\",\n category: \"Food & Drink\",\n aliases: [\"teapot\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🍵\",\n description: \"teacup without handle\",\n category: \"Food & Drink\",\n aliases: [\"tea\"],\n tags: [\"green\", \"breakfast\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍶\",\n description: \"sake\",\n category: \"Food & Drink\",\n aliases: [\"sake\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍾\",\n description: \"bottle with popping cork\",\n category: \"Food & Drink\",\n aliases: [\"champagne\"],\n tags: [\"bottle\", \"bubbly\", \"celebration\"],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🍷\",\n description: \"wine glass\",\n category: \"Food & Drink\",\n aliases: [\"wine_glass\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍸\",\n description: \"cocktail glass\",\n category: \"Food & Drink\",\n aliases: [\"cocktail\"],\n tags: [\"drink\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍹\",\n description: \"tropical drink\",\n category: \"Food & Drink\",\n aliases: [\"tropical_drink\"],\n tags: [\"summer\", \"vacation\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍺\",\n description: \"beer mug\",\n category: \"Food & Drink\",\n aliases: [\"beer\"],\n tags: [\"drink\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🍻\",\n description: \"clinking beer mugs\",\n category: \"Food & Drink\",\n aliases: [\"beers\"],\n tags: [\"drinks\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🥂\",\n description: \"clinking glasses\",\n category: \"Food & Drink\",\n aliases: [\"clinking_glasses\"],\n tags: [\"cheers\", \"toast\"],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🥃\",\n description: \"tumbler glass\",\n category: \"Food & Drink\",\n aliases: [\"tumbler_glass\"],\n tags: [\"whisky\"],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🥤\",\n description: \"cup with straw\",\n category: \"Food & Drink\",\n aliases: [\"cup_with_straw\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🧋\",\n description: \"bubble tea\",\n category: \"Food & Drink\",\n aliases: [\"bubble_tea\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🧃\",\n description: \"beverage box\",\n category: \"Food & Drink\",\n aliases: [\"beverage_box\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🧉\",\n description: \"mate\",\n category: \"Food & Drink\",\n aliases: [\"mate\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🧊\",\n description: \"ice\",\n category: \"Food & Drink\",\n aliases: [\"ice_cube\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🥢\",\n description: \"chopsticks\",\n category: \"Food & Drink\",\n aliases: [\"chopsticks\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🍽️\",\n description: \"fork and knife with plate\",\n category: \"Food & Drink\",\n aliases: [\"plate_with_cutlery\"],\n tags: [\"dining\", \"dinner\"],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🍴\",\n description: \"fork and knife\",\n category: \"Food & Drink\",\n aliases: [\"fork_and_knife\"],\n tags: [\"cutlery\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🥄\",\n description: \"spoon\",\n category: \"Food & Drink\",\n aliases: [\"spoon\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🔪\",\n description: \"kitchen knife\",\n category: \"Food & Drink\",\n aliases: [\"hocho\", \"knife\"],\n tags: [\"cut\", \"chop\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏺\",\n description: \"amphora\",\n category: \"Food & Drink\",\n aliases: [\"amphora\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🌍\",\n description: \"globe showing Europe-Africa\",\n category: \"Travel & Places\",\n aliases: [\"earth_africa\"],\n tags: [\"globe\", \"world\", \"international\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌎\",\n description: \"globe showing Americas\",\n category: \"Travel & Places\",\n aliases: [\"earth_americas\"],\n tags: [\"globe\", \"world\", \"international\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌏\",\n description: \"globe showing Asia-Australia\",\n category: \"Travel & Places\",\n aliases: [\"earth_asia\"],\n tags: [\"globe\", \"world\", \"international\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌐\",\n description: \"globe with meridians\",\n category: \"Travel & Places\",\n aliases: [\"globe_with_meridians\"],\n tags: [\"world\", \"global\", \"international\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🗺️\",\n description: \"world map\",\n category: \"Travel & Places\",\n aliases: [\"world_map\"],\n tags: [\"travel\"],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🗾\",\n description: \"map of Japan\",\n category: \"Travel & Places\",\n aliases: [\"japan\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🧭\",\n description: \"compass\",\n category: \"Travel & Places\",\n aliases: [\"compass\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🏔️\",\n description: \"snow-capped mountain\",\n category: \"Travel & Places\",\n aliases: [\"mountain_snow\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"⛰️\",\n description: \"mountain\",\n category: \"Travel & Places\",\n aliases: [\"mountain\"],\n tags: [],\n unicode_version: \"5.2\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🌋\",\n description: \"volcano\",\n category: \"Travel & Places\",\n aliases: [\"volcano\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🗻\",\n description: \"mount fuji\",\n category: \"Travel & Places\",\n aliases: [\"mount_fuji\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏕️\",\n description: \"camping\",\n category: \"Travel & Places\",\n aliases: [\"camping\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🏖️\",\n description: \"beach with umbrella\",\n category: \"Travel & Places\",\n aliases: [\"beach_umbrella\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🏜️\",\n description: \"desert\",\n category: \"Travel & Places\",\n aliases: [\"desert\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🏝️\",\n description: \"desert island\",\n category: \"Travel & Places\",\n aliases: [\"desert_island\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🏞️\",\n description: \"national park\",\n category: \"Travel & Places\",\n aliases: [\"national_park\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🏟️\",\n description: \"stadium\",\n category: \"Travel & Places\",\n aliases: [\"stadium\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🏛️\",\n description: \"classical building\",\n category: \"Travel & Places\",\n aliases: [\"classical_building\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🏗️\",\n description: \"building construction\",\n category: \"Travel & Places\",\n aliases: [\"building_construction\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🧱\",\n description: \"brick\",\n category: \"Travel & Places\",\n aliases: [\"bricks\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🪨\",\n description: \"rock\",\n category: \"Travel & Places\",\n aliases: [\"rock\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🪵\",\n description: \"wood\",\n category: \"Travel & Places\",\n aliases: [\"wood\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🛖\",\n description: \"hut\",\n category: \"Travel & Places\",\n aliases: [\"hut\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🏘️\",\n description: \"houses\",\n category: \"Travel & Places\",\n aliases: [\"houses\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🏚️\",\n description: \"derelict house\",\n category: \"Travel & Places\",\n aliases: [\"derelict_house\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🏠\",\n description: \"house\",\n category: \"Travel & Places\",\n aliases: [\"house\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏡\",\n description: \"house with garden\",\n category: \"Travel & Places\",\n aliases: [\"house_with_garden\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏢\",\n description: \"office building\",\n category: \"Travel & Places\",\n aliases: [\"office\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏣\",\n description: \"Japanese post office\",\n category: \"Travel & Places\",\n aliases: [\"post_office\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏤\",\n description: \"post office\",\n category: \"Travel & Places\",\n aliases: [\"european_post_office\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏥\",\n description: \"hospital\",\n category: \"Travel & Places\",\n aliases: [\"hospital\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏦\",\n description: \"bank\",\n category: \"Travel & Places\",\n aliases: [\"bank\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏨\",\n description: \"hotel\",\n category: \"Travel & Places\",\n aliases: [\"hotel\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏩\",\n description: \"love hotel\",\n category: \"Travel & Places\",\n aliases: [\"love_hotel\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏪\",\n description: \"convenience store\",\n category: \"Travel & Places\",\n aliases: [\"convenience_store\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏫\",\n description: \"school\",\n category: \"Travel & Places\",\n aliases: [\"school\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏬\",\n description: \"department store\",\n category: \"Travel & Places\",\n aliases: [\"department_store\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏭\",\n description: \"factory\",\n category: \"Travel & Places\",\n aliases: [\"factory\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏯\",\n description: \"Japanese castle\",\n category: \"Travel & Places\",\n aliases: [\"japanese_castle\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏰\",\n description: \"castle\",\n category: \"Travel & Places\",\n aliases: [\"european_castle\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💒\",\n description: \"wedding\",\n category: \"Travel & Places\",\n aliases: [\"wedding\"],\n tags: [\"marriage\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🗼\",\n description: \"Tokyo tower\",\n category: \"Travel & Places\",\n aliases: [\"tokyo_tower\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🗽\",\n description: \"Statue of Liberty\",\n category: \"Travel & Places\",\n aliases: [\"statue_of_liberty\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⛪\",\n description: \"church\",\n category: \"Travel & Places\",\n aliases: [\"church\"],\n tags: [],\n unicode_version: \"5.2\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕌\",\n description: \"mosque\",\n category: \"Travel & Places\",\n aliases: [\"mosque\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🛕\",\n description: \"hindu temple\",\n category: \"Travel & Places\",\n aliases: [\"hindu_temple\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🕍\",\n description: \"synagogue\",\n category: \"Travel & Places\",\n aliases: [\"synagogue\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"⛩️\",\n description: \"shinto shrine\",\n category: \"Travel & Places\",\n aliases: [\"shinto_shrine\"],\n tags: [],\n unicode_version: \"5.2\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🕋\",\n description: \"kaaba\",\n category: \"Travel & Places\",\n aliases: [\"kaaba\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"⛲\",\n description: \"fountain\",\n category: \"Travel & Places\",\n aliases: [\"fountain\"],\n tags: [],\n unicode_version: \"5.2\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⛺\",\n description: \"tent\",\n category: \"Travel & Places\",\n aliases: [\"tent\"],\n tags: [\"camping\"],\n unicode_version: \"5.2\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌁\",\n description: \"foggy\",\n category: \"Travel & Places\",\n aliases: [\"foggy\"],\n tags: [\"karl\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌃\",\n description: \"night with stars\",\n category: \"Travel & Places\",\n aliases: [\"night_with_stars\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏙️\",\n description: \"cityscape\",\n category: \"Travel & Places\",\n aliases: [\"cityscape\"],\n tags: [\"skyline\"],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🌄\",\n description: \"sunrise over mountains\",\n category: \"Travel & Places\",\n aliases: [\"sunrise_over_mountains\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌅\",\n description: \"sunrise\",\n category: \"Travel & Places\",\n aliases: [\"sunrise\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌆\",\n description: \"cityscape at dusk\",\n category: \"Travel & Places\",\n aliases: [\"city_sunset\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌇\",\n description: \"sunset\",\n category: \"Travel & Places\",\n aliases: [\"city_sunrise\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌉\",\n description: \"bridge at night\",\n category: \"Travel & Places\",\n aliases: [\"bridge_at_night\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"♨️\",\n description: \"hot springs\",\n category: \"Travel & Places\",\n aliases: [\"hotsprings\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎠\",\n description: \"carousel horse\",\n category: \"Travel & Places\",\n aliases: [\"carousel_horse\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎡\",\n description: \"ferris wheel\",\n category: \"Travel & Places\",\n aliases: [\"ferris_wheel\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎢\",\n description: \"roller coaster\",\n category: \"Travel & Places\",\n aliases: [\"roller_coaster\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💈\",\n description: \"barber pole\",\n category: \"Travel & Places\",\n aliases: [\"barber\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎪\",\n description: \"circus tent\",\n category: \"Travel & Places\",\n aliases: [\"circus_tent\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚂\",\n description: \"locomotive\",\n category: \"Travel & Places\",\n aliases: [\"steam_locomotive\"],\n tags: [\"train\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚃\",\n description: \"railway car\",\n category: \"Travel & Places\",\n aliases: [\"railway_car\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚄\",\n description: \"high-speed train\",\n category: \"Travel & Places\",\n aliases: [\"bullettrain_side\"],\n tags: [\"train\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚅\",\n description: \"bullet train\",\n category: \"Travel & Places\",\n aliases: [\"bullettrain_front\"],\n tags: [\"train\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚆\",\n description: \"train\",\n category: \"Travel & Places\",\n aliases: [\"train2\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚇\",\n description: \"metro\",\n category: \"Travel & Places\",\n aliases: [\"metro\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚈\",\n description: \"light rail\",\n category: \"Travel & Places\",\n aliases: [\"light_rail\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚉\",\n description: \"station\",\n category: \"Travel & Places\",\n aliases: [\"station\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚊\",\n description: \"tram\",\n category: \"Travel & Places\",\n aliases: [\"tram\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚝\",\n description: \"monorail\",\n category: \"Travel & Places\",\n aliases: [\"monorail\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚞\",\n description: \"mountain railway\",\n category: \"Travel & Places\",\n aliases: [\"mountain_railway\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚋\",\n description: \"tram car\",\n category: \"Travel & Places\",\n aliases: [\"train\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚌\",\n description: \"bus\",\n category: \"Travel & Places\",\n aliases: [\"bus\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚍\",\n description: \"oncoming bus\",\n category: \"Travel & Places\",\n aliases: [\"oncoming_bus\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚎\",\n description: \"trolleybus\",\n category: \"Travel & Places\",\n aliases: [\"trolleybus\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚐\",\n description: \"minibus\",\n category: \"Travel & Places\",\n aliases: [\"minibus\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚑\",\n description: \"ambulance\",\n category: \"Travel & Places\",\n aliases: [\"ambulance\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚒\",\n description: \"fire engine\",\n category: \"Travel & Places\",\n aliases: [\"fire_engine\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚓\",\n description: \"police car\",\n category: \"Travel & Places\",\n aliases: [\"police_car\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚔\",\n description: \"oncoming police car\",\n category: \"Travel & Places\",\n aliases: [\"oncoming_police_car\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚕\",\n description: \"taxi\",\n category: \"Travel & Places\",\n aliases: [\"taxi\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚖\",\n description: \"oncoming taxi\",\n category: \"Travel & Places\",\n aliases: [\"oncoming_taxi\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚗\",\n description: \"automobile\",\n category: \"Travel & Places\",\n aliases: [\"car\", \"red_car\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚘\",\n description: \"oncoming automobile\",\n category: \"Travel & Places\",\n aliases: [\"oncoming_automobile\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚙\",\n description: \"sport utility vehicle\",\n category: \"Travel & Places\",\n aliases: [\"blue_car\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🛻\",\n description: \"pickup truck\",\n category: \"Travel & Places\",\n aliases: [\"pickup_truck\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🚚\",\n description: \"delivery truck\",\n category: \"Travel & Places\",\n aliases: [\"truck\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚛\",\n description: \"articulated lorry\",\n category: \"Travel & Places\",\n aliases: [\"articulated_lorry\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚜\",\n description: \"tractor\",\n category: \"Travel & Places\",\n aliases: [\"tractor\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏎️\",\n description: \"racing car\",\n category: \"Travel & Places\",\n aliases: [\"racing_car\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🏍️\",\n description: \"motorcycle\",\n category: \"Travel & Places\",\n aliases: [\"motorcycle\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🛵\",\n description: \"motor scooter\",\n category: \"Travel & Places\",\n aliases: [\"motor_scooter\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🦽\",\n description: \"manual wheelchair\",\n category: \"Travel & Places\",\n aliases: [\"manual_wheelchair\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🦼\",\n description: \"motorized wheelchair\",\n category: \"Travel & Places\",\n aliases: [\"motorized_wheelchair\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🛺\",\n description: \"auto rickshaw\",\n category: \"Travel & Places\",\n aliases: [\"auto_rickshaw\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🚲\",\n description: \"bicycle\",\n category: \"Travel & Places\",\n aliases: [\"bike\"],\n tags: [\"bicycle\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🛴\",\n description: \"kick scooter\",\n category: \"Travel & Places\",\n aliases: [\"kick_scooter\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🛹\",\n description: \"skateboard\",\n category: \"Travel & Places\",\n aliases: [\"skateboard\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🛼\",\n description: \"roller skate\",\n category: \"Travel & Places\",\n aliases: [\"roller_skate\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🚏\",\n description: \"bus stop\",\n category: \"Travel & Places\",\n aliases: [\"busstop\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🛣️\",\n description: \"motorway\",\n category: \"Travel & Places\",\n aliases: [\"motorway\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🛤️\",\n description: \"railway track\",\n category: \"Travel & Places\",\n aliases: [\"railway_track\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🛢️\",\n description: \"oil drum\",\n category: \"Travel & Places\",\n aliases: [\"oil_drum\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"⛽\",\n description: \"fuel pump\",\n category: \"Travel & Places\",\n aliases: [\"fuelpump\"],\n tags: [],\n unicode_version: \"5.2\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚨\",\n description: \"police car light\",\n category: \"Travel & Places\",\n aliases: [\"rotating_light\"],\n tags: [\"911\", \"emergency\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚥\",\n description: \"horizontal traffic light\",\n category: \"Travel & Places\",\n aliases: [\"traffic_light\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚦\",\n description: \"vertical traffic light\",\n category: \"Travel & Places\",\n aliases: [\"vertical_traffic_light\"],\n tags: [\"semaphore\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🛑\",\n description: \"stop sign\",\n category: \"Travel & Places\",\n aliases: [\"stop_sign\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🚧\",\n description: \"construction\",\n category: \"Travel & Places\",\n aliases: [\"construction\"],\n tags: [\"wip\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⚓\",\n description: \"anchor\",\n category: \"Travel & Places\",\n aliases: [\"anchor\"],\n tags: [\"ship\"],\n unicode_version: \"4.1\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⛵\",\n description: \"sailboat\",\n category: \"Travel & Places\",\n aliases: [\"boat\", \"sailboat\"],\n tags: [],\n unicode_version: \"5.2\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🛶\",\n description: \"canoe\",\n category: \"Travel & Places\",\n aliases: [\"canoe\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🚤\",\n description: \"speedboat\",\n category: \"Travel & Places\",\n aliases: [\"speedboat\"],\n tags: [\"ship\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🛳️\",\n description: \"passenger ship\",\n category: \"Travel & Places\",\n aliases: [\"passenger_ship\"],\n tags: [\"cruise\"],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"⛴️\",\n description: \"ferry\",\n category: \"Travel & Places\",\n aliases: [\"ferry\"],\n tags: [],\n unicode_version: \"5.2\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🛥️\",\n description: \"motor boat\",\n category: \"Travel & Places\",\n aliases: [\"motor_boat\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🚢\",\n description: \"ship\",\n category: \"Travel & Places\",\n aliases: [\"ship\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"✈️\",\n description: \"airplane\",\n category: \"Travel & Places\",\n aliases: [\"airplane\"],\n tags: [\"flight\"],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🛩️\",\n description: \"small airplane\",\n category: \"Travel & Places\",\n aliases: [\"small_airplane\"],\n tags: [\"flight\"],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🛫\",\n description: \"airplane departure\",\n category: \"Travel & Places\",\n aliases: [\"flight_departure\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🛬\",\n description: \"airplane arrival\",\n category: \"Travel & Places\",\n aliases: [\"flight_arrival\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🪂\",\n description: \"parachute\",\n category: \"Travel & Places\",\n aliases: [\"parachute\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"💺\",\n description: \"seat\",\n category: \"Travel & Places\",\n aliases: [\"seat\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚁\",\n description: \"helicopter\",\n category: \"Travel & Places\",\n aliases: [\"helicopter\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚟\",\n description: \"suspension railway\",\n category: \"Travel & Places\",\n aliases: [\"suspension_railway\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚠\",\n description: \"mountain cableway\",\n category: \"Travel & Places\",\n aliases: [\"mountain_cableway\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚡\",\n description: \"aerial tramway\",\n category: \"Travel & Places\",\n aliases: [\"aerial_tramway\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🛰️\",\n description: \"satellite\",\n category: \"Travel & Places\",\n aliases: [\"artificial_satellite\"],\n tags: [\"orbit\", \"space\"],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🚀\",\n description: \"rocket\",\n category: \"Travel & Places\",\n aliases: [\"rocket\"],\n tags: [\"ship\", \"launch\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🛸\",\n description: \"flying saucer\",\n category: \"Travel & Places\",\n aliases: [\"flying_saucer\"],\n tags: [\"ufo\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🛎️\",\n description: \"bellhop bell\",\n category: \"Travel & Places\",\n aliases: [\"bellhop_bell\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🧳\",\n description: \"luggage\",\n category: \"Travel & Places\",\n aliases: [\"luggage\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"⌛\",\n description: \"hourglass done\",\n category: \"Travel & Places\",\n aliases: [\"hourglass\"],\n tags: [\"time\"],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⏳\",\n description: \"hourglass not done\",\n category: \"Travel & Places\",\n aliases: [\"hourglass_flowing_sand\"],\n tags: [\"time\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⌚\",\n description: \"watch\",\n category: \"Travel & Places\",\n aliases: [\"watch\"],\n tags: [\"time\"],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⏰\",\n description: \"alarm clock\",\n category: \"Travel & Places\",\n aliases: [\"alarm_clock\"],\n tags: [\"morning\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⏱️\",\n description: \"stopwatch\",\n category: \"Travel & Places\",\n aliases: [\"stopwatch\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"⏲️\",\n description: \"timer clock\",\n category: \"Travel & Places\",\n aliases: [\"timer_clock\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🕰️\",\n description: \"mantelpiece clock\",\n category: \"Travel & Places\",\n aliases: [\"mantelpiece_clock\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🕛\",\n description: \"twelve o’clock\",\n category: \"Travel & Places\",\n aliases: [\"clock12\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕧\",\n description: \"twelve-thirty\",\n category: \"Travel & Places\",\n aliases: [\"clock1230\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕐\",\n description: \"one o’clock\",\n category: \"Travel & Places\",\n aliases: [\"clock1\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕜\",\n description: \"one-thirty\",\n category: \"Travel & Places\",\n aliases: [\"clock130\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕑\",\n description: \"two o’clock\",\n category: \"Travel & Places\",\n aliases: [\"clock2\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕝\",\n description: \"two-thirty\",\n category: \"Travel & Places\",\n aliases: [\"clock230\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕒\",\n description: \"three o’clock\",\n category: \"Travel & Places\",\n aliases: [\"clock3\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕞\",\n description: \"three-thirty\",\n category: \"Travel & Places\",\n aliases: [\"clock330\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕓\",\n description: \"four o’clock\",\n category: \"Travel & Places\",\n aliases: [\"clock4\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕟\",\n description: \"four-thirty\",\n category: \"Travel & Places\",\n aliases: [\"clock430\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕔\",\n description: \"five o’clock\",\n category: \"Travel & Places\",\n aliases: [\"clock5\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕠\",\n description: \"five-thirty\",\n category: \"Travel & Places\",\n aliases: [\"clock530\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕕\",\n description: \"six o’clock\",\n category: \"Travel & Places\",\n aliases: [\"clock6\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕡\",\n description: \"six-thirty\",\n category: \"Travel & Places\",\n aliases: [\"clock630\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕖\",\n description: \"seven o’clock\",\n category: \"Travel & Places\",\n aliases: [\"clock7\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕢\",\n description: \"seven-thirty\",\n category: \"Travel & Places\",\n aliases: [\"clock730\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕗\",\n description: \"eight o’clock\",\n category: \"Travel & Places\",\n aliases: [\"clock8\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕣\",\n description: \"eight-thirty\",\n category: \"Travel & Places\",\n aliases: [\"clock830\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕘\",\n description: \"nine o’clock\",\n category: \"Travel & Places\",\n aliases: [\"clock9\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕤\",\n description: \"nine-thirty\",\n category: \"Travel & Places\",\n aliases: [\"clock930\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕙\",\n description: \"ten o’clock\",\n category: \"Travel & Places\",\n aliases: [\"clock10\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕥\",\n description: \"ten-thirty\",\n category: \"Travel & Places\",\n aliases: [\"clock1030\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕚\",\n description: \"eleven o’clock\",\n category: \"Travel & Places\",\n aliases: [\"clock11\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕦\",\n description: \"eleven-thirty\",\n category: \"Travel & Places\",\n aliases: [\"clock1130\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌑\",\n description: \"new moon\",\n category: \"Travel & Places\",\n aliases: [\"new_moon\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌒\",\n description: \"waxing crescent moon\",\n category: \"Travel & Places\",\n aliases: [\"waxing_crescent_moon\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌓\",\n description: \"first quarter moon\",\n category: \"Travel & Places\",\n aliases: [\"first_quarter_moon\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌔\",\n description: \"waxing gibbous moon\",\n category: \"Travel & Places\",\n aliases: [\"moon\", \"waxing_gibbous_moon\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌕\",\n description: \"full moon\",\n category: \"Travel & Places\",\n aliases: [\"full_moon\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌖\",\n description: \"waning gibbous moon\",\n category: \"Travel & Places\",\n aliases: [\"waning_gibbous_moon\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌗\",\n description: \"last quarter moon\",\n category: \"Travel & Places\",\n aliases: [\"last_quarter_moon\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌘\",\n description: \"waning crescent moon\",\n category: \"Travel & Places\",\n aliases: [\"waning_crescent_moon\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌙\",\n description: \"crescent moon\",\n category: \"Travel & Places\",\n aliases: [\"crescent_moon\"],\n tags: [\"night\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌚\",\n description: \"new moon face\",\n category: \"Travel & Places\",\n aliases: [\"new_moon_with_face\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌛\",\n description: \"first quarter moon face\",\n category: \"Travel & Places\",\n aliases: [\"first_quarter_moon_with_face\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌜\",\n description: \"last quarter moon face\",\n category: \"Travel & Places\",\n aliases: [\"last_quarter_moon_with_face\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌡️\",\n description: \"thermometer\",\n category: \"Travel & Places\",\n aliases: [\"thermometer\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"☀️\",\n description: \"sun\",\n category: \"Travel & Places\",\n aliases: [\"sunny\"],\n tags: [\"weather\"],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌝\",\n description: \"full moon face\",\n category: \"Travel & Places\",\n aliases: [\"full_moon_with_face\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌞\",\n description: \"sun with face\",\n category: \"Travel & Places\",\n aliases: [\"sun_with_face\"],\n tags: [\"summer\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🪐\",\n description: \"ringed planet\",\n category: \"Travel & Places\",\n aliases: [\"ringed_planet\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"⭐\",\n description: \"star\",\n category: \"Travel & Places\",\n aliases: [\"star\"],\n tags: [],\n unicode_version: \"5.1\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌟\",\n description: \"glowing star\",\n category: \"Travel & Places\",\n aliases: [\"star2\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌠\",\n description: \"shooting star\",\n category: \"Travel & Places\",\n aliases: [\"stars\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌌\",\n description: \"milky way\",\n category: \"Travel & Places\",\n aliases: [\"milky_way\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"☁️\",\n description: \"cloud\",\n category: \"Travel & Places\",\n aliases: [\"cloud\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⛅\",\n description: \"sun behind cloud\",\n category: \"Travel & Places\",\n aliases: [\"partly_sunny\"],\n tags: [\"weather\", \"cloud\"],\n unicode_version: \"5.2\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⛈️\",\n description: \"cloud with lightning and rain\",\n category: \"Travel & Places\",\n aliases: [\"cloud_with_lightning_and_rain\"],\n tags: [],\n unicode_version: \"5.2\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🌤️\",\n description: \"sun behind small cloud\",\n category: \"Travel & Places\",\n aliases: [\"sun_behind_small_cloud\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🌥️\",\n description: \"sun behind large cloud\",\n category: \"Travel & Places\",\n aliases: [\"sun_behind_large_cloud\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🌦️\",\n description: \"sun behind rain cloud\",\n category: \"Travel & Places\",\n aliases: [\"sun_behind_rain_cloud\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🌧️\",\n description: \"cloud with rain\",\n category: \"Travel & Places\",\n aliases: [\"cloud_with_rain\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🌨️\",\n description: \"cloud with snow\",\n category: \"Travel & Places\",\n aliases: [\"cloud_with_snow\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🌩️\",\n description: \"cloud with lightning\",\n category: \"Travel & Places\",\n aliases: [\"cloud_with_lightning\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🌪️\",\n description: \"tornado\",\n category: \"Travel & Places\",\n aliases: [\"tornado\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🌫️\",\n description: \"fog\",\n category: \"Travel & Places\",\n aliases: [\"fog\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🌬️\",\n description: \"wind face\",\n category: \"Travel & Places\",\n aliases: [\"wind_face\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🌀\",\n description: \"cyclone\",\n category: \"Travel & Places\",\n aliases: [\"cyclone\"],\n tags: [\"swirl\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌈\",\n description: \"rainbow\",\n category: \"Travel & Places\",\n aliases: [\"rainbow\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌂\",\n description: \"closed umbrella\",\n category: \"Travel & Places\",\n aliases: [\"closed_umbrella\"],\n tags: [\"weather\", \"rain\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"☂️\",\n description: \"umbrella\",\n category: \"Travel & Places\",\n aliases: [\"open_umbrella\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"☔\",\n description: \"umbrella with rain drops\",\n category: \"Travel & Places\",\n aliases: [\"umbrella\"],\n tags: [\"rain\", \"weather\"],\n unicode_version: \"4.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⛱️\",\n description: \"umbrella on ground\",\n category: \"Travel & Places\",\n aliases: [\"parasol_on_ground\"],\n tags: [\"beach_umbrella\"],\n unicode_version: \"5.2\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"⚡\",\n description: \"high voltage\",\n category: \"Travel & Places\",\n aliases: [\"zap\"],\n tags: [\"lightning\", \"thunder\"],\n unicode_version: \"4.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"❄️\",\n description: \"snowflake\",\n category: \"Travel & Places\",\n aliases: [\"snowflake\"],\n tags: [\"winter\", \"cold\", \"weather\"],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"☃️\",\n description: \"snowman\",\n category: \"Travel & Places\",\n aliases: [\"snowman_with_snow\"],\n tags: [\"winter\", \"christmas\"],\n unicode_version: \"\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"⛄\",\n description: \"snowman without snow\",\n category: \"Travel & Places\",\n aliases: [\"snowman\"],\n tags: [\"winter\"],\n unicode_version: \"5.2\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"☄️\",\n description: \"comet\",\n category: \"Travel & Places\",\n aliases: [\"comet\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🔥\",\n description: \"fire\",\n category: \"Travel & Places\",\n aliases: [\"fire\"],\n tags: [\"burn\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💧\",\n description: \"droplet\",\n category: \"Travel & Places\",\n aliases: [\"droplet\"],\n tags: [\"water\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🌊\",\n description: \"water wave\",\n category: \"Travel & Places\",\n aliases: [\"ocean\"],\n tags: [\"sea\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎃\",\n description: \"jack-o-lantern\",\n category: \"Activities\",\n aliases: [\"jack_o_lantern\"],\n tags: [\"halloween\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎄\",\n description: \"Christmas tree\",\n category: \"Activities\",\n aliases: [\"christmas_tree\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎆\",\n description: \"fireworks\",\n category: \"Activities\",\n aliases: [\"fireworks\"],\n tags: [\"festival\", \"celebration\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎇\",\n description: \"sparkler\",\n category: \"Activities\",\n aliases: [\"sparkler\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🧨\",\n description: \"firecracker\",\n category: \"Activities\",\n aliases: [\"firecracker\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"✨\",\n description: \"sparkles\",\n category: \"Activities\",\n aliases: [\"sparkles\"],\n tags: [\"shiny\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎈\",\n description: \"balloon\",\n category: \"Activities\",\n aliases: [\"balloon\"],\n tags: [\"party\", \"birthday\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎉\",\n description: \"party popper\",\n category: \"Activities\",\n aliases: [\"tada\"],\n tags: [\"hooray\", \"party\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎊\",\n description: \"confetti ball\",\n category: \"Activities\",\n aliases: [\"confetti_ball\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎋\",\n description: \"tanabata tree\",\n category: \"Activities\",\n aliases: [\"tanabata_tree\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎍\",\n description: \"pine decoration\",\n category: \"Activities\",\n aliases: [\"bamboo\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎎\",\n description: \"Japanese dolls\",\n category: \"Activities\",\n aliases: [\"dolls\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎏\",\n description: \"carp streamer\",\n category: \"Activities\",\n aliases: [\"flags\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎐\",\n description: \"wind chime\",\n category: \"Activities\",\n aliases: [\"wind_chime\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎑\",\n description: \"moon viewing ceremony\",\n category: \"Activities\",\n aliases: [\"rice_scene\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🧧\",\n description: \"red envelope\",\n category: \"Activities\",\n aliases: [\"red_envelope\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🎀\",\n description: \"ribbon\",\n category: \"Activities\",\n aliases: [\"ribbon\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎁\",\n description: \"wrapped gift\",\n category: \"Activities\",\n aliases: [\"gift\"],\n tags: [\"present\", \"birthday\", \"christmas\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎗️\",\n description: \"reminder ribbon\",\n category: \"Activities\",\n aliases: [\"reminder_ribbon\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🎟️\",\n description: \"admission tickets\",\n category: \"Activities\",\n aliases: [\"tickets\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🎫\",\n description: \"ticket\",\n category: \"Activities\",\n aliases: [\"ticket\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎖️\",\n description: \"military medal\",\n category: \"Activities\",\n aliases: [\"medal_military\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🏆\",\n description: \"trophy\",\n category: \"Activities\",\n aliases: [\"trophy\"],\n tags: [\"award\", \"contest\", \"winner\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏅\",\n description: \"sports medal\",\n category: \"Activities\",\n aliases: [\"medal_sports\"],\n tags: [\"gold\", \"winner\"],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🥇\",\n description: \"1st place medal\",\n category: \"Activities\",\n aliases: [\"1st_place_medal\"],\n tags: [\"gold\"],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🥈\",\n description: \"2nd place medal\",\n category: \"Activities\",\n aliases: [\"2nd_place_medal\"],\n tags: [\"silver\"],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🥉\",\n description: \"3rd place medal\",\n category: \"Activities\",\n aliases: [\"3rd_place_medal\"],\n tags: [\"bronze\"],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"⚽\",\n description: \"soccer ball\",\n category: \"Activities\",\n aliases: [\"soccer\"],\n tags: [\"sports\"],\n unicode_version: \"5.2\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⚾\",\n description: \"baseball\",\n category: \"Activities\",\n aliases: [\"baseball\"],\n tags: [\"sports\"],\n unicode_version: \"5.2\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🥎\",\n description: \"softball\",\n category: \"Activities\",\n aliases: [\"softball\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🏀\",\n description: \"basketball\",\n category: \"Activities\",\n aliases: [\"basketball\"],\n tags: [\"sports\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏐\",\n description: \"volleyball\",\n category: \"Activities\",\n aliases: [\"volleyball\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🏈\",\n description: \"american football\",\n category: \"Activities\",\n aliases: [\"football\"],\n tags: [\"sports\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏉\",\n description: \"rugby football\",\n category: \"Activities\",\n aliases: [\"rugby_football\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎾\",\n description: \"tennis\",\n category: \"Activities\",\n aliases: [\"tennis\"],\n tags: [\"sports\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🥏\",\n description: \"flying disc\",\n category: \"Activities\",\n aliases: [\"flying_disc\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🎳\",\n description: \"bowling\",\n category: \"Activities\",\n aliases: [\"bowling\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏏\",\n description: \"cricket game\",\n category: \"Activities\",\n aliases: [\"cricket_game\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🏑\",\n description: \"field hockey\",\n category: \"Activities\",\n aliases: [\"field_hockey\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🏒\",\n description: \"ice hockey\",\n category: \"Activities\",\n aliases: [\"ice_hockey\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🥍\",\n description: \"lacrosse\",\n category: \"Activities\",\n aliases: [\"lacrosse\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🏓\",\n description: \"ping pong\",\n category: \"Activities\",\n aliases: [\"ping_pong\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🏸\",\n description: \"badminton\",\n category: \"Activities\",\n aliases: [\"badminton\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🥊\",\n description: \"boxing glove\",\n category: \"Activities\",\n aliases: [\"boxing_glove\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🥋\",\n description: \"martial arts uniform\",\n category: \"Activities\",\n aliases: [\"martial_arts_uniform\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🥅\",\n description: \"goal net\",\n category: \"Activities\",\n aliases: [\"goal_net\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"⛳\",\n description: \"flag in hole\",\n category: \"Activities\",\n aliases: [\"golf\"],\n tags: [],\n unicode_version: \"5.2\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⛸️\",\n description: \"ice skate\",\n category: \"Activities\",\n aliases: [\"ice_skate\"],\n tags: [\"skating\"],\n unicode_version: \"5.2\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🎣\",\n description: \"fishing pole\",\n category: \"Activities\",\n aliases: [\"fishing_pole_and_fish\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🤿\",\n description: \"diving mask\",\n category: \"Activities\",\n aliases: [\"diving_mask\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🎽\",\n description: \"running shirt\",\n category: \"Activities\",\n aliases: [\"running_shirt_with_sash\"],\n tags: [\"marathon\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎿\",\n description: \"skis\",\n category: \"Activities\",\n aliases: [\"ski\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🛷\",\n description: \"sled\",\n category: \"Activities\",\n aliases: [\"sled\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🥌\",\n description: \"curling stone\",\n category: \"Activities\",\n aliases: [\"curling_stone\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🎯\",\n description: \"bullseye\",\n category: \"Activities\",\n aliases: [\"dart\"],\n tags: [\"target\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🪀\",\n description: \"yo-yo\",\n category: \"Activities\",\n aliases: [\"yo_yo\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🪁\",\n description: \"kite\",\n category: \"Activities\",\n aliases: [\"kite\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🎱\",\n description: \"pool 8 ball\",\n category: \"Activities\",\n aliases: [\"8ball\"],\n tags: [\"pool\", \"billiards\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔮\",\n description: \"crystal ball\",\n category: \"Activities\",\n aliases: [\"crystal_ball\"],\n tags: [\"fortune\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🪄\",\n description: \"magic wand\",\n category: \"Activities\",\n aliases: [\"magic_wand\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🧿\",\n description: \"nazar amulet\",\n category: \"Activities\",\n aliases: [\"nazar_amulet\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🎮\",\n description: \"video game\",\n category: \"Activities\",\n aliases: [\"video_game\"],\n tags: [\"play\", \"controller\", \"console\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕹️\",\n description: \"joystick\",\n category: \"Activities\",\n aliases: [\"joystick\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🎰\",\n description: \"slot machine\",\n category: \"Activities\",\n aliases: [\"slot_machine\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎲\",\n description: \"game die\",\n category: \"Activities\",\n aliases: [\"game_die\"],\n tags: [\"dice\", \"gambling\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🧩\",\n description: \"puzzle piece\",\n category: \"Activities\",\n aliases: [\"jigsaw\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🧸\",\n description: \"teddy bear\",\n category: \"Activities\",\n aliases: [\"teddy_bear\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🪅\",\n description: \"piñata\",\n category: \"Activities\",\n aliases: [\"pinata\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🪆\",\n description: \"nesting dolls\",\n category: \"Activities\",\n aliases: [\"nesting_dolls\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"♠️\",\n description: \"spade suit\",\n category: \"Activities\",\n aliases: [\"spades\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"♥️\",\n description: \"heart suit\",\n category: \"Activities\",\n aliases: [\"hearts\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"♦️\",\n description: \"diamond suit\",\n category: \"Activities\",\n aliases: [\"diamonds\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"♣️\",\n description: \"club suit\",\n category: \"Activities\",\n aliases: [\"clubs\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"♟️\",\n description: \"chess pawn\",\n category: \"Activities\",\n aliases: [\"chess_pawn\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🃏\",\n description: \"joker\",\n category: \"Activities\",\n aliases: [\"black_joker\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🀄\",\n description: \"mahjong red dragon\",\n category: \"Activities\",\n aliases: [\"mahjong\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎴\",\n description: \"flower playing cards\",\n category: \"Activities\",\n aliases: [\"flower_playing_cards\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎭\",\n description: \"performing arts\",\n category: \"Activities\",\n aliases: [\"performing_arts\"],\n tags: [\"theater\", \"drama\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🖼️\",\n description: \"framed picture\",\n category: \"Activities\",\n aliases: [\"framed_picture\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🎨\",\n description: \"artist palette\",\n category: \"Activities\",\n aliases: [\"art\"],\n tags: [\"design\", \"paint\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🧵\",\n description: \"thread\",\n category: \"Activities\",\n aliases: [\"thread\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🪡\",\n description: \"sewing needle\",\n category: \"Activities\",\n aliases: [\"sewing_needle\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🧶\",\n description: \"yarn\",\n category: \"Activities\",\n aliases: [\"yarn\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🪢\",\n description: \"knot\",\n category: \"Activities\",\n aliases: [\"knot\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"👓\",\n description: \"glasses\",\n category: \"Objects\",\n aliases: [\"eyeglasses\"],\n tags: [\"glasses\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕶️\",\n description: \"sunglasses\",\n category: \"Objects\",\n aliases: [\"dark_sunglasses\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🥽\",\n description: \"goggles\",\n category: \"Objects\",\n aliases: [\"goggles\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🥼\",\n description: \"lab coat\",\n category: \"Objects\",\n aliases: [\"lab_coat\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🦺\",\n description: \"safety vest\",\n category: \"Objects\",\n aliases: [\"safety_vest\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"👔\",\n description: \"necktie\",\n category: \"Objects\",\n aliases: [\"necktie\"],\n tags: [\"shirt\", \"formal\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👕\",\n description: \"t-shirt\",\n category: \"Objects\",\n aliases: [\"shirt\", \"tshirt\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👖\",\n description: \"jeans\",\n category: \"Objects\",\n aliases: [\"jeans\"],\n tags: [\"pants\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🧣\",\n description: \"scarf\",\n category: \"Objects\",\n aliases: [\"scarf\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🧤\",\n description: \"gloves\",\n category: \"Objects\",\n aliases: [\"gloves\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🧥\",\n description: \"coat\",\n category: \"Objects\",\n aliases: [\"coat\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🧦\",\n description: \"socks\",\n category: \"Objects\",\n aliases: [\"socks\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"👗\",\n description: \"dress\",\n category: \"Objects\",\n aliases: [\"dress\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👘\",\n description: \"kimono\",\n category: \"Objects\",\n aliases: [\"kimono\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🥻\",\n description: \"sari\",\n category: \"Objects\",\n aliases: [\"sari\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🩱\",\n description: \"one-piece swimsuit\",\n category: \"Objects\",\n aliases: [\"one_piece_swimsuit\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🩲\",\n description: \"briefs\",\n category: \"Objects\",\n aliases: [\"swim_brief\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🩳\",\n description: \"shorts\",\n category: \"Objects\",\n aliases: [\"shorts\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"👙\",\n description: \"bikini\",\n category: \"Objects\",\n aliases: [\"bikini\"],\n tags: [\"beach\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👚\",\n description: \"woman’s clothes\",\n category: \"Objects\",\n aliases: [\"womans_clothes\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👛\",\n description: \"purse\",\n category: \"Objects\",\n aliases: [\"purse\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👜\",\n description: \"handbag\",\n category: \"Objects\",\n aliases: [\"handbag\"],\n tags: [\"bag\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👝\",\n description: \"clutch bag\",\n category: \"Objects\",\n aliases: [\"pouch\"],\n tags: [\"bag\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🛍️\",\n description: \"shopping bags\",\n category: \"Objects\",\n aliases: [\"shopping\"],\n tags: [\"bags\"],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🎒\",\n description: \"backpack\",\n category: \"Objects\",\n aliases: [\"school_satchel\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🩴\",\n description: \"thong sandal\",\n category: \"Objects\",\n aliases: [\"thong_sandal\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"👞\",\n description: \"man’s shoe\",\n category: \"Objects\",\n aliases: [\"mans_shoe\", \"shoe\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👟\",\n description: \"running shoe\",\n category: \"Objects\",\n aliases: [\"athletic_shoe\"],\n tags: [\"sneaker\", \"sport\", \"running\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🥾\",\n description: \"hiking boot\",\n category: \"Objects\",\n aliases: [\"hiking_boot\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🥿\",\n description: \"flat shoe\",\n category: \"Objects\",\n aliases: [\"flat_shoe\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"👠\",\n description: \"high-heeled shoe\",\n category: \"Objects\",\n aliases: [\"high_heel\"],\n tags: [\"shoe\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👡\",\n description: \"woman’s sandal\",\n category: \"Objects\",\n aliases: [\"sandal\"],\n tags: [\"shoe\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🩰\",\n description: \"ballet shoes\",\n category: \"Objects\",\n aliases: [\"ballet_shoes\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"👢\",\n description: \"woman’s boot\",\n category: \"Objects\",\n aliases: [\"boot\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👑\",\n description: \"crown\",\n category: \"Objects\",\n aliases: [\"crown\"],\n tags: [\"king\", \"queen\", \"royal\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"👒\",\n description: \"woman’s hat\",\n category: \"Objects\",\n aliases: [\"womans_hat\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎩\",\n description: \"top hat\",\n category: \"Objects\",\n aliases: [\"tophat\"],\n tags: [\"hat\", \"classy\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎓\",\n description: \"graduation cap\",\n category: \"Objects\",\n aliases: [\"mortar_board\"],\n tags: [\"education\", \"college\", \"university\", \"graduation\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🧢\",\n description: \"billed cap\",\n category: \"Objects\",\n aliases: [\"billed_cap\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🪖\",\n description: \"military helmet\",\n category: \"Objects\",\n aliases: [\"military_helmet\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"⛑️\",\n description: \"rescue worker’s helmet\",\n category: \"Objects\",\n aliases: [\"rescue_worker_helmet\"],\n tags: [],\n unicode_version: \"5.2\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"📿\",\n description: \"prayer beads\",\n category: \"Objects\",\n aliases: [\"prayer_beads\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"💄\",\n description: \"lipstick\",\n category: \"Objects\",\n aliases: [\"lipstick\"],\n tags: [\"makeup\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💍\",\n description: \"ring\",\n category: \"Objects\",\n aliases: [\"ring\"],\n tags: [\"wedding\", \"marriage\", \"engaged\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💎\",\n description: \"gem stone\",\n category: \"Objects\",\n aliases: [\"gem\"],\n tags: [\"diamond\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔇\",\n description: \"muted speaker\",\n category: \"Objects\",\n aliases: [\"mute\"],\n tags: [\"sound\", \"volume\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔈\",\n description: \"speaker low volume\",\n category: \"Objects\",\n aliases: [\"speaker\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔉\",\n description: \"speaker medium volume\",\n category: \"Objects\",\n aliases: [\"sound\"],\n tags: [\"volume\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔊\",\n description: \"speaker high volume\",\n category: \"Objects\",\n aliases: [\"loud_sound\"],\n tags: [\"volume\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📢\",\n description: \"loudspeaker\",\n category: \"Objects\",\n aliases: [\"loudspeaker\"],\n tags: [\"announcement\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📣\",\n description: \"megaphone\",\n category: \"Objects\",\n aliases: [\"mega\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📯\",\n description: \"postal horn\",\n category: \"Objects\",\n aliases: [\"postal_horn\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔔\",\n description: \"bell\",\n category: \"Objects\",\n aliases: [\"bell\"],\n tags: [\"sound\", \"notification\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔕\",\n description: \"bell with slash\",\n category: \"Objects\",\n aliases: [\"no_bell\"],\n tags: [\"volume\", \"off\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎼\",\n description: \"musical score\",\n category: \"Objects\",\n aliases: [\"musical_score\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎵\",\n description: \"musical note\",\n category: \"Objects\",\n aliases: [\"musical_note\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎶\",\n description: \"musical notes\",\n category: \"Objects\",\n aliases: [\"notes\"],\n tags: [\"music\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎙️\",\n description: \"studio microphone\",\n category: \"Objects\",\n aliases: [\"studio_microphone\"],\n tags: [\"podcast\"],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🎚️\",\n description: \"level slider\",\n category: \"Objects\",\n aliases: [\"level_slider\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🎛️\",\n description: \"control knobs\",\n category: \"Objects\",\n aliases: [\"control_knobs\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🎤\",\n description: \"microphone\",\n category: \"Objects\",\n aliases: [\"microphone\"],\n tags: [\"sing\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎧\",\n description: \"headphone\",\n category: \"Objects\",\n aliases: [\"headphones\"],\n tags: [\"music\", \"earphones\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📻\",\n description: \"radio\",\n category: \"Objects\",\n aliases: [\"radio\"],\n tags: [\"podcast\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎷\",\n description: \"saxophone\",\n category: \"Objects\",\n aliases: [\"saxophone\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🪗\",\n description: \"accordion\",\n category: \"Objects\",\n aliases: [\"accordion\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🎸\",\n description: \"guitar\",\n category: \"Objects\",\n aliases: [\"guitar\"],\n tags: [\"rock\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎹\",\n description: \"musical keyboard\",\n category: \"Objects\",\n aliases: [\"musical_keyboard\"],\n tags: [\"piano\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎺\",\n description: \"trumpet\",\n category: \"Objects\",\n aliases: [\"trumpet\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎻\",\n description: \"violin\",\n category: \"Objects\",\n aliases: [\"violin\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🪕\",\n description: \"banjo\",\n category: \"Objects\",\n aliases: [\"banjo\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🥁\",\n description: \"drum\",\n category: \"Objects\",\n aliases: [\"drum\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🪘\",\n description: \"long drum\",\n category: \"Objects\",\n aliases: [\"long_drum\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"📱\",\n description: \"mobile phone\",\n category: \"Objects\",\n aliases: [\"iphone\"],\n tags: [\"smartphone\", \"mobile\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📲\",\n description: \"mobile phone with arrow\",\n category: \"Objects\",\n aliases: [\"calling\"],\n tags: [\"call\", \"incoming\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"☎️\",\n description: \"telephone\",\n category: \"Objects\",\n aliases: [\"phone\", \"telephone\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📞\",\n description: \"telephone receiver\",\n category: \"Objects\",\n aliases: [\"telephone_receiver\"],\n tags: [\"phone\", \"call\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📟\",\n description: \"pager\",\n category: \"Objects\",\n aliases: [\"pager\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📠\",\n description: \"fax machine\",\n category: \"Objects\",\n aliases: [\"fax\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔋\",\n description: \"battery\",\n category: \"Objects\",\n aliases: [\"battery\"],\n tags: [\"power\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔌\",\n description: \"electric plug\",\n category: \"Objects\",\n aliases: [\"electric_plug\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💻\",\n description: \"laptop\",\n category: \"Objects\",\n aliases: [\"computer\"],\n tags: [\"desktop\", \"screen\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🖥️\",\n description: \"desktop computer\",\n category: \"Objects\",\n aliases: [\"desktop_computer\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🖨️\",\n description: \"printer\",\n category: \"Objects\",\n aliases: [\"printer\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"⌨️\",\n description: \"keyboard\",\n category: \"Objects\",\n aliases: [\"keyboard\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🖱️\",\n description: \"computer mouse\",\n category: \"Objects\",\n aliases: [\"computer_mouse\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🖲️\",\n description: \"trackball\",\n category: \"Objects\",\n aliases: [\"trackball\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"💽\",\n description: \"computer disk\",\n category: \"Objects\",\n aliases: [\"minidisc\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💾\",\n description: \"floppy disk\",\n category: \"Objects\",\n aliases: [\"floppy_disk\"],\n tags: [\"save\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💿\",\n description: \"optical disk\",\n category: \"Objects\",\n aliases: [\"cd\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📀\",\n description: \"dvd\",\n category: \"Objects\",\n aliases: [\"dvd\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🧮\",\n description: \"abacus\",\n category: \"Objects\",\n aliases: [\"abacus\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🎥\",\n description: \"movie camera\",\n category: \"Objects\",\n aliases: [\"movie_camera\"],\n tags: [\"film\", \"video\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎞️\",\n description: \"film frames\",\n category: \"Objects\",\n aliases: [\"film_strip\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"📽️\",\n description: \"film projector\",\n category: \"Objects\",\n aliases: [\"film_projector\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🎬\",\n description: \"clapper board\",\n category: \"Objects\",\n aliases: [\"clapper\"],\n tags: [\"film\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📺\",\n description: \"television\",\n category: \"Objects\",\n aliases: [\"tv\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📷\",\n description: \"camera\",\n category: \"Objects\",\n aliases: [\"camera\"],\n tags: [\"photo\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📸\",\n description: \"camera with flash\",\n category: \"Objects\",\n aliases: [\"camera_flash\"],\n tags: [\"photo\"],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"📹\",\n description: \"video camera\",\n category: \"Objects\",\n aliases: [\"video_camera\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📼\",\n description: \"videocassette\",\n category: \"Objects\",\n aliases: [\"vhs\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔍\",\n description: \"magnifying glass tilted left\",\n category: \"Objects\",\n aliases: [\"mag\"],\n tags: [\"search\", \"zoom\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔎\",\n description: \"magnifying glass tilted right\",\n category: \"Objects\",\n aliases: [\"mag_right\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🕯️\",\n description: \"candle\",\n category: \"Objects\",\n aliases: [\"candle\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"💡\",\n description: \"light bulb\",\n category: \"Objects\",\n aliases: [\"bulb\"],\n tags: [\"idea\", \"light\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔦\",\n description: \"flashlight\",\n category: \"Objects\",\n aliases: [\"flashlight\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏮\",\n description: \"red paper lantern\",\n category: \"Objects\",\n aliases: [\"izakaya_lantern\", \"lantern\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🪔\",\n description: \"diya lamp\",\n category: \"Objects\",\n aliases: [\"diya_lamp\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"📔\",\n description: \"notebook with decorative cover\",\n category: \"Objects\",\n aliases: [\"notebook_with_decorative_cover\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📕\",\n description: \"closed book\",\n category: \"Objects\",\n aliases: [\"closed_book\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📖\",\n description: \"open book\",\n category: \"Objects\",\n aliases: [\"book\", \"open_book\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📗\",\n description: \"green book\",\n category: \"Objects\",\n aliases: [\"green_book\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📘\",\n description: \"blue book\",\n category: \"Objects\",\n aliases: [\"blue_book\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📙\",\n description: \"orange book\",\n category: \"Objects\",\n aliases: [\"orange_book\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📚\",\n description: \"books\",\n category: \"Objects\",\n aliases: [\"books\"],\n tags: [\"library\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📓\",\n description: \"notebook\",\n category: \"Objects\",\n aliases: [\"notebook\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📒\",\n description: \"ledger\",\n category: \"Objects\",\n aliases: [\"ledger\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📃\",\n description: \"page with curl\",\n category: \"Objects\",\n aliases: [\"page_with_curl\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📜\",\n description: \"scroll\",\n category: \"Objects\",\n aliases: [\"scroll\"],\n tags: [\"document\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📄\",\n description: \"page facing up\",\n category: \"Objects\",\n aliases: [\"page_facing_up\"],\n tags: [\"document\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📰\",\n description: \"newspaper\",\n category: \"Objects\",\n aliases: [\"newspaper\"],\n tags: [\"press\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🗞️\",\n description: \"rolled-up newspaper\",\n category: \"Objects\",\n aliases: [\"newspaper_roll\"],\n tags: [\"press\"],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"📑\",\n description: \"bookmark tabs\",\n category: \"Objects\",\n aliases: [\"bookmark_tabs\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔖\",\n description: \"bookmark\",\n category: \"Objects\",\n aliases: [\"bookmark\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏷️\",\n description: \"label\",\n category: \"Objects\",\n aliases: [\"label\"],\n tags: [\"tag\"],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"💰\",\n description: \"money bag\",\n category: \"Objects\",\n aliases: [\"moneybag\"],\n tags: [\"dollar\", \"cream\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🪙\",\n description: \"coin\",\n category: \"Objects\",\n aliases: [\"coin\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"💴\",\n description: \"yen banknote\",\n category: \"Objects\",\n aliases: [\"yen\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💵\",\n description: \"dollar banknote\",\n category: \"Objects\",\n aliases: [\"dollar\"],\n tags: [\"money\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💶\",\n description: \"euro banknote\",\n category: \"Objects\",\n aliases: [\"euro\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💷\",\n description: \"pound banknote\",\n category: \"Objects\",\n aliases: [\"pound\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💸\",\n description: \"money with wings\",\n category: \"Objects\",\n aliases: [\"money_with_wings\"],\n tags: [\"dollar\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💳\",\n description: \"credit card\",\n category: \"Objects\",\n aliases: [\"credit_card\"],\n tags: [\"subscription\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🧾\",\n description: \"receipt\",\n category: \"Objects\",\n aliases: [\"receipt\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"💹\",\n description: \"chart increasing with yen\",\n category: \"Objects\",\n aliases: [\"chart\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"✉️\",\n description: \"envelope\",\n category: \"Objects\",\n aliases: [\"envelope\"],\n tags: [\"letter\", \"email\"],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📧\",\n description: \"e-mail\",\n category: \"Objects\",\n aliases: [\"email\", \"e-mail\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📨\",\n description: \"incoming envelope\",\n category: \"Objects\",\n aliases: [\"incoming_envelope\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📩\",\n description: \"envelope with arrow\",\n category: \"Objects\",\n aliases: [\"envelope_with_arrow\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📤\",\n description: \"outbox tray\",\n category: \"Objects\",\n aliases: [\"outbox_tray\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📥\",\n description: \"inbox tray\",\n category: \"Objects\",\n aliases: [\"inbox_tray\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📦\",\n description: \"package\",\n category: \"Objects\",\n aliases: [\"package\"],\n tags: [\"shipping\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📫\",\n description: \"closed mailbox with raised flag\",\n category: \"Objects\",\n aliases: [\"mailbox\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📪\",\n description: \"closed mailbox with lowered flag\",\n category: \"Objects\",\n aliases: [\"mailbox_closed\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📬\",\n description: \"open mailbox with raised flag\",\n category: \"Objects\",\n aliases: [\"mailbox_with_mail\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📭\",\n description: \"open mailbox with lowered flag\",\n category: \"Objects\",\n aliases: [\"mailbox_with_no_mail\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📮\",\n description: \"postbox\",\n category: \"Objects\",\n aliases: [\"postbox\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🗳️\",\n description: \"ballot box with ballot\",\n category: \"Objects\",\n aliases: [\"ballot_box\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"✏️\",\n description: \"pencil\",\n category: \"Objects\",\n aliases: [\"pencil2\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"✒️\",\n description: \"black nib\",\n category: \"Objects\",\n aliases: [\"black_nib\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🖋️\",\n description: \"fountain pen\",\n category: \"Objects\",\n aliases: [\"fountain_pen\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🖊️\",\n description: \"pen\",\n category: \"Objects\",\n aliases: [\"pen\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🖌️\",\n description: \"paintbrush\",\n category: \"Objects\",\n aliases: [\"paintbrush\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🖍️\",\n description: \"crayon\",\n category: \"Objects\",\n aliases: [\"crayon\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"📝\",\n description: \"memo\",\n category: \"Objects\",\n aliases: [\"memo\", \"pencil\"],\n tags: [\"document\", \"note\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💼\",\n description: \"briefcase\",\n category: \"Objects\",\n aliases: [\"briefcase\"],\n tags: [\"business\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📁\",\n description: \"file folder\",\n category: \"Objects\",\n aliases: [\"file_folder\"],\n tags: [\"directory\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📂\",\n description: \"open file folder\",\n category: \"Objects\",\n aliases: [\"open_file_folder\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🗂️\",\n description: \"card index dividers\",\n category: \"Objects\",\n aliases: [\"card_index_dividers\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"📅\",\n description: \"calendar\",\n category: \"Objects\",\n aliases: [\"date\"],\n tags: [\"calendar\", \"schedule\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📆\",\n description: \"tear-off calendar\",\n category: \"Objects\",\n aliases: [\"calendar\"],\n tags: [\"schedule\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🗒️\",\n description: \"spiral notepad\",\n category: \"Objects\",\n aliases: [\"spiral_notepad\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🗓️\",\n description: \"spiral calendar\",\n category: \"Objects\",\n aliases: [\"spiral_calendar\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"📇\",\n description: \"card index\",\n category: \"Objects\",\n aliases: [\"card_index\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📈\",\n description: \"chart increasing\",\n category: \"Objects\",\n aliases: [\"chart_with_upwards_trend\"],\n tags: [\"graph\", \"metrics\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📉\",\n description: \"chart decreasing\",\n category: \"Objects\",\n aliases: [\"chart_with_downwards_trend\"],\n tags: [\"graph\", \"metrics\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📊\",\n description: \"bar chart\",\n category: \"Objects\",\n aliases: [\"bar_chart\"],\n tags: [\"stats\", \"metrics\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📋\",\n description: \"clipboard\",\n category: \"Objects\",\n aliases: [\"clipboard\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📌\",\n description: \"pushpin\",\n category: \"Objects\",\n aliases: [\"pushpin\"],\n tags: [\"location\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📍\",\n description: \"round pushpin\",\n category: \"Objects\",\n aliases: [\"round_pushpin\"],\n tags: [\"location\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📎\",\n description: \"paperclip\",\n category: \"Objects\",\n aliases: [\"paperclip\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🖇️\",\n description: \"linked paperclips\",\n category: \"Objects\",\n aliases: [\"paperclips\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"📏\",\n description: \"straight ruler\",\n category: \"Objects\",\n aliases: [\"straight_ruler\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📐\",\n description: \"triangular ruler\",\n category: \"Objects\",\n aliases: [\"triangular_ruler\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"✂️\",\n description: \"scissors\",\n category: \"Objects\",\n aliases: [\"scissors\"],\n tags: [\"cut\"],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🗃️\",\n description: \"card file box\",\n category: \"Objects\",\n aliases: [\"card_file_box\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🗄️\",\n description: \"file cabinet\",\n category: \"Objects\",\n aliases: [\"file_cabinet\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🗑️\",\n description: \"wastebasket\",\n category: \"Objects\",\n aliases: [\"wastebasket\"],\n tags: [\"trash\"],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🔒\",\n description: \"locked\",\n category: \"Objects\",\n aliases: [\"lock\"],\n tags: [\"security\", \"private\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔓\",\n description: \"unlocked\",\n category: \"Objects\",\n aliases: [\"unlock\"],\n tags: [\"security\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔏\",\n description: \"locked with pen\",\n category: \"Objects\",\n aliases: [\"lock_with_ink_pen\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔐\",\n description: \"locked with key\",\n category: \"Objects\",\n aliases: [\"closed_lock_with_key\"],\n tags: [\"security\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔑\",\n description: \"key\",\n category: \"Objects\",\n aliases: [\"key\"],\n tags: [\"lock\", \"password\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🗝️\",\n description: \"old key\",\n category: \"Objects\",\n aliases: [\"old_key\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🔨\",\n description: \"hammer\",\n category: \"Objects\",\n aliases: [\"hammer\"],\n tags: [\"tool\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🪓\",\n description: \"axe\",\n category: \"Objects\",\n aliases: [\"axe\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"⛏️\",\n description: \"pick\",\n category: \"Objects\",\n aliases: [\"pick\"],\n tags: [],\n unicode_version: \"5.2\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"⚒️\",\n description: \"hammer and pick\",\n category: \"Objects\",\n aliases: [\"hammer_and_pick\"],\n tags: [],\n unicode_version: \"4.1\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🛠️\",\n description: \"hammer and wrench\",\n category: \"Objects\",\n aliases: [\"hammer_and_wrench\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🗡️\",\n description: \"dagger\",\n category: \"Objects\",\n aliases: [\"dagger\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"⚔️\",\n description: \"crossed swords\",\n category: \"Objects\",\n aliases: [\"crossed_swords\"],\n tags: [],\n unicode_version: \"4.1\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🔫\",\n description: \"water pistol\",\n category: \"Objects\",\n aliases: [\"gun\"],\n tags: [\"shoot\", \"weapon\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🪃\",\n description: \"boomerang\",\n category: \"Objects\",\n aliases: [\"boomerang\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🏹\",\n description: \"bow and arrow\",\n category: \"Objects\",\n aliases: [\"bow_and_arrow\"],\n tags: [\"archery\"],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🛡️\",\n description: \"shield\",\n category: \"Objects\",\n aliases: [\"shield\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🪚\",\n description: \"carpentry saw\",\n category: \"Objects\",\n aliases: [\"carpentry_saw\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🔧\",\n description: \"wrench\",\n category: \"Objects\",\n aliases: [\"wrench\"],\n tags: [\"tool\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🪛\",\n description: \"screwdriver\",\n category: \"Objects\",\n aliases: [\"screwdriver\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🔩\",\n description: \"nut and bolt\",\n category: \"Objects\",\n aliases: [\"nut_and_bolt\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⚙️\",\n description: \"gear\",\n category: \"Objects\",\n aliases: [\"gear\"],\n tags: [],\n unicode_version: \"4.1\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🗜️\",\n description: \"clamp\",\n category: \"Objects\",\n aliases: [\"clamp\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"⚖️\",\n description: \"balance scale\",\n category: \"Objects\",\n aliases: [\"balance_scale\"],\n tags: [],\n unicode_version: \"4.1\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🦯\",\n description: \"white cane\",\n category: \"Objects\",\n aliases: [\"probing_cane\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🔗\",\n description: \"link\",\n category: \"Objects\",\n aliases: [\"link\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⛓️\",\n description: \"chains\",\n category: \"Objects\",\n aliases: [\"chains\"],\n tags: [],\n unicode_version: \"5.2\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🪝\",\n description: \"hook\",\n category: \"Objects\",\n aliases: [\"hook\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🧰\",\n description: \"toolbox\",\n category: \"Objects\",\n aliases: [\"toolbox\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🧲\",\n description: \"magnet\",\n category: \"Objects\",\n aliases: [\"magnet\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🪜\",\n description: \"ladder\",\n category: \"Objects\",\n aliases: [\"ladder\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"⚗️\",\n description: \"alembic\",\n category: \"Objects\",\n aliases: [\"alembic\"],\n tags: [],\n unicode_version: \"4.1\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🧪\",\n description: \"test tube\",\n category: \"Objects\",\n aliases: [\"test_tube\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🧫\",\n description: \"petri dish\",\n category: \"Objects\",\n aliases: [\"petri_dish\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🧬\",\n description: \"dna\",\n category: \"Objects\",\n aliases: [\"dna\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🔬\",\n description: \"microscope\",\n category: \"Objects\",\n aliases: [\"microscope\"],\n tags: [\"science\", \"laboratory\", \"investigate\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔭\",\n description: \"telescope\",\n category: \"Objects\",\n aliases: [\"telescope\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📡\",\n description: \"satellite antenna\",\n category: \"Objects\",\n aliases: [\"satellite\"],\n tags: [\"signal\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💉\",\n description: \"syringe\",\n category: \"Objects\",\n aliases: [\"syringe\"],\n tags: [\"health\", \"hospital\", \"needle\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🩸\",\n description: \"drop of blood\",\n category: \"Objects\",\n aliases: [\"drop_of_blood\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"💊\",\n description: \"pill\",\n category: \"Objects\",\n aliases: [\"pill\"],\n tags: [\"health\", \"medicine\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🩹\",\n description: \"adhesive bandage\",\n category: \"Objects\",\n aliases: [\"adhesive_bandage\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🩺\",\n description: \"stethoscope\",\n category: \"Objects\",\n aliases: [\"stethoscope\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🚪\",\n description: \"door\",\n category: \"Objects\",\n aliases: [\"door\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🛗\",\n description: \"elevator\",\n category: \"Objects\",\n aliases: [\"elevator\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🪞\",\n description: \"mirror\",\n category: \"Objects\",\n aliases: [\"mirror\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🪟\",\n description: \"window\",\n category: \"Objects\",\n aliases: [\"window\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🛏️\",\n description: \"bed\",\n category: \"Objects\",\n aliases: [\"bed\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🛋️\",\n description: \"couch and lamp\",\n category: \"Objects\",\n aliases: [\"couch_and_lamp\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🪑\",\n description: \"chair\",\n category: \"Objects\",\n aliases: [\"chair\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🚽\",\n description: \"toilet\",\n category: \"Objects\",\n aliases: [\"toilet\"],\n tags: [\"wc\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🪠\",\n description: \"plunger\",\n category: \"Objects\",\n aliases: [\"plunger\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🚿\",\n description: \"shower\",\n category: \"Objects\",\n aliases: [\"shower\"],\n tags: [\"bath\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🛁\",\n description: \"bathtub\",\n category: \"Objects\",\n aliases: [\"bathtub\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🪤\",\n description: \"mouse trap\",\n category: \"Objects\",\n aliases: [\"mouse_trap\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🪒\",\n description: \"razor\",\n category: \"Objects\",\n aliases: [\"razor\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🧴\",\n description: \"lotion bottle\",\n category: \"Objects\",\n aliases: [\"lotion_bottle\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🧷\",\n description: \"safety pin\",\n category: \"Objects\",\n aliases: [\"safety_pin\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🧹\",\n description: \"broom\",\n category: \"Objects\",\n aliases: [\"broom\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🧺\",\n description: \"basket\",\n category: \"Objects\",\n aliases: [\"basket\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🧻\",\n description: \"roll of paper\",\n category: \"Objects\",\n aliases: [\"roll_of_paper\"],\n tags: [\"toilet\"],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🪣\",\n description: \"bucket\",\n category: \"Objects\",\n aliases: [\"bucket\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🧼\",\n description: \"soap\",\n category: \"Objects\",\n aliases: [\"soap\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🪥\",\n description: \"toothbrush\",\n category: \"Objects\",\n aliases: [\"toothbrush\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🧽\",\n description: \"sponge\",\n category: \"Objects\",\n aliases: [\"sponge\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🧯\",\n description: \"fire extinguisher\",\n category: \"Objects\",\n aliases: [\"fire_extinguisher\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🛒\",\n description: \"shopping cart\",\n category: \"Objects\",\n aliases: [\"shopping_cart\"],\n tags: [],\n unicode_version: \"9.0\",\n ios_version: \"10.2\",\n },\n {\n emoji: \"🚬\",\n description: \"cigarette\",\n category: \"Objects\",\n aliases: [\"smoking\"],\n tags: [\"cigarette\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⚰️\",\n description: \"coffin\",\n category: \"Objects\",\n aliases: [\"coffin\"],\n tags: [\"funeral\"],\n unicode_version: \"4.1\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🪦\",\n description: \"headstone\",\n category: \"Objects\",\n aliases: [\"headstone\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"⚱️\",\n description: \"funeral urn\",\n category: \"Objects\",\n aliases: [\"funeral_urn\"],\n tags: [],\n unicode_version: \"4.1\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🗿\",\n description: \"moai\",\n category: \"Objects\",\n aliases: [\"moyai\"],\n tags: [\"stone\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🪧\",\n description: \"placard\",\n category: \"Objects\",\n aliases: [\"placard\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🏧\",\n description: \"ATM sign\",\n category: \"Symbols\",\n aliases: [\"atm\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚮\",\n description: \"litter in bin sign\",\n category: \"Symbols\",\n aliases: [\"put_litter_in_its_place\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚰\",\n description: \"potable water\",\n category: \"Symbols\",\n aliases: [\"potable_water\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"♿\",\n description: \"wheelchair symbol\",\n category: \"Symbols\",\n aliases: [\"wheelchair\"],\n tags: [\"accessibility\"],\n unicode_version: \"4.1\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚹\",\n description: \"men’s room\",\n category: \"Symbols\",\n aliases: [\"mens\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚺\",\n description: \"women’s room\",\n category: \"Symbols\",\n aliases: [\"womens\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚻\",\n description: \"restroom\",\n category: \"Symbols\",\n aliases: [\"restroom\"],\n tags: [\"toilet\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚼\",\n description: \"baby symbol\",\n category: \"Symbols\",\n aliases: [\"baby_symbol\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚾\",\n description: \"water closet\",\n category: \"Symbols\",\n aliases: [\"wc\"],\n tags: [\"toilet\", \"restroom\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🛂\",\n description: \"passport control\",\n category: \"Symbols\",\n aliases: [\"passport_control\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🛃\",\n description: \"customs\",\n category: \"Symbols\",\n aliases: [\"customs\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🛄\",\n description: \"baggage claim\",\n category: \"Symbols\",\n aliases: [\"baggage_claim\"],\n tags: [\"airport\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🛅\",\n description: \"left luggage\",\n category: \"Symbols\",\n aliases: [\"left_luggage\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⚠️\",\n description: \"warning\",\n category: \"Symbols\",\n aliases: [\"warning\"],\n tags: [\"wip\"],\n unicode_version: \"4.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚸\",\n description: \"children crossing\",\n category: \"Symbols\",\n aliases: [\"children_crossing\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⛔\",\n description: \"no entry\",\n category: \"Symbols\",\n aliases: [\"no_entry\"],\n tags: [\"limit\"],\n unicode_version: \"5.2\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚫\",\n description: \"prohibited\",\n category: \"Symbols\",\n aliases: [\"no_entry_sign\"],\n tags: [\"block\", \"forbidden\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚳\",\n description: \"no bicycles\",\n category: \"Symbols\",\n aliases: [\"no_bicycles\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚭\",\n description: \"no smoking\",\n category: \"Symbols\",\n aliases: [\"no_smoking\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚯\",\n description: \"no littering\",\n category: \"Symbols\",\n aliases: [\"do_not_litter\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚱\",\n description: \"non-potable water\",\n category: \"Symbols\",\n aliases: [\"non-potable_water\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚷\",\n description: \"no pedestrians\",\n category: \"Symbols\",\n aliases: [\"no_pedestrians\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📵\",\n description: \"no mobile phones\",\n category: \"Symbols\",\n aliases: [\"no_mobile_phones\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔞\",\n description: \"no one under eighteen\",\n category: \"Symbols\",\n aliases: [\"underage\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"☢️\",\n description: \"radioactive\",\n category: \"Symbols\",\n aliases: [\"radioactive\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"☣️\",\n description: \"biohazard\",\n category: \"Symbols\",\n aliases: [\"biohazard\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"⬆️\",\n description: \"up arrow\",\n category: \"Symbols\",\n aliases: [\"arrow_up\"],\n tags: [],\n unicode_version: \"4.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"↗️\",\n description: \"up-right arrow\",\n category: \"Symbols\",\n aliases: [\"arrow_upper_right\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"➡️\",\n description: \"right arrow\",\n category: \"Symbols\",\n aliases: [\"arrow_right\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"↘️\",\n description: \"down-right arrow\",\n category: \"Symbols\",\n aliases: [\"arrow_lower_right\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⬇️\",\n description: \"down arrow\",\n category: \"Symbols\",\n aliases: [\"arrow_down\"],\n tags: [],\n unicode_version: \"4.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"↙️\",\n description: \"down-left arrow\",\n category: \"Symbols\",\n aliases: [\"arrow_lower_left\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⬅️\",\n description: \"left arrow\",\n category: \"Symbols\",\n aliases: [\"arrow_left\"],\n tags: [],\n unicode_version: \"4.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"↖️\",\n description: \"up-left arrow\",\n category: \"Symbols\",\n aliases: [\"arrow_upper_left\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"↕️\",\n description: \"up-down arrow\",\n category: \"Symbols\",\n aliases: [\"arrow_up_down\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"↔️\",\n description: \"left-right arrow\",\n category: \"Symbols\",\n aliases: [\"left_right_arrow\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"↩️\",\n description: \"right arrow curving left\",\n category: \"Symbols\",\n aliases: [\"leftwards_arrow_with_hook\"],\n tags: [\"return\"],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"↪️\",\n description: \"left arrow curving right\",\n category: \"Symbols\",\n aliases: [\"arrow_right_hook\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⤴️\",\n description: \"right arrow curving up\",\n category: \"Symbols\",\n aliases: [\"arrow_heading_up\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⤵️\",\n description: \"right arrow curving down\",\n category: \"Symbols\",\n aliases: [\"arrow_heading_down\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔃\",\n description: \"clockwise vertical arrows\",\n category: \"Symbols\",\n aliases: [\"arrows_clockwise\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔄\",\n description: \"counterclockwise arrows button\",\n category: \"Symbols\",\n aliases: [\"arrows_counterclockwise\"],\n tags: [\"sync\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔙\",\n description: \"BACK arrow\",\n category: \"Symbols\",\n aliases: [\"back\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔚\",\n description: \"END arrow\",\n category: \"Symbols\",\n aliases: [\"end\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔛\",\n description: \"ON! arrow\",\n category: \"Symbols\",\n aliases: [\"on\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔜\",\n description: \"SOON arrow\",\n category: \"Symbols\",\n aliases: [\"soon\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔝\",\n description: \"TOP arrow\",\n category: \"Symbols\",\n aliases: [\"top\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🛐\",\n description: \"place of worship\",\n category: \"Symbols\",\n aliases: [\"place_of_worship\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"⚛️\",\n description: \"atom symbol\",\n category: \"Symbols\",\n aliases: [\"atom_symbol\"],\n tags: [],\n unicode_version: \"4.1\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🕉️\",\n description: \"om\",\n category: \"Symbols\",\n aliases: [\"om\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"✡️\",\n description: \"star of David\",\n category: \"Symbols\",\n aliases: [\"star_of_david\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"☸️\",\n description: \"wheel of dharma\",\n category: \"Symbols\",\n aliases: [\"wheel_of_dharma\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"☯️\",\n description: \"yin yang\",\n category: \"Symbols\",\n aliases: [\"yin_yang\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"✝️\",\n description: \"latin cross\",\n category: \"Symbols\",\n aliases: [\"latin_cross\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"☦️\",\n description: \"orthodox cross\",\n category: \"Symbols\",\n aliases: [\"orthodox_cross\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"☪️\",\n description: \"star and crescent\",\n category: \"Symbols\",\n aliases: [\"star_and_crescent\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"☮️\",\n description: \"peace symbol\",\n category: \"Symbols\",\n aliases: [\"peace_symbol\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🕎\",\n description: \"menorah\",\n category: \"Symbols\",\n aliases: [\"menorah\"],\n tags: [],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🔯\",\n description: \"dotted six-pointed star\",\n category: \"Symbols\",\n aliases: [\"six_pointed_star\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"♈\",\n description: \"Aries\",\n category: \"Symbols\",\n aliases: [\"aries\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"♉\",\n description: \"Taurus\",\n category: \"Symbols\",\n aliases: [\"taurus\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"♊\",\n description: \"Gemini\",\n category: \"Symbols\",\n aliases: [\"gemini\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"♋\",\n description: \"Cancer\",\n category: \"Symbols\",\n aliases: [\"cancer\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"♌\",\n description: \"Leo\",\n category: \"Symbols\",\n aliases: [\"leo\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"♍\",\n description: \"Virgo\",\n category: \"Symbols\",\n aliases: [\"virgo\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"♎\",\n description: \"Libra\",\n category: \"Symbols\",\n aliases: [\"libra\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"♏\",\n description: \"Scorpio\",\n category: \"Symbols\",\n aliases: [\"scorpius\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"♐\",\n description: \"Sagittarius\",\n category: \"Symbols\",\n aliases: [\"sagittarius\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"♑\",\n description: \"Capricorn\",\n category: \"Symbols\",\n aliases: [\"capricorn\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"♒\",\n description: \"Aquarius\",\n category: \"Symbols\",\n aliases: [\"aquarius\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"♓\",\n description: \"Pisces\",\n category: \"Symbols\",\n aliases: [\"pisces\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⛎\",\n description: \"Ophiuchus\",\n category: \"Symbols\",\n aliases: [\"ophiuchus\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔀\",\n description: \"shuffle tracks button\",\n category: \"Symbols\",\n aliases: [\"twisted_rightwards_arrows\"],\n tags: [\"shuffle\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔁\",\n description: \"repeat button\",\n category: \"Symbols\",\n aliases: [\"repeat\"],\n tags: [\"loop\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔂\",\n description: \"repeat single button\",\n category: \"Symbols\",\n aliases: [\"repeat_one\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"▶️\",\n description: \"play button\",\n category: \"Symbols\",\n aliases: [\"arrow_forward\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⏩\",\n description: \"fast-forward button\",\n category: \"Symbols\",\n aliases: [\"fast_forward\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⏭️\",\n description: \"next track button\",\n category: \"Symbols\",\n aliases: [\"next_track_button\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"⏯️\",\n description: \"play or pause button\",\n category: \"Symbols\",\n aliases: [\"play_or_pause_button\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"◀️\",\n description: \"reverse button\",\n category: \"Symbols\",\n aliases: [\"arrow_backward\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⏪\",\n description: \"fast reverse button\",\n category: \"Symbols\",\n aliases: [\"rewind\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⏮️\",\n description: \"last track button\",\n category: \"Symbols\",\n aliases: [\"previous_track_button\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🔼\",\n description: \"upwards button\",\n category: \"Symbols\",\n aliases: [\"arrow_up_small\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⏫\",\n description: \"fast up button\",\n category: \"Symbols\",\n aliases: [\"arrow_double_up\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔽\",\n description: \"downwards button\",\n category: \"Symbols\",\n aliases: [\"arrow_down_small\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⏬\",\n description: \"fast down button\",\n category: \"Symbols\",\n aliases: [\"arrow_double_down\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⏸️\",\n description: \"pause button\",\n category: \"Symbols\",\n aliases: [\"pause_button\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"⏹️\",\n description: \"stop button\",\n category: \"Symbols\",\n aliases: [\"stop_button\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"⏺️\",\n description: \"record button\",\n category: \"Symbols\",\n aliases: [\"record_button\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"⏏️\",\n description: \"eject button\",\n category: \"Symbols\",\n aliases: [\"eject_button\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🎦\",\n description: \"cinema\",\n category: \"Symbols\",\n aliases: [\"cinema\"],\n tags: [\"film\", \"movie\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔅\",\n description: \"dim button\",\n category: \"Symbols\",\n aliases: [\"low_brightness\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔆\",\n description: \"bright button\",\n category: \"Symbols\",\n aliases: [\"high_brightness\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📶\",\n description: \"antenna bars\",\n category: \"Symbols\",\n aliases: [\"signal_strength\"],\n tags: [\"wifi\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📳\",\n description: \"vibration mode\",\n category: \"Symbols\",\n aliases: [\"vibration_mode\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📴\",\n description: \"mobile phone off\",\n category: \"Symbols\",\n aliases: [\"mobile_phone_off\"],\n tags: [\"mute\", \"off\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"♀️\",\n description: \"female sign\",\n category: \"Symbols\",\n aliases: [\"female_sign\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"♂️\",\n description: \"male sign\",\n category: \"Symbols\",\n aliases: [\"male_sign\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"⚧️\",\n description: \"transgender symbol\",\n category: \"Symbols\",\n aliases: [\"transgender_symbol\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"✖️\",\n description: \"multiply\",\n category: \"Symbols\",\n aliases: [\"heavy_multiplication_x\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"➕\",\n description: \"plus\",\n category: \"Symbols\",\n aliases: [\"heavy_plus_sign\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"➖\",\n description: \"minus\",\n category: \"Symbols\",\n aliases: [\"heavy_minus_sign\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"➗\",\n description: \"divide\",\n category: \"Symbols\",\n aliases: [\"heavy_division_sign\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"♾️\",\n description: \"infinity\",\n category: \"Symbols\",\n aliases: [\"infinity\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"‼️\",\n description: \"double exclamation mark\",\n category: \"Symbols\",\n aliases: [\"bangbang\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⁉️\",\n description: \"exclamation question mark\",\n category: \"Symbols\",\n aliases: [\"interrobang\"],\n tags: [],\n unicode_version: \"3.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"❓\",\n description: \"red question mark\",\n category: \"Symbols\",\n aliases: [\"question\"],\n tags: [\"confused\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"❔\",\n description: \"white question mark\",\n category: \"Symbols\",\n aliases: [\"grey_question\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"❕\",\n description: \"white exclamation mark\",\n category: \"Symbols\",\n aliases: [\"grey_exclamation\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"❗\",\n description: \"red exclamation mark\",\n category: \"Symbols\",\n aliases: [\"exclamation\", \"heavy_exclamation_mark\"],\n tags: [\"bang\"],\n unicode_version: \"5.2\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"〰️\",\n description: \"wavy dash\",\n category: \"Symbols\",\n aliases: [\"wavy_dash\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💱\",\n description: \"currency exchange\",\n category: \"Symbols\",\n aliases: [\"currency_exchange\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💲\",\n description: \"heavy dollar sign\",\n category: \"Symbols\",\n aliases: [\"heavy_dollar_sign\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⚕️\",\n description: \"medical symbol\",\n category: \"Symbols\",\n aliases: [\"medical_symbol\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"♻️\",\n description: \"recycling symbol\",\n category: \"Symbols\",\n aliases: [\"recycle\"],\n tags: [\"environment\", \"green\"],\n unicode_version: \"3.2\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⚜️\",\n description: \"fleur-de-lis\",\n category: \"Symbols\",\n aliases: [\"fleur_de_lis\"],\n tags: [],\n unicode_version: \"4.1\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🔱\",\n description: \"trident emblem\",\n category: \"Symbols\",\n aliases: [\"trident\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"📛\",\n description: \"name badge\",\n category: \"Symbols\",\n aliases: [\"name_badge\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔰\",\n description: \"Japanese symbol for beginner\",\n category: \"Symbols\",\n aliases: [\"beginner\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⭕\",\n description: \"hollow red circle\",\n category: \"Symbols\",\n aliases: [\"o\"],\n tags: [],\n unicode_version: \"5.2\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"✅\",\n description: \"check mark button\",\n category: \"Symbols\",\n aliases: [\"white_check_mark\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"☑️\",\n description: \"check box with check\",\n category: \"Symbols\",\n aliases: [\"ballot_box_with_check\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"✔️\",\n description: \"check mark\",\n category: \"Symbols\",\n aliases: [\"heavy_check_mark\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"❌\",\n description: \"cross mark\",\n category: \"Symbols\",\n aliases: [\"x\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"❎\",\n description: \"cross mark button\",\n category: \"Symbols\",\n aliases: [\"negative_squared_cross_mark\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"➰\",\n description: \"curly loop\",\n category: \"Symbols\",\n aliases: [\"curly_loop\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"➿\",\n description: \"double curly loop\",\n category: \"Symbols\",\n aliases: [\"loop\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"〽️\",\n description: \"part alternation mark\",\n category: \"Symbols\",\n aliases: [\"part_alternation_mark\"],\n tags: [],\n unicode_version: \"3.2\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"✳️\",\n description: \"eight-spoked asterisk\",\n category: \"Symbols\",\n aliases: [\"eight_spoked_asterisk\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"✴️\",\n description: \"eight-pointed star\",\n category: \"Symbols\",\n aliases: [\"eight_pointed_black_star\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"❇️\",\n description: \"sparkle\",\n category: \"Symbols\",\n aliases: [\"sparkle\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"©️\",\n description: \"copyright\",\n category: \"Symbols\",\n aliases: [\"copyright\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"®️\",\n description: \"registered\",\n category: \"Symbols\",\n aliases: [\"registered\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"™️\",\n description: \"trade mark\",\n category: \"Symbols\",\n aliases: [\"tm\"],\n tags: [\"trademark\"],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"#️⃣\",\n description: \"keycap: #\",\n category: \"Symbols\",\n aliases: [\"hash\"],\n tags: [\"number\"],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"*️⃣\",\n description: \"keycap: *\",\n category: \"Symbols\",\n aliases: [\"asterisk\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"0️⃣\",\n description: \"keycap: 0\",\n category: \"Symbols\",\n aliases: [\"zero\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"1️⃣\",\n description: \"keycap: 1\",\n category: \"Symbols\",\n aliases: [\"one\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"2️⃣\",\n description: \"keycap: 2\",\n category: \"Symbols\",\n aliases: [\"two\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"3️⃣\",\n description: \"keycap: 3\",\n category: \"Symbols\",\n aliases: [\"three\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"4️⃣\",\n description: \"keycap: 4\",\n category: \"Symbols\",\n aliases: [\"four\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"5️⃣\",\n description: \"keycap: 5\",\n category: \"Symbols\",\n aliases: [\"five\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"6️⃣\",\n description: \"keycap: 6\",\n category: \"Symbols\",\n aliases: [\"six\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"7️⃣\",\n description: \"keycap: 7\",\n category: \"Symbols\",\n aliases: [\"seven\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"8️⃣\",\n description: \"keycap: 8\",\n category: \"Symbols\",\n aliases: [\"eight\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"9️⃣\",\n description: \"keycap: 9\",\n category: \"Symbols\",\n aliases: [\"nine\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔟\",\n description: \"keycap: 10\",\n category: \"Symbols\",\n aliases: [\"keycap_ten\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔠\",\n description: \"input latin uppercase\",\n category: \"Symbols\",\n aliases: [\"capital_abcd\"],\n tags: [\"letters\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔡\",\n description: \"input latin lowercase\",\n category: \"Symbols\",\n aliases: [\"abcd\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔢\",\n description: \"input numbers\",\n category: \"Symbols\",\n aliases: [\"1234\"],\n tags: [\"numbers\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔣\",\n description: \"input symbols\",\n category: \"Symbols\",\n aliases: [\"symbols\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔤\",\n description: \"input latin letters\",\n category: \"Symbols\",\n aliases: [\"abc\"],\n tags: [\"alphabet\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🅰️\",\n description: \"A button (blood type)\",\n category: \"Symbols\",\n aliases: [\"a\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🆎\",\n description: \"AB button (blood type)\",\n category: \"Symbols\",\n aliases: [\"ab\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🅱️\",\n description: \"B button (blood type)\",\n category: \"Symbols\",\n aliases: [\"b\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🆑\",\n description: \"CL button\",\n category: \"Symbols\",\n aliases: [\"cl\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🆒\",\n description: \"COOL button\",\n category: \"Symbols\",\n aliases: [\"cool\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🆓\",\n description: \"FREE button\",\n category: \"Symbols\",\n aliases: [\"free\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"ℹ️\",\n description: \"information\",\n category: \"Symbols\",\n aliases: [\"information_source\"],\n tags: [],\n unicode_version: \"3.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🆔\",\n description: \"ID button\",\n category: \"Symbols\",\n aliases: [\"id\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"Ⓜ️\",\n description: \"circled M\",\n category: \"Symbols\",\n aliases: [\"m\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🆕\",\n description: \"NEW button\",\n category: \"Symbols\",\n aliases: [\"new\"],\n tags: [\"fresh\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🆖\",\n description: \"NG button\",\n category: \"Symbols\",\n aliases: [\"ng\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🅾️\",\n description: \"O button (blood type)\",\n category: \"Symbols\",\n aliases: [\"o2\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🆗\",\n description: \"OK button\",\n category: \"Symbols\",\n aliases: [\"ok\"],\n tags: [\"yes\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🅿️\",\n description: \"P button\",\n category: \"Symbols\",\n aliases: [\"parking\"],\n tags: [],\n unicode_version: \"5.2\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🆘\",\n description: \"SOS button\",\n category: \"Symbols\",\n aliases: [\"sos\"],\n tags: [\"help\", \"emergency\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🆙\",\n description: \"UP! button\",\n category: \"Symbols\",\n aliases: [\"up\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🆚\",\n description: \"VS button\",\n category: \"Symbols\",\n aliases: [\"vs\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🈁\",\n description: \"Japanese “here” button\",\n category: \"Symbols\",\n aliases: [\"koko\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🈂️\",\n description: \"Japanese “service charge” button\",\n category: \"Symbols\",\n aliases: [\"sa\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🈷️\",\n description: \"Japanese “monthly amount” button\",\n category: \"Symbols\",\n aliases: [\"u6708\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🈶\",\n description: \"Japanese “not free of charge” button\",\n category: \"Symbols\",\n aliases: [\"u6709\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🈯\",\n description: \"Japanese “reserved” button\",\n category: \"Symbols\",\n aliases: [\"u6307\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🉐\",\n description: \"Japanese “bargain” button\",\n category: \"Symbols\",\n aliases: [\"ideograph_advantage\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🈹\",\n description: \"Japanese “discount” button\",\n category: \"Symbols\",\n aliases: [\"u5272\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🈚\",\n description: \"Japanese “free of charge” button\",\n category: \"Symbols\",\n aliases: [\"u7121\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🈲\",\n description: \"Japanese “prohibited” button\",\n category: \"Symbols\",\n aliases: [\"u7981\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🉑\",\n description: \"Japanese “acceptable” button\",\n category: \"Symbols\",\n aliases: [\"accept\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🈸\",\n description: \"Japanese “application” button\",\n category: \"Symbols\",\n aliases: [\"u7533\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🈴\",\n description: \"Japanese “passing grade” button\",\n category: \"Symbols\",\n aliases: [\"u5408\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🈳\",\n description: \"Japanese “vacancy” button\",\n category: \"Symbols\",\n aliases: [\"u7a7a\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"㊗️\",\n description: \"Japanese “congratulations” button\",\n category: \"Symbols\",\n aliases: [\"congratulations\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"㊙️\",\n description: \"Japanese “secret” button\",\n category: \"Symbols\",\n aliases: [\"secret\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🈺\",\n description: \"Japanese “open for business” button\",\n category: \"Symbols\",\n aliases: [\"u55b6\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🈵\",\n description: \"Japanese “no vacancy” button\",\n category: \"Symbols\",\n aliases: [\"u6e80\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔴\",\n description: \"red circle\",\n category: \"Symbols\",\n aliases: [\"red_circle\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🟠\",\n description: \"orange circle\",\n category: \"Symbols\",\n aliases: [\"orange_circle\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🟡\",\n description: \"yellow circle\",\n category: \"Symbols\",\n aliases: [\"yellow_circle\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🟢\",\n description: \"green circle\",\n category: \"Symbols\",\n aliases: [\"green_circle\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🔵\",\n description: \"blue circle\",\n category: \"Symbols\",\n aliases: [\"large_blue_circle\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🟣\",\n description: \"purple circle\",\n category: \"Symbols\",\n aliases: [\"purple_circle\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🟤\",\n description: \"brown circle\",\n category: \"Symbols\",\n aliases: [\"brown_circle\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"⚫\",\n description: \"black circle\",\n category: \"Symbols\",\n aliases: [\"black_circle\"],\n tags: [],\n unicode_version: \"4.1\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⚪\",\n description: \"white circle\",\n category: \"Symbols\",\n aliases: [\"white_circle\"],\n tags: [],\n unicode_version: \"4.1\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🟥\",\n description: \"red square\",\n category: \"Symbols\",\n aliases: [\"red_square\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🟧\",\n description: \"orange square\",\n category: \"Symbols\",\n aliases: [\"orange_square\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🟨\",\n description: \"yellow square\",\n category: \"Symbols\",\n aliases: [\"yellow_square\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🟩\",\n description: \"green square\",\n category: \"Symbols\",\n aliases: [\"green_square\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🟦\",\n description: \"blue square\",\n category: \"Symbols\",\n aliases: [\"blue_square\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🟪\",\n description: \"purple square\",\n category: \"Symbols\",\n aliases: [\"purple_square\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"🟫\",\n description: \"brown square\",\n category: \"Symbols\",\n aliases: [\"brown_square\"],\n tags: [],\n unicode_version: \"12.0\",\n ios_version: \"13.0\",\n },\n {\n emoji: \"⬛\",\n description: \"black large square\",\n category: \"Symbols\",\n aliases: [\"black_large_square\"],\n tags: [],\n unicode_version: \"5.1\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"⬜\",\n description: \"white large square\",\n category: \"Symbols\",\n aliases: [\"white_large_square\"],\n tags: [],\n unicode_version: \"5.1\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"◼️\",\n description: \"black medium square\",\n category: \"Symbols\",\n aliases: [\"black_medium_square\"],\n tags: [],\n unicode_version: \"3.2\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"◻️\",\n description: \"white medium square\",\n category: \"Symbols\",\n aliases: [\"white_medium_square\"],\n tags: [],\n unicode_version: \"3.2\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"◾\",\n description: \"black medium-small square\",\n category: \"Symbols\",\n aliases: [\"black_medium_small_square\"],\n tags: [],\n unicode_version: \"3.2\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"◽\",\n description: \"white medium-small square\",\n category: \"Symbols\",\n aliases: [\"white_medium_small_square\"],\n tags: [],\n unicode_version: \"3.2\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"▪️\",\n description: \"black small square\",\n category: \"Symbols\",\n aliases: [\"black_small_square\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"▫️\",\n description: \"white small square\",\n category: \"Symbols\",\n aliases: [\"white_small_square\"],\n tags: [],\n unicode_version: \"\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔶\",\n description: \"large orange diamond\",\n category: \"Symbols\",\n aliases: [\"large_orange_diamond\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔷\",\n description: \"large blue diamond\",\n category: \"Symbols\",\n aliases: [\"large_blue_diamond\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔸\",\n description: \"small orange diamond\",\n category: \"Symbols\",\n aliases: [\"small_orange_diamond\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔹\",\n description: \"small blue diamond\",\n category: \"Symbols\",\n aliases: [\"small_blue_diamond\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔺\",\n description: \"red triangle pointed up\",\n category: \"Symbols\",\n aliases: [\"small_red_triangle\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔻\",\n description: \"red triangle pointed down\",\n category: \"Symbols\",\n aliases: [\"small_red_triangle_down\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"💠\",\n description: \"diamond with a dot\",\n category: \"Symbols\",\n aliases: [\"diamond_shape_with_a_dot_inside\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔘\",\n description: \"radio button\",\n category: \"Symbols\",\n aliases: [\"radio_button\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔳\",\n description: \"white square button\",\n category: \"Symbols\",\n aliases: [\"white_square_button\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🔲\",\n description: \"black square button\",\n category: \"Symbols\",\n aliases: [\"black_square_button\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏁\",\n description: \"chequered flag\",\n category: \"Flags\",\n aliases: [\"checkered_flag\"],\n tags: [\"milestone\", \"finish\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🚩\",\n description: \"triangular flag\",\n category: \"Flags\",\n aliases: [\"triangular_flag_on_post\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🎌\",\n description: \"crossed flags\",\n category: \"Flags\",\n aliases: [\"crossed_flags\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🏴\",\n description: \"black flag\",\n category: \"Flags\",\n aliases: [\"black_flag\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🏳️\",\n description: \"white flag\",\n category: \"Flags\",\n aliases: [\"white_flag\"],\n tags: [],\n unicode_version: \"7.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🏳️🌈\",\n description: \"rainbow flag\",\n category: \"Flags\",\n aliases: [\"rainbow_flag\"],\n tags: [\"pride\"],\n unicode_version: \"6.0\",\n ios_version: \"10.0\",\n },\n {\n emoji: \"🏳️⚧️\",\n description: \"transgender flag\",\n category: \"Flags\",\n aliases: [\"transgender_flag\"],\n tags: [],\n unicode_version: \"13.0\",\n ios_version: \"14.0\",\n },\n {\n emoji: \"🏴☠️\",\n description: \"pirate flag\",\n category: \"Flags\",\n aliases: [\"pirate_flag\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🇦🇨\",\n description: \"flag: Ascension Island\",\n category: \"Flags\",\n aliases: [\"ascension_island\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🇦🇩\",\n description: \"flag: Andorra\",\n category: \"Flags\",\n aliases: [\"andorra\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇦🇪\",\n description: \"flag: United Arab Emirates\",\n category: \"Flags\",\n aliases: [\"united_arab_emirates\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇦🇫\",\n description: \"flag: Afghanistan\",\n category: \"Flags\",\n aliases: [\"afghanistan\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇦🇬\",\n description: \"flag: Antigua & Barbuda\",\n category: \"Flags\",\n aliases: [\"antigua_barbuda\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇦🇮\",\n description: \"flag: Anguilla\",\n category: \"Flags\",\n aliases: [\"anguilla\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇦🇱\",\n description: \"flag: Albania\",\n category: \"Flags\",\n aliases: [\"albania\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇦🇲\",\n description: \"flag: Armenia\",\n category: \"Flags\",\n aliases: [\"armenia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇦🇴\",\n description: \"flag: Angola\",\n category: \"Flags\",\n aliases: [\"angola\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇦🇶\",\n description: \"flag: Antarctica\",\n category: \"Flags\",\n aliases: [\"antarctica\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇦🇷\",\n description: \"flag: Argentina\",\n category: \"Flags\",\n aliases: [\"argentina\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇦🇸\",\n description: \"flag: American Samoa\",\n category: \"Flags\",\n aliases: [\"american_samoa\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇦🇹\",\n description: \"flag: Austria\",\n category: \"Flags\",\n aliases: [\"austria\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇦🇺\",\n description: \"flag: Australia\",\n category: \"Flags\",\n aliases: [\"australia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇦🇼\",\n description: \"flag: Aruba\",\n category: \"Flags\",\n aliases: [\"aruba\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇦🇽\",\n description: \"flag: Åland Islands\",\n category: \"Flags\",\n aliases: [\"aland_islands\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇦🇿\",\n description: \"flag: Azerbaijan\",\n category: \"Flags\",\n aliases: [\"azerbaijan\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇧🇦\",\n description: \"flag: Bosnia & Herzegovina\",\n category: \"Flags\",\n aliases: [\"bosnia_herzegovina\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇧🇧\",\n description: \"flag: Barbados\",\n category: \"Flags\",\n aliases: [\"barbados\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇧🇩\",\n description: \"flag: Bangladesh\",\n category: \"Flags\",\n aliases: [\"bangladesh\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇧🇪\",\n description: \"flag: Belgium\",\n category: \"Flags\",\n aliases: [\"belgium\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇧🇫\",\n description: \"flag: Burkina Faso\",\n category: \"Flags\",\n aliases: [\"burkina_faso\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇧🇬\",\n description: \"flag: Bulgaria\",\n category: \"Flags\",\n aliases: [\"bulgaria\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇧🇭\",\n description: \"flag: Bahrain\",\n category: \"Flags\",\n aliases: [\"bahrain\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇧🇮\",\n description: \"flag: Burundi\",\n category: \"Flags\",\n aliases: [\"burundi\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇧🇯\",\n description: \"flag: Benin\",\n category: \"Flags\",\n aliases: [\"benin\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇧🇱\",\n description: \"flag: St. Barthélemy\",\n category: \"Flags\",\n aliases: [\"st_barthelemy\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇧🇲\",\n description: \"flag: Bermuda\",\n category: \"Flags\",\n aliases: [\"bermuda\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇧🇳\",\n description: \"flag: Brunei\",\n category: \"Flags\",\n aliases: [\"brunei\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇧🇴\",\n description: \"flag: Bolivia\",\n category: \"Flags\",\n aliases: [\"bolivia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇧🇶\",\n description: \"flag: Caribbean Netherlands\",\n category: \"Flags\",\n aliases: [\"caribbean_netherlands\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇧🇷\",\n description: \"flag: Brazil\",\n category: \"Flags\",\n aliases: [\"brazil\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇧🇸\",\n description: \"flag: Bahamas\",\n category: \"Flags\",\n aliases: [\"bahamas\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇧🇹\",\n description: \"flag: Bhutan\",\n category: \"Flags\",\n aliases: [\"bhutan\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇧🇻\",\n description: \"flag: Bouvet Island\",\n category: \"Flags\",\n aliases: [\"bouvet_island\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🇧🇼\",\n description: \"flag: Botswana\",\n category: \"Flags\",\n aliases: [\"botswana\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇧🇾\",\n description: \"flag: Belarus\",\n category: \"Flags\",\n aliases: [\"belarus\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇧🇿\",\n description: \"flag: Belize\",\n category: \"Flags\",\n aliases: [\"belize\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇨🇦\",\n description: \"flag: Canada\",\n category: \"Flags\",\n aliases: [\"canada\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇨🇨\",\n description: \"flag: Cocos (Keeling) Islands\",\n category: \"Flags\",\n aliases: [\"cocos_islands\"],\n tags: [\"keeling\"],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇨🇩\",\n description: \"flag: Congo - Kinshasa\",\n category: \"Flags\",\n aliases: [\"congo_kinshasa\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇨🇫\",\n description: \"flag: Central African Republic\",\n category: \"Flags\",\n aliases: [\"central_african_republic\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇨🇬\",\n description: \"flag: Congo - Brazzaville\",\n category: \"Flags\",\n aliases: [\"congo_brazzaville\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇨🇭\",\n description: \"flag: Switzerland\",\n category: \"Flags\",\n aliases: [\"switzerland\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇨🇮\",\n description: \"flag: Côte d’Ivoire\",\n category: \"Flags\",\n aliases: [\"cote_divoire\"],\n tags: [\"ivory\"],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇨🇰\",\n description: \"flag: Cook Islands\",\n category: \"Flags\",\n aliases: [\"cook_islands\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇨🇱\",\n description: \"flag: Chile\",\n category: \"Flags\",\n aliases: [\"chile\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇨🇲\",\n description: \"flag: Cameroon\",\n category: \"Flags\",\n aliases: [\"cameroon\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇨🇳\",\n description: \"flag: China\",\n category: \"Flags\",\n aliases: [\"cn\"],\n tags: [\"china\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🇨🇴\",\n description: \"flag: Colombia\",\n category: \"Flags\",\n aliases: [\"colombia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇨🇵\",\n description: \"flag: Clipperton Island\",\n category: \"Flags\",\n aliases: [\"clipperton_island\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🇨🇷\",\n description: \"flag: Costa Rica\",\n category: \"Flags\",\n aliases: [\"costa_rica\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇨🇺\",\n description: \"flag: Cuba\",\n category: \"Flags\",\n aliases: [\"cuba\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇨🇻\",\n description: \"flag: Cape Verde\",\n category: \"Flags\",\n aliases: [\"cape_verde\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇨🇼\",\n description: \"flag: Curaçao\",\n category: \"Flags\",\n aliases: [\"curacao\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇨🇽\",\n description: \"flag: Christmas Island\",\n category: \"Flags\",\n aliases: [\"christmas_island\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇨🇾\",\n description: \"flag: Cyprus\",\n category: \"Flags\",\n aliases: [\"cyprus\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇨🇿\",\n description: \"flag: Czechia\",\n category: \"Flags\",\n aliases: [\"czech_republic\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇩🇪\",\n description: \"flag: Germany\",\n category: \"Flags\",\n aliases: [\"de\"],\n tags: [\"flag\", \"germany\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🇩🇬\",\n description: \"flag: Diego Garcia\",\n category: \"Flags\",\n aliases: [\"diego_garcia\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🇩🇯\",\n description: \"flag: Djibouti\",\n category: \"Flags\",\n aliases: [\"djibouti\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇩🇰\",\n description: \"flag: Denmark\",\n category: \"Flags\",\n aliases: [\"denmark\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇩🇲\",\n description: \"flag: Dominica\",\n category: \"Flags\",\n aliases: [\"dominica\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇩🇴\",\n description: \"flag: Dominican Republic\",\n category: \"Flags\",\n aliases: [\"dominican_republic\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇩🇿\",\n description: \"flag: Algeria\",\n category: \"Flags\",\n aliases: [\"algeria\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇪🇦\",\n description: \"flag: Ceuta & Melilla\",\n category: \"Flags\",\n aliases: [\"ceuta_melilla\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🇪🇨\",\n description: \"flag: Ecuador\",\n category: \"Flags\",\n aliases: [\"ecuador\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇪🇪\",\n description: \"flag: Estonia\",\n category: \"Flags\",\n aliases: [\"estonia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇪🇬\",\n description: \"flag: Egypt\",\n category: \"Flags\",\n aliases: [\"egypt\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇪🇭\",\n description: \"flag: Western Sahara\",\n category: \"Flags\",\n aliases: [\"western_sahara\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇪🇷\",\n description: \"flag: Eritrea\",\n category: \"Flags\",\n aliases: [\"eritrea\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇪🇸\",\n description: \"flag: Spain\",\n category: \"Flags\",\n aliases: [\"es\"],\n tags: [\"spain\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🇪🇹\",\n description: \"flag: Ethiopia\",\n category: \"Flags\",\n aliases: [\"ethiopia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇪🇺\",\n description: \"flag: European Union\",\n category: \"Flags\",\n aliases: [\"eu\", \"european_union\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇫🇮\",\n description: \"flag: Finland\",\n category: \"Flags\",\n aliases: [\"finland\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇫🇯\",\n description: \"flag: Fiji\",\n category: \"Flags\",\n aliases: [\"fiji\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇫🇰\",\n description: \"flag: Falkland Islands\",\n category: \"Flags\",\n aliases: [\"falkland_islands\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇫🇲\",\n description: \"flag: Micronesia\",\n category: \"Flags\",\n aliases: [\"micronesia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇫🇴\",\n description: \"flag: Faroe Islands\",\n category: \"Flags\",\n aliases: [\"faroe_islands\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇫🇷\",\n description: \"flag: France\",\n category: \"Flags\",\n aliases: [\"fr\"],\n tags: [\"france\", \"french\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🇬🇦\",\n description: \"flag: Gabon\",\n category: \"Flags\",\n aliases: [\"gabon\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇬🇧\",\n description: \"flag: United Kingdom\",\n category: \"Flags\",\n aliases: [\"gb\", \"uk\"],\n tags: [\"flag\", \"british\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🇬🇩\",\n description: \"flag: Grenada\",\n category: \"Flags\",\n aliases: [\"grenada\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇬🇪\",\n description: \"flag: Georgia\",\n category: \"Flags\",\n aliases: [\"georgia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇬🇫\",\n description: \"flag: French Guiana\",\n category: \"Flags\",\n aliases: [\"french_guiana\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇬🇬\",\n description: \"flag: Guernsey\",\n category: \"Flags\",\n aliases: [\"guernsey\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇬🇭\",\n description: \"flag: Ghana\",\n category: \"Flags\",\n aliases: [\"ghana\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇬🇮\",\n description: \"flag: Gibraltar\",\n category: \"Flags\",\n aliases: [\"gibraltar\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇬🇱\",\n description: \"flag: Greenland\",\n category: \"Flags\",\n aliases: [\"greenland\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇬🇲\",\n description: \"flag: Gambia\",\n category: \"Flags\",\n aliases: [\"gambia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇬🇳\",\n description: \"flag: Guinea\",\n category: \"Flags\",\n aliases: [\"guinea\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇬🇵\",\n description: \"flag: Guadeloupe\",\n category: \"Flags\",\n aliases: [\"guadeloupe\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇬🇶\",\n description: \"flag: Equatorial Guinea\",\n category: \"Flags\",\n aliases: [\"equatorial_guinea\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇬🇷\",\n description: \"flag: Greece\",\n category: \"Flags\",\n aliases: [\"greece\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇬🇸\",\n description: \"flag: South Georgia & South Sandwich Islands\",\n category: \"Flags\",\n aliases: [\"south_georgia_south_sandwich_islands\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇬🇹\",\n description: \"flag: Guatemala\",\n category: \"Flags\",\n aliases: [\"guatemala\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇬🇺\",\n description: \"flag: Guam\",\n category: \"Flags\",\n aliases: [\"guam\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇬🇼\",\n description: \"flag: Guinea-Bissau\",\n category: \"Flags\",\n aliases: [\"guinea_bissau\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇬🇾\",\n description: \"flag: Guyana\",\n category: \"Flags\",\n aliases: [\"guyana\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇭🇰\",\n description: \"flag: Hong Kong SAR China\",\n category: \"Flags\",\n aliases: [\"hong_kong\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇭🇲\",\n description: \"flag: Heard & McDonald Islands\",\n category: \"Flags\",\n aliases: [\"heard_mcdonald_islands\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🇭🇳\",\n description: \"flag: Honduras\",\n category: \"Flags\",\n aliases: [\"honduras\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇭🇷\",\n description: \"flag: Croatia\",\n category: \"Flags\",\n aliases: [\"croatia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇭🇹\",\n description: \"flag: Haiti\",\n category: \"Flags\",\n aliases: [\"haiti\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇭🇺\",\n description: \"flag: Hungary\",\n category: \"Flags\",\n aliases: [\"hungary\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇮🇨\",\n description: \"flag: Canary Islands\",\n category: \"Flags\",\n aliases: [\"canary_islands\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇮🇩\",\n description: \"flag: Indonesia\",\n category: \"Flags\",\n aliases: [\"indonesia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇮🇪\",\n description: \"flag: Ireland\",\n category: \"Flags\",\n aliases: [\"ireland\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇮🇱\",\n description: \"flag: Israel\",\n category: \"Flags\",\n aliases: [\"israel\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇮🇲\",\n description: \"flag: Isle of Man\",\n category: \"Flags\",\n aliases: [\"isle_of_man\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇮🇳\",\n description: \"flag: India\",\n category: \"Flags\",\n aliases: [\"india\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇮🇴\",\n description: \"flag: British Indian Ocean Territory\",\n category: \"Flags\",\n aliases: [\"british_indian_ocean_territory\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇮🇶\",\n description: \"flag: Iraq\",\n category: \"Flags\",\n aliases: [\"iraq\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇮🇷\",\n description: \"flag: Iran\",\n category: \"Flags\",\n aliases: [\"iran\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇮🇸\",\n description: \"flag: Iceland\",\n category: \"Flags\",\n aliases: [\"iceland\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇮🇹\",\n description: \"flag: Italy\",\n category: \"Flags\",\n aliases: [\"it\"],\n tags: [\"italy\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🇯🇪\",\n description: \"flag: Jersey\",\n category: \"Flags\",\n aliases: [\"jersey\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇯🇲\",\n description: \"flag: Jamaica\",\n category: \"Flags\",\n aliases: [\"jamaica\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇯🇴\",\n description: \"flag: Jordan\",\n category: \"Flags\",\n aliases: [\"jordan\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇯🇵\",\n description: \"flag: Japan\",\n category: \"Flags\",\n aliases: [\"jp\"],\n tags: [\"japan\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🇰🇪\",\n description: \"flag: Kenya\",\n category: \"Flags\",\n aliases: [\"kenya\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇰🇬\",\n description: \"flag: Kyrgyzstan\",\n category: \"Flags\",\n aliases: [\"kyrgyzstan\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇰🇭\",\n description: \"flag: Cambodia\",\n category: \"Flags\",\n aliases: [\"cambodia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇰🇮\",\n description: \"flag: Kiribati\",\n category: \"Flags\",\n aliases: [\"kiribati\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇰🇲\",\n description: \"flag: Comoros\",\n category: \"Flags\",\n aliases: [\"comoros\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇰🇳\",\n description: \"flag: St. Kitts & Nevis\",\n category: \"Flags\",\n aliases: [\"st_kitts_nevis\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇰🇵\",\n description: \"flag: North Korea\",\n category: \"Flags\",\n aliases: [\"north_korea\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇰🇷\",\n description: \"flag: South Korea\",\n category: \"Flags\",\n aliases: [\"kr\"],\n tags: [\"korea\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🇰🇼\",\n description: \"flag: Kuwait\",\n category: \"Flags\",\n aliases: [\"kuwait\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇰🇾\",\n description: \"flag: Cayman Islands\",\n category: \"Flags\",\n aliases: [\"cayman_islands\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇰🇿\",\n description: \"flag: Kazakhstan\",\n category: \"Flags\",\n aliases: [\"kazakhstan\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇱🇦\",\n description: \"flag: Laos\",\n category: \"Flags\",\n aliases: [\"laos\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇱🇧\",\n description: \"flag: Lebanon\",\n category: \"Flags\",\n aliases: [\"lebanon\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇱🇨\",\n description: \"flag: St. Lucia\",\n category: \"Flags\",\n aliases: [\"st_lucia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇱🇮\",\n description: \"flag: Liechtenstein\",\n category: \"Flags\",\n aliases: [\"liechtenstein\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇱🇰\",\n description: \"flag: Sri Lanka\",\n category: \"Flags\",\n aliases: [\"sri_lanka\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇱🇷\",\n description: \"flag: Liberia\",\n category: \"Flags\",\n aliases: [\"liberia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇱🇸\",\n description: \"flag: Lesotho\",\n category: \"Flags\",\n aliases: [\"lesotho\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇱🇹\",\n description: \"flag: Lithuania\",\n category: \"Flags\",\n aliases: [\"lithuania\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇱🇺\",\n description: \"flag: Luxembourg\",\n category: \"Flags\",\n aliases: [\"luxembourg\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇱🇻\",\n description: \"flag: Latvia\",\n category: \"Flags\",\n aliases: [\"latvia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇱🇾\",\n description: \"flag: Libya\",\n category: \"Flags\",\n aliases: [\"libya\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇲🇦\",\n description: \"flag: Morocco\",\n category: \"Flags\",\n aliases: [\"morocco\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇲🇨\",\n description: \"flag: Monaco\",\n category: \"Flags\",\n aliases: [\"monaco\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇲🇩\",\n description: \"flag: Moldova\",\n category: \"Flags\",\n aliases: [\"moldova\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇲🇪\",\n description: \"flag: Montenegro\",\n category: \"Flags\",\n aliases: [\"montenegro\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇲🇫\",\n description: \"flag: St. Martin\",\n category: \"Flags\",\n aliases: [\"st_martin\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🇲🇬\",\n description: \"flag: Madagascar\",\n category: \"Flags\",\n aliases: [\"madagascar\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇲🇭\",\n description: \"flag: Marshall Islands\",\n category: \"Flags\",\n aliases: [\"marshall_islands\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇲🇰\",\n description: \"flag: North Macedonia\",\n category: \"Flags\",\n aliases: [\"macedonia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇲🇱\",\n description: \"flag: Mali\",\n category: \"Flags\",\n aliases: [\"mali\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇲🇲\",\n description: \"flag: Myanmar (Burma)\",\n category: \"Flags\",\n aliases: [\"myanmar\"],\n tags: [\"burma\"],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇲🇳\",\n description: \"flag: Mongolia\",\n category: \"Flags\",\n aliases: [\"mongolia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇲🇴\",\n description: \"flag: Macao SAR China\",\n category: \"Flags\",\n aliases: [\"macau\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇲🇵\",\n description: \"flag: Northern Mariana Islands\",\n category: \"Flags\",\n aliases: [\"northern_mariana_islands\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇲🇶\",\n description: \"flag: Martinique\",\n category: \"Flags\",\n aliases: [\"martinique\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇲🇷\",\n description: \"flag: Mauritania\",\n category: \"Flags\",\n aliases: [\"mauritania\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇲🇸\",\n description: \"flag: Montserrat\",\n category: \"Flags\",\n aliases: [\"montserrat\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇲🇹\",\n description: \"flag: Malta\",\n category: \"Flags\",\n aliases: [\"malta\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇲🇺\",\n description: \"flag: Mauritius\",\n category: \"Flags\",\n aliases: [\"mauritius\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇲🇻\",\n description: \"flag: Maldives\",\n category: \"Flags\",\n aliases: [\"maldives\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇲🇼\",\n description: \"flag: Malawi\",\n category: \"Flags\",\n aliases: [\"malawi\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇲🇽\",\n description: \"flag: Mexico\",\n category: \"Flags\",\n aliases: [\"mexico\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇲🇾\",\n description: \"flag: Malaysia\",\n category: \"Flags\",\n aliases: [\"malaysia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇲🇿\",\n description: \"flag: Mozambique\",\n category: \"Flags\",\n aliases: [\"mozambique\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇳🇦\",\n description: \"flag: Namibia\",\n category: \"Flags\",\n aliases: [\"namibia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇳🇨\",\n description: \"flag: New Caledonia\",\n category: \"Flags\",\n aliases: [\"new_caledonia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇳🇪\",\n description: \"flag: Niger\",\n category: \"Flags\",\n aliases: [\"niger\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇳🇫\",\n description: \"flag: Norfolk Island\",\n category: \"Flags\",\n aliases: [\"norfolk_island\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇳🇬\",\n description: \"flag: Nigeria\",\n category: \"Flags\",\n aliases: [\"nigeria\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇳🇮\",\n description: \"flag: Nicaragua\",\n category: \"Flags\",\n aliases: [\"nicaragua\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇳🇱\",\n description: \"flag: Netherlands\",\n category: \"Flags\",\n aliases: [\"netherlands\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇳🇴\",\n description: \"flag: Norway\",\n category: \"Flags\",\n aliases: [\"norway\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇳🇵\",\n description: \"flag: Nepal\",\n category: \"Flags\",\n aliases: [\"nepal\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇳🇷\",\n description: \"flag: Nauru\",\n category: \"Flags\",\n aliases: [\"nauru\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇳🇺\",\n description: \"flag: Niue\",\n category: \"Flags\",\n aliases: [\"niue\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇳🇿\",\n description: \"flag: New Zealand\",\n category: \"Flags\",\n aliases: [\"new_zealand\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇴🇲\",\n description: \"flag: Oman\",\n category: \"Flags\",\n aliases: [\"oman\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇵🇦\",\n description: \"flag: Panama\",\n category: \"Flags\",\n aliases: [\"panama\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇵🇪\",\n description: \"flag: Peru\",\n category: \"Flags\",\n aliases: [\"peru\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇵🇫\",\n description: \"flag: French Polynesia\",\n category: \"Flags\",\n aliases: [\"french_polynesia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇵🇬\",\n description: \"flag: Papua New Guinea\",\n category: \"Flags\",\n aliases: [\"papua_new_guinea\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇵🇭\",\n description: \"flag: Philippines\",\n category: \"Flags\",\n aliases: [\"philippines\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇵🇰\",\n description: \"flag: Pakistan\",\n category: \"Flags\",\n aliases: [\"pakistan\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇵🇱\",\n description: \"flag: Poland\",\n category: \"Flags\",\n aliases: [\"poland\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇵🇲\",\n description: \"flag: St. Pierre & Miquelon\",\n category: \"Flags\",\n aliases: [\"st_pierre_miquelon\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇵🇳\",\n description: \"flag: Pitcairn Islands\",\n category: \"Flags\",\n aliases: [\"pitcairn_islands\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇵🇷\",\n description: \"flag: Puerto Rico\",\n category: \"Flags\",\n aliases: [\"puerto_rico\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇵🇸\",\n description: \"flag: Palestinian Territories\",\n category: \"Flags\",\n aliases: [\"palestinian_territories\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇵🇹\",\n description: \"flag: Portugal\",\n category: \"Flags\",\n aliases: [\"portugal\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇵🇼\",\n description: \"flag: Palau\",\n category: \"Flags\",\n aliases: [\"palau\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇵🇾\",\n description: \"flag: Paraguay\",\n category: \"Flags\",\n aliases: [\"paraguay\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇶🇦\",\n description: \"flag: Qatar\",\n category: \"Flags\",\n aliases: [\"qatar\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇷🇪\",\n description: \"flag: Réunion\",\n category: \"Flags\",\n aliases: [\"reunion\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇷🇴\",\n description: \"flag: Romania\",\n category: \"Flags\",\n aliases: [\"romania\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇷🇸\",\n description: \"flag: Serbia\",\n category: \"Flags\",\n aliases: [\"serbia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇷🇺\",\n description: \"flag: Russia\",\n category: \"Flags\",\n aliases: [\"ru\"],\n tags: [\"russia\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🇷🇼\",\n description: \"flag: Rwanda\",\n category: \"Flags\",\n aliases: [\"rwanda\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇸🇦\",\n description: \"flag: Saudi Arabia\",\n category: \"Flags\",\n aliases: [\"saudi_arabia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇸🇧\",\n description: \"flag: Solomon Islands\",\n category: \"Flags\",\n aliases: [\"solomon_islands\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇸🇨\",\n description: \"flag: Seychelles\",\n category: \"Flags\",\n aliases: [\"seychelles\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇸🇩\",\n description: \"flag: Sudan\",\n category: \"Flags\",\n aliases: [\"sudan\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇸🇪\",\n description: \"flag: Sweden\",\n category: \"Flags\",\n aliases: [\"sweden\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇸🇬\",\n description: \"flag: Singapore\",\n category: \"Flags\",\n aliases: [\"singapore\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇸🇭\",\n description: \"flag: St. Helena\",\n category: \"Flags\",\n aliases: [\"st_helena\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇸🇮\",\n description: \"flag: Slovenia\",\n category: \"Flags\",\n aliases: [\"slovenia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇸🇯\",\n description: \"flag: Svalbard & Jan Mayen\",\n category: \"Flags\",\n aliases: [\"svalbard_jan_mayen\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🇸🇰\",\n description: \"flag: Slovakia\",\n category: \"Flags\",\n aliases: [\"slovakia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇸🇱\",\n description: \"flag: Sierra Leone\",\n category: \"Flags\",\n aliases: [\"sierra_leone\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇸🇲\",\n description: \"flag: San Marino\",\n category: \"Flags\",\n aliases: [\"san_marino\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇸🇳\",\n description: \"flag: Senegal\",\n category: \"Flags\",\n aliases: [\"senegal\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇸🇴\",\n description: \"flag: Somalia\",\n category: \"Flags\",\n aliases: [\"somalia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇸🇷\",\n description: \"flag: Suriname\",\n category: \"Flags\",\n aliases: [\"suriname\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇸🇸\",\n description: \"flag: South Sudan\",\n category: \"Flags\",\n aliases: [\"south_sudan\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇸🇹\",\n description: \"flag: São Tomé & Príncipe\",\n category: \"Flags\",\n aliases: [\"sao_tome_principe\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇸🇻\",\n description: \"flag: El Salvador\",\n category: \"Flags\",\n aliases: [\"el_salvador\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇸🇽\",\n description: \"flag: Sint Maarten\",\n category: \"Flags\",\n aliases: [\"sint_maarten\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇸🇾\",\n description: \"flag: Syria\",\n category: \"Flags\",\n aliases: [\"syria\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇸🇿\",\n description: \"flag: Eswatini\",\n category: \"Flags\",\n aliases: [\"swaziland\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇹🇦\",\n description: \"flag: Tristan da Cunha\",\n category: \"Flags\",\n aliases: [\"tristan_da_cunha\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🇹🇨\",\n description: \"flag: Turks & Caicos Islands\",\n category: \"Flags\",\n aliases: [\"turks_caicos_islands\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇹🇩\",\n description: \"flag: Chad\",\n category: \"Flags\",\n aliases: [\"chad\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇹🇫\",\n description: \"flag: French Southern Territories\",\n category: \"Flags\",\n aliases: [\"french_southern_territories\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇹🇬\",\n description: \"flag: Togo\",\n category: \"Flags\",\n aliases: [\"togo\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇹🇭\",\n description: \"flag: Thailand\",\n category: \"Flags\",\n aliases: [\"thailand\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇹🇯\",\n description: \"flag: Tajikistan\",\n category: \"Flags\",\n aliases: [\"tajikistan\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇹🇰\",\n description: \"flag: Tokelau\",\n category: \"Flags\",\n aliases: [\"tokelau\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇹🇱\",\n description: \"flag: Timor-Leste\",\n category: \"Flags\",\n aliases: [\"timor_leste\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇹🇲\",\n description: \"flag: Turkmenistan\",\n category: \"Flags\",\n aliases: [\"turkmenistan\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇹🇳\",\n description: \"flag: Tunisia\",\n category: \"Flags\",\n aliases: [\"tunisia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇹🇴\",\n description: \"flag: Tonga\",\n category: \"Flags\",\n aliases: [\"tonga\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇹🇷\",\n description: \"flag: Turkey\",\n category: \"Flags\",\n aliases: [\"tr\"],\n tags: [\"turkey\"],\n unicode_version: \"8.0\",\n ios_version: \"9.1\",\n },\n {\n emoji: \"🇹🇹\",\n description: \"flag: Trinidad & Tobago\",\n category: \"Flags\",\n aliases: [\"trinidad_tobago\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇹🇻\",\n description: \"flag: Tuvalu\",\n category: \"Flags\",\n aliases: [\"tuvalu\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇹🇼\",\n description: \"flag: Taiwan\",\n category: \"Flags\",\n aliases: [\"taiwan\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇹🇿\",\n description: \"flag: Tanzania\",\n category: \"Flags\",\n aliases: [\"tanzania\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇺🇦\",\n description: \"flag: Ukraine\",\n category: \"Flags\",\n aliases: [\"ukraine\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇺🇬\",\n description: \"flag: Uganda\",\n category: \"Flags\",\n aliases: [\"uganda\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇺🇲\",\n description: \"flag: U.S. Outlying Islands\",\n category: \"Flags\",\n aliases: [\"us_outlying_islands\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🇺🇳\",\n description: \"flag: United Nations\",\n category: \"Flags\",\n aliases: [\"united_nations\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🇺🇸\",\n description: \"flag: United States\",\n category: \"Flags\",\n aliases: [\"us\"],\n tags: [\"flag\", \"united\", \"america\"],\n unicode_version: \"6.0\",\n ios_version: \"6.0\",\n },\n {\n emoji: \"🇺🇾\",\n description: \"flag: Uruguay\",\n category: \"Flags\",\n aliases: [\"uruguay\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇺🇿\",\n description: \"flag: Uzbekistan\",\n category: \"Flags\",\n aliases: [\"uzbekistan\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇻🇦\",\n description: \"flag: Vatican City\",\n category: \"Flags\",\n aliases: [\"vatican_city\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇻🇨\",\n description: \"flag: St. Vincent & Grenadines\",\n category: \"Flags\",\n aliases: [\"st_vincent_grenadines\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇻🇪\",\n description: \"flag: Venezuela\",\n category: \"Flags\",\n aliases: [\"venezuela\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇻🇬\",\n description: \"flag: British Virgin Islands\",\n category: \"Flags\",\n aliases: [\"british_virgin_islands\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇻🇮\",\n description: \"flag: U.S. Virgin Islands\",\n category: \"Flags\",\n aliases: [\"us_virgin_islands\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇻🇳\",\n description: \"flag: Vietnam\",\n category: \"Flags\",\n aliases: [\"vietnam\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇻🇺\",\n description: \"flag: Vanuatu\",\n category: \"Flags\",\n aliases: [\"vanuatu\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇼🇫\",\n description: \"flag: Wallis & Futuna\",\n category: \"Flags\",\n aliases: [\"wallis_futuna\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇼🇸\",\n description: \"flag: Samoa\",\n category: \"Flags\",\n aliases: [\"samoa\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇽🇰\",\n description: \"flag: Kosovo\",\n category: \"Flags\",\n aliases: [\"kosovo\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇾🇪\",\n description: \"flag: Yemen\",\n category: \"Flags\",\n aliases: [\"yemen\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇾🇹\",\n description: \"flag: Mayotte\",\n category: \"Flags\",\n aliases: [\"mayotte\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"9.0\",\n },\n {\n emoji: \"🇿🇦\",\n description: \"flag: South Africa\",\n category: \"Flags\",\n aliases: [\"south_africa\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇿🇲\",\n description: \"flag: Zambia\",\n category: \"Flags\",\n aliases: [\"zambia\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🇿🇼\",\n description: \"flag: Zimbabwe\",\n category: \"Flags\",\n aliases: [\"zimbabwe\"],\n tags: [],\n unicode_version: \"6.0\",\n ios_version: \"8.3\",\n },\n {\n emoji: \"🏴\",\n description: \"flag: England\",\n category: \"Flags\",\n aliases: [\"england\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🏴\",\n description: \"flag: Scotland\",\n category: \"Flags\",\n aliases: [\"scotland\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n {\n emoji: \"🏴\",\n description: \"flag: Wales\",\n category: \"Flags\",\n aliases: [\"wales\"],\n tags: [],\n unicode_version: \"11.0\",\n ios_version: \"12.1\",\n },\n];\n", "type": "registry:component" }, { "path": "src/components/editor/utils/get-dom-range-rect.ts", "content": "export function getDOMRangeRect(\n nativeSelection: Selection,\n rootElement: HTMLElement,\n): DOMRect {\n const domRange = nativeSelection.getRangeAt(0);\n\n let rect;\n\n if (nativeSelection.anchorNode === rootElement) {\n let inner = rootElement;\n while (inner.firstElementChild != null) {\n inner = inner.firstElementChild as HTMLElement;\n }\n rect = inner.getBoundingClientRect();\n } else {\n rect = domRange.getBoundingClientRect();\n }\n\n return rect;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/utils/get-selected-node.ts", "content": "import { $isAtNodeEnd } from \"@lexical/selection\";\nimport { type ElementNode, type RangeSelection, TextNode } from \"lexical\";\n\nexport function getSelectedNode(\n selection: RangeSelection,\n): TextNode | ElementNode {\n const anchor = selection.anchor;\n const focus = selection.focus;\n const anchorNode = selection.anchor.getNode();\n const focusNode = selection.focus.getNode();\n if (anchorNode === focusNode) {\n return anchorNode;\n }\n const isBackward = selection.isBackward();\n if (isBackward) {\n return $isAtNodeEnd(focus) ? anchorNode : focusNode;\n } else {\n return $isAtNodeEnd(anchor) ? anchorNode : focusNode;\n }\n}\n", "type": "registry:component" }, { "path": "src/components/editor/utils/guard.ts", "content": "export function isHTMLElement(x: unknown): x is HTMLElement {\n return x instanceof HTMLElement;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/utils/set-floating-elem-position-for-link-editor.ts", "content": "const VERTICAL_GAP = 10;\nconst HORIZONTAL_OFFSET = 5;\n\nexport function setFloatingElemPositionForLinkEditor(\n targetRect: DOMRect | null,\n floatingElem: HTMLElement,\n anchorElem: HTMLElement,\n verticalGap: number = VERTICAL_GAP,\n horizontalOffset: number = HORIZONTAL_OFFSET,\n): void {\n const scrollerElem = anchorElem.parentElement;\n\n if (targetRect === null || !scrollerElem) {\n floatingElem.style.opacity = \"0\";\n floatingElem.style.transform = \"translate(-10000px, -10000px)\";\n return;\n }\n\n const floatingElemRect = floatingElem.getBoundingClientRect();\n const anchorElementRect = anchorElem.getBoundingClientRect();\n const editorScrollerRect = scrollerElem.getBoundingClientRect();\n\n let top = targetRect.top - verticalGap;\n let left = targetRect.left - horizontalOffset;\n\n if (top < editorScrollerRect.top) {\n top += floatingElemRect.height + targetRect.height + verticalGap * 2;\n }\n\n if (left + floatingElemRect.width > editorScrollerRect.right) {\n left = editorScrollerRect.right - floatingElemRect.width - horizontalOffset;\n }\n\n top -= anchorElementRect.top;\n left -= anchorElementRect.left;\n\n floatingElem.style.opacity = \"1\";\n floatingElem.style.transform = `translate(${left}px, ${top}px)`;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/utils/set-floating-elem-position.ts", "content": "const VERTICAL_GAP = 10;\nconst HORIZONTAL_OFFSET = 5;\n\nexport function setFloatingElemPosition(\n targetRect: DOMRect | null,\n floatingElem: HTMLElement,\n anchorElem: HTMLElement,\n isLink: boolean = false,\n verticalGap: number = VERTICAL_GAP,\n horizontalOffset: number = HORIZONTAL_OFFSET,\n): void {\n const scrollerElem = anchorElem.parentElement;\n\n if (targetRect === null || !scrollerElem) {\n floatingElem.style.opacity = \"0\";\n floatingElem.style.transform = \"translate(-10000px, -10000px)\";\n return;\n }\n\n const floatingElemRect = floatingElem.getBoundingClientRect();\n const anchorElementRect = anchorElem.getBoundingClientRect();\n const editorScrollerRect = scrollerElem.getBoundingClientRect();\n\n let top = targetRect.top - floatingElemRect.height - verticalGap;\n let left = targetRect.left - horizontalOffset;\n\n if (top < editorScrollerRect.top) {\n // adjusted height for link element if the element is at top\n top +=\n floatingElemRect.height +\n targetRect.height +\n verticalGap * (isLink ? 9 : 2);\n }\n\n if (left + floatingElemRect.width > editorScrollerRect.right) {\n left = editorScrollerRect.right - floatingElemRect.width - horizontalOffset;\n }\n\n top -= anchorElementRect.top;\n left -= anchorElementRect.left;\n\n floatingElem.style.opacity = \"1\";\n floatingElem.style.transform = `translate(${left}px, ${top}px)`;\n}\n", "type": "registry:component" }, { "path": "src/components/editor/utils/swipe.ts", "content": "type Force = [number, number];\ntype Listener = (force: Force, e: TouchEvent) => void;\ntype ElementValues = {\n start: null | Force;\n listeners: Set;\n handleTouchstart: (e: TouchEvent) => void;\n handleTouchend: (e: TouchEvent) => void;\n};\n\nconst elements = new WeakMap();\n\nfunction readTouch(e: TouchEvent): [number, number] | null {\n const touch = e.changedTouches[0];\n if (touch === undefined) {\n return null;\n }\n return [touch.clientX, touch.clientY];\n}\n\nfunction addListener(element: HTMLElement, cb: Listener): () => void {\n let elementValues = elements.get(element);\n if (elementValues === undefined) {\n const listeners = new Set();\n const handleTouchstart = (e: TouchEvent) => {\n if (elementValues !== undefined) {\n elementValues.start = readTouch(e);\n }\n };\n const handleTouchend = (e: TouchEvent) => {\n if (elementValues === undefined) {\n return;\n }\n const start = elementValues.start;\n if (start === null) {\n return;\n }\n const end = readTouch(e);\n for (const listener of Array.from(listeners)) {\n if (end !== null) {\n listener([end[0] - start[0], end[1] - start[1]], e);\n }\n }\n };\n element.addEventListener(\"touchstart\", handleTouchstart);\n element.addEventListener(\"touchend\", handleTouchend);\n\n elementValues = {\n handleTouchend,\n handleTouchstart,\n listeners,\n start: null,\n };\n elements.set(element, elementValues);\n }\n elementValues.listeners.add(cb);\n return () => deleteListener(element, cb);\n}\n\nfunction deleteListener(element: HTMLElement, cb: Listener): void {\n const elementValues = elements.get(element);\n if (elementValues === undefined) {\n return;\n }\n const listeners = elementValues.listeners;\n listeners.delete(cb);\n if (listeners.size === 0) {\n elements.delete(element);\n element.removeEventListener(\"touchstart\", elementValues.handleTouchstart);\n element.removeEventListener(\"touchend\", elementValues.handleTouchend);\n }\n}\n\nexport function addSwipeLeftListener(\n element: HTMLElement,\n cb: (_force: number, e: TouchEvent) => void,\n) {\n return addListener(element, (force, e) => {\n const [x, y] = force;\n if (x < 0 && -x > Math.abs(y)) {\n cb(x, e);\n }\n });\n}\n\nexport function addSwipeRightListener(\n element: HTMLElement,\n cb: (_force: number, e: TouchEvent) => void,\n) {\n return addListener(element, (force, e) => {\n const [x, y] = force;\n if (x > 0 && x > Math.abs(y)) {\n cb(x, e);\n }\n });\n}\n\nexport function addSwipeUpListener(\n element: HTMLElement,\n cb: (_force: number, e: TouchEvent) => void,\n) {\n return addListener(element, (force, e) => {\n const [x, y] = force;\n if (y < 0 && -y > Math.abs(x)) {\n cb(x, e);\n }\n });\n}\n\nexport function addSwipeDownListener(\n element: HTMLElement,\n cb: (_force: number, e: TouchEvent) => void,\n) {\n return addListener(element, (force, e) => {\n const [x, y] = force;\n if (y > 0 && y > Math.abs(x)) {\n cb(x, e);\n }\n });\n}\n", "type": "registry:component" }, { "path": "src/components/editor/utils/url.ts", "content": "const SUPPORTED_URL_PROTOCOLS = new Set([\n \"http:\",\n \"https:\",\n \"mailto:\",\n \"sms:\",\n \"tel:\",\n]);\n\nexport function sanitizeUrl(url: string): string {\n try {\n const parsedUrl = new URL(url);\n if (!SUPPORTED_URL_PROTOCOLS.has(parsedUrl.protocol)) {\n return \"about:blank\";\n }\n } catch {\n return url;\n }\n return url;\n}\n\n// Source: https://stackoverflow.com/a/8234912/2013580\nconst urlRegExp = new RegExp(\n /((([A-Za-z]{3,9}:(?:\\/\\/)?)(?:[-;:&=+$,\\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\\w]+@)[A-Za-z0-9.-]+)((?:\\/[+~%/.\\w-_]*)?\\??(?:[-+=&;%@.\\w_]*)#?(?:[\\w]*))?)/,\n);\nexport function validateUrl(url: string): boolean {\n // TODO Fix UI for link insertion; it should never default to an invalid URL such as https://.\n // Maybe show a dialog where they user can type the URL before inserting it.\n return url === \"https://\" || urlRegExp.test(url);\n}\n", "type": "registry:component" } ], "type": "registry:block" }
{stats.characters} characters
{stats.words} words