From 3a9f8348143a9f5597efb099a5c0807946d81803 Mon Sep 17 00:00:00 2001 From: wwt444 <49834266+wwt444@users.noreply.github.com> Date: Sun, 16 Jun 2024 23:19:59 +0800 Subject: [PATCH 1/4] =?UTF-8?q?fix:=E5=85=BC=E5=AE=B9=E7=81=AB=E7=8B=90?= =?UTF-8?q?=E6=B5=8F=E8=A7=88=E5=99=A8=E6=97=A0=E6=B3=95=E6=AD=A3=E7=A1=AE?= =?UTF-8?q?=E5=88=A4=E6=96=AD=E5=85=83=E7=B4=A0=EF=BC=88DevCloudFE#1883?= =?UTF-8?q?=EF=BC=89=20(#1884)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/devui-vue/devui/shared/utils/dom.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/devui-vue/devui/shared/utils/dom.ts b/packages/devui-vue/devui/shared/utils/dom.ts index d3637a3c30..cb6547a130 100644 --- a/packages/devui-vue/devui/shared/utils/dom.ts +++ b/packages/devui-vue/devui/shared/utils/dom.ts @@ -9,6 +9,16 @@ export function isComponent(target: any): target is ComponentPublicInstance { return !!(target?.$el); } +/** + * 兼容火狐浏览器判断是否为元素节点 + * @param {any} element + * @returns {boolean} + */ +function judgeFireIsElement (element: Element | ComponentPublicInstance | null) { + const str = Object.prototype.toString.call(element); + return str.includes('object')&&str.includes('HTML')&&str.includes('Element'); +} + /** * 提取 Vue Intance 中的元素,如果本身就是元素,直接返回。 * @param {any} element @@ -17,13 +27,13 @@ export function isComponent(target: any): target is ComponentPublicInstance { export function getElement( element: Element | ComponentPublicInstance | null ): Element | null { - if (element instanceof Element) { + if (element instanceof Element || judgeFireIsElement(element)) { return element; } if ( element && typeof element === 'object' && - element.$el instanceof Element + (element.$el instanceof Element || judgeFireIsElement(element.$el)) ) { return element.$el; } From 94f99c38bfac52d11298190420f862595935d41f Mon Sep 17 00:00:00 2001 From: wwt444 <49834266+wwt444@users.noreply.github.com> Date: Sun, 16 Jun 2024 23:20:40 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat(tree):=E6=A0=91=E5=BD=A2=E6=8E=A7?= =?UTF-8?q?=E4=BB=B6=E9=80=89=E4=B8=AD=E8=8A=82=E7=82=B9=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E9=80=89=E4=B8=AD=E7=B1=BB=E5=90=8D=20(#1881)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/devui-vue/devui/tree/src/composables/use-tree-node.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/devui-vue/devui/tree/src/composables/use-tree-node.ts b/packages/devui-vue/devui/tree/src/composables/use-tree-node.ts index 9efd49ad1e..139655b67e 100644 --- a/packages/devui-vue/devui/tree/src/composables/use-tree-node.ts +++ b/packages/devui-vue/devui/tree/src/composables/use-tree-node.ts @@ -20,7 +20,7 @@ export interface IUseTreeNode { } export function useTreeNode(data: ComputedRef): IUseTreeNode { - const nodeClass = computed(() => [ns.e('node'), data.value?.expanded && ns.em('node', 'open')]); + const nodeClass = computed(() => [ns.e('node'), data.value?.expanded && ns.em('node', 'open'), data.value?.selected && 'active']); const nodeStyle = computed(() => { return { paddingLeft: `${NODE_INDENT * (data.value?.level - 1)}px` }; }); From 6f8c93ea533cd40cf3aa7bc7d1e9aede05ff9bb1 Mon Sep 17 00:00:00 2001 From: VRlie <2755561608@qq.com> Date: Mon, 17 Jun 2024 10:52:40 +0800 Subject: [PATCH 3/4] =?UTF-8?q?feat:=20=E4=BB=A3=E7=A0=81=E6=AF=94?= =?UTF-8?q?=E8=BE=83CodeReview=E7=BB=84=E4=BB=B6=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=A4=9A=E8=A1=8C=E9=80=89=E4=B8=AD=E5=8A=9F=E8=83=BD=E3=80=82?= =?UTF-8?q?=E4=BD=BF=E7=94=A8allow-checked=E5=8F=82=E6=95=B0=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=E3=80=82=E5=A2=9E=E5=8A=A0=E8=AF=A6=E7=BB=86=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E7=A4=BA=E4=BE=8B=E5=92=8C=E6=96=87=E6=A1=A3=E8=AF=B4?= =?UTF-8?q?=E6=98=8E=E3=80=82#1876=20(#1885)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 组件易用性优化,category-search组件唤出textInput时,input框自动聚焦 (#1842) * feat: 代码比较CodeReview组件增加行/多行选中功能。使用allow-checked参数控制。增加详细使用示例和文档说明。 #1876 --- .../devui/code-editor/src/code-editor.tsx | 16 +- packages/devui-vue/devui/code-review/index.ts | 2 +- .../code-review/src/code-review-types.ts | 4 + .../devui/code-review/src/code-review.scss | 47 +++ .../devui/code-review/src/code-review.tsx | 21 +- .../src/components/code-review-header.tsx | 1 + .../src/components/code-review-icons.tsx | 1 + .../composables/use-code-review-comment.ts | 208 +++++++++++- .../devui-vue/devui/code-review/src/utils.ts | 19 +- .../docs/components/code-review/index.md | 315 ++++++++++++++++++ 10 files changed, 603 insertions(+), 31 deletions(-) diff --git a/packages/devui-vue/devui/code-editor/src/code-editor.tsx b/packages/devui-vue/devui/code-editor/src/code-editor.tsx index 1bce5641d9..9c8ad83b02 100644 --- a/packages/devui-vue/devui/code-editor/src/code-editor.tsx +++ b/packages/devui-vue/devui/code-editor/src/code-editor.tsx @@ -1,3 +1,4 @@ +/* @jsxImportSource vue */ import { defineComponent } from 'vue'; import type { SetupContext } from 'vue'; import { codeEditorProps, CodeEditorProps } from './code-editor-types'; @@ -5,12 +6,11 @@ import { useCodeEditor } from './composables/use-code-editor'; import './code-editor.scss'; export default defineComponent({ - name: 'DCodeEditor', - props: codeEditorProps, - emits: ['update:modelValue', 'afterEditorInit', 'click'], - setup(props: CodeEditorProps, ctx: SetupContext) { - const { editorEl } = useCodeEditor(props, ctx); - - return () =>
; - } + name: 'DCodeEditor', + props: codeEditorProps, + emits: ['update:modelValue', 'afterEditorInit', 'click'], + setup(props: CodeEditorProps, ctx: SetupContext) { + const { editorEl } = useCodeEditor(props, ctx); + return () =>
; + } }); diff --git a/packages/devui-vue/devui/code-review/index.ts b/packages/devui-vue/devui/code-review/index.ts index b86ea709fb..ce1d93fa11 100644 --- a/packages/devui-vue/devui/code-review/index.ts +++ b/packages/devui-vue/devui/code-review/index.ts @@ -9,6 +9,6 @@ export default { category: '演进中', status: '100%', install(app: App): void { - app.component(CodeReview.name, CodeReview); + app.component(CodeReview.name as string, CodeReview); }, }; diff --git a/packages/devui-vue/devui/code-review/src/code-review-types.ts b/packages/devui-vue/devui/code-review/src/code-review-types.ts index 951f24c19a..74a368ab81 100644 --- a/packages/devui-vue/devui/code-review/src/code-review-types.ts +++ b/packages/devui-vue/devui/code-review/src/code-review-types.ts @@ -30,6 +30,10 @@ export const codeReviewProps = { type: Boolean, default: true, }, + allowChecked: { + type: Boolean, + default: false, + }, allowExpand: { type: Boolean, default: true, diff --git a/packages/devui-vue/devui/code-review/src/code-review.scss b/packages/devui-vue/devui/code-review/src/code-review.scss index 88077ea8d0..47a1c36bff 100644 --- a/packages/devui-vue/devui/code-review/src/code-review.scss +++ b/packages/devui-vue/devui/code-review/src/code-review.scss @@ -223,6 +223,53 @@ display: table-cell; } } + + .d2h-file-diff { + // 单栏 + .comment-checked { + &.d2h-cntx { + background-color: #fff8c5; // 通常选中 + } + + &.d2h-del { + background-color: #ffe5b4; // 删除行选中 + + &.d2h-code-linenumber { + background-color: #ffc89d; // 删除行中的number + } + } + + &.d2h-ins { + background-color: #d1f1a8; // 增加行选中 + + &.d2h-code-linenumber { + background-color: #daf4ae; // 增加行中的number + } + } + } + } + + .comment-checked { + &.d2h-cntx { + background-color: #fff8c5; // 通常选中 + } + + &.d2h-del { + background-color: #ffe5b4; // 删除行选中 + + &.d2h-code-side-linenumber { + background-color: #ffc89d; // 删除行中的number + } + } + + &.d2h-ins { + background-color: #d1f1a8; // 增加行选中 + + &.d2h-code-side-linenumber { + background-color: #daf4ae; // 增加行中的number + } + } + } } .comment-icon { diff --git a/packages/devui-vue/devui/code-review/src/code-review.tsx b/packages/devui-vue/devui/code-review/src/code-review.tsx index ac2e59574a..4337d95306 100644 --- a/packages/devui-vue/devui/code-review/src/code-review.tsx +++ b/packages/devui-vue/devui/code-review/src/code-review.tsx @@ -1,4 +1,5 @@ -import { defineComponent, onMounted, provide, toRefs } from 'vue'; +/* @jsxImportSource vue */ +import { defineComponent, onMounted, provide, toRefs, onBeforeUnmount } from 'vue'; import type { SetupContext } from 'vue'; import CodeReviewHeader from './components/code-review-header'; import { CommentIcon } from './components/code-review-icons'; @@ -19,13 +20,20 @@ export default defineComponent({ const { diffType } = toRefs(props); const { renderHtml, reviewContentRef, diffFile, onContentClick } = useCodeReview(props, ctx); const { isFold, toggleFold } = useCodeReviewFold(props, ctx); - const { commentLeft, commentTop, mouseEvent, onCommentMouseLeave, onCommentIconClick, insertComment, removeComment } = - useCodeReviewComment(reviewContentRef, props, ctx); + const { commentLeft, commentTop, + mouseEvent, onCommentMouseLeave, + onCommentIconClick, onCommentKeyDown, + unCommentKeyDown, insertComment, + removeComment, updateCheckedLineClass } = useCodeReviewComment(reviewContentRef, props, ctx); onMounted(() => { - ctx.emit('afterViewInit', { toggleFold, insertComment, removeComment }); + ctx.emit('afterViewInit', { toggleFold, insertComment, removeComment, updateCheckedLineClass }); + onCommentKeyDown(); + }); + // 销毁 + onBeforeUnmount(() => { + unCommentKeyDown(); }); - provide(CodeReviewInjectionKey, { diffType, reviewContentRef, diffInfo: diffFile.value[0], isFold, rootCtx: ctx }); return () => ( @@ -51,7 +59,8 @@ export default defineComponent({ class="comment-icon" style={{ left: commentLeft.value + 'px', top: commentTop.value + 'px' }} onClick={onCommentIconClick} - onMouseleave={onCommentMouseLeave}> + onMouseleave={onCommentMouseLeave} + > )} diff --git a/packages/devui-vue/devui/code-review/src/components/code-review-header.tsx b/packages/devui-vue/devui/code-review/src/components/code-review-header.tsx index aff973f89f..50ee8bf674 100644 --- a/packages/devui-vue/devui/code-review/src/components/code-review-header.tsx +++ b/packages/devui-vue/devui/code-review/src/components/code-review-header.tsx @@ -1,3 +1,4 @@ +/* @jsxImportSource vue */ import { defineComponent, inject } from 'vue'; import type { SetupContext } from 'vue'; import { Popover } from '../../../popover'; diff --git a/packages/devui-vue/devui/code-review/src/components/code-review-icons.tsx b/packages/devui-vue/devui/code-review/src/components/code-review-icons.tsx index 0aa7852e37..fea51ff085 100644 --- a/packages/devui-vue/devui/code-review/src/components/code-review-icons.tsx +++ b/packages/devui-vue/devui/code-review/src/components/code-review-icons.tsx @@ -1,3 +1,4 @@ +/* @jsxImportSource vue */ export function FoldIcon(): JSX.Element { return ( diff --git a/packages/devui-vue/devui/code-review/src/composables/use-code-review-comment.ts b/packages/devui-vue/devui/code-review/src/composables/use-code-review-comment.ts index c46c417eaf..c61346a564 100644 --- a/packages/devui-vue/devui/code-review/src/composables/use-code-review-comment.ts +++ b/packages/devui-vue/devui/code-review/src/composables/use-code-review-comment.ts @@ -1,4 +1,4 @@ -import { ref, toRefs, onUnmounted } from 'vue'; +import { ref, toRefs, onUnmounted, watch } from 'vue'; import type { SetupContext, Ref } from 'vue'; import type { LineSide, CodeReviewProps } from '../code-review-types'; import { useNamespace } from '../../../shared/hooks/use-namespace'; @@ -11,14 +11,25 @@ import { } from '../utils'; export function useCodeReviewComment(reviewContentRef: Ref, props: CodeReviewProps, ctx: SetupContext) { - const { outputFormat, allowComment } = toRefs(props); + const { outputFormat, allowComment, allowChecked } = toRefs(props); const ns = useNamespace('code-review'); const commentLeft = ref(-100); const commentTop = ref(-100); let currentLeftLineNumber = -1; let currentRightLineNumber = -1; let lastLineNumberContainer: HTMLElement | null; - + let checkedLineNumberContainer: Array = []; + let isShift = false; + let currentLeftLineNumbers: Array = []; + let currentRightLineNumbers: Array = []; + let checkedLineCodeString: Array | Record> = {}; + watch(() => outputFormat.value, () => { + // 如果出现单栏双栏切换则需要重置选中 + checkedLineNumberContainer = []; + currentLeftLineNumbers = []; + currentRightLineNumbers = []; + checkedLineCodeString = []; + }); const resetLeftTop = () => { commentLeft.value = -100; commentTop.value = -100; @@ -100,9 +111,178 @@ export function useCodeReviewComment(reviewContentRef: Ref, props: resetLeftTop(); } }; + function commentKeyDown(e: any) { + // keyCode已经被废弃了 用e.key代替 或者e.code代替 + switch (e.key) { + case 'Shift': + isShift = true; + break; + } + } + function commentKeyUp(e: any) { + e.preventDefault(); + switch (e.key) { + case 'Shift': + isShift = false; + break; + } + } + // 销毁键盘事件 + const unCommentKeyDown = () => { + document.removeEventListener('keydown', commentKeyDown); + document.removeEventListener('keyup', commentKeyUp); + }; + // 键盘事件 + const onCommentKeyDown = () => { + document.addEventListener('keydown', commentKeyDown); + document.addEventListener('keyup', commentKeyUp); + }; + // 获代码行 取值方法 + const getLineNumbers = (currentNumber: number, currentNumbers: Array, e: Event) => { + if (currentNumber === -1) { // 当前行没数据不代表之前选中的没数据,此时返回原来的 + return currentNumbers; + } + if (currentNumbers.length === 0) { + return [currentNumber]; + } + const numbers = [...currentNumbers]; + let max = Math.max(...numbers); + const min = Math.min(...numbers); + if (currentNumber > max) { // 限制规则只能从小选到大。 + max = currentNumber; + } + return Array.from({ length: max - min + 1 }, (_, i) => i + min); + }; + // 获取一些公共类和判断 + const getCommonClassAndJudge = (side: string) => { + const lineClassName = side === 'line-by-line' ? '.d2h-code-linenumber' : '.d2h-code-side-linenumber'; + const linenumberDom = reviewContentRef.value.querySelectorAll(lineClassName); + const checkedLine = [currentLeftLineNumbers, currentRightLineNumbers]; + return { + linenumberDom, + checkedLine + }; + }; + // 之前每次都先移出所有选中的方法过于浪费性能,增加具体dom节点选中方法(防重复添加) + const addCommentCheckedClass = (Dom: Element) => { + !Dom.classList.contains('comment-checked') && Dom.classList.add('comment-checked'); + }; + // 选中(单栏) + const addCommentClassSingle = (side: string) => { + const { linenumberDom, checkedLine } = getCommonClassAndJudge(side); + const checkedCodeContent = []; + // resetCommentClass(); + for (let i = 0; i < linenumberDom.length; i++) { + const lineNumberDomLeft = linenumberDom[i].children[0]; + const lineNumberDomRight = linenumberDom[i].children[1]; + if (lineNumberDomLeft || lineNumberDomRight) { + const codeLineNumberLeft = parseInt((lineNumberDomLeft as HTMLElement)?.innerText); + const codeLineNumberRight = parseInt((lineNumberDomRight as HTMLElement)?.innerText); + // 因为存在左边或者右边为空的num所以两边都要循环,但是同一个dom已经过就不需要再赋予 + if (checkedLine[0].includes(codeLineNumberLeft) || checkedLine[1].includes(codeLineNumberRight)) { + checkedLineNumberContainer.push(linenumberDom[i]); + // 两个节点之间可能间隔文本节点 + const codeNode = (linenumberDom[i].nextSibling as HTMLElement).nodeName === '#text' + ? (linenumberDom[i].nextSibling as HTMLElement).nextSibling + : linenumberDom[i].nextSibling; + checkedCodeContent.push((codeNode as HTMLElement)?.innerText); + addCommentCheckedClass(linenumberDom[i]); + addCommentCheckedClass(codeNode as HTMLElement); + } + } + } + checkedLineCodeString = checkedCodeContent; + }; + // 选中(双栏) + const addCommentClassDouble = (side: string) => { + const { linenumberDom, checkedLine } = getCommonClassAndJudge(side); + const checkedCodeContentLeft = []; + const checkedCodeContentRight = []; + + function checkedFunc(Dom: Element) { + checkedLineNumberContainer.push(Dom); + const codeNode = (Dom.nextSibling as HTMLElement).nodeName === '#text' + ? (Dom.nextSibling as HTMLElement).nextSibling + : Dom.nextSibling; + addCommentCheckedClass(Dom); + addCommentCheckedClass(codeNode as HTMLElement); + return (codeNode as HTMLElement)?.innerText; + } + for (let i = 0; i < linenumberDom.length; i++) { // 左右双栏一起遍历 + const codeLineNumber = parseInt(linenumberDom[i]?.innerHTML); + if (linenumberDom[i].classList.contains('d-code-left') && checkedLine[0].includes(codeLineNumber)) { + const lineNumText = checkedFunc(linenumberDom[i]); + checkedCodeContentLeft.push(lineNumText); + continue; + } + if (linenumberDom[i].classList.contains('d-code-right') && checkedLine[1].includes(codeLineNumber)) { + const lineNumText = checkedFunc(linenumberDom[i]); + checkedCodeContentRight.push(lineNumText); + } + } + checkedLineCodeString = { leftCode: checkedCodeContentLeft, rightCode: checkedCodeContentRight }; + }; + const updateCheckedLineClass = () => { + if (outputFormat.value === 'line-by-line') { + addCommentClassSingle(outputFormat.value); + return; + } + addCommentClassDouble(outputFormat.value); + }; + // 还原样式 + const resetCommentClass = () => { + for (let i = 0; i < checkedLineNumberContainer.length; i++) { + checkedLineNumberContainer[i].classList.remove('comment-checked'); + const codeNode = (checkedLineNumberContainer[i].nextSibling as HTMLElement).nodeName === '#text' + ? (checkedLineNumberContainer[i].nextSibling as HTMLElement).nextSibling + : checkedLineNumberContainer[i].nextSibling; + (codeNode as HTMLElement)?.classList.remove('comment-checked'); + } + checkedLineNumberContainer = []; + }; + // 按住shift键点击 + const commentShiftClick = (e: Event) => { + currentLeftLineNumbers = currentLeftLineNumber === -1 + ? currentLeftLineNumbers + : getLineNumbers(currentLeftLineNumber, currentLeftLineNumbers, e); + currentRightLineNumbers = currentRightLineNumber === -1 + ? currentRightLineNumbers + : getLineNumbers(currentRightLineNumber, currentRightLineNumbers, e); + updateCheckedLineClass(); + }; + // 点击 + const commentClick = (e: Event) => { + interface recordType { + left: number; + right: number; + details?: { + lefts: Array; + rights: Array; + codes: Record> | Record>; + }; + } + let obj: recordType = { left: currentLeftLineNumber, right: currentRightLineNumber }; + if (currentLeftLineNumbers.length >= 1 || currentRightLineNumbers.length >= 1 && allowChecked.value) { // 选中模式 + const maxCurrentLeftLineNumber = currentLeftLineNumbers[currentLeftLineNumbers.length - 1]; + const maxCurrentRightLineNumber = currentRightLineNumbers[currentRightLineNumbers.length - 1]; + if (maxCurrentLeftLineNumber === currentLeftLineNumber || maxCurrentRightLineNumber === currentRightLineNumber) { + // 点击添加评论图标触发的事件 + obj = { left: currentLeftLineNumber, right: currentRightLineNumber, details: { + lefts: currentLeftLineNumbers, rights: currentRightLineNumbers, codes: checkedLineCodeString + }}; + } else{ + currentLeftLineNumbers = []; + currentRightLineNumbers = []; + resetCommentClass(); + } + } + // 点击添加评论图标触发的事件 + ctx.emit('addComment', obj); + }; + // 图标或者单行的点击 const onCommentIconClick = (e: Event) => { - if (e) { + if (e) { // 根据时间反回的dom判断是否点击中的制定区域 const composedPath = e.composedPath() as HTMLElement[]; const lineNumberBox = composedPath.find( (item) => item.classList?.contains('comment-icon-hover') || item.classList?.contains('comment-icon') @@ -111,18 +291,13 @@ export function useCodeReviewComment(reviewContentRef: Ref, props: return; } } - const emitObj: Partial> = {}; - if (outputFormat.value === 'line-by-line') { - emitObj.left = currentLeftLineNumber; - emitObj.right = currentRightLineNumber; - } else if (currentLeftLineNumber !== -1) { - emitObj.left = currentLeftLineNumber; - } else { - emitObj.right = currentRightLineNumber; + // 按住shift键选中 + if (isShift && allowChecked.value) { + commentShiftClick(e); + return; } - ctx.emit('addComment', { left: currentLeftLineNumber, right: currentRightLineNumber }); + commentClick(e); }; - const insertComment = (lineNumber: number, lineSide: LineSide, commentDom: HTMLElement) => { if (outputFormat.value === 'line-by-line') { const lineHost = findReferenceDomForSingleColumn(reviewContentRef.value, lineNumber, lineSide); @@ -175,8 +350,13 @@ export function useCodeReviewComment(reviewContentRef: Ref, props: commentLeft, commentTop, mouseEvent, + // currentLeftLineNumbers, + // currentRightLineNumbers, + updateCheckedLineClass, onCommentMouseLeave, onCommentIconClick, + onCommentKeyDown, + unCommentKeyDown, insertComment, removeComment, }; diff --git a/packages/devui-vue/devui/code-review/src/utils.ts b/packages/devui-vue/devui/code-review/src/utils.ts index e4cb00ab5e..30ca771710 100644 --- a/packages/devui-vue/devui/code-review/src/utils.ts +++ b/packages/devui-vue/devui/code-review/src/utils.ts @@ -145,6 +145,19 @@ export function updateExpandLineCount(expandDom: HTMLElement, newExpandDom: HTML },${newChangedNumRight} @@`; } +// 左右分栏时增加额外的class来区分左右模块 +function addClassToDiffCode(codeStrArr: RegExpMatchArray | null, theClassName: string) { + if (!codeStrArr || codeStrArr.length === 0) { + return null; + } + const newArray = codeStrArr.map((item: string) => { + const classNames = item?.match(/class="([^"]+)"/)[1].split(' '); + classNames.push(theClassName); + return item.replace(/class="([^"]+)"/, `class="${classNames.join(' ')}"`); + }); + return newArray as RegExpMatchArray; +} + // 解析diff export function parseDiffCode(container: HTMLElement, code: string, outputFormat: OutputFormat, isAddCode = false) { const diff2HtmlUi = new Diff2HtmlUI(container, code, { @@ -164,8 +177,10 @@ export function parseDiffCode(container: HTMLElement, code: string, outputFormat let newTrStr = ''; const offset = trListLength / 2; for (let i = 0; i < trListLength / 2; i++) { - const leftTdList = trList[i].match(TableTdReg); - const rightTdList = trList[i + offset].match(TableTdReg); + let leftTdList = trList[i].match(TableTdReg); + let rightTdList = trList[i + offset].match(TableTdReg); + leftTdList = addClassToDiffCode(leftTdList, 'd-code-left'); + rightTdList = addClassToDiffCode(rightTdList, 'd-code-right'); newTrStr += `
${leftTdList?.join('')}${rightTdList?.join('')}
`; } const tbodyAttr = diffHtmlStr.match(TableTbodyAttrReg)?.[1] || ''; diff --git a/packages/devui-vue/docs/components/code-review/index.md b/packages/devui-vue/docs/components/code-review/index.md index 25952ff696..fd69a9682d 100644 --- a/packages/devui-vue/docs/components/code-review/index.md +++ b/packages/devui-vue/docs/components/code-review/index.md @@ -279,6 +279,300 @@ export default defineComponent({ ::: + +### 多选代码行用法 + +本示例将展示在开启多选代码行,多选后单击最后一个选中的行,添加评论,并且将选中代码行和代码块放入评论内容中。 + +:::demo + +```vue +
+ + + + + + + + +``` +::: + ### CodeReview 参数 | 参数名 | 类型 | 默认值 | 说明 | @@ -286,6 +580,7 @@ export default defineComponent({ | diff | `string` | '' | 必选,diff 内容 | | fold | `boolean` | false | 可选,是否折叠显示 | | allow-comment | `boolean` | true | 可选,是否支持评论 | +| allow-checked | `boolean` | false | 可选,是否支持代码行选中,开启后可以按住 shift 点击鼠标选中多行代码,只能按照从小到大的顺序选择,可以跨行选择,开启后add-comment事件的反回值会发生变化。参数内容详见[CommentPosition](#commentposition) | | show-blob | `boolean` | false | 可选,是否展示缩略内容,一般大文件或二进制文件等需要展示缩略内容时使用 | | output-format | [OutputFormat](#outputformat) | 'line-by-line' | 可选,diff 展示格式,单栏展示或者分栏展示 | | diff-type | [DiffType](#difftype) | 'modify' | 可选,文件 diff 类型 | @@ -340,6 +635,23 @@ interface CommentPosition { } ``` +allow-checked模式下的CommentPosition返回值会多出一个details,其中lefts和rights代表左右两侧被选中的行号,codes被选中部分的代码块,单栏模式下是一个数组,双栏模式下是一个对象,分别返回左侧和右侧被选中的代码块。如果选中行左侧或者右侧没值数组最后一项会为-1(单栏模式下需要注意不能直接跨方向使用,例如不能选中一个只有左侧行的行然后直接选择一个只有右侧行的行,例如示例中不能直接先选左10然后直接选右45) + +```ts +interface CommentPosition { + left: number, + right: number, + details?: { + lefts: Array, + rights: Array, + codes: Array | { + leftCode: Array, + rightCode: Array, + } + } +} +``` + #### CodeReviewMethods ```ts @@ -352,5 +664,8 @@ interface CodeReviewMethods { // 删除评论的方法,传入行号、left/right removeComment: (lineNumber: number, lineSide: LineSide) => void; + + // 更新选中行样式,直接调用一般用于展开时更新选中行样式,像示例中一样使用 + updateCheckedLineClass: (); } ``` From b772e49195008b4f95cd51f703070cfd6dd76b5e Mon Sep 17 00:00:00 2001 From: GreatZP Date: Tue, 18 Jun 2024 09:13:19 +0800 Subject: [PATCH 4/4] chore: update package.json --- packages/devui-vue/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/devui-vue/package.json b/packages/devui-vue/package.json index 4790f70d65..95f03ed01c 100644 --- a/packages/devui-vue/package.json +++ b/packages/devui-vue/package.json @@ -1,6 +1,6 @@ { "name": "vue-devui", - "version": "1.6.20", + "version": "1.6.21", "license": "MIT", "description": "DevUI components based on Vite and Vue3", "keywords": [