feat: improve ocd conflict merge ids

This commit is contained in:
hanshiyang 2026-06-03 20:32:16 +08:00
parent a946dc7ae8
commit 08d4e2a263
6 changed files with 125 additions and 7 deletions

View File

@ -72,6 +72,7 @@
"vue": "^3.2.45"
},
"dependencies": {
"nanoid": "3.3.11",
"prismjs": "^1.27.0"
},
"simple-git-hooks": {

View File

@ -8,6 +8,9 @@ importers:
.:
dependencies:
nanoid:
specifier: 3.3.11
version: 3.3.11
prismjs:
specifier: ^1.27.0
version: 1.30.0

View File

@ -29,6 +29,7 @@ import {
} from '../../interface/Element'
import { IRow, IRowElement } from '../../interface/Row'
import { deepClone, getUUID, nextTick } from '../../utils'
import { prepareOcdDocumentForSave } from '../../../utils/ocd'
import { Cursor } from '../cursor/Cursor'
import { CanvasEvent } from '../event/CanvasEvent'
import { GlobalEvent } from '../event/GlobalEvent'
@ -1196,7 +1197,7 @@ export class Draw {
}
return {
version,
data,
data: prepareOcdDocumentForSave(data),
options: deepClone(this.options)
}
}

View File

@ -8,6 +8,7 @@ import { ICatalog } from '../../interface/Catalog'
import { IEditorResult } from '../../interface/Editor'
import { IGetValueOption } from '../../interface/Draw'
import { deepClone } from '../../utils'
import { prepareOcdDocumentForSave } from '../../../utils/ocd'
export class WorkerManager {
private draw: Draw
@ -78,7 +79,7 @@ export class WorkerManager {
this.valueWorker.onmessage = evt => {
resolve({
version,
data: evt.data,
data: prepareOcdDocumentForSave(evt.data),
options: deepClone(this.draw.getOptions())
})
}

View File

@ -26,6 +26,10 @@ import { Dialog } from './components/dialog/Dialog'
import { formatPrismToken } from './utils/prism'
import { Signature } from './components/signature/Signature'
import { debounce, nextTick, scrollIntoView } from './utils'
import {
prepareOcdDocumentForOpen,
prepareOcdDocumentForSave
} from './utils/ocd'
/**
* URL中提取文件路径
@ -249,7 +253,7 @@ window.onload = function () {
} catch {
editorData = { main: [{ value: text }] }
}
instance.command.executeSetValue(editorData)
instance.command.executeSetValue(prepareOcdDocumentForOpen(editorData))
console.log('[iframe] 通过 Electron 加载本地文件完成:', localFilePath)
} catch (err) {
console.error('[iframe] 本地文件解析失败:', err)
@ -273,7 +277,7 @@ window.onload = function () {
} catch {
editorData = { main: [{ value: text }] }
}
instance.command.executeSetValue(editorData)
instance.command.executeSetValue(prepareOcdDocumentForOpen(editorData))
console.log('[iframe] 根据 filePath 加载完成:', url)
} catch (err) {
console.error('[iframe] 加载失败:', err)
@ -334,7 +338,7 @@ window.onload = function () {
}
// 设置编辑器内容
instance.command.executeSetValue(editorData)
instance.command.executeSetValue(prepareOcdDocumentForOpen(editorData))
console.log('文档已打开')
} catch (error) {
console.error('打开文档失败:', error)
@ -358,7 +362,11 @@ window.onload = function () {
try {
// 获取编辑器内容
const editorValue = instance.command.getValue()
const content = JSON.stringify(editorValue.data, null, 2)
const content = JSON.stringify(
prepareOcdDocumentForSave(editorValue.data),
null,
2
)
// 创建下载链接
const blob = new Blob([content], { type: 'application/json' })
@ -391,7 +399,11 @@ window.onload = function () {
saveCallbackDom.onclick = function () {
try {
const editorValue = instance.command.getValue()
const content = JSON.stringify(editorValue.data, null, 2)
const content = JSON.stringify(
prepareOcdDocumentForSave(editorValue.data),
null,
2
)
const parentWin: any = window.parent || window
const viewer =

100
src/utils/ocd.ts Normal file
View File

@ -0,0 +1,100 @@
import { nanoid } from 'nanoid'
import { IEditorData } from '../editor'
const CONFLICT_MARKER_HIGHLIGHT = '#fff3cd'
const CONFLICT_MARKER_COLOR = '#b42318'
const CONFLICT_MARKER_REG = /^(<<<<<<<|=======|>>>>>>>)\b/
type UnknownRecord = Record<string, unknown>
function isRecord(value: unknown): value is UnknownRecord {
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
}
function isElementList(value: unknown): value is UnknownRecord[] {
return Array.isArray(value) && value.every(isRecord)
}
function isConflictMarkerText(value: unknown) {
return typeof value === 'string' && CONFLICT_MARKER_REG.test(value.trim())
}
function normalizeConflictElement(element: UnknownRecord, isMarker: boolean) {
if (isMarker) {
element.highlight = CONFLICT_MARKER_HIGHLIGHT
element.color = CONFLICT_MARKER_COLOR
element.bold = true
return
}
if (element.highlight === CONFLICT_MARKER_HIGHLIGHT) {
delete element.highlight
}
if (element.color === CONFLICT_MARKER_COLOR) {
delete element.color
}
}
function prepareElementList(elementList: UnknownRecord[]) {
for (const element of elementList) {
if (isElementList(element.valueList)) {
const isMarker =
element._ocdConflictMarker === true ||
element.valueList.some(item => isConflictMarkerText(item.value))
for (const item of element.valueList) {
if (typeof item.id !== 'string' || !item.id) {
item.id = nanoid()
}
normalizeConflictElement(item, isMarker || isConflictMarkerText(item.value))
prepareNestedElement(item)
}
}
prepareNestedElement(element)
}
}
function prepareNestedElement(element: UnknownRecord) {
const trList = element.trList
if (Array.isArray(trList)) {
for (const tr of trList) {
if (!isRecord(tr) || !Array.isArray(tr.tdList)) continue
for (const td of tr.tdList) {
if (!isRecord(td) || !isElementList(td.value)) continue
prepareElementList(td.value)
}
}
}
const control = element.control
if (isRecord(control) && isElementList(control.value)) {
prepareElementList(control.value)
}
}
function cloneEditorData(data: IEditorData): IEditorData {
if (typeof structuredClone === 'function') {
return structuredClone(data) as IEditorData
}
return JSON.parse(JSON.stringify(data)) as IEditorData
}
export function prepareOcdDocumentForSave(data: IEditorData): IEditorData {
const prepared = cloneEditorData(data)
;(['header', 'main', 'footer'] as const).forEach(key => {
const elementList = prepared[key]
if (isElementList(elementList)) {
prepareElementList(elementList)
}
})
return prepared
}
export function prepareOcdDocumentForOpen(data: IEditorData): IEditorData {
if (!isRecord(data)) return data
;(['header', 'main', 'footer'] as const).forEach(key => {
const elementList = data[key]
if (isElementList(elementList)) {
prepareElementList(elementList)
}
})
return data
}