{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "editor",
  "title": "Editor",
  "description": "A rich text editor built on Lexical with plugins, nodes, extensions, and toolbar support.",
  "dependencies": [
    "class-variance-authority",
    "date-fns",
    "@lexical/code",
    "@lexical/code-prism",
    "@lexical/code-shiki",
    "@lexical/extension",
    "@lexical/file",
    "@lexical/hashtag",
    "@lexical/history",
    "@lexical/link",
    "@lexical/list",
    "@lexical/markdown",
    "@lexical/overflow",
    "@lexical/react",
    "@lexical/rich-text",
    "@lexical/selection",
    "@lexical/table",
    "@lexical/text",
    "@lexical/utils",
    "lexical",
    "lodash",
    "lucide-react",
    "sonner"
  ],
  "devDependencies": [
    "@types/lodash"
  ],
  "registryDependencies": [
    "button",
    "checkbox",
    "command",
    "dialog",
    "dropdown-menu",
    "field",
    "input",
    "input-group",
    "label",
    "popover",
    "scroll-area",
    "select",
    "separator",
    "slider",
    "tabs",
    "toggle",
    "toggle-group",
    "tooltip"
  ],
  "files": [
    {
      "path": "src/components/blocks/editor-x.tsx",
      "content": "import { useMemo, useState } from \"react\";\n\nimport { CodeHighlightNode, CodeNode } from \"@lexical/code\";\nimport {\n  AutoFocusExtension,\n  ClearEditorExtension,\n  DecoratorTextExtension,\n  HorizontalRuleExtension,\n  SelectionAlwaysOnDisplayExtension,\n} from \"@lexical/extension\";\nimport { HashtagExtension } from \"@lexical/hashtag\";\nimport { HistoryExtension } from \"@lexical/history\";\nimport {\n  AutoLinkExtension,\n  ClickableLinkExtension,\n  LinkExtension,\n} from \"@lexical/link\";\nimport { CheckListExtension, ListExtension } from \"@lexical/list\";\nimport {\n  CHECK_LIST,\n  ELEMENT_TRANSFORMERS,\n  MULTILINE_ELEMENT_TRANSFORMERS,\n  TEXT_FORMAT_TRANSFORMERS,\n  TEXT_MATCH_TRANSFORMERS,\n} from \"@lexical/markdown\";\nimport { OverflowNode } from \"@lexical/overflow\";\nimport { LexicalExtensionComposer } from \"@lexical/react/LexicalExtensionComposer\";\nimport { OnChangePlugin } from \"@lexical/react/LexicalOnChangePlugin\";\nimport { TabIndentationPlugin } from \"@lexical/react/LexicalTabIndentationPlugin\";\nimport { TablePlugin } from \"@lexical/react/LexicalTablePlugin\";\nimport { RichTextExtension } from \"@lexical/rich-text\";\nimport { TableCellNode, TableNode, TableRowNode } from \"@lexical/table\";\nimport {\n  type EditorState,\n  type SerializedEditorState,\n  configExtension,\n  defineExtension,\n} from \"lexical\";\n\nimport { ContentEditable } from \"@/components/editor/editor-ui/content-editable\";\nimport { DateTimeExtension } from \"@/components/editor/extensions/date-time-extension\";\nimport { DragDropPasteExtension } from \"@/components/editor/extensions/drag-drop-paste-extension\";\nimport { EmojisExtension } from \"@/components/editor/extensions/emojis-extension\";\nimport { ImagesExtension } from \"@/components/editor/extensions/images-extension\";\nimport { KeywordsExtension } from \"@/components/editor/extensions/keywords-extension\";\nimport { MarkdownShortcutsExtension } from \"@/components/editor/extensions/markdown-shortcuts-extension\";\nimport { MaxLengthExtension } from \"@/components/editor/extensions/max-length-extension\";\nimport { AutocompleteNode } from \"@/components/editor/nodes/autocomplete-node\";\nimport { TweetNode } from \"@/components/editor/nodes/embeds/tweet-node\";\nimport { YouTubeNode } from \"@/components/editor/nodes/embeds/youtube-node\";\nimport { EmojiNode } from \"@/components/editor/nodes/emoji-node\";\nimport { LayoutContainerNode } from \"@/components/editor/nodes/layout-container-node\";\nimport { LayoutItemNode } from \"@/components/editor/nodes/layout-item-node\";\nimport { MentionNode } from \"@/components/editor/nodes/mention-node\";\nimport { SpecialTextNode } from \"@/components/editor/nodes/special-text-node\";\nimport { ActionsPlugin } from \"@/components/editor/plugins/actions/actions-plugin\";\nimport { ClearEditorActionPlugin } from \"@/components/editor/plugins/actions/clear-editor-plugin\";\nimport { CounterCharacterPlugin } from \"@/components/editor/plugins/actions/counter-character-plugin\";\nimport { EditModeTogglePlugin } from \"@/components/editor/plugins/actions/edit-mode-toggle-plugin\";\nimport { ImportExportPlugin } from \"@/components/editor/plugins/actions/import-export-plugin\";\nimport { MarkdownTogglePlugin } from \"@/components/editor/plugins/actions/markdown-toggle-plugin\";\nimport { ShareContentPlugin } from \"@/components/editor/plugins/actions/share-content-plugin\";\nimport { SpeechToTextPlugin } from \"@/components/editor/plugins/actions/speech-to-text-plugin\";\nimport { TreeViewPlugin } from \"@/components/editor/plugins/actions/tree-view-plugin\";\nimport { AutoCompletePlugin } from \"@/components/editor/plugins/auto-complete-plugin\";\nimport { CodeActionMenuPlugin } from \"@/components/editor/plugins/code-action-menu-plugin\";\nimport { CodeHighlightPlugin } from \"@/components/editor/plugins/code-highlight-plugin\";\nimport { ComponentPickerMenuPlugin } from \"@/components/editor/plugins/component-picker-menu-plugin\";\nimport { ContextMenuPlugin } from \"@/components/editor/plugins/context-menu-plugin\";\nimport { DraggableBlockPlugin } from \"@/components/editor/plugins/draggable-block-plugin\";\nimport { AutoEmbedPlugin } from \"@/components/editor/plugins/embeds/auto-embed-plugin\";\nimport { TwitterPlugin } from \"@/components/editor/plugins/embeds/twitter-plugin\";\nimport { YouTubePlugin } from \"@/components/editor/plugins/embeds/youtube-plugin\";\nimport { EmojiPickerPlugin } from \"@/components/editor/plugins/emoji-picker-plugin\";\nimport { FloatingLinkEditorPlugin } from \"@/components/editor/plugins/floating-link-editor-plugin\";\nimport { FloatingTextFormatToolbarPlugin } from \"@/components/editor/plugins/floating-text-format-plugin\";\nimport { LayoutPlugin } from \"@/components/editor/plugins/layout-plugin\";\nimport { MentionsPlugin } from \"@/components/editor/plugins/mentions-plugin\";\nimport { AlignmentPickerPlugin } from \"@/components/editor/plugins/picker/alignment-picker-plugin\";\nimport { BulletedListPickerPlugin } from \"@/components/editor/plugins/picker/bulleted-list-picker-plugin\";\nimport { CheckListPickerPlugin } from \"@/components/editor/plugins/picker/check-list-picker-plugin\";\nimport { CodePickerPlugin } from \"@/components/editor/plugins/picker/code-picker-plugin\";\nimport { ColumnsLayoutPickerPlugin } from \"@/components/editor/plugins/picker/columns-layout-picker-plugin\";\nimport { DateTimePickerPlugin } from \"@/components/editor/plugins/picker/date-time-picker-plugin\";\nimport { DividerPickerPlugin } from \"@/components/editor/plugins/picker/divider-picker-plugin\";\nimport { EmbedsPickerPlugin } from \"@/components/editor/plugins/picker/embeds-picker-plugin\";\nimport { HeadingPickerPlugin } from \"@/components/editor/plugins/picker/heading-picker-plugin\";\nimport { ImagePickerPlugin } from \"@/components/editor/plugins/picker/image-picker-plugin\";\nimport { NumberedListPickerPlugin } from \"@/components/editor/plugins/picker/numbered-list-picker-plugin\";\nimport { ParagraphPickerPlugin } from \"@/components/editor/plugins/picker/paragraph-picker-plugin\";\nimport { QuotePickerPlugin } from \"@/components/editor/plugins/picker/quote-picker-plugin\";\nimport {\n  DynamicTablePickerPlugin,\n  TablePickerPlugin,\n} from \"@/components/editor/plugins/picker/table-picker-plugin\";\nimport { SpecialTextPlugin } from \"@/components/editor/plugins/special-text-plugin\";\nimport { TabFocusPlugin } from \"@/components/editor/plugins/tab-focus-plugin\";\nimport { BlockFormatDropDown } from \"@/components/editor/plugins/toolbar/block-format-toolbar-plugin\";\nimport { FormatBulletedList } from \"@/components/editor/plugins/toolbar/block-format/format-bulleted-list\";\nimport { FormatCheckList } from \"@/components/editor/plugins/toolbar/block-format/format-check-list\";\nimport { FormatCodeBlock } from \"@/components/editor/plugins/toolbar/block-format/format-code-block\";\nimport { FormatHeading } from \"@/components/editor/plugins/toolbar/block-format/format-heading\";\nimport { FormatNumberedList } from \"@/components/editor/plugins/toolbar/block-format/format-numbered-list\";\nimport { FormatParagraph } from \"@/components/editor/plugins/toolbar/block-format/format-paragraph\";\nimport { FormatQuote } from \"@/components/editor/plugins/toolbar/block-format/format-quote\";\nimport { BlockInsertPlugin } from \"@/components/editor/plugins/toolbar/block-insert-plugin\";\nimport { EditorLanguageSelect } from \"@/components/editor/editor-ui/language-select\";\nimport { InsertColumnsLayout } from \"@/components/editor/plugins/toolbar/block-insert/insert-columns-layout\";\nimport { InsertEmbeds } from \"@/components/editor/plugins/toolbar/block-insert/insert-embeds\";\nimport { InsertHorizontalRule } from \"@/components/editor/plugins/toolbar/block-insert/insert-horizontal-rule\";\nimport { InsertImage } from \"@/components/editor/plugins/toolbar/block-insert/insert-image\";\nimport { InsertTable } from \"@/components/editor/plugins/toolbar/block-insert/insert-table\";\nimport { ClearFormattingToolbarPlugin } from \"@/components/editor/plugins/toolbar/clear-formatting-toolbar-plugin\";\nimport { CodeLanguageToolbarPlugin } from \"@/components/editor/plugins/toolbar/code-language-toolbar-plugin\";\nimport { ElementFormatToolbarPlugin } from \"@/components/editor/plugins/toolbar/element-format-toolbar-plugin\";\nimport { FontBackgroundToolbarPlugin } from \"@/components/editor/plugins/toolbar/font-background-toolbar-plugin\";\nimport { FontColorToolbarPlugin } from \"@/components/editor/plugins/toolbar/font-color-toolbar-plugin\";\nimport { FontFamilyToolbarPlugin } from \"@/components/editor/plugins/toolbar/font-family-toolbar-plugin\";\nimport { FontFormatToolbarPlugin } from \"@/components/editor/plugins/toolbar/font-format-toolbar-plugin\";\nimport { FontSizeToolbarPlugin } from \"@/components/editor/plugins/toolbar/font-size-toolbar-plugin\";\nimport { HistoryToolbarPlugin } from \"@/components/editor/plugins/toolbar/history-toolbar-plugin\";\nimport { LinkToolbarPlugin } from \"@/components/editor/plugins/toolbar/link-toolbar-plugin\";\nimport { SubSuperToolbarPlugin } from \"@/components/editor/plugins/toolbar/subsuper-toolbar-plugin\";\nimport { ToolbarPlugin } from \"@/components/editor/plugins/toolbar/toolbar-plugin\";\nimport { TypingPerfPlugin } from \"@/components/editor/plugins/typing-pref-plugin\";\nimport { editorTheme } from \"@/components/editor/themes/editor-theme\";\nimport { EMOJI } from \"@/components/editor/transformers/markdown-emoji-transformer\";\nimport { HR } from \"@/components/editor/transformers/markdown-hr-transformer\";\nimport { IMAGE } from \"@/components/editor/transformers/markdown-image-transformer\";\nimport { TABLE } from \"@/components/editor/transformers/markdown-table-transformer\";\nimport { TWEET } from \"@/components/editor/transformers/markdown-tweet-transformer\";\nimport { validateUrl } from \"@/components/editor/utils/url\";\nimport {\n  EditorI18nProvider,\n  useEditorI18n,\n  type EditorMessages,\n  type TranslateFn,\n} from \"@/components/editor/i18n\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { TooltipProvider } from \"@/components/ui/tooltip\";\n\nconst maxLength = 3000;\n\nexport function Editor({\n  editorState,\n  editorSerializedState,\n  onChange,\n  onSerializedChange,\n  locale,\n  onLocaleChange,\n  showLanguageSelect = true,\n  messages,\n  t,\n}: {\n  editorState?: EditorState;\n  editorSerializedState?: SerializedEditorState;\n  onChange?: (editorState: EditorState) => void;\n  onSerializedChange?: (editorSerializedState: SerializedEditorState) => void;\n  locale?: string;\n  onLocaleChange?: (locale: string) => void;\n  showLanguageSelect?: boolean;\n  messages?: Partial<EditorMessages>;\n  t?: TranslateFn;\n}) {\n  return (\n    <EditorI18nProvider\n      locale={locale}\n      onLocaleChange={onLocaleChange}\n      messages={messages}\n      t={t}\n    >\n      <EditorInner\n        editorState={editorState}\n        editorSerializedState={editorSerializedState}\n        onChange={onChange}\n        onSerializedChange={onSerializedChange}\n        showLanguageSelect={showLanguageSelect}\n      />\n    </EditorI18nProvider>\n  );\n}\n\nfunction EditorInner({\n  editorState,\n  editorSerializedState,\n  onChange,\n  onSerializedChange,\n  showLanguageSelect,\n}: {\n  editorState?: EditorState;\n  editorSerializedState?: SerializedEditorState;\n  onChange?: (editorState: EditorState) => void;\n  onSerializedChange?: (editorSerializedState: SerializedEditorState) => void;\n  showLanguageSelect: boolean;\n}) {\n  const { t } = useEditorI18n();\n\n  const [floatingAnchorElem, setFloatingAnchorElem] =\n    useState<HTMLDivElement | null>(null);\n  const [isLinkEditMode, setIsLinkEditMode] = useState<boolean>(false);\n\n  const onRef = (_floatingAnchorElem: HTMLDivElement) => {\n    if (_floatingAnchorElem !== null) {\n      setFloatingAnchorElem(_floatingAnchorElem);\n    }\n  };\n\n  const AppExtension = useMemo(\n    () =>\n      defineExtension({\n        dependencies: [\n          RichTextExtension,\n          ImagesExtension,\n          HorizontalRuleExtension,\n          configExtension(ListExtension, { shouldPreserveNumbering: false }),\n          CheckListExtension,\n          configExtension(MarkdownShortcutsExtension, {\n            transformers: [\n              TABLE,\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          }),\n          AutoFocusExtension,\n          ClearEditorExtension,\n          DecoratorTextExtension,\n          HistoryExtension,\n          KeywordsExtension,\n          HashtagExtension,\n          DateTimeExtension,\n          configExtension(MaxLengthExtension, { disabled: false, maxLength }),\n          DragDropPasteExtension,\n          EmojisExtension,\n          configExtension(LinkExtension, {\n            validateUrl,\n            attributes: {\n              rel: \"noopener noreferrer\",\n              target: \"_blank\",\n            },\n          }),\n          AutoLinkExtension,\n          ClickableLinkExtension,\n          SelectionAlwaysOnDisplayExtension,\n        ],\n        // html: buildHTMLConfig(),\n        name: \"@shadcn-editor\",\n        namespace: \"Playground\",\n        nodes: [\n          OverflowNode,\n          TableNode,\n          TableCellNode,\n          TableRowNode,\n          CodeNode,\n          CodeHighlightNode,\n          MentionNode,\n          EmojiNode,\n          LayoutContainerNode,\n          LayoutItemNode,\n          TweetNode,\n          YouTubeNode,\n          AutocompleteNode,\n          SpecialTextNode,\n        ],\n        $initialEditorState(editor) {\n          if (editorSerializedState) {\n            editor.parseEditorState(editorSerializedState);\n          } else if (editorState) {\n            editor.setEditorState(editorState);\n          }\n        },\n        theme: editorTheme,\n      }),\n    [editorState, editorSerializedState],\n  );\n\n  const baseOptions = [\n    ParagraphPickerPlugin(t),\n    HeadingPickerPlugin({ n: 1, t }),\n    HeadingPickerPlugin({ n: 2, t }),\n    HeadingPickerPlugin({ n: 3, t }),\n    TablePickerPlugin(t),\n    CheckListPickerPlugin(t),\n    NumberedListPickerPlugin(t),\n    BulletedListPickerPlugin(t),\n    QuotePickerPlugin(t),\n    CodePickerPlugin(t),\n    DividerPickerPlugin(t),\n    EmbedsPickerPlugin({ embed: \"tweet\", t }),\n    EmbedsPickerPlugin({ embed: \"youtube-video\", t }),\n    ImagePickerPlugin(t),\n    ColumnsLayoutPickerPlugin(t),\n    DateTimePickerPlugin(t),\n    AlignmentPickerPlugin({ alignment: \"left\", t }),\n    AlignmentPickerPlugin({ alignment: \"center\", t }),\n    AlignmentPickerPlugin({ alignment: \"right\", t }),\n    AlignmentPickerPlugin({ alignment: \"justify\", t }),\n  ];\n\n  return (\n    <div className=\"bg-background flex h-full min-h-0 w-full flex-col overflow-hidden rounded-lg border shadow\">\n      <LexicalExtensionComposer extension={AppExtension} contentEditable={null}>\n        <TooltipProvider>\n          <div className=\"relative flex min-h-0 flex-1 flex-col\">\n            <ToolbarPlugin>\n              {({ blockType }) => (\n                <div className=\"bg-background sticky top-0 z-10 flex items-center gap-2 overflow-auto border-b p-1\">\n                  <HistoryToolbarPlugin />\n                  <Separator orientation=\"vertical\" className=\"h-6 my-auto\" />\n                  <BlockFormatDropDown>\n                    <FormatParagraph />\n                    <FormatHeading levels={[\"h1\", \"h2\", \"h3\"]} />\n                    <FormatNumberedList />\n                    <FormatBulletedList />\n                    <FormatCheckList />\n                    <FormatCodeBlock />\n                    <FormatQuote />\n                  </BlockFormatDropDown>\n                  {blockType === \"code\" ? (\n                    <CodeLanguageToolbarPlugin />\n                  ) : (\n                    <>\n                      <FontFamilyToolbarPlugin />\n                      <FontSizeToolbarPlugin />\n                      <Separator\n                        orientation=\"vertical\"\n                        className=\"h-6 my-auto\"\n                      />\n                      <FontFormatToolbarPlugin />\n                      <Separator\n                        orientation=\"vertical\"\n                        className=\"h-6 my-auto\"\n                      />\n                      <SubSuperToolbarPlugin />\n                      <LinkToolbarPlugin\n                        setIsLinkEditMode={setIsLinkEditMode}\n                      />\n                      <Separator\n                        orientation=\"vertical\"\n                        className=\"h-6 my-auto\"\n                      />\n                      <ClearFormattingToolbarPlugin />\n                      <Separator\n                        orientation=\"vertical\"\n                        className=\"h-6 my-auto\"\n                      />\n                      <FontColorToolbarPlugin />\n                      <FontBackgroundToolbarPlugin />\n                      <Separator\n                        orientation=\"vertical\"\n                        className=\"h-6 my-auto\"\n                      />\n                      <ElementFormatToolbarPlugin />\n                      <Separator\n                        orientation=\"vertical\"\n                        className=\"h-6 my-auto\"\n                      />\n                      <BlockInsertPlugin>\n                        <InsertHorizontalRule />\n                        <InsertImage />\n                        <InsertTable />\n                        <InsertColumnsLayout />\n                        <InsertEmbeds />\n                      </BlockInsertPlugin>\n                      {showLanguageSelect && <EditorLanguageSelect />}\n                    </>\n                  )}\n                </div>\n              )}\n            </ToolbarPlugin>\n            <div className=\"relative min-h-0 flex-1\">\n              <div className=\"h-full\">\n                <div className=\"h-full\" ref={onRef}>\n                  <ContentEditable\n                    placeholder={t(\"placeholder.editor\")}\n                    placeholderClassName=\"pl-14\"\n                    className=\"h-full pl-14\"\n                  />\n                </div>\n              </div>\n              <ComponentPickerMenuPlugin\n                baseOptions={baseOptions}\n                dynamicOptionsFn={({ queryString }) =>\n                  DynamicTablePickerPlugin({ queryString, t })\n                }\n              />\n              <EmojiPickerPlugin />\n              <AutoEmbedPlugin />\n              <MentionsPlugin />\n              <CodeHighlightPlugin />\n              <TablePlugin />\n              <TwitterPlugin />\n              <YouTubePlugin />\n              <TabFocusPlugin />\n              <TabIndentationPlugin />\n              <LayoutPlugin />\n              <FloatingLinkEditorPlugin\n                anchorElem={floatingAnchorElem}\n                isLinkEditMode={isLinkEditMode}\n                setIsLinkEditMode={setIsLinkEditMode}\n              />\n              <DraggableBlockPlugin\n                anchorElem={floatingAnchorElem}\n                baseOptions={baseOptions}\n                dynamicOptionsFn={({ queryString }) =>\n                  DynamicTablePickerPlugin({ queryString, t })\n                }\n              />\n              <CodeActionMenuPlugin anchorElem={floatingAnchorElem} />\n              <FloatingTextFormatToolbarPlugin\n                anchorElem={floatingAnchorElem}\n                setIsLinkEditMode={setIsLinkEditMode}\n              />\n              <AutoCompletePlugin />\n              <ContextMenuPlugin />\n              <SpecialTextPlugin />\n\n              <TypingPerfPlugin />\n            </div>\n            <ActionsPlugin>\n              <div className=\"clear-both flex items-center justify-between gap-2 overflow-auto border-t p-1\">\n                <div className=\"flex flex-1 justify-start text-xs text-gray-500\">\n                  <CounterCharacterPlugin\n                    charset=\"UTF-16\"\n                    maxLength={maxLength}\n                  />\n                </div>\n                <div className=\"flex flex-1 justify-end\">\n                  <SpeechToTextPlugin />\n                  <ShareContentPlugin />\n                  <ImportExportPlugin />\n                  <MarkdownTogglePlugin\n                    shouldPreserveNewLinesInMarkdown={true}\n                    transformers={[\n                      TABLE,\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                  />\n                  <EditModeTogglePlugin />\n                  <ClearEditorActionPlugin />\n                  <TreeViewPlugin />\n                </div>\n              </div>\n            </ActionsPlugin>\n          </div>\n\n          <OnChangePlugin\n            ignoreSelectionChange={true}\n            onChange={(editorState) => {\n              onChange?.(editorState);\n              onSerializedChange?.(editorState.toJSON());\n            }}\n          />\n        </TooltipProvider>\n      </LexicalExtensionComposer>\n    </div>\n  );\n}\n",
      "type": "registry:block"
    },
    {
      "path": "src/components/editor/i18n/en.ts",
      "content": "/**\n * Default (English) editor messages.\n *\n * The values are intentionally identical to the raw UI strings used upstream\n * so that when no i18n config is provided the editor renders exactly as before.\n * `en` is also the fallback dictionary for every locale.\n */\nexport const en = {\n  toolbar: {\n    paragraph: \"Paragraph\",\n    \"Heading 1\": \"Heading 1\",\n    \"Heading 2\": \"Heading 2\",\n    \"Heading 3\": \"Heading 3\",\n    number: \"Numbered List\",\n    bullet: \"Bulleted List\",\n    check: \"Check List\",\n    code: \"Code Block\",\n    quote: \"Quote\",\n    bold: \"Bold\",\n    italic: \"Italic\",\n    underline: \"Underline\",\n    strikethrough: \"Strikethrough\",\n    left: \"Left Align\",\n    center: \"Center Align\",\n    right: \"Right Align\",\n    justify: \"Justify Align\",\n    outdent: \"Outdent\",\n    indent: \"Indent\",\n    undo: \"Undo\",\n    redo: \"Redo\",\n    undoShortcutMac: \"Undo (⌘Z)\",\n    undoShortcut: \"Undo (Ctrl+Z)\",\n    redoShortcutMac: \"Redo (⇧⌘Z)\",\n    redoShortcut: \"Redo (Ctrl+Y)\",\n    decreaseFontSize: \"Decrease font size\",\n    increaseFontSize: \"Increase font size\",\n    toggleSubscript: \"Toggle subscript\",\n    toggleSuperscript: \"Toggle superscript\",\n    toggleLink: \"Toggle link\",\n    clearFormatting: \"Clear formatting\",\n    selectLanguage: \"Select Language\",\n    insert: \"Insert\",\n    language: \"Language\",\n    toggleBold: \"Toggle bold\",\n    toggleItalic: \"Toggle italic\",\n    toggleUnderline: \"Toggle underline\",\n    toggleStrikethrough: \"Toggle strikethrough\",\n    toggleCode: \"Toggle code\",\n  },\n  link: {\n    urlPlaceholder: \"https://example.com\",\n    cancel: \"Cancel\",\n    confirm: \"Confirm link\",\n    edit: \"Edit link\",\n    remove: \"Remove link\",\n  },\n  contextMenu: {\n    removeLink: \"Remove Link\",\n    cut: \"Cut\",\n    copy: \"Copy\",\n    paste: \"Paste\",\n    pastePlainText: \"Paste as Plain Text\",\n    deleteNode: \"Delete Node\",\n    pasteDenied: \"Not allowed to paste from clipboard.\",\n  },\n  action: {\n    shareTitle: \"Share\",\n    shareAria: \"Share Playground link to current editor state\",\n    shareContent: \"Share Content\",\n    shareCopied: \"URL copied to clipboard\",\n    shareCopyFailed: \"URL could not be copied to clipboard\",\n    markdownTitle: \"Convert From Markdown\",\n    markdownAria: \"Convert from markdown\",\n    editModeTitle: \"Read-Only Mode\",\n    editModeUnlockAria: \"Unlock read-only mode\",\n    editModeLockAria: \"Lock read-only mode\",\n    editViewOnly: \"View Only Mode\",\n    editEditMode: \"Edit Mode\",\n    speechTitle: \"Speech To Text\",\n    speechContent: \"Speech To Text\",\n    speechEnableAria: \"Enable speech to text\",\n    speechDisableAria: \"Disable speech to text\",\n    importTitle: \"Import\",\n    importAria: \"Import editor state from JSON\",\n    importContent: \"Import Content\",\n    exportTitle: \"Export\",\n    exportAria: \"Export editor state to JSON\",\n    exportContent: \"Export Content\",\n    exportFileName: \"Editor\",\n    clearTitle: \"Clear Editor\",\n    clearDescription: \"Are you sure you want to clear the editor?\",\n    cancel: \"Cancel\",\n    clear: \"Clear\",\n  },\n  counter: {\n    characters: \"characters\",\n    words: \"words\",\n    maxCharacters: \"/ {max} characters\",\n  },\n  imageDialog: {\n    url: \"Image URL\",\n    urlPlaceholder: \"i.e. https://source.unsplash.com/random\",\n    alt: \"Alt Text\",\n    altPlaceholder: \"Descriptive alternative text\",\n    randomAltPlaceholder: \"Random unsplash image\",\n    upload: \"Image Upload\",\n    confirm: \"Confirm\",\n    tabUrl: \"URL\",\n    tabFile: \"File\",\n  },\n  date: {\n    pick: \"Pick a date\",\n  },\n  embed: {\n    youtubeTitle: \"YouTube video\",\n    tweetLoading: \"Loading...\",\n  },\n  image: {\n    broken: \"Broken image\",\n    altText: \"alternative text\",\n  },\n  misc: {\n    copy: \"copy\",\n    filterBlocks: \"Filter blocks...\",\n    clickAddBelow: \"Click to add below (Alt/Ctrl: add above)\",\n    insertImage: \"Insert Image\",\n    insertTable: \"Insert Table\",\n    insertColumns: \"Insert Columns Layout\",\n    treeView: \"Tree View\",\n    noResults: \"No results found.\",\n  },\n  picker: {\n    paragraph: \"Paragraph\",\n    \"Heading 1\": \"Heading 1\",\n    \"Heading 2\": \"Heading 2\",\n    \"Heading 3\": \"Heading 3\",\n    quote: \"Quote\",\n    bullet: \"Bulleted List\",\n    check: \"Check List\",\n    number: \"Numbered List\",\n    code: \"Code Block\",\n    image: \"Image\",\n    table: \"Table\",\n    columns: \"Columns Layout\",\n    divider: \"Horizontal Rule\",\n    embeds: \"Embed\",\n    dateTime: \"Date & Time\",\n    alignLeft: \"Align Left\",\n    alignCenter: \"Align Center\",\n    alignRight: \"Align Right\",\n    alignJustify: \"Align Justify\",\n  },\n  color: {\n    noColor: \"No color selected\",\n    currentColor: \"Current color\",\n    hexValue: \"Hex color value\",\n    alphaPercentage: \"Alpha transparency percentage\",\n    redComponent: \"Red color component (0-255)\",\n    greenComponent: \"Green color component (0-255)\",\n    blueComponent: \"Blue color component (0-255)\",\n    hueDegree: \"Hue degree (0-360)\",\n    saturationPercentage: \"Saturation percentage (0-100)\",\n    lightnessPercentage: \"Lightness percentage (0-100)\",\n    brightnessPercentage: \"Brightness percentage (0-100)\",\n  },\n  placeholder: {\n    editor: \"Press / for commands...\",\n  },\n};\n\n/** Shape of every locale dictionary (derived from `en`). */\nexport type EditorMessages = typeof en;\n",
      "type": "registry:component"
    },
    {
      "path": "src/components/editor/i18n/zh-CN.ts",
      "content": "/**\n * Simplified Chinese editor messages.\n *\n * The structure must mirror `en` exactly (see `EditorMessages` in `en.ts`).\n * Missing keys at runtime fall back to English.\n */\nimport type { EditorMessages } from \"./en\";\n\nexport const zhCN: EditorMessages = {\n  toolbar: {\n    paragraph: \"正文\",\n    \"Heading 1\": \"标题 1\",\n    \"Heading 2\": \"标题 2\",\n    \"Heading 3\": \"标题 3\",\n    number: \"有序列表\",\n    bullet: \"无序列表\",\n    check: \"待办列表\",\n    code: \"代码块\",\n    quote: \"引用\",\n    bold: \"加粗\",\n    italic: \"斜体\",\n    underline: \"下划线\",\n    strikethrough: \"删除线\",\n    left: \"左对齐\",\n    center: \"居中对齐\",\n    right: \"右对齐\",\n    justify: \"两端对齐\",\n    outdent: \"减少缩进\",\n    indent: \"增加缩进\",\n    undo: \"撤销\",\n    redo: \"重做\",\n    undoShortcutMac: \"撤销 (⌘Z)\",\n    undoShortcut: \"撤销 (Ctrl+Z)\",\n    redoShortcutMac: \"重做 (⇧⌘Z)\",\n    redoShortcut: \"重做 (Ctrl+Y)\",\n    decreaseFontSize: \"减小字号\",\n    increaseFontSize: \"增大字号\",\n    toggleSubscript: \"切换下标\",\n    toggleSuperscript: \"切换上标\",\n    toggleLink: \"切换链接\",\n    clearFormatting: \"清除格式\",\n    selectLanguage: \"选择语言\",\n    insert: \"插入\",\n    language: \"语言\",\n    toggleBold: \"切换加粗\",\n    toggleItalic: \"切换斜体\",\n    toggleUnderline: \"切换下划线\",\n    toggleStrikethrough: \"切换删除线\",\n    toggleCode: \"切换代码\",\n  },\n  link: {\n    urlPlaceholder: \"https://example.com\",\n    cancel: \"取消\",\n    confirm: \"确认链接\",\n    edit: \"编辑链接\",\n    remove: \"移除链接\",\n  },\n  contextMenu: {\n    removeLink: \"移除链接\",\n    cut: \"剪切\",\n    copy: \"复制\",\n    paste: \"粘贴\",\n    pastePlainText: \"粘贴为纯文本\",\n    deleteNode: \"删除节点\",\n    pasteDenied: \"不允许从剪贴板粘贴。\",\n  },\n  action: {\n    shareTitle: \"分享\",\n    shareAria: \"分享当前编辑器状态的链接\",\n    shareContent: \"分享内容\",\n    shareCopied: \"链接已复制到剪贴板\",\n    shareCopyFailed: \"无法将链接复制到剪贴板\",\n    markdownTitle: \"从 Markdown 转换\",\n    markdownAria: \"从 Markdown 转换\",\n    editModeTitle: \"只读模式\",\n    editModeUnlockAria: \"解锁只读模式\",\n    editModeLockAria: \"锁定只读模式\",\n    editViewOnly: \"仅查看模式\",\n    editEditMode: \"编辑模式\",\n    speechTitle: \"语音转文字\",\n    speechContent: \"语音转文字\",\n    speechEnableAria: \"启用语音转文字\",\n    speechDisableAria: \"禁用语音转文字\",\n    importTitle: \"导入\",\n    importAria: \"从 JSON 导入编辑器状态\",\n    importContent: \"导入内容\",\n    exportTitle: \"导出\",\n    exportAria: \"导出编辑器状态为 JSON\",\n    exportContent: \"导出内容\",\n    exportFileName: \"编辑器\",\n    clearTitle: \"清空编辑器\",\n    clearDescription: \"确定要清空编辑器吗？\",\n    cancel: \"取消\",\n    clear: \"清空\",\n  },\n  counter: {\n    characters: \"字符\",\n    words: \"词\",\n    maxCharacters: \"/ {max} 字符\",\n  },\n  imageDialog: {\n    url: \"图片地址\",\n    urlPlaceholder: \"例如 https://source.unsplash.com/random\",\n    alt: \"替代文本\",\n    altPlaceholder: \"描述性替代文本\",\n    randomAltPlaceholder: \"随机 unsplash 图片\",\n    upload: \"图片上传\",\n    confirm: \"确认\",\n    tabUrl: \"URL\",\n    tabFile: \"文件\",\n  },\n  date: {\n    pick: \"选择日期\",\n  },\n  embed: {\n    youtubeTitle: \"YouTube 视频\",\n    tweetLoading: \"加载中...\",\n  },\n  image: {\n    broken: \"图片加载失败\",\n    altText: \"替代文本\",\n  },\n  misc: {\n    copy: \"复制\",\n    filterBlocks: \"筛选块...\",\n    clickAddBelow: \"点击下方添加（Alt/Ctrl：上方添加）\",\n    insertImage: \"插入图片\",\n    insertTable: \"插入表格\",\n    insertColumns: \"插入分栏布局\",\n    treeView: \"树状视图\",\n    noResults: \"未找到结果。\",\n  },\n  picker: {\n    paragraph: \"正文\",\n    \"Heading 1\": \"标题 1\",\n    \"Heading 2\": \"标题 2\",\n    \"Heading 3\": \"标题 3\",\n    quote: \"引用\",\n    bullet: \"无序列表\",\n    check: \"待办列表\",\n    number: \"有序列表\",\n    code: \"代码块\",\n    image: \"图片\",\n    table: \"表格\",\n    columns: \"分栏布局\",\n    divider: \"分割线\",\n    embeds: \"嵌入\",\n    dateTime: \"日期与时间\",\n    alignLeft: \"左对齐\",\n    alignCenter: \"居中对齐\",\n    alignRight: \"右对齐\",\n    alignJustify: \"两端对齐\",\n  },\n  color: {\n    noColor: \"未选择颜色\",\n    currentColor: \"当前颜色\",\n    hexValue: \"十六进制颜色值\",\n    alphaPercentage: \"透明度百分比\",\n    redComponent: \"红色分量 (0-255)\",\n    greenComponent: \"绿色分量 (0-255)\",\n    blueComponent: \"蓝色分量 (0-255)\",\n    hueDegree: \"色相 (0-360)\",\n    saturationPercentage: \"饱和度 (0-100)\",\n    lightnessPercentage: \"亮度 (0-100)\",\n    brightnessPercentage: \"明度 (0-100)\",\n  },\n  placeholder: {\n    editor: \"输入 / 以打开命令...\",\n  },\n};\n",
      "type": "registry:component"
    },
    {
      "path": "src/components/editor/i18n/index.tsx",
      "content": "import {\n  createContext,\n  useCallback,\n  useContext,\n  useMemo,\n  useState,\n  type ReactNode,\n} from \"react\";\n\nimport { en, type EditorMessages } from \"./en\";\nimport { zhCN } from \"./zh-CN\";\n\n/** Built-in locale dictionaries. */\nconst LOCALES: Record<string, EditorMessages> = {\n  en,\n  \"zh-CN\": zhCN,\n};\n\n/** Normalize common application locale formats to the built-in dictionaries. */\nexport function normalizeEditorLocale(locale: string): string {\n  const normalized = locale.replace(\"_\", \"-\").toLowerCase();\n\n  if (normalized === \"zh\" || normalized.startsWith(\"zh-\")) return \"zh-CN\";\n  if (normalized === \"en\" || normalized.startsWith(\"en-\")) return \"en\";\n\n  return locale;\n}\n\n/** A translation function: resolves a dotted key and interpolates `{var}`. */\nexport type TranslateFn = (\n  key: string,\n  vars?: Record<string, string | number>,\n) => string;\n\n/** Resolve a dotted path (e.g. `\"toolbar.bold\"`) inside a message object. */\nfunction lookup(messages: EditorMessages, key: string): string | undefined {\n  let node: unknown = messages;\n  for (const part of key.split(\".\")) {\n    if (typeof node !== \"object\" || node === null) return undefined;\n    node = (node as Record<string, unknown>)[part];\n  }\n  return typeof node === \"string\" ? node : undefined;\n}\n\nfunction interpolate(\n  template: string | undefined,\n  vars?: Record<string, string | number>,\n): string {\n  if (template === undefined) return \"\";\n  if (!vars) return template;\n  return template.replace(/\\{(\\w+)\\}/g, (match, name: string) =>\n    name in vars ? String(vars[name]) : match,\n  );\n}\n\ninterface EditorI18nContextValue {\n  t: TranslateFn;\n  locale: string;\n  setLocale: (locale: string) => void;\n}\n\nconst EditorI18nContext = createContext<EditorI18nContextValue>({\n  t: (key, vars) => interpolate(lookup(en, key), vars) || key,\n  locale: \"zh-CN\",\n  setLocale: () => {},\n});\n\nexport function EditorI18nProvider({\n  locale: controlledLocale,\n  onLocaleChange,\n  messages,\n  t: injectedT,\n  children,\n}: {\n  /** Active locale key, e.g. `\"en\"` or `\"zh-CN\"`. */\n  locale?: string;\n  /** Called when the in-editor language selector requests a locale change. */\n  onLocaleChange?: (locale: string) => void;\n  /** Per-key overrides merged on top of the built-in locale dictionary. */\n  messages?: Partial<EditorMessages>;\n  /** Optional translator injection (e.g. reuse your app's existing i18n). */\n  t?: TranslateFn;\n  children: ReactNode;\n}) {\n  // `locale` is a controlled prop: when provided it takes precedence and\n  // changes react immediately. When omitted it falls back to internal state,\n  // which `setLocale` (from the context) can update.\n  const [internalLocale, setInternalLocale] = useState(() =>\n    normalizeEditorLocale(controlledLocale ?? \"zh-CN\"),\n  );\n  const locale = normalizeEditorLocale(controlledLocale ?? internalLocale);\n  const setLocale = useCallback(\n    (nextLocale: string) => {\n      const normalized = normalizeEditorLocale(nextLocale);\n\n      if (controlledLocale === undefined) setInternalLocale(normalized);\n      onLocaleChange?.(normalized);\n    },\n    [controlledLocale, onLocaleChange],\n  );\n\n  const t = useMemo<TranslateFn>(() => {\n    if (injectedT) return injectedT;\n\n    const base = LOCALES[locale] ?? en;\n    const current: EditorMessages = messages\n      ? (deepMerge(base, messages) as EditorMessages)\n      : base;\n\n    return (key, vars) => {\n      const value =\n        lookup(current, key) ?? lookup(en, key) ?? lookup(zhCN, key);\n      // Fall back to the key itself when nothing is found, so a missing\n      // translation never renders empty text.\n      return interpolate(value, vars) || key;\n    };\n  }, [locale, messages, injectedT]);\n\n  const value = useMemo<EditorI18nContextValue>(\n    () => ({ t, locale, setLocale }),\n    [t, locale, setLocale],\n  );\n\n  return (\n    <EditorI18nContext.Provider value={value}>\n      {children}\n    </EditorI18nContext.Provider>\n  );\n}\n\n/** Access the editor translator and current locale. */\nexport function useEditorI18n(): EditorI18nContextValue {\n  return useContext(EditorI18nContext);\n}\n\nfunction deepMerge<T, U>(base: T, overrides: U): T | U {\n  if (\n    typeof base === \"object\" &&\n    base !== null &&\n    typeof overrides === \"object\" &&\n    overrides !== null\n  ) {\n    const out: Record<string, unknown> = {\n      ...(base as Record<string, unknown>),\n    };\n    for (const [key, value] of Object.entries(\n      overrides as Record<string, unknown>,\n    )) {\n      out[key] = deepMerge((base as Record<string, unknown>)[key], value);\n    }\n    return out as T | U;\n  }\n  return overrides as T | U;\n}\n\nexport const SUPPORTED_LOCALES: Array<{ value: string; label: string }> = [\n  { value: \"zh-CN\", label: \"中文\" },\n  { value: \"en\", label: \"English\" },\n];\n\nexport { en, zhCN };\nexport type { EditorMessages };\n",
      "type": "registry:component"
    },
    {
      "path": "src/components/editor/context/toolbar-context.tsx",
      "content": "import { 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    <Context.Provider\n      value={{\n        activeEditor,\n        $updateToolbar,\n        blockType,\n        setBlockType,\n        showModal,\n      }}\n    >\n      {children}\n    </Context.Provider>\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<T extends (...args: never[]) => void>(\n  fn: T,\n  ms: number,\n  maxWait?: number,\n) {\n  const funcRef = useRef<T | null>(null);\n  funcRef.current = fn;\n\n  return useMemo(\n    () =>\n      debounce(\n        (...args: Parameters<T>) => {\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    closeOnClickOutside: boolean;\n    content: JSX.Element;\n    title: string;\n  }>(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      <Dialog open={true} onOpenChange={onClose}>\n        <DialogContent>\n          <DialogHeader>\n            <DialogTitle>{title}</DialogTitle>\n          </DialogHeader>\n          {content}\n        </DialogContent>\n      </Dialog>\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<typeof setTimeout> {\n  const timer = useRef<ReturnType<typeof setTimeout> | 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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\ninterface Props {\n  editor: LexicalEditor;\n  getCodeDOMNode: () => HTMLElement | null;\n}\n\nexport function CopyButton({ editor, getCodeDOMNode }: Props) {\n  const [isCopyCompleted, setCopyCompleted] = useState<boolean>(false);\n  const { t } = useEditorI18n();\n\n  const removeSuccessIcon = useDebounce(() => {\n    setCopyCompleted(false);\n  }, 1000);\n\n  async function handleClick(): Promise<void> {\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    <button\n      className=\"text-foreground/50 flex shrink-0 cursor-pointer items-center rounded border border-transparent bg-none p-1 uppercase\"\n      onClick={handleClick}\n      aria-label={t(\"misc.copy\")}\n    >\n      {isCopyCompleted ? (\n        <CircleCheckIcon className=\"size-4\" />\n      ) : (\n        <CopyIcon className=\"size-4\" />\n      )}\n    </button>\n  );\n}\n",
      "type": "registry:component"
    },
    {
      "path": "src/components/editor/editor-ui/language-select.tsx",
      "content": "import {\n  SUPPORTED_LOCALES,\n  useEditorI18n,\n} from \"@/components/editor/i18n\";\nimport {\n  Select,\n  SelectContent,\n  SelectItem,\n  SelectTrigger,\n  SelectValue,\n} from \"@/components/ui/select\";\n\n/**\n * In-editor language switcher.\n *\n * Reads the current locale from `useEditorI18n()` and updates it via\n * `setLocale`, so it works as the single source of truth when the editor is\n * used without a controlled `locale` prop. Drop it into any toolbar.\n */\nexport function EditorLanguageSelect() {\n  const { locale, setLocale, t } = useEditorI18n();\n\n  return (\n    <Select\n      value={locale}\n      onValueChange={(value) => {\n        if (typeof value === \"string\" && value !== locale) {\n          setLocale(value);\n        }\n      }}\n    >\n      <SelectTrigger\n        size=\"sm\"\n        className=\"h-7 gap-1 border-none bg-transparent px-2 text-sm shadow-none hover:bg-muted dark:bg-transparent dark:hover:bg-muted\"\n        onMouseDown={(e) => e.stopPropagation()}\n      >\n        <SelectValue placeholder={t(\"toolbar.language\")}>\n          {SUPPORTED_LOCALES.find((item) => item.value === locale)?.label ??\n            locale}\n        </SelectValue>\n      </SelectTrigger>\n      <SelectContent onCloseAutoFocus={(event) => event.preventDefault()}>\n        {SUPPORTED_LOCALES.map((item) => (\n          <SelectItem key={item.value} value={item.value}>\n            {item.label}\n          </SelectItem>\n        ))}\n      </SelectContent>\n    </Select>\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\";\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<T> = React.Ref<T> | 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<T>(ref: PossibleRef<T>, 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<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\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<T>(...refs: PossibleRef<T>[]): React.RefCallback<T> {\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<T = InputValue> extends Omit<\n  React.InputHTMLAttributes<HTMLInputElement>,\n  \"value\" | \"checked\" | \"onReset\"\n> {\n  value?: T;\n  checked?: boolean;\n  control: HTMLElement | null;\n  bubbles?: boolean;\n}\n\nfunction VisuallyHiddenInput<T = InputValue>(\n  props: VisuallyHiddenInputProps<T>,\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<HTMLInputElement>(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<React.CSSProperties>(() => {\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    <input\n      type={type}\n      {...inputProps}\n      ref={inputRef}\n      aria-hidden={isCheckInput}\n      tabIndex={-1}\n      defaultChecked={isCheckInput ? checked : undefined}\n      style={composedStyle}\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<Direction | undefined>(undefined);\n\nfunction useDirection(dirProp?: Direction): Direction {\n  const contextDir = React.useContext(DirectionContext);\n  return dirProp ?? contextDir ?? \"ltr\";\n}\n\nfunction useLazyRef<T>(fn: () => T) {\n  const ref = React.useRef<T | null>(null);\n\n  if (ref.current === null) {\n    ref.current = fn();\n  }\n\n  return ref as React.RefObject<T>;\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<Set<() => void>>,\n  stateRef: React.RefObject<ColorPickerStoreState>,\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<U>(\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<ColorPickerStore | null>(\n  null,\n);\nconst ColorPickerContext = React.createContext<ColorPickerContextValue | null>(\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<React.ComponentProps<\"div\">, \"onValueChange\">,\n    Pick<\n      React.ComponentProps<typeof Popover>,\n      \"defaultOpen\" | \"open\" | \"modal\"\n    > {\n  value?: string;\n  defaultValue?: string;\n  onValueChange?: (value: string) => void;\n  onOpenChange?: (open: boolean) => void;\n  dir?: Direction;\n  format?: ColorFormat;\n  defaultFormat?: ColorFormat;\n  onFormatChange?: (format: ColorFormat) => void;\n  name?: string;\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<ColorPickerStoreCallbacks>(\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    <ColorPickerStoreContext.Provider value={store}>\n      <ColorPickerRootImpl\n        {...rootProps}\n        value={valueProp}\n        defaultOpen={defaultOpen}\n        open={openProp}\n        onOpenChange={onOpenChange}\n        name={name}\n        disabled={disabled}\n        inline={inline}\n        readOnly={readOnly}\n        required={required}\n      />\n    </ColorPickerStoreContext.Provider>\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    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<HTMLDivElement | null>(\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<ColorPickerContextValue>(\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  if (inline) {\n    return (\n      <ColorPickerContext.Provider value={contextValue}>\n        <div {...rootProps} ref={composedRef} />\n        {isFormControl && (\n          <VisuallyHiddenInput\n            type=\"hidden\"\n            control={formTrigger}\n            name={name}\n            value={value}\n            disabled={disabled}\n            readOnly={readOnly}\n            required={required}\n          />\n        )}\n      </ColorPickerContext.Provider>\n    );\n  }\n\n  return (\n    <ColorPickerContext.Provider value={contextValue}>\n      <Popover\n        defaultOpen={defaultOpen}\n        open={open}\n        onOpenChange={onPopoverOpenChange}\n        modal={modal}\n      >\n        <div {...rootProps} ref={composedRef} />\n        {isFormControl && (\n          <VisuallyHiddenInput\n            type=\"hidden\"\n            control={formTrigger}\n            name={name}\n            value={value}\n            disabled={disabled}\n            readOnly={readOnly}\n            required={required}\n          />\n        )}\n      </Popover>\n    </ColorPickerContext.Provider>\n  );\n}\n\ninterface ColorPickerTriggerProps extends React.ComponentProps<\n  typeof PopoverTrigger\n> {}\n\nfunction ColorPickerTrigger(props: ColorPickerTriggerProps) {\n  const { children, ...triggerProps } = props;\n  const context = useColorPickerContext(\"ColorPickerTrigger\");\n\n  return (\n    <PopoverTrigger\n      data-slot=\"color-picker-trigger\"\n      disabled={context.disabled}\n      asChild\n      {...triggerProps}\n    >\n      {children ?? <Button />}\n    </PopoverTrigger>\n  );\n}\n\ninterface ColorPickerContentProps extends React.ComponentProps<\n  typeof PopoverContent\n> {}\n\nfunction ColorPickerContent(props: ColorPickerContentProps) {\n  const { className, children, ...popoverContentProps } = props;\n  const context = useColorPickerContext(\"ColorPickerContent\");\n\n  if (context.inline) {\n    return (\n      <div\n        data-slot=\"color-picker-content\"\n        className={cn(\"flex w-[340px] flex-col gap-4 p-4\", className)}\n      >\n        {children}\n      </div>\n    );\n  }\n\n  return (\n    <PopoverContent\n      data-slot=\"color-picker-content\"\n      {...popoverContentProps}\n      className={cn(\"flex w-[340px] flex-col gap-4 p-4\", className)}\n    >\n      {children}\n    </PopoverContent>\n  );\n}\n\ninterface ColorPickerAreaProps extends React.ComponentProps<\"div\"> {}\n\nfunction ColorPickerArea(props: ColorPickerAreaProps) {\n  const { 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<HTMLDivElement>(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  return (\n    <div\n      data-slot=\"color-picker-area\"\n      {...areaProps}\n      className={cn(\n        \"relative h-40 w-full cursor-crosshair touch-none rounded-sm border\",\n        context.disabled && \"pointer-events-none opacity-50\",\n        className,\n      )}\n      ref={composedRef}\n      onPointerDown={onPointerDown}\n      onPointerMove={onPointerMove}\n      onPointerUp={onPointerUp}\n    >\n      <div className=\"absolute inset-0 overflow-hidden rounded-sm\">\n        <div\n          className=\"absolute inset-0\"\n          style={{\n            backgroundColor: `rgb(${backgroundHue.r}, ${backgroundHue.g}, ${backgroundHue.b})`,\n          }}\n        />\n        <div\n          className=\"absolute inset-0\"\n          style={{\n            background: \"linear-gradient(to right, #fff, transparent)\",\n          }}\n        />\n        <div\n          className=\"absolute inset-0\"\n          style={{\n            background: \"linear-gradient(to bottom, transparent, #000)\",\n          }}\n        />\n      </div>\n      <div\n        className=\"absolute size-3 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 border-white shadow-sm\"\n        style={{\n          left: `${hsv?.s ?? 0}%`,\n          top: `${100 - (hsv?.v ?? 0)}%`,\n        }}\n      />\n    </div>\n  );\n}\n\ninterface ColorPickerHueSliderProps extends React.ComponentProps<\n  typeof Slider\n> {}\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    (value: number | readonly number[]) => {\n      const hue = Array.isArray(value) ? (value[0] ?? 0) : (value as number);\n      const newHsv: HSVColorValue = {\n        h: hue,\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    <Slider\n      data-slot=\"color-picker-hue-slider\"\n      {...sliderProps}\n      max={360}\n      step={1}\n      className={cn(\n        \"[&_[data-slot='slider-track'][data-horizontal]]:h-3\",\n        \"[&_[data-slot='slider-track']]:bg-[linear-gradient(to_right,#ff0000_0%,#ffff00_16.66%,#00ff00_33.33%,#00ffff_50%,#0000ff_66.66%,#ff00ff_83.33%,#ff0000_100%)]\",\n        \"[&_[data-slot='slider-range']]:bg-transparent\",\n        \"[&_[data-slot='slider-thumb']]:border-primary/50 [&_[data-slot='slider-thumb']]:bg-background\",\n        className,\n      )}\n      value={[hsv?.h ?? 0]}\n      onValueChange={onValueChange}\n      disabled={context.disabled}\n    />\n  );\n}\n\ninterface ColorPickerAlphaSliderProps extends React.ComponentProps<\n  typeof Slider\n> {}\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    (value: number | readonly number[]) => {\n      const raw = Array.isArray(value) ? (value[0] ?? 0) : (value as number);\n      const alpha = raw / 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    <Slider\n      data-slot=\"color-picker-alpha-slider\"\n      {...sliderProps}\n      max={100}\n      step={1}\n      disabled={context.disabled}\n      style={{ \"--alpha-color\": gradientColor } as React.CSSProperties}\n      className={cn(\n        \"[&_[data-slot='slider-track'][data-horizontal]]:h-3\",\n        // checkerboard: each layer includes its own position/size to avoid ordering issues\n        \"[&_[data-slot='slider-track']]:[background:linear-gradient(45deg,var(--border)_25%,transparent_25%)_0_0_/_8px_8px,linear-gradient(-45deg,var(--border)_25%,transparent_25%)_0_4px_/_8px_8px,linear-gradient(45deg,transparent_75%,var(--border)_75%)_4px_-4px_/_8px_8px,linear-gradient(-45deg,transparent_75%,var(--border)_75%)_-4px_0px_/_8px_8px]\",\n        // color gradient overlay via ::before on the track\n        \"[&_[data-slot='slider-track']]:before:absolute [&_[data-slot='slider-track']]:before:inset-0 [&_[data-slot='slider-track']]:before:rounded-full [&_[data-slot='slider-track']]:before:content-['']\",\n        \"[&_[data-slot='slider-track']]:before:[background:linear-gradient(to_right,transparent,var(--alpha-color))]\",\n        \"[&_[data-slot='slider-range']]:bg-transparent\",\n        \"[&_[data-slot='slider-thumb']]:border-primary/50 [&_[data-slot='slider-thumb']]:bg-background\",\n        className,\n      )}\n      value={[Math.round((color?.a ?? 1) * 100)]}\n      onValueChange={onValueChange}\n    />\n  );\n}\n\ninterface ColorPickerSwatchProps extends React.ComponentProps<\"div\"> {}\n\nfunction ColorPickerSwatch(props: ColorPickerSwatchProps) {\n  const { 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  return (\n    <div\n      role=\"img\"\n      aria-label={ariaLabel}\n      data-slot=\"color-picker-swatch\"\n      {...swatchProps}\n      className={cn(\n        \"box-border rounded-sm border shadow-sm\",\n        context.disabled && \"opacity-50\",\n        className,\n      )}\n      style={{\n        ...backgroundStyle,\n        forcedColorAdjust: \"none\",\n      }}\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    <Button\n      data-slot=\"color-picker-eye-dropper\"\n      {...buttonProps}\n      variant=\"outline\"\n      size={buttonSize}\n      onClick={onEyeDropper}\n      disabled={context.disabled}\n    >\n      {children ?? <PipetteIcon />}\n    </Button>\n  );\n}\n\ninterface ColorPickerFormatSelectProps\n  extends\n    Omit<React.ComponentProps<typeof Select>, \"value\" | \"onValueChange\">,\n    Pick<React.ComponentProps<typeof SelectTrigger>, \"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: unknown) => {\n      if (\n        typeof value === \"string\" &&\n        colorFormats.includes(value as ColorFormat)\n      ) {\n        store.setFormat(value as ColorFormat);\n      }\n    },\n    [store],\n  );\n\n  return (\n    <Select\n      data-slot=\"color-picker-format-select\"\n      {...selectProps}\n      value={format}\n      onValueChange={onFormatChange}\n      disabled={context.disabled}\n    >\n      <SelectTrigger\n        data-slot=\"color-picker-format-select-trigger\"\n        size={size ?? \"sm\"}\n        className={cn(className)}\n      >\n        <SelectValue />\n      </SelectTrigger>\n      <SelectContent>\n        {colorFormats.map((format) => (\n          <SelectItem key={format} value={format}>\n            {format.toUpperCase()}\n          </SelectItem>\n        ))}\n      </SelectContent>\n    </Select>\n  );\n}\n\ninterface ColorPickerInputProps extends Omit<\n  React.ComponentProps<typeof Input>,\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      <HexInput\n        color={color}\n        onColorChange={onColorChange}\n        context={context}\n        {...props}\n      />\n    );\n  }\n\n  if (format === \"rgb\") {\n    return (\n      <RgbInput\n        color={color}\n        onColorChange={onColorChange}\n        context={context}\n        {...props}\n      />\n    );\n  }\n\n  if (format === \"hsl\") {\n    return (\n      <HslInput\n        color={color}\n        onColorChange={onColorChange}\n        context={context}\n        {...props}\n      />\n    );\n  }\n\n  if (format === \"hsb\") {\n    return (\n      <HsbInput\n        hsv={hsv}\n        onColorChange={onColorChange}\n        context={context}\n        {...props}\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<typeof Input>,\n    VariantProps<typeof inputGroupItemVariants> {}\n\nfunction InputGroupItem({\n  className,\n  position,\n  ...props\n}: InputGroupItemProps) {\n  return (\n    <Input\n      data-slot=\"color-picker-input\"\n      className={cn(inputGroupItemVariants({ position }), className)}\n      {...props}\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<HTMLInputElement>) => {\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<HTMLInputElement>) => {\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      <InputGroupItem\n        aria-label=\"Hex color value\"\n        position=\"isolated\"\n        {...inputProps}\n        placeholder=\"#000000\"\n        className={cn(\"font-mono\", className)}\n        value={hexValue}\n        onChange={onHexChange}\n        disabled={context.disabled}\n      />\n    );\n  }\n\n  return (\n    <div\n      data-slot=\"color-picker-input-wrapper\"\n      className={cn(\"flex items-center\", className)}\n    >\n      <InputGroupItem\n        aria-label=\"Hex color value\"\n        position=\"first\"\n        {...inputProps}\n        placeholder=\"#000000\"\n        className=\"flex-1 font-mono\"\n        value={hexValue}\n        onChange={onHexChange}\n        disabled={context.disabled}\n      />\n      <InputGroupItem\n        aria-label=\"Alpha transparency percentage\"\n        position=\"last\"\n        {...inputProps}\n        placeholder=\"100\"\n        inputMode=\"numeric\"\n        pattern=\"[0-9]*\"\n        min=\"0\"\n        max=\"100\"\n        className=\"w-14\"\n        value={alphaValue}\n        onChange={onAlphaChange}\n        disabled={context.disabled}\n      />\n    </div>\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<HTMLInputElement>) => {\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    <div\n      data-slot=\"color-picker-input-wrapper\"\n      className={cn(\"flex items-center\", className)}\n    >\n      <InputGroupItem\n        aria-label=\"Red color component (0-255)\"\n        position=\"first\"\n        {...inputProps}\n        placeholder=\"0\"\n        inputMode=\"numeric\"\n        pattern=\"[0-9]*\"\n        min=\"0\"\n        max=\"255\"\n        className=\"w-14\"\n        value={rValue}\n        onChange={onChannelChange(\"r\", 255)}\n        disabled={context.disabled}\n      />\n      <InputGroupItem\n        aria-label=\"Green color component (0-255)\"\n        position=\"middle\"\n        {...inputProps}\n        placeholder=\"0\"\n        inputMode=\"numeric\"\n        pattern=\"[0-9]*\"\n        min=\"0\"\n        max=\"255\"\n        className=\"w-14\"\n        value={gValue}\n        onChange={onChannelChange(\"g\", 255)}\n        disabled={context.disabled}\n      />\n      <InputGroupItem\n        aria-label=\"Blue color component (0-255)\"\n        position={withoutAlpha ? \"last\" : \"middle\"}\n        {...inputProps}\n        placeholder=\"0\"\n        inputMode=\"numeric\"\n        pattern=\"[0-9]*\"\n        min=\"0\"\n        max=\"255\"\n        className=\"w-14\"\n        value={bValue}\n        onChange={onChannelChange(\"b\", 255)}\n        disabled={context.disabled}\n      />\n      {!withoutAlpha && (\n        <InputGroupItem\n          aria-label=\"Alpha transparency percentage\"\n          position=\"last\"\n          {...inputProps}\n          placeholder=\"100\"\n          inputMode=\"numeric\"\n          pattern=\"[0-9]*\"\n          min=\"0\"\n          max=\"100\"\n          className=\"w-14\"\n          value={alphaValue}\n          onChange={onChannelChange(\"a\", 100, true)}\n          disabled={context.disabled}\n        />\n      )}\n    </div>\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<HTMLInputElement>) => {\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<HTMLInputElement>) => {\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    <div\n      data-slot=\"color-picker-input-wrapper\"\n      className={cn(\"flex items-center\", className)}\n    >\n      <InputGroupItem\n        aria-label=\"Hue degree (0-360)\"\n        position=\"first\"\n        {...inputProps}\n        placeholder=\"0\"\n        inputMode=\"numeric\"\n        pattern=\"[0-9]*\"\n        min=\"0\"\n        max=\"360\"\n        className=\"w-14\"\n        value={hsl.h}\n        onChange={onHslChannelChange(\"h\", 360)}\n        disabled={context.disabled}\n      />\n      <InputGroupItem\n        aria-label=\"Saturation percentage (0-100)\"\n        position=\"middle\"\n        {...inputProps}\n        placeholder=\"0\"\n        inputMode=\"numeric\"\n        pattern=\"[0-9]*\"\n        min=\"0\"\n        max=\"100\"\n        className=\"w-14\"\n        value={hsl.s}\n        onChange={onHslChannelChange(\"s\", 100)}\n        disabled={context.disabled}\n      />\n      <InputGroupItem\n        aria-label=\"Lightness percentage (0-100)\"\n        position={withoutAlpha ? \"last\" : \"middle\"}\n        {...inputProps}\n        placeholder=\"0\"\n        inputMode=\"numeric\"\n        pattern=\"[0-9]*\"\n        min=\"0\"\n        max=\"100\"\n        className=\"w-14\"\n        value={hsl.l}\n        onChange={onHslChannelChange(\"l\", 100)}\n        disabled={context.disabled}\n      />\n      {!withoutAlpha && (\n        <InputGroupItem\n          aria-label=\"Alpha transparency percentage\"\n          position=\"last\"\n          {...inputProps}\n          placeholder=\"100\"\n          inputMode=\"numeric\"\n          pattern=\"[0-9]*\"\n          min=\"0\"\n          max=\"100\"\n          className=\"w-14\"\n          value={alphaValue}\n          onChange={onAlphaChange}\n          disabled={context.disabled}\n        />\n      )}\n    </div>\n  );\n}\n\ninterface HsbInputProps extends Omit<FormatInputProps, \"color\"> {\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<HTMLInputElement>) => {\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<HTMLInputElement>) => {\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    <div\n      data-slot=\"color-picker-input-wrapper\"\n      className={cn(\"flex items-center\", className)}\n    >\n      <InputGroupItem\n        aria-label=\"Hue degree (0-360)\"\n        position=\"first\"\n        {...inputProps}\n        placeholder=\"0\"\n        inputMode=\"numeric\"\n        pattern=\"[0-9]*\"\n        min=\"0\"\n        max=\"360\"\n        className=\"w-14\"\n        value={hsv?.h ?? 0}\n        onChange={onHsvChannelChange(\"h\", 360)}\n        disabled={context.disabled}\n      />\n      <InputGroupItem\n        aria-label=\"Saturation percentage (0-100)\"\n        position=\"middle\"\n        {...inputProps}\n        placeholder=\"0\"\n        inputMode=\"numeric\"\n        pattern=\"[0-9]*\"\n        min=\"0\"\n        max=\"100\"\n        className=\"w-14\"\n        value={hsv?.s ?? 0}\n        onChange={onHsvChannelChange(\"s\", 100)}\n        disabled={context.disabled}\n      />\n      <InputGroupItem\n        aria-label=\"Brightness percentage (0-100)\"\n        position={withoutAlpha ? \"last\" : \"middle\"}\n        {...inputProps}\n        placeholder=\"0\"\n        inputMode=\"numeric\"\n        pattern=\"[0-9]*\"\n        min=\"0\"\n        max=\"100\"\n        className=\"w-14\"\n        value={hsv?.v ?? 0}\n        onChange={onHsvChannelChange(\"v\", 100)}\n        disabled={context.disabled}\n      />\n      {!withoutAlpha && (\n        <InputGroupItem\n          aria-label=\"Alpha transparency percentage\"\n          position=\"last\"\n          {...inputProps}\n          placeholder=\"100\"\n          inputMode=\"numeric\"\n          pattern=\"[0-9]*\"\n          min=\"0\"\n          max=\"100\"\n          className=\"w-14\"\n          value={alphaValue}\n          onChange={onAlphaChange}\n          disabled={context.disabled}\n        />\n      )}\n    </div>\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    <LexicalContentEditable\n      className={`ContentEditable__root relative block min-h-72 overflow-auto px-4 py-2 focus:outline-none ${className ?? \"\"}`.trim()}\n      aria-placeholder={placeholder}\n      placeholder={\n        <div\n          className={`text-muted-foreground pointer-events-none absolute top-0 left-0 overflow-hidden px-4 py-2 text-ellipsis select-none ${placeholderClassName ?? \"\"}`.trim()}\n        >\n          {placeholder}\n        </div>\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 { enUS, zhCN } from \"date-fns/locale\";\nimport { CalendarIcon } from \"lucide-react\";\n\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nimport { Button } from \"@/components/ui/button\";\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 { t, locale } = useEditorI18n();\n  const dateLocale = locale === \"zh-CN\" ? zhCN : enUS;\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<HTMLInputElement>) => {\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\", { locale: dateLocale })\n    : null;\n\n  return (\n    <Popover>\n      <PopoverTrigger asChild>\n        <Button\n          variant=\"outline\"\n          data-empty={!dateTime}\n          data-selected={isNodeSelected}\n          className={cn(\n            \"inline-flex h-auto gap-1.5 px-2 py-0.5 text-sm font-normal\",\n            \"data-[empty=true]:text-muted-foreground\",\n            \"data-[selected=true]:ring-2 data-[selected=true]:ring-primary\",\n            textFormat & IS_BOLD && \"font-bold\",\n            textFormat & IS_ITALIC && \"italic\",\n            textFormat & IS_UNDERLINE && \"underline\",\n            textFormat & IS_STRIKETHROUGH && \"line-through\",\n            textFormat & IS_HIGHLIGHT &&\n              \"bg-accent text-accent-foreground hover:bg-accent/80\",\n          )}\n        >\n          <CalendarIcon className=\"size-3.5\" />\n          {displayLabel ?? <span>{t(\"date.pick\")}</span>}\n        </Button>\n      </PopoverTrigger>\n      <PopoverContent className=\"w-auto p-3\" align=\"start\">\n        <Input\n          type=\"date\"\n          aria-label={t(\"date.pick\")}\n          min=\"1925-01-01\"\n          max=\"2042-08-31\"\n          value={selected ? format(selected, \"yyyy-MM-dd\") : \"\"}\n          onChange={(event) =>\n            handleDaySelect(\n              event.target.value\n                ? new Date(`${event.target.value}T00:00:00`)\n                : undefined,\n            )\n          }\n        />\n        <div className=\"mt-3 border-t pt-3\">\n          <div className=\"flex items-center gap-2\">\n            <Checkbox\n              id={`include-time-${nodeKey}`}\n              checked={includeTime}\n              onCheckedChange={(checked: boolean) =>\n                handleCheckedChange(checked)\n              }\n            />\n            <Label\n              htmlFor={`include-time-${nodeKey}`}\n              className=\"flex items-center gap-2\"\n            >\n              <Input\n                type=\"time\"\n                value={timeValue}\n                onChange={handleTimeChange}\n                disabled={!includeTime}\n                className=\"h-7 w-28 px-1.5 text-xs\"\n              />\n              <span className=\"text-xs text-muted-foreground\">\n                {userTimeZone}\n              </span>\n            </Label>\n          </div>\n        </div>\n      </PopoverContent>\n    </Popover>\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\ntype ImageStatus =\n  | { error: true }\n  | { error: false; width: number; height: number };\n\nconst imageCache = new Map<string, Promise<ImageStatus> | ImageStatus>();\n\nexport const RIGHT_CLICK_IMAGE_COMMAND: LexicalCommand<MouseEvent> =\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<ImageStatus>((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 <BrokenImage />;\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    <img\n      className={className || undefined}\n      src={src}\n      alt={altText}\n      ref={imageRef}\n      style={imageStyle}\n      onError={onError}\n      draggable=\"false\"\n    />\n  );\n}\n\nfunction BrokenImage(): JSX.Element {\n  const { t } = useEditorI18n();\n  return (\n    <img\n      src={\"\"}\n      style={{\n        height: 200,\n        opacity: 0.2,\n        width: 200,\n      }}\n      draggable=\"false\"\n      alt={t(\"image.broken\")}\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 | HTMLImageElement>(null);\n  const buttonRef = useRef<HTMLButtonElement | null>(null);\n  const [isSelected, setSelected, clearSelection] =\n    useLexicalNodeSelection(nodeKey);\n  const [editor] = useLexicalComposerContext();\n  const activeEditorRef = useRef<LexicalEditor | null>(null);\n  const [isLoadError, setIsLoadError] = useState<boolean>(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 | null) => {\n      const latestSelection = $getSelection();\n      const buttonElem = buttonRef.current;\n      if (\n        event !== null &&\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<MouseEvent>(\n        CLICK_COMMAND,\n        onClick,\n        COMMAND_PRIORITY_LOW,\n      ),\n      editor.registerCommand<MouseEvent>(\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    <Suspense fallback={null}>\n      <>\n        <div draggable={draggable}>\n          {isLoadError ? (\n            <BrokenImage />\n          ) : (\n            <LazyImage\n              className={\n                isFocused\n                  ? `focused ${isInNodeSelection ? \"draggable\" : \"\"}`\n                  : null\n              }\n              src={src}\n              altText={altText}\n              imageRef={imageRef}\n              width={width}\n              height={height}\n              maxWidth={maxWidth}\n              onError={() => setIsLoadError(true)}\n            />\n          )}\n        </div>\n      </>\n    </Suspense>\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<HTMLDivElement>(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<HTMLDivElement>,\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    <div ref={controlWrapperRef} className=\"absolute inset-0\">\n      <div\n        className=\"absolute -top-1.5 left-1/2 h-3 w-3 -translate-x-1/2 cursor-ns-resize rounded-sm border-2 border-background bg-primary shadow-sm\"\n        onPointerDown={(event) => {\n          handlePointerDown(event, Direction.north);\n        }}\n      />\n      <div\n        className=\"absolute -right-1.5 -top-1.5 h-3 w-3 cursor-nesw-resize rounded-sm border-2 border-background bg-primary shadow-sm\"\n        onPointerDown={(event) => {\n          handlePointerDown(event, Direction.north | Direction.east);\n        }}\n      />\n      <div\n        className=\"absolute -right-1.5 top-1/2 h-3 w-3 -translate-y-1/2 cursor-ew-resize rounded-sm border-2 border-background bg-primary shadow-sm\"\n        onPointerDown={(event) => {\n          handlePointerDown(event, Direction.east);\n        }}\n      />\n      <div\n        className=\"absolute -bottom-1.5 -right-1.5 h-3 w-3 cursor-nwse-resize rounded-sm border-2 border-background bg-primary shadow-sm\"\n        onPointerDown={(event) => {\n          handlePointerDown(event, Direction.south | Direction.east);\n        }}\n      />\n      <div\n        className=\"absolute -bottom-1.5 left-1/2 h-3 w-3 -translate-x-1/2 cursor-ns-resize rounded-sm border-2 border-background bg-primary shadow-sm\"\n        onPointerDown={(event) => {\n          handlePointerDown(event, Direction.south);\n        }}\n      />\n      <div\n        className=\"absolute -bottom-1.5 -left-1.5 h-3 w-3 cursor-nesw-resize rounded-sm border-2 border-background bg-primary shadow-sm\"\n        onPointerDown={(event) => {\n          handlePointerDown(event, Direction.south | Direction.west);\n        }}\n      />\n      <div\n        className=\"absolute -left-1.5 top-1/2 h-3 w-3 -translate-y-1/2 cursor-ew-resize rounded-sm border-2 border-background bg-primary shadow-sm\"\n        onPointerDown={(event) => {\n          handlePointerDown(event, Direction.west);\n        }}\n      />\n      <div\n        className=\"absolute -left-1.5 -top-1.5 h-3 w-3 cursor-nwse-resize rounded-sm border-2 border-background bg-primary shadow-sm\"\n        onPointerDown={(event) => {\n          handlePointerDown(event, Direction.north | Direction.west);\n        }}\n      />\n    </div>\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()@:%_+.~#?&//=]*)(?<![-.+():%])/;\n\nconst EMAIL_REGEX =\n  /(([^<>()[\\]\\\\.,;:\\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<CommandPayload> =\n  createCommand(\"INSERT_DATETIME_COMMAND\");\n\nexport const DateTimeExtension = defineExtension({\n  name: \"@shadcn-editor/DateTime\",\n  nodes: [DateTimeNode],\n  register: (editor) =>\n    editor.registerCommand<CommandPayload>(\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<string, [string, string]> = 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 { useEditorI18n } from \"@/components/editor/i18n\";\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<ImagePayload>;\n\nexport const INSERT_IMAGE_COMMAND: LexicalCommand<InsertImagePayload> =\n  createCommand(\"INSERT_IMAGE_COMMAND\");\n\nexport function InsertImageUriDialogBody({\n  onClick,\n}: {\n  onClick: (payload: InsertImagePayload) => void;\n}) {\n  const { t } = useEditorI18n();\n  const [src, setSrc] = useState(\"\");\n  const [altText, setAltText] = useState(\"\");\n\n  const isDisabled = src === \"\";\n\n  return (\n    <FieldGroup>\n      <Field>\n        <FieldLabel htmlFor=\"image-url\">{t(\"imageDialog.url\")}</FieldLabel>\n        <Input\n          id=\"image-url\"\n          placeholder={t(\"imageDialog.urlPlaceholder\")}\n          onChange={(e) => setSrc(e.target.value)}\n          value={src}\n          data-test-id=\"image-modal-url-input\"\n        />\n      </Field>\n      <Field>\n        <FieldLabel htmlFor=\"alt-text\">{t(\"imageDialog.alt\")}</FieldLabel>\n        <Input\n          id=\"alt-text\"\n          placeholder={t(\"imageDialog.randomAltPlaceholder\")}\n          onChange={(e) => setAltText(e.target.value)}\n          value={altText}\n          data-test-id=\"image-modal-alt-text-input\"\n        />\n      </Field>\n      <DialogFooter>\n        <Button\n          type=\"submit\"\n          disabled={isDisabled}\n          onClick={() => onClick({ altText, src })}\n          data-test-id=\"image-modal-confirm-btn\"\n        >\n          {t(\"imageDialog.confirm\")}\n        </Button>\n      </DialogFooter>\n    </FieldGroup>\n  );\n}\n\nexport function InsertImageUploadedDialogBody({\n  onClick,\n}: {\n  onClick: (payload: InsertImagePayload) => void;\n}) {\n  const { t } = useEditorI18n();\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    <FieldGroup>\n      <Field>\n        <FieldLabel htmlFor=\"image-upload\">\n          {t(\"imageDialog.upload\")}\n        </FieldLabel>\n        <Input\n          id=\"image-upload\"\n          type=\"file\"\n          onChange={(e) => loadImage(e.target.files)}\n          accept=\"image/*\"\n          className=\"text-foreground file:text-foreground\"\n          data-test-id=\"image-modal-file-upload\"\n        />\n      </Field>\n      <Field>\n        <FieldLabel htmlFor=\"alt-text\">{t(\"imageDialog.alt\")}</FieldLabel>\n        <Input\n          id=\"alt-text\"\n          placeholder={t(\"imageDialog.altPlaceholder\")}\n          onChange={(e) => setAltText(e.target.value)}\n          value={altText}\n          data-test-id=\"image-modal-alt-text-input\"\n        />\n      </Field>\n      <DialogFooter>\n        <Button\n          type=\"submit\"\n          disabled={isDisabled}\n          onClick={() => onClick({ altText, src })}\n          data-test-id=\"image-modal-file-upload-btn\"\n        >\n          {t(\"imageDialog.confirm\")}\n        </Button>\n      </DialogFooter>\n    </FieldGroup>\n  );\n}\n\nexport function InsertImageDialog({\n  activeEditor,\n  onClose,\n}: {\n  activeEditor: LexicalEditor;\n  onClose: () => void;\n}): JSX.Element {\n  const { t } = useEditorI18n();\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    <Tabs defaultValue=\"url\">\n      <TabsList className=\"w-full\">\n        <TabsTrigger value=\"url\">{t(\"imageDialog.tabUrl\")}</TabsTrigger>\n        <TabsTrigger value=\"file\">{t(\"imageDialog.tabFile\")}</TabsTrigger>\n      </TabsList>\n      <TabsContent value=\"url\">\n        <InsertImageUriDialogBody onClick={onClick} />\n      </TabsContent>\n      <TabsContent value=\"file\">\n        <InsertImageUploadedDialogBody onClick={onClick} />\n      </TabsContent>\n    </Tabs>\n  );\n}\n\nexport const ImagesExtension = defineExtension({\n  name: \"@shadcn-editor/Images\",\n  nodes: [ImageNode],\n  register: (editor) =>\n    mergeRegister(\n      editor.registerCommand<InsertImagePayload>(\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<DragEvent>(\n        DRAGSTART_COMMAND,\n        (event) => $onDragStart(event),\n        COMMAND_PRIORITY_HIGH,\n      ),\n      editor.registerCommand<DragEvent>(\n        DRAGOVER_COMMAND,\n        (event) => $onDragover(event),\n        COMMAND_PRIORITY_LOW,\n      ),\n      editor.registerCommand<DragEvent>(\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ªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԧԱ-Ֆՙա-ևא-תװ-ײؠ-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺࠀ-ࠕࠚࠤࠨࡀ-ࡘࢠࢢ-ࢬऄ-हऽॐक़-ॡॱ-ॷॹ-ॿঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-ళవ-హఽౘౙౠౡಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡೱೲഅ-ഌഎ-ഐഒ-ഺഽൎൠൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ະາຳຽເ-ໄໆໜ-ໟༀཀ-ཇཉ-ཬྈ-ྌက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏼᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᢰ-ᣵᤀ-ᤜᥐ-ᥭᥰ-ᥴᦀ-ᦫᧁ-ᧇᨀ-ᨖᨠ-ᩔᪧᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᮺ-ᯥᰀ-ᰣᱍ-ᱏᱚ-ᱽᳩ-ᳬᳮ-ᳱᳵᳶᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℹℼ-ℿⅅ-ⅉⅎↃↄⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞⸯ々〆〱-〵〻〼ぁ-ゖゝ-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿌ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙿ-ꚗꚠ-ꛥꜗ-ꜟꜢ-ꞈꞋ-ꞎꞐ-ꞓꞠ-Ɦꟸ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꣲ-ꣷꣻꤊ-ꤥꤰ-ꥆꥠ-ꥼꦄ-ꦲꧏꨀ-ꨨꩀ-ꩂꩄ-ꩋꩠ-ꩶꩺꪀ-ꪯꪱꪵꪶꪹ-ꪽꫀꫂꫛ-ꫝꫠ-ꫪꫲ-ꫴꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꯀ-ꯢ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ﬀ-ﬆﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼＡ-Ｚａ-ｚｦ-ﾾￂ-ￇￊ-ￏￒ-ￗￚ-ￜ])(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ªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԧԱ-Ֆՙա-ևא-תװ-ײؠ-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺࠀ-ࠕࠚࠤࠨࡀ-ࡘࢠࢢ-ࢬऄ-हऽॐक़-ॡॱ-ॷॹ-ॿঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-ళవ-హఽౘౙౠౡಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡೱೲഅ-ഌഎ-ഐഒ-ഺഽൎൠൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ະາຳຽເ-ໄໆໜ-ໟༀཀ-ཇཉ-ཬྈ-ྌက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏼᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᢰ-ᣵᤀ-ᤜᥐ-ᥭᥰ-ᥴᦀ-ᦫᧁ-ᧇᨀ-ᨖᨠ-ᩔᪧᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᮺ-ᯥᰀ-ᰣᱍ-ᱏᱚ-ᱽᳩ-ᳬᳮ-ᳱᳵᳶᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℹℼ-ℿⅅ-ⅉⅎↃↄⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞⸯ々〆〱-〵〻〼ぁ-ゖゝ-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿌ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙿ-ꚗꚠ-ꛥꜗ-ꜟꜢ-ꞈꞋ-ꞎꞐ-ꞓꞠ-Ɦꟸ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꣲ-ꣷꣻꤊ-ꤥꤰ-ꥆꥠ-ꥼꦄ-ꦲꧏꨀ-ꨨꩀ-ꩂꩄ-ꩋꩠ-ꩶꩺꪀ-ꪯꪱꪵꪶꪹ-ꪽꫀꫂꫛ-ꫝꫠ-ꫪꫲ-ꫴꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꯀ-ꯢ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ﬀ-ﬆﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼＡ-Ｚａ-ｚｦ-ﾾￂ-ￇￊ-ￏￒ-ￗￚ-ￜ])/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<Transformer> }>({ 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<MaxLengthConfig>({ 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<typeof dateTimeState> {\n    return $getState(this, dateTimeState);\n  }\n\n  setDateTime(valueOrUpdater: StateValueOrUpdater<typeof dateTimeState>): 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      <DateTimeComponent\n        dateTime={this.getDateTime()}\n        format={this.getFormat()}\n        nodeKey={this.__key}\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<HTMLDivElement | null>(null);\n\n  const previousTweetIDRef = useRef<string>(\"\");\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    <BlockWithAlignableContents\n      className={className}\n      format={format}\n      nodeKey={nodeKey}\n    >\n      {isTweetLoading ? loadingComponent : null}\n      <div\n        style={{ display: \"inline-block\", width: \"550px\" }}\n        ref={containerRef}\n      />\n    </BlockWithAlignableContents>\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<HTMLDivElement> | 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      <TweetComponent\n        className={className}\n        format={this.__format}\n        loadingComponent=\"Loading...\"\n        nodeKey={this.getKey()}\n        tweetID={this.__id}\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    <BlockWithAlignableContents\n      className={className}\n      format={format}\n      nodeKey={nodeKey}\n    >\n      <iframe\n        width=\"560\"\n        height=\"315\"\n        src={`https://www.youtube-nocookie.com/embed/${videoID}`}\n        frameBorder=\"0\"\n        allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture\"\n        allowFullScreen={true}\n        title=\"YouTube video\"\n      />\n    </BlockWithAlignableContents>\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      <YouTubeComponent\n        className={className}\n        format={this.__format}\n        nodeKey={this.getKey()}\n        videoID={this.__id}\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<JSX.Element> {\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<SerializedImageNode>): 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      <ImageComponent\n        src={this.__src}\n        altText={this.__altText}\n        width={this.__width}\n        height={this.__height}\n        maxWidth={this.__maxWidth}\n        nodeKey={this.getKey()}\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nexport function ClearEditorActionPlugin() {\n  const [editor] = useLexicalComposerContext();\n  const { t } = useEditorI18n();\n\n  return (\n    <Dialog>\n      <Tooltip>\n        <TooltipTrigger asChild>\n          <DialogTrigger asChild>\n            <Button size={\"sm\"} variant={\"ghost\"} className=\"p-2\">\n              <Trash2Icon className=\"size-4\" />\n            </Button>\n          </DialogTrigger>\n        </TooltipTrigger>\n        <TooltipContent>{t(\"action.clearTitle\")}</TooltipContent>\n      </Tooltip>\n\n      <DialogContent>\n        <DialogHeader>\n          <DialogTitle>{t(\"action.clearTitle\")}</DialogTitle>\n          <DialogDescription>{t(\"action.clearDescription\")}</DialogDescription>\n        </DialogHeader>\n        <DialogFooter>\n          <DialogClose asChild>\n            <Button variant=\"outline\">{t(\"action.cancel\")}</Button>\n          </DialogClose>\n\n          <DialogClose asChild>\n            <Button\n              variant=\"destructive\"\n              onClick={() => {\n                editor.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined);\n              }}\n            >\n              {t(\"action.clear\")}\n            </Button>\n          </DialogClose>\n        </DialogFooter>\n      </DialogContent>\n    </Dialog>\n  );\n}\n",
      "type": "registry:component"
    },
    {
      "path": "src/components/editor/plugins/actions/counter-character-plugin.tsx",
      "content": "import { useEffect, useState } from \"react\";\n\nimport { CharacterLimitPlugin } from \"@lexical/react/LexicalCharacterLimitPlugin\";\nimport { useLexicalComposerContext } from \"@lexical/react/LexicalComposerContext\";\nimport { $rootTextContent } from \"@lexical/text\";\n\nimport { cn } from \"@/lib/utils\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\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  maxLength?: number;\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  maxLength,\n}: CounterCharacterPluginProps) {\n  const [editor] = useLexicalComposerContext();\n  const { t } = useEditorI18n();\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  if (maxLength !== undefined) {\n    return (\n      <CharacterLimitPlugin\n        charset={charset}\n        maxLength={maxLength}\n        renderer={({ remainingCharacters }) => (\n          <div className=\"flex gap-2 text-xs whitespace-nowrap text-gray-500\">\n            <p>\n              <span\n                className={cn(remainingCharacters < 0 && \"text-destructive\")}\n              >\n                {stats.characters}\n              </span>\n              {t(\"counter.maxCharacters\", { max: maxLength })}\n            </p>\n            |\n            <p>\n              {stats.words} {t(\"counter.words\")}\n            </p>\n          </div>\n        )}\n      />\n    );\n  }\n\n  return (\n    <div className=\"flex gap-2 text-xs whitespace-nowrap text-gray-500\">\n      <p>\n        {stats.characters} {t(\"counter.characters\")}\n      </p>\n      |\n      <p>\n        {stats.words} {t(\"counter.words\")}\n      </p>\n    </div>\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nexport function EditModeTogglePlugin() {\n  const [editor] = useLexicalComposerContext();\n  const { t } = useEditorI18n();\n  const [isEditable, setIsEditable] = useState(() => editor.isEditable());\n\n  return (\n    <Tooltip>\n      <TooltipTrigger asChild>\n        <Button\n            variant={\"ghost\"}\n            onClick={() => {\n              editor.setEditable(!editor.isEditable());\n              setIsEditable(editor.isEditable());\n            }}\n            title={t(\"action.editModeTitle\")}\n            aria-label={`${!isEditable ? t(\"action.editModeUnlockAria\") : t(\"action.editModeLockAria\")}`}\n            size={\"sm\"}\n            className=\"p-2\"\n          >\n            {isEditable ? (\n              <LockIcon className=\"size-4\" />\n            ) : (\n              <UnlockIcon className=\"size-4\" />\n            )}\n        </Button>\n      </TooltipTrigger>\n      <TooltipContent>\n        {isEditable ? t(\"action.editViewOnly\") : t(\"action.editEditMode\")}\n      </TooltipContent>\n    </Tooltip>\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nexport function ImportExportPlugin() {\n  const [editor] = useLexicalComposerContext();\n  const { t } = useEditorI18n();\n  return (\n    <>\n      <Tooltip>\n        <TooltipTrigger asChild>\n          <Button\n              variant={\"ghost\"}\n              onClick={() => importFile(editor)}\n              title={t(\"action.importTitle\")}\n              aria-label={t(\"action.importAria\")}\n              size={\"sm\"}\n              className=\"p-2\"\n            >\n              <UploadIcon className=\"size-4\" />\n          </Button>\n        </TooltipTrigger>\n        <TooltipContent>{t(\"action.importContent\")}</TooltipContent>\n      </Tooltip>\n\n      <Tooltip>\n        <TooltipTrigger asChild>\n          <Button\n              variant={\"ghost\"}\n              onClick={() =>\n                exportFile(editor, {\n                  fileName: `${t(\"action.exportFileName\")} ${new Date().toISOString()}`,\n                  source: \"Editor\",\n                })\n              }\n              title={t(\"action.exportTitle\")}\n              aria-label={t(\"action.exportAria\")}\n              size={\"sm\"}\n              className=\"p-2\"\n            >\n              <DownloadIcon className=\"size-4\" />\n          </Button>\n        </TooltipTrigger>\n        <TooltipContent>{t(\"action.exportContent\")}</TooltipContent>\n      </Tooltip>\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nexport function MarkdownTogglePlugin({\n  shouldPreserveNewLinesInMarkdown,\n  transformers,\n}: {\n  shouldPreserveNewLinesInMarkdown: boolean;\n  transformers: Array<Transformer>;\n}) {\n  const [editor] = useLexicalComposerContext();\n  const { t } = useEditorI18n();\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    <Button\n      variant={\"ghost\"}\n      onClick={handleMarkdownToggle}\n      title={t(\"action.markdownTitle\")}\n      aria-label={t(\"action.markdownAria\")}\n      size={\"sm\"}\n      className=\"p-2\"\n    >\n      <FileTextIcon className=\"size-4\" />\n    </Button>\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nexport function ShareContentPlugin() {\n  const [editor] = useLexicalComposerContext();\n  const { t } = useEditorI18n();\n  async function shareDoc(doc: SerializedDocument): Promise<void> {\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    <Tooltip>\n      <TooltipTrigger asChild>\n        <Button\n            variant={\"ghost\"}\n            onClick={() =>\n              shareDoc(\n                serializedDocumentFromEditorState(editor.getEditorState(), {\n                  source: \"editor\",\n                }),\n              ).then(\n                () => toast.success(t(\"action.shareCopied\")),\n                () => toast.error(t(\"action.shareCopyFailed\")),\n              )\n            }\n            title={t(\"action.shareTitle\")}\n            aria-label={t(\"action.shareAria\")}\n            size={\"sm\"}\n            className=\"p-2\"\n          >\n            <SendIcon className=\"size-4\" />\n        </Button>\n      </TooltipTrigger>\n      <TooltipContent>{t(\"action.shareContent\")}</TooltipContent>\n    </Tooltip>\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nexport const SPEECH_TO_TEXT_COMMAND: LexicalCommand<boolean> = 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 { t } = useEditorI18n();\n  const [isEnabled, setIsEnabled] = useState<boolean>(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<typeof SpeechRecognition | null>(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    <Tooltip>\n      <TooltipTrigger asChild>\n        <Button\n            onClick={() => {\n              editor.dispatchCommand(SPEECH_TO_TEXT_COMMAND, !isSpeechToText);\n              setIsSpeechToText(!isSpeechToText);\n            }}\n            variant={isSpeechToText ? \"secondary\" : \"ghost\"}\n            title={t(\"action.speechTitle\")}\n            aria-label={`${isSpeechToText ? t(\"action.speechEnableAria\") : t(\"action.speechDisableAria\")}`}\n            className=\"p-2\"\n            size={\"sm\"}\n          >\n            <MicIcon className=\"size-4\" />\n        </Button>\n      </TooltipTrigger>\n      <TooltipContent>{t(\"action.speechContent\")}</TooltipContent>\n    </Tooltip>\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\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nexport function TreeViewPlugin(): JSX.Element {\n  const [editor] = useLexicalComposerContext();\n  const { t } = useEditorI18n();\n  return (\n    <Dialog>\n      <DialogTrigger asChild>\n        <Button size={\"sm\"} variant={\"ghost\"} className=\"p-2\">\n          <NotebookPenIcon className=\"size-4\" />\n        </Button>\n      </DialogTrigger>\n      <DialogContent>\n        <DialogHeader>\n          <DialogTitle>{t(\"misc.treeView\")}</DialogTitle>\n        </DialogHeader>\n        <ScrollArea className=\"bg-foreground text-background h-96 overflow-hidden rounded-lg p-2\">\n          <TreeView\n            viewClassName=\"tree-view-output\"\n            treeTypeButtonClassName=\"debug-treetype-button\"\n            timeTravelPanelClassName=\"debug-timetravel-panel\"\n            timeTravelButtonClassName=\"debug-timetravel-button\"\n            timeTravelPanelSliderClassName=\"debug-timetravel-panel-slider\"\n            timeTravelPanelButtonClassName=\"debug-timetravel-panel-button\"\n            editor={editor}\n          />\n          <ScrollBar orientation=\"vertical\" />\n        </ScrollArea>\n      </DialogContent>\n    </Dialog>\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<null | string>;\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<null | string> = 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<boolean>(false);\n  const [shouldListenMouseMove, setShouldListenMouseMove] =\n    useState<boolean>(false);\n  const [position, setPosition] = useState<Position>({\n    right: \"0\",\n    top: \"0\",\n  });\n  const codeSetRef = useRef<Set<string>>(new Set());\n  const codeDOMNodeRef = useRef<HTMLElement | null>(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        <div className=\"code-action-menu-container\" style={{ ...position }}>\n          <div className=\"code-highlight-language\">{codeFriendlyName}</div>\n          <CopyButton editor={editor} getCodeDOMNode={getCodeDOMNode} />\n        </div>\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<HTMLElement>(\n      \"code.PlaygroundEditorTheme__code\",\n    );\n    const isOutside = !(\n      codeDOMNode ||\n      target.closest<HTMLElement>(\"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    <CodeActionMenuContainer anchorElem={anchorElem} />,\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-shiki\";\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<ComponentPickerOption>\n//     ),\n// )\n\nfunction ComponentPickerMenu({\n  options,\n  selectedIndex,\n  selectOptionAndCleanUp,\n  setHighlightedIndex,\n}: {\n  options: Array<ComponentPickerOption>;\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    <div className=\"bg-popover text-popover-foreground absolute h-min min-w-48 rounded-md border shadow-md\">\n      <Command\n        onKeyDown={(e) => {\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        <CommandList className=\"w-56\">\n          <CommandGroup>\n            {options.map((option, index) => (\n              <CommandItem\n                key={option.key}\n                ref={(el) => {\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              </CommandItem>\n            ))}\n          </CommandGroup>\n        </CommandList>\n      </Command>\n    </div>\n  );\n}\n\nexport function ComponentPickerMenuPlugin({\n  baseOptions = [],\n  dynamicOptionsFn,\n}: {\n  baseOptions?: Array<ComponentPickerOption>;\n  dynamicOptionsFn?: ({\n    queryString,\n  }: {\n    queryString: string;\n  }) => Array<ComponentPickerOption>;\n}): JSX.Element {\n  const [editor] = useLexicalComposerContext();\n  const [modal, showModal] = useEditorModal();\n  const [queryString, setQueryString] = useState<string | null>(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      <LexicalTypeaheadMenuPlugin\n        onQueryChange={setQueryString}\n        onSelectOption={onSelectOption}\n        triggerFn={checkForTriggerMatch}\n        options={options}\n        menuRenderFn={(\n          anchorElementRef,\n          { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex },\n        ) => {\n          return anchorElementRef.current && options.length\n            ? createPortal(\n                <ComponentPickerMenu\n                  options={options}\n                  selectedIndex={selectedIndex}\n                  selectOptionAndCleanUp={selectOptionAndCleanUp}\n                  setHighlightedIndex={setHighlightedIndex}\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nexport function ContextMenuPlugin(): JSX.Element {\n  const [editor] = useLexicalComposerContext();\n  const { t } = useEditorI18n();\n\n  const items = useMemo(() => {\n    return [\n      new NodeContextMenuOption(t(\"contextMenu.removeLink\"), {\n        $onSelect: () => {\n          editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);\n        },\n        $showOn: (node: LexicalNode) => $isLinkNode(node.getParent()),\n        disabled: false,\n        icon: <Link2Off className=\"size-4\" />,\n      }),\n      new NodeContextMenuSeparator({\n        $showOn: (node: LexicalNode) => $isLinkNode(node.getParent()),\n      }),\n      new NodeContextMenuOption(t(\"contextMenu.cut\"), {\n        $onSelect: () => {\n          editor.dispatchCommand(CUT_COMMAND, null);\n        },\n        disabled: false,\n        icon: <Scissors className=\"size-4\" />,\n      }),\n      new NodeContextMenuOption(t(\"contextMenu.copy\"), {\n        $onSelect: () => {\n          editor.dispatchCommand(COPY_COMMAND, null);\n        },\n        disabled: false,\n        icon: <Copy className=\"size-4\" />,\n      }),\n      new NodeContextMenuOption(t(\"contextMenu.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(t(\"contextMenu.pasteDenied\"));\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: <Clipboard className=\"size-4\" />,\n      }),\n      new NodeContextMenuOption(t(\"contextMenu.pastePlainText\"), {\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: <ClipboardType className=\"size-4\" />,\n      }),\n      new NodeContextMenuSeparator(),\n      new NodeContextMenuOption(t(\"contextMenu.deleteNode\"), {\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: <Trash2 className=\"size-4\" />,\n      }),\n    ];\n  }, [editor, t]);\n\n  return (\n    <NodeContextMenuPlugin\n      className=\"bg-popover text-popover-foreground min-w-36 overflow-hidden rounded-md border p-1 shadow-md outline-none\"\n      itemClassName=\"relative w-full flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50\"\n      separatorClassName=\"bg-border -mx-1 my-1 h-px\"\n      items={items}\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\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<ComponentPickerOption>;\n  dynamicOptionsFn?: ({\n    queryString,\n  }: {\n    queryString: string;\n  }) => Array<ComponentPickerOption>;\n}): JSX.Element | null {\n  const [editor] = useLexicalComposerContext();\n  const { t } = useEditorI18n();\n  const [modal, showModal] = useEditorModal();\n  const menuRef = useRef<HTMLDivElement>(null);\n  const pickerRef = useRef<HTMLDivElement>(null);\n  const targetLineRef = useRef<HTMLDivElement>(null);\n  const [draggableElement, setDraggableElement] = useState<HTMLElement | null>(\n    null,\n  );\n  const [pickerState, setPickerState] = useState<PickerState | null>(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            <div\n              ref={pickerRef}\n              className=\"absolute z-50 w-56 rounded-md shadow-md\"\n              style={{\n                left: pickerPosition.left,\n                top: pickerPosition.top,\n              }}\n            >\n              <Command>\n                <CommandInput\n                  placeholder={t(\"misc.filterBlocks\")}\n                  value={queryString}\n                  onValueChange={setQueryString}\n                />\n                <CommandList>\n                  <CommandEmpty>{t(\"misc.noResults\")}</CommandEmpty>\n                  <CommandGroup>\n                    {options.map((option, i) => (\n                      <CommandItem\n                        key={option.key}\n                        value={option.title}\n                        onSelect={() => {\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                      </CommandItem>\n                    ))}\n                  </CommandGroup>\n                </CommandList>\n              </Command>\n            </div>,\n            document.body,\n          )\n        : null}\n      <DraggableBlockPlugin_EXPERIMENTAL\n        anchorElem={anchorElem}\n        menuRef={menuRef as React.RefObject<HTMLDivElement>}\n        targetLineRef={targetLineRef as React.RefObject<HTMLDivElement>}\n        menuComponent={\n          <div\n            ref={menuRef}\n            className=\"draggable-block-menu absolute top-0 left-0 flex items-center opacity-0 will-change-transform\"\n          >\n            <Button\n              variant=\"ghost\"\n              size=\"icon\"\n              title={t(\"misc.clickAddBelow\")}\n              className=\"size-6 cursor-pointer rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground\"\n              onClick={openComponentPicker}\n            >\n              <PlusIcon />\n            </Button>\n            <Button\n              variant=\"ghost\"\n              size=\"icon\"\n              className=\"size-6 cursor-grab rounded-sm text-muted-foreground hover:bg-accent hover:text-foreground active:cursor-grabbing\"\n              tabIndex={-1}\n            >\n              <GripVerticalIcon className=\"opacity-60\" />\n            </Button>\n          </div>\n        }\n        targetLineComponent={\n          <div\n            ref={targetLineRef}\n            className=\"bg-primary pointer-events-none absolute top-0 left-0 h-0.5 opacity-0 will-change-transform\"\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 { createPortal } from \"react-dom\";\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  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width=\"24\"\n    height=\"24\"\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"2\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n    className=\"lucide lucide-youtube\"\n  >\n    <path d=\"M2.5 17a24.12 24.12 0 0 1 0-10 2 2 0 0 1 1.4-1.4 49.56 49.56 0 0 1 16.2 0A2 2 0 0 1 21.5 7a24.12 24.12 0 0 1 0 10 2 2 0 0 1-1.4 1.4 49.55 49.55 0 0 1-16.2 0A2 2 0 0 1 2.5 17\" />\n    <path d=\"m10 15 5-3-5-3z\" />\n  </svg>\n);\n\nconst TwitterIcon = (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    width=\"24\"\n    height=\"24\"\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"2\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n    className=\"lucide lucide-twitter\"\n  >\n    <path d=\"M22 4s-.7 2.1-2 3.4c1.6 10-9.4 17.3-18 11.6 2.2.1 4.4-.6 6-2C3 15.5.5 9.6 3 5c2.2 2.6 5.6 4.1 9 4-.9-4.2 4-6.6 7-3.8 1.1 0 3-1.2 3-1.2z\" />\n  </svg>\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<string>;\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<EmbedMatchResult | null>(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    <div className=\"\">\n      <div className=\"space-y-4\">\n        <Input\n          type=\"text\"\n          placeholder={embedConfig.exampleUrl}\n          value={text}\n          data-test-id={`${embedConfig.type}-embed-modal-url`}\n          onChange={(e) => {\n            const { value } = e.target;\n            setText(value);\n            validateText(value);\n          }}\n        />\n        <DialogFooter>\n          <Button\n            disabled={!embedResult}\n            onClick={onClick}\n            data-test-id={`${embedConfig.type}-embed-modal-submit-btn`}\n          >\n            Embed\n          </Button>\n        </DialogFooter>\n      </div>\n    </div>\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      <AutoEmbedDialog embedConfig={embedConfig} onClose={onClose} />\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      <LexicalAutoEmbedPlugin<CustomEmbedConfig>\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            ? createPortal(\n                <Popover open={true}>\n                  <div className=\"-translate-y-full transform\">\n                    <PopoverTrigger />\n                    <PopoverContent\n                      className=\"min-w-36 p-0\"\n                      align=\"start\"\n                      side=\"right\"\n                    >\n                      <Command>\n                        <CommandList>\n                          <CommandGroup>\n                            {options.map((option, _i: number) => (\n                              <CommandItem\n                                key={option.key}\n                                value={option.title}\n                                onSelect={() => {\n                                  selectOptionAndCleanUp(option);\n                                }}\n                                className=\"flex items-center gap-2\"\n                              >\n                                {option.title}\n                              </CommandItem>\n                            ))}\n                          </CommandGroup>\n                        </CommandList>\n                      </Command>\n                    </PopoverContent>\n                  </div>\n                </Popover>,\n                anchorElementRef.current,\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<string> = 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<string>(\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<string> = 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<string>(\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<EmojiOption>\n//     ),\n//   { ssr: false }\n// )\n\nclass EmojiOption extends MenuOption {\n  title: string;\n  emoji: string;\n  keywords: Array<string>;\n\n  constructor(\n    title: string,\n    emoji: string,\n    options: {\n      keywords?: Array<string>;\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<string>;\n  tags: Array<string>;\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<string | null>(null);\n  const [emojis, setEmojis] = useState<Array<Emoji>>([]);\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<EmojiOption> = 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    <LexicalTypeaheadMenuPlugin\n      onQueryChange={setQueryString}\n      onSelectOption={onSelectOption}\n      triggerFn={checkForTriggerMatch}\n      options={options}\n      menuRenderFn={(\n        anchorElementRef,\n        { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex },\n      ) => {\n        return anchorElementRef.current && options.length\n          ? createPortal(\n              <div className=\"bg-popover text-popover-foreground absolute min-w-36 rounded-md border shadow-md\">\n                <Command\n                  onKeyDown={(e) => {\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                  <CommandList>\n                    <CommandGroup>\n                      {options.map((option, index) => (\n                        <CommandItem\n                          key={option.key}\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.emoji} {option.title}\n                        </CommandItem>\n                      ))}\n                    </CommandGroup>\n                  </CommandList>\n                </Command>\n              </div>,\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\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<boolean>;\n  anchorElem: HTMLElement;\n  isLinkEditMode: boolean;\n  setIsLinkEditMode: Dispatch<boolean>;\n}): JSX.Element {\n  const { t } = useEditorI18n();\n  const editorRef = useRef<HTMLDivElement | null>(null);\n  const inputRef = useRef<HTMLInputElement>(null);\n  const [linkUrl, setLinkUrl] = useState(\"\");\n  const [editedLinkUrl, setEditedLinkUrl] = useState(\"https://\");\n  const [lastSelection, setLastSelection] = useState<BaseSelection | null>(\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<HTMLInputElement>,\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    <div\n      ref={editorRef}\n      className=\"absolute top-0 left-0 w-full max-w-sm opacity-0\"\n    >\n      {!isLink ? null : isLinkEditMode ? (\n        <div className=\"bg-popover text-popover-foreground flex items-center gap-2 rounded-md border p-1 pl-2 shadow-md\">\n          <Input\n            ref={inputRef}\n            value={editedLinkUrl}\n            onChange={(event) => setEditedLinkUrl(event.target.value)}\n            onKeyDown={monitorInputInteraction}\n            className=\"h-8 flex-grow\"\n          />\n          <Button\n            size=\"icon\"\n            variant=\"ghost\"\n            className=\"size-8 shrink-0\"\n            onClick={() => {\n              setIsLinkEditMode(false);\n              setIsLink(false);\n            }}\n            aria-label={t(\"link.cancel\")}\n          >\n            <X />\n          </Button>\n          <Button\n            size=\"icon\"\n            onClick={handleLinkSubmission}\n            className=\"size-8 shrink-0\"\n            aria-label={t(\"link.confirm\")}\n          >\n            <Check />\n          </Button>\n        </div>\n      ) : (\n        <div className=\"bg-popover text-popover-foreground flex items-center justify-between gap-2 rounded-md border p-1 pl-2 shadow-md\">\n          <a\n            href={sanitizeUrl(linkUrl)}\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            className=\"text-primary truncate text-sm underline-offset-4 hover:underline\"\n          >\n            {linkUrl}\n          </a>\n          <div className=\"flex items-center gap-1\">\n            <Button\n              size=\"icon\"\n              variant=\"ghost\"\n              className=\"text-destructive hover:text-destructive size-8\"\n              onClick={() => {\n                setEditedLinkUrl(linkUrl);\n                setIsLinkEditMode(true);\n              }}\n              aria-label={t(\"link.edit\")}\n            >\n              <Pencil />\n            </Button>\n            <Button\n              size=\"icon\"\n              variant=\"ghost\"\n              className=\"size-8\"\n              onClick={() => {\n                editor.dispatchCommand(TOGGLE_LINK_COMMAND, null);\n              }}\n              aria-label={t(\"link.remove\")}\n            >\n              <Trash />\n            </Button>\n          </div>\n        </div>\n      )}\n    </div>\n  );\n}\n\nfunction useFloatingLinkEditorToolbar(\n  editor: LexicalEditor,\n  anchorElem: HTMLDivElement | null,\n  isLinkEditMode: boolean,\n  setIsLinkEditMode: Dispatch<boolean>,\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    <FloatingLinkEditor\n      editor={activeEditor}\n      isLink={isLink}\n      anchorElem={anchorElem}\n      setIsLink={setIsLink}\n      isLinkEditMode={isLinkEditMode}\n      setIsLinkEditMode={setIsLinkEditMode}\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<boolean>;\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\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<boolean>;\n}): JSX.Element {\n  const popupCharStylesEditorRef = useRef<HTMLDivElement | null>(null);\n  const { t } = useEditorI18n();\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    <div\n      ref={popupCharStylesEditorRef}\n      className=\"bg-popover text-popover-foreground absolute top-0 left-0 flex gap-1 rounded-md border p-1 opacity-0 shadow-md transition-opacity duration-300 will-change-transform\"\n    >\n      {editor.isEditable() && (\n        <>\n          <ToggleGroup\n            type=\"multiple\"\n            value={[\n              isBold ? \"bold\" : \"\",\n              isItalic ? \"italic\" : \"\",\n              isUnderline ? \"underline\" : \"\",\n              isStrikethrough ? \"strikethrough\" : \"\",\n              isSubscript ? \"subscript\" : \"\",\n              isSuperscript ? \"superscript\" : \"\",\n              isCode ? \"code\" : \"\",\n              isLink ? \"link\" : \"\",\n            ].filter(Boolean)}\n          >\n            <ToggleGroupItem\n              value=\"bold\"\n              aria-label={t(\"toolbar.toggleBold\")}\n              onClick={() => {\n                editor.dispatchCommand(FORMAT_TEXT_COMMAND, \"bold\");\n              }}\n              size=\"sm\"\n            >\n              <BoldIcon className=\"size-4\" />\n            </ToggleGroupItem>\n            <ToggleGroupItem\n              value=\"italic\"\n              aria-label={t(\"toolbar.toggleItalic\")}\n              onClick={() => {\n                editor.dispatchCommand(FORMAT_TEXT_COMMAND, \"italic\");\n              }}\n              size=\"sm\"\n            >\n              <ItalicIcon className=\"size-4\" />\n            </ToggleGroupItem>\n            <ToggleGroupItem\n              value=\"underline\"\n              aria-label={t(\"toolbar.toggleUnderline\")}\n              onClick={() => {\n                editor.dispatchCommand(FORMAT_TEXT_COMMAND, \"underline\");\n              }}\n              size=\"sm\"\n            >\n              <UnderlineIcon className=\"size-4\" />\n            </ToggleGroupItem>\n            <ToggleGroupItem\n              value=\"strikethrough\"\n              aria-label={t(\"toolbar.toggleStrikethrough\")}\n              onClick={() => {\n                editor.dispatchCommand(FORMAT_TEXT_COMMAND, \"strikethrough\");\n              }}\n              size=\"sm\"\n            >\n              <StrikethroughIcon className=\"size-4\" />\n            </ToggleGroupItem>\n            <Separator orientation=\"vertical\" />\n            <ToggleGroupItem\n              value=\"code\"\n              aria-label={t(\"toolbar.toggleCode\")}\n              onClick={() => {\n                editor.dispatchCommand(FORMAT_TEXT_COMMAND, \"code\");\n              }}\n              size=\"sm\"\n            >\n              <CodeIcon className=\"size-4\" />\n            </ToggleGroupItem>\n            <ToggleGroupItem\n              value=\"link\"\n              aria-label={t(\"toolbar.toggleLink\")}\n              onClick={insertLink}\n              size=\"sm\"\n            >\n              <LinkIcon className=\"size-4\" />\n            </ToggleGroupItem>\n            <Separator orientation=\"vertical\" />\n          </ToggleGroup>\n          <ToggleGroup\n            type=\"multiple\"\n            value={\n              isSubscript ? [\"subscript\"] : isSuperscript ? [\"superscript\"] : []\n            }\n          >\n            <ToggleGroupItem\n              value=\"subscript\"\n              aria-label={t(\"toolbar.toggleSubscript\")}\n              onClick={() => {\n                editor.dispatchCommand(FORMAT_TEXT_COMMAND, \"subscript\");\n              }}\n              size=\"sm\"\n            >\n              <SubscriptIcon className=\"size-4\" />\n            </ToggleGroupItem>\n            <ToggleGroupItem\n              value=\"superscript\"\n              aria-label={t(\"toolbar.toggleSuperscript\")}\n              onClick={() => {\n                editor.dispatchCommand(FORMAT_TEXT_COMMAND, \"superscript\");\n              }}\n              size=\"sm\"\n            >\n              <SuperscriptIcon className=\"size-4\" />\n            </ToggleGroupItem>\n          </ToggleGroup>\n        </>\n      )}\n    </div>\n  );\n}\n\nfunction useFloatingTextFormatToolbar(\n  editor: LexicalEditor,\n  anchorElem: HTMLDivElement | null,\n  setIsLinkEditMode: Dispatch<boolean>,\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    <TextFormatFloatingToolbar\n      editor={editor}\n      anchorElem={anchorElem}\n      isLink={isLink}\n      isBold={isBold}\n      isItalic={isItalic}\n      isStrikethrough={isStrikethrough}\n      isSubscript={isSubscript}\n      isSuperscript={isSuperscript}\n      isUnderline={isUnderline}\n      isCode={isCode}\n      setIsLinkEditMode={setIsLinkEditMode}\n    />,\n    anchorElem,\n  );\n}\n\nexport function FloatingTextFormatToolbarPlugin({\n  anchorElem,\n  setIsLinkEditMode,\n}: {\n  anchorElem: HTMLDivElement | null;\n  setIsLinkEditMode: Dispatch<boolean>;\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      <Select\n        onValueChange={(value: string) => {\n          if (value != null) setLayout(value);\n        }}\n        defaultValue={layout}\n      >\n        <SelectTrigger className=\"w-full\">\n          <SelectValue placeholder={buttonLabel} />\n        </SelectTrigger>\n        <SelectContent className=\"w-full\">\n          {LAYOUTS.map(({ label, value }) => (\n            <SelectItem key={value} value={value}>\n              {label}\n            </SelectItem>\n          ))}\n        </SelectContent>\n      </Select>\n      <Button onClick={onClick}>Insert</Button>\n    </>\n  );\n}\n\nexport const INSERT_LAYOUT_COMMAND: LexicalCommand<string> =\n  createCommand<string>();\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<ElementNode>();\n          const child =\n            parent &&\n            (before\n              ? parent.getFirstChild<LexicalNode>()\n              : parent?.getLastChild<LexicalNode>());\n          const descendant = before\n            ? container.getFirstDescendant<LexicalNode>()?.getKey()\n            : container.getLastDescendant<LexicalNode>()?.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<LexicalNode>(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<LexicalNode>(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<ElementNode>();\n        if (!$isLayoutContainerNode(parent)) {\n          const children = node.getChildren<LexicalNode>();\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<LexicalNode>();\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<MentionTypeaheadOption>\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<string>) => 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<Array<string>>([]);\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<string | null>(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              <CircleUserRoundIcon className=\"size-4\" />,\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    <LexicalTypeaheadMenuPlugin\n      onQueryChange={setQueryString}\n      onSelectOption={onSelectOption}\n      triggerFn={checkForMentionMatch}\n      options={options}\n      menuRenderFn={(\n        anchorElementRef,\n        { selectedIndex, selectOptionAndCleanUp, setHighlightedIndex },\n      ) => {\n        return anchorElementRef.current && results.length\n          ? createPortal(\n              <div className=\"absolute z-10 min-w-36 rounded-md shadow-md\">\n                <Command\n                  onKeyDown={(e) => {\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                  <CommandList>\n                    <CommandGroup>\n                      {options.map((option, index) => (\n                        <CommandItem\n                          key={option.key}\n                          value={option.name}\n                          onSelect={() => {\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                        </CommandItem>\n                      ))}\n                    </CommandGroup>\n                  </CommandList>\n                </Command>\n              </div>,\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\";\nimport type { TranslateFn } from \"@/components/editor/i18n\";\n\nexport function AlignmentPickerPlugin({\n  alignment,\n  t,\n}: {\n  alignment: \"left\" | \"center\" | \"right\" | \"justify\";\n  t: TranslateFn;\n}) {\n  const labelKey = `picker.align${alignment.charAt(0).toUpperCase()}${alignment.slice(1)}`;\n  return new ComponentPickerOption(t(labelKey), {\n    icon: <AlignIcons alignment={alignment} />,\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 <AlignLeftIcon className=\"size-4\" />;\n    case \"center\":\n      return <AlignCenterIcon className=\"size-4\" />;\n    case \"right\":\n      return <AlignRightIcon className=\"size-4\" />;\n    case \"justify\":\n      return <AlignJustifyIcon className=\"size-4\" />;\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\";\nimport type { TranslateFn } from \"@/components/editor/i18n\";\n\nexport function BulletedListPickerPlugin(t: TranslateFn) {\n  return new ComponentPickerOption(t(\"picker.bullet\"), {\n    icon: <ListIcon className=\"size-4\" />,\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\";\nimport type { TranslateFn } from \"@/components/editor/i18n\";\n\nexport function CheckListPickerPlugin(t: TranslateFn) {\n  return new ComponentPickerOption(t(\"picker.check\"), {\n    icon: <ListTodoIcon className=\"size-4\" />,\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\";\nimport type { TranslateFn } from \"@/components/editor/i18n\";\n\nexport function CodePickerPlugin(t: TranslateFn) {\n  return new ComponentPickerOption(t(\"picker.code\"), {\n    icon: <CodeIcon className=\"size-4\" />,\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\";\nimport type { TranslateFn } from \"@/components/editor/i18n\";\n\nexport function ColumnsLayoutPickerPlugin(t: TranslateFn) {\n  return new ComponentPickerOption(t(\"picker.columns\"), {\n    icon: <Columns3Icon className=\"size-4\" />,\n    keywords: [\"columns\", \"layout\", \"grid\"],\n    onSelect: (_, editor, showModal) =>\n      showModal(t(\"misc.insertColumns\"), (onClose) => (\n        <InsertLayoutDialog activeEditor={editor} onClose={onClose} />\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<string>;\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<string>;\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\";\nimport type { TranslateFn } from \"@/components/editor/i18n\";\n\nexport function DateTimePickerPlugin(t: TranslateFn) {\n  return new ComponentPickerOption(t(\"picker.dateTime\"), {\n    icon: <CalendarIcon className=\"size-4\" />,\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\";\nimport type { TranslateFn } from \"@/components/editor/i18n\";\n\nexport function DividerPickerPlugin(t: TranslateFn) {\n  return new ComponentPickerOption(t(\"picker.divider\"), {\n    icon: <MinusIcon className=\"size-4\" />,\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\";\nimport type { TranslateFn } from \"@/components/editor/i18n\";\n\nexport function EmbedsPickerPlugin({\n  embed,\n  t,\n}: {\n  embed: \"tweet\" | \"youtube-video\";\n  t: TranslateFn;\n}) {\n  const embedConfig = EmbedConfigs.find(\n    (config) => config.type === embed,\n  ) as CustomEmbedConfig;\n\n  return new ComponentPickerOption(\n    `${t(\"picker.embeds\")} ${embedConfig.contentName}`,\n    {\n      icon: embedConfig.icon,\n      keywords: [...embedConfig.keywords, \"embed\"],\n      onSelect: (_, editor) =>\n        editor.dispatchCommand(INSERT_EMBED_COMMAND, embedConfig.type),\n    },\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\";\nimport type { TranslateFn } from \"@/components/editor/i18n\";\n\nexport function HeadingPickerPlugin({\n  n,\n  t,\n}: {\n  n: 1 | 2 | 3;\n  t: TranslateFn;\n}) {\n  return new ComponentPickerOption(t(`picker.Heading ${n}`), {\n    icon: <HeadingIcons n={n} />,\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 <Heading1Icon className=\"size-4\" />;\n    case 2:\n      return <Heading2Icon className=\"size-4\" />;\n    case 3:\n      return <Heading3Icon className=\"size-4\" />;\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\";\nimport type { TranslateFn } from \"@/components/editor/i18n\";\n\nexport function ImagePickerPlugin(t: TranslateFn) {\n  return new ComponentPickerOption(t(\"picker.image\"), {\n    icon: <ImageIcon className=\"size-4\" />,\n    keywords: [\"image\", \"photo\", \"picture\", \"file\"],\n    onSelect: (_, editor, showModal) =>\n      showModal(t(\"misc.insertImage\"), (onClose) => (\n        <InsertImageDialog activeEditor={editor} onClose={onClose} />\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\";\nimport type { TranslateFn } from \"@/components/editor/i18n\";\n\nexport function NumberedListPickerPlugin(t: TranslateFn) {\n  return new ComponentPickerOption(t(\"picker.number\"), {\n    icon: <ListOrderedIcon className=\"size-4\" />,\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\";\nimport type { TranslateFn } from \"@/components/editor/i18n\";\n\nexport function ParagraphPickerPlugin(t: TranslateFn) {\n  return new ComponentPickerOption(t(\"picker.paragraph\"), {\n    icon: <TextIcon className=\"size-4\" />,\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\";\nimport type { TranslateFn } from \"@/components/editor/i18n\";\n\nexport function QuotePickerPlugin(t: TranslateFn) {\n  return new ComponentPickerOption(t(\"picker.quote\"), {\n    icon: <QuoteIcon className=\"size-4\" />,\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 type { TranslateFn } from \"@/components/editor/i18n\";\nimport { InsertTableDialog } from \"@/components/editor/plugins/table-plugin\";\n\nexport function TablePickerPlugin(t: TranslateFn) {\n  return new ComponentPickerOption(t(\"picker.table\"), {\n    icon: <TableIcon className=\"size-4\" />,\n    keywords: [\"table\", \"grid\", \"spreadsheet\", \"rows\", \"columns\"],\n    onSelect: (_, editor, showModal) =>\n      showModal(t(\"misc.insertTable\"), (onClose) => (\n        <InsertTableDialog activeEditor={editor} onClose={onClose} />\n      )),\n  });\n}\n\nexport function DynamicTablePickerPlugin({\n  queryString,\n  t,\n}: {\n  queryString: string;\n  t: TranslateFn;\n}) {\n  const options: Array<ComponentPickerOption> = [];\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(\n            `${rows}x${columns} ${t(\"picker.table\").toLowerCase()}`,\n            {\n              icon: <i className=\"icon table\" />,\n              keywords: [\"table\"],\n              onSelect: (_, editor) =>\n                editor.dispatchCommand(INSERT_TABLE_COMMAND, { columns, rows }),\n            },\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      <div className=\"grid gap-2\">\n        <Label htmlFor=\"table-modal-rows\">Rows</Label>\n        <Input\n          id=\"table-modal-rows\"\n          placeholder=\"# of rows (1-500)\"\n          onChange={(e) => setRows(e.target.value)}\n          value={rows}\n          data-test-id=\"table-modal-rows\"\n          type=\"number\"\n        />\n      </div>\n      <div className=\"grid gap-2\">\n        <Label htmlFor=\"table-modal-columns\">Columns</Label>\n        <Input\n          id=\"table-modal-columns\"\n          placeholder=\"# of columns (1-50)\"\n          onChange={(e) => setColumns(e.target.value)}\n          value={columns}\n          data-test-id=\"table-modal-columns\"\n          type=\"number\"\n        />\n      </div>\n      <DialogFooter data-test-id=\"table-model-confirm-insert\">\n        <Button disabled={isDisabled} onClick={onClick}>\n          Confirm\n        </Button>\n      </DialogFooter>\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  { labelKey: string; icon: React.ReactNode }\n> = {\n  paragraph: {\n    labelKey: \"toolbar.paragraph\",\n    icon: <TextIcon className=\"size-4\" />,\n  },\n  h1: {\n    labelKey: \"toolbar.Heading 1\",\n    icon: <Heading1Icon className=\"size-4\" />,\n  },\n  h2: {\n    labelKey: \"toolbar.Heading 2\",\n    icon: <Heading2Icon className=\"size-4\" />,\n  },\n  h3: {\n    labelKey: \"toolbar.Heading 3\",\n    icon: <Heading3Icon className=\"size-4\" />,\n  },\n  number: {\n    labelKey: \"toolbar.number\",\n    icon: <ListOrderedIcon className=\"size-4\" />,\n  },\n  bullet: {\n    labelKey: \"toolbar.bullet\",\n    icon: <ListIcon className=\"size-4\" />,\n  },\n  check: {\n    labelKey: \"toolbar.check\",\n    icon: <ListTodoIcon className=\"size-4\" />,\n  },\n  code: {\n    labelKey: \"toolbar.code\",\n    icon: <CodeIcon className=\"size-4\" />,\n  },\n  quote: {\n    labelKey: \"toolbar.quote\",\n    icon: <QuoteIcon className=\"size-4\" />,\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nconst BLOCK_FORMAT_VALUE = \"bullet\";\n\nexport function FormatBulletedList() {\n  const { activeEditor, blockType } = useToolbarContext();\n  const { t } = useEditorI18n();\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    <DropdownMenuItem onClick={formatBulletedList}>\n      <div className=\"flex items-center gap-1 font-normal\">\n        {blockTypeToBlockName[BLOCK_FORMAT_VALUE].icon}\n        {t(blockTypeToBlockName[BLOCK_FORMAT_VALUE].labelKey)}\n      </div>\n    </DropdownMenuItem>\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nconst BLOCK_FORMAT_VALUE = \"check\";\n\nexport function FormatCheckList() {\n  const { activeEditor, blockType } = useToolbarContext();\n  const { t } = useEditorI18n();\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    <DropdownMenuItem onClick={formatCheckList}>\n      <div className=\"flex items-center gap-1 font-normal\">\n        {blockTypeToBlockName[BLOCK_FORMAT_VALUE].icon}\n        {t(blockTypeToBlockName[BLOCK_FORMAT_VALUE].labelKey)}\n      </div>\n    </DropdownMenuItem>\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nconst BLOCK_FORMAT_VALUE = \"code\";\n\nexport function FormatCodeBlock() {\n  const { activeEditor, blockType } = useToolbarContext();\n  const { t } = useEditorI18n();\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    <DropdownMenuItem onClick={formatCode}>\n      <div className=\"flex items-center gap-1 font-normal\">\n        {blockTypeToBlockName[BLOCK_FORMAT_VALUE].icon}\n        {t(blockTypeToBlockName[BLOCK_FORMAT_VALUE].labelKey)}\n      </div>\n    </DropdownMenuItem>\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nexport function FormatHeading({ levels = [] }: { levels: HeadingTagType[] }) {\n  const { activeEditor, blockType } = useToolbarContext();\n  const { t } = useEditorI18n();\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    <DropdownMenuItem key={level} onClick={() => formatHeading(level)}>\n      <div className=\"flex items-center gap-1 font-normal\">\n        {blockTypeToBlockName[level].icon}\n        {t(blockTypeToBlockName[level].labelKey)}\n      </div>\n    </DropdownMenuItem>\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nconst BLOCK_FORMAT_VALUE = \"number\";\n\nexport function FormatNumberedList() {\n  const { activeEditor, blockType } = useToolbarContext();\n  const { t } = useEditorI18n();\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    <DropdownMenuItem onClick={formatNumberedList}>\n      <div className=\"flex items-center gap-1 font-normal\">\n        {blockTypeToBlockName[BLOCK_FORMAT_VALUE].icon}\n        {t(blockTypeToBlockName[BLOCK_FORMAT_VALUE].labelKey)}\n      </div>\n    </DropdownMenuItem>\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nconst BLOCK_FORMAT_VALUE = \"paragraph\";\n\nexport function FormatParagraph() {\n  const { activeEditor } = useToolbarContext();\n  const { t } = useEditorI18n();\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    <DropdownMenuItem onClick={formatParagraph}>\n      <div className=\"flex items-center gap-1 font-normal\">\n        {blockTypeToBlockName[BLOCK_FORMAT_VALUE].icon}\n        {t(blockTypeToBlockName[BLOCK_FORMAT_VALUE].labelKey)}\n      </div>\n    </DropdownMenuItem>\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nconst BLOCK_FORMAT_VALUE = \"quote\";\n\nexport function FormatQuote() {\n  const { activeEditor, blockType } = useToolbarContext();\n  const { t } = useEditorI18n();\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    <DropdownMenuItem onClick={formatQuote}>\n      <div className=\"flex items-center gap-1 font-normal\">\n        {blockTypeToBlockName[BLOCK_FORMAT_VALUE].icon}\n        {t(blockTypeToBlockName[BLOCK_FORMAT_VALUE].labelKey)}\n      </div>\n    </DropdownMenuItem>\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\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<ListNode>(\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 { labelKey, icon } =\n    blockTypeToBlockName[blockType] ?? blockTypeToBlockName.paragraph;\n  const { t } = useEditorI18n();\n\n  return (\n    <DropdownMenu>\n      <DropdownMenuTrigger asChild>\n        <Button variant=\"ghost\" className=\"h-7 gap-1 px-2\" size=\"sm\">\n          {icon}\n          <span className=\"text-sm\">{t(labelKey)}</span>\n          <ChevronDownIcon className=\"size-3\" />\n        </Button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent className=\"w-48\" align=\"start\">\n        {children}\n      </DropdownMenuContent>\n    </DropdownMenu>\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nexport function InsertColumnsLayout() {\n  const { activeEditor, showModal } = useToolbarContext();\n  const { t } = useEditorI18n();\n\n  return (\n    <DropdownMenuItem\n      onClick={() =>\n        showModal(t(\"misc.insertColumns\"), (onClose) => (\n          <InsertLayoutDialog activeEditor={activeEditor} onClose={onClose} />\n        ))\n      }\n    >\n      <div className=\"flex items-center gap-1\">\n        <Columns3Icon className=\"size-4\" />\n        <span>{t(\"picker.columns\")}</span>\n      </div>\n    </DropdownMenuItem>\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    <DropdownMenuItem\n      key={embedConfig.type}\n      onClick={() => {\n        activeEditor.dispatchCommand(INSERT_EMBED_COMMAND, embedConfig.type);\n      }}\n    >\n      <div className=\"flex items-center gap-1\">\n        {embedConfig.icon}\n        <span>{embedConfig.contentName}</span>\n      </div>\n    </DropdownMenuItem>\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 { useEditorI18n } from \"@/components/editor/i18n\";\nimport { useToolbarContext } from \"@/components/editor/context/toolbar-context\";\nimport { DropdownMenuItem } from \"@/components/ui/dropdown-menu\";\n\nexport function InsertHorizontalRule() {\n  const { activeEditor } = useToolbarContext();\n  const { t } = useEditorI18n();\n\n  return (\n    <DropdownMenuItem\n      onClick={() =>\n        activeEditor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined)\n      }\n    >\n      <div className=\"flex items-center gap-1\">\n        <ScissorsIcon className=\"size-4\" />\n        <span>{t(\"picker.divider\")}</span>\n      </div>\n    </DropdownMenuItem>\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nexport function InsertImage() {\n  const { activeEditor, showModal } = useToolbarContext();\n  const { t } = useEditorI18n();\n\n  return (\n    <DropdownMenuItem\n      onClick={() => {\n        showModal(t(\"misc.insertImage\"), (onClose) => (\n          <InsertImageDialog activeEditor={activeEditor} onClose={onClose} />\n        ));\n      }}\n    >\n      <div className=\"flex items-center gap-1\">\n        <ImageIcon className=\"size-4\" />\n        <span>{t(\"picker.image\")}</span>\n      </div>\n    </DropdownMenuItem>\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nexport function InsertTable() {\n  const { activeEditor, showModal } = useToolbarContext();\n  const { t } = useEditorI18n();\n\n  return (\n    <DropdownMenuItem\n      onClick={() =>\n        showModal(t(\"misc.insertTable\"), (onClose) => (\n          <InsertTableDialog activeEditor={activeEditor} onClose={onClose} />\n        ))\n      }\n    >\n      <div className=\"flex items-center gap-1\">\n        <TableIcon className=\"size-4\" />\n        <span>{t(\"picker.table\")}</span>\n      </div>\n    </DropdownMenuItem>\n  );\n}\n",
      "type": "registry:component"
    },
    {
      "path": "src/components/editor/plugins/toolbar/block-insert-plugin.tsx",
      "content": "import { PlusIcon } from \"lucide-react\";\n\nimport { useEditorI18n } from \"@/components/editor/i18n\";\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  const { t } = useEditorI18n();\n\n  return (\n    <>\n      {modal}\n      <DropdownMenu>\n        <DropdownMenuTrigger asChild>\n          <Button variant=\"ghost\" size=\"sm\" className=\"h-7 gap-1 px-2\">\n            <PlusIcon className=\"size-4\" />\n            <span className=\"text-sm\">{t(\"toolbar.insert\")}</span>\n          </Button>\n        </DropdownMenuTrigger>\n        <DropdownMenuContent className=\"w-fit\">{children}</DropdownMenuContent>\n      </DropdownMenu>\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nexport function ClearFormattingToolbarPlugin() {\n  const { activeEditor } = useToolbarContext();\n  const { t } = useEditorI18n();\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    <Button\n      aria-label={t(\"toolbar.clearFormatting\")}\n      variant=\"ghost\"\n      size=\"icon\"\n      className=\"size-7\"\n      onClick={clearFormatting}\n    >\n      <EraserIcon className=\"size-4\" />\n    </Button>\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  getCodeLanguageOptions,\n  normalizeCodeLanguage,\n} from \"@lexical/code-shiki\";\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nconst CODE_LANGUAGE_OPTIONS = getCodeLanguageOptions();\n\nexport function CodeLanguageToolbarPlugin() {\n  const { activeEditor } = useToolbarContext();\n  const { t } = useEditorI18n();\n  const [codeLanguage, setCodeLanguage] = useState<string>(\"\");\n  const [selectedElementKey, setSelectedElementKey] = useState<string | null>(\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          setCodeLanguage(normalizeCodeLanguage(element.getLanguage() || \"\"));\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      setCodeLanguage(value);\n    },\n    [activeEditor, selectedElementKey],\n  );\n\n  return (\n    <Select value={codeLanguage} onValueChange={onCodeLanguageSelect}>\n      <SelectTrigger\n        size=\"sm\"\n        className=\"border-none bg-transparent shadow-none hover:bg-muted dark:bg-transparent dark:hover:bg-muted\"\n        onMouseDown={(e) => e.stopPropagation()}\n      >\n        <SelectValue placeholder={t(\"toolbar.selectLanguage\")} />\n      </SelectTrigger>\n      <SelectContent onCloseAutoFocus={(event) => event.preventDefault()}>\n        {CODE_LANGUAGE_OPTIONS.map(([value, label]) => (\n          <SelectItem key={value} value={value}>\n            {label}\n          </SelectItem>\n        ))}\n      </SelectContent>\n    </Select>\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 { Button } from \"@/components/ui/button\";\nimport { Separator } from \"@/components/ui/separator\";\nimport { ToggleGroup, ToggleGroupItem } from \"@/components/ui/toggle-group\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nconst ELEMENT_FORMAT_OPTIONS: {\n  [key in Exclude<ElementFormatType, \"start\" | \"end\" | \"\">]: {\n    icon: React.ReactNode;\n    iconRTL: string;\n    nameKey: string;\n  };\n} = {\n  left: {\n    icon: <AlignLeftIcon className=\"size-4\" />,\n    iconRTL: \"left-align\",\n    nameKey: \"toolbar.left\",\n  },\n  center: {\n    icon: <AlignCenterIcon className=\"size-4\" />,\n    iconRTL: \"center-align\",\n    nameKey: \"toolbar.center\",\n  },\n  right: {\n    icon: <AlignRightIcon className=\"size-4\" />,\n    iconRTL: \"right-align\",\n    nameKey: \"toolbar.right\",\n  },\n  justify: {\n    icon: <AlignJustifyIcon className=\"size-4\" />,\n    iconRTL: \"justify-align\",\n    nameKey: \"toolbar.justify\",\n  },\n} as const;\n\nexport function ElementFormatToolbarPlugin({\n  separator = true,\n}: {\n  separator?: boolean;\n}) {\n  const { activeEditor } = useToolbarContext();\n  const { t } = useEditorI18n();\n  const [elementFormat, setElementFormat] = useState<ElementFormatType>(\"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    activeEditor.dispatchCommand(\n      FORMAT_ELEMENT_COMMAND,\n      value as ElementFormatType,\n    );\n  };\n\n  return (\n    <>\n      <ToggleGroup\n        type=\"single\"\n        value={elementFormat}\n        onValueChange={handleValueChange}\n      >\n        {/* Alignment toggles */}\n        {Object.entries(ELEMENT_FORMAT_OPTIONS).map(([value, option]) => (\n          <ToggleGroupItem\n            key={value}\n            value={value}\n            variant=\"default\"\n            size=\"sm\"\n            className=\"size-7 min-w-7\"\n            aria-label={t(option.nameKey)}\n          >\n            {option.icon}\n          </ToggleGroupItem>\n        ))}\n      </ToggleGroup>\n      {separator && (\n        <Separator orientation=\"vertical\" className=\"h-6 my-auto\" />\n      )}\n      {/* Indentation toggles */}\n      <div className=\"flex items-center\">\n        <Button\n          type=\"button\"\n          aria-label={t(\"toolbar.outdent\")}\n          variant=\"ghost\"\n          size=\"icon\"\n          className=\"size-7 min-w-7\"\n          onClick={() =>\n            activeEditor.dispatchCommand(OUTDENT_CONTENT_COMMAND, undefined)\n          }\n        >\n          <IndentDecreaseIcon className=\"size-4\" />\n        </Button>\n\n        <Button\n          type=\"button\"\n          aria-label={t(\"toolbar.indent\")}\n          variant=\"ghost\"\n          size=\"icon\"\n          className=\"size-7 min-w-7\"\n          onClick={() =>\n            activeEditor.dispatchCommand(INDENT_CONTENT_COMMAND, undefined)\n          }\n        >\n          <IndentIncreaseIcon className=\"size-4\" />\n        </Button>\n      </div>\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 getComputedStyle(document.documentElement).backgroundColor || \"#fff\";\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<string, string>, _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    <ColorPicker\n      modal\n      defaultFormat=\"hex\"\n      defaultValue={bgColor}\n      onValueChange={onBgColorSelect}\n      onOpenChange={(open) => {\n        if (!open) {\n          activeEditor.setEditable(true);\n          activeEditor.focus();\n        }\n      }}\n    >\n      <ColorPickerTrigger>\n        <Button variant=\"ghost\" size=\"icon\" className=\"size-7\">\n          <PaintBucketIcon className=\"size-4\" />\n        </Button>\n      </ColorPickerTrigger>\n      <ColorPickerContent>\n        <ColorPickerArea />\n        <div className=\"flex items-center gap-2\">\n          <ColorPickerEyeDropper />\n          <div className=\"flex flex-1 flex-col gap-4\">\n            <ColorPickerHueSlider />\n            <ColorPickerAlphaSlider />\n          </div>\n        </div>\n        <div className=\"flex items-center gap-2\">\n          <ColorPickerFormatSelect />\n          <ColorPickerInput />\n        </div>\n      </ColorPickerContent>\n    </ColorPicker>\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 getComputedStyle(document.documentElement).color || \"#000\";\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<string, string>) => {\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    <ColorPicker\n      modal\n      defaultFormat=\"hex\"\n      defaultValue={fontColor}\n      onValueChange={onFontColorSelect}\n      onOpenChange={(open) => {\n        if (!open) {\n          activeEditor.setEditable(true);\n          activeEditor.focus();\n        }\n      }}\n    >\n      <ColorPickerTrigger>\n        <Button variant=\"ghost\" size=\"icon\" className=\"size-7\">\n          <BaselineIcon className=\"size-4\" />\n        </Button>\n      </ColorPickerTrigger>\n      <ColorPickerContent>\n        <ColorPickerArea />\n        <div className=\"flex items-center gap-2\">\n          <ColorPickerEyeDropper />\n          <div className=\"flex flex-1 flex-col gap-4\">\n            <ColorPickerHueSlider />\n            <ColorPickerAlphaSlider />\n          </div>\n        </div>\n        <div className=\"flex items-center gap-2\">\n          <ColorPickerFormatSelect />\n          <ColorPickerInput />\n        </div>\n      </ColorPickerContent>\n    </ColorPicker>\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    <DropdownMenu>\n      <DropdownMenuTrigger asChild>\n        <Button\n            variant=\"ghost\"\n            className=\"h-7 w-min gap-1 px-2\"\n            size=\"sm\"\n            aria-label={buttonAriaLabel}\n          >\n            <TypeIcon className=\"size-4\" />\n            <span style={{ fontFamily }}>{fontFamily}</span>\n            <ChevronDownIcon className=\"size-3\" />\n        </Button>\n      </DropdownMenuTrigger>\n      <DropdownMenuContent className=\"w-48\" align=\"start\">\n        {FONT_FAMILY_OPTIONS.map((option) => (\n          <DropdownMenuItem\n            key={option}\n            style={{ fontFamily: option }}\n            onClick={() => handleClick(option)}\n          >\n            {option}\n          </DropdownMenuItem>\n        ))}\n      </DropdownMenuContent>\n    </DropdownMenu>\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nconst FORMATS = [\n  { format: \"bold\", icon: BoldIcon, labelKey: \"toolbar.bold\" },\n  { format: \"italic\", icon: ItalicIcon, labelKey: \"toolbar.italic\" },\n  { format: \"underline\", icon: UnderlineIcon, labelKey: \"toolbar.underline\" },\n  {\n    format: \"strikethrough\",\n    icon: StrikethroughIcon,\n    labelKey: \"toolbar.strikethrough\",\n  },\n] as const;\n\nexport function FontFormatToolbarPlugin() {\n  const { activeEditor } = useToolbarContext();\n  const { t } = useEditorI18n();\n  const [activeFormats, setActiveFormats] = useState<string[]>([]);\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    <ToggleGroup\n      type=\"multiple\"\n      value={activeFormats}\n      onValueChange={setActiveFormats}\n      variant=\"default\"\n      size=\"sm\"\n    >\n      {FORMATS.map(({ format, icon: Icon, labelKey }) => (\n        <ToggleGroupItem\n          key={format}\n          value={format}\n          aria-label={t(labelKey)}\n          className=\"size-7 min-w-7\"\n          onClick={() => {\n            activeEditor.dispatchCommand(\n              FORMAT_TEXT_COMMAND,\n              format as TextFormatType,\n            );\n          }}\n        >\n          <Icon className=\"size-4\" />\n        </ToggleGroupItem>\n      ))}\n    </ToggleGroup>\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 {\n  InputGroup,\n  InputGroupAddon,\n  InputGroupButton,\n  InputGroupInput,\n} from \"@/components/ui/input-group\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\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  const { t } = useEditorI18n();\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    <InputGroup className=\"h-7 w-fit border-none shadow-none dark:bg-transparent\">\n      <InputGroupAddon align=\"inline-start\" className=\"pl-1\">\n        <InputGroupButton\n          variant=\"ghost\"\n          size=\"icon-xs\"\n          className=\"size-7\"\n          aria-label={t(\"toolbar.decreaseFontSize\")}\n          onClick={() => updateFontSize(fontSize - 1)}\n          disabled={fontSize <= MIN_FONT_SIZE}\n        >\n          <Minus className=\"size-3\" />\n        </InputGroupButton>\n      </InputGroupAddon>\n      <InputGroupInput\n        value={fontSize}\n        onChange={(e) =>\n          updateFontSize(parseInt(e.target.value) || DEFAULT_FONT_SIZE)\n        }\n        className=\"w-10 px-0 text-center\"\n        min={MIN_FONT_SIZE}\n        max={MAX_FONT_SIZE}\n      />\n      <InputGroupAddon align=\"inline-end\" className=\"pr-1\">\n        <InputGroupButton\n          variant=\"ghost\"\n          size=\"icon-xs\"\n          className=\"size-7\"\n          aria-label={t(\"toolbar.increaseFontSize\")}\n          onClick={() => updateFontSize(fontSize + 1)}\n          disabled={fontSize >= MAX_FONT_SIZE}\n        >\n          <Plus className=\"size-3\" />\n        </InputGroupButton>\n      </InputGroupAddon>\n    </InputGroup>\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 { useEditorI18n } from \"@/components/editor/i18n\";\n\nexport function HistoryToolbarPlugin() {\n  const [editor] = useLexicalComposerContext();\n  const { activeEditor, $updateToolbar } = useToolbarContext();\n  const { t } = useEditorI18n();\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<boolean>(\n        CAN_UNDO_COMMAND,\n        (payload) => {\n          setCanUndo(payload);\n          return false;\n        },\n        COMMAND_PRIORITY_CRITICAL,\n      ),\n      activeEditor.registerCommand<boolean>(\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    <div className=\"flex items-center gap-0.5\">\n      <Button\n        disabled={!canUndo || !isEditable}\n        onClick={() => {\n          activeEditor.dispatchCommand(UNDO_COMMAND, undefined);\n        }}\n        title={\n          IS_APPLE ? t(\"toolbar.undoShortcutMac\") : t(\"toolbar.undoShortcut\")\n        }\n        type=\"button\"\n        aria-label={t(\"toolbar.undo\")}\n        size=\"icon\"\n        variant=\"ghost\"\n        className=\"size-7\"\n      >\n        <UndoIcon className=\"size-4\" />\n      </Button>\n      <Button\n        disabled={!canRedo || !isEditable}\n        onClick={() => {\n          activeEditor.dispatchCommand(REDO_COMMAND, undefined);\n        }}\n        title={\n          IS_APPLE ? t(\"toolbar.redoShortcutMac\") : t(\"toolbar.redoShortcut\")\n        }\n        type=\"button\"\n        aria-label={t(\"toolbar.redo\")}\n        variant=\"ghost\"\n        size=\"icon\"\n        className=\"size-7\"\n      >\n        <RedoIcon className=\"size-4\" />\n      </Button>\n    </div>\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nexport function LinkToolbarPlugin({\n  setIsLinkEditMode,\n}: {\n  setIsLinkEditMode: (isEditMode: boolean) => void;\n}) {\n  const { activeEditor } = useToolbarContext();\n  const { t } = useEditorI18n();\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    <Toggle\n      variant=\"default\"\n      size=\"sm\"\n      className=\"size-7 min-w-7\"\n      aria-label={t(\"toolbar.toggleLink\")}\n      onClick={insertLink}\n    >\n      <LinkIcon className=\"size-4\" />\n    </Toggle>\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\";\nimport { useEditorI18n } from \"@/components/editor/i18n\";\n\nexport function SubSuperToolbarPlugin() {\n  const { activeEditor } = useToolbarContext();\n  const { t } = useEditorI18n();\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    <ToggleGroup\n      type=\"multiple\"\n      value={isSubscript ? [\"subscript\"] : isSuperscript ? [\"superscript\"] : []}\n    >\n      <ToggleGroupItem\n        value=\"subscript\"\n        size=\"sm\"\n        className=\"size-7 min-w-7\"\n        aria-label={t(\"toolbar.toggleSubscript\")}\n        onClick={() => {\n          activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, \"subscript\");\n        }}\n        variant=\"default\"\n      >\n        <SubscriptIcon className=\"size-4\" />\n      </ToggleGroupItem>\n      <ToggleGroupItem\n        value=\"superscript\"\n        size=\"sm\"\n        className=\"size-7 min-w-7\"\n        aria-label={t(\"toolbar.toggleSuperscript\")}\n        onClick={() => {\n          activeEditor.dispatchCommand(FORMAT_TEXT_COMMAND, \"superscript\");\n        }}\n        variant=\"default\"\n      >\n        <SuperscriptIcon className=\"size-4\" />\n      </ToggleGroupItem>\n    </ToggleGroup>\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<string>(\"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    <ToolbarContext\n      activeEditor={activeEditor}\n      $updateToolbar={$updateToolbar}\n      blockType={blockType}\n      setBlockType={setBlockType}\n      showModal={showModal}\n    >\n      {modal}\n\n      {children({ blockType })}\n    </ToolbarContext>\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<typeof setTimeout> | null;\n    let keyPressTimerId: ReturnType<typeof setTimeout> | null;\n    let log: Array<DOMHighResTimeStamp> = [];\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<typeof undefined | boolean | null | string>\n): Array<string> {\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:component"
    },
    {
      "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 `![${node.getAltText()}](${node.getSrc()})`;\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<TableCellNode> | 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 `<tweet id=\"${node.getId()}\" />`;\n  },\n  regExp: /<tweet id=\"([^\"]+?)\"\\s?\\/>\\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<T = any>(\n  reader: ReadableStreamDefaultReader<T>,\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<string> {\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<string> {\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<SerializedDocument | null> {\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<Listener>;\n  handleTouchstart: (e: TouchEvent) => void;\n  handleTouchend: (e: TouchEvent) => void;\n};\n\nconst elements = new WeakMap<HTMLElement, ElementValues>();\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<Listener>();\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"
}