From 518cc494f7d042eff93ece1a04083111c1d64680 Mon Sep 17 00:00:00 2001 From: lixiaoshu Date: Mon, 2 Jun 2025 21:12:49 +0800 Subject: [PATCH 1/4] feat: Migrate the blob module code and refactor the breadcrumb and tree component module code. --- .../CodeView/TreeView/BreadCrumb.tsx | 50 +- .../CodeView/TreeView/CloneTabs.tsx | 8 +- .../components/CodeView/TreeView/RepoTree.tsx | 471 +++++---- .../CodeView/TreeView/editor/Editor.js | 74 ++ .../CodeView/TreeView/editor/ExampleTheme.js | 69 ++ .../CodeView/TreeView/editor/ToolbarPlugin.js | 609 +++++++++++ .../CodeView/TreeView/editor/editor.css | 721 +++++++++++++ .../CodeView/blobView/CodeContent.module.css | 34 + .../CodeView/blobView/CodeContent.tsx | 118 +++ moon/apps/web/package.json | 5 + .../pages/[org]/code/blob/[...path]/index.tsx | 90 ++ .../pages/[org]/code/tree/[...path]/index.tsx | 2 +- moon/pnpm-lock.yaml | 988 +++++++++++------- 13 files changed, 2642 insertions(+), 597 deletions(-) create mode 100644 moon/apps/web/components/CodeView/TreeView/editor/Editor.js create mode 100644 moon/apps/web/components/CodeView/TreeView/editor/ExampleTheme.js create mode 100644 moon/apps/web/components/CodeView/TreeView/editor/ToolbarPlugin.js create mode 100644 moon/apps/web/components/CodeView/TreeView/editor/editor.css create mode 100644 moon/apps/web/components/CodeView/blobView/CodeContent.module.css create mode 100644 moon/apps/web/components/CodeView/blobView/CodeContent.tsx create mode 100644 moon/apps/web/pages/[org]/code/blob/[...path]/index.tsx diff --git a/moon/apps/web/components/CodeView/TreeView/BreadCrumb.tsx b/moon/apps/web/components/CodeView/TreeView/BreadCrumb.tsx index d6cd35cd6..ec8ff5b69 100644 --- a/moon/apps/web/components/CodeView/TreeView/BreadCrumb.tsx +++ b/moon/apps/web/components/CodeView/TreeView/BreadCrumb.tsx @@ -1,29 +1,47 @@ +import React from 'react'; import 'github-markdown-css/github-markdown-light.css' -import { Breadcrumb } from 'antd' import { useRouter } from 'next/router'; +import { BreadcrumbLabel } from '@/components/Titlebar/BreadcrumbTitlebar' +import { Link } from '@gitmono/ui' +import { UrlObject } from 'url'; const Bread = ({ path }:any) => { const router = useRouter(); const scope = router.query.org as string - const breadCrumbItems = path?.map((sub_path: any, index: number) => { - if (index == path?.length - 1) { - return { - title: sub_path, - }; - } else { - const href = `/${scope}/code/tree/${path?.slice(0, index + 1).join('/')}`; + const breadCrumbItems = path?.map((subPath: any, index: number) => { + const href = `/${scope}/code/tree/${path.slice(0, index + 1).join('/')}`; - return { - title: sub_path, - href: href, - }; - } - }); + return { + title: subPath, + href: href, + isLast: index === path.length - 1, + }; + }); return ( -
- +
+ + {breadCrumbItems?.map((item: { isLast: any; title: string; href: string | UrlObject; }, index: number) => ( + + {/* displayed after the home item and before non-last items */} + {index > 0 && ( + / + )} + {/* Current breadcrumb item */} + {item.isLast ? ( + // last item + + {item?.title} + + ) : ( + // middle item + + {item?.title} + + )} + +))}
); }; diff --git a/moon/apps/web/components/CodeView/TreeView/CloneTabs.tsx b/moon/apps/web/components/CodeView/TreeView/CloneTabs.tsx index fe277d400..e7a0b66c4 100644 --- a/moon/apps/web/components/CodeView/TreeView/CloneTabs.tsx +++ b/moon/apps/web/components/CodeView/TreeView/CloneTabs.tsx @@ -1,8 +1,8 @@ import React, { useEffect, useState } from 'react'; import { Tabs, TabsProps, Button, Space, Popover, Input } from 'antd'; import copy from 'copy-to-clipboard'; -import {CopyIcon, AlarmCheckIcon, DownloadIcon} from '@gitmono/ui/Icons' -// import { CopyOutlined, CheckOutlined, DownloadOutlined } from '@ant-design/icons'; +import {CopyIcon, DownloadIcon} from '@gitmono/ui/Icons' +import { CheckOutlined, } from '@ant-design/icons'; import { usePathname } from 'next/navigation'; @@ -43,7 +43,7 @@ const CloneTabs = ({ endpoint }:any) => { children: - + + + + + + +
+ ); +} + +export default function ToolbarPlugin() { + const [editor] = useLexicalComposerContext(); + const toolbarRef = useRef(null); + const [blockType, setBlockType] = useState("paragraph"); + const [selectedElementKey, setSelectedElementKey] = useState(null); + const [showBlockOptionsDropDown, setShowBlockOptionsDropDown] = useState( + false + ); + const [codeLanguage, setCodeLanguage] = useState(""); + const [isLink, setIsLink] = useState(false); + const [isBold, setIsBold] = useState(false); + const [isItalic, setIsItalic] = useState(false); + const [isUnderline, setIsUnderline] = useState(false); + const [isStrikethrough, setIsStrikethrough] = useState(false); + const [isCode, setIsCode] = useState(false); + + const updateToolbar = useCallback(() => { + const selection = $getSelection(); + if ($isRangeSelection(selection)) { + const anchorNode = selection.anchor.getNode(); + const element = + anchorNode.getKey() === "root" + ? anchorNode + : anchorNode.getTopLevelElementOrThrow(); + const elementKey = element.getKey(); + const elementDOM = editor.getElementByKey(elementKey); + if (elementDOM !== null) { + setSelectedElementKey(elementKey); + if ($isListNode(element)) { + const parentList = $getNearestNodeOfType(anchorNode, ListNode); + const type = parentList ? parentList.getTag() : element.getTag(); + setBlockType(type); + } else { + const type = $isHeadingNode(element) + ? element.getTag() + : element.getType(); + setBlockType(type); + if ($isCodeNode(element)) { + setCodeLanguage(element.getLanguage() || getDefaultCodeLanguage()); + } + } + } + // Update text format + setIsBold(selection.hasFormat("bold")); + setIsItalic(selection.hasFormat("italic")); + setIsUnderline(selection.hasFormat("underline")); + setIsStrikethrough(selection.hasFormat("strikethrough")); + setIsCode(selection.hasFormat("code")); + + // Update links + const node = getSelectedNode(selection); + const parent = node.getParent(); + if ($isLinkNode(parent) || $isLinkNode(node)) { + setIsLink(true); + } else { + setIsLink(false); + } + } + }, [editor]); + + useEffect(() => { + return mergeRegister( + editor.registerUpdateListener(({ editorState }) => { + editorState.read(() => { + updateToolbar(); + }); + }), + editor.registerCommand( + SELECTION_CHANGE_COMMAND, + (_payload, newEditor) => { + updateToolbar(); + return false; + }, + LowPriority + ) + ); + }, [editor, updateToolbar]); + + const codeLanguges = useMemo(() => getCodeLanguages(), []); + const onCodeLanguageSelect = useCallback( + (e) => { + editor.update(() => { + if (selectedElementKey !== null) { + const node = $getNodeByKey(selectedElementKey); + if ($isCodeNode(node)) { + node.setLanguage(e.target.value); + } + } + }); + }, + [editor, selectedElementKey] + ); + + const insertLink = useCallback(() => { + if (!isLink) { + editor.dispatchCommand(TOGGLE_LINK_COMMAND, "https://"); + } else { + editor.dispatchCommand(TOGGLE_LINK_COMMAND, null); + } + }, [editor, isLink]); + + return ( +
+ {supportedBlockTypes.has(blockType) && ( + <> + + {showBlockOptionsDropDown && + createPortal( + , + document.body + )} + + + )} + {blockType === "code" ? ( + <> + { - setLinkUrl(event.target.value); - }} - onKeyDown={(event) => { - if (event.key === "Enter") { - event.preventDefault(); - if (lastSelection !== null) { - if (linkUrl !== "") { - editor.dispatchCommand(TOGGLE_LINK_COMMAND, linkUrl); - } - setEditMode(false); - } - } else if (event.key === "Escape") { - event.preventDefault(); - setEditMode(false); - } - }} - /> - ) : ( - <> -
- - {linkUrl} - -
event.preventDefault()} - onClick={() => { - setEditMode(true); - }} - /> -
- - )} -
- ); -} - -function Select({ onChange, className, options, value }) { - return ( - - ); -} - -function getSelectedNode(selection) { - const anchor = selection.anchor; - const focus = selection.focus; - const anchorNode = selection.anchor.getNode(); - const focusNode = selection.focus.getNode(); - if (anchorNode === focusNode) { - return anchorNode; - } - const isBackward = selection.isBackward(); - if (isBackward) { - return $isAtNodeEnd(focus) ? anchorNode : focusNode; - } else { - return $isAtNodeEnd(anchor) ? focusNode : anchorNode; - } -} - -function BlockOptionsDropdownList({ - editor, - blockType, - toolbarRef, - setShowBlockOptionsDropDown -}) { - const dropDownRef = useRef(null); - - useEffect(() => { - const toolbar = toolbarRef.current; - const dropDown = dropDownRef.current; - - if (toolbar !== null && dropDown !== null) { - const { top, left } = toolbar.getBoundingClientRect(); - dropDown.style.top = `${top + 40}px`; - dropDown.style.left = `${left}px`; - } - }, [dropDownRef, toolbarRef]); - - useEffect(() => { - const dropDown = dropDownRef.current; - const toolbar = toolbarRef.current; - - if (dropDown !== null && toolbar !== null) { - const handle = (event) => { - const target = event.target; - - if (!dropDown.contains(target) && !toolbar.contains(target)) { - setShowBlockOptionsDropDown(false); - } - }; - document.addEventListener("click", handle); - - return () => { - document.removeEventListener("click", handle); - }; - } - }, [dropDownRef, setShowBlockOptionsDropDown, toolbarRef]); - - const formatParagraph = () => { - if (blockType !== "paragraph") { - editor.update(() => { - const selection = $getSelection(); - - if ($isRangeSelection(selection)) { - $wrapNodes(selection, () => $createParagraphNode()); - } - }); - } - setShowBlockOptionsDropDown(false); - }; - - const formatLargeHeading = () => { - if (blockType !== "h1") { - editor.update(() => { - const selection = $getSelection(); - - if ($isRangeSelection(selection)) { - $wrapNodes(selection, () => $createHeadingNode("h1")); - } - }); - } - setShowBlockOptionsDropDown(false); - }; - - const formatSmallHeading = () => { - if (blockType !== "h2") { - editor.update(() => { - const selection = $getSelection(); - - if ($isRangeSelection(selection)) { - $wrapNodes(selection, () => $createHeadingNode("h2")); - } - }); - } - setShowBlockOptionsDropDown(false); - }; - - const formatBulletList = () => { - if (blockType !== "ul") { - editor.dispatchCommand(INSERT_UNORDERED_LIST_COMMAND); - } else { - editor.dispatchCommand(REMOVE_LIST_COMMAND); - } - setShowBlockOptionsDropDown(false); - }; - - const formatNumberedList = () => { - if (blockType !== "ol") { - editor.dispatchCommand(INSERT_ORDERED_LIST_COMMAND); - } else { - editor.dispatchCommand(REMOVE_LIST_COMMAND); - } - setShowBlockOptionsDropDown(false); - }; - - const formatQuote = () => { - if (blockType !== "quote") { - editor.update(() => { - const selection = $getSelection(); - - if ($isRangeSelection(selection)) { - $wrapNodes(selection, () => $createQuoteNode()); - } - }); - } - setShowBlockOptionsDropDown(false); - }; - - const formatCode = () => { - if (blockType !== "code") { - editor.update(() => { - const selection = $getSelection(); - - if ($isRangeSelection(selection)) { - $wrapNodes(selection, () => $createCodeNode()); - } - }); - } - setShowBlockOptionsDropDown(false); - }; - - return ( -
- - - - - - - -
- ); -} - -export default function ToolbarPlugin() { - const [editor] = useLexicalComposerContext(); - const toolbarRef = useRef(null); - const [blockType, setBlockType] = useState("paragraph"); - const [selectedElementKey, setSelectedElementKey] = useState(null); - const [showBlockOptionsDropDown, setShowBlockOptionsDropDown] = useState( - false - ); - const [codeLanguage, setCodeLanguage] = useState(""); - const [isLink, setIsLink] = useState(false); - const [isBold, setIsBold] = useState(false); - const [isItalic, setIsItalic] = useState(false); - const [isUnderline, setIsUnderline] = useState(false); - const [isStrikethrough, setIsStrikethrough] = useState(false); - const [isCode, setIsCode] = useState(false); - - const updateToolbar = useCallback(() => { - const selection = $getSelection(); - if ($isRangeSelection(selection)) { - const anchorNode = selection.anchor.getNode(); - const element = - anchorNode.getKey() === "root" - ? anchorNode - : anchorNode.getTopLevelElementOrThrow(); - const elementKey = element.getKey(); - const elementDOM = editor.getElementByKey(elementKey); - if (elementDOM !== null) { - setSelectedElementKey(elementKey); - if ($isListNode(element)) { - const parentList = $getNearestNodeOfType(anchorNode, ListNode); - const type = parentList ? parentList.getTag() : element.getTag(); - setBlockType(type); - } else { - const type = $isHeadingNode(element) - ? element.getTag() - : element.getType(); - setBlockType(type); - if ($isCodeNode(element)) { - setCodeLanguage(element.getLanguage() || getDefaultCodeLanguage()); - } - } - } - // Update text format - setIsBold(selection.hasFormat("bold")); - setIsItalic(selection.hasFormat("italic")); - setIsUnderline(selection.hasFormat("underline")); - setIsStrikethrough(selection.hasFormat("strikethrough")); - setIsCode(selection.hasFormat("code")); - - // Update links - const node = getSelectedNode(selection); - const parent = node.getParent(); - if ($isLinkNode(parent) || $isLinkNode(node)) { - setIsLink(true); - } else { - setIsLink(false); - } - } - }, [editor]); - - useEffect(() => { - return mergeRegister( - editor.registerUpdateListener(({ editorState }) => { - editorState.read(() => { - updateToolbar(); - }); - }), - editor.registerCommand( - SELECTION_CHANGE_COMMAND, - (_payload, newEditor) => { - updateToolbar(); - return false; - }, - LowPriority - ) - ); - }, [editor, updateToolbar]); - - const codeLanguges = useMemo(() => getCodeLanguages(), []); - const onCodeLanguageSelect = useCallback( - (e) => { - editor.update(() => { - if (selectedElementKey !== null) { - const node = $getNodeByKey(selectedElementKey); - if ($isCodeNode(node)) { - node.setLanguage(e.target.value); - } - } - }); - }, - [editor, selectedElementKey] - ); - - const insertLink = useCallback(() => { - if (!isLink) { - editor.dispatchCommand(TOGGLE_LINK_COMMAND, "https://"); - } else { - editor.dispatchCommand(TOGGLE_LINK_COMMAND, null); - } - }, [editor, isLink]); - - return ( -
- {supportedBlockTypes.has(blockType) && ( - <> - - {showBlockOptionsDropDown && - createPortal( - , - document.body - )} - - - )} - {blockType === "code" ? ( - <> -