Skip to content

Commit 6bd5d23

Browse files
committed
feat: 多选行交互由shift选中改为拖拽选中
1 parent 6e0cb41 commit 6bd5d23

File tree

5 files changed

+252
-117
lines changed

5 files changed

+252
-117
lines changed

packages/devui-vue/devui/code-review/src/code-review.tsx

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* @jsxImportSource vue */
2-
import { defineComponent, onMounted, provide, toRefs, onBeforeUnmount } from 'vue';
2+
import { defineComponent, onMounted, provide, toRefs } from 'vue';
33
import type { SetupContext } from 'vue';
44
import CodeReviewHeader from './components/code-review-header';
55
import { CommentIcon } from './components/code-review-icons';
@@ -20,21 +20,34 @@ export default defineComponent({
2020
const { diffType } = toRefs(props);
2121
const { renderHtml, reviewContentRef, diffFile, onContentClick } = useCodeReview(props, ctx);
2222
const { isFold, toggleFold } = useCodeReviewFold(props, ctx);
23-
const { commentLeft, commentTop,
24-
mouseEvent, onCommentMouseLeave,
25-
onCommentIconClick, onCommentKeyDown,
26-
unCommentKeyDown, insertComment,
27-
removeComment, updateCheckedLineClass, clearCheckedLines } = useCodeReviewComment(reviewContentRef, props, ctx);
23+
const {
24+
commentLeft,
25+
commentTop,
26+
mouseEvent,
27+
onCommentMouseLeave,
28+
onCommentIconClick,
29+
insertComment,
30+
removeComment,
31+
updateCheckedLineClass,
32+
clearCheckedLines,
33+
} = useCodeReviewComment(reviewContentRef, props, ctx);
2834

2935
onMounted(() => {
30-
ctx.emit('afterViewInit', { toggleFold, insertComment, removeComment, updateCheckedLineClass, clearCheckedLines });
31-
onCommentKeyDown();
36+
ctx.emit('afterViewInit', {
37+
toggleFold,
38+
insertComment,
39+
removeComment,
40+
updateCheckedLineClass,
41+
clearCheckedLines,
42+
});
3243
});
33-
// 销毁
34-
onBeforeUnmount(() => {
35-
unCommentKeyDown();
44+
provide(CodeReviewInjectionKey, {
45+
diffType,
46+
reviewContentRef,
47+
diffInfo: diffFile.value[0],
48+
isFold,
49+
rootCtx: ctx,
3650
});
37-
provide(CodeReviewInjectionKey, { diffType, reviewContentRef, diffInfo: diffFile.value[0], isFold, rootCtx: ctx });
3851

3952
return () => (
4053
<div class={ns.b()}>
@@ -59,8 +72,7 @@ export default defineComponent({
5972
class="comment-icon"
6073
style={{ left: commentLeft.value + 'px', top: commentTop.value + 'px' }}
6174
onClick={onCommentIconClick}
62-
onMouseleave={onCommentMouseLeave}
63-
>
75+
onMouseleave={onCommentMouseLeave}>
6476
<CommentIcon />
6577
</div>
6678
)}

packages/devui-vue/devui/code-review/src/composables/use-code-review-comment.ts

Lines changed: 87 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { ref, toRefs, onUnmounted, watch } from 'vue';
22
import type { SetupContext, Ref } from 'vue';
3+
import { useCodeReviewLineSelection } from './use-code-review-line-selection';
34
import type { LineSide, CodeReviewProps } from '../code-review-types';
45
import { useNamespace } from '../../../shared/hooks/use-namespace';
56
import {
@@ -13,23 +14,26 @@ import {
1314
export function useCodeReviewComment(reviewContentRef: Ref<HTMLElement>, props: CodeReviewProps, ctx: SetupContext) {
1415
const { outputFormat, allowComment, allowChecked } = toRefs(props);
1516
const ns = useNamespace('code-review');
17+
const { onMousedown } = useCodeReviewLineSelection(reviewContentRef, props, updateLineNumbers, updateLineNumbers);
1618
const commentLeft = ref(-100);
1719
const commentTop = ref(-100);
1820
let currentLeftLineNumber = -1;
1921
let currentRightLineNumber = -1;
2022
let lastLineNumberContainer: HTMLElement | null;
2123
let checkedLineNumberContainer: Array<Element> = [];
22-
let isShift = false;
2324
let currentLeftLineNumbers: Array<number> = [];
2425
let currentRightLineNumbers: Array<number> = [];
2526
let checkedLineCodeString: Array<string> | Record<string, Array<string>> = {};
26-
watch(() => outputFormat.value, () => {
27-
// 如果出现单栏双栏切换则需要重置选中
28-
checkedLineNumberContainer = [];
29-
currentLeftLineNumbers = [];
30-
currentRightLineNumbers = [];
31-
checkedLineCodeString = [];
32-
});
27+
watch(
28+
() => outputFormat.value,
29+
() => {
30+
// 如果出现单栏双栏切换则需要重置选中
31+
checkedLineNumberContainer = [];
32+
currentLeftLineNumbers = [];
33+
currentRightLineNumbers = [];
34+
checkedLineCodeString = [];
35+
}
36+
);
3337
const resetLeftTop = () => {
3438
commentLeft.value = -100;
3539
commentTop.value = -100;
@@ -111,105 +115,83 @@ export function useCodeReviewComment(reviewContentRef: Ref<HTMLElement>, props:
111115
resetLeftTop();
112116
}
113117
};
114-
function commentKeyDown(e: any) {
115-
// keyCode已经被废弃了 用e.key代替 或者e.code代替
116-
switch (e.key) {
117-
case 'Shift':
118-
isShift = true;
119-
break;
120-
}
121-
}
122-
function commentKeyUp(e: any) {
123-
e.preventDefault();
124-
switch (e.key) {
125-
case 'Shift':
126-
isShift = false;
127-
break;
128-
}
129-
}
130-
// 销毁键盘事件
131-
const unCommentKeyDown = () => {
132-
document.removeEventListener('keydown', commentKeyDown);
133-
document.removeEventListener('keyup', commentKeyUp);
134-
};
135-
// 键盘事件
136-
const onCommentKeyDown = () => {
137-
document.addEventListener('keydown', commentKeyDown);
138-
document.addEventListener('keyup', commentKeyUp);
139-
};
140118
// 获代码行 取值方法
141-
const getLineNumbers = (currentNumber: number, currentNumbers: Array<number>, e: Event) => {
142-
if (currentNumber === -1) { // 当前行没数据不代表之前选中的没数据,此时返回原来的
119+
const getLineNumbers = (currentNumber: number, currentNumbers: Array<number>) => {
120+
if (currentNumber === -1) {
121+
// 当前行没数据不代表之前选中的没数据,此时返回原来的
143122
return currentNumbers;
144123
}
145124
if (currentNumbers.length === 0) {
146125
return [currentNumber];
147126
}
148127
const numbers = [...currentNumbers];
149128
let max = Math.max(...numbers);
150-
const min = Math.min(...numbers);
151-
if (currentNumber > max) { // 限制规则只能从小选到大。
129+
let min = Math.min(...numbers);
130+
if (currentNumber < min) {
131+
min = currentNumber;
132+
}
133+
if (currentNumber > max) {
152134
max = currentNumber;
153135
}
154136
return Array.from({ length: max - min + 1 }, (_, i) => i + min);
155137
};
156138
// 获取一些公共类和判断
157-
const getCommonClassAndJudge = (side: string) => {
158-
const lineClassName = side === 'line-by-line' ? '.d2h-code-linenumber' : '.d2h-code-side-linenumber';
139+
const getCommonClassAndJudge = () => {
140+
const lineClassName = props.outputFormat === 'line-by-line' ? '.d2h-code-linenumber' : '.d2h-code-side-linenumber';
159141
const linenumberDom = reviewContentRef.value.querySelectorAll(lineClassName);
160142
const checkedLine = [currentLeftLineNumbers, currentRightLineNumbers];
161143
return {
162144
linenumberDom,
163-
checkedLine
145+
checkedLine,
164146
};
165147
};
166148
// 之前每次都先移出所有选中的方法过于浪费性能,增加具体dom节点选中方法(防重复添加)
167149
const addCommentCheckedClass = (Dom: Element) => {
168-
!Dom.classList.contains('comment-checked') && Dom.classList.add('comment-checked');
150+
!Dom.classList.contains('comment-checked') && Dom.classList.add('comment-checked');
169151
};
170-
// 选中(单栏)
171-
const addCommentClassSingle = (side: string) => {
172-
const { linenumberDom, checkedLine } = getCommonClassAndJudge(side);
152+
// 单栏
153+
function getSingleCheckedLineCode(shouldRenderClass: boolean) {
154+
const { linenumberDom, checkedLine } = getCommonClassAndJudge();
173155
const checkedCodeContent = [];
174-
// resetCommentClass();
175156
for (let i = 0; i < linenumberDom.length; i++) {
176157
const lineNumberDomLeft = linenumberDom[i].children[0];
177158
const lineNumberDomRight = linenumberDom[i].children[1];
178159
if (lineNumberDomLeft || lineNumberDomRight) {
179-
const codeLineNumberLeft = parseInt((lineNumberDomLeft as HTMLElement)?.innerText);
180-
const codeLineNumberRight = parseInt((lineNumberDomRight as HTMLElement)?.innerText);
160+
const codeLineNumberLeft = parseInt((lineNumberDomLeft as HTMLElement)?.innerText);
161+
const codeLineNumberRight = parseInt((lineNumberDomRight as HTMLElement)?.innerText);
181162
// 因为存在左边或者右边为空的num所以两边都要循环,但是同一个dom已经过就不需要再赋予
182163
if (checkedLine[0].includes(codeLineNumberLeft) || checkedLine[1].includes(codeLineNumberRight)) {
183164
checkedLineNumberContainer.push(linenumberDom[i]);
184165
// 两个节点之间可能间隔文本节点
185-
const codeNode = (linenumberDom[i].nextSibling as HTMLElement).nodeName === '#text'
186-
? (linenumberDom[i].nextSibling as HTMLElement).nextSibling
187-
: linenumberDom[i].nextSibling;
188-
checkedCodeContent.push((codeNode as HTMLElement)?.innerText);
189-
addCommentCheckedClass(linenumberDom[i]);
190-
addCommentCheckedClass(codeNode as HTMLElement);
166+
const codeNode = linenumberDom[i].nextElementSibling as HTMLElement;
167+
checkedCodeContent.push(codeNode?.innerText);
168+
if (shouldRenderClass) {
169+
addCommentCheckedClass(linenumberDom[i]);
170+
addCommentCheckedClass(codeNode);
171+
}
191172
}
192173
}
193174
}
194175
checkedLineCodeString = checkedCodeContent;
195-
};
196-
// 选中(双栏)
197-
const addCommentClassDouble = (side: string) => {
198-
const { linenumberDom, checkedLine } = getCommonClassAndJudge(side);
176+
}
177+
// 双栏
178+
function getDoubleCheckedLineCode(shouldRenderClass: boolean) {
179+
const { linenumberDom, checkedLine } = getCommonClassAndJudge();
199180
const checkedCodeContentLeft = [];
200181
const checkedCodeContentRight = [];
201182

202183
function checkedFunc(Dom: Element) {
203184
checkedLineNumberContainer.push(Dom);
204-
const codeNode = (Dom.nextSibling as HTMLElement).nodeName === '#text'
205-
? (Dom.nextSibling as HTMLElement).nextSibling
206-
: Dom.nextSibling;
207-
addCommentCheckedClass(Dom);
208-
addCommentCheckedClass(codeNode as HTMLElement);
209-
return (codeNode as HTMLElement)?.innerText;
185+
const codeNode = Dom.nextElementSibling as HTMLElement;
186+
if (shouldRenderClass) {
187+
addCommentCheckedClass(Dom);
188+
addCommentCheckedClass(codeNode);
189+
}
190+
return codeNode?.innerText;
210191
}
211192

212-
for (let i = 0; i < linenumberDom.length; i++) { // 左右双栏一起遍历
193+
for (let i = 0; i < linenumberDom.length; i++) {
194+
// 左右双栏一起遍历
213195
const codeLineNumber = parseInt(linenumberDom[i]?.innerHTML);
214196
if (linenumberDom[i].classList.contains('d-code-left') && checkedLine[0].includes(codeLineNumber)) {
215197
const lineNumText = checkedFunc(linenumberDom[i]);
@@ -222,37 +204,34 @@ export function useCodeReviewComment(reviewContentRef: Ref<HTMLElement>, props:
222204
}
223205
}
224206
checkedLineCodeString = { leftCode: checkedCodeContentLeft, rightCode: checkedCodeContentRight };
225-
};
226-
const updateCheckedLineClass = () => {
227-
if (outputFormat.value === 'line-by-line') {
228-
addCommentClassSingle(outputFormat.value);
229-
return;
207+
}
208+
function getCheckedLineCode(shouldRenderClass: boolean) {
209+
if (props.outputFormat === 'line-by-line') {
210+
return getSingleCheckedLineCode(shouldRenderClass);
230211
}
231-
addCommentClassDouble(outputFormat.value);
212+
getDoubleCheckedLineCode(shouldRenderClass);
213+
}
214+
function updateLineNumbers() {
215+
currentLeftLineNumbers =
216+
currentLeftLineNumber === -1 ? currentLeftLineNumbers : getLineNumbers(currentLeftLineNumber, currentLeftLineNumbers);
217+
currentRightLineNumbers =
218+
currentRightLineNumber === -1 ? currentRightLineNumbers : getLineNumbers(currentRightLineNumber, currentRightLineNumbers);
219+
getCheckedLineCode(false);
220+
}
221+
const updateCheckedLineClass = () => {
222+
getCheckedLineCode(true);
232223
};
233224
// 还原样式
234225
const resetCommentClass = () => {
235226
for (let i = 0; i < checkedLineNumberContainer.length; i++) {
236227
checkedLineNumberContainer[i].classList.remove('comment-checked');
237-
const codeNode = (checkedLineNumberContainer[i].nextSibling as HTMLElement).nodeName === '#text'
238-
? (checkedLineNumberContainer[i].nextSibling as HTMLElement).nextSibling
239-
: checkedLineNumberContainer[i].nextSibling;
228+
const codeNode = checkedLineNumberContainer[i].nextElementSibling;
240229
(codeNode as HTMLElement)?.classList.remove('comment-checked');
241230
}
242231
checkedLineNumberContainer = [];
243232
};
244-
// 按住shift键点击
245-
const commentShiftClick = (e: Event) => {
246-
currentLeftLineNumbers = currentLeftLineNumber === -1
247-
? currentLeftLineNumbers
248-
: getLineNumbers(currentLeftLineNumber, currentLeftLineNumbers, e);
249-
currentRightLineNumbers = currentRightLineNumber === -1
250-
? currentRightLineNumbers
251-
: getLineNumbers(currentRightLineNumber, currentRightLineNumbers, e);
252-
updateCheckedLineClass();
253-
};
254233
// 点击
255-
const commentClick = (e: Event) => {
234+
const commentClick = () => {
256235
interface recordType {
257236
left: number;
258237
right: number;
@@ -263,15 +242,22 @@ export function useCodeReviewComment(reviewContentRef: Ref<HTMLElement>, props:
263242
};
264243
}
265244
let obj: recordType = { left: currentLeftLineNumber, right: currentRightLineNumber };
266-
if (currentLeftLineNumbers.length >= 1 || currentRightLineNumbers.length >= 1 && allowChecked.value) { // 选中模式
245+
if ((currentLeftLineNumbers.length >= 1 || currentRightLineNumbers.length >= 1) && allowChecked.value) {
246+
// 选中模式
267247
const maxCurrentLeftLineNumber = currentLeftLineNumbers[currentLeftLineNumbers.length - 1];
268248
const maxCurrentRightLineNumber = currentRightLineNumbers[currentRightLineNumbers.length - 1];
269249
if (maxCurrentLeftLineNumber === currentLeftLineNumber || maxCurrentRightLineNumber === currentRightLineNumber) {
270250
// 点击添加评论图标触发的事件
271-
obj = { left: currentLeftLineNumber, right: currentRightLineNumber, details: {
272-
lefts: currentLeftLineNumbers, rights: currentRightLineNumbers, codes: checkedLineCodeString
273-
}};
274-
} else{
251+
obj = {
252+
left: currentLeftLineNumber,
253+
right: currentRightLineNumber,
254+
details: {
255+
lefts: currentLeftLineNumbers,
256+
rights: currentRightLineNumbers,
257+
codes: checkedLineCodeString,
258+
},
259+
};
260+
} else {
275261
currentLeftLineNumbers = [];
276262
currentRightLineNumbers = [];
277263
resetCommentClass();
@@ -282,7 +268,8 @@ export function useCodeReviewComment(reviewContentRef: Ref<HTMLElement>, props:
282268
};
283269
// 图标或者单行的点击
284270
const onCommentIconClick = (e: Event) => {
285-
if (e) { // 根据时间反回的dom判断是否点击中的制定区域
271+
if (e) {
272+
// 根据时间反回的dom判断是否点击中的制定区域
286273
const composedPath = e.composedPath() as HTMLElement[];
287274
const lineNumberBox = composedPath.find(
288275
(item) => item.classList?.contains('comment-icon-hover') || item.classList?.contains('comment-icon')
@@ -291,12 +278,7 @@ export function useCodeReviewComment(reviewContentRef: Ref<HTMLElement>, props:
291278
return;
292279
}
293280
}
294-
// 按住shift键选中
295-
if (isShift && allowChecked.value) {
296-
commentShiftClick(e);
297-
return;
298-
}
299-
commentClick(e);
281+
commentClick();
300282
};
301283
const insertComment = (lineNumber: number, lineSide: LineSide, commentDom: HTMLElement) => {
302284
if (outputFormat.value === 'line-by-line') {
@@ -345,7 +327,14 @@ export function useCodeReviewComment(reviewContentRef: Ref<HTMLElement>, props:
345327
resetCommentClass();
346328
};
347329

348-
const mouseEvent = allowComment.value ? { onMousemove: onMouseMove, onMouseleave: onMouseleave } : {};
330+
const mouseEvent: Record<string, (e: MouseEvent) => void> = {};
331+
if (allowComment.value) {
332+
mouseEvent.onMousemove = onMouseMove;
333+
mouseEvent.onMouseleave = onMouseleave;
334+
}
335+
if (props.allowChecked) {
336+
mouseEvent.onMousedown = onMousedown;
337+
}
349338

350339
window.addEventListener('scroll', resetLeftTop);
351340

@@ -357,14 +346,10 @@ export function useCodeReviewComment(reviewContentRef: Ref<HTMLElement>, props:
357346
commentLeft,
358347
commentTop,
359348
mouseEvent,
360-
// currentLeftLineNumbers,
361-
// currentRightLineNumbers,
362349
updateCheckedLineClass,
363350
clearCheckedLines,
364351
onCommentMouseLeave,
365352
onCommentIconClick,
366-
onCommentKeyDown,
367-
unCommentKeyDown,
368353
insertComment,
369354
removeComment,
370355
};

0 commit comments

Comments
 (0)