@yuhufe/wtool-vdiff 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -0
- package/dist/DiffViewer/DiffViewer.vue.d.ts +12 -0
- package/dist/DiffViewer/MonacoDiffViewer.vue.d.ts +24 -0
- package/dist/DiffViewer/TopBar.vue.d.ts +13 -0
- package/dist/DiffViewer/createDiffViewer.d.ts +10 -0
- package/dist/DiffViewer/index.d.ts +1 -0
- package/dist/DiffViewer/useDiffView.d.ts +27 -0
- package/dist/DiffViewer/utils/autoHeight.d.ts +9 -0
- package/dist/DiffViewer/utils/patch2Pair.d.ts +27 -0
- package/dist/index.d.ts +2 -0
- package/dist/types.d.ts +20 -0
- package/dist/wtool-vdiff.cjs.js +48 -0
- package/dist/wtool-vdiff.css +1 -0
- package/dist/wtool-vdiff.es.d.ts +2 -0
- package/dist/wtool-vdiff.es.js +13026 -0
- package/package.json +53 -0
- package/src/DiffViewer/DiffViewer.vue +143 -0
- package/src/DiffViewer/MonacoDiffViewer.vue +178 -0
- package/src/DiffViewer/TopBar.vue +121 -0
- package/src/DiffViewer/createDiffViewer.ts +49 -0
- package/src/DiffViewer/index.ts +8 -0
- package/src/DiffViewer/useDiffView.ts +16 -0
- package/src/DiffViewer/utils/autoHeight.ts +149 -0
- package/src/DiffViewer/utils/patch2Pair.ts +121 -0
- package/src/index.ts +2 -0
- package/src/types.ts +20 -0
- package/src/vite-env.d.ts +7 -0
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@yuhufe/wtool-vdiff",
|
|
3
|
+
"private": false,
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Monaco diff viewer as Vue 3 Web Component",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"git",
|
|
9
|
+
"diff",
|
|
10
|
+
"monaco",
|
|
11
|
+
"vue3",
|
|
12
|
+
"web-component"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"dev": "pnpm run dev:site",
|
|
16
|
+
"dev:lib": "vite build --watch --mode development",
|
|
17
|
+
"build:site": "vite build --mode production --config site/vite.config.ts",
|
|
18
|
+
"dev:site": "vite --config site/vite.config.ts",
|
|
19
|
+
"build": "vite build --mode production"
|
|
20
|
+
},
|
|
21
|
+
"main": "./dist/wtool-vdiff.cjs.js",
|
|
22
|
+
"module": "./dist/wtool-vdiff.es.js",
|
|
23
|
+
"files": [
|
|
24
|
+
"dist/*",
|
|
25
|
+
"src/*"
|
|
26
|
+
],
|
|
27
|
+
"types": "dist/wtool-vdiff.es.d.ts",
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"author": "",
|
|
32
|
+
"license": "ISC",
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"vue": "^3.5.0"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@yuhufe/web-ui": "workspace:*",
|
|
38
|
+
"@monaco-editor/loader": "^1.6.1",
|
|
39
|
+
"monaco-editor": "^0.53.0",
|
|
40
|
+
"less": "4.6.4"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@vitejs/plugin-vue": "^5.2.0",
|
|
44
|
+
"typescript": "^5.9.3",
|
|
45
|
+
"vite": "^7.3.1",
|
|
46
|
+
"vite-plugin-dts": "^4.5.3",
|
|
47
|
+
"vue": "^3.5.0"
|
|
48
|
+
},
|
|
49
|
+
"repository": {
|
|
50
|
+
"type": "git",
|
|
51
|
+
"url": "https://github.com/defghy/web-toolkits.git"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="diff-viewer-wrap" :style="viewerStyle">
|
|
3
|
+
<TopBar class="top-bar" :diffPair="diffPair" />
|
|
4
|
+
<div class="content-wrap" v-show="!viewed" v-loading="loading">
|
|
5
|
+
<MonacoDiffViewer
|
|
6
|
+
class="monaco-container"
|
|
7
|
+
:originalCode="originalCode"
|
|
8
|
+
:modifiedCode="modifiedCode"
|
|
9
|
+
:language="language"
|
|
10
|
+
:options="mergedOptions"
|
|
11
|
+
:modelOptions="modelOptions"
|
|
12
|
+
@render-complete="onMonacoRenderComplete"
|
|
13
|
+
/>
|
|
14
|
+
</div>
|
|
15
|
+
</div>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script setup lang="ts">
|
|
19
|
+
import { ref, computed } from 'vue'
|
|
20
|
+
|
|
21
|
+
import { loadingDirective } from '@yuhufe/web-ui'
|
|
22
|
+
import type { DiffEditorOptions, WtoolDiffViewerProps, ModelOptions } from '../types'
|
|
23
|
+
import MonacoDiffViewer from './MonacoDiffViewer.vue'
|
|
24
|
+
import TopBar from './TopBar.vue'
|
|
25
|
+
import { useDiffViewer } from './useDiffView'
|
|
26
|
+
import { patch2Pair } from './utils/patch2Pair'
|
|
27
|
+
import { autoHeight } from './utils/autoHeight'
|
|
28
|
+
|
|
29
|
+
const vLoading = loadingDirective
|
|
30
|
+
const loading = ref(true)
|
|
31
|
+
|
|
32
|
+
const _renderStart = performance.now()
|
|
33
|
+
const onMonacoRenderComplete = () => {
|
|
34
|
+
const cost = performance.now() - _renderStart
|
|
35
|
+
console.log(`[DiffViewer] 渲染耗时: ${cost.toFixed(2)} ms`)
|
|
36
|
+
loading.value = false
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const props = withDefaults(defineProps<WtoolDiffViewerProps>(), {
|
|
40
|
+
diffPair: () => [],
|
|
41
|
+
diffPatch: '',
|
|
42
|
+
language: 'plaintext',
|
|
43
|
+
options: () => ({}),
|
|
44
|
+
modelOptions: () => ({}),
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const diffPair = ref(props.diffPair || null)
|
|
48
|
+
const initDiff = function () {
|
|
49
|
+
if (diffPair.value?.length) {
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// diffPatch => diffPair
|
|
54
|
+
diffPair.value = patch2Pair(props.diffPatch)
|
|
55
|
+
}
|
|
56
|
+
initDiff()
|
|
57
|
+
console.log(`[DiffViewer] patch耗时: ${(performance.now() - _renderStart).toFixed(2)} ms`)
|
|
58
|
+
|
|
59
|
+
const originalCode = computed(() => diffPair.value[0].content)
|
|
60
|
+
const modifiedCode = computed(() => diffPair.value[1].content)
|
|
61
|
+
|
|
62
|
+
const { funcs, registerFunc } = useDiffViewer({ isMaster: true })
|
|
63
|
+
|
|
64
|
+
/** raw 勾选时展示全文;未勾选时折叠无变更块,仅保留差异附近的上下文行 */
|
|
65
|
+
const mergedOptions = computed(() => {
|
|
66
|
+
// canUnchangeVisible=false(patch 模式)时,未改动区域为空行,强制折叠且忽略 rawed
|
|
67
|
+
const forceHide = !canUnchangeVisible.value
|
|
68
|
+
|
|
69
|
+
// 折叠上下文
|
|
70
|
+
let hideUnchangedRegions = props.options.hideUnchangedRegions || {}
|
|
71
|
+
if (!forceHide && rawed.value) {
|
|
72
|
+
hideUnchangedRegions = {
|
|
73
|
+
enabled: false,
|
|
74
|
+
...hideUnchangedRegions,
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
hideUnchangedRegions = {
|
|
78
|
+
enabled: true,
|
|
79
|
+
contextLineCount: 3,
|
|
80
|
+
...hideUnchangedRegions,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
...props.options,
|
|
86
|
+
hideUnchangedRegions,
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
const viewed = ref<boolean>(false) // 是否已读
|
|
91
|
+
const rawed = ref<boolean>(false) // 是否显示原始文件
|
|
92
|
+
const canUnchangeVisible = ref(!props.diffPatch) // patch 模式下未改动区域为空行,不可展示
|
|
93
|
+
|
|
94
|
+
// 编辑器样式
|
|
95
|
+
const viewerHeight = computed(() => {
|
|
96
|
+
if (props.viewerStyle?.height) {
|
|
97
|
+
return props.viewerStyle.height
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const heightRange = {
|
|
101
|
+
minHeight: '100px',
|
|
102
|
+
maxHeight: '250px',
|
|
103
|
+
...(props.viewerStyle || {}),
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const height = autoHeight({
|
|
107
|
+
patch: props.diffPatch,
|
|
108
|
+
pair: props.diffPair,
|
|
109
|
+
...heightRange,
|
|
110
|
+
unchangedVisiable: funcs.rawed.value,
|
|
111
|
+
unchangedCtxLineNum: mergedOptions.value.hideUnchangedRegions.contextLineCount!,
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
return `${height}px`
|
|
115
|
+
})
|
|
116
|
+
const viewerStyle = computed(() => {
|
|
117
|
+
return {
|
|
118
|
+
'--viewer-width': props.viewerStyle?.width || '100%',
|
|
119
|
+
'--viewer-height': viewerHeight.value,
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
registerFunc({
|
|
124
|
+
viewed,
|
|
125
|
+
rawed,
|
|
126
|
+
canUnchangeVisible,
|
|
127
|
+
})
|
|
128
|
+
</script>
|
|
129
|
+
|
|
130
|
+
<style scoped>
|
|
131
|
+
.diff-viewer-wrap {
|
|
132
|
+
width: var(--viewer-width);
|
|
133
|
+
border: 1px solid #ddd;
|
|
134
|
+
|
|
135
|
+
.top-bar {
|
|
136
|
+
flex-shrink: 0;
|
|
137
|
+
}
|
|
138
|
+
.content-wrap {
|
|
139
|
+
height: var(--viewer-height);
|
|
140
|
+
overflow: hidden;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
</style>
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div ref="containerEl" class="monaco-editor-container" :class="{ 'hide-unchanged-actions': !canUnchangeVisible }" />
|
|
3
|
+
</template>
|
|
4
|
+
|
|
5
|
+
<script setup lang="ts">
|
|
6
|
+
import { ref, watch, onMounted, onBeforeUnmount, shallowRef } from 'vue'
|
|
7
|
+
import loader from '@monaco-editor/loader'
|
|
8
|
+
import type * as Monaco from 'monaco-editor'
|
|
9
|
+
import { useDiffViewer } from './useDiffView'
|
|
10
|
+
|
|
11
|
+
type DiffEditorOptions = Monaco.editor.IStandaloneDiffEditorConstructionOptions
|
|
12
|
+
type ModelOptions = Monaco.editor.ITextModelUpdateOptions
|
|
13
|
+
|
|
14
|
+
const props = withDefaults(
|
|
15
|
+
defineProps<{
|
|
16
|
+
originalCode?: string
|
|
17
|
+
modifiedCode?: string
|
|
18
|
+
language?: string
|
|
19
|
+
options?: DiffEditorOptions
|
|
20
|
+
modelOptions?: ModelOptions
|
|
21
|
+
}>(),
|
|
22
|
+
{
|
|
23
|
+
originalCode: '',
|
|
24
|
+
modifiedCode: '',
|
|
25
|
+
language: 'plaintext',
|
|
26
|
+
options: () => ({}),
|
|
27
|
+
modelOptions: () => ({}),
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const emit = defineEmits<{ renderComplete: [] }>()
|
|
32
|
+
|
|
33
|
+
const { funcs } = useDiffViewer()
|
|
34
|
+
const canUnchangeVisible = funcs.canUnchangeVisible
|
|
35
|
+
|
|
36
|
+
const containerEl = ref<HTMLDivElement | null>(null)
|
|
37
|
+
const monacoInstance = shallowRef<typeof Monaco | null>(null)
|
|
38
|
+
const editor = shallowRef<Monaco.editor.IStandaloneDiffEditor | null>(null)
|
|
39
|
+
const originalModel = shallowRef<Monaco.editor.ITextModel | null>(null)
|
|
40
|
+
const modifiedModel = shallowRef<Monaco.editor.ITextModel | null>(null)
|
|
41
|
+
|
|
42
|
+
let disposed = false
|
|
43
|
+
|
|
44
|
+
onMounted(() => {
|
|
45
|
+
disposed = false
|
|
46
|
+
|
|
47
|
+
const init = async () => {
|
|
48
|
+
const monaco = await loader.init()
|
|
49
|
+
if (disposed || !containerEl.value) return
|
|
50
|
+
|
|
51
|
+
monacoInstance.value = monaco
|
|
52
|
+
originalModel.value = monaco.editor.createModel(props.originalCode, props.language)
|
|
53
|
+
modifiedModel.value = monaco.editor.createModel(props.modifiedCode, props.language)
|
|
54
|
+
|
|
55
|
+
editor.value = monaco.editor.createDiffEditor(containerEl.value, {
|
|
56
|
+
automaticLayout: true,
|
|
57
|
+
readOnly: true,
|
|
58
|
+
renderSideBySide: true,
|
|
59
|
+
useInlineViewWhenSpaceIsLimited: false,
|
|
60
|
+
scrollBeyondLastLine: false,
|
|
61
|
+
hideUnchangedRegions: {
|
|
62
|
+
enabled: true,
|
|
63
|
+
contextLineCount: 3,
|
|
64
|
+
},
|
|
65
|
+
scrollbar: {
|
|
66
|
+
verticalScrollbarSize: 8,
|
|
67
|
+
horizontalScrollbarSize: 8,
|
|
68
|
+
},
|
|
69
|
+
...props.options,
|
|
70
|
+
})
|
|
71
|
+
editor.value.setModel({
|
|
72
|
+
original: originalModel.value,
|
|
73
|
+
modified: modifiedModel.value,
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
if (Object.keys(props.modelOptions).length > 0) {
|
|
77
|
+
originalModel.value.updateOptions(props.modelOptions)
|
|
78
|
+
modifiedModel.value.updateOptions(props.modelOptions)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 仅在首次 diff 计算完成后统计行数,之后立即注销监听。
|
|
82
|
+
// Monaco 用 endLineNumber === 0 表示该侧无变更行(纯删除或纯新增),span 需特殊处理。
|
|
83
|
+
const onDidUpdateDiffDisposable = editor.value.onDidUpdateDiff(() => {
|
|
84
|
+
const changes = editor.value?.getLineChanges() ?? []
|
|
85
|
+
const span = (s: number, e: number) => (e === 0 ? 0 : e - s + 1)
|
|
86
|
+
const added = changes.reduce((n, c) => n + span(c.modifiedStartLineNumber, c.modifiedEndLineNumber), 0)
|
|
87
|
+
const removed = changes.reduce((n, c) => n + span(c.originalStartLineNumber, c.originalEndLineNumber), 0)
|
|
88
|
+
funcs.updateChangedLines?.({ added, removed })
|
|
89
|
+
emit('renderComplete')
|
|
90
|
+
onDidUpdateDiffDisposable.dispose()
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
void init()
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
onBeforeUnmount(() => {
|
|
98
|
+
disposed = true
|
|
99
|
+
editor.value?.dispose()
|
|
100
|
+
originalModel.value?.dispose()
|
|
101
|
+
modifiedModel.value?.dispose()
|
|
102
|
+
editor.value = null
|
|
103
|
+
originalModel.value = null
|
|
104
|
+
modifiedModel.value = null
|
|
105
|
+
monacoInstance.value = null
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
watch(
|
|
109
|
+
() => props.options,
|
|
110
|
+
opts => {
|
|
111
|
+
if (!editor.value) return
|
|
112
|
+
editor.value.updateOptions(opts)
|
|
113
|
+
},
|
|
114
|
+
{ deep: true }
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
watch(
|
|
118
|
+
() => props.originalCode,
|
|
119
|
+
v => {
|
|
120
|
+
if (!originalModel.value) return
|
|
121
|
+
if (originalModel.value.getValue() !== v) {
|
|
122
|
+
originalModel.value.setValue(v)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
watch(
|
|
128
|
+
() => props.modifiedCode,
|
|
129
|
+
v => {
|
|
130
|
+
if (!modifiedModel.value) return
|
|
131
|
+
if (modifiedModel.value.getValue() !== v) {
|
|
132
|
+
modifiedModel.value.setValue(v)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
watch(
|
|
138
|
+
() => props.language,
|
|
139
|
+
lang => {
|
|
140
|
+
const monaco = monacoInstance.value
|
|
141
|
+
const om = originalModel.value
|
|
142
|
+
const mm = modifiedModel.value
|
|
143
|
+
if (!monaco || !om || !mm) return
|
|
144
|
+
monaco.editor.setModelLanguage(om, lang)
|
|
145
|
+
monaco.editor.setModelLanguage(mm, lang)
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
watch(
|
|
150
|
+
() => props.modelOptions,
|
|
151
|
+
mo => {
|
|
152
|
+
if (!originalModel.value || !modifiedModel.value) return
|
|
153
|
+
originalModel.value.updateOptions(mo)
|
|
154
|
+
modifiedModel.value.updateOptions(mo)
|
|
155
|
+
},
|
|
156
|
+
{ deep: true }
|
|
157
|
+
)
|
|
158
|
+
</script>
|
|
159
|
+
|
|
160
|
+
<style scoped>
|
|
161
|
+
.monaco-editor-container {
|
|
162
|
+
width: 100%;
|
|
163
|
+
height: 100%;
|
|
164
|
+
}
|
|
165
|
+
</style>
|
|
166
|
+
|
|
167
|
+
<style>
|
|
168
|
+
/* patch 模式:未改动区域为空行,隐藏 monaco 内置的展开未改动区域按钮(非 scoped,配合 JS class 控制) */
|
|
169
|
+
.monaco-editor-container.hide-unchanged-actions {
|
|
170
|
+
.diff-hidden-lines-widget {
|
|
171
|
+
cursor: not-allowed;
|
|
172
|
+
.diff-hidden-lines {
|
|
173
|
+
pointer-events: none;
|
|
174
|
+
opacity: 0.75;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
</style>
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="top-bar-wrap">
|
|
3
|
+
<div class="title-area">
|
|
4
|
+
<div class="filename">{{ filename }}</div>
|
|
5
|
+
<div class="diff-line-num">
|
|
6
|
+
<div class="add">+{{ changed.added }}</div>
|
|
7
|
+
<div class="del">-{{ changed.removed }}</div>
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
<div class="toolbar">
|
|
11
|
+
<label>
|
|
12
|
+
<input type="checkbox" :checked="viewed" @change="onViewedChange" />
|
|
13
|
+
viewed
|
|
14
|
+
</label>
|
|
15
|
+
<label v-if="canUnchangeVisible">
|
|
16
|
+
<input type="checkbox" :checked="rawed" @change="onRawedChange" />
|
|
17
|
+
raw
|
|
18
|
+
</label>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script setup lang="ts">
|
|
24
|
+
import { computed, ref, onMounted, reactive } from 'vue'
|
|
25
|
+
import { useDiffViewer } from './useDiffView'
|
|
26
|
+
|
|
27
|
+
const { funcs, registerFunc } = useDiffViewer()
|
|
28
|
+
|
|
29
|
+
const props = withDefaults(
|
|
30
|
+
defineProps<{
|
|
31
|
+
diffPair?: { filename: string; content: string }[]
|
|
32
|
+
}>(),
|
|
33
|
+
{
|
|
34
|
+
diffPair: () => [],
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
const filename = computed(() => props.diffPair[0].filename)
|
|
39
|
+
|
|
40
|
+
const { viewed, rawed, canUnchangeVisible } = funcs
|
|
41
|
+
const onViewedChange = function (evt) {
|
|
42
|
+
const checked = (evt.target as HTMLInputElement).checked
|
|
43
|
+
viewed.value = checked
|
|
44
|
+
}
|
|
45
|
+
const onRawedChange = function (evt) {
|
|
46
|
+
const checked = (evt.target as HTMLInputElement).checked
|
|
47
|
+
rawed.value = checked
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
onMounted(() => {
|
|
51
|
+
// funcs.options.toolbar?.render($el, {})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
// 变更行数
|
|
55
|
+
const changed = ref({ added: 0, removed: 0 })
|
|
56
|
+
function updateChangedLines(newVal) {
|
|
57
|
+
Object.assign(changed.value, newVal)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
registerFunc({
|
|
61
|
+
viewed,
|
|
62
|
+
rawed,
|
|
63
|
+
updateChangedLines,
|
|
64
|
+
})
|
|
65
|
+
</script>
|
|
66
|
+
|
|
67
|
+
<style scoped>
|
|
68
|
+
.top-bar-wrap {
|
|
69
|
+
box-sizing: border-box;
|
|
70
|
+
height: 32px;
|
|
71
|
+
|
|
72
|
+
display: flex;
|
|
73
|
+
overflow: hidden;
|
|
74
|
+
align-items: center;
|
|
75
|
+
background-color: #f7f7f7;
|
|
76
|
+
padding: 4px 8px;
|
|
77
|
+
|
|
78
|
+
.title-area {
|
|
79
|
+
flex: 1;
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
|
|
83
|
+
.filename {
|
|
84
|
+
font-size: 14px;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.diff-line-num {
|
|
88
|
+
display: inline-flex;
|
|
89
|
+
font-size: 12px;
|
|
90
|
+
font-weight: bold;
|
|
91
|
+
margin-left: 4px;
|
|
92
|
+
.add {
|
|
93
|
+
color: #1a7f37;
|
|
94
|
+
}
|
|
95
|
+
.del {
|
|
96
|
+
color: #d1242f;
|
|
97
|
+
margin-left: 4px;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
.toolbar {
|
|
102
|
+
font-size: 12px;
|
|
103
|
+
flex-shrink: 0;
|
|
104
|
+
display: flex;
|
|
105
|
+
align-items: center;
|
|
106
|
+
gap: 4px;
|
|
107
|
+
|
|
108
|
+
label {
|
|
109
|
+
display: inline-flex;
|
|
110
|
+
align-items: center;
|
|
111
|
+
cursor: pointer;
|
|
112
|
+
|
|
113
|
+
&.disabled {
|
|
114
|
+
opacity: 0.4;
|
|
115
|
+
cursor: not-allowed;
|
|
116
|
+
pointer-events: none;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
</style>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { defineCustomElement } from 'vue'
|
|
2
|
+
import type { WtoolDiffViewerProps } from '../types'
|
|
3
|
+
import DiffViewer from './DiffViewer.vue'
|
|
4
|
+
|
|
5
|
+
export type DiffViewerProps = WtoolDiffViewerProps
|
|
6
|
+
|
|
7
|
+
export const WTOOL_DIFF_VIEWER_TAG = 'wtool-diff-viewer'
|
|
8
|
+
|
|
9
|
+
export const WtoolDiffViewer = defineCustomElement(DiffViewer, {
|
|
10
|
+
shadowRoot: false,
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
export function register(tagName: string = WTOOL_DIFF_VIEWER_TAG): void {
|
|
14
|
+
if (customElements.get(tagName)) return
|
|
15
|
+
customElements.define(tagName, WtoolDiffViewer)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface DiffViewerInstance {
|
|
19
|
+
update(props: Partial<DiffViewerProps>): void
|
|
20
|
+
destroy(): void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function applyProps(el: InstanceType<typeof WtoolDiffViewer>, props: Partial<DiffViewerProps>): void {
|
|
24
|
+
const node = el as unknown as Record<string, unknown>
|
|
25
|
+
for (const key of Object.keys(props) as (keyof DiffViewerProps)[]) {
|
|
26
|
+
const v = props[key]
|
|
27
|
+
if (v !== undefined) {
|
|
28
|
+
node[key as string] = v
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function createDiffViewer(target: HTMLElement, initialProps: DiffViewerProps = {}): DiffViewerInstance {
|
|
34
|
+
register()
|
|
35
|
+
const el = new WtoolDiffViewer()
|
|
36
|
+
|
|
37
|
+
applyProps(el, initialProps)
|
|
38
|
+
|
|
39
|
+
target.appendChild(el)
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
update(newProps: Partial<DiffViewerProps>) {
|
|
43
|
+
applyProps(el, newProps)
|
|
44
|
+
},
|
|
45
|
+
destroy() {
|
|
46
|
+
el.remove()
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Ref } from 'vue'
|
|
2
|
+
import { useCompExp } from '@yuhufe/web-ui'
|
|
3
|
+
|
|
4
|
+
// 存放单个diffEditor的数据
|
|
5
|
+
export const useDiffViewer = function ({ isMaster = false } = {}) {
|
|
6
|
+
const exp = useCompExp<{
|
|
7
|
+
viewed: Ref<boolean>
|
|
8
|
+
rawed: Ref<boolean>
|
|
9
|
+
updateViewed: (viewed: boolean) => any // 控制是否viewed
|
|
10
|
+
updateRawed: (args: boolean) => any // 控制是否收起展开
|
|
11
|
+
updateChangedLines: (args: { added: number; removed: number }) => any
|
|
12
|
+
canUnchangeVisible: Ref<boolean> // 未改动区域是否可见
|
|
13
|
+
}>({ isMaster, key: 'diffViewer' })
|
|
14
|
+
|
|
15
|
+
return { ...exp }
|
|
16
|
+
}
|