diff --git a/package.json b/package.json index 48a050d9..ee303c3d 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "react-i18next": "^12.2.0", "react-infinite-scroller": "1.2.6", "react-parallax-tilt": "^1.7.127", + "react-popper": "^2.3.0", "react-scroll": "1.8.9", "react-sortablejs": "6.1.4", "react-string-replace": "^1.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fd973c47..9a91c35d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -190,6 +190,9 @@ dependencies: react-parallax-tilt: specifier: ^1.7.127 version: 1.7.127(react-dom@18.2.0)(react@18.2.0) + react-popper: + specifier: ^2.3.0 + version: 2.3.0(@popperjs/core@2.11.7)(react-dom@18.2.0)(react@18.2.0) react-scroll: specifier: 1.8.9 version: 1.8.9(react-dom@18.2.0)(react@18.2.0) @@ -3195,6 +3198,7 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] requiresBuild: true optional: true @@ -3203,6 +3207,7 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] requiresBuild: true optional: true @@ -3211,6 +3216,7 @@ packages: engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] requiresBuild: true optional: true @@ -3219,6 +3225,7 @@ packages: engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] requiresBuild: true optional: true @@ -3317,6 +3324,7 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] requiresBuild: true dev: false optional: true @@ -3326,6 +3334,7 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] requiresBuild: true dev: false optional: true @@ -3335,6 +3344,7 @@ packages: engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] requiresBuild: true dev: false optional: true @@ -3344,6 +3354,7 @@ packages: engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] requiresBuild: true dev: false optional: true @@ -3445,6 +3456,10 @@ packages: resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} dev: true + /@popperjs/core@2.11.7: + resolution: {integrity: sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==} + dev: false + /@prisma/client@4.12.0(prisma@4.12.0): resolution: {integrity: sha512-j9/ighfWwux97J2dS15nqhl60tYoH8V0IuSsgZDb6bCFcQD3fXbXmxjYC8GHhIgOk3lB7Pq+8CwElz2MiDpsSg==} engines: {node: '>=14.17'} @@ -11153,6 +11168,10 @@ packages: react: 18.2.0 dev: false + /react-fast-compare@3.2.1: + resolution: {integrity: sha512-xTYf9zFim2pEif/Fw16dBiXpe0hoy5PxcD8+OwBnTtNLfIm3g6WxhKNurY+6OmdH1u6Ta/W/Vl6vjbYP1MFnDg==} + dev: false + /react-google-recaptcha@2.1.0(react@18.2.0): resolution: {integrity: sha512-K9jr7e0CWFigi8KxC3WPvNqZZ47df2RrMAta6KmRoE4RUi7Ys6NmNjytpXpg4HI/svmQJLKR+PncEPaNJ98DqQ==} peerDependencies: @@ -11240,6 +11259,20 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /react-popper@2.3.0(@popperjs/core@2.11.7)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==} + peerDependencies: + '@popperjs/core': ^2.0.0 + react: ^16.8.0 || ^17 || ^18 + react-dom: ^16.8.0 || ^17 || ^18 + dependencies: + '@popperjs/core': 2.11.7 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-fast-compare: 3.2.1 + warning: 4.0.3 + dev: false + /react-refresh@0.14.0: resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} engines: {node: '>=0.10.0'} @@ -13474,6 +13507,12 @@ packages: - zod dev: false + /warning@4.0.3: + resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==} + dependencies: + loose-envify: 1.4.0 + dev: false + /watchpack@2.4.0: resolution: {integrity: sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==} engines: {node: '>=10.13.0'} diff --git a/public/locales/zh/dashboard.json b/public/locales/zh/dashboard.json index a6fee648..5b7b3755 100644 --- a/public/locales/zh/dashboard.json +++ b/public/locales/zh/dashboard.json @@ -59,6 +59,7 @@ "Link": "链接", "Image": "图片", "Upload Image": "上传图片", + "Mention": "提及", "Tip: xLog Flavored Markdown": "提示:xLog 风格的 Markdown", "Preview": "预览", "Publish": "发布", diff --git a/src/components/common/EmojiPicker.tsx b/src/components/common/EmojiPicker.tsx index d510af90..9975f708 100644 --- a/src/components/common/EmojiPicker.tsx +++ b/src/components/common/EmojiPicker.tsx @@ -1,23 +1,36 @@ import { useEffect, useRef } from "react" +import { useTranslation } from "react-i18next" +import { useMediaStore } from "~/hooks/useDarkMode" export const EmojiPicker: React.FC<{ onEmojiSelect: (e: any) => void }> = ({ onEmojiSelect }) => { const ref = useRef() + const isDark = useMediaStore((state) => state.isDark) useEffect(() => { import("emoji-mart").then(async (EmojiMart) => { const response = await fetch( - "https://cdn.jsdelivr.net/npm/@emoji-mart/data", + "https://fastly.jsdelivr.net/npm/@emoji-mart/data", ) const data = await response.json() new EmojiMart.Picker({ onEmojiSelect, data, ref, + theme: isDark ? "dark" : "light", }) }) }, []) - return
+ const { t } = useTranslation("common") + + return ( +
+ {t("Loading")} +
+ ) } diff --git a/src/components/ui/EditorToolbar.tsx b/src/components/ui/EditorToolbar.tsx index aaa8caeb..75f09be9 100644 --- a/src/components/ui/EditorToolbar.tsx +++ b/src/components/ui/EditorToolbar.tsx @@ -1,9 +1,11 @@ import { EditorView } from "@codemirror/view" import { cn } from "~/lib/utils" -import { Dispatch, FC, SetStateAction } from "react" +import { FC, useState } from "react" import { ICommand } from "~/editor" import { Tooltip } from "./Tooltip" import { useTranslation } from "next-i18next" +import { Popover } from "@headlessui/react" +import { usePopper } from "react-popper" export interface EditorToolbarProps { view?: EditorView @@ -15,22 +17,97 @@ enum ToolbarMode { Preview, } +type IToolbarItemProps = ICommand & { view?: EditorView } + +const ToolbarItemWithPopover: FC = ({ + name, + icon, + label, + execute, + ui, + view, +}) => { + const { t } = useTranslation("dashboard") + let [popoverButtonRef, setPopoverButtonRef] = + useState(null) + let [popoverPanelRef, setPopoverPanelRef] = useState( + null, + ) + let { styles: popStyles, attributes: popAttributes } = usePopper( + popoverButtonRef, + popoverPanelRef, + ) + return ( + + {({ open }: { open: boolean }) => ( + <> + + + + + + + {ui && + view && + ui({ + transferPayload: (payload) => { + view && + execute({ + view, + options: { container: view.dom }, + payload, + }) + }, + view, + })} + + + )} + + ) +} + export const EditorToolbar: FC = ({ view, toolbars }) => { const { t } = useTranslation("dashboard") const renderToolbar = (mode: ToolbarMode) => // eslint-disable-next-line react/display-name - ({ name, icon, label, execute }: ICommand) => { - return ( + ({ name, icon, label, execute, ui }: ICommand) => { + return ui ? ( + + ) : (